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.
Big Picture
Section titled “Big Picture”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/wmis the public project CLI. It is the command surface humans and compatibility scripts should reach for.@wavemap/operationsis 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 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.shSo 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.shThe 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-commandThe mental model to carry through the rest of this page is:
@wavemap/clianswers: “Which public command was requested, and what owns it?”- Shell wrappers answer: “How do we safely run this workflow as a process?”
@wavemap/operationsanswers: “What does the typed operational data mean?”- Root package scripts answer: “Which old or convenient command names should still work?”
Documentation Boundary
Section titled “Documentation Boundary”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.
Package Boundary
Section titled “Package Boundary”Current ownership is intentionally narrow:
| Surface | Owner | Responsibility |
|---|---|---|
| Public command tree | @wavemap/cli in packages/cli | Register wavemap / wm routes, help text, and route contracts. |
| Typed operation logic | @wavemap/operations in infra/operations | Parse, validate, plan, summarize, and project typed operational data. |
| Shell wrappers | bin/dev-deploy, bin/docs-deploy, bin/dev-docker, bin/infra-topology | Run live tools, wait loops, mutation gates, provider CLIs, and operator process control. |
| Root package scripts | Root package.json | Preserve compatibility names and delegate through pnpm wavemap -- .... |
The important dependency direction is:
@wavemap/cli -> route metadata and shell wrappersshell 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.
Code Entry Points
Section titled “Code Entry Points”| File | Why It Matters |
|---|---|
packages/cli/src/bin/wavemap.ts | Thin executable entrypoint for both wavemap and wm. |
packages/cli/src/wavemap-cli/index.ts | Creates the Commander program and registers the route table. |
packages/cli/src/wavemap-cli/helpers/routes | Public route declarations grouped by domain. |
packages/cli/src/wavemap-cli/helpers/commands.ts | Builds nested Commander commands from route segments. |
packages/cli/src/wavemap-cli/helpers/root-cli-runner.ts | Resolves 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.ts | Typed 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.
Command Flow
Section titled “Command Flow”A normal compatibility command follows this path:
pnpm deploy:dev:runtimepnpm wavemap -- deploy dev runtime deploy@wavemap/cli route tablebin/dev-deploy/deploy-runtime.sh@wavemap/operations typed planner/helper commandslive provider tools and operator gatesThe 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.
TypeScript Leaf Commands
Section titled “TypeScript Leaf Commands”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 -- --executeroot package script: pnpm wavemap -- deploy dev runtime deploy --execute@wavemap/cli route: deploy dev runtime deploybash bin/dev-deploy/deploy-runtime.sh --executepnpm -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.tsinfra/operations/src/deployed-dev/runtime-deploy-plan.tsThe 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:
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.
Route Shape
Section titled “Route Shape”Public routes are grouped by stable user-facing domains:
| Route family | Purpose |
|---|---|
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.
Contract Hooks
Section titled “Contract Hooks”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.
Adding Or Changing A Route
Section titled “Adding Or Changing A Route”Treat route names as compatibility surfaces:
- Add or update the route entry in
packages/cli/src/wavemap-cli/helpers/routes. - Keep the route pointed at the direct shell wrapper that owns live execution.
- Add or update the root compatibility script so it delegates through
pnpm wavemap -- .... - Update route contract tests when the public surface changes intentionally.
- Keep typed parsing, validation, planning, and summaries in
@wavemap/operations. - 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.
Verification
Section titled “Verification”Useful focused checks are:
pnpm -F @wavemap/cli typecheckpnpm -F @wavemap/cli testpnpm wavemap -- --helppnpm wm -- --helppnpm verify:scriptsWhen a route calls into typed operations, also run the relevant @wavemap/operations tests or typecheck.
Future Scope
Section titled “Future Scope”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.