@crouton-kit/capture 1.3.8 → 1.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/bin/capture +29841 -8539
  2. package/package.json +6 -3
  3. package/src/capture.ts +86 -173
  4. package/src/cdp/a11y.ts +113 -14
  5. package/src/cdp/args.ts +300 -205
  6. package/src/cdp/bridge/protocol.ts +93 -6
  7. package/src/cdp/bridge/server.ts +118 -53
  8. package/src/cdp/bridge/spawn.ts +53 -6
  9. package/src/cdp/client.ts +13 -3
  10. package/src/cdp/commands/bridge-serve.ts +20 -14
  11. package/src/cdp/commands/cdp.ts +259 -90
  12. package/src/cdp/commands/gate-guard.ts +43 -0
  13. package/src/cdp/commands/lib.ts +339 -213
  14. package/src/cdp/commands/measure/census.ts +76 -20
  15. package/src/cdp/commands/measure/check.ts +100 -26
  16. package/src/cdp/commands/measure/diff.ts +109 -21
  17. package/src/cdp/commands/measure/explain.ts +121 -19
  18. package/src/cdp/commands/measure/index.ts +31 -39
  19. package/src/cdp/commands/measure/map-ax.ts +52 -0
  20. package/src/cdp/commands/measure/map-focus.ts +41 -13
  21. package/src/cdp/commands/measure/map-layers.ts +49 -15
  22. package/src/cdp/commands/measure/map-scroll.ts +85 -15
  23. package/src/cdp/commands/measure/snap.ts +405 -25
  24. package/src/cdp/commands/measure/sweep.ts +217 -20
  25. package/src/cdp/commands/motion/index.ts +18 -17
  26. package/src/cdp/commands/motion/jank.ts +119 -9
  27. package/src/cdp/commands/motion/mask.ts +81 -12
  28. package/src/cdp/commands/motion/rec.ts +520 -19
  29. package/src/cdp/commands/motion/response.ts +65 -15
  30. package/src/cdp/commands/motion/timeline.ts +139 -14
  31. package/src/cdp/commands/page/click.ts +194 -0
  32. package/src/cdp/commands/page/elements.ts +154 -0
  33. package/src/cdp/commands/page/exec.ts +317 -0
  34. package/src/cdp/commands/page/index.ts +72 -0
  35. package/src/cdp/commands/page/navigate.ts +330 -0
  36. package/src/cdp/commands/page/scroll.ts +107 -0
  37. package/src/cdp/commands/page/shot.ts +208 -0
  38. package/src/cdp/commands/page/type.ts +104 -0
  39. package/src/cdp/commands/tab/index.ts +59 -0
  40. package/src/cdp/commands/tab/list.ts +167 -0
  41. package/src/cdp/commands/tab/network.ts +162 -0
  42. package/src/cdp/commands/tab/open.ts +90 -0
  43. package/src/cdp/commands/tab/reset.ts +160 -0
  44. package/src/cdp/connection.ts +368 -37
  45. package/src/cdp/detect.ts +42 -13
  46. package/src/cdp/dispatch.ts +74 -28
  47. package/src/cdp/har-recorder.ts +648 -115
  48. package/src/cdp/leaf-grammar.ts +99 -0
  49. package/src/cdp/measure/census.ts +205 -0
  50. package/src/cdp/measure/check.ts +318 -0
  51. package/src/cdp/measure/collectors/animation.ts +385 -0
  52. package/src/cdp/measure/collectors/ax.ts +278 -0
  53. package/src/cdp/measure/collectors/focus.ts +1041 -0
  54. package/src/cdp/measure/collectors/forms.ts +905 -0
  55. package/src/cdp/measure/collectors/geometry.ts +1038 -0
  56. package/src/cdp/measure/collectors/hittest.ts +835 -0
  57. package/src/cdp/measure/collectors/layers.ts +697 -0
  58. package/src/cdp/measure/collectors/media.ts +522 -0
  59. package/src/cdp/measure/collectors/pixels.ts +1584 -0
  60. package/src/cdp/measure/collectors/queries.ts +464 -0
  61. package/src/cdp/measure/collectors/scroll.ts +599 -0
  62. package/src/cdp/measure/collectors/states.ts +992 -0
  63. package/src/cdp/measure/collectors/style-provenance.ts +643 -0
  64. package/src/cdp/measure/collectors/styles.ts +355 -0
  65. package/src/cdp/measure/collectors/text.ts +713 -0
  66. package/src/cdp/measure/diff.ts +470 -0
  67. package/src/cdp/measure/explain.ts +498 -0
  68. package/src/cdp/measure/map-ax.ts +633 -0
  69. package/src/cdp/measure/map-focus.ts +188 -0
  70. package/src/cdp/measure/map-layers.ts +177 -0
  71. package/src/cdp/measure/map-scroll.ts +317 -0
  72. package/src/cdp/measure/redaction.ts +51 -0
  73. package/src/cdp/measure/settle.ts +1051 -0
  74. package/src/cdp/measure/snapshot.ts +390 -0
  75. package/src/cdp/measure/sweep.ts +330 -0
  76. package/src/cdp/measure/types.ts +220 -0
  77. package/src/cdp/measure/v2/collectors.ts +19 -0
  78. package/src/cdp/measure/v2/orchestrator.ts +46 -0
  79. package/src/cdp/measure/v2/snap-selection.ts +33 -0
  80. package/src/cdp/measure/v2/target-lock.ts +15 -0
  81. package/src/cdp/measure/v2/target.ts +16 -0
  82. package/src/cdp/motion/jank.ts +433 -0
  83. package/src/cdp/motion/mask.ts +421 -0
  84. package/src/cdp/motion/recorder.ts +764 -0
  85. package/src/cdp/motion/response.ts +356 -0
  86. package/src/cdp/motion/timeline.ts +215 -0
  87. package/src/cdp/record.ts +5 -212
  88. package/src/cdp/recorder-bridge.ts +1478 -105
  89. package/src/cdp/recorder-client.ts +326 -0
  90. package/src/cdp/scope-lock.ts +73 -0
  91. package/src/cdp/screenshot.ts +199 -66
  92. package/src/cdp/targets.ts +21 -1
  93. package/src/cdp/timing.ts +15 -10
  94. package/src/cdp/types.ts +22 -7
  95. package/src/cdp/viewport.ts +21 -0
  96. package/src/cdp.ts +12 -58
  97. package/src/command-help.ts +29 -0
  98. package/src/command-resolution.ts +22 -0
  99. package/src/command-walker.ts +35 -0
  100. package/src/contracts/a11y.ts +33 -0
  101. package/src/contracts/cursor.ts +35 -0
  102. package/src/contracts/index.ts +15 -0
  103. package/src/contracts/motion.ts +19 -0
  104. package/src/contracts/neutral.ts +56 -0
  105. package/src/contracts/primitives.ts +205 -0
  106. package/src/contracts/release.ts +17 -0
  107. package/src/contracts/results.ts +208 -0
  108. package/src/contracts/routes.ts +140 -0
  109. package/src/contracts/snapshot.ts +92 -0
  110. package/src/cursor/index.ts +13 -0
  111. package/src/cursor/service.ts +310 -0
  112. package/src/errors.ts +71 -0
  113. package/src/har-manager.ts +992 -41
  114. package/src/interact.ts +410 -98
  115. package/src/output/artifact-lifecycle.ts +371 -0
  116. package/src/output/artifact.ts +6 -0
  117. package/src/output/exact-raw.ts +114 -0
  118. package/src/output/render.ts +396 -2
  119. package/src/output/structured.ts +211 -0
  120. package/src/registry.ts +208 -0
  121. package/src/session/artifacts.ts +452 -200
  122. package/src/session/commands.ts +1162 -397
  123. package/src/session/coordinator.ts +251 -0
  124. package/src/session/har-filter.ts +57 -0
  125. package/src/session/log-tailer.ts +653 -0
  126. package/src/session-context.ts +279 -51
  127. package/src/vault/bundle.ts +32 -17
  128. package/src/cdp/commands/exec.ts +0 -105
  129. package/src/cdp/commands/har.ts +0 -128
  130. package/src/cdp/commands/tabs.ts +0 -183
  131. package/src/cdp/commands/traffic.ts +0 -121
  132. package/src/cdp/commands/ui.ts +0 -172
  133. package/src/cdp/execute.ts +0 -113
  134. package/src/cdp/har-output.ts +0 -47
  135. package/src/cdp/usage.ts +0 -79
  136. package/src/tab-session.ts +0 -91
@@ -4,15 +4,30 @@
4
4
  * Pure local reads, NO CDP connection. Mirrors `nl lib`
5
5
  * (northlight apps/core/src/cli/families/lib/lib-cli.provider.ts): same search
6
6
  * scoring and the discover→schema→run loop, sourced from on-demand doc-gen
7
- * (`../../vault/docs.ts`) + sha cache. Output contract (capture convention):
8
- * the machine result → stdout `JSON.stringify(x, null, 2)`; next-step hints →
9
- * stderr. Every object naming a lib/function carries `srcPath` = the absolute
10
- * path to that lib's `.ts` source — the agent's pointer to rebuild on the fly.
7
+ * (`../../vault/docs.ts`) + sha cache. Output contract: every result is a
8
+ * render.ts block — `<libs>` selection lists for `list`/`search`, a `<lib>`
9
+ * block for `show`/`read` — with `--json` mirroring the same result. Every
10
+ * block naming a lib carries `src` = the absolute path to that lib's `.ts`
11
+ * source, the agent's full-fidelity pointer; `lib read` is the schema-level
12
+ * full-fidelity leaf. Dev-checkout only: with `vault/` source missing
13
+ * (published package) every leaf throws a typed `dev_only` failure, rendered
14
+ * at the root boundary as `<error code="dev_only">`, exit 1. Every failure
15
+ * here throws typed (`CaptureError`) — the `src/capture.ts` root boundary is
16
+ * the one renderer and exit-code authority.
11
17
  */
12
18
 
13
19
  import * as path from "path";
20
+ import { captureError, invalidInput } from "../../errors.js";
14
21
  import { type ParsedArgs } from "../types.js";
15
- import { vaultLibsDir, listLibs } from "../../vault/paths.js";
22
+ import {
23
+ emitResult,
24
+ fact,
25
+ text,
26
+ lineList,
27
+ capped,
28
+ type FactLine,
29
+ } from "../../output/render.js";
30
+ import { vaultLibsDir, listLibs, DEV_ONLY_MSG } from "../../vault/paths.js";
16
31
  import {
17
32
  getDoc,
18
33
  listDocs,
@@ -20,15 +35,50 @@ import {
20
35
  type LibraryDoc,
21
36
  } from "../../vault/docs.js";
22
37
 
23
- const USAGE =
24
- "Usage: capture lib <list|search|show|read> [options]\n\n" +
25
- " list List available libs (name, fn count, description, srcPath)\n" +
26
- ' search "<query>" [--limit N] Fuzzy-search functions across all libs (ranked)\n' +
27
- " show <name> Lib + function summaries (no schemas)\n" +
28
- " read <name> [fn…] Full input/output JSON schemas (+ .ts source path)\n\n" +
29
- "Then run a lib in the active authenticated tab:\n" +
30
- " capture exec \"import {<fn>} from 'libs/<name>'; return await <fn>({…})\"\n\n" +
31
- "Dev-checkout-only feature (requires vault/ source + esbuild).";
38
+ /** Root-help representation of this leaf, assembled by `src/capture.ts`. */
39
+ export const COMMAND_BLOCK = `<command name="lib">
40
+ vault-lib introspection (dev checkout only) — discover the forked vault libs and their function schemas, then run them in the tab
41
+ use when scripting against a bundled service lib: find the function, read its schema, invoke it via page exec
42
+ list · search · show · read — \`capture lib -h\`
43
+ </command>`;
44
+
45
+ const LIB_USAGE = `capture lib — vault-lib introspection (dev checkout only): discover the
46
+ forked vault libs and their function schemas, then run them in the tab via
47
+ \`capture page exec\`.
48
+
49
+ Pure local reads — no CDP connection. Requires a capture checkout with
50
+ vault/ source + esbuild; the published package answers every leaf with a
51
+ structured dev_only error.
52
+
53
+ <subcommand name="list" args="" whenToUse="list every lib — name, function count, one-line summary"/>
54
+ <subcommand name="search" args="<query> [--limit <n>]" whenToUse="rank functions across all libs against a query (default limit 20, max 500; multi-word queries require every token to match)"/>
55
+ <subcommand name="show" args="<name>" whenToUse="one lib's function summaries — names + one-line descriptions, no schemas"/>
56
+ <subcommand name="read" args="<name> [fn…]" whenToUse="full input/output JSON Schemas for named functions — the full-fidelity step before exec"/>
57
+
58
+ capture lib <leaf> -h Per-leaf usage`;
59
+
60
+ const LEAF_USAGE: Record<string, string> = {
61
+ list: `capture lib list — list every vault lib: name, function count, one-line summary.
62
+
63
+ Input: none.
64
+ Output: <libs count=…> selection block, one row per lib (name — summary — function count).
65
+ Effects: local reads only (doc cache maintained under vault/.cache/docs); no CDP connection. Dev checkout required.`,
66
+ search: `capture lib search <query> [--limit <n>] — rank functions across all libs against a query.
67
+
68
+ Input: <query> (required; multi-word queries require every token to contribute a match); --limit <n> caps rows shown (default 20, max 500).
69
+ Output: <libs query=… hits=… shown=…> selection block, one row per hit (service.function — match kind — summary), best score first.
70
+ Effects: local reads only; no CDP connection. Dev checkout required.`,
71
+ show: `capture lib show <name> — one lib's function summaries, no schemas.
72
+
73
+ Input: <name> (required; exact lib name or unambiguous case-insensitive prefix).
74
+ Output: <lib name=… functions=… src=…> block — description, notes/login when present, one row per function (name — summary).
75
+ Effects: local reads only; no CDP connection. Dev checkout required.`,
76
+ read: `capture lib read <name> [fn…] — full input/output JSON Schemas for named functions.
77
+
78
+ Input: <name> (required; exact lib name or unambiguous case-insensitive prefix); [fn…] function names — with none given, falls back to the function summary list.
79
+ Output: <lib name=… src=…> block — per named function: description, notes, input and output JSON Schema (capped inline; the src path is the full-fidelity pointer).
80
+ Effects: local reads only; no CDP connection. Dev checkout required.`,
81
+ };
32
82
 
33
83
  // ─── scoring (ported verbatim from lib-cli.provider.ts:71-152) ───────────────
34
84
 
@@ -120,237 +170,313 @@ function scoreHit(
120
170
  const svcName = (doc: LibraryDoc): string =>
121
171
  doc.library.replace(/^@vallum\//, "");
122
172
 
173
+ /** Plain-string truncation for thrown messages (the `capped()` render marker
174
+ * only works inside `fact` templates, not in a `CaptureError` message). */
175
+ const cap = (value: string, maxLength: number): string =>
176
+ value.length > maxLength ? `${value.slice(0, maxLength - 1)}…` : value;
177
+
123
178
  const srcPathFor = (name: string): string =>
124
179
  path.join(vaultLibsDir(), name, "index.ts");
125
180
 
126
181
  /** Resolve a user-supplied name against a candidate set: exact, then ci-prefix. */
127
- function resolveName(
128
- query: string,
129
- names: string[],
130
- ): { name: string } | { error: string } {
182
+ type NameResolution =
183
+ | { name: string }
184
+ | { ambiguous: string[] }
185
+ | { unknown: true };
186
+
187
+ function resolveName(query: string, names: string[]): NameResolution {
131
188
  if (names.includes(query)) return { name: query };
132
189
  const lower = query.toLowerCase();
133
190
  const prefix = names.filter((n) => n.toLowerCase().startsWith(lower));
134
191
  if (prefix.length === 1) return { name: prefix[0] };
135
- if (prefix.length > 1) {
136
- return { error: `Ambiguous lib "${query}" — matches: ${prefix.join(", ")}` };
137
- }
138
- return { error: `Unknown lib "${query}". Available: ${names.join(", ")}` };
192
+ if (prefix.length > 1) return { ambiguous: prefix };
193
+ return { unknown: true };
139
194
  }
140
195
 
141
- // ─── command ─────────────────────────────────────────────────────────────────
142
-
143
- export async function cmdLib(
144
- parsed: ParsedArgs,
145
- _args: string[],
146
- ): Promise<void> {
147
- if (parsed.help) {
148
- console.log(USAGE);
149
- process.exit(0);
196
+ /** Resolve a lib name or throw typed (unknown/ambiguous). */
197
+ function resolveNameOrFail(command: string, query: string): string {
198
+ const names = listLibs();
199
+ const r = resolveName(query, names);
200
+ if ("ambiguous" in r) {
201
+ throw invalidInput(
202
+ `${command} received: lib name \`${query}\` — matches ${r.ambiguous.join(", ")}; expected exactly one lib. Re-run with the full lib name.`,
203
+ "ambiguous_lib",
204
+ );
150
205
  }
206
+ if ("unknown" in r) {
207
+ throw invalidInput(
208
+ `${command} received: unknown lib \`${query}\`; available: ${cap(names.join(", "), 1600)}. Run \`capture lib list\` for every lib with its summary.`,
209
+ "unknown_lib",
210
+ );
211
+ }
212
+ return r.name;
213
+ }
151
214
 
152
- const sub = parsed.positional[0];
153
-
154
- // ── lib list ──────────────────────────────────────────────────────────────
155
- if (sub === "list") {
156
- const docs = await listDocs();
157
- const rows = docs.map((d) => {
158
- const name = svcName(d);
159
- return {
160
- name,
161
- functionCount: d.functions.length,
162
- description: d.description,
163
- srcPath: srcPathFor(name),
164
- };
165
- });
166
- console.log(JSON.stringify(rows, null, 2));
167
- console.error(
168
- `\n${rows.length} libs. Next: capture lib show <name> · capture lib read <name> <fn>`,
215
+ /** `getDoc` or throw typed. `DEV_ONLY_MSG` rethrows to the shared dev-only
216
+ * handler in `cmdLib`; any other build failure is a typed doc_unavailable
217
+ * failure. */
218
+ async function getDocOrFail(command: string, name: string): Promise<LibraryDoc> {
219
+ try {
220
+ return await getDoc(name);
221
+ } catch (e) {
222
+ if (e instanceof Error && e.message === DEV_ONLY_MSG) throw e;
223
+ throw captureError(
224
+ "world",
225
+ "doc_unavailable",
226
+ `${command}: doc generation for lib \`${name}\` failed: ${(e as Error).message}`,
227
+ e,
169
228
  );
170
- return;
171
229
  }
230
+ }
172
231
 
173
- // ── lib search ──────────────────────────────────────────────────────────────
174
- if (sub === "search") {
175
- const query = parsed.positional[1];
176
- if (!query) {
177
- console.error('Usage: capture lib search "<query>" [--limit N]');
178
- process.exit(1);
179
- }
180
- const limit = Math.min(
181
- typeof parsed.limit === "number" && parsed.limit > 0 ? parsed.limit : 20,
182
- 500,
232
+ // ─── subcommands ─────────────────────────────────────────────────────────────
233
+
234
+ async function libList(parsed: ParsedArgs): Promise<void> {
235
+ const docs = await listDocs();
236
+ const rows = docs.map((d) => {
237
+ const name = svcName(d);
238
+ return fact`${name} — ${capped(d.description ?? "", 160)} (${d.functions.length} functions)`;
239
+ });
240
+ emitResult(
241
+ {
242
+ tag: "libs",
243
+ attrs: { count: docs.length },
244
+ summary: fact`${docs.length} vault libs.`,
245
+ sections: rows.length > 0 ? [lineList(rows)] : undefined,
246
+ followUp: text`\`capture lib show <name>\` lists one lib's functions; \`capture lib read <name> <fn…>\` loads full schemas.`,
247
+ },
248
+ { json: parsed.json },
249
+ );
250
+ }
251
+
252
+ async function libSearch(parsed: ParsedArgs): Promise<void> {
253
+ const query = parsed.positional[1];
254
+ if (!query) {
255
+ throw invalidInput(
256
+ "lib search received: no query; expected: capture lib search <query> [--limit <n>].",
183
257
  );
184
- const docs = await listDocs();
185
- const hits: Array<{
186
- service: string;
187
- function: string;
188
- match: string;
189
- score: number;
190
- description: string;
191
- srcPath: string;
192
- }> = [];
193
- for (const d of docs) {
194
- const service = svcName(d);
195
- for (const fn of d.functions) {
196
- const { score, match } = scoreHit(
197
- query,
258
+ }
259
+ const limit = Math.min(
260
+ typeof parsed.limit === "number" && parsed.limit > 0 ? parsed.limit : 20,
261
+ 500,
262
+ );
263
+ const docs = await listDocs();
264
+ const hits: Array<{
265
+ service: string;
266
+ fn: string;
267
+ match: string;
268
+ score: number;
269
+ description: string;
270
+ }> = [];
271
+ for (const d of docs) {
272
+ const service = svcName(d);
273
+ for (const fn of d.functions) {
274
+ const { score, match } = scoreHit(
275
+ query,
276
+ service,
277
+ fn.name,
278
+ fn.description ?? "",
279
+ d.description ?? "",
280
+ );
281
+ if (score > 0) {
282
+ hits.push({
198
283
  service,
199
- fn.name,
200
- fn.description ?? "",
201
- d.description ?? "",
202
- );
203
- if (score > 0) {
204
- hits.push({
205
- service,
206
- function: fn.name,
207
- match,
208
- score,
209
- description: fn.description,
210
- srcPath: srcPathFor(service),
211
- });
212
- }
284
+ fn: fn.name,
285
+ match,
286
+ score,
287
+ description: fn.description ?? "",
288
+ });
213
289
  }
214
290
  }
215
- hits.sort((a, b) => b.score - a.score);
216
- const top = hits.slice(0, limit);
217
- console.log(JSON.stringify(top, null, 2));
218
- console.error(
219
- `\n${hits.length} hit(s)${hits.length > limit ? ` (showing ${limit})` : ""}.` +
220
- ` Next: capture lib read <service> <function>`,
291
+ }
292
+ hits.sort((a, b) => b.score - a.score);
293
+ const top = hits.slice(0, limit);
294
+ const rows = top.map(
295
+ (h) =>
296
+ fact`${h.service}.${h.fn} — ${h.match} — ${capped(h.description, 160)}`,
297
+ );
298
+ emitResult(
299
+ {
300
+ tag: "libs",
301
+ attrs: { query, hits: hits.length, shown: top.length },
302
+ summary: fact`${hits.length} functions matched; showing ${top.length}, best score first.`,
303
+ sections: rows.length > 0 ? [lineList(rows)] : undefined,
304
+ followUp: text`\`capture lib read <service> <fn>\` loads the full input/output schemas.`,
305
+ },
306
+ { json: parsed.json },
307
+ );
308
+ }
309
+
310
+ /** The show-style summary block shared by `show` and fn-less `read`. */
311
+ function emitLibSummary(
312
+ parsed: ParsedArgs,
313
+ service: string,
314
+ doc: LibraryDoc,
315
+ followUp: FactLine,
316
+ ): void {
317
+ const sections: FactLine[] = [];
318
+ if (doc.loginUrl) sections.push(fact`login: ${doc.loginUrl}`);
319
+ if (doc.notes) sections.push(fact`notes: ${capped(doc.notes, 400)}`);
320
+ if (doc.functions.length > 0) {
321
+ sections.push(
322
+ lineList(
323
+ doc.functions.map(
324
+ (f) => fact`${f.name} — ${capped(f.description ?? "", 160)}`,
325
+ ),
326
+ ),
221
327
  );
222
- return;
223
328
  }
329
+ emitResult(
330
+ {
331
+ tag: "lib",
332
+ attrs: {
333
+ name: service,
334
+ functions: doc.functions.length,
335
+ src: srcPathFor(service),
336
+ },
337
+ summary: fact`${capped(doc.description ?? "", 300)}`,
338
+ sections,
339
+ followUp,
340
+ },
341
+ { json: parsed.json },
342
+ );
343
+ }
224
344
 
225
- // ── lib show ──────────────────────────────────────────────────────────────
226
- if (sub === "show") {
227
- const name = parsed.positional[1];
228
- if (!name) {
229
- console.error("Usage: capture lib show <name>");
230
- process.exit(1);
231
- }
232
- // Resolve + build ONLY the named lib (design §3.1: show is single-lib, not
233
- // an all-lib bundle). Mirrors the read path; adds show's visibility filter
234
- // (a present-but-non-visible lib is treated as Unknown).
235
- const r = resolveName(name, listLibs());
236
- if ("error" in r) {
237
- console.error(r.error);
238
- process.exit(1);
239
- }
240
- let doc: LibraryDoc;
241
- try {
242
- doc = await getDoc(r.name);
243
- } catch (e) {
244
- console.error((e as Error).message);
245
- process.exit(1);
246
- }
247
- if (!isAgentVisible(doc)) {
248
- console.error(
249
- `Unknown lib "${name}". Available: ${listLibs().join(", ")}`,
250
- );
251
- process.exit(1);
252
- }
253
- const service = r.name;
254
- const out = {
345
+ async function libShow(parsed: ParsedArgs): Promise<void> {
346
+ const name = parsed.positional[1];
347
+ if (!name) {
348
+ throw invalidInput(
349
+ "lib show received: no lib name; expected: capture lib show <name>.",
350
+ );
351
+ }
352
+ // Resolve + build ONLY the named lib (design §3.1: show is single-lib, not
353
+ // an all-lib bundle). Mirrors the read path; adds show's visibility filter
354
+ // (a present-but-non-visible lib is treated as unknown).
355
+ const resolved = resolveNameOrFail("lib show", name);
356
+ const doc = await getDocOrFail("lib show", resolved);
357
+ if (!isAgentVisible(doc)) {
358
+ throw invalidInput(
359
+ `lib show received: unknown lib \`${name}\`; available: ${cap(listLibs().join(", "), 1600)}. Run \`capture lib list\` for every lib with its summary.`,
360
+ "unknown_lib",
361
+ );
362
+ }
363
+ emitLibSummary(
364
+ parsed,
365
+ resolved,
366
+ doc,
367
+ fact`\`capture lib read ${resolved} <fn>\` loads full input/output JSON Schemas.`,
368
+ );
369
+ }
370
+
371
+ async function libRead(parsed: ParsedArgs): Promise<void> {
372
+ const name = parsed.positional[1];
373
+ if (!name) {
374
+ throw invalidInput(
375
+ "lib read received: no lib name; expected: capture lib read <name> [fn…].",
376
+ );
377
+ }
378
+ // `read` may read ANY present lib (no visibility filter).
379
+ const resolved = resolveNameOrFail("lib read", name);
380
+ const doc = await getDocOrFail("lib read", resolved);
381
+ const service = svcName(doc);
382
+ const fns = parsed.positional.slice(2);
383
+
384
+ // No function names → summary fallback (show-style).
385
+ if (fns.length === 0) {
386
+ emitLibSummary(
387
+ parsed,
255
388
  service,
256
- description: doc.description,
257
- ...(doc.loginUrl ? { loginUrl: doc.loginUrl } : {}),
258
- ...(doc.notes ? { notes: doc.notes } : {}),
259
- srcPath: srcPathFor(service),
260
- functions: doc.functions.map((f) => ({
261
- name: f.name,
262
- description: f.description,
263
- })),
264
- };
265
- console.log(JSON.stringify(out, null, 2));
266
- console.error(
267
- `\nSchemas required before exec — capture lib read ${service} <fn>`,
389
+ doc,
390
+ fact`Name functions to load schemas: \`capture lib read ${service} <fn> [fn…]\`.`,
268
391
  );
269
392
  return;
270
393
  }
271
394
 
272
- // ── lib read ──────────────────────────────────────────────────────────────
273
- if (sub === "read") {
274
- const name = parsed.positional[1];
275
- if (!name) {
276
- console.error("Usage: capture lib read <name> [fn…]");
277
- process.exit(1);
278
- }
279
- // `read` may read ANY present lib (no visibility filter).
280
- const r = resolveName(name, listLibs());
281
- if ("error" in r) {
282
- console.error(r.error);
283
- process.exit(1);
284
- }
285
- let doc: LibraryDoc;
286
- try {
287
- doc = await getDoc(r.name);
288
- } catch (e) {
289
- console.error((e as Error).message);
290
- process.exit(1);
291
- }
292
- const service = svcName(doc);
293
- const fns = parsed.positional.slice(2);
395
+ const byName = new Map(doc.functions.map((f) => [f.name, f]));
396
+ const found: typeof doc.functions = [];
397
+ const missing: string[] = [];
398
+ for (const op of fns) {
399
+ const fn = byName.get(op);
400
+ if (fn) found.push(fn);
401
+ else missing.push(op);
402
+ }
294
403
 
295
- // No function names → summary fallback (show-style) + hint to name fns.
296
- if (fns.length === 0) {
297
- const out = {
298
- service,
299
- description: doc.description,
300
- ...(doc.notes ? { notes: doc.notes } : {}),
301
- srcPath: srcPathFor(service),
302
- functions: doc.functions.map((f) => ({
303
- name: f.name,
304
- description: f.description,
305
- })),
306
- };
307
- console.log(JSON.stringify(out, null, 2));
308
- console.error(
309
- `\nName functions to load schemas: capture lib read ${service} <fn> [fn…]`,
310
- );
311
- return;
312
- }
404
+ if (found.length === 0) {
405
+ throw invalidInput(
406
+ `lib read received: ${missing.join(", ")} — not in ${service}; available: ${cap(
407
+ doc.functions.map((f) => f.name).join(", "),
408
+ 1600,
409
+ )}. \`capture lib show ${service}\` lists every function with its summary.`,
410
+ "unknown_function",
411
+ );
412
+ }
313
413
 
314
- const byName = new Map(doc.functions.map((f) => [f.name, f]));
315
- const found: typeof doc.functions = [];
316
- const missing: string[] = [];
317
- for (const op of fns) {
318
- const fn = byName.get(op);
319
- if (fn) found.push(fn);
320
- else missing.push(op);
321
- }
414
+ const sections: FactLine[] = found.map((f) =>
415
+ lineList([
416
+ fact`${f.name} — ${capped(f.description ?? "", 200)}`,
417
+ ...(f.notes ? [fact`notes: ${capped(f.notes, 300)}`] : []),
418
+ fact`input: ${capped(JSON.stringify(f.input), 2000)}`,
419
+ fact`output: ${capped(JSON.stringify(f.output), 2000)}`,
420
+ ]),
421
+ );
422
+ if (missing.length > 0) {
423
+ sections.push(fact`not in ${service}: ${missing.join(", ")}`);
424
+ }
425
+ const firstFn = found[0].name;
426
+ emitResult(
427
+ {
428
+ tag: "lib",
429
+ attrs: { name: service, src: srcPathFor(service) },
430
+ summary: fact`${found.length} of ${doc.functions.length} functions, full schemas; full source at the src path.`,
431
+ sections,
432
+ followUp: fact`Run it in the tab: capture page exec "import {${firstFn}} from 'libs/${service}'; return await ${firstFn}({…})".`,
433
+ },
434
+ { json: parsed.json },
435
+ );
436
+ }
322
437
 
323
- if (found.length === 0) {
324
- const available = doc.functions.map((f) => f.name).join(", ");
325
- const label =
326
- missing.length > 1
327
- ? `Functions "${missing.join('", "')}"`
328
- : `Function "${missing[0]}"`;
329
- console.error(`${label} not in ${service}. Available: ${available}`);
330
- process.exit(1);
331
- }
438
+ // ─── command ─────────────────────────────────────────────────────────────────
332
439
 
333
- const out = {
334
- service,
335
- srcPath: srcPathFor(service),
336
- functions: found.map((f) => ({
337
- name: f.name,
338
- description: f.description,
339
- ...(f.notes ? { notes: f.notes } : {}),
340
- input: f.input,
341
- output: f.output,
342
- })),
343
- ...(missing.length > 0 ? { missing } : {}),
344
- };
345
- console.log(JSON.stringify(out, null, 2));
346
- const firstFn = found[0].name;
347
- console.error(
348
- `\nRun it: capture exec "import {${firstFn}} from 'libs/${service}'; return await ${firstFn}({…})"`,
349
- );
440
+ export async function cmdLib(
441
+ parsed: ParsedArgs,
442
+ _args: string[],
443
+ ): Promise<void> {
444
+ const sub = parsed.positional[0];
445
+
446
+ // Help never touches vault/ — it must work in the published package too.
447
+ if (parsed.help) {
448
+ console.log(sub && LEAF_USAGE[sub] ? LEAF_USAGE[sub] : LIB_USAGE);
350
449
  return;
351
450
  }
352
451
 
353
- // ── lib (no/unknown subcommand) ─────────────────────────────────────────────
354
- console.error(USAGE);
355
- process.exit(1);
452
+ try {
453
+ switch (sub) {
454
+ case "list":
455
+ return await libList(parsed);
456
+ case "search":
457
+ return await libSearch(parsed);
458
+ case "show":
459
+ return await libShow(parsed);
460
+ case "read":
461
+ return await libRead(parsed);
462
+ case undefined:
463
+ // Bare `lib` prints branch usage, exit 0 — same posture as the other
464
+ // root branches (page/tab/measure/motion).
465
+ console.log(LIB_USAGE);
466
+ return;
467
+ default:
468
+ // Defense-in-depth mirror of the sibling branch routers; central
469
+ // dispatch (`validateCliInvocation`) rejects unknown lib leaves with
470
+ // the same typed shape before this switch runs.
471
+ throw invalidInput(`Unknown lib leaf: ${sub}.`, "unknown_command");
472
+ }
473
+ } catch (e) {
474
+ // The dev-only gate (vault/ source or esbuild missing) surfaces here from
475
+ // any leaf; gate behavior is unchanged, only its presentation is the
476
+ // typed vocabulary rendered at the root boundary.
477
+ if (e instanceof Error && e.message === DEV_ONLY_MSG) {
478
+ throw captureError("precondition", "dev_only", e.message, e);
479
+ }
480
+ throw e;
481
+ }
356
482
  }