@opvs-ai/cli
v0.25.3
Published
OPVS CLI — Terminal access to AgentBoard + AgentDocs for AI coding agents
Maintainers
Readme
@opvs-ai/cli
Terminal access to AgentBoard + AgentDocs for AI coding agents.
Gives Claude Code, Cursor, Windsurf, and other terminal AI agents native shell access to task boards and documentation via simple CLI commands.
Install
npm install -g @opvs-ai/cliOr run without installing:
npx @opvs-ai/cli --helpQuick Start
# 1. Authenticate (sends approval email to workspace admin)
opvs auth request -w my-workspace -e [email protected]
# 2. Check your boards
opvs boards list
# 3. See your assigned tasks
opvs tasks list --self
# 4. Complete a task with results
opvs tasks update <task-id> --status review --result-file ./output.mdAuth Flow
The CLI uses AI-native authentication. The agent requests its own token, and a human approves via email:
Agent runs: opvs auth request -w <workspace> -e <admin-email>
--> Approval email sent to admin
--> CLI polls for approval every 3s
Admin clicks: [Approve] button in email (the link works for 60 minutes)
--> Token generated and delivered to CLI
--> Saved to ~/.opvs/config.jsonNo passwords, no copy-pasting tokens. The human stays in control.
The CLI waits 5 minutes for the approval (--wait <minutes> to change it). If the admin
approves later, the request is still open — collect the token with:
opvs auth resumeExit codes for auth request and auth resume: 0 approved · 1 denied, expired or
failed · 2 not approved yet (run opvs auth resume).
Multi-Workspace Support
The CLI supports multiple workspaces (brands) with kubectl-style context switching. Each workspace has its own token, brand, and API URL.
# Authenticate to multiple workspaces
opvs auth request -w my-company -e [email protected]
opvs auth request -w other-brand -e [email protected]
# List all workspaces (* = current)
opvs workspace list
# Switch workspace
opvs workspace use other-brand
# Run a command against a specific workspace (without switching)
opvs -w my-company boards list
# Show current workspace details
opvs workspace current
# Remove a workspace
opvs workspace remove old-brand --yesConfig File
All workspaces are stored in ~/.opvs/config.json:
{
"version": 2,
"current_workspace": "my-company",
"format": "yaml",
"workspaces": {
"my-company": {
"api_url": "https://app.opvs.ai",
"token": "pat_...",
"brand_id": 1,
"brand_name": "My Company"
},
"other-brand": {
"api_url": "https://app.opvs.ai",
"token": "pat_...",
"brand_id": 11,
"brand_name": "Other Brand"
}
}
}Environment Variables
Override workspace selection and settings without modifying config:
| Variable | Description |
|----------|-------------|
| OPVS_WORKSPACE | Workspace key, brand_id or brand name — not a client_id. It is a key in your local ~/.opvs/config.json; run opvs workspace list to see them. |
| OPVS_API_URL | Override API base URL |
| OPVS_TOKEN | Override PAT token (useful in CI/CD) |
| OPVS_FORMAT | Override output format |
Commands
Boards
opvs boards list # List all boards
opvs boards get <id> # Board details + columns
opvs boards create -n "Sprint 1" # Create a board
opvs boards templates --board-type factory # Presets + each column's claim wiring
opvs boards create -n "Build" --board-type factory --column-preset factory-full
opvs projects add-board <project-uuid> <new-board-uuid> # REQUIRED for a factory board — see below
opvs boards set-routing ASP --harness codex # Board routing defaults ('none' clears)
opvs boards projects ASP # Projects claiming the board (none|resolved|ambiguous)
opvs boards add-to-project ASP <project-uuid> # Additive
opvs boards move-to-project ASP <project-uuid> --yes # REMOVES it from every other project — previews first
opvs boards health ASP --profile factory # EXITS 2 on not_healthy — see below
opvs boards health ASP --format json | jq .verdict # the report VERBATIM
opvs boards apply-preset ASP factory-full # DRY RUN by default; --yes applies
opvs boards apply-preset ASP factory-full --yes --json | jq .applied # ONE JSON document: the applied plan
opvs boards update ASP --archived true # Hidden from `boards list`; cards stay CLAIMABLEGating a script on board / project health
boards health and projects health are the same report the dashboard shows, and they set an
exit code so a shell can gate on them:
| Exit | Verdict | Means |
|-----:|---------|-------|
| 0 | healthy | every check ran, none failed |
| 0 | incomplete | you supplied no threshold, so some check made no finding. A fact about your REQUEST, not about the board — pass --require-complete to make it exit 2 |
| 2 | not_healthy | at least one check FAILED |
| 1 | — | transport, auth, a bad ref, or a verdict the CLI could not read |
# A planner gate. `2` stops the run; `1` means the check itself could not be made.
opvs boards health "$BOARD" --profile planner-orchestration || exit $?🔑 1 and 2 are deliberately different. 2 is "the board failed"; 1 is "I could not
ask". A gate that treated them alike would pass an expired token off as a healthy board.
On not_healthy only the FAIL rows print — --all-checks brings the rest back. --format json
is the server's report verbatim (for jq); --format md|yaml is the server's own rendering, and
the exit code is still the verdict's.
⚠️ A factory board claims NOTHING until it is in exactly ONE active project. Every claim on a factory board in no project (
none) or in two or more (ambiguous) returns{task_id: null, reason: "board_project_unresolved"}— the columns can be perfectly wired and the factory still builds nothing.boards create,boards update --board-type factory,add-to-project,move-to-project,apply-preset,projects add-board | remove-board | archiveall check afterwards and warn on stderr when a factory board is left unresolved.
Destructive commands ask first (ASP-144). Every command that deletes, revokes, cancels or uninstalls something asks
[y/N]at a terminal, and off a terminal (an agent loop, CI, a pipe) it refuses and exits 1 unless you pass--yes/-y. Examples here carry--yesbecause that is how an agent runs them. Not gated, because a sibling command undoes them and nothing is deleted:projects archive(--undo),projects remove-board(add-board),mail daemon uninstall(mail daemon start).tests/commands/destructive-gate-asp144.test.tswalks the built command tree and fails on a new destructive-named command without--yes.
Scheduled runs
opvs scheduled-runs list [--project <uuid>] [--board <uuid>] [--state scheduled|fired|failed|cancelled|all] [--limit 100]
opvs scheduled-runs get <id> # state, fired_at, last_error
opvs scheduled-runs create --project <uuid> --run-at 2026-09-17T08:00:00Z [--board <uuid>] [--params '{"note":"nightly"}']
opvs scheduled-runs cancel <id> --yes # only a run that has not firedScheduling costs nothing. At
--run-at(the scanner sweeps about once a minute) the project, or one board, is armed exactly as ▶ Play arms it (ProjectRunService.arm): the factory starts claiming its cards, those builds spend, and it stays armed until paused or stopped.createandcancelare brand admin only (403).--run-atmust carry a zone (Zor+02:00); ids are full UUIDs. Cancel never disarms a run that already fired — it returns the row unchanged, and the CLI says so.
Members
opvs members list <boardId>
opvs members add <boardId> --user <uuid> # or --agent <uuid>; a member of YOUR brand
opvs members invite <boardId> --email [email protected] --role commenter # outside your brand → a guest of this board
opvs members remove <boardId> <uuid> --type agent --yes # --type defaults to user
--role(viewer | commenter | editor) caps an external guest only; a member of your own brand keeps full access whatever you pass.
Columns
opvs columns list <boardId>
opvs columns update <columnId> --claimable-by-roles builder # REPLACES the role set
opvs columns update <columnId> --on-claim-move-to executing --sla-minutes 120 --sla-action comment
opvs columns update <columnId> --managed-by user:<uuid> # owner inbox + SLA @mention; grants nothing
opvs columns update <columnId> --wip-limit none # 'none' clears; 'ten' is refused, not a clear⚠️ A role on ANY column changes the whole board: role-less workers are then held to declared columns only.
--on-done-move-tois advisory — nothing moves a card by it today.
Projects
opvs projects set-routing <project-uuid> --model claude-opus-5
opvs projects health <project-uuid> --profile planner-orchestration # EXITS 2 on not_healthy
opvs projects bundle set <project-uuid> --command ship --command-body ship=./ship.md # a body map REPLACESTasks
opvs tasks list --board <id> # List tasks on a board
opvs tasks list --self # My assigned tasks
opvs tasks get <id> # Task details
opvs tasks create --board <id> -t "Title" # Create task
opvs tasks update <id> --status review # Update status
opvs tasks update <id> --result-file out.md # Attach result from file
opvs tasks update <id> --pr 1201 --branch fix/x --worktree llm16:/root/OPVS-x --deploy-needs agentboard
opvs tasks update <id> --blocked-reason "CI red" --status blocked # the reason alone changes no status
opvs tasks create --board <id> -t "Title" --workflow-doc ship --workflow-rule "Never force-push" --estimate 45
opvs tasks claim <boardId> --role builder # Take ONE runnable card; there is no lease
opvs tasks search -q OSB-78 # By ref, PR number (#1256), title, description
opvs tasks search -q deploy --board OSB,ASP --status pending --column Review --offset 20Comments
opvs comments list <task-id> # List task comments
opvs comments add <task-id> "message" # Add inline comment
opvs comments add <task-id> -f output.md # Comment from file
opvs comments add <task-id> "x" --internal # A LABEL — every reader still sees it
opvs comments edit <comment-id> "new text" # Any board writer can edit any comment; old text is not kept
opvs files fetch-url <task-id> <url> # Store the file's BYTES on the card (attach-url keeps a link)Docs
opvs docs list # List doc projects
opvs docs get <project> <slug> # Read a page
opvs docs create <project> -t "Title" -s "slug" -f content.md
opvs docs update <project> <slug> -f content.md
opvs docs search "query" # Search docsCode index
# "Does this already exist?" — ask BEFORE writing it.
opvs code context "resolve_brand" --project <project-id>
opvs code context "resolveBrand" --project <id> --kind function --limit 5
opvs code summary --project <project-id> # Repo orientation: languages, entry points, top filesTwo answers you must not conflate: indexed: false means there was nothing to
search (no index for this project, or it is not yours), while verdict: none
means we searched and found nothing. Only the second is evidence of absence.
A capped result set says so — read truncated, and read
freshness.index_truncated separately: that one means the index is partial.
freshness.scan_truncated says the SCAN stopped early, so parts of the repo were
never read at all; freshness.symbols_dropped_at_ingest counts symbols the API
could not store, from files that were read.
Needs the code:read scope, which is in the default ceiling. There is no
opvs code index command on purpose — writing an index is code:write, which
a worker PAT does not carry, and the ingest caller is the sidecar on the desk.
Skill lessons
Pull and contribute the lessons a skill's collectors left in the typed marketplace
(AS-590). The reference is <vendor>/<name> (a leading @ on the vendor is
optional); the CLI validates it locally before any request.
opvs memory lessons get opvs-ai/opvs-vibe # Pull a skill's lessons
opvs memory lessons get opvs-ai/opvs-vibe --query retry # FTS over body + tags
opvs memory lessons get opvs-ai/opvs-vibe --limit 50 --since cur-1
opvs memory lessons add opvs-ai/opvs-vibe \
--kind learning \
--body "Retry on 429 with jitter; never under 200ms"
opvs memory lessons add opvs-ai/opvs-vibe \
--kind pattern --body-file ./note.md --tags "retry,http"add ships five keys exactly — kind, body, tags, source_engagement_hash,
craft_id — because the server-side CraftContribute schema sets
extra="forbid" (AS-445 sealed anything that looked like a credential). The
provenance hash is sha256("cli:" + workspace_slug + ":" + body) and NEVER
carries a brand id.
Runtime memory hooks
Install the SessionStart memory hook for Codex, the complete checked-in plugin hook set for Claude Code, the OPVS MCP read path and capture hooks for Cursor, or a managed capture plugin for OpenCode or Cline. Existing JSON entries are merged and repeated installs are no-ops.
opvs memory install codex
opvs memory install claude-code
opvs memory install cursor
opvs memory install opencode
opvs memory install cline
opvs memory install codex --project # writes ./.codex/hooks.json
opvs memory install cursor --project # writes ./.cursor/{mcp,hooks}.json
opvs memory install opencode --project # writes ./.opencode/plugins/opvs-memory.ts
opvs memory uninstall codex # removes only OPVS memory hooks
opvs memory uninstall cursor # removes only OPVS MCP/hooks entries
opvs memory uninstall cline # removes only the managed OPVS plugin
opvs memory uninstall codex --projectUser-level Codex hooks use $CODEX_HOME/hooks.json (default
~/.codex/hooks.json). User-level Claude Code hooks use
$CLAUDE_CONFIG_DIR/settings.json (default ~/.claude/settings.json), with
the plugin hook set merged under its hooks key. --project uses
./.codex/hooks.json or ./.claude/settings.json in the current project.
Installing the bundled Claude Code plugin through Claude Code's plugin
installer is an alternative to opvs memory install claude-code; plugins load
their own hooks/hooks.json as part of plugin installation.
Cursor uses ~/.cursor/mcp.json and ~/.cursor/hooks.json, or the matching
project files with --project. The MCP entry starts opvs-mcp, which provides
the OPVS memory read tools. The installed postToolUse, stop, and
sessionEnd hooks feed the same local capture pipeline as Claude Code.
OpenCode installs opvs-memory.ts under
~/.config/opencode/plugins/ (or ./.opencode/plugins/ with --project).
Cline installs it under ~/.cline/plugins/ (or ./.cline/plugins/ with
--project). Each file is marked as OPVS-managed, so uninstall will not remove
an unmarked file or any other plugin in the directory. The plugins forward
tool-after and idle/run-end events to opvs memory hook with a two-second
timeout and fail open. They only upload capture data after the user explicitly
runs opvs memory capture on.
Installing hooks does not enable capture: run opvs memory capture on with
a dedicated PAT whose only scope is memory:ingest, and use
opvs memory capture off to remove that local PAT again.
Session
opvs session get --self # Your context + assigned tasks
opvs session get --board <id> # Board overviewConfig
opvs config set api_url https://app.opvs.ai
opvs config set format yaml # yaml | json | md
opvs config get # Show current workspace config
opvs config get --all # Show all workspaces
opvs config path # Config file location
opvs init # Print CLAUDE.md snippetAuth
opvs auth request -w <slug> -e <email> # Request token
opvs auth resume # Collect a token approved after the wait
opvs auth status # Current auth info
opvs auth revoke --yes # Revoke token
opvs auth list # List agent tokens (admin)Tools (Marketplace skills)
Mirrors the 6 always-on MCP tools from the skills-meta plugin so terminal users can drive the marketplace runtime without going through an agent session. All operations are pinned to the workspace's current brand.
# Discover
opvs tools search "find restaurants in Aarhus" # Semantic registry search
opvs tools help @opvs-ai/agentboard # Full registry descriptor
opvs tools help @opvs-ai/agentboard --section methods # Just the methods
# Operate
opvs tools list # Installed packages on this brand
opvs tools install @opvs-ai/agentboard \
--grants '{"vendor.opvs-ai.agentboard.read":true}' # Install with entitlements
opvs tools uninstall @opvs-ai/agentboard --yes # Soft-uninstall
# Invoke
opvs tools call @opvs-ai/agentboard list_tasks \
--args '{"board_id":"abc-123"}' # Forward through runtime proxy
opvs tools call agentboard list_tasks --args-file ./params.jsonopvs tools pin <skill> is reserved for a CLI-facing pin endpoint that
ships in W2.1; until then it emits a stub error. For agent sessions, the
gateway plugin skill_pin tool is the canonical surface.
Workspace
opvs workspace list # List all workspaces
opvs workspace use <slug> # Switch current workspace
opvs workspace current # Show current workspace details
opvs workspace remove <slug> --yes # Remove a saved workspaceYAML Output
All read commands return YAML by default for token-efficient AI agent consumption (40-76% fewer tokens than JSON). Set format with:
opvs config set format yaml # default
opvs config set format json
opvs config set format mdClaude Code Integration
Run opvs init to generate a CLAUDE.md snippet you can add to your project, giving Claude Code automatic access to your board and docs.
Requirements
- Node.js 18+
- An OPVS workspace (opvs.ai)
License
Proprietary - OPVS.ai
