@vendian/cli 0.0.27 → 0.0.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,6 +8,7 @@ vendian
8
8
  vendian login
9
9
  vendian init --output-dir ./agents
10
10
  vendian create "My Agent" --output-dir ./agents
11
+ vendian cloud local run --collection-id YOUR_COLLECTION --path ./agents/my-agent --input-json '{}'
11
12
  vendian cloud local serve --agents-dir ./agents
12
13
  ```
13
14
 
@@ -23,11 +24,13 @@ vendian create "My Agent" --output-dir ./agents
23
24
  vendian validate ./agents/my-agent --runtime
24
25
  vendian test ./agents/my-agent --dry-run --mock-credentials
25
26
  vendian models
27
+ vendian cloud local run --collection-id YOUR_COLLECTION --path ./agents/my-agent --input-json '{}'
26
28
  vendian cloud local serve --agents-dir ./agents
27
29
  ```
28
30
 
29
31
  Run `vendian` with no arguments to open the interactive menu. It shows your
30
- current endpoint connections and guides common local workflows.
32
+ current endpoint connections and guides common local workflows, including
33
+ running one local agent immediately or starting the long-lived local daemon.
31
34
 
32
35
  `vendian login` opens a browser sign-in, prepares the local runtime, and stores
33
36
  the credentials needed by the CLI for the selected endpoint. `vendian setup` is
package/cli-wrapper.mjs CHANGED
@@ -1107,7 +1107,7 @@ import path8 from "node:path";
1107
1107
  import readlinePromises from "node:readline/promises";
1108
1108
 
1109
1109
  // src/version.js
1110
- var CLI_VERSION = true ? "0.0.27" : process.env.npm_package_version || "0.0.0-dev";
1110
+ var CLI_VERSION = true ? "0.0.29" : 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;
@@ -2399,6 +2399,87 @@ async function loadTerm() {
2399
2399
  }
2400
2400
  var HEADER_ROWS_BASE = 7;
2401
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;
2477
+ }
2478
+ function stopSignalForAttempt(attempt = 0) {
2479
+ if (!Number.isFinite(attempt) || attempt <= 0) return "SIGINT";
2480
+ if (attempt === 1) return "SIGTERM";
2481
+ return "SIGKILL";
2482
+ }
2402
2483
  async function runTui({
2403
2484
  env = process.env,
2404
2485
  platform = process.platform,
@@ -2412,12 +2493,12 @@ async function runTui({
2412
2493
  await refreshInteractiveManagedRuntime({ env, platform, output });
2413
2494
  await loadTerm();
2414
2495
  term.fullscreen(true);
2415
- term.hideCursor();
2496
+ setCursorVisible(false);
2416
2497
  term.grabInput({ mouse: "motion" });
2417
2498
  const cleanup = () => {
2418
2499
  try {
2419
2500
  term.grabInput(false);
2420
- term.showCursor();
2501
+ setCursorVisible(true);
2421
2502
  term.fullscreen(false);
2422
2503
  } catch {
2423
2504
  }
@@ -2451,6 +2532,9 @@ async function mainLoop({ env, platform }) {
2451
2532
  while (true) {
2452
2533
  let next;
2453
2534
  switch (screen) {
2535
+ case "run":
2536
+ next = await showRun({ env, platform });
2537
+ break;
2454
2538
  case "home":
2455
2539
  next = await showHome({ env, platform });
2456
2540
  break;
@@ -2532,6 +2616,7 @@ var HOME_ACTIONS = [
2532
2616
  { value: "connect", label: "Sign in", desc: "Connect to a different account or environment" },
2533
2617
  { value: "create", label: "Create Agent", desc: "Build a new agent from a template" },
2534
2618
  { value: "commands", label: "Commands", desc: "Show all available CLI commands" },
2619
+ { value: "run", label: "Run once", desc: "Execute one local agent immediately" },
2535
2620
  { value: "exit", label: "Exit", desc: "" }
2536
2621
  ];
2537
2622
  async function showHome({ env, platform }) {
@@ -2628,6 +2713,41 @@ async function showCreate({ env, platform }) {
2628
2713
  );
2629
2714
  return "home";
2630
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
+ }
2631
2751
  var COMMAND_GROUPS = [
2632
2752
  { section: "Getting started", commands: [
2633
2753
  { cmd: "vendian login", desc: "Sign in and set up your computer" },
@@ -2640,6 +2760,7 @@ var COMMAND_GROUPS = [
2640
2760
  { cmd: "vendian models", desc: "List available AI models" }
2641
2761
  ] },
2642
2762
  { section: "Running agents", commands: [
2763
+ { cmd: "vendian cloud local run --collection-id ID --path . --input-json '{}'", desc: "Run one local agent once" },
2643
2764
  { cmd: "vendian cloud local serve --agents-dir .", desc: "Start agents (advanced mode)" },
2644
2765
  { cmd: "vendian login --backend staging", desc: "Sign in to staging environment" }
2645
2766
  ] },
@@ -2680,57 +2801,14 @@ async function showServe({ env, platform }) {
2680
2801
  const picked = await pickAgentsToRun({ env, platform, candidates });
2681
2802
  if (!picked) return "home";
2682
2803
  const { agentsDir } = picked;
2683
- term.clear();
2684
- drawHeader({ env, platform });
2685
- term("\n");
2686
- term.bold(` Getting ready to run your agents\u2026
2687
-
2688
- `);
2689
- let wsResult;
2690
- try {
2691
- wsResult = await listCloudWorkspaces({
2692
- env,
2693
- platform,
2694
- onProgress: (msg) => {
2695
- term.moveTo(1, HEADER_ROWS_BASE + 3);
2696
- term.eraseLine();
2697
- term.gray(` ${clip(msg, 60)}
2698
- `);
2699
- }
2700
- });
2701
- } catch (error) {
2702
- term.red(`
2703
- Couldn't connect to the server: ${errMsg(error)}
2704
- `);
2705
- term.gray(" Make sure you are signed in (choose Sign in from the main menu).\n");
2706
- await pressEnterToContinue({ grabActive: false });
2707
- return "home";
2708
- }
2709
- if (!wsResult.ok) {
2710
- term.red(`
2711
- ${fig.cross} ${wsResult.error}
2712
- `);
2713
- await pressEnterToContinue({ grabActive: false });
2714
- return "home";
2715
- }
2716
- let collectionId = wsResult.workspaces[0]?.id || "";
2717
- if (wsResult.workspaces.length > 1) {
2718
- term.clear();
2719
- drawHeader({ env, platform });
2720
- term("\n");
2721
- term.bold(" Which project do you want to run?\n\n");
2722
- const wsItems = [...wsResult.workspaces.map((w) => workspaceLabel(w)), "\u2190 Back"];
2723
- const wsResp = await term.singleColumnMenu(wsItems, {
2724
- cancelable: true,
2725
- style: term.gray,
2726
- selectedStyle: term.bgCyan.black.bold,
2727
- leftPadding: " ",
2728
- selectedLeftPadding: " \u203A "
2729
- }).promise;
2730
- if (wsResp.canceled || wsResp.selectedIndex === wsItems.length - 1) return "home";
2731
- collectionId = wsResult.workspaces[wsResp.selectedIndex]?.id || "";
2732
- }
2733
- return await runServeDashboard({ env, platform, agentsDir, collectionId });
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 });
2734
2812
  }
2735
2813
  async function pickAgentsToRun({ env, platform, candidates }) {
2736
2814
  if (candidates.length === 0) {
@@ -2804,6 +2882,107 @@ async function pickAgentsFromFolder({ env, platform, candidate }) {
2804
2882
  if (resp.selectedIndex === 0) return { agentsDir: candidate.path };
2805
2883
  return { agentsDir: named[resp.selectedIndex - 1].path };
2806
2884
  }
2885
+ async function pickSingleAgentToRun({ env, platform, candidates }) {
2886
+ 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") };
2897
+ }
2898
+ const agents = candidates.flatMap((candidate) => {
2899
+ const folders = findAgentFolders(candidate.absolutePath);
2900
+ if (folders.length > 0) {
2901
+ return folders.map((folder) => ({
2902
+ path: folder.path,
2903
+ displayName: readManifestName(folder.absolutePath) || friendlyName(folder.name || path8.basename(folder.path))
2904
+ }));
2905
+ }
2906
+ return [{
2907
+ path: candidate.path,
2908
+ displayName: readManifestName(candidate.absolutePath) || friendlyName(path8.basename(candidate.absolutePath) || candidate.path)
2909
+ }];
2910
+ });
2911
+ 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
+
2938
+ `);
2939
+ let wsResult;
2940
+ try {
2941
+ wsResult = await listCloudWorkspaces({
2942
+ env,
2943
+ platform,
2944
+ onProgress: (msg) => {
2945
+ term.moveTo(1, HEADER_ROWS_BASE + 3);
2946
+ term.eraseLine();
2947
+ term.gray(` ${clip(msg, 60)}
2948
+ `);
2949
+ }
2950
+ });
2951
+ } 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");
2956
+ await pressEnterToContinue({ grabActive: false });
2957
+ return null;
2958
+ }
2959
+ if (!wsResult.ok) {
2960
+ term.red(`
2961
+ ${fig.cross} ${wsResult.error}
2962
+ `);
2963
+ await pressEnterToContinue({ grabActive: false });
2964
+ return null;
2965
+ }
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
+ }
2807
2986
  async function runServeDashboard({ env, platform, agentsDir, collectionId }) {
2808
2987
  let state = initialServeState();
2809
2988
  let child = null;
@@ -2814,6 +2993,10 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId }) {
2814
2993
  let dashboardClosed = false;
2815
2994
  let exitPromptActive = false;
2816
2995
  let selectedIdx = 0;
2996
+ let pressedAgentIdx = null;
2997
+ let stopRequested = false;
2998
+ let stopSignalAttempt = 0;
2999
+ let stopTimer = null;
2817
3000
  const agentRowMap = /* @__PURE__ */ new Map();
2818
3001
  function agentDisplayList() {
2819
3002
  return state.agents.map((agent, i) => {
@@ -2841,12 +3024,26 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId }) {
2841
3024
  }
2842
3025
  function redraw() {
2843
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;
2844
3038
  drawHeader({ env, platform, serveState: state });
2845
3039
  term.moveTo(1, SERVE_CONTENT_ROW);
2846
3040
  term.eraseDisplayBelow();
2847
3041
  agentRowMap.clear();
2848
3042
  if (startupError) {
2849
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)}
2850
3047
  `);
2851
3048
  } else if (state.stopped) {
2852
3049
  term.yellow(` ${fig.warning} Your agents have stopped.
@@ -2860,12 +3057,15 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId }) {
2860
3057
  term.gray(`${clip(state.activity || "Starting up\u2026", 55)}
2861
3058
  `);
2862
3059
  }
2863
- const agents = agentDisplayList();
2864
3060
  if (agents.length > 0) {
2865
- term("\n");
3061
+ if (viewport.showBlankAfterStatus) term("\n");
2866
3062
  term.bold.gray(" Your agents:\n");
2867
- let lineOffset = SERVE_CONTENT_ROW + 3;
2868
- for (const ag of agents) {
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) {
2869
3069
  const isSelected = ag.num - 1 === selectedIdx;
2870
3070
  agentRowMap.set(lineOffset, ag.num - 1);
2871
3071
  const numStr = String(ag.num).padStart(2);
@@ -2916,26 +3116,32 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId }) {
2916
3116
  }
2917
3117
  term("\n");
2918
3118
  lineOffset++;
2919
- if (ag.errMsg) {
3119
+ if (viewport.showInlineErrors && isSelected && ag.errMsg) {
2920
3120
  term.red(` \u2514\u2500 ${clip(ag.errMsg, (term.width || 80) - 14)}
2921
3121
  `);
2922
3122
  lineOffset++;
2923
3123
  }
2924
3124
  }
2925
- term("\n");
2926
- term.gray(` Click an agent or use \u2191\u2193 + Enter to see its activity log
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}
2927
3129
  `);
3130
+ }
3131
+ if (viewport.showAgentHint) {
3132
+ term.gray(` Click an agent or use \u2191\u2193 + Enter to see its activity log
3133
+ `);
3134
+ }
2928
3135
  } else {
2929
- term("\n");
3136
+ if (viewport.showBlankAfterStatus) term("\n");
2930
3137
  term.gray(" Looking for your agents\u2026\n");
2931
3138
  }
2932
- const activity = recentActivityLines();
2933
- if (activity.length > 0) {
3139
+ if (viewport.showActivityHeader) {
3140
+ const visibleActivity = activity.slice(-viewport.visibleActivityCount);
2934
3141
  const divW = Math.min((term.width || 80) - 4, 60);
2935
- term("\n");
2936
3142
  term.gray(` ${"\u2500".repeat(3)} Recent activity ${"\u2500".repeat(Math.max(0, divW - 18))}
2937
3143
  `);
2938
- for (const e of activity) {
3144
+ for (const e of visibleActivity) {
2939
3145
  const t = formatLogTime(e.timestamp);
2940
3146
  const name = clip(e._agentName || "Agent", 20);
2941
3147
  const msg = clip(friendlyActivityLine(e), (term.width || 80) - 30);
@@ -2957,13 +3163,12 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId }) {
2957
3163
  }
2958
3164
  }
2959
3165
  }
2960
- const diagnostics = daemonDebugLines();
2961
- if (diagnostics.length > 0) {
3166
+ if (viewport.showDiagnosticsHeader) {
3167
+ const visibleDiagnostics = diagnostics.slice(-viewport.visibleDiagnosticsCount);
2962
3168
  const divW = Math.min((term.width || 80) - 4, 72);
2963
- term("\n");
2964
3169
  term.gray(` ${"\u2500".repeat(3)} Daemon diagnostics ${"\u2500".repeat(Math.max(0, divW - 23))}
2965
3170
  `);
2966
- for (const entry of diagnostics) {
3171
+ for (const entry of visibleDiagnostics) {
2967
3172
  const t = formatLogTime(entry.timestamp);
2968
3173
  const type = clip(entry.eventType || "event", 24).padEnd(24);
2969
3174
  const msg = clip(entry.message || "", (term.width || 80) - 38);
@@ -2983,6 +3188,10 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId }) {
2983
3188
  term("\n");
2984
3189
  if (ctrlCArmed) {
2985
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");
2986
3195
  } else {
2987
3196
  term.gray(" ");
2988
3197
  term.brightBlue.bold("S");
@@ -3039,9 +3248,11 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId }) {
3039
3248
  }
3040
3249
  return new Promise((resolve) => {
3041
3250
  function openAgentLog(idx) {
3042
- if (dashboardClosed || exitPromptActive || idx < 0 || idx >= state.agents.length) return;
3251
+ const agents = agentDisplayList();
3252
+ const clampedIdx = clampListIndex(idx, agents.length);
3253
+ if (dashboardClosed || exitPromptActive || agents.length === 0 || idx !== clampedIdx) return;
3043
3254
  overlayActive = true;
3044
- const ag = agentDisplayList()[idx];
3255
+ const ag = agents[clampedIdx];
3045
3256
  showAgentLog({ agent: ag, state, env, platform }).then(() => {
3046
3257
  if (dashboardClosed) return;
3047
3258
  overlayActive = false;
@@ -3056,14 +3267,62 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId }) {
3056
3267
  ctrlCArmTimer = null;
3057
3268
  }
3058
3269
  }
3270
+ function clearStopTimer() {
3271
+ if (stopTimer) {
3272
+ clearTimeout(stopTimer);
3273
+ stopTimer = null;
3274
+ }
3275
+ }
3276
+ function childIsRunning() {
3277
+ return Boolean(child) && child.exitCode == null && child.signalCode == null;
3278
+ }
3279
+ function scheduleStopEscalation() {
3280
+ clearStopTimer();
3281
+ if (!childIsRunning()) return;
3282
+ stopTimer = setTimeout(() => {
3283
+ if (!stopRequested || dashboardClosed || !childIsRunning()) return;
3284
+ stopSignalAttempt += 1;
3285
+ const signal = stopSignalForAttempt(stopSignalAttempt);
3286
+ state = {
3287
+ ...state,
3288
+ activity: signal === "SIGTERM" ? "Still stopping your agents\u2026" : "Force stopping your agents\u2026"
3289
+ };
3290
+ try {
3291
+ child.kill(signal);
3292
+ } catch {
3293
+ }
3294
+ if (!overlayActive) redraw();
3295
+ if (signal !== "SIGKILL") scheduleStopEscalation();
3296
+ }, 4e3);
3297
+ }
3298
+ function requestStop() {
3299
+ if (dashboardClosed) return;
3300
+ if (stopRequested) return;
3301
+ if (!childIsRunning()) {
3302
+ finish("home");
3303
+ return;
3304
+ }
3305
+ stopRequested = true;
3306
+ stopSignalAttempt = 0;
3307
+ state = { ...state, activity: "Stopping your agents\u2026" };
3308
+ clearCtrlCArm();
3309
+ try {
3310
+ child.kill(stopSignalForAttempt(stopSignalAttempt));
3311
+ } catch {
3312
+ }
3313
+ scheduleStopEscalation();
3314
+ if (!overlayActive) redraw();
3315
+ }
3059
3316
  function detachDashboardHandlers() {
3060
3317
  term.off("key", handleKey);
3061
3318
  term.off("mouse", handleMouse);
3319
+ term.off("resize", handleResize);
3062
3320
  }
3063
3321
  function finish(next) {
3064
3322
  if (dashboardClosed) return;
3065
3323
  dashboardClosed = true;
3066
3324
  clearCtrlCArm();
3325
+ clearStopTimer();
3067
3326
  detachDashboardHandlers();
3068
3327
  resolve(next);
3069
3328
  }
@@ -3083,41 +3342,62 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId }) {
3083
3342
  return;
3084
3343
  }
3085
3344
  if (overlayActive) return;
3086
- const agCount = state.agents.length;
3345
+ if (stopRequested) return;
3346
+ const agCount = agentDisplayList().length;
3087
3347
  if (name >= "1" && name <= "9") {
3088
3348
  openAgentLog(parseInt(name, 10) - 1);
3089
3349
  return;
3090
3350
  }
3091
3351
  if (name === "UP" && agCount > 0) {
3092
- selectedIdx = (selectedIdx - 1 + agCount) % agCount;
3352
+ selectedIdx = clampListIndex(selectedIdx - 1, agCount);
3093
3353
  redraw();
3094
3354
  } else if (name === "DOWN" && agCount > 0) {
3095
- selectedIdx = (selectedIdx + 1) % agCount;
3355
+ selectedIdx = clampListIndex(selectedIdx + 1, agCount);
3096
3356
  redraw();
3097
3357
  } else if (name === "ENTER") {
3098
3358
  openAgentLog(selectedIdx);
3099
3359
  } else if (name === "s" || name === "S") {
3100
- child?.kill("SIGINT");
3101
- finish("home");
3360
+ requestStop();
3102
3361
  }
3103
3362
  }
3104
3363
  function handleMouse(name, data) {
3105
- if (overlayActive) return;
3364
+ if (overlayActive || stopRequested) return;
3106
3365
  const idx = agentRowMap.get(data.y);
3107
- if (idx === void 0) return;
3108
3366
  if (name === "MOUSE_LEFT_BUTTON_PRESSED") {
3109
- openAgentLog(idx);
3110
- } else if (name !== "MOUSE_LEFT_BUTTON_RELEASED") {
3367
+ pressedAgentIdx = idx ?? null;
3368
+ if (idx !== void 0 && idx !== selectedIdx) {
3369
+ selectedIdx = idx;
3370
+ redraw();
3371
+ }
3372
+ return;
3373
+ }
3374
+ if (name === "MOUSE_LEFT_BUTTON_RELEASED") {
3375
+ const shouldOpen = idx !== void 0 && idx === pressedAgentIdx;
3376
+ pressedAgentIdx = null;
3377
+ if (shouldOpen) openAgentLog(idx);
3378
+ return;
3379
+ }
3380
+ if (idx === void 0) return;
3381
+ if (name !== "MOUSE_LEFT_BUTTON_RELEASED") {
3111
3382
  if (idx !== selectedIdx) {
3112
3383
  selectedIdx = idx;
3113
3384
  redraw();
3114
3385
  }
3115
3386
  }
3116
3387
  }
3388
+ function handleResize() {
3389
+ if (!overlayActive) redraw();
3390
+ }
3117
3391
  term.on("key", handleKey);
3118
3392
  term.on("mouse", handleMouse);
3393
+ term.on("resize", handleResize);
3119
3394
  if (child) {
3120
3395
  child.once("exit", () => {
3396
+ clearStopTimer();
3397
+ if (stopRequested) {
3398
+ finish("home");
3399
+ return;
3400
+ }
3121
3401
  setTimeout(() => {
3122
3402
  if (dashboardClosed || overlayActive) return;
3123
3403
  exitPromptActive = true;
@@ -3350,7 +3630,7 @@ function attachServeChild(child, onUpdate, onExit, logStore = null) {
3350
3630
  }
3351
3631
  async function withOutputMode(label, fn) {
3352
3632
  term.fullscreen(false);
3353
- term.showCursor();
3633
+ setCursorVisible(true);
3354
3634
  term.grabInput(false);
3355
3635
  process.stdout.write(`
3356
3636
  ${label}
@@ -3366,7 +3646,7 @@ async function withOutputMode(label, fn) {
3366
3646
  }
3367
3647
  await pressEnterToContinue({ grabActive: false });
3368
3648
  term.fullscreen(true);
3369
- term.hideCursor();
3649
+ setCursorVisible(false);
3370
3650
  term.grabInput({ mouse: "motion" });
3371
3651
  }
3372
3652
  function waitForKey(keys = null) {
@@ -3380,6 +3660,19 @@ function waitForKey(keys = null) {
3380
3660
  term.on("key", handler);
3381
3661
  });
3382
3662
  }
3663
+ function setCursorVisible(visible) {
3664
+ if (typeof term?.hideCursor === "function") {
3665
+ term.hideCursor(!visible);
3666
+ return;
3667
+ }
3668
+ if (visible && typeof term?.showCursor === "function") {
3669
+ term.showCursor();
3670
+ return;
3671
+ }
3672
+ if (!visible && typeof term?.hideCursor === "function") {
3673
+ term.hideCursor();
3674
+ }
3675
+ }
3383
3676
  async function pressEnterToContinue({ grabActive = true } = {}) {
3384
3677
  if (grabActive) {
3385
3678
  term("\n Press any key to continue\u2026");
@@ -3499,6 +3792,23 @@ function endpointErrorStatus(error) {
3499
3792
  const message = error && typeof error.message === "string" ? error.message : String(error || "Connection failed");
3500
3793
  return `${fig.cross} ${message}`;
3501
3794
  }
3795
+ function buildLocalRunArgs({
3796
+ agentPath = ".",
3797
+ collectionId = "",
3798
+ inputJson = "{}"
3799
+ } = {}) {
3800
+ return [
3801
+ "cloud",
3802
+ "local",
3803
+ "run",
3804
+ "--collection-id",
3805
+ collectionId,
3806
+ "--path",
3807
+ agentPath || ".",
3808
+ "--input-json",
3809
+ inputJson || "{}"
3810
+ ];
3811
+ }
3502
3812
  function runtimeSummary({ env = process.env, platform = process.platform, now = Date.now() } = {}) {
3503
3813
  const config = loadConfig(env, platform);
3504
3814
  const venvPath = managedVenvPath(env, platform);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vendian/cli",
3
- "version": "0.0.27",
3
+ "version": "0.0.29",
4
4
  "description": "Public Vendian CLI bootstrapper and launcher",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,