@vendian/cli 0.0.30 → 0.0.32

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/cli-wrapper.mjs +557 -1212
  2. package/package.json +2 -4
package/cli-wrapper.mjs CHANGED
@@ -1104,10 +1104,10 @@ function reportProgress(onProgress, message) {
1104
1104
  // src/tui.js
1105
1105
  import fs11 from "node:fs";
1106
1106
  import path8 from "node:path";
1107
- import readlinePromises from "node:readline/promises";
1107
+ import readline from "node:readline";
1108
1108
 
1109
1109
  // src/version.js
1110
- var CLI_VERSION = true ? "0.0.30" : process.env.npm_package_version || "0.0.0-dev";
1110
+ var CLI_VERSION = true ? "0.0.32" : process.env.npm_package_version || "0.0.0-dev";
1111
1111
 
1112
1112
  // src/npm-update.js
1113
1113
  var NPM_CHECK_INTERVAL_MS = 30 * 60 * 1e3;
@@ -1383,10 +1383,6 @@ function serveProcessExitMessage({ stderr = "", code = 0, signal = "" } = {}) {
1383
1383
  }
1384
1384
  return "";
1385
1385
  }
1386
- function serveProcessExitStderrTail(stderr = "", limit = 1200) {
1387
- const text = String(stderr || "").trim();
1388
- return text.length > limit ? text.slice(-limit) : text;
1389
- }
1390
1386
  function applyServeEvent(state, event) {
1391
1387
  if (!event || typeof event.type !== "string") {
1392
1388
  return state;
@@ -1678,9 +1674,6 @@ function appendAgentLog(agentLogs, event) {
1678
1674
  [key]: [...existing, normalized.entry].slice(-5e3)
1679
1675
  };
1680
1676
  }
1681
- function agentLogEntries(agentLogs, relativePath) {
1682
- return (agentLogs || {})[relativePath] || [];
1683
- }
1684
1677
  function mergeAgentLogRecords(agentLogs, records = []) {
1685
1678
  let next = agentLogs || {};
1686
1679
  for (const record of records) {
@@ -1872,36 +1865,6 @@ function serveEventAgentLogEntry(event) {
1872
1865
  }
1873
1866
  return null;
1874
1867
  }
1875
- function agentRuntimeStatus(agent, agentRunState = {}) {
1876
- const path9 = stringValue(agent?.relativePath || ".");
1877
- const run2 = (agentRunState || {})[path9];
1878
- const inventoryStatus = stringValue(agent?.status);
1879
- if (run2?.status === "running") {
1880
- return { status: "running", label: "running", run: run2 };
1881
- }
1882
- if (run2?.status === "preparing") {
1883
- return { status: "preparing", label: "preparing", run: run2 };
1884
- }
1885
- if (run2?.status === "error" || inventoryStatus === "error") {
1886
- return { status: "error", label: "error", run: run2 };
1887
- }
1888
- if (run2?.status === "disabled" || inventoryStatus === "disabled") {
1889
- return {
1890
- status: "disabled",
1891
- label: "disabled",
1892
- run: run2,
1893
- disabledReason: run2?.disabledReason || agent?.disabledReason || "System dependency not met locally",
1894
- resolutionHints: run2?.resolutionHints || agent?.resolutionHints || []
1895
- };
1896
- }
1897
- if (inventoryStatus === "online") {
1898
- return { status: run2?.status === "completed" ? "completed" : "ready", label: "ready", run: run2 };
1899
- }
1900
- if (run2?.status === "completed") {
1901
- return { status: "completed", label: "ready", run: run2 };
1902
- }
1903
- return { status: inventoryStatus || "unknown", label: inventoryStatus || "unknown", run: run2 };
1904
- }
1905
1868
  function appendError(errors, scope, message) {
1906
1869
  return [...errors, { scope, message: stringValue(message) }].slice(-20);
1907
1870
  }
@@ -2391,288 +2354,152 @@ var ascii = {
2391
2354
  var fig = supportsUnicode ? unicode : ascii;
2392
2355
 
2393
2356
  // src/tui.js
2394
- var term;
2395
- async function loadTerm() {
2396
- if (term) return;
2397
- const tk = await import("terminal-kit");
2398
- term = tk.default.terminal;
2399
- }
2400
- var HEADER_ROWS_BASE = 7;
2401
- var SERVE_CONTENT_ROW = 10;
2402
- function clampListIndex(index, count) {
2403
- if (!Number.isFinite(count) || count <= 0) return 0;
2404
- if (!Number.isFinite(index)) return 0;
2405
- return Math.max(0, Math.min(count - 1, index));
2406
- }
2407
- function centeredWindowStart(selectedIdx, visibleCount, totalCount) {
2408
- if (visibleCount >= totalCount) return 0;
2409
- const half = Math.floor(visibleCount / 2);
2410
- const maxStart = Math.max(0, totalCount - visibleCount);
2411
- return Math.max(0, Math.min(maxStart, selectedIdx - half));
2412
- }
2413
- function computeServeDashboardViewport({
2414
- termHeight = 24,
2415
- agentCount = 0,
2416
- selectedIdx = 0,
2417
- activityCount = 0,
2418
- diagnosticsCount = 0
2419
- } = {}) {
2420
- const availableRows = Math.max(4, (termHeight || 24) - SERVE_CONTENT_ROW + 1);
2421
- const clampedSelectedIdx = clampListIndex(selectedIdx, agentCount);
2422
- const viewport = {
2423
- selectedIdx: clampedSelectedIdx,
2424
- showBlankAfterStatus: false,
2425
- showAgentHeader: agentCount > 0,
2426
- visibleAgentCount: 0,
2427
- agentWindowStart: 0,
2428
- showAgentHint: false,
2429
- showAgentRange: false,
2430
- visibleActivityCount: 0,
2431
- showActivityHeader: false,
2432
- visibleDiagnosticsCount: 0,
2433
- showDiagnosticsHeader: false,
2434
- showInlineErrors: (termHeight || 24) >= 24
2435
- };
2436
- let remaining = availableRows;
2437
- remaining -= 1;
2438
- remaining -= 1;
2439
- if (agentCount > 0) {
2440
- remaining -= 1;
2441
- viewport.visibleAgentCount = 1;
2442
- remaining -= 1;
2443
- } else {
2444
- remaining -= 1;
2445
- }
2446
- if (remaining > 0) {
2447
- viewport.showBlankAfterStatus = true;
2448
- remaining -= 1;
2449
- }
2450
- if (agentCount > 1 && remaining > 0) {
2451
- const extraAgentRows = Math.min(agentCount - 1, remaining);
2452
- viewport.visibleAgentCount += extraAgentRows;
2453
- remaining -= extraAgentRows;
2454
- }
2455
- if (agentCount > 0) {
2456
- viewport.agentWindowStart = centeredWindowStart(
2457
- clampedSelectedIdx,
2458
- viewport.visibleAgentCount,
2459
- agentCount
2460
- );
2461
- viewport.showAgentHint = remaining > 0;
2462
- if (viewport.showAgentHint) remaining -= 1;
2463
- viewport.showAgentRange = viewport.visibleAgentCount > 0 && viewport.visibleAgentCount < agentCount;
2464
- }
2465
- if (activityCount > 0 && remaining > 1) {
2466
- viewport.showActivityHeader = true;
2467
- remaining -= 1;
2468
- viewport.visibleActivityCount = Math.min(activityCount, remaining);
2469
- remaining -= viewport.visibleActivityCount;
2470
- }
2471
- if (diagnosticsCount > 0 && remaining > 1) {
2472
- viewport.showDiagnosticsHeader = true;
2473
- remaining -= 1;
2474
- viewport.visibleDiagnosticsCount = Math.min(diagnosticsCount, remaining);
2475
- }
2476
- return viewport;
2357
+ var ANSI = {
2358
+ reset: "\x1B[0m",
2359
+ bold: "\x1B[1m",
2360
+ gray: "\x1B[90m",
2361
+ cyan: "\x1B[36m",
2362
+ green: "\x1B[32m",
2363
+ yellow: "\x1B[33m",
2364
+ red: "\x1B[31m",
2365
+ blue: "\x1B[94m"
2366
+ };
2367
+ function colorize(code, text) {
2368
+ return process.stdout.isTTY ? `${code}${String(text)}${ANSI.reset}` : String(text);
2369
+ }
2370
+ var col = {
2371
+ bold: (t) => colorize(ANSI.bold, t),
2372
+ gray: (t) => colorize(ANSI.gray, t),
2373
+ cyan: (t) => colorize(ANSI.cyan, t),
2374
+ green: (t) => colorize(ANSI.green, t),
2375
+ yellow: (t) => colorize(ANSI.yellow, t),
2376
+ red: (t) => colorize(ANSI.red, t),
2377
+ blue: (t) => colorize(ANSI.blue, t)
2378
+ };
2379
+ function print(line = "") {
2380
+ process.stdout.write(`${line}
2381
+ `);
2477
2382
  }
2478
- function stopSignalForAttempt(attempt = 0) {
2479
- if (!Number.isFinite(attempt) || attempt <= 0) return "SIGINT";
2480
- if (attempt === 1) return "SIGTERM";
2481
- return "SIGKILL";
2383
+ function hr(w) {
2384
+ return "\u2500".repeat(Math.min(w || 60, process.stdout.columns || 80));
2482
2385
  }
2483
- async function runTui({
2484
- env = process.env,
2485
- platform = process.platform,
2486
- output = process.stdout
2487
- } = {}) {
2488
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
2489
- output.write(`${helpText()}
2490
- `);
2491
- return;
2492
- }
2493
- await refreshInteractiveManagedRuntime({ env, platform, output });
2494
- await loadTerm();
2495
- term.fullscreen(true);
2496
- setCursorVisible(false);
2497
- term.grabInput({ mouse: "motion" });
2498
- const cleanup = () => {
2499
- try {
2500
- term.grabInput(false);
2501
- setCursorVisible(true);
2502
- term.fullscreen(false);
2503
- } catch {
2504
- }
2505
- };
2506
- process.once("exit", cleanup);
2507
- try {
2508
- await mainLoop({ env, platform });
2509
- } finally {
2510
- process.off("exit", cleanup);
2511
- cleanup();
2512
- }
2386
+ function setCursorVisible(visible) {
2387
+ if (!process.stdout.isTTY) return;
2388
+ process.stdout.write(visible ? "\x1B[?25h" : "\x1B[?25l");
2513
2389
  }
2514
- async function refreshInteractiveManagedRuntime({
2515
- env = process.env,
2516
- platform = process.platform,
2517
- output = process.stderr,
2518
- refreshPackageAccess = refreshPackageAccessFromCloudAuth,
2519
- autoUpdate = maybeAutoUpdateManagedEnv
2520
- } = {}) {
2521
- if (env.VENDIAN_SKIP_AUTO_UPDATE === "1") return false;
2390
+ function makeRl() {
2391
+ return readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
2392
+ }
2393
+ async function ask(question, defaultValue = "") {
2394
+ const rl = makeRl();
2395
+ const suffix = defaultValue ? ` [${defaultValue}]` : "";
2396
+ return new Promise((resolve) => {
2397
+ rl.question(` ${question}${suffix}: `, (answer) => {
2398
+ rl.close();
2399
+ resolve(answer.trim() || defaultValue);
2400
+ });
2401
+ });
2402
+ }
2403
+ async function pressEnterToContinue({ grabActive = true } = {}) {
2404
+ return new Promise((resolve) => {
2405
+ const rl = makeRl();
2406
+ process.stdout.write("\n Press Enter to continue\u2026 ");
2407
+ rl.once("line", () => {
2408
+ rl.close();
2409
+ resolve();
2410
+ });
2411
+ rl.once("close", resolve);
2412
+ });
2413
+ }
2414
+ async function withOutputMode(label, fn) {
2415
+ setCursorVisible(true);
2416
+ print("");
2417
+ print(` ${label}`);
2418
+ print(` ${"\u2500".repeat(50)}`);
2419
+ print("");
2522
2420
  try {
2523
- await refreshPackageAccess({ env, platform });
2421
+ await fn();
2524
2422
  } catch (error) {
2525
- output.write(`[vendian] Package access refresh failed; continuing. ${errMsg(error)}
2526
- `);
2527
- }
2528
- return autoUpdate({ env, platform, force: true });
2529
- }
2530
- async function mainLoop({ env, platform }) {
2531
- let screen = "home";
2532
- while (true) {
2533
- let next;
2534
- switch (screen) {
2535
- case "run":
2536
- next = await showRun({ env, platform });
2537
- break;
2538
- case "home":
2539
- next = await showHome({ env, platform });
2540
- break;
2541
- case "connect":
2542
- next = await showConnect({ env, platform });
2543
- break;
2544
- case "create":
2545
- next = await showCreate({ env, platform });
2546
- break;
2547
- case "serve":
2548
- next = await showServe({ env, platform });
2549
- break;
2550
- case "commands":
2551
- next = await showCommands();
2552
- break;
2553
- default:
2554
- next = "exit";
2555
- }
2556
- if (!next || next === "exit") break;
2557
- screen = next;
2423
+ print(`
2424
+ Something went wrong: ${errMsg(error)}`);
2558
2425
  }
2426
+ await pressEnterToContinue({ grabActive: false });
2559
2427
  }
2560
- function drawHeader({ env, platform, serveState = null } = {}) {
2428
+ function printHeader({ env, platform } = {}) {
2561
2429
  const active = activeCloudAuthStatus({ env, platform });
2562
- const runtime = runtimeSummary({ env, platform });
2563
- const pkg = packageAccessSummary({ env, platform });
2564
- const w = Math.min(term.width || 80, 90);
2565
- const inner = w - 2;
2566
- const ver = `v${CLI_VERSION}`;
2567
- const title = ` ${fig.arrowUp} VENDIAN`;
2568
- const gap = Math.max(1, inner - title.length - ver.length - 2);
2569
- term.moveTo(1, 1);
2570
- term.gray(`\u256D${"\u2500".repeat(inner)}\u256E
2571
- `);
2572
- term.gray("\u2502");
2573
- term.cyan.bold(title);
2574
- term(" ".repeat(gap));
2575
- term.gray(`${ver} \u2502
2576
- `);
2577
- term.gray(`\u251C${"\u2500".repeat(inner)}\u2524
2578
- `);
2579
- const accountVal = active.authenticated ? `${active.email || "Signed in"} \u2014 ${envLabel(active.apiUrl)}` : "Not signed in \u2014 choose Sign in from the menu";
2580
- drawHdrRow(inner, active.authenticated ? "ok" : "warn", "Account", accountVal);
2581
- const systemOk = runtime.installed && pkg.configured;
2582
- const systemVal = systemOk ? "Ready to run agents" : !runtime.installed ? "Not set up yet \u2014 choose Sign in from the menu" : "Package access missing \u2014 choose Sign in from the menu";
2583
- drawHdrRow(inner, systemOk ? "ok" : "warn", "System", systemVal);
2584
- if (serveState !== null) {
2585
- const st = serveState.stopped ? "err" : serveState.connected ? "ok" : "warn";
2586
- const val = serveState.stopped ? "Stopped" : serveState.connected ? `Running \u2014 ${clip(serveState.daemonId || "", 16)}` : clip(serveState.activity || "Starting up\u2026", 52);
2587
- drawHdrRow(inner, st, "Agents", val);
2588
- }
2589
- term.gray(`\u2570${"\u2500".repeat(inner)}\u256F
2590
- `);
2591
- }
2592
- function drawHdrRow(inner, status, label, value) {
2593
- const maxVal = Math.max(0, inner - 18);
2594
- const clipped = clip(value, maxVal);
2595
- const pad = Math.max(0, inner - 15 - clipped.length - 3);
2596
- term.gray("\u2502 ");
2597
- if (status === "ok") term.green("\u25CF");
2598
- else if (status === "warn") term.yellow("\u25CF");
2599
- else term.red("\u25CF");
2600
- term(" ");
2601
- term.bold(label.padEnd(10));
2602
- term(clipped);
2603
- term(" ".repeat(pad));
2604
- term.gray(" \u2502\n");
2430
+ const acct = active.authenticated ? `${active.email || "signed in"} @ ${envLabel(active.apiUrl)}` : col.yellow("not signed in");
2431
+ const W = Math.min(process.stdout.columns || 80, 100);
2432
+ print(col.gray(hr(W)));
2433
+ print(` ${col.cyan(col.bold("\u2191 VENDIAN"))} ${col.gray(`v${CLI_VERSION}`)} ${col.gray("\u2502")} ${acct}`);
2434
+ print(col.gray(hr(W)));
2605
2435
  }
2606
2436
  function envLabel(apiUrl) {
2607
2437
  if (!apiUrl) return "";
2608
- if (apiUrl.includes("localhost") || apiUrl.includes("127.0.0.1")) return "Local";
2609
- if (apiUrl.includes("staging")) return "Staging";
2610
- if (apiUrl.includes(".dev.")) return "Development";
2611
- if (apiUrl.includes("vendian.ai") && !apiUrl.includes(".")) return "Production";
2438
+ if (apiUrl.includes("localhost") || apiUrl.includes("127.0.0.1")) return "local";
2439
+ if (apiUrl.includes("staging")) return "staging";
2440
+ if (apiUrl.includes(".dev.")) return "dev";
2612
2441
  return clip(apiUrl.replace(/^https?:\/\//, ""), 30);
2613
2442
  }
2614
2443
  var HOME_ACTIONS = [
2615
- { value: "serve", label: "Run my agents", desc: "Start your agents and keep them running" },
2616
- { value: "connect", label: "Sign in", desc: "Connect to a different account or environment" },
2444
+ { value: "serve", label: "Serve my agents", desc: "Start agents and stream their logs" },
2445
+ { value: "connect", label: "Login / Switch", desc: "Sign in or switch to a different environment" },
2617
2446
  { value: "create", label: "Create Agent", desc: "Build a new agent from a template" },
2618
- { value: "commands", label: "Commands", desc: "Show all available CLI commands" },
2619
- { value: "run", label: "Run once", desc: "Execute one local agent immediately" },
2447
+ { value: "run", label: "Run once", desc: "Execute one agent immediately" },
2448
+ { value: "commands", label: "Commands", desc: "Show all CLI commands" },
2620
2449
  { value: "exit", label: "Exit", desc: "" }
2621
2450
  ];
2622
2451
  async function showHome({ env, platform }) {
2623
- term.clear();
2624
- drawHeader({ env, platform });
2625
- term("\n");
2626
- term.gray(" What would you like to do?\n\n");
2627
- const items = HOME_ACTIONS.map(
2628
- (a) => a.desc ? `${a.label.padEnd(20)} ${a.desc}` : a.label
2629
- );
2630
- const resp = await term.singleColumnMenu(items, {
2631
- cancelable: false,
2632
- style: term.gray,
2633
- selectedStyle: term.bgCyan.black.bold,
2634
- leftPadding: " ",
2635
- selectedLeftPadding: " \u203A "
2636
- }).promise;
2637
- if (resp.canceled) return "exit";
2638
- return HOME_ACTIONS[resp.selectedIndex]?.value ?? "exit";
2452
+ print("");
2453
+ printHeader({ env, platform });
2454
+ print("");
2455
+ print(col.bold(" What would you like to do?"));
2456
+ print("");
2457
+ HOME_ACTIONS.forEach((action, i) => {
2458
+ const desc = action.desc ? col.gray(` \u2014 ${action.desc}`) : "";
2459
+ print(` ${col.blue(String(i + 1))} ${action.label.padEnd(22)}${desc}`);
2460
+ });
2461
+ print("");
2462
+ const ans = await ask("Choose a number");
2463
+ const n = parseInt(ans, 10);
2464
+ if (!Number.isFinite(n) || n < 1 || n > HOME_ACTIONS.length) return "home";
2465
+ return HOME_ACTIONS[n - 1].value;
2639
2466
  }
2640
2467
  var ENDPOINTS = [
2641
2468
  { key: "prod", label: "Production", subtitle: "Recommended for live use" },
2642
2469
  { key: "staging", label: "Staging", subtitle: "Pre-release testing" },
2643
2470
  { key: "dev", label: "Development", subtitle: "Developer preview" },
2644
- { key: "local", label: "Local (your computer)", subtitle: "Runs on localhost:3000" }
2471
+ { key: "local", label: "Local (localhost:3000)", subtitle: "Local backend" }
2645
2472
  ];
2646
2473
  async function showConnect({ env, platform }) {
2647
2474
  while (true) {
2648
- term.clear();
2649
- drawHeader({ env, platform });
2650
- term("\n");
2651
- term.bold(" Sign in to your account:\n\n");
2475
+ print("");
2476
+ printHeader({ env, platform });
2477
+ print("");
2478
+ print(col.bold(" Login / Switch environment:"));
2479
+ print("");
2652
2480
  const rows = endpointRows({ env, platform });
2653
2481
  const items = [
2654
2482
  ...ENDPOINTS.map((ep2) => {
2655
2483
  const row = rows.find((r) => r.key === ep2.key);
2656
- const badge = row?.active ? " \u25CF signed in (active)" : row?.status === "signed in" ? " \u25CB signed in" : ` \u2014 ${ep2.subtitle}`;
2484
+ const badge = row?.active ? col.green(" \u25CF active") : row?.status === "signed in" ? col.cyan(" \u25CB signed in") : col.gray(` \u2014 ${ep2.subtitle}`);
2657
2485
  return `${ep2.label.padEnd(26)}${badge}`;
2658
2486
  }),
2659
2487
  "Custom server URL\u2026",
2660
- "\u2190 Back"
2488
+ col.gray("Back")
2661
2489
  ];
2662
- const resp = await term.singleColumnMenu(items, {
2663
- cancelable: true,
2664
- style: term.gray,
2665
- selectedStyle: term.bgCyan.black.bold,
2666
- leftPadding: " ",
2667
- selectedLeftPadding: " \u203A "
2668
- }).promise;
2669
- if (resp.canceled || resp.selectedIndex === items.length - 1) return "home";
2670
- if (resp.selectedIndex === items.length - 2) {
2490
+ items.forEach((item, i) => print(` ${col.blue(String(i + 1))} ${item}`));
2491
+ print("");
2492
+ const ans = await ask("Choose a number, or 0 to go back");
2493
+ const n = parseInt(ans, 10);
2494
+ if (n === 0 || isNaN(n)) return "home";
2495
+ if (n === items.length) return "home";
2496
+ if (n < 1 || n > items.length) continue;
2497
+ if (n === items.length - 1) {
2671
2498
  await connectCustomUrl({ env, platform });
2672
2499
  continue;
2673
2500
  }
2674
- const ep = ENDPOINTS[resp.selectedIndex];
2675
- term("\n");
2501
+ const ep = ENDPOINTS[n - 1];
2502
+ print("");
2676
2503
  const outcome = await connectEndpointInteractive({ backend: ep.key, label: ep.label, env, platform });
2677
2504
  if (outcome.promptedInTui) {
2678
2505
  await pressEnterToContinue();
@@ -2680,74 +2507,18 @@ async function showConnect({ env, platform }) {
2680
2507
  }
2681
2508
  }
2682
2509
  async function connectCustomUrl({ env, platform }) {
2683
- term.clear();
2684
- drawHeader({ env, platform });
2685
- term("\n");
2686
- term.bold(" Enter your server URL:\n\n");
2687
- term.gray(" URL: ");
2688
- const url = await term.inputField({ cancelable: true, style: term.cyan, default: "https://" }).promise;
2689
- if (!url) return;
2690
- term("\n\n");
2691
- term.gray(" Connecting\u2026\n");
2692
- const outcome = await connectEndpointInteractive({ apiUrl: url, label: url, env, platform, successLabel: "Connected successfully" });
2510
+ print("");
2511
+ print(col.bold(" Enter your server URL:"));
2512
+ print("");
2513
+ const url = await ask("URL", "https://");
2514
+ if (!url || url === "https://") return;
2515
+ print("");
2516
+ print(col.gray(" Connecting\u2026"));
2517
+ const outcome = await connectEndpointInteractive({ apiUrl: url, label: url, env, platform });
2693
2518
  if (outcome.promptedInTui) {
2694
2519
  await pressEnterToContinue();
2695
2520
  }
2696
2521
  }
2697
- async function showCreate({ env, platform }) {
2698
- term.clear();
2699
- drawHeader({ env, platform });
2700
- term("\n");
2701
- term.bold(" Create a new agent\n\n");
2702
- term.gray(" Agent name: ");
2703
- const name = await term.inputField({ cancelable: true, style: term.cyan }).promise;
2704
- if (!name?.trim()) return "home";
2705
- term("\n");
2706
- term.gray(" Save it in folder: ");
2707
- const dir = await term.inputField({ cancelable: true, style: term.cyan, default: "./agents" }).promise;
2708
- if (dir === null) return "home";
2709
- term("\n");
2710
- await withOutputMode(
2711
- `Creating agent: ${name.trim()}`,
2712
- () => forwardToPythonVendian(["create", name.trim(), "--output-dir", dir || "./agents"], { env, platform })
2713
- );
2714
- return "home";
2715
- }
2716
- async function showRun({ env, platform }) {
2717
- const candidates = findAgentDirectoryCandidates();
2718
- const picked = await pickSingleAgentToRun({ env, platform, candidates });
2719
- if (!picked) return "home";
2720
- const workspace = await chooseCloudWorkspace({
2721
- env,
2722
- platform,
2723
- loadingTitle: " Getting ready to run this agent\u2026",
2724
- pickerTitle: " Which project should own this run?\n\n"
2725
- });
2726
- if (!workspace) return "home";
2727
- term.clear();
2728
- drawHeader({ env, platform });
2729
- term("\n");
2730
- term.bold(` Run ${picked.displayName}
2731
-
2732
- `);
2733
- term.gray(` Agent: ${picked.path}
2734
- `);
2735
- term.gray(` Project: ${workspace.label}
2736
-
2737
- `);
2738
- term.gray(" Input JSON: ");
2739
- const inputJson = await term.inputField({ cancelable: true, style: term.cyan, default: "{}" }).promise;
2740
- if (inputJson === null) return "home";
2741
- await withOutputMode(
2742
- `Running ${picked.displayName}`,
2743
- () => forwardToPythonVendian(buildLocalRunArgs({
2744
- agentPath: picked.path,
2745
- collectionId: workspace.id,
2746
- inputJson: inputJson || "{}"
2747
- }), { env, platform })
2748
- );
2749
- return "home";
2750
- }
2751
2522
  var COMMAND_GROUPS = [
2752
2523
  { section: "Getting started", commands: [
2753
2524
  { cmd: "vendian login", desc: "Sign in and set up your computer" },
@@ -2756,13 +2527,12 @@ var COMMAND_GROUPS = [
2756
2527
  { section: "Building agents", commands: [
2757
2528
  { cmd: 'vendian create "My Agent" --output-dir .', desc: "Create a new agent" },
2758
2529
  { cmd: "vendian validate ./agents/my-agent", desc: "Check an agent for errors" },
2759
- { cmd: "vendian test ./agents/my-agent --dry-run", desc: "Test an agent without running it" },
2760
2530
  { cmd: "vendian models", desc: "List available AI models" }
2761
2531
  ] },
2762
2532
  { section: "Running agents", commands: [
2763
- { cmd: "vendian cloud local run --collection-id ID --path . --input-json '{}'", desc: "Run one local agent once" },
2764
- { cmd: "vendian cloud local serve --agents-dir .", desc: "Start agents (advanced mode)" },
2765
- { cmd: "vendian login --backend staging", desc: "Sign in to staging environment" }
2533
+ { cmd: "vendian cloud local run --collection-id ID --path . --input-json '{}'", desc: "Run one agent" },
2534
+ { cmd: "vendian cloud local serve --agents-dir .", desc: "Start agents" },
2535
+ { cmd: "vendian login --backend staging", desc: "Sign in to staging" }
2766
2536
  ] },
2767
2537
  { section: "Maintenance", commands: [
2768
2538
  { cmd: "vendian doctor", desc: "Check if everything is set up" },
@@ -2770,89 +2540,52 @@ var COMMAND_GROUPS = [
2770
2540
  ] }
2771
2541
  ];
2772
2542
  async function showCommands() {
2773
- term.clear();
2774
- const w = Math.min(term.width || 80, 90);
2775
- term.gray(`\u256D${"\u2500".repeat(w - 2)}\u256E
2776
- `);
2777
- term.gray("\u2502");
2778
- term.cyan.bold(` ${fig.arrowUp} VENDIAN All Commands`);
2779
- term(" ".repeat(Math.max(0, w - 26)));
2780
- term.gray("\u2502\n");
2781
- term.gray(`\u2570${"\u2500".repeat(w - 2)}\u256F
2782
-
2783
- `);
2543
+ print("");
2544
+ print(col.bold(` \u2191 VENDIAN v${CLI_VERSION} \u2014 All Commands`));
2545
+ print("");
2784
2546
  for (const g of COMMAND_GROUPS) {
2785
- term.brightBlue.bold(` ${g.section}
2786
- `);
2547
+ print(col.blue(col.bold(` ${g.section}`)));
2787
2548
  for (const item of g.commands) {
2788
- term.gray(" ");
2789
- term.cyan(item.cmd.padEnd(46));
2790
- term.gray(`${item.desc}
2791
- `);
2549
+ print(` ${col.cyan(item.cmd.padEnd(52))} ${col.gray(item.desc)}`);
2792
2550
  }
2793
- term("\n");
2551
+ print("");
2794
2552
  }
2795
- term.gray(" Press any key to go back\u2026");
2796
- await waitForKey();
2553
+ await pressEnterToContinue();
2797
2554
  return "home";
2798
2555
  }
2799
- async function showServe({ env, platform }) {
2800
- const candidates = findAgentDirectoryCandidates();
2801
- const picked = await pickAgentsToRun({ env, platform, candidates });
2802
- if (!picked) return "home";
2803
- const { agentsDir } = picked;
2804
- const workspace = await chooseCloudWorkspace({
2805
- env,
2806
- platform,
2807
- loadingTitle: " Getting ready to run your agents\u2026",
2808
- pickerTitle: " Which project do you want to run?\n\n"
2809
- });
2810
- if (!workspace) return "home";
2811
- return await runServeDashboard({ env, platform, agentsDir, collectionId: workspace.id });
2812
- }
2813
2556
  async function pickAgentsToRun({ env, platform, candidates }) {
2814
2557
  if (candidates.length === 0) {
2815
- term.clear();
2816
- drawHeader({ env, platform });
2817
- term("\n");
2818
- term.bold(" I couldn't find any agents on your computer.\n\n");
2819
- term.gray(" Enter the path to your agents folder:\n\n");
2820
- term.gray(" Folder: ");
2821
- const input = await term.inputField({ cancelable: true, style: term.cyan, default: "./agents" }).promise;
2822
- if (input === null) return null;
2823
- return { agentsDir: input || "./agents" };
2558
+ print("");
2559
+ print(col.yellow(" Couldn't find any agents. Enter your agents folder path:"));
2560
+ const input = await ask("Folder", "./agents");
2561
+ return input ? { agentsDir: input } : null;
2824
2562
  }
2825
2563
  if (candidates.length === 1 && candidates[0].agentCount === 1) {
2826
2564
  return { agentsDir: candidates[0].path };
2827
2565
  }
2828
2566
  if (candidates.length === 1) {
2829
- return await pickAgentsFromFolder({ env, platform, candidate: candidates[0] });
2567
+ return pickAgentsFromFolder({ env, platform, candidate: candidates[0] });
2830
2568
  }
2831
- return await pickProject({ env, platform, candidates });
2569
+ return pickProject({ env, platform, candidates });
2832
2570
  }
2833
2571
  async function pickProject({ env, platform, candidates }) {
2834
- term.clear();
2835
- drawHeader({ env, platform });
2836
- term("\n");
2837
- term.bold(" Which project do you want to run?\n\n");
2838
- const items = [
2839
- ...candidates.map((c) => {
2840
- const projectName = readManifestName(c.absolutePath) || friendlyName(path8.basename(c.absolutePath) || c.path);
2841
- return `${projectName.padEnd(32)} ${c.agentCount} ${c.agentCount === 1 ? "agent" : "agents"}`;
2842
- }),
2843
- "\u2190 Back"
2844
- ];
2845
- const resp = await term.singleColumnMenu(items, {
2846
- cancelable: true,
2847
- style: term.gray,
2848
- selectedStyle: term.bgCyan.black.bold,
2849
- leftPadding: " ",
2850
- selectedLeftPadding: " \u203A "
2851
- }).promise;
2852
- if (resp.canceled || resp.selectedIndex === items.length - 1) return null;
2853
- const chosen = candidates[resp.selectedIndex];
2572
+ print("");
2573
+ printHeader({ env, platform });
2574
+ print("");
2575
+ print(col.bold(" Which project do you want to run?"));
2576
+ print("");
2577
+ candidates.forEach((candidate, i) => {
2578
+ const name = readManifestName(candidate.absolutePath) || friendlyName(path8.basename(candidate.absolutePath) || candidate.path);
2579
+ print(` ${col.blue(String(i + 1))} ${name.padEnd(32)} ${col.gray(`${candidate.agentCount} agent${candidate.agentCount === 1 ? "" : "s"}`)}`);
2580
+ });
2581
+ print(` ${col.gray("0 Back")}`);
2582
+ print("");
2583
+ const ans = await ask("Choose");
2584
+ const n = parseInt(ans, 10);
2585
+ if (n === 0 || isNaN(n) || n > candidates.length) return null;
2586
+ const chosen = candidates[n - 1];
2854
2587
  if (chosen.agentCount <= 1) return { agentsDir: chosen.path };
2855
- return await pickAgentsFromFolder({ env, platform, candidate: chosen });
2588
+ return pickAgentsFromFolder({ env, platform, candidate: chosen });
2856
2589
  }
2857
2590
  async function pickAgentsFromFolder({ env, platform, candidate }) {
2858
2591
  const folders = findAgentFolders(candidate.absolutePath);
@@ -2861,39 +2594,28 @@ async function pickAgentsFromFolder({ env, platform, candidate }) {
2861
2594
  ...f,
2862
2595
  displayName: readManifestName(f.absolutePath) || friendlyName(f.name || path8.basename(f.path))
2863
2596
  }));
2864
- term.clear();
2865
- drawHeader({ env, platform });
2866
- term("\n");
2867
- term.bold(" Which agents do you want to start?\n\n");
2868
- term.gray(" Tip: running fewer agents uses less memory and starts faster.\n\n");
2869
- const items = [
2870
- `Run all ${folders.length} agents`,
2871
- ...named.map((f) => f.displayName),
2872
- "\u2190 Back"
2873
- ];
2874
- const resp = await term.singleColumnMenu(items, {
2875
- cancelable: true,
2876
- style: term.gray,
2877
- selectedStyle: term.bgCyan.black.bold,
2878
- leftPadding: " ",
2879
- selectedLeftPadding: " \u203A "
2880
- }).promise;
2881
- if (resp.canceled || resp.selectedIndex === items.length - 1) return null;
2882
- if (resp.selectedIndex === 0) return { agentsDir: candidate.path };
2883
- return { agentsDir: named[resp.selectedIndex - 1].path };
2597
+ print("");
2598
+ printHeader({ env, platform });
2599
+ print("");
2600
+ print(col.bold(" Which agents do you want to start?"));
2601
+ print("");
2602
+ print(` ${col.blue("1")} Run all ${folders.length} agents`);
2603
+ named.forEach((f, i) => print(` ${col.blue(String(i + 2))} ${f.displayName}`));
2604
+ print(` ${col.gray("0 Back")}`);
2605
+ print("");
2606
+ const ans = await ask("Choose");
2607
+ const n = parseInt(ans, 10);
2608
+ if (n === 0 || isNaN(n) || n > named.length + 1) return null;
2609
+ if (n === 1) return { agentsDir: candidate.path };
2610
+ return { agentsDir: named[n - 2].path };
2884
2611
  }
2885
2612
  async function pickSingleAgentToRun({ env, platform, candidates }) {
2886
2613
  if (candidates.length === 0) {
2887
- term.clear();
2888
- drawHeader({ env, platform });
2889
- term("\n");
2890
- term.bold(" I couldn't find any agents on your computer.\n\n");
2891
- term.gray(" Enter the path to one agent folder:\n\n");
2892
- term.gray(" Folder: ");
2893
- const input = await term.inputField({ cancelable: true, style: term.cyan, default: "./agents/my-agent" }).promise;
2894
- if (input === null) return null;
2895
- const agentPath = input || "./agents/my-agent";
2896
- return { path: agentPath, displayName: friendlyName(path8.basename(agentPath) || "Agent") };
2614
+ print("");
2615
+ print(col.yellow(" Couldn't find any agents."));
2616
+ const input = await ask("Agent folder", "./agents/my-agent");
2617
+ if (!input) return null;
2618
+ return { path: input, displayName: friendlyName(path8.basename(input) || "Agent") };
2897
2619
  }
2898
2620
  const agents = candidates.flatMap((candidate) => {
2899
2621
  const folders = findAgentFolders(candidate.absolutePath);
@@ -2909,32 +2631,24 @@ async function pickSingleAgentToRun({ env, platform, candidates }) {
2909
2631
  }];
2910
2632
  });
2911
2633
  if (agents.length === 1) return agents[0];
2912
- term.clear();
2913
- drawHeader({ env, platform });
2914
- term("\n");
2915
- term.bold(" Which agent do you want to run now?\n\n");
2916
- const items = [...agents.map((agent) => `${agent.displayName.padEnd(32)} ${agent.path}`), "\u2190 Back"];
2917
- const resp = await term.singleColumnMenu(items, {
2918
- cancelable: true,
2919
- style: term.gray,
2920
- selectedStyle: term.bgCyan.black.bold,
2921
- leftPadding: " ",
2922
- selectedLeftPadding: " \u203A "
2923
- }).promise;
2924
- if (resp.canceled || resp.selectedIndex === items.length - 1) return null;
2925
- return agents[resp.selectedIndex];
2926
- }
2927
- async function chooseCloudWorkspace({
2928
- env,
2929
- platform,
2930
- loadingTitle,
2931
- pickerTitle
2932
- } = {}) {
2933
- term.clear();
2934
- drawHeader({ env, platform });
2935
- term("\n");
2936
- term.bold(`${loadingTitle}
2937
-
2634
+ print("");
2635
+ printHeader({ env, platform });
2636
+ print("");
2637
+ print(col.bold(" Which agent do you want to run?"));
2638
+ print("");
2639
+ agents.forEach((agent, i) => {
2640
+ print(` ${col.blue(String(i + 1))} ${agent.displayName.padEnd(32)} ${col.gray(agent.path)}`);
2641
+ });
2642
+ print(` ${col.gray("0 Back")}`);
2643
+ print("");
2644
+ const ans = await ask("Choose");
2645
+ const n = parseInt(ans, 10);
2646
+ if (n === 0 || isNaN(n) || n > agents.length) return null;
2647
+ return agents[n - 1];
2648
+ }
2649
+ async function chooseCloudWorkspace({ env, platform, loadingTitle, pickerTitle } = {}) {
2650
+ print("");
2651
+ process.stdout.write(` ${col.gray(loadingTitle || "Loading workspaces\u2026")}
2938
2652
  `);
2939
2653
  let wsResult;
2940
2654
  try {
@@ -2942,279 +2656,214 @@ async function chooseCloudWorkspace({
2942
2656
  env,
2943
2657
  platform,
2944
2658
  onProgress: (msg) => {
2945
- term.moveTo(1, HEADER_ROWS_BASE + 3);
2946
- term.eraseLine();
2947
- term.gray(` ${clip(msg, 60)}
2948
- `);
2659
+ process.stdout.write(`\r ${col.gray(clip(msg, 60))} `);
2949
2660
  }
2950
2661
  });
2662
+ process.stdout.write("\r" + " ".repeat(70) + "\r");
2951
2663
  } catch (error) {
2952
- term.red(`
2953
- Couldn't connect to the server: ${errMsg(error)}
2954
- `);
2955
- term.gray(" Make sure you are signed in (choose Sign in from the main menu).\n");
2664
+ process.stdout.write("\r" + " ".repeat(70) + "\r");
2665
+ print(col.red(`
2666
+ Couldn't connect: ${errMsg(error)}`));
2667
+ print(col.gray(" Make sure you are signed in (choose Login / Switch from the menu)."));
2956
2668
  await pressEnterToContinue({ grabActive: false });
2957
2669
  return null;
2958
2670
  }
2959
2671
  if (!wsResult.ok) {
2960
- term.red(`
2961
- ${fig.cross} ${wsResult.error}
2962
- `);
2672
+ print(col.red(`
2673
+ ${fig.cross} ${wsResult.error}`));
2963
2674
  await pressEnterToContinue({ grabActive: false });
2964
2675
  return null;
2965
2676
  }
2966
- if (wsResult.workspaces.length <= 1) {
2967
- const workspace2 = wsResult.workspaces[0];
2968
- return workspace2 ? { id: workspace2.id || "", label: workspaceLabel(workspace2) } : null;
2969
- }
2970
- term.clear();
2971
- drawHeader({ env, platform });
2972
- term("\n");
2973
- term.bold(pickerTitle);
2974
- const wsItems = [...wsResult.workspaces.map((w) => workspaceLabel(w)), "\u2190 Back"];
2975
- const wsResp = await term.singleColumnMenu(wsItems, {
2976
- cancelable: true,
2977
- style: term.gray,
2978
- selectedStyle: term.bgCyan.black.bold,
2979
- leftPadding: " ",
2980
- selectedLeftPadding: " \u203A "
2981
- }).promise;
2982
- if (wsResp.canceled || wsResp.selectedIndex === wsItems.length - 1) return null;
2983
- const workspace = wsResult.workspaces[wsResp.selectedIndex];
2984
- return { id: workspace?.id || "", label: workspaceLabel(workspace) };
2985
- }
2986
- async function runServeDashboard({ env, platform, agentsDir, collectionId }) {
2677
+ if (wsResult.workspaces.length === 0) return null;
2678
+ if (wsResult.workspaces.length === 1) {
2679
+ const ws2 = wsResult.workspaces[0];
2680
+ return { id: ws2.id || "", label: workspaceLabel(ws2) };
2681
+ }
2682
+ print("");
2683
+ print(col.bold(` ${pickerTitle || "Choose a workspace:"}`));
2684
+ print("");
2685
+ wsResult.workspaces.forEach((ws2, i) => {
2686
+ print(` ${col.blue(String(i + 1))} ${workspaceLabel(ws2)}`);
2687
+ });
2688
+ print(` ${col.gray("0 Back")}`);
2689
+ print("");
2690
+ const ans = await ask("Choose");
2691
+ const n = parseInt(ans, 10);
2692
+ if (n === 0 || isNaN(n) || n > wsResult.workspaces.length) return null;
2693
+ const ws = wsResult.workspaces[n - 1];
2694
+ return { id: ws.id || "", label: workspaceLabel(ws) };
2695
+ }
2696
+ async function showCreate({ env, platform }) {
2697
+ print("");
2698
+ printHeader({ env, platform });
2699
+ print("");
2700
+ print(col.bold(" Create a new agent"));
2701
+ print("");
2702
+ const name = await ask("Agent name");
2703
+ if (!name?.trim()) return "home";
2704
+ const dir = await ask("Save it in folder", "./agents");
2705
+ await withOutputMode(
2706
+ `Creating agent: ${name.trim()}`,
2707
+ () => forwardToPythonVendian(["create", name.trim(), "--output-dir", dir || "./agents"], { env, platform })
2708
+ );
2709
+ return "home";
2710
+ }
2711
+ async function showRun({ env, platform }) {
2712
+ const candidates = findAgentDirectoryCandidates();
2713
+ const picked = await pickSingleAgentToRun({ env, platform, candidates });
2714
+ if (!picked) return "home";
2715
+ const workspace = await chooseCloudWorkspace({
2716
+ env,
2717
+ platform,
2718
+ loadingTitle: "Getting ready to run this agent\u2026",
2719
+ pickerTitle: "Which project should own this run?"
2720
+ });
2721
+ if (!workspace) return "home";
2722
+ const inputJson = await ask("Input JSON", "{}");
2723
+ await withOutputMode(
2724
+ `Running ${picked.displayName}`,
2725
+ () => forwardToPythonVendian(buildLocalRunArgs({
2726
+ agentPath: picked.path,
2727
+ collectionId: workspace.id,
2728
+ inputJson: inputJson || "{}"
2729
+ }), { env, platform })
2730
+ );
2731
+ return "home";
2732
+ }
2733
+ async function showServe({ env, platform }) {
2734
+ const candidates = findAgentDirectoryCandidates();
2735
+ const picked = await pickAgentsToRun({ env, platform, candidates });
2736
+ if (!picked) return "home";
2737
+ const { agentsDir } = picked;
2738
+ const workspace = await chooseCloudWorkspace({
2739
+ env,
2740
+ platform,
2741
+ loadingTitle: "Getting ready to run your agents\u2026",
2742
+ pickerTitle: "Which project do you want to run?"
2743
+ });
2744
+ if (!workspace) return "home";
2745
+ return await runServeDashboard({ env, platform, agentsDir, collectionId: workspace.id, wsLabel: workspace.label });
2746
+ }
2747
+ async function runServeDashboard({ env, platform, agentsDir, collectionId, wsLabel = "" }) {
2987
2748
  let state = initialServeState();
2988
2749
  let child = null;
2989
- let startupError = "";
2990
- let overlayActive = false;
2991
2750
  let ctrlCArmed = false;
2992
2751
  let ctrlCArmTimer = null;
2993
- let dashboardClosed = false;
2994
- let exitPromptActive = false;
2995
- let selectedIdx = 0;
2996
- let pressedAgentIdx = null;
2997
2752
  let stopRequested = false;
2998
2753
  let stopSignalAttempt = 0;
2999
2754
  let stopTimer = null;
3000
- const agentRowMap = /* @__PURE__ */ new Map();
3001
- function agentDisplayList() {
3002
- return state.agents.map((agent, i) => {
3003
- const name = agent.manifestName || agent.manifest?.name || friendlyName(agent.relativePath || ".");
3004
- const rt = agentRuntimeStatus(agent, state.agentRunState);
3005
- const status = friendlyStatus(rt.status);
3006
- const path_ = agent.relativePath || ".";
3007
- const runst = state.agentRunState[path_];
3008
- const errMsg2 = rt.status === "error" ? runst?.errorMessage || "Something went wrong \u2014 no details available" : null;
3009
- return { num: i + 1, name, status, errMsg: errMsg2, path: path_ };
3010
- });
3011
- }
3012
- function recentActivityLines(maxLines = 7) {
3013
- const all = [];
3014
- for (const [agentPath, entries] of Object.entries(state.agentLogs || {})) {
3015
- const agent = state.agents.find((a) => (a.relativePath || ".") === agentPath);
3016
- const name = agent?.manifestName || agent?.manifest?.name || friendlyName(agentPath);
3017
- for (const e of entries.slice(-30)) all.push({ ...e, _agentName: name });
3018
- }
3019
- all.sort((a, b) => (a.timestamp || "").localeCompare(b.timestamp || ""));
3020
- return all.slice(-maxLines);
3021
- }
3022
- function daemonDebugLines(maxLines = 8) {
3023
- return serveDebugEntries(state.logs, maxLines);
3024
- }
3025
- function redraw() {
3026
- if (dashboardClosed || overlayActive) return;
3027
- const agents = agentDisplayList();
3028
- const activity = recentActivityLines();
3029
- const diagnostics = daemonDebugLines();
3030
- const viewport = computeServeDashboardViewport({
3031
- termHeight: term.height || 24,
3032
- agentCount: agents.length,
3033
- selectedIdx,
3034
- activityCount: activity.length,
3035
- diagnosticsCount: diagnostics.length
3036
- });
3037
- selectedIdx = viewport.selectedIdx;
3038
- drawHeader({ env, platform, serveState: state });
3039
- term.moveTo(1, SERVE_CONTENT_ROW);
3040
- term.eraseDisplayBelow();
3041
- agentRowMap.clear();
3042
- if (startupError) {
3043
- term.red(` ${fig.cross} ${clip(startupError, (term.width || 80) - 6)}
3044
- `);
3045
- } else if (stopRequested) {
3046
- term.yellow(` ${fig.warning} ${clip(state.activity || "Stopping your agents\u2026", (term.width || 80) - 6)}
3047
- `);
3048
- } else if (state.stopped) {
3049
- term.yellow(` ${fig.warning} Your agents have stopped.
2755
+ let dashboardClosed = false;
2756
+ let logStore = null;
2757
+ const W = Math.min(process.stdout.columns || 80, 100);
2758
+ setCursorVisible(false);
2759
+ print("");
2760
+ print(col.gray("\u2550".repeat(W)));
2761
+ print(` ${col.cyan(col.bold("\u2191 VENDIAN"))} ${col.bold("Serving agents")}`);
2762
+ print(` ${col.gray("Dir:")} ${agentsDir}`);
2763
+ print(` ${col.gray("Workspace:")} ${wsLabel || collectionId || "\u2014"}`);
2764
+ print(col.gray(hr(W)));
2765
+ print(col.gray(" S stop gracefully | ^c ^c exit"));
2766
+ print(col.gray(hr(W)));
2767
+ print("");
2768
+ function ts() {
2769
+ return formatLogTime((/* @__PURE__ */ new Date()).toISOString());
2770
+ }
2771
+ function printRun(agentName, icon, message) {
2772
+ const name = clip(agentName || "agent", 22).padEnd(22);
2773
+ process.stdout.write(`${ts()} ${col.blue("RUN ")} ${name} ${icon} ${message}
3050
2774
  `);
3051
- } else if (state.connected) {
3052
- term.green(` ${fig.dot} `);
3053
- term.gray(`${clip(state.activity || "Your agents are running", 55)}
2775
+ }
2776
+ function printAgent(agentName, statusText) {
2777
+ const name = clip(agentName || "agent", 22).padEnd(22);
2778
+ process.stdout.write(`${ts()} ${col.cyan("AGENT ")} ${name} ${statusText}
3054
2779
  `);
3055
- } else {
3056
- term.cyan(` ${fig.dotEmpty} `);
3057
- term.gray(`${clip(state.activity || "Starting up\u2026", 55)}
2780
+ }
2781
+ function printDaemon(eventType, message) {
2782
+ const evt = clip(String(eventType || "event"), 26).padEnd(26);
2783
+ process.stdout.write(`${ts()} ${col.gray("DAEMON")} ${evt} ${message}
3058
2784
  `);
2785
+ }
2786
+ function onServeEvent(event) {
2787
+ if (!event) return;
2788
+ const type = String(event.type || "");
2789
+ const agentPath = String(event.relativePath || event.path || "");
2790
+ const agentName = agentPath ? friendlyName(path8.basename(agentPath)) || agentPath : "";
2791
+ if (type === "job_started") {
2792
+ printRun(agentName, col.blue("\u25B6"), `Started ${event.jobType || "job"} ${shortId2(event.runId || "")}`);
2793
+ return;
3059
2794
  }
3060
- if (agents.length > 0) {
3061
- if (viewport.showBlankAfterStatus) term("\n");
3062
- term.bold.gray(" Your agents:\n");
3063
- let lineOffset = SERVE_CONTENT_ROW + 2 + (viewport.showBlankAfterStatus ? 1 : 0);
3064
- const visibleAgents = agents.slice(
3065
- viewport.agentWindowStart,
3066
- viewport.agentWindowStart + viewport.visibleAgentCount
3067
- );
3068
- for (const ag of visibleAgents) {
3069
- const isSelected = ag.num - 1 === selectedIdx;
3070
- agentRowMap.set(lineOffset, ag.num - 1);
3071
- const numStr = String(ag.num).padStart(2);
3072
- const nameStr = clip(ag.name, 28).padEnd(28);
3073
- const statLine = ` ${numStr} ${ag.status.icon} ${nameStr} ${ag.status.text}`;
3074
- if (isSelected) term.bgCyan.black.bold(statLine);
3075
- else {
3076
- term(` ${numStr} `);
3077
- switch (ag.status.color) {
3078
- case "green":
3079
- term.green(ag.status.icon);
3080
- break;
3081
- case "brightBlue":
3082
- term.brightBlue(ag.status.icon);
3083
- break;
3084
- case "cyan":
3085
- term.cyan(ag.status.icon);
3086
- break;
3087
- case "yellow":
3088
- term.yellow(ag.status.icon);
3089
- break;
3090
- case "red":
3091
- term.red(ag.status.icon);
3092
- break;
3093
- default:
3094
- term.gray(ag.status.icon);
3095
- }
3096
- term(` ${nameStr} `);
3097
- switch (ag.status.color) {
3098
- case "green":
3099
- term.green(ag.status.text);
3100
- break;
3101
- case "brightBlue":
3102
- term.brightBlue(ag.status.text);
3103
- break;
3104
- case "cyan":
3105
- term.cyan(ag.status.text);
3106
- break;
3107
- case "yellow":
3108
- term.yellow(ag.status.text);
3109
- break;
3110
- case "red":
3111
- term.red(ag.status.text);
3112
- break;
3113
- default:
3114
- term.gray(ag.status.text);
3115
- }
3116
- }
3117
- term("\n");
3118
- lineOffset++;
3119
- if (viewport.showInlineErrors && isSelected && ag.errMsg) {
3120
- term.red(` \u2514\u2500 ${clip(ag.errMsg, (term.width || 80) - 14)}
3121
- `);
3122
- lineOffset++;
3123
- }
3124
- }
3125
- if (viewport.showAgentRange) {
3126
- const first = viewport.agentWindowStart + 1;
3127
- const last = viewport.agentWindowStart + visibleAgents.length;
3128
- term.gray(` Showing agents ${first}\u2013${last} of ${agents.length}
3129
- `);
2795
+ if (type === "job_completed") {
2796
+ const ok = event.success !== false;
2797
+ printRun(agentName, ok ? col.green("\u2714") : col.red("\u2716"), ok ? "Completed" : `Failed: ${fmtErr(event.error)}`);
2798
+ return;
2799
+ }
2800
+ if (type === "run_log") {
2801
+ const lvl = String(event.level || "info");
2802
+ const msg = String(event.message || event.eventType || "");
2803
+ if (!msg) return;
2804
+ const isCompletionEvent = event.eventType === "completion";
2805
+ const icon = lvl === "error" ? col.red("\u2716") : isCompletionEvent ? event.success !== false ? col.green("\u2714") : col.red("\u2716") : "\u2014";
2806
+ printRun(agentName || "?", icon, clip(msg, 80));
2807
+ return;
2808
+ }
2809
+ if (type === "error" && agentPath) {
2810
+ printRun(agentName, col.red("\u2716"), `Error: ${fmtErr(event.error) || String(event.code || "unknown")}`);
2811
+ return;
2812
+ }
2813
+ if (type === "agent_prepare_started") {
2814
+ printAgent(agentName, col.gray("Preparing\u2026"));
2815
+ return;
2816
+ }
2817
+ if (type === "agent_prepare_progress") {
2818
+ const msg = String(event.message || event.stage || "Preparing");
2819
+ printAgent(agentName, col.gray(clip(msg, 60)));
2820
+ return;
2821
+ }
2822
+ if (type === "agent_prepare_completed") {
2823
+ if (event.status === "error") {
2824
+ printAgent(agentName, col.red(`\u2716 Setup failed: ${fmtErr(event.error)}`));
2825
+ return;
3130
2826
  }
3131
- if (viewport.showAgentHint) {
3132
- term.gray(` Click an agent or use \u2191\u2193 + Enter to see its activity log
3133
- `);
2827
+ if (event.status === "disabled") {
2828
+ printAgent(agentName, col.yellow(`\u26A0 Disabled: ${event.disabledReason || "deps not met locally"}`));
2829
+ return;
3134
2830
  }
3135
- } else {
3136
- if (viewport.showBlankAfterStatus) term("\n");
3137
- term.gray(" Looking for your agents\u2026\n");
2831
+ printAgent(agentName, col.green("\u2714 Ready"));
2832
+ return;
3138
2833
  }
3139
- if (viewport.showActivityHeader) {
3140
- const visibleActivity = activity.slice(-viewport.visibleActivityCount);
3141
- const divW = Math.min((term.width || 80) - 4, 60);
3142
- term.gray(` ${"\u2500".repeat(3)} Recent activity ${"\u2500".repeat(Math.max(0, divW - 18))}
3143
- `);
3144
- for (const e of visibleActivity) {
3145
- const t = formatLogTime(e.timestamp);
3146
- const name = clip(e._agentName || "Agent", 20);
3147
- const msg = clip(friendlyActivityLine(e), (term.width || 80) - 30);
3148
- if (e.level === "error" || e.eventType === "completion" && e.success === false) {
3149
- term.gray(` ${t} `);
3150
- term.red(`${name} \u2014 ${msg}
3151
- `);
3152
- } else if (e.eventType === "job_completed" && e.success !== false) {
3153
- term.gray(` ${t} `);
3154
- term.green(`${name} \u2014 ${msg}
3155
- `);
3156
- } else if (e.eventType === "job_started") {
3157
- term.gray(` ${t} `);
3158
- term.brightBlue(`${name} \u2014 ${msg}
3159
- `);
3160
- } else {
3161
- term.gray(` ${t} ${name} \u2014 ${msg}
3162
- `);
3163
- }
3164
- }
3165
- }
3166
- if (viewport.showDiagnosticsHeader) {
3167
- const visibleDiagnostics = diagnostics.slice(-viewport.visibleDiagnosticsCount);
3168
- const divW = Math.min((term.width || 80) - 4, 72);
3169
- term.gray(` ${"\u2500".repeat(3)} Daemon diagnostics ${"\u2500".repeat(Math.max(0, divW - 23))}
3170
- `);
3171
- for (const entry of visibleDiagnostics) {
3172
- const t = formatLogTime(entry.timestamp);
3173
- const type = clip(entry.eventType || "event", 24).padEnd(24);
3174
- const msg = clip(entry.message || "", (term.width || 80) - 38);
3175
- term.gray(` ${t} `);
3176
- if (entry.level === "error") {
3177
- term.red(`${type} ${msg}
3178
- `);
3179
- } else if (entry.level === "warn") {
3180
- term.yellow(`${type} ${msg}
3181
- `);
3182
- } else {
3183
- term.gray(`${type} ${msg}
3184
- `);
2834
+ if (type === "inventory_synced") {
2835
+ const agents = Array.isArray(event.agents) ? event.agents : [];
2836
+ for (const a of agents) {
2837
+ const name = friendlyName(path8.basename(a.relativePath || ".")) || a.manifestName || a.relativePath || "?";
2838
+ if (a.status === "error") {
2839
+ printAgent(name, col.red(`\u2716 Error: ${a.errorMessage || ""}`));
2840
+ continue;
2841
+ }
2842
+ if (a.status === "disabled") {
2843
+ printAgent(name, col.yellow(`\u26A0 Disabled: ${a.disabledReason || ""}`));
2844
+ continue;
2845
+ }
2846
+ if (a.status === "online") {
2847
+ printAgent(name, col.green("\u25CF Online"));
2848
+ continue;
3185
2849
  }
3186
2850
  }
2851
+ return;
3187
2852
  }
3188
- term("\n");
3189
- if (ctrlCArmed) {
3190
- term.yellow.bold(" Press Ctrl+C again to exit.\n");
3191
- } else if (stopRequested) {
3192
- term.yellow(" Stopping your agents\u2026");
3193
- if (stopSignalAttempt > 0) term.gray(" (retrying shutdown)");
3194
- term("\n");
3195
- } else {
3196
- term.gray(" ");
3197
- term.brightBlue.bold("S");
3198
- term.gray(" Stop ");
3199
- term.brightBlue.bold("^c ^c");
3200
- term.gray(" Exit\n");
2853
+ const dbgArr = serveDebugEntries([event], 1);
2854
+ if (dbgArr.length > 0) {
2855
+ const dbg = dbgArr[0];
2856
+ const msg = dbg.level === "error" ? col.red(dbg.message) : dbg.level === "warn" ? col.yellow(dbg.message) : col.gray(dbg.message);
2857
+ printDaemon(dbg.eventType, msg);
3201
2858
  }
3202
2859
  }
3203
- term.clear();
3204
- drawHeader({ env, platform, serveState: state });
3205
- term.moveTo(1, SERVE_CONTENT_ROW);
3206
- term.cyan(` ${fig.dotEmpty} Getting your agents ready\u2026
3207
- `);
3208
2860
  try {
3209
- state = { ...state, activity: "Loading previous logs" };
3210
- const logStore = createServeLogStore({ agentsDir, collectionId, env, platform });
2861
+ logStore = createServeLogStore({ agentsDir, collectionId, env, platform });
3211
2862
  const historicalLogs = logStore.load();
3212
2863
  if (historicalLogs.length > 0) {
3213
2864
  const agentLogs = mergeAgentLogRecords(state.agentLogs, historicalLogs);
3214
2865
  state = { ...state, agentLogs, agentRunState: { ...agentRunStateFromLogs(agentLogs, { includeRunning: false }), ...state.agentRunState } };
3215
2866
  }
3216
- state = { ...state, activity: "Preparing your agents\u2026" };
3217
- redraw();
3218
2867
  child = await spawnLocalServeEventStream({
3219
2868
  agentsDir,
3220
2869
  collectionId,
@@ -3222,543 +2871,219 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId }) {
3222
2871
  platform,
3223
2872
  onProgress: (msg) => {
3224
2873
  if (dashboardClosed) return;
3225
- state = { ...state, activity: msg || "Preparing\u2026" };
3226
- redraw();
2874
+ process.stdout.write(`\r ${col.gray(clip(msg || "Preparing\u2026", 70))} `);
3227
2875
  }
3228
2876
  });
3229
- state = { ...state, activity: "Connecting to server\u2026" };
3230
- redraw();
3231
- attachServeChild(
3232
- child,
3233
- (updater) => {
3234
- if (dashboardClosed) return;
3235
- state = updater(state);
3236
- redraw();
3237
- },
3238
- ({ message } = {}) => {
3239
- if (dashboardClosed) return;
3240
- if (!state.stopped) state = { ...state, stopped: true, activity: message || "Stopped" };
3241
- redraw();
3242
- },
3243
- logStore
3244
- );
2877
+ process.stdout.write("\r" + " ".repeat(80) + "\r");
2878
+ printDaemon("serve_started", col.gray(`dir=${agentsDir}`));
3245
2879
  } catch (error) {
3246
- startupError = errMsg(error);
3247
- redraw();
2880
+ setCursorVisible(true);
2881
+ print(col.red(` ${fig.cross} Failed to start: ${errMsg(error)}`));
2882
+ await pressEnterToContinue({ grabActive: false });
2883
+ return "home";
3248
2884
  }
3249
2885
  return new Promise((resolve) => {
3250
- function openAgentLog(idx) {
3251
- const agents = agentDisplayList();
3252
- const clampedIdx = clampListIndex(idx, agents.length);
3253
- if (dashboardClosed || exitPromptActive || agents.length === 0 || idx !== clampedIdx) return;
3254
- overlayActive = true;
3255
- const ag = agents[clampedIdx];
3256
- showAgentLog({ agent: ag, getState: () => state, env, platform }).then(() => {
3257
- if (dashboardClosed) return;
3258
- overlayActive = false;
3259
- term.clear();
3260
- redraw();
3261
- });
3262
- }
3263
- function clearCtrlCArm() {
3264
- ctrlCArmed = false;
3265
- if (ctrlCArmTimer) {
3266
- clearTimeout(ctrlCArmTimer);
3267
- ctrlCArmTimer = null;
3268
- }
3269
- }
3270
- function clearStopTimer() {
3271
- if (stopTimer) {
3272
- clearTimeout(stopTimer);
3273
- stopTimer = null;
3274
- }
2886
+ let removeKeypress = () => {
2887
+ };
2888
+ function finish(next) {
2889
+ if (dashboardClosed) return;
2890
+ dashboardClosed = true;
2891
+ clearTimeout(ctrlCArmTimer);
2892
+ clearTimeout(stopTimer);
2893
+ removeKeypress();
2894
+ setCursorVisible(true);
2895
+ resolve(next);
3275
2896
  }
3276
2897
  function childIsRunning() {
3277
2898
  return Boolean(child) && child.exitCode == null && child.signalCode == null;
3278
2899
  }
3279
2900
  function scheduleStopEscalation() {
3280
- clearStopTimer();
2901
+ clearTimeout(stopTimer);
3281
2902
  if (!childIsRunning()) return;
3282
2903
  stopTimer = setTimeout(() => {
3283
2904
  if (!stopRequested || dashboardClosed || !childIsRunning()) return;
3284
2905
  stopSignalAttempt += 1;
3285
2906
  const signal = stopSignalForAttempt(stopSignalAttempt);
3286
- state = {
3287
- ...state,
3288
- activity: signal === "SIGTERM" ? "Still stopping your agents\u2026" : "Force stopping your agents\u2026"
3289
- };
3290
2907
  try {
3291
2908
  child.kill(signal);
3292
2909
  } catch {
3293
2910
  }
3294
- if (!overlayActive) redraw();
3295
2911
  if (signal !== "SIGKILL") scheduleStopEscalation();
3296
2912
  }, 4e3);
3297
2913
  }
3298
2914
  function requestStop() {
3299
- if (dashboardClosed) return;
3300
- if (stopRequested) return;
2915
+ if (dashboardClosed || stopRequested) return;
3301
2916
  if (!childIsRunning()) {
3302
2917
  finish("home");
3303
2918
  return;
3304
2919
  }
3305
2920
  stopRequested = true;
3306
- stopSignalAttempt = 0;
3307
2921
  state = { ...state, activity: "Stopping your agents\u2026" };
3308
- clearCtrlCArm();
2922
+ print(col.yellow("\n Stopping your agents\u2026"));
2923
+ clearTimeout(ctrlCArmTimer);
2924
+ ctrlCArmed = false;
3309
2925
  try {
3310
2926
  child.kill(stopSignalForAttempt(stopSignalAttempt));
3311
2927
  } catch {
3312
2928
  }
3313
2929
  scheduleStopEscalation();
3314
- if (!overlayActive) redraw();
3315
- }
3316
- function detachDashboardHandlers() {
3317
- term.off("key", handleKey);
3318
- term.off("mouse", handleMouse);
3319
- term.off("resize", handleResize);
3320
- }
3321
- function finish(next) {
3322
- if (dashboardClosed) return;
3323
- dashboardClosed = true;
3324
- clearCtrlCArm();
3325
- clearStopTimer();
3326
- detachDashboardHandlers();
3327
- resolve(next);
3328
2930
  }
3329
- function handleKey(name) {
3330
- if (name === "CTRL_C") {
3331
- if (ctrlCArmed) {
3332
- child?.kill("SIGINT");
3333
- finish("exit");
2931
+ if (process.stdin.isTTY) {
2932
+ let onKeypress = function(str, key) {
2933
+ if (!key) return;
2934
+ if (key.ctrl && key.name === "c") {
2935
+ if (ctrlCArmed) {
2936
+ child?.kill("SIGINT");
2937
+ finish("exit");
2938
+ return;
2939
+ }
2940
+ ctrlCArmed = true;
2941
+ process.stdout.write("\n" + col.yellow(" Press Ctrl+C again to exit.") + "\n");
2942
+ ctrlCArmTimer = setTimeout(() => {
2943
+ ctrlCArmed = false;
2944
+ }, 1500);
3334
2945
  return;
3335
2946
  }
3336
- ctrlCArmed = true;
3337
- if (!overlayActive) redraw();
3338
- ctrlCArmTimer = setTimeout(() => {
3339
- ctrlCArmed = false;
3340
- if (!overlayActive) redraw();
3341
- }, 1500);
3342
- return;
3343
- }
3344
- if (overlayActive) return;
3345
- if (stopRequested) return;
3346
- const agCount = agentDisplayList().length;
3347
- if (name >= "1" && name <= "9") {
3348
- openAgentLog(parseInt(name, 10) - 1);
3349
- return;
3350
- }
3351
- if (name === "UP" && agCount > 0) {
3352
- selectedIdx = clampListIndex(selectedIdx - 1, agCount);
3353
- redraw();
3354
- } else if (name === "DOWN" && agCount > 0) {
3355
- selectedIdx = clampListIndex(selectedIdx + 1, agCount);
3356
- redraw();
3357
- } else if (name === "ENTER") {
3358
- openAgentLog(selectedIdx);
3359
- } else if (name === "s" || name === "S") {
3360
- requestStop();
3361
- }
2947
+ if (stopRequested) return;
2948
+ if (key.name === "s" || key.name === "S") {
2949
+ requestStop();
2950
+ }
2951
+ };
2952
+ readline.emitKeypressEvents(process.stdin);
2953
+ process.stdin.setRawMode(true);
2954
+ process.stdin.resume();
2955
+ process.stdin.on("keypress", onKeypress);
2956
+ removeKeypress = () => {
2957
+ process.stdin.off("keypress", onKeypress);
2958
+ try {
2959
+ if (process.stdin.isTTY) process.stdin.setRawMode(false);
2960
+ } catch {
2961
+ }
2962
+ };
3362
2963
  }
3363
- function handleMouse(name, data) {
3364
- if (overlayActive || stopRequested) return;
3365
- const idx = agentRowMap.get(data.y);
3366
- if (name === "MOUSE_LEFT_BUTTON_PRESSED") {
3367
- pressedAgentIdx = idx ?? null;
3368
- if (idx !== void 0 && idx !== selectedIdx) {
3369
- selectedIdx = idx;
3370
- redraw();
2964
+ let stdoutBuffer = "";
2965
+ const stderrChunks = [];
2966
+ child.stdout.setEncoding("utf8");
2967
+ child.stdout.on("data", (chunk) => {
2968
+ stdoutBuffer += chunk;
2969
+ const lines = stdoutBuffer.split(/\r?\n/);
2970
+ stdoutBuffer = lines.pop() || "";
2971
+ for (const line of lines) {
2972
+ try {
2973
+ const event = parseServeEventLine(line);
2974
+ if (event) {
2975
+ try {
2976
+ logStore?.append(event);
2977
+ } catch {
2978
+ }
2979
+ state = applyServeEvent(state, event);
2980
+ onServeEvent(event);
2981
+ }
2982
+ } catch {
3371
2983
  }
3372
- return;
3373
2984
  }
3374
- if (name === "MOUSE_LEFT_BUTTON_RELEASED") {
3375
- const shouldOpen = idx !== void 0 && idx === pressedAgentIdx;
3376
- pressedAgentIdx = null;
3377
- if (shouldOpen) openAgentLog(idx);
2985
+ });
2986
+ child.stderr.setEncoding("utf8");
2987
+ child.stderr.on("data", (chunk) => {
2988
+ const text = String(chunk).trim();
2989
+ if (!text) return;
2990
+ stderrChunks.push(text);
2991
+ printDaemon("stderr", col.yellow(clip(text, 120)));
2992
+ state = applyServeEvent(state, {
2993
+ type: "daemon_stderr",
2994
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2995
+ message: text
2996
+ });
2997
+ });
2998
+ child.on("error", (error) => {
2999
+ print(col.red(` Error: ${errMsg(error)}`));
3000
+ });
3001
+ child.once("exit", () => {
3002
+ if (stopRequested) {
3003
+ finish("home");
3378
3004
  return;
3379
3005
  }
3380
- if (idx === void 0) return;
3381
- if (name !== "MOUSE_LEFT_BUTTON_RELEASED") {
3382
- if (idx !== selectedIdx) {
3383
- selectedIdx = idx;
3384
- redraw();
3385
- }
3006
+ const stderr = stderrChunks.join("\n");
3007
+ const message = serveProcessExitMessage({ stderr, code: child.exitCode, signal: child.signalCode });
3008
+ if (message) {
3009
+ print(col.yellow(`
3010
+ ${message}`));
3011
+ } else {
3012
+ print(col.gray("\n Agents stopped."));
3386
3013
  }
3387
- }
3388
- function handleResize() {
3389
- if (!overlayActive) redraw();
3390
- }
3391
- term.on("key", handleKey);
3392
- term.on("mouse", handleMouse);
3393
- term.on("resize", handleResize);
3394
- if (child) {
3395
- child.once("exit", () => {
3396
- clearStopTimer();
3397
- if (stopRequested) {
3398
- finish("home");
3399
- return;
3400
- }
3401
- setTimeout(() => {
3402
- if (dashboardClosed || overlayActive) return;
3403
- exitPromptActive = true;
3404
- detachDashboardHandlers();
3405
- redraw();
3406
- term("\n");
3407
- term.gray(" Your agents have stopped. Press any key to go back\u2026");
3408
- term.once("key", () => finish("home"));
3409
- }, 600);
3014
+ try {
3015
+ logStore?.compact();
3016
+ } catch {
3017
+ }
3018
+ removeKeypress();
3019
+ process.stdout.write("\n Press Enter to return to menu\u2026 ");
3020
+ const rl = makeRl();
3021
+ rl.once("line", () => {
3022
+ rl.close();
3023
+ finish("home");
3410
3024
  });
3411
- }
3025
+ rl.once("close", () => finish("home"));
3026
+ });
3412
3027
  });
3413
3028
  }
3414
- async function showAgentLog({ agent, getState, env, platform }) {
3415
- const liveState = typeof getState === "function" ? getState : () => getState;
3416
- let scrollOffset = 0;
3417
- let autoScroll = true;
3418
- const visibleCount = Math.max(8, (term.height || 24) - 14);
3419
- function currentLogs() {
3420
- return agentLogEntries(liveState().agentLogs, agent.path);
3421
- }
3422
- function draw() {
3423
- const state = liveState();
3424
- const logs = currentLogs();
3425
- const agentObj = state.agents.find((a) => (a.relativePath || ".") === agent.path);
3426
- const runtime = agentObj ? agentRuntimeStatus(agentObj, state.agentRunState) : { status: "unknown", label: "offline" };
3427
- const status = friendlyStatus(runtime.status);
3428
- if (autoScroll) {
3429
- scrollOffset = Math.max(0, logs.length - visibleCount);
3430
- }
3431
- term.clear();
3432
- drawHeader({ env, platform, serveState: state });
3433
- term("\n");
3434
- term.bold(` ${agent.name}
3435
- `);
3436
- term.gray(` ${agent.path} \u2014 `);
3437
- switch (status.color) {
3438
- case "green":
3439
- term.green(`${status.icon} ${status.text}
3440
- `);
3029
+ async function mainLoop({ env, platform }) {
3030
+ let screen = "home";
3031
+ while (true) {
3032
+ let next;
3033
+ switch (screen) {
3034
+ case "home":
3035
+ next = await showHome({ env, platform });
3441
3036
  break;
3442
- case "brightBlue":
3443
- term.brightBlue(`${status.icon} ${status.text}
3444
- `);
3037
+ case "connect":
3038
+ next = await showConnect({ env, platform });
3445
3039
  break;
3446
- case "yellow":
3447
- term.yellow(`${status.icon} ${status.text}
3448
- `);
3040
+ case "create":
3041
+ next = await showCreate({ env, platform });
3449
3042
  break;
3450
- case "red":
3451
- term.red(`${status.icon} ${status.text}
3452
- `);
3043
+ case "run":
3044
+ next = await showRun({ env, platform });
3045
+ break;
3046
+ case "serve":
3047
+ next = await showServe({ env, platform });
3048
+ break;
3049
+ case "commands":
3050
+ next = await showCommands();
3453
3051
  break;
3454
3052
  default:
3455
- term.gray(`${status.icon} ${status.text}
3456
- `);
3457
- }
3458
- term("\n");
3459
- if (logs.length === 0) {
3460
- term.gray(" No activity recorded yet. Logs will appear when this agent runs a job.\n");
3461
- } else {
3462
- const eff = scrollOffset >= logs.length - visibleCount - 2 ? Math.max(0, logs.length - visibleCount) : scrollOffset;
3463
- const visible = logs.slice(eff, eff + visibleCount);
3464
- const msgW = Math.max(40, (term.width || 80) - 16);
3465
- for (const entry of visible) {
3466
- const t = formatLogTime(entry.timestamp);
3467
- const text = clip(formatAgentLogEntry(entry), msgW);
3468
- term.gray(` ${t} `);
3469
- if (entry.level === "error" || entry.eventType === "completion" && entry.success === false) {
3470
- term.red(`\u2716 ${text}
3471
- `);
3472
- } else if (entry.eventType === "job_completed" && entry.success !== false) {
3473
- term.green(`\u2714 ${text}
3474
- `);
3475
- } else if (entry.eventType === "job_started") {
3476
- term.brightBlue(`\u25B6 ${text}
3477
- `);
3478
- } else {
3479
- term.gray(`\u2014 ${text}
3480
- `);
3481
- }
3482
- }
3483
- if (logs.length > visibleCount) {
3484
- term.gray(`
3485
- Showing ${eff + 1}\u2013${eff + visible.length} of ${logs.length} entries
3486
- `);
3487
- }
3488
- }
3489
- term("\n");
3490
- term.gray(" ");
3491
- term.brightBlue.bold("\u2191\u2193");
3492
- term.gray(" scroll ");
3493
- term.brightBlue.bold("PgUp/PgDn");
3494
- term.gray(" page ");
3495
- if (autoScroll) {
3496
- term.brightBlue.bold("Esc");
3497
- term.gray(" back ");
3498
- term.cyan("\u2193 live\n");
3499
- } else {
3500
- term.brightBlue.bold("Esc");
3501
- term.gray(" back ");
3502
- term.gray("\u2193 ");
3503
- term.brightBlue.bold("End");
3504
- term.gray(" resume live\n");
3505
- }
3506
- }
3507
- draw();
3508
- let overlayDone = false;
3509
- const redrawInterval = setInterval(() => {
3510
- if (!overlayDone) draw();
3511
- }, 500);
3512
- await new Promise((resolve) => {
3513
- function handler(name) {
3514
- const logs = currentLogs();
3515
- const maxOff = Math.max(0, logs.length - visibleCount);
3516
- if (name === "ESCAPE") {
3517
- term.off("key", handler);
3518
- overlayDone = true;
3519
- clearInterval(redrawInterval);
3520
- resolve();
3521
- } else if (name === "UP") {
3522
- autoScroll = false;
3523
- scrollOffset = Math.max(0, scrollOffset - 1);
3524
- draw();
3525
- } else if (name === "DOWN") {
3526
- scrollOffset = Math.min(maxOff, scrollOffset + 1);
3527
- if (scrollOffset >= maxOff) autoScroll = true;
3528
- draw();
3529
- } else if (name === "PAGE_UP") {
3530
- autoScroll = false;
3531
- scrollOffset = Math.max(0, scrollOffset - visibleCount);
3532
- draw();
3533
- } else if (name === "PAGE_DOWN") {
3534
- scrollOffset = Math.min(maxOff, scrollOffset + visibleCount);
3535
- if (scrollOffset >= maxOff) autoScroll = true;
3536
- draw();
3537
- } else if (name === "END") {
3538
- autoScroll = true;
3539
- draw();
3540
- }
3541
- }
3542
- term.on("key", handler);
3543
- });
3544
- }
3545
- function friendlyStatus(status) {
3546
- switch (status) {
3547
- case "running":
3548
- return { icon: "\u25B6", text: "Running a job right now", color: "brightBlue" };
3549
- case "preparing":
3550
- return { icon: "\u25CB", text: "Getting ready\u2026", color: "cyan" };
3551
- case "ready":
3552
- case "completed":
3553
- return { icon: "\u2714", text: "Ready and waiting", color: "green" };
3554
- case "disabled":
3555
- return { icon: "\u26A0", text: "Needs setup to run locally", color: "yellow" };
3556
- case "error":
3557
- return { icon: "\u2716", text: "Something went wrong", color: "red" };
3558
- default:
3559
- return { icon: "\u25CB", text: "Starting up\u2026", color: "gray" };
3560
- }
3561
- }
3562
- function friendlyActivityLine(entry) {
3563
- switch (entry.eventType) {
3564
- case "job_started":
3565
- return `Started a new job`;
3566
- case "job_completed":
3567
- return entry.success === false ? `Job failed \u2014 ${fmtErr(entry.error) || entry.message || "unknown error"}` : `Job completed successfully`;
3568
- case "error":
3569
- return `Error \u2014 ${entry.message || fmtErr(entry.error) || "unknown"}`;
3570
- case "log":
3571
- return entry.message || "Activity";
3572
- case "completion":
3573
- return entry.success ? "Finished successfully" : `Failed \u2014 ${fmtErr(entry.error) || "error"}`;
3574
- case "step_event":
3575
- return `Step ${entry.stepId || ""}${entry.message ? ": " + entry.message : ""}`;
3576
- case "progress": {
3577
- const p = entry.current != null && entry.total != null ? ` (${entry.current}/${entry.total})` : "";
3578
- return `${entry.message || "Working"}${p}`;
3579
- }
3580
- default:
3581
- return entry.message || entry.eventType || "Activity";
3582
- }
3583
- }
3584
- function friendlyName(raw) {
3585
- return String(raw || "").replace(/.*[\\/]/, "").replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()).trim() || raw;
3586
- }
3587
- function readManifestName(absoluteFolderPath) {
3588
- if (!absoluteFolderPath) return null;
3589
- for (const fname of ["manifest.yaml", "manifest.yml"]) {
3590
- try {
3591
- const content = fs11.readFileSync(path8.join(absoluteFolderPath, fname), "utf8");
3592
- const m = content.match(/^name\s*:\s*['"]?([^'"\n#]+?)['"]?\s*$/m);
3593
- if (m?.[1]) return m[1].trim();
3594
- } catch {
3053
+ next = "exit";
3595
3054
  }
3055
+ if (!next || next === "exit") break;
3056
+ screen = next;
3596
3057
  }
3597
- return null;
3598
- }
3599
- function attachServeChild(child, onUpdate, onExit, logStore = null) {
3600
- let buffer = "";
3601
- const stderrChunks = [];
3602
- child.stdout.setEncoding("utf8");
3603
- child.stdout.on("data", (chunk) => {
3604
- buffer += chunk;
3605
- const lines = buffer.split(/\r?\n/);
3606
- buffer = lines.pop() || "";
3607
- const events = [];
3608
- for (const line of lines) {
3609
- try {
3610
- const event = parseServeEventLine(line);
3611
- if (event) {
3612
- try {
3613
- logStore?.append(event);
3614
- } catch {
3615
- }
3616
- events.push(event);
3617
- }
3618
- } catch {
3619
- }
3620
- }
3621
- if (events.length > 0) onUpdate((cur) => events.reduce((s, e) => applyServeEvent(s, e), cur));
3622
- });
3623
- child.stderr.setEncoding("utf8");
3624
- child.stderr.on("data", (chunk) => {
3625
- const text = String(chunk).trim();
3626
- if (text) {
3627
- stderrChunks.push(text);
3628
- onUpdate((cur) => applyServeEvent(cur, {
3629
- type: "daemon_stderr",
3630
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3631
- message: text
3632
- }));
3633
- }
3634
- });
3635
- child.on("error", (error) => {
3636
- onUpdate((cur) => ({ ...cur, errors: [...cur.errors, { scope: "daemon", message: errMsg(error) }].slice(-20) }));
3637
- });
3638
- child.on("exit", (code, signal) => {
3639
- const stderr = stderrChunks.join("\n");
3640
- const stderrTail = serveProcessExitStderrTail(stderr);
3641
- const message = serveProcessExitMessage({ stderr, code, signal });
3642
- if (message) {
3643
- onUpdate((cur) => {
3644
- const event = { type: "process_exit", timestamp: (/* @__PURE__ */ new Date()).toISOString(), code, signal, message, stderrTail, relativePath: cur.currentJob?.relativePath || "", runId: cur.currentJob?.runId || "" };
3645
- try {
3646
- logStore?.append(event);
3647
- } catch {
3648
- }
3649
- try {
3650
- logStore?.compact();
3651
- } catch {
3652
- }
3653
- return applyServeEvent(cur, event);
3654
- });
3655
- }
3656
- try {
3657
- logStore?.compact();
3658
- } catch {
3659
- }
3660
- onExit({ code, signal, message });
3661
- });
3662
3058
  }
3663
- async function withOutputMode(label, fn) {
3664
- term.fullscreen(false);
3665
- setCursorVisible(true);
3666
- term.grabInput(false);
3667
- process.stdout.write(`
3668
- ${label}
3669
- ${"\u2500".repeat(50)}
3670
-
3671
- `);
3672
- try {
3673
- await fn();
3674
- } catch (error) {
3675
- process.stdout.write(`
3676
- Something went wrong: ${errMsg(error)}
3059
+ async function runTui({
3060
+ env = process.env,
3061
+ platform = process.platform,
3062
+ output = process.stdout
3063
+ } = {}) {
3064
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
3065
+ output.write(`${helpText()}
3677
3066
  `);
3678
- }
3679
- await pressEnterToContinue({ grabActive: false });
3680
- term.fullscreen(true);
3681
- setCursorVisible(false);
3682
- term.grabInput({ mouse: "motion" });
3683
- }
3684
- function waitForKey(keys = null) {
3685
- return new Promise((resolve) => {
3686
- function handler(name) {
3687
- if (!keys || keys.includes(name)) {
3688
- term.off("key", handler);
3689
- resolve(name);
3690
- }
3691
- }
3692
- term.on("key", handler);
3693
- });
3694
- }
3695
- function setCursorVisible(visible) {
3696
- if (typeof term?.hideCursor === "function") {
3697
- term.hideCursor(!visible);
3698
3067
  return;
3699
3068
  }
3700
- if (visible && typeof term?.showCursor === "function") {
3701
- term.showCursor();
3702
- return;
3703
- }
3704
- if (!visible && typeof term?.hideCursor === "function") {
3705
- term.hideCursor();
3706
- }
3707
- }
3708
- async function pressEnterToContinue({ grabActive = true } = {}) {
3709
- if (grabActive) {
3710
- term("\n Press any key to continue\u2026");
3711
- await waitForKey();
3712
- return;
3713
- }
3714
- process.stdout.write("\n Press Enter to continue\u2026 ");
3715
- return new Promise((resolve) => {
3716
- const rl = readlinePromises.createInterface({ input: process.stdin });
3717
- rl.once("line", () => {
3718
- rl.close();
3719
- resolve();
3720
- });
3721
- rl.once("close", resolve);
3722
- });
3723
- }
3724
- function formatAgentLogEntry(entry) {
3725
- if (!entry) return "";
3726
- switch (entry.eventType) {
3727
- case "job_started":
3728
- return entry.message || `Started ${entry.jobType || "job"}`;
3729
- case "job_completed":
3730
- return entry.success === false ? `Failed: ${fmtErr(entry.error) || entry.message || "error"}` : entry.message || "Completed";
3731
- case "error":
3732
- return `Error: ${entry.message || fmtErr(entry.error) || "unknown"}`;
3733
- case "log":
3734
- return entry.message || "";
3735
- case "progress": {
3736
- const p = entry.current != null && entry.total != null ? `${entry.current}/${entry.total} ` : "";
3737
- return `${p}${entry.message || "progress"}`;
3738
- }
3739
- case "completion":
3740
- return entry.success ? "Completed" : `Failed: ${fmtErr(entry.error) || "error"}`;
3741
- case "step_event":
3742
- return `Step ${entry.stepId || ""}${entry.message ? ": " + entry.message : ""}`;
3743
- default:
3744
- return entry.message || entry.eventType || "event";
3745
- }
3746
- }
3747
- function fmtErr(error) {
3748
- if (!error) return "";
3749
- return typeof error === "object" ? error.message || "error" : String(error);
3069
+ await refreshInteractiveManagedRuntime({ env, platform, output });
3070
+ await mainLoop({ env, platform });
3750
3071
  }
3751
- function formatLogTime(ts) {
3752
- if (!ts) return " ";
3072
+ async function refreshInteractiveManagedRuntime({
3073
+ env = process.env,
3074
+ platform = process.platform,
3075
+ output = process.stderr,
3076
+ refreshPackageAccess = refreshPackageAccessFromCloudAuth,
3077
+ autoUpdate = maybeAutoUpdateManagedEnv
3078
+ } = {}) {
3079
+ if (env.VENDIAN_SKIP_AUTO_UPDATE === "1") return false;
3753
3080
  try {
3754
- const d = new Date(ts);
3755
- return `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`;
3756
- } catch {
3757
- return " ";
3081
+ await refreshPackageAccess({ env, platform });
3082
+ } catch (error) {
3083
+ output.write(`[vendian] Package access refresh failed; continuing. ${errMsg(error)}
3084
+ `);
3758
3085
  }
3759
- }
3760
- function pad2(n) {
3761
- return String(n).padStart(2, "0");
3086
+ return autoUpdate({ env, platform, force: true });
3762
3087
  }
3763
3088
  function endpointRows({ env = process.env, platform = process.platform } = {}) {
3764
3089
  return ENDPOINTS.map((ep) => {
@@ -3803,15 +3128,12 @@ async function connectEndpointInteractive({
3803
3128
  const status = cloudAuthStatus({ backend, apiUrl, env, platform });
3804
3129
  const destination = label || envLabel(status.apiUrl);
3805
3130
  if (status.authenticated) {
3806
- term.gray(` Connecting to ${destination}\u2026
3807
- `);
3131
+ print(col.gray(` Connecting to ${destination}\u2026`));
3808
3132
  try {
3809
3133
  await switchOrLoginEndpoint({ backend, apiUrl, env, platform, setupFn, activateFn });
3810
- term.green(` ${fig.check} ${successLabel || `Signed in to ${destination} successfully`}
3811
- `);
3134
+ print(col.green(` ${fig.check} ${successLabel || `Signed in to ${destination} successfully`}`));
3812
3135
  } catch (error) {
3813
- term.red(` ${endpointErrorStatus(error)}
3814
- `);
3136
+ print(col.red(` ${endpointErrorStatus(error)}`));
3815
3137
  }
3816
3138
  return { promptedInTui: true };
3817
3139
  }
@@ -3841,26 +3163,15 @@ function buildLocalRunArgs({
3841
3163
  inputJson || "{}"
3842
3164
  ];
3843
3165
  }
3844
- function runtimeSummary({ env = process.env, platform = process.platform, now = Date.now() } = {}) {
3845
- const config = loadConfig(env, platform);
3846
- const venvPath = managedVenvPath(env, platform);
3847
- const installed = fs11.existsSync(venvVendian(venvPath, platform));
3848
- const lastUpdate = Date.parse(config.lastManagedUpdateAt || "");
3849
- const stale = !Number.isFinite(lastUpdate) || now - lastUpdate > 24 * 60 * 60 * 1e3;
3850
- return {
3851
- installed,
3852
- detail: installed ? venvPath : "Run vendian login to prepare it",
3853
- updateHint: installed && stale ? "Run vendian update to refresh packages" : ""
3854
- };
3855
- }
3856
- function packageAccessSummary({ env = process.env, platform = process.platform } = {}) {
3857
- const registry = registryConfig(loadConfig(env, platform), env, platform);
3858
- return { configured: Boolean(registry.token), source: registry.tokenSource || "local config" };
3166
+ function stopSignalForAttempt(attempt = 0) {
3167
+ if (!Number.isFinite(attempt) || attempt <= 0) return "SIGINT";
3168
+ if (attempt === 1) return "SIGTERM";
3169
+ return "SIGKILL";
3859
3170
  }
3860
3171
  function helpText() {
3861
3172
  return [
3862
3173
  "",
3863
- ` ${fig.arrowUp} VENDIAN CLI v${CLI_VERSION}`,
3174
+ ` \u2191 VENDIAN CLI v${CLI_VERSION}`,
3864
3175
  "",
3865
3176
  " Usage:",
3866
3177
  " vendian Open the interactive shell",
@@ -3876,7 +3187,6 @@ function helpText() {
3876
3187
  " vendian init --output-dir ./agents",
3877
3188
  ' vendian create "My Agent" --output-dir ./agents',
3878
3189
  " vendian validate ./agents/my-agent --runtime",
3879
- " vendian test ./agents/my-agent --dry-run --mock-credentials",
3880
3190
  " vendian models",
3881
3191
  " vendian cloud local serve --agents-dir ./agents",
3882
3192
  " vendian doctor",
@@ -3886,6 +3196,21 @@ function helpText() {
3886
3196
  ""
3887
3197
  ].join("\n");
3888
3198
  }
3199
+ function friendlyName(raw) {
3200
+ return String(raw || "").replace(/.*[\\/]/, "").replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()).trim() || String(raw || "");
3201
+ }
3202
+ function readManifestName(absoluteFolderPath) {
3203
+ if (!absoluteFolderPath) return null;
3204
+ for (const fname of ["manifest.yaml", "manifest.yml"]) {
3205
+ try {
3206
+ const content = fs11.readFileSync(path8.join(absoluteFolderPath, fname), "utf8");
3207
+ const m = content.match(/^name\s*:\s*['"]?([^'"\n#]+?)['"]?\s*$/m);
3208
+ if (m?.[1]) return m[1].trim();
3209
+ } catch {
3210
+ }
3211
+ }
3212
+ return null;
3213
+ }
3889
3214
  function clip(value, length) {
3890
3215
  const text = String(value || "");
3891
3216
  return text.length <= length ? text : `${text.slice(0, Math.max(0, length - 1))}\u2026`;
@@ -3893,6 +3218,26 @@ function clip(value, length) {
3893
3218
  function errMsg(error) {
3894
3219
  return error && typeof error.message === "string" ? error.message : String(error);
3895
3220
  }
3221
+ function fmtErr(error) {
3222
+ if (!error) return "";
3223
+ return typeof error === "object" ? error.message || "error" : String(error);
3224
+ }
3225
+ function shortId2(value) {
3226
+ const text = String(value || "");
3227
+ return text ? text.slice(0, 8) : "";
3228
+ }
3229
+ function formatLogTime(ts) {
3230
+ if (!ts) return " ";
3231
+ try {
3232
+ const d = new Date(ts);
3233
+ return `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`;
3234
+ } catch {
3235
+ return " ";
3236
+ }
3237
+ }
3238
+ function pad2(n) {
3239
+ return String(n).padStart(2, "0");
3240
+ }
3896
3241
 
3897
3242
  // src/main.js
3898
3243
  function printHelp() {