npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

orbitmap

v0.20.0

Published

Project management CLI for AI coding agents — works with Gemini CLI, GPT Codex, Claude Code, and any agent with shell access.

Readme

OrbitMap CLI

Project management for AI coding agents — without MCP.

What is this?

OrbitMap CLI gives AI agents (Gemini CLI, Codex, Claude Code without MCP, etc.) access to OrbitMap project management through simple shell commands. It's the non-MCP alternative to orbitmap-mcp, and tracks the same Agent API contract — the exact schema_version it is pinned to lives in tests/contract/fixtures.ts (PINNED_SCHEMA_VERSION) and is bumped by the contract re-vendor, so it is stated in one place instead of drifting here.

Use cases:

  • AI agents that can run shell commands but don't support MCP (Gemini CLI, Codex CLI)
  • Environments where MCP configuration is impractical
  • Quick task management from your terminal

Quick Start

# 1. Run the setup wizard — it sets up an Orbitmap.ai account (cloud) by default.
#    Local files, no account: `orbitmap init --local` (see "Local mode" below).
npx orbitmap init

# 2. Enter your credentials
#    - API Key (from OrbitMap dashboard)
#    - Area ID (optional — auto-detected if you belong to a single area)

# 3. Confirm where the agent skills go — `init` installs them in the same run for
#    Claude Code AND Codex (and Cursor when it is installed), without asking
#    which agent you use, creating
#    CLAUDE.md / AGENTS.md if there is none yet. `--agent <type>` installs for one
#    agent only; `--no-skills` opts out.

# 4. Start working
npx orbitmap start

init is the whole install: config, credentials and the agent skills. There is no mandatory second command.

Local mode

The CLI runs in one of two modes. orbitmap init sets up cloud mode by default — the account holder has already made that choice by signing up — and local-files mode is not retired, only reached deliberately, via orbitmap init --local:

| | Cloud mode | Local mode | |---|---|---| | Storage | Orbitmap.ai workspace (Agent API) | Markdown files with YAML frontmatter on your disk | | Account | required | none | | Web UI, team, multiple agents | yes | no — solo developer | | Commands | all of them | all of them, minus the limitations below |

Both modes speak the same commands with the same output — orbitmap start, orbitmap log, orbitmap tasks --json behave identically, and agent skills installed by orbitmap init never need to know which mode is active.

Set up local mode

--local is the only way in — there is no interactive mode question anymore, so --local always takes what you pass and falls back to sensible defaults (workspace directory ./.orbitmap, workspace name, first area = current directory name) rather than prompting:

npx orbitmap init --local \
  --workspace ~/orbits/personal \   # where the files live (default: ./.orbitmap)
  --workspace-name "My Orbit" \     # default: current directory name
  --area "My App" \                 # first area (default: current directory name)
  --no-skills                       # skip installing agent skills

If the workspace directory is outside the project, init writes a pointer at <project>/.orbitmap/link.json ({"workspace": "…", "area": "…"}) and adds .orbitmap/link.json and .orbitmap/docs/ to .gitignore (not the whole .orbitmap/ directory — .orbitmap/config.json, see Configuration, is meant to be committed). The pointer is found by walking up from the current directory, like .git, so commands work from any subdirectory. If the workspace is ./.orbitmap, nothing is gitignored — the data is meant to be committable.

Resolution order for the workspace directory: --workspace → ORBITMAP_WORKSPACE → .orbitmap/link.json → ./.orbitmap/workspace.yml → workspace_path in ~/.orbitmap/config.json. The mode itself is resolved per-project, first match wins: --workspace (explicit → local) → ORBITMAP_MODE=local|cloud → nearest .orbitmap/config.json mode field → auto-detect (walk up from cwd looking for .orbitmap/workspace.yml or .orbitmap/link.json, same as git looking for .git → local) → mode in the global config file, defaulting to cloud when absent. The auto-detect rung (and the project config rung above it) is what lets one project stay local and another stay cloud on the same machine — init never writes mode to the global config for the local path.

Where the data lives

<workspace-dir>/
  workspace.yml                # workspace header, default area + entity-number counters
  missions/MS-<num>.md
  intents/IN-<num>.md
  ideas/ID-<num>.md
  docs/<slug>.md               # one copy of each document, same path and format as the
                               # cloud doc cache (`<project>/.orbitmap/docs/`)
  issues/IS-<num>.md           # workspace-level issues (no area)
  areas/
    <area-slug>/
      area.yml                 # area header (git url, default branch, …)
      tasks/TS-<num>.md
      issues/IS-<num>.md       # area-scoped issues
      vibes/VB-<num>.md

Every entity is one Markdown file: the YAML frontmatter holds the fields, the body holds the long text (## Description, and for tasks an append-only ## Work Log). Relations are stored as entity numbers and slugs, so the files stay readable and diff cleanly in git.

The CLI is the only writer. Editing these files by hand (or letting an agent do it) bypasses validation and the counters — always go through orbitmap commands.

Current limitations

Local mode is a proof of concept. Not supported yet:

  • No import/export between a local workspace and the cloud (planned next; entity-number format and response shapes are already identical so the future import is a data copy).
  • No orbits — orbit * commands fail with NOT_SUPPORTED_IN_LOCAL_MODE and exit 1.
  • No document section-patching, sharing or presigned uploads — doc-patch, doc-share, doc-unshare, doc-visibility fail the same way. docs, doc, doc-import, doc-update, doc-meta and doc-status work.
  • No document history — doc-status and doc-meta rewrite the file's frontmatter and drop --changelog; the file and git are the audit trail.
  • No document scope — a local workspace keeps documents in one flat set with no document-to-area links, so doc-import --scope is refused rather than ignored.
  • No OAuth (login / logout) — meaningless without a server.
  • No human team members: exactly one implicit user (me) and one implicit agent (local-agent). members returns them; assign accepts only them.
  • No sync or merge between machines — you may sync the workspace directory with git yourself, the CLI does not mediate conflicts.

See docs/architecture.md for the adapter split and docs/adr/0001-local-mode.md for why local storage exists at all.

Object model

OrbitMap organises work on several levels. The CLI mirrors them 1:1:

| Level | Prefix | What it is | |-------|--------|-----------| | Workspace | — | Top-level container that owns areas, orbits, intents and missions | | Area | — | A single product/repo/domain (formerly called project) — the default scope of most commands | | Orbit | — | Cross-area grouping of work (a theme, an initiative); items of any type can be attached | | Intent | IN- | A desired outcome at workspace level; tasks and issues can be linked to it | | Mission | MS- | A time-boxed workspace focus; only one mission can be active | | Task | TS- | The unit of work agents execute | | Issue | IS- | A reported problem | | Idea | ID- | A future possibility | | Vibe | VB- | Ad-hoc work context outside the task lifecycle |

Project → Area rename. What used to be called a project is now an area. The global flag is --area, the env var is ORBITMAP_AREA, and the config key is area. The legacy ORBITMAP_PROJECT_ID env var and project_id config key were read-only fallbacks for one release and were removed by ADR 0002 — they had the same cross-project leak as the machine-wide area they aliased. Set area in your project's .orbitmap/config.json instead.

Commands

Task Management

| Command | Description | |---------|-------------| | orbitmap tasks | List tasks across every area you reach and every assignee (--area, --status, --priority, --orbit, --intent, --mission, --agent, --unassigned, --limit, --all) | | orbitmap task <id-or-number> | Show full task details | | orbitmap start [id-or-number] | Start working on a task (auto-picks next TODO if no ID) | | orbitmap status <id-or-number> <status> | Update task status (--force to move a parent to done with unfinished subtasks) | | orbitmap create <title> | Create a new task (--from-issue, --intent, --mission — mutually exclusive with --intent, --orbit) | | orbitmap subtask <parent-id-or-number> <title> | Create a subtask | | orbitmap assign <id-or-number> [agent-id] | Assign a task, issue, idea, or intent to an agent or user | | orbitmap get <prefixed-id> | Show any object by prefixed ID (TS-, IS-, ID-, VB-, IN-, MS-) | | orbitmap dep add <id-or-number> <target-id-or-number> | Add a dependency between tasks | | orbitmap dep remove <id-or-number> <target-id-or-number> | Remove a dependency | | orbitmap task update <id-or-number> | Edit a task after creation: title, description, agent instructions, priority, estimated effort, branch name (the old task-edit-content still works) |

Valid statuses: backlog, todo, in_progress, in_review, review_changes, done, blocked, cancelled

What a list shows, and what it hides

Every list — tasks, issues, ideas, vibes, docs, intents, missions — spans every area your key reaches and every assignee. It is not scoped to your own work, and it is not scoped to the area your directory maps to: that area is the context for writes and for single-object reads, and the server deliberately ignores it when listing.

Narrowing is explicit, and always visible in the output:

orbitmap tasks                      # everything you can reach
orbitmap tasks --area demo          # one area
orbitmap tasks --area demo,web      # two areas (comma-separated, no spaces)
orbitmap tasks --area all           # cancel an ORBITMAP_AREA narrowing for this call
orbitmap tasks --agent me           # only your own tasks — the pre-9.0.0 default
orbitmap tasks --agent jakub-agent  # a colleague's
orbitmap tasks --unassigned         # the free backlog
orbitmap tasks --intent IN-nahetc   # everything under one intent, in one call
orbitmap tasks --mission MS-abc123
orbitmap tasks --status all         # every status, on every list

Lists are paged: 30 rows by default, 100 at most per page. The page is not the answer — --all is:

orbitmap tasks --limit 100          # one bigger page
orbitmap tasks --all                # follow every page and print all 143 rows

Under every result the CLI prints what produced it, whether the list was empty, truncated or complete:

30 of 143 shown across 7 areas — run with --all for every row.
Scope: all 7 reachable areas, status todo,in_progress (46 hidden, add --status all), assignee any.

An empty list carries the same line, so "nothing exists" and "you did not ask for it" are never the same sentence:

No tasks. Scope: areas demo (1 of 7), status todo,in_progress (46 hidden, add --status all), assignee unassigned.

--json carries the whole meta block: total, next_cursor, per_area ({slug, shown, total} per area), applied_filters, scope and workspace, plus each row's own area, agent_id and assigned_user_id.

--area names one area everywhere else (create, log, status, doc-import, …) — that is the area a write lands in, so a comma-separated --area is refused there rather than silently taking the first. --area all is refused on the document writes for the same reason: it clears the list filter and nothing else, so on a write it would quietly mean "whichever area this directory maps to". Say where a new document belongs with doc-import --scope; every other document command reaches its document by id or slug across the whole workspace.

Referring to objects: <id-or-number>

Every command that takes an object reference accepts all three forms interchangeably:

  • the prefixed display ID the CLI prints — TS-uw9yh5, IS-4k2p9q, IN-6htga8
  • the bare 6-character code — uw9yh5
  • the UUID

You can therefore paste any identifier straight out of orbitmap tasks, orbitmap context or the message orbitmap create prints. This was previously true of orbitmap task and orbitmap get only: status, log, subtask, start, assign, task update and dep add/dep remove rejected prefixed IDs with a 404, including identifiers the CLI had just printed itself.

The Agent API resolves a prefixed ID inline for issues, ideas, intents and missions, but not for tasks — a task reference that is not a UUID needs a /tasks/by-number/{n} lookup first. The CLI now does that once, in the adapter layer, so every task command behaves the same and a UUID still costs exactly one request. See src/id-resolve.ts.

assign auto-detects the object type from the prefix (IS-… → issue, IN-… → intent, …); pass --type task|issue|idea|intent for bare UUIDs and bare codes.

Work Logging

| Command | Description | |---------|-------------| | orbitmap log <id-or-number> <message> | Log work on a task, intent, mission or issue |

The target follows from the reference: TS-… writes against the task, IN-… against the intent, MS-… against the mission, IS-… against the issue. A bare UUID or bare code means a task unless --target says otherwise.

Options:

  • --type <type> — Log type: note (default), code_change, decision, blocker. For discovery, exploration and fix use orbitmap vibe log — those are vibe types, not work-log types. status_change is written by the server on a status change and cannot be authored by a client.
  • --target <kind> — Which object a bare UUID names: task (default), intent, mission, issue
  • --meta <json> — JSON metadata, e.g. '{"files":["src/app.ts"]}'

Examples:

orbitmap log abc123 "Implemented user auth endpoint" --type code_change
orbitmap log abc123 "Using JWT over sessions for stateless auth" --type decision
orbitmap log abc123 "Blocked on missing API credentials" --type blocker
orbitmap log IN-a1b2c3 "Chose 3 FKs + a CHECK over a polymorphic target" --type decision
orbitmap log MS-a1b2c3 "Mission scope trimmed to the two gating intents" --type note
orbitmap log IS-a1b2c3 "Root cause is the stale cache, not the query" --type decision

Work log on an issue is cloud-only for now — local mode has no ## Work Log section on issue files and refuses orbitmap log IS-… with NOT_SUPPORTED_IN_LOCAL_MODE.

Work log entries on a status change

intent update, mission update and mission status take a repeatable --log <type>:<content>, sent as one batch with the update. The content is split on the FIRST colon only, so it may contain colons of its own.

orbitmap intent update IN-a1b2c3 --status plan \
  --log decision:"chose 3 FKs + CHECK" \
  --log note:"resume: CLI next"

The API requires at least one entry when the status changes and writes nothing at all when there is none — so a rejected transition is re-run with --log, not retried as-is.

Documents

| Command | Description | |---------|-------------| | orbitmap docs | List documents across every area you reach, plus workspace-level ones (--area, --type, --search, --status — default published, CSV or all — --limit, --all) | | orbitmap doc <id-or-slug> | Show document (TOC by default, --full, --section) | | orbitmap doc-import <title> | Import a markdown file (--scope workspace\|area, --workspace-id for which workspace, --context to set when-to-use hint, --status draft to import as a draft) | | orbitmap doc-update <id-or-slug> | Replace a document from a file (--changelog, --context to rewrite the when-to-use hint) | | orbitmap doc-patch <id-or-slug> | Patch a document with section operations (--changelog, --context, --dry-run) | | orbitmap doc-meta <id-or-slug> | Change a document's title, type or context — never its content (the slug and the version stay as they are; --base-version, --changelog) | | orbitmap doc-status <id-or-slug> <draft\|published\|archived> | Archive, restore or publish a document without touching its content, version or history (--base-version, --changelog) | | orbitmap doc-share <doc-id> <area-id> | Link a document to another area | | orbitmap doc-unshare <doc-id> <area-id> | Unlink a document from an area (--confirm-workspace-scope to detach the last one) | | orbitmap doc-visibility <doc-id> <area-id> | Restrict a share to specific agents (or allow all) | | orbitmap docs pull [slug] | Download document(s) to local cache | | orbitmap docs list-cache | Show locally cached documents | | orbitmap docs clean | Remove stale cached documents |

Examples:

# List all API docs
orbitmap docs --type api

# Search documents by title
orbitmap docs --search "auth"

# Read cheaply: TOC → one section → full content
orbitmap doc api-specification
orbitmap doc api-specification --section "## Authentication"
orbitmap doc api-specification --full

# Import a new document into the current area
orbitmap doc-import "API Spec" --type api --file ./docs/api-spec.md

# Import one at workspace level: linked to no area, reachable from every one
orbitmap doc-import "Coding Standards" --type guide --file ./docs/standards.md --scope workspace

# Update existing document
orbitmap doc-update api-specification --file ./docs/api-spec.md --changelog "Added auth section"

# Update the content AND the when-to-use hint shown in `orbitmap docs`
orbitmap doc-update api-specification --file ./docs/api-spec.md \
  --context "Reference when implementing or debugging authentication flows"

# Patch a single section (optimistic locking via --base-version)
orbitmap doc-patch api-specification --base-version 7 \
  --replace-section "## Authentication" --content "Bearer tokens only." \
  --changelog "Reworded auth"

# Relabel a mislabelled document — columns only: no upload, no version bump, same slug
orbitmap doc-meta api-specification --title "Agent API Specification" --type api

# Share with another area
orbitmap doc-share <doc-uuid> <area-uuid> --agents agent1-uuid,agent2-uuid

# Detach the last area — the document becomes workspace-level, so it must be confirmed
orbitmap doc-unshare <doc-uuid> <area-uuid> --confirm-workspace-scope

Scope. A document belongs to a workspace and is linked to any number of areas; zero links means workspace scope. --scope at import says where a new one lands; doc-share / doc-unshare move an existing one between the two. A reference (slug or UUID) resolves across the whole workspace on every path, so a refusal names the real cause — the document is unreachable at your scope, or not writable by your role — rather than reading as a wrong slug.

Area & Agent Info

| Command | Description | |---------|-------------| | orbitmap context | Session matching material: agent identity, area/workspace, missions and intents with TL;DRs, mission workitems, areas, document metadata | | orbitmap areas | List all areas assigned to this agent | | orbitmap create-area <name> | Create a new area (--desc, --slug, --workspace-id) | | orbitmap members <area-id> | List agents and users in an area (accepts slug) | | orbitmap overview [area-id] | Area overview with task statistics (uses default area) |

Issues, Ideas & Vibes

| Command | Description | |---------|-------------| | orbitmap issues | List open issues across every area you reach (--area, --status, --severity, --intent, --limit, --all); status shows as resolved · on_branch once a delivery is set | | orbitmap issue show <id> | Show issue details (UUID or number), including its work log (oldest-first) and delivery beside the status | | orbitmap issue register | Register a new issue (--intent to link it to an intent) | | orbitmap issue edit <id> | Edit issue content (--title, --description, --severity) | | orbitmap issue status <id> <status> | Update issue status (--delivery <on_branch\|merged\|released>, only with a resulting status of resolved; --resolved-task accepts a UUID or a TS- display number) | | orbitmap issue resolve <id> | Resolve an issue (same --delivery/--resolved-task options as issue status) | | orbitmap ideas | List ideas across every area you reach (--area, --status, --priority, --limit, --all) | | orbitmap idea add | Add a new idea — into this directory's area, or at workspace level from the workspace root (--workspace forces it, --area names one) | | orbitmap idea show <id> | Show idea details (UUID or number) | | orbitmap idea update <id> | Correct an idea after filing it (--title, --content, --priority) | | orbitmap idea status <id> | Update idea status | | orbitmap vibes | List vibes across every area you reach (--area, --type, --since, --limit, --all) | | orbitmap vibe log | Log a new vibe | | orbitmap vibe show <id> | Show vibe details | | orbitmap vibe update <id> | Update a vibe | | orbitmap vibe delete <id> | Delete a vibe permanently — asks first, --yes confirms non-interactively (--json deletes without prompting) |

Intents

Workspace-level desired outcomes. Tasks and issues hang off an intent; an intent can sit in an orbit.

| Command | Description | |---------|-------------| | orbitmap intents | List intents — workspace-wide (--status, --mission, --limit, --all) | | orbitmap intent show <id-or-number> | Show an intent (UUID or IN-xxxxxx) — embeds the context bundle (--no-bundle strips it, --no-logs skips the work-log tail, --slim is the freshness probe) | | orbitmap intent create <title> | Create an intent (--outcome, --design, --plan, --status, --areas <slug>,<slug>, --log <type>:<content> repeatable, --orbit) | | orbitmap intent update <id> | Update title, outcome, design, plan, or status (--roadmap-priority <lane> sets the roadmap lane; --areas <slug>,<slug> declares its areas; --log <type>:<content>, repeatable) | | orbitmap intent link <id> --type task\|issue --id <id-or-number> | Link a task or issue (UUID or display number) | | orbitmap intent unlink <id> --type task\|issue --id <id-or-number> | Unlink a task or issue | | orbitmap intent orbit <id> [orbit-id] | Set the intent's orbit (--clear to remove) |

Statuses: new, design, plan, build, done, cancelled

Roadmap lanes (--roadmap-priority, quote the two-word ones): parking lot, candidate, roadmap, mission, done

orbitmap intent create "Cut onboarding time in half" \
  --outcome "New users reach first value in under 5 minutes" \
  --design "Two-step wizard replacing the current five-step form." \
  --plan "Phase 1: wizard skeleton. Phase 2: inline validation." \
  --status design --orbit growth-q3

orbitmap intent link IN-a1b2c3 --type task --id TS-d4e5f6

# A status change carries its reason — the API rejects it (422) without at least one entry.
orbitmap intent update IN-a1b2c3 --status plan \
  --log decision:"chose 3 FKs + CHECK" \
  --log note:"resume: CLI next"

intent create --orbit is create-then-attach. If the attach step fails the intent is still created — the CLI reports the failure and does not retry.

The context bundle. intent show prints a Context bundle: block after the intent's own outcome/design/plan and before the work-log tail: the mission handle with its outcome TL;DR, the documents that surround the intent (listed by their when_to_use, with the slug to pass to orbitmap doc), sibling intent handles, and the intent's own tasks and issues. It is handle tier — TL;DRs and titles, never bodies. --no-bundle strips it when you only want the intent itself.

The freshness probe. --slim replaces the whole body with a ~150-token probe: handle, the outcome/design/plan TL;DRs, declared areas, mission, task counts per status, and the updated_at + last_log_id pair. Hold that pair from a full fetch; run the probe to see whether the intent moved. Unchanged means your copy is current — do not re-fetch. A work log never touches updated_at, so both halves have to be compared. --slim wins over --no-bundle and --no-logs.

orbitmap intent show IN-a1b2c3                # intent + context bundle + work-log tail
orbitmap intent show IN-a1b2c3 --no-bundle    # just the intent and its logs
orbitmap intent show IN-a1b2c3 --slim         # freshness probe: TL;DRs + updated_at/last_log_id

Missions

Time-boxed workspace focus. Only one mission can be active at a time.

| Command | Description | |---------|-------------| | orbitmap missions | List missions — workspace-wide (--status, --limit, --all) | | orbitmap mission show <id-or-number> | Show a mission (UUID or MS-xxxxxx) | | orbitmap mission create <title> | Create a mission (--desc, --outcome, --end-date) | | orbitmap mission update <id> | Update title, description, outcome, or end date (--log <type>:<content>, repeatable) | | orbitmap mission status <id> <status> | Set lifecycle status (--move-to to relocate unfinished work; --log required by the API) | | orbitmap mission link <id> --type <t> --id <id-or-number> | Link a task, issue, idea, or intent (UUID or display number) | | orbitmap mission unlink <id> --type <t> --id <id-or-number> | Unlink a directly-linked item |

Statuses: created as new; then active, completed, cancelled.

orbitmap mission create "Ship v2 beta" --outcome "Beta live for 50 customers" --end-date 2026-09-30
orbitmap mission status MS-a1b2c3 active
orbitmap mission status MS-a1b2c3 completed --move-to MS-d4e5f6

Linking a task or issue to a mission clears its intent_id — an item belongs either to an intent or directly to a mission.

Orbits

| Command | Description | |---------|-------------| | orbitmap orbit list | List orbits (workspace-scoped from the area context, --workspace) | | orbitmap orbit show <id-or-slug> | Show orbit details (areas, task counts, resources) | | orbitmap orbit create <name> | Create an orbit (--desc, --color, --areas) | | orbitmap orbit update <id-or-slug> | Update name, description, status, or color | | orbitmap orbit items <id-or-slug> | List items in an orbit (--type) | | orbitmap orbit attach <id-or-slug> | Attach an object to an orbit | | orbitmap orbit detach <id-or-slug> | Detach an object from an orbit | | orbitmap orbit area link <id-or-slug> <area-id> | Link an area to an orbit | | orbitmap orbit area unlink <id-or-slug> <area-id> | Unlink an area from an orbit | | orbitmap orbit dep <id-or-slug> <target-orbit-id> | Add an orbit-to-orbit dependency |

Item types: task, issue, vibe, idea, document Orbit statuses: active, completed, archived

orbitmap orbit create "Growth Q3" --color "#5B8DEF" --areas <area-uuid>,<area-uuid>
orbitmap orbit attach growth-q3 --type task --id <task-uuid> --note "core flow"
orbitmap orbit items growth-q3 --type task

There is no orbit delete and no dependency remove in the API — archive via orbit update --status archived.

Delivery Status

Track code delivery lifecycle on tasks:

orbitmap status abc123 done --delivery on_branch
orbitmap status abc123 done --delivery merged
orbitmap status abc123 done --delivery released

Setup helpers

| Command | Description | |---------|-------------| | orbitmap init | Full setup — mode, then the workspace + its area directories (cloud) or a local workspace, then the agent skills (--key, --workspace-id, --new-workspace [name], --area, --allow-nested, --advanced, --global; --local, --workspace, --workspace-name; --agent <type>, --scope <user|project>, --no-skills, --skip-update-check) | | orbitmap off / orbitmap on | Switch OrbitMap off for this folder (and its sub-folders), or back on. off writes a short "OrbitMap is off in this folder" block into the folder's CLAUDE.md / AGENTS.md and "disabled": true into .orbitmap/config.json. Every command except on, off, init, uninstall, setup-agent and help then exits 1 with OrbitMap is off in this folder. Run `orbitmap on` to turn it back on. on (from the folder or any sub-folder) removes both. No network call | | orbitmap login / orbitmap logout | Browser-based OAuth login / token revocation | | orbitmap setup-agent | Install or repair the instructions for Claude Code and Codex, and Cursor when it is installed — init installs them once already, and orbitmap context refreshes them after a CLI upgrade by itself. --agent <type> handles one agent only, e.g. Cursor, Gemini, Windsurf or generic (--scope, --clean-project, --skip-update-check) |

Global Options

| Option | Description | |--------|-------------| | --json | Output raw JSON (for scripting/piping) | | --area <id> | Override area context (UUID or slug) | | --workspace [path] | Override the local workspace directory (local mode only). The path is optional only so the bare flag can reach idea add, where it means "file at workspace level"; with no path it never selects a directory or a mode |

Configuration

See docs/adr/0002-project-scoped-config.md for the full design and rationale (this replaced a single global config file — breaking for scripted init callers that relied on that, see the ADR's Consequences section).

Setup

Run npx orbitmap init for interactive setup. Cloud setup is workspace-first: the key is validated with GET /areas, you pick the workspace (--workspace-id in a script), and init works out which directory holds which area — by directory name, by the origin git remote matched against the area's repository URL, by the slugified name, or by asking. It writes <root>/.orbitmap/config.json (committable, no secrets) by default; pass --global to write ~/.orbitmap/config.json instead (a machine-wide default, like git config --global). The API key always goes to ~/.orbitmap/credentials.json, under a named agent profile — never into a config file, either way. Re-running init merges, so running it inside a newly cloned area adds that area to the workspace map that already exists above it.

A second project needs no key paste (Agent API 10.22.0). Without --key, init first checks the keys already saved in ~/.orbitmap/credentials.json with GET /me and lists every one that works on this server, labelled (all workspaces · <first workspace>) or (workspaces: a, b, c +N) so keys of different accounts can be told apart. One match is offered in the key prompt (Enter = use it, or paste a new key); several give a numbered pick where Enter means [k], paste a new key; none falls back to the key prompt. A scripted run reuses nothing, and piped input reuses only a single all-workspaces key. When that agent may create workspaces, the workspace question also offers Create a new workspace beside one Use existing: <name> (areas: …) line per workspace. It is the default for a folder outside every connected workspace, asks for a name that has no default (three empty answers stop the run with nothing written), and is created with POST /workspaces only after you confirm the write manifest. In a script, pass --new-workspace [name] (default name: the folder's). An older server answers GET /me with 404, and init then works exactly as before.

Project config file — <project>/.orbitmap/config.json

{
  "schema_version": 1,
  "mode": "cloud",
  "agent": "orbitmap-app-bot",
  "workspace": "my-workspace",
  "areas": { "orbitmap-cli": "orbitmap-cli", "packages/api": "api" }
}

mode is "cloud" (the default when the key is absent) or "local". agent names a profile in ~/.orbitmap/credentials.json — the project says which agent to use, the credentials file (below) holds that agent's key. workspace is a workspace slug or UUID, never a path. areas maps directories (relative to this file's parent; "." is that directory) to area slugs, which is how one root can hold several areas; each mapped subdirectory also gets a marker config of its own holding nothing but {"area": "<slug>"}. The older single area field is still read for back-compat. This file is meant to be committed: a team shares the workspace, the agent name and the area layout through version control, and each member's own copy of credentials.json supplies their own agent's key. Areas living outside the tree cannot be expressed portably, so they go to the gitignored <root>/.orbitmap/link.json instead ("areas": { "<slug>": "/abs/path" }).

Global config file — ~/.orbitmap/config.json

Same shape, used as the fallback for any field a project's own config doesn't set, and as the only place workspace_path (the default local workspace directory) is read from. Only written by orbitmap init --global, or left over from before ADR 0002 — in which case a lingering area is still honoured but prints a one-time deprecation warning telling you to move it into a project's own .orbitmap/config.json.

Credentials file — ~/.orbitmap/credentials.json

Never committed, never leaves the machine. Also holds the OAuth session used by orbitmap login/orbitmap logout (unrelated top-level fields, unchanged shape):

{
  "agents": {
    "orbitmap-app-bot": { "api_key": "orbitmap_..." },
    "orbitmap-cli-bot": { "api_key": "orbitmap_..." }
  }
}

An agent name that resolves to nothing here (nothing named agent anywhere, including the global config) falls back to the "default" profile — which is also where a pre-ADR-0002 global api_key is migrated to. That migration runs inside loadConfig(), i.e. on the first command of any kind after upgrading, not only on orbitmap init; it rewrites ~/.orbitmap/config.json without the key (preserving every other key in the file) and prints a one-time notice. If the write fails — read-only $HOME, a credentials.json owned by another user — it says so and keeps using the key from the config file, so the CLI never locks you out over a key that is right there on disk.

~/.orbitmap itself can be relocated with ORBITMAP_CONFIG_HOME or the standard XDG_CONFIG_HOME (as $XDG_CONFIG_HOME/orbitmap); reads fall back to the legacy ~/.orbitmap location when the new one has nothing yet.

Environment Variables

Environment variables take precedence over every config file:

| Variable | Description | |----------|-------------| | ORBITMAP_API_KEY | Agent API key (bypasses agent-profile lookup entirely) | | ORBITMAP_API_URL | API base URL — the only remaining override; there is no config-file equivalent any more (see the ADR) | | ORBITMAP_AGENT | Name of the agent profile in credentials.json to use | | ORBITMAP_AREA | Area UUID or slug | | ORBITMAP_MODE | cloud or local — overrides the config mode | | ORBITMAP_WORKSPACE | Local workspace directory (local mode) | | ORBITMAP_CONFIG_HOME / XDG_CONFIG_HOME | Relocate ~/.orbitmap | | ORBITMAP_NO_UPDATE_NOTIFIER | 1 silences the once-a-day "newer CLI available" notice (it is also skipped when CI is set) |

Resolution Priority

Area (first match wins):

  1. Explicit --area flag
  2. ORBITMAP_AREA env
  3. .orbitmap/link.json (area) found by walking up from the current directory — local mode
  4. Nearest .orbitmap/config.json that sets area, found the same way. Resolution is per field: a packages/api/.orbitmap/config.json that sets only agent does not hide an area pinned by the repository root
  5. default_area in the workspace's workspace.yml — local mode; written by orbitmap init for the first area, so a workspace outranks the machine-wide default of an unrelated project
  6. Global config area (deprecated — prints a warning when hit)
  7. Defaults (local mode: the only area in the workspace, when there is exactly one)

Agent / API key (first match wins):

  1. ORBITMAP_API_KEY env — used directly, no profile lookup
  2. Otherwise resolve an agent name (ORBITMAP_AGENT env → nearest .orbitmap/config.json that sets agent → global config agent → "default") and look up its key in credentials.json

Mode (first match wins): --workspace (explicit → local) → ORBITMAP_MODE → nearest .orbitmap/config.json that sets mode → local auto-detect (.orbitmap/workspace.yml or .orbitmap/link.json found by walking up) → mode in the global config, defaulting to cloud.

Workspace directory (local mode, first match wins): --workspace → ORBITMAP_WORKSPACE → .orbitmap/link.json (workspace) → nearest .orbitmap/workspace.yml → the .orbitmap/ directory of the nearest config that sets mode: "local", when it is itself a workspace → workspace_path in the global config, which must be an absolute path (a relative one would mean a different directory in every project, so it is ignored with a warning).

Authentication

In cloud mode, OrbitMap CLI uses a single Agent API Key for authentication (Authorization: Bearer header). Local mode has no authentication at all — access to the workspace directory is the only credential. Each agent has its own key, named by an agent profile in ~/.orbitmap/credentials.json — get the key itself from the OrbitMap dashboard. Area context travels in the X-Orbitmap-Area header.

Optionally, set a default Area ID if your agent is assigned to multiple areas.

Error handling

API errors are printed as Error [CODE] (status): message. Alongside the regular envelope, the client normalises the flat billing/usage shape returned by 402/429 responses into:

| Code | When | |------|------| | PAYMENT_REQUIRED | HTTP 402 — billing action needed | | RATE_LIMITED | HTTP 429 — too many requests | | USAGE_LIMIT | Other flat-error responses | | TIMEOUT | No response within 30s |

For AI Agents

orbitmap init already teaches your agent about OrbitMap — the skills are its last step, in both cloud and local mode. It installs them for Claude Code and Codex together, and for Cursor when it is installed, without asking which agent you use, and creates the agent config file when there is none:

npx orbitmap init                  # skills for Claude Code and Codex (+ Cursor when installed)
npx orbitmap init --agent claude   # one agent only; --no-skills opts out

Why both: they cost a handful of small files, while asking a new user which coding agent they work with was the step most likely to lose them. Nothing about the directory is inspected, and the install does not require either agent to be present — if neither Claude Code nor Codex is on the machine, the run ends with this notice (and cloud init skips its "Next" block):

No Claude Code or Codex found on this machine. OrbitMap works through one of them —
install either and it picks up the OrbitMap skills installed just now:
  Claude (desktop app with the Code tab): https://claude.com/download
  Codex (in the ChatGPT desktop app):     https://chatgpt.com/codex

When at least one is found, cloud init closes with a "Next" block that names only the places this machine can open the folder from — a Terminal: line (cd <folder> && claude, or codex) and/or a Desktop: line (Claude app → Code tab, ChatGPT app → Codex), plus a Cursor: line when Cursor is installed — and tells you to ask "what should we build first?". A machine with only Cursor gets the "Next" block and no notice.

setup-agent does the same install on its own — for repairing an install, and for adding one of the four agents init does not install by default (after a CLI upgrade the skills refresh by themselves, see "Staying current" below):

npx orbitmap setup-agent                    # Claude Code and Codex (+ Cursor when installed), like init
npx orbitmap setup-agent --agent gemini     # one agent only: claude, codex, gemini, cursor, windsurf, generic

Gemini, Windsurf and generic have no skill ecosystem, so installing them means inlining ~86 KB of instructions into one file; that is why they are reached deliberately with --agent rather than by default.

Cursor loads skills too (Cursor 2.4+). init and a bare setup-agent install it alongside the default pair when Cursor is on the machine — the cursor / cursor-agent command, the Cursor app, or Cursor's app-data folder (~/.cursor alone does not count: OrbitMap creates it). Install it explicitly for Cursor Cloud Agents on a machine without Cursor, or to commit the skills to a repository:

npx orbitmap setup-agent --agent cursor                   # ~/.cursor/skills/ + ~/.cursor/rules/orbitmap.mdc
npx orbitmap setup-agent --agent cursor --scope project   # ./.cursor/skills/ + ./.cursor/rules/orbitmap.mdc

Cloud Agents see only ~/.cursor/skills/ after you turn on Settings → Agents → Sync Skills, or skills committed to the repository (.cursor/). ~/.claude/skills/ never reaches them. The install at project scope removes the OrbitMap block an older CLI inlined into .cursorrules, and orbitmap context keeps a Cursor skills install current after an upgrade, like the other two. Cursor also reads ~/.claude/skills/ and ~/.codex/skills/, so on a machine with all three installs it may list each OrbitMap skill more than once.

Staying current

  • Skills refresh themselves. orbitmap context refreshes the skills, instructions block and hook entry that OrbitMap wrote earlier — Claude Code, Codex and Cursor, user and project scope — when their <!-- ORBITMAP_START v<ver> --> marker is older than the CLI you run, and prints one stderr line: [orbitmap] Updated the OrbitMap skills to v<new> (were v<old>): ~/.claude, ~/.codex. No setup-agent run is needed after an upgrade.
  • Daily notice. At most once per calendar day the CLI ends a command with one stderr line: [orbitmap] A newer CLI is available: <latest> (you run <current>). Update: <command> (npm i -g orbitmap@latest for a global install, npx orbitmap@latest for npx). The registry is asked at most once per 24 hours, for at most 1.5 seconds, and silently skipped offline. Set ORBITMAP_NO_UPDATE_NOTIFIER=1 to turn it off; it is also skipped when CI is set. The installed skills tell the agent to relay [orbitmap] notices once per session.
  • init and setup-agent refuse a stale CLI. They check the registry live; when a newer version exists they print This is orbitmap <cur>; the latest is <latest>., Update first: <command> and Continue with <cur> anyway? [y/N] — Enter stops with nothing written. A non-interactive run (--key, no terminal) exits with code 1 and suggests --skip-update-check, which skips the check. uninstall never checks.
  • Server minimum. The CLI sends X-Orbitmap-Cli-Version on every Agent API request. A server that sets a minimum version answers an older CLI with 426 CLI_UPGRADE_REQUIRED; the CLI prints Error [CLI_UPGRADE_REQUIRED] (426): <message> and Update: <command>. The gate is off until the server sets one.

Where it installs: --scope user (global) vs --scope project

Run interactively, both commands ask once where the skills should go — one answer for every agent being installed:

Where should the OrbitMap skills be installed?
  [1] Global (~/.claude and ~/.codex) — every session and subagent sees them, in any directory;
                                        writes into your home directory  (recommended)
  [2] Project (this dir)              — only sessions started in this directory can load them

Global is recommended and is what Enter picks; --scope skips the question; a scripted run takes global without prompting and says so in its output.

Global (user) scope means ~/.claude/skills/ and ~/.claude/CLAUDE.md for Claude Code, ~/.codex/skills/ and ~/.codex/AGENTS.md for Codex, and ~/.gemini/GEMINI.md for Gemini. Agent skills do not traverse directories: a project-level .claude/skills/ is invisible to a session started in a sibling or child repository, while CLAUDE.md is inherited downwards. A user-scope install is the only one that is consistent everywhere — and it matters now that one workspace routinely spans several area directories, where a single project install would cover just one.

A user-scope install writes nothing into your project directory.

npx orbitmap setup-agent --scope project   # this directory only
npx orbitmap init --agent claude --scope project

At project scope each agent's skills go where that agent reads them: ./.claude/skills/ and ./CLAUDE.md for Claude Code; ./.agents/skills/, ./AGENTS.md and ./.codex/hooks.json for Codex. The hook says so explicitly, so an agent that inherits CLAUDE.md / AGENTS.md from a parent directory but cannot load the skills knows why, and knows to re-run with --scope user.

CODEX_HOME. Codex's personal directory is ~/.codex unless CODEX_HOME is set to an absolute path, in which case Codex reads $CODEX_HOME/skills and $CODEX_HOME/AGENTS.md instead — and so does the installer: a user-scope Codex install, its hook file (hooks.json) and the refresh checks all follow CODEX_HOME. A relative value is ignored.

The Codex hook needs one approval. Both agents get a PostToolUse hook that carries the server's Instructions blocks to the agent. Codex never runs a freshly written hook until you trust it, so on a machine where Codex is present the install ends its Codex part with:

Codex: approve the OrbitMap hook once — run /hooks inside Codex and trust `orbitmap emit-instructions`.

That is the one manual step; Claude Code needs nothing.

When Claude Code is found and Codex is not, the install prints Codex not found (looked for codex on PATH, ~/.codex, Codex.app and ChatGPT.app). If you use Codex, its skills are installed anyway — run /hooks inside Codex and trust orbitmap emit-instructions. instead. From inside an agent, orbitmap context also reports (once per day) a missing skills directory, instructions block or hook entry for the agent that called it, and a Codex hook that was never approved.

Coming from a pre-skills Codex install? Those wrote all six skill bodies inlined into ~/.codex/AGENTS.md — around 86 KB of markdown carried in every turn. Re-running the install at user scope replaces that block in place, between its <!-- ORBITMAP_START/END --> markers, with the thin hook plus real skill files under ~/.codex/skills/ — nothing else to run:

npx orbitmap setup-agent

The shrink needs no manual cleanup.

Upgrading from an older setup? If you already have skills in ./.claude/skills/ (or ./.agents/skills/ for Codex), that project copy silently wins in this directory. A user-scope install detects it and names the paths; it offers to clean up when run interactively, and otherwise tells you to run:

npx orbitmap setup-agent --scope user --clean-project

Cleanup removes only the six OrbitMap skill directories and OrbitMap's own marker block and hook entry — never your own skills, never unrelated CLAUDE.md content, never a whole .claude directory.

Either way it installs two layers, mirroring the OrbitMap MCP server:

  • A thin always-on hook (~10 lines) in your agent config (CLAUDE.md, AGENTS.md, …) so the agent always knows the project uses OrbitMap and knows the workflow skeleton.
  • The deep, on-demand instructions — full parity with the MCP full profile (plus orbitmap-brainstorm and orbitmap-design, CLI-native, no MCP counterpart). The agents that can load skills — Claude Code, Codex and Cursor — get them as six loaded-on-demand skills (orbitmap, orbitmap-execute, orbitmap-plan, orbitmap-brainstorm, orbitmap-design, orbitmap-docs, zero per-turn context cost) under ~/.claude/skills/, ~/.codex/skills/ and ~/.cursor/skills/ respectively — or ./.claude/skills/, ./.agents/skills/ and ./.cursor/skills/ at project scope ($CODEX_HOME/skills/ when CODEX_HOME is set). Gemini, Windsurf and generic have no skill ecosystem, so the same content is inlined into their config file as a markdown fallback.

A skill is not always a single file: orbitmap-execute ships SKILL.md plus references/worker-contract.md (the rules a dispatched worker subagent follows). init and setup-agent install every file a skill carries, and refresh them all on a re-run.

The instructions are mode-agnostic: they reference CLI commands only, so the same skills drive an agent whether the data goes to the cloud or to local files.

Executing work: two flows

The installed orbitmap-execute skill covers two ways of working, chosen by what you ask for:

  • "work on TS-x" runs a single tracked task — start or resume it, implement, log, and finish with the correct status (in_review by default, so a human verifies before closing).
  • "execute IN-x" delivers a whole planned intent in the background: tasks are built by parallel workers in dependency-ordered waves, each task gated by its own tests, then the intent-level test and an impact pass diffed against a test baseline captured before the run, with a goal-based fix loop (no retry cap) until the gates are green. A stuck task ends blocked with a logged blocker while the rest of the intent completes. The run ends with every completed task in_review on an intent/<number>-<slug> branch — merging is left to you: the run never merges and never pushes to the default branch.

Both flows follow a test policy: one test per guarantee, existing tests first, and tests a change made redundant are pruned; each finished task records the tests it reused, added, changed or removed.

See Agent Instructions for details.

Requirements

  • Node.js 18+

License

MIT