veefore 1.4.0 → 1.5.0
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/veefore.mjs +1221 -274
- package/package.json +1 -1
package/dist/veefore.mjs
CHANGED
|
@@ -236,29 +236,29 @@ var ApiClient = class {
|
|
|
236
236
|
setToken(token) {
|
|
237
237
|
this.token = token;
|
|
238
238
|
}
|
|
239
|
-
async get(
|
|
239
|
+
async get(path14, query = {}) {
|
|
240
240
|
const search = new URLSearchParams();
|
|
241
241
|
for (const [key, value] of Object.entries(query)) {
|
|
242
242
|
if (value !== void 0) search.set(key, String(value));
|
|
243
243
|
}
|
|
244
244
|
const suffix = search.toString() === "" ? "" : `?${search.toString()}`;
|
|
245
|
-
return this.send("GET", `${
|
|
245
|
+
return this.send("GET", `${path14}${suffix}`);
|
|
246
246
|
}
|
|
247
|
-
async post(
|
|
248
|
-
return this.send("POST",
|
|
247
|
+
async post(path14, body) {
|
|
248
|
+
return this.send("POST", path14, body);
|
|
249
249
|
}
|
|
250
|
-
async patch(
|
|
251
|
-
return this.send("PATCH",
|
|
250
|
+
async patch(path14, body) {
|
|
251
|
+
return this.send("PATCH", path14, body);
|
|
252
252
|
}
|
|
253
|
-
async delete(
|
|
254
|
-
return this.send("DELETE",
|
|
253
|
+
async delete(path14) {
|
|
254
|
+
return this.send("DELETE", path14);
|
|
255
255
|
}
|
|
256
256
|
/** Raw request for endpoints that return a non-JSON body (exports). */
|
|
257
|
-
async raw(method,
|
|
258
|
-
return this.fetchWithTimeout(method,
|
|
257
|
+
async raw(method, path14, body) {
|
|
258
|
+
return this.fetchWithTimeout(method, path14, body);
|
|
259
259
|
}
|
|
260
|
-
async send(method,
|
|
261
|
-
const response = await this.fetchWithTimeout(method,
|
|
260
|
+
async send(method, path14, body) {
|
|
261
|
+
const response = await this.fetchWithTimeout(method, path14, body);
|
|
262
262
|
const text = await response.text();
|
|
263
263
|
const parsed = parseJson(text);
|
|
264
264
|
if (response.ok) {
|
|
@@ -266,14 +266,14 @@ var ApiClient = class {
|
|
|
266
266
|
if (parsed && "data" in parsed) return parsed.data;
|
|
267
267
|
throw new CliApiError(
|
|
268
268
|
"UNEXPECTED_RESPONSE",
|
|
269
|
-
`The control plane returned an unexpected body for ${method} ${
|
|
269
|
+
`The control plane returned an unexpected body for ${method} ${path14}.`,
|
|
270
270
|
response.status,
|
|
271
271
|
response.headers.get("x-request-id")
|
|
272
272
|
);
|
|
273
273
|
}
|
|
274
274
|
const error = new CliApiError(
|
|
275
275
|
parsed?.error?.code ?? `HTTP_${response.status}`,
|
|
276
|
-
parsed?.error?.message ?? `${method} ${
|
|
276
|
+
parsed?.error?.message ?? `${method} ${path14} failed with ${response.status} ${response.statusText}.`,
|
|
277
277
|
response.status,
|
|
278
278
|
parsed?.error?.request_id ?? response.headers.get("x-request-id"),
|
|
279
279
|
parsed?.error?.details ?? {}
|
|
@@ -291,7 +291,7 @@ var ApiClient = class {
|
|
|
291
291
|
}
|
|
292
292
|
throw error;
|
|
293
293
|
}
|
|
294
|
-
async fetchWithTimeout(method,
|
|
294
|
+
async fetchWithTimeout(method, path14, body) {
|
|
295
295
|
const controller = new AbortController();
|
|
296
296
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
297
297
|
const headers = { accept: "application/json" };
|
|
@@ -300,7 +300,7 @@ var ApiClient = class {
|
|
|
300
300
|
if (this.workspaceId) headers["x-veefore-workspace"] = this.workspaceId;
|
|
301
301
|
if (this.environmentId) headers["x-veefore-environment"] = this.environmentId;
|
|
302
302
|
try {
|
|
303
|
-
return await fetch(`${this.controlPlaneUrl}${
|
|
303
|
+
return await fetch(`${this.controlPlaneUrl}${path14}`, {
|
|
304
304
|
method,
|
|
305
305
|
headers,
|
|
306
306
|
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
@@ -586,16 +586,6 @@ var LOGO = [
|
|
|
586
586
|
" \u2588\u2588 \u2588\u2588 \u2588\u2588",
|
|
587
587
|
" \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588"
|
|
588
588
|
];
|
|
589
|
-
var LOGO_SMALL = [
|
|
590
|
-
"\u2580\u2588\u2580\u2580\u2580\u2580\u2580\u2580\u2588 \u2588\u2580\u2580\u2580\u2580\u2580\u2580\u2588\u2580",
|
|
591
|
-
" \u2580\u2584 \u2584\u2584 \u2580\u2588 \u2588\u2580 \u2584\u2584 \u2584\u2588",
|
|
592
|
-
" \u2588\u2584 \u2588 \u2588\u2580 \u2588 \u2584\u2588",
|
|
593
|
-
" \u2588\u2584 \u2588 \u2588\u2580 \u2588 \u2584\u2588",
|
|
594
|
-
" \u2588 \u2580\u2588\u2584\u2580 \u2588\u2580 \u2588",
|
|
595
|
-
" \u2588 \u2580\u2588 \u2588\u2580 \u2588",
|
|
596
|
-
" \u2580\u2588 \u2584\u2580 \u2588\u2580",
|
|
597
|
-
" \u2580\u2588\u2584\u2584\u2588\u2588 \u2588\u2580"
|
|
598
|
-
];
|
|
599
589
|
|
|
600
590
|
// src/tui.ts
|
|
601
591
|
function isInteractive() {
|
|
@@ -655,7 +645,7 @@ var STARS = [
|
|
|
655
645
|
];
|
|
656
646
|
async function showWelcome(context, options = {}) {
|
|
657
647
|
if (!isInteractive()) return;
|
|
658
|
-
const lines = welcomeLines(context
|
|
648
|
+
const lines = welcomeLines(context);
|
|
659
649
|
if ((options.animate ?? true) && process.env.VEEFORE_NO_ANIMATION === void 0) {
|
|
660
650
|
for (const line of lines) {
|
|
661
651
|
out(line);
|
|
@@ -665,9 +655,9 @@ async function showWelcome(context, options = {}) {
|
|
|
665
655
|
}
|
|
666
656
|
for (const line of lines) out(line);
|
|
667
657
|
}
|
|
668
|
-
function welcomeLines(context
|
|
658
|
+
function welcomeLines(context) {
|
|
669
659
|
const width = Math.min(columns(), 100);
|
|
670
|
-
const art =
|
|
660
|
+
const art = LOGO;
|
|
671
661
|
const logoWidth = Math.max(...art.map((line) => [...line].length));
|
|
672
662
|
const indent = 5;
|
|
673
663
|
const gap = 6;
|
|
@@ -677,7 +667,7 @@ function welcomeLines(context, reserve) {
|
|
|
677
667
|
);
|
|
678
668
|
const sideBySide = width >= indent + logoWidth + gap + panelWidth + 2;
|
|
679
669
|
const panel = renderPanel(context.facts);
|
|
680
|
-
const showLogo = width >= indent + logoWidth + 2
|
|
670
|
+
const showLogo = width >= indent + logoWidth + 2;
|
|
681
671
|
const rule = style.grey("\xB7".repeat(Math.max(10, Math.min(width - 2, 72))));
|
|
682
672
|
const lines = [];
|
|
683
673
|
const add = (line = "") => void lines.push(line);
|
|
@@ -735,7 +725,7 @@ function renderPanel(facts) {
|
|
|
735
725
|
}
|
|
736
726
|
function clearScreen() {
|
|
737
727
|
if (!isInteractive()) return;
|
|
738
|
-
process.stdout.write("\x1B[H\x1B[
|
|
728
|
+
process.stdout.write("\x1B[H\x1B[J");
|
|
739
729
|
}
|
|
740
730
|
var appDepth = 0;
|
|
741
731
|
var restoreInstalled = false;
|
|
@@ -749,7 +739,7 @@ function enterApp() {
|
|
|
749
739
|
if (appDepth > 0) process.stdout.write("\x1B[?25h\x1B[?1049l");
|
|
750
740
|
});
|
|
751
741
|
}
|
|
752
|
-
process.stdout.write("\x1B[?1049h\x1B[H\x1B[
|
|
742
|
+
process.stdout.write("\x1B[?1049h\x1B[?25l\x1B[H\x1B[J");
|
|
753
743
|
}
|
|
754
744
|
function leaveApp() {
|
|
755
745
|
if (!isInteractive() || appDepth === 0) return;
|
|
@@ -757,6 +747,9 @@ function leaveApp() {
|
|
|
757
747
|
if (appDepth > 0) return;
|
|
758
748
|
process.stdout.write("\x1B[?25h\x1B[?1049l");
|
|
759
749
|
}
|
|
750
|
+
function inApp() {
|
|
751
|
+
return appDepth > 0;
|
|
752
|
+
}
|
|
760
753
|
async function onOrdinaryScreen(body) {
|
|
761
754
|
const depth = appDepth;
|
|
762
755
|
if (depth > 0) {
|
|
@@ -768,35 +761,19 @@ async function onOrdinaryScreen(body) {
|
|
|
768
761
|
} finally {
|
|
769
762
|
if (depth > 0) {
|
|
770
763
|
appDepth = depth;
|
|
771
|
-
process.stdout.write("\x1B[?1049h\x1B[H\x1B[
|
|
764
|
+
process.stdout.write("\x1B[?1049h\x1B[H\x1B[J");
|
|
772
765
|
}
|
|
773
766
|
}
|
|
774
767
|
}
|
|
775
|
-
function compactHeader(context) {
|
|
776
|
-
const facts = Object.fromEntries(context.facts);
|
|
777
|
-
const parts = [
|
|
778
|
-
brand(style.bold("Veefore")),
|
|
779
|
-
style.grey(`v${context.version}`),
|
|
780
|
-
...[facts.Folder, facts["Signed in"], facts.Veefore].filter((v) => Boolean(v))
|
|
781
|
-
];
|
|
782
|
-
out();
|
|
783
|
-
out(fit(` ${parts.join(style.grey(" \xB7 "))}`, columns() - 1));
|
|
784
|
-
out(` ${style.grey("\xB7".repeat(Math.max(10, Math.min(columns() - 2, 72))))}`);
|
|
785
|
-
}
|
|
786
768
|
async function beginScreen(source, options = {}) {
|
|
787
769
|
if (!isInteractive()) return;
|
|
788
770
|
clearScreen();
|
|
789
|
-
|
|
790
|
-
if (options.compact || rows() < LOGO_SMALL.length + 6 + reserve) compactHeader(source());
|
|
791
|
-
else await showWelcome(source(), { animate: options.animate ?? false, reserve });
|
|
771
|
+
await showWelcome(source(), { animate: options.animate ?? false });
|
|
792
772
|
}
|
|
793
|
-
function drawScreen(source
|
|
773
|
+
function drawScreen(source) {
|
|
794
774
|
if (!isInteractive()) return;
|
|
795
775
|
clearScreen();
|
|
796
|
-
const
|
|
797
|
-
const context = source();
|
|
798
|
-
if (options.compact || rows() < LOGO_SMALL.length + 6 + reserve) compactHeader(context);
|
|
799
|
-
else for (const line of welcomeLines(context, reserve)) out(line);
|
|
776
|
+
for (const line of welcomeLines(source())) out(line);
|
|
800
777
|
}
|
|
801
778
|
async function showOutput(lines, options) {
|
|
802
779
|
if (!isInteractive()) {
|
|
@@ -987,14 +964,19 @@ async function select(question, real, settings = {}) {
|
|
|
987
964
|
return new Promise((resolve, reject) => {
|
|
988
965
|
const wasRaw = stdin.isRaw;
|
|
989
966
|
const restoreCursor = () => {
|
|
990
|
-
stdout.write("\x1B[?25h");
|
|
967
|
+
if (!inApp()) stdout.write("\x1B[?25h");
|
|
991
968
|
};
|
|
992
969
|
const stopWatching = settings.redraw ? watchFacts(repaint) : void 0;
|
|
970
|
+
const onResize = () => {
|
|
971
|
+
if (settings.redraw) repaint();
|
|
972
|
+
else draw();
|
|
973
|
+
};
|
|
993
974
|
const finish = (result) => {
|
|
994
975
|
stdin.off("keypress", onKey);
|
|
995
976
|
stdin.setRawMode(wasRaw);
|
|
996
977
|
stdin.pause();
|
|
997
978
|
stopWatching?.();
|
|
979
|
+
process.stdout.off("resize", onResize);
|
|
998
980
|
process.off("exit", restoreCursor);
|
|
999
981
|
if (rendered > 0) stdout.write(`\x1B[${rendered}A\r\x1B[J`);
|
|
1000
982
|
restoreCursor();
|
|
@@ -1044,6 +1026,7 @@ async function select(question, real, settings = {}) {
|
|
|
1044
1026
|
stdin.resume();
|
|
1045
1027
|
stdout.write("\x1B[?25l");
|
|
1046
1028
|
stdin.on("keypress", onKey);
|
|
1029
|
+
process.stdout.on("resize", onResize);
|
|
1047
1030
|
draw();
|
|
1048
1031
|
});
|
|
1049
1032
|
}
|
|
@@ -1963,11 +1946,11 @@ function renderRevokeConfirmation(agent, environment, preview) {
|
|
|
1963
1946
|
out(` ${style.red("This action is irreversible for this identity.")}`);
|
|
1964
1947
|
out();
|
|
1965
1948
|
}
|
|
1966
|
-
async function lifecycle(args, flags,
|
|
1949
|
+
async function lifecycle(args, flags, path14, resultingStatus) {
|
|
1967
1950
|
const context = await resolveContext(flags);
|
|
1968
1951
|
const agentId = positional(args) ?? context.config.agentId;
|
|
1969
1952
|
const agent = await context.api.get(`/v1/agents/${agentId}`);
|
|
1970
|
-
const reasonRequired =
|
|
1953
|
+
const reasonRequired = path14 === "suspend";
|
|
1971
1954
|
const reason = valueFor(args, "--reason");
|
|
1972
1955
|
if (reasonRequired && (!reason || reason.trim().length < 10)) {
|
|
1973
1956
|
throw new CliError(
|
|
@@ -1977,24 +1960,24 @@ async function lifecycle(args, flags, path13, resultingStatus) {
|
|
|
1977
1960
|
}
|
|
1978
1961
|
out();
|
|
1979
1962
|
out(
|
|
1980
|
-
`${symbol.info} ${
|
|
1963
|
+
`${symbol.info} ${path14} ${style.bold(agent.name)} ${style.grey(`(${agent.status} \u2192 ${resultingStatus})`)}`
|
|
1981
1964
|
);
|
|
1982
|
-
if (
|
|
1965
|
+
if (path14 !== "resume") {
|
|
1983
1966
|
const confirmed = await confirm("Continue?", { yes: flags.yes });
|
|
1984
1967
|
if (!confirmed) {
|
|
1985
1968
|
out(style.grey("Cancelled."));
|
|
1986
1969
|
return 1;
|
|
1987
1970
|
}
|
|
1988
1971
|
}
|
|
1989
|
-
const actionSpinner = spinner(`Applying ${
|
|
1972
|
+
const actionSpinner = spinner(`Applying ${path14}`);
|
|
1990
1973
|
try {
|
|
1991
1974
|
await withStepUp(
|
|
1992
1975
|
context.api,
|
|
1993
|
-
() => context.api.post(`/v1/agents/${agentId}/${
|
|
1976
|
+
() => context.api.post(`/v1/agents/${agentId}/${path14}`, reason ? { reason } : {})
|
|
1994
1977
|
);
|
|
1995
1978
|
actionSpinner.succeed(`${agent.name} is ${resultingStatus}`);
|
|
1996
1979
|
} catch (error) {
|
|
1997
|
-
actionSpinner.fail(`Could not ${
|
|
1980
|
+
actionSpinner.fail(`Could not ${path14} the agent`);
|
|
1998
1981
|
throw error;
|
|
1999
1982
|
}
|
|
2000
1983
|
out();
|
|
@@ -2306,6 +2289,7 @@ import { spawnSync } from "node:child_process";
|
|
|
2306
2289
|
import { existsSync as existsSync2, readFileSync as readFileSync2, realpathSync } from "node:fs";
|
|
2307
2290
|
import path3 from "node:path";
|
|
2308
2291
|
var MIN_APPROVAL_WAIT_SDK = "1.1.0";
|
|
2292
|
+
var MIN_CHECKPOINT_SDK = "1.2.0";
|
|
2309
2293
|
function versionLessThan(a, b) {
|
|
2310
2294
|
if (!a) return true;
|
|
2311
2295
|
const left = a.split(/[.+-]/).slice(0, 3).map(Number);
|
|
@@ -2352,7 +2336,8 @@ function installedSdks(root, options = {}) {
|
|
|
2352
2336
|
language: "js",
|
|
2353
2337
|
location: path3.relative(top, path3.dirname(manifest)) || ".",
|
|
2354
2338
|
version,
|
|
2355
|
-
waitsForApproval: !versionLessThan(version, MIN_APPROVAL_WAIT_SDK)
|
|
2339
|
+
waitsForApproval: !versionLessThan(version, MIN_APPROVAL_WAIT_SDK),
|
|
2340
|
+
hasCheckpoint: !versionLessThan(version, MIN_CHECKPOINT_SDK)
|
|
2356
2341
|
});
|
|
2357
2342
|
}
|
|
2358
2343
|
if (options.python) {
|
|
@@ -2369,7 +2354,8 @@ function installedSdks(root, options = {}) {
|
|
|
2369
2354
|
language: "py",
|
|
2370
2355
|
location: "python: veefore-py",
|
|
2371
2356
|
version,
|
|
2372
|
-
waitsForApproval: !versionLessThan(version, MIN_APPROVAL_WAIT_SDK)
|
|
2357
|
+
waitsForApproval: !versionLessThan(version, MIN_APPROVAL_WAIT_SDK),
|
|
2358
|
+
hasCheckpoint: !versionLessThan(version, MIN_CHECKPOINT_SDK)
|
|
2373
2359
|
});
|
|
2374
2360
|
}
|
|
2375
2361
|
}
|
|
@@ -3317,12 +3303,12 @@ function isLoosening(from, to) {
|
|
|
3317
3303
|
}
|
|
3318
3304
|
|
|
3319
3305
|
// src/commands/init.ts
|
|
3320
|
-
import { readFileSync as
|
|
3321
|
-
import
|
|
3306
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
3307
|
+
import path13 from "node:path";
|
|
3322
3308
|
|
|
3323
3309
|
// src/integrate.ts
|
|
3324
|
-
import { readFileSync as
|
|
3325
|
-
import
|
|
3310
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
3311
|
+
import path10 from "node:path";
|
|
3326
3312
|
|
|
3327
3313
|
// ../types/src/permissions.ts
|
|
3328
3314
|
var PERMISSIONS = [
|
|
@@ -3659,6 +3645,11 @@ var EVENT_TYPES = [
|
|
|
3659
3645
|
"user.profile_updated",
|
|
3660
3646
|
"onboarding.completed",
|
|
3661
3647
|
"session.revoked",
|
|
3648
|
+
// Sign-in is served from its own origin, so a session cannot be carried
|
|
3649
|
+
// across to the console by cookie; it is re-issued there from a one-time
|
|
3650
|
+
// code. Recorded at both ends, so the trail shows one session ending and
|
|
3651
|
+
// its replacement beginning rather than an unexplained gap.
|
|
3652
|
+
"session.handed_over",
|
|
3662
3653
|
"cli.authorization_approved",
|
|
3663
3654
|
"cli.authorization_denied",
|
|
3664
3655
|
"cli.session_issued",
|
|
@@ -4201,10 +4192,33 @@ function printDispatchInstructions(items, where) {
|
|
|
4201
4192
|
out(
|
|
4202
4193
|
` ${items.length} tool${items.length === 1 ? " is" : "s are"} defined as data, so no one function performs each tool.`
|
|
4203
4194
|
);
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4195
|
+
const keys = [...new Set(items.map((item) => item.key))];
|
|
4196
|
+
const sites = new Map(
|
|
4197
|
+
keys.flatMap((key) => {
|
|
4198
|
+
const site = where?.sites?.get(key);
|
|
4199
|
+
return site ? [[key, site]] : [];
|
|
4200
|
+
})
|
|
4201
|
+
);
|
|
4202
|
+
const places = new Set([...sites.values()].map((site) => `${site.file}:${site.line}`));
|
|
4203
|
+
const only = places.size === 1 && sites.size === keys.length ? [...sites.values()][0] : null;
|
|
4204
|
+
if (sites.size > 0 && !only) {
|
|
4205
|
+
out(" Wrap the call that performs each one, where your code runs it:");
|
|
4206
|
+
out();
|
|
4207
|
+
for (const key of keys) {
|
|
4208
|
+
const site = sites.get(key);
|
|
4209
|
+
out(
|
|
4210
|
+
` ${style.bold(key)} ${style.grey(site ? `${site.file}:${site.line}` : "where it is run; this could not be found")}`
|
|
4211
|
+
);
|
|
4212
|
+
if (site) out(` ${style.grey(site.code)}`);
|
|
4213
|
+
}
|
|
4214
|
+
out();
|
|
4215
|
+
out(" The change is the same shape in each place:");
|
|
4216
|
+
} else if (only || where) {
|
|
4217
|
+
const file = only?.file ?? where?.file;
|
|
4218
|
+
const line = only?.line ?? where?.line ?? 1;
|
|
4219
|
+
const named = where?.functionName && file === where.file ? ` (${where.functionName})` : "";
|
|
4220
|
+
out(` Your code runs them in ${style.bold(`${file}:${line}`)}${named}.`);
|
|
4221
|
+
if (only) out(` ${style.grey(only.code)}`);
|
|
4208
4222
|
out(" Wrap the call there that performs the chosen tool, once:");
|
|
4209
4223
|
} else {
|
|
4210
4224
|
out(" Wrap the code that runs whichever tool the model picked, once:");
|
|
@@ -4222,7 +4236,9 @@ function printDispatchInstructions(items, where) {
|
|
|
4222
4236
|
out(` ${style.cyan("import { guard } from '@veefore/sdk';")}`);
|
|
4223
4237
|
out();
|
|
4224
4238
|
out(` ${style.grey("// before: const result = await runTool(call);")}`);
|
|
4225
|
-
out(
|
|
4239
|
+
out(
|
|
4240
|
+
` ${style.cyan("const result = await guard(call.name, (_args) => runTool(call))(call.args);")}`
|
|
4241
|
+
);
|
|
4226
4242
|
}
|
|
4227
4243
|
out();
|
|
4228
4244
|
out(
|
|
@@ -4250,7 +4266,7 @@ function toCamel(key) {
|
|
|
4250
4266
|
}
|
|
4251
4267
|
function findGuardedKeys(sources) {
|
|
4252
4268
|
const found = /* @__PURE__ */ new Map();
|
|
4253
|
-
const pattern = /(?:\b|@)[A-Za-z_$]*[gG]uard\(\s*['"]([a-z][a-z0-9_]*)['"]/g;
|
|
4269
|
+
const pattern = /(?:\b|@)(?:[A-Za-z_$]*[gG]uard|[A-Za-z_$]*[cC]heckpoint)\(\s*['"]([a-z][a-z0-9_]*)['"]/g;
|
|
4254
4270
|
for (const { file, text } of sources) {
|
|
4255
4271
|
for (const match of text.matchAll(pattern)) {
|
|
4256
4272
|
const key = match[1];
|
|
@@ -4663,12 +4679,95 @@ function restoreBackup(root, backup, options = {}) {
|
|
|
4663
4679
|
}
|
|
4664
4680
|
|
|
4665
4681
|
// src/rewrite.ts
|
|
4666
|
-
import { readFileSync as
|
|
4667
|
-
import
|
|
4682
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
4683
|
+
import path9 from "node:path";
|
|
4668
4684
|
|
|
4669
4685
|
// src/scan.ts
|
|
4670
|
-
import { existsSync as existsSync6, readFileSync as
|
|
4671
|
-
import
|
|
4686
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "node:fs";
|
|
4687
|
+
import path8 from "node:path";
|
|
4688
|
+
|
|
4689
|
+
// src/anchors.ts
|
|
4690
|
+
var TOOL_ANCHORS = [
|
|
4691
|
+
// Schema-defined tools: findToolSchemas requires one of these keys.
|
|
4692
|
+
"parameters",
|
|
4693
|
+
"input_schema",
|
|
4694
|
+
"inputSchema",
|
|
4695
|
+
"parametersJsonSchema",
|
|
4696
|
+
// The OpenAI function shape, which may carry no description at all.
|
|
4697
|
+
"'function'",
|
|
4698
|
+
'"function"',
|
|
4699
|
+
"`function`",
|
|
4700
|
+
// Framework calls and decorators.
|
|
4701
|
+
"tool(",
|
|
4702
|
+
"Tool(",
|
|
4703
|
+
"tool (",
|
|
4704
|
+
"Tool (",
|
|
4705
|
+
"registerTool",
|
|
4706
|
+
// Python decorators, including the `@mcp.tool` / `@agent.tool` forms, which
|
|
4707
|
+
// carry neither `@tool` nor `tool(`.
|
|
4708
|
+
"@tool",
|
|
4709
|
+
".tool",
|
|
4710
|
+
"function_tool",
|
|
4711
|
+
"StructuredTool",
|
|
4712
|
+
"DynamicTool"
|
|
4713
|
+
];
|
|
4714
|
+
var AGENT_ANCHORS = [
|
|
4715
|
+
// Model SDKs and agent frameworks.
|
|
4716
|
+
"openai",
|
|
4717
|
+
"OpenAI",
|
|
4718
|
+
"@anthropic-ai/sdk",
|
|
4719
|
+
"anthropic",
|
|
4720
|
+
"Anthropic",
|
|
4721
|
+
"@google/genai",
|
|
4722
|
+
"@google/generative-ai",
|
|
4723
|
+
"google.generativeai",
|
|
4724
|
+
"google-generativeai",
|
|
4725
|
+
"langchain",
|
|
4726
|
+
"langgraph",
|
|
4727
|
+
"crewai",
|
|
4728
|
+
"llama_index",
|
|
4729
|
+
"llamaindex",
|
|
4730
|
+
"autogen",
|
|
4731
|
+
"litellm",
|
|
4732
|
+
"mistralai",
|
|
4733
|
+
"ollama",
|
|
4734
|
+
"bedrock-runtime",
|
|
4735
|
+
"bedrock_runtime",
|
|
4736
|
+
"@modelcontextprotocol",
|
|
4737
|
+
"mcp.server",
|
|
4738
|
+
// The vocabulary of a tool call, whatever produced it.
|
|
4739
|
+
"tool_calls",
|
|
4740
|
+
"toolCalls",
|
|
4741
|
+
"tool_call",
|
|
4742
|
+
"toolCall",
|
|
4743
|
+
"function_call",
|
|
4744
|
+
"functionCall",
|
|
4745
|
+
"tool_choice",
|
|
4746
|
+
"toolChoice",
|
|
4747
|
+
"toolUse",
|
|
4748
|
+
"tool_use"
|
|
4749
|
+
];
|
|
4750
|
+
var GUARD_ANCHORS = ["uard(", "uard (", "checkpoint("];
|
|
4751
|
+
var RISK_ANCHORS = [
|
|
4752
|
+
"://",
|
|
4753
|
+
"process.env",
|
|
4754
|
+
"import.meta.env",
|
|
4755
|
+
"os.environ",
|
|
4756
|
+
"getenv",
|
|
4757
|
+
"Deno.env"
|
|
4758
|
+
];
|
|
4759
|
+
function toolKeyAnchors(toolKeys) {
|
|
4760
|
+
const anchors = /* @__PURE__ */ new Set();
|
|
4761
|
+
for (const key of toolKeys) {
|
|
4762
|
+
anchors.add(key);
|
|
4763
|
+
const camel = key.replace(/_([a-z0-9])/g, (_match, char) => char.toUpperCase());
|
|
4764
|
+
if (camel !== key) anchors.add(camel);
|
|
4765
|
+
}
|
|
4766
|
+
return [...anchors];
|
|
4767
|
+
}
|
|
4768
|
+
var SCAN_ANCHORS = [
|
|
4769
|
+
.../* @__PURE__ */ new Set([...TOOL_ANCHORS, ...AGENT_ANCHORS, ...GUARD_ANCHORS, ...RISK_ANCHORS])
|
|
4770
|
+
];
|
|
4672
4771
|
|
|
4673
4772
|
// src/framework.ts
|
|
4674
4773
|
import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync as readdirSync2 } from "node:fs";
|
|
@@ -4864,19 +4963,11 @@ function integrationSnippet(framework) {
|
|
|
4864
4963
|
].join("\n");
|
|
4865
4964
|
}
|
|
4866
4965
|
|
|
4867
|
-
// src/
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
const currency = CURRENCY_PARAMETER.exec(definition)?.[1] ?? null;
|
|
4873
|
-
if (!amount && !currency) return null;
|
|
4874
|
-
return {
|
|
4875
|
-
amountParameter: amount,
|
|
4876
|
-
amountUnit: amount ? "MINOR" : null,
|
|
4877
|
-
currencyParameter: currency
|
|
4878
|
-
};
|
|
4879
|
-
}
|
|
4966
|
+
// src/search.ts
|
|
4967
|
+
import { execFileSync } from "node:child_process";
|
|
4968
|
+
import { mkdtempSync, readFileSync as readFileSync6, readdirSync as readdirSync3, rmSync as rmSync3, statSync as statSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
4969
|
+
import { tmpdir } from "node:os";
|
|
4970
|
+
import path7 from "node:path";
|
|
4880
4971
|
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
4881
4972
|
".ts",
|
|
4882
4973
|
".tsx",
|
|
@@ -4912,8 +5003,247 @@ var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
|
4912
5003
|
"tests",
|
|
4913
5004
|
"test"
|
|
4914
5005
|
]);
|
|
4915
|
-
var
|
|
4916
|
-
var
|
|
5006
|
+
var SKIP_FILES = /\.(test|spec)\.[cm]?[jt]sx?$|^test_.*\.py$|_test\.py$|\.d\.ts$|\.min\.[cm]?js$/;
|
|
5007
|
+
var DEFAULT_BUDGET_MS = 6e4;
|
|
5008
|
+
var MAX_PARSE_BYTES = 4 * 1024 * 1024;
|
|
5009
|
+
function indexFiles(root, options = {}) {
|
|
5010
|
+
const deadline2 = Date.now() + (options.budgetMs ?? DEFAULT_BUDGET_MS);
|
|
5011
|
+
const fromGit = gitFiles(root, deadline2);
|
|
5012
|
+
if (fromGit) {
|
|
5013
|
+
return {
|
|
5014
|
+
files: fromGit.tracked.filter(isCandidate),
|
|
5015
|
+
method: "git",
|
|
5016
|
+
truncated: false,
|
|
5017
|
+
unsearchable: fromGit.unsearchable.filter(isCandidate)
|
|
5018
|
+
};
|
|
5019
|
+
}
|
|
5020
|
+
const files = [];
|
|
5021
|
+
const truncated = walk(root, root, files, deadline2);
|
|
5022
|
+
return { files, method: "walk", truncated, unsearchable: [] };
|
|
5023
|
+
}
|
|
5024
|
+
function gitFiles(root, deadline2) {
|
|
5025
|
+
const output = git(
|
|
5026
|
+
root,
|
|
5027
|
+
["ls-files", "-z", "--cached", "--others", "--exclude-standard"],
|
|
5028
|
+
deadline2
|
|
5029
|
+
);
|
|
5030
|
+
if (output === null) return null;
|
|
5031
|
+
const tracked = new Set(output.split("\0").filter((entry) => entry !== ""));
|
|
5032
|
+
const unsearchable = /* @__PURE__ */ new Set();
|
|
5033
|
+
const submodules = git(root, ["ls-files", "-z", "--cached", "--recurse-submodules"], deadline2);
|
|
5034
|
+
if (submodules !== null) {
|
|
5035
|
+
for (const entry of submodules.split("\0")) {
|
|
5036
|
+
if (entry !== "" && !tracked.has(entry)) unsearchable.add(entry);
|
|
5037
|
+
}
|
|
5038
|
+
}
|
|
5039
|
+
for (const file of ignoredSources(root, deadline2)) {
|
|
5040
|
+
if (!tracked.has(file)) unsearchable.add(file);
|
|
5041
|
+
}
|
|
5042
|
+
return { tracked: [...tracked, ...unsearchable], unsearchable: [...unsearchable] };
|
|
5043
|
+
}
|
|
5044
|
+
function ignoredSources(root, deadline2) {
|
|
5045
|
+
const output = git(
|
|
5046
|
+
root,
|
|
5047
|
+
["ls-files", "-z", "--others", "--ignored", "--exclude-standard", "--directory"],
|
|
5048
|
+
deadline2
|
|
5049
|
+
);
|
|
5050
|
+
if (output === null) return [];
|
|
5051
|
+
const files = [];
|
|
5052
|
+
for (const entry of output.split("\0")) {
|
|
5053
|
+
if (entry === "") continue;
|
|
5054
|
+
if (entry.endsWith("/")) {
|
|
5055
|
+
const name = entry.replace(/\/$/, "").split("/").pop() ?? "";
|
|
5056
|
+
if (SKIP_DIRECTORIES.has(name) || name.startsWith(".")) continue;
|
|
5057
|
+
const nested = [];
|
|
5058
|
+
walk(root, path7.join(root, entry), nested, deadline2);
|
|
5059
|
+
files.push(...nested);
|
|
5060
|
+
continue;
|
|
5061
|
+
}
|
|
5062
|
+
if (isCandidate(entry)) files.push(entry);
|
|
5063
|
+
}
|
|
5064
|
+
return files;
|
|
5065
|
+
}
|
|
5066
|
+
function git(root, args, deadline2) {
|
|
5067
|
+
const timeout = deadline2 - Date.now();
|
|
5068
|
+
if (timeout <= 0) return null;
|
|
5069
|
+
try {
|
|
5070
|
+
return execFileSync("git", ["-c", "core.quotepath=off", ...args], {
|
|
5071
|
+
cwd: root,
|
|
5072
|
+
encoding: "utf8",
|
|
5073
|
+
timeout,
|
|
5074
|
+
maxBuffer: 512 * 1024 * 1024,
|
|
5075
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
5076
|
+
});
|
|
5077
|
+
} catch {
|
|
5078
|
+
return null;
|
|
5079
|
+
}
|
|
5080
|
+
}
|
|
5081
|
+
function isCandidate(file) {
|
|
5082
|
+
if (!SOURCE_EXTENSIONS.has(path7.extname(file))) return false;
|
|
5083
|
+
const segments = file.split(/[/\\]/);
|
|
5084
|
+
const name = segments[segments.length - 1] ?? "";
|
|
5085
|
+
if (SKIP_FILES.test(name)) return false;
|
|
5086
|
+
return !segments.slice(0, -1).some((segment) => SKIP_DIRECTORIES.has(segment) || segment.startsWith("."));
|
|
5087
|
+
}
|
|
5088
|
+
function walk(root, dir, out2, deadline2) {
|
|
5089
|
+
let entries;
|
|
5090
|
+
try {
|
|
5091
|
+
entries = readdirSyncSafe(dir);
|
|
5092
|
+
} catch {
|
|
5093
|
+
return false;
|
|
5094
|
+
}
|
|
5095
|
+
for (const entry of entries) {
|
|
5096
|
+
if (Date.now() > deadline2) return true;
|
|
5097
|
+
if (entry.startsWith(".")) continue;
|
|
5098
|
+
const full = path7.join(dir, entry);
|
|
5099
|
+
let directory;
|
|
5100
|
+
try {
|
|
5101
|
+
directory = statSync4(full).isDirectory();
|
|
5102
|
+
} catch {
|
|
5103
|
+
continue;
|
|
5104
|
+
}
|
|
5105
|
+
if (directory) {
|
|
5106
|
+
if (SKIP_DIRECTORIES.has(entry)) continue;
|
|
5107
|
+
if (walk(root, full, out2, deadline2)) return true;
|
|
5108
|
+
continue;
|
|
5109
|
+
}
|
|
5110
|
+
const relative = path7.relative(root, full);
|
|
5111
|
+
if (isCandidate(relative)) out2.push(relative);
|
|
5112
|
+
}
|
|
5113
|
+
return false;
|
|
5114
|
+
}
|
|
5115
|
+
function readdirSyncSafe(dir) {
|
|
5116
|
+
return readdirSync3(dir).sort();
|
|
5117
|
+
}
|
|
5118
|
+
function shortlist(root, index, anchors, options = {}) {
|
|
5119
|
+
const considered = index.files.length;
|
|
5120
|
+
if (anchors.length === 0 || considered === 0) {
|
|
5121
|
+
return { files: [], method: "in-process", considered, truncated: false };
|
|
5122
|
+
}
|
|
5123
|
+
const deadline2 = Date.now() + (options.budgetMs ?? DEFAULT_BUDGET_MS);
|
|
5124
|
+
const allowed = new Set(index.files);
|
|
5125
|
+
const reached = index.unsearchable.filter((file) => allowed.has(file));
|
|
5126
|
+
const extra = reached.length > 0 ? inProcessSearch(root, reached, anchors, deadline2, reached.length) : null;
|
|
5127
|
+
const combine = (files, method) => ({
|
|
5128
|
+
files: [.../* @__PURE__ */ new Set([...files, ...extra?.files ?? []])],
|
|
5129
|
+
method,
|
|
5130
|
+
considered,
|
|
5131
|
+
truncated: extra?.truncated ?? false
|
|
5132
|
+
});
|
|
5133
|
+
if (index.method === "git") {
|
|
5134
|
+
const matched = searchWith(
|
|
5135
|
+
"git",
|
|
5136
|
+
root,
|
|
5137
|
+
["-c", "core.quotepath=off", "grep", "--no-color", "-l", "-I", "-z", "-F", "--untracked"],
|
|
5138
|
+
anchors,
|
|
5139
|
+
deadline2
|
|
5140
|
+
);
|
|
5141
|
+
if (matched) {
|
|
5142
|
+
return combine(
|
|
5143
|
+
matched.filter((file) => allowed.has(file)),
|
|
5144
|
+
"git-grep"
|
|
5145
|
+
);
|
|
5146
|
+
}
|
|
5147
|
+
}
|
|
5148
|
+
const ripgrep = searchWith(
|
|
5149
|
+
"rg",
|
|
5150
|
+
root,
|
|
5151
|
+
["--files-with-matches", "--fixed-strings", "--null", "--no-messages"],
|
|
5152
|
+
anchors,
|
|
5153
|
+
deadline2
|
|
5154
|
+
);
|
|
5155
|
+
if (ripgrep) {
|
|
5156
|
+
return combine(
|
|
5157
|
+
ripgrep.map(normalise).filter((file) => allowed.has(file)),
|
|
5158
|
+
"ripgrep"
|
|
5159
|
+
);
|
|
5160
|
+
}
|
|
5161
|
+
return inProcessSearch(root, index.files, anchors, deadline2, considered);
|
|
5162
|
+
}
|
|
5163
|
+
function searchWith(command, root, args, anchors, deadline2) {
|
|
5164
|
+
const timeout = deadline2 - Date.now();
|
|
5165
|
+
if (timeout <= 0) return null;
|
|
5166
|
+
let directory = null;
|
|
5167
|
+
try {
|
|
5168
|
+
directory = mkdtempSync(path7.join(tmpdir(), "veefore-search-"));
|
|
5169
|
+
const patterns = path7.join(directory, "anchors.txt");
|
|
5170
|
+
writeFileSync4(patterns, `${anchors.join("\n")}
|
|
5171
|
+
`);
|
|
5172
|
+
const output = execFileSync(command, [...args, "-f", patterns], {
|
|
5173
|
+
cwd: root,
|
|
5174
|
+
encoding: "utf8",
|
|
5175
|
+
timeout,
|
|
5176
|
+
maxBuffer: 512 * 1024 * 1024,
|
|
5177
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
5178
|
+
});
|
|
5179
|
+
return output.split("\0").filter((entry) => entry !== "");
|
|
5180
|
+
} catch (error) {
|
|
5181
|
+
const status = error.status;
|
|
5182
|
+
if (status === 1) return [];
|
|
5183
|
+
return null;
|
|
5184
|
+
} finally {
|
|
5185
|
+
if (directory) rmSync3(directory, { recursive: true, force: true });
|
|
5186
|
+
}
|
|
5187
|
+
}
|
|
5188
|
+
function inProcessSearch(root, files, anchors, deadline2, considered) {
|
|
5189
|
+
const needles = anchors.map((anchor) => Buffer.from(anchor, "utf8"));
|
|
5190
|
+
const matched = [];
|
|
5191
|
+
let truncated = false;
|
|
5192
|
+
for (const [position, file] of files.entries()) {
|
|
5193
|
+
if ((position & 63) === 0 && Date.now() > deadline2) {
|
|
5194
|
+
truncated = true;
|
|
5195
|
+
break;
|
|
5196
|
+
}
|
|
5197
|
+
let buffer;
|
|
5198
|
+
try {
|
|
5199
|
+
buffer = readFileSync6(path7.join(root, file));
|
|
5200
|
+
} catch {
|
|
5201
|
+
continue;
|
|
5202
|
+
}
|
|
5203
|
+
const nul = buffer.indexOf(0);
|
|
5204
|
+
if (nul >= 0 && nul < 8192) continue;
|
|
5205
|
+
if (needles.some((needle) => buffer.includes(needle))) matched.push(file);
|
|
5206
|
+
}
|
|
5207
|
+
return { files: matched, method: "in-process", considered, truncated };
|
|
5208
|
+
}
|
|
5209
|
+
function normalise(file) {
|
|
5210
|
+
return file.startsWith(`.${path7.sep}`) || file.startsWith("./") ? file.slice(2) : file;
|
|
5211
|
+
}
|
|
5212
|
+
function tooBigToParse(text) {
|
|
5213
|
+
if (text.length > MAX_PARSE_BYTES) return true;
|
|
5214
|
+
if (text.length < 128 * 1024) return false;
|
|
5215
|
+
const head = text.slice(0, 128 * 1024);
|
|
5216
|
+
const lines = head.split("\n").length;
|
|
5217
|
+
return head.length / lines > 500;
|
|
5218
|
+
}
|
|
5219
|
+
function readFiles(root, files) {
|
|
5220
|
+
const out2 = [];
|
|
5221
|
+
for (const file of files) {
|
|
5222
|
+
let text;
|
|
5223
|
+
try {
|
|
5224
|
+
text = readFileSync6(path7.join(root, file), "utf8");
|
|
5225
|
+
} catch {
|
|
5226
|
+
continue;
|
|
5227
|
+
}
|
|
5228
|
+
if (tooBigToParse(text)) continue;
|
|
5229
|
+
out2.push({ file, text });
|
|
5230
|
+
}
|
|
5231
|
+
return out2;
|
|
5232
|
+
}
|
|
5233
|
+
|
|
5234
|
+
// src/scan.ts
|
|
5235
|
+
var MINOR_AMOUNT_PARAMETER = /["']?\b(amount_minor|amountMinor|amount_cents|amountCents|amount_in_cents|amountInCents|amount_minor_units|amountMinorUnits)\b["']?\s*[:=(]/;
|
|
5236
|
+
var CURRENCY_PARAMETER = /["']?\b(currency_code|currencyCode|currency)\b["']?\s*[:=(]/;
|
|
5237
|
+
function inferMoneyMapping(definition) {
|
|
5238
|
+
const amount = MINOR_AMOUNT_PARAMETER.exec(definition)?.[1] ?? null;
|
|
5239
|
+
const currency = CURRENCY_PARAMETER.exec(definition)?.[1] ?? null;
|
|
5240
|
+
if (!amount && !currency) return null;
|
|
5241
|
+
return {
|
|
5242
|
+
amountParameter: amount,
|
|
5243
|
+
amountUnit: amount ? "MINOR" : null,
|
|
5244
|
+
currencyParameter: currency
|
|
5245
|
+
};
|
|
5246
|
+
}
|
|
4917
5247
|
var MODEL_HOSTS = [
|
|
4918
5248
|
"api.openai.com",
|
|
4919
5249
|
"api.anthropic.com",
|
|
@@ -4930,24 +5260,74 @@ var MODEL_SECRET = /^(OPENAI|ANTHROPIC|GOOGLE|GEMINI|AZURE_OPENAI|MISTRAL|GROQ|C
|
|
|
4930
5260
|
var SECRET_NAME = /(KEY|TOKEN|SECRET|PASSWORD|PASSWD|PRIVATE|CREDENTIAL|AUTH|DSN)(_|$)|^DATABASE_URL$|_CONNECTION_STRING$/;
|
|
4931
5261
|
var NOT_SECRET = /^(VEEFORE_|NODE_|NEXT_PUBLIC_|VITE_|PUBLIC_|NPM_)/;
|
|
4932
5262
|
function listSourceFiles(root = process.cwd()) {
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
5263
|
+
return listSources(root).files;
|
|
5264
|
+
}
|
|
5265
|
+
function listSources(root = process.cwd()) {
|
|
5266
|
+
const index = indexFiles(root);
|
|
5267
|
+
return { files: index.files, truncated: index.truncated, method: index.method };
|
|
5268
|
+
}
|
|
5269
|
+
function discoverAgents(root = process.cwd()) {
|
|
5270
|
+
const scan = scanProject(root);
|
|
5271
|
+
const index = indexFiles(root);
|
|
5272
|
+
const byDirectory = /* @__PURE__ */ new Map();
|
|
5273
|
+
for (const tool of scan.tools) {
|
|
5274
|
+
const directory = nearestManifest(root, tool.location.file);
|
|
5275
|
+
const existing = byDirectory.get(directory);
|
|
5276
|
+
const agent = existing ?? {
|
|
5277
|
+
directory,
|
|
5278
|
+
name: readProjectName(path8.join(root, directory)),
|
|
5279
|
+
files: [],
|
|
5280
|
+
tools: []
|
|
5281
|
+
};
|
|
5282
|
+
if (!agent.files.includes(tool.location.file)) agent.files.push(tool.location.file);
|
|
5283
|
+
agent.tools.push(tool);
|
|
5284
|
+
byDirectory.set(directory, agent);
|
|
5285
|
+
}
|
|
5286
|
+
const known = new Set(scan.tools.map((tool) => tool.location.file));
|
|
5287
|
+
const agentFiles = shortlist(root, index, [...AGENT_ANCHORS]);
|
|
5288
|
+
const unreadable = [];
|
|
5289
|
+
for (const { file, text } of readFiles(root, agentFiles.files)) {
|
|
5290
|
+
if (known.has(file)) continue;
|
|
5291
|
+
const python = file.endsWith(".py");
|
|
5292
|
+
if ((python ? findPythonTools(text) : findJsTools(text)).length > 0) continue;
|
|
5293
|
+
const hint = /\btools?\s*[:=]|\btool_calls\b|\btoolCalls\b|\bfunction_call\b/.exec(text);
|
|
5294
|
+
if (!hint) continue;
|
|
5295
|
+
unreadable.push({
|
|
5296
|
+
file,
|
|
5297
|
+
line: lineLocator(text)(hint.index),
|
|
5298
|
+
reason: `passes tools to a model, but none could be read from this file`
|
|
5299
|
+
});
|
|
5300
|
+
}
|
|
5301
|
+
return {
|
|
5302
|
+
agents: [...byDirectory.values()].sort((a, b) => a.directory.localeCompare(b.directory)),
|
|
5303
|
+
unreadable,
|
|
5304
|
+
filesScanned: scan.filesScanned,
|
|
5305
|
+
truncated: scan.truncated || agentFiles.truncated
|
|
5306
|
+
};
|
|
5307
|
+
}
|
|
5308
|
+
function nearestManifest(root, file) {
|
|
5309
|
+
const MANIFESTS = ["package.json", "pyproject.toml", "go.mod", "Cargo.toml", "setup.py"];
|
|
5310
|
+
let dir = path8.dirname(file);
|
|
5311
|
+
for (let depth = 0; depth < 32; depth += 1) {
|
|
5312
|
+
const absolute = path8.join(root, dir);
|
|
5313
|
+
if (MANIFESTS.some((manifest) => existsSync6(path8.join(absolute, manifest)))) {
|
|
5314
|
+
return dir === "" ? "." : dir;
|
|
5315
|
+
}
|
|
5316
|
+
const parent = path8.dirname(dir);
|
|
5317
|
+
if (parent === dir || dir === "." || dir === "") break;
|
|
5318
|
+
dir = parent;
|
|
5319
|
+
}
|
|
5320
|
+
return ".";
|
|
4936
5321
|
}
|
|
4937
5322
|
function scanProject(root = process.cwd()) {
|
|
4938
5323
|
const framework = detectFramework(root);
|
|
4939
|
-
const
|
|
4940
|
-
const
|
|
5324
|
+
const index = indexFiles(root);
|
|
5325
|
+
const candidates = shortlist(root, index, [...SCAN_ANCHORS]);
|
|
5326
|
+
const truncated = index.truncated || candidates.truncated;
|
|
4941
5327
|
const tools = [];
|
|
4942
5328
|
const hosts = /* @__PURE__ */ new Map();
|
|
4943
5329
|
const secrets = /* @__PURE__ */ new Map();
|
|
4944
|
-
for (const relative of files) {
|
|
4945
|
-
let text;
|
|
4946
|
-
try {
|
|
4947
|
-
text = readFileSync6(path7.join(root, relative), "utf8");
|
|
4948
|
-
} catch {
|
|
4949
|
-
continue;
|
|
4950
|
-
}
|
|
5330
|
+
for (const { file: relative, text } of readFiles(root, candidates.files)) {
|
|
4951
5331
|
const python = relative.endsWith(".py");
|
|
4952
5332
|
const lineOf = lineLocator(text);
|
|
4953
5333
|
const fileHosts = findHosts(text).map((hit) => ({ ...hit, line: lineOf(hit.index) }));
|
|
@@ -4963,7 +5343,7 @@ function scanProject(root = process.cwd()) {
|
|
|
4963
5343
|
found.forEach((hit, position) => {
|
|
4964
5344
|
const end = found[position + 1]?.index ?? text.length;
|
|
4965
5345
|
const start = hit.index;
|
|
4966
|
-
const inRegion = (
|
|
5346
|
+
const inRegion = (index2) => index2 >= start && index2 < end;
|
|
4967
5347
|
const ownHosts = unique(fileHosts.filter((h) => inRegion(h.index)).map((h) => h.host));
|
|
4968
5348
|
const ownSecrets = unique(
|
|
4969
5349
|
fileSecrets.filter((s) => inRegion(s.index) && classifySecret(s.name) === "service").map((s) => s.name)
|
|
@@ -4991,7 +5371,9 @@ function scanProject(root = process.cwd()) {
|
|
|
4991
5371
|
projectName: readProjectName(root),
|
|
4992
5372
|
framework,
|
|
4993
5373
|
entryPoints: findEntryPoints(root),
|
|
4994
|
-
filesScanned: files.length,
|
|
5374
|
+
filesScanned: index.files.length,
|
|
5375
|
+
filesParsed: candidates.files.length,
|
|
5376
|
+
searchMethod: candidates.method,
|
|
4995
5377
|
truncated,
|
|
4996
5378
|
tools: dedupeTools(tools),
|
|
4997
5379
|
hosts: [...hosts.entries()].map(([host, locations]) => ({ host, locations })).sort((a, b) => a.host.localeCompare(b.host)),
|
|
@@ -5065,18 +5447,8 @@ function findJsTools(text) {
|
|
|
5065
5447
|
}
|
|
5066
5448
|
}
|
|
5067
5449
|
}
|
|
5068
|
-
for (const
|
|
5069
|
-
|
|
5070
|
-
const body = text.slice(index, index + 400);
|
|
5071
|
-
const name = stringField(body, "name");
|
|
5072
|
-
if (name) {
|
|
5073
|
-
hits.push({
|
|
5074
|
-
name,
|
|
5075
|
-
source: "openai-function",
|
|
5076
|
-
index,
|
|
5077
|
-
description: stringField(body, "description")
|
|
5078
|
-
});
|
|
5079
|
-
}
|
|
5450
|
+
for (const hit of findFunctionToolSchemas(text)) {
|
|
5451
|
+
hits.push({ ...hit, source: "openai-function" });
|
|
5080
5452
|
}
|
|
5081
5453
|
addSchemaTools(hits, text, false);
|
|
5082
5454
|
if (mcp) {
|
|
@@ -5140,6 +5512,71 @@ function findToolSchemas(text, python) {
|
|
|
5140
5512
|
}
|
|
5141
5513
|
return hits;
|
|
5142
5514
|
}
|
|
5515
|
+
function findFunctionToolSchemas(text) {
|
|
5516
|
+
const hits = [];
|
|
5517
|
+
const NAME = /\bname\s*:\s*(['"`])([\w.-]{1,80})\1/;
|
|
5518
|
+
for (const match of text.matchAll(/\btype\s*:\s*(['"`])function\1/g)) {
|
|
5519
|
+
const at = match.index ?? 0;
|
|
5520
|
+
const from = at + match[0].length;
|
|
5521
|
+
const sibling = /^[^{}]{0,400}?(?<![\w$.])function\s*:\s*\{/.exec(text.slice(from));
|
|
5522
|
+
if (sibling) {
|
|
5523
|
+
const open = from + sibling[0].length - 1;
|
|
5524
|
+
const close = matchingBrace(text, open);
|
|
5525
|
+
if (close > open) {
|
|
5526
|
+
const inner = text.slice(open, close + 1);
|
|
5527
|
+
const found = NAME.exec(inner);
|
|
5528
|
+
if (found) {
|
|
5529
|
+
hits.push({
|
|
5530
|
+
name: found[2],
|
|
5531
|
+
index: open + found.index,
|
|
5532
|
+
description: stringField(inner, "description")
|
|
5533
|
+
});
|
|
5534
|
+
continue;
|
|
5535
|
+
}
|
|
5536
|
+
}
|
|
5537
|
+
}
|
|
5538
|
+
const opener = text.lastIndexOf("{", at);
|
|
5539
|
+
const windows = [
|
|
5540
|
+
[
|
|
5541
|
+
Math.max(opener + 1, at - 400),
|
|
5542
|
+
opener < 0 ? "" : text.slice(Math.max(opener + 1, at - 400), at)
|
|
5543
|
+
],
|
|
5544
|
+
[from, text.slice(from, from + 400).split(/[{}]/)[0] ?? ""]
|
|
5545
|
+
];
|
|
5546
|
+
for (const [offset, window] of windows) {
|
|
5547
|
+
const found = NAME.exec(window);
|
|
5548
|
+
if (!found) continue;
|
|
5549
|
+
hits.push({
|
|
5550
|
+
name: found[2],
|
|
5551
|
+
index: offset + found.index,
|
|
5552
|
+
description: stringField(window, "description")
|
|
5553
|
+
});
|
|
5554
|
+
break;
|
|
5555
|
+
}
|
|
5556
|
+
}
|
|
5557
|
+
return hits;
|
|
5558
|
+
}
|
|
5559
|
+
function matchingBrace(text, open) {
|
|
5560
|
+
let depth = 0;
|
|
5561
|
+
for (let index = open; index < text.length; index += 1) {
|
|
5562
|
+
const char = text[index];
|
|
5563
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
5564
|
+
const quote2 = char;
|
|
5565
|
+
index += 1;
|
|
5566
|
+
while (index < text.length && text[index] !== quote2) {
|
|
5567
|
+
if (text[index] === "\\") index += 1;
|
|
5568
|
+
index += 1;
|
|
5569
|
+
}
|
|
5570
|
+
continue;
|
|
5571
|
+
}
|
|
5572
|
+
if (char === "{") depth += 1;
|
|
5573
|
+
else if (char === "}") {
|
|
5574
|
+
depth -= 1;
|
|
5575
|
+
if (depth === 0) return index;
|
|
5576
|
+
}
|
|
5577
|
+
}
|
|
5578
|
+
return -1;
|
|
5579
|
+
}
|
|
5143
5580
|
function addSchemaTools(hits, text, python) {
|
|
5144
5581
|
const known = new Set(hits.map((hit) => toToolKey(hit.name)));
|
|
5145
5582
|
for (const hit of findToolSchemas(text, python)) {
|
|
@@ -5431,52 +5868,21 @@ function toToolKey(name) {
|
|
|
5431
5868
|
const safe = /^[a-z]/.test(key) ? key : `tool_${key}`;
|
|
5432
5869
|
return safe.slice(0, 80) || "tool";
|
|
5433
5870
|
}
|
|
5434
|
-
function walk(root, dir, out2) {
|
|
5435
|
-
let entries;
|
|
5436
|
-
try {
|
|
5437
|
-
entries = readdirSync3(dir).sort();
|
|
5438
|
-
} catch {
|
|
5439
|
-
return false;
|
|
5440
|
-
}
|
|
5441
|
-
for (const entry of entries) {
|
|
5442
|
-
if (out2.length >= MAX_FILES) return true;
|
|
5443
|
-
if (entry.startsWith(".") && entry !== ".") {
|
|
5444
|
-
continue;
|
|
5445
|
-
}
|
|
5446
|
-
const full = path7.join(dir, entry);
|
|
5447
|
-
let stats;
|
|
5448
|
-
try {
|
|
5449
|
-
stats = statSync4(full);
|
|
5450
|
-
} catch {
|
|
5451
|
-
continue;
|
|
5452
|
-
}
|
|
5453
|
-
if (stats.isDirectory()) {
|
|
5454
|
-
if (SKIP_DIRECTORIES.has(entry)) continue;
|
|
5455
|
-
if (walk(root, full, out2)) return true;
|
|
5456
|
-
continue;
|
|
5457
|
-
}
|
|
5458
|
-
if (!stats.isFile() || stats.size > MAX_FILE_BYTES) continue;
|
|
5459
|
-
if (!SOURCE_EXTENSIONS.has(path7.extname(entry))) continue;
|
|
5460
|
-
if (/\.(test|spec)\.[cm]?[jt]sx?$|^test_.*\.py$|_test\.py$|\.d\.ts$/.test(entry)) continue;
|
|
5461
|
-
out2.push(path7.relative(root, full));
|
|
5462
|
-
}
|
|
5463
|
-
return false;
|
|
5464
|
-
}
|
|
5465
5871
|
function readProjectName(root) {
|
|
5466
|
-
const pkg =
|
|
5872
|
+
const pkg = path8.join(root, "package.json");
|
|
5467
5873
|
if (existsSync6(pkg)) {
|
|
5468
5874
|
try {
|
|
5469
|
-
const name = JSON.parse(
|
|
5875
|
+
const name = JSON.parse(readFileSync7(pkg, "utf8")).name;
|
|
5470
5876
|
if (typeof name === "string" && name.trim()) return name.replace(/^@[^/]+\//, "").trim();
|
|
5471
5877
|
} catch {
|
|
5472
5878
|
}
|
|
5473
5879
|
}
|
|
5474
|
-
const pyproject =
|
|
5880
|
+
const pyproject = path8.join(root, "pyproject.toml");
|
|
5475
5881
|
if (existsSync6(pyproject)) {
|
|
5476
|
-
const name = /^\s*name\s*=\s*["']([^"']+)["']/m.exec(
|
|
5882
|
+
const name = /^\s*name\s*=\s*["']([^"']+)["']/m.exec(readFileSync7(pyproject, "utf8"))?.[1];
|
|
5477
5883
|
if (name) return name.trim();
|
|
5478
5884
|
}
|
|
5479
|
-
return
|
|
5885
|
+
return path8.basename(root) || null;
|
|
5480
5886
|
}
|
|
5481
5887
|
function stringField(body, field) {
|
|
5482
5888
|
const match = new RegExp(`\\b${field}\\s*:\\s*(['"\`])([^]{1,300}?)\\1`).exec(body);
|
|
@@ -5531,7 +5937,7 @@ function planRewrite(root, files, toolKeys) {
|
|
|
5531
5937
|
for (const file of files) {
|
|
5532
5938
|
let before;
|
|
5533
5939
|
try {
|
|
5534
|
-
before =
|
|
5940
|
+
before = readFileSync8(path9.join(root, file), "utf8");
|
|
5535
5941
|
} catch {
|
|
5536
5942
|
continue;
|
|
5537
5943
|
}
|
|
@@ -5636,27 +6042,20 @@ function rewriteJs(source, wanted) {
|
|
|
5636
6042
|
wrap3(key, match.index ?? 0, start, trimEnd(source, start, handler.end));
|
|
5637
6043
|
}
|
|
5638
6044
|
}
|
|
5639
|
-
|
|
5640
|
-
const name = /\bname\s*:\s*['"]([\w.-]{1,80})['"]/.exec(
|
|
5641
|
-
source.slice(match.index ?? 0, (match.index ?? 0) + 400)
|
|
5642
|
-
)?.[1];
|
|
5643
|
-
if (!name || !wanted.has(toToolKey(name))) continue;
|
|
5644
|
-
changes.push({
|
|
5645
|
-
key: toToolKey(name),
|
|
5646
|
-
line: lineOf(match.index ?? 0),
|
|
5647
|
-
status: "manual",
|
|
5648
|
-
reason: `this is only the schema; wrap the function your code calls for it, e.g. \`guard(${quote(toToolKey(name))}, yourFunction)\``
|
|
5649
|
-
});
|
|
5650
|
-
}
|
|
5651
|
-
changes.push(...schemaOnly(source, false, wanted));
|
|
6045
|
+
changes.push(...schemaOnly(source, false, wanted, new Set(changes.map((c) => c.key))));
|
|
5652
6046
|
const wrapped = edits.length > 0;
|
|
5653
6047
|
let after = applyEdits(source, edits);
|
|
5654
6048
|
if (wrapped) after = addJsImport(after, alias);
|
|
5655
6049
|
return { after, changes: dedupeChanges(changes) };
|
|
5656
6050
|
}
|
|
5657
6051
|
function addJsImport(source, alias) {
|
|
5658
|
-
|
|
5659
|
-
|
|
6052
|
+
return addJsSdkImport(source, "guard", alias);
|
|
6053
|
+
}
|
|
6054
|
+
function addJsSdkImport(source, name, alias) {
|
|
6055
|
+
if (existingImport(source, name, "js")) return source;
|
|
6056
|
+
const specifier = alias === name ? name : `${name} as ${alias}`;
|
|
6057
|
+
const joined = joinExistingSdkImport(source, specifier, alias, name);
|
|
6058
|
+
if (joined) return joined;
|
|
5660
6059
|
const imports = [
|
|
5661
6060
|
...source.matchAll(
|
|
5662
6061
|
/^import\s+(?:type\s+)?(?:[\s\S]*?\sfrom\s*)?['"][^'"\n]+['"]\s*;?[^\S\n]*$/gm
|
|
@@ -5669,11 +6068,33 @@ function addJsImport(source, alias) {
|
|
|
5669
6068
|
import { ${specifier} } from '@veefore/sdk';${source.slice(at)}`;
|
|
5670
6069
|
}
|
|
5671
6070
|
const cjs = /\brequire\(\s*['"][^'"]+['"]\s*\)/.test(source) && !/^\s*export\s/m.test(source);
|
|
5672
|
-
const line = cjs ? `const { ${alias ===
|
|
6071
|
+
const line = cjs ? `const { ${alias === name ? name : `${name}: ${alias}`} } = require('@veefore/sdk');` : `import { ${specifier} } from '@veefore/sdk';`;
|
|
5673
6072
|
const prologue = /^(?:#![^\n]*\n)?(?:\s*(['"])use [a-z ]+\1;?[^\S\n]*\n)*/.exec(source)?.[0] ?? "";
|
|
5674
6073
|
return `${source.slice(0, prologue.length)}${line}
|
|
5675
6074
|
${source.slice(prologue.length)}`;
|
|
5676
6075
|
}
|
|
6076
|
+
function joinExistingSdkImport(source, specifier, alias, name) {
|
|
6077
|
+
const esm = /import\s*\{([^}]*)\}\s*from\s*(['"])@veefore\/sdk\2/.exec(source);
|
|
6078
|
+
if (esm) {
|
|
6079
|
+
const names = (esm[1] ?? "").trim();
|
|
6080
|
+
const replacement = esm[0].replace(
|
|
6081
|
+
`{${esm[1] ?? ""}}`,
|
|
6082
|
+
names === "" ? `{ ${specifier} }` : `{ ${names}, ${specifier} }`
|
|
6083
|
+
);
|
|
6084
|
+
return source.replace(esm[0], replacement);
|
|
6085
|
+
}
|
|
6086
|
+
const cjs = /const\s*\{([^}]*)\}\s*=\s*require\(\s*(['"])@veefore\/sdk\2\s*\)/.exec(source);
|
|
6087
|
+
if (cjs) {
|
|
6088
|
+
const names = (cjs[1] ?? "").trim();
|
|
6089
|
+
const entry = alias === name ? name : `${name}: ${alias}`;
|
|
6090
|
+
const replacement = cjs[0].replace(
|
|
6091
|
+
`{${cjs[1] ?? ""}}`,
|
|
6092
|
+
names === "" ? `{ ${entry} }` : `{ ${names}, ${entry} }`
|
|
6093
|
+
);
|
|
6094
|
+
return source.replace(cjs[0], replacement);
|
|
6095
|
+
}
|
|
6096
|
+
return null;
|
|
6097
|
+
}
|
|
5677
6098
|
function rewritePython(source, wanted) {
|
|
5678
6099
|
const changes = [];
|
|
5679
6100
|
const lines = source.split("\n");
|
|
@@ -5716,7 +6137,7 @@ function rewritePython(source, wanted) {
|
|
|
5716
6137
|
reason: `decorate the function passed as func= with @guard(${JSON.stringify(toToolKey(name))})`
|
|
5717
6138
|
});
|
|
5718
6139
|
}
|
|
5719
|
-
changes.push(...schemaOnly(source, true, wanted));
|
|
6140
|
+
changes.push(...schemaOnly(source, true, wanted, new Set(changes.map((c) => c.key))));
|
|
5720
6141
|
if (inserts.length === 0) return { after: source, changes: dedupeChanges(changes) };
|
|
5721
6142
|
const out2 = [...lines];
|
|
5722
6143
|
for (const insert of [...inserts].sort((a, b) => b.before - a.before)) {
|
|
@@ -5926,9 +6347,63 @@ function topLevelString(text, open, close, name) {
|
|
|
5926
6347
|
const literal = /^(['"`])([\w .-]{1,80})\1$/.exec(text.slice(value.start, value.end));
|
|
5927
6348
|
return literal?.[2] ?? null;
|
|
5928
6349
|
}
|
|
5929
|
-
var DISPATCH_NAME = /^(?:execute|run|call|dispatch|handle|invoke|perform)_?(?:tool|tool_?call|function_?call)s?$/i;
|
|
6350
|
+
var DISPATCH_NAME = /^(?:execute|run|call|dispatch|handle|invoke|perform)[A-Za-z0-9_]*?_?(?:tool|tool_?call|function_?call)s?$/i;
|
|
5930
6351
|
var KEY_PROPERTIES = ["tool", "name", "toolName", "tool_name"];
|
|
5931
6352
|
var ARG_PROPERTIES = ["params", "args", "arguments", "input", "parameters"];
|
|
6353
|
+
function findRunSites(files, read, toolNames) {
|
|
6354
|
+
const sites = /* @__PURE__ */ new Map();
|
|
6355
|
+
if (toolNames.length === 0) return sites;
|
|
6356
|
+
const patterns = (name) => {
|
|
6357
|
+
const quoted = `['"\`]${escapeForRegex(name)}['"\`]`;
|
|
6358
|
+
return [
|
|
6359
|
+
// `switch (call.tool) { case 'x':` — unmistakable.
|
|
6360
|
+
new RegExp(`case\\s+${quoted}`),
|
|
6361
|
+
// `call.name === 'x'`: the key read off the call being run, which is
|
|
6362
|
+
// what a dispatch does and what a view's own state does not.
|
|
6363
|
+
new RegExp(
|
|
6364
|
+
`\\.\\s*[A-Za-z_$][\\w$]*\\s*={2,3}\\s*${quoted}|${quoted}\\s*={2,3}\\s*[A-Za-z_$][\\w$]*\\s*\\.`
|
|
6365
|
+
),
|
|
6366
|
+
// `handlers['x']`, `executors['x']`
|
|
6367
|
+
new RegExp(`\\[\\s*${quoted}\\s*\\]`),
|
|
6368
|
+
// `'x': async (args) => …`, a map of handlers by key.
|
|
6369
|
+
new RegExp(`${quoted}\\s*:\\s*(?:async\\b|function\\b|\\(|[\\w$]+\\s*=>)`),
|
|
6370
|
+
// Any other comparison against the key. `!==` is deliberately not one:
|
|
6371
|
+
// a tool is not run where its name is ruled out, and taking the first
|
|
6372
|
+
// of those sent the instruction to a view that merely checks which
|
|
6373
|
+
// tool is selected.
|
|
6374
|
+
new RegExp(`(?<![!<>=])={2,3}\\s*${quoted}|${quoted}\\s*={2,3}`)
|
|
6375
|
+
];
|
|
6376
|
+
};
|
|
6377
|
+
const best = /* @__PURE__ */ new Map();
|
|
6378
|
+
for (const file of files) {
|
|
6379
|
+
const source = read(file);
|
|
6380
|
+
if (source === null) continue;
|
|
6381
|
+
const lineOf = lineLocator2(source);
|
|
6382
|
+
for (const name of toolNames) {
|
|
6383
|
+
if (!source.includes(name)) continue;
|
|
6384
|
+
for (const [rank, pattern] of patterns(name).entries()) {
|
|
6385
|
+
const found = pattern.exec(source);
|
|
6386
|
+
if (!found) continue;
|
|
6387
|
+
const current = best.get(name);
|
|
6388
|
+
if (current && current.rank <= rank) break;
|
|
6389
|
+
const at = found.index;
|
|
6390
|
+
const start = source.lastIndexOf("\n", at) + 1;
|
|
6391
|
+
const end = source.indexOf("\n", at);
|
|
6392
|
+
best.set(name, {
|
|
6393
|
+
rank,
|
|
6394
|
+
site: {
|
|
6395
|
+
file,
|
|
6396
|
+
line: lineOf(at),
|
|
6397
|
+
code: source.slice(start, end < 0 ? source.length : end).trim().slice(0, 160)
|
|
6398
|
+
}
|
|
6399
|
+
});
|
|
6400
|
+
break;
|
|
6401
|
+
}
|
|
6402
|
+
}
|
|
6403
|
+
}
|
|
6404
|
+
for (const [name, entry] of best) sites.set(name, entry.site);
|
|
6405
|
+
return sites;
|
|
6406
|
+
}
|
|
5932
6407
|
function planDispatch(files, read, toolNames) {
|
|
5933
6408
|
let best = null;
|
|
5934
6409
|
for (const file of files) {
|
|
@@ -5950,27 +6425,36 @@ function planDispatch(files, read, toolNames) {
|
|
|
5950
6425
|
};
|
|
5951
6426
|
}
|
|
5952
6427
|
}
|
|
5953
|
-
|
|
5954
|
-
best = { file, line: lineOf(found.index), score: Number.MAX_SAFE_INTEGER, fn: found.name };
|
|
5955
|
-
}
|
|
6428
|
+
best ??= { file, line: lineOf(found.index), fn: found.name };
|
|
5956
6429
|
}
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
)
|
|
5962
|
-
|
|
5963
|
-
const first = toolNames.map((name) => source.search(new RegExp(`case\\s+['"\`]${escapeForRegex(name)}['"\`]`))).filter((index) => index >= 0).sort((a, b) => a - b)[0];
|
|
5964
|
-
best = { file, line: first === void 0 ? 1 : lineOf(first), score: named, fn: null };
|
|
6430
|
+
}
|
|
6431
|
+
const sites = findRunSites(files, read, toolNames);
|
|
6432
|
+
if (!best) {
|
|
6433
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
6434
|
+
for (const site of sites.values()) {
|
|
6435
|
+
byFile.set(site.file, [...byFile.get(site.file) ?? [], site]);
|
|
5965
6436
|
}
|
|
6437
|
+
const chosen = [...byFile.entries()].sort(
|
|
6438
|
+
(a, b) => b[1].length - a[1].length || a[0].localeCompare(b[0])
|
|
6439
|
+
)[0];
|
|
6440
|
+
if (!chosen) return null;
|
|
6441
|
+
const line = Math.min(...chosen[1].map((site) => site.line));
|
|
6442
|
+
return {
|
|
6443
|
+
file: chosen[0],
|
|
6444
|
+
line,
|
|
6445
|
+
functionName: null,
|
|
6446
|
+
after: null,
|
|
6447
|
+
reason: "this file runs the tools by name",
|
|
6448
|
+
sites
|
|
6449
|
+
};
|
|
5966
6450
|
}
|
|
5967
|
-
if (!best) return null;
|
|
5968
6451
|
return {
|
|
5969
6452
|
file: best.file,
|
|
5970
6453
|
line: best.line,
|
|
5971
6454
|
functionName: best.fn,
|
|
5972
6455
|
after: null,
|
|
5973
|
-
reason:
|
|
6456
|
+
reason: `${best.fn}() runs the tools, but where to put the guard inside it is not certain`,
|
|
6457
|
+
sites
|
|
5974
6458
|
};
|
|
5975
6459
|
}
|
|
5976
6460
|
function dispatchFunctions(source, python) {
|
|
@@ -6043,13 +6527,220 @@ function guardDispatch(source, fn, typescript) {
|
|
|
6043
6527
|
);
|
|
6044
6528
|
return { after, at: target.start };
|
|
6045
6529
|
}
|
|
6530
|
+
var CHECKPOINT_ARG_PROPERTIES = ["args", "arguments", "params", "parameters", "input", "payload"];
|
|
6531
|
+
function planCheckpoints(files, read, toolKeys) {
|
|
6532
|
+
const plans = [];
|
|
6533
|
+
const outstanding = new Set(toolKeys);
|
|
6534
|
+
if (outstanding.size === 0) return plans;
|
|
6535
|
+
for (const file of files) {
|
|
6536
|
+
if (outstanding.size === 0) break;
|
|
6537
|
+
if (file.endsWith(".py") || !JS_EXTENSIONS.test(file)) continue;
|
|
6538
|
+
const source = read(file);
|
|
6539
|
+
if (source === null) continue;
|
|
6540
|
+
const present = [...outstanding].filter(
|
|
6541
|
+
(key) => source.includes(key) || source.includes(camelOf(key))
|
|
6542
|
+
);
|
|
6543
|
+
if (present.length === 0) continue;
|
|
6544
|
+
const scopes = functionScopes(source);
|
|
6545
|
+
const lineOf = lineLocator2(source);
|
|
6546
|
+
const alias = chooseAliasFor(source, "checkpoint", "js");
|
|
6547
|
+
const edits = [];
|
|
6548
|
+
const placed = [];
|
|
6549
|
+
for (const key of present) {
|
|
6550
|
+
const site = checkpointSite(source, key, scopes);
|
|
6551
|
+
if (!site) continue;
|
|
6552
|
+
const indent = blockIndent(source, site.at);
|
|
6553
|
+
const args = site.argsExpression ? `, ${site.argsExpression}` : "";
|
|
6554
|
+
edits.push({
|
|
6555
|
+
start: site.at,
|
|
6556
|
+
end: site.at,
|
|
6557
|
+
text: `
|
|
6558
|
+
${indent}await ${alias}(${quote(key)}${args});`
|
|
6559
|
+
});
|
|
6560
|
+
placed.push({ key, line: lineOf(site.at), shape: site.shape });
|
|
6561
|
+
outstanding.delete(key);
|
|
6562
|
+
}
|
|
6563
|
+
if (edits.length === 0) continue;
|
|
6564
|
+
const after = addJsSdkImport(applyEdits(source, edits), "checkpoint", alias);
|
|
6565
|
+
plans.push({ file, before: source, after, placed });
|
|
6566
|
+
}
|
|
6567
|
+
return plans;
|
|
6568
|
+
}
|
|
6569
|
+
function checkpointSite(source, key, scopes) {
|
|
6570
|
+
const quoted = `['"\`]${escapeForRegex(key)}['"\`]`;
|
|
6571
|
+
const usable = (at) => enclosingAsync(scopes, at) === true;
|
|
6572
|
+
for (const match of source.matchAll(new RegExp(`\\bcase\\s+${quoted}\\s*:`, "g"))) {
|
|
6573
|
+
const colon = (match.index ?? 0) + match[0].length;
|
|
6574
|
+
const braced = /^\s*\{/.exec(source.slice(colon));
|
|
6575
|
+
const at = braced ? colon + braced[0].length : colon;
|
|
6576
|
+
if (!usable(at)) continue;
|
|
6577
|
+
return { at, shape: "case", argsExpression: switchArguments(source, match.index ?? 0) };
|
|
6578
|
+
}
|
|
6579
|
+
const comparison = new RegExp(
|
|
6580
|
+
`(?:if|else\\s+if)\\s*\\(([^()]{0,200}?)${quoted}([^()]{0,200}?)\\)\\s*\\{`,
|
|
6581
|
+
"g"
|
|
6582
|
+
);
|
|
6583
|
+
for (const match of source.matchAll(comparison)) {
|
|
6584
|
+
const left = match[1] ?? "";
|
|
6585
|
+
if (!/(?<![!<>])={2,3}\s*$/.test(left)) continue;
|
|
6586
|
+
const at = (match.index ?? 0) + match[0].length;
|
|
6587
|
+
if (!usable(at)) continue;
|
|
6588
|
+
const object = /([A-Za-z_$][\w$]*)\s*(?:\?\.|\.)\s*[A-Za-z_$][\w$]*\s*={2,3}\s*$/.exec(left);
|
|
6589
|
+
return {
|
|
6590
|
+
at,
|
|
6591
|
+
shape: "branch",
|
|
6592
|
+
argsExpression: object ? argumentsOf(source, object[1]) : null
|
|
6593
|
+
};
|
|
6594
|
+
}
|
|
6595
|
+
const declaration = new RegExp(
|
|
6596
|
+
`\\b(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)\\s*(?::[^=\\n]{0,120})?=[^;\\n]{0,400}?${quoted}[^;]{0,200}?;`,
|
|
6597
|
+
"g"
|
|
6598
|
+
);
|
|
6599
|
+
for (const match of source.matchAll(declaration)) {
|
|
6600
|
+
const name = match[1];
|
|
6601
|
+
const end = (match.index ?? 0) + match[0].length;
|
|
6602
|
+
const head = new RegExp(`^\\s*if\\s*\\(\\s*${escapeForRegex(name)}\\b`).exec(
|
|
6603
|
+
source.slice(end, end + 200)
|
|
6604
|
+
);
|
|
6605
|
+
if (!head) continue;
|
|
6606
|
+
const parenOpen = end + head[0].lastIndexOf("(", head[0].length - name.length);
|
|
6607
|
+
const parenClose = matchBracket(source, parenOpen);
|
|
6608
|
+
if (parenClose < 0) continue;
|
|
6609
|
+
const brace = /^\s*\{/.exec(source.slice(parenClose + 1));
|
|
6610
|
+
if (!brace) continue;
|
|
6611
|
+
const at = parenClose + 1 + brace[0].length;
|
|
6612
|
+
if (!usable(at)) continue;
|
|
6613
|
+
return { at, shape: "found", argsExpression: argumentsOf(source, name) };
|
|
6614
|
+
}
|
|
6615
|
+
const handler = new RegExp(
|
|
6616
|
+
`${quoted}\\s*:\\s*(?:async\\s*)?\\(([^)]{0,200})\\)\\s*(?::[^=>{]{0,120})?=>\\s*\\{`,
|
|
6617
|
+
"g"
|
|
6618
|
+
);
|
|
6619
|
+
for (const match of source.matchAll(handler)) {
|
|
6620
|
+
const at = (match.index ?? 0) + match[0].length;
|
|
6621
|
+
if (!usable(at)) continue;
|
|
6622
|
+
return { at, shape: "handler", argsExpression: parameterExpression(match[1] ?? "") };
|
|
6623
|
+
}
|
|
6624
|
+
const camel = camelOf(key);
|
|
6625
|
+
const named = new RegExp(
|
|
6626
|
+
`\\basync\\s+function\\s+(?:${escapeForRegex(camel)}|${escapeForRegex(key)})\\s*\\(`,
|
|
6627
|
+
"g"
|
|
6628
|
+
);
|
|
6629
|
+
for (const match of source.matchAll(named)) {
|
|
6630
|
+
const parenOpen = (match.index ?? 0) + match[0].length - 1;
|
|
6631
|
+
const parenClose = matchBracket(source, parenOpen);
|
|
6632
|
+
if (parenClose < 0) continue;
|
|
6633
|
+
const bodyOpen = source.indexOf("{", parenClose);
|
|
6634
|
+
if (bodyOpen < 0) continue;
|
|
6635
|
+
if (!/^\s*(?::[^{;]*)?$/.test(source.slice(parenClose + 1, bodyOpen))) continue;
|
|
6636
|
+
return {
|
|
6637
|
+
at: bodyOpen + 1,
|
|
6638
|
+
shape: "function",
|
|
6639
|
+
argsExpression: parameterExpression(source.slice(parenOpen + 1, parenClose))
|
|
6640
|
+
};
|
|
6641
|
+
}
|
|
6642
|
+
return null;
|
|
6643
|
+
}
|
|
6644
|
+
function parameterExpression(parameters) {
|
|
6645
|
+
const names = parameters.split(",").map((part) => /^\s*(?:\.\.\.)?([A-Za-z_$][\w$]*)/.exec(part)?.[1]).filter((name) => name !== void 0);
|
|
6646
|
+
const named = names.find((name) => CHECKPOINT_ARG_PROPERTIES.includes(name));
|
|
6647
|
+
if (named) return named;
|
|
6648
|
+
return names.length === 1 ? names[0] : null;
|
|
6649
|
+
}
|
|
6650
|
+
function camelOf(key) {
|
|
6651
|
+
return key.replace(/_([a-z0-9])/g, (_match, char) => char.toUpperCase());
|
|
6652
|
+
}
|
|
6653
|
+
function switchArguments(source, at) {
|
|
6654
|
+
const before = source.slice(Math.max(0, at - 4e3), at);
|
|
6655
|
+
const found = [
|
|
6656
|
+
...before.matchAll(/\bswitch\s*\(\s*([A-Za-z_$][\w$]*)\s*(?:\?\.|\.)\s*[A-Za-z_$][\w$]*/g)
|
|
6657
|
+
];
|
|
6658
|
+
const last = found[found.length - 1];
|
|
6659
|
+
return last ? argumentsOf(source, last[1]) : null;
|
|
6660
|
+
}
|
|
6661
|
+
function argumentsOf(source, object) {
|
|
6662
|
+
for (const property of CHECKPOINT_ARG_PROPERTIES) {
|
|
6663
|
+
const pattern = new RegExp(`\\b${escapeForRegex(object)}\\s*(?:\\?\\.|\\.)\\s*${property}\\b`);
|
|
6664
|
+
if (pattern.test(source)) return `${object}.${property}`;
|
|
6665
|
+
}
|
|
6666
|
+
return null;
|
|
6667
|
+
}
|
|
6668
|
+
function blockIndent(source, at) {
|
|
6669
|
+
const lineEnd = source.indexOf("\n", at);
|
|
6670
|
+
if (lineEnd >= 0) {
|
|
6671
|
+
const next = /^([ \t]*)\S/.exec(source.slice(lineEnd + 1, lineEnd + 200));
|
|
6672
|
+
if (next && !source.slice(lineEnd + 1).trimStart().startsWith("}"))
|
|
6673
|
+
return next[1];
|
|
6674
|
+
}
|
|
6675
|
+
const lineStart = source.lastIndexOf("\n", at) + 1;
|
|
6676
|
+
return `${/^[ \t]*/.exec(source.slice(lineStart, at))?.[0] ?? ""} `;
|
|
6677
|
+
}
|
|
6678
|
+
function functionScopes(source) {
|
|
6679
|
+
const scopes = [];
|
|
6680
|
+
const add = (isAsync, parenOpen, arrow) => {
|
|
6681
|
+
const parenClose = matchBracket(source, parenOpen);
|
|
6682
|
+
if (parenClose < 0) return;
|
|
6683
|
+
const bodyOpen = source.indexOf("{", parenClose);
|
|
6684
|
+
if (bodyOpen < 0) return;
|
|
6685
|
+
const between = source.slice(parenClose + 1, bodyOpen);
|
|
6686
|
+
if (arrow !== /=>/.test(between)) return;
|
|
6687
|
+
if (!/^\s*(?::[^{;=]*)?(?:=>\s*)?$/.test(between)) return;
|
|
6688
|
+
const bodyClose = matchBracket(source, bodyOpen);
|
|
6689
|
+
if (bodyClose < 0) return;
|
|
6690
|
+
scopes.push({ bodyOpen, bodyClose, async: isAsync });
|
|
6691
|
+
};
|
|
6692
|
+
for (const match of source.matchAll(/\b(async\s+)?function\s*\*?\s*[A-Za-z_$][\w$]*\s*\(/g)) {
|
|
6693
|
+
add(match[1] !== void 0, (match.index ?? 0) + match[0].length - 1, false);
|
|
6694
|
+
}
|
|
6695
|
+
for (const match of source.matchAll(/(\basync\s*)?\(/g)) {
|
|
6696
|
+
add(match[1] !== void 0, (match.index ?? 0) + match[0].length - 1, true);
|
|
6697
|
+
}
|
|
6698
|
+
const method = /^[ \t]*((?:(?:public|private|protected|static|override|async|get|set)\s+)*)\*?\s*([A-Za-z_$][\w$]*)\s*\(/gm;
|
|
6699
|
+
for (const match of source.matchAll(method)) {
|
|
6700
|
+
if (CONTROL_KEYWORDS.has(match[2])) continue;
|
|
6701
|
+
add(/\basync\b/.test(match[1] ?? ""), (match.index ?? 0) + match[0].length - 1, false);
|
|
6702
|
+
}
|
|
6703
|
+
for (const match of source.matchAll(/\b(async\s+)?([A-Za-z_$][\w$]*)\s*=>\s*\{/g)) {
|
|
6704
|
+
const bodyOpen = (match.index ?? 0) + match[0].length - 1;
|
|
6705
|
+
const bodyClose = matchBracket(source, bodyOpen);
|
|
6706
|
+
if (bodyClose > 0) scopes.push({ bodyOpen, bodyClose, async: match[1] !== void 0 });
|
|
6707
|
+
}
|
|
6708
|
+
return scopes;
|
|
6709
|
+
}
|
|
6710
|
+
var CONTROL_KEYWORDS = /* @__PURE__ */ new Set([
|
|
6711
|
+
"if",
|
|
6712
|
+
"else",
|
|
6713
|
+
"for",
|
|
6714
|
+
"while",
|
|
6715
|
+
"do",
|
|
6716
|
+
"switch",
|
|
6717
|
+
"catch",
|
|
6718
|
+
"with",
|
|
6719
|
+
"return",
|
|
6720
|
+
"typeof",
|
|
6721
|
+
"new",
|
|
6722
|
+
"await",
|
|
6723
|
+
"function",
|
|
6724
|
+
"throw",
|
|
6725
|
+
"case"
|
|
6726
|
+
]);
|
|
6727
|
+
function enclosingAsync(scopes, at) {
|
|
6728
|
+
let best = null;
|
|
6729
|
+
for (const scope of scopes) {
|
|
6730
|
+
if (at <= scope.bodyOpen || at > scope.bodyClose) continue;
|
|
6731
|
+
if (!best || scope.bodyOpen > best.bodyOpen) best = scope;
|
|
6732
|
+
}
|
|
6733
|
+
return best ? best.async : null;
|
|
6734
|
+
}
|
|
6046
6735
|
function unguardJs(source) {
|
|
6047
|
-
const
|
|
6048
|
-
|
|
6736
|
+
const checkpoints = uncheckpointJs(source);
|
|
6737
|
+
const imported = jsGuardImport(checkpoints.after);
|
|
6738
|
+
if (!imported) return checkpoints;
|
|
6739
|
+
source = checkpoints.after;
|
|
6049
6740
|
const { alias } = imported;
|
|
6050
6741
|
const lineOf = lineLocator2(source);
|
|
6051
6742
|
const edits = [];
|
|
6052
|
-
const leftovers = [];
|
|
6743
|
+
const leftovers = [...checkpoints.leftovers];
|
|
6053
6744
|
const calls = new RegExp(`(?<![\\w$.])${escapeForRegex(alias)}\\s*\\(`, "g");
|
|
6054
6745
|
for (const match of source.matchAll(calls)) {
|
|
6055
6746
|
const start = match.index ?? 0;
|
|
@@ -6088,8 +6779,35 @@ function unguardJs(source) {
|
|
|
6088
6779
|
}
|
|
6089
6780
|
let after = applyEdits(source, edits);
|
|
6090
6781
|
const stillUsed = new RegExp(`(?<![\\w$.])${escapeForRegex(alias)}\\s*\\(`).test(after);
|
|
6091
|
-
if (!stillUsed) after =
|
|
6092
|
-
return { after, removed: edits.length, leftovers };
|
|
6782
|
+
if (!stillUsed) after = removeJsSdkImport(after, "guard");
|
|
6783
|
+
return { after, removed: edits.length + checkpoints.removed, leftovers };
|
|
6784
|
+
}
|
|
6785
|
+
function uncheckpointJs(source) {
|
|
6786
|
+
const alias = existingImport(source, "checkpoint", "js");
|
|
6787
|
+
if (!alias) return { after: source, removed: 0, leftovers: [] };
|
|
6788
|
+
const lineOf = lineLocator2(source);
|
|
6789
|
+
const leftovers = [];
|
|
6790
|
+
let removed = 0;
|
|
6791
|
+
let after = source.replace(
|
|
6792
|
+
new RegExp(
|
|
6793
|
+
`^[ \\t]*(?:await[ \\t]+)?${escapeForRegex(alias)}\\(\\s*(['"\`])[a-z][a-z0-9_]*\\1[^\\n]*\\)[ \\t]*;?[ \\t]*\\r?\\n`,
|
|
6794
|
+
"gm"
|
|
6795
|
+
),
|
|
6796
|
+
() => {
|
|
6797
|
+
removed += 1;
|
|
6798
|
+
return "";
|
|
6799
|
+
}
|
|
6800
|
+
);
|
|
6801
|
+
for (const match of after.matchAll(
|
|
6802
|
+
new RegExp(`(?<![\\w$.])${escapeForRegex(alias)}\\s*\\(`, "g")
|
|
6803
|
+
)) {
|
|
6804
|
+
leftovers.push({
|
|
6805
|
+
line: lineOf(match.index ?? 0),
|
|
6806
|
+
text: after.slice(match.index ?? 0, (match.index ?? 0) + 80).split("\n")[0] ?? ""
|
|
6807
|
+
});
|
|
6808
|
+
}
|
|
6809
|
+
if (leftovers.length === 0) after = removeJsSdkImport(after, "checkpoint");
|
|
6810
|
+
return { after, removed, leftovers };
|
|
6093
6811
|
}
|
|
6094
6812
|
function unguardPython(source) {
|
|
6095
6813
|
const imported = /^from[ \t]+veefore[ \t]+import[ \t]+([^\n]+)$/m.exec(source);
|
|
@@ -6180,29 +6898,28 @@ function jsGuardImport(source) {
|
|
|
6180
6898
|
}
|
|
6181
6899
|
return null;
|
|
6182
6900
|
}
|
|
6183
|
-
function
|
|
6184
|
-
const
|
|
6185
|
-
|
|
6901
|
+
function removeJsSdkImport(source, name) {
|
|
6902
|
+
const escaped = escapeForRegex(name);
|
|
6903
|
+
const drop = (names, rebuild, target) => {
|
|
6904
|
+
const rest = names.split(",").map((part) => part.trim()).filter((part) => part && !target.test(part));
|
|
6186
6905
|
return rest.length === 0 ? "" : rebuild(rest);
|
|
6187
6906
|
};
|
|
6188
6907
|
let after = source.replace(
|
|
6189
6908
|
/^[ \t]*import\s*\{([^}]*)\}\s*from\s*(['"])@veefore\/sdk\2\s*;?[ \t]*\r?\n?/m,
|
|
6190
|
-
(
|
|
6191
|
-
full,
|
|
6909
|
+
(_full, names, quote2) => drop(
|
|
6192
6910
|
names,
|
|
6193
6911
|
(rest) => `import { ${rest.join(", ")} } from ${quote2}@veefore/sdk${quote2};
|
|
6194
6912
|
`,
|
|
6195
|
-
|
|
6913
|
+
new RegExp(`^${escaped}(\\s+as\\s+[\\w$]+)?$`)
|
|
6196
6914
|
)
|
|
6197
6915
|
);
|
|
6198
6916
|
after = after.replace(
|
|
6199
6917
|
/^[ \t]*const\s*\{([^}]*)\}\s*=\s*require\(\s*(['"])@veefore\/sdk\2\s*\)\s*;?[ \t]*\r?\n?/m,
|
|
6200
|
-
(
|
|
6201
|
-
full,
|
|
6918
|
+
(_full, names, quote2) => drop(
|
|
6202
6919
|
names,
|
|
6203
6920
|
(rest) => `const { ${rest.join(", ")} } = require(${quote2}@veefore/sdk${quote2});
|
|
6204
6921
|
`,
|
|
6205
|
-
|
|
6922
|
+
new RegExp(`^${escaped}(\\s*:\\s*[\\w$]+)?$`)
|
|
6206
6923
|
)
|
|
6207
6924
|
);
|
|
6208
6925
|
return after;
|
|
@@ -6213,22 +6930,35 @@ function escapeForRegex(value) {
|
|
|
6213
6930
|
function isGuarded(value, alias) {
|
|
6214
6931
|
return new RegExp(`^\\s*(${alias}|guard|veeforeGuard)\\(`).test(value);
|
|
6215
6932
|
}
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
var PY_GUARD_IMPORT = /^from\s+veefore\s+import\s+[^\n]*\bguard\b(?:\s+as\s+(\w+))?/m;
|
|
6219
|
-
function existingGuardImport(source, language) {
|
|
6933
|
+
function existingImport(source, name, language) {
|
|
6934
|
+
const escaped = escapeForRegex(name);
|
|
6220
6935
|
if (language === "py") {
|
|
6221
|
-
const match2 =
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6936
|
+
const match2 = new RegExp(
|
|
6937
|
+
`^from\\s+veefore\\s+import\\s+[^\\n]*\\b${escaped}\\b(?:\\s+as\\s+(\\w+))?`,
|
|
6938
|
+
"m"
|
|
6939
|
+
).exec(source);
|
|
6940
|
+
return match2 ? match2[1] ?? name : null;
|
|
6941
|
+
}
|
|
6942
|
+
const esm = new RegExp(
|
|
6943
|
+
`import\\s*\\{[^}]*\\b${escaped}\\b(?:\\s+as\\s+([A-Za-z_$][\\w$]*))?[^}]*\\}\\s*from\\s*['"]@veefore/sdk['"]`
|
|
6944
|
+
).exec(source);
|
|
6945
|
+
const cjs = new RegExp(
|
|
6946
|
+
`\\{[^}]*\\b${escaped}\\b(?:\\s*:\\s*([A-Za-z_$][\\w$]*))?[^}]*\\}\\s*=\\s*require\\(\\s*['"]@veefore/sdk['"]\\s*\\)`
|
|
6947
|
+
).exec(source);
|
|
6948
|
+
const match = esm ?? cjs;
|
|
6949
|
+
return match ? match[1] ?? name : null;
|
|
6226
6950
|
}
|
|
6227
|
-
function
|
|
6228
|
-
|
|
6951
|
+
function existingGuardImport(source, language) {
|
|
6952
|
+
return existingImport(source, "guard", language);
|
|
6953
|
+
}
|
|
6954
|
+
function chooseAliasFor(source, name, language) {
|
|
6955
|
+
const existing = existingImport(source, name, language);
|
|
6229
6956
|
if (existing) return existing;
|
|
6230
|
-
if (
|
|
6231
|
-
return language === "py" ?
|
|
6957
|
+
if (!new RegExp(`\\b${escapeForRegex(name)}\\b`).test(source)) return name;
|
|
6958
|
+
return language === "py" ? `veefore_${name}` : `veefore${name[0]?.toUpperCase() ?? ""}${name.slice(1)}`;
|
|
6959
|
+
}
|
|
6960
|
+
function chooseAlias(source, language) {
|
|
6961
|
+
return chooseAliasFor(source, "guard", language);
|
|
6232
6962
|
}
|
|
6233
6963
|
function applyEdits(source, edits) {
|
|
6234
6964
|
let out2 = source;
|
|
@@ -6237,14 +6967,22 @@ function applyEdits(source, edits) {
|
|
|
6237
6967
|
}
|
|
6238
6968
|
return out2;
|
|
6239
6969
|
}
|
|
6240
|
-
|
|
6970
|
+
var SCHEMA_ONLY_REASON = "this is only the schema; guard the code that runs the tool the model picked (often one dispatch function for every tool)";
|
|
6971
|
+
function schemaOnly(source, python, wanted, placed = /* @__PURE__ */ new Set()) {
|
|
6241
6972
|
const lineOf = lineLocator2(source);
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6973
|
+
const hits = [
|
|
6974
|
+
...findToolSchemas(source, python).map((hit) => ({ name: hit.name, index: hit.index })),
|
|
6975
|
+
...python ? [] : findFunctionToolSchemas(source).map((hit) => ({ name: hit.name, index: hit.index }))
|
|
6976
|
+
].sort((a, b) => a.index - b.index);
|
|
6977
|
+
const changes = [];
|
|
6978
|
+
const seen = new Set(placed);
|
|
6979
|
+
for (const hit of hits) {
|
|
6980
|
+
const key = toToolKey(hit.name);
|
|
6981
|
+
if (!wanted.has(key) || seen.has(key)) continue;
|
|
6982
|
+
seen.add(key);
|
|
6983
|
+
changes.push({ key, line: lineOf(hit.index), status: "manual", reason: SCHEMA_ONLY_REASON });
|
|
6984
|
+
}
|
|
6985
|
+
return changes;
|
|
6248
6986
|
}
|
|
6249
6987
|
function dedupeChanges(changes) {
|
|
6250
6988
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -6364,10 +7102,30 @@ function myers(a, b) {
|
|
|
6364
7102
|
|
|
6365
7103
|
// src/integrate.ts
|
|
6366
7104
|
async function integrate(root, toolKeys, options) {
|
|
6367
|
-
const
|
|
7105
|
+
const index = indexFiles(root);
|
|
7106
|
+
const anchors = [...toolKeyAnchors(toolKeys), ...TOOL_ANCHORS, ...GUARD_ANCHORS];
|
|
7107
|
+
const candidates = shortlist(root, index, anchors);
|
|
7108
|
+
const sourceFiles = candidates.files;
|
|
7109
|
+
const truncated = index.truncated || candidates.truncated;
|
|
7110
|
+
const plan = planRewrite(root, sourceFiles, toolKeys);
|
|
6368
7111
|
const interactive = options.interactive ?? (process.stdin.isTTY === true && process.stdout.isTTY === true && !options.yes);
|
|
6369
|
-
|
|
7112
|
+
if (truncated && !options.quiet) {
|
|
7113
|
+
out(
|
|
7114
|
+
`${symbol.warn} The search ran out of time before it finished, so a tool may be reported as not found, or the place it runs may be missed.`
|
|
7115
|
+
);
|
|
7116
|
+
}
|
|
7117
|
+
const sources = readSources(root, sourceFiles);
|
|
7118
|
+
const dispatchGuardFiles = findDispatchGuards(sources);
|
|
6370
7119
|
const dispatchGuarded = dispatchGuardFiles.length > 0;
|
|
7120
|
+
const alreadyGuarded = guardedToolKeys(sources, toolKeys);
|
|
7121
|
+
for (const file of plan.files) {
|
|
7122
|
+
for (const change of file.changes) {
|
|
7123
|
+
if (change.status === "manual" && alreadyGuarded.has(change.key)) {
|
|
7124
|
+
change.status = "already";
|
|
7125
|
+
delete change.reason;
|
|
7126
|
+
}
|
|
7127
|
+
}
|
|
7128
|
+
}
|
|
6371
7129
|
let manual = plan.files.flatMap(
|
|
6372
7130
|
(file) => file.changes.filter((change) => change.status === "manual").map((change) => ({
|
|
6373
7131
|
file: file.file,
|
|
@@ -6391,7 +7149,7 @@ async function integrate(root, toolKeys, options) {
|
|
|
6391
7149
|
}
|
|
6392
7150
|
dispatch = [];
|
|
6393
7151
|
} else if (dispatch.length > 0) {
|
|
6394
|
-
dispatchPlan = planDispatch(
|
|
7152
|
+
dispatchPlan = planDispatch(sourceFiles, (file) => currentText(root, plan, file), [
|
|
6395
7153
|
...new Set(dispatch.map((item) => item.key))
|
|
6396
7154
|
]);
|
|
6397
7155
|
if (dispatchPlan?.after) {
|
|
@@ -6399,7 +7157,22 @@ async function integrate(root, toolKeys, options) {
|
|
|
6399
7157
|
dispatch = [];
|
|
6400
7158
|
}
|
|
6401
7159
|
}
|
|
6402
|
-
|
|
7160
|
+
if (dispatch.length > 0 && checkpointUsable(root, options.quiet === true)) {
|
|
7161
|
+
const placed = applyCheckpoints(
|
|
7162
|
+
plan,
|
|
7163
|
+
planCheckpoints(sourceFiles, (file) => currentText(root, plan, file), [
|
|
7164
|
+
...new Set(dispatch.map((item) => item.key))
|
|
7165
|
+
])
|
|
7166
|
+
);
|
|
7167
|
+
dispatch = dispatch.filter((item) => !placed.has(item.key));
|
|
7168
|
+
}
|
|
7169
|
+
const runsElsewhere = (key) => {
|
|
7170
|
+
const site = dispatchPlan?.sites?.get(key);
|
|
7171
|
+
return site !== void 0 && site.file !== dispatchPlan?.file;
|
|
7172
|
+
};
|
|
7173
|
+
const dispatchKeys = [
|
|
7174
|
+
...new Set(dispatch.filter((item) => !runsElsewhere(item.key)).map((item) => item.key))
|
|
7175
|
+
];
|
|
6403
7176
|
const dispatchItems = dispatchPlan ? dispatchKeys.map((key) => ({
|
|
6404
7177
|
file: dispatchPlan?.file,
|
|
6405
7178
|
key,
|
|
@@ -6407,7 +7180,7 @@ async function integrate(root, toolKeys, options) {
|
|
|
6407
7180
|
reason: `guard the code that runs the tool: ${dispatchPlan?.reason ?? ""}`,
|
|
6408
7181
|
language: languageOf(dispatchPlan?.file)
|
|
6409
7182
|
})) : [];
|
|
6410
|
-
const unplaced = dispatchPlan ?
|
|
7183
|
+
const unplaced = dispatchPlan ? dispatch.filter((item) => runsElsewhere(item.key)) : dispatch;
|
|
6411
7184
|
const pending = [...manual, ...dispatchItems];
|
|
6412
7185
|
if (pending.length > 0 && !options.dryRun) {
|
|
6413
7186
|
const hostedAvailable = await hostedEnabled(options.api ?? null);
|
|
@@ -6462,8 +7235,9 @@ async function integrate(root, toolKeys, options) {
|
|
|
6462
7235
|
const entry = plan.files.find((file) => file.file === proposal.file);
|
|
6463
7236
|
if (!entry) continue;
|
|
6464
7237
|
entry.after = proposal.proposal;
|
|
7238
|
+
const guarded = findGuardedKeys([{ file: proposal.file, text: proposal.proposal }]);
|
|
6465
7239
|
for (const change of entry.changes) {
|
|
6466
|
-
if (change.status === "manual" &&
|
|
7240
|
+
if (change.status === "manual" && guarded.has(change.key)) {
|
|
6467
7241
|
change.status = "wrapped";
|
|
6468
7242
|
change.via = "ai";
|
|
6469
7243
|
delete change.reason;
|
|
@@ -6477,29 +7251,37 @@ async function integrate(root, toolKeys, options) {
|
|
|
6477
7251
|
dispatch = remaining.filter(isDispatchItem);
|
|
6478
7252
|
if (method !== "skip" && !options.quiet) {
|
|
6479
7253
|
if (manual.length > 0) printManualInstructions(manual);
|
|
6480
|
-
|
|
7254
|
+
printDispatchInstructions([...dispatch, ...unplaced], dispatchPlan);
|
|
6481
7255
|
}
|
|
6482
7256
|
} else if (pending.length > 0 && !options.quiet) {
|
|
6483
|
-
|
|
7257
|
+
printDispatchInstructions([...dispatchItems, ...unplaced], dispatchPlan);
|
|
6484
7258
|
dispatch = dispatchItems;
|
|
7259
|
+
} else if (unplaced.length > 0 && !options.quiet && options.assist !== "skip") {
|
|
7260
|
+
printDispatchInstructions(unplaced, dispatchPlan);
|
|
6485
7261
|
}
|
|
6486
|
-
|
|
6487
|
-
printDispatchInstructions(unplaced, null);
|
|
6488
|
-
}
|
|
6489
|
-
manual = [...manual, ...dispatch, ...unplaced];
|
|
7262
|
+
manual = stillManual([...manual, ...dispatch, ...unplaced], plan);
|
|
6490
7263
|
const toWrite = plan.files.filter((file) => file.before !== file.after);
|
|
6491
7264
|
const languages = new Set(toWrite.map((file) => file.file.endsWith(".py") ? "py" : "js"));
|
|
6492
7265
|
if (!options.quiet) printPlan(plan);
|
|
6493
7266
|
if (toWrite.length === 0) {
|
|
6494
|
-
|
|
7267
|
+
const quiet = options.quiet === true || options.dryRun === true;
|
|
7268
|
+
if (!quiet) {
|
|
6495
7269
|
const guardedFiles = plan.files.filter((file) => file.changes.some((change) => change.status !== "manual")).map((file) => file.file);
|
|
6496
7270
|
reportSdkReadiness(root, [.../* @__PURE__ */ new Set([...guardedFiles, ...dispatchGuardFiles])]);
|
|
6497
7271
|
}
|
|
6498
|
-
|
|
7272
|
+
const unguarded2 = reportProtection(root, sourceFiles, toolKeys, quiet);
|
|
7273
|
+
return { status: "nothing-to-do", plan, checks: [], backupId: null, manual, unguarded: unguarded2 };
|
|
6499
7274
|
}
|
|
6500
7275
|
if (options.dryRun) {
|
|
6501
7276
|
out(style.grey("Dry run: nothing was written."));
|
|
6502
|
-
return {
|
|
7277
|
+
return {
|
|
7278
|
+
status: "dry-run",
|
|
7279
|
+
plan,
|
|
7280
|
+
checks: [],
|
|
7281
|
+
backupId: null,
|
|
7282
|
+
manual,
|
|
7283
|
+
unguarded: reportProtection(root, sourceFiles, toolKeys, true)
|
|
7284
|
+
};
|
|
6503
7285
|
}
|
|
6504
7286
|
if (!options.quiet) {
|
|
6505
7287
|
reportSdkReadiness(
|
|
@@ -6513,7 +7295,14 @@ async function integrate(root, toolKeys, options) {
|
|
|
6513
7295
|
);
|
|
6514
7296
|
if (!proceed) {
|
|
6515
7297
|
out(style.grey("No files were changed. Run `veefore integrate` when you are ready."));
|
|
6516
|
-
return {
|
|
7298
|
+
return {
|
|
7299
|
+
status: "declined",
|
|
7300
|
+
plan,
|
|
7301
|
+
checks: [],
|
|
7302
|
+
backupId: null,
|
|
7303
|
+
manual,
|
|
7304
|
+
unguarded: reportProtection(root, sourceFiles, toolKeys, true)
|
|
7305
|
+
};
|
|
6517
7306
|
}
|
|
6518
7307
|
const jsReady = languages.has("js") && sdkInstalled(root, "js");
|
|
6519
7308
|
const baseline = jsReady ? typecheck(
|
|
@@ -6521,13 +7310,20 @@ async function integrate(root, toolKeys, options) {
|
|
|
6521
7310
|
toWrite.map((file) => file.file)
|
|
6522
7311
|
) : null;
|
|
6523
7312
|
for (const file of toWrite) {
|
|
6524
|
-
if (
|
|
7313
|
+
if (readFileSync9(path10.join(root, file.file), "utf8") !== file.before) {
|
|
6525
7314
|
out(`${symbol.fail} ${file.file} changed while setup was running. Nothing was written.`);
|
|
6526
|
-
return {
|
|
7315
|
+
return {
|
|
7316
|
+
status: "declined",
|
|
7317
|
+
plan,
|
|
7318
|
+
checks: [],
|
|
7319
|
+
backupId: null,
|
|
7320
|
+
manual,
|
|
7321
|
+
unguarded: reportProtection(root, sourceFiles, toolKeys, true)
|
|
7322
|
+
};
|
|
6527
7323
|
}
|
|
6528
7324
|
}
|
|
6529
7325
|
const backup = createBackup(root, toWrite);
|
|
6530
|
-
for (const file of toWrite) writeFilePreservingMode(
|
|
7326
|
+
for (const file of toWrite) writeFilePreservingMode(path10.join(root, file.file), file.after);
|
|
6531
7327
|
out(
|
|
6532
7328
|
`${symbol.pass} Updated ${toWrite.length} file${toWrite.length === 1 ? "" : "s"} ${style.grey(`(backup ${backup.id})`)}`
|
|
6533
7329
|
);
|
|
@@ -6583,7 +7379,14 @@ async function integrate(root, toolKeys, options) {
|
|
|
6583
7379
|
`${symbol.warn} A check failed after the change, so every file was put back as it was ${style.grey(`(${restored.restored.length} restored)`)}.`
|
|
6584
7380
|
);
|
|
6585
7381
|
out(style.grey(" Please report this: the rewrite should never break a project."));
|
|
6586
|
-
return {
|
|
7382
|
+
return {
|
|
7383
|
+
status: "rolled-back",
|
|
7384
|
+
plan,
|
|
7385
|
+
checks,
|
|
7386
|
+
backupId: null,
|
|
7387
|
+
manual,
|
|
7388
|
+
unguarded: reportProtection(root, sourceFiles, toolKeys, true)
|
|
7389
|
+
};
|
|
6587
7390
|
}
|
|
6588
7391
|
for (const language of languages) {
|
|
6589
7392
|
if (!sdkInstalled(root, language)) {
|
|
@@ -6592,13 +7395,34 @@ async function integrate(root, toolKeys, options) {
|
|
|
6592
7395
|
);
|
|
6593
7396
|
}
|
|
6594
7397
|
}
|
|
7398
|
+
const unguarded = reportProtection(root, sourceFiles, toolKeys, options.quiet === true);
|
|
6595
7399
|
out(style.grey(" Undo at any time with `veefore undo`."));
|
|
6596
|
-
return { status: "applied", plan, checks, backupId: backup.id, manual };
|
|
7400
|
+
return { status: "applied", plan, checks, backupId: backup.id, manual, unguarded };
|
|
7401
|
+
}
|
|
7402
|
+
function reportProtection(root, files, toolKeys, quiet) {
|
|
7403
|
+
const guarded = guardedToolKeys(readSources(root, files), toolKeys);
|
|
7404
|
+
const unguarded = toolKeys.filter((key) => !guarded.has(key));
|
|
7405
|
+
if (quiet) return unguarded;
|
|
7406
|
+
if (unguarded.length === 0) {
|
|
7407
|
+
out(
|
|
7408
|
+
`${symbol.pass} Every registered tool goes through Veefore ${style.grey(`(${toolKeys.length})`)}`
|
|
7409
|
+
);
|
|
7410
|
+
return unguarded;
|
|
7411
|
+
}
|
|
7412
|
+
out(
|
|
7413
|
+
`${symbol.warn} ${toolKeys.length - unguarded.length} of ${toolKeys.length} registered tools go through Veefore. Still unprotected: ${style.bold(unguarded.join(", "))}`
|
|
7414
|
+
);
|
|
7415
|
+
out(
|
|
7416
|
+
style.grey(
|
|
7417
|
+
" The instructions above say what each one needs. Then `veefore integrate --check` to confirm."
|
|
7418
|
+
)
|
|
7419
|
+
);
|
|
7420
|
+
return unguarded;
|
|
6597
7421
|
}
|
|
6598
7422
|
function reportSdkReadiness(root, files) {
|
|
6599
7423
|
const problems = [];
|
|
6600
7424
|
if (files.length === 0) return problems;
|
|
6601
|
-
const dirs = [...new Set(files.map((file) =>
|
|
7425
|
+
const dirs = [...new Set(files.map((file) => path10.dirname(file)))];
|
|
6602
7426
|
const python = files.some((file) => file.endsWith(".py"));
|
|
6603
7427
|
const sdks = installedSdks(root, { dirs, python });
|
|
6604
7428
|
for (const sdk of sdks) {
|
|
@@ -6675,7 +7499,7 @@ async function removeGuards(root, options) {
|
|
|
6675
7499
|
const tsFiles = changed.filter((file) => /\.(m|c)?tsx?$/.test(file));
|
|
6676
7500
|
const baseline = tsFiles.length > 0 ? typecheck(root, tsFiles) : null;
|
|
6677
7501
|
const backup = createBackup(root, toWrite);
|
|
6678
|
-
for (const entry of toWrite) writeFilePreservingMode(
|
|
7502
|
+
for (const entry of toWrite) writeFilePreservingMode(path10.join(root, entry.file), entry.after);
|
|
6679
7503
|
const checks = [];
|
|
6680
7504
|
const py = changed.filter((file) => file.endsWith(".py"));
|
|
6681
7505
|
if (py.length > 0) checks.push(pythonSyntax(root, py));
|
|
@@ -6699,7 +7523,7 @@ function currentText(root, plan, file) {
|
|
|
6699
7523
|
const entry = plan.files.find((candidate) => candidate.file === file);
|
|
6700
7524
|
if (entry) return entry.after;
|
|
6701
7525
|
try {
|
|
6702
|
-
return
|
|
7526
|
+
return readFileSync9(path10.join(root, file), "utf8");
|
|
6703
7527
|
} catch {
|
|
6704
7528
|
return null;
|
|
6705
7529
|
}
|
|
@@ -6721,17 +7545,76 @@ function applyDispatch(root, plan, file, after, line, items, via) {
|
|
|
6721
7545
|
entry.changes.push({ key, line, status: "wrapped", ...via ? { via } : {} });
|
|
6722
7546
|
}
|
|
6723
7547
|
}
|
|
6724
|
-
function
|
|
6725
|
-
|
|
7548
|
+
function stillManual(items, plan) {
|
|
7549
|
+
const open = new Set(
|
|
7550
|
+
plan.files.flatMap(
|
|
7551
|
+
(file) => file.changes.filter((change) => change.status === "manual").map((change) => change.key)
|
|
7552
|
+
)
|
|
7553
|
+
);
|
|
7554
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7555
|
+
return items.filter((item) => {
|
|
7556
|
+
if (!open.has(item.key)) return false;
|
|
7557
|
+
const id = isDispatchItem(item) ? item.key : `${item.key}\0${item.file}`;
|
|
7558
|
+
if (seen.has(id)) return false;
|
|
7559
|
+
seen.add(id);
|
|
7560
|
+
return true;
|
|
7561
|
+
});
|
|
7562
|
+
}
|
|
7563
|
+
function checkpointUsable(root, quiet) {
|
|
7564
|
+
const sdks = installedSdks(root).filter((sdk) => sdk.language === "js");
|
|
7565
|
+
const stale = sdks.filter((sdk) => !sdk.hasCheckpoint);
|
|
7566
|
+
if (sdks.length === 0 || stale.length === 0) return true;
|
|
7567
|
+
if (!quiet) {
|
|
7568
|
+
out(
|
|
7569
|
+
`${symbol.warn} Tools your code runs in a branch rather than a function are guarded with ${style.bold("checkpoint()")}, added in @veefore/sdk ${MIN_CHECKPOINT_SDK}. ${stale[0]?.version ?? "The installed version"} does not have it, so they are left for you.`
|
|
7570
|
+
);
|
|
7571
|
+
out(
|
|
7572
|
+
` Upgrade and re-run: ${style.bold(`${installCommand(root, "js")}@^${MIN_CHECKPOINT_SDK}`)}`
|
|
7573
|
+
);
|
|
7574
|
+
}
|
|
7575
|
+
return false;
|
|
7576
|
+
}
|
|
7577
|
+
function applyCheckpoints(plan, plans) {
|
|
7578
|
+
const placed = /* @__PURE__ */ new Set();
|
|
7579
|
+
for (const checkpoint of plans) {
|
|
7580
|
+
let entry = plan.files.find((candidate) => candidate.file === checkpoint.file);
|
|
7581
|
+
if (!entry) {
|
|
7582
|
+
entry = {
|
|
7583
|
+
file: checkpoint.file,
|
|
7584
|
+
before: checkpoint.before,
|
|
7585
|
+
after: checkpoint.after,
|
|
7586
|
+
changes: []
|
|
7587
|
+
};
|
|
7588
|
+
plan.files.push(entry);
|
|
7589
|
+
}
|
|
7590
|
+
entry.after = checkpoint.after;
|
|
7591
|
+
for (const place of checkpoint.placed) {
|
|
7592
|
+
placed.add(place.key);
|
|
7593
|
+
entry.changes.push({
|
|
7594
|
+
key: place.key,
|
|
7595
|
+
line: place.line,
|
|
7596
|
+
status: "wrapped",
|
|
7597
|
+
via: "checkpoint"
|
|
7598
|
+
});
|
|
7599
|
+
}
|
|
7600
|
+
}
|
|
7601
|
+
for (const file of plan.files) {
|
|
7602
|
+
file.changes = file.changes.filter(
|
|
7603
|
+
(change) => !(change.status === "manual" && placed.has(change.key) && isDispatchItem({ reason: change.reason ?? "" }))
|
|
7604
|
+
);
|
|
7605
|
+
}
|
|
7606
|
+
return placed;
|
|
7607
|
+
}
|
|
7608
|
+
function readSources(root, files = listSourceFiles(root)) {
|
|
7609
|
+
return files.flatMap((file) => {
|
|
6726
7610
|
try {
|
|
6727
|
-
return [{ file, text:
|
|
7611
|
+
return [{ file, text: readFileSync9(path10.join(root, file), "utf8") }];
|
|
6728
7612
|
} catch {
|
|
6729
7613
|
return [];
|
|
6730
7614
|
}
|
|
6731
7615
|
});
|
|
6732
7616
|
}
|
|
6733
|
-
function
|
|
6734
|
-
const sources = readSources(root);
|
|
7617
|
+
function guardedToolKeys(sources, toolKeys) {
|
|
6735
7618
|
const guarded = findGuardedKeys(sources);
|
|
6736
7619
|
const dispatchFiles = findDispatchGuards(sources);
|
|
6737
7620
|
if (dispatchFiles.length > 0) {
|
|
@@ -6746,6 +7629,10 @@ function checkIntegration(root, toolKeys) {
|
|
|
6746
7629
|
}
|
|
6747
7630
|
}
|
|
6748
7631
|
}
|
|
7632
|
+
return new Map([...guarded].filter(([key]) => toolKeys.includes(key)));
|
|
7633
|
+
}
|
|
7634
|
+
function checkIntegration(root, toolKeys) {
|
|
7635
|
+
const guarded = guardedToolKeys(readSources(root), toolKeys);
|
|
6749
7636
|
heading("Protection check");
|
|
6750
7637
|
for (const key of toolKeys) {
|
|
6751
7638
|
const file = guarded.get(key);
|
|
@@ -6808,22 +7695,31 @@ function printPlan(plan) {
|
|
|
6808
7695
|
const wrapped = all.filter((entry) => entry.change.status === "wrapped");
|
|
6809
7696
|
const already = all.filter((entry) => entry.change.status === "already");
|
|
6810
7697
|
const manual = all.filter((entry) => entry.change.status === "manual");
|
|
7698
|
+
const tools = (entries) => new Set(entries.map((e) => e.change.key)).size;
|
|
7699
|
+
const keys = (entries) => [...new Set(entries.map((e) => e.change.key))];
|
|
6811
7700
|
if (wrapped.length > 0) {
|
|
7701
|
+
const count = tools(wrapped);
|
|
7702
|
+
const only = new Set(wrapped.map((entry) => entry.change.via ?? "guard"));
|
|
7703
|
+
const how = only.size === 1 && only.has("checkpoint") ? `gated with ${style.bold("checkpoint()")} where your code runs them` : `wrapped with ${style.bold("guard()")}`;
|
|
6812
7704
|
out(
|
|
6813
|
-
`${symbol.pass} ${
|
|
7705
|
+
`${symbol.pass} ${count} tool${count === 1 ? "" : "s"} will be ${how}, so Veefore decides each call:`
|
|
6814
7706
|
);
|
|
7707
|
+
const note = {
|
|
7708
|
+
ai: style.amber("proposed by AI \u2014 review the diff below"),
|
|
7709
|
+
checkpoint: style.grey("guarded where your code runs it")
|
|
7710
|
+
};
|
|
6815
7711
|
for (const { file, change } of wrapped) {
|
|
6816
7712
|
out(
|
|
6817
|
-
` ${style.bold(change.key.padEnd(24))} ${style.grey(`${file}:${change.line}`)}` + (change.via
|
|
7713
|
+
` ${style.bold(change.key.padEnd(24))} ${style.grey(`${file}:${change.line}`)}` + (change.via ? ` ${note[change.via] ?? ""}` : "")
|
|
6818
7714
|
);
|
|
6819
7715
|
}
|
|
6820
7716
|
}
|
|
6821
7717
|
if (already.length > 0) {
|
|
6822
|
-
out(`${symbol.pass} Already protected: ${already
|
|
7718
|
+
out(`${symbol.pass} Already protected: ${keys(already).join(", ")}`);
|
|
6823
7719
|
}
|
|
6824
7720
|
if (manual.length > 0) {
|
|
6825
7721
|
out(
|
|
6826
|
-
`${symbol.warn} Not changed automatically: ${manual
|
|
7722
|
+
`${symbol.warn} Not changed automatically: ${keys(manual).join(", ")} ${style.grey("(see instructions above)")}`
|
|
6827
7723
|
);
|
|
6828
7724
|
}
|
|
6829
7725
|
if (plan.notFound.length > 0) {
|
|
@@ -6845,8 +7741,8 @@ function printPlan(plan) {
|
|
|
6845
7741
|
}
|
|
6846
7742
|
|
|
6847
7743
|
// src/commands/remove.ts
|
|
6848
|
-
import { rmSync as
|
|
6849
|
-
import
|
|
7744
|
+
import { rmSync as rmSync4 } from "node:fs";
|
|
7745
|
+
import path11 from "node:path";
|
|
6850
7746
|
|
|
6851
7747
|
// src/discover.ts
|
|
6852
7748
|
var LOCAL_CANDIDATE_PORTS = [4e3, 4400, 4100, 4200, 4300, 4500, 8e3, 8080, 3e3];
|
|
@@ -6960,7 +7856,7 @@ async function removeCommand(args, flags, known) {
|
|
|
6960
7856
|
if (!file || !config) {
|
|
6961
7857
|
throw new CliError("This project is not connected to Veefore.", "There is nothing to remove.");
|
|
6962
7858
|
}
|
|
6963
|
-
const root =
|
|
7859
|
+
const root = path11.dirname(file);
|
|
6964
7860
|
const state = known ?? await agentState(config);
|
|
6965
7861
|
heading("Remove Veefore from this project");
|
|
6966
7862
|
details([
|
|
@@ -7011,7 +7907,7 @@ async function removeCommand(args, flags, known) {
|
|
|
7011
7907
|
out(style.grey("The project stays connected."));
|
|
7012
7908
|
return 0;
|
|
7013
7909
|
}
|
|
7014
|
-
|
|
7910
|
+
rmSync4(file);
|
|
7015
7911
|
const envRemoved = removeLocalEnvBlock(root);
|
|
7016
7912
|
out(
|
|
7017
7913
|
`${symbol.pass} Disconnected: removed veefore.config.json${envRemoved ? " and the Veefore block in .env.local" : ""}.`
|
|
@@ -7095,6 +7991,7 @@ async function integrateCommand(args, flags) {
|
|
|
7095
7991
|
files: outcome.plan.files.map((file) => ({ file: file.file, changes: file.changes })),
|
|
7096
7992
|
not_found: outcome.plan.notFound,
|
|
7097
7993
|
manual: outcome.manual,
|
|
7994
|
+
unguarded: outcome.unguarded,
|
|
7098
7995
|
checks: outcome.checks
|
|
7099
7996
|
},
|
|
7100
7997
|
null,
|
|
@@ -7494,10 +8391,10 @@ async function resolveAuthorizedTools(context, filter) {
|
|
|
7494
8391
|
|
|
7495
8392
|
// src/panel.ts
|
|
7496
8393
|
import { homedir as homedir2 } from "node:os";
|
|
7497
|
-
import
|
|
8394
|
+
import path12 from "node:path";
|
|
7498
8395
|
|
|
7499
8396
|
// src/version.ts
|
|
7500
|
-
var CLI_VERSION = "1.
|
|
8397
|
+
var CLI_VERSION = "1.5.0";
|
|
7501
8398
|
|
|
7502
8399
|
// src/panel.ts
|
|
7503
8400
|
function welcomeSource(flags = {}) {
|
|
@@ -7521,7 +8418,7 @@ function welcomeSource(flags = {}) {
|
|
|
7521
8418
|
}
|
|
7522
8419
|
function tilde(dir) {
|
|
7523
8420
|
const home = homedir2();
|
|
7524
|
-
return dir === home || dir.startsWith(`${home}${
|
|
8421
|
+
return dir === home || dir.startsWith(`${home}${path12.sep}`) ? `~${dir.slice(home.length)}` : dir;
|
|
7525
8422
|
}
|
|
7526
8423
|
function hostOf(url) {
|
|
7527
8424
|
try {
|
|
@@ -7588,7 +8485,7 @@ function planTool(tool) {
|
|
|
7588
8485
|
}
|
|
7589
8486
|
function printScan(scan) {
|
|
7590
8487
|
out(
|
|
7591
|
-
`${symbol.pass}
|
|
8488
|
+
`${symbol.pass} Searched ${scan.filesScanned.toLocaleString("en-US")} file${scan.filesScanned === 1 ? "" : "s"}, read ${scan.filesParsed.toLocaleString("en-US")}` + (scan.truncated ? style.amber(" (ran out of time; findings may be incomplete)") : "") + style.grey(" \u2014 locally, nothing was uploaded")
|
|
7592
8489
|
);
|
|
7593
8490
|
if (scan.tools.length === 0) {
|
|
7594
8491
|
out(`${symbol.warn} No tools found, so there is nothing for Veefore to decide on yet.`);
|
|
@@ -7621,6 +8518,40 @@ function printScan(scan) {
|
|
|
7621
8518
|
`${symbol.warn} Your code reads ${service.length} credential${service.length === 1 ? "" : "s"}: ${service.map((s) => s.name).join(", ")} ${style.grey("(names only; values were not read)")}`
|
|
7622
8519
|
);
|
|
7623
8520
|
}
|
|
8521
|
+
printGaps(scan.root);
|
|
8522
|
+
}
|
|
8523
|
+
function printGaps(root) {
|
|
8524
|
+
let discovery;
|
|
8525
|
+
try {
|
|
8526
|
+
discovery = discoverAgents(root);
|
|
8527
|
+
} catch {
|
|
8528
|
+
return;
|
|
8529
|
+
}
|
|
8530
|
+
if (discovery.agents.length > 1) {
|
|
8531
|
+
out();
|
|
8532
|
+
out(`${symbol.info} ${discovery.agents.length} agents found in this project:`);
|
|
8533
|
+
for (const agent of discovery.agents) {
|
|
8534
|
+
out(
|
|
8535
|
+
` ${style.bold(agent.name ?? agent.directory)} ${style.grey(`${agent.directory} \u2014 ${agent.tools.length} tool${agent.tools.length === 1 ? "" : "s"}`)}`
|
|
8536
|
+
);
|
|
8537
|
+
}
|
|
8538
|
+
out(style.grey(" Set each one up from its own directory, so its tools stay its own."));
|
|
8539
|
+
}
|
|
8540
|
+
if (discovery.unreadable.length === 0) return;
|
|
8541
|
+
const shown = discovery.unreadable.slice(0, 5);
|
|
8542
|
+
out();
|
|
8543
|
+
out(
|
|
8544
|
+
`${symbol.warn} ${discovery.unreadable.length} file${discovery.unreadable.length === 1 ? "" : "s"} look${discovery.unreadable.length === 1 ? "s" : ""} like agent code whose tools could not be read:`
|
|
8545
|
+
);
|
|
8546
|
+
for (const gap of shown) out(` ${style.grey(`${gap.file}:${gap.line}`)}`);
|
|
8547
|
+
if (discovery.unreadable.length > shown.length) {
|
|
8548
|
+
out(style.grey(` \u2026and ${discovery.unreadable.length - shown.length} more`));
|
|
8549
|
+
}
|
|
8550
|
+
out(
|
|
8551
|
+
style.grey(
|
|
8552
|
+
" Tools defined in a shape this scan does not know are not protected. Register them\n in the console by hand, or report the shape so it can be detected."
|
|
8553
|
+
)
|
|
8554
|
+
);
|
|
7624
8555
|
}
|
|
7625
8556
|
function printPlan2(plan, environment) {
|
|
7626
8557
|
out();
|
|
@@ -8164,20 +9095,36 @@ async function homeCommand(args, flags) {
|
|
|
8164
9095
|
return await homeLoop(args, flags);
|
|
8165
9096
|
} finally {
|
|
8166
9097
|
leaveApp();
|
|
9098
|
+
printFarewell();
|
|
8167
9099
|
}
|
|
8168
9100
|
}
|
|
9101
|
+
var farewellPrinted = false;
|
|
9102
|
+
function printFarewell() {
|
|
9103
|
+
if (!isInteractive() || inApp() || farewellPrinted) return;
|
|
9104
|
+
const project = readProjectConfig();
|
|
9105
|
+
if (!project) return;
|
|
9106
|
+
farewellPrinted = true;
|
|
9107
|
+
out();
|
|
9108
|
+
out(
|
|
9109
|
+
`${brand(style.bold("Veefore"))} ${style.grey("\xB7")} ${style.bold(project.agentName)} ${style.grey(`\xB7 ${project.environmentName} \xB7 ${project.controlPlaneUrl}`)}`
|
|
9110
|
+
);
|
|
9111
|
+
out(style.grey(" veefore dev watch decisions live"));
|
|
9112
|
+
out(style.grey(" veefore doctor check the security posture"));
|
|
9113
|
+
out(style.grey(" veefore open this again"));
|
|
9114
|
+
out();
|
|
9115
|
+
}
|
|
8169
9116
|
async function homeLoop(args, flags) {
|
|
8170
9117
|
const source = welcomeSource(flags);
|
|
8171
9118
|
let first = true;
|
|
8172
9119
|
for (; ; ) {
|
|
8173
9120
|
if (readProjectConfig()) return initCommand(args, flags);
|
|
8174
9121
|
const body = () => {
|
|
8175
|
-
drawScreen(source
|
|
9122
|
+
drawScreen(source);
|
|
8176
9123
|
out();
|
|
8177
9124
|
out(style.bold("Let's get started."));
|
|
8178
9125
|
out();
|
|
8179
9126
|
};
|
|
8180
|
-
await beginScreen(source, { animate: first
|
|
9127
|
+
await beginScreen(source, { animate: first });
|
|
8181
9128
|
first = false;
|
|
8182
9129
|
out();
|
|
8183
9130
|
out(style.bold("Let's get started."));
|
|
@@ -8209,7 +9156,11 @@ async function homeLoop(args, flags) {
|
|
|
8209
9156
|
// Setup done: "Continue", and the next pass opens the project home
|
|
8210
9157
|
// with the panel filled in. Otherwise "Back to menu", to read why and
|
|
8211
9158
|
// choose again.
|
|
8212
|
-
label: () => readProjectConfig() ? "Continue" : "Back to menu"
|
|
9159
|
+
label: () => readProjectConfig() ? "Continue" : "Back to menu",
|
|
9160
|
+
// Setup asks questions and draws its own screens as it goes, so it has
|
|
9161
|
+
// to reach the terminal as it runs. Collecting it would hold those
|
|
9162
|
+
// screens back and then print them all at once.
|
|
9163
|
+
live: true
|
|
8213
9164
|
});
|
|
8214
9165
|
continue;
|
|
8215
9166
|
}
|
|
@@ -8234,12 +9185,8 @@ async function initWithWelcome(args, flags) {
|
|
|
8234
9185
|
return initCommand(args, flags);
|
|
8235
9186
|
}
|
|
8236
9187
|
async function runOnScreen(source, flags, run2, options = {}) {
|
|
8237
|
-
const header = () =>
|
|
8238
|
-
out();
|
|
8239
|
-
compactHeader(source());
|
|
8240
|
-
};
|
|
9188
|
+
const header = () => drawScreen(source);
|
|
8241
9189
|
if (options.live) {
|
|
8242
|
-
clearScreen();
|
|
8243
9190
|
header();
|
|
8244
9191
|
let code2 = 0;
|
|
8245
9192
|
try {
|
|
@@ -8566,7 +9513,7 @@ async function initCommand(args, flags) {
|
|
|
8566
9513
|
}
|
|
8567
9514
|
function previousApprovalMode(dir = process.cwd()) {
|
|
8568
9515
|
try {
|
|
8569
|
-
const text =
|
|
9516
|
+
const text = readFileSync10(path13.join(dir, ".env.local"), "utf8");
|
|
8570
9517
|
const value = /^\s*VEEFORE_APPROVAL_MODE\s*=\s*['"]?(\w+)/m.exec(text)?.[1]?.toLowerCase();
|
|
8571
9518
|
return value === "wait" || value === "throw" ? value : null;
|
|
8572
9519
|
} catch {
|
|
@@ -8708,7 +9655,7 @@ async function selectEnvironment(api, me, workspaceId, options, flags) {
|
|
|
8708
9655
|
}
|
|
8709
9656
|
}
|
|
8710
9657
|
async function stepScreen(source, decided, step, total) {
|
|
8711
|
-
await beginScreen(source
|
|
9658
|
+
await beginScreen(source);
|
|
8712
9659
|
out();
|
|
8713
9660
|
out(` ${style.bold("Set up this project")} ${style.grey(`step ${step} of ${total}`)}`);
|
|
8714
9661
|
if (decided.length > 0) {
|
|
@@ -8943,13 +9890,14 @@ async function projectHome(existing, flags) {
|
|
|
8943
9890
|
return await projectHomeLoop(existing, flags);
|
|
8944
9891
|
} finally {
|
|
8945
9892
|
leaveApp();
|
|
9893
|
+
printFarewell();
|
|
8946
9894
|
}
|
|
8947
9895
|
}
|
|
8948
9896
|
async function projectHomeLoop(existing, flags) {
|
|
8949
9897
|
const source = welcomeSource(flags);
|
|
8950
9898
|
let last = 0;
|
|
8951
9899
|
for (let round = 0; ; round += 1) {
|
|
8952
|
-
await beginScreen(source, { animate: round === 0
|
|
9900
|
+
await beginScreen(source, { animate: round === 0 });
|
|
8953
9901
|
const state = await agentState(existing);
|
|
8954
9902
|
printProjectSummary(existing, state);
|
|
8955
9903
|
const choices = homeChoices(existing, state, flags);
|
|
@@ -8964,7 +9912,7 @@ async function projectHomeLoop(existing, flags) {
|
|
|
8964
9912
|
// Repainted as soon as a sign-in, sign-out or project change is
|
|
8965
9913
|
// noticed, so the panel and the status never sit stale.
|
|
8966
9914
|
redraw: () => {
|
|
8967
|
-
drawScreen(source
|
|
9915
|
+
drawScreen(source);
|
|
8968
9916
|
printProjectSummary(existing, state);
|
|
8969
9917
|
out();
|
|
8970
9918
|
}
|
|
@@ -8976,8 +9924,7 @@ async function projectHomeLoop(existing, flags) {
|
|
|
8976
9924
|
}
|
|
8977
9925
|
if (picked.leaves) {
|
|
8978
9926
|
return onOrdinaryScreen(async () => {
|
|
8979
|
-
|
|
8980
|
-
compactHeader(source());
|
|
9927
|
+
drawScreen(source);
|
|
8981
9928
|
try {
|
|
8982
9929
|
return await picked.run();
|
|
8983
9930
|
} catch (error) {
|