|
2 | 2 | /* =========================================================== |
3 | 3 | 🌌 HUMAN PATTERN LAB — CLI ENTRYPOINT |
4 | 4 | ----------------------------------------------------------- |
5 | | - Commands: |
6 | | - - version |
7 | | - - capabilities |
8 | | - - health |
9 | | - - notes list |
10 | | - - notes get <slug> |
11 | | - Contract: --json => JSON only on stdout |
| 5 | + Purpose: |
| 6 | + - Register top-level commands |
| 7 | + - Define global flags (--json) |
| 8 | + - Parse argv |
| 9 | + Contract: |
| 10 | + --json => JSON only on stdout (enforced in command handlers) |
12 | 11 | Notes: |
13 | | - - Avoid process.exit() inside command handlers (can trip libuv on Windows + tsx). |
| 12 | + Avoid process.exit() inside handlers (Windows + tsx stability). |
14 | 13 | =========================================================== */ |
15 | 14 |
|
16 | 15 | import { Command } from "commander"; |
17 | | -import { writeHuman, writeJson } from "../src/io"; |
18 | | -import { EXIT } from "../src/contract/exitCodes"; |
19 | | -import { runVersion } from "../src/commands/version"; |
20 | | -import { runCapabilities } from "../src/commands/capabilities"; |
21 | | -import { runHealth } from "../src/commands/health"; |
22 | | -import { runNotesList } from "../src/commands/notes/list"; |
23 | | -import { runNotesGet } from "../src/commands/notes/get"; |
24 | | -import { renderTable } from "../src/render/table"; |
25 | | -import { formatTags, safeLine, stripHtml } from "../src/render/text"; |
26 | 16 |
|
27 | | -type GlobalOpts = { json?: boolean }; |
| 17 | +import { versionCommand } from "../src/commands/version.js"; |
| 18 | +import { capabilitiesCommand } from "../src/commands/capabilities.js"; |
| 19 | +import { healthCommand } from "../src/commands/health.js"; |
| 20 | +import { notesCommand } from "../src/commands/notes/notes.js"; |
28 | 21 |
|
29 | | -const program = new Command(); |
30 | | - |
31 | | -program |
32 | | - .name("hpl") |
33 | | - .description("Human Pattern Lab CLI (alpha)") |
34 | | - .option("--json", "Emit contract JSON only on stdout") |
35 | | - .showHelpAfterError(); |
36 | | - |
37 | | -function setExit(code: number) { |
38 | | - // Let Node exit naturally (important for Windows + tsx stability). |
39 | | - process.exitCode = code; |
40 | | -} |
41 | | - |
42 | | -program |
43 | | - .command("version") |
44 | | - .description("Show CLI version (contract: show_version)") |
45 | | - .action(() => { |
46 | | - const opts = program.opts<GlobalOpts>(); |
47 | | - const envelope = runVersion("version"); |
48 | | - if (opts.json) writeJson(envelope); |
49 | | - else writeHuman(`${envelope.data.name} ${envelope.data.version}`); |
50 | | - setExit(EXIT.OK); |
51 | | - }); |
| 22 | +import { EXIT } from "../src/contract/exitCodes.js"; |
52 | 23 |
|
53 | | -program |
54 | | - .command("capabilities") |
55 | | - .description("Show CLI capabilities for agents (contract: show_capabilities)") |
56 | | - .action(() => { |
57 | | - const opts = program.opts<GlobalOpts>(); |
58 | | - const envelope = runCapabilities("capabilities"); |
59 | | - if (opts.json) writeJson(envelope); |
60 | | - else { |
61 | | - writeHuman(`intentTier: ${envelope.data.intentTier}`); |
62 | | - writeHuman(`schemaVersions: ${envelope.data.schemaVersions.join(", ")}`); |
63 | | - writeHuman(`supportedIntents:`); |
64 | | - for (const i of envelope.data.supportedIntents) writeHuman(` - ${i}`); |
65 | | - } |
66 | | - setExit(EXIT.OK); |
67 | | - }); |
| 24 | +const program = new Command(); |
68 | 25 |
|
69 | 26 | program |
70 | | - .command("health") |
71 | | - .description("Check API health (contract: check_health)") |
72 | | - .action(async () => { |
73 | | - const opts = program.opts<GlobalOpts>(); |
74 | | - const result = await runHealth("health"); |
75 | | - |
76 | | - if (opts.json) { |
77 | | - writeJson(result.envelope); |
78 | | - } else { |
79 | | - if (result.envelope.status === "ok") { |
80 | | - const d: any = (result.envelope as any).data; |
81 | | - const db = d.dbPath ? ` (db: ${d.dbPath})` : ""; |
82 | | - writeHuman(`ok${db}`); |
83 | | - } else { |
84 | | - const e: any = (result.envelope as any).error; |
85 | | - writeHuman(`error: ${e.code} — ${e.message}`); |
86 | | - } |
87 | | - } |
88 | | - setExit(result.exitCode); |
89 | | - }); |
90 | | - |
91 | | -const notes = program.command("notes").description("Lab Notes commands"); |
92 | | - |
93 | | -notes |
94 | | - .command("list") |
95 | | - .description("List lab notes (contract: render_lab_note)") |
96 | | - .option("--limit <n>", "Limit number of rows (client-side)", (v) => parseInt(v, 10)) |
97 | | - .action(async (cmdOpts: { limit?: number }) => { |
98 | | - const opts = program.opts<GlobalOpts>(); |
99 | | - const result = await runNotesList("notes list"); |
100 | | - |
101 | | - if (opts.json) { |
102 | | - writeJson(result.envelope); |
103 | | - setExit(result.exitCode); |
104 | | - return; |
105 | | - } |
106 | | - |
107 | | - if (result.envelope.status !== "ok") { |
108 | | - const e: any = (result.envelope as any).error; |
109 | | - writeHuman(`error: ${e.code} — ${e.message}`); |
110 | | - setExit(result.exitCode); |
111 | | - return; |
112 | | - } |
113 | | - |
114 | | - const data: any = (result.envelope as any).data; |
115 | | - const rows = (data.notes as any[]) ?? []; |
116 | | - const limit = Number.isFinite(cmdOpts.limit) && (cmdOpts.limit as any) > 0 ? (cmdOpts.limit as any) : rows.length; |
117 | | - const slice = rows.slice(0, limit); |
118 | | - |
119 | | - const table = renderTable(slice, [ |
120 | | - { header: "slug", width: 28, value: (n) => safeLine(String((n as any).slug ?? "")) }, |
121 | | - { header: "title", width: 34, value: (n) => safeLine(String((n as any).title ?? "")) }, |
122 | | - { header: "status", width: 10, value: (n) => safeLine(String((n as any).status ?? "-")) }, |
123 | | - { header: "dept", width: 8, value: (n) => safeLine(String((n as any).department_id ?? "-")) }, |
124 | | - { header: "tags", width: 22, value: (n) => formatTags((n as any).tags) }, |
125 | | - ]); |
126 | | - |
127 | | - writeHuman(table); |
128 | | - writeHuman(`\ncount: ${data.count}`); |
129 | | - setExit(result.exitCode); |
130 | | - }); |
131 | | - |
132 | | -notes |
133 | | - .command("get") |
134 | | - .description("Get a lab note by slug (contract: render_lab_note)") |
135 | | - .argument("<slug>", "Lab Note slug") |
136 | | - .option("--raw", "Print raw contentHtml (no HTML stripping)") |
137 | | - .action(async (slug: string, cmdOpts: { raw?: boolean }) => { |
138 | | - const opts = program.opts<GlobalOpts>(); |
139 | | - const result = await runNotesGet(slug, "notes get"); |
140 | | - |
141 | | - if (opts.json) { |
142 | | - writeJson(result.envelope); |
143 | | - setExit(result.exitCode); |
144 | | - return; |
145 | | - } |
146 | | - |
147 | | - if (result.envelope.status !== "ok") { |
148 | | - const e: any = (result.envelope as any).error; |
149 | | - writeHuman(`error: ${e.code} — ${e.message}`); |
150 | | - setExit(result.exitCode); |
151 | | - return; |
152 | | - } |
153 | | - |
154 | | - const n: any = (result.envelope as any).data; |
155 | | - |
156 | | - writeHuman(`# ${n.title}`); |
157 | | - writeHuman(`slug: ${n.slug}`); |
158 | | - if (n.status) writeHuman(`status: ${n.status}`); |
159 | | - if (n.type) writeHuman(`type: ${n.type}`); |
160 | | - if (n.department_id) writeHuman(`department_id: ${n.department_id}`); |
161 | | - if (n.published) writeHuman(`published: ${n.published}`); |
162 | | - if (Array.isArray(n.tags)) writeHuman(`tags: ${formatTags(n.tags)}`); |
163 | | - writeHuman(""); |
164 | | - |
165 | | - const body = cmdOpts.raw ? String(n.contentHtml ?? "") : stripHtml(String(n.contentHtml ?? "")); |
166 | | - writeHuman(body || "(no content)"); |
167 | | - setExit(result.exitCode); |
168 | | - }); |
169 | | - |
170 | | -// Let commander handle errors; set exit code without hard exit. |
171 | | -program.parseAsync(process.argv).catch(() => setExit(EXIT.UNKNOWN)); |
| 27 | + .name("hpl") |
| 28 | + .description("Human Pattern Lab CLI (alpha)") |
| 29 | + .option("--json", "Emit contract JSON only on stdout") |
| 30 | + .showHelpAfterError() |
| 31 | + .configureHelp({ helpWidth: 100 }); |
| 32 | + |
| 33 | +program.addCommand(versionCommand()); |
| 34 | +program.addCommand(capabilitiesCommand()); |
| 35 | +program.addCommand(healthCommand()); |
| 36 | +program.addCommand(notesCommand()); |
| 37 | + |
| 38 | +program.parseAsync(process.argv).catch(() => { |
| 39 | + process.exitCode = EXIT.UNKNOWN; |
| 40 | +}); |
0 commit comments