Work on the LenserFight CLI (lf).
Scope
- Source:
apps/cli/src/**
- Commands:
apps/cli/src/commands/*.ts
- Utilities:
apps/cli/src/utils/ (api, output, auth, auth-recovery, ansi, error-reporter, profiles, supabase-client, automation-objects, battle-stream-broadcaster, local-battle-engine, local-battle-paths, local-battle-storage)
- Libraries:
apps/cli/src/lib/ (exec-context, safety, telemetry, redact, onboarding, combine-seeds, command-inventory)
- TUI:
apps/cli/src/tui/ (dashboard.ts + tui/ink/ — first-party dashboard, default lf behavior; runtime-telemetry — shared with lf top)
- Adapters:
apps/cli/src/adapters/
CLI Structure
src/main.ts — entry point, uses defineCommand + runMain from citty; binary name is lenserfight / lf
- Each command file exports a
defineCommand(...) default and is lazy-imported in main.ts
src/utils/api.ts — callRpc + handleError; all Supabase RPC calls go through here
src/utils/output.ts — printTable, printJson, truncate; uses consola for logging
src/utils/auth.ts + src/utils/supabase-client.ts — auth and Supabase client
src/utils/ansi.ts — ANSI helper A and symbols sym
src/lib/exec-context.ts — setExecContext / getExecContext for --local / --debug flags
src/lib/safety/ — assertSafe for input validation
src/lib/telemetry.ts — telemetry adapter
Command pattern
import { defineCommand } from 'citty';
import consola from 'consola';
import { callRpc, handleError } from '../utils/api';
import { printTable, printJson } from '../utils/output';
const subCommand = defineCommand({
meta: { name: 'list', description: 'List items.' },
args: {
json: { type: 'boolean', default: false, description: 'Output as JSON' },
},
async run({ args }) {
try {
const data = await callRpc('fn_example', {});
if (args.json) printJson(data);
else printTable(['ID', 'Name'], data.map((r) => [r.id, r.name]));
} catch (err) {
handleError(err);
}
},
});
export default defineCommand({
meta: { name: 'example', description: 'Example group.' },
subCommands: { list: subCommand },
});
Registration in main.ts
const main = defineCommand({
subCommands: {
example: () => import('./commands/example').then((m) => m.default),
},
});
Global flags
--local → sets LF_LOCAL=1 (parsed early via parseGlobalFlagsEarly before citty)
--debug → sets LF_DEBUG=1
- Default (
lf with no subcommand) → opens the first-party TUI dashboard (tui/dashboard.ts)
Constraints
- Never use fetch/axios directly — use
callRpc / handleError from utils/api.ts
- Never add NestJS, Prisma, or backend dependencies
- Keep dependency footprint minimal (citty, consola, dotenv)
- Output via
printTable / printJson / consola.* — never raw console.log
- Build:
pnpm nx build cli | Test: pnpm nx test cli
References
- citty patterns and integration details: REFERENCE.md
1---2name: cli-developer3description: Review, modify, and improve the LenserFight CLI at apps/cli/. Covers citty commands, API client integration, output formatting, TUI, and configuration.4---56Work on the LenserFight CLI (`lf`).78## Scope910- Source: `apps/cli/src/**`11- Commands: `apps/cli/src/commands/*.ts`12- Utilities: `apps/cli/src/utils/` (api, output, auth, auth-recovery, ansi, error-reporter, profiles, supabase-client, automation-objects, battle-stream-broadcaster, local-battle-engine, local-battle-paths, local-battle-storage)13- Libraries: `apps/cli/src/lib/` (exec-context, safety, telemetry, redact, onboarding, combine-seeds, command-inventory)14- TUI: `apps/cli/src/tui/` (`dashboard.ts` + `tui/ink/` — first-party dashboard, default `lf` behavior; `runtime-telemetry` — shared with `lf top`)15- Adapters: `apps/cli/src/adapters/`1617## CLI Structure1819- `src/main.ts` — entry point, uses `defineCommand` + `runMain` from **citty**; binary name is `lenserfight` / `lf`20- Each command file exports a `defineCommand(...)` default and is lazy-imported in `main.ts`21- `src/utils/api.ts` — `callRpc` + `handleError`; all Supabase RPC calls go through here22- `src/utils/output.ts` — `printTable`, `printJson`, `truncate`; uses **consola** for logging23- `src/utils/auth.ts` + `src/utils/supabase-client.ts` — auth and Supabase client24- `src/utils/ansi.ts` — ANSI helper `A` and symbols `sym`25- `src/lib/exec-context.ts` — `setExecContext` / `getExecContext` for `--local` / `--debug` flags26- `src/lib/safety/` — `assertSafe` for input validation27- `src/lib/telemetry.ts` — telemetry adapter2829## Command pattern3031```typescript32import { defineCommand } from 'citty';33import consola from 'consola';34import { callRpc, handleError } from '../utils/api';35import { printTable, printJson } from '../utils/output';3637const subCommand = defineCommand({38 meta: { name: 'list', description: 'List items.' },39 args: {40 json: { type: 'boolean', default: false, description: 'Output as JSON' },41 },42 async run({ args }) {43 try {44 const data = await callRpc('fn_example', {});45 if (args.json) printJson(data);46 else printTable(['ID', 'Name'], data.map((r) => [r.id, r.name]));47 } catch (err) {48 handleError(err);49 }50 },51});5253export default defineCommand({54 meta: { name: 'example', description: 'Example group.' },55 subCommands: { list: subCommand },56});57```5859## Registration in main.ts6061```typescript62const main = defineCommand({63 subCommands: {64 example: () => import('./commands/example').then((m) => m.default),65 },66});67```6869## Global flags7071- `--local` → sets `LF_LOCAL=1` (parsed early via `parseGlobalFlagsEarly` before citty)72- `--debug` → sets `LF_DEBUG=1`73- Default (`lf` with no subcommand) → opens the first-party TUI dashboard (`tui/dashboard.ts`)7475## Constraints7677- Never use fetch/axios directly — use `callRpc` / `handleError` from `utils/api.ts`78- Never add NestJS, Prisma, or backend dependencies79- Keep dependency footprint minimal (citty, consola, dotenv)80- Output via `printTable` / `printJson` / `consola.*` — never raw `console.log`81- Build: `pnpm nx build cli` | Test: `pnpm nx test cli`8283## References8485- citty patterns and integration details: [REFERENCE.md](references/REFERENCE.md)