@crouton-kit/capture 1.3.4 → 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 +662 -70
- package/package.json +7 -3
- package/src/capture.ts +9 -0
- package/src/cdp/commands/exec.ts +26 -8
- package/src/cdp/commands/lib.ts +356 -0
- package/src/cdp/dispatch.ts +2 -0
- package/src/cdp/usage.ts +7 -0
- package/src/vault/bundle.ts +205 -0
- package/src/vault/docs.ts +224 -0
- package/src/vault/paths.ts +87 -0
package/bin/capture
CHANGED
|
@@ -2235,7 +2235,7 @@ var require_websocket = __commonJS({
|
|
|
2235
2235
|
var http = require("http");
|
|
2236
2236
|
var net = require("net");
|
|
2237
2237
|
var tls = require("tls");
|
|
2238
|
-
var { randomBytes, createHash } = require("crypto");
|
|
2238
|
+
var { randomBytes, createHash: createHash2 } = require("crypto");
|
|
2239
2239
|
var { Duplex, Readable } = require("stream");
|
|
2240
2240
|
var { URL: URL2 } = require("url");
|
|
2241
2241
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -2895,7 +2895,7 @@ var require_websocket = __commonJS({
|
|
|
2895
2895
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
2896
2896
|
return;
|
|
2897
2897
|
}
|
|
2898
|
-
const digest =
|
|
2898
|
+
const digest = createHash2("sha1").update(key + GUID).digest("base64");
|
|
2899
2899
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
2900
2900
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
2901
2901
|
return;
|
|
@@ -3262,7 +3262,7 @@ var require_websocket_server = __commonJS({
|
|
|
3262
3262
|
var EventEmitter = require("events");
|
|
3263
3263
|
var http = require("http");
|
|
3264
3264
|
var { Duplex } = require("stream");
|
|
3265
|
-
var { createHash } = require("crypto");
|
|
3265
|
+
var { createHash: createHash2 } = require("crypto");
|
|
3266
3266
|
var extension2 = require_extension();
|
|
3267
3267
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
3268
3268
|
var subprotocol2 = require_subprotocol();
|
|
@@ -3563,7 +3563,7 @@ var require_websocket_server = __commonJS({
|
|
|
3563
3563
|
);
|
|
3564
3564
|
}
|
|
3565
3565
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
3566
|
-
const digest =
|
|
3566
|
+
const digest = createHash2("sha1").update(key + GUID).digest("base64");
|
|
3567
3567
|
const headers = [
|
|
3568
3568
|
"HTTP/1.1 101 Switching Protocols",
|
|
3569
3569
|
"Upgrade: websocket",
|
|
@@ -3678,13 +3678,13 @@ var init_client = __esm({
|
|
|
3678
3678
|
ready;
|
|
3679
3679
|
constructor(wsUrl, connectTimeout = 5e3) {
|
|
3680
3680
|
this.ws = new wrapper_default(wsUrl);
|
|
3681
|
-
this.ready = new Promise((
|
|
3681
|
+
this.ready = new Promise((resolve4, reject) => {
|
|
3682
3682
|
const timeout = setTimeout(() => {
|
|
3683
3683
|
reject(new Error(`WebSocket connection timeout (${connectTimeout}ms)`));
|
|
3684
3684
|
}, connectTimeout);
|
|
3685
3685
|
this.ws.on("open", () => {
|
|
3686
3686
|
clearTimeout(timeout);
|
|
3687
|
-
|
|
3687
|
+
resolve4();
|
|
3688
3688
|
});
|
|
3689
3689
|
this.ws.on("error", (err) => {
|
|
3690
3690
|
clearTimeout(timeout);
|
|
@@ -3720,8 +3720,8 @@ var init_client = __esm({
|
|
|
3720
3720
|
async send(method, params = {}, timeout = 6e4) {
|
|
3721
3721
|
await this.ready;
|
|
3722
3722
|
const id = ++this.messageId;
|
|
3723
|
-
return new Promise((
|
|
3724
|
-
this.pendingRequests.set(id, { resolve:
|
|
3723
|
+
return new Promise((resolve4, reject) => {
|
|
3724
|
+
this.pendingRequests.set(id, { resolve: resolve4, reject });
|
|
3725
3725
|
this.ws.send(JSON.stringify({ id, method, params }));
|
|
3726
3726
|
const timer = setTimeout(() => {
|
|
3727
3727
|
if (this.pendingRequests.has(id)) {
|
|
@@ -4491,14 +4491,14 @@ async function navigateAndRecord(options) {
|
|
|
4491
4491
|
try {
|
|
4492
4492
|
await Promise.race([
|
|
4493
4493
|
(async () => {
|
|
4494
|
-
await new Promise((
|
|
4494
|
+
await new Promise((resolve4) => {
|
|
4495
4495
|
const timer = setTimeout(() => {
|
|
4496
4496
|
console.error("Page load timeout (10s), continuing with settle...");
|
|
4497
|
-
|
|
4497
|
+
resolve4();
|
|
4498
4498
|
}, 1e4);
|
|
4499
4499
|
client.on("Page.loadEventFired", () => {
|
|
4500
4500
|
clearTimeout(timer);
|
|
4501
|
-
|
|
4501
|
+
resolve4();
|
|
4502
4502
|
});
|
|
4503
4503
|
});
|
|
4504
4504
|
console.error(`Settling for ${settle}ms...`);
|
|
@@ -4539,14 +4539,14 @@ async function navigateAndWait(port, url, options = {}) {
|
|
|
4539
4539
|
const client = new CDPClient(tab.webSocketDebuggerUrl);
|
|
4540
4540
|
await client.waitReady();
|
|
4541
4541
|
await client.send("Page.enable");
|
|
4542
|
-
await new Promise((
|
|
4542
|
+
await new Promise((resolve4, reject) => {
|
|
4543
4543
|
const timer = setTimeout(
|
|
4544
4544
|
() => reject(new Error(`Page load timeout: ${url}`)),
|
|
4545
4545
|
timeout
|
|
4546
4546
|
);
|
|
4547
4547
|
client.on("Page.loadEventFired", () => {
|
|
4548
4548
|
clearTimeout(timer);
|
|
4549
|
-
|
|
4549
|
+
resolve4();
|
|
4550
4550
|
});
|
|
4551
4551
|
});
|
|
4552
4552
|
client.close();
|
|
@@ -4800,6 +4800,10 @@ Commands:
|
|
|
4800
4800
|
har create Create a HAR recording (returns id)
|
|
4801
4801
|
har read [id] Read accumulated HAR entries (id optional in active session)
|
|
4802
4802
|
har delete <id> Delete a HAR recording
|
|
4803
|
+
lib list List available vault libs (dev checkout only)
|
|
4804
|
+
lib search "<q>" Fuzzy-search lib functions [--limit N]
|
|
4805
|
+
lib show <name> Lib + function summaries
|
|
4806
|
+
lib read <name> [fn\u2026] Full input/output schemas (+ .ts source path)
|
|
4803
4807
|
|
|
4804
4808
|
Options:
|
|
4805
4809
|
--port <port> Override CDP port
|
|
@@ -4842,6 +4846,9 @@ Examples:
|
|
|
4842
4846
|
capture har create
|
|
4843
4847
|
capture exec "document.querySelector('form').submit()" --target <id> --har <id>
|
|
4844
4848
|
capture har read <id>
|
|
4849
|
+
capture lib search "search emails"
|
|
4850
|
+
capture lib read gmail searchEmails
|
|
4851
|
+
capture exec "import {searchEmails} from 'libs/gmail'; const ctx = await getContext(); return await searchEmails({ ...ctx, query: 'invoice' })"
|
|
4845
4852
|
`;
|
|
4846
4853
|
}
|
|
4847
4854
|
});
|
|
@@ -4994,11 +5001,11 @@ async function cmdResetTab(parsed, _args) {
|
|
|
4994
5001
|
const client = new CDPClient(tab.webSocketDebuggerUrl);
|
|
4995
5002
|
await client.waitReady();
|
|
4996
5003
|
await client.send("Page.enable");
|
|
4997
|
-
await new Promise((
|
|
4998
|
-
const timer = setTimeout(() =>
|
|
5004
|
+
await new Promise((resolve4) => {
|
|
5005
|
+
const timer = setTimeout(() => resolve4(), 1e4);
|
|
4999
5006
|
client.on("Page.loadEventFired", () => {
|
|
5000
5007
|
clearTimeout(timer);
|
|
5001
|
-
|
|
5008
|
+
resolve4();
|
|
5002
5009
|
});
|
|
5003
5010
|
});
|
|
5004
5011
|
client.close();
|
|
@@ -5150,6 +5157,173 @@ var init_connection = __esm({
|
|
|
5150
5157
|
}
|
|
5151
5158
|
});
|
|
5152
5159
|
|
|
5160
|
+
// src/vault/paths.ts
|
|
5161
|
+
function vaultLibsDir() {
|
|
5162
|
+
const candidates = [
|
|
5163
|
+
path4.resolve(__dirname, "..", "vault", "libs"),
|
|
5164
|
+
// built bin: bin/../vault/libs
|
|
5165
|
+
path4.resolve(__dirname, "..", "..", "vault", "libs")
|
|
5166
|
+
// tsx dev: src/vault/../../vault/libs
|
|
5167
|
+
];
|
|
5168
|
+
const hit = candidates.find(
|
|
5169
|
+
(p) => fs7.existsSync(path4.join(p, "_runtime", "index.ts"))
|
|
5170
|
+
);
|
|
5171
|
+
if (!hit) throw new Error(DEV_ONLY_MSG);
|
|
5172
|
+
return hit;
|
|
5173
|
+
}
|
|
5174
|
+
function vaultRepoRoot() {
|
|
5175
|
+
return path4.dirname(vaultLibsDir());
|
|
5176
|
+
}
|
|
5177
|
+
function listLibs() {
|
|
5178
|
+
return fs7.readdirSync(vaultLibsDir(), { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith("_")).map((e) => e.name).sort();
|
|
5179
|
+
}
|
|
5180
|
+
function unknownLibMsg(name) {
|
|
5181
|
+
return `Unknown lib "${name}". Available: ${listLibs().join(", ")}`;
|
|
5182
|
+
}
|
|
5183
|
+
function isValidLibName(name) {
|
|
5184
|
+
return VALID_LIB_NAME.test(name) && !name.includes("..");
|
|
5185
|
+
}
|
|
5186
|
+
async function loadEsbuild() {
|
|
5187
|
+
return await import("esbuild").catch(() => {
|
|
5188
|
+
throw new Error(DEV_ONLY_MSG);
|
|
5189
|
+
});
|
|
5190
|
+
}
|
|
5191
|
+
var fs7, path4, DEV_ONLY_MSG, VALID_LIB_NAME;
|
|
5192
|
+
var init_paths = __esm({
|
|
5193
|
+
"src/vault/paths.ts"() {
|
|
5194
|
+
fs7 = __toESM(require("fs"));
|
|
5195
|
+
path4 = __toESM(require("path"));
|
|
5196
|
+
DEV_ONLY_MSG = "This is a dev-only feature of capture \u2014 run it from a capture checkout\n(vault/ source + `pnpm install`). Not available in the published package.";
|
|
5197
|
+
VALID_LIB_NAME = /^[A-Za-z0-9_.-]+$/;
|
|
5198
|
+
}
|
|
5199
|
+
});
|
|
5200
|
+
|
|
5201
|
+
// src/vault/bundle.ts
|
|
5202
|
+
function splitLeadingImports(code) {
|
|
5203
|
+
const imports = [];
|
|
5204
|
+
let body = code;
|
|
5205
|
+
for (; ; ) {
|
|
5206
|
+
const m = IMPORT_STMT_RE.exec(body);
|
|
5207
|
+
if (!m) break;
|
|
5208
|
+
imports.push(m[0]);
|
|
5209
|
+
body = body.slice(m[0].length);
|
|
5210
|
+
}
|
|
5211
|
+
return { imports: imports.join(""), body };
|
|
5212
|
+
}
|
|
5213
|
+
function hasImports(code) {
|
|
5214
|
+
return splitLeadingImports(code).imports.length > 0;
|
|
5215
|
+
}
|
|
5216
|
+
function preflightLibs(imports) {
|
|
5217
|
+
const libs = vaultLibsDir();
|
|
5218
|
+
LIB_SPECIFIER_RE.lastIndex = 0;
|
|
5219
|
+
let m;
|
|
5220
|
+
while ((m = LIB_SPECIFIER_RE.exec(imports)) !== null) {
|
|
5221
|
+
const name = m[2];
|
|
5222
|
+
if (!isValidLibName(name) || !fs8.existsSync(path5.join(libs, name, "index.ts"))) {
|
|
5223
|
+
throw new Error(unknownLibMsg(name));
|
|
5224
|
+
}
|
|
5225
|
+
}
|
|
5226
|
+
}
|
|
5227
|
+
function buildEntry(code) {
|
|
5228
|
+
const { imports, body } = splitLeadingImports(code);
|
|
5229
|
+
const expr = body.trim().replace(/;+\s*$/, "");
|
|
5230
|
+
return [
|
|
5231
|
+
imports,
|
|
5232
|
+
`const ${SENTINEL} = (async () => { ${expr} })();`,
|
|
5233
|
+
`${SENTINEL}.then();`
|
|
5234
|
+
].join("\n");
|
|
5235
|
+
}
|
|
5236
|
+
function vaultResolver() {
|
|
5237
|
+
const libs = vaultLibsDir();
|
|
5238
|
+
const runtime = path5.join(libs, "_runtime", "index.ts");
|
|
5239
|
+
return {
|
|
5240
|
+
name: "vault",
|
|
5241
|
+
setup(build) {
|
|
5242
|
+
build.onResolve({ filter: /^@vallum\/_runtime$/ }, () => ({
|
|
5243
|
+
path: runtime
|
|
5244
|
+
}));
|
|
5245
|
+
build.onResolve({ filter: /^libs\// }, (args) => {
|
|
5246
|
+
const name = args.path.slice("libs/".length);
|
|
5247
|
+
if (!isValidLibName(name)) {
|
|
5248
|
+
return { errors: [{ text: unknownLibMsg(name) }] };
|
|
5249
|
+
}
|
|
5250
|
+
const entry = path5.join(libs, name, "index.ts");
|
|
5251
|
+
return fs8.existsSync(entry) ? { path: entry } : { errors: [{ text: unknownLibMsg(name) }] };
|
|
5252
|
+
});
|
|
5253
|
+
}
|
|
5254
|
+
};
|
|
5255
|
+
}
|
|
5256
|
+
async function runEsbuild(entry) {
|
|
5257
|
+
const esbuild = await loadEsbuild();
|
|
5258
|
+
const opts = {
|
|
5259
|
+
stdin: {
|
|
5260
|
+
contents: entry,
|
|
5261
|
+
resolveDir: vaultRepoRoot(),
|
|
5262
|
+
loader: "ts",
|
|
5263
|
+
sourcefile: "exec.ts"
|
|
5264
|
+
},
|
|
5265
|
+
plugins: [vaultResolver()],
|
|
5266
|
+
bundle: true,
|
|
5267
|
+
write: false,
|
|
5268
|
+
format: "iife",
|
|
5269
|
+
target: "es2020",
|
|
5270
|
+
platform: "browser",
|
|
5271
|
+
treeShaking: true,
|
|
5272
|
+
minify: false,
|
|
5273
|
+
logLevel: "silent"
|
|
5274
|
+
};
|
|
5275
|
+
try {
|
|
5276
|
+
return await esbuild.build(opts);
|
|
5277
|
+
} catch (error) {
|
|
5278
|
+
if (error instanceof Error && error.message.includes("service is no longer running")) {
|
|
5279
|
+
esbuild.stop();
|
|
5280
|
+
return await esbuild.build(opts);
|
|
5281
|
+
}
|
|
5282
|
+
throw error;
|
|
5283
|
+
}
|
|
5284
|
+
}
|
|
5285
|
+
function applyReturnInsert(bundled) {
|
|
5286
|
+
const anchor = `${SENTINEL}.then(`;
|
|
5287
|
+
const i = bundled.lastIndexOf(anchor);
|
|
5288
|
+
return i === -1 ? bundled : bundled.slice(0, i) + "return " + bundled.slice(i);
|
|
5289
|
+
}
|
|
5290
|
+
function formatBuildErrors(errors) {
|
|
5291
|
+
return errors.map((e) => {
|
|
5292
|
+
const l = e.location;
|
|
5293
|
+
const loc = l ? ` (${l.file}:${l.line}:${l.column})` : "";
|
|
5294
|
+
return `${e.text}${loc}`;
|
|
5295
|
+
}).join("\n");
|
|
5296
|
+
}
|
|
5297
|
+
async function bundleExec(code) {
|
|
5298
|
+
preflightLibs(splitLeadingImports(code).imports);
|
|
5299
|
+
const entry = buildEntry(code);
|
|
5300
|
+
let result;
|
|
5301
|
+
try {
|
|
5302
|
+
result = await runEsbuild(entry);
|
|
5303
|
+
} catch (error) {
|
|
5304
|
+
const errs = error?.errors;
|
|
5305
|
+
if (Array.isArray(errs) && errs.length > 0) {
|
|
5306
|
+
throw new Error(formatBuildErrors(errs));
|
|
5307
|
+
}
|
|
5308
|
+
throw error;
|
|
5309
|
+
}
|
|
5310
|
+
if (!result.outputFiles || result.outputFiles.length === 0) {
|
|
5311
|
+
throw new Error("esbuild produced no output");
|
|
5312
|
+
}
|
|
5313
|
+
return applyReturnInsert(result.outputFiles[0].text);
|
|
5314
|
+
}
|
|
5315
|
+
var fs8, path5, SENTINEL, IMPORT_STMT_RE, LIB_SPECIFIER_RE;
|
|
5316
|
+
var init_bundle = __esm({
|
|
5317
|
+
"src/vault/bundle.ts"() {
|
|
5318
|
+
fs8 = __toESM(require("fs"));
|
|
5319
|
+
path5 = __toESM(require("path"));
|
|
5320
|
+
init_paths();
|
|
5321
|
+
SENTINEL = "__CAPTURE_RESULT";
|
|
5322
|
+
IMPORT_STMT_RE = /^\s*(?:(?:\/\/[^\n]*|\/\*[\s\S]*?\*\/)\s*)*import\b(?!\s*\()[^'"`]*?(['"])(?:[^'"`\\]|\\.)*\1[^\n;]*;?/;
|
|
5323
|
+
LIB_SPECIFIER_RE = /(['"])libs\/((?:[^'"\\]|\\.)*)\1/g;
|
|
5324
|
+
}
|
|
5325
|
+
});
|
|
5326
|
+
|
|
5153
5327
|
// src/cdp/commands/exec.ts
|
|
5154
5328
|
async function cmdExec(parsed, _args) {
|
|
5155
5329
|
if (parsed.help) {
|
|
@@ -5162,13 +5336,16 @@ Examples:
|
|
|
5162
5336
|
capture exec "document.title" --target <id>
|
|
5163
5337
|
capture exec "await fetch('/api/data').then(r=>r.json())" --target <id>
|
|
5164
5338
|
capture exec "document.querySelector('.btn').click()" --target <id>
|
|
5165
|
-
capture exec --file /tmp/scrape.js --target <id> --record
|
|
5339
|
+
capture exec --file /tmp/scrape.js --target <id> --record
|
|
5340
|
+
|
|
5341
|
+
Import vault libs (dev checkout only): static imports first, then your code.
|
|
5342
|
+
capture exec "import {searchEmails} from 'libs/gmail'; const ctx = await getContext(); return await searchEmails({ ...ctx, query: 'invoice' })"`
|
|
5166
5343
|
);
|
|
5167
5344
|
process.exit(0);
|
|
5168
5345
|
}
|
|
5169
5346
|
let code;
|
|
5170
5347
|
if (parsed.file) {
|
|
5171
|
-
code =
|
|
5348
|
+
code = fs9.readFileSync(parsed.file, "utf-8");
|
|
5172
5349
|
} else {
|
|
5173
5350
|
code = parsed.positional[0];
|
|
5174
5351
|
if (!code) {
|
|
@@ -5185,6 +5362,15 @@ Examples:
|
|
|
5185
5362
|
process.exit(1);
|
|
5186
5363
|
}
|
|
5187
5364
|
}
|
|
5365
|
+
let prebuilt;
|
|
5366
|
+
if (hasImports(code)) {
|
|
5367
|
+
try {
|
|
5368
|
+
prebuilt = await bundleExec(code);
|
|
5369
|
+
} catch (e) {
|
|
5370
|
+
console.error(`ERROR: ${e.message}`);
|
|
5371
|
+
process.exit(1);
|
|
5372
|
+
}
|
|
5373
|
+
}
|
|
5188
5374
|
await withConnection(
|
|
5189
5375
|
parsed,
|
|
5190
5376
|
async (client) => {
|
|
@@ -5194,8 +5380,7 @@ Examples:
|
|
|
5194
5380
|
standaloneRecorder = new HARRecorder(client);
|
|
5195
5381
|
await standaloneRecorder.start();
|
|
5196
5382
|
}
|
|
5197
|
-
const
|
|
5198
|
-
const expression = needsAsyncWrap ? `(async () => { ${code} })()` : code;
|
|
5383
|
+
const expression = prebuilt ? prebuilt : /\bawait\b/.test(code) ? `(async () => { ${code} })()` : code;
|
|
5199
5384
|
const evalResult = await client.send("Runtime.evaluate", {
|
|
5200
5385
|
expression,
|
|
5201
5386
|
awaitPromise: true,
|
|
@@ -5217,13 +5402,14 @@ Examples:
|
|
|
5217
5402
|
);
|
|
5218
5403
|
return;
|
|
5219
5404
|
}
|
|
5220
|
-
var
|
|
5405
|
+
var fs9;
|
|
5221
5406
|
var init_exec = __esm({
|
|
5222
5407
|
"src/cdp/commands/exec.ts"() {
|
|
5223
|
-
|
|
5408
|
+
fs9 = __toESM(require("fs"));
|
|
5224
5409
|
init_connection();
|
|
5225
5410
|
init_har_recorder();
|
|
5226
5411
|
init_har_output();
|
|
5412
|
+
init_bundle();
|
|
5227
5413
|
}
|
|
5228
5414
|
});
|
|
5229
5415
|
|
|
@@ -5330,7 +5516,7 @@ async function cmdScreenshot(parsed, _args) {
|
|
|
5330
5516
|
async (client) => {
|
|
5331
5517
|
const png = await captureScreenshot(client, viewport, { fullPage: parsed.fullPage });
|
|
5332
5518
|
const outPath = parsed.out ? parsed.out : nextStepPath("screenshot", "manual") ?? `/tmp/capture-screenshot-${Date.now()}.png`;
|
|
5333
|
-
|
|
5519
|
+
fs10.writeFileSync(outPath, png);
|
|
5334
5520
|
return { path: outPath, bytes: png.length };
|
|
5335
5521
|
},
|
|
5336
5522
|
{ settle: 0 }
|
|
@@ -5436,10 +5622,10 @@ Consider adding role and aria-label attributes to interactive elements in your f
|
|
|
5436
5622
|
}
|
|
5437
5623
|
return;
|
|
5438
5624
|
}
|
|
5439
|
-
var
|
|
5625
|
+
var fs10;
|
|
5440
5626
|
var init_ui = __esm({
|
|
5441
5627
|
"src/cdp/commands/ui.ts"() {
|
|
5442
|
-
|
|
5628
|
+
fs10 = __toESM(require("fs"));
|
|
5443
5629
|
init_connection();
|
|
5444
5630
|
init_screenshot();
|
|
5445
5631
|
init_a11y();
|
|
@@ -5668,6 +5854,400 @@ var init_har = __esm({
|
|
|
5668
5854
|
}
|
|
5669
5855
|
});
|
|
5670
5856
|
|
|
5857
|
+
// src/vault/docs.ts
|
|
5858
|
+
function isAgentVisible(doc) {
|
|
5859
|
+
return AGENT_VISIBLE_VISIBILITIES.has(doc.visibility);
|
|
5860
|
+
}
|
|
5861
|
+
function cacheDir() {
|
|
5862
|
+
return path6.join(vaultRepoRoot(), ".cache", "docs");
|
|
5863
|
+
}
|
|
5864
|
+
function schemasPathFor(svc) {
|
|
5865
|
+
return path6.join(vaultLibsDir(), svc, "schemas.ts");
|
|
5866
|
+
}
|
|
5867
|
+
function srcSha(svc) {
|
|
5868
|
+
return (0, import_crypto.createHash)("sha256").update(fs11.readFileSync(schemasPathFor(svc), "utf8")).digest("hex");
|
|
5869
|
+
}
|
|
5870
|
+
function toJsonSchema(zodSchema) {
|
|
5871
|
+
const s = zodSchema;
|
|
5872
|
+
if (s?._zod?.def?.type === "void") {
|
|
5873
|
+
const desc = s.description;
|
|
5874
|
+
return desc ? { type: "null", description: desc } : { type: "null" };
|
|
5875
|
+
}
|
|
5876
|
+
if (!s?.toJSONSchema) {
|
|
5877
|
+
throw new Error(
|
|
5878
|
+
"Schema missing toJSONSchema method - ensure Zod 4+ is installed"
|
|
5879
|
+
);
|
|
5880
|
+
}
|
|
5881
|
+
return s.toJSONSchema();
|
|
5882
|
+
}
|
|
5883
|
+
async function buildDoc(svc) {
|
|
5884
|
+
const esbuild = await loadEsbuild();
|
|
5885
|
+
const result = await esbuild.build({
|
|
5886
|
+
entryPoints: [schemasPathFor(svc)],
|
|
5887
|
+
bundle: true,
|
|
5888
|
+
write: false,
|
|
5889
|
+
format: "cjs",
|
|
5890
|
+
platform: "node",
|
|
5891
|
+
target: "es2020",
|
|
5892
|
+
logLevel: "silent"
|
|
5893
|
+
});
|
|
5894
|
+
const text = result.outputFiles?.[0]?.text;
|
|
5895
|
+
if (!text) throw new Error(`esbuild produced no output for ${svc}/schemas.ts`);
|
|
5896
|
+
const mod = { exports: {} };
|
|
5897
|
+
new Function("module", "exports", "require", text)(
|
|
5898
|
+
mod,
|
|
5899
|
+
mod.exports,
|
|
5900
|
+
cjsRequire
|
|
5901
|
+
);
|
|
5902
|
+
const x = mod.exports;
|
|
5903
|
+
const allSchemas = x.allSchemas ?? [];
|
|
5904
|
+
const doc = {
|
|
5905
|
+
library: `@vallum/${svc}`,
|
|
5906
|
+
description: x.libraryDescription ?? `${svc} service library`,
|
|
5907
|
+
visibility: x.libraryVisibility ?? "public",
|
|
5908
|
+
functions: allSchemas.map((schema) => ({
|
|
5909
|
+
name: schema.name,
|
|
5910
|
+
description: schema.description,
|
|
5911
|
+
...schema.notes ? { notes: schema.notes } : {},
|
|
5912
|
+
input: toJsonSchema(schema.input),
|
|
5913
|
+
output: toJsonSchema(schema.output)
|
|
5914
|
+
}))
|
|
5915
|
+
};
|
|
5916
|
+
if (x.libraryNotes) doc.notes = x.libraryNotes;
|
|
5917
|
+
if (x.loginUrl) doc.loginUrl = x.loginUrl;
|
|
5918
|
+
return doc;
|
|
5919
|
+
}
|
|
5920
|
+
async function getDoc(name) {
|
|
5921
|
+
if (!isValidLibName(name) || !fs11.existsSync(schemasPathFor(name))) {
|
|
5922
|
+
throw new Error(unknownLibMsg(name));
|
|
5923
|
+
}
|
|
5924
|
+
const sha = srcSha(name);
|
|
5925
|
+
const cacheFile = path6.join(cacheDir(), `${name}.json`);
|
|
5926
|
+
if (fs11.existsSync(cacheFile)) {
|
|
5927
|
+
try {
|
|
5928
|
+
const cached = JSON.parse(fs11.readFileSync(cacheFile, "utf8"));
|
|
5929
|
+
if (cached._srcSha === sha) {
|
|
5930
|
+
delete cached._srcSha;
|
|
5931
|
+
return cached;
|
|
5932
|
+
}
|
|
5933
|
+
} catch {
|
|
5934
|
+
}
|
|
5935
|
+
}
|
|
5936
|
+
const doc = await buildDoc(name);
|
|
5937
|
+
fs11.mkdirSync(cacheDir(), { recursive: true });
|
|
5938
|
+
fs11.writeFileSync(
|
|
5939
|
+
cacheFile,
|
|
5940
|
+
JSON.stringify({ ...doc, _srcSha: sha }, null, 2)
|
|
5941
|
+
);
|
|
5942
|
+
return doc;
|
|
5943
|
+
}
|
|
5944
|
+
async function listDocs() {
|
|
5945
|
+
const out = [];
|
|
5946
|
+
for (const name of listLibs()) {
|
|
5947
|
+
let doc;
|
|
5948
|
+
try {
|
|
5949
|
+
doc = await getDoc(name);
|
|
5950
|
+
} catch (e) {
|
|
5951
|
+
console.error(
|
|
5952
|
+
`WARN: skipping lib "${name}" \u2014 ${e.message}`
|
|
5953
|
+
);
|
|
5954
|
+
continue;
|
|
5955
|
+
}
|
|
5956
|
+
if (isAgentVisible(doc)) out.push(doc);
|
|
5957
|
+
}
|
|
5958
|
+
return out;
|
|
5959
|
+
}
|
|
5960
|
+
var fs11, path6, import_crypto, AGENT_VISIBLE_VISIBILITIES, cjsRequire;
|
|
5961
|
+
var init_docs = __esm({
|
|
5962
|
+
"src/vault/docs.ts"() {
|
|
5963
|
+
fs11 = __toESM(require("fs"));
|
|
5964
|
+
path6 = __toESM(require("path"));
|
|
5965
|
+
import_crypto = require("crypto");
|
|
5966
|
+
init_paths();
|
|
5967
|
+
AGENT_VISIBLE_VISIBILITIES = /* @__PURE__ */ new Set(["public", "chat"]);
|
|
5968
|
+
cjsRequire = (id) => {
|
|
5969
|
+
if (typeof require === "function") return require(id);
|
|
5970
|
+
throw new Error(`cannot require('${id}') \u2014 unavailable in this runtime`);
|
|
5971
|
+
};
|
|
5972
|
+
}
|
|
5973
|
+
});
|
|
5974
|
+
|
|
5975
|
+
// src/cdp/commands/lib.ts
|
|
5976
|
+
function scoreToken(token, service, fnName, fnDesc, serviceDesc) {
|
|
5977
|
+
const q = token.toLowerCase();
|
|
5978
|
+
const fn = fnName.toLowerCase();
|
|
5979
|
+
const svc = service.toLowerCase();
|
|
5980
|
+
const fnD = fnDesc.toLowerCase();
|
|
5981
|
+
const svcD = serviceDesc.toLowerCase();
|
|
5982
|
+
let score = 0;
|
|
5983
|
+
let match = "";
|
|
5984
|
+
if (fn === q) {
|
|
5985
|
+
score += FUNCTION_NAME_EXACT;
|
|
5986
|
+
match = "function-name (exact)";
|
|
5987
|
+
} else if (fn.startsWith(q)) {
|
|
5988
|
+
score += FUNCTION_NAME_PREFIX;
|
|
5989
|
+
match = "function-name (prefix)";
|
|
5990
|
+
} else if (fn.includes(q)) {
|
|
5991
|
+
score += FUNCTION_NAME_SUBSTRING;
|
|
5992
|
+
match = "function-name";
|
|
5993
|
+
}
|
|
5994
|
+
if (svc.includes(q)) {
|
|
5995
|
+
score += SERVICE_NAME_MATCH;
|
|
5996
|
+
if (!match) match = "service-name";
|
|
5997
|
+
}
|
|
5998
|
+
if (fnD.includes(q)) {
|
|
5999
|
+
score += FUNCTION_DESC_SUBSTRING;
|
|
6000
|
+
if (!match) match = "function-desc";
|
|
6001
|
+
}
|
|
6002
|
+
if (svcD.includes(q)) {
|
|
6003
|
+
score += SERVICE_DESC_SUBSTRING;
|
|
6004
|
+
if (!match) match = "service-desc";
|
|
6005
|
+
}
|
|
6006
|
+
return { score, match };
|
|
6007
|
+
}
|
|
6008
|
+
function scoreHit(query, service, fnName, fnDesc, serviceDesc) {
|
|
6009
|
+
const tokens = query.split(/\s+/).filter(Boolean);
|
|
6010
|
+
if (tokens.length === 0) return { score: 0, match: "" };
|
|
6011
|
+
if (tokens.length === 1) {
|
|
6012
|
+
return scoreToken(tokens[0], service, fnName, fnDesc, serviceDesc);
|
|
6013
|
+
}
|
|
6014
|
+
let total = 0;
|
|
6015
|
+
let bestScore = -1;
|
|
6016
|
+
let bestMatch = "";
|
|
6017
|
+
for (const token of tokens) {
|
|
6018
|
+
const r = scoreToken(token, service, fnName, fnDesc, serviceDesc);
|
|
6019
|
+
if (r.score === 0) return { score: 0, match: "" };
|
|
6020
|
+
total += r.score;
|
|
6021
|
+
if (r.score > bestScore) {
|
|
6022
|
+
bestScore = r.score;
|
|
6023
|
+
bestMatch = r.match;
|
|
6024
|
+
}
|
|
6025
|
+
}
|
|
6026
|
+
return { score: total, match: bestMatch };
|
|
6027
|
+
}
|
|
6028
|
+
function resolveName(query, names) {
|
|
6029
|
+
if (names.includes(query)) return { name: query };
|
|
6030
|
+
const lower = query.toLowerCase();
|
|
6031
|
+
const prefix = names.filter((n) => n.toLowerCase().startsWith(lower));
|
|
6032
|
+
if (prefix.length === 1) return { name: prefix[0] };
|
|
6033
|
+
if (prefix.length > 1) {
|
|
6034
|
+
return { error: `Ambiguous lib "${query}" \u2014 matches: ${prefix.join(", ")}` };
|
|
6035
|
+
}
|
|
6036
|
+
return { error: `Unknown lib "${query}". Available: ${names.join(", ")}` };
|
|
6037
|
+
}
|
|
6038
|
+
async function cmdLib(parsed, _args) {
|
|
6039
|
+
if (parsed.help) {
|
|
6040
|
+
console.log(USAGE);
|
|
6041
|
+
process.exit(0);
|
|
6042
|
+
}
|
|
6043
|
+
const sub = parsed.positional[0];
|
|
6044
|
+
if (sub === "list") {
|
|
6045
|
+
const docs = await listDocs();
|
|
6046
|
+
const rows = docs.map((d) => {
|
|
6047
|
+
const name = svcName(d);
|
|
6048
|
+
return {
|
|
6049
|
+
name,
|
|
6050
|
+
functionCount: d.functions.length,
|
|
6051
|
+
description: d.description,
|
|
6052
|
+
srcPath: srcPathFor(name)
|
|
6053
|
+
};
|
|
6054
|
+
});
|
|
6055
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
6056
|
+
console.error(
|
|
6057
|
+
`
|
|
6058
|
+
${rows.length} libs. Next: capture lib show <name> \xB7 capture lib read <name> <fn>`
|
|
6059
|
+
);
|
|
6060
|
+
return;
|
|
6061
|
+
}
|
|
6062
|
+
if (sub === "search") {
|
|
6063
|
+
const query = parsed.positional[1];
|
|
6064
|
+
if (!query) {
|
|
6065
|
+
console.error('Usage: capture lib search "<query>" [--limit N]');
|
|
6066
|
+
process.exit(1);
|
|
6067
|
+
}
|
|
6068
|
+
const limit = Math.min(
|
|
6069
|
+
typeof parsed.limit === "number" && parsed.limit > 0 ? parsed.limit : 20,
|
|
6070
|
+
500
|
|
6071
|
+
);
|
|
6072
|
+
const docs = await listDocs();
|
|
6073
|
+
const hits = [];
|
|
6074
|
+
for (const d of docs) {
|
|
6075
|
+
const service = svcName(d);
|
|
6076
|
+
for (const fn of d.functions) {
|
|
6077
|
+
const { score, match } = scoreHit(
|
|
6078
|
+
query,
|
|
6079
|
+
service,
|
|
6080
|
+
fn.name,
|
|
6081
|
+
fn.description ?? "",
|
|
6082
|
+
d.description ?? ""
|
|
6083
|
+
);
|
|
6084
|
+
if (score > 0) {
|
|
6085
|
+
hits.push({
|
|
6086
|
+
service,
|
|
6087
|
+
function: fn.name,
|
|
6088
|
+
match,
|
|
6089
|
+
score,
|
|
6090
|
+
description: fn.description,
|
|
6091
|
+
srcPath: srcPathFor(service)
|
|
6092
|
+
});
|
|
6093
|
+
}
|
|
6094
|
+
}
|
|
6095
|
+
}
|
|
6096
|
+
hits.sort((a, b) => b.score - a.score);
|
|
6097
|
+
const top = hits.slice(0, limit);
|
|
6098
|
+
console.log(JSON.stringify(top, null, 2));
|
|
6099
|
+
console.error(
|
|
6100
|
+
`
|
|
6101
|
+
${hits.length} hit(s)${hits.length > limit ? ` (showing ${limit})` : ""}. Next: capture lib read <service> <function>`
|
|
6102
|
+
);
|
|
6103
|
+
return;
|
|
6104
|
+
}
|
|
6105
|
+
if (sub === "show") {
|
|
6106
|
+
const name = parsed.positional[1];
|
|
6107
|
+
if (!name) {
|
|
6108
|
+
console.error("Usage: capture lib show <name>");
|
|
6109
|
+
process.exit(1);
|
|
6110
|
+
}
|
|
6111
|
+
const r = resolveName(name, listLibs());
|
|
6112
|
+
if ("error" in r) {
|
|
6113
|
+
console.error(r.error);
|
|
6114
|
+
process.exit(1);
|
|
6115
|
+
}
|
|
6116
|
+
let doc;
|
|
6117
|
+
try {
|
|
6118
|
+
doc = await getDoc(r.name);
|
|
6119
|
+
} catch (e) {
|
|
6120
|
+
console.error(e.message);
|
|
6121
|
+
process.exit(1);
|
|
6122
|
+
}
|
|
6123
|
+
if (!isAgentVisible(doc)) {
|
|
6124
|
+
console.error(
|
|
6125
|
+
`Unknown lib "${name}". Available: ${listLibs().join(", ")}`
|
|
6126
|
+
);
|
|
6127
|
+
process.exit(1);
|
|
6128
|
+
}
|
|
6129
|
+
const service = r.name;
|
|
6130
|
+
const out = {
|
|
6131
|
+
service,
|
|
6132
|
+
description: doc.description,
|
|
6133
|
+
...doc.loginUrl ? { loginUrl: doc.loginUrl } : {},
|
|
6134
|
+
...doc.notes ? { notes: doc.notes } : {},
|
|
6135
|
+
srcPath: srcPathFor(service),
|
|
6136
|
+
functions: doc.functions.map((f) => ({
|
|
6137
|
+
name: f.name,
|
|
6138
|
+
description: f.description
|
|
6139
|
+
}))
|
|
6140
|
+
};
|
|
6141
|
+
console.log(JSON.stringify(out, null, 2));
|
|
6142
|
+
console.error(
|
|
6143
|
+
`
|
|
6144
|
+
Schemas required before exec \u2014 capture lib read ${service} <fn>`
|
|
6145
|
+
);
|
|
6146
|
+
return;
|
|
6147
|
+
}
|
|
6148
|
+
if (sub === "read") {
|
|
6149
|
+
const name = parsed.positional[1];
|
|
6150
|
+
if (!name) {
|
|
6151
|
+
console.error("Usage: capture lib read <name> [fn\u2026]");
|
|
6152
|
+
process.exit(1);
|
|
6153
|
+
}
|
|
6154
|
+
const r = resolveName(name, listLibs());
|
|
6155
|
+
if ("error" in r) {
|
|
6156
|
+
console.error(r.error);
|
|
6157
|
+
process.exit(1);
|
|
6158
|
+
}
|
|
6159
|
+
let doc;
|
|
6160
|
+
try {
|
|
6161
|
+
doc = await getDoc(r.name);
|
|
6162
|
+
} catch (e) {
|
|
6163
|
+
console.error(e.message);
|
|
6164
|
+
process.exit(1);
|
|
6165
|
+
}
|
|
6166
|
+
const service = svcName(doc);
|
|
6167
|
+
const fns = parsed.positional.slice(2);
|
|
6168
|
+
if (fns.length === 0) {
|
|
6169
|
+
const out2 = {
|
|
6170
|
+
service,
|
|
6171
|
+
description: doc.description,
|
|
6172
|
+
...doc.notes ? { notes: doc.notes } : {},
|
|
6173
|
+
srcPath: srcPathFor(service),
|
|
6174
|
+
functions: doc.functions.map((f) => ({
|
|
6175
|
+
name: f.name,
|
|
6176
|
+
description: f.description
|
|
6177
|
+
}))
|
|
6178
|
+
};
|
|
6179
|
+
console.log(JSON.stringify(out2, null, 2));
|
|
6180
|
+
console.error(
|
|
6181
|
+
`
|
|
6182
|
+
Name functions to load schemas: capture lib read ${service} <fn> [fn\u2026]`
|
|
6183
|
+
);
|
|
6184
|
+
return;
|
|
6185
|
+
}
|
|
6186
|
+
const byName = new Map(doc.functions.map((f) => [f.name, f]));
|
|
6187
|
+
const found = [];
|
|
6188
|
+
const missing = [];
|
|
6189
|
+
for (const op of fns) {
|
|
6190
|
+
const fn = byName.get(op);
|
|
6191
|
+
if (fn) found.push(fn);
|
|
6192
|
+
else missing.push(op);
|
|
6193
|
+
}
|
|
6194
|
+
if (found.length === 0) {
|
|
6195
|
+
const available = doc.functions.map((f) => f.name).join(", ");
|
|
6196
|
+
const label = missing.length > 1 ? `Functions "${missing.join('", "')}"` : `Function "${missing[0]}"`;
|
|
6197
|
+
console.error(`${label} not in ${service}. Available: ${available}`);
|
|
6198
|
+
process.exit(1);
|
|
6199
|
+
}
|
|
6200
|
+
const out = {
|
|
6201
|
+
service,
|
|
6202
|
+
srcPath: srcPathFor(service),
|
|
6203
|
+
functions: found.map((f) => ({
|
|
6204
|
+
name: f.name,
|
|
6205
|
+
description: f.description,
|
|
6206
|
+
...f.notes ? { notes: f.notes } : {},
|
|
6207
|
+
input: f.input,
|
|
6208
|
+
output: f.output
|
|
6209
|
+
})),
|
|
6210
|
+
...missing.length > 0 ? { missing } : {}
|
|
6211
|
+
};
|
|
6212
|
+
console.log(JSON.stringify(out, null, 2));
|
|
6213
|
+
const firstFn = found[0].name;
|
|
6214
|
+
console.error(
|
|
6215
|
+
`
|
|
6216
|
+
Run it: capture exec "import {${firstFn}} from 'libs/${service}'; return await ${firstFn}({\u2026})"`
|
|
6217
|
+
);
|
|
6218
|
+
return;
|
|
6219
|
+
}
|
|
6220
|
+
console.error(USAGE);
|
|
6221
|
+
process.exit(1);
|
|
6222
|
+
}
|
|
6223
|
+
var path7, USAGE, FUNCTION_NAME_EXACT, FUNCTION_NAME_PREFIX, FUNCTION_NAME_SUBSTRING, SERVICE_NAME_MATCH, FUNCTION_DESC_SUBSTRING, SERVICE_DESC_SUBSTRING, svcName, srcPathFor;
|
|
6224
|
+
var init_lib = __esm({
|
|
6225
|
+
"src/cdp/commands/lib.ts"() {
|
|
6226
|
+
path7 = __toESM(require("path"));
|
|
6227
|
+
init_paths();
|
|
6228
|
+
init_docs();
|
|
6229
|
+
USAGE = `Usage: capture lib <list|search|show|read> [options]
|
|
6230
|
+
|
|
6231
|
+
list List available libs (name, fn count, description, srcPath)
|
|
6232
|
+
search "<query>" [--limit N] Fuzzy-search functions across all libs (ranked)
|
|
6233
|
+
show <name> Lib + function summaries (no schemas)
|
|
6234
|
+
read <name> [fn\u2026] Full input/output JSON schemas (+ .ts source path)
|
|
6235
|
+
|
|
6236
|
+
Then run a lib in the active authenticated tab:
|
|
6237
|
+
capture exec "import {<fn>} from 'libs/<name>'; return await <fn>({\u2026})"
|
|
6238
|
+
|
|
6239
|
+
Dev-checkout-only feature (requires vault/ source + esbuild).`;
|
|
6240
|
+
FUNCTION_NAME_EXACT = 100;
|
|
6241
|
+
FUNCTION_NAME_PREFIX = 60;
|
|
6242
|
+
FUNCTION_NAME_SUBSTRING = 40;
|
|
6243
|
+
SERVICE_NAME_MATCH = 50;
|
|
6244
|
+
FUNCTION_DESC_SUBSTRING = 20;
|
|
6245
|
+
SERVICE_DESC_SUBSTRING = 10;
|
|
6246
|
+
svcName = (doc) => doc.library.replace(/^@vallum\//, "");
|
|
6247
|
+
srcPathFor = (name) => path7.join(vaultLibsDir(), name, "index.ts");
|
|
6248
|
+
}
|
|
6249
|
+
});
|
|
6250
|
+
|
|
5671
6251
|
// src/cdp/dispatch.ts
|
|
5672
6252
|
async function cdpMain2() {
|
|
5673
6253
|
const args = process.argv.slice(2);
|
|
@@ -5699,6 +6279,8 @@ async function cdpMain2() {
|
|
|
5699
6279
|
return cmdNetwork(parsed, args);
|
|
5700
6280
|
case "har":
|
|
5701
6281
|
return cmdHar(parsed, args);
|
|
6282
|
+
case "lib":
|
|
6283
|
+
return cmdLib(parsed, args);
|
|
5702
6284
|
default:
|
|
5703
6285
|
console.log(CDP_USAGE);
|
|
5704
6286
|
}
|
|
@@ -5712,6 +6294,7 @@ var init_dispatch = __esm({
|
|
|
5712
6294
|
init_ui();
|
|
5713
6295
|
init_traffic();
|
|
5714
6296
|
init_har();
|
|
6297
|
+
init_lib();
|
|
5715
6298
|
}
|
|
5716
6299
|
});
|
|
5717
6300
|
|
|
@@ -5769,30 +6352,30 @@ var init_cdp = __esm({
|
|
|
5769
6352
|
});
|
|
5770
6353
|
|
|
5771
6354
|
// src/capture.ts
|
|
5772
|
-
var
|
|
5773
|
-
var
|
|
6355
|
+
var fs13 = __toESM(require("fs"));
|
|
6356
|
+
var path9 = __toESM(require("path"));
|
|
5774
6357
|
init_cdp();
|
|
5775
6358
|
|
|
5776
6359
|
// src/session/commands.ts
|
|
5777
|
-
var
|
|
5778
|
-
var
|
|
6360
|
+
var fs12 = __toESM(require("fs"));
|
|
6361
|
+
var path8 = __toESM(require("path"));
|
|
5779
6362
|
var os3 = __toESM(require("os"));
|
|
5780
6363
|
var import_child_process2 = require("child_process");
|
|
5781
6364
|
init_har_manager();
|
|
5782
6365
|
init_session_context();
|
|
5783
|
-
var CAPTURE_ROOT2 =
|
|
6366
|
+
var CAPTURE_ROOT2 = path8.join(os3.tmpdir(), "capture-sessions");
|
|
5784
6367
|
function sessionDir(id) {
|
|
5785
|
-
return
|
|
6368
|
+
return path8.join(CAPTURE_ROOT2, id);
|
|
5786
6369
|
}
|
|
5787
6370
|
function sessionMetaPath(id) {
|
|
5788
|
-
return
|
|
6371
|
+
return path8.join(sessionDir(id), ".session.json");
|
|
5789
6372
|
}
|
|
5790
6373
|
function readSession(id) {
|
|
5791
6374
|
const metaPath = sessionMetaPath(id);
|
|
5792
|
-
if (!
|
|
6375
|
+
if (!fs12.existsSync(metaPath)) {
|
|
5793
6376
|
throw new Error(`No capture session found: ${id}`);
|
|
5794
6377
|
}
|
|
5795
|
-
return JSON.parse(
|
|
6378
|
+
return JSON.parse(fs12.readFileSync(metaPath, "utf-8"));
|
|
5796
6379
|
}
|
|
5797
6380
|
function generateId() {
|
|
5798
6381
|
return `cap-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
@@ -5806,8 +6389,8 @@ async function start(args) {
|
|
|
5806
6389
|
}
|
|
5807
6390
|
const id = generateId();
|
|
5808
6391
|
const dir = sessionDir(id);
|
|
5809
|
-
|
|
5810
|
-
|
|
6392
|
+
fs12.mkdirSync(path8.join(dir, "shots"), { recursive: true });
|
|
6393
|
+
fs12.mkdirSync(path8.join(dir, "a11y"), { recursive: true });
|
|
5811
6394
|
let harId = null;
|
|
5812
6395
|
try {
|
|
5813
6396
|
const harResult = createHarRecording();
|
|
@@ -5836,7 +6419,7 @@ async function start(args) {
|
|
|
5836
6419
|
stepCount: 0,
|
|
5837
6420
|
logPids: []
|
|
5838
6421
|
};
|
|
5839
|
-
|
|
6422
|
+
fs12.writeFileSync(sessionMetaPath(id), JSON.stringify(session, null, 2));
|
|
5840
6423
|
setActiveSession({
|
|
5841
6424
|
sessionId: id,
|
|
5842
6425
|
dir,
|
|
@@ -5849,8 +6432,8 @@ async function start(args) {
|
|
|
5849
6432
|
bundleDir: dir,
|
|
5850
6433
|
harId,
|
|
5851
6434
|
targetId,
|
|
5852
|
-
shotsDir:
|
|
5853
|
-
a11yDir:
|
|
6435
|
+
shotsDir: path8.join(dir, "shots"),
|
|
6436
|
+
a11yDir: path8.join(dir, "a11y")
|
|
5854
6437
|
};
|
|
5855
6438
|
console.log(JSON.stringify(result, null, 2));
|
|
5856
6439
|
console.error(`
|
|
@@ -5867,8 +6450,8 @@ function logCommand(args) {
|
|
|
5867
6450
|
console.error("Usage: capture log <path> [--name label] [--session <id>]");
|
|
5868
6451
|
process.exit(1);
|
|
5869
6452
|
}
|
|
5870
|
-
const resolved =
|
|
5871
|
-
if (!
|
|
6453
|
+
const resolved = path8.resolve(sourcePath);
|
|
6454
|
+
if (!fs12.existsSync(resolved)) {
|
|
5872
6455
|
throw new Error(`Log file not found: ${resolved}`);
|
|
5873
6456
|
}
|
|
5874
6457
|
let name = null;
|
|
@@ -5885,21 +6468,21 @@ function logCommand(args) {
|
|
|
5885
6468
|
sessionId = active.sessionId;
|
|
5886
6469
|
}
|
|
5887
6470
|
const session = readSession(sessionId);
|
|
5888
|
-
name = name ??
|
|
5889
|
-
const logsDir =
|
|
5890
|
-
|
|
5891
|
-
const destPath =
|
|
5892
|
-
const outFd =
|
|
6471
|
+
name = name ?? path8.basename(resolved, path8.extname(resolved));
|
|
6472
|
+
const logsDir = path8.join(session.dir, "logs");
|
|
6473
|
+
fs12.mkdirSync(logsDir, { recursive: true });
|
|
6474
|
+
const destPath = path8.join(logsDir, `${name}.log`);
|
|
6475
|
+
const outFd = fs12.openSync(destPath, "a");
|
|
5893
6476
|
const child = (0, import_child_process2.spawn)(
|
|
5894
6477
|
"sh",
|
|
5895
6478
|
["-c", `tail -f "${resolved}" | perl -MPOSIX -ne 'print strftime("%Y-%m-%dT%H:%M:%SZ",gmtime())." ".$_'`],
|
|
5896
6479
|
{ detached: true, stdio: ["ignore", outFd, "ignore"] }
|
|
5897
6480
|
);
|
|
5898
6481
|
child.unref();
|
|
5899
|
-
|
|
6482
|
+
fs12.closeSync(outFd);
|
|
5900
6483
|
const pid = child.pid;
|
|
5901
6484
|
session.logPids.push({ pid, name, sourcePath: resolved });
|
|
5902
|
-
|
|
6485
|
+
fs12.writeFileSync(sessionMetaPath(session.id), JSON.stringify(session, null, 2));
|
|
5903
6486
|
console.log(JSON.stringify({ name, sourcePath: resolved, destPath, pid }, null, 2));
|
|
5904
6487
|
}
|
|
5905
6488
|
async function stop(args) {
|
|
@@ -5921,17 +6504,17 @@ async function stop(args) {
|
|
|
5921
6504
|
if (session.logPids?.length) {
|
|
5922
6505
|
await new Promise((r) => setTimeout(r, 200));
|
|
5923
6506
|
}
|
|
5924
|
-
const shotsDir =
|
|
5925
|
-
const screenshots =
|
|
5926
|
-
const a11yDir =
|
|
5927
|
-
const a11y =
|
|
6507
|
+
const shotsDir = path8.join(session.dir, "shots");
|
|
6508
|
+
const screenshots = fs12.existsSync(shotsDir) ? fs12.readdirSync(shotsDir).filter((f) => f.endsWith(".png") || f.endsWith(".jpg")).map((f) => ({ name: f, path: path8.join(shotsDir, f) })) : [];
|
|
6509
|
+
const a11yDir = path8.join(session.dir, "a11y");
|
|
6510
|
+
const a11y = fs12.existsSync(a11yDir) ? fs12.readdirSync(a11yDir).filter((f) => f.endsWith(".json") || f.endsWith(".txt")).map((f) => ({ name: f, path: path8.join(a11yDir, f) })) : [];
|
|
5928
6511
|
let har = null;
|
|
5929
6512
|
if (session.harId) {
|
|
5930
6513
|
try {
|
|
5931
6514
|
const harData = readHarRecording(session.harId);
|
|
5932
6515
|
if (harData) {
|
|
5933
|
-
const harPath =
|
|
5934
|
-
|
|
6516
|
+
const harPath = path8.join(session.dir, "har.json");
|
|
6517
|
+
fs12.writeFileSync(harPath, JSON.stringify(harData, null, 2));
|
|
5935
6518
|
har = { id: session.harId, path: harPath, entryCount: harData.log.entries.length };
|
|
5936
6519
|
try {
|
|
5937
6520
|
deleteHarRecording(session.harId);
|
|
@@ -5942,16 +6525,16 @@ async function stop(args) {
|
|
|
5942
6525
|
console.error(`Warning: could not read HAR: ${err instanceof Error ? err.message : err}`);
|
|
5943
6526
|
}
|
|
5944
6527
|
}
|
|
5945
|
-
const logsDir =
|
|
5946
|
-
const logs =
|
|
5947
|
-
const filePath =
|
|
5948
|
-
const content =
|
|
6528
|
+
const logsDir = path8.join(session.dir, "logs");
|
|
6529
|
+
const logs = fs12.existsSync(logsDir) ? fs12.readdirSync(logsDir).filter((f) => f.endsWith(".log")).map((f) => {
|
|
6530
|
+
const filePath = path8.join(logsDir, f);
|
|
6531
|
+
const content = fs12.readFileSync(filePath, "utf-8");
|
|
5949
6532
|
const lines = content ? content.split("\n").filter(Boolean).length : 0;
|
|
5950
6533
|
return { name: f, path: filePath, lines };
|
|
5951
6534
|
}) : [];
|
|
5952
6535
|
const knownDirs = /* @__PURE__ */ new Set(["shots", "a11y", "logs"]);
|
|
5953
6536
|
const knownFiles = /* @__PURE__ */ new Set([".session.json", "har.json", "bundle.json"]);
|
|
5954
|
-
const other =
|
|
6537
|
+
const other = fs12.readdirSync(session.dir).filter((f) => !knownDirs.has(f) && !knownFiles.has(f)).map((f) => ({ name: f, path: path8.join(session.dir, f) }));
|
|
5955
6538
|
clearActiveSession();
|
|
5956
6539
|
const manifest = {
|
|
5957
6540
|
id: session.id,
|
|
@@ -5965,8 +6548,8 @@ async function stop(args) {
|
|
|
5965
6548
|
logs,
|
|
5966
6549
|
other
|
|
5967
6550
|
};
|
|
5968
|
-
const bundlePath =
|
|
5969
|
-
|
|
6551
|
+
const bundlePath = path8.join(session.dir, "bundle.json");
|
|
6552
|
+
fs12.writeFileSync(bundlePath, JSON.stringify(manifest, null, 2));
|
|
5970
6553
|
console.log(JSON.stringify({
|
|
5971
6554
|
bundlePath,
|
|
5972
6555
|
summary: {
|
|
@@ -5983,13 +6566,13 @@ Bundle written: ${bundlePath}`);
|
|
|
5983
6566
|
console.error(`Read it: capture session view ${id}`);
|
|
5984
6567
|
}
|
|
5985
6568
|
function list() {
|
|
5986
|
-
if (!
|
|
6569
|
+
if (!fs12.existsSync(CAPTURE_ROOT2)) {
|
|
5987
6570
|
console.log("[]");
|
|
5988
6571
|
return;
|
|
5989
6572
|
}
|
|
5990
|
-
const sessions =
|
|
6573
|
+
const sessions = fs12.readdirSync(CAPTURE_ROOT2).filter((d) => fs12.existsSync(sessionMetaPath(d))).map((d) => {
|
|
5991
6574
|
const session = readSession(d);
|
|
5992
|
-
const hasBundled =
|
|
6575
|
+
const hasBundled = fs12.existsSync(path8.join(session.dir, "bundle.json"));
|
|
5993
6576
|
return { id: session.id, startedAt: session.startedAt, url: session.url, status: hasBundled ? "stopped" : "active" };
|
|
5994
6577
|
});
|
|
5995
6578
|
console.log(JSON.stringify(sessions, null, 2));
|
|
@@ -6001,12 +6584,12 @@ function view(args) {
|
|
|
6001
6584
|
process.exit(1);
|
|
6002
6585
|
}
|
|
6003
6586
|
const session = readSession(id);
|
|
6004
|
-
const bundlePath =
|
|
6005
|
-
if (!
|
|
6587
|
+
const bundlePath = path8.join(session.dir, "bundle.json");
|
|
6588
|
+
if (!fs12.existsSync(bundlePath)) {
|
|
6006
6589
|
console.error(`Session ${id} hasn't been stopped yet. Run: capture session stop ${id}`);
|
|
6007
6590
|
process.exit(1);
|
|
6008
6591
|
}
|
|
6009
|
-
const manifest = JSON.parse(
|
|
6592
|
+
const manifest = JSON.parse(fs12.readFileSync(bundlePath, "utf-8"));
|
|
6010
6593
|
const filter = args.find((_, i) => args[i - 1] === "--filter");
|
|
6011
6594
|
if (filter) {
|
|
6012
6595
|
const section = manifest[filter];
|
|
@@ -6059,14 +6642,14 @@ See also: capture --help Full command list`);
|
|
|
6059
6642
|
async function main() {
|
|
6060
6643
|
const [command, ...args] = process.argv.slice(2);
|
|
6061
6644
|
if (command === "--version" || command === "-v" || command === "version") {
|
|
6062
|
-
const declared = "1.3.
|
|
6645
|
+
const declared = "1.3.5";
|
|
6063
6646
|
if (declared) {
|
|
6064
6647
|
console.log(declared);
|
|
6065
6648
|
return;
|
|
6066
6649
|
}
|
|
6067
6650
|
try {
|
|
6068
|
-
const pkgPath =
|
|
6069
|
-
const pkg = JSON.parse(
|
|
6651
|
+
const pkgPath = path9.resolve(__dirname, "..", "package.json");
|
|
6652
|
+
const pkg = JSON.parse(fs13.readFileSync(pkgPath, "utf-8"));
|
|
6070
6653
|
console.log(pkg.version);
|
|
6071
6654
|
} catch {
|
|
6072
6655
|
console.log("unknown");
|
|
@@ -6090,6 +6673,7 @@ async function main() {
|
|
|
6090
6673
|
case "record":
|
|
6091
6674
|
case "navigate":
|
|
6092
6675
|
case "har":
|
|
6676
|
+
case "lib":
|
|
6093
6677
|
case "network":
|
|
6094
6678
|
case "list":
|
|
6095
6679
|
return cdpMain2();
|
|
@@ -6145,6 +6729,14 @@ DIAGNOSTICS & ONE-OFFS (no session needed)
|
|
|
6145
6729
|
network <offline|online> Toggle connectivity for a tab
|
|
6146
6730
|
har create | read [id] | delete <id> Manage standalone HAR recordings
|
|
6147
6731
|
|
|
6732
|
+
LIBRARY (vault libs \u2014 dev checkout only)
|
|
6733
|
+
|
|
6734
|
+
lib list List available libs
|
|
6735
|
+
lib search "<query>" [--limit N] Fuzzy-search functions across libs
|
|
6736
|
+
lib show <name> Lib + function summaries
|
|
6737
|
+
lib read <name> [fn\u2026] Full input/output schemas (+ .ts source path)
|
|
6738
|
+
exec "import {fn} from 'libs/<name>'; return await fn({\u2026})" Run a lib in the active tab
|
|
6739
|
+
|
|
6148
6740
|
TARGETING (only when NOT in a session, or picking a parallel tab)
|
|
6149
6741
|
|
|
6150
6742
|
--target <tabId> Exact id; a prefix of 8 chars is enough (preferred, parallel-safe)
|