This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
@mx-space/cli — the mxs binary. Command-line interface for managing a deployed mx-core instance (auth, content, configuration). User-facing surface and behavior are documented in README.md; internal architecture is documented in docs/architecture.md. Forward roadmap is in ROADMAP.md.
The implementation language is Effect-TS on top of Effect v4 (effect, effect/cli, effect/http). There is intentionally no Commander, no global mutable state, and no ad-hoc Promise handling — see "Architectural conventions" below.
All commands run from this package directory (packages/cli). Node 22+, pnpm via Corepack.
| Task | Command |
|---|---|
| Run from source (live) | pnpm dev -- <args> — tsx src/bin/mxs.ts |
| Run locally via PATH | mxs <args> — .envrc puts node_modules/.bin on PATH; pnpm install runs setup-local-bin.cjs and symlinks mxs to bin/mxs.cjs |
| Typecheck | pnpm typecheck — tsc -p tsconfig.json --noEmit |
| Test (all) | pnpm test — Vitest, single run |
| Test (watch) | pnpm test:watch |
| Test (one file) | pnpm test -- test/cli/post/list.test.ts |
| Test (pattern) | pnpm test -- -t "resolves an active profile" |
| Bundle for publish | pnpm package / pnpm build — tsdown → dist/ |
Lint/format are not wired at the package level — the workspace root runs them. Per the global rule, only check files you actually modified.
After implementing or changing user-facing CLI behavior (commands, flags, output modes, auth, configuration, file formats), update README.md in the same change set. This is enforced by agents.md. Internal refactors, test-only diffs, or bug fixes with no observable surface change do not need README updates.
Read docs/architecture.md for the full walkthrough. The minimum mental model:
- Services live in
src/services/*.tsasContext.Service+Layerpairs. The.Defaultlayer is the production wiring; tests substitute alternatives. Most services are wired insrc/layers/App.ts. Two exceptions —ApiandResolverdepend on per-invocation global flags (--api-url,--token,--api-key,--profile,--dry-run,--lang) and are constructed insidesrc/bin/mxs.tsafterparseGlobalFlags, then merged in viaLayer.provideMerge. - Commands live in
src/cli/<resource>/<verb>.tsas smallCommand.make+Effect.genblocks thatyield*the services they need. The aggregator filesrc/cli/<resource>/index.tswires verbs together withCommand.withSubcommandsand is registered on the root command insrc/bin/mxs.ts. Keep handlers thin — non-trivial logic belongs in services.Flag.Booleanis required ineffect/cliv4 unless piped throughFlag.withDefault(false)orFlag.optional— always add one. - Errors are
Data.TaggedErrorclasses insrc/domain/errors.ts. Exit-code mapping isexitCodeForTag(single source of truth). UseEffect.catchTag('Foo', ...)for narrow recovery; reservecatchfor the top-level shim inbin/mxs.ts. - External Promise APIs (fs beyond
effect/FileSystem, editor subprocess, package-manager spawn, lexical bridges) are wrapped withEffect.tryPromise(orEffect.tryfor sync throws). Do not reach forEffect.runPromise/unsafeRun*inside handlers — if a service boundary feels wrong, fix the service, not the call site. - Global flags are pre-parsed.
src/domain/runtime-flags.ts#parseGlobalFlagsstrips global flags from argv beforeeffect/clisees them, then propagates them viaContext.References (currentOutputOptions,currentDryRun).effect/clidoes not know about--api-url,--json, etc. — do not declare them on subcommands. - Help rendering is overridden at the root and group levels (
src/cli/help/). Baremxs,mxs --help,mxs <group>, andmxs <group> --helpare intercepted inbin/mxs.ts#detectHelpTargetand rendered by our code; verb-level help (mxs post create --help) is left toeffect/cli. When adding a new top-level group, register it in the help data builders too. - Output is centralized in
src/services/Renderer/(emitfor typed views,emitSuccess/emitError/emitInfo/emitWarn/emitInfoBlock). Modes:pretty-json,json(envelope{ ok, data }),readable,llm,envelope. The renderer reads theOutputOptionsContext.Reference — don't pass options into handlers. - Lexical content is processed through
@haklex/rich-headlessand@haklex/rich-litexmlviasrc/services/Lexical.ts. LiteXML<mxpost>/<mxnote>envelopes are parsed to Lexical JSON before sending to the server.
Vitest with @effect/vitest. Use it.effect to run an Effect directly and provide layers with Effect.provide. Two custom helpers cover the common substitutions:
test/helper/test-fs.ts— in-memoryFileSystem(used wherever a service reads/writes disk:Config,Profile,Migration).test/helper/test-http.ts— canned-responseHttpClient(used byApi,Resolver,Auth).test/helper/handler.ts—handler(cmd)(input)invokes a command's handler directly with already-parsed input (v4 no longer exposescmd.handler).
Integration tests under test/integration/ spawn the actual binary via child_process.spawn and assert on stdout/stderr/exit code — these cover the CLI surface end-to-end. See cli-error-envelope.test.ts for the canonical pattern.
.envrcaddsnode_modules/.binto PATH via direnv —mxstyped at the prompt then resolves to the local shim which loadssrc/bin/mxs.tsviatsx, not the published dist build. This requiresdirenv allowonce.- Running from source automatically opts into the
local-devdefault profile (seeLOCAL_DEV_ENVinsrc/services/Config.ts), so a bare invocation does not need an active profile. - The published JavaScript API surface is intentionally minimal —
src/index.tsre-exports onlyrunplus the error-tag table. Do not export internal services.
- New verb file
src/cli/<resource>/<verb>.ts—Command.make+ smallEffect.genhandler thatyield*s services. - Register in
src/cli/<resource>/index.tsaggregator'sCommand.withSubcommands. - If a new resource group: register in
src/bin/mxs.ts#rootCmdand insrc/cli/help/(group metadata + help data). - If a new capability: add a service under
src/services/, register its layer insrc/layers/App.ts(or inbin/mxs.tsif it depends on flags). - Add a unit test under
test/cli/<resource>/<verb>.test.tsusingit.effect+ canned layers, plus an integration test entry if there is observable output. - Update
README.mdper the documentation rule.