veefore 1.0.0 → 1.2.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 +1405 -851
- 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(path12, 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", `${path12}${suffix}`);
|
|
246
246
|
}
|
|
247
|
-
async post(
|
|
248
|
-
return this.send("POST",
|
|
247
|
+
async post(path12, body) {
|
|
248
|
+
return this.send("POST", path12, body);
|
|
249
249
|
}
|
|
250
|
-
async patch(
|
|
251
|
-
return this.send("PATCH",
|
|
250
|
+
async patch(path12, body) {
|
|
251
|
+
return this.send("PATCH", path12, body);
|
|
252
252
|
}
|
|
253
|
-
async delete(
|
|
254
|
-
return this.send("DELETE",
|
|
253
|
+
async delete(path12) {
|
|
254
|
+
return this.send("DELETE", path12);
|
|
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, path12, body) {
|
|
258
|
+
return this.fetchWithTimeout(method, path12, body);
|
|
259
259
|
}
|
|
260
|
-
async send(method,
|
|
261
|
-
const response = await this.fetchWithTimeout(method,
|
|
260
|
+
async send(method, path12, body) {
|
|
261
|
+
const response = await this.fetchWithTimeout(method, path12, 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} ${path12}.`,
|
|
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} ${path12} 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, path12, 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}${path12}`, {
|
|
304
304
|
method,
|
|
305
305
|
headers,
|
|
306
306
|
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
@@ -566,6 +566,332 @@ function sleep(ms) {
|
|
|
566
566
|
|
|
567
567
|
// src/terminal.ts
|
|
568
568
|
import { createInterface } from "node:readline/promises";
|
|
569
|
+
|
|
570
|
+
// src/tui.ts
|
|
571
|
+
import { emitKeypressEvents } from "node:readline";
|
|
572
|
+
|
|
573
|
+
// src/logo.ts
|
|
574
|
+
var LOGO = [
|
|
575
|
+
"\u2580\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2584 \u2584\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2580",
|
|
576
|
+
" \u2588\u2588 \u2580\u2588 \u2588\u2580 \u2588\u2588",
|
|
577
|
+
" \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588",
|
|
578
|
+
" \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588",
|
|
579
|
+
" \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588",
|
|
580
|
+
" \u2580\u2588\u2584 \u2588\u2584 \u2584\u2588 \u2584\u2588 \u2584\u2588\u2580",
|
|
581
|
+
" \u2580\u2588\u2584 \u2580\u2588\u2588\u2588\u2580 \u2584\u2588\u2580 \u2584\u2588\u2580",
|
|
582
|
+
" \u2580\u2588\u2584 \u2580\u2588\u2580 \u2584\u2588\u2580 \u2584\u2588\u2580",
|
|
583
|
+
" \u2580\u2588 \u2580 \u2588\u2580 \u2588\u2580",
|
|
584
|
+
" \u2588\u2588 \u2588\u2588 \u2588\u2588",
|
|
585
|
+
" \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588"
|
|
586
|
+
];
|
|
587
|
+
|
|
588
|
+
// src/tui.ts
|
|
589
|
+
function isInteractive() {
|
|
590
|
+
return process.stdin.isTTY === true && process.stdout.isTTY === true && process.env.TERM !== "dumb" && process.env.CI === void 0;
|
|
591
|
+
}
|
|
592
|
+
var COLOR = process.env.NO_COLOR === void 0 && process.stdout.isTTY === true;
|
|
593
|
+
function colorDepth() {
|
|
594
|
+
const colorterm = (process.env.COLORTERM ?? "").toLowerCase();
|
|
595
|
+
if (colorterm === "truecolor" || colorterm === "24bit") return 24;
|
|
596
|
+
if (process.env.TERM_PROGRAM === "vscode" || process.env.TERM_PROGRAM === "iTerm.app") return 24;
|
|
597
|
+
if ((process.env.TERM ?? "").includes("256")) return 8;
|
|
598
|
+
return 4;
|
|
599
|
+
}
|
|
600
|
+
function paint(rgb, text) {
|
|
601
|
+
if (!COLOR) return text;
|
|
602
|
+
const depth = colorDepth();
|
|
603
|
+
if (depth === 24) return `\x1B[38;2;${rgb[0]};${rgb[1]};${rgb[2]}m${text}\x1B[0m`;
|
|
604
|
+
if (depth === 8) {
|
|
605
|
+
const level = (value) => Math.round(value / 255 * 5);
|
|
606
|
+
const code = 16 + 36 * level(rgb[0]) + 6 * level(rgb[1]) + level(rgb[2]);
|
|
607
|
+
return `\x1B[38;5;${code}m${text}\x1B[0m`;
|
|
608
|
+
}
|
|
609
|
+
return `\x1B[94m${text}\x1B[0m`;
|
|
610
|
+
}
|
|
611
|
+
var BRAND_TOP = [150, 172, 236];
|
|
612
|
+
var BRAND_BOTTOM = [84, 108, 186];
|
|
613
|
+
var BRAND = [118, 140, 214];
|
|
614
|
+
var brand = (text) => paint(BRAND, text);
|
|
615
|
+
function gradient(row, rows) {
|
|
616
|
+
const t = rows <= 1 ? 0 : row / (rows - 1);
|
|
617
|
+
const mix = (a, b) => Math.round(a + (b - a) * t);
|
|
618
|
+
return [
|
|
619
|
+
mix(BRAND_TOP[0], BRAND_BOTTOM[0]),
|
|
620
|
+
mix(BRAND_TOP[1], BRAND_BOTTOM[1]),
|
|
621
|
+
mix(BRAND_TOP[2], BRAND_BOTTOM[2])
|
|
622
|
+
];
|
|
623
|
+
}
|
|
624
|
+
var ANSI = /\u001B\[[0-9;?]*[A-Za-z]/g;
|
|
625
|
+
function visibleLength(text) {
|
|
626
|
+
return [...text.replace(ANSI, "")].length;
|
|
627
|
+
}
|
|
628
|
+
function columns() {
|
|
629
|
+
return process.stdout.columns && process.stdout.columns > 0 ? process.stdout.columns : 80;
|
|
630
|
+
}
|
|
631
|
+
var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
632
|
+
var STARS = [
|
|
633
|
+
[0, -3, "*"],
|
|
634
|
+
[3, -2, "\xB7"],
|
|
635
|
+
[7, -3, "*"],
|
|
636
|
+
[10, -1, "\xB7"],
|
|
637
|
+
[1, 33, "\xB7"],
|
|
638
|
+
[5, 34, "*"],
|
|
639
|
+
[9, 32, "\xB7"]
|
|
640
|
+
];
|
|
641
|
+
async function showWelcome(context, options = {}) {
|
|
642
|
+
if (!isInteractive()) return;
|
|
643
|
+
const width = Math.min(columns(), 100);
|
|
644
|
+
const logoWidth = Math.max(...LOGO.map((line) => [...line].length));
|
|
645
|
+
const indent = 5;
|
|
646
|
+
const gap = 6;
|
|
647
|
+
const panelWidth = Math.max(
|
|
648
|
+
24,
|
|
649
|
+
...context.facts.map(([label, value]) => label.length + 2 + value.length)
|
|
650
|
+
);
|
|
651
|
+
const sideBySide = width >= indent + logoWidth + gap + panelWidth + 2;
|
|
652
|
+
const showLogo = width >= indent + logoWidth + 2;
|
|
653
|
+
const rule = style.grey("\xB7".repeat(Math.max(10, Math.min(width - 2, 72))));
|
|
654
|
+
const animate = (options.animate ?? true) && process.env.VEEFORE_NO_ANIMATION === void 0;
|
|
655
|
+
out();
|
|
656
|
+
out(` ${brand(style.bold("Welcome to Veefore"))} ${style.grey(`v${context.version}`)}`);
|
|
657
|
+
out(` ${rule}`);
|
|
658
|
+
out();
|
|
659
|
+
const panel = renderPanel(context.facts);
|
|
660
|
+
if (showLogo) {
|
|
661
|
+
for (let row = 0; row < LOGO.length; row += 1) {
|
|
662
|
+
const line = LOGO[row] ?? "";
|
|
663
|
+
const left = starsFor(row, indent, (column) => column < 0, -indent);
|
|
664
|
+
const right = starsFor(row, 0, (column) => column >= logoWidth, logoWidth);
|
|
665
|
+
const art = paint(gradient(row, LOGO.length), line.padEnd(logoWidth));
|
|
666
|
+
let text = `${left}${art}`;
|
|
667
|
+
if (sideBySide) {
|
|
668
|
+
const top = Math.max(0, Math.floor((LOGO.length - panel.length) / 2));
|
|
669
|
+
const side = panel[row - top];
|
|
670
|
+
text += side === void 0 ? right : `${" ".repeat(gap)}${side}`;
|
|
671
|
+
} else {
|
|
672
|
+
text += right;
|
|
673
|
+
}
|
|
674
|
+
out(text.replace(/\s+$/, ""));
|
|
675
|
+
if (animate) await sleep2(14);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
if (!sideBySide) {
|
|
679
|
+
out();
|
|
680
|
+
for (const line of panel) out(fit(` ${line}`, width - 1));
|
|
681
|
+
}
|
|
682
|
+
out();
|
|
683
|
+
out(` ${rule}`);
|
|
684
|
+
}
|
|
685
|
+
function starsFor(row, width, inBand, offset) {
|
|
686
|
+
const cells = Array.from({ length: Math.max(width, 6) }, () => " ");
|
|
687
|
+
for (const [starRow, column, glyph] of STARS) {
|
|
688
|
+
if (starRow !== row || !inBand(column)) continue;
|
|
689
|
+
const index = column - offset;
|
|
690
|
+
if (index >= 0 && index < cells.length) cells[index] = style.grey(glyph);
|
|
691
|
+
}
|
|
692
|
+
return width === 0 ? cells.join("").replace(/\s+$/, "") : cells.slice(0, width).join("");
|
|
693
|
+
}
|
|
694
|
+
function renderPanel(facts) {
|
|
695
|
+
const lines = [
|
|
696
|
+
style.bold("Veefore"),
|
|
697
|
+
style.grey("Identity, authority and policy"),
|
|
698
|
+
style.grey("for AI agents"),
|
|
699
|
+
""
|
|
700
|
+
];
|
|
701
|
+
const labelWidth = Math.max(0, ...facts.map(([label]) => label.length));
|
|
702
|
+
for (const [label, value] of facts) {
|
|
703
|
+
lines.push(`${style.grey(label.padEnd(labelWidth))} ${value}`);
|
|
704
|
+
}
|
|
705
|
+
return lines;
|
|
706
|
+
}
|
|
707
|
+
function clearScreen() {
|
|
708
|
+
if (!isInteractive()) return;
|
|
709
|
+
process.stdout.write("\x1B[H\x1B[2J");
|
|
710
|
+
}
|
|
711
|
+
function compactHeader(context) {
|
|
712
|
+
const facts = Object.fromEntries(context.facts);
|
|
713
|
+
const parts = [
|
|
714
|
+
brand(style.bold("Veefore")),
|
|
715
|
+
style.grey(`v${context.version}`),
|
|
716
|
+
...[facts.Folder, facts["Signed in"], facts.Veefore].filter((v) => Boolean(v))
|
|
717
|
+
];
|
|
718
|
+
out();
|
|
719
|
+
out(fit(` ${parts.join(style.grey(" \xB7 "))}`, columns() - 1));
|
|
720
|
+
out(` ${style.grey("\xB7".repeat(Math.max(10, Math.min(columns() - 2, 72))))}`);
|
|
721
|
+
}
|
|
722
|
+
async function beginScreen(source, options = {}) {
|
|
723
|
+
if (!isInteractive()) return;
|
|
724
|
+
clearScreen();
|
|
725
|
+
if (options.compact) compactHeader(source());
|
|
726
|
+
else await showWelcome(source(), { animate: options.animate ?? false });
|
|
727
|
+
}
|
|
728
|
+
async function pressBack(label = "Back") {
|
|
729
|
+
if (!isInteractive()) return;
|
|
730
|
+
const stdin = process.stdin;
|
|
731
|
+
const stdout = process.stdout;
|
|
732
|
+
out();
|
|
733
|
+
stdout.write(`${brand("\u2190")} ${style.bold(label)} ${style.grey("\xB7 enter or esc")}`);
|
|
734
|
+
await new Promise((resolve) => {
|
|
735
|
+
const wasRaw = stdin.isRaw;
|
|
736
|
+
const done = () => {
|
|
737
|
+
stdin.off("keypress", onKey);
|
|
738
|
+
stdin.setRawMode(wasRaw);
|
|
739
|
+
stdin.pause();
|
|
740
|
+
stdout.write("\r\x1B[K");
|
|
741
|
+
resolve();
|
|
742
|
+
};
|
|
743
|
+
const onKey = (input, key = {}) => {
|
|
744
|
+
if (key.name === "return" || key.name === "enter" || key.name === "escape" || key.name === "left" || key.name === "backspace" || input === "b" || key.ctrl && key.name === "c") {
|
|
745
|
+
done();
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
emitKeypressEvents(stdin);
|
|
749
|
+
stdin.setRawMode(true);
|
|
750
|
+
stdin.resume();
|
|
751
|
+
stdin.on("keypress", onKey);
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
var BACK = /* @__PURE__ */ Symbol("veefore.back");
|
|
755
|
+
var MenuCancelled = class extends CliError {
|
|
756
|
+
constructor() {
|
|
757
|
+
super("Cancelled.", void 0, 130);
|
|
758
|
+
this.name = "MenuCancelled";
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
async function select(question, real, settings = {}) {
|
|
762
|
+
const options = settings.back ? [...real, { label: settings.backLabel ?? "Back", value: BACK, hint: "\u2190" }] : real;
|
|
763
|
+
const enabled = options.map((option, index) => option.disabled ? -1 : index).filter((i) => i >= 0);
|
|
764
|
+
if (enabled.length === 0) throw new CliError(`Nothing can be chosen for "${question}".`);
|
|
765
|
+
const choosable = enabled.filter((index) => options[index]?.value !== BACK);
|
|
766
|
+
const fallback = choosable.includes(settings.initial ?? -1) ? settings.initial : choosable[0] ?? enabled[0];
|
|
767
|
+
if (!isInteractive()) return options[fallback].value;
|
|
768
|
+
let cursor = fallback;
|
|
769
|
+
let rendered = 0;
|
|
770
|
+
const stdin = process.stdin;
|
|
771
|
+
const stdout = process.stdout;
|
|
772
|
+
const labelWidth = Math.max(...options.map((option) => [...option.label].length));
|
|
773
|
+
const draw = () => {
|
|
774
|
+
const width = columns();
|
|
775
|
+
const lines = [];
|
|
776
|
+
lines.push(`${brand("?")} ${style.bold(question)}`);
|
|
777
|
+
if (settings.help) lines.push(` ${style.grey(settings.help)}`);
|
|
778
|
+
options.forEach((option, index) => {
|
|
779
|
+
const active = index === cursor;
|
|
780
|
+
const pointer = active ? brand("\u276F") : " ";
|
|
781
|
+
const number = index < 9 ? `${index + 1}.` : " ";
|
|
782
|
+
const text = option.hint ? option.label.padEnd(labelWidth) : option.label;
|
|
783
|
+
const hint = option.hint ? ` ${style.grey(option.hint)}` : "";
|
|
784
|
+
const label = option.disabled ? style.grey(`${number} ${text}`) : active ? brand(`${number} ${text}`) : `${number} ${text}`;
|
|
785
|
+
lines.push(fit(`${pointer} ${label}${hint}`, width - 1));
|
|
786
|
+
});
|
|
787
|
+
lines.push(
|
|
788
|
+
style.grey(
|
|
789
|
+
settings.back ? " \u2191\u2193 move \xB7 enter choose \xB7 \u2190 back \xB7 esc cancel" : " \u2191\u2193 move \xB7 enter choose \xB7 esc cancel"
|
|
790
|
+
)
|
|
791
|
+
);
|
|
792
|
+
if (rendered > 0) stdout.write(`\x1B[${rendered}A\r\x1B[J`);
|
|
793
|
+
stdout.write(`${lines.join("\n")}
|
|
794
|
+
`);
|
|
795
|
+
rendered = lines.length;
|
|
796
|
+
};
|
|
797
|
+
const move = (direction) => {
|
|
798
|
+
const position = enabled.indexOf(cursor);
|
|
799
|
+
const next = (position + direction + enabled.length) % enabled.length;
|
|
800
|
+
cursor = enabled[next];
|
|
801
|
+
};
|
|
802
|
+
return new Promise((resolve, reject) => {
|
|
803
|
+
const wasRaw = stdin.isRaw;
|
|
804
|
+
const restoreCursor = () => {
|
|
805
|
+
stdout.write("\x1B[?25h");
|
|
806
|
+
};
|
|
807
|
+
const finish = (result) => {
|
|
808
|
+
stdin.off("keypress", onKey);
|
|
809
|
+
stdin.setRawMode(wasRaw);
|
|
810
|
+
stdin.pause();
|
|
811
|
+
process.off("exit", restoreCursor);
|
|
812
|
+
if (rendered > 0) stdout.write(`\x1B[${rendered}A\r\x1B[J`);
|
|
813
|
+
restoreCursor();
|
|
814
|
+
if (result) {
|
|
815
|
+
if (!settings.quiet && result.value !== BACK) {
|
|
816
|
+
stdout.write(`${style.green("\u2713")} ${question} ${brand(result.label)}
|
|
817
|
+
`);
|
|
818
|
+
}
|
|
819
|
+
resolve(result.value);
|
|
820
|
+
} else {
|
|
821
|
+
stdout.write(`${style.grey("\u2717")} ${question} ${style.grey("cancelled")}
|
|
822
|
+
`);
|
|
823
|
+
reject(new MenuCancelled());
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
const choose = (index) => {
|
|
827
|
+
const option = options[index];
|
|
828
|
+
if (!option || option.disabled) return;
|
|
829
|
+
finish({ value: option.value, label: option.label });
|
|
830
|
+
};
|
|
831
|
+
const onKey = (input, key = {}) => {
|
|
832
|
+
if (key.ctrl && key.name === "c" || key.name === "escape") return finish(null);
|
|
833
|
+
if (key.name === "return" || key.name === "enter") return choose(cursor);
|
|
834
|
+
if (settings.back && (key.name === "left" || key.name === "backspace")) {
|
|
835
|
+
return finish({ value: BACK, label: settings.backLabel ?? "Back" });
|
|
836
|
+
}
|
|
837
|
+
if (key.name === "up" || key.name === "k") move(-1);
|
|
838
|
+
else if (key.name === "down" || key.name === "j" || key.name === "tab") move(1);
|
|
839
|
+
else if (input && /^[1-9]$/.test(input)) {
|
|
840
|
+
const index = Number(input) - 1;
|
|
841
|
+
if (options[index] && !options[index]?.disabled) return choose(index);
|
|
842
|
+
return;
|
|
843
|
+
} else if (input) {
|
|
844
|
+
const match = options.findIndex(
|
|
845
|
+
(option) => !option.disabled && option.key?.toLowerCase() === input.toLowerCase()
|
|
846
|
+
);
|
|
847
|
+
if (match >= 0) return choose(match);
|
|
848
|
+
return;
|
|
849
|
+
} else {
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
draw();
|
|
853
|
+
};
|
|
854
|
+
process.once("exit", restoreCursor);
|
|
855
|
+
emitKeypressEvents(stdin);
|
|
856
|
+
stdin.setRawMode(true);
|
|
857
|
+
stdin.resume();
|
|
858
|
+
stdout.write("\x1B[?25l");
|
|
859
|
+
stdin.on("keypress", onKey);
|
|
860
|
+
draw();
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
async function selectYesNo(question, defaultYes = false) {
|
|
864
|
+
return select(
|
|
865
|
+
question,
|
|
866
|
+
[
|
|
867
|
+
{ label: "Yes", value: true, key: "y" },
|
|
868
|
+
{ label: "No", value: false, key: "n" }
|
|
869
|
+
],
|
|
870
|
+
{ initial: defaultYes ? 0 : 1 }
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
function fit(text, width) {
|
|
874
|
+
if (visibleLength(text) <= width) return text;
|
|
875
|
+
let shown = 0;
|
|
876
|
+
let result = "";
|
|
877
|
+
for (let index = 0; index < text.length; ) {
|
|
878
|
+
ANSI.lastIndex = index;
|
|
879
|
+
const escape = ANSI.exec(text);
|
|
880
|
+
if (escape && escape.index === index) {
|
|
881
|
+
result += escape[0];
|
|
882
|
+
index += escape[0].length;
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
if (shown >= width - 1) break;
|
|
886
|
+
const char = String.fromCodePoint(text.codePointAt(index));
|
|
887
|
+
result += char;
|
|
888
|
+
shown += 1;
|
|
889
|
+
index += char.length;
|
|
890
|
+
}
|
|
891
|
+
return `${result}\u2026\x1B[0m`;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// src/terminal.ts
|
|
569
895
|
var COLOR_ENABLED = process.env.NO_COLOR === void 0 && process.env.TERM !== "dumb" && process.stdout.isTTY === true;
|
|
570
896
|
function wrap(code, text) {
|
|
571
897
|
return COLOR_ENABLED ? `\x1B[${code}m${text}\x1B[0m` : text;
|
|
@@ -599,12 +925,12 @@ function heading(text) {
|
|
|
599
925
|
out(style.bold(text));
|
|
600
926
|
out(style.grey("\u2500".repeat(Math.min(text.length, 72))));
|
|
601
927
|
}
|
|
602
|
-
function table(
|
|
928
|
+
function table(columns2, rows) {
|
|
603
929
|
if (rows.length === 0) {
|
|
604
930
|
out(style.grey(" (none)"));
|
|
605
931
|
return;
|
|
606
932
|
}
|
|
607
|
-
const widths =
|
|
933
|
+
const widths = columns2.map((column) => {
|
|
608
934
|
const longest = Math.max(
|
|
609
935
|
column.header.length,
|
|
610
936
|
...rows.map((row) => (row[column.key] ?? "").length)
|
|
@@ -615,17 +941,17 @@ function table(columns, rows) {
|
|
|
615
941
|
const parts = cells.map((cell, index) => {
|
|
616
942
|
const width = widths[index] ?? cell.length;
|
|
617
943
|
const text = cell.length > width ? `${cell.slice(0, Math.max(0, width - 1))}\u2026` : cell;
|
|
618
|
-
return
|
|
944
|
+
return columns2[index]?.align === "right" ? text.padStart(width) : text.padEnd(width);
|
|
619
945
|
});
|
|
620
946
|
out(` ${decorate(parts.join(" "))}`);
|
|
621
947
|
};
|
|
622
948
|
renderRow(
|
|
623
|
-
|
|
949
|
+
columns2.map((column) => column.header),
|
|
624
950
|
style.grey
|
|
625
951
|
);
|
|
626
952
|
for (const row of rows) {
|
|
627
953
|
renderRow(
|
|
628
|
-
|
|
954
|
+
columns2.map((column) => row[column.key] ?? ""),
|
|
629
955
|
(text) => text
|
|
630
956
|
);
|
|
631
957
|
}
|
|
@@ -696,6 +1022,14 @@ async function confirm(question, options = {}) {
|
|
|
696
1022
|
out(`${question} ${style.grey("[y/N]")} ${style.amber("y (--yes)")}`);
|
|
697
1023
|
return true;
|
|
698
1024
|
}
|
|
1025
|
+
if (isInteractive()) {
|
|
1026
|
+
try {
|
|
1027
|
+
return await selectYesNo(question);
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
if (error instanceof MenuCancelled) return false;
|
|
1030
|
+
throw error;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
699
1033
|
if (!process.stdin.isTTY) {
|
|
700
1034
|
err(
|
|
701
1035
|
"This command requires confirmation but the terminal is not interactive. Re-run with --yes to confirm explicitly."
|
|
@@ -1405,11 +1739,11 @@ function renderRevokeConfirmation(agent, environment, preview) {
|
|
|
1405
1739
|
out(` ${style.red("This action is irreversible for this identity.")}`);
|
|
1406
1740
|
out();
|
|
1407
1741
|
}
|
|
1408
|
-
async function lifecycle(args, flags,
|
|
1742
|
+
async function lifecycle(args, flags, path12, resultingStatus) {
|
|
1409
1743
|
const context = await resolveContext(flags);
|
|
1410
1744
|
const agentId = positional(args) ?? context.config.agentId;
|
|
1411
1745
|
const agent = await context.api.get(`/v1/agents/${agentId}`);
|
|
1412
|
-
const reasonRequired =
|
|
1746
|
+
const reasonRequired = path12 === "suspend";
|
|
1413
1747
|
const reason = valueFor(args, "--reason");
|
|
1414
1748
|
if (reasonRequired && (!reason || reason.trim().length < 10)) {
|
|
1415
1749
|
throw new CliError(
|
|
@@ -1419,24 +1753,24 @@ async function lifecycle(args, flags, path11, resultingStatus) {
|
|
|
1419
1753
|
}
|
|
1420
1754
|
out();
|
|
1421
1755
|
out(
|
|
1422
|
-
`${symbol.info} ${
|
|
1756
|
+
`${symbol.info} ${path12} ${style.bold(agent.name)} ${style.grey(`(${agent.status} \u2192 ${resultingStatus})`)}`
|
|
1423
1757
|
);
|
|
1424
|
-
if (
|
|
1758
|
+
if (path12 !== "resume") {
|
|
1425
1759
|
const confirmed = await confirm("Continue?", { yes: flags.yes });
|
|
1426
1760
|
if (!confirmed) {
|
|
1427
1761
|
out(style.grey("Cancelled."));
|
|
1428
1762
|
return 1;
|
|
1429
1763
|
}
|
|
1430
1764
|
}
|
|
1431
|
-
const actionSpinner = spinner(`Applying ${
|
|
1765
|
+
const actionSpinner = spinner(`Applying ${path12}`);
|
|
1432
1766
|
try {
|
|
1433
1767
|
await withStepUp(
|
|
1434
1768
|
context.api,
|
|
1435
|
-
() => context.api.post(`/v1/agents/${agentId}/${
|
|
1769
|
+
() => context.api.post(`/v1/agents/${agentId}/${path12}`, reason ? { reason } : {})
|
|
1436
1770
|
);
|
|
1437
1771
|
actionSpinner.succeed(`${agent.name} is ${resultingStatus}`);
|
|
1438
1772
|
} catch (error) {
|
|
1439
|
-
actionSpinner.fail(`Could not ${
|
|
1773
|
+
actionSpinner.fail(`Could not ${path12} the agent`);
|
|
1440
1774
|
throw error;
|
|
1441
1775
|
}
|
|
1442
1776
|
out();
|
|
@@ -2760,7 +3094,7 @@ function isLoosening(from, to) {
|
|
|
2760
3094
|
|
|
2761
3095
|
// src/commands/init.ts
|
|
2762
3096
|
import { readFileSync as readFileSync9 } from "node:fs";
|
|
2763
|
-
import
|
|
3097
|
+
import path11 from "node:path";
|
|
2764
3098
|
|
|
2765
3099
|
// src/integrate.ts
|
|
2766
3100
|
import { readFileSync as readFileSync8 } from "node:fs";
|
|
@@ -3556,32 +3890,30 @@ async function chooseMethod(items, options) {
|
|
|
3556
3890
|
out(` ${style.bold(item.key.padEnd(24))} ${style.grey(`${item.file}:${item.line}`)}`);
|
|
3557
3891
|
}
|
|
3558
3892
|
out();
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
out(style.amber(" Enter 1, 2, 3 or 4."));
|
|
3893
|
+
try {
|
|
3894
|
+
return await select("How would you like to handle them?", [
|
|
3895
|
+
{
|
|
3896
|
+
label: "I will change them myself",
|
|
3897
|
+
hint: "show me exactly what to change",
|
|
3898
|
+
value: "manual"
|
|
3899
|
+
},
|
|
3900
|
+
{
|
|
3901
|
+
label: "Use AI with my own Anthropic or OpenAI key",
|
|
3902
|
+
hint: "code goes only to that provider",
|
|
3903
|
+
value: "own"
|
|
3904
|
+
},
|
|
3905
|
+
{
|
|
3906
|
+
label: "Use Veefore AI",
|
|
3907
|
+
hint: options.hostedAvailable ? "code is sent to Veefore, then to its model provider" : "not enabled on this deployment",
|
|
3908
|
+
value: "veefore",
|
|
3909
|
+
disabled: !options.hostedAvailable
|
|
3910
|
+
},
|
|
3911
|
+
{ label: "Skip for now", value: "skip" }
|
|
3912
|
+
]);
|
|
3913
|
+
} catch (error) {
|
|
3914
|
+
if (error instanceof MenuCancelled) return "skip";
|
|
3915
|
+
throw error;
|
|
3583
3916
|
}
|
|
3584
|
-
return "manual";
|
|
3585
3917
|
}
|
|
3586
3918
|
function printManualInstructions(items) {
|
|
3587
3919
|
heading("Make these changes yourself");
|
|
@@ -3666,9 +3998,7 @@ function printDispatchInstructions(items, where) {
|
|
|
3666
3998
|
out(` ${style.cyan("import { guard } from '@veefore/sdk';")}`);
|
|
3667
3999
|
out();
|
|
3668
4000
|
out(` ${style.grey("// before: const result = await runTool(call);")}`);
|
|
3669
|
-
out(
|
|
3670
|
-
` ${style.cyan("const result = await guard(call.name, (_args: typeof call.args) => runTool(call))(call.args);")}`
|
|
3671
|
-
);
|
|
4001
|
+
out(` ${style.cyan("const result = await runTool(call);")}`);
|
|
3672
4002
|
}
|
|
3673
4003
|
out();
|
|
3674
4004
|
out(
|
|
@@ -3793,16 +4123,21 @@ async function chooseModel(method, context) {
|
|
|
3793
4123
|
async function pick(label, choices, preferred, interactive) {
|
|
3794
4124
|
if (preferred && choices.includes(preferred)) return preferred;
|
|
3795
4125
|
if (!interactive) return choices[0] ?? null;
|
|
3796
|
-
const
|
|
4126
|
+
const OTHER = "\0other";
|
|
3797
4127
|
out();
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
4128
|
+
let picked;
|
|
4129
|
+
try {
|
|
4130
|
+
picked = await select(label, [
|
|
4131
|
+
...choices.slice(0, 20).map((choice) => ({ label: choice, value: choice })),
|
|
4132
|
+
...choices.length > 20 ? [{ label: "Another\u2026", hint: `${choices.length - 20} more`, value: OTHER }] : []
|
|
4133
|
+
]);
|
|
4134
|
+
} catch (error) {
|
|
4135
|
+
if (error instanceof MenuCancelled) return null;
|
|
4136
|
+
throw error;
|
|
4137
|
+
}
|
|
4138
|
+
if (picked !== OTHER) return picked;
|
|
4139
|
+
const typed = (await ask(`${label} id:`)).trim();
|
|
4140
|
+
return choices.includes(typed) ? typed : null;
|
|
3806
4141
|
}
|
|
3807
4142
|
async function runAssist(files, choice, context) {
|
|
3808
4143
|
const accepted = [];
|
|
@@ -6551,120 +6886,570 @@ async function undoCommand(args, _flags) {
|
|
|
6551
6886
|
return undoLatest(process.cwd(), { force: args.includes("--force") });
|
|
6552
6887
|
}
|
|
6553
6888
|
|
|
6554
|
-
// src/
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
description: tool.description ?? `Detected ${sourceLabel(tool.source)} tool.`,
|
|
6570
|
-
// One capability per tool keeps authority explicit: granting the agent this
|
|
6571
|
-
// tool grants nothing else. `tool.<key>` satisfies `resource.action`.
|
|
6572
|
-
capability: `tool.${tool.key}`,
|
|
6573
|
-
riskLevel: tool.riskLevel,
|
|
6574
|
-
dangerous: tool.dangerous,
|
|
6575
|
-
destinations: tool.hosts,
|
|
6576
|
-
location: `${tool.location.file}:${tool.location.line}`,
|
|
6577
|
-
money: tool.money ?? null
|
|
6578
|
-
};
|
|
6579
|
-
}
|
|
6580
|
-
function printScan(scan) {
|
|
6581
|
-
out(
|
|
6582
|
-
`${symbol.pass} Scanned ${scan.filesScanned} file${scan.filesScanned === 1 ? "" : "s"}` + (scan.truncated ? style.amber(" (limit reached; findings may be incomplete)") : "") + style.grey(" \u2014 locally, nothing was uploaded")
|
|
6583
|
-
);
|
|
6584
|
-
if (scan.tools.length === 0) {
|
|
6585
|
-
out(`${symbol.warn} No tools found, so there is nothing for Veefore to decide on yet.`);
|
|
6586
|
-
out(style.grey(" Tools are found where they are declared for the model to call, not from"));
|
|
6587
|
-
out(
|
|
6588
|
-
style.grey(
|
|
6589
|
-
scan.framework.language === "python" ? " plain functions: @tool, @function_tool, @mcp.tool, Tool(name=\u2026), or a dict" : ' plain functions: tool({\u2026}), DynamicStructuredTool, { type: "function" },'
|
|
6590
|
-
)
|
|
6591
|
-
);
|
|
6592
|
-
out(
|
|
6593
|
-
style.grey(
|
|
6594
|
-
scan.framework.language === "python" ? " with name, description and an input schema." : " server.tool(\u2026), or an object with name, description and parameters."
|
|
6595
|
-
)
|
|
6596
|
-
);
|
|
6597
|
-
out(style.grey(" Register them in the console, or add them and run `veefore scan` again."));
|
|
6598
|
-
} else {
|
|
6599
|
-
out(`${symbol.pass} Found ${scan.tools.length} tool${scan.tools.length === 1 ? "" : "s"}:`);
|
|
6600
|
-
for (const tool of scan.tools) {
|
|
6601
|
-
const label = tool.riskLevel.padEnd(7);
|
|
6602
|
-
const risk = tool.riskLevel === "HIGH" ? style.red(label) : tool.riskLevel === "MEDIUM" ? style.amber(label) : style.green(label);
|
|
6603
|
-
out(
|
|
6604
|
-
` ${style.bold(tool.key.padEnd(24))} ${risk} ${style.grey(`${tool.location.file}:${tool.location.line}`)}`
|
|
6605
|
-
);
|
|
6606
|
-
if (tool.hosts.length > 0) out(style.grey(` calls ${tool.hosts.join(", ")}`));
|
|
6607
|
-
}
|
|
6889
|
+
// src/commands/logs.ts
|
|
6890
|
+
async function logsCommand(args, flags) {
|
|
6891
|
+
const context = await resolveContext(flags);
|
|
6892
|
+
const follow = args.includes("--follow") || args.includes("-f");
|
|
6893
|
+
const limit = Number(valueFor(args, "--limit") ?? 25);
|
|
6894
|
+
const decisionFilter = valueFor(args, "--decision");
|
|
6895
|
+
const agentFilter = valueFor(args, "--agent") ?? (args.includes("--all") ? void 0 : context.config.agentId);
|
|
6896
|
+
const initial = await fetchActions(context, {
|
|
6897
|
+
limit,
|
|
6898
|
+
decision: decisionFilter,
|
|
6899
|
+
agentId: agentFilter
|
|
6900
|
+
});
|
|
6901
|
+
if (flags.json && !follow) {
|
|
6902
|
+
printJson(initial);
|
|
6903
|
+
return 0;
|
|
6608
6904
|
}
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
|
|
6612
|
-
|
|
6613
|
-
);
|
|
6905
|
+
heading(`Decisions \u2014 ${context.config.environmentName} (${context.config.environmentType})`);
|
|
6906
|
+
renderActions(initial, flags.verbose);
|
|
6907
|
+
if (!follow) {
|
|
6908
|
+
out();
|
|
6909
|
+
out(style.grey(" Use --follow to stream new decisions as they happen."));
|
|
6910
|
+
out();
|
|
6911
|
+
return 0;
|
|
6614
6912
|
}
|
|
6913
|
+
return streamActions(context, {
|
|
6914
|
+
decision: decisionFilter,
|
|
6915
|
+
agentId: agentFilter,
|
|
6916
|
+
seen: new Set(initial.map((action) => action.id)),
|
|
6917
|
+
verbose: flags.verbose,
|
|
6918
|
+
json: flags.json
|
|
6919
|
+
});
|
|
6615
6920
|
}
|
|
6616
|
-
function
|
|
6921
|
+
async function devCommand(args, flags) {
|
|
6922
|
+
const context = await resolveContext(flags);
|
|
6923
|
+
const environment = await context.api.get(`/v1/environments/${context.config.environmentId}`);
|
|
6924
|
+
const gateway = await probeGateway(context.config.gatewayUrl);
|
|
6925
|
+
const agent = await context.api.get(
|
|
6926
|
+
`/v1/agents/${context.config.agentId}`
|
|
6927
|
+
);
|
|
6928
|
+
heading(`veefore dev \u2014 ${context.config.organizationName} \u203A ${environment.name}`);
|
|
6617
6929
|
out();
|
|
6618
|
-
out(style.bold("Veefore will set up"));
|
|
6619
6930
|
out(
|
|
6620
|
-
` ${symbol.
|
|
6931
|
+
` ${gateway.reachable ? symbol.pass : symbol.fail} Gateway ${gateway.reachable ? style.green("connected") : style.red("unreachable")} ${style.grey(context.config.gatewayUrl)}`
|
|
6621
6932
|
);
|
|
6622
6933
|
out(
|
|
6623
|
-
`
|
|
6934
|
+
` ${agent.status === "ACTIVE" ? symbol.pass : symbol.warn} Agent ${style.bold(agent.name)} ${decisionStyle(agent.status)}`
|
|
6624
6935
|
);
|
|
6625
|
-
if (plan.tools.length > 0) {
|
|
6626
|
-
out(
|
|
6627
|
-
` ${symbol.bullet} ${plan.tools.length} tool${plan.tools.length === 1 ? "" : "s"}, each with its own capability, authorized for this agent only`
|
|
6628
|
-
);
|
|
6629
|
-
const dangerous = plan.tools.filter((tool) => tool.dangerous).map((tool) => tool.key);
|
|
6630
|
-
if (dangerous.length > 0) {
|
|
6631
|
-
out(
|
|
6632
|
-
` ${style.grey(`flagged high impact (approval under the starter policy): ${dangerous.join(", ")}`)}`
|
|
6633
|
-
);
|
|
6634
|
-
}
|
|
6635
|
-
}
|
|
6636
6936
|
out(
|
|
6637
|
-
` ${
|
|
6937
|
+
` ${environment.gatewayMode === "ENFORCE" ? symbol.pass : symbol.warn} Mode ${decisionStyle(environment.status === "PAUSED" ? "PAUSED" : environment.gatewayMode)}`
|
|
6638
6938
|
);
|
|
6639
|
-
if (
|
|
6939
|
+
if (!gateway.reachable) {
|
|
6640
6940
|
out();
|
|
6641
6941
|
out(
|
|
6642
|
-
style.
|
|
6643
|
-
` Your agent keeps holding ${plan.agentHeldSecrets.join(", ")} for now. Veefore will decide every call
|
|
6644
|
-
and record it, but a call that skips the SDK is not stopped until those keys move into
|
|
6645
|
-
the Veefore credential broker. Setup does not read or move any secret.`
|
|
6646
|
-
)
|
|
6942
|
+
` ${symbol.fail} ${style.red("The gateway is not reachable, so no action can be permitted.")}`
|
|
6647
6943
|
);
|
|
6944
|
+
out(style.grey(" Start it with `npm run dev:gateway`, then re-run `veefore dev`."));
|
|
6945
|
+
out();
|
|
6946
|
+
return 1;
|
|
6648
6947
|
}
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
];
|
|
6654
|
-
for (const tool of plan.tools) {
|
|
6655
|
-
await createIfAbsent(
|
|
6656
|
-
() => api.post("/v1/capabilities", {
|
|
6657
|
-
key: tool.capability,
|
|
6658
|
-
name: `Use ${tool.name}`.slice(0, 200),
|
|
6659
|
-
description: tool.description.slice(0, 1e3),
|
|
6660
|
-
dangerous: tool.dangerous
|
|
6661
|
-
})
|
|
6948
|
+
if (environment.gatewayMode === "OBSERVE") {
|
|
6949
|
+
out();
|
|
6950
|
+
out(
|
|
6951
|
+
` ${symbol.info} ${style.cyan("OBSERVE mode:")} every decision is evaluated and recorded, but nothing is blocked.`
|
|
6662
6952
|
);
|
|
6953
|
+
out(style.grey(" This is the safe way to learn what your agent actually does."));
|
|
6663
6954
|
}
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
"
|
|
6667
|
-
|
|
6955
|
+
if (agent.isolated) {
|
|
6956
|
+
out();
|
|
6957
|
+
out(` ${symbol.fail} ${style.red("This agent is isolated by an emergency control.")}`);
|
|
6958
|
+
}
|
|
6959
|
+
out();
|
|
6960
|
+
out(style.grey(" Watching for decisions. Press Ctrl+C to stop."));
|
|
6961
|
+
out();
|
|
6962
|
+
const existing = await fetchActions(context, { limit: 5, agentId: context.config.agentId });
|
|
6963
|
+
if (existing.length === 0) {
|
|
6964
|
+
out(
|
|
6965
|
+
` ${symbol.info} No actions recorded yet. Call ${style.bold("agent.act({ tool: \u2026 })")} from your agent to see a decision here.`
|
|
6966
|
+
);
|
|
6967
|
+
out();
|
|
6968
|
+
} else {
|
|
6969
|
+
renderActions(existing, flags.verbose);
|
|
6970
|
+
}
|
|
6971
|
+
return streamActions(context, {
|
|
6972
|
+
agentId: args.includes("--all") ? void 0 : context.config.agentId,
|
|
6973
|
+
seen: new Set(existing.map((action) => action.id)),
|
|
6974
|
+
verbose: true,
|
|
6975
|
+
json: false,
|
|
6976
|
+
coach: true
|
|
6977
|
+
});
|
|
6978
|
+
}
|
|
6979
|
+
async function fetchActions(context, options) {
|
|
6980
|
+
const response = await context.api.get("/v1/actions", {
|
|
6981
|
+
limit: options.limit ?? 25,
|
|
6982
|
+
decision: options.decision,
|
|
6983
|
+
agent_id: options.agentId,
|
|
6984
|
+
environment_id: context.config.environmentId
|
|
6985
|
+
});
|
|
6986
|
+
return [...response.data].reverse();
|
|
6987
|
+
}
|
|
6988
|
+
function renderActions(actions, verbose) {
|
|
6989
|
+
if (actions.length === 0) {
|
|
6990
|
+
out(style.grey(" (no decisions yet)"));
|
|
6991
|
+
return;
|
|
6992
|
+
}
|
|
6993
|
+
if (!verbose) {
|
|
6994
|
+
table(
|
|
6995
|
+
[
|
|
6996
|
+
{ header: "WHEN", key: "when", width: 12 },
|
|
6997
|
+
{ header: "TOOL", key: "tool", width: 24 },
|
|
6998
|
+
{ header: "DECISION", key: "decision", width: 20 },
|
|
6999
|
+
{ header: "OUTCOME", key: "outcome", width: 24 },
|
|
7000
|
+
{ header: "MS", key: "latency", width: 6, align: "right" }
|
|
7001
|
+
],
|
|
7002
|
+
actions.map((action) => ({
|
|
7003
|
+
when: formatWhen(action.startedAt),
|
|
7004
|
+
tool: action.toolKey ?? action.action,
|
|
7005
|
+
decision: decisionStyle(effectiveDecision(action)),
|
|
7006
|
+
outcome: decisionStyle(action.outcome),
|
|
7007
|
+
latency: String(Math.round(action.latencyMs))
|
|
7008
|
+
}))
|
|
7009
|
+
);
|
|
7010
|
+
return;
|
|
7011
|
+
}
|
|
7012
|
+
for (const action of actions) renderAction(action);
|
|
7013
|
+
}
|
|
7014
|
+
function effectiveDecision(action) {
|
|
7015
|
+
return action.shadowDecision ?? action.decision ?? "UNKNOWN";
|
|
7016
|
+
}
|
|
7017
|
+
function renderAction(action, coach = false) {
|
|
7018
|
+
const decision = effectiveDecision(action);
|
|
7019
|
+
const icon = decision === "ALLOW" ? symbol.pass : decision === "BLOCK" ? symbol.fail : symbol.warn;
|
|
7020
|
+
const amount = action.amount === null ? "" : style.grey(` ${formatAmount(action.amount, action.currency)}`);
|
|
7021
|
+
out(
|
|
7022
|
+
`${icon} ${style.grey(new Date(action.startedAt).toISOString().slice(11, 19))} ${style.bold(action.toolKey ?? action.action)}${amount} ${decisionStyle(decision)} ${style.grey(`(${action.outcome})`)}`
|
|
7023
|
+
);
|
|
7024
|
+
if (action.denyReason) out(` ${action.denyReason}`);
|
|
7025
|
+
const facts = [];
|
|
7026
|
+
if (action.policyVersionNumber !== null) facts.push(`policy v${action.policyVersionNumber}`);
|
|
7027
|
+
if (action.matchedRuleIds.length > 0)
|
|
7028
|
+
facts.push(`${action.matchedRuleIds.length} rule(s) matched`);
|
|
7029
|
+
if (action.riskLevel) facts.push(`risk ${action.riskLevel}`);
|
|
7030
|
+
if (action.protocol !== "HTTP") facts.push(action.protocol);
|
|
7031
|
+
if (action.gatewayMode !== "ENFORCE") facts.push(`mode ${action.gatewayMode}`);
|
|
7032
|
+
facts.push(`${Math.round(action.latencyMs)}ms`);
|
|
7033
|
+
out(` ${style.grey(facts.join(" \xB7 "))}`);
|
|
7034
|
+
if (coach) {
|
|
7035
|
+
const hint = coachingFor(action);
|
|
7036
|
+
if (hint) out(` ${style.cyan("\u2192")} ${style.grey(hint)}`);
|
|
7037
|
+
}
|
|
7038
|
+
}
|
|
7039
|
+
function coachingFor(action) {
|
|
7040
|
+
const decision = effectiveDecision(action);
|
|
7041
|
+
if (action.gatewayMode === "OBSERVE" && decision !== "ALLOW") {
|
|
7042
|
+
return `In ENFORCE mode this would have been ${decision}. Simulate before switching.`;
|
|
7043
|
+
}
|
|
7044
|
+
switch (decision) {
|
|
7045
|
+
case "BLOCK":
|
|
7046
|
+
return action.matchedRuleIds.length > 0 ? "Blocked by a policy rule. Adjust the rule, or grant the capability if this is legitimate." : "Blocked before policy evaluation. Check the tool is registered and the agent is authorized for it.";
|
|
7047
|
+
case "REQUIRE_APPROVAL":
|
|
7048
|
+
return "Call actWithApproval() so your agent waits for the reviewer instead of failing.";
|
|
7049
|
+
case "LIMIT":
|
|
7050
|
+
return "The amount was capped by policy. Read limits.appliedAmount from the decision.";
|
|
7051
|
+
case "MASK":
|
|
7052
|
+
return "Some fields were masked before reaching the tool. Check limits.maskedFields.";
|
|
7053
|
+
case "STEP_UP_AUTH":
|
|
7054
|
+
return "A human must re-authenticate. Agents cannot satisfy step-up on their own.";
|
|
7055
|
+
default:
|
|
7056
|
+
return null;
|
|
7057
|
+
}
|
|
7058
|
+
}
|
|
7059
|
+
function formatAmount(minorUnits, currency) {
|
|
7060
|
+
const major = (minorUnits / 100).toLocaleString("en-IN", {
|
|
7061
|
+
minimumFractionDigits: 2,
|
|
7062
|
+
maximumFractionDigits: 2
|
|
7063
|
+
});
|
|
7064
|
+
return currency ? `${major} ${currency}` : major;
|
|
7065
|
+
}
|
|
7066
|
+
async function streamActions(context, options) {
|
|
7067
|
+
let interval = 1e3;
|
|
7068
|
+
let stopped = false;
|
|
7069
|
+
const stop = () => {
|
|
7070
|
+
stopped = true;
|
|
7071
|
+
};
|
|
7072
|
+
process.once("SIGINT", stop);
|
|
7073
|
+
process.once("SIGTERM", stop);
|
|
7074
|
+
let blocked = 0;
|
|
7075
|
+
let approvals = 0;
|
|
7076
|
+
let allowed = 0;
|
|
7077
|
+
while (!stopped) {
|
|
7078
|
+
let fresh = [];
|
|
7079
|
+
try {
|
|
7080
|
+
const recent = await fetchActions(context, {
|
|
7081
|
+
limit: 50,
|
|
7082
|
+
decision: options.decision,
|
|
7083
|
+
agentId: options.agentId
|
|
7084
|
+
});
|
|
7085
|
+
fresh = recent.filter((action) => !options.seen.has(action.id));
|
|
7086
|
+
for (const action of fresh) options.seen.add(action.id);
|
|
7087
|
+
} catch (error) {
|
|
7088
|
+
out(
|
|
7089
|
+
`${symbol.warn} ${style.amber("Lost contact with the control plane;")} ${style.grey(error instanceof Error ? error.message : String(error))}`
|
|
7090
|
+
);
|
|
7091
|
+
interval = Math.min(interval * 2, 15e3);
|
|
7092
|
+
await delay(interval);
|
|
7093
|
+
continue;
|
|
7094
|
+
}
|
|
7095
|
+
for (const action of fresh) {
|
|
7096
|
+
if (options.json) {
|
|
7097
|
+
out(JSON.stringify(action));
|
|
7098
|
+
} else {
|
|
7099
|
+
renderAction(action, options.coach);
|
|
7100
|
+
}
|
|
7101
|
+
const decision = effectiveDecision(action);
|
|
7102
|
+
if (decision === "BLOCK") blocked += 1;
|
|
7103
|
+
else if (decision === "REQUIRE_APPROVAL") approvals += 1;
|
|
7104
|
+
else allowed += 1;
|
|
7105
|
+
}
|
|
7106
|
+
interval = fresh.length > 0 ? 1e3 : Math.min(interval * 1.5, 5e3);
|
|
7107
|
+
await delay(interval);
|
|
7108
|
+
}
|
|
7109
|
+
if (!options.json) {
|
|
7110
|
+
out();
|
|
7111
|
+
out(
|
|
7112
|
+
` ${style.green(`${allowed} allowed`)} ${style.amber(`${approvals} needing approval`)} ${style.red(`${blocked} blocked`)}`
|
|
7113
|
+
);
|
|
7114
|
+
out();
|
|
7115
|
+
}
|
|
7116
|
+
return 0;
|
|
7117
|
+
}
|
|
7118
|
+
function delay(ms) {
|
|
7119
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7120
|
+
}
|
|
7121
|
+
|
|
7122
|
+
// src/commands/test.ts
|
|
7123
|
+
async function testCommand(args, flags) {
|
|
7124
|
+
const context = await resolveContext(flags);
|
|
7125
|
+
const toolFilter = valueFor(args, "--tool");
|
|
7126
|
+
heading(
|
|
7127
|
+
`Enforcement test \u2014 ${context.config.environmentName} (${context.config.environmentType})`
|
|
7128
|
+
);
|
|
7129
|
+
const results = [];
|
|
7130
|
+
const record = (check, passed, detail) => {
|
|
7131
|
+
results.push({ check, passed, detail });
|
|
7132
|
+
out(`${passed ? symbol.pass : symbol.fail} ${check} ${style.grey(detail)}`);
|
|
7133
|
+
};
|
|
7134
|
+
const gateway = await probeGateway(context.config.gatewayUrl);
|
|
7135
|
+
record(
|
|
7136
|
+
"Gateway reachable",
|
|
7137
|
+
gateway.reachable,
|
|
7138
|
+
gateway.reachable ? `${context.config.gatewayUrl} \u2014 posture ${gateway.posture ?? "unknown"}` : gateway.error ?? "unreachable"
|
|
7139
|
+
);
|
|
7140
|
+
const identities = await context.api.get(`/v1/agents/${context.config.agentId}/identities`).catch(() => []);
|
|
7141
|
+
const activeIdentity = identities.find((identity) => identity.status === "ACTIVE");
|
|
7142
|
+
record(
|
|
7143
|
+
"Agent identity active",
|
|
7144
|
+
Boolean(activeIdentity),
|
|
7145
|
+
activeIdentity ? `generation ${activeIdentity.generation}, expires ${activeIdentity.expiresAt.slice(0, 10)}` : "no ACTIVE identity \u2014 the agent cannot authenticate"
|
|
7146
|
+
);
|
|
7147
|
+
const policies = await context.api.get("/v1/policies", { workspace_id: context.config.workspaceId });
|
|
7148
|
+
const effective = policies.filter(
|
|
7149
|
+
(policy) => policy.status === "ACTIVE" && policy.bindings.length > 0
|
|
7150
|
+
);
|
|
7151
|
+
record(
|
|
7152
|
+
"Active, bound policy exists",
|
|
7153
|
+
effective.length > 0,
|
|
7154
|
+
effective.length > 0 ? effective.map((policy) => policy.name).join(", ") : "no active policy is bound to a scope, so no rule can match"
|
|
7155
|
+
);
|
|
7156
|
+
let caseTotal = 0;
|
|
7157
|
+
let caseFailed = 0;
|
|
7158
|
+
for (const policy of policies) {
|
|
7159
|
+
const result = await context.api.post(`/v1/policies/${policy.id}/tests/run`, {}).catch(() => null);
|
|
7160
|
+
if (!result) continue;
|
|
7161
|
+
caseTotal += result.passed + result.failed;
|
|
7162
|
+
caseFailed += result.failed;
|
|
7163
|
+
}
|
|
7164
|
+
record(
|
|
7165
|
+
"Policy test cases",
|
|
7166
|
+
caseFailed === 0,
|
|
7167
|
+
caseTotal === 0 ? "no test cases recorded \u2014 add cases so rule changes are provable" : `${caseTotal - caseFailed}/${caseTotal} passing`
|
|
7168
|
+
);
|
|
7169
|
+
const tools = await resolveAuthorizedTools(context, toolFilter);
|
|
7170
|
+
if (tools.length === 0) {
|
|
7171
|
+
record(
|
|
7172
|
+
"Authorized tools",
|
|
7173
|
+
false,
|
|
7174
|
+
"this agent is authorized for no tools, so it cannot do anything"
|
|
7175
|
+
);
|
|
7176
|
+
} else {
|
|
7177
|
+
record("Authorized tools", true, tools.map((tool) => tool.key).join(", "));
|
|
7178
|
+
}
|
|
7179
|
+
heading("Decision probe (dry run \u2014 nothing is executed)");
|
|
7180
|
+
const probes = [];
|
|
7181
|
+
for (const tool of tools) {
|
|
7182
|
+
const probeSpinner = spinner(`Evaluating ${tool.key}`);
|
|
7183
|
+
try {
|
|
7184
|
+
const outcome = await context.api.post("/v1/policies/simulate", {
|
|
7185
|
+
agent_id: context.config.agentId,
|
|
7186
|
+
tool: tool.key,
|
|
7187
|
+
capability: tool.requiredCapability,
|
|
7188
|
+
environment_id: context.config.environmentId,
|
|
7189
|
+
parameters: {}
|
|
7190
|
+
});
|
|
7191
|
+
probeSpinner.succeed(`${tool.key} \u2192 ${outcome.decision}`);
|
|
7192
|
+
probes.push({ tool, outcome });
|
|
7193
|
+
} catch (error) {
|
|
7194
|
+
probeSpinner.fail(`${tool.key} could not be evaluated`);
|
|
7195
|
+
probes.push({
|
|
7196
|
+
tool,
|
|
7197
|
+
outcome: null,
|
|
7198
|
+
error: error instanceof Error ? error.message : String(error)
|
|
7199
|
+
});
|
|
7200
|
+
}
|
|
7201
|
+
}
|
|
7202
|
+
out();
|
|
7203
|
+
table(
|
|
7204
|
+
[
|
|
7205
|
+
{ header: "TOOL", key: "tool", width: 24 },
|
|
7206
|
+
{ header: "RISK", key: "risk", width: 18 },
|
|
7207
|
+
{ header: "DECISION", key: "decision", width: 20 },
|
|
7208
|
+
{ header: "MATCHED RULE", key: "rule", width: 32 }
|
|
7209
|
+
],
|
|
7210
|
+
probes.map((probe) => ({
|
|
7211
|
+
tool: probe.tool.dangerous ? `${probe.tool.key} ${style.red("(dangerous)")}` : probe.tool.key,
|
|
7212
|
+
risk: decisionStyle(probe.tool.riskLevel),
|
|
7213
|
+
decision: probe.outcome ? decisionStyle(probe.outcome.decision) : style.red("ERROR"),
|
|
7214
|
+
rule: probe.outcome?.matchedRules[0]?.name ?? style.grey(probe.error ?? "default decision")
|
|
7215
|
+
}))
|
|
7216
|
+
);
|
|
7217
|
+
const unguarded = probes.filter(
|
|
7218
|
+
(probe) => probe.tool.dangerous && probe.outcome?.decision === "ALLOW" && probe.outcome.matchedRules.length === 0
|
|
7219
|
+
);
|
|
7220
|
+
out();
|
|
7221
|
+
record(
|
|
7222
|
+
"Dangerous tools are guarded",
|
|
7223
|
+
unguarded.length === 0,
|
|
7224
|
+
unguarded.length === 0 ? "every dangerous tool is either refused or matched by an explicit rule" : `${unguarded.map((probe) => probe.tool.key).join(", ")} allowed by the default decision with no rule`
|
|
7225
|
+
);
|
|
7226
|
+
const errored = probes.filter((probe) => probe.outcome === null);
|
|
7227
|
+
record(
|
|
7228
|
+
"All evaluations completed",
|
|
7229
|
+
errored.length === 0,
|
|
7230
|
+
errored.length === 0 ? `${probes.length} evaluated` : `${errored.length} failed to evaluate`
|
|
7231
|
+
);
|
|
7232
|
+
const impure = probes.filter((probe) => probe.outcome && probe.outcome.sideEffectFree !== true);
|
|
7233
|
+
record(
|
|
7234
|
+
"Simulation was side-effect free",
|
|
7235
|
+
impure.length === 0,
|
|
7236
|
+
impure.length === 0 ? "no action records were written" : "the server reported side effects"
|
|
7237
|
+
);
|
|
7238
|
+
const failed = results.filter((result) => !result.passed);
|
|
7239
|
+
out();
|
|
7240
|
+
out(
|
|
7241
|
+
failed.length === 0 ? ` ${symbol.pass} ${style.green(`${results.length} check(s) passing.`)}` : ` ${symbol.fail} ${style.red(`${failed.length} of ${results.length} check(s) failing.`)}`
|
|
7242
|
+
);
|
|
7243
|
+
if (failed.length > 0) {
|
|
7244
|
+
out();
|
|
7245
|
+
for (const failure of failed) out(` ${symbol.fail} ${failure.check}: ${failure.detail}`);
|
|
7246
|
+
}
|
|
7247
|
+
out();
|
|
7248
|
+
if (flags.json) printJson({ results, probes });
|
|
7249
|
+
return failed.length > 0 ? 1 : 0;
|
|
7250
|
+
}
|
|
7251
|
+
async function resolveAuthorizedTools(context, filter) {
|
|
7252
|
+
const tools = await context.api.get("/v1/tools", {
|
|
7253
|
+
workspace_id: context.config.workspaceId,
|
|
7254
|
+
agent_id: context.config.agentId,
|
|
7255
|
+
limit: 100
|
|
7256
|
+
});
|
|
7257
|
+
const candidates = tools.items.filter(
|
|
7258
|
+
(tool) => tool.status !== "DISABLED" && (tool.environmentId === null || tool.environmentId === context.config.environmentId)
|
|
7259
|
+
);
|
|
7260
|
+
if (!filter) return candidates;
|
|
7261
|
+
const match = candidates.filter((tool) => tool.key === filter);
|
|
7262
|
+
if (match.length === 0) {
|
|
7263
|
+
throw new CliError(
|
|
7264
|
+
`Tool "${filter}" is not available to this agent in ${context.config.environmentName}.`,
|
|
7265
|
+
`Available: ${candidates.map((tool) => tool.key).join(", ") || "none"}`
|
|
7266
|
+
);
|
|
7267
|
+
}
|
|
7268
|
+
return match;
|
|
7269
|
+
}
|
|
7270
|
+
|
|
7271
|
+
// src/panel.ts
|
|
7272
|
+
import { homedir as homedir2 } from "node:os";
|
|
7273
|
+
import path10 from "node:path";
|
|
7274
|
+
|
|
7275
|
+
// src/version.ts
|
|
7276
|
+
var CLI_VERSION = "1.2.0";
|
|
7277
|
+
|
|
7278
|
+
// src/panel.ts
|
|
7279
|
+
function welcomeSource(flags = {}) {
|
|
7280
|
+
return () => {
|
|
7281
|
+
const project = readProjectConfig();
|
|
7282
|
+
const controlPlaneUrl = project?.controlPlaneUrl ?? resolveControlPlaneUrl(flags.controlPlaneUrl);
|
|
7283
|
+
const session = loadSession(controlPlaneUrl);
|
|
7284
|
+
return {
|
|
7285
|
+
version: CLI_VERSION,
|
|
7286
|
+
facts: [
|
|
7287
|
+
["Folder", shorten(tilde(process.cwd()), 34)],
|
|
7288
|
+
["Signed in", session ? session.email : style.grey("not yet")],
|
|
7289
|
+
[
|
|
7290
|
+
"Project",
|
|
7291
|
+
project ? `${project.agentName} \xB7 ${project.environmentName}` : style.grey("not set up here")
|
|
7292
|
+
],
|
|
7293
|
+
["Veefore", hostOf(controlPlaneUrl)]
|
|
7294
|
+
]
|
|
7295
|
+
};
|
|
7296
|
+
};
|
|
7297
|
+
}
|
|
7298
|
+
function tilde(dir) {
|
|
7299
|
+
const home = homedir2();
|
|
7300
|
+
return dir === home || dir.startsWith(`${home}${path10.sep}`) ? `~${dir.slice(home.length)}` : dir;
|
|
7301
|
+
}
|
|
7302
|
+
function hostOf(url) {
|
|
7303
|
+
try {
|
|
7304
|
+
return new URL(url).host;
|
|
7305
|
+
} catch {
|
|
7306
|
+
return url;
|
|
7307
|
+
}
|
|
7308
|
+
}
|
|
7309
|
+
function shorten(text, width) {
|
|
7310
|
+
return text.length <= width ? text : `\u2026${text.slice(text.length - width + 1)}`;
|
|
7311
|
+
}
|
|
7312
|
+
|
|
7313
|
+
// src/report.ts
|
|
7314
|
+
function reportError(error, flags) {
|
|
7315
|
+
if (error instanceof CliError) {
|
|
7316
|
+
err();
|
|
7317
|
+
err(`${style.red("\u2717")} ${error.message}`);
|
|
7318
|
+
if (error.hint) err(style.grey(` ${error.hint}`));
|
|
7319
|
+
err();
|
|
7320
|
+
return error.exitCode;
|
|
7321
|
+
}
|
|
7322
|
+
if (error instanceof CliApiError) {
|
|
7323
|
+
err();
|
|
7324
|
+
reportApiError(error);
|
|
7325
|
+
err();
|
|
7326
|
+
return 1;
|
|
7327
|
+
}
|
|
7328
|
+
err();
|
|
7329
|
+
err(`${style.red("\u2717")} ${error instanceof Error ? error.message : String(error)}`);
|
|
7330
|
+
if (flags.verbose && error instanceof Error && error.stack) {
|
|
7331
|
+
err(style.grey(error.stack));
|
|
7332
|
+
} else {
|
|
7333
|
+
err(style.grey(" Re-run with --verbose for the stack trace."));
|
|
7334
|
+
}
|
|
7335
|
+
err();
|
|
7336
|
+
return 1;
|
|
7337
|
+
}
|
|
7338
|
+
|
|
7339
|
+
// src/setup.ts
|
|
7340
|
+
import { generateKeyPairSync } from "node:crypto";
|
|
7341
|
+
function buildPlan(scan, options = {}) {
|
|
7342
|
+
return {
|
|
7343
|
+
agentName: (options.agentName ?? scan.projectName ?? "my-agent").slice(0, 200),
|
|
7344
|
+
tools: scan.tools.map(planTool),
|
|
7345
|
+
agentHeldSecrets: scan.secrets.filter((s) => s.kind === "service").map((s) => s.name),
|
|
7346
|
+
modelSecrets: scan.secrets.filter((s) => s.kind === "model").map((s) => s.name),
|
|
7347
|
+
extraCapabilities: options.extraCapabilities ?? []
|
|
7348
|
+
};
|
|
7349
|
+
}
|
|
7350
|
+
function planTool(tool) {
|
|
7351
|
+
return {
|
|
7352
|
+
key: tool.key,
|
|
7353
|
+
name: tool.name,
|
|
7354
|
+
description: tool.description ?? `Detected ${sourceLabel(tool.source)} tool.`,
|
|
7355
|
+
// One capability per tool keeps authority explicit: granting the agent this
|
|
7356
|
+
// tool grants nothing else. `tool.<key>` satisfies `resource.action`.
|
|
7357
|
+
capability: `tool.${tool.key}`,
|
|
7358
|
+
riskLevel: tool.riskLevel,
|
|
7359
|
+
dangerous: tool.dangerous,
|
|
7360
|
+
destinations: tool.hosts,
|
|
7361
|
+
location: `${tool.location.file}:${tool.location.line}`,
|
|
7362
|
+
money: tool.money ?? null
|
|
7363
|
+
};
|
|
7364
|
+
}
|
|
7365
|
+
function printScan(scan) {
|
|
7366
|
+
out(
|
|
7367
|
+
`${symbol.pass} Scanned ${scan.filesScanned} file${scan.filesScanned === 1 ? "" : "s"}` + (scan.truncated ? style.amber(" (limit reached; findings may be incomplete)") : "") + style.grey(" \u2014 locally, nothing was uploaded")
|
|
7368
|
+
);
|
|
7369
|
+
if (scan.tools.length === 0) {
|
|
7370
|
+
out(`${symbol.warn} No tools found, so there is nothing for Veefore to decide on yet.`);
|
|
7371
|
+
out(style.grey(" Tools are found where they are declared for the model to call, not from"));
|
|
7372
|
+
out(
|
|
7373
|
+
style.grey(
|
|
7374
|
+
scan.framework.language === "python" ? " plain functions: @tool, @function_tool, @mcp.tool, Tool(name=\u2026), or a dict" : ' plain functions: tool({\u2026}), DynamicStructuredTool, { type: "function" },'
|
|
7375
|
+
)
|
|
7376
|
+
);
|
|
7377
|
+
out(
|
|
7378
|
+
style.grey(
|
|
7379
|
+
scan.framework.language === "python" ? " with name, description and an input schema." : " server.tool(\u2026), or an object with name, description and parameters."
|
|
7380
|
+
)
|
|
7381
|
+
);
|
|
7382
|
+
out(style.grey(" Register them in the console, or add them and run `veefore scan` again."));
|
|
7383
|
+
} else {
|
|
7384
|
+
out(`${symbol.pass} Found ${scan.tools.length} tool${scan.tools.length === 1 ? "" : "s"}:`);
|
|
7385
|
+
for (const tool of scan.tools) {
|
|
7386
|
+
const label = tool.riskLevel.padEnd(7);
|
|
7387
|
+
const risk = tool.riskLevel === "HIGH" ? style.red(label) : tool.riskLevel === "MEDIUM" ? style.amber(label) : style.green(label);
|
|
7388
|
+
out(
|
|
7389
|
+
` ${style.bold(tool.key.padEnd(24))} ${risk} ${style.grey(`${tool.location.file}:${tool.location.line}`)}`
|
|
7390
|
+
);
|
|
7391
|
+
if (tool.hosts.length > 0) out(style.grey(` calls ${tool.hosts.join(", ")}`));
|
|
7392
|
+
}
|
|
7393
|
+
}
|
|
7394
|
+
const service = scan.secrets.filter((s) => s.kind === "service");
|
|
7395
|
+
if (service.length > 0) {
|
|
7396
|
+
out(
|
|
7397
|
+
`${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)")}`
|
|
7398
|
+
);
|
|
7399
|
+
}
|
|
7400
|
+
}
|
|
7401
|
+
function printPlan2(plan, environment) {
|
|
7402
|
+
out();
|
|
7403
|
+
out(style.bold("Veefore will set up"));
|
|
7404
|
+
out(
|
|
7405
|
+
` ${symbol.bullet} Agent ${style.bold(plan.agentName)} in ${environment.name} (${environment.type})`
|
|
7406
|
+
);
|
|
7407
|
+
out(
|
|
7408
|
+
` ${style.grey("with its own key pair, generated here. The private key never leaves this machine.")}`
|
|
7409
|
+
);
|
|
7410
|
+
if (plan.tools.length > 0) {
|
|
7411
|
+
out(
|
|
7412
|
+
` ${symbol.bullet} ${plan.tools.length} tool${plan.tools.length === 1 ? "" : "s"}, each with its own capability, authorized for this agent only`
|
|
7413
|
+
);
|
|
7414
|
+
const dangerous = plan.tools.filter((tool) => tool.dangerous).map((tool) => tool.key);
|
|
7415
|
+
if (dangerous.length > 0) {
|
|
7416
|
+
out(
|
|
7417
|
+
` ${style.grey(`flagged high impact (approval under the starter policy): ${dangerous.join(", ")}`)}`
|
|
7418
|
+
);
|
|
7419
|
+
}
|
|
7420
|
+
}
|
|
7421
|
+
out(
|
|
7422
|
+
` ${symbol.bullet} A starter policy, in ${style.bold("OBSERVE")} mode: decisions are recorded, nothing is blocked yet`
|
|
7423
|
+
);
|
|
7424
|
+
if (plan.agentHeldSecrets.length > 0) {
|
|
7425
|
+
out();
|
|
7426
|
+
out(
|
|
7427
|
+
style.grey(
|
|
7428
|
+
` Your agent keeps holding ${plan.agentHeldSecrets.join(", ")} for now. Veefore will decide every call
|
|
7429
|
+
and record it, but a call that skips the SDK is not stopped until those keys move into
|
|
7430
|
+
the Veefore credential broker. Setup does not read or move any secret.`
|
|
7431
|
+
)
|
|
7432
|
+
);
|
|
7433
|
+
}
|
|
7434
|
+
}
|
|
7435
|
+
async function applyPlan(api, plan, scope, report = (line) => out(line), options = {}) {
|
|
7436
|
+
const capabilityKeys = [
|
|
7437
|
+
.../* @__PURE__ */ new Set([...plan.tools.map((t) => t.capability), ...plan.extraCapabilities])
|
|
7438
|
+
];
|
|
7439
|
+
for (const tool of plan.tools) {
|
|
7440
|
+
await createIfAbsent(
|
|
7441
|
+
() => api.post("/v1/capabilities", {
|
|
7442
|
+
key: tool.capability,
|
|
7443
|
+
name: `Use ${tool.name}`.slice(0, 200),
|
|
7444
|
+
description: tool.description.slice(0, 1e3),
|
|
7445
|
+
dangerous: tool.dangerous
|
|
7446
|
+
})
|
|
7447
|
+
);
|
|
7448
|
+
}
|
|
7449
|
+
const keys = generateKeyPairSync("ed25519");
|
|
7450
|
+
const publicKey = keys.publicKey.export({ type: "spki", format: "der" }).toString(
|
|
7451
|
+
"base64"
|
|
7452
|
+
);
|
|
6668
7453
|
const privateKey = keys.privateKey.export({ type: "pkcs8", format: "der" }).toString(
|
|
6669
7454
|
"base64"
|
|
6670
7455
|
);
|
|
@@ -6857,7 +7642,7 @@ var DEFAULT_GATEWAY = "http://localhost:4010";
|
|
|
6857
7642
|
async function initCommand(args, flags) {
|
|
6858
7643
|
const options = parseInitArgs(args);
|
|
6859
7644
|
const existing = readProjectConfig();
|
|
6860
|
-
if (existing && !options.force &&
|
|
7645
|
+
if (existing && !options.force && isInteractive() && !flags.json) {
|
|
6861
7646
|
return projectHome(existing, flags);
|
|
6862
7647
|
}
|
|
6863
7648
|
if (existing && !options.force) {
|
|
@@ -6898,20 +7683,34 @@ async function initCommand(args, flags) {
|
|
|
6898
7683
|
}
|
|
6899
7684
|
if (!session) throw new CliError("Sign-in did not produce a usable session.");
|
|
6900
7685
|
const api = new ApiClient({ controlPlaneUrl, token: session.token });
|
|
6901
|
-
|
|
6902
|
-
const
|
|
6903
|
-
const
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
);
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
7686
|
+
let me = await api.get("/v1/auth/session");
|
|
7687
|
+
const STEPS = ["organization", "workspace", "environment"];
|
|
7688
|
+
const asked = /* @__PURE__ */ new Map();
|
|
7689
|
+
let organization;
|
|
7690
|
+
let workspace;
|
|
7691
|
+
let environment;
|
|
7692
|
+
for (let at = 0; at < STEPS.length; ) {
|
|
7693
|
+
const step = STEPS[at];
|
|
7694
|
+
const answer = step === "organization" ? await selectOrganization(api, me, options, flags) : step === "workspace" ? await selectWorkspace(api, me, options, flags) : await selectEnvironment(api, me, workspace.id, options, flags);
|
|
7695
|
+
if (answer === BACK) {
|
|
7696
|
+
let previous = at - 1;
|
|
7697
|
+
while (previous >= 0 && !asked.get(STEPS[previous])) previous -= 1;
|
|
7698
|
+
if (previous < 0) {
|
|
7699
|
+
throw new MenuCancelled();
|
|
7700
|
+
}
|
|
7701
|
+
at = previous;
|
|
7702
|
+
continue;
|
|
7703
|
+
}
|
|
7704
|
+
asked.set(step, answer.asked);
|
|
7705
|
+
if (step === "organization") {
|
|
7706
|
+
organization = answer.value;
|
|
7707
|
+
if (answer.asked) me = await api.get("/v1/auth/session");
|
|
7708
|
+
} else if (step === "workspace") {
|
|
7709
|
+
workspace = answer.value;
|
|
7710
|
+
} else {
|
|
7711
|
+
environment = answer.value;
|
|
6914
7712
|
}
|
|
7713
|
+
at += 1;
|
|
6915
7714
|
}
|
|
6916
7715
|
heading("Looking at your project");
|
|
6917
7716
|
const scan = scanProject(process.cwd());
|
|
@@ -6920,15 +7719,41 @@ async function initCommand(args, flags) {
|
|
|
6920
7719
|
agentName: options.agentName,
|
|
6921
7720
|
extraCapabilities: options.capabilities
|
|
6922
7721
|
});
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
7722
|
+
for (; ; ) {
|
|
7723
|
+
if (!options.agentName && !flags.yes) {
|
|
7724
|
+
const name = await askAgentName(plan.agentName);
|
|
7725
|
+
if (name === BACK) {
|
|
7726
|
+
const again = await selectEnvironment(api, me, workspace.id, options, flags);
|
|
7727
|
+
if (again !== BACK) environment = again.value;
|
|
7728
|
+
continue;
|
|
7729
|
+
}
|
|
7730
|
+
plan.agentName = name;
|
|
7731
|
+
}
|
|
7732
|
+
printPlan2(plan, environment);
|
|
7733
|
+
out();
|
|
7734
|
+
const proceed = await confirm("Set this up?", { yes: flags.yes });
|
|
7735
|
+
if (proceed) break;
|
|
7736
|
+
if (flags.yes || options.agentName) {
|
|
7737
|
+
out(style.grey("Nothing was created. Run `veefore init` again when you are ready."));
|
|
7738
|
+
return 1;
|
|
7739
|
+
}
|
|
7740
|
+
const next = await select(
|
|
7741
|
+
"What would you like to do?",
|
|
7742
|
+
[
|
|
7743
|
+
{ label: "Change the agent name", value: "name" },
|
|
7744
|
+
{ label: "Choose a different environment", value: "environment" },
|
|
7745
|
+
{ label: "Leave setup", value: "leave" }
|
|
7746
|
+
],
|
|
7747
|
+
{ initial: 0 }
|
|
7748
|
+
);
|
|
7749
|
+
if (next === "leave") {
|
|
7750
|
+
out(style.grey("Nothing was created. Run `veefore init` again when you are ready."));
|
|
7751
|
+
return 1;
|
|
7752
|
+
}
|
|
7753
|
+
if (next === "environment") {
|
|
7754
|
+
const again = await selectEnvironment(api, me, workspace.id, options, flags);
|
|
7755
|
+
if (again !== BACK) environment = again.value;
|
|
7756
|
+
}
|
|
6932
7757
|
}
|
|
6933
7758
|
heading("Setting up");
|
|
6934
7759
|
let created;
|
|
@@ -7096,7 +7921,7 @@ async function initCommand(args, flags) {
|
|
|
7096
7921
|
}
|
|
7097
7922
|
function previousApprovalMode(dir = process.cwd()) {
|
|
7098
7923
|
try {
|
|
7099
|
-
const text = readFileSync9(
|
|
7924
|
+
const text = readFileSync9(path11.join(dir, ".env.local"), "utf8");
|
|
7100
7925
|
const value = /^\s*VEEFORE_APPROVAL_MODE\s*=\s*['"]?(\w+)/m.exec(text)?.[1]?.toLowerCase();
|
|
7101
7926
|
return value === "wait" || value === "throw" ? value : null;
|
|
7102
7927
|
} catch {
|
|
@@ -7142,35 +7967,28 @@ async function selectOrganization(api, me, options, flags) {
|
|
|
7142
7967
|
`Available: ${organizations.map((org) => org.id).join(", ")}`
|
|
7143
7968
|
);
|
|
7144
7969
|
}
|
|
7145
|
-
return match;
|
|
7970
|
+
return { value: match, asked: false };
|
|
7146
7971
|
}
|
|
7147
7972
|
if (organizations.length === 1 || flags.yes) {
|
|
7148
7973
|
const chosen2 = organizations[0];
|
|
7149
7974
|
if (!chosen2) throw new CliError("Your account is not a member of any organization.");
|
|
7150
7975
|
out(`${symbol.pass} Organization: ${style.bold(chosen2.name)}`);
|
|
7151
|
-
return chosen2;
|
|
7976
|
+
return { value: chosen2, asked: false };
|
|
7152
7977
|
}
|
|
7153
7978
|
out();
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
organizations.map((org, index) => ({
|
|
7163
|
-
index: String(index + 1),
|
|
7164
|
-
name: org.name,
|
|
7165
|
-
role: org.roleKey,
|
|
7166
|
-
id: org.id
|
|
7167
|
-
}))
|
|
7979
|
+
const chosen = await select(
|
|
7980
|
+
"Which organization?",
|
|
7981
|
+
organizations.map((org) => ({
|
|
7982
|
+
label: org.name,
|
|
7983
|
+
hint: `${org.roleKey} \xB7 ${org.id}`,
|
|
7984
|
+
value: org
|
|
7985
|
+
})),
|
|
7986
|
+
{ quiet: true, back: true, backLabel: "Back" }
|
|
7168
7987
|
);
|
|
7169
|
-
|
|
7170
|
-
if (!chosen) throw new CliError("No organization selected.");
|
|
7988
|
+
if (chosen === BACK) return BACK;
|
|
7171
7989
|
await api.post("/v1/auth/scope", { organization_id: chosen.id });
|
|
7172
7990
|
out(`${symbol.pass} Organization: ${style.bold(chosen.name)}`);
|
|
7173
|
-
return chosen;
|
|
7991
|
+
return { value: chosen, asked: true };
|
|
7174
7992
|
}
|
|
7175
7993
|
async function selectWorkspace(api, me, options, flags) {
|
|
7176
7994
|
const workspaces = me.workspaces.length > 0 ? me.workspaces : await api.get("/v1/workspaces");
|
|
@@ -7183,31 +8001,26 @@ async function selectWorkspace(api, me, options, flags) {
|
|
|
7183
8001
|
if (options.workspaceId) {
|
|
7184
8002
|
const match = workspaces.find((workspace) => workspace.id === options.workspaceId);
|
|
7185
8003
|
if (!match) throw new CliError(`Workspace ${options.workspaceId} was not found.`);
|
|
7186
|
-
return match;
|
|
8004
|
+
return { value: match, asked: false };
|
|
7187
8005
|
}
|
|
7188
8006
|
if (workspaces.length === 1 || flags.yes) {
|
|
7189
8007
|
const chosen2 = workspaces[0];
|
|
7190
8008
|
out(`${symbol.pass} Workspace: ${style.bold(chosen2.name)}`);
|
|
7191
|
-
return chosen2;
|
|
8009
|
+
return { value: chosen2, asked: false };
|
|
7192
8010
|
}
|
|
7193
8011
|
out();
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
index: String(index + 1),
|
|
7203
|
-
name: workspace.name,
|
|
7204
|
-
id: workspace.id
|
|
7205
|
-
}))
|
|
8012
|
+
const chosen = await select(
|
|
8013
|
+
"Which workspace?",
|
|
8014
|
+
workspaces.map((workspace) => ({
|
|
8015
|
+
label: workspace.name,
|
|
8016
|
+
hint: workspace.id,
|
|
8017
|
+
value: workspace
|
|
8018
|
+
})),
|
|
8019
|
+
{ quiet: true, back: true }
|
|
7206
8020
|
);
|
|
7207
|
-
|
|
7208
|
-
if (!chosen) throw new CliError("No workspace selected.");
|
|
8021
|
+
if (chosen === BACK) return BACK;
|
|
7209
8022
|
out(`${symbol.pass} Workspace: ${style.bold(chosen.name)}`);
|
|
7210
|
-
return chosen;
|
|
8023
|
+
return { value: chosen, asked: true };
|
|
7211
8024
|
}
|
|
7212
8025
|
async function selectEnvironment(api, me, workspaceId, options, flags) {
|
|
7213
8026
|
const all = me.environments.length > 0 ? me.environments : await api.get("/v1/environments", { workspace_id: workspaceId });
|
|
@@ -7221,35 +8034,61 @@ async function selectEnvironment(api, me, workspaceId, options, flags) {
|
|
|
7221
8034
|
if (options.environmentId) {
|
|
7222
8035
|
const match = environments.find((environment) => environment.id === options.environmentId);
|
|
7223
8036
|
if (!match) throw new CliError(`Environment ${options.environmentId} was not found.`);
|
|
7224
|
-
return match;
|
|
8037
|
+
return { value: match, asked: false };
|
|
8038
|
+
}
|
|
8039
|
+
if (flags.yes) {
|
|
8040
|
+
const chosen = environments.find((environment) => environment.type === "DEVELOPMENT") ?? environments[0];
|
|
8041
|
+
out(`${symbol.pass} Environment: ${style.bold(chosen.name)} (${chosen.type})`);
|
|
8042
|
+
return { value: chosen, asked: false };
|
|
8043
|
+
}
|
|
8044
|
+
const development = environments.findIndex((environment) => environment.type === "DEVELOPMENT");
|
|
8045
|
+
for (; ; ) {
|
|
8046
|
+
out();
|
|
8047
|
+
const chosen = await select(
|
|
8048
|
+
"Which environment?",
|
|
8049
|
+
environments.map((environment) => ({
|
|
8050
|
+
label: environment.name,
|
|
8051
|
+
hint: `${environment.type} \xB7 ${environment.gatewayMode ?? "\u2014"} \xB7 ${environment.id}`,
|
|
8052
|
+
value: environment
|
|
8053
|
+
})),
|
|
8054
|
+
{ quiet: true, initial: development >= 0 ? development : 0, back: true }
|
|
8055
|
+
);
|
|
8056
|
+
if (chosen === BACK) return BACK;
|
|
8057
|
+
if (chosen.type === "PRODUCTION" && !options.force) {
|
|
8058
|
+
out();
|
|
8059
|
+
out(
|
|
8060
|
+
`${symbol.warn} ${style.amber("You selected a PRODUCTION environment.")} A new agent here starts in ${style.bold("OBSERVE")} mode and performs no enforcement until you switch it.`
|
|
8061
|
+
);
|
|
8062
|
+
if (!await confirm("Continue with the production environment?", { yes: flags.yes })) {
|
|
8063
|
+
out(style.grey(" Choose another environment."));
|
|
8064
|
+
continue;
|
|
8065
|
+
}
|
|
8066
|
+
}
|
|
8067
|
+
out(`${symbol.pass} Environment: ${style.bold(chosen.name)} (${chosen.type})`);
|
|
8068
|
+
return { value: chosen, asked: true };
|
|
7225
8069
|
}
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
8070
|
+
}
|
|
8071
|
+
async function askAgentName(suggested) {
|
|
8072
|
+
for (; ; ) {
|
|
8073
|
+
out();
|
|
8074
|
+
const choice = await select(
|
|
8075
|
+
"Agent name",
|
|
8076
|
+
[
|
|
8077
|
+
{ label: suggested, hint: "suggested", value: "keep" },
|
|
8078
|
+
{ label: "Type a different name\u2026", value: "type" }
|
|
8079
|
+
],
|
|
8080
|
+
{ quiet: true, back: true }
|
|
8081
|
+
);
|
|
8082
|
+
if (choice === BACK) return BACK;
|
|
8083
|
+
if (choice === "keep") {
|
|
8084
|
+
out(`${symbol.pass} Agent name: ${style.bold(suggested)}`);
|
|
8085
|
+
return suggested;
|
|
8086
|
+
}
|
|
8087
|
+
const typed = (await ask(`Agent name ${style.grey(`[${suggested}]`)}:`, suggested)).trim();
|
|
8088
|
+
if (typed === "") continue;
|
|
8089
|
+
out(`${symbol.pass} Agent name: ${style.bold(typed)}`);
|
|
8090
|
+
return typed;
|
|
7230
8091
|
}
|
|
7231
|
-
out();
|
|
7232
|
-
out(style.bold("Select an environment"));
|
|
7233
|
-
table(
|
|
7234
|
-
[
|
|
7235
|
-
{ header: "#", key: "index", align: "right" },
|
|
7236
|
-
{ header: "NAME", key: "name" },
|
|
7237
|
-
{ header: "TYPE", key: "type" },
|
|
7238
|
-
{ header: "MODE", key: "mode" },
|
|
7239
|
-
{ header: "ID", key: "id" }
|
|
7240
|
-
],
|
|
7241
|
-
environments.map((environment, index) => ({
|
|
7242
|
-
index: String(index + 1),
|
|
7243
|
-
name: environment.name,
|
|
7244
|
-
type: environment.type,
|
|
7245
|
-
mode: environment.gatewayMode ?? "\u2014",
|
|
7246
|
-
id: environment.id
|
|
7247
|
-
}))
|
|
7248
|
-
);
|
|
7249
|
-
const chosen = environments[await pickIndex(environments.length)];
|
|
7250
|
-
if (!chosen) throw new CliError("No environment selected.");
|
|
7251
|
-
out(`${symbol.pass} Environment: ${style.bold(chosen.name)} (${chosen.type})`);
|
|
7252
|
-
return chosen;
|
|
7253
8092
|
}
|
|
7254
8093
|
async function discoverAudience(gatewayUrl) {
|
|
7255
8094
|
try {
|
|
@@ -7264,15 +8103,6 @@ async function discoverAudience(gatewayUrl) {
|
|
|
7264
8103
|
return gatewayUrl;
|
|
7265
8104
|
}
|
|
7266
8105
|
}
|
|
7267
|
-
async function pickIndex(count) {
|
|
7268
|
-
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
7269
|
-
const answer = await ask(`Choice [1-${count}]:`, "1");
|
|
7270
|
-
const index = Number(answer) - 1;
|
|
7271
|
-
if (Number.isInteger(index) && index >= 0 && index < count) return index;
|
|
7272
|
-
out(style.amber(` Enter a number between 1 and ${count}.`));
|
|
7273
|
-
}
|
|
7274
|
-
throw new CliError("No valid selection was made.");
|
|
7275
|
-
}
|
|
7276
8106
|
async function configureStarterPolicy(api, workspaceId, flags, options) {
|
|
7277
8107
|
if (options.skipPolicy) {
|
|
7278
8108
|
out(`${symbol.info} Skipped starter policy (--skip-policy).`);
|
|
@@ -7348,443 +8178,266 @@ async function validateConnection(api, gatewayUrl, agentId, environmentId) {
|
|
|
7348
8178
|
checks.push({
|
|
7349
8179
|
name: "Gateway reachable",
|
|
7350
8180
|
status: "fail",
|
|
7351
|
-
message: `${gatewayUrl} \u2014 ${error instanceof Error ? error.message : String(error)}. Start it with \`npm run dev:gateway\`.`
|
|
7352
|
-
});
|
|
7353
|
-
}
|
|
7354
|
-
try {
|
|
7355
|
-
const identities = await api.get(
|
|
7356
|
-
`/v1/agents/${agentId}/identities`
|
|
7357
|
-
);
|
|
7358
|
-
const active = identities.find((identity) => identity.status === "ACTIVE");
|
|
7359
|
-
checks.push({
|
|
7360
|
-
name: "Agent identity active",
|
|
7361
|
-
status: active ? "pass" : "fail",
|
|
7362
|
-
message: active ? `expires ${active.expiresAt.slice(0, 10)}` : "no ACTIVE identity"
|
|
7363
|
-
});
|
|
7364
|
-
} catch (error) {
|
|
7365
|
-
checks.push({
|
|
7366
|
-
name: "Agent identity active",
|
|
7367
|
-
status: "warn",
|
|
7368
|
-
message: error instanceof Error ? error.message : String(error)
|
|
7369
|
-
});
|
|
7370
|
-
}
|
|
7371
|
-
try {
|
|
7372
|
-
const passport = await api.get(
|
|
7373
|
-
`/v1/passports/${agentId}`
|
|
7374
|
-
);
|
|
7375
|
-
checks.push({
|
|
7376
|
-
name: "Agent passport issued",
|
|
7377
|
-
status: passport ? "pass" : "warn",
|
|
7378
|
-
message: passport ? `version ${passport.version}` : "not issued yet"
|
|
7379
|
-
});
|
|
7380
|
-
} catch {
|
|
7381
|
-
checks.push({ name: "Agent passport issued", status: "warn", message: "could not read" });
|
|
7382
|
-
}
|
|
7383
|
-
try {
|
|
7384
|
-
const environment = await api.get(
|
|
7385
|
-
`/v1/environments/${environmentId}`
|
|
7386
|
-
);
|
|
7387
|
-
checks.push({
|
|
7388
|
-
name: "Enforcement mode",
|
|
7389
|
-
status: "pass",
|
|
7390
|
-
message: environment.gatewayMode === "ENFORCE" ? "ENFORCE \u2014 decisions are enforced" : `${environment.gatewayMode} \u2014 decisions are recorded but not enforced`
|
|
7391
|
-
});
|
|
7392
|
-
} catch {
|
|
7393
|
-
checks.push({ name: "Enforcement mode", status: "warn", message: "could not read" });
|
|
7394
|
-
}
|
|
7395
|
-
return checks;
|
|
7396
|
-
}
|
|
7397
|
-
function parseInitArgs(args) {
|
|
7398
|
-
const options = { force: false, skipPolicy: false, skipCode: false };
|
|
7399
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
7400
|
-
const arg = args[index];
|
|
7401
|
-
const next = () => {
|
|
7402
|
-
const value = args[index + 1];
|
|
7403
|
-
if (value === void 0 || value.startsWith("-")) {
|
|
7404
|
-
throw new CliError(`Option ${String(arg)} requires a value.`);
|
|
7405
|
-
}
|
|
7406
|
-
index += 1;
|
|
7407
|
-
return value;
|
|
7408
|
-
};
|
|
7409
|
-
switch (arg) {
|
|
7410
|
-
case "--force":
|
|
7411
|
-
options.force = true;
|
|
7412
|
-
break;
|
|
7413
|
-
case "--skip-policy":
|
|
7414
|
-
options.skipPolicy = true;
|
|
7415
|
-
break;
|
|
7416
|
-
case "--skip-code":
|
|
7417
|
-
options.skipCode = true;
|
|
7418
|
-
break;
|
|
7419
|
-
case "--new-agent":
|
|
7420
|
-
options.newAgent = true;
|
|
7421
|
-
break;
|
|
7422
|
-
case "--control-plane":
|
|
7423
|
-
options.controlPlaneUrl = next();
|
|
7424
|
-
break;
|
|
7425
|
-
case "--gateway":
|
|
7426
|
-
options.gatewayUrl = next();
|
|
7427
|
-
break;
|
|
7428
|
-
case "--organization":
|
|
7429
|
-
options.organizationId = next();
|
|
7430
|
-
break;
|
|
7431
|
-
case "--workspace":
|
|
7432
|
-
options.workspaceId = next();
|
|
7433
|
-
break;
|
|
7434
|
-
case "--environment":
|
|
7435
|
-
options.environmentId = next();
|
|
7436
|
-
break;
|
|
7437
|
-
case "--name":
|
|
7438
|
-
options.agentName = next();
|
|
7439
|
-
break;
|
|
7440
|
-
case "--capability":
|
|
7441
|
-
options.capabilities = [...options.capabilities ?? [], next()];
|
|
7442
|
-
break;
|
|
7443
|
-
case "--approval-mode": {
|
|
7444
|
-
const value = next().toLowerCase();
|
|
7445
|
-
if (value !== "wait" && value !== "throw") {
|
|
7446
|
-
throw new CliError('--approval-mode must be "wait" or "throw".');
|
|
7447
|
-
}
|
|
7448
|
-
options.approvalMode = value;
|
|
7449
|
-
break;
|
|
7450
|
-
}
|
|
7451
|
-
default:
|
|
7452
|
-
if (arg?.startsWith("-")) {
|
|
7453
|
-
throw new CliError(`Unknown option for \`veefore init\`: ${arg}`);
|
|
7454
|
-
}
|
|
7455
|
-
}
|
|
7456
|
-
}
|
|
7457
|
-
return options;
|
|
7458
|
-
}
|
|
7459
|
-
async function projectHome(existing, flags) {
|
|
7460
|
-
const state = await agentState(existing);
|
|
7461
|
-
out();
|
|
7462
|
-
out(`${style.bold("Veefore")} ${style.grey(existing.controlPlaneUrl)}`);
|
|
7463
|
-
details([
|
|
7464
|
-
["Agent", `${existing.agentName} (${existing.agentId})`],
|
|
7465
|
-
["Environment", `${existing.environmentName} (${existing.environmentType})`],
|
|
7466
|
-
[
|
|
7467
|
-
"Status",
|
|
7468
|
-
state.kind === "ok" ? state.status : state.kind === "signed-out" ? "not signed in" : state.kind === "session-ended" ? "signed out" : state.kind === "missing" ? "agent not found" : "Veefore not reachable"
|
|
7469
|
-
]
|
|
7470
|
-
]);
|
|
7471
|
-
if (state.kind === "session-ended") {
|
|
7472
|
-
out(`${symbol.warn} ${state.message}`);
|
|
7473
|
-
}
|
|
7474
|
-
const choices = [];
|
|
7475
|
-
const ended = state.kind === "missing" || state.kind === "ok" && ENDED_STATUSES.includes(state.status);
|
|
7476
|
-
if (ended) {
|
|
7477
|
-
out();
|
|
7478
|
-
out(
|
|
7479
|
-
`${symbol.warn} ${style.amber(
|
|
7480
|
-
state.kind === "missing" ? "This agent no longer exists in Veefore." : `This agent was ${state.kind === "ok" ? state.status.toLowerCase() : "removed"} in Veefore.`
|
|
7481
|
-
)} Every tool call wrapped in guard() is now refused.`
|
|
7482
|
-
);
|
|
7483
|
-
choices.push(
|
|
7484
|
-
{
|
|
7485
|
-
label: "Remove Veefore from this project (take guard() out of the code, disconnect)",
|
|
7486
|
-
run: () => removeCommand([], flags, state)
|
|
7487
|
-
},
|
|
7488
|
-
{
|
|
7489
|
-
label: "Set it up again with a new agent (the guarded code keeps working)",
|
|
7490
|
-
run: () => initCommand(["--force"], flags)
|
|
7491
|
-
}
|
|
7492
|
-
);
|
|
7493
|
-
} else {
|
|
7494
|
-
if (state.kind === "signed-out" || state.kind === "session-ended") {
|
|
7495
|
-
choices.push({
|
|
7496
|
-
label: "Sign in",
|
|
7497
|
-
run: async () => {
|
|
7498
|
-
await login(existing.controlPlaneUrl);
|
|
7499
|
-
return 0;
|
|
7500
|
-
}
|
|
7501
|
-
});
|
|
7502
|
-
}
|
|
7503
|
-
choices.push(
|
|
7504
|
-
{ label: "Check status", run: () => statusCommand([], flags) },
|
|
7505
|
-
{
|
|
7506
|
-
label: "Protect tools in the code (veefore integrate)",
|
|
7507
|
-
run: () => integrateCommand([], flags)
|
|
7508
|
-
},
|
|
7509
|
-
{ label: "Re-run setup for this project", run: () => initCommand(["--force"], flags) },
|
|
7510
|
-
{
|
|
7511
|
-
label: "Remove Veefore from this project",
|
|
7512
|
-
run: () => removeCommand([], flags, state)
|
|
7513
|
-
}
|
|
7514
|
-
);
|
|
7515
|
-
if (state.kind === "ok" || state.kind === "unreachable") {
|
|
7516
|
-
choices.push({
|
|
7517
|
-
label: "Sign out",
|
|
7518
|
-
run: async () => {
|
|
7519
|
-
await logout(existing.controlPlaneUrl);
|
|
7520
|
-
return 0;
|
|
7521
|
-
}
|
|
7522
|
-
});
|
|
7523
|
-
}
|
|
7524
|
-
}
|
|
7525
|
-
choices.push({ label: "Exit", run: async () => 0 });
|
|
7526
|
-
out();
|
|
7527
|
-
out("What would you like to do?");
|
|
7528
|
-
choices.forEach((choice, index) => out(` ${style.bold(String(index + 1))} ${choice.label}`));
|
|
7529
|
-
const answer = Number(
|
|
7530
|
-
(await ask(`Choice [1-${choices.length}]:`, String(choices.length))).trim()
|
|
7531
|
-
);
|
|
7532
|
-
const picked = choices[answer - 1];
|
|
7533
|
-
if (!picked) {
|
|
7534
|
-
out(style.grey("Nothing changed."));
|
|
7535
|
-
return 0;
|
|
7536
|
-
}
|
|
7537
|
-
return picked.run();
|
|
7538
|
-
}
|
|
7539
|
-
|
|
7540
|
-
// src/commands/scan.ts
|
|
7541
|
-
async function scanCommand(_args, flags) {
|
|
7542
|
-
const scan = scanProject(process.cwd());
|
|
7543
|
-
if (flags.json) {
|
|
7544
|
-
out(JSON.stringify({ scan, plan: buildPlan(scan) }, null, 2));
|
|
7545
|
-
return 0;
|
|
7546
|
-
}
|
|
7547
|
-
heading("Veefore scan");
|
|
7548
|
-
out(
|
|
7549
|
-
`${symbol.pass} Framework: ${style.bold(scan.framework.name)} ${style.grey(`(${scan.framework.language}, ${scan.framework.evidence})`)}`
|
|
7550
|
-
);
|
|
7551
|
-
printScan(scan);
|
|
7552
|
-
out();
|
|
7553
|
-
out(style.grey("Nothing was changed. Run `veefore` to set this project up."));
|
|
7554
|
-
return 0;
|
|
7555
|
-
}
|
|
7556
|
-
|
|
7557
|
-
// src/commands/logs.ts
|
|
7558
|
-
async function logsCommand(args, flags) {
|
|
7559
|
-
const context = await resolveContext(flags);
|
|
7560
|
-
const follow = args.includes("--follow") || args.includes("-f");
|
|
7561
|
-
const limit = Number(valueFor(args, "--limit") ?? 25);
|
|
7562
|
-
const decisionFilter = valueFor(args, "--decision");
|
|
7563
|
-
const agentFilter = valueFor(args, "--agent") ?? (args.includes("--all") ? void 0 : context.config.agentId);
|
|
7564
|
-
const initial = await fetchActions(context, {
|
|
7565
|
-
limit,
|
|
7566
|
-
decision: decisionFilter,
|
|
7567
|
-
agentId: agentFilter
|
|
7568
|
-
});
|
|
7569
|
-
if (flags.json && !follow) {
|
|
7570
|
-
printJson(initial);
|
|
7571
|
-
return 0;
|
|
7572
|
-
}
|
|
7573
|
-
heading(`Decisions \u2014 ${context.config.environmentName} (${context.config.environmentType})`);
|
|
7574
|
-
renderActions(initial, flags.verbose);
|
|
7575
|
-
if (!follow) {
|
|
7576
|
-
out();
|
|
7577
|
-
out(style.grey(" Use --follow to stream new decisions as they happen."));
|
|
7578
|
-
out();
|
|
7579
|
-
return 0;
|
|
7580
|
-
}
|
|
7581
|
-
return streamActions(context, {
|
|
7582
|
-
decision: decisionFilter,
|
|
7583
|
-
agentId: agentFilter,
|
|
7584
|
-
seen: new Set(initial.map((action) => action.id)),
|
|
7585
|
-
verbose: flags.verbose,
|
|
7586
|
-
json: flags.json
|
|
7587
|
-
});
|
|
7588
|
-
}
|
|
7589
|
-
async function devCommand(args, flags) {
|
|
7590
|
-
const context = await resolveContext(flags);
|
|
7591
|
-
const environment = await context.api.get(`/v1/environments/${context.config.environmentId}`);
|
|
7592
|
-
const gateway = await probeGateway(context.config.gatewayUrl);
|
|
7593
|
-
const agent = await context.api.get(
|
|
7594
|
-
`/v1/agents/${context.config.agentId}`
|
|
7595
|
-
);
|
|
7596
|
-
heading(`veefore dev \u2014 ${context.config.organizationName} \u203A ${environment.name}`);
|
|
7597
|
-
out();
|
|
7598
|
-
out(
|
|
7599
|
-
` ${gateway.reachable ? symbol.pass : symbol.fail} Gateway ${gateway.reachable ? style.green("connected") : style.red("unreachable")} ${style.grey(context.config.gatewayUrl)}`
|
|
7600
|
-
);
|
|
7601
|
-
out(
|
|
7602
|
-
` ${agent.status === "ACTIVE" ? symbol.pass : symbol.warn} Agent ${style.bold(agent.name)} ${decisionStyle(agent.status)}`
|
|
7603
|
-
);
|
|
7604
|
-
out(
|
|
7605
|
-
` ${environment.gatewayMode === "ENFORCE" ? symbol.pass : symbol.warn} Mode ${decisionStyle(environment.status === "PAUSED" ? "PAUSED" : environment.gatewayMode)}`
|
|
7606
|
-
);
|
|
7607
|
-
if (!gateway.reachable) {
|
|
7608
|
-
out();
|
|
7609
|
-
out(
|
|
7610
|
-
` ${symbol.fail} ${style.red("The gateway is not reachable, so no action can be permitted.")}`
|
|
7611
|
-
);
|
|
7612
|
-
out(style.grey(" Start it with `npm run dev:gateway`, then re-run `veefore dev`."));
|
|
7613
|
-
out();
|
|
7614
|
-
return 1;
|
|
8181
|
+
message: `${gatewayUrl} \u2014 ${error instanceof Error ? error.message : String(error)}. Start it with \`npm run dev:gateway\`.`
|
|
8182
|
+
});
|
|
7615
8183
|
}
|
|
7616
|
-
|
|
7617
|
-
|
|
7618
|
-
|
|
7619
|
-
` ${symbol.info} ${style.cyan("OBSERVE mode:")} every decision is evaluated and recorded, but nothing is blocked.`
|
|
8184
|
+
try {
|
|
8185
|
+
const identities = await api.get(
|
|
8186
|
+
`/v1/agents/${agentId}/identities`
|
|
7620
8187
|
);
|
|
7621
|
-
|
|
7622
|
-
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
8188
|
+
const active = identities.find((identity) => identity.status === "ACTIVE");
|
|
8189
|
+
checks.push({
|
|
8190
|
+
name: "Agent identity active",
|
|
8191
|
+
status: active ? "pass" : "fail",
|
|
8192
|
+
message: active ? `expires ${active.expiresAt.slice(0, 10)}` : "no ACTIVE identity"
|
|
8193
|
+
});
|
|
8194
|
+
} catch (error) {
|
|
8195
|
+
checks.push({
|
|
8196
|
+
name: "Agent identity active",
|
|
8197
|
+
status: "warn",
|
|
8198
|
+
message: error instanceof Error ? error.message : String(error)
|
|
8199
|
+
});
|
|
7626
8200
|
}
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
const existing = await fetchActions(context, { limit: 5, agentId: context.config.agentId });
|
|
7631
|
-
if (existing.length === 0) {
|
|
7632
|
-
out(
|
|
7633
|
-
` ${symbol.info} No actions recorded yet. Call ${style.bold("agent.act({ tool: \u2026 })")} from your agent to see a decision here.`
|
|
8201
|
+
try {
|
|
8202
|
+
const passport = await api.get(
|
|
8203
|
+
`/v1/passports/${agentId}`
|
|
7634
8204
|
);
|
|
7635
|
-
|
|
7636
|
-
|
|
7637
|
-
|
|
7638
|
-
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
verbose: true,
|
|
7643
|
-
json: false,
|
|
7644
|
-
coach: true
|
|
7645
|
-
});
|
|
7646
|
-
}
|
|
7647
|
-
async function fetchActions(context, options) {
|
|
7648
|
-
const response = await context.api.get("/v1/actions", {
|
|
7649
|
-
limit: options.limit ?? 25,
|
|
7650
|
-
decision: options.decision,
|
|
7651
|
-
agent_id: options.agentId,
|
|
7652
|
-
environment_id: context.config.environmentId
|
|
7653
|
-
});
|
|
7654
|
-
return [...response.data].reverse();
|
|
7655
|
-
}
|
|
7656
|
-
function renderActions(actions, verbose) {
|
|
7657
|
-
if (actions.length === 0) {
|
|
7658
|
-
out(style.grey(" (no decisions yet)"));
|
|
7659
|
-
return;
|
|
8205
|
+
checks.push({
|
|
8206
|
+
name: "Agent passport issued",
|
|
8207
|
+
status: passport ? "pass" : "warn",
|
|
8208
|
+
message: passport ? `version ${passport.version}` : "not issued yet"
|
|
8209
|
+
});
|
|
8210
|
+
} catch {
|
|
8211
|
+
checks.push({ name: "Agent passport issued", status: "warn", message: "could not read" });
|
|
7660
8212
|
}
|
|
7661
|
-
|
|
7662
|
-
|
|
7663
|
-
|
|
7664
|
-
{ header: "WHEN", key: "when", width: 12 },
|
|
7665
|
-
{ header: "TOOL", key: "tool", width: 24 },
|
|
7666
|
-
{ header: "DECISION", key: "decision", width: 20 },
|
|
7667
|
-
{ header: "OUTCOME", key: "outcome", width: 24 },
|
|
7668
|
-
{ header: "MS", key: "latency", width: 6, align: "right" }
|
|
7669
|
-
],
|
|
7670
|
-
actions.map((action) => ({
|
|
7671
|
-
when: formatWhen(action.startedAt),
|
|
7672
|
-
tool: action.toolKey ?? action.action,
|
|
7673
|
-
decision: decisionStyle(effectiveDecision(action)),
|
|
7674
|
-
outcome: decisionStyle(action.outcome),
|
|
7675
|
-
latency: String(Math.round(action.latencyMs))
|
|
7676
|
-
}))
|
|
8213
|
+
try {
|
|
8214
|
+
const environment = await api.get(
|
|
8215
|
+
`/v1/environments/${environmentId}`
|
|
7677
8216
|
);
|
|
7678
|
-
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
}
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
}
|
|
7685
|
-
function renderAction(action, coach = false) {
|
|
7686
|
-
const decision = effectiveDecision(action);
|
|
7687
|
-
const icon = decision === "ALLOW" ? symbol.pass : decision === "BLOCK" ? symbol.fail : symbol.warn;
|
|
7688
|
-
const amount = action.amount === null ? "" : style.grey(` ${formatAmount(action.amount, action.currency)}`);
|
|
7689
|
-
out(
|
|
7690
|
-
`${icon} ${style.grey(new Date(action.startedAt).toISOString().slice(11, 19))} ${style.bold(action.toolKey ?? action.action)}${amount} ${decisionStyle(decision)} ${style.grey(`(${action.outcome})`)}`
|
|
7691
|
-
);
|
|
7692
|
-
if (action.denyReason) out(` ${action.denyReason}`);
|
|
7693
|
-
const facts = [];
|
|
7694
|
-
if (action.policyVersionNumber !== null) facts.push(`policy v${action.policyVersionNumber}`);
|
|
7695
|
-
if (action.matchedRuleIds.length > 0)
|
|
7696
|
-
facts.push(`${action.matchedRuleIds.length} rule(s) matched`);
|
|
7697
|
-
if (action.riskLevel) facts.push(`risk ${action.riskLevel}`);
|
|
7698
|
-
if (action.protocol !== "HTTP") facts.push(action.protocol);
|
|
7699
|
-
if (action.gatewayMode !== "ENFORCE") facts.push(`mode ${action.gatewayMode}`);
|
|
7700
|
-
facts.push(`${Math.round(action.latencyMs)}ms`);
|
|
7701
|
-
out(` ${style.grey(facts.join(" \xB7 "))}`);
|
|
7702
|
-
if (coach) {
|
|
7703
|
-
const hint = coachingFor(action);
|
|
7704
|
-
if (hint) out(` ${style.cyan("\u2192")} ${style.grey(hint)}`);
|
|
8217
|
+
checks.push({
|
|
8218
|
+
name: "Enforcement mode",
|
|
8219
|
+
status: "pass",
|
|
8220
|
+
message: environment.gatewayMode === "ENFORCE" ? "ENFORCE \u2014 decisions are enforced" : `${environment.gatewayMode} \u2014 decisions are recorded but not enforced`
|
|
8221
|
+
});
|
|
8222
|
+
} catch {
|
|
8223
|
+
checks.push({ name: "Enforcement mode", status: "warn", message: "could not read" });
|
|
7705
8224
|
}
|
|
8225
|
+
return checks;
|
|
7706
8226
|
}
|
|
7707
|
-
function
|
|
7708
|
-
const
|
|
7709
|
-
|
|
7710
|
-
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
|
-
|
|
7722
|
-
|
|
7723
|
-
|
|
7724
|
-
|
|
8227
|
+
function parseInitArgs(args) {
|
|
8228
|
+
const options = { force: false, skipPolicy: false, skipCode: false };
|
|
8229
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
8230
|
+
const arg = args[index];
|
|
8231
|
+
const next = () => {
|
|
8232
|
+
const value = args[index + 1];
|
|
8233
|
+
if (value === void 0 || value.startsWith("-")) {
|
|
8234
|
+
throw new CliError(`Option ${String(arg)} requires a value.`);
|
|
8235
|
+
}
|
|
8236
|
+
index += 1;
|
|
8237
|
+
return value;
|
|
8238
|
+
};
|
|
8239
|
+
switch (arg) {
|
|
8240
|
+
case "--force":
|
|
8241
|
+
options.force = true;
|
|
8242
|
+
break;
|
|
8243
|
+
case "--skip-policy":
|
|
8244
|
+
options.skipPolicy = true;
|
|
8245
|
+
break;
|
|
8246
|
+
case "--skip-code":
|
|
8247
|
+
options.skipCode = true;
|
|
8248
|
+
break;
|
|
8249
|
+
case "--new-agent":
|
|
8250
|
+
options.newAgent = true;
|
|
8251
|
+
break;
|
|
8252
|
+
case "--control-plane":
|
|
8253
|
+
options.controlPlaneUrl = next();
|
|
8254
|
+
break;
|
|
8255
|
+
case "--gateway":
|
|
8256
|
+
options.gatewayUrl = next();
|
|
8257
|
+
break;
|
|
8258
|
+
case "--organization":
|
|
8259
|
+
options.organizationId = next();
|
|
8260
|
+
break;
|
|
8261
|
+
case "--workspace":
|
|
8262
|
+
options.workspaceId = next();
|
|
8263
|
+
break;
|
|
8264
|
+
case "--environment":
|
|
8265
|
+
options.environmentId = next();
|
|
8266
|
+
break;
|
|
8267
|
+
case "--name":
|
|
8268
|
+
options.agentName = next();
|
|
8269
|
+
break;
|
|
8270
|
+
case "--capability":
|
|
8271
|
+
options.capabilities = [...options.capabilities ?? [], next()];
|
|
8272
|
+
break;
|
|
8273
|
+
case "--approval-mode": {
|
|
8274
|
+
const value = next().toLowerCase();
|
|
8275
|
+
if (value !== "wait" && value !== "throw") {
|
|
8276
|
+
throw new CliError('--approval-mode must be "wait" or "throw".');
|
|
8277
|
+
}
|
|
8278
|
+
options.approvalMode = value;
|
|
8279
|
+
break;
|
|
8280
|
+
}
|
|
8281
|
+
default:
|
|
8282
|
+
if (arg?.startsWith("-")) {
|
|
8283
|
+
throw new CliError(`Unknown option for \`veefore init\`: ${arg}`);
|
|
8284
|
+
}
|
|
8285
|
+
}
|
|
7725
8286
|
}
|
|
8287
|
+
return options;
|
|
7726
8288
|
}
|
|
7727
|
-
function
|
|
7728
|
-
const
|
|
7729
|
-
|
|
7730
|
-
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
stopped = true;
|
|
7739
|
-
};
|
|
7740
|
-
process.once("SIGINT", stop);
|
|
7741
|
-
process.once("SIGTERM", stop);
|
|
7742
|
-
let blocked = 0;
|
|
7743
|
-
let approvals = 0;
|
|
7744
|
-
let allowed = 0;
|
|
7745
|
-
while (!stopped) {
|
|
7746
|
-
let fresh = [];
|
|
8289
|
+
async function projectHome(existing, flags) {
|
|
8290
|
+
const source = welcomeSource(flags);
|
|
8291
|
+
let last = 0;
|
|
8292
|
+
for (let round = 0; ; round += 1) {
|
|
8293
|
+
await beginScreen(source, { animate: round === 0 });
|
|
8294
|
+
const state = await agentState(existing);
|
|
8295
|
+
printProjectSummary(existing, state);
|
|
8296
|
+
const choices = homeChoices(existing, state, flags);
|
|
8297
|
+
choices.push({ label: "Exit", hint: "esc", run: async () => last, leaves: true });
|
|
8298
|
+
out();
|
|
8299
|
+
let picked;
|
|
7747
8300
|
try {
|
|
7748
|
-
|
|
7749
|
-
|
|
7750
|
-
|
|
7751
|
-
agentId: options.agentId
|
|
7752
|
-
});
|
|
7753
|
-
fresh = recent.filter((action) => !options.seen.has(action.id));
|
|
7754
|
-
for (const action of fresh) options.seen.add(action.id);
|
|
7755
|
-
} catch (error) {
|
|
7756
|
-
out(
|
|
7757
|
-
`${symbol.warn} ${style.amber("Lost contact with the control plane;")} ${style.grey(error instanceof Error ? error.message : String(error))}`
|
|
8301
|
+
picked = await select(
|
|
8302
|
+
"What would you like to do?",
|
|
8303
|
+
choices.map((choice) => ({ label: choice.label, hint: choice.hint, value: choice }))
|
|
7758
8304
|
);
|
|
7759
|
-
|
|
7760
|
-
|
|
7761
|
-
|
|
8305
|
+
} catch (error) {
|
|
8306
|
+
if (error instanceof MenuCancelled) return last;
|
|
8307
|
+
throw error;
|
|
7762
8308
|
}
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
8309
|
+
if (picked.leaves) {
|
|
8310
|
+
await beginScreen(source, { compact: true });
|
|
8311
|
+
try {
|
|
8312
|
+
return await picked.run();
|
|
8313
|
+
} catch (error) {
|
|
8314
|
+
return error instanceof MenuCancelled ? last : reportError(error, flags);
|
|
7768
8315
|
}
|
|
7769
|
-
const decision = effectiveDecision(action);
|
|
7770
|
-
if (decision === "BLOCK") blocked += 1;
|
|
7771
|
-
else if (decision === "REQUIRE_APPROVAL") approvals += 1;
|
|
7772
|
-
else allowed += 1;
|
|
7773
8316
|
}
|
|
7774
|
-
|
|
7775
|
-
|
|
8317
|
+
await beginScreen(source, { compact: true });
|
|
8318
|
+
try {
|
|
8319
|
+
last = await picked.run();
|
|
8320
|
+
} catch (error) {
|
|
8321
|
+
last = error instanceof MenuCancelled ? last : reportError(error, flags);
|
|
8322
|
+
}
|
|
8323
|
+
if (!isInteractive()) return last;
|
|
8324
|
+
await pressBack("Back to menu");
|
|
8325
|
+
}
|
|
8326
|
+
}
|
|
8327
|
+
function printProjectSummary(existing, state) {
|
|
8328
|
+
out();
|
|
8329
|
+
details([
|
|
8330
|
+
["Agent", `${existing.agentName} (${existing.agentId})`],
|
|
8331
|
+
["Environment", `${existing.environmentName} (${existing.environmentType})`],
|
|
8332
|
+
[
|
|
8333
|
+
"Status",
|
|
8334
|
+
state.kind === "ok" ? decisionStyle(state.status) : state.kind === "signed-out" ? style.amber("not signed in") : state.kind === "session-ended" ? style.amber("signed out") : state.kind === "missing" ? style.red("agent not found") : style.amber("Veefore not reachable")
|
|
8335
|
+
]
|
|
8336
|
+
]);
|
|
8337
|
+
if (state.kind === "session-ended") {
|
|
8338
|
+
out(`${symbol.warn} ${state.message}`);
|
|
7776
8339
|
}
|
|
7777
|
-
|
|
8340
|
+
}
|
|
8341
|
+
function homeChoices(existing, state, flags) {
|
|
8342
|
+
const ended = state.kind === "missing" || state.kind === "ok" && ENDED_STATUSES.includes(state.status);
|
|
8343
|
+
if (ended) {
|
|
7778
8344
|
out();
|
|
7779
8345
|
out(
|
|
7780
|
-
|
|
8346
|
+
`${symbol.warn} ${style.amber(
|
|
8347
|
+
state.kind === "missing" ? "This agent no longer exists in Veefore." : `This agent was ${state.kind === "ok" ? state.status.toLowerCase() : "removed"} in Veefore.`
|
|
8348
|
+
)} Every tool call wrapped in guard() is now refused.`
|
|
7781
8349
|
);
|
|
7782
|
-
|
|
8350
|
+
return [
|
|
8351
|
+
{
|
|
8352
|
+
label: "Remove Veefore from this project",
|
|
8353
|
+
hint: "take guard() out of the code, disconnect",
|
|
8354
|
+
run: () => removeCommand([], flags, state),
|
|
8355
|
+
leaves: true
|
|
8356
|
+
},
|
|
8357
|
+
{
|
|
8358
|
+
label: "Set it up again with a new agent",
|
|
8359
|
+
hint: "the guarded code keeps working",
|
|
8360
|
+
run: () => initCommand(["--force"], flags),
|
|
8361
|
+
leaves: true
|
|
8362
|
+
}
|
|
8363
|
+
];
|
|
7783
8364
|
}
|
|
7784
|
-
|
|
8365
|
+
const choices = [];
|
|
8366
|
+
if (state.kind === "signed-out" || state.kind === "session-ended") {
|
|
8367
|
+
choices.push({
|
|
8368
|
+
label: "Sign in",
|
|
8369
|
+
hint: "veefore login",
|
|
8370
|
+
run: async () => {
|
|
8371
|
+
await login(existing.controlPlaneUrl);
|
|
8372
|
+
return 0;
|
|
8373
|
+
}
|
|
8374
|
+
});
|
|
8375
|
+
}
|
|
8376
|
+
choices.push(
|
|
8377
|
+
{ label: "Watch decisions live", hint: "veefore dev", run: () => devCommand([], flags) },
|
|
8378
|
+
{ label: "Check status", hint: "veefore status", run: () => statusCommand([], flags) },
|
|
8379
|
+
{
|
|
8380
|
+
label: "Protect tools in the code",
|
|
8381
|
+
hint: "veefore integrate",
|
|
8382
|
+
run: () => integrateCommand([], flags)
|
|
8383
|
+
},
|
|
8384
|
+
{
|
|
8385
|
+
label: "Check every tool is guarded",
|
|
8386
|
+
hint: "veefore integrate --check",
|
|
8387
|
+
run: () => integrateCommand(["--check"], flags)
|
|
8388
|
+
},
|
|
8389
|
+
{ label: "Test enforcement", hint: "veefore test", run: () => testCommand([], flags) },
|
|
8390
|
+
{
|
|
8391
|
+
label: "Audit the security posture",
|
|
8392
|
+
hint: "veefore doctor",
|
|
8393
|
+
run: () => doctorCommand([], flags)
|
|
8394
|
+
},
|
|
8395
|
+
{ label: "Turn on enforcement", hint: "veefore deploy", run: () => deployCommand([], flags) },
|
|
8396
|
+
{
|
|
8397
|
+
label: "Re-run setup for this project",
|
|
8398
|
+
hint: "veefore init --force",
|
|
8399
|
+
run: () => initCommand(["--force"], flags),
|
|
8400
|
+
leaves: true
|
|
8401
|
+
},
|
|
8402
|
+
{
|
|
8403
|
+
label: "Remove Veefore from this project",
|
|
8404
|
+
hint: "veefore remove",
|
|
8405
|
+
run: () => removeCommand([], flags, state),
|
|
8406
|
+
leaves: true
|
|
8407
|
+
}
|
|
8408
|
+
);
|
|
8409
|
+
if (state.kind === "ok" || state.kind === "unreachable") {
|
|
8410
|
+
choices.push({
|
|
8411
|
+
label: "Sign out",
|
|
8412
|
+
hint: "veefore logout",
|
|
8413
|
+
run: async () => {
|
|
8414
|
+
await logout(existing.controlPlaneUrl);
|
|
8415
|
+
return 0;
|
|
8416
|
+
}
|
|
8417
|
+
// Deliberately not `leaves`: signing out comes back to this menu, so the
|
|
8418
|
+
// panel and the status redraw as signed out and the next step on offer
|
|
8419
|
+
// is Sign in. Leaving instead would quit on the one action whose result
|
|
8420
|
+
// a person most wants to see confirmed.
|
|
8421
|
+
});
|
|
8422
|
+
}
|
|
8423
|
+
return choices;
|
|
7785
8424
|
}
|
|
7786
|
-
|
|
7787
|
-
|
|
8425
|
+
|
|
8426
|
+
// src/commands/scan.ts
|
|
8427
|
+
async function scanCommand(_args, flags) {
|
|
8428
|
+
const scan = scanProject(process.cwd());
|
|
8429
|
+
if (flags.json) {
|
|
8430
|
+
out(JSON.stringify({ scan, plan: buildPlan(scan) }, null, 2));
|
|
8431
|
+
return 0;
|
|
8432
|
+
}
|
|
8433
|
+
heading("Veefore scan");
|
|
8434
|
+
out(
|
|
8435
|
+
`${symbol.pass} Framework: ${style.bold(scan.framework.name)} ${style.grey(`(${scan.framework.language}, ${scan.framework.evidence})`)}`
|
|
8436
|
+
);
|
|
8437
|
+
printScan(scan);
|
|
8438
|
+
out();
|
|
8439
|
+
out(style.grey("Nothing was changed. Run `veefore` to set this project up."));
|
|
8440
|
+
return 0;
|
|
7788
8441
|
}
|
|
7789
8442
|
|
|
7790
8443
|
// src/commands/policies.ts
|
|
@@ -8172,155 +8825,6 @@ async function diffPolicy(args, flags) {
|
|
|
8172
8825
|
return 0;
|
|
8173
8826
|
}
|
|
8174
8827
|
|
|
8175
|
-
// src/commands/test.ts
|
|
8176
|
-
async function testCommand(args, flags) {
|
|
8177
|
-
const context = await resolveContext(flags);
|
|
8178
|
-
const toolFilter = valueFor(args, "--tool");
|
|
8179
|
-
heading(
|
|
8180
|
-
`Enforcement test \u2014 ${context.config.environmentName} (${context.config.environmentType})`
|
|
8181
|
-
);
|
|
8182
|
-
const results = [];
|
|
8183
|
-
const record = (check, passed, detail) => {
|
|
8184
|
-
results.push({ check, passed, detail });
|
|
8185
|
-
out(`${passed ? symbol.pass : symbol.fail} ${check} ${style.grey(detail)}`);
|
|
8186
|
-
};
|
|
8187
|
-
const gateway = await probeGateway(context.config.gatewayUrl);
|
|
8188
|
-
record(
|
|
8189
|
-
"Gateway reachable",
|
|
8190
|
-
gateway.reachable,
|
|
8191
|
-
gateway.reachable ? `${context.config.gatewayUrl} \u2014 posture ${gateway.posture ?? "unknown"}` : gateway.error ?? "unreachable"
|
|
8192
|
-
);
|
|
8193
|
-
const identities = await context.api.get(`/v1/agents/${context.config.agentId}/identities`).catch(() => []);
|
|
8194
|
-
const activeIdentity = identities.find((identity) => identity.status === "ACTIVE");
|
|
8195
|
-
record(
|
|
8196
|
-
"Agent identity active",
|
|
8197
|
-
Boolean(activeIdentity),
|
|
8198
|
-
activeIdentity ? `generation ${activeIdentity.generation}, expires ${activeIdentity.expiresAt.slice(0, 10)}` : "no ACTIVE identity \u2014 the agent cannot authenticate"
|
|
8199
|
-
);
|
|
8200
|
-
const policies = await context.api.get("/v1/policies", { workspace_id: context.config.workspaceId });
|
|
8201
|
-
const effective = policies.filter(
|
|
8202
|
-
(policy) => policy.status === "ACTIVE" && policy.bindings.length > 0
|
|
8203
|
-
);
|
|
8204
|
-
record(
|
|
8205
|
-
"Active, bound policy exists",
|
|
8206
|
-
effective.length > 0,
|
|
8207
|
-
effective.length > 0 ? effective.map((policy) => policy.name).join(", ") : "no active policy is bound to a scope, so no rule can match"
|
|
8208
|
-
);
|
|
8209
|
-
let caseTotal = 0;
|
|
8210
|
-
let caseFailed = 0;
|
|
8211
|
-
for (const policy of policies) {
|
|
8212
|
-
const result = await context.api.post(`/v1/policies/${policy.id}/tests/run`, {}).catch(() => null);
|
|
8213
|
-
if (!result) continue;
|
|
8214
|
-
caseTotal += result.passed + result.failed;
|
|
8215
|
-
caseFailed += result.failed;
|
|
8216
|
-
}
|
|
8217
|
-
record(
|
|
8218
|
-
"Policy test cases",
|
|
8219
|
-
caseFailed === 0,
|
|
8220
|
-
caseTotal === 0 ? "no test cases recorded \u2014 add cases so rule changes are provable" : `${caseTotal - caseFailed}/${caseTotal} passing`
|
|
8221
|
-
);
|
|
8222
|
-
const tools = await resolveAuthorizedTools(context, toolFilter);
|
|
8223
|
-
if (tools.length === 0) {
|
|
8224
|
-
record(
|
|
8225
|
-
"Authorized tools",
|
|
8226
|
-
false,
|
|
8227
|
-
"this agent is authorized for no tools, so it cannot do anything"
|
|
8228
|
-
);
|
|
8229
|
-
} else {
|
|
8230
|
-
record("Authorized tools", true, tools.map((tool) => tool.key).join(", "));
|
|
8231
|
-
}
|
|
8232
|
-
heading("Decision probe (dry run \u2014 nothing is executed)");
|
|
8233
|
-
const probes = [];
|
|
8234
|
-
for (const tool of tools) {
|
|
8235
|
-
const probeSpinner = spinner(`Evaluating ${tool.key}`);
|
|
8236
|
-
try {
|
|
8237
|
-
const outcome = await context.api.post("/v1/policies/simulate", {
|
|
8238
|
-
agent_id: context.config.agentId,
|
|
8239
|
-
tool: tool.key,
|
|
8240
|
-
capability: tool.requiredCapability,
|
|
8241
|
-
environment_id: context.config.environmentId,
|
|
8242
|
-
parameters: {}
|
|
8243
|
-
});
|
|
8244
|
-
probeSpinner.succeed(`${tool.key} \u2192 ${outcome.decision}`);
|
|
8245
|
-
probes.push({ tool, outcome });
|
|
8246
|
-
} catch (error) {
|
|
8247
|
-
probeSpinner.fail(`${tool.key} could not be evaluated`);
|
|
8248
|
-
probes.push({
|
|
8249
|
-
tool,
|
|
8250
|
-
outcome: null,
|
|
8251
|
-
error: error instanceof Error ? error.message : String(error)
|
|
8252
|
-
});
|
|
8253
|
-
}
|
|
8254
|
-
}
|
|
8255
|
-
out();
|
|
8256
|
-
table(
|
|
8257
|
-
[
|
|
8258
|
-
{ header: "TOOL", key: "tool", width: 24 },
|
|
8259
|
-
{ header: "RISK", key: "risk", width: 18 },
|
|
8260
|
-
{ header: "DECISION", key: "decision", width: 20 },
|
|
8261
|
-
{ header: "MATCHED RULE", key: "rule", width: 32 }
|
|
8262
|
-
],
|
|
8263
|
-
probes.map((probe) => ({
|
|
8264
|
-
tool: probe.tool.dangerous ? `${probe.tool.key} ${style.red("(dangerous)")}` : probe.tool.key,
|
|
8265
|
-
risk: decisionStyle(probe.tool.riskLevel),
|
|
8266
|
-
decision: probe.outcome ? decisionStyle(probe.outcome.decision) : style.red("ERROR"),
|
|
8267
|
-
rule: probe.outcome?.matchedRules[0]?.name ?? style.grey(probe.error ?? "default decision")
|
|
8268
|
-
}))
|
|
8269
|
-
);
|
|
8270
|
-
const unguarded = probes.filter(
|
|
8271
|
-
(probe) => probe.tool.dangerous && probe.outcome?.decision === "ALLOW" && probe.outcome.matchedRules.length === 0
|
|
8272
|
-
);
|
|
8273
|
-
out();
|
|
8274
|
-
record(
|
|
8275
|
-
"Dangerous tools are guarded",
|
|
8276
|
-
unguarded.length === 0,
|
|
8277
|
-
unguarded.length === 0 ? "every dangerous tool is either refused or matched by an explicit rule" : `${unguarded.map((probe) => probe.tool.key).join(", ")} allowed by the default decision with no rule`
|
|
8278
|
-
);
|
|
8279
|
-
const errored = probes.filter((probe) => probe.outcome === null);
|
|
8280
|
-
record(
|
|
8281
|
-
"All evaluations completed",
|
|
8282
|
-
errored.length === 0,
|
|
8283
|
-
errored.length === 0 ? `${probes.length} evaluated` : `${errored.length} failed to evaluate`
|
|
8284
|
-
);
|
|
8285
|
-
const impure = probes.filter((probe) => probe.outcome && probe.outcome.sideEffectFree !== true);
|
|
8286
|
-
record(
|
|
8287
|
-
"Simulation was side-effect free",
|
|
8288
|
-
impure.length === 0,
|
|
8289
|
-
impure.length === 0 ? "no action records were written" : "the server reported side effects"
|
|
8290
|
-
);
|
|
8291
|
-
const failed = results.filter((result) => !result.passed);
|
|
8292
|
-
out();
|
|
8293
|
-
out(
|
|
8294
|
-
failed.length === 0 ? ` ${symbol.pass} ${style.green(`${results.length} check(s) passing.`)}` : ` ${symbol.fail} ${style.red(`${failed.length} of ${results.length} check(s) failing.`)}`
|
|
8295
|
-
);
|
|
8296
|
-
if (failed.length > 0) {
|
|
8297
|
-
out();
|
|
8298
|
-
for (const failure of failed) out(` ${symbol.fail} ${failure.check}: ${failure.detail}`);
|
|
8299
|
-
}
|
|
8300
|
-
out();
|
|
8301
|
-
if (flags.json) printJson({ results, probes });
|
|
8302
|
-
return failed.length > 0 ? 1 : 0;
|
|
8303
|
-
}
|
|
8304
|
-
async function resolveAuthorizedTools(context, filter) {
|
|
8305
|
-
const tools = await context.api.get("/v1/tools", {
|
|
8306
|
-
workspace_id: context.config.workspaceId,
|
|
8307
|
-
agent_id: context.config.agentId,
|
|
8308
|
-
limit: 100
|
|
8309
|
-
});
|
|
8310
|
-
const candidates = tools.items.filter(
|
|
8311
|
-
(tool) => tool.status !== "DISABLED" && (tool.environmentId === null || tool.environmentId === context.config.environmentId)
|
|
8312
|
-
);
|
|
8313
|
-
if (!filter) return candidates;
|
|
8314
|
-
const match = candidates.filter((tool) => tool.key === filter);
|
|
8315
|
-
if (match.length === 0) {
|
|
8316
|
-
throw new CliError(
|
|
8317
|
-
`Tool "${filter}" is not available to this agent in ${context.config.environmentName}.`,
|
|
8318
|
-
`Available: ${candidates.map((tool) => tool.key).join(", ") || "none"}`
|
|
8319
|
-
);
|
|
8320
|
-
}
|
|
8321
|
-
return match;
|
|
8322
|
-
}
|
|
8323
|
-
|
|
8324
8828
|
// src/help.ts
|
|
8325
8829
|
var COMMANDS = [
|
|
8326
8830
|
{
|
|
@@ -8600,8 +9104,78 @@ function wrap2(text, width) {
|
|
|
8600
9104
|
return lines;
|
|
8601
9105
|
}
|
|
8602
9106
|
|
|
9107
|
+
// src/home.ts
|
|
9108
|
+
async function homeCommand(args, flags) {
|
|
9109
|
+
const source = welcomeSource(flags);
|
|
9110
|
+
let first = true;
|
|
9111
|
+
for (; ; ) {
|
|
9112
|
+
if (readProjectConfig()) return initCommand(args, flags);
|
|
9113
|
+
await beginScreen(source, { animate: first });
|
|
9114
|
+
first = false;
|
|
9115
|
+
out();
|
|
9116
|
+
out(style.bold("Let's get started."));
|
|
9117
|
+
out();
|
|
9118
|
+
let action;
|
|
9119
|
+
try {
|
|
9120
|
+
action = await select("What would you like to do?", [
|
|
9121
|
+
{ label: "Set up this project", hint: "recommended \xB7 veefore init", value: "setup" },
|
|
9122
|
+
{
|
|
9123
|
+
label: "Preview what setup would find",
|
|
9124
|
+
hint: "veefore scan \xB7 changes nothing",
|
|
9125
|
+
value: "scan"
|
|
9126
|
+
},
|
|
9127
|
+
{ label: "Sign in", hint: "veefore login", value: "login" },
|
|
9128
|
+
{ label: "Show every command", hint: "veefore help", value: "help" },
|
|
9129
|
+
{ label: "Exit", hint: "esc", value: "exit" }
|
|
9130
|
+
]);
|
|
9131
|
+
} catch (error) {
|
|
9132
|
+
if (error instanceof MenuCancelled) return 0;
|
|
9133
|
+
throw error;
|
|
9134
|
+
}
|
|
9135
|
+
if (action === "exit") return 0;
|
|
9136
|
+
if (action === "setup") {
|
|
9137
|
+
await runOnScreen(source, flags, () => initCommand(args, flags), {
|
|
9138
|
+
// Setup done: "Continue", and the next pass opens the project home
|
|
9139
|
+
// with the panel filled in. Otherwise "Back to menu", to read why and
|
|
9140
|
+
// choose again.
|
|
9141
|
+
label: () => readProjectConfig() ? "Continue" : "Back to menu"
|
|
9142
|
+
});
|
|
9143
|
+
continue;
|
|
9144
|
+
}
|
|
9145
|
+
const code = await runOnScreen(source, flags, async () => {
|
|
9146
|
+
switch (action) {
|
|
9147
|
+
case "scan":
|
|
9148
|
+
return scanCommand([], flags);
|
|
9149
|
+
case "login":
|
|
9150
|
+
await login(resolveControlPlaneUrl(flags.controlPlaneUrl));
|
|
9151
|
+
return 0;
|
|
9152
|
+
case "help":
|
|
9153
|
+
printRootHelp(source().version);
|
|
9154
|
+
return 0;
|
|
9155
|
+
}
|
|
9156
|
+
return 0;
|
|
9157
|
+
});
|
|
9158
|
+
if (!isInteractive()) return code;
|
|
9159
|
+
}
|
|
9160
|
+
}
|
|
9161
|
+
async function initWithWelcome(args, flags) {
|
|
9162
|
+
await beginScreen(welcomeSource(flags), { animate: true });
|
|
9163
|
+
return initCommand(args, flags);
|
|
9164
|
+
}
|
|
9165
|
+
async function runOnScreen(source, flags, run2, options = {}) {
|
|
9166
|
+
await beginScreen(source, { compact: true });
|
|
9167
|
+
let code = 0;
|
|
9168
|
+
try {
|
|
9169
|
+
code = await run2();
|
|
9170
|
+
} catch (error) {
|
|
9171
|
+
code = error instanceof MenuCancelled ? 0 : reportError(error, flags);
|
|
9172
|
+
}
|
|
9173
|
+
const label = options.label ?? "Back";
|
|
9174
|
+
await pressBack(typeof label === "function" ? label() : label);
|
|
9175
|
+
return code;
|
|
9176
|
+
}
|
|
9177
|
+
|
|
8603
9178
|
// src/index.ts
|
|
8604
|
-
var CLI_VERSION = "1.0.0";
|
|
8605
9179
|
async function run(argv) {
|
|
8606
9180
|
const { flags, rest } = parseGlobalFlags(argv);
|
|
8607
9181
|
if (rest.includes("--version") || rest.includes("-V")) {
|
|
@@ -8610,9 +9184,9 @@ async function run(argv) {
|
|
|
8610
9184
|
}
|
|
8611
9185
|
const [command, ...args] = rest.filter((arg) => arg !== "--help" && arg !== "-h");
|
|
8612
9186
|
const wantsHelp = rest.includes("--help") || rest.includes("-h");
|
|
8613
|
-
if (command === void 0 && !wantsHelp &&
|
|
9187
|
+
if (command === void 0 && !wantsHelp && isInteractive()) {
|
|
8614
9188
|
try {
|
|
8615
|
-
return await initCommand(args, flags);
|
|
9189
|
+
return flags.json ? await initCommand(args, flags) : await homeCommand(args, flags);
|
|
8616
9190
|
} catch (error) {
|
|
8617
9191
|
return handleError(error, flags);
|
|
8618
9192
|
}
|
|
@@ -8632,7 +9206,7 @@ async function run(argv) {
|
|
|
8632
9206
|
switch (command) {
|
|
8633
9207
|
case "init":
|
|
8634
9208
|
case "setup":
|
|
8635
|
-
return await initCommand(args, flags);
|
|
9209
|
+
return flags.json || !isInteractive() ? await initCommand(args, flags) : await initWithWelcome(args, flags);
|
|
8636
9210
|
case "scan":
|
|
8637
9211
|
return await scanCommand(args, flags);
|
|
8638
9212
|
case "integrate":
|
|
@@ -8690,28 +9264,8 @@ async function run(argv) {
|
|
|
8690
9264
|
}
|
|
8691
9265
|
}
|
|
8692
9266
|
function handleError(error, flags) {
|
|
8693
|
-
if (error instanceof
|
|
8694
|
-
|
|
8695
|
-
err(`${style.red("\u2717")} ${error.message}`);
|
|
8696
|
-
if (error.hint) err(style.grey(` ${error.hint}`));
|
|
8697
|
-
err();
|
|
8698
|
-
return error.exitCode;
|
|
8699
|
-
}
|
|
8700
|
-
if (error instanceof CliApiError) {
|
|
8701
|
-
err();
|
|
8702
|
-
reportApiError(error);
|
|
8703
|
-
err();
|
|
8704
|
-
return 1;
|
|
8705
|
-
}
|
|
8706
|
-
err();
|
|
8707
|
-
err(`${style.red("\u2717")} ${error instanceof Error ? error.message : String(error)}`);
|
|
8708
|
-
if (flags.verbose && error instanceof Error && error.stack) {
|
|
8709
|
-
err(style.grey(error.stack));
|
|
8710
|
-
} else {
|
|
8711
|
-
err(style.grey(" Re-run with --verbose for the stack trace."));
|
|
8712
|
-
}
|
|
8713
|
-
err();
|
|
8714
|
-
return 1;
|
|
9267
|
+
if (error instanceof MenuCancelled) return error.exitCode;
|
|
9268
|
+
return reportError(error, flags);
|
|
8715
9269
|
}
|
|
8716
9270
|
function parseGlobalFlags(argv) {
|
|
8717
9271
|
const flags = { json: false, yes: false, verbose: false };
|