Obris logo

CLI

The Obris CLI keeps a local directory in two-way sync with an Obris topic. Pull every item in a topic down to disk, push your local edits back, and bulk-upload new files in one command. The same CLI is what the Claude and Codex plugins shell out to.

Install

Requires Python 3.10+.

pip install obris-cli
obris --version

Or run directly with uv without installing:

uvx --from obris-cli obris --help

The PyPI package is obris-cli; the installed command is obris.

Authenticate

Authentication is browser-based. No API keys to copy or paste.

obris auth login

Opens your browser, you authorize the CLI, and the command finishes. Tokens are stored locally per environment.

From inside an AI agent (two-step)

If something else is driving the CLI (a plugin, an agent), use the two-step variant so the caller doesn't block on a polling loop:

obris auth login --no-wait    # prints a URL and exits immediately
obris auth complete           # finalizes once the user has authorized

Check status

obris auth status

Reports whether the active environment is authenticated. Read-only.

Log out

obris auth logout

Sync

obris sync is the headline workflow. It links a local directory to one or more Obris root topics and keeps them in sync.

First sync: link a topic to a directory

obris sync -p <directory> -t <topic-id>

Pulls every item in the topic (and its subtopics) down into the directory. Subtopics become subdirectories. The CLI stores its tracking metadata in <directory>/.obris/ so you never have to repeat the topic ID on later runs.

If you omit -t, the CLI creates a new root topic named after the directory and links it. Pass --no-create if you'd rather it error than create a topic.

Link multiple topics to the same directory

You can link more than one root topic to a single directory. Run obris sync -t <topic-id> again with a different topic ID and the CLI sets up another sync alongside the first.

obris sync -p <directory> -t <topic-id-1>      # link the first topic
obris sync -p <directory> -t <topic-id-2>      # link a second topic alongside it
obris sync -p <directory>                      # syncs every linked topic in one pass

Each topic gets its own .obris/<topic-id>.json state file, and files are tracked independently. A file you add to topic A does not show up under topic B unless you explicitly add it there too. To add new files in a multi-topic directory, route them with obris sync add <file> -t <topic-id> so the right topic gets the file.

Bootstrap a directory full of files

If the directory already has files you want in the topic, add --add-all on the first sync. The CLI walks the directory, uploads every untracked file in one bulk request, and creates subtopics on the server to mirror local subdir structure:

obris sync -p <directory> -t <topic-id> --add-all

--add-all is idempotent. Re-running picks up newly-added local files without touching the ones already tracked.

Ongoing sync

obris sync -p <directory>

No flags. The CLI auto-detects the linked topic from .obris/, picks up remote changes (pulls), pushes local edits, and reports new untracked files without uploading them.

Dry-run preview

obris sync -p <directory> --dry-run
obris sync -p <directory> --dry-run --add-all

--dry-run never mutates server or disk state. Combined with --add-all, it shows exactly which files would be uploaded before you commit. If the directory has no linked topic yet, the dry-run shows what topic name and subtopic structure would be created without actually creating them.

Common flags

  • Name
    -p, --path
    Type
    string
    Description

    Local directory. Defaults to the current directory.

  • Name
    -t, --topic
    Type
    string
    Description

    Root topic ID. Required for the first sync of a new directory unless you want a topic created automatically.

  • Name
    -i, --item
    Type
    string
    Description

    Sync only specific item IDs. Repeatable.

  • Name
    --include
    Type
    string
    Description

    Subtopic name-path glob. Repeatable; patterns OR together. See Subtopic filtering.

  • Name
    --add-all
    Type
    flag
    Description

    Upload every untracked file under the synced directory.

  • Name
    --no-subtopics
    Type
    flag
    Description

    With --add-all, skip files in subdirs instead of creating matching subtopics.

  • Name
    --no-create
    Type
    flag
    Description

    Error instead of creating a root topic when the directory has no sync state.

  • Name
    --dry-run
    Type
    flag
    Description

    Preview without making changes.

  • Name
    -v, --verbose
    Type
    flag
    Description

    List every untracked, excluded, or symlink path.

Sync configuration

Excluding files

obris sync exclude <pattern> -p <directory>

Stops syncing files that match the pattern. Examples:

obris sync exclude scratch/         # an entire folder
obris sync exclude notes.draft.md   # a specific file
obris sync exclude '*.draft.md'     # all drafts

Including files (overriding exclusions)

obris sync include <pattern> -p <directory>

Forces a file or pattern to sync, overriding any exclusion. Works whether the exclusion is a built-in default or one you added previously. Use this whenever obris sync says a file is being skipped and you actually want it.

obris sync include .env.example   # sync the example even though .env.* is excluded
obris sync include .DS_Store      # override a built-in default
obris sync include scratch/draft  # un-exclude a file in an excluded folder

exclude and include are strict inverses. Last call wins. Toggling the same pattern back and forth cancels the previous entry rather than stacking; if you include a pattern that wasn't excluded in the first place, the CLI prints a "no-effect" warning so you can see the action did nothing.

Listing current settings

obris sync exclude --list -p <directory>

Shows every pattern currently excluded or included for the directory.

Default exclusions

--add-all and obris sync skip a sensible default set so secrets and tooling cruft never land in the knowledge base:

  • VCS: .git/, .svn/, .hg/
  • Dependencies: node_modules/, .venv/, venv/, __pycache__/
  • OS cruft: .DS_Store, Thumbs.db
  • Credentials: .env, .env.*, .envrc, .netrc, .npmrc, .pypirc, .aws/, .gnupg/, .ssh/
  • Sync metadata: .obris/ (the CLI's own state directory)

Common AI tool config dirs (.claude/, .cursor/, .github/) are not skipped. They're treated as content. Override any default with obris sync include <pattern>.

Subtopic filtering

Sync only matching subtopics with --include:

obris sync -p <directory> --include 'Projects/**' --include '**/skill-*'

--include scopes every operation in the sync: pulls of new items, push and pull of tracked items, conflict detection, remote-deleted detection, and --add-all bulk uploads. Files outside the include scope are surfaced separately so you know they were skipped on purpose:

Skipped 2 untracked file(s) outside --include scope:
  Misc/random.md
  README.md

Patterns OR together; supports ** (zero or more segments), *, ?, [abc]. Case-insensitive.

Per-file sync operations

sync add

Add a single local file to a synced topic.

obris sync add <filepath>
obris sync add <filepath> -t <topic-id>

If the directory is synced to exactly one topic, -t is optional. Pass -t to target a specific subtopic.

obris sync add bypasses the exclusion list since you've explicitly named the file. That makes it the right way to sync a file that matches a default exclusion.

sync link

Relink a renamed file to its remote item.

obris sync link <filepath> -i <item-id>
obris sync link <filepath> -i <item-id> -t <topic-id>

When you rename a synced file, the next sync unlinks it. sync link reattaches the new filename to the existing remote item so future syncs pick up your edits instead of treating the file as new.

sync unlink

Break the local-to-remote sync link without deleting either side.

obris sync unlink <id-or-filename>
obris sync unlink notes.md draft.md     # multiple at once

Both the local file and the remote item stay in place; subsequent obris sync calls will not re-pull these items. Re-link later with obris sync link or obris sync add. To permanently delete the remote item, use obris knowledge delete <id>.

sync conflicts

When the same item changed on both sides between syncs, the CLI flags it conflicted and skips it for both push and pull. Other items sync normally; the conflict is opt-in to resolve.

obris sync conflicts list -p <directory>
obris sync conflicts resolve <filename> --keep-local -p <directory>
obris sync conflicts resolve <filename> --keep-remote -p <directory>

--keep-local pushes the local copy up. --keep-remote pulls the remote copy down and saves the local copy as a sibling <name> (conflict YYYY-MM-DD).<ext> file so nothing is lost.

Where state lives

The CLI stores tracking metadata at <directory>/.obris/: one JSON file per linked topic, plus a .gitignore so the directory self-excludes from any git/svn checkout your project might be inside. The engine ignores .obris/ when scanning for files to sync.

rm -rf <directory> cleans up tracking state along with the local files. No global state is left orphaned.

JSON output

The top-level --json flag on obris itself (not on the subcommand) makes commands emit a structured JSON summary instead of human-readable lines. Useful when relaying outcomes to other tools or counting exactly what moved.

obris --json sync -p <directory>

Sync JSON fields: path, pulled, pushed, conflicts, errors, untracked, excluded_count, symlinks, conflicts_pending, missing_local, skipped_by_include.

--json works on every command. obris --json topic list, obris --json knowledge view <id>, and so on.

Other commands

topic

obris topic list                    # root topics
obris topic list --all              # every topic, hierarchical
obris topic list --parent <id>      # children of a topic
obris topic tree <topic-id>         # subtree as a table
obris topic view <topic-id>         # subtopics + items

knowledge

obris knowledge view <id>
obris knowledge move <id> --topic <id>
obris knowledge delete <id>

save

Save a file or screenshot to a topic. --screenshot requires macOS or Linux.

obris save photo.png                       # upload a file to Scratch
obris save photo.png --topic <id>          # upload to a specific topic
obris save --screenshot                    # take a screenshot, upload to Scratch
obris save --screenshot --name "diagram"   # name it explicitly
obris save --screenshot --prompt           # open a name dialog before capturing
  • Name
    filepath
    Type
    string
    Description

    Path to a file. Optional if --screenshot is set.

  • Name
    --screenshot
    Type
    flag
    Description

    Take an interactive screenshot and upload it. macOS or Linux only.

  • Name
    --name
    Type
    string
    Description

    Display name for the saved item.

  • Name
    --prompt
    Type
    flag
    Description

    Open a dialog to enter a name before capturing.

  • Name
    --topic
    Type
    string
    Description

    Topic ID to upload to. Defaults to your Scratch topic.

Environments

obris env manages multiple environments (e.g. prod, dev, local, a self-hosted instance).

obris env list                       # all environments
obris env view                       # show the active one
obris env use dev                    # set the default environment
obris env add staging --url https://obris.staging.example.com
obris env remove staging

Use --env <name> on any command to override for a single invocation, without changing the default:

obris --env dev sync -p <directory>
obris --env local auth login --no-wait

Each environment keeps its own auth tokens.

Hotkeys

Bind keyboard shortcuts to obris save --screenshot using any automation tool: Alfred, Raycast, Keyboard Maestro, macOS Shortcuts, etc.

Example: Alfred

Create a workflow with a Hotkey trigger connected to a Run Script action (language: /bin/zsh):

Quick capture:

/full/path/to/obris save --screenshot

Capture with name prompt:

/full/path/to/obris save --screenshot --prompt

Platform support

PlatformSyncSave (file)Save (screenshot)
macOSYesYesYes
LinuxYesYesYes
WindowsYesYesNo