veefore 1.1.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.
Files changed (2) hide show
  1. package/dist/veefore.mjs +289 -118
  2. package/package.json +1 -1
package/dist/veefore.mjs CHANGED
@@ -638,7 +638,7 @@ var STARS = [
638
638
  [5, 34, "*"],
639
639
  [9, 32, "\xB7"]
640
640
  ];
641
- async function showWelcome(context) {
641
+ async function showWelcome(context, options = {}) {
642
642
  if (!isInteractive()) return;
643
643
  const width = Math.min(columns(), 100);
644
644
  const logoWidth = Math.max(...LOGO.map((line) => [...line].length));
@@ -651,7 +651,7 @@ async function showWelcome(context) {
651
651
  const sideBySide = width >= indent + logoWidth + gap + panelWidth + 2;
652
652
  const showLogo = width >= indent + logoWidth + 2;
653
653
  const rule = style.grey("\xB7".repeat(Math.max(10, Math.min(width - 2, 72))));
654
- const animate = process.env.VEEFORE_NO_ANIMATION === void 0;
654
+ const animate = (options.animate ?? true) && process.env.VEEFORE_NO_ANIMATION === void 0;
655
655
  out();
656
656
  out(` ${brand(style.bold("Welcome to Veefore"))} ${style.grey(`v${context.version}`)}`);
657
657
  out(` ${rule}`);
@@ -704,16 +704,66 @@ function renderPanel(facts) {
704
704
  }
705
705
  return lines;
706
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");
707
755
  var MenuCancelled = class extends CliError {
708
756
  constructor() {
709
757
  super("Cancelled.", void 0, 130);
710
758
  this.name = "MenuCancelled";
711
759
  }
712
760
  };
713
- async function select(question, options, settings = {}) {
761
+ async function select(question, real, settings = {}) {
762
+ const options = settings.back ? [...real, { label: settings.backLabel ?? "Back", value: BACK, hint: "\u2190" }] : real;
714
763
  const enabled = options.map((option, index) => option.disabled ? -1 : index).filter((i) => i >= 0);
715
764
  if (enabled.length === 0) throw new CliError(`Nothing can be chosen for "${question}".`);
716
- const fallback = enabled.includes(settings.initial ?? -1) ? settings.initial : enabled[0];
765
+ const choosable = enabled.filter((index) => options[index]?.value !== BACK);
766
+ const fallback = choosable.includes(settings.initial ?? -1) ? settings.initial : choosable[0] ?? enabled[0];
717
767
  if (!isInteractive()) return options[fallback].value;
718
768
  let cursor = fallback;
719
769
  let rendered = 0;
@@ -734,7 +784,11 @@ async function select(question, options, settings = {}) {
734
784
  const label = option.disabled ? style.grey(`${number} ${text}`) : active ? brand(`${number} ${text}`) : `${number} ${text}`;
735
785
  lines.push(fit(`${pointer} ${label}${hint}`, width - 1));
736
786
  });
737
- lines.push(style.grey(" \u2191\u2193 move \xB7 enter choose \xB7 esc cancel"));
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
+ );
738
792
  if (rendered > 0) stdout.write(`\x1B[${rendered}A\r\x1B[J`);
739
793
  stdout.write(`${lines.join("\n")}
740
794
  `);
@@ -758,7 +812,7 @@ async function select(question, options, settings = {}) {
758
812
  if (rendered > 0) stdout.write(`\x1B[${rendered}A\r\x1B[J`);
759
813
  restoreCursor();
760
814
  if (result) {
761
- if (!settings.quiet) {
815
+ if (!settings.quiet && result.value !== BACK) {
762
816
  stdout.write(`${style.green("\u2713")} ${question} ${brand(result.label)}
763
817
  `);
764
818
  }
@@ -777,6 +831,9 @@ async function select(question, options, settings = {}) {
777
831
  const onKey = (input, key = {}) => {
778
832
  if (key.ctrl && key.name === "c" || key.name === "escape") return finish(null);
779
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
+ }
780
837
  if (key.name === "up" || key.name === "k") move(-1);
781
838
  else if (key.name === "down" || key.name === "j" || key.name === "tab") move(1);
782
839
  else if (input && /^[1-9]$/.test(input)) {
@@ -3037,7 +3094,7 @@ function isLoosening(from, to) {
3037
3094
 
3038
3095
  // src/commands/init.ts
3039
3096
  import { readFileSync as readFileSync9 } from "node:fs";
3040
- import path10 from "node:path";
3097
+ import path11 from "node:path";
3041
3098
 
3042
3099
  // src/integrate.ts
3043
3100
  import { readFileSync as readFileSync8 } from "node:fs";
@@ -7211,6 +7268,48 @@ async function resolveAuthorizedTools(context, filter) {
7211
7268
  return match;
7212
7269
  }
7213
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
+
7214
7313
  // src/report.ts
7215
7314
  function reportError(error, flags) {
7216
7315
  if (error instanceof CliError) {
@@ -7543,7 +7642,7 @@ var DEFAULT_GATEWAY = "http://localhost:4010";
7543
7642
  async function initCommand(args, flags) {
7544
7643
  const options = parseInitArgs(args);
7545
7644
  const existing = readProjectConfig();
7546
- if (existing && !options.force && process.stdin.isTTY && process.stdout.isTTY && !flags.json) {
7645
+ if (existing && !options.force && isInteractive() && !flags.json) {
7547
7646
  return projectHome(existing, flags);
7548
7647
  }
7549
7648
  if (existing && !options.force) {
@@ -7584,20 +7683,34 @@ async function initCommand(args, flags) {
7584
7683
  }
7585
7684
  if (!session) throw new CliError("Sign-in did not produce a usable session.");
7586
7685
  const api = new ApiClient({ controlPlaneUrl, token: session.token });
7587
- const me = await api.get("/v1/auth/session");
7588
- const organization = await selectOrganization(api, me, options, flags);
7589
- const workspace = await selectWorkspace(api, me, options, flags);
7590
- const environment = await selectEnvironment(api, me, workspace.id, options, flags);
7591
- if (environment.type === "PRODUCTION" && !options.force) {
7592
- out();
7593
- out(
7594
- `${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.`
7595
- );
7596
- const proceed2 = await confirm("Continue with the production environment?", { yes: flags.yes });
7597
- if (!proceed2) {
7598
- out(style.grey("Cancelled. Re-run and choose a development environment."));
7599
- return 1;
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;
7600
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;
7712
+ }
7713
+ at += 1;
7601
7714
  }
7602
7715
  heading("Looking at your project");
7603
7716
  const scan = scanProject(process.cwd());
@@ -7606,15 +7719,41 @@ async function initCommand(args, flags) {
7606
7719
  agentName: options.agentName,
7607
7720
  extraCapabilities: options.capabilities
7608
7721
  });
7609
- if (!options.agentName && !flags.yes) {
7610
- plan.agentName = await ask(`Agent name ${style.grey(`[${plan.agentName}]`)}:`, plan.agentName);
7611
- }
7612
- printPlan2(plan, environment);
7613
- out();
7614
- const proceed = await confirm("Set this up?", { yes: flags.yes });
7615
- if (!proceed) {
7616
- out(style.grey("Nothing was created. Run `veefore init` again when you are ready."));
7617
- return 1;
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
+ }
7618
7757
  }
7619
7758
  heading("Setting up");
7620
7759
  let created;
@@ -7782,7 +7921,7 @@ async function initCommand(args, flags) {
7782
7921
  }
7783
7922
  function previousApprovalMode(dir = process.cwd()) {
7784
7923
  try {
7785
- const text = readFileSync9(path10.join(dir, ".env.local"), "utf8");
7924
+ const text = readFileSync9(path11.join(dir, ".env.local"), "utf8");
7786
7925
  const value = /^\s*VEEFORE_APPROVAL_MODE\s*=\s*['"]?(\w+)/m.exec(text)?.[1]?.toLowerCase();
7787
7926
  return value === "wait" || value === "throw" ? value : null;
7788
7927
  } catch {
@@ -7828,13 +7967,13 @@ async function selectOrganization(api, me, options, flags) {
7828
7967
  `Available: ${organizations.map((org) => org.id).join(", ")}`
7829
7968
  );
7830
7969
  }
7831
- return match;
7970
+ return { value: match, asked: false };
7832
7971
  }
7833
7972
  if (organizations.length === 1 || flags.yes) {
7834
7973
  const chosen2 = organizations[0];
7835
7974
  if (!chosen2) throw new CliError("Your account is not a member of any organization.");
7836
7975
  out(`${symbol.pass} Organization: ${style.bold(chosen2.name)}`);
7837
- return chosen2;
7976
+ return { value: chosen2, asked: false };
7838
7977
  }
7839
7978
  out();
7840
7979
  const chosen = await select(
@@ -7844,11 +7983,12 @@ async function selectOrganization(api, me, options, flags) {
7844
7983
  hint: `${org.roleKey} \xB7 ${org.id}`,
7845
7984
  value: org
7846
7985
  })),
7847
- { quiet: true }
7986
+ { quiet: true, back: true, backLabel: "Back" }
7848
7987
  );
7988
+ if (chosen === BACK) return BACK;
7849
7989
  await api.post("/v1/auth/scope", { organization_id: chosen.id });
7850
7990
  out(`${symbol.pass} Organization: ${style.bold(chosen.name)}`);
7851
- return chosen;
7991
+ return { value: chosen, asked: true };
7852
7992
  }
7853
7993
  async function selectWorkspace(api, me, options, flags) {
7854
7994
  const workspaces = me.workspaces.length > 0 ? me.workspaces : await api.get("/v1/workspaces");
@@ -7861,12 +8001,12 @@ async function selectWorkspace(api, me, options, flags) {
7861
8001
  if (options.workspaceId) {
7862
8002
  const match = workspaces.find((workspace) => workspace.id === options.workspaceId);
7863
8003
  if (!match) throw new CliError(`Workspace ${options.workspaceId} was not found.`);
7864
- return match;
8004
+ return { value: match, asked: false };
7865
8005
  }
7866
8006
  if (workspaces.length === 1 || flags.yes) {
7867
8007
  const chosen2 = workspaces[0];
7868
8008
  out(`${symbol.pass} Workspace: ${style.bold(chosen2.name)}`);
7869
- return chosen2;
8009
+ return { value: chosen2, asked: false };
7870
8010
  }
7871
8011
  out();
7872
8012
  const chosen = await select(
@@ -7876,10 +8016,11 @@ async function selectWorkspace(api, me, options, flags) {
7876
8016
  hint: workspace.id,
7877
8017
  value: workspace
7878
8018
  })),
7879
- { quiet: true }
8019
+ { quiet: true, back: true }
7880
8020
  );
8021
+ if (chosen === BACK) return BACK;
7881
8022
  out(`${symbol.pass} Workspace: ${style.bold(chosen.name)}`);
7882
- return chosen;
8023
+ return { value: chosen, asked: true };
7883
8024
  }
7884
8025
  async function selectEnvironment(api, me, workspaceId, options, flags) {
7885
8026
  const all = me.environments.length > 0 ? me.environments : await api.get("/v1/environments", { workspace_id: workspaceId });
@@ -7893,26 +8034,61 @@ async function selectEnvironment(api, me, workspaceId, options, flags) {
7893
8034
  if (options.environmentId) {
7894
8035
  const match = environments.find((environment) => environment.id === options.environmentId);
7895
8036
  if (!match) throw new CliError(`Environment ${options.environmentId} was not found.`);
7896
- return match;
8037
+ return { value: match, asked: false };
7897
8038
  }
7898
8039
  if (flags.yes) {
7899
- const chosen2 = environments.find((environment) => environment.type === "DEVELOPMENT") ?? environments[0];
7900
- out(`${symbol.pass} Environment: ${style.bold(chosen2.name)} (${chosen2.type})`);
7901
- return chosen2;
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 };
7902
8043
  }
7903
- out();
7904
8044
  const development = environments.findIndex((environment) => environment.type === "DEVELOPMENT");
7905
- const chosen = await select(
7906
- "Which environment?",
7907
- environments.map((environment) => ({
7908
- label: environment.name,
7909
- hint: `${environment.type} \xB7 ${environment.gatewayMode ?? "\u2014"} \xB7 ${environment.id}`,
7910
- value: environment
7911
- })),
7912
- { quiet: true, initial: development >= 0 ? development : 0 }
7913
- );
7914
- out(`${symbol.pass} Environment: ${style.bold(chosen.name)} (${chosen.type})`);
7915
- return chosen;
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 };
8069
+ }
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;
8091
+ }
7916
8092
  }
7917
8093
  async function discoverAudience(gatewayUrl) {
7918
8094
  try {
@@ -8111,13 +8287,11 @@ function parseInitArgs(args) {
8111
8287
  return options;
8112
8288
  }
8113
8289
  async function projectHome(existing, flags) {
8290
+ const source = welcomeSource(flags);
8114
8291
  let last = 0;
8115
8292
  for (let round = 0; ; round += 1) {
8293
+ await beginScreen(source, { animate: round === 0 });
8116
8294
  const state = await agentState(existing);
8117
- if (round > 0) {
8118
- out();
8119
- out(style.grey("\u2500".repeat(Math.min(process.stdout.columns ?? 72, 72))));
8120
- }
8121
8295
  printProjectSummary(existing, state);
8122
8296
  const choices = homeChoices(existing, state, flags);
8123
8297
  choices.push({ label: "Exit", hint: "esc", run: async () => last, leaves: true });
@@ -8132,24 +8306,32 @@ async function projectHome(existing, flags) {
8132
8306
  if (error instanceof MenuCancelled) return last;
8133
8307
  throw error;
8134
8308
  }
8135
- out();
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);
8315
+ }
8316
+ }
8317
+ await beginScreen(source, { compact: true });
8136
8318
  try {
8137
8319
  last = await picked.run();
8138
8320
  } catch (error) {
8139
8321
  last = error instanceof MenuCancelled ? last : reportError(error, flags);
8140
8322
  }
8141
- if (picked.leaves) return last;
8323
+ if (!isInteractive()) return last;
8324
+ await pressBack("Back to menu");
8142
8325
  }
8143
8326
  }
8144
8327
  function printProjectSummary(existing, state) {
8145
8328
  out();
8146
- out(`${style.bold(existing.agentName)} ${style.grey(`\xB7 ${existing.controlPlaneUrl}`)}`);
8147
8329
  details([
8148
8330
  ["Agent", `${existing.agentName} (${existing.agentId})`],
8149
8331
  ["Environment", `${existing.environmentName} (${existing.environmentType})`],
8150
8332
  [
8151
8333
  "Status",
8152
- 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"
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")
8153
8335
  ]
8154
8336
  ]);
8155
8337
  if (state.kind === "session-ended") {
@@ -8231,8 +8413,11 @@ function homeChoices(existing, state, flags) {
8231
8413
  run: async () => {
8232
8414
  await logout(existing.controlPlaneUrl);
8233
8415
  return 0;
8234
- },
8235
- leaves: true
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.
8236
8421
  });
8237
8422
  }
8238
8423
  return choices;
@@ -8920,14 +9105,15 @@ function wrap2(text, width) {
8920
9105
  }
8921
9106
 
8922
9107
  // src/home.ts
8923
- import { homedir as homedir2 } from "node:os";
8924
- import path11 from "node:path";
8925
- async function homeCommand(args, flags, version) {
8926
- await showWelcome(welcomeContext(flags, version));
8927
- if (readProjectConfig()) return initCommand(args, flags);
8928
- out();
8929
- out(style.bold("Let's get started."));
9108
+ async function homeCommand(args, flags) {
9109
+ const source = welcomeSource(flags);
9110
+ let first = true;
8930
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."));
8931
9117
  out();
8932
9118
  let action;
8933
9119
  try {
@@ -8946,65 +9132,50 @@ async function homeCommand(args, flags, version) {
8946
9132
  if (error instanceof MenuCancelled) return 0;
8947
9133
  throw error;
8948
9134
  }
8949
- out();
8950
- try {
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 () => {
8951
9146
  switch (action) {
8952
- case "setup":
8953
- return await initCommand(args, flags);
8954
9147
  case "scan":
8955
- await scanCommand([], flags);
8956
- break;
9148
+ return scanCommand([], flags);
8957
9149
  case "login":
8958
9150
  await login(resolveControlPlaneUrl(flags.controlPlaneUrl));
8959
- break;
9151
+ return 0;
8960
9152
  case "help":
8961
- printRootHelp(version);
8962
- break;
8963
- case "exit":
9153
+ printRootHelp(source().version);
8964
9154
  return 0;
8965
9155
  }
8966
- } catch (error) {
8967
- if (!(error instanceof MenuCancelled)) reportError(error, flags);
8968
- }
9156
+ return 0;
9157
+ });
9158
+ if (!isInteractive()) return code;
8969
9159
  }
8970
9160
  }
8971
- async function initWithWelcome(args, flags, version) {
8972
- await showWelcome(welcomeContext(flags, version));
9161
+ async function initWithWelcome(args, flags) {
9162
+ await beginScreen(welcomeSource(flags), { animate: true });
8973
9163
  return initCommand(args, flags);
8974
9164
  }
8975
- function welcomeContext(flags, version) {
8976
- const project = readProjectConfig();
8977
- const controlPlaneUrl = project?.controlPlaneUrl ?? resolveControlPlaneUrl(flags.controlPlaneUrl);
8978
- const session = loadSession(controlPlaneUrl);
8979
- const home = homedir2();
8980
- const cwd = process.cwd();
8981
- const folder = cwd === home || cwd.startsWith(`${home}${path11.sep}`) ? `~${cwd.slice(home.length)}` : cwd;
8982
- return {
8983
- version,
8984
- facts: [
8985
- ["Folder", shorten(folder, 34)],
8986
- ["Signed in", session ? session.email : style.grey("not yet")],
8987
- [
8988
- "Project",
8989
- project ? `${project.agentName} \xB7 ${project.environmentName}` : style.grey("not set up here")
8990
- ],
8991
- ["Veefore", hostOf(controlPlaneUrl)]
8992
- ]
8993
- };
8994
- }
8995
- function hostOf(url) {
9165
+ async function runOnScreen(source, flags, run2, options = {}) {
9166
+ await beginScreen(source, { compact: true });
9167
+ let code = 0;
8996
9168
  try {
8997
- return new URL(url).host;
8998
- } catch {
8999
- return url;
9169
+ code = await run2();
9170
+ } catch (error) {
9171
+ code = error instanceof MenuCancelled ? 0 : reportError(error, flags);
9000
9172
  }
9001
- }
9002
- function shorten(text, width) {
9003
- return text.length <= width ? text : `\u2026${text.slice(text.length - width + 1)}`;
9173
+ const label = options.label ?? "Back";
9174
+ await pressBack(typeof label === "function" ? label() : label);
9175
+ return code;
9004
9176
  }
9005
9177
 
9006
9178
  // src/index.ts
9007
- var CLI_VERSION = "1.1.0";
9008
9179
  async function run(argv) {
9009
9180
  const { flags, rest } = parseGlobalFlags(argv);
9010
9181
  if (rest.includes("--version") || rest.includes("-V")) {
@@ -9013,9 +9184,9 @@ async function run(argv) {
9013
9184
  }
9014
9185
  const [command, ...args] = rest.filter((arg) => arg !== "--help" && arg !== "-h");
9015
9186
  const wantsHelp = rest.includes("--help") || rest.includes("-h");
9016
- if (command === void 0 && !wantsHelp && process.stdin.isTTY && process.stdout.isTTY) {
9187
+ if (command === void 0 && !wantsHelp && isInteractive()) {
9017
9188
  try {
9018
- return flags.json ? await initCommand(args, flags) : await homeCommand(args, flags, CLI_VERSION);
9189
+ return flags.json ? await initCommand(args, flags) : await homeCommand(args, flags);
9019
9190
  } catch (error) {
9020
9191
  return handleError(error, flags);
9021
9192
  }
@@ -9035,7 +9206,7 @@ async function run(argv) {
9035
9206
  switch (command) {
9036
9207
  case "init":
9037
9208
  case "setup":
9038
- return flags.json || !isInteractive() ? await initCommand(args, flags) : await initWithWelcome(args, flags, CLI_VERSION);
9209
+ return flags.json || !isInteractive() ? await initCommand(args, flags) : await initWithWelcome(args, flags);
9039
9210
  case "scan":
9040
9211
  return await scanCommand(args, flags);
9041
9212
  case "integrate":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veefore",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Veefore command line: sign in, scan an agent project, set it up and protect its tools.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://veefore.com",