feat: scaffold CLI and minimal issue list (task 0001)

Tracer bullet for gitea-axi: runnable npm package on axi-sdk-js with
gitea-js as the sole HTTP layer, ESM on Node 20+.

- issue list with --state/--limit, default fields, count line from
  X-Total-Count, type=issues guard, explicit empty state, and next-step
  suggestions
- repo context detection from the git origin remote (SSH/scp/HTTPS),
  tea credential discovery with the three-way login-matching split, and
  -R/--repo and --login overrides (flag > env > auto)
- token retrieval via tea login helper get: tea's login list JSON
  carries no token (ADR 0001 amended)
- full AxiError classification table with path-based 404 split, TOON
  errors on stdout, exit codes 0/1/2
- test mode (GITEA_AXI_API_URL/TOKEN/REPO) suppressing subprocesses,
  fixture server, and vitest suites driving the CLI seam (50 tests)
This commit is contained in:
2026-07-11 07:11:07 -04:00
parent 21a075f8cd
commit 38026f963d
34 changed files with 3892 additions and 0 deletions

44
src/render.ts Normal file
View File

@@ -0,0 +1,44 @@
import { encode } from "@toon-format/toon";
// Same shape the SDK's own error formatter produces; its renderError helper
// exists in axi-sdk-js but is not exported from the package index.
export function renderErrorOutput(
message: string,
code: string,
suggestions: string[] = [],
): string {
const output: Record<string, unknown> = { error: message, code };
if (suggestions.length > 0) {
output.help = suggestions;
}
return encode(output);
}
export function formatCountLine(
shown: number,
total: number | undefined,
atLimit: boolean,
): string {
if (total === undefined) {
if (atLimit) {
return `count: ${shown} (showing first ${shown})`;
}
return `count: ${shown} of ${shown} total`;
}
return `count: ${shown} of ${total} total`;
}
export interface RenderListOptions {
noun: string;
rows: Record<string, unknown>[];
countLine: string;
help: string[];
}
export function renderList(options: RenderListOptions): string {
const body =
options.rows.length > 0
? encode({ [options.noun]: options.rows })
: `${options.noun}[0]: (none)`;
return [options.countLine, body, encode({ help: options.help })].join("\n");
}