@ted-galago/wave-cli
v0.1.37
Published
Machine-first CLI used by a Node LangGraph agent to execute semantic Wave operations against the Rails API.
Readme
@wave/cli
Machine-first CLI used by a Node LangGraph agent to execute semantic Wave operations against the Rails API.
See also:
./.env.example./docs/ENVIRONMENT_CONTRACT_CHECKLIST.md
Principles
stdoutemits exactly one JSON envelope for machine commands;wave osmd lintdefaults to readable text and supports--jsonfor the stable envelope.stderris for debug/diagnostic logs only.- No prompts, colors, or interactive UX.
- JWT forwarding is centralized.
Install
npm install @wave/cliOr run locally:
npm run dev -- tasks list --project-id 123 --organization-id 42 --base-url https://api.example.com --token tokenSecure stdin auth/context examples:
printf '%s' "token-value" | wave --token-stdin --base-url https://api.example.com --organization-id 42 tasks list --page 1 --per 10
printf '%s' '{"token":"token-value","baseUrl":"https://api.example.com","organizationId":"42","agentName":"atlas"}' \
| wave --auth-json-stdin tasks list --page 1 --per 10Benchmark Wave vs Obsidian CLI
Run the side-by-side benchmark harness:
npm run benchmark:cliUseful options:
npm run benchmark:cli -- --runs 20 --warmup 3 --query "meeting notes"
npm run benchmark:cli -- --obsidian-bin /Applications/Obsidian.app/Contents/MacOS/obsidian-cli
npm run benchmark:cli -- --json-out /tmp/cli-benchmark.jsonNotes:
- Wave scenarios expect JSON envelopes for domain operations.
- Obsidian scenarios require the CLI to be registered and the Obsidian app available.
Runtime Contract
Required (from flags, stdin, or env):
WAVE_API_TOKENWAVE_API_BASE_URLWAVE_ORGANIZATION_ID
Optional env:
WAVE_AGENT_NAMEWAVE_AGENT_RUN_IDWAVE_REQUEST_IDWAVE_TIMEOUT_MSWAVE_DEBUGWAVE_OPENAPI_PATH(local contract file, e.g.openapi/wave-cli.yaml)WAVE_OPENAPI_URL(hosted contract file)WAVE_OPENAPI_VERSION(pinned spec version/hash)
Deterministic precedence:
- explicit flags (
--token/--jwt,--base-url,--organization-id, and tracing/runtime flags) - stdin context (
--token-stdinor--auth-json-stdin) - env vars (
WAVE_*) - failure with structured JSON error
Commands
Examples:
wave tasks list --project-id 123
wave tasks create --project-id 123 --title "Fix auth bug"
wave tasks update --id 99 --status in_progress
wave projects list
wave projects show --id 123
wave rocks update-status --id 55 --status on_track
wave meetings show --id 77
wave members show --id 44
wave meetings notes --id 77 --content "Decisions captured"
wave issues create --issue-group-id 321 --name "Auth issue" --issue-type short_term
wave foundation strategic-plans show
wave foundation strategic-objectives update --data-json '{"strategic_objective":{"summary":"Updated"}}'
wave foundation annual-objectives create --data-json '{"annual_objective":{"strategic_objective_id":"7","name":"Annual Goal"}}'
wave foundation quarterly-objectives create --data-json '{"quarterly_objective":{"strategic_objective_id":"7","annual_objective_id":"8","name":"Quarterly Goal"}}'
wave lists create --data-json '{"name":"Weekly List"}'
wave list-items create --data-json '{"list_id":"123","summary":"Follow up"}'
wave todos create --data-json '{"todo_group_id":"55","name":"Send update","status":"open"}'
wave knowledge create --data-json '{"content":{"name":"Runbook","content_type":"process","status":"draft","member_id":"67"}}'
wave content create-member --target-member-id 67 --name "1:1 Note" --body "Strong progress"
wave content create-manager --actor-member-id 67 --target-member-id 68 --name "Manager Note" --body "Coaching plan"
wave content create-team --actor-member-id 67 --team-id 9 --name "Team Note" --body "Team context"
wave content create-project --actor-member-id 67 --project-id 80 --name "Project Note" --body "Project context"
wave content create-customer --actor-member-id 67 --customer-id 30 --name "Customer Note" --body "Customer context"
wave content create-contact --actor-member-id 67 --contact-id 31 --name "Contact Note" --body "Contact context"
wave content list --contentable-type Member --contentable-id 67 --member-id 67 --page 1 --per 20
wave content show --id 1001
wave content update --id 1001 --name "Updated Note" --body "Updated body" --status published
wave content destroy --id 1001
wave markdown-tree root --tree-view
wave markdown-tree resolve --tool-key projects --node-key tasks --parent-id 123
wave markdown-tree children --tool-key directory --node-key members --record-id 67
wave markdown-tree subtree --tool-key knowledge --node-key knowledge --content-type processes --depth 2
wave osmd tree
wave osmd search "Wave Tools" --scope combined --limit 10
wave osmd lint --scope combined
wave osmd lint --scope combined --json
wave osmd ingest text "Customer interview notes" --source-type pasted_text --slug customer-interviews --parent-ref "projects/Wave Tools" --summary "Customer interview source summary" --run-lint
wave osmd ingest file ./source.md --source-type uploaded_markdown --slug source-document --parent-ref "projects/Wave Tools" --summary "Source document summary"
wave osmd ingest url "https://example.com/source" --source-type url --slug example-source --parent-ref "projects/Wave Tools" --summary "Example source summary"
wave osmd ingest transcript --file ./meeting-transcript.md --source-type meeting_transcript --slug team-meeting-transcript --parent-ref "projects/Wave Tools" --summary "Meeting transcript summary"
wave osmd read "projects/Wave Tools"
wave osmd children "projects/Wave Tools"
wave osmd status "projects/Wave Tools/.agent/notes.md" --parent-ref "projects/Wave Tools"
wave osmd agent init "projects/Wave Tools"
wave osmd agent status "projects/Wave Tools" notes.md
wave osmd agent read "projects/Wave Tools" notes.md
wave osmd agent create "projects/Wave Tools" notes.md --content "Working notes"
wave osmd agent update "projects/Wave Tools" customer-interviews.md --content "# Customer Interviews"
wave osmd agent children "projects/Wave Tools"
wave osmd wiki read index.md
wave osmd wiki read log.md
wave osmd wiki index-upsert ".agent/customer-interviews.md" --parent-ref "projects/Wave Tools" --summary "Customer interview source summary"
wave osmd wiki append-log --content "Observed durable event"
wave find "Ted"
wave open "Ted Martinez"
wave ls --path "directory/Members/Ted Martinez"
wave cat --path "directory/Members/Ted Martinez/Member Profile"
wave tree --path "knowledge/Processes" --depth 2
wave pulse update --id 12 --data-json '{"status":"on_track"}'
wave subtasks list --task-id 123 --page 1 --per 20
wave milestones list --rock-id 234 --page 1 --per 20
wave subitems list --list-item-id 345 --page 1 --per 20
wave subtodos list --todo-id 456 --page 1 --per 20
wave subissues list --issue-id 567 --page 1 --per 20
wave talking-points list --meeting-id 678 --page 1 --per 20
wave subtasks create --data-json '{"subtask":{"task_id":"123","member_id":"67","name":"Subtask name","description":"Subtask description"}}'
wave subissues destroy --id 999All commands require organization context via --organization-id or WAVE_ORGANIZATION_ID.
You can also provide organizationId via --auth-json-stdin.
Parent-Child Create Rules
These child resources enforce parent IDs at CLI validation time:
list-items.createrequireslist_idissues.createrequiresissue_group_idtodos.createrequirestodo_group_idrocks.createrequiresrock_collection_idkpis.createrequiressmart_kpi_view_idscorecards.createrequiresmeasurable_group_idsubtasks.createrequirestask_idmilestones.createrequiresrock_idsubitems.createrequireslist_item_idsubtodos.createrequirestodo_idsubissues.createrequiresissue_idtalking-points.createrequiresmeeting_id
If a required parent field is missing, CLI returns JSON error with exit code 2.
kpis.create contract notes:
smart_kpi.namemust be one of the backend enum values.- If payload uses free-text
smart_kpi.namewithmeasurable_group_id, CLI routes tocreate_measurable(scorecards.create) instead ofcreate_smart_kpi.
meetings.create contract notes:
meeting.typeis required and must beTeamMeetingorOneOnOneMeeting.- Required fields:
member_id,date,start_time. meeting.repeatsdefaults toneverif omitted.- For
TeamMeeting,teams_idsis required.
organizations.update contract notes:
organization.workspace_nameis normalized toorganization.organization_detail_attributes.workspace_name.
organizations.meta-profile.update contract notes:
- Payload must be shaped as
organization_meta_profile.profile.<section>.<field>. - Legacy shorthand
organization_meta_profile.titleis normalized toprofile.company_identity.one_sentence_summary.
organizations.key-metric-meta-profile.update contract notes:
- Payload must be shaped as
key_metric_meta_profile.profile.<section>.<field>. - Legacy shorthand
key_metric_meta_profile.annual_revenueis normalized toprofile.financial.revenue.
knowledge.create contract notes:
content.content_typemust be one ofcompany,policy,process.content.statusmust bedraftorpublished.content.member_idis required.content.typeis not required for create.
knowledge.list filter notes:
--assigned trueuses backend "assigned for current authenticated member" logic.--assigned truedefaults to incomplete items only.- CLI
knowledge listdoes not currently expose aninclude_completedtoggle. --member-idfilterscontents.member_id(owner/author), not assignment target.--focus-member-idfilterscontents.focus_member_id(focus scope), not assignment target.- For manager/admin users,
--assigned truemay include creator-visible items with assignments, not only direct assignee matches.
Examples:
# Assigned to current authenticated member (incomplete only by default)
wave knowledge list --assigned true --page 1 --per 50
# Owner filter (not assignment target filter)
wave knowledge list --member-id 67 --page 1 --per 50
# Focus scope filter (not assignment target filter)
wave knowledge list --focus-member-id 67 --page 1 --per 50news.create contract notes:
- Requires
headline.member_id,headline.status, andheadline.headline_type. headline.statusmust beactiveorcompleted.headline.headline_typemust beteamororg_wide.
questions.create/update contract notes:
- Use
question.name(legacysummaryis normalized toname). questions.createrequiresquestion.member_idandquestion.name.question.status, when provided, must beaskedoranswered.questions.listsupports onlyaskedandansweredfilters (pluspage/per).questions.listdoes not expose--query-json; unsupported filters are rejected at CLI parse time.
pulse.create contract notes:
- Requires
health_update.health_updatable_id,health_update.health_updatable_type,health_update.member_id, andhealth_update.status. - Legacy
updatable_id/updatable_typeare normalized tohealth_updatable_id/health_updatable_type. health_update.statusmust beon_track,at_risk, oroff_track.
surveys.create/update contract notes:
- Use
survey.nameandsurvey.recipient_type(titleis normalized toname). survey.recipient_typemust bemember,team, ororg_wide.- Survey results are written via
surveys.updateusing nestedsurvey.survey_results_attributes. - There is no standalone
createSurveyResult/updateSurveyResultCLI command. - Valid
survey.namevalues:employee_net_promoter_scorecontinuous_performance_reviewcore_value_alignmentengagementwellness_and_mental_healthworkspace_culturepeer_reviewmanager_reviewonboarding
feedbacks.create/update contract notes:
- Use
feedback.name(titleis normalized toname). feedbacks.createrequiresfeedback.name,feedback.quarter,feedback.year.feedback.quartermust beq1,q2,q3, orq4.
accountability.create/update contract notes:
- Use
responsibility.name(summaryis normalized toname). accountability.createrequiresresponsibility.nameandresponsibility.member_id.
Markdown Tree Traversal
Thin GraphQL adapter over backend markdown-tree services. CLI does not re-implement traversal/scope rules.
Commands:
wave markdown-tree root [--tree-view]wave markdown-tree resolve --tool-key <k> --node-key <k> [--parent-id <id>] [--record-id <id>] [--member-id <id>] [--team-id <id>] [--content-type <t>] [--tree-view]wave markdown-tree children --tool-key <k> --node-key <k> [--parent-id <id>] [--record-id <id>] [--member-id <id>] [--team-id <id>] [--content-type <t>] [--tree-view]wave markdown-tree subtree --tool-key <k> --node-key <k> --depth <n> [--parent-id <id>] [--record-id <id>] [--member-id <id>] [--team-id <id>] [--content-type <t>] [--tree-view]
GraphQL mapping:
root->markdownTreeRootresolve->markdownTreeNodechildren->markdownTreeChildrensubtree->markdownTreeSubtree
Backend error codes are surfaced explicitly from GraphQL errors[].extensions.code:
UNAUTHORIZEDNOT_FOUNDINVALID_SCOPEUNSUPPORTED_BRANCH
OSMD Agent Markdown
The osmd command group is the machine interface for canonical OSMD, .agent pages, and Agent Wiki index/log files. Canonical OSMD files are read-only. New synthesized pages are written under an OSMD parent node's .agent/ folder; Agent Wiki is limited to index.md, append-only log.md, and legacy read/status visibility for older content folders.
Commands:
wave osmd tree [--depth <n>] [--tree-view]wave osmd search "<query>" [--scope os-only|agent-overlay-only|agent-wiki-only|combined] [--limit <n>]wave osmd lint [--scope combined|agent-overlay-only|agent-wiki-only] [--json] [--fix=false]wave osmd memory-quality [--scope agent-overlay-only] [--json | --readable]wave osmd ingest text [text] [--content <text> | --file <path> | stdin] --slug <slug> --parent-ref <canonicalPath> --summary <summary> [--source-type <type>] [--title <title>] [--run-lint] [--dry-run] [--readable]wave osmd ingest file <path> --slug <slug> --parent-ref <canonicalPath> --summary <summary> [--source-type <type>] [--title <title>] [--run-lint] [--dry-run] [--readable]wave osmd ingest url <url> --slug <slug> --parent-ref <canonicalPath> --summary <summary> [--source-type <type>] [--title <title>] [--run-lint] [--dry-run] [--readable]wave osmd ingest transcript [text] [--content <text> | --file <path> | stdin] --slug <slug> --parent-ref <canonicalPath> --summary <summary> [--source-type <type>] [--title <title>] [--run-lint] [--dry-run] [--readable]wave osmd phantom run [--dry-run] [--readable]wave osmd read <path> [--parent-ref <canonicalPath>]wave osmd children [path] [--parent-ref <canonicalPath>] [--source canonical_osmd|agent_overlay|agent_wiki]wave osmd status <path> [--parent-ref <canonicalPath>]wave osmd agent status <parent-ref> <file>wave osmd agent init <parent-ref>wave osmd agent read <parent-ref> <file>wave osmd agent create <parent-ref> <file> (--content <markdown> | --file <path> | stdin)wave osmd agent update <parent-ref> <file> (--content <markdown> | --file <path> | stdin)wave osmd agent children <parent-ref>wave osmd wiki status <path>wave osmd wiki read <path>wave osmd wiki index-upsert <.agent-file> --summary <summary> --parent-ref <canonicalPath>wave osmd wiki append-log [log.md] (--content <markdown> | --file <path> | stdin)
GraphQL mapping:
osmd status/osmd agent status/osmd wiki status->agentMarkdownStatusosmd search->osMdSearchosmd lint->osMdWikiLintosmd memory-quality->osMdMemoryQualityosmd ingest text/osmd ingest file/osmd ingest url/osmd ingest transcript->registerRawSource, theningestRawSourceosmd ingest ... --dry-run->rawSourceIngestionPlanosmd phantom run --dry-run->runPhantomOsmdMaintenance(dryRun: true)osmd phantom run->runPhantomOsmdMaintenance(dryRun: false)osmd read/osmd agent read/osmd wiki read->agentMarkdownFileosmd children/osmd agent children->agentMarkdownChildrenosmd agent init->initAgentMarkdownosmd agent create->createAgentMarkdownFileosmd agent update->updateAgentMarkdownFileosmd wiki create/osmd wiki update-> localunsupported_contractosmd wiki index-upsert->upsertAgentWikiIndexEntryosmd wiki append-log->recordAgentMarkdownLog
Path and permission rules:
- Canonical OSMD reads use backend
pathvalues from tree/status/file responses. - The OSMD display root is the organization name returned by
wave osmd tree, formatted by the backend as${OrgName}.MD. Callers must use the returnedruntimeLabel/pathand must not hardcode a fixed root label. - UI display folders such as
Org.MD,Members.MD,Teams.MD,Tools.MD, andAgent Wiki.MDare presentation labels, not storage paths. Callers should resolve them fromwave osmd treemetadata and then pass backendpathvalues toosmd status/osmd read. Members.MDmaps to the canonical directory branch identified bytoolKey: "directory"andnodeKey: "members"; member records beneath it expose backend paths such asdirectory/<member name>.Teams.MDmaps totoolKey: "directory"andnodeKey: "teams"; team records beneath it expose backend paths such asdirectory/<team name>.Org.MDmaps to the canonical organization branch identified bytoolKey: "org"andnodeKey: "root"; the root backend path isorg.Tools.MDis a UI grouping over the remaining canonical tool roots returned byosmd treesuch asprojects,knowledge,issues, and other backendtoolKeyroots. Use each returned node'spath.Agent Wiki.MDmaps to the Agent Wiki root pathAgent Wiki; new writes are limited toindex-upsertand append-onlylog.md.- Canonical OSMD writes are blocked before mutation when status reports
source: "canonical_osmd"oraccess: "read_only". .agentwrite commands require a canonicalparent-ref; the CLI passes it as backend metadata and does not invent backend storage rules..agentfiles supportnotes.md,summary.md, and<lowercase-kebab-slug>.md.osmd agent init <parent-ref>is idempotent for default.agentfiles.- Agent Wiki index paths may be
index.mdorAgent Wiki/index.md; the CLI normalizes these toAgent Wiki/index.md. - Legacy Agent Wiki content folder paths may still be used for
read/statusvisibility when needed:concepts/<slug>.md,playbooks/<slug>.md,decisions/<slug>.md,org-summaries/<slug>.md,sources/<slug>.md, or the same forms prefixed withAgent Wiki/. - New writes to Agent Wiki content folders are rejected locally with
unsupported_contract. - Agent Wiki log paths may be
log.mdorAgent Wiki/log.md; the CLI normalizes these toAgent Wiki/log.md. - Agent Wiki
index-upsertupserts one backend-managedAgent Wiki/index.mdentry for a.agenttarget using backend metadata and permissions. index-upserttargets must be.agent/notes.md,.agent/summary.md, or.agent/<lowercase-kebab-slug>.mdwith--parent-ref <canonicalPath>.- Full parent paths such as
directory/Wave Tools Team/.agent/notes.mdare intentionally unsupported forindex-upsert; pass.agent/notes.mdand--parent-ref "directory/Wave Tools Team"instead. index-upsertis not a broad write command and cannot targetAgent Wiki/index.md,Agent Wiki/log.md, or Agent Wiki content folders.- Agent Wiki
append-logsupports onlylog.mdand calls status first before appending. - The CLI uses backend GraphQL metadata directly and does not derive S3 paths, SHA paths, or backend storage conventions.
- Atlas should inspect
path,source,access,owner,parentRef,exists,canCreate,canUpdate,canAppend,wikiRole,formatVersion,createdAt,updatedAt,ingestedAt,lastSeenAt,canonicalParentUpdatedAt,suggestedAction, anderrorCode. - Atlas owns report filename date choices and log-entry date content. The CLI does not generate, normalize, or decide report/log dates.
- OSMD status/read/children responses pass backend timestamp metadata through as strings when provided:
createdAt,updatedAt,ingestedAt,lastSeenAt, andcanonicalParentUpdatedAt. Missing timestamp fields are emitted asnull. - The CLI does not generate or normalize OSMD timestamp values. Atlas should treat timestamp strings as backend-owned metadata.
osmd lintremains backend pass-through for lint payloads and does not add OSMD file timestamp fields.- When Atlas needs organization timezone context for report naming, use
wave organizations showand inspectdata.organization.organizationDetail.timezonewhen the backend provides it. osmd searchscopes map to backendsourcevalues:os-only->canonical_osmd,agent-overlay-only->agent_overlay,agent-wiki-only->agent_wiki, andcombined-> backend merged search.osmd searchreturns backend candidates withpath,source,access,owner,wikiRole,formatVersion,parentRef,exists,agentChildrenAllowed,title,label,snippet,rank,matchReason,toolKey,nodeKey, andnodeKindwhere the backend provides them.osmd lintis read-only and delegates all lint rules to backendosMdWikiLint; the CLI does not implement, infer, or auto-fix lint rules.osmd lintscopes map to backend values:combined->combined,agent-overlay-only->agent_overlay, andagent-wiki-only->agent_wiki.osmd lint --fix=trueis unsupported and rejected before GraphQL. Lint never calls create, update, init, append-log, or index-upsert mutations.osmd lintdefaults to readable text showing status, counts, and issue details for human review.osmd lint --jsonreturns the stable global JSON envelope. The backend lint payload is passed through indatawithstatus,summary.totalIssues,summary.errors,summary.warnings,summary.suggestions, andissues.osmd memory-qualityis read-only and delegates Semantic Memory Quality Evaluation to backendosMdMemoryQuality.osmd memory-quality --jsonreturns the stable global JSON envelope and passes backend structured scores, weakest overlays, and issues through indata.osmd memory-quality --readablerenders status, evaluated/pass/warn/fail counts, weakest overlays, issue code, dimension, evidence snippet, suggested action, andcanAutoFix: false. It does not expose fix, delete, path-target, or canonical write flags.osmd ingestcommands are Source Ingestion Phase 1 pass-throughs. The CLI registers a raw source with backendregisterRawSource, then calls backendingestRawSourcewith the returnedrawSourceId.osmd ingestrequires--slug <lowercase-kebab-slug>,--parent-ref <canonicalPath>, and--summary <summary>so backend ingestion targets.agent/<slug>.mdunder the resolved parent node with an explicit summary.- Supported raw source types are
pasted_text,uploaded_markdown,uploaded_text,uploaded_json,uploaded_csv,meeting_transcript, andurl. registerRawSourcereceives flat variables:organization_id,source_type,title,content,file_name,url, andmetadata.ingestRawSourcereceives flat variables:organization_id,raw_source_id,slug,parent_ref,summary,overlay_updates, andrun_lint.rawSourceIngestionPlanreceives flat planning variables:organization_id,slug,parent_ref,source_type,title,file_name,url,overlay_updates, andrun_lint; it is used for--dry-runand does not callregisterRawSourceoringestRawSource.osmd ingestJSON output is the default machine interface and includesrawSourceId,sourceSummaryPath,createdFiles,updatedFiles,skippedUpdates, and optionallintResult.osmd ingest --run-lintis passed to backendingestRawSource; the CLI does not callosmd lint, does not auto-fix, and does not run lint before ingestion.osmd ingest --dry-runcalls backendrawSourceIngestionPlanand returns the backend plan through the same output schema.osmd ingest --update-overlaysis rejected locally withunsupported_contract; CLI Phase 1 does not pass a blind overlay boolean.osmd ingest filereads local file content and sends the filename as source metadata. The CLI does not mutate raw sources, upload raw-source storage objects, infer source storage paths, or implement summarization rules.osmd phantom runis organization-scoped, manual-only, and fixed to the backend OSMD/agent semantic memory scope. The CLI does not schedule cron jobs.osmd phantom run --dry-runcalls backendrunPhantomOsmdMaintenancewithdryRun: trueand does not write files directly.osmd phantom runpasses backend result fields through, including run id, run status, inventory count fields, optionalcountSemantics, lint summary, report path, updated files, skipped nodes, and notification metadata when provided.- Phantom runs use a 120 second default HTTP timeout because the backend maintenance pass can outlive the CLI's normal 10 second default. Operators can still override this with
--timeout-ms <milliseconds>orWAVE_TIMEOUT_MS. - Reads never create files.
- Create/update/append-log flows call status first and inspect
exists,canCreate,canUpdate,canAppend,suggestedAction, anderrorCode. createfails whenexistsis true.updatefails whenexistsis false.append-logfails unless status reportsexists: true,access: "append_only", andcanAppend: true.- When
Agent Wiki/log.mdis missing and status reportssuggestedAction: "init",append-logreturns a JSON error and does not initialize files implicitly. - Missing
Agent Wiki/index.mdis create-ready when status returnsexists: false,canCreate: true,canUpdate: false, andsuggestedAction: "create".
Atlas Agent Wiki log flow:
status_log: runwave osmd wiki status log.md.- Require
data.exists === true,data.access === "append_only", anddata.canAppend === true. append_log: runwave osmd wiki append-log --content "<markdown log entry>".
Atlas must not skip the status checks. append-log never initializes files implicitly; Agent Wiki index/log initialization is backend-owned.
Atlas maintenance examples:
Routine catalog maintenance should use backend-managed index-upsert, not manual Agent Wiki/index.md rewrites. Atlas should:
- Create or update a
.agentpage with an explicit parent ref. - Run
wave osmd wiki index-upsertwith the relative.agent/<file>.mdtarget and the backend-returned parent ref. - Append a parseable
Agent Wiki/log.mdentry.
Entity note maintenance:
wave osmd agent update "directory/Wave Tools Team" notes.md --content "## Working notes\n\n- New durable entity note."
wave osmd wiki index-upsert .agent/notes.md \
--parent-ref "directory/Wave Tools Team" \
--summary "Durable working notes for the Wave Tools Team"
wave osmd wiki append-log --content "```yaml
event: entity_note_updated
parentRef: directory/Wave Tools Team
targetPath: directory/Wave Tools Team/.agent/notes.md
summary: Added durable working notes for the Wave Tools Team.
```"Synthesized page maintenance:
wave osmd agent status "projects/Wave Tools" customer-interviews.md
wave osmd agent create "projects/Wave Tools" customer-interviews.md --content "# Customer Interviews\n\nSynthesized source summary."
wave osmd wiki index-upsert .agent/customer-interviews.md \
--parent-ref "projects/Wave Tools" \
--summary "Summary of customer interview source material"
wave osmd wiki append-log --content "```yaml
event: agent_page_updated
parentRef: projects/Wave Tools
targetPath: projects/Wave Tools/.agent/customer-interviews.md
summary: Created the customer interview synthesized page.
```"Direct Agent Wiki/index.md create/update and content folder writes are not part of the current CLI write contract.
Append a parseable log entry. Use a stable fenced block so Atlas or backend jobs can parse the chronology later:
wave osmd wiki append-log --content "```yaml
event: entity_note_updated
parentRef: directory/Wave Tools Team
agentFile: .agent/notes.md
wikiPath: Agent Wiki/index.md
summary: Added durable working notes for the Wave Tools Team.
```"append-log accepts multi-line markdown from --content, --file, or stdin. Use --file for larger structured entries:
wave osmd wiki append-log --file ./agent-wiki-log-entry.mdWhen not using --token-stdin or --auth-json-stdin, stdin may also provide the log body:
printf '%s\n' '```yaml' 'event: wiki_maintenance' 'summary: Recorded Agent Wiki maintenance event.' '```' | wave osmd wiki append-logOSMD command envelopes keep the global CLI envelope and put the file metadata directly in data:
{
"ok": true,
"command": "osmd.agent.read",
"status": 200,
"data": {
"path": "projects/Wave Tools/.agent/notes.md",
"source": "agent_overlay",
"access": "read_write",
"owner": "atlas",
"exists": true,
"canCreate": false,
"canUpdate": true,
"canAppend": false,
"wikiRole": "notes",
"formatVersion": "agent_markdown_v1",
"createdAt": "2026-05-07T18:10:11.123-07:00",
"updatedAt": "2026-05-07T18:20:21.456-07:00",
"ingestedAt": "2026-05-07T18:11:00.000-07:00",
"lastSeenAt": "2026-05-07T18:30:31.789-07:00",
"canonicalParentUpdatedAt": "2026-05-07T17:59:59.999-07:00",
"parentRef": "projects/Wave Tools",
"suggestedAction": "update",
"errorCode": null,
"content": "..."
},
"error": null,
"meta": { "requestId": "req_123" }
}Merged OSMD search keeps the same envelope and returns backend-ranked candidates:
{
"ok": true,
"command": "osmd.search",
"status": 200,
"data": {
"query": "Wave Tools",
"scope": "combined",
"count": 3,
"candidates": [
{
"path": "directory/Wave Tools Team/.agent/notes.md",
"source": "agent_overlay",
"access": "read_write",
"owner": "atlas",
"wikiRole": "notes",
"formatVersion": "agent_markdown_v1",
"parentRef": "directory/Wave Tools Team",
"exists": true,
"agentChildrenAllowed": false,
"title": "notes.md",
"label": "notes.md",
"snippet": "...",
"rank": 1,
"matchReason": "content",
"toolKey": "agent_overlay",
"nodeKey": "notes",
"nodeKind": "agent_overlay_file"
}
]
},
"error": null,
"meta": { "requestId": "req_125" }
}OSMD lint keeps the same envelope and returns the backend-owned lint result. Human readers should use data.status and data.summary for the high-level status/counts, then inspect data.issues for backend-provided issue details:
{
"ok": true,
"command": "osmd.lint",
"status": 200,
"data": {
"status": "warn",
"summary": {
"totalIssues": 2,
"errors": 0,
"warnings": 1,
"suggestions": 1
},
"issues": [
{
"code": "missing_index_entry",
"severity": "warning",
"path": "Agent Wiki/concepts/customer-health.md",
"source": "agent_wiki",
"parentRef": null,
"message": "Controlled Agent Wiki page is missing from Agent Wiki/index.md.",
"suggestedAction": "upsert_index_entry",
"canAutoFix": false
}
]
},
"error": null,
"meta": { "requestId": "req_126" }
}On write preflight errors, data contains the status metadata and error.suggestedAction mirrors backend guidance:
{
"ok": false,
"command": "osmd.agent.update",
"status": 404,
"data": {
"path": "projects/Wave Tools/.agent/customer-interviews.md",
"source": "agent_overlay",
"access": "read_write",
"owner": "atlas",
"exists": false,
"canCreate": true,
"canUpdate": false,
"canAppend": false,
"wikiRole": "index",
"formatVersion": "agent_markdown_v1",
"createdAt": null,
"updatedAt": null,
"ingestedAt": null,
"lastSeenAt": null,
"canonicalParentUpdatedAt": null,
"parentRef": "projects/Wave Tools",
"suggestedAction": "create",
"errorCode": null
},
"error": {
"code": "agent_file_not_found",
"message": "Agent markdown file does not exist: projects/Wave Tools/.agent/customer-interviews.md",
"suggestedAction": "create",
"details": {}
},
"meta": { "requestId": "req_124" }
}Navigation Shell
Primary agent-facing discovery/navigation commands:
wave find "<query>" [--under <path>] [--limit <n>]wave open "<name>" [--under <path>] [--tree-view]wave ls [query] --path <path>orwave ls [query] --tool-key ... --node-key ...wave cat --path <path>orwave cat --tool-key ... --node-key ...wave tree --depth <n> --path <path>orwave tree --depth <n> --tool-key ... --node-key ...
These wrap backend-owned markdown-tree primitives:
find->markdownTreeFindopen->markdownTreeFind+markdownTreeNodels->markdownTreeChildrencat->markdownTreeNodetree->markdownTreeSubtree
Discovery, ranking, canonical paths, and narrowing are backend-owned.
An empty find result (candidates: []) means no evidence for that exact query/scope, not confirmed global absence.
markdownTreeFind remains canonical-focused. Agent overlay and Agent Wiki files are discoverable through osmd tree, osmd children, and osmd status, not through find.
Use wave osmd search "<query>" --scope combined when Atlas needs merged canonical OSMD, .agent, and Agent Wiki search. Use narrower scopes for targeted memory lookup:
--scope os-onlysearches canonical OSMD only.--scope agent-overlay-onlysearches entity-specific.agentfiles.--scope agent-wiki-onlysearches backend Agent Wiki files, includingindex.md, legacy indexed pages when present, and append-onlylog.md.--scope combinedsearches the backend merged OSMD surface.
Ambiguity contract:
- no silent bad guesses
- returns
error.code = "ambiguous_match"with ranked candidates inerror.details.candidates
Content Command Contract
The content command uses exact GraphQL operation names:
CreateContentUpdateContentDestroyContentContentShowContentsIndex
Placement rules enforced by command shape:
content create-membercontentable_type: "Member"contentable_id: TARGET_MEMBER_IDmember_id: TARGET_MEMBER_IDfocus_member_id: nullfocus_team_id: null
content create-managercontentable_type: "Member"contentable_id: ACTOR_MEMBER_IDmember_id: ACTOR_MEMBER_IDfocus_member_id: TARGET_MEMBER_ID
content create-teamcontentable_type: "Team"contentable_id: TEAM_IDmember_id: ACTOR_MEMBER_IDfocus_team_id: TEAM_ID
content create-projectcontentable_type: "Project"contentable_id: PROJECT_IDmember_id: ACTOR_MEMBER_ID
content create-customercontentable_type: "Customer"contentable_id: CUSTOMER_IDmember_id: ACTOR_MEMBER_ID
content create-contactcontentable_type: "Contact"contentable_id: CONTACT_IDmember_id: ACTOR_MEMBER_ID
JSON Envelope
Success:
{
"ok": true,
"command": "tasks.list",
"status": 200,
"data": {},
"error": null,
"meta": {
"requestId": "req_123"
}
}Failure:
{
"ok": false,
"command": "tasks.create",
"status": 403,
"data": null,
"error": {
"code": "forbidden",
"message": "Forbidden",
"details": {}
},
"meta": {
"requestId": "req_124"
}
}Exit Codes
0: success1: generic uncaught error2: invalid args/config3: missing or invalid auth4: forbidden5: not found6: validation failure7: network or upstream error
