@ideaspaces/cli
v0.2.4
Published
IdeaSpaces CLI — capture durable agent knowledge from the command line
Maintainers
Readme
IdeaSpaces CLI
The command line for folders that agents inhabit.
An ideaspace is a folder of Markdown under git that holds your knowledge and how to work with it. Open an agent inside it and that's who you're talking to. The protocol defines the shape.
Most people use the shape through a plugin for Claude Code, Codex, or Cowork, or Pi. The plugins bundle this CLI. Install it yourself for scripts, automation, and other harnesses.
A space is a real git repository on your disk. Nothing leaves your machine until you publish or push.
Install
npm install -g @ideaspaces/cliNode 20+ and git. ideaspaces doctor checks both.
Five minutes
ideaspaces create my-space --yes # a folder with _agent/ in it, committed
cd my-space
ideaspaces navigate . # what is here, what changed since last time
ideaspaces look . --depth children # one target at a chosen rung
ideaspaces write decisions/pricing.md --name "Pricing" --content "# Pricing
Per seat, billed yearly."
ideaspaces commit -m "Capture the pricing decision" decisions/pricing.mdcreate --home <dir> --yes makes a dedicated Home with an Agreement and an empty home.map.md; it refuses code repos and preserves an existing home.map.md. map <dir> opens that curated file even before it has roots, and navigate <dir> names it. Without a Map note (*.map.md or a README.md with a map block), map derives the local tree as before. create writes one contract file, _agent/agreement.md, under the knowledge kind — or the agent kind with --agent — and references that kind's public Agreement Space in its frontmatter (agreement: knowledge:repo:…). Its sections are prompts to replace in conversation; navigate --json reports the reference as agreementReference. Older Spaces on _agent/foundation.md still load; navigate prefers Agreement when both exist, and --contract foundation reads the older frame explicitly. create --foundation writes the older shape for one more release. Ambient navigation renders the protocol-owned stable head, then local working-set/catalog handles, then the volatile tail; activity belongs only to the tail. look <path> --depth name|summary|surface|children|full reads exactly one local Note or directory beneath a reference-only frame; the existing navigate --focus and inspect readers remain available during convergence. create --agent makes a folder that is an agent. Inside a code repo, create --yes keeps _agent/ local to your machine; --shared commits it.
Take one home, hand one over
git clone https://github.com/IdeaSpaces-xyz/hn-reader && cd hn-reader # any git host
ideaspaces fork https://ideaspaces.xyz/repos/<repo-id> ./theirs # public space, no account
ideaspaces update --yes # later: pull in source changes that don't conflictideaspaces login
ideaspaces publish --yes # host it, private to you
ideaspaces share person [email protected] --grade explore
ideaspaces share visibility public --yes # anyone can view and fork
ideaspaces push
ideaspaces pullAnything that leaves your machine prints its plan and runs only with --yes.
share stays recipient-shaped: person, team, list, remove, resend, history, and
visibility. It does not expose repository memberships, internal Grant records, or organization
administration.
Commands
ideaspaces <command> --help for usage. --json on reads and local writes.
| Job | Commands |
|---|---|
| Look around | navigate, look, status, inspect, ls, search, skills, map, times |
| Write things down | write, commit, change, node |
| Start, bring home | create, get (clone, fork, link), clones, forget |
| Share and sync | login, status account (whoami), publish, share, push, integrate (pull, update), sync, repos, catalog |
| Talk | conversation, conversations, threads (inbox legacy alias for one release), spaces, agents, agent list |
| Run a local agent | agent run (--runtime=pi or --runtime=claude, --model, pinned local --thread), pi-status, pi-login, pi-logout, pi-models, claude-status, claude-models, conversation send --local (--runtime=pi, the default, or --runtime=claude for your own Claude Code), conversation compact --local |
| Housekeeping | status doctor (doctor), credential, power logout |
What the CLI promises
writetouches only the file you name and keeps frontmatter you did not set.--map JSON|YAML|FILEwrites a validated Map block to a Note. Pass the returnedshaas--if-matchfor a safe second write;--forceoverwrites.commitcommits only the paths you name. Other staged work, yours or a teammate's, is left alone. The author is git'suser.nameanduser.email, never a hidden credential.- A space has one id. Agreement is the preferred contract source; until it declares identity, a valid Foundation identity remains the compatibility evidence for that same Space.
createmints identity into the Agreement it writes (or into Foundation under--foundation),publishadopts it, and conflicting declarations fail closed.clonekeeps identity;forkremints it in every projected root entrypoint. Nothing rekeys a space silently. getshows before it does. Given a Space URL it says what clone would do (same identity, fetch/push truth, full history) and what fork would do (new identity, no source history), and mutates nothing until--yes --as clone|forknames one; given a folder it offerslink. It offers only a mode your account can reach; otherwise it says which access (Viewer, Allow copying, Editor) would open one. The choice between clone and fork is yours, never guessed.clone,fork, andlinkremain the explicit verbs.integratefollows what the checkout is. A clone integrates its upstream; an unpublished fork integrates its maintained source; a published fork with source lineage defaults to its own remote and takes--from sourcefor the other. Plan-first:--yesapplies.pullandupdateremain as the underlying verbs.statusis the tail, and only the tail. It renders local State (branch, upstream, working tree, captures awaiting commit), the repo catalog when you pass--workspace, and what moved since last session — the same composition an agent runtime appends after its cached head, so the two never disagree. Nothingnavigatealready showed in the head. Login state and installation health are separate sections:status accountandstatus doctor(whoamianddoctorstill work for one release).lookdeepens one target without adopting its terms. The applicable Agreement or Foundation is reference context only. JSON adds a portablemaponly for a clean, pinned, identified root; dirty, unborn, ignored, local-only, or invalid roots remain honest local projections.- Read through a Map, not a folder.
look @notes//ideas/first.md --map space.map.mdreads a Map member by address —@<root name>//<position>,@<root_node_id>//<position>, or//<position>for your own root — with no filesystem path, from any working directory.navigate @notes//ideas --map space.map.mdfocuses on a member directory the same way. A Space's Map reads each root's checkout at HEAD and says when it has drifted from the pin; a Thread's Map (one under_threads/) reads the pin;--at pin|headoverrides either. The bytes come from the commit, never the working tree, and the result names the root and carries its identity form, so what you keep resolves without the Map. A root with no local checkout fails with its name and the reason.look <path> --pin <sha>reads your own checkout at an authored commit. - A launch Map is read, not just listed.
agent runandconversation sendwith--map <note>start the session with each member read at its declared depth, within 12,000 characters: over that, the last member is read at a lower depth first, then the one before it, and nothing is cut mid-member. The same Map at the same commits gives the same bytes. The launched session gets the Map asIDEASPACES_MAP, so an address read there needs no--map. It is a convenience, not a boundary: it grants and restricts nothing. agent run <pov> --message <text>launches from an explicit local Agreement POV path under Pi or Claude and streams Keeper events. It passes that POV's own Agreement as child orientation;--model,--pi-thinking, and supported Claude--claude-effortare runtime choices, not task modes. Pi child runs require explicit--ext <paths>(and--skill <dirs>for selected package skills). They disable discovery of both Pi extensions and skills; only the forwarded paths load, not additional user or target-project skills. Relative paths resolve from the selected POV, not the caller's cwd. Installed packages or ambientIDEASPACES_PI_EXTENSIONSdo not authorize a child launch. Missing selection refuses before spawning. A supplied--conversation <id>must already have a nonempty transcript under that exact POV and runtime; omit it for the first turn. Pi trust now defaults to saved onagent run; older scripts that relied on implicit project approval must explicitly pass--pi-trust explicitafter a person approves that target. Directconversation send --localretains its legacy explicit default for Desktop callers; pass--pi-trust savedthere when appropriate.--read-onlyrestricts Claude to Read/Grep/Glob without MCP tools (not a filesystem sandbox);--permission-mode bypassPermissionsrequires opting out of read-only.agent runbounds messages to 8 KiB and the combined contract/Thread orientation to 16 KiB before child spawn rather than silently truncating a contract.agent run --thread <path> --thread-map <note> --thread-member <ordinal>launches from a selected authored Map member (zero-based ordinal), never from the Thread path alone or HEAD.--thread-mapnames a regular authored Map file (not inline YAML), and--thread-memberis its zero-based member index. The member must name a post in that local Thread at an available commit; the Thread Agreement and selected post summary are read at the pin, separate from the user's message. A successful run appends its closing response as a snapshot under the POV Agreement's name, citing that pinned member in its Map; a failed run writes nothing. The post id and path appear inturn_complete.result.thread_snapshotin the Keeper JSONL stream (and on stderr in human mode); a failed append emits a terminal error instead ofturn_complete. Hostedx_…Threads are not supported here.threadsis one family for local files and hostedx_ids.threads listshows local and hosted rows together when logged in;threads open <slug|path>reads a local Thread at name, summary or full depth, whilethreads read <x_…>,send,reply, andexpandkeep the hosted wire unchanged.threads new,post,close(a closure post), andrenderwork without a login. Posts are exclusive-create Markdown; commit and push their exact paths through the repo's usual Git flow for a second machine to see them. A successful local agentis_threads postis harvested from its returned file path as a write; list, open, close, and failed posts are not.--reply-tofills the ancestor chain,--mapaccepts a validated authored Map, andrenderderives the timeline without overwriting the curated README.open --newuses a private per-thread cursor; only--ackmoves it. Same-Spaceopen --pin <40-hex-sha> --position _threads/<thread>/<post>.mdremains a low-level pinned read; an unavailable commit never falls back to HEAD.- Select a pinned local Thread across Spaces. Use
open <slug> --map <file> --member <ordinal>to read only an authored pinned post, including one in another Space, without leaving the caller's Agreement;--depth fullreturns the pinned bytes, not the live Thread, and selected reads do not support live--newor--ack. A unique registered checkout resolves the Map root; for an unregistered or multiply checked-out Space, pass--checkout <absolute Space root>explicitly. A Map-selected read requires a verifiable root identity even when the Thread is in the caller's Space; that caller Space can serve as its own checkout without a registry entry, and same-Space paths still work once identity is established. Unlike earlier same-Spaceopen --map --member, selectedpostscontains only the pinned post andthread.closedis not reported from the live tree; unselected same-Spaceopenand explicit--pin/--positionretain their live timeline. For an orphan_threads/worktree, give the parent Space root as--checkout, not the orphan worktree directory. Symlinked_threads/is refused; use the real orphan Git worktree instead. The checkout's root identity and any canonical origin or registry evidence must agree with the authored Map root. - Reply to the selected Thread.
post <slug> --map <file> --member <ordinal> --reply-to <existing-pinned-post-id> --message <body>rechecks the live Thread before appending under the caller's Agent Agreement name; a newer post need not be the parent. A changed/closed Thread, missing parent, missing pin, symlink or arbitrary cross-Space path refuses. A changed Thread README or Agreement needs a re-authored Map at an updated commit. The late live-target recheck is best-effort against concurrent edits, not a cross-process lock; creation is exclusive.--mapwithout--memberon a same-Space post remains a citation, not target selection. - Private Threads stay off the default remote. For a private code repo,
threads initmounts an orphanthreadsbranch at ignored_threads/;threads push --remote <team-remote>explicitly names a non-origin destination ongit.ideaspaces.xyz(or a local file remote for offline testing); unknown hosts, including GitHub Enterprise domains, are refused rather than guessed safe. Never publish that branch to GitHub. Theinboxalias prints a deprecation notice for one release; the server's/inboxAPI stays unchanged. search --jsonseals its hits as a Map under the same gate. Ranked results always come back;map_statusisavailablewith a parse-validmaponly for a clean, pinned, identified root with every hit tracked at an unchanged HEAD, elseprojection_pendingwithmap: null. Rank, score, and snippet stay inresults; member order carries the rank.mapopens a curated*.map.mdor a folder'sREADME.mdwith amapblock. Pass the file to select one lens explicitly; a folder prefershome.map.md, then a Map-bearingREADME.md, then another*.map.md.navigate <folder>names the chosen Map.map create team.map.md --name Team --summary 'Team work'creates an empty Space Map without overwriting an existing file.map add team.map.md thread:x_0123456789abcdef01234567 --depth summaryadds an open address;map add team.map.md --root-node-id n_0123456789abcdef01234567 --sha <commit> --position . --depth summaryadds a pinned root and position; use--root <index>for an existing root.map remove team.map.md <address>drops an unambiguous address; for an index, pass--if-match <file_sha>frommap team.map.md --json, so a concurrent edit cannot change which member the index means. Roots remain (their indices are stable). Concurrent CLI edits serialize under<map-note>.lock; a busy/stale lock refuses after four seconds, and--if-match <sha>refuses a moved base. If a writer crashed, check no writer is running before removing its empty lock directory withrmdir <map-note>.lock; never delete a live lock. The lock and hidden temporary file are transient siblings: stage the exact Map file, notgit add -Aduring an edit. YAML nodes and comments survive edits, but long scalar formatting may change; the source's LF/CRLF convention is kept. Removing a member keeps roots and their indices stable; a duplicate address must be removed by index. These file edits do not stage or commit. The reader shows each pinned root aspinned(checkout HEAD matches),moved(HEAD differs), orunresolved(no matching local checkout), and names other Maps in the same folder. Without one, it derives the local tree: JSON includes a portablemapblock only for a clean, exactly pinned root with stable identity that passes strict protocol validation. Dirty, unborn, unidentified, or invalid trees remain inspectable underprojectionwithout leaking their checkout path into a Map.forkandupdatevalidate before touching your disk and never overwrite your work; conflicts are reported.--jsonreturns astatus, the revision, and typed failure details. A partial write or commit exits non-zero.conversation send --local --runtime=clauderuns Claude Code — the copy you installed and signed in to, unmodified. Usage bills to your own Claude plan (or to your API key with--claude-auth=api-key— without it, a strayANTHROPIC_API_KEYin your shell is kept away from the turn; provider routing you configured yourself, such as Bedrock or Vertex, stays in force); the CLI never sees your credentials and reads only the session transcripts Claude Code writes under~/.claude/projects/. A turn streams the same events as a pi or hosted turn, so every client renders it the same way.
Which local runtime
Both stream the same transcript; they differ in whose agent runs and how it is paid for.
| | --runtime=pi (default) | --runtime=claude |
|---|---|---|
| What runs | pi, bundled with the desktop or pi on your PATH | the Claude Code you installed and signed in to |
| Who pays | your API key for the provider you chose (pi-login) | your Claude plan, or your API key with --claude-auth=api-key |
| Models | any provider pi supports | Anthropic |
| Agent context | our extensions and skills, passed with --ext and --skill | whatever your Claude Code already carries — plugin, skills, _agent/, memory |
| Where sessions live | <context>/.pi/sessions/, inside the space | ~/.claude/projects/, outside the space |
| Reasoning in the transcript | shown | not shown — Claude Code redacts it when run headless |
| Needs a Claude account | no | yes |
| Is it ready? | pi-status | claude-status — asks the binary (claude --version, claude auth status); never reads ~/.claude |
| Models | pi-models — any provider pi supports | claude-models — Anthropic (1M context for Opus/Sonnet/Fable, 200k for Haiku) |
| Compaction | context skills (context_cleanup) | conversation compact --local and --autocompact (100k–1M tokens) |
Pick claude to continue a session you started in Claude Code; the same session id resumes it. Close it there first — one session should have one writer at a time. Pick pi for another provider, an API-key setup, or a machine without Claude Code.
Configuration
| Path | What |
|---|---|
| ~/.ideaspaces/credentials.json | API credentials |
| ~/.ideaspaces/spaces.json | Known spaces and remotes |
| ~/.ideaspaces/cursors/ | Private per-reader local Thread acknowledgements |
| ~/.pi/agent/auth.json | Local-agent model credentials |
IS_API_KEY overrides stored credentials. IS_API_URL points at another host. IDEASPACES_PI_EXTENSIONS lists extension paths for direct conversation send --local (not agent run child authority). CLAUDE_CONFIG_DIR relocates the Claude Code sessions --runtime=claude reads, as it does for Claude Code itself.
Contributing
Every server operation the CLI performs is listed in contract/api-calls.json, generated from src/auth/api.ts by npm run api:inventory; a test fails when the source changes without it. npm run check:api -- <openapi.json> holds that inventory against a server's OpenAPI document and fails on any operation the server does not serve or marks deprecated. The document is an input — set IDEASPACES_OPENAPI=<path> to run the same check inside npm test.
License
MIT
