xaq-cli 0.1.0-nightly.92 → 0.1.0-nightly.93
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/dist/attachment-parser.js +43 -29
- package/dist/{cli-yN8ExqrA.js → cli-Wp2ejVBT.js} +2 -2
- package/dist/{cli-yN8ExqrA.js.map → cli-Wp2ejVBT.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{run-D3Qmg-MB.js → run-zakbVgQR.js} +41 -11
- package/dist/run-zakbVgQR.js.map +1 -0
- package/dist/{server-BmxoM9--.js → server-0wxSpx6s.js} +3 -3
- package/dist/server-0wxSpx6s.js.map +1 -0
- package/dist/skill/SKILL.md +3 -2
- package/dist/{smoke-DA7EXNuS.js → smoke-ZqEGwDQl.js} +62 -11
- package/dist/{smoke-DA7EXNuS.js.map → smoke-ZqEGwDQl.js.map} +1 -1
- package/package.json +1 -1
- package/dist/run-D3Qmg-MB.js.map +0 -1
- package/dist/server-BmxoM9--.js.map +0 -1
|
@@ -110,8 +110,14 @@ const PARSE_LIMITS = {
|
|
|
110
110
|
/** Characters kept per cell. */
|
|
111
111
|
cellChars: 2e3
|
|
112
112
|
};
|
|
113
|
-
/** A file the parser refuses to read (zip bomb, XXE, entry outside the archive, …): said in plain words. */
|
|
114
|
-
var UnsafeDocumentError = class extends Error {
|
|
113
|
+
/** A file the parser refuses to read (zip bomb, XXE, entry outside the archive, …): said in plain words, with a code. */
|
|
114
|
+
var UnsafeDocumentError = class extends Error {
|
|
115
|
+
code;
|
|
116
|
+
constructor(code, message) {
|
|
117
|
+
super(message);
|
|
118
|
+
this.code = code;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
115
121
|
//#endregion
|
|
116
122
|
//#region server/mail/extract/csv.ts
|
|
117
123
|
function guessDelimiter(text) {
|
|
@@ -7479,12 +7485,12 @@ const ELEMENT = 1;
|
|
|
7479
7485
|
const TEXT = 3;
|
|
7480
7486
|
const CDATA = 4;
|
|
7481
7487
|
function parseXml(source) {
|
|
7482
|
-
if (/<!DOCTYPE|<!ENTITY/i.test(source)) throw new UnsafeDocumentError("The document declares XML entities (a DOCTYPE), which Office files never do; it wasn't read.");
|
|
7488
|
+
if (/<!DOCTYPE|<!ENTITY/i.test(source)) throw new UnsafeDocumentError("hostile", "The document declares XML entities (a DOCTYPE), which Office files never do; it wasn't read.");
|
|
7483
7489
|
let failed = null;
|
|
7484
7490
|
const root = new import_lib.DOMParser({ onError: (level, msg) => {
|
|
7485
7491
|
if (level !== "warning") failed ??= msg;
|
|
7486
7492
|
} }).parseFromString(source, "text/xml").documentElement;
|
|
7487
|
-
if (failed || !root) throw new UnsafeDocumentError("The document has malformed XML.");
|
|
7493
|
+
if (failed || !root) throw new UnsafeDocumentError("corrupt", "The document has malformed XML.");
|
|
7488
7494
|
return root;
|
|
7489
7495
|
}
|
|
7490
7496
|
const local = (n) => n.localName ?? n.nodeName.replace(/^.*:/, "");
|
|
@@ -7568,25 +7574,25 @@ var ZipPackage = class {
|
|
|
7568
7574
|
inflated = 0;
|
|
7569
7575
|
constructor(buf) {
|
|
7570
7576
|
this.buf = buf;
|
|
7571
|
-
if (buf.length < 22) throw new UnsafeDocumentError("This isn't a valid zip package.");
|
|
7577
|
+
if (buf.length < 22) throw new UnsafeDocumentError("corrupt", "This isn't a valid zip package.");
|
|
7572
7578
|
let eocd = -1;
|
|
7573
7579
|
for (let i = buf.length - 22; i >= Math.max(0, buf.length - 22 - 65535); i--) if (buf.readUInt32LE(i) === EOCD) {
|
|
7574
7580
|
eocd = i;
|
|
7575
7581
|
break;
|
|
7576
7582
|
}
|
|
7577
|
-
if (eocd < 0) throw new UnsafeDocumentError("This isn't a valid zip package (no central directory).");
|
|
7583
|
+
if (eocd < 0) throw new UnsafeDocumentError("corrupt", "This isn't a valid zip package (no central directory).");
|
|
7578
7584
|
const disk = buf.readUInt16LE(eocd + 4);
|
|
7579
7585
|
const cdDisk = buf.readUInt16LE(eocd + 6);
|
|
7580
7586
|
const count = buf.readUInt16LE(eocd + 10);
|
|
7581
7587
|
const cdSize = buf.readUInt32LE(eocd + 12);
|
|
7582
7588
|
const cdOffset = buf.readUInt32LE(eocd + 16);
|
|
7583
|
-
if (disk !== 0 || cdDisk !== 0) throw new UnsafeDocumentError("Multi-part zip archives aren't supported.");
|
|
7584
|
-
if (count === 65535 || cdOffset === 4294967295) throw new UnsafeDocumentError("ZIP64 archives aren't supported.");
|
|
7585
|
-
if (count > PARSE_LIMITS.zipEntries) throw new UnsafeDocumentError(`The package has ${count} entries; at most ${PARSE_LIMITS.zipEntries} are read.`);
|
|
7586
|
-
if (cdOffset + cdSize > eocd) throw new UnsafeDocumentError("This zip package is corrupt (central directory out of range).");
|
|
7589
|
+
if (disk !== 0 || cdDisk !== 0) throw new UnsafeDocumentError("unsupported", "Multi-part zip archives aren't supported.");
|
|
7590
|
+
if (count === 65535 || cdOffset === 4294967295) throw new UnsafeDocumentError("unsupported", "ZIP64 archives aren't supported.");
|
|
7591
|
+
if (count > PARSE_LIMITS.zipEntries) throw new UnsafeDocumentError("too_complex", `The package has ${count} entries; at most ${PARSE_LIMITS.zipEntries} are read.`);
|
|
7592
|
+
if (cdOffset + cdSize > eocd) throw new UnsafeDocumentError("corrupt", "This zip package is corrupt (central directory out of range).");
|
|
7587
7593
|
let p = cdOffset;
|
|
7588
7594
|
for (let i = 0; i < count; i++) {
|
|
7589
|
-
if (p + 46 > eocd || buf.readUInt32LE(p) !== CDH) throw new UnsafeDocumentError("This zip package is corrupt (bad central directory).");
|
|
7595
|
+
if (p + 46 > eocd || buf.readUInt32LE(p) !== CDH) throw new UnsafeDocumentError("corrupt", "This zip package is corrupt (bad central directory).");
|
|
7590
7596
|
const flags = buf.readUInt16LE(p + 8);
|
|
7591
7597
|
const method = buf.readUInt16LE(p + 10);
|
|
7592
7598
|
const compressedSize = buf.readUInt32LE(p + 20);
|
|
@@ -7597,10 +7603,10 @@ var ZipPackage = class {
|
|
|
7597
7603
|
const localOffset = buf.readUInt32LE(p + 42);
|
|
7598
7604
|
const name = buf.toString(flags & 2048 ? "utf8" : "latin1", p + 46, p + 46 + nameLen);
|
|
7599
7605
|
p += 46 + nameLen + extraLen + commentLen;
|
|
7600
|
-
if (flags & 1) throw new UnsafeDocumentError("Encrypted documents can't be read.");
|
|
7601
|
-
if (compressedSize === 4294967295 || size === 4294967295 || localOffset === 4294967295) throw new UnsafeDocumentError("ZIP64 archives aren't supported.");
|
|
7602
|
-
if (!safeEntryName(name)) throw new UnsafeDocumentError("The package has an entry with a path outside the archive; it wasn't read.");
|
|
7603
|
-
if (this.entries.has(name)) throw new UnsafeDocumentError("The package lists the same entry twice; it wasn't read.");
|
|
7606
|
+
if (flags & 1) throw new UnsafeDocumentError("encrypted", "Encrypted documents can't be read.");
|
|
7607
|
+
if (compressedSize === 4294967295 || size === 4294967295 || localOffset === 4294967295) throw new UnsafeDocumentError("unsupported", "ZIP64 archives aren't supported.");
|
|
7608
|
+
if (!safeEntryName(name)) throw new UnsafeDocumentError("hostile", "The package has an entry with a path outside the archive; it wasn't read.");
|
|
7609
|
+
if (this.entries.has(name)) throw new UnsafeDocumentError("hostile", "The package lists the same entry twice; it wasn't read.");
|
|
7604
7610
|
if (name.endsWith("/")) continue;
|
|
7605
7611
|
this.entries.set(name, {
|
|
7606
7612
|
name,
|
|
@@ -7622,23 +7628,23 @@ var ZipPackage = class {
|
|
|
7622
7628
|
const e = this.entries.get(name);
|
|
7623
7629
|
if (!e) return null;
|
|
7624
7630
|
const b = this.buf;
|
|
7625
|
-
if (e.localOffset + 30 > b.length || b.readUInt32LE(e.localOffset) !== LFH) throw new UnsafeDocumentError("This zip package is corrupt (bad entry header).");
|
|
7631
|
+
if (e.localOffset + 30 > b.length || b.readUInt32LE(e.localOffset) !== LFH) throw new UnsafeDocumentError("corrupt", "This zip package is corrupt (bad entry header).");
|
|
7626
7632
|
const start = e.localOffset + 30 + b.readUInt16LE(e.localOffset + 26) + b.readUInt16LE(e.localOffset + 28);
|
|
7627
|
-
if (start + e.compressedSize > b.length) throw new UnsafeDocumentError("This zip package is corrupt (entry out of range).");
|
|
7633
|
+
if (start + e.compressedSize > b.length) throw new UnsafeDocumentError("corrupt", "This zip package is corrupt (entry out of range).");
|
|
7628
7634
|
const data = b.subarray(start, start + e.compressedSize);
|
|
7629
7635
|
const budget = Math.min(PARSE_LIMITS.zipEntryBytes, PARSE_LIMITS.zipTotalBytes - this.inflated);
|
|
7630
|
-
if (e.size > 1048576 && e.size / Math.max(1, e.compressedSize) > PARSE_LIMITS.zipRatio) throw new UnsafeDocumentError("The document looks like a zip bomb (it decompresses to far more than its size); it wasn't read.");
|
|
7631
|
-
if (e.size > budget) throw new UnsafeDocumentError("The document is too large to read once decompressed.");
|
|
7636
|
+
if (e.size > 1048576 && e.size / Math.max(1, e.compressedSize) > PARSE_LIMITS.zipRatio) throw new UnsafeDocumentError("hostile", "The document looks like a zip bomb (it decompresses to far more than its size); it wasn't read.");
|
|
7637
|
+
if (e.size > budget) throw new UnsafeDocumentError("too_complex", "The document is too large to read once decompressed.");
|
|
7632
7638
|
let out;
|
|
7633
7639
|
if (e.method === 0) out = data;
|
|
7634
7640
|
else if (e.method === 8) try {
|
|
7635
7641
|
out = inflateRawSync(data, { maxOutputLength: Math.max(1, budget) });
|
|
7636
7642
|
} catch (err) {
|
|
7637
|
-
if (err.code === "ERR_BUFFER_TOO_LARGE") throw new UnsafeDocumentError("The document looks like a zip bomb (it decompresses to far more than its size); it wasn't read.");
|
|
7638
|
-
throw new UnsafeDocumentError("This zip package is corrupt (an entry doesn't decompress).");
|
|
7643
|
+
if (err.code === "ERR_BUFFER_TOO_LARGE") throw new UnsafeDocumentError("hostile", "The document looks like a zip bomb (it decompresses to far more than its size); it wasn't read.");
|
|
7644
|
+
throw new UnsafeDocumentError("corrupt", "This zip package is corrupt (an entry doesn't decompress).");
|
|
7639
7645
|
}
|
|
7640
|
-
else throw new UnsafeDocumentError("The package uses a compression method that isn't supported.");
|
|
7641
|
-
if (out.length > budget) throw new UnsafeDocumentError("The document is too large to read once decompressed.");
|
|
7646
|
+
else throw new UnsafeDocumentError("unsupported", "The package uses a compression method that isn't supported.");
|
|
7647
|
+
if (out.length > budget) throw new UnsafeDocumentError("too_complex", "The document is too large to read once decompressed.");
|
|
7642
7648
|
this.inflated += out.length;
|
|
7643
7649
|
return out;
|
|
7644
7650
|
}
|
|
@@ -7684,7 +7690,7 @@ function extractDocx(input) {
|
|
|
7684
7690
|
const pkg = new ZipPackage(input);
|
|
7685
7691
|
const part = mainPart(pkg, "word/document.xml");
|
|
7686
7692
|
const xml = pkg.text(part);
|
|
7687
|
-
if (xml === null) throw new UnsafeDocumentError("The file isn't a Word document (no word/document.xml).");
|
|
7693
|
+
if (xml === null) throw new UnsafeDocumentError("corrupt", "The file isn't a Word document (no word/document.xml).");
|
|
7688
7694
|
const rels = relationships(pkg.text(relsPath(part)));
|
|
7689
7695
|
const byType = (suffix) => {
|
|
7690
7696
|
for (const r of rels.values()) if (r.type.endsWith(suffix) && !r.external) return resolvePart(part, r.target);
|
|
@@ -77743,8 +77749,8 @@ async function extractPdf(input) {
|
|
|
77743
77749
|
try {
|
|
77744
77750
|
doc = await task.promise;
|
|
77745
77751
|
} catch (err) {
|
|
77746
|
-
if (err.name === "PasswordException") throw new UnsafeDocumentError("The PDF is password-protected.");
|
|
77747
|
-
throw new UnsafeDocumentError("The file isn't a readable PDF.");
|
|
77752
|
+
if (err.name === "PasswordException") throw new UnsafeDocumentError("password", "The PDF is password-protected.");
|
|
77753
|
+
throw new UnsafeDocumentError("corrupt", "The file isn't a readable PDF.");
|
|
77748
77754
|
}
|
|
77749
77755
|
const totalPages = doc.numPages;
|
|
77750
77756
|
const pages = [];
|
|
@@ -77808,7 +77814,7 @@ function extractPptx(input) {
|
|
|
77808
77814
|
const pkg = new ZipPackage(input);
|
|
77809
77815
|
const part = mainPart(pkg, "ppt/presentation.xml");
|
|
77810
77816
|
const presXml = pkg.text(part);
|
|
77811
|
-
if (presXml === null) throw new UnsafeDocumentError("The file isn't a PowerPoint presentation (no ppt/presentation.xml).");
|
|
77817
|
+
if (presXml === null) throw new UnsafeDocumentError("corrupt", "The file isn't a PowerPoint presentation (no ppt/presentation.xml).");
|
|
77812
77818
|
const rels = relationships(pkg.text(relsPath(part)));
|
|
77813
77819
|
const pres = parseXml(presXml);
|
|
77814
77820
|
const ids = [...descendants(firstChild(pres, "sldIdLst") ?? pres, "sldId")];
|
|
@@ -77902,7 +77908,7 @@ function extractXlsx(input) {
|
|
|
77902
77908
|
const pkg = new ZipPackage(input);
|
|
77903
77909
|
const part = mainPart(pkg, "xl/workbook.xml");
|
|
77904
77910
|
const wbXml = pkg.text(part);
|
|
77905
|
-
if (wbXml === null) throw new UnsafeDocumentError("The file isn't an Excel workbook (no xl/workbook.xml).");
|
|
77911
|
+
if (wbXml === null) throw new UnsafeDocumentError("corrupt", "The file isn't an Excel workbook (no xl/workbook.xml).");
|
|
77906
77912
|
const wb = parseXml(wbXml);
|
|
77907
77913
|
const rels = relationships(pkg.text(relsPath(part)));
|
|
77908
77914
|
const relOf = (suffix) => {
|
|
@@ -78064,6 +78070,7 @@ function cleanParts(r, clean) {
|
|
|
78064
78070
|
async function run(input, task) {
|
|
78065
78071
|
if (input.length > PARSE_LIMITS.inputBytes) return {
|
|
78066
78072
|
ok: false,
|
|
78073
|
+
code: "too_complex",
|
|
78067
78074
|
error: "The file is too large to read."
|
|
78068
78075
|
};
|
|
78069
78076
|
try {
|
|
@@ -78086,6 +78093,7 @@ async function run(input, task) {
|
|
|
78086
78093
|
break;
|
|
78087
78094
|
default: return {
|
|
78088
78095
|
ok: false,
|
|
78096
|
+
code: "parser_error",
|
|
78089
78097
|
error: "Unknown format."
|
|
78090
78098
|
};
|
|
78091
78099
|
}
|
|
@@ -78094,9 +78102,15 @@ async function run(input, task) {
|
|
|
78094
78102
|
result: cleanAll(result)
|
|
78095
78103
|
};
|
|
78096
78104
|
} catch (err) {
|
|
78105
|
+
if (err instanceof UnsafeDocumentError) return {
|
|
78106
|
+
ok: false,
|
|
78107
|
+
code: err.code,
|
|
78108
|
+
error: err.message
|
|
78109
|
+
};
|
|
78097
78110
|
return {
|
|
78098
78111
|
ok: false,
|
|
78099
|
-
|
|
78112
|
+
code: "parser_error",
|
|
78113
|
+
error: "The file couldn't be parsed."
|
|
78100
78114
|
};
|
|
78101
78115
|
}
|
|
78102
78116
|
}
|
|
@@ -74,7 +74,7 @@ Example:
|
|
|
74
74
|
const { openAdminChannel } = await import("./admin-DbJXEo2d.js");
|
|
75
75
|
const { TerminalPrompter } = await import("./prompt-BEH311I7.js");
|
|
76
76
|
const { connectAgentMcp } = await import("./agent-B8a-Ivyd.js");
|
|
77
|
-
const { SmokeRun } = await import("./run-
|
|
77
|
+
const { SmokeRun } = await import("./run-zakbVgQR.js");
|
|
78
78
|
const { renderReport } = await import("./report-D2D_6E5O.js");
|
|
79
79
|
if (!o.scratch) throw new Error("smoke google-workspace runs live on the connected Google account: it reads existing files and mail, and writes only into a new scratch folder and ONE draft to the account itself, which it removes again. Pass --scratch to run it.");
|
|
80
80
|
const out = safeWriter(o.json ? process.stderr : process.stdout);
|
|
@@ -123,4 +123,4 @@ Example:
|
|
|
123
123
|
//#endregion
|
|
124
124
|
export { registerSmokeCommands };
|
|
125
125
|
|
|
126
|
-
//# sourceMappingURL=cli-
|
|
126
|
+
//# sourceMappingURL=cli-Wp2ejVBT.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli-yN8ExqrA.js","names":[],"sources":["../server/smoke/cli.ts"],"sourcesContent":["// `xaq smoke google-workspace --scratch`: run on xaq's machine, through the daemon's admin API over the local admin\n// channel (server/connections-cli/admin.ts) and /mcp as a temporary agent. See run.ts for what it does.\n\nimport type { Command } from \"commander\";\nimport { LEGACY_PRODUCT } from \"../brand.ts\";\nimport { resolveDataDir } from \"../config.ts\";\nimport { NPX } from \"../listen/version.ts\";\nimport type { SmokeFlags } from \"./run.ts\";\n\nconst GROUP = \"MCP connections (on xaq's machine):\";\n\n/**\n * A line printer that can't kill the run: once the stream fails (EIO or EPIPE from a terminal that went away, e.g.\n * after SIGHUP), later lines are dropped instead of throwing or crashing on an unhandled 'error'.\n */\nexport function safeWriter(stream: NodeJS.WritableStream): { print: (line?: string) => void; dispose: () => void } {\n let dead = false;\n const onError = () => {\n dead = true;\n };\n stream.on(\"error\", onError);\n return {\n print(line = \"\") {\n if (dead) return;\n try {\n stream.write(`${line}\\n`, (err) => {\n if (err) dead = true;\n });\n } catch {\n dead = true;\n }\n },\n dispose: () => stream.removeListener(\"error\", onError),\n };\n}\n\nexport interface SmokeSignalOptions {\n print: (line: string) => void;\n exit: (code: number) => void;\n /** The second Ctrl-C: the quick part of cleanup (agent, attachmentDirs), bounded. */\n fastCleanup: () => Promise<void>;\n /** What may still be there, in a sentence. */\n leftovers: () => string;\n /** Upper bound on the second Ctrl-C's cleanup before quitting anyway (default 8 s). */\n quitWithinMs?: number;\n}\n\n/**\n * Signals during a run. SIGTERM and SIGHUP (a closed terminal, a stopped service) always act like the first Ctrl-C:\n * no new step starts and the run's full cleanup runs. The first Ctrl-C does the same; a second one runs the quick\n * cleanup (delete the agent, restore attachmentDirs) with a short timeout, says what may be left and quits.\n * Returns the remover.\n */\nexport function installSmokeSignals(controller: AbortController, opts: SmokeSignalOptions): () => void {\n let interrupts = 0;\n let quitting = false;\n const stop = (why: string) => {\n if (controller.signal.aborted) return;\n opts.print(`\\n${why}: stopping after the current call, then cleaning up.${why === \"Ctrl-C\" ? \" Press Ctrl-C again to quit sooner (only the agent and attachmentDirs are cleaned up then).\" : \"\"}`);\n controller.abort();\n };\n const onInt = () => {\n interrupts++;\n if (interrupts === 1) return stop(\"Ctrl-C\");\n if (quitting) return;\n quitting = true;\n opts.print(\"\\nQuitting: deleting the temporary agent and restoring attachmentDirs first…\");\n const deadline = new Promise<void>((r) => setTimeout(r, opts.quitWithinMs ?? 8000).unref?.());\n void Promise.race([opts.fastCleanup().catch(() => {}), deadline]).then(() => {\n opts.print(`Quit before the cleanup finished. ${opts.leftovers()}`);\n opts.exit(130);\n });\n };\n const onTerm = () => stop(\"SIGTERM\");\n const onHup = () => stop(\"SIGHUP\");\n process.on(\"SIGINT\", onInt);\n process.on(\"SIGTERM\", onTerm);\n process.on(\"SIGHUP\", onHup);\n return () => {\n process.removeListener(\"SIGINT\", onInt);\n process.removeListener(\"SIGTERM\", onTerm);\n process.removeListener(\"SIGHUP\", onHup);\n };\n}\n\nexport function registerSmokeCommands(program: Command): void {\n const smoke = program\n .command(\"smoke\")\n .description(\"live end-to-end checks against the connected accounts, safe by construction\")\n .summary(\"live end-to-end check of a connection\")\n .helpGroup(GROUP);\n\n smoke\n .command(\"google-workspace\")\n .description(\"check Gmail attachments and the google-workspace extension end to end on the real account: reads anything, writes only into a new scratch folder and ONE draft to itself, cleans up\")\n .option(\"--scratch\", \"required: confirms the run may create its scratch folder, its draft and its temporary agent (all removed again)\")\n .option(\"--account <email>\", \"the Gmail account (default: the only one, or the one the Workspace connection is signed in as)\")\n .option(\"--connection <slug>\", \"the google-workspace connection (default: the only one)\")\n .option(\"--json\", \"print the result table as JSON on stdout (progress goes to stderr)\")\n .option(\"-y, --yes\", \"don't ask before running\")\n .option(\"-d, --data <dir>\", `xaq's data directory (default ~/.xaq, or an existing ~/.${LEGACY_PRODUCT}; env XAQ_DATA)`)\n .addHelpText(\n \"after\",\n `\nPrints the plan first and asks (or --yes). Exit code 0 only when every step passes and nothing is left behind.\n\nExample:\n ${NPX} smoke google-workspace --scratch`,\n )\n .action(async (o: SmokeFlags & { data?: string }) => {\n const { openAdminChannel } = await import(\"../connections-cli/admin.ts\");\n const { TerminalPrompter } = await import(\"../connections-cli/prompt.ts\");\n const { connectAgentMcp } = await import(\"./agent.ts\");\n const { SmokeRun } = await import(\"./run.ts\");\n const { renderReport } = await import(\"./report.ts\");\n if (!o.scratch) {\n throw new Error(\n \"smoke google-workspace runs live on the connected Google account: it reads existing files and mail, and writes only into a new scratch folder and ONE draft to the account itself, which it removes again. Pass --scratch to run it.\",\n );\n }\n const out = safeWriter(o.json ? process.stderr : process.stdout);\n const err = o.json ? out : safeWriter(process.stderr);\n const json = o.json ? safeWriter(process.stdout) : null;\n const channel = openAdminChannel({ dataDir: resolveDataDir(o.data, { quiet: true }) });\n const prompter = new TerminalPrompter(!!o.yes, { input: process.stdin, output: o.json ? process.stderr : process.stdout });\n const controller = new AbortController();\n const run = new SmokeRun(o, {\n admin: channel,\n connectMcp: (key) => connectAgentMcp(channel.baseUrl, key),\n print: out.print,\n confirm: (q) => prompter.confirm(q, false),\n signal: controller.signal,\n });\n const remove = installSmokeSignals(controller, {\n print: (l) => err.print(l),\n fastCleanup: () => run.fastCleanup(3000),\n exit: (code) => {\n channel.close();\n process.exit(code);\n },\n leftovers: () => {\n const left = run.pendingLeftovers();\n return left.length ? `These may be left: ${left.join(\"; \")}. The next run removes the agent and the attachmentDirs entry and lists the rest.` : \"Nothing was left.\";\n },\n });\n try {\n const report = await run.run();\n if (json) json.print(JSON.stringify(report, null, 2));\n else for (const l of [\"\", ...renderReport(report)]) out.print(l);\n process.exitCode = report.ok ? 0 : report.interrupted ? 130 : 1;\n } finally {\n remove();\n prompter.close();\n channel.close();\n out.dispose();\n if (err !== out) err.dispose();\n json?.dispose();\n }\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli-Wp2ejVBT.js","names":[],"sources":["../server/smoke/cli.ts"],"sourcesContent":["// `xaq smoke google-workspace --scratch`: run on xaq's machine, through the daemon's admin API over the local admin\n// channel (server/connections-cli/admin.ts) and /mcp as a temporary agent. See run.ts for what it does.\n\nimport type { Command } from \"commander\";\nimport { LEGACY_PRODUCT } from \"../brand.ts\";\nimport { resolveDataDir } from \"../config.ts\";\nimport { NPX } from \"../listen/version.ts\";\nimport type { SmokeFlags } from \"./run.ts\";\n\nconst GROUP = \"MCP connections (on xaq's machine):\";\n\n/**\n * A line printer that can't kill the run: once the stream fails (EIO or EPIPE from a terminal that went away, e.g.\n * after SIGHUP), later lines are dropped instead of throwing or crashing on an unhandled 'error'.\n */\nexport function safeWriter(stream: NodeJS.WritableStream): { print: (line?: string) => void; dispose: () => void } {\n let dead = false;\n const onError = () => {\n dead = true;\n };\n stream.on(\"error\", onError);\n return {\n print(line = \"\") {\n if (dead) return;\n try {\n stream.write(`${line}\\n`, (err) => {\n if (err) dead = true;\n });\n } catch {\n dead = true;\n }\n },\n dispose: () => stream.removeListener(\"error\", onError),\n };\n}\n\nexport interface SmokeSignalOptions {\n print: (line: string) => void;\n exit: (code: number) => void;\n /** The second Ctrl-C: the quick part of cleanup (agent, attachmentDirs), bounded. */\n fastCleanup: () => Promise<void>;\n /** What may still be there, in a sentence. */\n leftovers: () => string;\n /** Upper bound on the second Ctrl-C's cleanup before quitting anyway (default 8 s). */\n quitWithinMs?: number;\n}\n\n/**\n * Signals during a run. SIGTERM and SIGHUP (a closed terminal, a stopped service) always act like the first Ctrl-C:\n * no new step starts and the run's full cleanup runs. The first Ctrl-C does the same; a second one runs the quick\n * cleanup (delete the agent, restore attachmentDirs) with a short timeout, says what may be left and quits.\n * Returns the remover.\n */\nexport function installSmokeSignals(controller: AbortController, opts: SmokeSignalOptions): () => void {\n let interrupts = 0;\n let quitting = false;\n const stop = (why: string) => {\n if (controller.signal.aborted) return;\n opts.print(`\\n${why}: stopping after the current call, then cleaning up.${why === \"Ctrl-C\" ? \" Press Ctrl-C again to quit sooner (only the agent and attachmentDirs are cleaned up then).\" : \"\"}`);\n controller.abort();\n };\n const onInt = () => {\n interrupts++;\n if (interrupts === 1) return stop(\"Ctrl-C\");\n if (quitting) return;\n quitting = true;\n opts.print(\"\\nQuitting: deleting the temporary agent and restoring attachmentDirs first…\");\n const deadline = new Promise<void>((r) => setTimeout(r, opts.quitWithinMs ?? 8000).unref?.());\n void Promise.race([opts.fastCleanup().catch(() => {}), deadline]).then(() => {\n opts.print(`Quit before the cleanup finished. ${opts.leftovers()}`);\n opts.exit(130);\n });\n };\n const onTerm = () => stop(\"SIGTERM\");\n const onHup = () => stop(\"SIGHUP\");\n process.on(\"SIGINT\", onInt);\n process.on(\"SIGTERM\", onTerm);\n process.on(\"SIGHUP\", onHup);\n return () => {\n process.removeListener(\"SIGINT\", onInt);\n process.removeListener(\"SIGTERM\", onTerm);\n process.removeListener(\"SIGHUP\", onHup);\n };\n}\n\nexport function registerSmokeCommands(program: Command): void {\n const smoke = program\n .command(\"smoke\")\n .description(\"live end-to-end checks against the connected accounts, safe by construction\")\n .summary(\"live end-to-end check of a connection\")\n .helpGroup(GROUP);\n\n smoke\n .command(\"google-workspace\")\n .description(\"check Gmail attachments and the google-workspace extension end to end on the real account: reads anything, writes only into a new scratch folder and ONE draft to itself, cleans up\")\n .option(\"--scratch\", \"required: confirms the run may create its scratch folder, its draft and its temporary agent (all removed again)\")\n .option(\"--account <email>\", \"the Gmail account (default: the only one, or the one the Workspace connection is signed in as)\")\n .option(\"--connection <slug>\", \"the google-workspace connection (default: the only one)\")\n .option(\"--json\", \"print the result table as JSON on stdout (progress goes to stderr)\")\n .option(\"-y, --yes\", \"don't ask before running\")\n .option(\"-d, --data <dir>\", `xaq's data directory (default ~/.xaq, or an existing ~/.${LEGACY_PRODUCT}; env XAQ_DATA)`)\n .addHelpText(\n \"after\",\n `\nPrints the plan first and asks (or --yes). Exit code 0 only when every step passes and nothing is left behind.\n\nExample:\n ${NPX} smoke google-workspace --scratch`,\n )\n .action(async (o: SmokeFlags & { data?: string }) => {\n const { openAdminChannel } = await import(\"../connections-cli/admin.ts\");\n const { TerminalPrompter } = await import(\"../connections-cli/prompt.ts\");\n const { connectAgentMcp } = await import(\"./agent.ts\");\n const { SmokeRun } = await import(\"./run.ts\");\n const { renderReport } = await import(\"./report.ts\");\n if (!o.scratch) {\n throw new Error(\n \"smoke google-workspace runs live on the connected Google account: it reads existing files and mail, and writes only into a new scratch folder and ONE draft to the account itself, which it removes again. Pass --scratch to run it.\",\n );\n }\n const out = safeWriter(o.json ? process.stderr : process.stdout);\n const err = o.json ? out : safeWriter(process.stderr);\n const json = o.json ? safeWriter(process.stdout) : null;\n const channel = openAdminChannel({ dataDir: resolveDataDir(o.data, { quiet: true }) });\n const prompter = new TerminalPrompter(!!o.yes, { input: process.stdin, output: o.json ? process.stderr : process.stdout });\n const controller = new AbortController();\n const run = new SmokeRun(o, {\n admin: channel,\n connectMcp: (key) => connectAgentMcp(channel.baseUrl, key),\n print: out.print,\n confirm: (q) => prompter.confirm(q, false),\n signal: controller.signal,\n });\n const remove = installSmokeSignals(controller, {\n print: (l) => err.print(l),\n fastCleanup: () => run.fastCleanup(3000),\n exit: (code) => {\n channel.close();\n process.exit(code);\n },\n leftovers: () => {\n const left = run.pendingLeftovers();\n return left.length ? `These may be left: ${left.join(\"; \")}. The next run removes the agent and the attachmentDirs entry and lists the rest.` : \"Nothing was left.\";\n },\n });\n try {\n const report = await run.run();\n if (json) json.print(JSON.stringify(report, null, 2));\n else for (const l of [\"\", ...renderReport(report)]) out.print(l);\n process.exitCode = report.ok ? 0 : report.interrupted ? 130 : 1;\n } finally {\n remove();\n prompter.close();\n channel.close();\n out.dispose();\n if (err !== out) err.dispose();\n json?.dispose();\n }\n });\n}\n"],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -59,7 +59,7 @@ const SERVER_GROUP = "Run xaq:";
|
|
|
59
59
|
const AGENT_GROUP = "On an agent's machine:";
|
|
60
60
|
withServerOptions(program.command("start", { isDefault: true }).description("run xaq in this terminal and print the setup link (the default command)").summary("run it in this terminal (the default command)").helpGroup(SERVER_GROUP)).option("--no-open", "don't open a browser on first run").option("--dev", "development: API only, the console runs on Vite (:5173)").allowExcessArguments().action(async (o, cmd) => {
|
|
61
61
|
if (cmd.args.length) throw new Error(unknownCommand(cmd.args[0]));
|
|
62
|
-
const { runForeground } = await import("./server-
|
|
62
|
+
const { runForeground } = await import("./server-0wxSpx6s.js");
|
|
63
63
|
await runForeground({
|
|
64
64
|
...configInput(o),
|
|
65
65
|
dev: !!o.dev,
|
|
@@ -144,7 +144,7 @@ const { registerConnectionsCommands } = await import("./commands-CgmkYCYG.js");
|
|
|
144
144
|
registerConnectionsCommands(program);
|
|
145
145
|
const { registerExtensionCommands } = await import("./cli-B8cuKWiS.js");
|
|
146
146
|
registerExtensionCommands(program);
|
|
147
|
-
const { registerSmokeCommands } = await import("./cli-
|
|
147
|
+
const { registerSmokeCommands } = await import("./cli-Wp2ejVBT.js");
|
|
148
148
|
registerSmokeCommands(program);
|
|
149
149
|
const { registerSettingsCommands } = await import("./settings-cli-DfWc-yWJ.js");
|
|
150
150
|
registerSettingsCommands(program);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { r as cleanLine } from "./content-Di_U9ZZ6.js";
|
|
2
3
|
import { t as NPX } from "./version-BxH7OuWD.js";
|
|
3
|
-
import { a as smokeSubjectPrefix, f as PARSE_LIMITS, i as smokeName, n as SMOKE_TEMP_RE, s as attachmentKind, t as SMOKE_NAME_RE } from "./smoke-
|
|
4
|
+
import { a as smokeSubjectPrefix, f as PARSE_LIMITS, i as smokeName, n as SMOKE_TEMP_RE, s as attachmentKind, t as SMOKE_NAME_RE } from "./smoke-ZqEGwDQl.js";
|
|
4
5
|
import "./agent-mYT2noTp.js";
|
|
5
6
|
import { t as finish } from "./report-BLbtzUlC.js";
|
|
6
7
|
import { chmodSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
@@ -212,13 +213,14 @@ const MAIL_TOOLS = [
|
|
|
212
213
|
"create_draft"
|
|
213
214
|
];
|
|
214
215
|
/**
|
|
215
|
-
* What get_attachment (server/mcp/attachments.ts)
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
216
|
+
* What get_attachment (server/mcp/attachments.ts) can show inline, from its own rules: attachmentKind() on the listed
|
|
217
|
+
* type and name, images under MAX_ATTACHMENT_BYTES, PDF/DOCX/XLSX/PPTX/CSV under PARSE_LIMITS.inputBytes, text and
|
|
218
|
+
* HTML at any size (it reads the first part). The run asks with format "inline", which never saves; for anything
|
|
219
|
+
* else that would only download the owner's file to answer "not shown", so the run doesn't ask. The bytes aren't
|
|
220
|
+
* known here: attachmentKind's content sniff (a PDF sent as octet-stream without an extension) sees an empty buffer
|
|
221
|
+
* and says "other", which skips the file.
|
|
220
222
|
*/
|
|
221
|
-
const
|
|
223
|
+
const SKIPPED_NOT_INLINE = "skipped (can't be shown inline)";
|
|
222
224
|
/** The kind get_attachment would show inline, or null when it would save the file to disk (so the run skips it). */
|
|
223
225
|
function inlineKind(a) {
|
|
224
226
|
const kind = attachmentKind(a.mimeType, a.filename, Buffer.alloc(0));
|
|
@@ -297,7 +299,7 @@ function planLines(marker, account, connection) {
|
|
|
297
299
|
"Reads (existing files and mail, read only; prints names, the subject, kinds and sizes, never content):",
|
|
298
300
|
" - Drive: search the newest Doc, Sheet and Slides file, list the top of My Drive, get, read and export each",
|
|
299
301
|
" - Gmail: ONE email with attachments: list its attachments and extract each one get_attachment shows",
|
|
300
|
-
" inline (anything it
|
|
302
|
+
" inline, asking it never to save a copy (anything it can't show inline is skipped)",
|
|
301
303
|
`Writes (only inside a new Drive folder "${marker}" at the top of My Drive; every write is checked first):`,
|
|
302
304
|
" - Docs: create a doc from markdown, append, find and replace (count checked), comment, read back",
|
|
303
305
|
" - Sheets: create a sheet, write a range, append rows, read back",
|
|
@@ -363,6 +365,27 @@ function describeExtraction(out) {
|
|
|
363
365
|
if (res?.resource?.blob !== void 0) return `binary, ${res.resource.blob.length} chars (base64)`;
|
|
364
366
|
return `text, ${blocks.filter((b) => b.type === "text").map((b) => b.text ?? "").join("\n").split("\n").slice(1).join("\n").length} chars`;
|
|
365
367
|
}
|
|
368
|
+
/**
|
|
369
|
+
* Parse failures that are the file's own doing: the smoke passes them as "couldn't read (…)". Every other one (corrupt,
|
|
370
|
+
* a parser that's missing, crashed, timed out, busy, …) may be xaq's fault, and this run is what would notice.
|
|
371
|
+
*/
|
|
372
|
+
const FILE_OWN_PARSE_FAILURES = /* @__PURE__ */ new Set(["password", "encrypted"]);
|
|
373
|
+
/**
|
|
374
|
+
* A format "inline" answer that didn't show the file (structuredContent.attachment.notShown): its code, the parser's
|
|
375
|
+
* failure code when it couldn't read it, and the reason without a closing period. The reason is xaq's own words (the
|
|
376
|
+
* parser's fixed messages), never the file's content, unless the parser child is subverted (it's cleaned anyway).
|
|
377
|
+
*/
|
|
378
|
+
function notShownOf(out) {
|
|
379
|
+
const a = out.structured?.attachment;
|
|
380
|
+
if (!a || typeof a.notShown !== "string" || !a.notShown) return null;
|
|
381
|
+
const reason = typeof a.reason === "string" ? cleanLine(a.reason, 200).replace(/\.$/, "") : "";
|
|
382
|
+
const parseFailure = typeof a.parseFailure === "string" && a.parseFailure ? cleanLine(a.parseFailure, 40) : null;
|
|
383
|
+
return {
|
|
384
|
+
code: cleanLine(a.notShown, 40),
|
|
385
|
+
parseFailure,
|
|
386
|
+
reason: reason || "no reason given"
|
|
387
|
+
};
|
|
388
|
+
}
|
|
366
389
|
/** A get_attachment answer that saved the file to disk (A2: structuredContent.attachment.savedPath). */
|
|
367
390
|
function savedPathOf(out) {
|
|
368
391
|
const a = out.structured?.attachment;
|
|
@@ -757,12 +780,13 @@ var SmokeRun = class {
|
|
|
757
780
|
});
|
|
758
781
|
for (const a of attachments.slice(0, 10)) await this.step("get_attachment", `extract "${a.filename}"`, async () => {
|
|
759
782
|
const kind = inlineKind(a);
|
|
760
|
-
if (!kind) return
|
|
783
|
+
if (!kind) return SKIPPED_NOT_INLINE;
|
|
761
784
|
const m = message;
|
|
762
785
|
const out = await this.rawCall("get_attachment", {
|
|
763
786
|
account,
|
|
764
787
|
message_id: m.id,
|
|
765
|
-
attachment_id: a.id
|
|
788
|
+
attachment_id: a.id,
|
|
789
|
+
format: "inline"
|
|
766
790
|
});
|
|
767
791
|
const saved = savedPathOf(out);
|
|
768
792
|
if (saved) {
|
|
@@ -770,6 +794,12 @@ var SmokeRun = class {
|
|
|
770
794
|
throw new Error(`get_attachment saved "${a.filename}" to disk (${saved}) instead of showing it`);
|
|
771
795
|
}
|
|
772
796
|
if (out.isError) throw new Error(out.text);
|
|
797
|
+
const not = notShownOf(out);
|
|
798
|
+
if (not?.code === "unreadable") {
|
|
799
|
+
if (not.parseFailure && FILE_OWN_PARSE_FAILURES.has(not.parseFailure)) return `${kind}: couldn't read (${not.reason})`;
|
|
800
|
+
throw new Error(`get_attachment couldn't read "${a.filename}" (${not.parseFailure ?? "no failure code"}: ${not.reason})`);
|
|
801
|
+
}
|
|
802
|
+
if (not) return `${kind}: not shown inline (${not.reason})`;
|
|
773
803
|
return `${kind}: ${describeExtraction(out)}`;
|
|
774
804
|
});
|
|
775
805
|
}
|
|
@@ -1222,4 +1252,4 @@ var SmokeRun = class {
|
|
|
1222
1252
|
//#endregion
|
|
1223
1253
|
export { SmokeRun };
|
|
1224
1254
|
|
|
1225
|
-
//# sourceMappingURL=run-
|
|
1255
|
+
//# sourceMappingURL=run-zakbVgQR.js.map
|