@livx.cc/agentx 0.99.32 → 0.99.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +170 -76
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +30 -2
- package/dist/index.js +174 -76
- package/dist/index.js.map +1 -1
- package/dist/tools.shell.js +191 -0
- package/dist/tools.shell.js.map +1 -1
- package/package.json +1 -1
package/dist/tools.shell.js
CHANGED
|
@@ -15,6 +15,7 @@ var log2 = forComponent("llm");
|
|
|
15
15
|
|
|
16
16
|
// src/redact.ts
|
|
17
17
|
var REDACTED = "\u2039redacted\u203A";
|
|
18
|
+
var CONFIG_FILE_RE = /(^|\/)\.(agent|claude)\/(settings(\.[\w-]+)?\.json|config\.(json|js|mjs|cjs|ts))$/i;
|
|
18
19
|
var SECRET_PAIR = /((?:^|[\s,{[])(?:export\s+)?["']?[\w.\-]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|PRIVATE_KEY|ACCESS_?KEY|AUTH(?:_?TOKEN)?|BEARER)[\w.\-]*["']?\s*[:=]\s*)(["']?)([^\s"',{}\]]+)/gi;
|
|
19
20
|
var SECRET_TOKEN = /\b(sk-ant-[\w-]{12,}|sk-[A-Za-z0-9]{20,}|ghp_[A-Za-z0-9]{20,}|gho_[A-Za-z0-9]{20,}|github_pat_[\w]{20,}|xox[baprs]-[\w-]{10,}|AKIA[0-9A-Z]{12,}|AIza[\w-]{20,}|eyJ[\w-]{8,}\.[\w-]{8,}\.[\w-]{8,})\b/g;
|
|
20
21
|
function redactSecrets(text) {
|
|
@@ -22,6 +23,196 @@ function redactSecrets(text) {
|
|
|
22
23
|
return text.replace(SECRET_PAIR, (_m, head, quote, _val) => `${head}${quote}${REDACTED}`).replace(SECRET_TOKEN, REDACTED);
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
// src/tools.structured.ts
|
|
27
|
+
function ckAbort(signal) {
|
|
28
|
+
if (signal?.aborted) throw new Error("aborted");
|
|
29
|
+
}
|
|
30
|
+
var DEFAULT_SKIP_DIRS = ["node_modules", ".git", "dist", ".tmp"];
|
|
31
|
+
var DEFAULT_MAX_DEPTH = 24;
|
|
32
|
+
function realOf(fs, p) {
|
|
33
|
+
const rp = fs.canonicalPath;
|
|
34
|
+
if (typeof rp !== "function") return p;
|
|
35
|
+
try {
|
|
36
|
+
return rp.call(fs, p) || p;
|
|
37
|
+
} catch {
|
|
38
|
+
return p;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function walkFiles(fs, dir, signal, out = [], st, depth = 0) {
|
|
42
|
+
const state = st ?? { skip: new Set(DEFAULT_SKIP_DIRS), maxDepth: DEFAULT_MAX_DEPTH, seen: /* @__PURE__ */ new Set(), prunedDirs: /* @__PURE__ */ new Set(), depthCuts: 0 };
|
|
43
|
+
if (depth > state.maxDepth) {
|
|
44
|
+
state.depthCuts++;
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
const real = realOf(fs, dir);
|
|
48
|
+
if (state.seen.has(real)) return out;
|
|
49
|
+
state.seen.add(real);
|
|
50
|
+
let entries;
|
|
51
|
+
try {
|
|
52
|
+
entries = await fs.readDir(dir);
|
|
53
|
+
} catch {
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
for (const name of entries.sort()) {
|
|
57
|
+
ckAbort(signal);
|
|
58
|
+
if (state.skip.has(name)) {
|
|
59
|
+
state.prunedDirs.add(name);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const p = dir === "/" ? `/${name}` : `${dir}/${name}`;
|
|
63
|
+
if (await fs.isDirectory(p)) await walkFiles(fs, p, signal, out, state, depth + 1);
|
|
64
|
+
else out.push(p);
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
function scanRoots(fs, path, opts) {
|
|
69
|
+
const asked = path == null ? [] : (Array.isArray(path) ? path : [path]).map((p) => String(p)).filter(Boolean);
|
|
70
|
+
const roots = (asked.length ? asked : opts?.roots?.length ? opts.roots : [fsCwd(fs)]).map((r) => r.length > 1 && r.endsWith("/") ? r.slice(0, -1) : r);
|
|
71
|
+
return [...new Set(roots)].filter((r, _i, all) => !all.some((other) => other !== r && r.startsWith(`${other}/`)));
|
|
72
|
+
}
|
|
73
|
+
function scanNote(states) {
|
|
74
|
+
const pruned = [...new Set(states.flatMap((st) => [...st.prunedDirs]))].sort();
|
|
75
|
+
const cuts = states.reduce((n, st) => n + st.depthCuts, 0);
|
|
76
|
+
const parts = [];
|
|
77
|
+
if (pruned.length) parts.push(`skipped ${pruned.join(", ")} (pass skipDirs to include them)`);
|
|
78
|
+
if (cuts) parts.push(`stopped at depth ${states[0]?.maxDepth} in ${cuts} place${cuts === 1 ? "" : "s"} (pass maxDepth to go deeper)`);
|
|
79
|
+
return parts.length ? `
|
|
80
|
+
[scan: ${parts.join("; ")}]` : "";
|
|
81
|
+
}
|
|
82
|
+
async function walkRoots(fs, roots, signal, opts, call) {
|
|
83
|
+
const skip = new Set(call?.skipDirs ?? opts?.skipDirs ?? DEFAULT_SKIP_DIRS);
|
|
84
|
+
const maxDepth = call?.maxDepth ?? opts?.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
85
|
+
const out = [];
|
|
86
|
+
const states = [];
|
|
87
|
+
for (const root of roots) {
|
|
88
|
+
const state = { skip, maxDepth, seen: /* @__PURE__ */ new Set(), prunedDirs: /* @__PURE__ */ new Set(), depthCuts: 0 };
|
|
89
|
+
states.push(state);
|
|
90
|
+
await walkFiles(fs, root, signal, out, state, 0);
|
|
91
|
+
}
|
|
92
|
+
return { files: out, note: scanNote(states) };
|
|
93
|
+
}
|
|
94
|
+
function callScan(args) {
|
|
95
|
+
const sd = args.skipDirs;
|
|
96
|
+
return {
|
|
97
|
+
skipDirs: Array.isArray(sd) ? sd.map(String) : typeof sd === "string" ? sd.split(",").map((x) => x.trim()).filter(Boolean) : void 0,
|
|
98
|
+
maxDepth: args.maxDepth == null ? void 0 : Number(args.maxDepth)
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
var SCAN_PARAMS = {
|
|
102
|
+
path: { type: "string", description: "optional directory (or list) to search instead of the default roots; absolute VFS path" },
|
|
103
|
+
skipDirs: { type: "string", description: `comma-separated directory names to prune (default: ${DEFAULT_SKIP_DIRS.join(", ")}); pass an empty string to prune nothing` },
|
|
104
|
+
maxDepth: { type: "number", description: `max directory depth below each root (default ${DEFAULT_MAX_DEPTH})` }
|
|
105
|
+
};
|
|
106
|
+
function anchoredGlobAt(root, glob) {
|
|
107
|
+
if (glob.startsWith("/")) return globToRegExp(glob);
|
|
108
|
+
return globToRegExp(`${root === "/" ? "" : root}/${glob}`);
|
|
109
|
+
}
|
|
110
|
+
function globMatcher(roots, glob) {
|
|
111
|
+
const res = roots.map((r) => anchoredGlobAt(r, glob));
|
|
112
|
+
return (p) => res.some((re) => re.test(p));
|
|
113
|
+
}
|
|
114
|
+
function globToRegExp(glob, caseInsensitive = false) {
|
|
115
|
+
const g = glob.startsWith("/") ? glob : `/${glob}`;
|
|
116
|
+
let re = "";
|
|
117
|
+
for (let i = 0; i < g.length; i++) {
|
|
118
|
+
const c = g[i];
|
|
119
|
+
if (c === "*") {
|
|
120
|
+
if (g[i + 1] === "*") {
|
|
121
|
+
re += ".*";
|
|
122
|
+
i++;
|
|
123
|
+
if (g[i + 1] === "/") i++;
|
|
124
|
+
} else re += "[^/]*";
|
|
125
|
+
} else if (c === "?") re += "[^/]";
|
|
126
|
+
else re += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
127
|
+
}
|
|
128
|
+
return new RegExp(`^${re}$`, caseInsensitive ? "i" : "");
|
|
129
|
+
}
|
|
130
|
+
var fsCwd = (fs) => fs.getCwd();
|
|
131
|
+
function makeGlobTool(opts) {
|
|
132
|
+
return {
|
|
133
|
+
name: "Glob",
|
|
134
|
+
description: 'Find files by glob pattern (e.g. "**/*.ts", "src/**/*.test.ts"). Returns sorted paths, one per line. Space-separated patterns combine; `!`-prefix excludes (e.g. "**/*.ts !**/*.test.ts"). Optional `path` to search a specific directory. Prefer over `bash find` for locating files \u2014 one call, structured output.',
|
|
135
|
+
parameters: {
|
|
136
|
+
type: "object",
|
|
137
|
+
required: ["pattern"],
|
|
138
|
+
properties: {
|
|
139
|
+
pattern: { type: "string", description: "glob pattern(s); ** matches across directories; prefix a pattern with ! to exclude" },
|
|
140
|
+
...SCAN_PARAMS
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
async run({ pattern, path, skipDirs, maxDepth }, ctx) {
|
|
144
|
+
const roots = scanRoots(ctx.fs, path, opts);
|
|
145
|
+
const pats = String(pattern ?? "").trim().split(/\s+/).filter(Boolean);
|
|
146
|
+
const include = pats.filter((p) => !p.startsWith("!")).map((p) => globMatcher(roots, p));
|
|
147
|
+
const exclude = pats.filter((p) => p.startsWith("!")).map((p) => globMatcher(roots, p.slice(1)));
|
|
148
|
+
const includes = include.length ? include : [globMatcher(roots, "**")];
|
|
149
|
+
const { files, note } = await walkRoots(ctx.fs, roots, ctx.signal, opts, callScan({ skipDirs, maxDepth }));
|
|
150
|
+
const hits = files.filter((p) => includes.some((m) => m(p)) && !exclude.some((m) => m(p)));
|
|
151
|
+
return (hits.length ? [...new Set(hits)].sort().join("\n") : "(no matches)") + note;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function makeGrepTool(opts) {
|
|
156
|
+
return {
|
|
157
|
+
name: "Grep",
|
|
158
|
+
description: "Search file contents by regex. Returns `path:line: text` hits. Optional `glob` to scope files, `path` to search a specific directory, `context` for surrounding lines, `filesOnly` for matching paths only. Prefer over `bash grep` for file content search \u2014 structured results, no re-parse needed. Use `bash` instead for running commands, tests, or piped workflows.",
|
|
159
|
+
parameters: {
|
|
160
|
+
type: "object",
|
|
161
|
+
required: ["pattern"],
|
|
162
|
+
properties: {
|
|
163
|
+
pattern: { type: "string", description: "JS regular expression" },
|
|
164
|
+
glob: { type: "string", description: "optional file glob to restrict the search" },
|
|
165
|
+
...SCAN_PARAMS,
|
|
166
|
+
context: { type: "number", description: "lines of context before/after each hit" },
|
|
167
|
+
filesOnly: { type: "boolean", description: "only list matching file paths" }
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
async run({ pattern, glob, path, skipDirs, maxDepth, context, filesOnly }, ctx) {
|
|
171
|
+
let re;
|
|
172
|
+
try {
|
|
173
|
+
re = new RegExp(String(pattern ?? ""));
|
|
174
|
+
} catch (e) {
|
|
175
|
+
throw new Error(`invalid regex: ${String(e)}`);
|
|
176
|
+
}
|
|
177
|
+
const roots = scanRoots(ctx.fs, path, opts);
|
|
178
|
+
const scope = glob ? globMatcher(roots, String(glob)) : null;
|
|
179
|
+
const walked = await walkRoots(ctx.fs, roots, ctx.signal, opts, callScan({ skipDirs, maxDepth }));
|
|
180
|
+
const files = [...new Set(walked.files)].sort().filter((p) => !scope || scope(p));
|
|
181
|
+
const ctxN = Math.max(0, Number(context ?? 0));
|
|
182
|
+
const out = [];
|
|
183
|
+
const matched = [];
|
|
184
|
+
let skipped = 0;
|
|
185
|
+
for (const path2 of files) {
|
|
186
|
+
ckAbort(ctx.signal);
|
|
187
|
+
let content;
|
|
188
|
+
try {
|
|
189
|
+
content = await ctx.fs.readFile(path2);
|
|
190
|
+
} catch {
|
|
191
|
+
skipped++;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
const lines = content.split("\n");
|
|
195
|
+
const mask = CONFIG_FILE_RE.test(path2);
|
|
196
|
+
let fileHit = false;
|
|
197
|
+
for (let i = 0; i < lines.length; i++) {
|
|
198
|
+
if (!re.test(lines[i])) continue;
|
|
199
|
+
fileHit = true;
|
|
200
|
+
if (filesOnly) break;
|
|
201
|
+
const lo = Math.max(0, i - ctxN), hi = Math.min(lines.length - 1, i + ctxN);
|
|
202
|
+
for (let j = lo; j <= hi; j++) out.push(`${path2}:${j + 1}: ${mask ? redactSecrets(lines[j]) : lines[j]}`);
|
|
203
|
+
}
|
|
204
|
+
if (fileHit) matched.push(path2);
|
|
205
|
+
}
|
|
206
|
+
const note = (skipped ? `
|
|
207
|
+
[skipped ${skipped} unreadable file${skipped === 1 ? "" : "s"}]` : "") + walked.note;
|
|
208
|
+
if (filesOnly) return (matched.length ? matched.join("\n") : "(no matches)") + note;
|
|
209
|
+
return (out.length ? out.join("\n") : "(no matches)") + note;
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
var globTool = makeGlobTool();
|
|
214
|
+
var grepTool = makeGrepTool();
|
|
215
|
+
|
|
25
216
|
// src/tools.web.ts
|
|
26
217
|
var log3 = forComponent("web");
|
|
27
218
|
function htmlToText(html) {
|