Skip to content

Latest commit

 

History

History
57 lines (40 loc) · 5.16 KB

File metadata and controls

57 lines (40 loc) · 5.16 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project status

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.

Commands

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

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

Architecture

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 — typed fetch wrapper for the 10x-toolkit delivery API. Returns a discriminated ApiResult<T> ({ ok: true, data } | { ok: false, code, error }) — callers must branch on ok and surface failures via outputError. Network errors collapse to code: "network_error", status 0. The HTTP surface is described by src/generated/api-types.ts, which is generated from /openapi.json and 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). saveAuth writes atomically via tmp + renameSync with mode 0o600, and AuthData is versioned (AUTH_FILE_VERSION = 1) — bumping the schema means bumping the version and handling the older payload in readAuth.
  • output.ts — the I/O contract every command must follow. Three rules to internalize:
    1. Stdout is reserved for data; humans read stderr. output() writes JSON to stdout or a human message to stderr — never both.
    2. 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.
    3. Exit codes are semantic (ExitCodes): 0 SUCCESS, 1 ERROR, 2 USAGE, 3 AUTH_REQUIRED, 4 FORBIDDEN, 5 NOT_FOUND. Use outputError(ctx, code, message, exitCode, hint) rather than process.exit ad-hoc, so the JSON envelope { status: "error", error: { code, message, hint } } stays consistent.

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.

Testing

  • Test runner: bun test (not vitest, not Jest)
  • Imports: import { describe, it, expect, mock } from "bun:test"
  • Module mocks: use mock.module() from bun:test, not vi.mock
  • Prefer dependency injection over module mocking where possible
  • Run tests with bun test, not vitest run or npx jest

Conventions worth knowing

  • TypeScript is strict + noUncheckedIndexedAccess + noImplicitOverride. Index access on arrays/records returns T | 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.ts catches that and exits 2 (USAGE) with an ERROR 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" in api-client.ts.