Skip to content

Wavemap CLI

The public project CLI is wavemap. The local short alias is wm.

The CLI is meant to make project operations discoverable without turning every operational behavior into CLI package logic. It owns command registration, route tables, help text, route contract tests, and thin bin entrypoints. The packages and scripts underneath still own the work.

The Wavemap CLI has a deliberately small center. It is easiest to understand as a public routing layer that gives humans one discoverable command tree, then hands work to the package or script that already owns the actual behavior.

There are two different CLI surfaces involved:

  • wavemap / wm is the public project CLI. It is the command surface humans and compatibility scripts should reach for.
  • @wavemap/operations is an internal typed operations CLI. Shell wrappers call it when they need TypeScript to parse, validate, plan, summarize, or write structured files.

The public CLI does not import operational workflows and the operations package does not import the public route table. That separation keeps each layer small:

Conceptual diagram showing the Wavemap CLI layered flow from human or CI commands through root package scripts, the public CLI, shell wrappers, typed operations leaf commands, and provider tools or operator output.

Conceptual Wavemap CLI layered flow. Authored Mermaid source: fig-wavemap-cli-layered-flow.mmd.

The slightly counterintuitive part is that root scripts point into wavemap, while wavemap points directly to shell wrappers. That is intentional. Root scripts are compatibility names, not the source of implementation truth. If a public route called the matching root script again, it would loop back into itself.

Compatibility name:
pnpm deploy:dev:runtime
-> pnpm wavemap -- deploy dev runtime deploy
Public route:
wavemap deploy dev runtime deploy
-> bash bin/dev-deploy/deploy-runtime.sh

So the route table connects three pieces of information:

segments
The public command shape: deploy dev runtime deploy
scriptName
The existing root compatibility script: deploy:dev:runtime
implementationCommand
The real executable handoff: bash bin/dev-deploy/deploy-runtime.sh

The shell wrapper is where the work becomes operational. It can call the typed operations CLI multiple times, read the files those helpers produce, then decide which live command to run next. For example, runtime deploy looks like this:

pnpm deploy:dev:runtime -- --execute
-> pnpm wavemap -- deploy dev runtime deploy --execute
-> @wavemap/cli route table
-> bash bin/dev-deploy/deploy-runtime.sh --execute
-> pnpm -C infra/operations deployed-dev:runtime-deploy-plan -- ...
-> shell reads the generated plan files
-> shell runs live commands such as curl or aws ssm send-command

The mental model to carry through the rest of this page is:

  • @wavemap/cli answers: “Which public command was requested, and what owns it?”
  • Shell wrappers answer: “How do we safely run this workflow as a process?”
  • @wavemap/operations answers: “What does the typed operational data mean?”
  • Root package scripts answer: “Which old or convenient command names should still work?”

CLI docs are the canonical home for the public command surface: route names, compatibility scripts, wrapper entrypoints, argument forwarding behavior, expected flags, and mutation posture. Operations docs should link back to that command surface while explaining when commands are used, how pipeline stages sequence, which approval gates apply, and which recovery or runbook path follows.

Use Wavemap CLI Command Reference for command entries. Keep this page focused on the CLI boundary, flow, route-contract rules, and package ownership model.

Document internal @wavemap/operations leaf commands only when they clarify typed file contracts, implementation extension points, or shell-wrapper handoffs. They are not operator-facing commands unless a public wavemap route or root compatibility script intentionally exposes them.

Current ownership is intentionally narrow:

SurfaceOwnerResponsibility
Public command tree@wavemap/cli in packages/cliRegister wavemap / wm routes, help text, and route contracts.
Typed operation logic@wavemap/operations in infra/operationsParse, validate, plan, summarize, and project typed operational data.
Shell wrappersbin/dev-deploy, bin/docs-deploy, bin/dev-docker, bin/infra-topologyRun live tools, wait loops, mutation gates, provider CLIs, and operator process control.
Root package scriptsRoot package.jsonPreserve compatibility names and delegate through pnpm wavemap -- ....

The important dependency direction is:

@wavemap/cli -> route metadata and shell wrappers
shell wrappers -> @wavemap/operations helpers where typed planning is needed
@wavemap/operations -/-> @wavemap/cli

@wavemap/operations should not depend on the public CLI package. This keeps typed operations reusable from shell wrappers, CI jobs, and future provider adapters without importing the trunk route table.

FileWhy It Matters
packages/cli/src/bin/wavemap.tsThin executable entrypoint for both wavemap and wm.
packages/cli/src/wavemap-cli/index.tsCreates the Commander program and registers the route table.
packages/cli/src/wavemap-cli/helpers/routesPublic route declarations grouped by domain.
packages/cli/src/wavemap-cli/helpers/commands.tsBuilds nested Commander commands from route segments.
packages/cli/src/wavemap-cli/helpers/root-cli-runner.tsResolves the repo root and executes the route implementation command.
packages/cli/src/wavemap-cli/__tests__Contract tests for registration, routing, root scripts, wrappers, and package direction.
infra/operations/src/bin/operations.tsTyped operations package CLI entrypoint used by shell wrappers.
bin/*Shell wrappers that perform live execution and call typed helpers. Follow Shell Scripting Conventions when editing them.

The trunk CLI is intentionally table-driven. A route entry is the key hook:

{
segments: ["infra", "topology", "project-diagram"],
scriptName: "infra:topology:project-diagram",
implementationCommand: ["bash", "bin/infra-topology/project-semantic-mermaid-diagram.sh"],
description: "Projects a private semantic infra topology diagram candidate.",
}

segments define the public command, scriptName defines the compatibility script, and implementationCommand points directly at the shell wrapper that owns execution.

A normal compatibility command follows this path:

pnpm deploy:dev:runtime
pnpm wavemap -- deploy dev runtime deploy
@wavemap/cli route table
bin/dev-deploy/deploy-runtime.sh
@wavemap/operations typed planner/helper commands
live provider tools and operator gates

The route table stores a direct implementationCommand for each current public route. That avoids route-to-script-to-route recursion while root scripts stay stable for humans, CI, and older notes.

The TypeScript operation leaves usually start inside the shell wrapper, not inside the public wavemap route.

The public CLI launches a shell script. The shell script then calls package scripts in @wavemap/operations when it needs typed parsing, validation, planning, classification, or structured output.

For runtime deploy, the concrete chain is:

pnpm deploy:dev:runtime -- --execute
root package script: pnpm wavemap -- deploy dev runtime deploy --execute
@wavemap/cli route: deploy dev runtime deploy
bash bin/dev-deploy/deploy-runtime.sh --execute
pnpm -C infra/operations deployed-dev:runtime-deploy-plan -- ...
tsx src/bin/operations.ts -- deployed-dev runtime-deploy-plan ...
infra/operations/src/deployed-dev/runtime-deploy-plan-cli.ts
infra/operations/src/deployed-dev/runtime-deploy-plan.ts

The operation package scripts are the bridge between shell and TypeScript:

{
"deployed-dev:runtime-deploy-plan": "tsx src/bin/operations.ts -- deployed-dev runtime-deploy-plan",
"deployed-dev:runtime-ssm-helper": "tsx src/bin/operations.ts -- deployed-dev runtime-ssm-helper",
"infra-topology:project-graph": "tsx src/bin/operations.ts -- infra-topology project-graph"
}

Inside deploy-runtime.sh, the shell asks TypeScript to build the deploy plan and write small files that shell can read back:

Terminal window
pnpm --silent -C "$OPERATIONS_DIR" deployed-dev:runtime-deploy-plan -- \
--bundle-json "$BUNDLE_JSON_PATH" \
--manifest-json-out "$RUNTIME_DEPLOY_PLAN_JSON_PATH" \
--parameters-json-out "$PARAMETERS_JSON_PATH" \
--app-url-out "$APP_URL_PATH" \
--target-region-out "$TARGET_REGION_PATH"
APP_URL="$(<"$APP_URL_PATH")"
AWS_REGION_NAME="$(<"$TARGET_REGION_PATH")"

That pattern is the main handoff:

  • TypeScript decides, validates, formats, classifies, and writes deterministic files or summaries.
  • Shell reads those results, performs live commands, handles waits and retries, and owns mutation gates such as --execute.

The internal operations CLI is separate from the public project CLI. infra/operations/src/bin/operations.ts calls runOperationsCli, which registers command groups such as deployed-dev, docs-deploy, and infra-topology. Each group then registers reusable leaf commands, for example runtime-deploy-plan, runtime-ssm-helper, publish-plan, or project-diagram.

This is why a shell wrapper may call TypeScript multiple times. In runtime deploy, one TypeScript leaf creates the plan, another validates or classifies SSM helper data, and the shell still owns the actual curl, aws ssm send-command, and polling behavior.

The runner keeps one fallback path for future routes that do not yet define a direct implementation command:

route.implementationCommand === undefined
? ["pnpm", "run", route.scriptName, "--", ...args]
: [...route.implementationCommand, ...args]

Current trunk routes use direct implementation commands. The fallback is useful as a migration escape hatch, but route contract tests expect the present public surface to avoid route recursion.

Public routes are grouped by stable user-facing domains:

Route familyPurpose
deploy dev ...Deployed-dev preflight, planning, runtime deployment, rollback, reset, media checks, and environment validation.
deploy docs ...Docs deploy target resolution and static artifact publication.
smoke ...Read-only smoke checks for deployed-dev and docs surfaces.
infra topology ...Private capture and projection commands for topology processing.
dev docker ...Local Docker stack control.

The CLI allows unknown options and extra arguments at leaf routes so existing shell wrappers can keep their own option contracts. Route tests protect the public command names, the root compatibility script mapping, wrapper existence, and the one-way package boundary.

The CLI package is small because the important behavior is guarded by contracts rather than local business logic:

expect(rootPackageJson.scripts?.wavemap).toBe("pnpm -F @wavemap/cli wavemap")
expect(rootPackageJson.scripts?.wm).toBe("pnpm -F @wavemap/cli wm")
expect(operationsPackageJson.dependencies?.["@wavemap/cli"]).toBeUndefined()

Other route tests check that:

  • Every route points to an existing root compatibility script.
  • Every compatibility script delegates through the matching pnpm wavemap -- ... command.
  • Every bash implementation command points at an existing wrapper file.
  • Public route segments are unique.

Treat route names as compatibility surfaces:

  1. Add or update the route entry in packages/cli/src/wavemap-cli/helpers/routes.
  2. Keep the route pointed at the direct shell wrapper that owns live execution.
  3. Add or update the root compatibility script so it delegates through pnpm wavemap -- ....
  4. Update route contract tests when the public surface changes intentionally.
  5. Keep typed parsing, validation, planning, and summaries in @wavemap/operations.
  6. Update docs or working notes when the route changes operator behavior.

Prefer adding a new compatibility route over silently changing the meaning of an existing one. Transitional aliases are acceptable when they preserve an older command name while the public tree moves to clearer wording.

Use the Command Change Convention to decide which command reference, operations, runbook, route-contract, wrapper, and typed-helper surfaces need to move in the same change.

Useful focused checks are:

Terminal window
pnpm -F @wavemap/cli typecheck
pnpm -F @wavemap/cli test
pnpm wavemap -- --help
pnpm wm -- --help
pnpm verify:scripts

When a route calls into typed operations, also run the relevant @wavemap/operations tests or typecheck.

The package split leaves room for richer CLI behavior without pulling implementation semantics into @wavemap/cli:

  • Add generated route reference docs from the route table.
  • Add shared route metadata for examples, argument forwarding notes, and safety posture.
  • Add route groups for future provider adapters while keeping provider execution inside wrappers or adapter packages.
  • Add machine-readable command contracts for CI smoke checks.
  • Promote the package from private monorepo package to a distributable CLI only after the bin, build, and release story is settled.
  • Keep root package scripts as compatibility aliases even if the public CLI grows clearer nested routes over time.