name: openkms
description: >-
Operates and queries an openKMS deployment over its HTTP API using a personal API key.
Read paths: global search across documents/articles/wiki/KBs; list & fetch markdown for
documents and articles; list & get markdown for articles; article LLM content review (latest rubric scores + suggestions); list & get wiki pages by path; wiki page substring/semantic search; list wiki space stored files (vault .md, assets, uploads) and linked
channel documents; KB semantic search and grounded
Q&A; list glossaries and terms, export/import term payloads; Knowledge Map tree and resource links; document lineage (relationships)
and policy lifecycle fields; article↔article relationship list/create/delete; list evaluations, items, and runs (including wiki content coverage when a wiki space is linked); run Cypher (or natural-language questions) against the ontology graph.
Write paths: create channels, upload documents,
create articles (incl. from URL), run article content review, upsert wiki pages, delete wiki stored files (incl. vault .md), link/unlink
wiki↔documents, create KB FAQs and evaluations (update evaluation metadata and items in place), trigger evaluation runs;
re-index one linked wiki space into a KB (or queue full KB index); glossary and term CRUD, bulk import, AI term suggest; knowledge map nodes and channel/wiki mappings; document
lifecycle PATCH and relationship create/delete; article relationship create/delete; ontology object/link CRUD and Neo4j index (bulk or per-type).
Use when the user wants an agent — or any external
tool — to read content from or push content to openKMS without the web UI. Agents must use
the bundled scripts/cli.py only (no ad-hoc curl or custom HTTP). Do not modify skill
source files; only config.yml may be created/updated for credentials when the user asks.
openKMS skill
Mandatory for agents: use scripts/cli.py only
Do not implement openKMS access with hand-written curl, ad-hoc httpx/requests/fetch, or throwaway scripts that call /api/… directly. Do not treat reference.md as something to copy into new code—it documents how each existing CLI subcommand maps to HTTP for operators and code review, not as a second implementation path.
Every read and write against this deployment must go through python scripts/cli.py …. That preserves Bearer auth, mutation gates (--yes / --dry-run), multipart uploads, path encoding, and error handling in one place. If a workflow is missing from the CLI, extend openkms-skill in the repository (or ask the user to)—do not bypass the bundled scripts.
Dependencies (requirements.txt)
- Installed into an openKMS project (Agents → Skills → install): the platform runs
pip install -r requirements.txtonce when the skill is copied into.openkms/skills/openkms/. Do not runpip installagain on each CLI call. Ifpython …/scripts/cli.pyfails withModuleNotFoundError, ask the user to reinstall or update the skill in project settings — do not patch deps by hand. - Standalone copy (OpenCode, Claude Code, manual
install.sh, or dev checkout): runpip install -r requirements.txtonce after copying the skill tree, then use the CLI as usual. Still not before every command. - Smoke check —
python …/scripts/cli.py pingverifies auth and that imports work; that is enough. No separate install step per session.
openKMS project agents (workspace under {project_id}/)
When this skill is installed at .openkms/skills/openkms/ inside a project workspace:
- Shell cwd is the project root. Run one command — no
cd, nopip install:python .openkms/skills/openkms/scripts/cli.py wiki-spaces list OPENKMS_API_KEY,OPENKMS_API_BASE_URL, andOPENKMS_SKILL_ROOTare injected by the agent runtime;config.ymlis optional.- Example:
python .openkms/skills/openkms/scripts/cli.py ping
Standalone / OpenCode / Claude installs: see Dependencies above and Before you act §3.
Evaluations. To change an evaluation’s name, description, or wiki link, use evaluations update. To add, edit, or remove question rows, use evaluations items add, evaluations items update, and evaluations items delete. Do not delete an evaluation and evaluations create a replacement just to “refresh” data—that drops saved runs and changes the evaluation id (bad for bookmarks, scripts, and comparisons). Reserve evaluations create for when the user explicitly wants a new evaluation.
Do not modify this skill’s shipped files. Never edit, delete, or add files under this skill directory except config.yml — and only to set api_base_url and api_key when the user explicitly asks you to store them (see Before you act §2). Do not touch SKILL.md, README.md, reference.md, scripts/, install.sh, requirements.txt, tests, or any other path here; do not patch or extend the CLI inside the install tree. Changes belong in the openKMS repository with a normal human review, not in the agent’s copy of the skill.
Before you act
Config — The runtime reads
config.ymlin this directory (next toSKILL.md). It must include:api_base_url— backend origin only, e.g.http://127.0.0.1:8102(no trailing slash).api_key— personal key from Settings → API keys in openKMS (user menu Settings;okms.{uuid}.{secret}).- Optional:
default_document_channel_id/default_article_channel_id— UUID strings; when set,documents list|uploadandarticles list|create|from-urlmay omit--channel-idand use these defaults (seeconfig.yml.example). - Optional:
default_pipeline_id— pipeline id string (e.g.pipeline_baidu_doc_parse); when set,document-channels createassigns it as the channel's default parse pipeline unless--pipeline-idis passed.
If either value is missing — In a project agent session,
OPENKMS_API_KEYandOPENKMS_API_BASE_URLare usually already set; you do not needconfig.yml. Otherwise ask the user for the backend URL and a new key from Settings (they see the full token once when creating it). Then write or updateconfig.ymlin this skill directory. Never echo the key back in full unless the user explicitly asks.Run the bundled CLI
Project agent (skill at .openkms/skills/openkms/, cwd = project root; deps already installed — see Dependencies):
python .openkms/skills/openkms/scripts/cli.py ping
Standalone install (skill directory is cwd; run pip install -r requirements.txt once after copying — not before every command):
pip install -q -r requirements.txt # first time only
python scripts/cli.py ping
- Auth header — The CLI sends
Authorization: Bearer <api_key>. Same permissions as the user who created the key.
Install (OpenCode and Claude Code)
From the repo:
./install.sh # auto: installs to whichever runtimes are present
./install.sh --target opencode # OpenCode only → ~/.config/opencode/skills/openkms/
./install.sh --target claude-code # Claude Code only → ~/.claude/skills/openkms/
./install.sh --target both # both runtimes
./install.sh --dest /custom/path # explicit destination
Auto mode picks targets based on which dirs exist (~/.config/opencode/, ~/.claude/). Re-run after pulling repo changes. An existing config.yml in any destination is preserved (not overwritten by the fresh tree).
How to use this skill
The skill is a thin Python CLI over openKMS's HTTP API. Every command JSON-prints the raw API response (json.dumps, indented). You parse it, then drive the next call. There is no client-side magic — pagination, retries, follow-ups are all yours.
Some practical guidance:
- Document channels need a pipeline for PDF parse. Uploading PDFs/images/Office to a channel without a default pipeline leaves documents stuck at
uploaded— Process is disabled in the UI andPOST /api/jobsfails. Before creating a channel for parseable files: runpipelines list(orpipelines list --table) to pick an active pipeline id, thendocument-channels create --pipeline-id …or setdefault_pipeline_idinconfig.yml. XLSX/XMind previews do not need a pipeline. To fix an existing channel:document-channels update --id DC_ID --pipeline-id … --yes. - Discover before you fetch. Most agent workflows start with
search(ordocuments list --search …/articles list --search …) to find candidates by name, then aget/markdownto pull content. Don't fetch a whole channel just to grep — server-side--searchis keyword-substring against names/titles. - Article content review. Channels can configure an LLM rubric (
review_model_id,review_prompt,review_criteria). After a review exists,articles reviews latest --id ART_IDreturnsresult.pass,result.overall_score, per-criterion scores/notes, andresult.suggestions— use these to revise markdown. 404 means no review yet; runarticles review run --id ART_ID --yes(needs channel review model). Review does not edit the article; you apply suggestions yourself. kb askvskb search.askproxies to the QA agent and returns a grounded answer (with citations).searchis now hybrid (BM25 + dense + RRF + cross-encoder rerank) and returns raw chunks + FAQ matches with confidence scores. Lexical tokens like product codes (e.g.WWY,MIL) are heavily weighted via BM25, sokb search --q "WWY 年化收益"returns WWY-specific chunks even when the embedding alone wouldn't. First call per KB cold-starts the BM25 index (paginates all chunks/FAQs); subsequent calls are fast. Useaskwhen the user wants an answer; usesearchwhen you need source material to reason over yourself.- KB wiki indexing. After
wiki put-page(or bulk wiki edits), re-chunk into a linked KB withkb wiki-spaces reindex --kb-id KB_ID --space-id SP_ID --yes— replaces prior wiki chunks for that space only (one page ≈ one chunk when ≤8000 chars). Usekb wiki-spaces list --kb-id KB_IDto see linked spaces. Full document + all wiki spaces:kb index --id KB_ID --yes. Both return aJobResponse(id,status, …); poll/api/jobs/{id}or the Job runs UI for completion. ontology askis a 3-call chain. It runstext-to-cypher→explore→answerfor you. Use when the question is graph-shaped and you don't want to chain by hand. Use the individual subcommands when you need to inspect or rewrite the Cypher.- Permission model is enforced server-side. API key carries the user's scope. List endpoints filter to readable channels; per-id GET returns 404 (not 403) when out of scope. Don't try to bypass — surface the error.
- Write commands and confirm gating. Every mutating CLI subcommand uses the same pattern as ontology objects/links:
--dry-runprints the planned[METHOD] path + body(upload uses a JSON summary withchannel_idandfilepath, not file bytes) and exits 0 without HTTP;-y/--yesskips the prompt; without either on a TTY you getProceed? [y/N]; on a non-TTY without--yesthe command exits 2 — agents must pass--yesdeliberately. - Ontology read vs write.
ontology cypher/text-to-cypher/answer/askgo through/api/ontology/*and are read-only (server regex-blocksCREATE/MERGE/DELETE/SET/REMOVE/DETACH/DROP/CALL/apoc/dbms). To enrich the graph, useontology objects ...andontology links ...(Postgres ontology layer) and thenontology objects sync-neo4j/ontology links sync-neo4j(all indexable types) orsync-neo4j-typeon one type id to MERGE into Neo4j. wiki filesis the whole space file store, not “attachments only”.wiki files list/wiki files deleteoperate on/api/wiki-spaces/…/files: vault imports (including mirrored.mdand images/PDFs), uploads, etc. Deleting a row removes that stored object (DB + storage). To change wiki page body (the page entity), usewiki put-page(or the app editor) — do not treat “files” as only sidecar attachments.- Knowledge map (
knowledge-map). Mirrors Console Knowledge Map under/api/knowledge-map/*: term tree, node CRUD, and mapping document/article channels or wiki spaces to nodes. Requiresknowledge_map:read/knowledge_map:writewhen the server enforces those permissions (same as the SPA). - Output is verbose. Each list response can include long arrays. If you're scanning many records, pipe through
jqto project just the fields you need rather than dumping everything into context.
Read / query tasks
| Goal | Command |
|---|---|
| Verify connectivity | python scripts/cli.py ping |
| Global search across content | python scripts/cli.py search --q "乳腺癌" --types documents,articles --limit 20 |
| List document channels as indented tree (human-readable) | python scripts/cli.py document-channels list --tree |
| List document processing pipelines | python scripts/cli.py pipelines list (or --table for id / name / active) |
| List article channels as indented tree (human-readable) | python scripts/cli.py article-channels list --tree |
| List documents (filter by channel/keyword) | python scripts/cli.py documents list --channel-id ID --search "心梗" --limit 50 |
| Get document metadata + body | python scripts/cli.py documents get --id DOC_ID |
| List document lineage (outgoing + incoming edges) | python scripts/cli.py documents relationships list --id DOC_ID |
| Save just the document markdown to a file | python scripts/cli.py documents markdown --id DOC_ID --out ./case.md |
| List articles (filter by channel/keyword) | python scripts/cli.py articles list --channel-id ID --search "豁免" |
| List article lineage (outgoing + incoming edges) | python scripts/cli.py articles relationships list --id ART_ID |
| Get article markdown | python scripts/cli.py articles markdown --id ART_ID |
| Latest LLM content review (scores + suggestions) | python scripts/cli.py articles reviews latest --id ART_ID |
| List recent content reviews | python scripts/cli.py articles reviews list --id ART_ID --limit 10 |
| List wiki pages in a space | python scripts/cli.py wiki list-pages --space-id SP_ID |
| Search wiki pages (substring or semantic when indexed) | python scripts/cli.py wiki pages semantic-matches --space-id SP_ID --q "onboarding" --top-k 10 |
| List wiki space stored files (vault .md, assets, uploads; not attachments-only) | python scripts/cli.py wiki files list --space-id SP_ID |
| List channel documents linked to a wiki space (UI “linked documents”) | python scripts/cli.py wiki-spaces documents list --space-id SP_ID |
| Get one wiki page by Obsidian path | python scripts/cli.py wiki get-page --space-id SP_ID --path notes/onboarding |
| List knowledge bases | python scripts/cli.py kb list |
| List wiki spaces linked to a KB | python scripts/cli.py kb wiki-spaces list --kb-id KB_ID |
| Semantic search over KB chunks + FAQs | python scripts/cli.py kb search --id KB_ID --q "既往症定义" --limit 10 |
| Ask the KB a question (grounded answer) | python scripts/cli.py kb ask --id KB_ID --question "..." |
| List FAQs on a KB | python scripts/cli.py kb-faq list --kb-id KB_ID |
| List glossaries | python scripts/cli.py glossaries list |
| Get one glossary | python scripts/cli.py glossaries get --id GL_ID |
| List terms in a glossary (optional search) | python scripts/cli.py glossaries terms list --glossary-id GL_ID --search "心梗" |
| Get one glossary term | python scripts/cli.py glossaries terms get --glossary-id GL_ID --term-id TERM_ID |
| Export glossary terms (JSON) | python scripts/cli.py glossaries export --glossary-id GL_ID |
| Run a Cypher query against the ontology graph | python scripts/cli.py ontology cypher --query "MATCH (n:Customer) RETURN n LIMIT 10" |
| NL question → Cypher (just the translation) | python scripts/cli.py ontology text-to-cypher --question "..." |
| NL question → Cypher → results → NL answer (3-call chain) | python scripts/cli.py ontology ask --question "..." |
| List object types | python scripts/cli.py ontology objects list [--master-data-only] [--count-from-neo4j] |
| Get one object type | python scripts/cli.py ontology objects get --id OT_ID |
| List instances of a type | python scripts/cli.py ontology objects instances list --type-id OT_ID --limit 50 |
| Get one instance | python scripts/cli.py ontology objects instances get --type-id OT_ID --id OI_ID |
| List link types | python scripts/cli.py ontology links list |
| Get one link type | python scripts/cli.py ontology links get --id LT_ID |
| List instances of a link type | python scripts/cli.py ontology links instances list --type-id LT_ID --limit 50 |
| Get one evaluation's metadata | python scripts/cli.py evaluations get --id DS_ID |
| List items in an evaluation | python scripts/cli.py evaluations items list --id DS_ID --limit 50 |
| List runs for an evaluation | python scripts/cli.py evaluation-runs list --evaluation-id DS_ID |
| Get one run with per-item results | python scripts/cli.py evaluation-runs get --evaluation-id DS_ID --run-id RUN_ID |
| Compare two runs | python scripts/cli.py evaluation-runs compare --evaluation-id DS_ID --run-a A --run-b B |
| Get Knowledge Map tree | python scripts/cli.py knowledge-map nodes tree |
| List all knowledge map resource links | python scripts/cli.py knowledge-map resource-links list |
Write tasks
Mutating commands below use -y/--yes and --dry-run like ontology writes (non-TTY without --yes exits 2).
| Goal | Command |
|---|---|
| List document channels (tree) | python scripts/cli.py document-channels list |
| Create document channel | python scripts/cli.py document-channels create --name "Inbox" --yes |
| Create document channel with parse pipeline | python scripts/cli.py pipelines list --table then document-channels create --name "Insurance" --pipeline-id pipeline_baidu_doc_parse --yes |
| Update document channel | python scripts/cli.py document-channels update --id DC_ID --name "Renamed" --yes |
| Set channel parse pipeline | python scripts/cli.py document-channels update --id DC_ID --pipeline-id pipeline_baidu_doc_parse --yes |
| Upload a file to a channel | python scripts/cli.py documents upload --channel-id ID --file /path/to/doc.pdf --yes (or omit --channel-id if default_document_channel_id is set in config.yml) |
| Patch document lifecycle (series, dates, status) | python scripts/cli.py documents lifecycle patch --id DOC_ID --lifecycle-status in_force --series-id SER_UUID --yes |
| Add lineage edge (this doc → other) | python scripts/cli.py documents relationships create --id DOC_ID --target-id OTHER_ID --relation-type supersedes --yes |
| Remove an outgoing lineage edge | python scripts/cli.py documents relationships delete --id DOC_ID --relationship-id REL_ID --yes |
| List article channels (tree) | python scripts/cli.py article-channels list |
| Create article channel | python scripts/cli.py article-channels create --name "Internal Wiki" --yes |
| Update article channel | python scripts/cli.py article-channels update --id AC_ID --parent-id PARENT_UUID --yes |
| Create article from a markdown file | python scripts/cli.py articles create --channel-id ID --name "Title" --markdown-file ./x.md --yes |
| Import article from a URL (HTML → text heuristic) | python scripts/cli.py articles from-url --channel-id ID --url https://example.com/a --yes |
| Run LLM content review (persist) | python scripts/cli.py articles review run --id ART_ID --yes |
| Add article lineage edge (this article → other) | python scripts/cli.py articles relationships create --id ART_ID --target-id OTHER_ID --relation-type supersedes --yes |
| Remove an outgoing article lineage edge | python scripts/cli.py articles relationships delete --id ART_ID --relationship-id REL_ID --yes |
| List wiki spaces | python scripts/cli.py wiki-spaces list |
| Create wiki space | python scripts/cli.py wiki-spaces create --name "Field Notes" --yes |
| Link a channel document to a wiki space | python scripts/cli.py wiki-spaces documents link --space-id SP_ID --document-id DOC_ID --yes |
| Unlink a document from a wiki space (does not delete the document) | python scripts/cli.py wiki-spaces documents unlink --space-id SP_ID --document-id DOC_ID --yes |
| Upsert wiki page from file | python scripts/cli.py wiki put-page --space-id ID --path my/page --title "T" --file ./note.md --yes |
| Re-index one linked wiki space into a KB | python scripts/cli.py kb wiki-spaces reindex --kb-id KB_ID --space-id SP_ID --yes |
| Queue full KB reindex (documents + all linked wiki spaces) | python scripts/cli.py kb index --id KB_ID --yes |
| Delete one wiki stored file by id (vault .md or any stored path; DB + storage) | python scripts/cli.py wiki files delete --space-id SP_ID --file-id FILE_ID --yes |
| Create FAQ on a KB | python scripts/cli.py kb-faq create --kb-id ID --question "Q" --answer "A" --yes |
| List evaluations | python scripts/cli.py evaluations list |
| Create evaluation | python scripts/cli.py evaluations create --name "…" --kb-id KB_ID --wiki-space-id SP_ID --yes |
| Update evaluation (name / description / KB or wiki link; same id, keeps runs) | python scripts/cli.py evaluations update --id EV_ID --name "…" --yes (optional --description, --knowledge-base-id ID, --wiki-space-id ID, or --clear-wiki-space) |
| Add one evaluation item | python scripts/cli.py evaluations items add --id EV_ID --query "…" --expected-answer "…" --yes (optional --topic, --sort-order) |
| Update one evaluation item | python scripts/cli.py evaluations items update --id EV_ID --item-id ITEM_ID --query "…" --yes (any of --query, --expected-answer, --topic, --sort-order) |
| Delete one evaluation item | python scripts/cli.py evaluations items delete --id EV_ID --item-id ITEM_ID --yes |
| Trigger an evaluation run | python scripts/cli.py evaluations run --id EV_ID --type qa_answer --yes (use --type wiki_content_coverage when the evaluation has a linked wiki space; expected_answer is the checklist text for the judge) |
| Create glossary | python scripts/cli.py glossaries create --name "Product terms" --yes |
| Update / delete glossary | python scripts/cli.py glossaries update --id GL_ID --description "…" --yes / glossaries delete --id GL_ID --yes |
| Create / update / delete term | python scripts/cli.py glossaries terms create --glossary-id GL_ID --primary-en "MI" --primary-cn "心梗" --yes (and terms update / terms delete) |
| Bulk-import terms from JSON file | python scripts/cli.py glossaries import --glossary-id GL_ID --terms-file ./terms.json --mode replace --yes |
| AI suggest for a term (uses default LLM) | python scripts/cli.py glossaries terms suggest --glossary-id GL_ID --primary-en "STEMI" --yes |
| Create knowledge map node | python scripts/cli.py knowledge-map nodes create --name "Claims" --yes |
| Patch / delete knowledge map node | python scripts/cli.py knowledge-map nodes patch --id NODE_ID --name "Renamed" --yes / knowledge-map nodes delete --id NODE_ID --yes |
| Map a channel or wiki space to a map node | python scripts/cli.py knowledge-map resource-links put --knowledge-map-node-id NODE --resource-type document_channel --resource-id CHAN_ID --yes |
| Unmap a resource from the knowledge map | python scripts/cli.py knowledge-map resource-links delete --resource-type wiki_space --resource-id WS_ID --yes |
Ontology objects + links
Same confirmation rules as other writes.
| Goal | Command |
|---|---|
| Create object type | python scripts/cli.py ontology objects create-type --name "Disease" --properties-json '[{"name":"icd","type":"string","required":true}]' --yes |
| Update object type | python scripts/cli.py ontology objects update-type --id OT --display-property name --yes |
| Delete object type | python scripts/cli.py ontology objects delete-type --id OT --yes |
| Create object instance | python scripts/cli.py ontology objects instances create --type-id OT --data-json '{"icd":"C50"}' --yes |
| Update object instance | python scripts/cli.py ontology objects instances update --type-id OT --id OI --data-json '{"icd":"C50.1"}' --yes |
| Delete object instance | python scripts/cli.py ontology objects instances delete --type-id OT --id OI --yes |
| MERGE all indexable object types into Neo4j | python scripts/cli.py ontology objects sync-neo4j --neo4j-data-source-id DS --yes |
| MERGE one object type into Neo4j | python scripts/cli.py ontology objects sync-neo4j-type --type-id OT_ID --neo4j-data-source-id DS --yes |
| Create link type | python scripts/cli.py ontology links create-type --name covers --source-type-id OT_PROD --target-type-id OT_DIS --cardinality many-to-many --yes |
| Update link type | python scripts/cli.py ontology links update-type --id LT --description "..." --yes |
| Delete link type | python scripts/cli.py ontology links delete-type --id LT --yes |
| Create link instance | python scripts/cli.py ontology links instances create --type-id LT --source-object-id OI_A --target-object-id OI_B --yes |
| Delete link instance | python scripts/cli.py ontology links instances delete --type-id LT --id LI --yes |
| MERGE all indexable link types into Neo4j | python scripts/cli.py ontology links sync-neo4j --neo4j-data-source-id DS --yes |
| MERGE one link type into Neo4j | python scripts/cli.py ontology links sync-neo4j-type --type-id LT_ID --neo4j-data-source-id DS --yes |
When a link type is many-to-many and dataset-backed, the server is the source of truth via the junction table — ontology links instances create/delete will return 4xx. Surface that error rather than trying to bypass.
Workflow recipes
A. Find a doc by phrase, fetch its markdown.
python scripts/cli.py search --q "乳腺癌" --types documents --limit 5
# pick the doc id you want from the response
python scripts/cli.py documents markdown --id <doc_id> --out ./case.md
B. Ask the KB a question (grounded with citations).
python scripts/cli.py kb list # find the KB id
python scripts/cli.py kb ask --id <kb_id> --question "既往症的判定标准是什么?"
C. NL question against the ontology graph.
python scripts/cli.py ontology ask --question "列出与'重疾豁免'触发条件相关的合规通函"
# returns {question, cypher, explanation, columns, rows, answer}
D. Tight-loop content discovery + read.
# list all "claims" articles, then pull markdown of every one matching a regex
python scripts/cli.py articles list --channel-id <ch_id> --limit 200 \
| jq -r '.items[] | select(.name | test("乳腺癌")) | .id' \
| while read id; do python scripts/cli.py articles markdown --id "$id" --out "./$id.md"; done
E. After wiki page edits, refresh KB search for one linked space.
python scripts/cli.py kb wiki-spaces list --kb-id <kb_id>
python scripts/cli.py kb wiki-spaces reindex --kb-id <kb_id> --space-id <space_id> --yes
# → JobResponse with "id"; worker runs kb-index --wiki-space-id …
F. Improve an article using the latest content review.
python scripts/cli.py articles reviews latest --id <art_id>
# → .result.suggestions[], .result.criteria[] (scores + notes), .result.pass, .result.summary
# If 404: channel needs review_model_id — run review first:
python scripts/cli.py articles review run --id <art_id> --yes
python scripts/cli.py articles markdown --id <art_id> # read body, apply suggestions, edit locally
Reference
- Per-command JSON shapes & curl equivalents: reference.md
- Canonical route list: project
docs/features/api-reference.md - Tests:
tests/—pytest -vagainst an httpx mock transport (seedev-requirements.txt)