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

@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

  • stdout emits exactly one JSON envelope for machine commands; wave osmd lint defaults to readable text and supports --json for the stable envelope.
  • stderr is for debug/diagnostic logs only.
  • No prompts, colors, or interactive UX.
  • JWT forwarding is centralized.

Install

npm install @wave/cli

Or run locally:

npm run dev -- tasks list --project-id 123 --organization-id 42 --base-url https://api.example.com --token token

Secure 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 10

Benchmark Wave vs Obsidian CLI

Run the side-by-side benchmark harness:

npm run benchmark:cli

Useful 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.json

Notes:

  • 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_TOKEN
  • WAVE_API_BASE_URL
  • WAVE_ORGANIZATION_ID

Optional env:

  • WAVE_AGENT_NAME
  • WAVE_AGENT_RUN_ID
  • WAVE_REQUEST_ID
  • WAVE_TIMEOUT_MS
  • WAVE_DEBUG
  • WAVE_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-stdin or --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 999

All 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.create requires list_id
  • issues.create requires issue_group_id
  • todos.create requires todo_group_id
  • rocks.create requires rock_collection_id
  • kpis.create requires smart_kpi_view_id
  • scorecards.create requires measurable_group_id
  • subtasks.create requires task_id
  • milestones.create requires rock_id
  • subitems.create requires list_item_id
  • subtodos.create requires todo_id
  • subissues.create requires issue_id
  • talking-points.create requires meeting_id

If a required parent field is missing, CLI returns JSON error with exit code 2.

kpis.create contract notes:

  • smart_kpi.name must be one of the backend enum values.
  • If payload uses free-text smart_kpi.name with measurable_group_id, CLI routes to create_measurable (scorecards.create) instead of create_smart_kpi.

meetings.create contract notes:

  • meeting.type is required and must be TeamMeeting or OneOnOneMeeting.
  • Required fields: member_id, date, start_time.
  • meeting.repeats defaults to never if omitted.
  • For TeamMeeting, teams_ids is required.

organizations.update contract notes:

  • organization.workspace_name is normalized to organization.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.title is normalized to profile.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_revenue is normalized to profile.financial.revenue.

knowledge.create contract notes:

  • content.content_type must be one of company, policy, process.
  • content.status must be draft or published.
  • content.member_id is required.
  • content.type is not required for create.

knowledge.list filter notes:

  • --assigned true uses backend "assigned for current authenticated member" logic.
  • --assigned true defaults to incomplete items only.
  • CLI knowledge list does not currently expose an include_completed toggle.
  • --member-id filters contents.member_id (owner/author), not assignment target.
  • --focus-member-id filters contents.focus_member_id (focus scope), not assignment target.
  • For manager/admin users, --assigned true may 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 50

news.create contract notes:

  • Requires headline.member_id, headline.status, and headline.headline_type.
  • headline.status must be active or completed.
  • headline.headline_type must be team or org_wide.

questions.create/update contract notes:

  • Use question.name (legacy summary is normalized to name).
  • questions.create requires question.member_id and question.name.
  • question.status, when provided, must be asked or answered.
  • questions.list supports only asked and answered filters (plus page / per).
  • questions.list does 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, and health_update.status.
  • Legacy updatable_id/updatable_type are normalized to health_updatable_id/health_updatable_type.
  • health_update.status must be on_track, at_risk, or off_track.

surveys.create/update contract notes:

  • Use survey.name and survey.recipient_type (title is normalized to name).
  • survey.recipient_type must be member, team, or org_wide.
  • Survey results are written via surveys.update using nested survey.survey_results_attributes.
  • There is no standalone createSurveyResult / updateSurveyResult CLI command.
  • Valid survey.name values:
    • employee_net_promoter_score
    • continuous_performance_review
    • core_value_alignment
    • engagement
    • wellness_and_mental_health
    • workspace_culture
    • peer_review
    • manager_review
    • onboarding

feedbacks.create/update contract notes:

  • Use feedback.name (title is normalized to name).
  • feedbacks.create requires feedback.name, feedback.quarter, feedback.year.
  • feedback.quarter must be q1, q2, q3, or q4.

accountability.create/update contract notes:

  • Use responsibility.name (summary is normalized to name).
  • accountability.create requires responsibility.name and responsibility.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 -> markdownTreeRoot
  • resolve -> markdownTreeNode
  • children -> markdownTreeChildren
  • subtree -> markdownTreeSubtree

Backend error codes are surfaced explicitly from GraphQL errors[].extensions.code:

  • UNAUTHORIZED
  • NOT_FOUND
  • INVALID_SCOPE
  • UNSUPPORTED_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 -> agentMarkdownStatus
  • osmd search -> osMdSearch
  • osmd lint -> osMdWikiLint
  • osmd memory-quality -> osMdMemoryQuality
  • osmd ingest text / osmd ingest file / osmd ingest url / osmd ingest transcript -> registerRawSource, then ingestRawSource
  • osmd ingest ... --dry-run -> rawSourceIngestionPlan
  • osmd phantom run --dry-run -> runPhantomOsmdMaintenance(dryRun: true)
  • osmd phantom run -> runPhantomOsmdMaintenance(dryRun: false)
  • osmd read / osmd agent read / osmd wiki read -> agentMarkdownFile
  • osmd children / osmd agent children -> agentMarkdownChildren
  • osmd agent init -> initAgentMarkdown
  • osmd agent create -> createAgentMarkdownFile
  • osmd agent update -> updateAgentMarkdownFile
  • osmd wiki create / osmd wiki update -> local unsupported_contract
  • osmd wiki index-upsert -> upsertAgentWikiIndexEntry
  • osmd wiki append-log -> recordAgentMarkdownLog

Path and permission rules:

  • Canonical OSMD reads use backend path values 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 returned runtimeLabel/path and must not hardcode a fixed root label.
  • UI display folders such as Org.MD, Members.MD, Teams.MD, Tools.MD, and Agent Wiki.MD are presentation labels, not storage paths. Callers should resolve them from wave osmd tree metadata and then pass backend path values to osmd status/osmd read.
  • Members.MD maps to the canonical directory branch identified by toolKey: "directory" and nodeKey: "members"; member records beneath it expose backend paths such as directory/<member name>.
  • Teams.MD maps to toolKey: "directory" and nodeKey: "teams"; team records beneath it expose backend paths such as directory/<team name>.
  • Org.MD maps to the canonical organization branch identified by toolKey: "org" and nodeKey: "root"; the root backend path is org.
  • Tools.MD is a UI grouping over the remaining canonical tool roots returned by osmd tree such as projects, knowledge, issues, and other backend toolKey roots. Use each returned node's path.
  • Agent Wiki.MD maps to the Agent Wiki root path Agent Wiki; new writes are limited to index-upsert and append-only log.md.
  • Canonical OSMD writes are blocked before mutation when status reports source: "canonical_osmd" or access: "read_only".
  • .agent write commands require a canonical parent-ref; the CLI passes it as backend metadata and does not invent backend storage rules.
  • .agent files support notes.md, summary.md, and <lowercase-kebab-slug>.md.
  • osmd agent init <parent-ref> is idempotent for default .agent files.
  • Agent Wiki index paths may be index.md or Agent Wiki/index.md; the CLI normalizes these to Agent Wiki/index.md.
  • Legacy Agent Wiki content folder paths may still be used for read/status visibility when needed: concepts/<slug>.md, playbooks/<slug>.md, decisions/<slug>.md, org-summaries/<slug>.md, sources/<slug>.md, or the same forms prefixed with Agent Wiki/.
  • New writes to Agent Wiki content folders are rejected locally with unsupported_contract.
  • Agent Wiki log paths may be log.md or Agent Wiki/log.md; the CLI normalizes these to Agent Wiki/log.md.
  • Agent Wiki index-upsert upserts one backend-managed Agent Wiki/index.md entry for a .agent target using backend metadata and permissions.
  • index-upsert targets must be .agent/notes.md, .agent/summary.md, or .agent/<lowercase-kebab-slug>.md with --parent-ref <canonicalPath>.
  • Full parent paths such as directory/Wave Tools Team/.agent/notes.md are intentionally unsupported for index-upsert; pass .agent/notes.md and --parent-ref "directory/Wave Tools Team" instead.
  • index-upsert is not a broad write command and cannot target Agent Wiki/index.md, Agent Wiki/log.md, or Agent Wiki content folders.
  • Agent Wiki append-log supports only log.md and 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, and errorCode.
  • 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, and canonicalParentUpdatedAt. Missing timestamp fields are emitted as null.
  • The CLI does not generate or normalize OSMD timestamp values. Atlas should treat timestamp strings as backend-owned metadata.
  • osmd lint remains 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 show and inspect data.organization.organizationDetail.timezone when the backend provides it.
  • osmd search scopes map to backend source values: os-only -> canonical_osmd, agent-overlay-only -> agent_overlay, agent-wiki-only -> agent_wiki, and combined -> backend merged search.
  • osmd search returns backend candidates with path, source, access, owner, wikiRole, formatVersion, parentRef, exists, agentChildrenAllowed, title, label, snippet, rank, matchReason, toolKey, nodeKey, and nodeKind where the backend provides them.
  • osmd lint is read-only and delegates all lint rules to backend osMdWikiLint; the CLI does not implement, infer, or auto-fix lint rules.
  • osmd lint scopes map to backend values: combined -> combined, agent-overlay-only -> agent_overlay, and agent-wiki-only -> agent_wiki.
  • osmd lint --fix=true is unsupported and rejected before GraphQL. Lint never calls create, update, init, append-log, or index-upsert mutations.
  • osmd lint defaults to readable text showing status, counts, and issue details for human review.
  • osmd lint --json returns the stable global JSON envelope. The backend lint payload is passed through in data with status, summary.totalIssues, summary.errors, summary.warnings, summary.suggestions, and issues.
  • osmd memory-quality is read-only and delegates Semantic Memory Quality Evaluation to backend osMdMemoryQuality.
  • osmd memory-quality --json returns the stable global JSON envelope and passes backend structured scores, weakest overlays, and issues through in data.
  • osmd memory-quality --readable renders status, evaluated/pass/warn/fail counts, weakest overlays, issue code, dimension, evidence snippet, suggested action, and canAutoFix: false. It does not expose fix, delete, path-target, or canonical write flags.
  • osmd ingest commands are Source Ingestion Phase 1 pass-throughs. The CLI registers a raw source with backend registerRawSource, then calls backend ingestRawSource with the returned rawSourceId.
  • osmd ingest requires --slug <lowercase-kebab-slug>, --parent-ref <canonicalPath>, and --summary <summary> so backend ingestion targets .agent/<slug>.md under 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, and url.
  • registerRawSource receives flat variables: organization_id, source_type, title, content, file_name, url, and metadata.
  • ingestRawSource receives flat variables: organization_id, raw_source_id, slug, parent_ref, summary, overlay_updates, and run_lint.
  • rawSourceIngestionPlan receives flat planning variables: organization_id, slug, parent_ref, source_type, title, file_name, url, overlay_updates, and run_lint; it is used for --dry-run and does not call registerRawSource or ingestRawSource.
  • osmd ingest JSON output is the default machine interface and includes rawSourceId, sourceSummaryPath, createdFiles, updatedFiles, skippedUpdates, and optional lintResult.
  • osmd ingest --run-lint is passed to backend ingestRawSource; the CLI does not call osmd lint, does not auto-fix, and does not run lint before ingestion.
  • osmd ingest --dry-run calls backend rawSourceIngestionPlan and returns the backend plan through the same output schema.
  • osmd ingest --update-overlays is rejected locally with unsupported_contract; CLI Phase 1 does not pass a blind overlay boolean.
  • osmd ingest file reads 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 run is 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-run calls backend runPhantomOsmdMaintenance with dryRun: true and does not write files directly.
  • osmd phantom run passes backend result fields through, including run id, run status, inventory count fields, optional countSemantics, 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> or WAVE_TIMEOUT_MS.
  • Reads never create files.
  • Create/update/append-log flows call status first and inspect exists, canCreate, canUpdate, canAppend, suggestedAction, and errorCode.
  • create fails when exists is true.
  • update fails when exists is false.
  • append-log fails unless status reports exists: true, access: "append_only", and canAppend: true.
  • When Agent Wiki/log.md is missing and status reports suggestedAction: "init", append-log returns a JSON error and does not initialize files implicitly.
  • Missing Agent Wiki/index.md is create-ready when status returns exists: false, canCreate: true, canUpdate: false, and suggestedAction: "create".

Atlas Agent Wiki log flow:

  1. status_log: run wave osmd wiki status log.md.
  2. Require data.exists === true, data.access === "append_only", and data.canAppend === true.
  3. append_log: run wave 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:

  1. Create or update a .agent page with an explicit parent ref.
  2. Run wave osmd wiki index-upsert with the relative .agent/<file>.md target and the backend-returned parent ref.
  3. Append a parseable Agent Wiki/log.md entry.

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.md

When 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-log

OSMD 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> or wave ls [query] --tool-key ... --node-key ...
  • wave cat --path <path> or wave cat --tool-key ... --node-key ...
  • wave tree --depth <n> --path <path> or wave tree --depth <n> --tool-key ... --node-key ...

These wrap backend-owned markdown-tree primitives:

  • find -> markdownTreeFind
  • open -> markdownTreeFind + markdownTreeNode
  • ls -> markdownTreeChildren
  • cat -> markdownTreeNode
  • tree -> 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-only searches canonical OSMD only.
  • --scope agent-overlay-only searches entity-specific .agent files.
  • --scope agent-wiki-only searches backend Agent Wiki files, including index.md, legacy indexed pages when present, and append-only log.md.
  • --scope combined searches the backend merged OSMD surface.

Ambiguity contract:

  • no silent bad guesses
  • returns error.code = "ambiguous_match" with ranked candidates in error.details.candidates

Content Command Contract

The content command uses exact GraphQL operation names:

  • CreateContent
  • UpdateContent
  • DestroyContent
  • ContentShow
  • ContentsIndex

Placement rules enforced by command shape:

  • content create-member
    • contentable_type: "Member"
    • contentable_id: TARGET_MEMBER_ID
    • member_id: TARGET_MEMBER_ID
    • focus_member_id: null
    • focus_team_id: null
  • content create-manager
    • contentable_type: "Member"
    • contentable_id: ACTOR_MEMBER_ID
    • member_id: ACTOR_MEMBER_ID
    • focus_member_id: TARGET_MEMBER_ID
  • content create-team
    • contentable_type: "Team"
    • contentable_id: TEAM_ID
    • member_id: ACTOR_MEMBER_ID
    • focus_team_id: TEAM_ID
  • content create-project
    • contentable_type: "Project"
    • contentable_id: PROJECT_ID
    • member_id: ACTOR_MEMBER_ID
  • content create-customer
    • contentable_type: "Customer"
    • contentable_id: CUSTOMER_ID
    • member_id: ACTOR_MEMBER_ID
  • content create-contact
    • contentable_type: "Contact"
    • contentable_id: CONTACT_ID
    • member_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: success
  • 1: generic uncaught error
  • 2: invalid args/config
  • 3: missing or invalid auth
  • 4: forbidden
  • 5: not found
  • 6: validation failure
  • 7: network or upstream error