@crouton-kit/capture 1.3.3 → 1.3.5
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.
- package/bin/capture +2293 -1477
- package/package.json +7 -3
- package/src/capture.ts +10 -383
- package/src/cdp/a11y.ts +104 -0
- package/src/cdp/args.ts +101 -0
- package/src/cdp/client.ts +97 -0
- package/src/cdp/commands/exec.ts +102 -0
- package/src/cdp/commands/har.ts +128 -0
- package/src/cdp/commands/lib.ts +356 -0
- package/src/cdp/commands/tabs.ts +189 -0
- package/src/cdp/commands/traffic.ts +121 -0
- package/src/cdp/commands/ui.ts +172 -0
- package/src/cdp/connection.ts +108 -0
- package/src/cdp/console-recorder.ts +80 -0
- package/src/cdp/detect.ts +150 -0
- package/src/cdp/dispatch.ts +32 -0
- package/src/cdp/execute.ts +117 -0
- package/src/cdp/har-output.ts +47 -0
- package/src/cdp/har-recorder.ts +147 -0
- package/src/cdp/record.ts +221 -0
- package/src/cdp/screenshot.ts +100 -0
- package/src/cdp/tab-lock.ts +132 -0
- package/src/cdp/targets.ts +112 -0
- package/src/cdp/types.ts +36 -0
- package/src/cdp/usage.ts +69 -0
- package/src/cdp.ts +17 -2337
- package/src/session/commands.ts +385 -0
- package/src/vault/bundle.ts +205 -0
- package/src/vault/docs.ts +224 -0
- package/src/vault/paths.ts +87 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vault/docs — on-demand library doc generation + sha-keyed cache.
|
|
3
|
+
*
|
|
4
|
+
* Reimplements vault's `generateDocs` (northlight-vault
|
|
5
|
+
* lib-pipeline/build/build-libs.ts:138-184) in-process, dropping the
|
|
6
|
+
* manifest/sha/scripts half: for a lib we esbuild-bundle its `schemas.ts` to a
|
|
7
|
+
* self-contained CJS string (zod inlined from node_modules by `bundle:true`),
|
|
8
|
+
* evaluate it in-memory, then convert each function's Zod input/output to JSON
|
|
9
|
+
* Schema (Zod-4-native `toJSONSchema()`, with the `void` special-case ported
|
|
10
|
+
* verbatim). Results are cached gitignored under `vault/.cache/docs/<svc>.json`
|
|
11
|
+
* with an embedded `_srcSha` = sha256(schemas.ts); a stale/missing sha rebuilds.
|
|
12
|
+
*
|
|
13
|
+
* Nothing here imports `esbuild` at the top level — it is loaded lazily via
|
|
14
|
+
* `loadEsbuild()` so the published package (no `vault/`, no `esbuild`) degrades
|
|
15
|
+
* to a single `DEV_ONLY_MSG` instead of crashing.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import * as fs from "fs";
|
|
19
|
+
import * as path from "path";
|
|
20
|
+
import { createHash } from "crypto";
|
|
21
|
+
import {
|
|
22
|
+
vaultLibsDir,
|
|
23
|
+
vaultRepoRoot,
|
|
24
|
+
listLibs,
|
|
25
|
+
loadEsbuild,
|
|
26
|
+
isValidLibName,
|
|
27
|
+
unknownLibMsg,
|
|
28
|
+
} from "./paths.js";
|
|
29
|
+
|
|
30
|
+
/** A single library function's doc entry (input/output are JSON Schema). */
|
|
31
|
+
export interface LibraryDocFunction {
|
|
32
|
+
name: string;
|
|
33
|
+
description: string;
|
|
34
|
+
notes?: string;
|
|
35
|
+
input: unknown;
|
|
36
|
+
output: unknown;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A library's generated doc — same shape `nl lib read --json` returns. */
|
|
40
|
+
export interface LibraryDoc {
|
|
41
|
+
/** `@vallum/<svc>` */
|
|
42
|
+
library: string;
|
|
43
|
+
description: string;
|
|
44
|
+
notes?: string;
|
|
45
|
+
loginUrl?: string;
|
|
46
|
+
/** `'public' | 'chat' | 'hidden'` (default `'public'`). */
|
|
47
|
+
visibility: string;
|
|
48
|
+
functions: LibraryDocFunction[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Shape of each entry in a lib's `schemas.ts` exported `allSchemas` array. */
|
|
52
|
+
interface FunctionSchema {
|
|
53
|
+
name: string;
|
|
54
|
+
description: string;
|
|
55
|
+
notes?: string;
|
|
56
|
+
input: unknown;
|
|
57
|
+
output: unknown;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Mirrors `nl lib`'s agent-visible filter (vault-client.service.ts:49). */
|
|
61
|
+
const AGENT_VISIBLE_VISIBILITIES = new Set(["public", "chat"]);
|
|
62
|
+
|
|
63
|
+
/** True iff a doc is agent-visible (visibility ∈ {public, chat}). */
|
|
64
|
+
export function isAgentVisible(doc: LibraryDoc): boolean {
|
|
65
|
+
return AGENT_VISIBLE_VISIBILITIES.has(doc.visibility);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* A `require` for the in-memory CJS eval below. zod is inlined by `bundle:true`
|
|
70
|
+
* so this is never actually hit — it's passed defensively. Ambient `require` is
|
|
71
|
+
* available under both the built CJS bin (native) and tsx-ESM (injected into a
|
|
72
|
+
* real module). We avoid `createRequire(import.meta.url)` because `import.meta`
|
|
73
|
+
* is empty under the CJS bin build (esbuild) and would throw at module load.
|
|
74
|
+
*/
|
|
75
|
+
declare const require: ((id: string) => unknown) | undefined;
|
|
76
|
+
const cjsRequire = (id: string): unknown => {
|
|
77
|
+
if (typeof require === "function") return require(id);
|
|
78
|
+
throw new Error(`cannot require('${id}') — unavailable in this runtime`);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
function cacheDir(): string {
|
|
82
|
+
return path.join(vaultRepoRoot(), ".cache", "docs");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function schemasPathFor(svc: string): string {
|
|
86
|
+
return path.join(vaultLibsDir(), svc, "schemas.ts");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function srcSha(svc: string): string {
|
|
90
|
+
return createHash("sha256")
|
|
91
|
+
.update(fs.readFileSync(schemasPathFor(svc), "utf8"))
|
|
92
|
+
.digest("hex");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Zod → JSON Schema, porting build-libs.ts:155-173 verbatim. `z.void()` has no
|
|
97
|
+
* JSON-Schema representation, so it maps to `{type:'null'}` (carrying its
|
|
98
|
+
* description if present); everything else uses Zod 4's `toJSONSchema()`.
|
|
99
|
+
*/
|
|
100
|
+
function toJsonSchema(zodSchema: unknown): unknown {
|
|
101
|
+
const s = zodSchema as {
|
|
102
|
+
_zod?: { def?: { type?: string } };
|
|
103
|
+
description?: string;
|
|
104
|
+
toJSONSchema?: () => unknown;
|
|
105
|
+
};
|
|
106
|
+
if (s?._zod?.def?.type === "void") {
|
|
107
|
+
const desc = s.description;
|
|
108
|
+
return desc ? { type: "null", description: desc } : { type: "null" };
|
|
109
|
+
}
|
|
110
|
+
if (!s?.toJSONSchema) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
"Schema missing toJSONSchema method - ensure Zod 4+ is installed",
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return s.toJSONSchema();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Build one lib's doc from source: esbuild-bundle its `schemas.ts` to a
|
|
120
|
+
* self-contained CJS string, eval it in-memory, assemble the `LibraryDoc`.
|
|
121
|
+
*/
|
|
122
|
+
async function buildDoc(svc: string): Promise<LibraryDoc> {
|
|
123
|
+
const esbuild = await loadEsbuild();
|
|
124
|
+
const result = await esbuild.build({
|
|
125
|
+
entryPoints: [schemasPathFor(svc)],
|
|
126
|
+
bundle: true,
|
|
127
|
+
write: false,
|
|
128
|
+
format: "cjs",
|
|
129
|
+
platform: "node",
|
|
130
|
+
target: "es2020",
|
|
131
|
+
logLevel: "silent",
|
|
132
|
+
});
|
|
133
|
+
const text = result.outputFiles?.[0]?.text;
|
|
134
|
+
if (!text) throw new Error(`esbuild produced no output for ${svc}/schemas.ts`);
|
|
135
|
+
|
|
136
|
+
// In-memory CJS eval (no temp file). zod is inlined by bundle:true, so the
|
|
137
|
+
// `require` arg is never actually hit — passed defensively for any edge.
|
|
138
|
+
const mod = { exports: {} as Record<string, unknown> };
|
|
139
|
+
new Function("module", "exports", "require", text)(
|
|
140
|
+
mod,
|
|
141
|
+
mod.exports,
|
|
142
|
+
cjsRequire,
|
|
143
|
+
);
|
|
144
|
+
const x = mod.exports as {
|
|
145
|
+
allSchemas?: FunctionSchema[];
|
|
146
|
+
libraryDescription?: string;
|
|
147
|
+
libraryNotes?: string;
|
|
148
|
+
loginUrl?: string;
|
|
149
|
+
libraryVisibility?: string;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const allSchemas = x.allSchemas ?? [];
|
|
153
|
+
const doc: LibraryDoc = {
|
|
154
|
+
library: `@vallum/${svc}`,
|
|
155
|
+
description: x.libraryDescription ?? `${svc} service library`,
|
|
156
|
+
visibility: x.libraryVisibility ?? "public",
|
|
157
|
+
functions: allSchemas.map((schema) => ({
|
|
158
|
+
name: schema.name,
|
|
159
|
+
description: schema.description,
|
|
160
|
+
...(schema.notes ? { notes: schema.notes } : {}),
|
|
161
|
+
input: toJsonSchema(schema.input),
|
|
162
|
+
output: toJsonSchema(schema.output),
|
|
163
|
+
})),
|
|
164
|
+
};
|
|
165
|
+
if (x.libraryNotes) doc.notes = x.libraryNotes;
|
|
166
|
+
if (x.loginUrl) doc.loginUrl = x.loginUrl;
|
|
167
|
+
return doc;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get one lib's doc — reads the sha-valid cache if present, else (re)builds and
|
|
172
|
+
* writes it. May read ANY present lib (no visibility filter); used by
|
|
173
|
+
* `lib read`. Throws `Unknown lib …` if the named lib has no `schemas.ts`.
|
|
174
|
+
*/
|
|
175
|
+
export async function getDoc(name: string): Promise<LibraryDoc> {
|
|
176
|
+
if (!isValidLibName(name) || !fs.existsSync(schemasPathFor(name))) {
|
|
177
|
+
throw new Error(unknownLibMsg(name));
|
|
178
|
+
}
|
|
179
|
+
const sha = srcSha(name);
|
|
180
|
+
const cacheFile = path.join(cacheDir(), `${name}.json`);
|
|
181
|
+
if (fs.existsSync(cacheFile)) {
|
|
182
|
+
try {
|
|
183
|
+
const cached = JSON.parse(fs.readFileSync(cacheFile, "utf8")) as
|
|
184
|
+
& LibraryDoc
|
|
185
|
+
& { _srcSha?: string };
|
|
186
|
+
if (cached._srcSha === sha) {
|
|
187
|
+
delete cached._srcSha;
|
|
188
|
+
return cached;
|
|
189
|
+
}
|
|
190
|
+
} catch {
|
|
191
|
+
// corrupt/unreadable cache → fall through and rebuild
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const doc = await buildDoc(name);
|
|
195
|
+
fs.mkdirSync(cacheDir(), { recursive: true });
|
|
196
|
+
fs.writeFileSync(
|
|
197
|
+
cacheFile,
|
|
198
|
+
JSON.stringify({ ...doc, _srcSha: sha }, null, 2),
|
|
199
|
+
);
|
|
200
|
+
return doc;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* All agent-visible libs' docs (visibility ∈ {public, chat}). Lazily builds any
|
|
205
|
+
* missing/stale doc, then filters by visibility. A lib whose `schemas.ts` fails
|
|
206
|
+
* to build is skipped with a stderr warning rather than failing the whole list.
|
|
207
|
+
* Used by `lib list`/`search`/`show`.
|
|
208
|
+
*/
|
|
209
|
+
export async function listDocs(): Promise<LibraryDoc[]> {
|
|
210
|
+
const out: LibraryDoc[] = [];
|
|
211
|
+
for (const name of listLibs()) {
|
|
212
|
+
let doc: LibraryDoc;
|
|
213
|
+
try {
|
|
214
|
+
doc = await getDoc(name);
|
|
215
|
+
} catch (e) {
|
|
216
|
+
console.error(
|
|
217
|
+
`WARN: skipping lib "${name}" — ${(e as Error).message}`,
|
|
218
|
+
);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (isAgentVisible(doc)) out.push(doc);
|
|
222
|
+
}
|
|
223
|
+
return out;
|
|
224
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vault/paths — locate the forked vault lib source on disk + the dev-only guard.
|
|
3
|
+
*
|
|
4
|
+
* Shared foundation for the import-driven exec bundler (`bundle.ts`) and the
|
|
5
|
+
* on-demand doc generator (`docs.ts`). This module must NOT import `esbuild` at
|
|
6
|
+
* the top level — esbuild is loaded lazily (see `loadEsbuild`) so the published
|
|
7
|
+
* package (which ships neither `vault/` nor `esbuild`) never crashes at startup.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as fs from "fs";
|
|
11
|
+
import * as path from "path";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The single dev-only degradation message. Thrown when `vault/libs` is missing
|
|
15
|
+
* (published package) or when `esbuild` cannot be loaded. (design §2.6, verbatim)
|
|
16
|
+
*/
|
|
17
|
+
export const DEV_ONLY_MSG =
|
|
18
|
+
"This is a dev-only feature of capture — run it from a capture checkout\n" +
|
|
19
|
+
"(vault/ source + `pnpm install`). Not available in the published package.";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Absolute path to the forked `vault/libs` directory. Dual `__dirname`
|
|
23
|
+
* candidates cover the built bin (`bin/../vault/libs`) and tsx dev
|
|
24
|
+
* (`src/vault/../../vault/libs`) — mirrors the `__dirname`+`..` precedent in
|
|
25
|
+
* `capture.ts`. Throws `DEV_ONLY_MSG` if neither candidate holds the source.
|
|
26
|
+
*/
|
|
27
|
+
export function vaultLibsDir(): string {
|
|
28
|
+
const candidates = [
|
|
29
|
+
path.resolve(__dirname, "..", "vault", "libs"), // built bin: bin/../vault/libs
|
|
30
|
+
path.resolve(__dirname, "..", "..", "vault", "libs"), // tsx dev: src/vault/../../vault/libs
|
|
31
|
+
];
|
|
32
|
+
const hit = candidates.find((p) =>
|
|
33
|
+
fs.existsSync(path.join(p, "_runtime", "index.ts"))
|
|
34
|
+
);
|
|
35
|
+
if (!hit) throw new Error(DEV_ONLY_MSG);
|
|
36
|
+
return hit;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The `vault/` directory (parent of `vault/libs`). Used as esbuild's
|
|
41
|
+
* `resolveDir` so its native node-resolution finds `node_modules/zod` etc.
|
|
42
|
+
*/
|
|
43
|
+
export function vaultRepoRoot(): string {
|
|
44
|
+
return path.dirname(vaultLibsDir());
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Sorted list of available lib names — directories under `vault/libs`,
|
|
49
|
+
* excluding `_`-prefixed internal dirs (e.g. `_runtime`). Used by the
|
|
50
|
+
* resolver's "Available: …" error and by the docs/lib surfaces.
|
|
51
|
+
*/
|
|
52
|
+
export function listLibs(): string[] {
|
|
53
|
+
return fs
|
|
54
|
+
.readdirSync(vaultLibsDir(), { withFileTypes: true })
|
|
55
|
+
.filter((e) => e.isDirectory() && !e.name.startsWith("_"))
|
|
56
|
+
.map((e) => e.name)
|
|
57
|
+
.sort();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The friendly "Unknown lib" message, shared by the exec resolver/preflight
|
|
62
|
+
* (`bundle.ts`) and the doc generator (`docs.ts`) so an unknown OR malformed
|
|
63
|
+
* lib name surfaces byte-identical text everywhere.
|
|
64
|
+
*/
|
|
65
|
+
export function unknownLibMsg(name: string): string {
|
|
66
|
+
return `Unknown lib "${name}". Available: ${listLibs().join(", ")}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// A lib name is a single path segment of `[A-Za-z0-9_.-]` (covers `hunter.io`)
|
|
70
|
+
// with no `..`. Rejects path separators / traversal (`libs/../x`, `libs/a/b`)
|
|
71
|
+
// before a name reaches `path.join`/esbuild — defense-in-depth (exec already
|
|
72
|
+
// runs arbitrary JS, so this only buys a clearer error, not isolation).
|
|
73
|
+
const VALID_LIB_NAME = /^[A-Za-z0-9_.-]+$/;
|
|
74
|
+
export function isValidLibName(name: string): boolean {
|
|
75
|
+
return VALID_LIB_NAME.test(name) && !name.includes("..");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Lazily load `esbuild`. Kept here so both `bundle.ts` and `docs.ts` share one
|
|
80
|
+
* lazy loader and the top-level module never statically imports esbuild — a
|
|
81
|
+
* published install (no esbuild) only fails when the feature path is exercised.
|
|
82
|
+
*/
|
|
83
|
+
export async function loadEsbuild(): Promise<typeof import("esbuild")> {
|
|
84
|
+
return await import("esbuild").catch(() => {
|
|
85
|
+
throw new Error(DEV_ONLY_MSG);
|
|
86
|
+
});
|
|
87
|
+
}
|