Index the web through human-assigned tags, with support for archiving content via external tools.
- Tag Management: Flat, human-assigned tags with alias support. Tags with the same name or alias can coexist and be disambiguated cleanly.
- Entry Management: Save web URLs with descriptions and tags.
- Pluggable Archivers: Save snapshots (HTML, videos, etc.) locally using external archivers (e.g.,
single-file,yt-dlp) configurable viatool.toml. - JSONL-based Storage: Tags and entries are stored as one JSON record per line (
tags.jsonl,entries.jsonl). Existing TOML data is migrated on first startup.
Build with Cargo:
cargo build --releaseOr run directly via Cargo:
cargo run -- <COMMAND>Pantagraph looks for config.toml in your data directory (e.g. data/config.toml) or the current working directory.
A sample configuration is provided in config.toml.sample:
cp config.toml.sample config.tomlDuplicate tag names and duplicate qualified references can be configured independently:
[tag]
same_name_policy = "allow" # "allow" | "warn" | "error"
same_qualified_ref_policy = "error" # "allow" | "warn" | "error"same_name_policy("allow"by default): Controls duplicate names without a specifier.same_qualified_ref_policy("error"by default): Controls duplicatename@specifierreferences."warn"prints a warning and continues;"error"aborts with conflict details.
Pantagraph allows you to configure external archiving tools flexibly. By default, it looks for tool.toml in your data directory (e.g. data/tool.toml) or the current working directory.
A sample configuration is provided in tool.toml.sample:
cp tool.toml.sample tool.toml[archiver.single-file]
command = "single-file"
args = [
"--browser-executable-path=google-chrome",
"--browser-headless=true",
"--output-directory={outdir}",
"{url}"
]
[archiver.ytdlp]
command = "yt-dlp"
args = [
"-P", "{outdir}",
"{url}"
]{url}: Target URL to archive.{outdir}(or{output}): Output directory assigned for this entry (<data_dir>/archives/<entry_id>/).
pantagraph tag create <NAME> [--specifier <SPEC>] [--description <DESC>] [--alias <ALIAS>...]
# Example:
pantagraph tag create rust -d "Rust programming language" -a rs -a rust-lang
# Or create with a specifier directly (via flag or name@specifier):
pantagraph tag create "rust@lang" -d "Rust programming language"
pantagraph tag create rust -s game -d "Rust survival game"pantagraph tag list [--name <FILTER>]Note: Filters by tag name or alias.
pantagraph tag show <NAME_OR_ALIAS_OR_ID>Note: If multiple tags share the same name or alias, use name@specifier (e.g. rust@lang), name#N (e.g. rust#1, rust#2), or the tag's UUID to disambiguate.
pantagraph tag rename <NAME_OR_ALIAS_OR_ID> <NEW_NAME>
# Example:
pantagraph tag rename rust@lang rust-langNote: If multiple tags share the same name or alias, use name@specifier, name#N, or the tag's UUID to disambiguate.
Aliases can be used interchangeably with tag names in all commands (e.g., tag show, tag rename, entry add --tag, entry list --tag, entry tag).
# Add an alias to a tag
pantagraph tag alias add <NAME_OR_ALIAS_OR_ID> <ALIAS>
# Example:
pantagraph tag alias add rust rs
# Remove an alias from a tag
pantagraph tag alias remove <NAME_OR_ALIAS_OR_ID> <ALIAS>
# Example:
pantagraph tag alias remove rust rs
# List aliases for a tag
pantagraph tag alias list <NAME_OR_ALIAS_OR_ID>By default, --archiver is empty (""), which saves the entry as a simple URL bookmark without running an external tool:
pantagraph entry add <URL> --tag <TAG> [-d <DESC>]
# Example:
pantagraph entry add https://doc.rust-lang.org --tag rust -d "Official Rust Documentation"Specify --archiver=<NAME> to invoke the corresponding archiver defined in tool.toml:
pantagraph entry add <URL> --archiver single-file --tag <TAG>This runs the archiver, saves snapshots into <data_dir>/archives/<entry_id>/, and records archive metadata in entries.jsonl under meta (including archived, archiver, and archive_path).
# List all entries
pantagraph entry list
# Filter by tag (repeatable: matches entries having ALL specified tags)
pantagraph entry list --tag rust --tag dev
# Filter by archiver
pantagraph entry list --archiver single-file
# Filter URL-only bookmarks (no archiver)
pantagraph entry list --archiver ""pantagraph entry show <ENTRY_ID>pantagraph entry tag <ENTRY_ID> <TAG>Data is stored in the data directory (data/ by default, customizable with --data-dir <PATH>):
data/
├── entries.jsonl
├── tags.jsonl
├── tool.toml # (Optional location for tool.toml)
└── archives/
└── <entry_id>/ # Files generated by external archivers
Meta attributes are stored under the meta field in entries.jsonl (a key–value map of strings). Archiver-related information is recorded in meta:
archived(string):"true"if an external archiver was successfully executed.archiver(string): Name of the archiver used.archive_path(string): Filesystem path or identifier recorded by the archiver (typically<data_dir>/archives/<entry_id>/).
Example:
{"id":"...","url":"...","description":"...","tag_ids":["..."],"meta":{"archived":"true","archiver":"single-file","archive_path":"archives/<entry_id>/"}}You can also manage arbitrary meta keys via:
pantagraph entry meta set <ENTRY_ID> <KEY> <VALUE>pantagraph entry meta remove <ENTRY_ID> <KEY>pantagraph entry meta list <ENTRY_ID>
Entries can be filtered using the --query option with a boolean expression over tag references. Supported operators:
- Space or
*for AND (intersection) +for OR (union)!for NOT (complement)- Parentheses
()for grouping
In addition, meta:<key>[=<value>] filters by meta attributes (exact match for value; presence only if value omitted).
Examples:
# Entries matching both rust AND game tags
pantagraph entry list --query "rust game"
pantagraph entry list --query "rust * game"
# Entries matching rust OR game
pantagraph entry list --query "rust + game"
# Entries with rust but NOT game
pantagraph entry list --query "rust * !game"
pantagraph entry list --query "rust !game"
# Entries archived with yt-dlp
pantagraph entry list --query "meta:archiver=yt-dlp"
# Entries that have an archiver (any value)
pantagraph entry list --query "meta:archiver"
# Entries without any archiver
pantagraph entry list --query "!meta:archiver"
# Complex combination
pantagraph entry list --query "(rust + cpp) * meta:archived=true"