This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Early scaffold for the @przeprogramowani/10x-cli. Most commands are deliberate stubs that exit via exitNotImplemented and reference the phase in which they land. The full roadmap lives at thoughts/shared/plans/2026-04-07-10x-cli-design.md (in the sibling 10x-toolkit repo, not in this one). When asked to implement something, check that plan first to understand which phase the work belongs to and what envelope/exit-code conventions apply.
Runtime is Bun (≥ Node 20 declared in package.json for the published binary, but local dev uses Bun directly).
bun install
bun run dev -- <args> # run CLI from source, e.g. `bun run dev -- --help`
bun run typecheck # tsc --noEmit
bun run lint # oxlint (config in .oxlintrc.json)
bun test # bun:test runner; tests live in tests/
bun test tests/smoke.test.ts # single file
bun run build # node-target ESM bundle → dist/index.mjs
bun run build:binary # standalone compiled binary → dist/10x (~59MB)
bun run generate-types # refetch /openapi.json → src/generated/api-types.tsgenerate-types hits the production delivery API by default. To regenerate against a local backend: API_BASE_URL=http://localhost:8787 bun run generate-types. The same env var is read at CLI runtime by resolveApiBase() to point the CLI at a non-production API. The allowlist is strict: only the exact production host or http://localhost / http://127.0.0.1 (any port) are accepted — any other URL throws and exits 2. If you need a staging host, add it explicitly to PROD_HOSTNAME / DEV_HOSTNAMES in src/lib/api-client.ts.
CI (.github/workflows/ci.yml) runs typecheck → lint → test → build → build:binary on every PR. Anything that breaks one of those steps will block merge.
The CLI is a thin CAC-based command dispatcher (src/index.ts) that wires command modules into a single cac("10x") instance and parses argv. Three concerns are factored into src/lib/:
api-client.ts— typedfetchwrapper for the 10x-toolkit delivery API. Returns a discriminatedApiResult<T>({ ok: true, data }|{ ok: false, code, error }) — callers must branch onokand surface failures viaoutputError. Network errors collapse tocode: "network_error", status0. The HTTP surface is described bysrc/generated/api-types.ts, which is generated from/openapi.jsonand committed to git; never hand-edit it.config.ts— XDG-compliant local credential store at$XDG_CONFIG_HOME/10x-cli/auth.json(Windows:%APPDATA%/10x-cli/auth.json).saveAuthwrites atomically viatmp+renameSyncwith mode0o600, andAuthDatais versioned (AUTH_FILE_VERSION = 1) — bumping the schema means bumping the version and handling the older payload inreadAuth.output.ts— the I/O contract every command must follow. Three rules to internalize:- Stdout is reserved for data; humans read stderr.
output()writes JSON to stdout or a human message to stderr — never both. - JSON mode is implied when stdout is not a TTY, even without
--json.resolveContext()handles this; commands should always go through it instead of checking flags directly. - Exit codes are semantic (
ExitCodes):0SUCCESS,1ERROR,2USAGE,3AUTH_REQUIRED,4FORBIDDEN,5NOT_FOUND. UseoutputError(ctx, code, message, exitCode, hint)rather thanprocess.exitad-hoc, so the JSON envelope{ status: "error", error: { code, message, hint } }stays consistent.
- Stdout is reserved for data; humans read stderr.
Each command in src/commands/ exports a register*Command(cli) function that attaches itself to the shared CAC instance. Adding a new command means: create src/commands/foo.ts exporting registerFooCommand, import + call it in src/index.ts. Action callbacks receive their positional args followed by an options object that already includes the global --json / --verbose flags — pass that object straight into resolveContext / outputError.
Stub commands intentionally call exitNotImplemented(name, phase, options) so machine consumers still get a parseable error envelope. When implementing a phase, replace that call rather than working around it.
- Test runner:
bun test(not vitest, not Jest) - Imports:
import { describe, it, expect, mock } from "bun:test" - Module mocks: use
mock.module()frombun:test, notvi.mock - Prefer dependency injection over module mocking where possible
- Run tests with
bun test, notvitest runornpx jest
- TypeScript is
strict+noUncheckedIndexedAccess+noImplicitOverride. Index access on arrays/records returnsT | undefined— handle it. - Generated code lives under
src/generated/and is excluded from oxlint via.oxlintrc.json. - The CAC parser throws on unknown options;
src/index.tscatches that and exits2(USAGE) with anERROR usage:prefix on stderr. Preserve this behavior — it's how scripts detect bad invocations. - The CLI's user-agent is hard-coded to
"10x-cli"inapi-client.ts.