@agentdocumentformat/cli 0.7.2
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/LICENSE +21 -0
- package/README.md +45 -0
- package/dist/adf.mjs +162 -0
- package/dist/chunks/APEv2Parser-2CNNHMXV.mjs +22 -0
- package/dist/chunks/AiffParser-7BR4RL6G.mjs +162 -0
- package/dist/chunks/App-L5L37JI5.mjs +16747 -0
- package/dist/chunks/AsfParser-VVRSLFOM.mjs +538 -0
- package/dist/chunks/DsdiffParser-CMD4AX7A.mjs +156 -0
- package/dist/chunks/DsfParser-UOGZDS2M.mjs +94 -0
- package/dist/chunks/FlacParser-HB3T3CD4.mjs +23 -0
- package/dist/chunks/MP4Parser-66HBRUSX.mjs +973 -0
- package/dist/chunks/MatroskaParser-J3HUIMEW.mjs +579 -0
- package/dist/chunks/MpegParser-YW6LIUEW.mjs +442 -0
- package/dist/chunks/MusepackParser-PAUSXP7D.mjs +252 -0
- package/dist/chunks/OggParser-NUBV3GWC.mjs +359 -0
- package/dist/chunks/WavPackParser-WQAGT7TC.mjs +171 -0
- package/dist/chunks/WaveParser-EWLQVDX4.mjs +238 -0
- package/dist/chunks/adf-schema-JRU2BTOG.mjs +62 -0
- package/dist/chunks/build-QF5W473T.mjs +60 -0
- package/dist/chunks/chatgpt-subscription-BQGWYZXE.mjs +23 -0
- package/dist/chunks/chunk-2DIECGFL.mjs +6 -0
- package/dist/chunks/chunk-2NAP2ZF7.mjs +60 -0
- package/dist/chunks/chunk-3AT7IIP7.mjs +25696 -0
- package/dist/chunks/chunk-3GNIDM53.mjs +35 -0
- package/dist/chunks/chunk-3M4ZPOA3.mjs +1477 -0
- package/dist/chunks/chunk-4MQPTCXY.mjs +510 -0
- package/dist/chunks/chunk-5PO5PKRV.mjs +366 -0
- package/dist/chunks/chunk-5QU5VY7N.mjs +52 -0
- package/dist/chunks/chunk-5W7KES6P.mjs +436 -0
- package/dist/chunks/chunk-62NE4LG4.mjs +61 -0
- package/dist/chunks/chunk-64OKE4X3.mjs +45 -0
- package/dist/chunks/chunk-6GJ4QHRI.mjs +1278 -0
- package/dist/chunks/chunk-6VQEXLE2.mjs +144 -0
- package/dist/chunks/chunk-6XUV6EK6.mjs +41 -0
- package/dist/chunks/chunk-776TSXEP.mjs +43 -0
- package/dist/chunks/chunk-7CV2SA3H.mjs +285 -0
- package/dist/chunks/chunk-7MTRVW2U.mjs +3103 -0
- package/dist/chunks/chunk-7N2R2VCG.mjs +1434 -0
- package/dist/chunks/chunk-7T2YMTWL.mjs +374 -0
- package/dist/chunks/chunk-7XYNI52E.mjs +149 -0
- package/dist/chunks/chunk-APHX6HSV.mjs +7731 -0
- package/dist/chunks/chunk-APRU6PHC.mjs +40 -0
- package/dist/chunks/chunk-AUYKRQPJ.mjs +499 -0
- package/dist/chunks/chunk-DXWDD5XJ.mjs +136 -0
- package/dist/chunks/chunk-EWL5FG4I.mjs +39 -0
- package/dist/chunks/chunk-EYX2YLJW.mjs +117 -0
- package/dist/chunks/chunk-F5ELW46V.mjs +27 -0
- package/dist/chunks/chunk-G426FACA.mjs +49 -0
- package/dist/chunks/chunk-GEKSJHIV.mjs +2472 -0
- package/dist/chunks/chunk-HBUENVQL.mjs +570 -0
- package/dist/chunks/chunk-HPVFJYXR.mjs +112 -0
- package/dist/chunks/chunk-HWLWPXQ7.mjs +721 -0
- package/dist/chunks/chunk-I2HJYHFT.mjs +665 -0
- package/dist/chunks/chunk-I3UYSITV.mjs +68 -0
- package/dist/chunks/chunk-IC4JDZCI.mjs +3864 -0
- package/dist/chunks/chunk-IHDDCZBS.mjs +112 -0
- package/dist/chunks/chunk-IIGMZHQQ.mjs +10416 -0
- package/dist/chunks/chunk-KWFOS5WC.mjs +41 -0
- package/dist/chunks/chunk-LAZIXV2F.mjs +5290 -0
- package/dist/chunks/chunk-LFMB4PAA.mjs +10814 -0
- package/dist/chunks/chunk-M2RX3S2T.mjs +49 -0
- package/dist/chunks/chunk-M3WPIJYM.mjs +65 -0
- package/dist/chunks/chunk-NBTUWMGP.mjs +263 -0
- package/dist/chunks/chunk-NGK6PVSF.mjs +2676 -0
- package/dist/chunks/chunk-NZ7JTHEO.mjs +85 -0
- package/dist/chunks/chunk-OQN33RGQ.mjs +52 -0
- package/dist/chunks/chunk-RIX34QHC.mjs +2935 -0
- package/dist/chunks/chunk-SGUTYISF.mjs +16 -0
- package/dist/chunks/chunk-SLJ5L7PY.mjs +1473 -0
- package/dist/chunks/chunk-SLUKGXKH.mjs +68 -0
- package/dist/chunks/chunk-TMT32MXT.mjs +419 -0
- package/dist/chunks/chunk-UYY5M2PB.mjs +151 -0
- package/dist/chunks/chunk-VAUOM3IN.mjs +298 -0
- package/dist/chunks/chunk-VMHTYDH4.mjs +172 -0
- package/dist/chunks/chunk-VZS54AA5.mjs +200 -0
- package/dist/chunks/chunk-WFQUVXML.mjs +295 -0
- package/dist/chunks/chunk-X34PDRUH.mjs +809 -0
- package/dist/chunks/chunk-XNPZBLYL.mjs +33 -0
- package/dist/chunks/chunk-XX7OSWTO.mjs +157 -0
- package/dist/chunks/chunk-XYTWGHFS.mjs +600 -0
- package/dist/chunks/chunk-XYV3VCIX.mjs +336 -0
- package/dist/chunks/chunk-Y5ZLKFLE.mjs +1469 -0
- package/dist/chunks/chunk-ZJKXHM4Q.mjs +230 -0
- package/dist/chunks/cli-TCMBNZF6.mjs +1030 -0
- package/dist/chunks/commands-DTEKM4ZL.mjs +20 -0
- package/dist/chunks/daemon-ZC3FZ2P4.mjs +112449 -0
- package/dist/chunks/daemon-control-WDX7PMTC.mjs +300 -0
- package/dist/chunks/daemon-token-WRSULP7G.mjs +23 -0
- package/dist/chunks/devtools-H7CI2ZVG.mjs +85 -0
- package/dist/chunks/discord-IWDQJA7A.mjs +70588 -0
- package/dist/chunks/dist-C57KYFS2.mjs +4952 -0
- package/dist/chunks/dist-I3XBAGDL.mjs +11 -0
- package/dist/chunks/email-V4Q4VJON.mjs +58679 -0
- package/dist/chunks/environment-NX277MQG.mjs +15 -0
- package/dist/chunks/grok-subscription-4PYX7TBF.mjs +19 -0
- package/dist/chunks/layout-EPJQNFAI.mjs +22 -0
- package/dist/chunks/lib-N7FCZWKH.mjs +4038 -0
- package/dist/chunks/open-OTQDGDH5.mjs +36 -0
- package/dist/chunks/parser-JVY2PWSM.mjs +19 -0
- package/dist/chunks/pipeline-executor-BHBSJX2K.mjs +19 -0
- package/dist/chunks/react-IUIDQ5LX.mjs +11 -0
- package/dist/chunks/screen-54BUTYCW.mjs +27 -0
- package/dist/chunks/slack-3L5QTDZX.mjs +8113 -0
- package/dist/chunks/store-WR7TZJZS.mjs +32 -0
- package/dist/chunks/telegram-5ISVFBV3.mjs +14918 -0
- package/dist/chunks/terminal-O5WO7C5U.mjs +48 -0
- package/dist/chunks/theme-NORU4J74.mjs +20 -0
- package/dist/chunks/themes-F2FKU7CP.mjs +23 -0
- package/dist/chunks/token-6G3HCMNX.mjs +54 -0
- package/dist/chunks/token-util-UXYSDQIW.mjs +12 -0
- package/dist/chunks/tui-ODVYUIMT.mjs +197 -0
- package/dist/chunks/welcome-XHXFV7SH.mjs +32 -0
- package/dist/chunks/whatsapp-XSAMNGPJ.mjs +85412 -0
- package/dist/chunks/xterm-headless-YF53XWXE.mjs +3353 -0
- package/package.json +53 -0
- package/resources/wasm/coreutils.LICENSE +18 -0
- package/resources/wasm/coreutils.wasm +0 -0
|
@@ -0,0 +1,3103 @@
|
|
|
1
|
+
import { createRequire as __adfCreateRequire } from 'node:module';
|
|
2
|
+
import { fileURLToPath as __adfFileURLToPath } from 'node:url';
|
|
3
|
+
import { dirname as __adfDirname } from 'node:path';
|
|
4
|
+
const require = __adfCreateRequire(import.meta.url);
|
|
5
|
+
const __filename = __adfFileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = __adfDirname(__filename);
|
|
7
|
+
import {
|
|
8
|
+
META_PROTECTION_LEVELS,
|
|
9
|
+
SETTABLE_STATES
|
|
10
|
+
} from "./chunk-WFQUVXML.mjs";
|
|
11
|
+
import {
|
|
12
|
+
__esm
|
|
13
|
+
} from "./chunk-M2RX3S2T.mjs";
|
|
14
|
+
|
|
15
|
+
// src/main/runtime/execution-context.ts
|
|
16
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
17
|
+
function withSource(source, agentIdOrFn, maybeFn) {
|
|
18
|
+
let agentId = typeof agentIdOrFn == "function" ? void 0 : agentIdOrFn, fn = typeof agentIdOrFn == "function" ? agentIdOrFn : maybeFn, parent = store.getStore(), loop = parent?.loop && (agentId === void 0 || agentId === parent.agentId) ? parent.loop : void 0;
|
|
19
|
+
return store.run(loop ? { source, agentId, loop } : { source, agentId }, fn);
|
|
20
|
+
}
|
|
21
|
+
function withLoop(loop, fn) {
|
|
22
|
+
let parent = store.getStore();
|
|
23
|
+
return store.run({ source: parent?.source ?? "system:unknown", agentId: parent?.agentId, loop }, fn);
|
|
24
|
+
}
|
|
25
|
+
function currentLoop() {
|
|
26
|
+
return store.getStore()?.loop;
|
|
27
|
+
}
|
|
28
|
+
function currentAgentId() {
|
|
29
|
+
return store.getStore()?.agentId;
|
|
30
|
+
}
|
|
31
|
+
function currentSourceOrUnknown() {
|
|
32
|
+
return store.getStore()?.source ?? "system:unknown";
|
|
33
|
+
}
|
|
34
|
+
var store, init_execution_context = __esm({
|
|
35
|
+
"src/main/runtime/execution-context.ts"() {
|
|
36
|
+
"use strict";
|
|
37
|
+
store = new AsyncLocalStorage();
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// src/main/tools/shell/commands/types.ts
|
|
42
|
+
var EXIT = {
|
|
43
|
+
SUCCESS: 0,
|
|
44
|
+
ERROR: 1,
|
|
45
|
+
DISABLED: 126,
|
|
46
|
+
NOT_FOUND: 127,
|
|
47
|
+
APPROVAL: 128,
|
|
48
|
+
INTERCEPTED: 130
|
|
49
|
+
};
|
|
50
|
+
function ok(stdout) {
|
|
51
|
+
return { exit_code: EXIT.SUCCESS, stdout, stderr: "" };
|
|
52
|
+
}
|
|
53
|
+
function err(stderr, code = EXIT.ERROR) {
|
|
54
|
+
return { exit_code: code, stdout: "", stderr };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/main/tools/shell/commands/filesystem.ts
|
|
58
|
+
init_execution_context();
|
|
59
|
+
|
|
60
|
+
// src/main/tools/built-in/mime-utils.ts
|
|
61
|
+
var TEXT_MIME_PREFIXES = ["text/"], TEXT_MIME_EXACT = /* @__PURE__ */ new Set([
|
|
62
|
+
"application/json",
|
|
63
|
+
"application/xml",
|
|
64
|
+
"application/yaml",
|
|
65
|
+
"application/x-yaml",
|
|
66
|
+
"application/javascript",
|
|
67
|
+
"application/typescript",
|
|
68
|
+
"application/x-sh",
|
|
69
|
+
"application/sql",
|
|
70
|
+
"application/graphql",
|
|
71
|
+
"application/xhtml+xml",
|
|
72
|
+
"application/x-httpd-php",
|
|
73
|
+
// Line/record textual data formats — an agent that writes these expects
|
|
74
|
+
// `cat`/`head`/coreutils to see the bytes, not a binary placeholder.
|
|
75
|
+
"application/x-ndjson",
|
|
76
|
+
"application/ndjson",
|
|
77
|
+
"application/csv",
|
|
78
|
+
"application/x-yaml",
|
|
79
|
+
"application/toml"
|
|
80
|
+
]);
|
|
81
|
+
function isTextMime(mime) {
|
|
82
|
+
return mime ? TEXT_MIME_PREFIXES.some((p) => mime.startsWith(p)) || /\+(json|xml|yaml)$/.test(mime) ? !0 : TEXT_MIME_EXACT.has(mime) : !1;
|
|
83
|
+
}
|
|
84
|
+
var VISION_MIMES = /* @__PURE__ */ new Set([
|
|
85
|
+
"image/png",
|
|
86
|
+
"image/jpeg",
|
|
87
|
+
"image/gif",
|
|
88
|
+
"image/webp"
|
|
89
|
+
]);
|
|
90
|
+
function isVisionMime(mime) {
|
|
91
|
+
return mime ? VISION_MIMES.has(mime) : !1;
|
|
92
|
+
}
|
|
93
|
+
var AUDIO_INPUT_MIMES = /* @__PURE__ */ new Set([
|
|
94
|
+
"audio/wav",
|
|
95
|
+
"audio/mpeg",
|
|
96
|
+
"audio/mp3",
|
|
97
|
+
"audio/aiff",
|
|
98
|
+
"audio/aac",
|
|
99
|
+
"audio/ogg",
|
|
100
|
+
"audio/flac",
|
|
101
|
+
"audio/x-m4a",
|
|
102
|
+
"audio/webm"
|
|
103
|
+
]);
|
|
104
|
+
function isAudioInputMime(mime) {
|
|
105
|
+
return mime ? AUDIO_INPUT_MIMES.has(mime) : !1;
|
|
106
|
+
}
|
|
107
|
+
var VIDEO_INPUT_MIMES = /* @__PURE__ */ new Set([
|
|
108
|
+
"video/mp4",
|
|
109
|
+
"video/mpeg",
|
|
110
|
+
"video/quicktime",
|
|
111
|
+
"video/webm"
|
|
112
|
+
]);
|
|
113
|
+
function isVideoInputMime(mime) {
|
|
114
|
+
return mime ? VIDEO_INPUT_MIMES.has(mime) : !1;
|
|
115
|
+
}
|
|
116
|
+
var AUDIO_FORMAT_MAP = {
|
|
117
|
+
"audio/wav": "wav",
|
|
118
|
+
"audio/mpeg": "mp3",
|
|
119
|
+
"audio/mp3": "mp3",
|
|
120
|
+
"audio/aiff": "aiff",
|
|
121
|
+
"audio/aac": "aac",
|
|
122
|
+
"audio/ogg": "ogg",
|
|
123
|
+
"audio/flac": "flac",
|
|
124
|
+
"audio/x-m4a": "m4a",
|
|
125
|
+
"audio/webm": "webm"
|
|
126
|
+
};
|
|
127
|
+
function mimeToAudioFormat(mime) {
|
|
128
|
+
return AUDIO_FORMAT_MAP[mime] ?? "wav";
|
|
129
|
+
}
|
|
130
|
+
function formatSize(bytes) {
|
|
131
|
+
return bytes < 1024 ? `${bytes} B` : bytes < 1048576 ? `${(bytes / 1024).toFixed(1)} KB` : `${(bytes / 1048576).toFixed(1)} MB`;
|
|
132
|
+
}
|
|
133
|
+
var MIME_TO_EXT = {
|
|
134
|
+
"image/png": ".png",
|
|
135
|
+
"image/jpeg": ".jpg",
|
|
136
|
+
"image/gif": ".gif",
|
|
137
|
+
"image/webp": ".webp",
|
|
138
|
+
"image/svg+xml": ".svg",
|
|
139
|
+
"image/bmp": ".bmp",
|
|
140
|
+
"image/tiff": ".tiff",
|
|
141
|
+
"audio/mpeg": ".mp3",
|
|
142
|
+
"audio/wav": ".wav",
|
|
143
|
+
"audio/ogg": ".ogg",
|
|
144
|
+
"audio/webm": ".webm",
|
|
145
|
+
"video/mp4": ".mp4",
|
|
146
|
+
"video/webm": ".webm",
|
|
147
|
+
"application/pdf": ".pdf",
|
|
148
|
+
"application/json": ".json",
|
|
149
|
+
"text/plain": ".txt",
|
|
150
|
+
"text/html": ".html",
|
|
151
|
+
"text/css": ".css"
|
|
152
|
+
};
|
|
153
|
+
function mimeToExt(mimeType) {
|
|
154
|
+
return MIME_TO_EXT[mimeType] ?? ".bin";
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/main/tools/shell/commands/fs-read-helper.ts
|
|
158
|
+
function isMediaMime(mime) {
|
|
159
|
+
return isVisionMime(mime) || isAudioInputMime(mime) || isVideoInputMime(mime);
|
|
160
|
+
}
|
|
161
|
+
function isTextRow(row) {
|
|
162
|
+
return isTextMime(row.mime_type);
|
|
163
|
+
}
|
|
164
|
+
async function shellReadFileRow(toolRegistry, workspace, path, options) {
|
|
165
|
+
let input = { path };
|
|
166
|
+
options?.start_line !== void 0 && (input.start_line = options.start_line), options?.end_line !== void 0 && (input.end_line = options.end_line);
|
|
167
|
+
let result = await toolRegistry.executeTool("fs_read", input, workspace);
|
|
168
|
+
if (result.isError)
|
|
169
|
+
return [null, result.content];
|
|
170
|
+
try {
|
|
171
|
+
let row = JSON.parse(result.content);
|
|
172
|
+
return [{
|
|
173
|
+
content: row.content ?? "",
|
|
174
|
+
mime_type: row.mime_type,
|
|
175
|
+
size: row.size,
|
|
176
|
+
path: row.path ?? path
|
|
177
|
+
}, null];
|
|
178
|
+
} catch {
|
|
179
|
+
return [null, "Failed to parse fs_read result"];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async function shellReadFile(toolRegistry, workspace, path, options) {
|
|
183
|
+
let input = { path };
|
|
184
|
+
options?.start_line !== void 0 && (input.start_line = options.start_line), options?.end_line !== void 0 && (input.end_line = options.end_line);
|
|
185
|
+
let result = await toolRegistry.executeTool("fs_read", input, workspace);
|
|
186
|
+
if (result.isError)
|
|
187
|
+
return [null, result.content];
|
|
188
|
+
try {
|
|
189
|
+
return [JSON.parse(result.content).content ?? "", null];
|
|
190
|
+
} catch {
|
|
191
|
+
return [null, "Failed to parse fs_read result"];
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/main/tools/shell/commands/filesystem.ts
|
|
196
|
+
function vfsPath(p) {
|
|
197
|
+
return p === "." || p === "./" || p === "/" ? "" : p.replace(/^\.\//, "").replace(/^\//, "");
|
|
198
|
+
}
|
|
199
|
+
var catHandler = {
|
|
200
|
+
name: "cat",
|
|
201
|
+
summary: "Read file contents",
|
|
202
|
+
helpText: [
|
|
203
|
+
"cat <path> Read file contents",
|
|
204
|
+
"cat -n <path> With line numbers",
|
|
205
|
+
"cat --text <path> Force text output even if the MIME says binary",
|
|
206
|
+
"",
|
|
207
|
+
"Options:",
|
|
208
|
+
" -n Show line numbers",
|
|
209
|
+
" --text Decode as UTF-8 text regardless of MIME classification"
|
|
210
|
+
].join(`
|
|
211
|
+
`),
|
|
212
|
+
category: "filesystem",
|
|
213
|
+
resolvedTools: ["fs_read"],
|
|
214
|
+
async execute(ctx) {
|
|
215
|
+
let showLineNumbers = !!ctx.flags.n, forceText = !!ctx.flags.text, paths = ctx.args;
|
|
216
|
+
if (paths.length === 0)
|
|
217
|
+
return ctx.stdin ? ok(ctx.stdin) : err("cat: missing file path");
|
|
218
|
+
let outputs = [], media = [];
|
|
219
|
+
for (let rawPath of paths) {
|
|
220
|
+
let path = vfsPath(rawPath), [row, error] = await shellReadFileRow(ctx.toolRegistry, ctx.workspace, path);
|
|
221
|
+
if (error !== null) return err(`cat: ${error}`);
|
|
222
|
+
if (isMediaMime(row.mime_type)) {
|
|
223
|
+
let mime = row.mime_type, limits = ctx.config.limits ?? {}, maxSize = mime.startsWith("image/") ? limits.max_image_size_bytes ?? 5242880 : mime.startsWith("audio/") ? limits.max_audio_size_bytes ?? 10485760 : limits.max_video_size_bytes ?? 10485760;
|
|
224
|
+
if (row.size !== void 0 && row.size > maxSize) {
|
|
225
|
+
outputs.push(`[${mime}: ${row.path ?? path}, ${row.size} bytes \u2014 too large to attach (limit ${maxSize} bytes)]`);
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
media.push({ path: row.path ?? path, mime_type: mime });
|
|
229
|
+
let kind = mime.startsWith("image/") ? "image" : mime.startsWith("audio/") ? "audio" : "video";
|
|
230
|
+
outputs.push(`[${kind}: ${row.path ?? path} (${mime})${row.size ? `, ${row.size} bytes` : ""} \u2014 attached for viewing if your model supports this modality]`);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (!isTextRow(row)) {
|
|
234
|
+
if (forceText) {
|
|
235
|
+
let decoded = Buffer.from(row.content, "base64").toString("utf8");
|
|
236
|
+
outputs.push(showLineNumbers ? decoded.split(`
|
|
237
|
+
`).map((l, i) => `${String(i + 1).padStart(6)} ${l}`).join(`
|
|
238
|
+
`) : decoded);
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
outputs.push(`[binary: ${row.path ?? path}${row.size ? `, ${row.size} bytes` : ""}${row.mime_type ? `, ${row.mime_type}` : ""} \u2014 not shown; use \`cat --text\` if this is text]`);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (showLineNumbers) {
|
|
245
|
+
let lines = row.content.split(`
|
|
246
|
+
`);
|
|
247
|
+
outputs.push(lines.map((l, i) => `${String(i + 1).padStart(6)} ${l}`).join(`
|
|
248
|
+
`));
|
|
249
|
+
} else
|
|
250
|
+
outputs.push(row.content);
|
|
251
|
+
}
|
|
252
|
+
let result = ok(outputs.join(`
|
|
253
|
+
`));
|
|
254
|
+
return media.length > 0 && (result.media = media), result;
|
|
255
|
+
}
|
|
256
|
+
}, LS_MAX_ENTRIES = 500, lsHandler = {
|
|
257
|
+
name: "ls",
|
|
258
|
+
summary: "List files",
|
|
259
|
+
helpText: [
|
|
260
|
+
"ls [prefix...] List files (multiple args merged, deduped)",
|
|
261
|
+
"ls -l [prefix...] Long format (size, dates, protected)",
|
|
262
|
+
"",
|
|
263
|
+
"Options:",
|
|
264
|
+
" -l Long listing format",
|
|
265
|
+
"",
|
|
266
|
+
"Output is ONE JSON array, not one line per file \u2014 piping to head/tail",
|
|
267
|
+
"slices nothing. Slice with jq: ls | jq -r '.[].path' or ls | jq '.[0:3]'",
|
|
268
|
+
"",
|
|
269
|
+
"Each arg is a path prefix (exact file OR directory-ish prefix); results",
|
|
270
|
+
"are merged and deduped, so `ls *.md` (shell-expanded) lists every match.",
|
|
271
|
+
'An arg matching nothing prints "ls: <arg>: No such file or directory" on',
|
|
272
|
+
"stderr; exit 2 only if NO arg matched. Stdout is always one JSON array.",
|
|
273
|
+
"",
|
|
274
|
+
`Long listings are capped at ${LS_MAX_ENTRIES} files; the cap is reported on stderr.`
|
|
275
|
+
].join(`
|
|
276
|
+
`),
|
|
277
|
+
category: "filesystem",
|
|
278
|
+
resolvedTools: ["fs_list"],
|
|
279
|
+
async execute(ctx) {
|
|
280
|
+
let long = !!ctx.flags.l, prefixes = ctx.args.length > 0 ? ctx.args.map((a) => vfsPath(a)) : [""], merged = [], seen = /* @__PURE__ */ new Set(), missing = [];
|
|
281
|
+
for (let i = 0; i < prefixes.length; i++) {
|
|
282
|
+
let result = await ctx.toolRegistry.executeTool("fs_list", {
|
|
283
|
+
prefix: prefixes[i],
|
|
284
|
+
include_metadata: long,
|
|
285
|
+
_full: !0
|
|
286
|
+
}, ctx.workspace);
|
|
287
|
+
if (result.isError) return err(`ls: ${result.content}`);
|
|
288
|
+
let rows;
|
|
289
|
+
try {
|
|
290
|
+
let parsed = JSON.parse(result.content);
|
|
291
|
+
rows = Array.isArray(parsed) ? parsed : [];
|
|
292
|
+
} catch {
|
|
293
|
+
return err("ls: unexpected fs_list output (not a JSON array)");
|
|
294
|
+
}
|
|
295
|
+
if (rows.length === 0 && ctx.args.length > 0) {
|
|
296
|
+
missing.push(ctx.args[i]);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
for (let row of rows) {
|
|
300
|
+
let key = row && typeof row == "object" && typeof row.path == "string" ? row.path : JSON.stringify(row);
|
|
301
|
+
seen.has(key) || (seen.add(key), merged.push(row));
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
let notes = missing.map((m) => `ls: ${m}: No such file or directory`);
|
|
305
|
+
return merged.length > LS_MAX_ENTRIES && (notes.push(`ls: showing first ${LS_MAX_ENTRIES} of ${merged.length} files \u2014 narrow the prefix or slice with jq`), merged.length = LS_MAX_ENTRIES), {
|
|
306
|
+
exit_code: ctx.args.length > 0 && merged.length === 0 ? 2 : EXIT.SUCCESS,
|
|
307
|
+
stdout: JSON.stringify(merged),
|
|
308
|
+
stderr: notes.join(`
|
|
309
|
+
`)
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
}, rmHandler = {
|
|
313
|
+
name: "rm",
|
|
314
|
+
summary: "Delete a file",
|
|
315
|
+
helpText: "rm <path> Delete a file from the VFS",
|
|
316
|
+
category: "filesystem",
|
|
317
|
+
resolvedTools: ["fs_delete"],
|
|
318
|
+
async execute(ctx) {
|
|
319
|
+
if (ctx.args.length === 0) return err("rm: missing file path");
|
|
320
|
+
for (let rawPath of ctx.args) {
|
|
321
|
+
let path = vfsPath(rawPath), result = await ctx.toolRegistry.executeTool("fs_delete", { path }, ctx.workspace);
|
|
322
|
+
if (result.isError) return err(`rm: ${result.content}`);
|
|
323
|
+
}
|
|
324
|
+
return ok("");
|
|
325
|
+
}
|
|
326
|
+
}, cpHandler = {
|
|
327
|
+
name: "cp",
|
|
328
|
+
summary: "Copy a file",
|
|
329
|
+
helpText: "cp <src> <dst> Copy a file",
|
|
330
|
+
category: "filesystem",
|
|
331
|
+
resolvedTools: ["fs_read", "fs_write"],
|
|
332
|
+
async execute(ctx) {
|
|
333
|
+
if (ctx.args.length < 2) return err("cp: usage: cp <src> <dst>");
|
|
334
|
+
let src = vfsPath(ctx.args[0]), dst = vfsPath(ctx.args[1]), [content, readErr] = await shellReadFile(ctx.toolRegistry, ctx.workspace, src);
|
|
335
|
+
if (readErr) return err(`cp: ${readErr}`);
|
|
336
|
+
let write = await ctx.toolRegistry.executeTool("fs_write", { mode: "write", path: dst, content }, ctx.workspace);
|
|
337
|
+
return write.isError ? err(`cp: ${write.content}`) : ok("");
|
|
338
|
+
}
|
|
339
|
+
}, mvHandler = {
|
|
340
|
+
name: "mv",
|
|
341
|
+
summary: "Move/rename a file",
|
|
342
|
+
helpText: [
|
|
343
|
+
"mv <src> <dst> Move or rename a file",
|
|
344
|
+
"",
|
|
345
|
+
"A rename preserves content, so mv needs fs_read + fs_write \u2014 not the",
|
|
346
|
+
"destructive fs_delete. Protected sources (read_only/no_delete) still",
|
|
347
|
+
"require a HIL override before the old path is released."
|
|
348
|
+
].join(`
|
|
349
|
+
`),
|
|
350
|
+
category: "filesystem",
|
|
351
|
+
// A rename preserves content — the honest capability set is read-the-source
|
|
352
|
+
// + write-the-destination, NOT the destructive fs_delete: gating mv on
|
|
353
|
+
// delete priced "rename" the same as "destroy", so an agent without
|
|
354
|
+
// fs_delete couldn't rename anything. The delete-like half (releasing a
|
|
355
|
+
// PROTECTED src path) is covered by the inline protection/HIL check below,
|
|
356
|
+
// which fails closed / asks the human exactly as before.
|
|
357
|
+
// (Previously []: fully ungated, a permission-escalation seam.)
|
|
358
|
+
resolvedTools: ["fs_read", "fs_write"],
|
|
359
|
+
async execute(ctx) {
|
|
360
|
+
if (ctx.args.length < 2) return err("mv: usage: mv <src> <dst>");
|
|
361
|
+
let src = vfsPath(ctx.args[0]), dst = vfsPath(ctx.args[1]), protection = ctx.workspace.getFileProtection(src), wasProtected = protection === "read_only" || protection === "no_delete", bypass = null;
|
|
362
|
+
if (wasProtected)
|
|
363
|
+
if (ctx.authorized)
|
|
364
|
+
bypass = "authorized";
|
|
365
|
+
else {
|
|
366
|
+
let gate = ctx.gate;
|
|
367
|
+
if (!gate?.onProtectionBlocked || gate.authorized)
|
|
368
|
+
return err(`mv: cannot move "${src}": file is protected (${protection}).`);
|
|
369
|
+
let decision = await gate.onProtectionBlocked(
|
|
370
|
+
"fs_delete",
|
|
371
|
+
{ path: src },
|
|
372
|
+
{
|
|
373
|
+
kind: "file_protection",
|
|
374
|
+
target: src,
|
|
375
|
+
level: protection,
|
|
376
|
+
description: `Move "${src}" \u2014 file is protected (${protection})`
|
|
377
|
+
},
|
|
378
|
+
gate.command ?? `mv ${src} ${dst}`
|
|
379
|
+
);
|
|
380
|
+
if (!decision.approved) {
|
|
381
|
+
let fb = decision.feedback?.trim();
|
|
382
|
+
return err(`mv: cannot move "${src}": file is protected (${protection}). Override rejected by the user.${fb ? ` Feedback: ${fb}` : ""} Do not retry.`, EXIT.INTERCEPTED);
|
|
383
|
+
}
|
|
384
|
+
bypass = "human-approved";
|
|
385
|
+
}
|
|
386
|
+
try {
|
|
387
|
+
if (!ctx.workspace.renameInternalFile(src, dst)) return err(`mv: ${src}: no such file`);
|
|
388
|
+
if (bypass) {
|
|
389
|
+
let reason = bypass === "human-approved" ? "human-approved override" : `authorized code bypass (${currentSourceOrUnknown()})`;
|
|
390
|
+
return ctx.workspace.insertLog?.(
|
|
391
|
+
"warn",
|
|
392
|
+
"protection",
|
|
393
|
+
"bypass",
|
|
394
|
+
src,
|
|
395
|
+
`Moved protected file "${src}" (${protection}) \u2014 ${reason}`
|
|
396
|
+
), ok(`Moved "${src}" (\u26A0 protection override: ${protection}, ${bypass}).`);
|
|
397
|
+
}
|
|
398
|
+
return ok("");
|
|
399
|
+
} catch (e) {
|
|
400
|
+
return err(`mv: ${e instanceof Error ? e.message : String(e)}`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}, touchHandler = {
|
|
404
|
+
name: "touch",
|
|
405
|
+
summary: "Create an empty file",
|
|
406
|
+
helpText: "touch <path> Create an empty file (only if it doesn't exist)",
|
|
407
|
+
category: "filesystem",
|
|
408
|
+
resolvedTools: ["fs_write"],
|
|
409
|
+
async execute(ctx) {
|
|
410
|
+
if (ctx.args.length === 0) return err("touch: missing file path");
|
|
411
|
+
for (let rawPath of ctx.args) {
|
|
412
|
+
let path = vfsPath(rawPath);
|
|
413
|
+
if (!ctx.workspace.fileExists(path)) {
|
|
414
|
+
let result = await ctx.toolRegistry.executeTool("fs_write", { mode: "write", path, content: "" }, ctx.workspace);
|
|
415
|
+
if (result.isError) return err(`touch: ${result.content}`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return ok("");
|
|
419
|
+
}
|
|
420
|
+
}, findHandler = {
|
|
421
|
+
name: "find",
|
|
422
|
+
summary: "Find files by pattern",
|
|
423
|
+
helpText: [
|
|
424
|
+
"find [path] List files (optionally under a path)",
|
|
425
|
+
"find [path] -name <glob> Find files whose basename matches a glob",
|
|
426
|
+
"",
|
|
427
|
+
"Options:",
|
|
428
|
+
" -name <glob> Glob with * and ? (* never crosses a / segment).",
|
|
429
|
+
' Quote the glob ("*.md") so find does the matching;',
|
|
430
|
+
" an unquoted glob is expanded by the shell first."
|
|
431
|
+
].join(`
|
|
432
|
+
`),
|
|
433
|
+
category: "filesystem",
|
|
434
|
+
resolvedTools: [],
|
|
435
|
+
// reads the VFS list directly, like grep -r
|
|
436
|
+
async execute(ctx) {
|
|
437
|
+
let raw = ctx.rawArgs ?? ctx.args, positionals = [], patterns = [];
|
|
438
|
+
for (let i = 0; i < raw.length; i++) {
|
|
439
|
+
let tok = raw[i];
|
|
440
|
+
if (tok === "-name" || tok === "--name") {
|
|
441
|
+
let before = patterns.length;
|
|
442
|
+
for (; i + 1 < raw.length && !raw[i + 1].startsWith("-"); ) patterns.push(raw[++i]);
|
|
443
|
+
if (patterns.length === before) return err('find: -name requires a pattern (quote it: -name "*.md")');
|
|
444
|
+
} else {
|
|
445
|
+
if (tok.startsWith("-") && tok.length > 1)
|
|
446
|
+
return err(`find: unsupported option ${tok} \u2014 usage: find [path] [-name <glob>]`);
|
|
447
|
+
positionals.push(tok);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
let prefix = vfsPath(positionals[0] ?? ""), paths = ctx.workspace.listFiles().map((f) => f.path);
|
|
451
|
+
if (prefix && (paths = paths.filter((p) => p === prefix || p.startsWith(prefix + "/"))), patterns.length > 0) {
|
|
452
|
+
let matchers = patterns.map((g) => ({
|
|
453
|
+
regex: new RegExp("^" + g.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]") + "$"),
|
|
454
|
+
// A pattern containing / (e.g. a shell-pre-expanded full path) matches
|
|
455
|
+
// the whole path; a plain glob matches the basename (GNU -name).
|
|
456
|
+
fullPath: g.includes("/")
|
|
457
|
+
}));
|
|
458
|
+
paths = paths.filter((p) => matchers.some((m) => m.regex.test(m.fullPath ? p : p.split("/").pop() ?? p)));
|
|
459
|
+
}
|
|
460
|
+
return ok(paths.join(`
|
|
461
|
+
`));
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
function formatSize2(bytes) {
|
|
465
|
+
return bytes < 1024 ? `${bytes}` : bytes < 1024 * 1024 ? `${(bytes / 1024).toFixed(1)}K` : bytes < 1024 * 1024 * 1024 ? `${(bytes / (1024 * 1024)).toFixed(1)}M` : `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}G`;
|
|
466
|
+
}
|
|
467
|
+
var duHandler = {
|
|
468
|
+
name: "du",
|
|
469
|
+
summary: "Show file sizes",
|
|
470
|
+
helpText: [
|
|
471
|
+
"du [path] Show disk usage of files",
|
|
472
|
+
"",
|
|
473
|
+
"Options:",
|
|
474
|
+
" -h Human-readable sizes (default)"
|
|
475
|
+
].join(`
|
|
476
|
+
`),
|
|
477
|
+
category: "filesystem",
|
|
478
|
+
resolvedTools: [],
|
|
479
|
+
async execute(ctx) {
|
|
480
|
+
let prefix = vfsPath(ctx.args[0] ?? ""), files = ctx.workspace.listFiles();
|
|
481
|
+
if (prefix && (files = files.filter((f) => f.path.startsWith(prefix))), files.length === 0) return ok("");
|
|
482
|
+
files.sort((a, b) => b.size - a.size);
|
|
483
|
+
let total = files.reduce((sum, f) => sum + f.size, 0), lines = files.map((f) => `${formatSize2(f.size)} ${f.path}`);
|
|
484
|
+
return lines.push(`${formatSize2(total)} total`), ok(lines.join(`
|
|
485
|
+
`));
|
|
486
|
+
}
|
|
487
|
+
}, chmodHandler = {
|
|
488
|
+
name: "chmod",
|
|
489
|
+
summary: "Set file protection",
|
|
490
|
+
helpText: [
|
|
491
|
+
"chmod +p <path> Protect a file (default level: read_only)",
|
|
492
|
+
"chmod +p=read_only <path> Protect: no writes, no deletion",
|
|
493
|
+
"chmod +p=no_delete <path> Protect: writable, but cannot be deleted",
|
|
494
|
+
"chmod -p <path> Remove protection",
|
|
495
|
+
"",
|
|
496
|
+
"Protection levels: read_only (no writes or deletion), no_delete",
|
|
497
|
+
"(writable but not deletable), none (unprotected). Plain +p defaults to",
|
|
498
|
+
"read_only. Removing or changing an EXISTING protection pauses for a",
|
|
499
|
+
"human (HIL) override approval; raising protection on an unprotected",
|
|
500
|
+
"file needs none.",
|
|
501
|
+
"",
|
|
502
|
+
"adf files have no unix modes \u2014 numeric (644) and symbolic (u+x) modes",
|
|
503
|
+
"are not supported. +p/-p is the whole interface."
|
|
504
|
+
].join(`
|
|
505
|
+
`),
|
|
506
|
+
category: "filesystem",
|
|
507
|
+
resolvedTools: ["fs_write"],
|
|
508
|
+
async execute(ctx) {
|
|
509
|
+
let usage = "chmod: usage: chmod [+p[=read_only|no_delete]|-p] <path>", mode = ctx.flags.p === !0 ? "-p" : ctx.args[0], pathArg = ctx.flags.p === !0 ? ctx.args[0] : ctx.args[1];
|
|
510
|
+
if (!mode || !pathArg) return err(usage);
|
|
511
|
+
let target;
|
|
512
|
+
if (mode === "-p")
|
|
513
|
+
target = "none";
|
|
514
|
+
else if (mode === "+p")
|
|
515
|
+
target = "read_only";
|
|
516
|
+
else if (mode.startsWith("+p=")) {
|
|
517
|
+
let level = mode.slice(3);
|
|
518
|
+
if (level !== "read_only" && level !== "no_delete")
|
|
519
|
+
return err(`chmod: invalid protection level "${level}". Use +p=read_only or +p=no_delete (or -p to unprotect)`);
|
|
520
|
+
target = level;
|
|
521
|
+
} else return /^[0-7]{1,4}$/.test(mode) || /^[ugoa]*[+\-=][rwxXstugo]+$/.test(mode) ? err("chmod: only +p (protect) and -p (unprotect) are supported \u2014 adf files have no unix modes") : err(`chmod: invalid mode "${mode}". Use +p, +p=read_only, +p=no_delete, or -p`);
|
|
522
|
+
let path = vfsPath(pathArg), current = ctx.workspace.getFileProtection(path);
|
|
523
|
+
if (current === null) return err(`chmod: ${pathArg}: No such file or directory`);
|
|
524
|
+
if (current === target) return ok("");
|
|
525
|
+
let wasProtected = current === "read_only" || current === "no_delete", bypass = null;
|
|
526
|
+
if (wasProtected)
|
|
527
|
+
if (ctx.authorized)
|
|
528
|
+
bypass = "authorized";
|
|
529
|
+
else {
|
|
530
|
+
let gate = ctx.gate;
|
|
531
|
+
if (!gate?.onProtectionBlocked || gate.authorized)
|
|
532
|
+
return err(`chmod: cannot change protection of "${path}": file is protected (${current}).`);
|
|
533
|
+
let decision = await gate.onProtectionBlocked(
|
|
534
|
+
"fs_write",
|
|
535
|
+
{ path, protection: target },
|
|
536
|
+
{
|
|
537
|
+
kind: "file_protection",
|
|
538
|
+
target: path,
|
|
539
|
+
level: current,
|
|
540
|
+
description: `Change protection of "${path}" (currently ${current})`
|
|
541
|
+
},
|
|
542
|
+
gate.command ?? `chmod ${mode} ${pathArg}`
|
|
543
|
+
);
|
|
544
|
+
if (!decision.approved) {
|
|
545
|
+
let fb = decision.feedback?.trim();
|
|
546
|
+
return err(`chmod: cannot change protection of "${path}": file is protected (${current}). Override rejected by the user.${fb ? ` Feedback: ${fb}` : ""} Do not retry.`, EXIT.INTERCEPTED);
|
|
547
|
+
}
|
|
548
|
+
bypass = "human-approved";
|
|
549
|
+
}
|
|
550
|
+
try {
|
|
551
|
+
if (ctx.workspace.setFileProtection(path, target), bypass) {
|
|
552
|
+
let reason = bypass === "human-approved" ? "human-approved override" : `authorized code bypass (${currentSourceOrUnknown()})`;
|
|
553
|
+
return ctx.workspace.insertLog?.(
|
|
554
|
+
"warn",
|
|
555
|
+
"protection",
|
|
556
|
+
"bypass",
|
|
557
|
+
path,
|
|
558
|
+
`Changed protection of "${path}" (${current} -> ${target}) \u2014 ${reason}`
|
|
559
|
+
), ok(`Changed protection of "${path}" to ${target} (\u26A0 protection override: ${current}, ${bypass}).`);
|
|
560
|
+
}
|
|
561
|
+
return ok("");
|
|
562
|
+
} catch (e) {
|
|
563
|
+
return err(`chmod: ${e instanceof Error ? e.message : String(e)}`);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}, headHandler = {
|
|
567
|
+
name: "head",
|
|
568
|
+
summary: "Show first N lines",
|
|
569
|
+
helpText: [
|
|
570
|
+
"head [-N] [file] Show first N lines (default 10)",
|
|
571
|
+
"",
|
|
572
|
+
"Options:",
|
|
573
|
+
" -n <N> Number of lines (or -N shorthand)",
|
|
574
|
+
" -c <N> First N BYTES instead of lines (UTF-8 byte slice;",
|
|
575
|
+
" a multi-byte char cut at the boundary becomes U+FFFD).",
|
|
576
|
+
" Takes precedence over -n if both are given."
|
|
577
|
+
].join(`
|
|
578
|
+
`),
|
|
579
|
+
category: "filesystem",
|
|
580
|
+
resolvedTools: ["fs_read"],
|
|
581
|
+
valueFlags: /* @__PURE__ */ new Set(["n", "c"]),
|
|
582
|
+
async execute(ctx) {
|
|
583
|
+
if (ctx.flags.c !== void 0) {
|
|
584
|
+
let raw = ctx.flags.c;
|
|
585
|
+
if (typeof raw != "string" || !/^\d+$/.test(raw))
|
|
586
|
+
return err("head: -c requires a plain byte count (e.g. head -c 80 file); suffixes (K/M) and negative counts are not supported");
|
|
587
|
+
let bytes = parseInt(raw, 10), content = ctx.stdin;
|
|
588
|
+
if (ctx.args.length > 0) {
|
|
589
|
+
let [fileContent, error] = await shellReadFile(ctx.toolRegistry, ctx.workspace, vfsPath(ctx.args[0]));
|
|
590
|
+
if (error) return err(`head: ${error}`);
|
|
591
|
+
content = fileContent;
|
|
592
|
+
}
|
|
593
|
+
return content ? ok(Buffer.from(content, "utf8").subarray(0, bytes).toString("utf8")) : ok("");
|
|
594
|
+
}
|
|
595
|
+
let n = 10;
|
|
596
|
+
ctx.flags.n && typeof ctx.flags.n == "string" && (n = parseInt(ctx.flags.n, 10));
|
|
597
|
+
for (let key of Object.keys(ctx.flags))
|
|
598
|
+
/^\d+$/.test(key) && (n = parseInt(key, 10));
|
|
599
|
+
let text = ctx.stdin;
|
|
600
|
+
if (ctx.args.length > 0) {
|
|
601
|
+
let [content, error] = await shellReadFile(ctx.toolRegistry, ctx.workspace, vfsPath(ctx.args[0]), { end_line: n });
|
|
602
|
+
return error ? err(`head: ${error}`) : ok(content);
|
|
603
|
+
}
|
|
604
|
+
if (!text) return ok("");
|
|
605
|
+
let lines = text.split(`
|
|
606
|
+
`);
|
|
607
|
+
return ok(lines.slice(0, n).join(`
|
|
608
|
+
`));
|
|
609
|
+
}
|
|
610
|
+
}, tailHandler = {
|
|
611
|
+
name: "tail",
|
|
612
|
+
summary: "Show last N lines",
|
|
613
|
+
helpText: [
|
|
614
|
+
"tail [-N] [file] Show last N lines (default 10)",
|
|
615
|
+
"",
|
|
616
|
+
"Options:",
|
|
617
|
+
" -n <N> Number of lines (or -N shorthand); -n +N starts at line N",
|
|
618
|
+
" -c <N> Last N BYTES instead of lines; -c +N starts at byte N",
|
|
619
|
+
" (UTF-8 byte slice; a multi-byte char cut at the",
|
|
620
|
+
" boundary becomes U+FFFD). Takes precedence over -n."
|
|
621
|
+
].join(`
|
|
622
|
+
`),
|
|
623
|
+
category: "filesystem",
|
|
624
|
+
resolvedTools: ["fs_read"],
|
|
625
|
+
valueFlags: /* @__PURE__ */ new Set(["n", "c"]),
|
|
626
|
+
async execute(ctx) {
|
|
627
|
+
if (ctx.flags.c !== void 0) {
|
|
628
|
+
let raw = ctx.flags.c;
|
|
629
|
+
if (typeof raw != "string" || !/^\+?\d+$/.test(raw))
|
|
630
|
+
return err("tail: -c requires a plain byte count (e.g. tail -c 80 file); suffixes (K/M) and negative counts are not supported");
|
|
631
|
+
let fromByte = raw.startsWith("+"), bytes = parseInt(raw, 10), content = ctx.stdin;
|
|
632
|
+
if (ctx.args.length > 0) {
|
|
633
|
+
let [fileContent, error] = await shellReadFile(ctx.toolRegistry, ctx.workspace, vfsPath(ctx.args[0]));
|
|
634
|
+
if (error) return err(`tail: ${error}`);
|
|
635
|
+
content = fileContent;
|
|
636
|
+
}
|
|
637
|
+
if (!content) return ok("");
|
|
638
|
+
let buf = Buffer.from(content, "utf8"), sliced = fromByte ? buf.subarray(Math.max(0, bytes - 1)) : bytes <= 0 ? buf.subarray(buf.length) : buf.subarray(Math.max(0, buf.length - bytes));
|
|
639
|
+
return ok(sliced.toString("utf8"));
|
|
640
|
+
}
|
|
641
|
+
let n = 10, fromLine = !1;
|
|
642
|
+
if (typeof ctx.flags.n == "string") {
|
|
643
|
+
let raw = ctx.flags.n;
|
|
644
|
+
fromLine = raw.startsWith("+"), n = parseInt(raw, 10);
|
|
645
|
+
}
|
|
646
|
+
for (let key of Object.keys(ctx.flags))
|
|
647
|
+
/^\d+$/.test(key) && (n = parseInt(key, 10));
|
|
648
|
+
if (isNaN(n)) return err("tail: invalid number of lines");
|
|
649
|
+
let text = ctx.stdin;
|
|
650
|
+
if (ctx.args.length > 0) {
|
|
651
|
+
let [content, error] = await shellReadFile(ctx.toolRegistry, ctx.workspace, vfsPath(ctx.args[0]));
|
|
652
|
+
if (error) return err(`tail: ${error}`);
|
|
653
|
+
text = content;
|
|
654
|
+
}
|
|
655
|
+
if (!text) return ok("");
|
|
656
|
+
let lines = text.split(`
|
|
657
|
+
`);
|
|
658
|
+
return lines.length > 0 && lines[lines.length - 1] === "" && lines.pop(), fromLine ? ok(lines.slice(Math.max(0, n - 1)).join(`
|
|
659
|
+
`)) : n <= 0 ? ok("") : ok(lines.slice(-n).join(`
|
|
660
|
+
`));
|
|
661
|
+
}
|
|
662
|
+
}, mkdirHandler = {
|
|
663
|
+
name: "mkdir",
|
|
664
|
+
summary: "No-op: the VFS is flat, directories are implicit",
|
|
665
|
+
helpText: [
|
|
666
|
+
"mkdir [-p] <path> No-op. The VFS has no directory objects \u2014 a path like",
|
|
667
|
+
" archive/notes.md is just a name, so write it directly."
|
|
668
|
+
].join(`
|
|
669
|
+
`),
|
|
670
|
+
category: "filesystem",
|
|
671
|
+
resolvedTools: [],
|
|
672
|
+
async execute(ctx) {
|
|
673
|
+
return ctx.args.length === 0 && ctx.flags.p === void 0 ? err("mkdir: missing operand") : {
|
|
674
|
+
exit_code: 0,
|
|
675
|
+
stdout: "",
|
|
676
|
+
stderr: "mkdir: nothing to do \u2014 the VFS is flat; directory names are just part of the path (write archive/file.md directly)"
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
}, filesystemHandlers = [
|
|
680
|
+
catHandler,
|
|
681
|
+
lsHandler,
|
|
682
|
+
rmHandler,
|
|
683
|
+
cpHandler,
|
|
684
|
+
mvHandler,
|
|
685
|
+
touchHandler,
|
|
686
|
+
findHandler,
|
|
687
|
+
duHandler,
|
|
688
|
+
chmodHandler,
|
|
689
|
+
headHandler,
|
|
690
|
+
tailHandler,
|
|
691
|
+
mkdirHandler
|
|
692
|
+
];
|
|
693
|
+
|
|
694
|
+
// src/main/tools/shell/commands/wasi-applet-adapter.ts
|
|
695
|
+
import { readFileSync } from "fs";
|
|
696
|
+
import { Worker } from "node:worker_threads";
|
|
697
|
+
|
|
698
|
+
// src/main/tools/shell/commands/locate-resource.ts
|
|
699
|
+
import { existsSync } from "fs";
|
|
700
|
+
import { join, dirname } from "path";
|
|
701
|
+
function locate(segments) {
|
|
702
|
+
let starts = [];
|
|
703
|
+
typeof __dirname < "u" && starts.push(__dirname), starts.push(process.cwd());
|
|
704
|
+
for (let start of starts) {
|
|
705
|
+
let dir = start;
|
|
706
|
+
for (let i = 0; i < 8; i++) {
|
|
707
|
+
let candidate = join(dir, ...segments);
|
|
708
|
+
if (existsSync(candidate)) return candidate;
|
|
709
|
+
let parent = dirname(dir);
|
|
710
|
+
if (parent === dir) break;
|
|
711
|
+
dir = parent;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
throw new Error(`not found: ${segments.join("/")}`);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// src/main/tools/shell/commands/wasi-applet-adapter.ts
|
|
718
|
+
function locateWasm() {
|
|
719
|
+
return locate(["resources", "wasm", "coreutils.wasm"]);
|
|
720
|
+
}
|
|
721
|
+
function locateShim() {
|
|
722
|
+
return locate(["node_modules", "@bjorn3", "browser_wasi_shim", "dist", "index.js"]);
|
|
723
|
+
}
|
|
724
|
+
var modulePromise = null;
|
|
725
|
+
function getModule() {
|
|
726
|
+
return modulePromise || (modulePromise = WebAssembly.compile(readFileSync(locateWasm()))), modulePromise;
|
|
727
|
+
}
|
|
728
|
+
var WORKER_SRC = `
|
|
729
|
+
const { parentPort, workerData } = require('node:worker_threads')
|
|
730
|
+
const { WASI, File, Directory, OpenFile, PreopenDirectory, ConsoleStdout, WASIProcExit } = require(workerData.shimPath)
|
|
731
|
+
|
|
732
|
+
function buildTree(files) {
|
|
733
|
+
const enc = new TextEncoder()
|
|
734
|
+
const root = new Map()
|
|
735
|
+
for (const [path, content] of Object.entries(files)) {
|
|
736
|
+
const segments = String(path).split('/').filter(Boolean)
|
|
737
|
+
if (segments.length === 0) continue
|
|
738
|
+
const bytes = typeof content === 'string' ? enc.encode(content) : content
|
|
739
|
+
let dir = root, conflict = false
|
|
740
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
741
|
+
const existing = dir.get(segments[i])
|
|
742
|
+
if (existing instanceof Directory) { dir = existing.contents; continue }
|
|
743
|
+
// Don't overwrite a File with a Directory (path 'a' and 'a/b' collide) \u2014
|
|
744
|
+
// skip the conflicting entry rather than corrupt the earlier one.
|
|
745
|
+
if (existing !== undefined) { conflict = true; break }
|
|
746
|
+
const next = new Directory(new Map())
|
|
747
|
+
dir.set(segments[i], next)
|
|
748
|
+
dir = next.contents
|
|
749
|
+
}
|
|
750
|
+
if (conflict) continue
|
|
751
|
+
const leaf = segments[segments.length - 1]
|
|
752
|
+
if (dir.get(leaf) instanceof Directory) continue
|
|
753
|
+
dir.set(leaf, new File(bytes))
|
|
754
|
+
}
|
|
755
|
+
return root
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
;(async () => {
|
|
759
|
+
const { wasmModule, applet, argv, stdin, files } = workerData
|
|
760
|
+
let stdout = '', stderr = ''
|
|
761
|
+
const od = new TextDecoder(), ed = new TextDecoder()
|
|
762
|
+
const fds = [
|
|
763
|
+
new OpenFile(new File(new TextEncoder().encode(stdin))),
|
|
764
|
+
new ConsoleStdout((b) => { stdout += od.decode(b, { stream: true }) }),
|
|
765
|
+
new ConsoleStdout((b) => { stderr += ed.decode(b, { stream: true }) }),
|
|
766
|
+
new PreopenDirectory('.', buildTree(files)),
|
|
767
|
+
]
|
|
768
|
+
const wasi = new WASI(['coreutils', applet, ...argv], [], fds, { debug: false })
|
|
769
|
+
const instance = await WebAssembly.instantiate(wasmModule, { wasi_snapshot_preview1: wasi.wasiImport })
|
|
770
|
+
let exitCode = 0
|
|
771
|
+
try { exitCode = wasi.start(instance) }
|
|
772
|
+
catch (e) { if (e instanceof WASIProcExit) exitCode = e.code; else throw e }
|
|
773
|
+
// Flush any bytes held back by a multibyte sequence at a chunk boundary.
|
|
774
|
+
stdout += od.decode(); stderr += ed.decode()
|
|
775
|
+
parentPort.postMessage({ stdout, stderr, exitCode })
|
|
776
|
+
})().catch((e) => parentPort.postMessage({ error: String((e && e.message) || e) }))
|
|
777
|
+
`, MAX_CONCURRENT_WORKERS = 8, activeWorkers = 0, workerQueue = [];
|
|
778
|
+
function acquireSlot() {
|
|
779
|
+
return activeWorkers < MAX_CONCURRENT_WORKERS ? (activeWorkers++, Promise.resolve()) : new Promise((resolve) => workerQueue.push(resolve));
|
|
780
|
+
}
|
|
781
|
+
function releaseSlot() {
|
|
782
|
+
let next = workerQueue.shift();
|
|
783
|
+
next ? next() : activeWorkers--;
|
|
784
|
+
}
|
|
785
|
+
async function runApplet(applet, argv, stdin, files = {}, opts = {}) {
|
|
786
|
+
let wasmModule = await getModule(), shimPath = locateShim(), timeoutMs = opts.timeoutMs && opts.timeoutMs > 0 ? opts.timeoutMs : 6e4;
|
|
787
|
+
return await acquireSlot(), new Promise((resolve) => {
|
|
788
|
+
let worker;
|
|
789
|
+
try {
|
|
790
|
+
worker = new Worker(WORKER_SRC, {
|
|
791
|
+
eval: !0,
|
|
792
|
+
workerData: { wasmModule, applet, argv, stdin, files, shimPath }
|
|
793
|
+
});
|
|
794
|
+
} catch (e) {
|
|
795
|
+
releaseSlot(), resolve({ stdout: "", stderr: `${applet}: worker start failed: ${e instanceof Error ? e.message : String(e)}`, exitCode: 1 });
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
let settled = !1, finish = (r) => {
|
|
799
|
+
settled || (settled = !0, clearTimeout(timer), worker.terminate(), releaseSlot(), resolve(r));
|
|
800
|
+
}, timer = setTimeout(
|
|
801
|
+
() => finish({ stdout: "", stderr: `${applet}: timed out after ${Math.round(timeoutMs / 1e3)}s`, exitCode: 124 }),
|
|
802
|
+
timeoutMs
|
|
803
|
+
);
|
|
804
|
+
if (worker.once("message", (m) => {
|
|
805
|
+
m?.error ? finish({ stdout: "", stderr: `${applet}: ${m.error}`, exitCode: 1 }) : finish({ stdout: m.stdout ?? "", stderr: m.stderr ?? "", exitCode: m.exitCode ?? 0 });
|
|
806
|
+
}), worker.once("error", (e) => finish({ stdout: "", stderr: `${applet}: ${e.message}`, exitCode: 1 })), opts.signal) {
|
|
807
|
+
let onAbort = () => finish({ stdout: "", stderr: `${applet}: aborted`, exitCode: 130 });
|
|
808
|
+
opts.signal.aborted ? onAbort() : opts.signal.addEventListener("abort", onAbort, { once: !0 });
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// src/main/tools/shell/commands/regex-worker.ts
|
|
814
|
+
import { Worker as Worker2 } from "node:worker_threads";
|
|
815
|
+
var OFFLOAD_MIN_BYTES = 64 * 1024;
|
|
816
|
+
function mayBacktrack(source) {
|
|
817
|
+
if (/\\[1-9]/.test(source)) return !0;
|
|
818
|
+
let groupStarts = [];
|
|
819
|
+
for (let i = 0; i < source.length; i++) {
|
|
820
|
+
let c = source[i];
|
|
821
|
+
if (c === "\\") {
|
|
822
|
+
i++;
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
if (c === "[") {
|
|
826
|
+
for (i++, source[i] === "^" && i++, source[i] === "]" && i++; i < source.length && source[i] !== "]"; )
|
|
827
|
+
source[i] === "\\" && i++, i++;
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
if (c === "(") {
|
|
831
|
+
groupStarts.push(i);
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
834
|
+
if (c === ")") {
|
|
835
|
+
let start = groupStarts.pop();
|
|
836
|
+
if (start === void 0) continue;
|
|
837
|
+
let next = source[i + 1];
|
|
838
|
+
if ((next === "*" || next === "+" || next === "{") && /[*+{|]/.test(source.slice(start + 1, i)))
|
|
839
|
+
return !0;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
return !1;
|
|
843
|
+
}
|
|
844
|
+
var WORKER_SRC2 = `
|
|
845
|
+
const { parentPort } = require('node:worker_threads')
|
|
846
|
+
|
|
847
|
+
function scan(re, line) {
|
|
848
|
+
const out = []
|
|
849
|
+
if (!re.global) {
|
|
850
|
+
const m = re.exec(line)
|
|
851
|
+
if (m) out.push({ index: m.index, match: m[0], groups: m.slice(1) })
|
|
852
|
+
return out
|
|
853
|
+
}
|
|
854
|
+
re.lastIndex = 0
|
|
855
|
+
let m
|
|
856
|
+
while ((m = re.exec(line)) !== null) {
|
|
857
|
+
out.push({ index: m.index, match: m[0], groups: m.slice(1) })
|
|
858
|
+
if (m[0] === '') re.lastIndex++
|
|
859
|
+
}
|
|
860
|
+
return out
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
parentPort.on('message', (job) => {
|
|
864
|
+
try {
|
|
865
|
+
if (job.kind === 'select') {
|
|
866
|
+
const re = new RegExp(job.source, job.flags)
|
|
867
|
+
const oRe = job.pieces ? new RegExp(job.source, job.flags.includes('g') ? job.flags : job.flags + 'g') : null
|
|
868
|
+
const idx = []
|
|
869
|
+
const pieces = oRe ? [] : null
|
|
870
|
+
for (let i = 0; i < job.lines.length && idx.length < job.maxCount; i++) {
|
|
871
|
+
const line = job.lines[i]
|
|
872
|
+
if (re.test(line) !== job.invert) {
|
|
873
|
+
idx.push(i)
|
|
874
|
+
if (pieces) pieces.push((line.match(oRe) || []).filter((s) => s.length > 0))
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
parentPort.postMessage({ id: job.id, ok: true, value: { idx, pieces } })
|
|
878
|
+
} else {
|
|
879
|
+
const re = new RegExp(job.source, job.flags)
|
|
880
|
+
const matches = job.lines.map((line) => scan(re, line))
|
|
881
|
+
parentPort.postMessage({ id: job.id, ok: true, value: matches })
|
|
882
|
+
}
|
|
883
|
+
} catch (e) {
|
|
884
|
+
parentPort.postMessage({ id: job.id, ok: false, message: String((e && e.message) || e) })
|
|
885
|
+
}
|
|
886
|
+
})
|
|
887
|
+
`, MAX_WORKERS = 4, idleWorkers = [], liveWorkers = 0, waiters = [], jobSeq = 0;
|
|
888
|
+
function spawn() {
|
|
889
|
+
let w = new Worker2(WORKER_SRC2, { eval: !0 });
|
|
890
|
+
w.unref();
|
|
891
|
+
let drop = () => {
|
|
892
|
+
let at = idleWorkers.indexOf(w);
|
|
893
|
+
at >= 0 && (idleWorkers.splice(at, 1), liveWorkers--);
|
|
894
|
+
};
|
|
895
|
+
return w.on("error", drop), w.on("exit", drop), w;
|
|
896
|
+
}
|
|
897
|
+
function acquire() {
|
|
898
|
+
let w = idleWorkers.pop();
|
|
899
|
+
if (w)
|
|
900
|
+
return w.ref(), Promise.resolve(w);
|
|
901
|
+
if (liveWorkers < MAX_WORKERS) {
|
|
902
|
+
liveWorkers++;
|
|
903
|
+
let fresh = spawn();
|
|
904
|
+
return fresh.ref(), Promise.resolve(fresh);
|
|
905
|
+
}
|
|
906
|
+
return new Promise((resolve) => waiters.push(resolve));
|
|
907
|
+
}
|
|
908
|
+
function release(w) {
|
|
909
|
+
let next = waiters.shift();
|
|
910
|
+
if (w) {
|
|
911
|
+
if (next) {
|
|
912
|
+
next(w);
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
w.unref(), idleWorkers.push(w);
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
if (liveWorkers--, next) {
|
|
919
|
+
liveWorkers++;
|
|
920
|
+
let fresh = spawn();
|
|
921
|
+
fresh.ref(), next(fresh);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
async function run(job, opts) {
|
|
925
|
+
let timeoutMs = opts.timeoutMs && opts.timeoutMs > 0 ? opts.timeoutMs : 6e4, worker = await acquire();
|
|
926
|
+
return new Promise((resolve) => {
|
|
927
|
+
let settled = !1, onMessage = (m) => {
|
|
928
|
+
settled || (settled = !0, clearTimeout(timer), worker.off("message", onMessage), worker.off("error", onError), opts.signal?.removeEventListener("abort", onAbort), release(worker), m?.ok ? resolve({ ok: !0, value: m.value }) : resolve({ ok: !1, reason: "error", message: m?.message ?? "regex worker failed" }));
|
|
929
|
+
}, kill = (reason, message) => {
|
|
930
|
+
settled || (settled = !0, clearTimeout(timer), worker.off("message", onMessage), worker.off("error", onError), opts.signal?.removeEventListener("abort", onAbort), worker.terminate(), release(null), resolve({ ok: !1, reason, message }));
|
|
931
|
+
}, onError = (e) => kill("error", e.message), onAbort = () => kill("aborted", "aborted"), timer = setTimeout(
|
|
932
|
+
() => kill("timeout", `timed out after ${Math.round(timeoutMs / 1e3)}s`),
|
|
933
|
+
timeoutMs
|
|
934
|
+
);
|
|
935
|
+
if (worker.on("message", onMessage), worker.on("error", onError), opts.signal) {
|
|
936
|
+
if (opts.signal.aborted) {
|
|
937
|
+
onAbort();
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
opts.signal.addEventListener("abort", onAbort, { once: !0 });
|
|
941
|
+
}
|
|
942
|
+
worker.postMessage({ ...job, id: ++jobSeq });
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
function selectLines(job, opts = {}) {
|
|
946
|
+
return run({ kind: "select", ...job }, opts);
|
|
947
|
+
}
|
|
948
|
+
function execLines(job, opts = {}) {
|
|
949
|
+
return run({ kind: "exec", ...job }, opts);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// src/main/tools/shell/commands/text.ts
|
|
953
|
+
function regexFailure(cmd, reason, message) {
|
|
954
|
+
return err(`${cmd}: ${message}`, reason === "timeout" ? 124 : reason === "aborted" ? 130 : 1);
|
|
955
|
+
}
|
|
956
|
+
function vfsPath2(p) {
|
|
957
|
+
return p === "." || p === "./" || p === "/" ? "" : p.replace(/^\.\//, "").replace(/^\//, "");
|
|
958
|
+
}
|
|
959
|
+
function interpretEscapes(s) {
|
|
960
|
+
return s.replace(/\\n/g, `
|
|
961
|
+
`).replace(/\\t/g, " ").replace(/\\r/g, "\r").replace(/\\\\/g, "\\");
|
|
962
|
+
}
|
|
963
|
+
async function coreutilsExec(applet, ctx, opts = { fileArgs: !0 }) {
|
|
964
|
+
let files = {}, argv = [...ctx.rawArgs ?? ctx.args];
|
|
965
|
+
if ((/* @__PURE__ */ new Set(["sort", "shuf"])).has(applet)) {
|
|
966
|
+
let badFlag = argv.find((a) => /^-o/.test(a) || /^--o/.test(a));
|
|
967
|
+
if (badFlag)
|
|
968
|
+
return err(`${applet}: writing to a file (${badFlag}) is not supported in the sandbox \u2014 redirect stdout instead (e.g. \`${applet} ... > out.txt\`)`);
|
|
969
|
+
}
|
|
970
|
+
if (opts.fileArgs)
|
|
971
|
+
for (let i = 0; i < argv.length; i++) {
|
|
972
|
+
let tok = argv[i];
|
|
973
|
+
if (tok.startsWith("-")) continue;
|
|
974
|
+
let norm = vfsPath2(tok);
|
|
975
|
+
if (!norm) continue;
|
|
976
|
+
let [row, readErr] = await shellReadFileRow(ctx.toolRegistry, ctx.workspace, norm);
|
|
977
|
+
readErr === null && (files[norm] = isTextRow(row) ? row.content : Buffer.from(row.content, "base64"), argv[i] = norm);
|
|
978
|
+
}
|
|
979
|
+
try {
|
|
980
|
+
let { stdout, stderr, exitCode } = await runApplet(applet, argv, ctx.stdin || "", files, {
|
|
981
|
+
timeoutMs: ctx.config.limits?.execution_timeout_ms,
|
|
982
|
+
signal: ctx.signal
|
|
983
|
+
});
|
|
984
|
+
return exitCode !== 0 ? { exit_code: exitCode, stdout, stderr: stderr.trim() || `${applet}: exit ${exitCode}` } : ok(stdout);
|
|
985
|
+
} catch (e) {
|
|
986
|
+
return err(`${applet}: ${e instanceof Error ? e.message : String(e)}`);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
function escapeRegExp(s) {
|
|
990
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
991
|
+
}
|
|
992
|
+
function splitLines(text) {
|
|
993
|
+
let lines = text.split(`
|
|
994
|
+
`);
|
|
995
|
+
return lines.length > 0 && lines[lines.length - 1] === "" && lines.pop(), lines;
|
|
996
|
+
}
|
|
997
|
+
var GREP_KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
998
|
+
"i",
|
|
999
|
+
"v",
|
|
1000
|
+
"c",
|
|
1001
|
+
"n",
|
|
1002
|
+
"r",
|
|
1003
|
+
"R",
|
|
1004
|
+
"o",
|
|
1005
|
+
"F",
|
|
1006
|
+
"w",
|
|
1007
|
+
"x",
|
|
1008
|
+
"l",
|
|
1009
|
+
"q",
|
|
1010
|
+
"m",
|
|
1011
|
+
"E",
|
|
1012
|
+
"e",
|
|
1013
|
+
"h",
|
|
1014
|
+
"H",
|
|
1015
|
+
"A",
|
|
1016
|
+
"B",
|
|
1017
|
+
"C",
|
|
1018
|
+
"include",
|
|
1019
|
+
"exclude"
|
|
1020
|
+
]), grepHandler = {
|
|
1021
|
+
name: "grep",
|
|
1022
|
+
summary: "Filter lines matching a pattern",
|
|
1023
|
+
helpText: [
|
|
1024
|
+
"grep <pattern> [file] Filter lines matching a regex (ERE-style, JS regex)",
|
|
1025
|
+
"",
|
|
1026
|
+
"Supported: -i case-insensitive, -v invert, -c count, -n line numbers,",
|
|
1027
|
+
" -r recursive (VFS), -o only-matching, -F fixed string, -w word match,",
|
|
1028
|
+
" -x whole-line, -l files-with-matches, -q quiet, -m <N> max matches,",
|
|
1029
|
+
" -e <pat>, -A/-B/-C <N> context, --include/--exclude=<glob>.",
|
|
1030
|
+
"Unsupported flags (e.g. -P) are rejected, not ignored. Regex is JS/ERE:",
|
|
1031
|
+
" -P (PCRE) is not supported, and BRE escapes are REJECTED rather than",
|
|
1032
|
+
" silently matching nothing \u2014 write | ( ) { } + ? unescaped (plain `grep`",
|
|
1033
|
+
" on a real system is BRE, so \\| habits produce zero matches here)."
|
|
1034
|
+
].join(`
|
|
1035
|
+
`),
|
|
1036
|
+
category: "text",
|
|
1037
|
+
resolvedTools: [],
|
|
1038
|
+
// pure text operation; -r uses fs_list + fs_read
|
|
1039
|
+
valueFlags: /* @__PURE__ */ new Set(["A", "B", "C", "m", "e"]),
|
|
1040
|
+
async execute(ctx) {
|
|
1041
|
+
let unknown = Object.keys(ctx.flags).filter((f) => !GREP_KNOWN_FLAGS.has(f));
|
|
1042
|
+
if (unknown.length > 0)
|
|
1043
|
+
return err(`grep: unsupported option${unknown.length > 1 ? "s" : ""}: ${unknown.map((f) => (f.length === 1 ? "-" : "--") + f).join(", ")}`);
|
|
1044
|
+
let patternFromE = typeof ctx.flags.e == "string" ? ctx.flags.e : void 0, pattern = patternFromE ?? ctx.args[0];
|
|
1045
|
+
if (pattern === void 0) return err("grep: missing pattern");
|
|
1046
|
+
let fileArgs = patternFromE ? ctx.args : ctx.args.slice(1), ignoreCase = !!ctx.flags.i, invert = !!ctx.flags.v, count = !!ctx.flags.c, showNumbers = !!ctx.flags.n, recursive = !!ctx.flags.r || !!ctx.flags.R, onlyMatching = !!ctx.flags.o, listFiles = !!ctx.flags.l, quiet = !!ctx.flags.q, maxCount = typeof ctx.flags.m == "string" ? parseInt(ctx.flags.m, 10) : 1 / 0, afterCtx = typeof ctx.flags.A == "string" ? parseInt(ctx.flags.A, 10) : 0, beforeCtx = typeof ctx.flags.B == "string" ? parseInt(ctx.flags.B, 10) : 0, aroundCtx = typeof ctx.flags.C == "string" ? parseInt(ctx.flags.C, 10) : 0, ctxBefore = aroundCtx || beforeCtx, ctxAfter = aroundCtx || afterCtx;
|
|
1047
|
+
if (!ctx.flags.F) {
|
|
1048
|
+
let bre = pattern.match(/\\[|(){}+?]/g);
|
|
1049
|
+
if (bre) {
|
|
1050
|
+
let seen = [...new Set(bre)], ere = seen.map((s) => `${s} \u2192 ${s[1]}`).join(", ");
|
|
1051
|
+
return err(
|
|
1052
|
+
`grep: ${seen.join(" ")} is BRE syntax; this grep is ERE/JS where it means a LITERAL character and would match nothing. Write ${ere}. For a literal character use a bracket class ([|]) or -F with the bare character (-F takes no escapes).`
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
let src = ctx.flags.F ? escapeRegExp(pattern) : pattern;
|
|
1057
|
+
ctx.flags.w && (src = `(?<![A-Za-z0-9_])(?:${src})(?![A-Za-z0-9_])`), ctx.flags.x && (src = `^(?:${src})$`);
|
|
1058
|
+
let lineRe, oRe = null;
|
|
1059
|
+
try {
|
|
1060
|
+
lineRe = new RegExp(src, ignoreCase ? "i" : ""), onlyMatching && (oRe = new RegExp(src, ignoreCase ? "ig" : "g"));
|
|
1061
|
+
} catch (e) {
|
|
1062
|
+
return err(`grep: invalid pattern: ${e instanceof Error ? e.message : String(pattern)}`);
|
|
1063
|
+
}
|
|
1064
|
+
let globToRe = (g) => g ? new RegExp("^" + g.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".") + "$") : void 0, includeRe = globToRe(typeof ctx.flags.include == "string" ? ctx.flags.include : void 0), excludeRe = globToRe(typeof ctx.flags.exclude == "string" ? ctx.flags.exclude : void 0), noFilename = !!ctx.flags.h, forceFilename = !!ctx.flags.H, reFlags = ignoreCase ? "i" : "", wantPieces = onlyMatching && !listFiles && !quiet && !count, offloadPattern = mayBacktrack(src), timeoutMs = ctx.config.limits?.execution_timeout_ms, select = async (lines2, bytes) => {
|
|
1065
|
+
if (!offloadPattern && bytes <= OFFLOAD_MIN_BYTES) {
|
|
1066
|
+
let idx = [], pieces = wantPieces ? [] : null;
|
|
1067
|
+
for (let i = 0; i < lines2.length && idx.length < maxCount; i++)
|
|
1068
|
+
lineRe.test(lines2[i]) !== invert && (idx.push(i), pieces && oRe && pieces.push((lines2[i].match(oRe) ?? []).filter((m) => m.length > 0)));
|
|
1069
|
+
return { idx, pieces };
|
|
1070
|
+
}
|
|
1071
|
+
let r = await selectLines(
|
|
1072
|
+
{ source: src, flags: reFlags, lines: lines2, invert, maxCount, pieces: wantPieces },
|
|
1073
|
+
{ timeoutMs, signal: ctx.signal }
|
|
1074
|
+
);
|
|
1075
|
+
return r.ok ? r.value : { failure: regexFailure("grep", r.reason, r.message) };
|
|
1076
|
+
}, emit = (line, prefix, out2, pieces) => {
|
|
1077
|
+
if (wantPieces && pieces) {
|
|
1078
|
+
for (let m of pieces) out2.push(`${prefix}${m}`);
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
out2.push(`${prefix}${line}`);
|
|
1082
|
+
};
|
|
1083
|
+
if (recursive || fileArgs.length > 1 || fileArgs.length === 1 && (listFiles || recursive)) {
|
|
1084
|
+
let startPath = vfsPath2(fileArgs[0] ?? ""), all = ctx.workspace.listFiles(), files = recursive ? startPath ? all.filter((f) => f.path.startsWith(startPath)) : all : all.filter((f) => fileArgs.map(vfsPath2).includes(f.path));
|
|
1085
|
+
includeRe && (files = files.filter((f) => includeRe.test(f.path.split("/").pop() ?? f.path))), excludeRe && (files = files.filter((f) => !excludeRe.test(f.path.split("/").pop() ?? f.path)));
|
|
1086
|
+
let withName = forceFilename || !noFilename, out2 = [], counts = [], matchedFiles = [], anyMatch = !1;
|
|
1087
|
+
for (let file of files) {
|
|
1088
|
+
if (file.mime_type && !isTextMime(file.mime_type)) continue;
|
|
1089
|
+
let [content, readErr] = await shellReadFile(ctx.toolRegistry, ctx.workspace, file.path);
|
|
1090
|
+
if (readErr) continue;
|
|
1091
|
+
let lines2 = splitLines(content), sel2 = await select(lines2, content?.length ?? 0);
|
|
1092
|
+
if ("failure" in sel2) return sel2.failure;
|
|
1093
|
+
let fileMatches = sel2.idx.length;
|
|
1094
|
+
if (fileMatches > 0 && (anyMatch = !0), !listFiles && !quiet && !count)
|
|
1095
|
+
for (let k = 0; k < sel2.idx.length; k++) {
|
|
1096
|
+
let i = sel2.idx[k], prefix = `${withName ? `${file.path}:` : ""}${showNumbers ? `${i + 1}:` : ""}`;
|
|
1097
|
+
emit(lines2[i], prefix, out2, sel2.pieces ? sel2.pieces[k] : null);
|
|
1098
|
+
}
|
|
1099
|
+
count && counts.push(`${withName ? `${file.path}:` : ""}${fileMatches}`), fileMatches > 0 && matchedFiles.push(file.path);
|
|
1100
|
+
}
|
|
1101
|
+
return quiet ? { exit_code: anyMatch ? 0 : 1, stdout: "", stderr: "" } : listFiles ? matchedFiles.length > 0 ? ok(matchedFiles.join(`
|
|
1102
|
+
`)) : { exit_code: 1, stdout: "", stderr: "" } : count ? { exit_code: anyMatch ? 0 : 1, stdout: counts.join(`
|
|
1103
|
+
`), stderr: "" } : anyMatch ? ok(out2.join(`
|
|
1104
|
+
`)) : { exit_code: 1, stdout: "", stderr: "" };
|
|
1105
|
+
}
|
|
1106
|
+
let text = ctx.stdin;
|
|
1107
|
+
if (fileArgs.length === 1) {
|
|
1108
|
+
let [content, readErr] = await shellReadFile(ctx.toolRegistry, ctx.workspace, vfsPath2(fileArgs[0]));
|
|
1109
|
+
if (readErr) return err(`grep: ${fileArgs[0]}: No such file or directory`);
|
|
1110
|
+
text = content;
|
|
1111
|
+
}
|
|
1112
|
+
if (!text)
|
|
1113
|
+
return { exit_code: 1, stdout: !quiet && count ? "0" : "", stderr: "" };
|
|
1114
|
+
let lines = splitLines(text), sel = await select(lines, text.length);
|
|
1115
|
+
if ("failure" in sel) return sel.failure;
|
|
1116
|
+
let matchIdx = sel.idx;
|
|
1117
|
+
if (quiet) return { exit_code: matchIdx.length > 0 ? 0 : 1, stdout: "", stderr: "" };
|
|
1118
|
+
if (count) return { exit_code: matchIdx.length > 0 ? 0 : 1, stdout: String(matchIdx.length), stderr: "" };
|
|
1119
|
+
if (matchIdx.length === 0) return { exit_code: 1, stdout: "", stderr: "" };
|
|
1120
|
+
let out = [];
|
|
1121
|
+
if (ctxBefore === 0 && ctxAfter === 0) {
|
|
1122
|
+
for (let k = 0; k < matchIdx.length; k++) {
|
|
1123
|
+
let i = matchIdx[k];
|
|
1124
|
+
emit(lines[i], showNumbers ? `${i + 1}:` : "", out, sel.pieces ? sel.pieces[k] : null);
|
|
1125
|
+
}
|
|
1126
|
+
return ok(out.join(`
|
|
1127
|
+
`));
|
|
1128
|
+
}
|
|
1129
|
+
let included = /* @__PURE__ */ new Set();
|
|
1130
|
+
for (let idx of matchIdx)
|
|
1131
|
+
for (let j = Math.max(0, idx - ctxBefore); j <= Math.min(lines.length - 1, idx + ctxAfter); j++) included.add(j);
|
|
1132
|
+
let lastPrinted = -2;
|
|
1133
|
+
for (let idx of [...included].sort((a, b) => a - b))
|
|
1134
|
+
lastPrinted >= 0 && idx > lastPrinted + 1 && out.push("--"), out.push(`${showNumbers ? `${idx + 1}:` : ""}${lines[idx]}`), lastPrinted = idx;
|
|
1135
|
+
return ok(out.join(`
|
|
1136
|
+
`));
|
|
1137
|
+
}
|
|
1138
|
+
};
|
|
1139
|
+
function renderSedReplacement(template, matchArgs) {
|
|
1140
|
+
let offsetIdx = matchArgs.findIndex((a) => typeof a == "number"), groups = offsetIdx > 0 ? matchArgs.slice(0, offsetIdx) : [matchArgs[0]], out = "";
|
|
1141
|
+
for (let i = 0; i < template.length; i++) {
|
|
1142
|
+
let c = template[i];
|
|
1143
|
+
if (c === "\\") {
|
|
1144
|
+
let n = template[i + 1];
|
|
1145
|
+
if (n === void 0) {
|
|
1146
|
+
out += "\\";
|
|
1147
|
+
break;
|
|
1148
|
+
}
|
|
1149
|
+
n >= "0" && n <= "9" ? out += String(groups[Number(n)] ?? "") : n === "&" ? out += "&" : n === "n" ? out += `
|
|
1150
|
+
` : n === "t" ? out += " " : out += n, i++;
|
|
1151
|
+
} else c === "&" ? out += String(groups[0] ?? "") : out += c;
|
|
1152
|
+
}
|
|
1153
|
+
return out;
|
|
1154
|
+
}
|
|
1155
|
+
function applySedMatches(line, matches, template) {
|
|
1156
|
+
if (matches.length === 0) return line;
|
|
1157
|
+
let out = "", pos = 0;
|
|
1158
|
+
for (let m of matches)
|
|
1159
|
+
out += line.slice(pos, m.index) + renderSedReplacement(template, [m.match, ...m.groups, m.index, line]), pos = m.index + m.match.length;
|
|
1160
|
+
return out + line.slice(pos);
|
|
1161
|
+
}
|
|
1162
|
+
var SED_KNOWN_FLAGS = /* @__PURE__ */ new Set(["i", "E", "r"]), sedHandler = {
|
|
1163
|
+
name: "sed",
|
|
1164
|
+
summary: "Stream editor (s/// substitution)",
|
|
1165
|
+
helpText: [
|
|
1166
|
+
"sed 's/old/new/[gi]' [file] Regex substitution (JS/ERE regex)",
|
|
1167
|
+
"sed -i 's/old/new/' <file> In-place edit",
|
|
1168
|
+
"",
|
|
1169
|
+
"Replacement: & = whole match, \\1..\\9 = capture groups, \\n \\t supported.",
|
|
1170
|
+
"Options: -i in-place, -E/-r extended regex (default). Addresses (1p, /re/d)",
|
|
1171
|
+
"and -n are NOT supported \u2014 they error rather than silently misbehave."
|
|
1172
|
+
].join(`
|
|
1173
|
+
`),
|
|
1174
|
+
category: "text",
|
|
1175
|
+
resolvedTools: ["fs_read", "fs_write"],
|
|
1176
|
+
async execute(ctx) {
|
|
1177
|
+
if (ctx.args.length === 0) return err("sed: missing expression");
|
|
1178
|
+
let unknown = Object.keys(ctx.flags).filter((f) => !SED_KNOWN_FLAGS.has(f));
|
|
1179
|
+
if (unknown.length > 0)
|
|
1180
|
+
return err(`sed: unsupported option${unknown.length > 1 ? "s" : ""}: ${unknown.map((f) => (f.length === 1 ? "-" : "--") + f).join(", ")} (only s/// substitution is supported)`);
|
|
1181
|
+
let inPlace = !!ctx.flags.i, expr = ctx.args[0], match = expr.match(/^s(.)(.+?)\1(.*?)\1([gi]*)$/);
|
|
1182
|
+
if (!match) return err(`sed: unsupported expression "${expr}" \u2014 only s/old/new/[gi] is supported`);
|
|
1183
|
+
let [, , pattern, rawReplacement, flags] = match, reFlags = (flags.includes("i") ? "i" : "") + (flags.includes("g") ? "g" : ""), regex;
|
|
1184
|
+
try {
|
|
1185
|
+
regex = new RegExp(pattern, reFlags);
|
|
1186
|
+
} catch (e) {
|
|
1187
|
+
return err(`sed: invalid pattern: ${e instanceof Error ? e.message : pattern}`);
|
|
1188
|
+
}
|
|
1189
|
+
let replacer = (...matchArgs) => renderSedReplacement(rawReplacement, matchArgs), text = ctx.stdin || "", filePath = null;
|
|
1190
|
+
if (ctx.args.length > 1) {
|
|
1191
|
+
filePath = vfsPath2(ctx.args[1]);
|
|
1192
|
+
let [sedContent, sedErr] = await shellReadFile(ctx.toolRegistry, ctx.workspace, filePath);
|
|
1193
|
+
if (sedErr) return err(`sed: ${filePath}: No such file or directory`);
|
|
1194
|
+
text = sedContent;
|
|
1195
|
+
} else if (!text)
|
|
1196
|
+
return ok("");
|
|
1197
|
+
let lines = text.split(`
|
|
1198
|
+
`), result;
|
|
1199
|
+
if (!mayBacktrack(pattern) && text.length <= OFFLOAD_MIN_BYTES)
|
|
1200
|
+
result = lines.map((line) => line.replace(regex, replacer)).join(`
|
|
1201
|
+
`);
|
|
1202
|
+
else {
|
|
1203
|
+
let r = await execLines(
|
|
1204
|
+
{ source: pattern, flags: reFlags, lines },
|
|
1205
|
+
{ timeoutMs: ctx.config.limits?.execution_timeout_ms, signal: ctx.signal }
|
|
1206
|
+
);
|
|
1207
|
+
if (!r.ok) return regexFailure("sed", r.reason, r.message);
|
|
1208
|
+
result = lines.map((line, i) => applySedMatches(line, r.value[i], rawReplacement)).join(`
|
|
1209
|
+
`);
|
|
1210
|
+
}
|
|
1211
|
+
if (inPlace && filePath) {
|
|
1212
|
+
let writeResult = await ctx.toolRegistry.executeTool("fs_write", { mode: "write", path: filePath, content: result }, ctx.workspace);
|
|
1213
|
+
return writeResult.isError ? err(`sed: ${writeResult.content}`) : ok("");
|
|
1214
|
+
}
|
|
1215
|
+
return ok(result);
|
|
1216
|
+
}
|
|
1217
|
+
}, sortHandler = {
|
|
1218
|
+
name: "sort",
|
|
1219
|
+
summary: "Sort lines",
|
|
1220
|
+
helpText: [
|
|
1221
|
+
"sort [file ...] Sort lines (real GNU-compatible sort via WASM)",
|
|
1222
|
+
"",
|
|
1223
|
+
"Common options: -r reverse, -n numeric, -u unique, -k <N> key field,",
|
|
1224
|
+
"-t <sep> field separator, -f ignore case, -h human-numeric, -V version sort."
|
|
1225
|
+
].join(`
|
|
1226
|
+
`),
|
|
1227
|
+
category: "text",
|
|
1228
|
+
resolvedTools: ["fs_read"],
|
|
1229
|
+
valueFlags: /* @__PURE__ */ new Set(["k", "t"]),
|
|
1230
|
+
async execute(ctx) {
|
|
1231
|
+
return coreutilsExec("sort", ctx);
|
|
1232
|
+
}
|
|
1233
|
+
}, uniqHandler = {
|
|
1234
|
+
name: "uniq",
|
|
1235
|
+
summary: "Deduplicate adjacent lines",
|
|
1236
|
+
helpText: [
|
|
1237
|
+
"uniq [file] Remove adjacent duplicate lines (real coreutils)",
|
|
1238
|
+
"",
|
|
1239
|
+
"Common options: -c count, -d only-duplicates, -i ignore case,",
|
|
1240
|
+
"-f <N> skip fields, -s <N> skip chars."
|
|
1241
|
+
].join(`
|
|
1242
|
+
`),
|
|
1243
|
+
category: "text",
|
|
1244
|
+
resolvedTools: ["fs_read"],
|
|
1245
|
+
valueFlags: /* @__PURE__ */ new Set(["f", "s", "w"]),
|
|
1246
|
+
async execute(ctx) {
|
|
1247
|
+
return coreutilsExec("uniq", ctx);
|
|
1248
|
+
}
|
|
1249
|
+
}, wcHandler = {
|
|
1250
|
+
name: "wc",
|
|
1251
|
+
summary: "Count lines, words, characters",
|
|
1252
|
+
helpText: [
|
|
1253
|
+
"wc [file ...] Count lines, words, bytes (real coreutils)",
|
|
1254
|
+
"",
|
|
1255
|
+
"Common options: -l lines, -w words, -c bytes, -m chars, -L max line length."
|
|
1256
|
+
].join(`
|
|
1257
|
+
`),
|
|
1258
|
+
category: "text",
|
|
1259
|
+
resolvedTools: ["fs_read"],
|
|
1260
|
+
async execute(ctx) {
|
|
1261
|
+
return coreutilsExec("wc", ctx);
|
|
1262
|
+
}
|
|
1263
|
+
}, cutHandler = {
|
|
1264
|
+
name: "cut",
|
|
1265
|
+
summary: "Extract fields from lines",
|
|
1266
|
+
helpText: [
|
|
1267
|
+
"cut -d<delim> -f<N> [file] Extract fields/chars (real coreutils)",
|
|
1268
|
+
"",
|
|
1269
|
+
"Common options: -d <delim> delimiter, -f <list> fields (1,3-5, 2-),",
|
|
1270
|
+
"-c <list> characters, -b <list> bytes."
|
|
1271
|
+
].join(`
|
|
1272
|
+
`),
|
|
1273
|
+
category: "text",
|
|
1274
|
+
resolvedTools: ["fs_read"],
|
|
1275
|
+
valueFlags: /* @__PURE__ */ new Set(["d", "f", "c", "b"]),
|
|
1276
|
+
async execute(ctx) {
|
|
1277
|
+
return coreutilsExec("cut", ctx);
|
|
1278
|
+
}
|
|
1279
|
+
}, trHandler = {
|
|
1280
|
+
name: "tr",
|
|
1281
|
+
summary: "Translate characters",
|
|
1282
|
+
helpText: [
|
|
1283
|
+
"tr <set1> [set2] Translate/delete characters (real coreutils)",
|
|
1284
|
+
"",
|
|
1285
|
+
"Supports ranges (a-z), classes ([:digit:]), -d delete, -s squeeze, -c complement."
|
|
1286
|
+
].join(`
|
|
1287
|
+
`),
|
|
1288
|
+
category: "text",
|
|
1289
|
+
resolvedTools: [],
|
|
1290
|
+
async execute(ctx) {
|
|
1291
|
+
return coreutilsExec("tr", ctx, { fileArgs: !1 });
|
|
1292
|
+
}
|
|
1293
|
+
}, teeHandler = {
|
|
1294
|
+
name: "tee",
|
|
1295
|
+
summary: "Write to file and pass through",
|
|
1296
|
+
helpText: "tee <path> Write stdin to file AND pass to stdout",
|
|
1297
|
+
category: "text",
|
|
1298
|
+
resolvedTools: ["fs_write"],
|
|
1299
|
+
async execute(ctx) {
|
|
1300
|
+
if (ctx.args.length === 0) return err("tee: missing file path");
|
|
1301
|
+
let path = vfsPath2(ctx.args[0]), text = ctx.stdin || "";
|
|
1302
|
+
return await ctx.toolRegistry.executeTool("fs_write", { mode: "write", path, content: text }, ctx.workspace), ok(text);
|
|
1303
|
+
}
|
|
1304
|
+
};
|
|
1305
|
+
function fileArgsResolveTools(args) {
|
|
1306
|
+
return args.some((a) => a.type !== "literal" || !a.value.startsWith("-")) ? ["fs_read"] : [];
|
|
1307
|
+
}
|
|
1308
|
+
async function readFileArgsOrStdin(cmd, ctx) {
|
|
1309
|
+
if (ctx.args.length === 0) return [ctx.stdin || "", null];
|
|
1310
|
+
let parts = [];
|
|
1311
|
+
for (let rawPath of ctx.args) {
|
|
1312
|
+
let [content, readErr] = await shellReadFile(ctx.toolRegistry, ctx.workspace, vfsPath2(rawPath));
|
|
1313
|
+
if (readErr !== null) return [null, err(`${cmd}: ${readErr}`)];
|
|
1314
|
+
parts.push(content);
|
|
1315
|
+
}
|
|
1316
|
+
return [parts.join(""), null];
|
|
1317
|
+
}
|
|
1318
|
+
var revHandler = {
|
|
1319
|
+
name: "rev",
|
|
1320
|
+
summary: "Reverse each line",
|
|
1321
|
+
helpText: "rev [file ...] Reverse characters in each line (files or stdin)",
|
|
1322
|
+
category: "text",
|
|
1323
|
+
resolvedTools: [],
|
|
1324
|
+
resolveToolsFromArgs: fileArgsResolveTools,
|
|
1325
|
+
async execute(ctx) {
|
|
1326
|
+
let [text, readError] = await readFileArgsOrStdin("rev", ctx);
|
|
1327
|
+
return readError !== null ? readError : text ? ok(text.split(`
|
|
1328
|
+
`).map((l) => l.split("").reverse().join("")).join(`
|
|
1329
|
+
`)) : ok("");
|
|
1330
|
+
}
|
|
1331
|
+
}, tacHandler = {
|
|
1332
|
+
name: "tac",
|
|
1333
|
+
summary: "Reverse line order",
|
|
1334
|
+
helpText: "tac [file ...] Print lines in reverse order (files or stdin)",
|
|
1335
|
+
category: "text",
|
|
1336
|
+
resolvedTools: [],
|
|
1337
|
+
resolveToolsFromArgs: fileArgsResolveTools,
|
|
1338
|
+
async execute(ctx) {
|
|
1339
|
+
let [text, readError] = await readFileArgsOrStdin("tac", ctx);
|
|
1340
|
+
return readError !== null ? readError : text ? ok(splitLines(text).reverse().join(`
|
|
1341
|
+
`)) : ok("");
|
|
1342
|
+
}
|
|
1343
|
+
};
|
|
1344
|
+
function lineDiff(a, b) {
|
|
1345
|
+
let n = a.length, m = b.length, ops = [];
|
|
1346
|
+
if ((n + 1) * (m + 1) > 4e6) {
|
|
1347
|
+
let maxLen = Math.max(n, m);
|
|
1348
|
+
for (let i2 = 0; i2 < maxLen; i2++)
|
|
1349
|
+
i2 < n && i2 < m && a[i2] === b[i2] ? ops.push({ t: " ", line: a[i2] }) : (i2 < n && ops.push({ t: "-", line: a[i2] }), i2 < m && ops.push({ t: "+", line: b[i2] }));
|
|
1350
|
+
return ops;
|
|
1351
|
+
}
|
|
1352
|
+
let width = m + 1, dp = new Uint32Array((n + 1) * width);
|
|
1353
|
+
for (let i2 = n - 1; i2 >= 0; i2--)
|
|
1354
|
+
for (let j2 = m - 1; j2 >= 0; j2--)
|
|
1355
|
+
dp[i2 * width + j2] = a[i2] === b[j2] ? dp[(i2 + 1) * width + j2 + 1] + 1 : Math.max(dp[(i2 + 1) * width + j2], dp[i2 * width + j2 + 1]);
|
|
1356
|
+
let i = 0, j = 0;
|
|
1357
|
+
for (; i < n && j < m; )
|
|
1358
|
+
a[i] === b[j] ? (ops.push({ t: " ", line: a[i] }), i++, j++) : dp[(i + 1) * width + j] >= dp[i * width + j + 1] ? ops.push({ t: "-", line: a[i++] }) : ops.push({ t: "+", line: b[j++] });
|
|
1359
|
+
for (; i < n; ) ops.push({ t: "-", line: a[i++] });
|
|
1360
|
+
for (; j < m; ) ops.push({ t: "+", line: b[j++] });
|
|
1361
|
+
return ops;
|
|
1362
|
+
}
|
|
1363
|
+
function formatUnifiedDiff(file1, file2, ops, context = 3) {
|
|
1364
|
+
let oldAt = [], newAt = [], ol = 1, nl = 1;
|
|
1365
|
+
for (let op of ops)
|
|
1366
|
+
oldAt.push(ol), newAt.push(nl), op.t !== "+" && ol++, op.t !== "-" && nl++;
|
|
1367
|
+
let changed = ops.map((op, idx) => op.t === " " ? -1 : idx).filter((x) => x >= 0), ranges = [], start = Math.max(0, changed[0] - context), end = Math.min(ops.length - 1, changed[0] + context);
|
|
1368
|
+
for (let c of changed.slice(1))
|
|
1369
|
+
c - context <= end + 1 || (ranges.push([start, end]), start = Math.max(0, c - context)), end = Math.min(ops.length - 1, c + context);
|
|
1370
|
+
ranges.push([start, end]);
|
|
1371
|
+
let out = [`--- ${file1}`, `+++ ${file2}`];
|
|
1372
|
+
for (let [rs, re] of ranges) {
|
|
1373
|
+
let oldCount = 0, newCount = 0;
|
|
1374
|
+
for (let k = rs; k <= re; k++)
|
|
1375
|
+
ops[k].t !== "+" && oldCount++, ops[k].t !== "-" && newCount++;
|
|
1376
|
+
out.push(`@@ -${oldAt[rs]},${oldCount} +${newAt[rs]},${newCount} @@`);
|
|
1377
|
+
for (let k = rs; k <= re; k++) out.push(`${ops[k].t}${ops[k].line}`);
|
|
1378
|
+
}
|
|
1379
|
+
return out.join(`
|
|
1380
|
+
`);
|
|
1381
|
+
}
|
|
1382
|
+
var DIFF_KNOWN_FLAGS = /* @__PURE__ */ new Set(["u", "q"]), diffHandler = {
|
|
1383
|
+
name: "diff",
|
|
1384
|
+
summary: "Compare two files",
|
|
1385
|
+
helpText: [
|
|
1386
|
+
"diff <file1> <file2> Compare files (unified-style output)",
|
|
1387
|
+
"",
|
|
1388
|
+
"Exit codes (bash semantics): 0 = identical (no output), 1 = files differ,",
|
|
1389
|
+
"2 = error (missing file, unsupported option).",
|
|
1390
|
+
"Options: -u accepted (output is already unified-style), -q report-only."
|
|
1391
|
+
].join(`
|
|
1392
|
+
`),
|
|
1393
|
+
category: "text",
|
|
1394
|
+
resolvedTools: ["fs_read"],
|
|
1395
|
+
async execute(ctx) {
|
|
1396
|
+
let unknown = Object.keys(ctx.flags).filter((f) => !DIFF_KNOWN_FLAGS.has(f));
|
|
1397
|
+
if (unknown.length > 0)
|
|
1398
|
+
return err(`diff: unsupported option${unknown.length > 1 ? "s" : ""}: ${unknown.map((f) => (f.length === 1 ? "-" : "--") + f).join(", ")}`, 2);
|
|
1399
|
+
if (ctx.args.length < 2) return err("diff: usage: diff <file1> <file2>", 2);
|
|
1400
|
+
let path1 = vfsPath2(ctx.args[0]), path2 = vfsPath2(ctx.args[1]), [row1, err1] = await shellReadFileRow(ctx.toolRegistry, ctx.workspace, path1);
|
|
1401
|
+
if (err1 !== null) return err(`diff: ${ctx.args[0]}: ${err1}`, 2);
|
|
1402
|
+
let [row2, err2] = await shellReadFileRow(ctx.toolRegistry, ctx.workspace, path2);
|
|
1403
|
+
if (err2 !== null) return err(`diff: ${ctx.args[1]}: ${err2}`, 2);
|
|
1404
|
+
if (!isTextRow(row1) || !isTextRow(row2))
|
|
1405
|
+
return isTextRow(row1) === isTextRow(row2) && row1.content === row2.content ? ok("") : { exit_code: 1, stdout: `Binary files ${path1} and ${path2} differ`, stderr: "" };
|
|
1406
|
+
if (row1.content === row2.content) return ok("");
|
|
1407
|
+
if (ctx.flags.q) return { exit_code: 1, stdout: `Files ${path1} and ${path2} differ`, stderr: "" };
|
|
1408
|
+
let ops = lineDiff(splitLines(row1.content), splitLines(row2.content));
|
|
1409
|
+
return ops.some((o) => o.t !== " ") ? { exit_code: 1, stdout: formatUnifiedDiff(path1, path2, ops), stderr: "" } : { exit_code: 1, stdout: `Files ${path1} and ${path2} differ only in a trailing newline`, stderr: "" };
|
|
1410
|
+
}
|
|
1411
|
+
};
|
|
1412
|
+
function shellQuote(s) {
|
|
1413
|
+
return "'" + s.split("'").join("'\\''") + "'";
|
|
1414
|
+
}
|
|
1415
|
+
var xargsHandler = {
|
|
1416
|
+
name: "xargs",
|
|
1417
|
+
summary: "Build and run a command from stdin",
|
|
1418
|
+
helpText: [
|
|
1419
|
+
"xargs <cmd> [args] Append whitespace-split stdin items as arguments",
|
|
1420
|
+
"xargs -n N <cmd> Same, but run one invocation per batch of N items",
|
|
1421
|
+
"xargs -I {} <cmd> Run command once per input LINE, substituting {}",
|
|
1422
|
+
"",
|
|
1423
|
+
"Options:",
|
|
1424
|
+
" -I <placeholder> Per-line substitution mode (e.g. -I {})",
|
|
1425
|
+
" -n <count> Batch mode: split stdin items into groups of <count>",
|
|
1426
|
+
" and run the command once per group, sequentially.",
|
|
1427
|
+
" Later batches still run if one fails; the exit code is",
|
|
1428
|
+
" the HIGHEST batch exit code (real xargs collapses any",
|
|
1429
|
+
" failure to 123 \u2014 we keep the real code instead). A",
|
|
1430
|
+
" batch blocked by tool gating (exit 126/130) stops the",
|
|
1431
|
+
" remaining batches. Cannot be combined with -I.",
|
|
1432
|
+
"",
|
|
1433
|
+
"Default mode (no -I/-n) follows real xargs: stdin is split on whitespace and",
|
|
1434
|
+
"the items are appended to the command \u2014 but as ONE invocation (real xargs",
|
|
1435
|
+
"batches by ARG_MAX; use -n for explicit batching). Items are passed as",
|
|
1436
|
+
"literal arguments; {} is only special with -I.",
|
|
1437
|
+
"Example: ls | jq -r '.[].path' | xargs -n 10 rm"
|
|
1438
|
+
].join(`
|
|
1439
|
+
`),
|
|
1440
|
+
category: "text",
|
|
1441
|
+
resolvedTools: [],
|
|
1442
|
+
// resolved at runtime based on target command
|
|
1443
|
+
valueFlags: /* @__PURE__ */ new Set(["I", "n"]),
|
|
1444
|
+
async execute(ctx) {
|
|
1445
|
+
if (ctx.args.length === 0) return err("xargs: missing command");
|
|
1446
|
+
let cmdName = ctx.args[0], cmdArgs = ctx.args.slice(1), text = ctx.stdin || "", batchSize;
|
|
1447
|
+
if ("n" in ctx.flags) {
|
|
1448
|
+
if (ctx.flags.I !== void 0)
|
|
1449
|
+
return err("xargs: -n cannot be combined with -I (real xargs silently ignores -n here; we refuse instead \u2014 -I already runs one invocation per line, so drop -n)");
|
|
1450
|
+
let rawFlag = ctx.flags.n, raw = Array.isArray(rawFlag) ? rawFlag[rawFlag.length - 1] : rawFlag;
|
|
1451
|
+
if (raw === !0) return err("xargs: -n requires a count (e.g. -n 2)");
|
|
1452
|
+
let n = /^-?\d+$/.test(String(raw)) ? parseInt(String(raw), 10) : NaN;
|
|
1453
|
+
if (!Number.isInteger(n) || n <= 0)
|
|
1454
|
+
return err(`xargs: -n: invalid number '${raw}'`);
|
|
1455
|
+
batchSize = n;
|
|
1456
|
+
}
|
|
1457
|
+
if (!text) return ok("");
|
|
1458
|
+
let { parse } = await import("./parser-JVY2PWSM.mjs"), { executeNode } = await import("./pipeline-executor-BHBSJX2K.mjs"), subCtx = {
|
|
1459
|
+
workspace: ctx.workspace,
|
|
1460
|
+
toolRegistry: ctx.toolRegistry,
|
|
1461
|
+
config: ctx.config,
|
|
1462
|
+
env: ctx.env,
|
|
1463
|
+
gate: ctx.gate,
|
|
1464
|
+
// forward gating to the spawned sub-command
|
|
1465
|
+
signal: ctx.signal,
|
|
1466
|
+
// forward abort so a cancelled shell stops xargs
|
|
1467
|
+
depth: (ctx.depth ?? 0) + 1
|
|
1468
|
+
};
|
|
1469
|
+
if (ctx.flags.I !== void 0) {
|
|
1470
|
+
let placeholder = typeof ctx.flags.I == "string" ? ctx.flags.I : "{}", lines = text.split(`
|
|
1471
|
+
`).filter((l) => l), outputs2 = [];
|
|
1472
|
+
for (let line of lines) {
|
|
1473
|
+
let resolvedArgs = cmdArgs.map((a) => a === placeholder ? line : a.split(placeholder).join(line)), fullCmd = `${cmdName} ${resolvedArgs.map((a) => `"${a}"`).join(" ")}`, ast = parse(fullCmd), result = await executeNode(ast, "", subCtx);
|
|
1474
|
+
if (ctx.signal?.aborted) return err("xargs: aborted", 130);
|
|
1475
|
+
if (result.exit_code !== 0) return result;
|
|
1476
|
+
result.stdout && outputs2.push(result.stdout);
|
|
1477
|
+
}
|
|
1478
|
+
return ok(outputs2.join(`
|
|
1479
|
+
`));
|
|
1480
|
+
}
|
|
1481
|
+
let items = text.split(/\s+/).filter(Boolean), size = batchSize ?? items.length, outputs = [], errors = [], worstExit = 0;
|
|
1482
|
+
for (let i = 0; i < items.length; i += size) {
|
|
1483
|
+
if (ctx.signal?.aborted) return err("xargs: aborted", 130);
|
|
1484
|
+
let batch = items.slice(i, i + size), fullCmd = [cmdName, ...cmdArgs.map(shellQuote), ...batch.map(shellQuote)].join(" "), ast = parse(fullCmd), result = await executeNode(ast, "", subCtx);
|
|
1485
|
+
if (ctx.signal?.aborted) return err("xargs: aborted", 130);
|
|
1486
|
+
if (result.stdout && outputs.push(result.stdout), result.stderr && errors.push(result.stderr), worstExit = Math.max(worstExit, result.exit_code), result.exit_code === 126 || result.exit_code === 130)
|
|
1487
|
+
return { exit_code: result.exit_code, stdout: outputs.join(""), stderr: errors.join(`
|
|
1488
|
+
`) };
|
|
1489
|
+
}
|
|
1490
|
+
return { exit_code: worstExit, stdout: outputs.join(""), stderr: errors.join(`
|
|
1491
|
+
`) };
|
|
1492
|
+
}
|
|
1493
|
+
}, seqHandler = {
|
|
1494
|
+
name: "seq",
|
|
1495
|
+
summary: "Generate number sequence",
|
|
1496
|
+
helpText: [
|
|
1497
|
+
"seq <last> Print 1 to last",
|
|
1498
|
+
"seq <first> <last> Print first to last",
|
|
1499
|
+
"seq <first> <inc> <last> Print with increment"
|
|
1500
|
+
].join(`
|
|
1501
|
+
`),
|
|
1502
|
+
category: "text",
|
|
1503
|
+
resolvedTools: [],
|
|
1504
|
+
async execute(ctx) {
|
|
1505
|
+
let nums0 = (ctx.rawArgs ?? ctx.args).filter((t) => /^-?\d+(\.\d+)?$/.test(t)).map(Number), first = 1, inc = 1, last = 1;
|
|
1506
|
+
if (nums0.length === 1)
|
|
1507
|
+
last = nums0[0];
|
|
1508
|
+
else if (nums0.length === 2)
|
|
1509
|
+
first = nums0[0], last = nums0[1];
|
|
1510
|
+
else if (nums0.length >= 3)
|
|
1511
|
+
first = nums0[0], inc = nums0[1], last = nums0[2];
|
|
1512
|
+
else
|
|
1513
|
+
return err("seq: missing operand");
|
|
1514
|
+
if (isNaN(first) || isNaN(inc) || isNaN(last) || inc === 0) return err("seq: invalid arguments");
|
|
1515
|
+
let maxItems = 1e5, estimated = Math.floor((last - first) / inc) + 1;
|
|
1516
|
+
if (estimated > maxItems)
|
|
1517
|
+
return err(`seq: range too large (${estimated} items > ${maxItems} limit)`);
|
|
1518
|
+
let nums = [];
|
|
1519
|
+
if (inc > 0)
|
|
1520
|
+
for (let i = first; i <= last; i += inc) nums.push(i);
|
|
1521
|
+
else
|
|
1522
|
+
for (let i = first; i >= last; i += inc) nums.push(i);
|
|
1523
|
+
return ok(nums.join(`
|
|
1524
|
+
`));
|
|
1525
|
+
}
|
|
1526
|
+
}, printfHandler = {
|
|
1527
|
+
name: "printf",
|
|
1528
|
+
summary: "Format and print text",
|
|
1529
|
+
helpText: [
|
|
1530
|
+
"printf <format> [args...] Format and print text",
|
|
1531
|
+
"",
|
|
1532
|
+
"Supports: %s (string), %d (integer), %f (float), %% (literal %)",
|
|
1533
|
+
"Escape sequences: \\n, \\t, \\r, \\\\"
|
|
1534
|
+
].join(`
|
|
1535
|
+
`),
|
|
1536
|
+
category: "text",
|
|
1537
|
+
resolvedTools: [],
|
|
1538
|
+
async execute(ctx) {
|
|
1539
|
+
if (ctx.args.length === 0) return err("printf: missing format string");
|
|
1540
|
+
let fmt = interpretEscapes(ctx.args[0]), fmtArgs = ctx.args.slice(1);
|
|
1541
|
+
if ((fmt.match(/%[sdfe]/g) ?? []).length === 0) return ok(fmt);
|
|
1542
|
+
let out = "", argIdx = 0;
|
|
1543
|
+
do
|
|
1544
|
+
out += fmt.replace(/%([sdfe%])/g, (match, spec) => {
|
|
1545
|
+
if (spec === "%") return "%";
|
|
1546
|
+
let arg = fmtArgs[argIdx++] ?? "";
|
|
1547
|
+
switch (spec) {
|
|
1548
|
+
case "s":
|
|
1549
|
+
return arg;
|
|
1550
|
+
case "d":
|
|
1551
|
+
return String(parseInt(arg, 10) || 0);
|
|
1552
|
+
case "f":
|
|
1553
|
+
case "e":
|
|
1554
|
+
return String(parseFloat(arg) || 0);
|
|
1555
|
+
default:
|
|
1556
|
+
return match;
|
|
1557
|
+
}
|
|
1558
|
+
});
|
|
1559
|
+
while (argIdx < fmtArgs.length);
|
|
1560
|
+
return ok(out);
|
|
1561
|
+
}
|
|
1562
|
+
}, textHandlers = [
|
|
1563
|
+
grepHandler,
|
|
1564
|
+
sedHandler,
|
|
1565
|
+
sortHandler,
|
|
1566
|
+
uniqHandler,
|
|
1567
|
+
wcHandler,
|
|
1568
|
+
cutHandler,
|
|
1569
|
+
trHandler,
|
|
1570
|
+
teeHandler,
|
|
1571
|
+
revHandler,
|
|
1572
|
+
tacHandler,
|
|
1573
|
+
diffHandler,
|
|
1574
|
+
xargsHandler,
|
|
1575
|
+
seqHandler,
|
|
1576
|
+
printfHandler
|
|
1577
|
+
];
|
|
1578
|
+
|
|
1579
|
+
// src/main/tools/shell/commands/jq-wasm-adapter.ts
|
|
1580
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
1581
|
+
import { Worker as Worker3 } from "node:worker_threads";
|
|
1582
|
+
function locateJqEntry() {
|
|
1583
|
+
return locate(["node_modules", "jq-wasm", "dist", "index.cjs"]);
|
|
1584
|
+
}
|
|
1585
|
+
function locateJqWasm() {
|
|
1586
|
+
return locate(["node_modules", "jq-wasm", "dist", "build", "jq.wasm"]);
|
|
1587
|
+
}
|
|
1588
|
+
var modulePromise2 = null;
|
|
1589
|
+
function getModule2() {
|
|
1590
|
+
return modulePromise2 || (modulePromise2 = WebAssembly.compile(readFileSync2(locateJqWasm()))), modulePromise2;
|
|
1591
|
+
}
|
|
1592
|
+
var WORKER_SRC3 = `
|
|
1593
|
+
const { parentPort, workerData } = require('node:worker_threads')
|
|
1594
|
+
;(async () => {
|
|
1595
|
+
const { entryPath, wasmModule, input, filter, flags } = workerData
|
|
1596
|
+
const jq = require(entryPath)
|
|
1597
|
+
const handle = await jq.loadJq({ wasmModule })
|
|
1598
|
+
const r = await handle.raw(input, filter, flags)
|
|
1599
|
+
parentPort.postMessage({ stdout: r.stdout, stderr: r.stderr, exitCode: r.exitCode })
|
|
1600
|
+
})().catch((e) => parentPort.postMessage({ error: String((e && e.message) || e) }))
|
|
1601
|
+
`, MAX_CONCURRENT_WORKERS2 = 4, activeWorkers2 = 0, workerQueue2 = [];
|
|
1602
|
+
function acquireSlot2() {
|
|
1603
|
+
return activeWorkers2 < MAX_CONCURRENT_WORKERS2 ? (activeWorkers2++, Promise.resolve()) : new Promise((resolve) => workerQueue2.push(resolve));
|
|
1604
|
+
}
|
|
1605
|
+
function releaseSlot2() {
|
|
1606
|
+
let next = workerQueue2.shift();
|
|
1607
|
+
next ? next() : activeWorkers2--;
|
|
1608
|
+
}
|
|
1609
|
+
async function runJq(input, filter, flags = [], opts = {}) {
|
|
1610
|
+
let wasmModule = await getModule2(), entryPath = locateJqEntry(), timeoutMs = opts.timeoutMs && opts.timeoutMs > 0 ? opts.timeoutMs : 6e4;
|
|
1611
|
+
return await acquireSlot2(), new Promise((resolve) => {
|
|
1612
|
+
let worker;
|
|
1613
|
+
try {
|
|
1614
|
+
worker = new Worker3(WORKER_SRC3, {
|
|
1615
|
+
eval: !0,
|
|
1616
|
+
workerData: { entryPath, wasmModule, input, filter, flags }
|
|
1617
|
+
});
|
|
1618
|
+
} catch (e) {
|
|
1619
|
+
releaseSlot2(), resolve({ stdout: "", stderr: `jq: worker start failed: ${e instanceof Error ? e.message : String(e)}`, exitCode: 1 });
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
let settled = !1, finish = (r) => {
|
|
1623
|
+
settled || (settled = !0, clearTimeout(timer), worker.terminate(), releaseSlot2(), resolve(r));
|
|
1624
|
+
}, timer = setTimeout(
|
|
1625
|
+
() => finish({ stdout: "", stderr: `jq: timed out after ${Math.round(timeoutMs / 1e3)}s`, exitCode: 124 }),
|
|
1626
|
+
timeoutMs
|
|
1627
|
+
);
|
|
1628
|
+
if (worker.once("message", (m) => {
|
|
1629
|
+
m?.error ? finish({ stdout: "", stderr: `jq: ${m.error}`, exitCode: 1 }) : finish({ stdout: m.stdout ?? "", stderr: m.stderr ?? "", exitCode: m.exitCode ?? 0 });
|
|
1630
|
+
}), worker.once("error", (e) => finish({ stdout: "", stderr: `jq: ${e.message}`, exitCode: 1 })), opts.signal) {
|
|
1631
|
+
let onAbort = () => finish({ stdout: "", stderr: "jq: aborted", exitCode: 130 });
|
|
1632
|
+
opts.signal.aborted ? onAbort() : opts.signal.addEventListener("abort", onAbort, { once: !0 });
|
|
1633
|
+
}
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
// src/main/tools/shell/commands/structured.ts
|
|
1638
|
+
var JQ_PASSTHROUGH_FLAGS = [
|
|
1639
|
+
["r", "-r"],
|
|
1640
|
+
["s", "-s"],
|
|
1641
|
+
["c", "-c"],
|
|
1642
|
+
["n", "-n"],
|
|
1643
|
+
["e", "-e"],
|
|
1644
|
+
["j", "-j"],
|
|
1645
|
+
["R", "-R"],
|
|
1646
|
+
["S", "-S"],
|
|
1647
|
+
["a", "-a"],
|
|
1648
|
+
["slurp", "-s"],
|
|
1649
|
+
["raw-input", "-R"],
|
|
1650
|
+
["raw-output", "-r"],
|
|
1651
|
+
["sort-keys", "-S"],
|
|
1652
|
+
["compact-output", "-c"],
|
|
1653
|
+
["null-input", "-n"],
|
|
1654
|
+
["tab", "--tab"],
|
|
1655
|
+
["ascii-output", "-a"]
|
|
1656
|
+
], jqHandler = {
|
|
1657
|
+
name: "jq",
|
|
1658
|
+
summary: "JSON processor",
|
|
1659
|
+
helpText: [
|
|
1660
|
+
"jq '<expr>' Process JSON with jq expression",
|
|
1661
|
+
"",
|
|
1662
|
+
"Full jq 1.8.2 (real jq via WebAssembly): def, foreach, label/break,",
|
|
1663
|
+
"multi-document input, @base64/@uri/@sh, and everything else jq supports.",
|
|
1664
|
+
"",
|
|
1665
|
+
"Options (standard jq flags pass through):",
|
|
1666
|
+
" -r raw output -R raw input -s slurp -S sort keys",
|
|
1667
|
+
" -c compact -n null input -e exit status -j join -a ascii",
|
|
1668
|
+
" --tab long forms: --raw-input/--raw-output/--sort-keys/\u2026"
|
|
1669
|
+
].join(`
|
|
1670
|
+
`),
|
|
1671
|
+
category: "data",
|
|
1672
|
+
resolvedTools: [],
|
|
1673
|
+
async execute(ctx) {
|
|
1674
|
+
let expression = ctx.args[0];
|
|
1675
|
+
for (let key of ["slurp", "tab"])
|
|
1676
|
+
expression === void 0 && typeof ctx.flags[key] == "string" && (expression = ctx.flags[key]);
|
|
1677
|
+
if (expression === void 0) return err("jq: missing expression");
|
|
1678
|
+
let extraPositionals = ctx.args.slice(1);
|
|
1679
|
+
if (extraPositionals.length > 0)
|
|
1680
|
+
return err(`jq: file arguments not supported (${extraPositionals.join(", ")}) \u2014 pipe the file instead: \`cat ${extraPositionals[0]} | jq '${expression}'\``);
|
|
1681
|
+
let flags = JQ_PASSTHROUGH_FLAGS.filter(([key]) => !!ctx.flags[key]).map(([, cliFlag]) => cliFlag), dedupedFlags = [...new Set(flags)], nullInput = dedupedFlags.includes("-n"), text = ctx.stdin || "";
|
|
1682
|
+
if (!text && !nullInput) return err("jq: no input");
|
|
1683
|
+
let { stdout, stderr, exitCode } = await runJq(text, expression, dedupedFlags, {
|
|
1684
|
+
timeoutMs: ctx.config.limits?.execution_timeout_ms,
|
|
1685
|
+
signal: ctx.signal
|
|
1686
|
+
});
|
|
1687
|
+
return exitCode !== 0 ? {
|
|
1688
|
+
exit_code: exitCode,
|
|
1689
|
+
stdout,
|
|
1690
|
+
stderr: stderr.trim() || `jq: exit ${exitCode}`
|
|
1691
|
+
} : ok(stdout);
|
|
1692
|
+
}
|
|
1693
|
+
};
|
|
1694
|
+
function isReadQuery(sql) {
|
|
1695
|
+
let first = sql.trimStart().split(/\s/)[0].toUpperCase();
|
|
1696
|
+
return ["SELECT", "WITH", "EXPLAIN"].includes(first);
|
|
1697
|
+
}
|
|
1698
|
+
function isDbPath(arg) {
|
|
1699
|
+
return /\.(adf|db|sqlite)$/i.test(arg) || /^:.*:$/.test(arg);
|
|
1700
|
+
}
|
|
1701
|
+
function splitStatements(sql) {
|
|
1702
|
+
let stmts = [], current = "", inString = !1, quote = "";
|
|
1703
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1704
|
+
let ch = sql[i];
|
|
1705
|
+
if (inString)
|
|
1706
|
+
current += ch, ch === quote && (i + 1 < sql.length && sql[i + 1] === quote ? (current += sql[i + 1], i++) : inString = !1);
|
|
1707
|
+
else if (ch === "'" || ch === '"')
|
|
1708
|
+
inString = !0, quote = ch, current += ch;
|
|
1709
|
+
else if (ch === ";") {
|
|
1710
|
+
let trimmed2 = current.trim();
|
|
1711
|
+
trimmed2 && stmts.push(trimmed2), current = "";
|
|
1712
|
+
} else
|
|
1713
|
+
current += ch;
|
|
1714
|
+
}
|
|
1715
|
+
let trimmed = current.trim();
|
|
1716
|
+
return trimmed && stmts.push(trimmed), stmts;
|
|
1717
|
+
}
|
|
1718
|
+
function parseRows(content) {
|
|
1719
|
+
try {
|
|
1720
|
+
let parsed = JSON.parse(content);
|
|
1721
|
+
if (Array.isArray(parsed) && parsed.length > 0 && typeof parsed[0] == "object") return parsed;
|
|
1722
|
+
} catch {
|
|
1723
|
+
}
|
|
1724
|
+
return null;
|
|
1725
|
+
}
|
|
1726
|
+
function formatOutput(content, format) {
|
|
1727
|
+
let rows = parseRows(content);
|
|
1728
|
+
if (!rows) return content;
|
|
1729
|
+
let cols = Object.keys(rows[0]);
|
|
1730
|
+
if (format === "csv") {
|
|
1731
|
+
let csvEscape = (v) => {
|
|
1732
|
+
let s = v == null ? "" : String(v);
|
|
1733
|
+
return s.includes(",") || s.includes('"') || s.includes(`
|
|
1734
|
+
`) ? `"${s.replace(/"/g, '""')}"` : s;
|
|
1735
|
+
}, lines = [cols.join(",")];
|
|
1736
|
+
for (let row of rows)
|
|
1737
|
+
lines.push(cols.map((c) => csvEscape(row[c])).join(","));
|
|
1738
|
+
return lines.join(`
|
|
1739
|
+
`);
|
|
1740
|
+
}
|
|
1741
|
+
let widths = cols.map((c) => c.length), stringRows = rows.map(
|
|
1742
|
+
(row) => cols.map((c, i) => {
|
|
1743
|
+
let s = row[c] === null || row[c] === void 0 ? "NULL" : String(row[c]);
|
|
1744
|
+
return widths[i] = Math.max(widths[i], s.length), s;
|
|
1745
|
+
})
|
|
1746
|
+
), header = cols.map((c, i) => c.padEnd(widths[i])).join(" "), sep = widths.map((w) => "-".repeat(w)).join(" "), body = stringRows.map((r) => r.map((s, i) => s.padEnd(widths[i])).join(" ")).join(`
|
|
1747
|
+
`);
|
|
1748
|
+
return `${header}
|
|
1749
|
+
${sep}
|
|
1750
|
+
${body}`;
|
|
1751
|
+
}
|
|
1752
|
+
var sqlite3Handler = {
|
|
1753
|
+
name: "sqlite3",
|
|
1754
|
+
summary: "Execute SQL queries",
|
|
1755
|
+
helpText: [
|
|
1756
|
+
'sqlite3 "<sql>" Query the agent database',
|
|
1757
|
+
'sqlite3 --exec "<sql>" Force write mode',
|
|
1758
|
+
`sqlite3 "SELECT * FROM t WHERE k = ?" --params '["v"]' Bind parameters`,
|
|
1759
|
+
"",
|
|
1760
|
+
"Auto-detects SELECT/WITH/EXPLAIN as reads (db_query),",
|
|
1761
|
+
"INSERT/UPDATE/DELETE/CREATE/DROP as writes (db_execute).",
|
|
1762
|
+
"PRAGMA is not supported. Multiple statements separated by ; are supported.",
|
|
1763
|
+
"Database path arguments are ignored (always uses agent DB).",
|
|
1764
|
+
"",
|
|
1765
|
+
"Options:",
|
|
1766
|
+
" --exec Force execute mode (INSERT/UPDATE/DELETE)",
|
|
1767
|
+
" --params '[...]' JSON array of values to bind to ? placeholders",
|
|
1768
|
+
" --csv CSV output",
|
|
1769
|
+
" --json JSON output"
|
|
1770
|
+
].join(`
|
|
1771
|
+
`),
|
|
1772
|
+
category: "data",
|
|
1773
|
+
resolvedTools: ["db_query", "db_execute"],
|
|
1774
|
+
async execute(ctx) {
|
|
1775
|
+
let execFlag = ctx.flags.exec, jsonFlag = ctx.flags.json, csvFlag = ctx.flags.csv, forceExec = !!execFlag, format = csvFlag ? "csv" : jsonFlag ? "json" : "table", params;
|
|
1776
|
+
if (ctx.flags.params !== void 0) {
|
|
1777
|
+
let raw = ctx.flags.params;
|
|
1778
|
+
try {
|
|
1779
|
+
let parsed = JSON.parse(typeof raw == "string" ? raw : "[]");
|
|
1780
|
+
if (!Array.isArray(parsed)) return err(`sqlite3: --params must be a JSON array, e.g. --params '["a", 1]'`);
|
|
1781
|
+
params = parsed;
|
|
1782
|
+
} catch {
|
|
1783
|
+
return err(`sqlite3: invalid --params JSON: ${String(raw)}`);
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
let sqlParts = [];
|
|
1787
|
+
for (let flag of [execFlag, jsonFlag, csvFlag])
|
|
1788
|
+
typeof flag == "string" && sqlParts.push(flag);
|
|
1789
|
+
for (let a of ctx.args)
|
|
1790
|
+
isDbPath(a) || sqlParts.push(a);
|
|
1791
|
+
sqlParts.length === 0 && ctx.stdin && sqlParts.push(ctx.stdin.trim());
|
|
1792
|
+
let rawSql = sqlParts.join("; ");
|
|
1793
|
+
if (!rawSql) return err("sqlite3: missing SQL query");
|
|
1794
|
+
let statements = splitStatements(rawSql);
|
|
1795
|
+
if (statements.length === 0) return err("sqlite3: empty SQL");
|
|
1796
|
+
let outputs = [];
|
|
1797
|
+
for (let sql of statements) {
|
|
1798
|
+
let toolName = !forceExec && isReadQuery(sql) ? "db_query" : "db_execute", toolInput = { sql };
|
|
1799
|
+
params !== void 0 && (toolInput.params = params);
|
|
1800
|
+
let result = await ctx.toolRegistry.executeTool(toolName, toolInput, ctx.workspace);
|
|
1801
|
+
if (result.isError) {
|
|
1802
|
+
let msg = result.content;
|
|
1803
|
+
return msg.includes("restricted") || msg.includes("not allowed") ? err(`sqlite3: ${msg}
|
|
1804
|
+
Note: Only adf_* tables are accessible. Use "sqlite3 'SELECT name FROM sqlite_master WHERE type=\\'table\\''" to list tables.`) : err(`sqlite3: ${msg}`);
|
|
1805
|
+
}
|
|
1806
|
+
result.content && outputs.push(result.content);
|
|
1807
|
+
}
|
|
1808
|
+
let combined = outputs.join(`
|
|
1809
|
+
`);
|
|
1810
|
+
return format === "json" || !combined ? ok(combined) : ok(formatOutput(combined, format));
|
|
1811
|
+
}
|
|
1812
|
+
}, structuredHandlers = [jqHandler, sqlite3Handler];
|
|
1813
|
+
|
|
1814
|
+
// src/main/tools/shell/commands/messaging.ts
|
|
1815
|
+
function discoveryScope(ctx) {
|
|
1816
|
+
return ctx.flags.local !== void 0 ? "local" : "all";
|
|
1817
|
+
}
|
|
1818
|
+
var MSG_SUBCOMMAND_TOOLS = [
|
|
1819
|
+
["read", "msg_read"],
|
|
1820
|
+
["list", "msg_list"],
|
|
1821
|
+
["agents", "agent_discover"],
|
|
1822
|
+
["update", "msg_update"],
|
|
1823
|
+
["archive", "msg_update"],
|
|
1824
|
+
["delete", "msg_update"]
|
|
1825
|
+
];
|
|
1826
|
+
function staticArgValue(arg) {
|
|
1827
|
+
return arg.type === "literal" ? arg.value : arg.type === "quoted" && arg.parts.every((p) => p.type === "literal") ? arg.parts.map((p) => p.value).join("") : null;
|
|
1828
|
+
}
|
|
1829
|
+
var msgHandler = {
|
|
1830
|
+
name: "msg",
|
|
1831
|
+
summary: "Send, read, and manage messages",
|
|
1832
|
+
helpText: [
|
|
1833
|
+
'msg <to> "body" Send a message (to = did:key:\u2026, type:id, or a local handle)',
|
|
1834
|
+
'echo "body" | msg <to> Send with piped body',
|
|
1835
|
+
"msg --read [--status S] [--limit N] Read inbox (default: unread; also read|archived)",
|
|
1836
|
+
"msg --list List message counts",
|
|
1837
|
+
"msg --agents [--local] List discoverable agents (mesh-wide; --local = this runtime only)",
|
|
1838
|
+
"msg --update <ids> --status <read|archived|delete> Update message status",
|
|
1839
|
+
"msg --archive <ids> Archive messages",
|
|
1840
|
+
"msg --delete <ids> Delete messages (archives first, then deletes)",
|
|
1841
|
+
"",
|
|
1842
|
+
"Heredoc:",
|
|
1843
|
+
" msg <to> <<TAG",
|
|
1844
|
+
" subject: Report ready",
|
|
1845
|
+
" attach: data/report.md",
|
|
1846
|
+
" ---",
|
|
1847
|
+
" Here is the body text.",
|
|
1848
|
+
" TAG",
|
|
1849
|
+
"",
|
|
1850
|
+
"Flags: --address, --attach, --subject, --thread, --parent"
|
|
1851
|
+
].join(`
|
|
1852
|
+
`),
|
|
1853
|
+
category: "messaging",
|
|
1854
|
+
// Tools are resolved per-subcommand below — a static ['msg_send'] would gate
|
|
1855
|
+
// `msg --read` on msg_send instead of msg_read.
|
|
1856
|
+
resolvedTools: [],
|
|
1857
|
+
resolveToolsFromArgs(args) {
|
|
1858
|
+
let present = /* @__PURE__ */ new Set();
|
|
1859
|
+
for (let arg of args) {
|
|
1860
|
+
let v = staticArgValue(arg);
|
|
1861
|
+
if (v === "--") break;
|
|
1862
|
+
!v || !v.startsWith("--") || present.add(v.slice(2).split("=")[0]);
|
|
1863
|
+
}
|
|
1864
|
+
for (let [flag, tool] of MSG_SUBCOMMAND_TOOLS)
|
|
1865
|
+
if (present.has(flag)) return [tool];
|
|
1866
|
+
return ["msg_send"];
|
|
1867
|
+
},
|
|
1868
|
+
async execute(ctx) {
|
|
1869
|
+
if (ctx.flags.h !== void 0 || ctx.flags.help !== void 0) return ok(msgHandler.helpText);
|
|
1870
|
+
if (ctx.flags.read !== void 0) return msgRead(ctx);
|
|
1871
|
+
if (ctx.flags.list !== void 0) return msgList(ctx);
|
|
1872
|
+
if (ctx.flags.agents !== void 0) return msgListAgents(ctx);
|
|
1873
|
+
if (ctx.flags.update !== void 0) return msgUpdate(ctx);
|
|
1874
|
+
if (ctx.flags.archive !== void 0) return msgArchive(ctx);
|
|
1875
|
+
if (ctx.flags.delete !== void 0) return msgDelete(ctx);
|
|
1876
|
+
if (ctx.args.length === 0) return err("msg: missing recipient");
|
|
1877
|
+
if (ctx.args[0] === "send" && (ctx.flags.to !== void 0 || ctx.flags.content !== void 0))
|
|
1878
|
+
return err('msg: there is no `send` subcommand and no --to/--content flags \u2014 the recipient and body are positional: msg <to> "body" (or pipe/heredoc the body). See msg -h.');
|
|
1879
|
+
let to = ctx.args[0], body = ctx.args.slice(1).join(" ");
|
|
1880
|
+
if (!body && ctx.stdin && (body = ctx.stdin), !body) return err("msg: missing message body");
|
|
1881
|
+
let subject = ctx.flags.subject, attachments, attachFlag = ctx.flags.attach;
|
|
1882
|
+
if (typeof attachFlag == "string" ? attachments = [attachFlag] : Array.isArray(attachFlag) && (attachments = attachFlag), body.includes("---")) {
|
|
1883
|
+
let parts = body.split("---"), headerLines = parts[0].split(`
|
|
1884
|
+
`), bodyPart = parts.slice(1).join("---").trim();
|
|
1885
|
+
for (let line of headerLines) {
|
|
1886
|
+
let trimmed = line.trim();
|
|
1887
|
+
trimmed.toLowerCase().startsWith("subject:") ? subject = trimmed.slice(8).trim() : trimmed.toLowerCase().startsWith("attach:") && (attachments || (attachments = []), attachments.push(trimmed.slice(7).trim()));
|
|
1888
|
+
}
|
|
1889
|
+
body = bodyPart;
|
|
1890
|
+
}
|
|
1891
|
+
let input = {
|
|
1892
|
+
recipient: to,
|
|
1893
|
+
content: body
|
|
1894
|
+
};
|
|
1895
|
+
subject && (input.subject = subject), attachments?.length && (input.attachments = attachments), ctx.flags.address && (input.address = ctx.flags.address), ctx.flags.thread && (input.thread_id = ctx.flags.thread), ctx.flags.parent && (input.parent_id = ctx.flags.parent);
|
|
1896
|
+
let result = await ctx.toolRegistry.executeTool("msg_send", input, ctx.workspace);
|
|
1897
|
+
return result.isError ? err(`msg: ${result.content}`) : ok(result.content);
|
|
1898
|
+
}
|
|
1899
|
+
}, MSG_READ_STATUSES = ["unread", "read", "archived"], MSG_UPDATE_STATUSES = ["read", "archived", "delete"];
|
|
1900
|
+
async function msgRead(ctx) {
|
|
1901
|
+
let status = typeof ctx.flags.status == "string" ? ctx.flags.status : "unread";
|
|
1902
|
+
if (!MSG_READ_STATUSES.includes(status))
|
|
1903
|
+
return err(`msg --read: invalid --status "${status}". Valid: ${MSG_READ_STATUSES.join(", ")}.`);
|
|
1904
|
+
let limit = ctx.flags.limit ? parseInt(String(ctx.flags.limit), 10) : 50, input = { status, limit }, result = await ctx.toolRegistry.executeTool("msg_read", input, ctx.workspace);
|
|
1905
|
+
return result.isError ? err(`msg --read: ${result.content}`) : ok(result.content);
|
|
1906
|
+
}
|
|
1907
|
+
async function msgList(ctx) {
|
|
1908
|
+
let result = await ctx.toolRegistry.executeTool("msg_list", {}, ctx.workspace);
|
|
1909
|
+
return result.isError ? err(`msg --list: ${result.content}`) : ok(result.content);
|
|
1910
|
+
}
|
|
1911
|
+
async function msgListAgents(ctx) {
|
|
1912
|
+
let result = await ctx.toolRegistry.executeTool("agent_discover", { scope: discoveryScope(ctx) }, ctx.workspace);
|
|
1913
|
+
return result.isError ? err(`msg --agents: ${result.content}`) : ok(result.content);
|
|
1914
|
+
}
|
|
1915
|
+
async function msgUpdate(ctx) {
|
|
1916
|
+
let ids = typeof ctx.flags.update == "string" ? ctx.flags.update : ctx.args[0];
|
|
1917
|
+
if (!ids) return err("msg --update: missing message IDs");
|
|
1918
|
+
let status = ctx.flags.status;
|
|
1919
|
+
if (!status) return err(`msg --update: missing --status (${MSG_UPDATE_STATUSES.join(", ")})`);
|
|
1920
|
+
if (!MSG_UPDATE_STATUSES.includes(status))
|
|
1921
|
+
return err(`msg --update: invalid --status "${status}". Valid: ${MSG_UPDATE_STATUSES.join(", ")}.`);
|
|
1922
|
+
let idList = ids.split(",").map((id) => id.trim()), result = await ctx.toolRegistry.executeTool("msg_update", { message_ids: idList, status }, ctx.workspace);
|
|
1923
|
+
return result.isError ? err(`msg --update: ${result.content}`) : ok(result.content);
|
|
1924
|
+
}
|
|
1925
|
+
async function msgArchive(ctx) {
|
|
1926
|
+
let ids = typeof ctx.flags.archive == "string" ? ctx.flags.archive : ctx.args[0];
|
|
1927
|
+
if (!ids) return err("msg --archive: missing message IDs");
|
|
1928
|
+
let idList = ids.split(",").map((id) => id.trim()), result = await ctx.toolRegistry.executeTool("msg_update", { message_ids: idList, status: "archived" }, ctx.workspace);
|
|
1929
|
+
return result.isError ? err(`msg --archive: ${result.content}`) : ok(result.content);
|
|
1930
|
+
}
|
|
1931
|
+
async function msgDelete(ctx) {
|
|
1932
|
+
let ids = typeof ctx.flags.delete == "string" ? ctx.flags.delete : ctx.args[0];
|
|
1933
|
+
if (!ids) return err("msg --delete: missing message IDs");
|
|
1934
|
+
let idList = ids.split(",").map((id) => id.trim()), archived = await ctx.toolRegistry.executeTool("msg_update", { message_ids: idList, status: "archived" }, ctx.workspace);
|
|
1935
|
+
if (archived.isError) return err(`msg --delete (archive step): ${archived.content}`);
|
|
1936
|
+
let result = await ctx.toolRegistry.executeTool("msg_update", { message_ids: idList, status: "delete" }, ctx.workspace);
|
|
1937
|
+
return result.isError ? err(`msg --delete: ${result.content}`) : ok(result.content);
|
|
1938
|
+
}
|
|
1939
|
+
var whoHandler = {
|
|
1940
|
+
name: "who",
|
|
1941
|
+
summary: "List discoverable agents",
|
|
1942
|
+
helpText: "who [--local] List discoverable agents, mesh-wide by default (alias for msg --agents). --local = this runtime only",
|
|
1943
|
+
category: "messaging",
|
|
1944
|
+
resolvedTools: ["agent_discover"],
|
|
1945
|
+
async execute(ctx) {
|
|
1946
|
+
let result = await ctx.toolRegistry.executeTool("agent_discover", { scope: discoveryScope(ctx) }, ctx.workspace);
|
|
1947
|
+
return result.isError ? err(`who: ${result.content}`) : ok(result.content);
|
|
1948
|
+
}
|
|
1949
|
+
}, pingHandler = {
|
|
1950
|
+
name: "ping",
|
|
1951
|
+
summary: "Check agent reachability",
|
|
1952
|
+
helpText: "ping <recipient> Check if an agent is reachable mesh-wide (by handle or DID)",
|
|
1953
|
+
category: "messaging",
|
|
1954
|
+
resolvedTools: ["agent_discover"],
|
|
1955
|
+
async execute(ctx) {
|
|
1956
|
+
if (ctx.args.length === 0) return err("ping: missing recipient");
|
|
1957
|
+
let target = ctx.args[0], agents = await ctx.toolRegistry.executeTool("agent_discover", { scope: "all" }, ctx.workspace);
|
|
1958
|
+
if (agents.isError) return err(`ping: ${agents.content}`);
|
|
1959
|
+
let cards = null;
|
|
1960
|
+
try {
|
|
1961
|
+
let parsed = JSON.parse(agents.content);
|
|
1962
|
+
Array.isArray(parsed) && (cards = parsed);
|
|
1963
|
+
} catch {
|
|
1964
|
+
}
|
|
1965
|
+
return !cards || cards.length === 0 ? ok(`${target}: not found (no agents reachable from this runtime)`) : cards.find((c) => c.handle === target || c.did === target) ? ok(`${target}: reachable`) : ok(`${target}: not found (not among the ${cards.length} discoverable agent${cards.length === 1 ? "" : "s"})`);
|
|
1966
|
+
}
|
|
1967
|
+
}, messagingHandlers = [msgHandler, whoHandler, pingHandler];
|
|
1968
|
+
|
|
1969
|
+
// src/main/tools/shell/commands/networking.ts
|
|
1970
|
+
function vfsPath3(p) {
|
|
1971
|
+
return p === "." || p === "./" || p === "/" ? "" : p.replace(/^\.\//, "").replace(/^\//, "");
|
|
1972
|
+
}
|
|
1973
|
+
function staticArgValue2(arg) {
|
|
1974
|
+
return arg.type === "literal" ? arg.value : arg.type === "quoted" && arg.parts.length === 1 && arg.parts[0].type === "literal" ? arg.parts[0].value : null;
|
|
1975
|
+
}
|
|
1976
|
+
var curlHandler = {
|
|
1977
|
+
name: "curl",
|
|
1978
|
+
aliases: ["wget"],
|
|
1979
|
+
summary: "HTTP requests",
|
|
1980
|
+
helpText: [
|
|
1981
|
+
"curl <url> GET request",
|
|
1982
|
+
"curl -X POST -d 'data' <url> POST with body",
|
|
1983
|
+
'curl -H "Header: Value" <url> Custom headers',
|
|
1984
|
+
"curl -o <path> <url> Save response BODY to file",
|
|
1985
|
+
"",
|
|
1986
|
+
"This is a wrapper over the sys_fetch tool, not real curl. Default stdout",
|
|
1987
|
+
"is a JSON envelope {status,statusText,headers,body}, NOT the raw body.",
|
|
1988
|
+
"Idioms: curl -s url | jq -r .body curl -s url | jq .status",
|
|
1989
|
+
'Binary responses carry a base64 body plus "_body_encoding": "base64".',
|
|
1990
|
+
"",
|
|
1991
|
+
"Options:",
|
|
1992
|
+
" -X <method> HTTP method",
|
|
1993
|
+
" -H <header> Add header (repeatable)",
|
|
1994
|
+
" -d <data> Request body",
|
|
1995
|
+
" -o <path> Save the raw response body (only) to a VFS",
|
|
1996
|
+
" file; a one-line saved note goes to stderr",
|
|
1997
|
+
" unless -s. Base64 bodies are saved as-is.",
|
|
1998
|
+
" -O <path> Same as -o (wget compat)",
|
|
1999
|
+
" -s Silent: suppress the -o saved note",
|
|
2000
|
+
" -w <fmt> Write-out after the output; supports ONLY",
|
|
2001
|
+
" %{http_code} (plus \\n/\\t/\\r escapes) \u2014",
|
|
2002
|
+
" any other %{var} is an error",
|
|
2003
|
+
" -v NOT supported (errors) \u2014 the envelope already",
|
|
2004
|
+
" has it: curl -s url | jq '{status,headers}'"
|
|
2005
|
+
].join(`
|
|
2006
|
+
`),
|
|
2007
|
+
category: "network",
|
|
2008
|
+
resolvedTools: ["sys_fetch"],
|
|
2009
|
+
// -o/-O writes the response into the VFS via fs_write — surface that to the
|
|
2010
|
+
// pre-gate so disabling fs_write actually blocks `curl -o` (it previously
|
|
2011
|
+
// wrote ungated). Matches -o, -O, and the attached form -opath; a flag we
|
|
2012
|
+
// can't see statically (e.g. a variable) stays sys_fetch-only.
|
|
2013
|
+
resolveToolsFromArgs(args) {
|
|
2014
|
+
return args.some((a) => {
|
|
2015
|
+
let v = staticArgValue2(a);
|
|
2016
|
+
return v !== null && /^-[oO]/.test(v);
|
|
2017
|
+
}) ? ["fs_write"] : [];
|
|
2018
|
+
},
|
|
2019
|
+
valueFlags: /* @__PURE__ */ new Set(["X", "H", "d", "o", "O", "w"]),
|
|
2020
|
+
async execute(ctx) {
|
|
2021
|
+
let url = ctx.args[ctx.args.length - 1];
|
|
2022
|
+
if (!url) return err("curl: missing URL");
|
|
2023
|
+
let method = ctx.flags.X ?? (ctx.flags.d ? "POST" : "GET"), body = ctx.flags.d, outputPath = ctx.flags.o ?? ctx.flags.O, silent = !!ctx.flags.s, verbose = !!ctx.flags.v, writeOut = typeof ctx.flags.w == "string" ? ctx.flags.w : void 0, headers = {}, headerFlag = ctx.flags.H;
|
|
2024
|
+
if (headerFlag) {
|
|
2025
|
+
let headerList = Array.isArray(headerFlag) ? headerFlag : [headerFlag];
|
|
2026
|
+
for (let h of headerList) {
|
|
2027
|
+
let colonIdx = h.indexOf(":");
|
|
2028
|
+
colonIdx > 0 && (headers[h.slice(0, colonIdx).trim()] = h.slice(colonIdx + 1).trim());
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
let requestBody = body ?? (ctx.stdin || void 0), input = {
|
|
2032
|
+
url,
|
|
2033
|
+
method: method.toUpperCase()
|
|
2034
|
+
};
|
|
2035
|
+
if (Object.keys(headers).length > 0 && (input.headers = headers), requestBody && (input.body = requestBody), verbose)
|
|
2036
|
+
return err("curl: -v is not supported in adf_shell \u2014 the default stdout envelope already carries it: curl -s url | jq '{status,headers}'");
|
|
2037
|
+
let result = await ctx.toolRegistry.executeTool("sys_fetch", input, ctx.workspace);
|
|
2038
|
+
if (result.isError) return err(`curl: ${result.content}`);
|
|
2039
|
+
let envelope = null;
|
|
2040
|
+
try {
|
|
2041
|
+
let parsed = JSON.parse(result.content);
|
|
2042
|
+
parsed && typeof parsed == "object" && (envelope = parsed);
|
|
2043
|
+
} catch {
|
|
2044
|
+
}
|
|
2045
|
+
let writeOutText = "";
|
|
2046
|
+
if (writeOut !== void 0) {
|
|
2047
|
+
if (!envelope || typeof envelope.status != "number")
|
|
2048
|
+
return err("curl: -w: response envelope has no status to report");
|
|
2049
|
+
let unsupported = [];
|
|
2050
|
+
if (writeOutText = writeOut.replace(/%\{([^}]*)\}/g, (_m, name) => name === "http_code" ? String(envelope.status) : (unsupported.push(name), "")).replace(/\\n/g, `
|
|
2051
|
+
`).replace(/\\t/g, " ").replace(/\\r/g, "\r"), unsupported.length > 0)
|
|
2052
|
+
return err(`curl: -w: %{${unsupported[0]}} is not supported in adf_shell \u2014 only %{http_code} is`);
|
|
2053
|
+
}
|
|
2054
|
+
if (outputPath) {
|
|
2055
|
+
let normalizedPath = vfsPath3(outputPath);
|
|
2056
|
+
if (normalizedPath === "dev/null")
|
|
2057
|
+
return { exit_code: 0, stdout: writeOutText, stderr: "" };
|
|
2058
|
+
if (normalizedPath === "dev/stdout" || normalizedPath === "dev/stderr")
|
|
2059
|
+
return err(`curl: -o ${outputPath} is not supported in adf_shell \u2014 omit -o and pipe stdout instead`);
|
|
2060
|
+
let fileContent = envelope && typeof envelope.body == "string" ? envelope.body : result.content, write = await ctx.toolRegistry.executeTool("fs_write", {
|
|
2061
|
+
mode: "write",
|
|
2062
|
+
path: normalizedPath,
|
|
2063
|
+
content: fileContent
|
|
2064
|
+
}, ctx.workspace);
|
|
2065
|
+
return write.isError ? err(`curl: -o ${outputPath}: ${write.content}`) : {
|
|
2066
|
+
exit_code: 0,
|
|
2067
|
+
stdout: writeOutText,
|
|
2068
|
+
// Real curl keeps stdout clean and reports progress on stderr; -s silences it.
|
|
2069
|
+
stderr: silent ? "" : `curl: saved response body to ${outputPath} (${Buffer.byteLength(fileContent, "utf8")} bytes)`
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
return ok(result.content + writeOutText);
|
|
2073
|
+
}
|
|
2074
|
+
}, networkingHandlers = [curlHandler];
|
|
2075
|
+
|
|
2076
|
+
// src/main/tools/shell/commands/timers.ts
|
|
2077
|
+
function parseDuration(input) {
|
|
2078
|
+
let match = input.match(/^(\d+)(s|m|h|d)$/);
|
|
2079
|
+
if (!match) return null;
|
|
2080
|
+
let [, num, unit] = match, n = parseInt(num, 10);
|
|
2081
|
+
switch (unit) {
|
|
2082
|
+
case "s":
|
|
2083
|
+
return n * 1e3;
|
|
2084
|
+
case "m":
|
|
2085
|
+
return n * 60 * 1e3;
|
|
2086
|
+
case "h":
|
|
2087
|
+
return n * 60 * 60 * 1e3;
|
|
2088
|
+
case "d":
|
|
2089
|
+
return n * 24 * 60 * 60 * 1e3;
|
|
2090
|
+
default:
|
|
2091
|
+
return null;
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
var atHandler = {
|
|
2095
|
+
name: "at",
|
|
2096
|
+
summary: "Schedule a timer",
|
|
2097
|
+
helpText: [
|
|
2098
|
+
'at "<iso_time>" <lambda> One-shot at absolute time',
|
|
2099
|
+
"at --delay <duration> <lambda> One-shot after delay (30s, 5m, 1h, 2d)",
|
|
2100
|
+
"at --every <duration> <lambda> Recurring interval",
|
|
2101
|
+
'at --cron "<expr>" <lambda> Cron expression (5-field)',
|
|
2102
|
+
"",
|
|
2103
|
+
"Options:",
|
|
2104
|
+
" --scope system System scope (default: agent)",
|
|
2105
|
+
' --payload "<data>" Attach payload string'
|
|
2106
|
+
].join(`
|
|
2107
|
+
`),
|
|
2108
|
+
category: "timers",
|
|
2109
|
+
resolvedTools: ["sys_set_timer"],
|
|
2110
|
+
async execute(ctx) {
|
|
2111
|
+
let scope = ctx.flags.scope ?? "agent", payload = ctx.flags.payload, delay = ctx.flags.delay, every = ctx.flags.every, cron = ctx.flags.cron, lambda = ctx.args[ctx.args.length - 1];
|
|
2112
|
+
if (!lambda) return err("at: missing lambda path");
|
|
2113
|
+
let input = { scope: [scope] };
|
|
2114
|
+
if (payload && (input.payload = payload), delay) {
|
|
2115
|
+
let ms = parseDuration(delay);
|
|
2116
|
+
if (ms === null) return err(`at: invalid duration: ${delay}`);
|
|
2117
|
+
input.schedule = { type: "delay", delay_ms: ms }, input.lambda = lambda;
|
|
2118
|
+
} else if (every) {
|
|
2119
|
+
let ms = parseDuration(every);
|
|
2120
|
+
if (ms === null) return err(`at: invalid duration: ${every}`);
|
|
2121
|
+
input.schedule = { type: "interval", every_ms: ms }, input.lambda = lambda;
|
|
2122
|
+
} else if (cron)
|
|
2123
|
+
input.schedule = { type: "cron", cron }, input.lambda = lambda;
|
|
2124
|
+
else if (ctx.args.length >= 2) {
|
|
2125
|
+
let isoTime = ctx.args[0], ms = new Date(isoTime).getTime();
|
|
2126
|
+
if (isNaN(ms)) return err(`at: invalid ISO time: ${isoTime}`);
|
|
2127
|
+
input.schedule = { type: "once", at: ms }, input.lambda = lambda;
|
|
2128
|
+
} else
|
|
2129
|
+
return err("at: specify --delay, --every, --cron, or an ISO time");
|
|
2130
|
+
let result = await ctx.toolRegistry.executeTool("sys_set_timer", input, ctx.workspace);
|
|
2131
|
+
return result.isError ? err(`at: ${result.content}`) : ok(result.content);
|
|
2132
|
+
}
|
|
2133
|
+
}, crontabHandler = {
|
|
2134
|
+
name: "crontab",
|
|
2135
|
+
summary: "Manage timers",
|
|
2136
|
+
helpText: [
|
|
2137
|
+
"crontab -l List active timers",
|
|
2138
|
+
"crontab -l --all Also list expired/completed timers (alias: --expired, -a)",
|
|
2139
|
+
"crontab -d <id> Delete a timer"
|
|
2140
|
+
].join(`
|
|
2141
|
+
`),
|
|
2142
|
+
category: "timers",
|
|
2143
|
+
resolvedTools: ["sys_list_timers", "sys_delete_timer"],
|
|
2144
|
+
valueFlags: /* @__PURE__ */ new Set(["d"]),
|
|
2145
|
+
async execute(ctx) {
|
|
2146
|
+
if (ctx.flags.l !== void 0) {
|
|
2147
|
+
let includeExpired = ctx.flags.all !== void 0 || ctx.flags.expired !== void 0 || ctx.flags.a === !0, result = await ctx.toolRegistry.executeTool(
|
|
2148
|
+
"sys_list_timers",
|
|
2149
|
+
includeExpired ? { include_expired: !0 } : {},
|
|
2150
|
+
ctx.workspace
|
|
2151
|
+
);
|
|
2152
|
+
if (result.isError) return err(`crontab: ${result.content}`);
|
|
2153
|
+
let content = includeExpired ? result.content : result.content.replace(/pass include_expired: true to (?:list|see) them/g, "run `crontab -l --all` to list them");
|
|
2154
|
+
return ok(content);
|
|
2155
|
+
}
|
|
2156
|
+
if (ctx.flags.d !== void 0) {
|
|
2157
|
+
let timerId = typeof ctx.flags.d == "string" ? ctx.flags.d : ctx.args[0];
|
|
2158
|
+
if (!timerId) return err("crontab -d: missing timer ID");
|
|
2159
|
+
let id = Number(timerId);
|
|
2160
|
+
if (!Number.isInteger(id) || id <= 0) return err(`crontab -d: invalid timer id "${timerId}" (expected a positive integer)`);
|
|
2161
|
+
let result = await ctx.toolRegistry.executeTool("sys_delete_timer", { id }, ctx.workspace);
|
|
2162
|
+
return result.isError ? err(`crontab: ${result.content}`) : ok(result.content);
|
|
2163
|
+
}
|
|
2164
|
+
return err("crontab: use -l to list or -d <id> to delete");
|
|
2165
|
+
}
|
|
2166
|
+
}, timerHandlers = [atHandler, crontabHandler];
|
|
2167
|
+
|
|
2168
|
+
// src/main/tools/shell/commands/code.ts
|
|
2169
|
+
var CODE_DELEGATING_TOOLS = /* @__PURE__ */ new Set(["sys_lambda", "sys_code", "sys_create_adf"]), UNWRAP = /* @__PURE__ */ Symbol("baseRegistry");
|
|
2170
|
+
function baseRegistry(registry2) {
|
|
2171
|
+
return registry2[UNWRAP] ?? registry2;
|
|
2172
|
+
}
|
|
2173
|
+
function authorizedRegistry(registry2) {
|
|
2174
|
+
let base = baseRegistry(registry2);
|
|
2175
|
+
return new Proxy(base, {
|
|
2176
|
+
get(target, prop, receiver) {
|
|
2177
|
+
return prop === UNWRAP ? target : prop === "executeTool" ? (name, input, ws) => {
|
|
2178
|
+
let injected = CODE_DELEGATING_TOOLS.has(name) ? input : { ...input ?? {}, _authorized: !0 };
|
|
2179
|
+
return target.executeTool(name, injected, ws);
|
|
2180
|
+
} : Reflect.get(target, prop, receiver);
|
|
2181
|
+
}
|
|
2182
|
+
});
|
|
2183
|
+
}
|
|
2184
|
+
var nodeHandler = {
|
|
2185
|
+
name: "node",
|
|
2186
|
+
summary: "Execute inline JavaScript/TypeScript",
|
|
2187
|
+
helpText: [
|
|
2188
|
+
'node -e "<code>" Execute inline code via sys_code',
|
|
2189
|
+
'node -p "<expr>" Evaluate expression and print its value',
|
|
2190
|
+
"",
|
|
2191
|
+
"The code runs in a sandboxed environment with access to adf.* methods.",
|
|
2192
|
+
"Top-level await is supported in both -e code and -p expressions."
|
|
2193
|
+
].join(`
|
|
2194
|
+
`),
|
|
2195
|
+
category: "code",
|
|
2196
|
+
resolvedTools: ["sys_code"],
|
|
2197
|
+
valueFlags: /* @__PURE__ */ new Set(["e", "p"]),
|
|
2198
|
+
async execute(ctx) {
|
|
2199
|
+
let code, printExpr = !1;
|
|
2200
|
+
if (typeof ctx.flags.p == "string" ? (code = ctx.flags.p, printExpr = !0) : typeof ctx.flags.e == "string" ? code = ctx.flags.e : ctx.args.length > 0 && (code = ctx.args.join(" ")), !code) return err('node: usage: node -e "<code>" or node -p "<expr>"');
|
|
2201
|
+
printExpr && (code = `const __p_value = await (async () => (
|
|
2202
|
+
` + code + `
|
|
2203
|
+
))(); console.log(typeof __p_value === "string" ? __p_value : (JSON.stringify(__p_value, null, 2) ?? String(__p_value)))`);
|
|
2204
|
+
let envVars = ctx.env.listAll();
|
|
2205
|
+
envVars.length > 0 && (code = envVars.map((v) => `process.env[${JSON.stringify(v.key)}] = ${JSON.stringify(v.value)};`).join(" ") + " " + code);
|
|
2206
|
+
let result = await ctx.toolRegistry.executeTool("sys_code", { code }, ctx.workspace);
|
|
2207
|
+
return result.isError ? err(`node: ${result.content}`) : ok(result.content);
|
|
2208
|
+
}
|
|
2209
|
+
};
|
|
2210
|
+
function coerceArgValue(v) {
|
|
2211
|
+
if (typeof v == "boolean") return v;
|
|
2212
|
+
if (v === "true") return !0;
|
|
2213
|
+
if (v === "false") return !1;
|
|
2214
|
+
if (/^-?(?:0|[1-9]\d*)(?:\.\d+)?$/.test(v)) {
|
|
2215
|
+
let n = Number(v);
|
|
2216
|
+
if (Number.isFinite(n) && String(n) === v) return n;
|
|
2217
|
+
}
|
|
2218
|
+
return v;
|
|
2219
|
+
}
|
|
2220
|
+
function flagsToArgs(flags) {
|
|
2221
|
+
let out = {};
|
|
2222
|
+
for (let [key, val] of Object.entries(flags))
|
|
2223
|
+
key !== "args" && (out[key] = Array.isArray(val) ? val.map(coerceArgValue) : coerceArgValue(val));
|
|
2224
|
+
return out;
|
|
2225
|
+
}
|
|
2226
|
+
var scriptHandler = {
|
|
2227
|
+
name: "./",
|
|
2228
|
+
summary: "Execute VFS script or lambda",
|
|
2229
|
+
helpText: [
|
|
2230
|
+
"./<path> Execute script, calls main()",
|
|
2231
|
+
"./<path> <function> Call specific function",
|
|
2232
|
+
'./<path> <fn> --key value Named args \u2192 { key: "value" } (repeat a flag \u2192 array)',
|
|
2233
|
+
"./<path> <fn> --args '{}' Exact JSON args (nested objects/arrays, precise types)",
|
|
2234
|
+
'echo "data" | ./<path> Pass stdin as { stdin: "data" }',
|
|
2235
|
+
"",
|
|
2236
|
+
"Named --key flags are coerced: 3 \u2192 number, true/false \u2192 boolean, else string.",
|
|
2237
|
+
"--args (JSON) and --key flags merge; named flags win on key collision.",
|
|
2238
|
+
"",
|
|
2239
|
+
".sh files: parsed as one script \u2014 heredocs and multi-line chains work;",
|
|
2240
|
+
" bash-like: failures do not stop the script unless chained with &&",
|
|
2241
|
+
".ts/.js files: dispatch to sys_lambda"
|
|
2242
|
+
].join(`
|
|
2243
|
+
`),
|
|
2244
|
+
category: "code",
|
|
2245
|
+
resolvedTools: ["sys_lambda"],
|
|
2246
|
+
async execute(ctx) {
|
|
2247
|
+
let scriptPath = ctx.args[0];
|
|
2248
|
+
if (!scriptPath) return err("./: missing script path");
|
|
2249
|
+
let path = scriptPath.startsWith("./") ? scriptPath.slice(2) : scriptPath;
|
|
2250
|
+
if (path.endsWith(".sh"))
|
|
2251
|
+
return executeShellScript(path, ctx);
|
|
2252
|
+
let fnName = ctx.args[1], argsStr = ctx.flags.args, input = { source: fnName ? `${path}:${fnName}` : path }, base = {};
|
|
2253
|
+
if (argsStr) {
|
|
2254
|
+
let parsed;
|
|
2255
|
+
try {
|
|
2256
|
+
parsed = JSON.parse(argsStr);
|
|
2257
|
+
} catch {
|
|
2258
|
+
return err(`./: invalid JSON args: ${argsStr}`);
|
|
2259
|
+
}
|
|
2260
|
+
if (typeof parsed != "object" || parsed === null || Array.isArray(parsed))
|
|
2261
|
+
return err(`./: --args must be a JSON object, e.g. --args '{"key":"value"}'`);
|
|
2262
|
+
base = parsed;
|
|
2263
|
+
}
|
|
2264
|
+
let named = flagsToArgs(ctx.flags), merged = { ...base, ...named };
|
|
2265
|
+
Object.keys(merged).length > 0 ? input.args = ctx.stdin ? { stdin: ctx.stdin, ...merged } : merged : ctx.stdin && (input.args = { stdin: ctx.stdin });
|
|
2266
|
+
let result = await ctx.toolRegistry.executeTool("sys_lambda", input, ctx.workspace);
|
|
2267
|
+
return result.isError ? err(`./${path}: ${result.content}`) : ok(result.content);
|
|
2268
|
+
}
|
|
2269
|
+
};
|
|
2270
|
+
async function executeShellScript(path, ctx) {
|
|
2271
|
+
let [scriptContent, readErr] = await shellReadFile(ctx.toolRegistry, ctx.workspace, path);
|
|
2272
|
+
if (readErr !== null) return err(`./${path}: ${readErr}`);
|
|
2273
|
+
let source = scriptContent.replace(/^#![^\n]*\n?/, ""), authorized = !1;
|
|
2274
|
+
try {
|
|
2275
|
+
authorized = ctx.workspace.isFileAuthorized(path);
|
|
2276
|
+
} catch {
|
|
2277
|
+
}
|
|
2278
|
+
let gate = { ...ctx.gate ?? {}, authorized }, base = baseRegistry(ctx.toolRegistry), toolRegistry = gate.authorized ? authorizedRegistry(base) : base, { parse } = await import("./parser-JVY2PWSM.mjs"), { executeNode, MAX_SHELL_DEPTH } = await import("./pipeline-executor-BHBSJX2K.mjs"), depth = (ctx.depth ?? 0) + 1;
|
|
2279
|
+
if (depth > MAX_SHELL_DEPTH)
|
|
2280
|
+
return err(`./${path}: script nesting too deep (${MAX_SHELL_DEPTH}) \u2014 possible infinite recursion`);
|
|
2281
|
+
try {
|
|
2282
|
+
let ast = parse(source);
|
|
2283
|
+
return await executeNode(ast, ctx.stdin || "", {
|
|
2284
|
+
workspace: ctx.workspace,
|
|
2285
|
+
toolRegistry,
|
|
2286
|
+
config: ctx.config,
|
|
2287
|
+
env: ctx.env,
|
|
2288
|
+
gate,
|
|
2289
|
+
signal: ctx.signal,
|
|
2290
|
+
// forward abort so a cancelled shell stops the script
|
|
2291
|
+
depth
|
|
2292
|
+
});
|
|
2293
|
+
} catch (e) {
|
|
2294
|
+
return err(`./${path}: parse error: ${e instanceof Error ? e.message : String(e)}`);
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
var codeHandlers = [nodeHandler, scriptHandler];
|
|
2298
|
+
|
|
2299
|
+
// src/main/tools/shell/executor/preflight.ts
|
|
2300
|
+
function resolveCommandTools(cmd) {
|
|
2301
|
+
let tools = [], handler = getCommand(cmd.name);
|
|
2302
|
+
handler && (tools.push(...handler.resolvedTools), handler.resolveToolsFromArgs && tools.push(...handler.resolveToolsFromArgs(cmd.args)));
|
|
2303
|
+
for (let r of cmd.redirects)
|
|
2304
|
+
(r.type === "out" || r.type === "append") && tools.push("fs_write"), r.type === "in" && tools.push("fs_read");
|
|
2305
|
+
return [...new Set(tools)];
|
|
2306
|
+
}
|
|
2307
|
+
function evaluateToolNames(toolNames, config) {
|
|
2308
|
+
let disabled = [], approvalRequired = [], intercepted = [];
|
|
2309
|
+
for (let toolName of toolNames) {
|
|
2310
|
+
let decl = findDeclaration(toolName, config), serverRestricted = mcpServerIsRestricted(toolName, config);
|
|
2311
|
+
if (matchesToolCallTrigger(toolName, config) && intercepted.push(toolName), !decl) {
|
|
2312
|
+
serverRestricted && approvalRequired.push(toolName);
|
|
2313
|
+
continue;
|
|
2314
|
+
}
|
|
2315
|
+
if (!decl.enabled) {
|
|
2316
|
+
disabled.push(toolName);
|
|
2317
|
+
continue;
|
|
2318
|
+
}
|
|
2319
|
+
(decl.restricted || serverRestricted) && approvalRequired.push(toolName);
|
|
2320
|
+
}
|
|
2321
|
+
return {
|
|
2322
|
+
disabled: [...new Set(disabled)],
|
|
2323
|
+
approvalRequired: [...new Set(approvalRequired)],
|
|
2324
|
+
intercepted: [...new Set(intercepted)],
|
|
2325
|
+
resolvedTools: toolNames
|
|
2326
|
+
};
|
|
2327
|
+
}
|
|
2328
|
+
function evaluateCommand(cmd, config) {
|
|
2329
|
+
return evaluateToolNames(resolveCommandTools(cmd), config);
|
|
2330
|
+
}
|
|
2331
|
+
async function enforceToolGate(evalr, gate, config, command) {
|
|
2332
|
+
if (!gate.authorized) {
|
|
2333
|
+
if (evalr.disabled.length > 0) {
|
|
2334
|
+
let tools = evalr.disabled.join(", ");
|
|
2335
|
+
return { exit_code: EXIT.DISABLED, stdout: "", stderr: `${tools} is disabled \u2014 this command needs the ${tools} tool${evalr.disabled.length > 1 ? "s" : ""} enabled in the agent's tool config` };
|
|
2336
|
+
}
|
|
2337
|
+
if (evalr.approvalRequired.length > 0) {
|
|
2338
|
+
if (!gate.onApprovalRequired)
|
|
2339
|
+
return { exit_code: EXIT.INTERCEPTED, stdout: "", stderr: `Tools [${evalr.approvalRequired.join(", ")}] require approval but no approval handler is configured.` };
|
|
2340
|
+
for (let tool of evalr.approvalRequired)
|
|
2341
|
+
if (!await gate.onApprovalRequired(tool, command)) return { exit_code: EXIT.INTERCEPTED, stdout: "", stderr: `Tool "${tool}" was rejected by the user.` };
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
return null;
|
|
2345
|
+
}
|
|
2346
|
+
function notifyToolCallObservers(intercepted, gate, config, command) {
|
|
2347
|
+
if (intercepted.length === 0 || !gate.onToolCallIntercepted) return;
|
|
2348
|
+
let argsStr = JSON.stringify({ command, intercepted_by: intercepted }), origin = config.id ? `agent:${config.name}:${config.id}` : `agent:${config.name}`;
|
|
2349
|
+
for (let tool of intercepted)
|
|
2350
|
+
try {
|
|
2351
|
+
gate.onToolCallIntercepted(tool, argsStr, "", origin);
|
|
2352
|
+
} catch {
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
function findDeclaration(name, config) {
|
|
2356
|
+
return config.tools.find((t) => t.name === name);
|
|
2357
|
+
}
|
|
2358
|
+
function mcpServerIsRestricted(toolName, config) {
|
|
2359
|
+
if (!toolName.startsWith("mcp_")) return !1;
|
|
2360
|
+
let owner;
|
|
2361
|
+
for (let server of config.mcp?.servers ?? [])
|
|
2362
|
+
toolName.startsWith(`mcp_${server.name}_`) && (!owner || server.name.length > owner.name.length) && (owner = server);
|
|
2363
|
+
return owner?.restricted === !0;
|
|
2364
|
+
}
|
|
2365
|
+
function matchesToolCallTrigger(toolName, config) {
|
|
2366
|
+
let cfg = config.triggers?.on_tool_call;
|
|
2367
|
+
if (!cfg?.enabled) return !1;
|
|
2368
|
+
let targets = cfg.targets ?? [];
|
|
2369
|
+
for (let target of targets)
|
|
2370
|
+
if (target.filter?.tools) {
|
|
2371
|
+
for (let pattern of target.filter.tools)
|
|
2372
|
+
if (new RegExp("^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$").test(toolName)) return !0;
|
|
2373
|
+
}
|
|
2374
|
+
return !1;
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
// src/main/tools/shell/commands/mcp.ts
|
|
2378
|
+
function coerceFlag(value, type) {
|
|
2379
|
+
if (typeof value != "string") return value;
|
|
2380
|
+
switch (type) {
|
|
2381
|
+
case "number":
|
|
2382
|
+
case "integer": {
|
|
2383
|
+
let n = Number(value);
|
|
2384
|
+
return Number.isNaN(n) ? value : n;
|
|
2385
|
+
}
|
|
2386
|
+
case "boolean":
|
|
2387
|
+
return value === "true" ? !0 : value === "false" ? !1 : value;
|
|
2388
|
+
case "object":
|
|
2389
|
+
case "array":
|
|
2390
|
+
try {
|
|
2391
|
+
return JSON.parse(value);
|
|
2392
|
+
} catch {
|
|
2393
|
+
return value;
|
|
2394
|
+
}
|
|
2395
|
+
default:
|
|
2396
|
+
return value;
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
function pickStdinParam(props, required) {
|
|
2400
|
+
for (let name of ["content", "text", "body", "message", "prompt", "query", "input", "sql"])
|
|
2401
|
+
if (name in props) return name;
|
|
2402
|
+
return required.find((r) => props[r]?.type === "string" || props[r]?.type === void 0) ?? "content";
|
|
2403
|
+
}
|
|
2404
|
+
var mcpHandler = {
|
|
2405
|
+
name: "mcp",
|
|
2406
|
+
summary: "MCP tool discovery and invocation",
|
|
2407
|
+
helpText: [
|
|
2408
|
+
"mcp --list List MCP servers",
|
|
2409
|
+
"mcp <server> --list List tools on server",
|
|
2410
|
+
"mcp <server> <tool> -h Show tool schema",
|
|
2411
|
+
"mcp <server> <tool> [--flags] Invoke tool with args",
|
|
2412
|
+
"",
|
|
2413
|
+
"Examples:",
|
|
2414
|
+
' mcp github create_issue --title "Bug" --body "Details"',
|
|
2415
|
+
' cat data.md | mcp slack send_message --channel "#reports"'
|
|
2416
|
+
].join(`
|
|
2417
|
+
`),
|
|
2418
|
+
category: "mcp",
|
|
2419
|
+
// resolvedTools stays [] and there is NO resolveToolsFromArgs: the server/
|
|
2420
|
+
// tool names are only known after arg resolution (they may be quoted, a
|
|
2421
|
+
// variable, or a substitution), so the executor's static pre-gate cannot see
|
|
2422
|
+
// the real tool. mcp self-gates on the resolved tool name below instead —
|
|
2423
|
+
// static resolution here would let `mcp "github" create_issue` be gated as a
|
|
2424
|
+
// different/phantom tool while the real restricted one runs.
|
|
2425
|
+
resolvedTools: [],
|
|
2426
|
+
async execute(ctx) {
|
|
2427
|
+
if (ctx.flags.list !== void 0 && ctx.args.length === 0)
|
|
2428
|
+
return listServers(ctx);
|
|
2429
|
+
if (ctx.args.length === 0) return err("mcp: usage: mcp <server> <tool> [--flags]");
|
|
2430
|
+
let server = ctx.args[0];
|
|
2431
|
+
if (ctx.flags.list !== void 0)
|
|
2432
|
+
return listServerTools(server, ctx);
|
|
2433
|
+
if (ctx.args.length < 2) return err(`mcp: usage: mcp ${server} <tool> [--flags]`);
|
|
2434
|
+
let tool = ctx.args[1], mcpToolName = `mcp_${server}_${tool}`;
|
|
2435
|
+
if (ctx.flags.h !== void 0 || ctx.flags.help !== void 0)
|
|
2436
|
+
return showToolHelp(mcpToolName, ctx);
|
|
2437
|
+
let observed = [];
|
|
2438
|
+
if (ctx.gate) {
|
|
2439
|
+
let evalr = evaluateToolNames([mcpToolName], ctx.config), blocked = await enforceToolGate(evalr, ctx.gate, ctx.config, ctx.gate.command ?? `mcp ${server} ${tool}`);
|
|
2440
|
+
if (blocked) return blocked;
|
|
2441
|
+
observed = evalr.intercepted;
|
|
2442
|
+
}
|
|
2443
|
+
let schema = ctx.toolRegistry.get(mcpToolName)?.toProviderFormat().input_schema, props = schema?.properties ?? {}, toolArgs = {};
|
|
2444
|
+
for (let [key, value] of Object.entries(ctx.flags))
|
|
2445
|
+
key === "h" || key === "help" || key === "list" || key.startsWith("_") || (toolArgs[key] = coerceFlag(value, props[key]?.type));
|
|
2446
|
+
if (ctx.stdin) {
|
|
2447
|
+
let target = pickStdinParam(props, schema?.required ?? []);
|
|
2448
|
+
target in toolArgs || (toolArgs[target] = ctx.stdin);
|
|
2449
|
+
}
|
|
2450
|
+
let result = await ctx.toolRegistry.executeTool(mcpToolName, toolArgs, ctx.workspace);
|
|
2451
|
+
return ctx.gate && notifyToolCallObservers(observed, ctx.gate, ctx.config, ctx.gate.command ?? `mcp ${server} ${tool}`), result.isError ? err(`mcp ${server} ${tool}: ${result.content}`) : ok(result.content);
|
|
2452
|
+
}
|
|
2453
|
+
};
|
|
2454
|
+
function listServers(ctx) {
|
|
2455
|
+
let allTools = ctx.toolRegistry.getAll(), servers = /* @__PURE__ */ new Set();
|
|
2456
|
+
for (let tool of allTools)
|
|
2457
|
+
if (tool.name.startsWith("mcp_")) {
|
|
2458
|
+
let parts = tool.name.split("_");
|
|
2459
|
+
parts.length >= 3 && servers.add(parts[1]);
|
|
2460
|
+
}
|
|
2461
|
+
return servers.size === 0 ? ok("No MCP servers connected.") : ok([...servers].sort().join(`
|
|
2462
|
+
`));
|
|
2463
|
+
}
|
|
2464
|
+
function listServerTools(server, ctx) {
|
|
2465
|
+
let allTools = ctx.toolRegistry.getAll(), prefix = `mcp_${server}_`, tools = allTools.filter((t) => t.name.startsWith(prefix)).map((t) => `${t.name.slice(prefix.length).padEnd(30)} ${t.description.slice(0, 60)}`);
|
|
2466
|
+
return tools.length === 0 ? ok(`No tools found for server: ${server}`) : ok(tools.join(`
|
|
2467
|
+
`));
|
|
2468
|
+
}
|
|
2469
|
+
function showToolHelp(mcpToolName, ctx) {
|
|
2470
|
+
let tool = ctx.toolRegistry.get(mcpToolName);
|
|
2471
|
+
if (!tool) return err(`Unknown MCP tool: ${mcpToolName}`);
|
|
2472
|
+
let schema = tool.toProviderFormat(), lines = [
|
|
2473
|
+
schema.description || "(no description)",
|
|
2474
|
+
"",
|
|
2475
|
+
"Parameters:"
|
|
2476
|
+
], inputSchema = schema.input_schema, props = inputSchema.properties ?? {}, required = new Set(inputSchema.required ?? []);
|
|
2477
|
+
for (let [name, prop] of Object.entries(props)) {
|
|
2478
|
+
let req = required.has(name) ? " (required)" : "", desc = prop.description ?? "";
|
|
2479
|
+
lines.push(` --${name.padEnd(20)} ${prop.type ?? "any"}${req} ${desc}`);
|
|
2480
|
+
}
|
|
2481
|
+
return ok(lines.join(`
|
|
2482
|
+
`));
|
|
2483
|
+
}
|
|
2484
|
+
var mcpHandlers = [mcpHandler];
|
|
2485
|
+
|
|
2486
|
+
// src/main/tools/shell/commands/status.ts
|
|
2487
|
+
function staticArgValue3(arg) {
|
|
2488
|
+
if (!arg) return null;
|
|
2489
|
+
if (arg.type === "literal") return arg.value;
|
|
2490
|
+
if (arg.type === "quoted") {
|
|
2491
|
+
let out = "";
|
|
2492
|
+
for (let part of arg.parts) {
|
|
2493
|
+
let v = staticArgValue3(part);
|
|
2494
|
+
if (v === null) return null;
|
|
2495
|
+
out += v;
|
|
2496
|
+
}
|
|
2497
|
+
return out;
|
|
2498
|
+
}
|
|
2499
|
+
return null;
|
|
2500
|
+
}
|
|
2501
|
+
var psHandler = {
|
|
2502
|
+
name: "ps",
|
|
2503
|
+
summary: "List tasks",
|
|
2504
|
+
helpText: [
|
|
2505
|
+
"ps List pending/running tasks",
|
|
2506
|
+
"ps --all List all tasks (including completed)"
|
|
2507
|
+
].join(`
|
|
2508
|
+
`),
|
|
2509
|
+
category: "process",
|
|
2510
|
+
resolvedTools: ["db_query"],
|
|
2511
|
+
async execute(ctx) {
|
|
2512
|
+
let sql = !!ctx.flags.all ? "SELECT id, tool, status, origin, created_at FROM adf_tasks ORDER BY created_at DESC" : "SELECT id, tool, status, origin, created_at FROM adf_tasks WHERE status IN ('pending', 'running') ORDER BY created_at DESC", result = await ctx.toolRegistry.executeTool("db_query", { sql }, ctx.workspace);
|
|
2513
|
+
return result.isError ? err(`ps: ${result.content}`) : ok(result.content || "No tasks.");
|
|
2514
|
+
}
|
|
2515
|
+
}, killHandler = {
|
|
2516
|
+
name: "kill",
|
|
2517
|
+
summary: "Cancel a task",
|
|
2518
|
+
helpText: "kill <task_id> Cancel a pending/running task",
|
|
2519
|
+
category: "process",
|
|
2520
|
+
resolvedTools: ["db_execute"],
|
|
2521
|
+
async execute(ctx) {
|
|
2522
|
+
if (ctx.args.length === 0) return err("kill: missing task ID");
|
|
2523
|
+
let taskId = ctx.args[0], result = await ctx.toolRegistry.executeTool("db_execute", {
|
|
2524
|
+
sql: "UPDATE adf_tasks SET status = 'cancelled' WHERE id = ? AND status IN ('pending', 'running')",
|
|
2525
|
+
params: [taskId]
|
|
2526
|
+
}, ctx.workspace);
|
|
2527
|
+
return result.isError ? err(`kill: ${result.content}`) : ok(`Task ${taskId} cancelled.`);
|
|
2528
|
+
}
|
|
2529
|
+
}, waitHandler = {
|
|
2530
|
+
name: "wait",
|
|
2531
|
+
summary: "Wait for a task to complete",
|
|
2532
|
+
helpText: "wait <task_id> Block until task reaches terminal state",
|
|
2533
|
+
category: "process",
|
|
2534
|
+
resolvedTools: ["db_query"],
|
|
2535
|
+
async execute(ctx) {
|
|
2536
|
+
if (ctx.args.length === 0) return err("wait: missing task ID");
|
|
2537
|
+
let taskId = ctx.args[0], maxWait = 3e4, start = Date.now(), pollInterval = 500;
|
|
2538
|
+
for (; Date.now() - start < maxWait; ) {
|
|
2539
|
+
let result = await ctx.toolRegistry.executeTool("db_query", {
|
|
2540
|
+
sql: "SELECT status FROM adf_tasks WHERE id = ?",
|
|
2541
|
+
params: [taskId]
|
|
2542
|
+
}, ctx.workspace);
|
|
2543
|
+
if (result.isError) return err(`wait: ${result.content}`);
|
|
2544
|
+
try {
|
|
2545
|
+
let parsed = JSON.parse(result.content), rows = Array.isArray(parsed) ? parsed : parsed?.rows ?? [];
|
|
2546
|
+
if (rows.length > 0) {
|
|
2547
|
+
let status = rows[0]?.status ?? rows[0];
|
|
2548
|
+
if (status === "completed" || status === "failed" || status === "cancelled")
|
|
2549
|
+
return ok(`Task ${taskId}: ${status}`);
|
|
2550
|
+
}
|
|
2551
|
+
} catch {
|
|
2552
|
+
if (result.content.includes("completed") || result.content.includes("failed") || result.content.includes("cancelled"))
|
|
2553
|
+
return ok(result.content);
|
|
2554
|
+
}
|
|
2555
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
2556
|
+
}
|
|
2557
|
+
return err(`wait: timed out after ${maxWait / 1e3}s`);
|
|
2558
|
+
}
|
|
2559
|
+
}, whoamiHandler = {
|
|
2560
|
+
name: "whoami",
|
|
2561
|
+
summary: "Show agent identity",
|
|
2562
|
+
helpText: "whoami Display name, DID, state, description, owner",
|
|
2563
|
+
category: "identity",
|
|
2564
|
+
resolvedTools: [],
|
|
2565
|
+
async execute(ctx) {
|
|
2566
|
+
let config = ctx.config, lines = [
|
|
2567
|
+
`Name: ${config.name ?? "unknown"}`,
|
|
2568
|
+
`State: ${config.state ?? "unknown"}`,
|
|
2569
|
+
`Description: ${config.description ?? ""}`
|
|
2570
|
+
];
|
|
2571
|
+
try {
|
|
2572
|
+
let did = ctx.env.resolve("AGENT_DID");
|
|
2573
|
+
did && lines.splice(1, 0, `DID: ${did}`);
|
|
2574
|
+
} catch {
|
|
2575
|
+
}
|
|
2576
|
+
return ok(lines.join(`
|
|
2577
|
+
`));
|
|
2578
|
+
}
|
|
2579
|
+
}, configHandler = {
|
|
2580
|
+
name: "config",
|
|
2581
|
+
summary: "View or update agent config",
|
|
2582
|
+
helpText: [
|
|
2583
|
+
"config Show full agent configuration",
|
|
2584
|
+
"config set <path> <value> Update a config field",
|
|
2585
|
+
"config tools List ALL tools (incl. hidden/absorbed): name, state, summary",
|
|
2586
|
+
"config tools <name|pattern> Full schema(s) for matching tools \u2014 use before",
|
|
2587
|
+
" writing lambda code that calls adf.<tool>(...)",
|
|
2588
|
+
"config card Show signed agent card",
|
|
2589
|
+
"config provider Show LLM provider status/usage"
|
|
2590
|
+
].join(`
|
|
2591
|
+
`),
|
|
2592
|
+
category: "identity",
|
|
2593
|
+
// Arg-dependent gating: read subcommands (bare config / tools / card /
|
|
2594
|
+
// provider) only need sys_get_config; only `config set` needs
|
|
2595
|
+
// sys_update_config. Gating reads on the write tool made `config get` exit
|
|
2596
|
+
// 126 whenever sys_update_config was disabled — and made
|
|
2597
|
+
// `config set tools.sys_update_config.enabled true` impossible to bootstrap.
|
|
2598
|
+
resolvedTools: [],
|
|
2599
|
+
resolveToolsFromArgs(args) {
|
|
2600
|
+
let sub = staticArgValue3(args[0]);
|
|
2601
|
+
return sub === "set" ? ["sys_update_config"] : sub === null && args.length > 0 ? ["sys_get_config", "sys_update_config"] : ["sys_get_config"];
|
|
2602
|
+
},
|
|
2603
|
+
async execute(ctx) {
|
|
2604
|
+
if (ctx.args[0] === "tools") return configTools(ctx);
|
|
2605
|
+
if (ctx.args[0] === "card" || ctx.args[0] === "provider") {
|
|
2606
|
+
let section = ctx.args[0] === "card" ? "card" : "provider_status", result2 = await ctx.toolRegistry.executeTool("sys_get_config", { section }, ctx.workspace);
|
|
2607
|
+
return result2.isError ? err(`config: ${result2.content}`) : ok(result2.content);
|
|
2608
|
+
}
|
|
2609
|
+
if (ctx.args[0] === "set") {
|
|
2610
|
+
if (ctx.args.length < 3) return err("config: usage: config set <path> <value>");
|
|
2611
|
+
let path = ctx.args[1], value = ctx.args[2];
|
|
2612
|
+
try {
|
|
2613
|
+
value = JSON.parse(ctx.args[2]);
|
|
2614
|
+
} catch {
|
|
2615
|
+
}
|
|
2616
|
+
let result2 = await ctx.toolRegistry.executeTool("sys_update_config", {
|
|
2617
|
+
path,
|
|
2618
|
+
value
|
|
2619
|
+
}, ctx.workspace);
|
|
2620
|
+
return result2.isError ? err(`config: ${result2.content}`) : ok(result2.content);
|
|
2621
|
+
}
|
|
2622
|
+
let result = await ctx.toolRegistry.executeTool("sys_get_config", {}, ctx.workspace);
|
|
2623
|
+
return result.isError ? err(`config: ${result.content}`) : ok(result.content);
|
|
2624
|
+
}
|
|
2625
|
+
};
|
|
2626
|
+
async function configTools(ctx) {
|
|
2627
|
+
let result = await ctx.toolRegistry.executeTool("sys_get_config", { section: "tools" }, ctx.workspace);
|
|
2628
|
+
if (result.isError) return err(`config tools: ${result.content}`);
|
|
2629
|
+
let tools;
|
|
2630
|
+
try {
|
|
2631
|
+
tools = JSON.parse(result.content).tools ?? [];
|
|
2632
|
+
} catch {
|
|
2633
|
+
return err("config tools: failed to parse tool discovery");
|
|
2634
|
+
}
|
|
2635
|
+
let query = ctx.args[1]?.toLowerCase();
|
|
2636
|
+
if (query) {
|
|
2637
|
+
let matches = tools.filter((t) => t.name.toLowerCase().includes(query));
|
|
2638
|
+
return matches.length === 0 ? err(`config tools: no tool matching "${ctx.args[1]}"`) : ok(JSON.stringify(matches.map((t) => ({
|
|
2639
|
+
name: t.name,
|
|
2640
|
+
enabled: t.enabled,
|
|
2641
|
+
visible: t.visible,
|
|
2642
|
+
restricted: t.restricted,
|
|
2643
|
+
source: t.source,
|
|
2644
|
+
description: t.description,
|
|
2645
|
+
schema: t.schema
|
|
2646
|
+
})), null, 2));
|
|
2647
|
+
}
|
|
2648
|
+
let lines = tools.map((t) => {
|
|
2649
|
+
let state = [
|
|
2650
|
+
t.enabled ? null : "disabled",
|
|
2651
|
+
t.enabled && !t.visible ? "hidden" : null,
|
|
2652
|
+
t.restricted ? "restricted" : null
|
|
2653
|
+
].filter(Boolean).join(","), summary = (t.description.split(/[.\n]/)[0] ?? "").slice(0, 80);
|
|
2654
|
+
return `${t.name.padEnd(28)} ${(state || "on").padEnd(20)} ${summary}`;
|
|
2655
|
+
});
|
|
2656
|
+
return lines.push(""), lines.push("`config tools <name>` for full schema \u2014 needed before calling adf.<tool>(...) from code."), ok(lines.join(`
|
|
2657
|
+
`));
|
|
2658
|
+
}
|
|
2659
|
+
var statusHandler = {
|
|
2660
|
+
name: "status",
|
|
2661
|
+
summary: "Show agent status summary",
|
|
2662
|
+
helpText: "status State, token usage, budget remaining",
|
|
2663
|
+
category: "identity",
|
|
2664
|
+
resolvedTools: ["sys_get_config"],
|
|
2665
|
+
async execute(ctx) {
|
|
2666
|
+
let config = ctx.config, lines = [
|
|
2667
|
+
`Agent: ${config.name ?? "unknown"}`,
|
|
2668
|
+
`State: ${config.state ?? "unknown"}`,
|
|
2669
|
+
`Model: ${config.model?.provider ?? ""}/${config.model?.model_id ?? ""}`,
|
|
2670
|
+
`Autonomy: ${config.autonomous ? "enabled" : "disabled"}`
|
|
2671
|
+
];
|
|
2672
|
+
return config.limits?.token_budget && lines.push(`Budget: ${config.limits.token_budget} tokens`), ok(lines.join(`
|
|
2673
|
+
`));
|
|
2674
|
+
}
|
|
2675
|
+
}, stateHandler = {
|
|
2676
|
+
name: "state",
|
|
2677
|
+
summary: "Show or set your state (setting ends the turn)",
|
|
2678
|
+
helpText: [
|
|
2679
|
+
"state Print your current state",
|
|
2680
|
+
"state idle Yield; stay responsive to all triggers",
|
|
2681
|
+
"state hibernate Deep idle \u2014 timers and direct messages only",
|
|
2682
|
+
"state off Full shutdown \u2014 only a human brings you back",
|
|
2683
|
+
"",
|
|
2684
|
+
"Setting a state ends the turn when the whole invocation finishes, so it",
|
|
2685
|
+
'chains: `meta set status "shipped" && state idle`. Commands after it still',
|
|
2686
|
+
"run \u2014 put it last. A `state` inside $(...) is a subshell side effect and is",
|
|
2687
|
+
"discarded. Reading the state needs no tool; setting it needs sys_set_state."
|
|
2688
|
+
].join(`
|
|
2689
|
+
`),
|
|
2690
|
+
category: "identity",
|
|
2691
|
+
resolvedTools: [],
|
|
2692
|
+
// Bare `state` is a read off config — gating it on sys_set_state would make
|
|
2693
|
+
// the agent unable to see its own state whenever the tool is disabled.
|
|
2694
|
+
resolveToolsFromArgs(args) {
|
|
2695
|
+
if (args.length === 0) return [];
|
|
2696
|
+
let first = staticArgValue3(args[0]);
|
|
2697
|
+
return first !== null && first.startsWith("-") ? [] : ["sys_set_state"];
|
|
2698
|
+
},
|
|
2699
|
+
async execute(ctx) {
|
|
2700
|
+
if (ctx.args.length === 0) return ok(String(ctx.config.state ?? "unknown"));
|
|
2701
|
+
let target = ctx.args[0], valid = SETTABLE_STATES;
|
|
2702
|
+
if (ctx.args.length > 1 || !valid.includes(target))
|
|
2703
|
+
return err(`state: usage: state [${valid.join("|")}] \u2014 active and suspended are runtime-managed`);
|
|
2704
|
+
let result = await ctx.toolRegistry.executeTool("sys_set_state", { state: target }, ctx.workspace);
|
|
2705
|
+
return result.isError ? err(`state: ${result.content}`) : result.endTurn ? {
|
|
2706
|
+
exit_code: 0,
|
|
2707
|
+
stdout: `state: ${target} (turn ends)`,
|
|
2708
|
+
stderr: "",
|
|
2709
|
+
end_turn: !0,
|
|
2710
|
+
target_state: target
|
|
2711
|
+
} : ok(`state: ${target}`);
|
|
2712
|
+
}
|
|
2713
|
+
}, envHandler = {
|
|
2714
|
+
name: "env",
|
|
2715
|
+
summary: "List environment variables",
|
|
2716
|
+
helpText: "env List all environment variables (sensitive values redacted)",
|
|
2717
|
+
category: "identity",
|
|
2718
|
+
resolvedTools: [],
|
|
2719
|
+
async execute(ctx) {
|
|
2720
|
+
let vars = ctx.env.listAll();
|
|
2721
|
+
if (vars.length === 0) return ok("No environment variables set.");
|
|
2722
|
+
let lines = vars.map((v) => {
|
|
2723
|
+
let redacted = v.source === "identity" ? "***" : v.value;
|
|
2724
|
+
return `${v.key}=${redacted}`;
|
|
2725
|
+
});
|
|
2726
|
+
return ok(lines.join(`
|
|
2727
|
+
`));
|
|
2728
|
+
}
|
|
2729
|
+
}, exportHandler = {
|
|
2730
|
+
name: "export",
|
|
2731
|
+
summary: "Set an environment variable",
|
|
2732
|
+
helpText: [
|
|
2733
|
+
"export KEY=value Write to adf_identity (plain, non-encrypted)",
|
|
2734
|
+
'export KEY="value" Same with quoted value'
|
|
2735
|
+
].join(`
|
|
2736
|
+
`),
|
|
2737
|
+
category: "identity",
|
|
2738
|
+
resolvedTools: [],
|
|
2739
|
+
async execute(ctx) {
|
|
2740
|
+
if (ctx.args.length === 0) return err("export: usage: export KEY=value");
|
|
2741
|
+
let assignment = ctx.args.join(" "), eqIdx = assignment.indexOf("=");
|
|
2742
|
+
if (eqIdx <= 0) return err("export: invalid format. Use KEY=value");
|
|
2743
|
+
let key = assignment.slice(0, eqIdx).trim(), value = assignment.slice(eqIdx + 1).trim();
|
|
2744
|
+
(value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) && (value = value.slice(1, -1)), ctx.env.export(key, value);
|
|
2745
|
+
try {
|
|
2746
|
+
ctx.workspace.setIdentity(key.toLowerCase(), value);
|
|
2747
|
+
} catch {
|
|
2748
|
+
}
|
|
2749
|
+
return ok("");
|
|
2750
|
+
}
|
|
2751
|
+
}, pwdHandler = {
|
|
2752
|
+
name: "pwd",
|
|
2753
|
+
summary: "Print working directory",
|
|
2754
|
+
helpText: "pwd Always / (VFS root)",
|
|
2755
|
+
category: "identity",
|
|
2756
|
+
resolvedTools: [],
|
|
2757
|
+
async execute(ctx) {
|
|
2758
|
+
return ok("/");
|
|
2759
|
+
}
|
|
2760
|
+
}, dateHandler = {
|
|
2761
|
+
name: "date",
|
|
2762
|
+
summary: "Print current date/time",
|
|
2763
|
+
helpText: [
|
|
2764
|
+
"date Print current date/time (ISO 8601)",
|
|
2765
|
+
"date +<format> Strftime-style: %Y, %m, %d, %H, %M, %S, %s"
|
|
2766
|
+
].join(`
|
|
2767
|
+
`),
|
|
2768
|
+
category: "identity",
|
|
2769
|
+
resolvedTools: [],
|
|
2770
|
+
async execute(ctx) {
|
|
2771
|
+
let now = /* @__PURE__ */ new Date(), fmt = ctx.args[0];
|
|
2772
|
+
if (fmt && fmt.startsWith("+")) {
|
|
2773
|
+
let pattern = fmt.slice(1), pad = (n) => String(n).padStart(2, "0"), result = pattern.replace(/%Y/g, String(now.getFullYear())).replace(/%m/g, pad(now.getMonth() + 1)).replace(/%d/g, pad(now.getDate())).replace(/%H/g, pad(now.getHours())).replace(/%M/g, pad(now.getMinutes())).replace(/%S/g, pad(now.getSeconds())).replace(/%s/g, String(Math.floor(now.getTime() / 1e3)));
|
|
2774
|
+
return ok(result);
|
|
2775
|
+
}
|
|
2776
|
+
return ok(now.toISOString());
|
|
2777
|
+
}
|
|
2778
|
+
}, trueHandler = {
|
|
2779
|
+
name: "true",
|
|
2780
|
+
summary: "Return success (exit 0)",
|
|
2781
|
+
helpText: "true Always exits 0",
|
|
2782
|
+
category: "general",
|
|
2783
|
+
resolvedTools: [],
|
|
2784
|
+
async execute() {
|
|
2785
|
+
return { exit_code: 0, stdout: "", stderr: "" };
|
|
2786
|
+
}
|
|
2787
|
+
}, falseHandler = {
|
|
2788
|
+
name: "false",
|
|
2789
|
+
summary: "Return failure (exit 1)",
|
|
2790
|
+
helpText: "false Always exits 1",
|
|
2791
|
+
category: "general",
|
|
2792
|
+
resolvedTools: [],
|
|
2793
|
+
async execute() {
|
|
2794
|
+
return { exit_code: 1, stdout: "", stderr: "" };
|
|
2795
|
+
}
|
|
2796
|
+
}, sleepHandler = {
|
|
2797
|
+
name: "sleep",
|
|
2798
|
+
summary: "Pause for N seconds",
|
|
2799
|
+
helpText: "sleep <seconds> Pause execution",
|
|
2800
|
+
category: "general",
|
|
2801
|
+
resolvedTools: [],
|
|
2802
|
+
async execute(ctx) {
|
|
2803
|
+
let secs = parseFloat(ctx.args[0] ?? "0");
|
|
2804
|
+
if (isNaN(secs) || secs < 0) return err("sleep: invalid duration");
|
|
2805
|
+
let ms = Math.min(secs * 1e3, 6e4);
|
|
2806
|
+
return await new Promise((resolve) => setTimeout(resolve, ms)), ok("");
|
|
2807
|
+
}
|
|
2808
|
+
}, statusHandlers = [
|
|
2809
|
+
psHandler,
|
|
2810
|
+
killHandler,
|
|
2811
|
+
waitHandler,
|
|
2812
|
+
whoamiHandler,
|
|
2813
|
+
configHandler,
|
|
2814
|
+
statusHandler,
|
|
2815
|
+
stateHandler,
|
|
2816
|
+
envHandler,
|
|
2817
|
+
exportHandler,
|
|
2818
|
+
pwdHandler,
|
|
2819
|
+
dateHandler,
|
|
2820
|
+
trueHandler,
|
|
2821
|
+
falseHandler,
|
|
2822
|
+
sleepHandler
|
|
2823
|
+
];
|
|
2824
|
+
|
|
2825
|
+
// src/main/tools/shell/commands/meta.ts
|
|
2826
|
+
var metaHandler = {
|
|
2827
|
+
name: "meta",
|
|
2828
|
+
summary: "Read and write adf_meta key-value pairs",
|
|
2829
|
+
helpText: [
|
|
2830
|
+
"meta Same as `meta list`",
|
|
2831
|
+
"meta list List all key-value pairs (with protection)",
|
|
2832
|
+
"meta get <key> Get value for a key",
|
|
2833
|
+
"meta set <key> <value> [protection] Set a key-value pair (agent keys only)",
|
|
2834
|
+
"meta incr <key> <delta> Atomically add to a numeric counter",
|
|
2835
|
+
"meta delete <key> Delete a key (agent keys only)",
|
|
2836
|
+
"",
|
|
2837
|
+
"Subcommands: get, set, incr, list, delete \u2014 there is no `meta status` etc.",
|
|
2838
|
+
"Use incr for counters: `meta set` reads-then-writes, so two concurrent",
|
|
2839
|
+
"tasks lose an update (and an increment-protected key then rejects the",
|
|
2840
|
+
"lower write). `meta incr tokens 1200` cannot race.",
|
|
2841
|
+
"Values with spaces need no quotes on set: meta set greeting hello world",
|
|
2842
|
+
"Protection levels: none (default), readonly, increment",
|
|
2843
|
+
"Protection is set at creation and cannot be changed by the agent."
|
|
2844
|
+
].join(`
|
|
2845
|
+
`),
|
|
2846
|
+
category: "identity",
|
|
2847
|
+
resolvedTools: ["sys_get_meta", "sys_set_meta", "sys_delete_meta"],
|
|
2848
|
+
async execute(ctx) {
|
|
2849
|
+
let sub = ctx.args[0] ?? "list";
|
|
2850
|
+
switch (sub) {
|
|
2851
|
+
case "get": {
|
|
2852
|
+
if (!ctx.args[1]) return err("meta get: missing key");
|
|
2853
|
+
let result = await ctx.toolRegistry.executeTool("sys_get_meta", { key: ctx.args[1] }, ctx.workspace);
|
|
2854
|
+
return result.isError ? err(`meta: ${result.content}`) : ok(result.content);
|
|
2855
|
+
}
|
|
2856
|
+
case "set": {
|
|
2857
|
+
if (!ctx.args[1] || ctx.args[2] === void 0) return err("meta set: usage: meta set <key> <value> [protection]");
|
|
2858
|
+
let lastArg = ctx.args[ctx.args.length - 1], isProtection = ctx.args.length > 3 && META_PROTECTION_LEVELS.includes(lastArg), value = isProtection ? ctx.args.slice(2, -1).join(" ") : ctx.args.slice(2).join(" "), protection = isProtection ? lastArg : void 0, toolInput = { key: ctx.args[1], value };
|
|
2859
|
+
protection && (toolInput.protection = protection);
|
|
2860
|
+
let result = await ctx.toolRegistry.executeTool("sys_set_meta", toolInput, ctx.workspace);
|
|
2861
|
+
return result.isError ? err(`meta: ${result.content}`) : ok(result.content);
|
|
2862
|
+
}
|
|
2863
|
+
case "incr": {
|
|
2864
|
+
if (!ctx.args[1] || ctx.args[2] === void 0) return err("meta incr: usage: meta incr <key> <delta>");
|
|
2865
|
+
let delta = Number(ctx.args[2]);
|
|
2866
|
+
if (!Number.isFinite(delta)) return err(`meta incr: delta must be a number, got "${ctx.args[2]}"`);
|
|
2867
|
+
let result = await ctx.toolRegistry.executeTool("sys_set_meta", { key: ctx.args[1], value: String(delta), inc: !0 }, ctx.workspace);
|
|
2868
|
+
return result.isError ? err(`meta: ${result.content}`) : ok(result.content);
|
|
2869
|
+
}
|
|
2870
|
+
case "delete": {
|
|
2871
|
+
if (!ctx.args[1]) return err("meta delete: missing key");
|
|
2872
|
+
let result = await ctx.toolRegistry.executeTool("sys_delete_meta", { key: ctx.args[1] }, ctx.workspace);
|
|
2873
|
+
return result.isError ? err(`meta: ${result.content}`) : ok(result.content);
|
|
2874
|
+
}
|
|
2875
|
+
case "list": {
|
|
2876
|
+
let result = await ctx.toolRegistry.executeTool("sys_get_meta", {}, ctx.workspace);
|
|
2877
|
+
return result.isError ? err(`meta: ${result.content}`) : ok(result.content);
|
|
2878
|
+
}
|
|
2879
|
+
default:
|
|
2880
|
+
return err([
|
|
2881
|
+
`meta: unknown subcommand "${sub}". Valid subcommands:`,
|
|
2882
|
+
" meta list List all key-value pairs",
|
|
2883
|
+
" meta get <key> Get value for a key",
|
|
2884
|
+
" meta set <key> <value> [protection] Set a key-value pair",
|
|
2885
|
+
" meta incr <key> <delta> Atomically add to a counter",
|
|
2886
|
+
" meta delete <key> Delete a key"
|
|
2887
|
+
].join(`
|
|
2888
|
+
`));
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
}, metaHandlers = [metaHandler];
|
|
2892
|
+
|
|
2893
|
+
// src/main/tools/built-in/loop-reset-tools.ts
|
|
2894
|
+
var LOOP_RESET_TOOLS = /* @__PURE__ */ new Set(["loop_compact", "loop_clear"]), INDIRECT_EFFECT = {
|
|
2895
|
+
loop_compact: "it would report compaction started while nothing is summarized or cleared",
|
|
2896
|
+
loop_clear: "it would delete the loop rows while the live session keeps sending them, desyncing the two"
|
|
2897
|
+
};
|
|
2898
|
+
function loopResetRefusal(tool, via) {
|
|
2899
|
+
let effect = INDIRECT_EFFECT[tool] ?? "it would not take effect";
|
|
2900
|
+
return `${tool} only works as a direct tool call \u2014 the runtime completes the loop reset after the turn by matching the top-level tool name. Invoked through ${via}, ${effect}. Call the ${tool} tool directly instead.`;
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2903
|
+
// src/main/tools/shell/commands/adf.ts
|
|
2904
|
+
function staticArgValue4(arg) {
|
|
2905
|
+
return arg.type === "literal" ? arg.value : arg.type === "quoted" && arg.parts.every((p) => p.type === "literal") ? arg.parts.map((p) => p.value).join("") : null;
|
|
2906
|
+
}
|
|
2907
|
+
var adfHandler = {
|
|
2908
|
+
name: "adf",
|
|
2909
|
+
summary: "Invoke any tool by name with JSON input",
|
|
2910
|
+
helpText: [
|
|
2911
|
+
"adf <tool_name> ['<json>'] Invoke a tool directly with a JSON input object",
|
|
2912
|
+
"",
|
|
2913
|
+
"The generic tool-bus door: reaches tools that have no dedicated shell",
|
|
2914
|
+
"command (chat_info, fs_transfer, compute_exec, ...). Input is ONE JSON",
|
|
2915
|
+
`object argument \u2014 single-quote it: adf agent_discover '{"scope":"all"}'.`,
|
|
2916
|
+
"Omitted input defaults to {}. Fetch a tool's schema first: config tools <name>.",
|
|
2917
|
+
"",
|
|
2918
|
+
"Rules (violations fail with the reason):",
|
|
2919
|
+
"- The tool name must be a literal \u2014 variables/substitutions are refused.",
|
|
2920
|
+
'- Top-level input keys starting with "_" are reserved for the runtime.',
|
|
2921
|
+
"- adf_shell cannot be invoked through adf (recursion) \u2014 run the command directly.",
|
|
2922
|
+
"- loop_compact / loop_clear cannot be invoked through adf \u2014 they only take effect",
|
|
2923
|
+
" as direct tool calls; call them as tools instead."
|
|
2924
|
+
].join(`
|
|
2925
|
+
`),
|
|
2926
|
+
category: "general",
|
|
2927
|
+
resolvedTools: [],
|
|
2928
|
+
resolveToolsFromArgs(args) {
|
|
2929
|
+
let first = args[0];
|
|
2930
|
+
if (!first) return [];
|
|
2931
|
+
let name = staticArgValue4(first);
|
|
2932
|
+
if (name === null)
|
|
2933
|
+
throw new Error("adf: tool name must be a literal \u2014 write the tool name directly, e.g. adf chat_info '{}'");
|
|
2934
|
+
return name.startsWith("-") ? [] : [name];
|
|
2935
|
+
},
|
|
2936
|
+
async execute(ctx) {
|
|
2937
|
+
if (ctx.flags.h !== void 0 || ctx.flags.help !== void 0) return ok(adfHandler.helpText);
|
|
2938
|
+
if (ctx.args.length === 0) return err(adfHandler.helpText);
|
|
2939
|
+
let strayFlags = Object.keys(ctx.flags);
|
|
2940
|
+
if (strayFlags.length > 0)
|
|
2941
|
+
return err(`adf: unexpected flag -${strayFlags[0].length > 1 ? "-" : ""}${strayFlags[0]} \u2014 pass tool input as ONE single-quoted JSON argument: adf <tool> '<json>'`);
|
|
2942
|
+
if (ctx.args.length > 2)
|
|
2943
|
+
return err("adf: expected at most one JSON argument \u2014 single-quote it so it stays one argument: adf <tool> '<json>'");
|
|
2944
|
+
let tool = ctx.args[0];
|
|
2945
|
+
if (tool === "adf_shell")
|
|
2946
|
+
return err("adf: refusing to invoke adf_shell through adf (recursive shell) \u2014 run the command directly");
|
|
2947
|
+
if (LOOP_RESET_TOOLS.has(tool))
|
|
2948
|
+
return err(`adf: ${loopResetRefusal(tool, "adf_shell")}`);
|
|
2949
|
+
let raw = ctx.args[1] ?? "{}", input;
|
|
2950
|
+
try {
|
|
2951
|
+
input = JSON.parse(raw);
|
|
2952
|
+
} catch (e) {
|
|
2953
|
+
return err(`adf: invalid JSON input: ${e instanceof Error ? e.message : String(e)} \u2014 single-quote the JSON: adf ${tool} '{"key":"value"}'`);
|
|
2954
|
+
}
|
|
2955
|
+
if (input === null || typeof input != "object" || Array.isArray(input))
|
|
2956
|
+
return err(`adf: input must be a JSON object, e.g. adf ${tool} '{"key":"value"}'`);
|
|
2957
|
+
let reserved = Object.keys(input).filter((k) => k.startsWith("_"));
|
|
2958
|
+
if (reserved.length > 0)
|
|
2959
|
+
return err(`adf: input key${reserved.length > 1 ? "s" : ""} ${reserved.map((k) => `"${k}"`).join(", ")} refused \u2014 underscore-prefixed params are runtime-injected privileges and cannot be passed from the shell`);
|
|
2960
|
+
let result = await ctx.toolRegistry.executeTool(tool, input, ctx.workspace);
|
|
2961
|
+
if (result.isError) return err(`adf ${tool}: ${result.content}`);
|
|
2962
|
+
if (result.endTurn) {
|
|
2963
|
+
let targetState;
|
|
2964
|
+
try {
|
|
2965
|
+
let parsed = JSON.parse(result.content);
|
|
2966
|
+
typeof parsed?.target_state == "string" && (targetState = parsed.target_state);
|
|
2967
|
+
} catch {
|
|
2968
|
+
}
|
|
2969
|
+
return {
|
|
2970
|
+
exit_code: 0,
|
|
2971
|
+
stdout: result.content,
|
|
2972
|
+
stderr: "",
|
|
2973
|
+
end_turn: !0,
|
|
2974
|
+
...targetState ? { target_state: targetState } : {}
|
|
2975
|
+
};
|
|
2976
|
+
}
|
|
2977
|
+
return ok(result.content);
|
|
2978
|
+
}
|
|
2979
|
+
}, adfHandlers = [adfHandler];
|
|
2980
|
+
|
|
2981
|
+
// src/main/tools/shell/commands/help.ts
|
|
2982
|
+
var CATEGORY_ORDER = [
|
|
2983
|
+
"filesystem",
|
|
2984
|
+
"text",
|
|
2985
|
+
"data",
|
|
2986
|
+
"messaging",
|
|
2987
|
+
"network",
|
|
2988
|
+
"timers",
|
|
2989
|
+
"code",
|
|
2990
|
+
"process",
|
|
2991
|
+
"identity",
|
|
2992
|
+
"mcp",
|
|
2993
|
+
"general"
|
|
2994
|
+
], CATEGORY_LABELS = {
|
|
2995
|
+
filesystem: "Filesystem",
|
|
2996
|
+
text: "Text",
|
|
2997
|
+
data: "Data",
|
|
2998
|
+
messaging: "Messaging",
|
|
2999
|
+
network: "Network",
|
|
3000
|
+
timers: "Timers",
|
|
3001
|
+
code: "Code",
|
|
3002
|
+
process: "Process",
|
|
3003
|
+
identity: "Identity",
|
|
3004
|
+
mcp: "MCP",
|
|
3005
|
+
general: "General"
|
|
3006
|
+
}, helpHandler = {
|
|
3007
|
+
name: "help",
|
|
3008
|
+
summary: "List all commands or get help for a specific command",
|
|
3009
|
+
helpText: [
|
|
3010
|
+
"help List all available commands grouped by category",
|
|
3011
|
+
"help <command> Show detailed help for a command",
|
|
3012
|
+
"<command> -h Same as help <command>"
|
|
3013
|
+
].join(`
|
|
3014
|
+
`),
|
|
3015
|
+
category: "general",
|
|
3016
|
+
resolvedTools: [],
|
|
3017
|
+
async execute(ctx) {
|
|
3018
|
+
if (ctx.args.length > 0) {
|
|
3019
|
+
let { getCommand: getCommand2 } = await import("./commands-DTEKM4ZL.mjs"), handler = getCommand2(ctx.args[0]);
|
|
3020
|
+
return handler ? ok(handler.helpText) : ok(`Unknown command: ${ctx.args[0]}`);
|
|
3021
|
+
}
|
|
3022
|
+
let commands = getAllCommands(), grouped = /* @__PURE__ */ new Map();
|
|
3023
|
+
for (let cmd of commands) {
|
|
3024
|
+
if (cmd.name === "help") continue;
|
|
3025
|
+
let list = grouped.get(cmd.category) ?? [];
|
|
3026
|
+
list.push(cmd), grouped.set(cmd.category, list);
|
|
3027
|
+
}
|
|
3028
|
+
let lines = [];
|
|
3029
|
+
for (let cat of CATEGORY_ORDER) {
|
|
3030
|
+
let cmds = grouped.get(cat);
|
|
3031
|
+
if (!cmds || cmds.length === 0) continue;
|
|
3032
|
+
let label = CATEGORY_LABELS[cat].padEnd(12), names = cmds.map((c) => {
|
|
3033
|
+
let aliases = c.aliases ? ` (${c.aliases.join(", ")})` : "";
|
|
3034
|
+
return c.name + aliases;
|
|
3035
|
+
}).join(", ");
|
|
3036
|
+
lines.push(`${label} ${names}`);
|
|
3037
|
+
}
|
|
3038
|
+
return lines.push(""), lines.push("jq is real jq 1.8.2; sort/uniq/wc/cut/tr are real GNU coreutils (WASM)."), lines.push("Use <command> -h for details. `config tools [name]` shows tool schemas."), lines.push("Run saved scripts: ./script.sh (shell) or ./script.ts (lambda)."), lines.push("Pipelines exit with the LAST stage's code (no pipefail) \u2014 to branch on a"), lines.push("failed/gated producer, use `cmd 2>err.txt; echo $?` rather than piping it."), ok(lines.join(`
|
|
3039
|
+
`));
|
|
3040
|
+
}
|
|
3041
|
+
};
|
|
3042
|
+
|
|
3043
|
+
// src/main/tools/shell/commands/index.ts
|
|
3044
|
+
var registry = /* @__PURE__ */ new Map();
|
|
3045
|
+
function registerAll(handlers) {
|
|
3046
|
+
for (let handler of handlers)
|
|
3047
|
+
if (registry.set(handler.name, handler), handler.aliases)
|
|
3048
|
+
for (let alias of handler.aliases)
|
|
3049
|
+
registry.set(alias, handler);
|
|
3050
|
+
}
|
|
3051
|
+
registerAll(filesystemHandlers);
|
|
3052
|
+
registerAll(textHandlers);
|
|
3053
|
+
registerAll(structuredHandlers);
|
|
3054
|
+
registerAll(messagingHandlers);
|
|
3055
|
+
registerAll(networkingHandlers);
|
|
3056
|
+
registerAll(timerHandlers);
|
|
3057
|
+
registerAll(codeHandlers);
|
|
3058
|
+
registerAll(mcpHandlers);
|
|
3059
|
+
registerAll(statusHandlers);
|
|
3060
|
+
registerAll(metaHandlers);
|
|
3061
|
+
registerAll(adfHandlers);
|
|
3062
|
+
registerAll([helpHandler]);
|
|
3063
|
+
function getCommand(name) {
|
|
3064
|
+
return registry.get(name);
|
|
3065
|
+
}
|
|
3066
|
+
function getAllCommands() {
|
|
3067
|
+
let seen = /* @__PURE__ */ new Set(), result = [];
|
|
3068
|
+
for (let handler of registry.values())
|
|
3069
|
+
seen.has(handler.name) || (seen.add(handler.name), result.push(handler));
|
|
3070
|
+
return result;
|
|
3071
|
+
}
|
|
3072
|
+
function hasCommand(name) {
|
|
3073
|
+
return registry.has(name);
|
|
3074
|
+
}
|
|
3075
|
+
|
|
3076
|
+
export {
|
|
3077
|
+
withSource,
|
|
3078
|
+
withLoop,
|
|
3079
|
+
currentLoop,
|
|
3080
|
+
currentAgentId,
|
|
3081
|
+
currentSourceOrUnknown,
|
|
3082
|
+
init_execution_context,
|
|
3083
|
+
isTextMime,
|
|
3084
|
+
isVisionMime,
|
|
3085
|
+
isAudioInputMime,
|
|
3086
|
+
isVideoInputMime,
|
|
3087
|
+
mimeToAudioFormat,
|
|
3088
|
+
formatSize,
|
|
3089
|
+
mimeToExt,
|
|
3090
|
+
EXIT,
|
|
3091
|
+
err,
|
|
3092
|
+
shellReadFile,
|
|
3093
|
+
evaluateToolNames,
|
|
3094
|
+
evaluateCommand,
|
|
3095
|
+
enforceToolGate,
|
|
3096
|
+
notifyToolCallObservers,
|
|
3097
|
+
LOOP_RESET_TOOLS,
|
|
3098
|
+
loopResetRefusal,
|
|
3099
|
+
registry,
|
|
3100
|
+
getCommand,
|
|
3101
|
+
getAllCommands,
|
|
3102
|
+
hasCommand
|
|
3103
|
+
};
|