@vendian/cli 0.0.9 → 0.0.10

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 +1020 -137
  2. package/package.json +2 -1
package/cli-wrapper.mjs CHANGED
@@ -1381,7 +1381,7 @@ var require_react_development = __commonJS({
1381
1381
  var dispatcher = resolveDispatcher();
1382
1382
  return dispatcher.useReducer(reducer, initialArg, init);
1383
1383
  }
1384
- function useRef2(initialValue) {
1384
+ function useRef3(initialValue) {
1385
1385
  var dispatcher = resolveDispatcher();
1386
1386
  return dispatcher.useRef(initialValue);
1387
1387
  }
@@ -2175,7 +2175,7 @@ var require_react_development = __commonJS({
2175
2175
  exports.useLayoutEffect = useLayoutEffect2;
2176
2176
  exports.useMemo = useMemo3;
2177
2177
  exports.useReducer = useReducer;
2178
- exports.useRef = useRef2;
2178
+ exports.useRef = useRef3;
2179
2179
  exports.useState = useState6;
2180
2180
  exports.useSyncExternalStore = useSyncExternalStore;
2181
2181
  exports.useTransition = useTransition;
@@ -36691,16 +36691,27 @@ function packageIndexEnv(config = {}, env3 = process.env, platform2 = process.pl
36691
36691
  function joinIndexUrls(urls) {
36692
36692
  return urls.flatMap((value) => String(value || "").split(/\s+/)).map((value) => value.trim()).filter(Boolean).filter((value, index, all) => all.indexOf(value) === index).join(" ");
36693
36693
  }
36694
- function maybeAutoUpdateManagedEnv({ env: env3 = process.env, platform: platform2 = process.platform, venvPath = managedVenvPath(env3, platform2) } = {}) {
36694
+ function maybeAutoUpdateManagedEnv({
36695
+ env: env3 = process.env,
36696
+ platform: platform2 = process.platform,
36697
+ venvPath = managedVenvPath(env3, platform2),
36698
+ force = false,
36699
+ log = true,
36700
+ load = loadConfig,
36701
+ save = saveConfig,
36702
+ installPackages = installVendianPackages,
36703
+ refreshDocs = refreshAgentDocsWorkspaces,
36704
+ now = Date.now()
36705
+ } = {}) {
36695
36706
  if (env3.VENDIAN_SKIP_AUTO_UPDATE === "1") {
36696
36707
  return false;
36697
36708
  }
36698
- const config = loadConfig(env3, platform2);
36709
+ const config = load(env3, platform2);
36699
36710
  const lastUpdate = Date.parse(config.lastManagedUpdateAt || "");
36700
- if (Number.isFinite(lastUpdate) && Date.now() - lastUpdate < AUTO_UPDATE_INTERVAL_MS) {
36711
+ if (!force && Number.isFinite(lastUpdate) && now - lastUpdate < AUTO_UPDATE_INTERVAL_MS) {
36701
36712
  return false;
36702
36713
  }
36703
- const registry = registryConfig(config, env3);
36714
+ const registry = registryConfig(config, env3, platform2);
36704
36715
  if (!registry.token) {
36705
36716
  return false;
36706
36717
  }
@@ -36709,14 +36720,19 @@ function maybeAutoUpdateManagedEnv({ env: env3 = process.env, platform: platform
36709
36720
  return false;
36710
36721
  }
36711
36722
  try {
36712
- console.error("[vendian] Checking managed CLI/runtime updates...");
36713
- installVendianPackages({ pythonPath, venvPath, config, env: env3 });
36714
- const next = { ...config, lastManagedUpdateAt: (/* @__PURE__ */ new Date()).toISOString() };
36715
- refreshAgentDocsWorkspaces({ config: next, venvPath, env: env3, platform: platform2 });
36723
+ if (log) {
36724
+ console.error("[vendian] Checking managed CLI/runtime updates...");
36725
+ }
36726
+ installPackages({ pythonPath, venvPath, config, env: env3, platform: platform2 });
36727
+ const next = { ...config, lastManagedUpdateAt: new Date(now).toISOString() };
36728
+ save(next, env3, platform2);
36729
+ refreshDocs({ config: next, venvPath, env: env3, platform: platform2 });
36716
36730
  return true;
36717
36731
  } catch (error) {
36718
36732
  const message = error && typeof error.message === "string" ? error.message : String(error);
36719
- console.error(`[vendian] Update check failed; continuing with installed CLI. ${message}`);
36733
+ if (log) {
36734
+ console.error(`[vendian] Update check failed; continuing with installed CLI. ${message}`);
36735
+ }
36720
36736
  return false;
36721
36737
  }
36722
36738
  }
@@ -36725,16 +36741,12 @@ function maybeAutoUpdateManagedEnv({ env: env3 = process.env, platform: platform
36725
36741
  import fs11 from "node:fs";
36726
36742
  import readlinePromises from "node:readline/promises";
36727
36743
 
36728
- // src/npm-update.js
36729
- import { execFile } from "node:child_process";
36730
- import { promisify } from "node:util";
36731
-
36732
36744
  // src/version.js
36733
- var CLI_VERSION = true ? "0.0.9" : process.env.npm_package_version || "0.0.0-dev";
36745
+ var CLI_VERSION = true ? "0.0.10" : process.env.npm_package_version || "0.0.0-dev";
36734
36746
 
36735
36747
  // src/npm-update.js
36736
- var execFileAsync = promisify(execFile);
36737
36748
  var NPM_CHECK_INTERVAL_MS = 30 * 60 * 1e3;
36749
+ var CLI_PACKAGE_NAME = "@vendian/cli";
36738
36750
  function npmPackageUpdateSummary({
36739
36751
  config = {},
36740
36752
  currentVersion = CLI_VERSION,
@@ -36757,21 +36769,14 @@ function npmPackageUpdateSummary({
36757
36769
  async function checkNpmPackageUpdate({
36758
36770
  env: env3 = process.env,
36759
36771
  platform: platform2 = process.platform,
36760
- packageName = "@vendian/cli",
36772
+ packageName = CLI_PACKAGE_NAME,
36761
36773
  registry = "https://registry.npmjs.org/",
36762
36774
  currentVersion = CLI_VERSION,
36763
- timeoutMs = 4e3
36775
+ timeoutMs = 4e3,
36776
+ fetchImpl = globalThis.fetch
36764
36777
  } = {}) {
36765
- const npm = platform2 === "win32" ? "npm.cmd" : "npm";
36766
- const args = ["view", packageName, "dist-tags.latest", "--json", `--registry=${registry}`];
36767
36778
  try {
36768
- const { stdout } = await execFileAsync(npm, args, {
36769
- env: env3,
36770
- encoding: "utf8",
36771
- timeout: timeoutMs,
36772
- windowsHide: true
36773
- });
36774
- const latestVersion = parseNpmLatest(stdout);
36779
+ const latestVersion = await latestNpmPackageVersion({ packageName, registry, timeoutMs, fetchImpl });
36775
36780
  const config = {
36776
36781
  ...loadConfig(env3, platform2),
36777
36782
  latestNpmCliVersion: latestVersion,
@@ -36786,6 +36791,33 @@ async function checkNpmPackageUpdate({
36786
36791
  };
36787
36792
  }
36788
36793
  }
36794
+ async function latestNpmPackageVersion({
36795
+ packageName = CLI_PACKAGE_NAME,
36796
+ registry = "https://registry.npmjs.org/",
36797
+ timeoutMs = 4e3,
36798
+ fetchImpl = globalThis.fetch
36799
+ } = {}) {
36800
+ if (typeof fetchImpl !== "function") {
36801
+ throw new Error("fetch is unavailable");
36802
+ }
36803
+ const controller = new AbortController();
36804
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
36805
+ try {
36806
+ const response = await fetchImpl(registryPackageUrl(registry, packageName), {
36807
+ headers: {
36808
+ accept: "application/vnd.npm.install-v1+json, application/json"
36809
+ },
36810
+ signal: controller.signal
36811
+ });
36812
+ if (!response?.ok) {
36813
+ throw new Error(`npm registry returned ${response?.status || "no response"}`);
36814
+ }
36815
+ const metadata = await response.json();
36816
+ return parseNpmLatest(metadata?.["dist-tags"]?.latest);
36817
+ } finally {
36818
+ clearTimeout(timeout);
36819
+ }
36820
+ }
36789
36821
  function updateLabel({ runtime, npmUpdate }) {
36790
36822
  if (npmUpdate?.updateAvailable) {
36791
36823
  return `${npmUpdate.label} (current ${npmUpdate.currentVersion})`;
@@ -36813,6 +36845,19 @@ function parseNpmLatest(stdout) {
36813
36845
  return raw.replace(/^"|"$/g, "").trim();
36814
36846
  }
36815
36847
  }
36848
+ function registryPackageUrl(registry, packageName) {
36849
+ const base = String(registry || "https://registry.npmjs.org/");
36850
+ const normalizedBase = base.endsWith("/") ? base : `${base}/`;
36851
+ return new URL(encodeRegistryPackageName(packageName), normalizedBase).toString();
36852
+ }
36853
+ function encodeRegistryPackageName(packageName) {
36854
+ const name = String(packageName || "").trim();
36855
+ if (!name.startsWith("@")) {
36856
+ return encodeURIComponent(name);
36857
+ }
36858
+ const [scope, pkg] = name.slice(1).split("/");
36859
+ return `@${encodeURIComponent(scope || "")}%2f${encodeURIComponent(pkg || "")}`;
36860
+ }
36816
36861
  function compareSemver(left, right) {
36817
36862
  const leftParts = semverParts(left);
36818
36863
  const rightParts = semverParts(right);
@@ -37047,6 +37092,9 @@ function initialServeState() {
37047
37092
  retry: null,
37048
37093
  errors: [],
37049
37094
  logs: [],
37095
+ agentLogs: {},
37096
+ // per-agent logs keyed by relativePath
37097
+ newAgents: [],
37050
37098
  stopped: false,
37051
37099
  jobsRun: 0
37052
37100
  };
@@ -37062,6 +37110,19 @@ function parseServeEventLine(line) {
37062
37110
  }
37063
37111
  return parsed;
37064
37112
  }
37113
+ function serveProcessExitMessage({ stderr = "", code = 0, signal = "" } = {}) {
37114
+ const text = String(stderr || "");
37115
+ if (/unrecognized arguments:\s*--event-stream/i.test(text)) {
37116
+ return "Managed Vendian runtime is too old for the local serve dashboard. Run `vendian update`, then start Serve Agents again.";
37117
+ }
37118
+ if (code && code !== 0) {
37119
+ return `Local serve exited with code ${code}. Toggle details for stderr.`;
37120
+ }
37121
+ if (signal && signal !== "SIGINT" && signal !== "SIGTERM") {
37122
+ return `Local serve exited from ${signal}. Toggle details for stderr.`;
37123
+ }
37124
+ return "";
37125
+ }
37065
37126
  function applyServeEvent(state, event) {
37066
37127
  if (!event || typeof event.type !== "string") {
37067
37128
  return state;
@@ -37093,10 +37154,13 @@ function applyServeEvent(state, event) {
37093
37154
  };
37094
37155
  }
37095
37156
  if (event.type === "inventory_synced") {
37157
+ const agents = Array.isArray(event.agents) ? event.agents : state.agents;
37158
+ const newAgents = state.agents.length > 0 ? findNewAgents(state.agents, agents) : [];
37096
37159
  return {
37097
37160
  ...next,
37098
- agents: Array.isArray(event.agents) ? event.agents : state.agents,
37099
- activity: `Inventory synced (${Number(event.agentCount ?? event.agents?.length ?? 0)} agent(s))`
37161
+ agents,
37162
+ newAgents,
37163
+ activity: newAgents.length ? `${newAgents.length} new agent${newAgents.length === 1 ? "" : "s"} synced` : `Inventory synced (${Number(event.agentCount ?? agents.length ?? 0)} agent(s))`
37100
37164
  };
37101
37165
  }
37102
37166
  if (event.type === "job_started") {
@@ -37140,6 +37204,12 @@ function applyServeEvent(state, event) {
37140
37204
  jobsRun: Number(event.jobsRun ?? state.jobsRun)
37141
37205
  };
37142
37206
  }
37207
+ if (event.type === "run_log") {
37208
+ return {
37209
+ ...next,
37210
+ agentLogs: appendAgentLog(state.agentLogs, event)
37211
+ };
37212
+ }
37143
37213
  return next;
37144
37214
  }
37145
37215
  function agentSummary(agents = []) {
@@ -37161,9 +37231,45 @@ function agentSummary(agents = []) {
37161
37231
  function appendLog(logs, event) {
37162
37232
  return [...logs, event].slice(-200);
37163
37233
  }
37234
+ function appendAgentLog(agentLogs, event) {
37235
+ const key = stringValue(event.relativePath || ".");
37236
+ const existing = agentLogs[key] || [];
37237
+ const entry = {
37238
+ timestamp: event.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
37239
+ runId: stringValue(event.runId),
37240
+ eventType: stringValue(event.eventType),
37241
+ level: stringValue(event.level || "info"),
37242
+ message: stringValue(event.message),
37243
+ stepId: event.stepId ? stringValue(event.stepId) : null,
37244
+ current: event.current ?? null,
37245
+ total: event.total ?? null,
37246
+ success: event.success ?? null,
37247
+ error: event.error ?? null
37248
+ };
37249
+ return {
37250
+ ...agentLogs,
37251
+ [key]: [...existing, entry].slice(-100)
37252
+ };
37253
+ }
37254
+ function agentLogEntries(agentLogs, relativePath) {
37255
+ return (agentLogs || {})[relativePath] || [];
37256
+ }
37164
37257
  function appendError(errors, scope, message) {
37165
37258
  return [...errors, { scope, message: stringValue(message) }].slice(-20);
37166
37259
  }
37260
+ function findNewAgents(previous, next) {
37261
+ const seen = new Set(previous.map(agentKey).filter(Boolean));
37262
+ return next.filter((agent) => {
37263
+ const key = agentKey(agent);
37264
+ return key && !seen.has(key);
37265
+ }).slice(0, 8);
37266
+ }
37267
+ function agentKey(agent) {
37268
+ if (!agent || typeof agent !== "object") {
37269
+ return "";
37270
+ }
37271
+ return stringValue(agent.localAgentId || agent.relativePath || agent.path || agent.manifestName || agent.id);
37272
+ }
37167
37273
  function agentLabel(event) {
37168
37274
  return stringValue(event.relativePath || event.path || ".");
37169
37275
  }
@@ -37182,27 +37288,152 @@ function formatSeconds(value) {
37182
37288
  import { spawn as spawn2 } from "node:child_process";
37183
37289
  async function spawnLocalServeEventStream({
37184
37290
  agentsDir = "./agents",
37291
+ collectionId = "",
37185
37292
  env: env3 = process.env,
37186
37293
  platform: platform2 = process.platform
37187
37294
  } = {}) {
37188
- const invocation = await preparePythonVendianInvocation([
37295
+ const invocation = await preparePythonVendianInvocation(buildLocalServeEventStreamArgs({ agentsDir, collectionId }), { env: env3, platform: platform2 });
37296
+ return spawn2(invocation.command, invocation.args, {
37297
+ env: invocation.env,
37298
+ stdio: ["ignore", "pipe", "pipe"],
37299
+ shell: false
37300
+ });
37301
+ }
37302
+ function buildLocalServeEventStreamArgs({ agentsDir = "./agents", collectionId = "" } = {}) {
37303
+ const args = [
37189
37304
  "cloud",
37190
37305
  "local",
37191
37306
  "serve",
37192
37307
  "--agents-dir",
37193
37308
  agentsDir || "./agents",
37194
37309
  "--event-stream"
37195
- ], { env: env3, platform: platform2 });
37196
- return spawn2(invocation.command, invocation.args, {
37197
- env: invocation.env,
37198
- stdio: ["ignore", "pipe", "pipe"],
37199
- shell: false
37200
- });
37310
+ ];
37311
+ if (collectionId) {
37312
+ args.push("--collection-id", collectionId);
37313
+ }
37314
+ return args;
37315
+ }
37316
+
37317
+ // src/workspaces.js
37318
+ async function listCloudWorkspaces({
37319
+ env: env3 = process.env,
37320
+ platform: platform2 = process.platform,
37321
+ prepareInvocation = preparePythonVendianInvocation,
37322
+ run: run2 = runCapture
37323
+ } = {}) {
37324
+ const invocation = await prepareInvocation(["cloud", "collections", "list", "--json"], { env: env3, platform: platform2 });
37325
+ const result = run2(invocation.command, invocation.args, { env: invocation.env });
37326
+ if (!result.ok) {
37327
+ return {
37328
+ ok: false,
37329
+ workspaces: [],
37330
+ error: (result.stderr || result.stdout || `workspace list exited with code ${result.status}`).trim()
37331
+ };
37332
+ }
37333
+ try {
37334
+ return { ok: true, workspaces: normalizeWorkspaceList(JSON.parse(result.stdout)), error: "" };
37335
+ } catch (error) {
37336
+ const message = error && typeof error.message === "string" ? error.message : String(error);
37337
+ return { ok: false, workspaces: [], error: `Could not parse workspace list: ${message}` };
37338
+ }
37201
37339
  }
37340
+ function normalizeWorkspaceList(payload) {
37341
+ const data = payload && typeof payload === "object" && "data" in payload ? payload.data : payload;
37342
+ const raw = Array.isArray(data) ? data : data && typeof data === "object" && Array.isArray(data.data) ? data.data : data && typeof data === "object" && Array.isArray(data.collections) ? data.collections : [];
37343
+ return raw.filter((item) => item && typeof item === "object").map((item) => ({
37344
+ id: stringValue2(item.id),
37345
+ name: stringValue2(item.name || item.slug || item.id || "Unnamed workspace"),
37346
+ slug: stringValue2(item.slug)
37347
+ })).filter((item) => item.id);
37348
+ }
37349
+ function workspaceLabel(workspace) {
37350
+ const name = workspace?.name || workspace?.slug || workspace?.id || "Unnamed workspace";
37351
+ const slug = workspace?.slug && workspace.slug !== name ? ` (${workspace.slug})` : "";
37352
+ return `${name}${slug}`;
37353
+ }
37354
+ function stringValue2(value) {
37355
+ return value == null ? "" : String(value).trim();
37356
+ }
37357
+
37358
+ // src/ui/theme.js
37359
+ var colors = {
37360
+ brand: "cyan",
37361
+ brandBold: "cyanBright",
37362
+ success: "green",
37363
+ error: "red",
37364
+ warning: "yellow",
37365
+ muted: "gray",
37366
+ accent: "blueBright",
37367
+ text: "white",
37368
+ dim: "gray"
37369
+ };
37370
+ var spacing = {
37371
+ labelWidth: 14,
37372
+ indent: 2
37373
+ };
37374
+
37375
+ // src/ui/figures.js
37376
+ var supportsUnicode = process.platform !== "win32" || Boolean(
37377
+ process.env.WT_SESSION || // Windows Terminal
37378
+ process.env.TERM_PROGRAM === "vscode" || // VS Code terminal
37379
+ process.env.TERM === "xterm-256color" || process.env.ConEmuTask || process.env.TERMINAL_EMULATOR
37380
+ );
37381
+ var unicode = {
37382
+ dot: "\u25CF",
37383
+ dotEmpty: "\u25CB",
37384
+ check: "\u2714",
37385
+ cross: "\u2716",
37386
+ warning: "\u26A0",
37387
+ arrow: "\u25B6",
37388
+ arrowRight: "\u203A",
37389
+ arrowUp: "\u25B2",
37390
+ dash: "\u2500",
37391
+ ellipsis: "\u2026",
37392
+ topLeft: "\u256D",
37393
+ topRight: "\u256E",
37394
+ bottomLeft: "\u2570",
37395
+ bottomRight: "\u256F",
37396
+ horizontal: "\u2500",
37397
+ vertical: "\u2502",
37398
+ teeRight: "\u251C",
37399
+ teeLeft: "\u2524",
37400
+ teeDown: "\u252C",
37401
+ teeUp: "\u2534",
37402
+ cross_join: "\u253C",
37403
+ bar: "\u2588",
37404
+ barEmpty: "\u2591"
37405
+ };
37406
+ var ascii = {
37407
+ dot: "*",
37408
+ dotEmpty: "o",
37409
+ check: "+",
37410
+ cross: "x",
37411
+ warning: "!",
37412
+ arrow: ">",
37413
+ arrowRight: ">",
37414
+ arrowUp: "^",
37415
+ dash: "-",
37416
+ ellipsis: "...",
37417
+ topLeft: "+",
37418
+ topRight: "+",
37419
+ bottomLeft: "+",
37420
+ bottomRight: "+",
37421
+ horizontal: "-",
37422
+ vertical: "|",
37423
+ teeRight: "+",
37424
+ teeLeft: "+",
37425
+ teeDown: "+",
37426
+ teeUp: "+",
37427
+ cross_join: "+",
37428
+ bar: "#",
37429
+ barEmpty: "."
37430
+ };
37431
+ var fig = supportsUnicode ? unicode : ascii;
37202
37432
 
37203
37433
  // src/tui.js
37204
37434
  var h;
37205
37435
  var useEffect6;
37436
+ var useRef2;
37206
37437
  var useState5;
37207
37438
  var Box2;
37208
37439
  var Text2;
@@ -37219,15 +37450,25 @@ var ENDPOINTS = [
37219
37450
  { key: "prod", label: "Production" }
37220
37451
  ];
37221
37452
  var ACTIONS = [
37222
- { value: "connect", label: "Connect Endpoint" },
37223
- { value: "init", label: "Initialize Docs" },
37224
- { value: "create", label: "Create Agent" },
37225
- { value: "serve", label: "Serve Agents" },
37226
- { value: "doctor", label: "Doctor" },
37227
- { value: "update", label: "Update" },
37228
- { value: "commands", label: "Commands" },
37229
- { value: "exit", label: "Exit" }
37453
+ { value: "connect", label: "Connect Endpoint", desc: "Switch between environments" },
37454
+ { value: "init", label: "Initialize Docs", desc: "Set up SDK documentation workspace" },
37455
+ { value: "create", label: "Create Agent", desc: "Scaffold a new agent from templates" },
37456
+ { value: "serve", label: "Serve Agents", desc: "Start local development server" },
37457
+ { value: "doctor", label: "Doctor", desc: "Check system health" },
37458
+ { value: "update", label: "Update", desc: "Update runtime & packages" },
37459
+ { value: "commands", label: "Commands", desc: "Show CLI command reference" },
37460
+ { value: "exit", label: "Exit", desc: "Close interactive shell" }
37230
37461
  ];
37462
+ var SCREEN_TITLES = {
37463
+ home: "Home",
37464
+ connect: "Connect Endpoint",
37465
+ init: "Initialize Docs",
37466
+ create: "Create Agent",
37467
+ serve: "Serve Agents",
37468
+ doctor: "Doctor",
37469
+ update: "Update",
37470
+ commands: "Commands"
37471
+ };
37231
37472
  var COMMANDS = [
37232
37473
  "vendian login",
37233
37474
  "vendian init --output-dir ./agents",
@@ -37247,6 +37488,7 @@ async function runTui({ env: env3 = process.env, platform: platform2 = process.p
37247
37488
  `);
37248
37489
  return;
37249
37490
  }
37491
+ await refreshInteractiveManagedRuntime({ env: env3, platform: platform2, output });
37250
37492
  await loadInkRuntime();
37251
37493
  const app = renderInk(h(VendianShell, { env: env3, platform: platform2, input, output }), {
37252
37494
  stdin: input,
@@ -37255,6 +37497,25 @@ async function runTui({ env: env3 = process.env, platform: platform2 = process.p
37255
37497
  });
37256
37498
  await app.waitUntilExit();
37257
37499
  }
37500
+ async function refreshInteractiveManagedRuntime({
37501
+ env: env3 = process.env,
37502
+ platform: platform2 = process.platform,
37503
+ output = process.stderr,
37504
+ refreshPackageAccess = refreshPackageAccessFromCloudAuth,
37505
+ autoUpdate = maybeAutoUpdateManagedEnv
37506
+ } = {}) {
37507
+ if (env3.VENDIAN_SKIP_AUTO_UPDATE === "1") {
37508
+ return false;
37509
+ }
37510
+ try {
37511
+ await refreshPackageAccess({ env: env3, platform: platform2 });
37512
+ } catch (error) {
37513
+ const message = errorMessage2(error);
37514
+ output.write(`[vendian] Package access refresh failed; continuing with installed runtime. ${message}
37515
+ `);
37516
+ }
37517
+ return autoUpdate({ env: env3, platform: platform2, force: true });
37518
+ }
37258
37519
  async function loadInkRuntime() {
37259
37520
  if (h) {
37260
37521
  return;
@@ -37268,6 +37529,7 @@ async function loadInkRuntime() {
37268
37529
  ]);
37269
37530
  h = reactModule.default.createElement;
37270
37531
  useEffect6 = reactModule.useEffect;
37532
+ useRef2 = reactModule.useRef;
37271
37533
  useState5 = reactModule.useState;
37272
37534
  Box2 = inkModule.Box;
37273
37535
  Text2 = inkModule.Text;
@@ -37278,6 +37540,20 @@ async function loadInkRuntime() {
37278
37540
  TextInput2 = textInputModule.default;
37279
37541
  Spinner2 = spinnerModule.default;
37280
37542
  }
37543
+ function endpointRows({ env: env3 = process.env, platform: platform2 = process.platform } = {}) {
37544
+ return ENDPOINTS.map((endpoint) => {
37545
+ const status = cloudAuthStatus({ backend: endpoint.key, env: env3, platform: platform2 });
37546
+ const active = status.activeApiUrl === status.apiUrl;
37547
+ return {
37548
+ key: endpoint.key,
37549
+ label: endpoint.label,
37550
+ apiUrl: status.apiUrl,
37551
+ active,
37552
+ status: status.authenticated ? active ? "connected" : "signed in" : "not signed in",
37553
+ detail: status.authenticated ? status.email || status.apiUrl : status.apiUrl
37554
+ };
37555
+ });
37556
+ }
37281
37557
  function runtimeSummary({ env: env3 = process.env, platform: platform2 = process.platform, now = Date.now() } = {}) {
37282
37558
  const config = loadConfig(env3, platform2);
37283
37559
  const venvPath = managedVenvPath(env3, platform2);
@@ -37298,37 +37574,92 @@ function packageAccessSummary({ env: env3 = process.env, platform: platform2 = p
37298
37574
  source: registry.tokenSource || "local config"
37299
37575
  };
37300
37576
  }
37577
+ function isCtrlCInput(input = "", key = {}) {
37578
+ return input === "" || input === "" || Boolean(key?.ctrl && (key.name === "c" || input === "c"));
37579
+ }
37301
37580
  function VendianShell({ env: env3, platform: platform2, input, output }) {
37302
37581
  const app = useApp2();
37303
37582
  const [screen, setScreen] = useState5("home");
37304
37583
  const [serveState, setServeState] = useState5(null);
37305
- useInput2((_, key) => {
37306
- if (key.ctrl && key.name === "c" && screen !== "serve") {
37584
+ const [exitArmed, setExitArmed] = useState5(false);
37585
+ const exitTimer = useRef2(null);
37586
+ const lastInterruptAt = useRef2(0);
37587
+ function armExit() {
37588
+ if (exitArmed) {
37307
37589
  app.exit();
37590
+ return;
37591
+ }
37592
+ setExitArmed(true);
37593
+ if (exitTimer.current) {
37594
+ clearTimeout(exitTimer.current);
37595
+ }
37596
+ exitTimer.current = setTimeout(() => setExitArmed(false), 1500);
37597
+ }
37598
+ function handleInterrupt() {
37599
+ const now = Date.now();
37600
+ if (now - lastInterruptAt.current < 75) {
37601
+ return;
37602
+ }
37603
+ lastInterruptAt.current = now;
37604
+ if (screen === "serve") {
37605
+ return;
37606
+ }
37607
+ armExit();
37608
+ }
37609
+ useEffect6(() => () => {
37610
+ if (exitTimer.current) {
37611
+ clearTimeout(exitTimer.current);
37612
+ }
37613
+ }, []);
37614
+ useEffect6(() => {
37615
+ const onData = (chunk) => {
37616
+ if (isCtrlCInput(String(chunk || ""))) {
37617
+ handleInterrupt();
37618
+ }
37619
+ };
37620
+ input?.on?.("data", onData);
37621
+ process.on("SIGINT", handleInterrupt);
37622
+ return () => {
37623
+ input?.off?.("data", onData);
37624
+ process.off("SIGINT", handleInterrupt);
37625
+ };
37626
+ }, [input, screen, exitArmed]);
37627
+ useInput2((typedInput, key) => {
37628
+ if (isCtrlCInput(typedInput, key)) {
37629
+ handleInterrupt();
37308
37630
  }
37309
37631
  });
37310
37632
  const goHome = () => setScreen("home");
37633
+ const breadcrumb = screen !== "home" ? ["Home", SCREEN_TITLES[screen] || screen] : ["Home"];
37311
37634
  return h(
37312
37635
  Box2,
37313
37636
  { flexDirection: "column" },
37314
- h(Header, { env: env3, platform: platform2, serveState }),
37637
+ h(Header, { env: env3, platform: platform2, serveState, screen }),
37638
+ h(
37639
+ Text2,
37640
+ { color: colors.muted },
37641
+ breadcrumb.map(
37642
+ (part, i) => i < breadcrumb.length - 1 ? `${part} ${fig.arrowRight} ` : part
37643
+ ).join("")
37644
+ ),
37645
+ h(Text2, null, ""),
37315
37646
  screen === "home" && h(HomeScreen, { onSelect: (value) => value === "exit" ? app.exit() : setScreen(value) }),
37316
37647
  screen === "connect" && h(ConnectScreen, { env: env3, platform: platform2, onBack: goHome }),
37317
37648
  screen === "init" && h(InitScreen, { env: env3, platform: platform2, onBack: goHome }),
37318
37649
  screen === "create" && h(CreateScreen, { env: env3, platform: platform2, onBack: goHome }),
37319
- screen === "serve" && h(ServeScreen, { env: env3, platform: platform2, input, output, onBack: goHome, onState: setServeState }),
37650
+ screen === "serve" && h(ServeScreen, { env: env3, platform: platform2, input, output, onBack: goHome, onState: setServeState, onExitApp: app.exit }),
37320
37651
  screen === "doctor" && h(DoctorScreen, { env: env3, platform: platform2, onBack: goHome }),
37321
37652
  screen === "update" && h(UpdateScreen, { env: env3, platform: platform2, onBack: goHome }),
37322
- screen === "commands" && h(CommandsScreen, { onBack: goHome })
37653
+ screen === "commands" && h(CommandsScreen, { onBack: goHome }),
37654
+ exitArmed && screen !== "serve" && h(Text2, { color: colors.warning, bold: true }, " Press Ctrl+C again to exit.")
37323
37655
  );
37324
37656
  }
37325
- function Header({ env: env3, platform: platform2, serveState }) {
37657
+ function Header({ env: env3, platform: platform2, serveState, screen }) {
37326
37658
  const active = activeCloudAuthStatus({ env: env3, platform: platform2 });
37327
37659
  const runtime = runtimeSummary({ env: env3, platform: platform2 });
37328
37660
  const [npmUpdate, setNpmUpdate] = useState5(() => npmPackageUpdateSummary({ config: loadConfig(env3, platform2) }));
37329
37661
  const [npmCheckStarted, setNpmCheckStarted] = useState5(false);
37330
37662
  const pkg = packageAccessSummary({ env: env3, platform: platform2 });
37331
- const daemon = serveState?.daemonId ? `Daemon ${serveState.daemonId} | ${serveState.activity}` : "Daemon idle";
37332
37663
  useEffect6(() => {
37333
37664
  let cancelled = false;
37334
37665
  if (!npmUpdate.stale || npmCheckStarted) {
@@ -37344,29 +37675,111 @@ function Header({ env: env3, platform: platform2, serveState }) {
37344
37675
  cancelled = true;
37345
37676
  };
37346
37677
  }, [env3, platform2, npmCheckStarted, npmUpdate.stale]);
37678
+ const connStatus = active.authenticated ? "ok" : "error";
37679
+ const connValue = active.authenticated ? `${active.email || "Authenticated"} ${fig.dash} ${active.apiUrl}` : "Not connected \u2014 run vendian login";
37680
+ const rtStatus = runtime.installed ? "ok" : "error";
37681
+ const rtValue = runtime.installed ? "Ready" : "Missing \u2014 run vendian login";
37682
+ const pkgStatus = pkg.configured ? "ok" : "warning";
37683
+ const pkgValue = pkg.configured ? `Configured (${pkg.source})` : "Missing";
37684
+ const updValue = updateLabel({ runtime, npmUpdate });
37685
+ const updStatus = updValue.includes("available") ? "warning" : "ok";
37686
+ const w = Math.min(process.stdout.columns || 80, 90);
37687
+ const inner = w - 2;
37688
+ const titleLeft = `${fig.vertical} ${fig.arrowUp} VENDIAN`;
37689
+ const titleRight = `v${CLI_VERSION} ${fig.vertical}`;
37690
+ const titlePad = Math.max(0, inner - titleLeft.length - titleRight.length + 2);
37347
37691
  return h(
37348
37692
  Box2,
37349
37693
  { flexDirection: "column", marginBottom: 1 },
37350
- h(Text2, { bold: true, color: "cyan" }, `VENDIAN CLI ${CLI_VERSION}`),
37351
- h(Text2, null, `Endpoint: ${active.authenticated ? `${active.apiUrl}${active.email ? ` (${active.email})` : ""}` : "not connected"}`),
37352
- h(Text2, null, `Runtime: ${runtime.installed ? "ready" : "missing"} | Package access: ${pkg.configured ? `configured (${pkg.source})` : "missing"} | Update: ${updateLabel({ runtime, npmUpdate })}`),
37353
- h(Text2, null, daemon)
37694
+ h(Text2, { color: colors.muted }, `${fig.topLeft}${fig.horizontal.repeat(inner)}${fig.topRight}`),
37695
+ h(
37696
+ Text2,
37697
+ null,
37698
+ h(Text2, { color: colors.muted }, fig.vertical),
37699
+ h(Text2, { bold: true, color: colors.brand }, ` ${fig.arrowUp} VENDIAN`),
37700
+ h(Text2, null, " ".repeat(titlePad)),
37701
+ h(Text2, { color: colors.muted }, `v${CLI_VERSION} `),
37702
+ h(Text2, { color: colors.muted }, fig.vertical)
37703
+ ),
37704
+ h(Text2, { color: colors.muted }, `${fig.teeRight}${fig.horizontal.repeat(inner)}${fig.teeLeft}`),
37705
+ h(StatusRow, { status: connStatus, label: "Endpoint", value: connValue, width: inner }),
37706
+ h(StatusRow, { status: rtStatus, label: "Runtime", value: rtValue, width: inner }),
37707
+ h(StatusRow, { status: pkgStatus, label: "Packages", value: pkgValue, width: inner }),
37708
+ h(StatusRow, { status: updStatus, label: "Updates", value: updValue, width: inner }),
37709
+ serveState?.daemonId && h(StatusRow, {
37710
+ status: serveState.connected ? "ok" : "warning",
37711
+ label: "Daemon",
37712
+ value: serveState.activity || "Running",
37713
+ width: inner
37714
+ }),
37715
+ h(Text2, { color: colors.muted }, `${fig.bottomLeft}${fig.horizontal.repeat(inner)}${fig.bottomRight}`)
37716
+ );
37717
+ }
37718
+ function StatusRow({ status, label, value, width }) {
37719
+ const dotColor = status === "ok" ? colors.success : status === "warning" ? colors.warning : colors.error;
37720
+ const dot = status === "ok" ? fig.dot : status === "warning" ? fig.dot : fig.dot;
37721
+ const paddedLabel = String(label).padEnd(spacing.labelWidth);
37722
+ const maxVal = Math.max(0, (width || 70) - spacing.labelWidth - 6);
37723
+ const clipped = clip(value || "", maxVal);
37724
+ return h(
37725
+ Text2,
37726
+ null,
37727
+ h(Text2, { color: colors.muted }, `${fig.vertical} `),
37728
+ h(Text2, { color: dotColor }, `${dot} `),
37729
+ h(Text2, { bold: true }, paddedLabel),
37730
+ h(Text2, null, clipped),
37731
+ h(Text2, { color: colors.muted }, ` ${fig.vertical}`)
37354
37732
  );
37355
37733
  }
37356
37734
  function HomeScreen({ onSelect }) {
37735
+ const [activeIndex, setActiveIndex] = useState5(0);
37736
+ useInput2((input, key) => {
37737
+ if (key.upArrow) {
37738
+ setActiveIndex((i) => (i - 1 + ACTIONS.length) % ACTIONS.length);
37739
+ } else if (key.downArrow) {
37740
+ setActiveIndex((i) => (i + 1) % ACTIONS.length);
37741
+ } else if (key.return) {
37742
+ onSelect(ACTIONS[activeIndex].value);
37743
+ }
37744
+ });
37357
37745
  return h(
37358
37746
  Box2,
37359
37747
  { flexDirection: "column" },
37360
- h(Text2, { bold: true }, "Home"),
37361
- h(SelectInput2, { items: ACTIONS, onSelect: (item) => onSelect(item.value) })
37748
+ ...ACTIONS.map((action, i) => {
37749
+ const isActive = i === activeIndex;
37750
+ const isSeparator = action.value === "doctor";
37751
+ return h(
37752
+ Box2,
37753
+ { key: action.value, flexDirection: "column" },
37754
+ isSeparator && h(Text2, { color: colors.muted }, ` ${fig.horizontal.repeat(40)}`),
37755
+ h(
37756
+ Text2,
37757
+ null,
37758
+ h(Text2, { color: isActive ? colors.accent : colors.muted }, isActive ? ` ${fig.arrow} ` : " "),
37759
+ h(Text2, { bold: isActive, color: isActive ? colors.text : colors.muted }, action.label.padEnd(22)),
37760
+ h(Text2, { color: colors.dim }, action.desc)
37761
+ )
37762
+ );
37763
+ }),
37764
+ h(Text2, null, ""),
37765
+ h(FooterBar, { items: [
37766
+ { key: "\u2191\u2193", label: "Navigate" },
37767
+ { key: "\u23CE", label: "Select" },
37768
+ { key: "^c ^c", label: "Exit" }
37769
+ ] })
37362
37770
  );
37363
37771
  }
37364
37772
  function ConnectScreen({ env: env3, platform: platform2, onBack }) {
37365
37773
  const [mode, setMode] = useState5("select");
37366
37774
  const [apiUrl, setApiUrl] = useState5("");
37367
37775
  const [status, setStatus] = useState5("");
37776
+ const rows = endpointRows({ env: env3, platform: platform2 });
37368
37777
  const items = [
37369
- ...ENDPOINTS.map((endpoint) => ({ label: `${endpoint.label} ${BACKEND_TARGETS[endpoint.key]}`, value: endpoint.key })),
37778
+ ...ENDPOINTS.map((endpoint) => {
37779
+ const row = rows.find((r) => r.key === endpoint.key);
37780
+ const statusLabel = row ? row.active ? "\u25CF Connected" : row.status : "";
37781
+ return { label: `${endpoint.label.padEnd(12)} ${BACKEND_TARGETS[endpoint.key]} ${statusLabel}`, value: endpoint.key };
37782
+ }),
37370
37783
  { label: "Custom API URL", value: "custom" },
37371
37784
  { label: "Back", value: "back" }
37372
37785
  ];
@@ -37379,41 +37792,47 @@ function ConnectScreen({ env: env3, platform: platform2, onBack }) {
37379
37792
  setMode("custom");
37380
37793
  return;
37381
37794
  }
37382
- setStatus(`Connecting ${value}`);
37795
+ setStatus(`Connecting to ${value}...`);
37383
37796
  await setup({ backend: value, forceAuth: true, env: env3, platform: platform2 });
37384
- setStatus("Connected");
37797
+ setStatus(`${fig.check} Connected`);
37385
37798
  }
37386
37799
  if (mode === "custom") {
37387
37800
  return h(
37388
37801
  Box2,
37389
37802
  { flexDirection: "column" },
37390
- h(Text2, { bold: true }, "Connect Endpoint"),
37803
+ h(Text2, { bold: true }, " Enter custom API URL:"),
37804
+ h(Text2, null, ""),
37391
37805
  h(TextInput2, {
37392
37806
  value: apiUrl,
37393
37807
  placeholder: "https://api.example.test",
37394
37808
  onChange: setApiUrl,
37395
37809
  onSubmit: async (value) => {
37396
37810
  if (!value) return;
37397
- setStatus("Connecting custom endpoint");
37811
+ setStatus("Connecting...");
37398
37812
  await setup({ apiUrl: value, forceAuth: true, env: env3, platform: platform2 });
37399
- setStatus("Connected");
37813
+ setStatus(`${fig.check} Connected`);
37400
37814
  }
37401
37815
  }),
37402
- h(Text2, null, status),
37816
+ status && h(Text2, { color: status.includes(fig.check) ? colors.success : colors.muted }, ` ${status}`),
37817
+ h(Text2, null, ""),
37818
+ h(FooterBar, { items: [{ key: "\u23CE", label: "Submit" }, { key: "esc", label: "Back" }] }),
37403
37819
  h(BackHint, { onBack })
37404
37820
  );
37405
37821
  }
37406
37822
  return h(
37407
37823
  Box2,
37408
37824
  { flexDirection: "column" },
37409
- h(Text2, { bold: true }, "Connect Endpoint"),
37825
+ h(Text2, { bold: true }, " Select environment:"),
37826
+ h(Text2, null, ""),
37410
37827
  h(SelectInput2, { items, onSelect: (item) => connect(item.value) }),
37411
- status && h(Text2, null, status)
37828
+ status && h(Text2, { color: status.includes(fig.check) ? colors.success : colors.muted }, ` ${status}`),
37829
+ h(Text2, null, ""),
37830
+ h(FooterBar, { items: [{ key: "\u2191\u2193", label: "Navigate" }, { key: "\u23CE", label: "Select" }, { key: "esc", label: "Back" }] })
37412
37831
  );
37413
37832
  }
37414
37833
  function InitScreen({ env: env3, platform: platform2, onBack }) {
37415
37834
  return h(CommandPromptScreen, {
37416
- title: "Initialize Docs",
37835
+ title: " Initialize Docs",
37417
37836
  label: "Workspace directory",
37418
37837
  initialValue: ".",
37419
37838
  onBack,
@@ -37453,53 +37872,165 @@ function CreateScreen({ env: env3, platform: platform2, onBack }) {
37453
37872
  h(BackHint, { onBack })
37454
37873
  );
37455
37874
  }
37456
- function ServeScreen({ env: env3, platform: platform2, onBack, onState }) {
37875
+ function ServeScreen({ env: env3, platform: platform2, input, onBack, onState, onExitApp }) {
37457
37876
  const [agentDirCandidates] = useState5(() => findAgentDirectoryCandidates());
37458
37877
  const [agentsDir, setAgentsDir] = useState5(() => defaultAgentsDir(agentDirCandidates));
37459
37878
  const [selectingDir, setSelectingDir] = useState5(() => agentDirCandidates.length > 0);
37879
+ const [workspaceChoices, setWorkspaceChoices] = useState5([]);
37880
+ const [pendingAgentsDir, setPendingAgentsDir] = useState5("");
37881
+ const [loadingWorkspaces, setLoadingWorkspaces] = useState5(false);
37460
37882
  const [started, setStarted] = useState5(false);
37461
37883
  const [state, setState] = useState5(initialServeState());
37462
37884
  const [child, setChild] = useState5(null);
37463
- const [showLogs, setShowLogs] = useState5(false);
37885
+ const [logMode, setLogMode] = useState5(null);
37464
37886
  const [confirmExit, setConfirmExit] = useState5(false);
37887
+ const [exitArmed, setExitArmed] = useState5(false);
37465
37888
  const [startupError, setStartupError] = useState5("");
37889
+ const exitTimer = useRef2(null);
37890
+ const lastInterruptAt = useRef2(0);
37466
37891
  useEffect6(() => {
37467
37892
  onState(state);
37468
37893
  }, [state, onState]);
37469
- useInput2((input, key) => {
37894
+ useEffect6(() => () => {
37895
+ if (exitTimer.current) {
37896
+ clearTimeout(exitTimer.current);
37897
+ }
37898
+ }, []);
37899
+ function armExit() {
37900
+ if (exitArmed) {
37901
+ child?.kill("SIGINT");
37902
+ onState(null);
37903
+ onExitApp();
37904
+ return;
37905
+ }
37906
+ setExitArmed(true);
37907
+ setConfirmExit(false);
37908
+ if (exitTimer.current) {
37909
+ clearTimeout(exitTimer.current);
37910
+ }
37911
+ exitTimer.current = setTimeout(() => setExitArmed(false), 1500);
37912
+ }
37913
+ function handleInterrupt() {
37914
+ const now = Date.now();
37915
+ if (now - lastInterruptAt.current < 75) {
37916
+ return;
37917
+ }
37918
+ lastInterruptAt.current = now;
37919
+ armExit();
37920
+ }
37921
+ useEffect6(() => {
37922
+ const onData = (chunk) => {
37923
+ if (isCtrlCInput(String(chunk || ""))) {
37924
+ handleInterrupt();
37925
+ }
37926
+ };
37927
+ input?.on?.("data", onData);
37928
+ process.on("SIGINT", handleInterrupt);
37929
+ return () => {
37930
+ input?.off?.("data", onData);
37931
+ process.off("SIGINT", handleInterrupt);
37932
+ };
37933
+ }, [input, exitArmed, child]);
37934
+ useInput2((input2, key) => {
37935
+ if (isCtrlCInput(input2, key)) {
37936
+ handleInterrupt();
37937
+ return;
37938
+ }
37470
37939
  if (!started) return;
37940
+ if (logMode !== null) {
37941
+ if (key.escape) {
37942
+ if (logMode === "picker") {
37943
+ setLogMode(null);
37944
+ } else {
37945
+ setLogMode("picker");
37946
+ }
37947
+ }
37948
+ return;
37949
+ }
37471
37950
  if (confirmExit) {
37472
- if (input.toLowerCase() === "y" || key.return) {
37951
+ if (input2.toLowerCase() === "y" || key.return) {
37473
37952
  child?.kill("SIGINT");
37474
37953
  onState(null);
37475
37954
  onBack();
37476
- } else if (input.toLowerCase() === "n" || key.escape) {
37955
+ } else if (input2.toLowerCase() === "n" || key.escape) {
37477
37956
  setConfirmExit(false);
37478
37957
  }
37479
37958
  return;
37480
37959
  }
37481
- if (input === "l") {
37482
- setShowLogs((value) => !value);
37483
- } else if (input === "q" || key.ctrl && key.name === "c") {
37960
+ if (input2 === "l") {
37961
+ setLogMode("picker");
37962
+ } else if (input2 === "q") {
37484
37963
  setConfirmExit(true);
37485
37964
  }
37486
37965
  });
37487
- async function start(selectedAgentsDir = agentsDir) {
37966
+ async function start(selectedAgentsDir = agentsDir, collectionId = "") {
37488
37967
  const serveRoot = selectedAgentsDir || "./agents";
37489
37968
  setAgentsDir(serveRoot);
37490
37969
  setStarted(true);
37491
37970
  setStartupError("");
37492
37971
  try {
37493
- const nextChild = await spawnLocalServeEventStream({ agentsDir: serveRoot, env: env3, platform: platform2 });
37972
+ const nextChild = await spawnLocalServeEventStream({ agentsDir: serveRoot, collectionId, env: env3, platform: platform2 });
37494
37973
  setChild(nextChild);
37495
- attachServeChild(nextChild, setState, setStartupError, () => {
37496
- setState((current) => current.stopped ? current : { ...current, stopped: true, activity: "Process exited" });
37974
+ attachServeChild(nextChild, setState, setStartupError, ({ message } = {}) => {
37975
+ setState((current) => current.stopped ? current : { ...current, stopped: true, activity: message ? "Runtime update required" : "Process exited" });
37497
37976
  });
37498
37977
  } catch (error) {
37499
37978
  setStartupError(errorMessage2(error));
37500
37979
  }
37501
37980
  }
37981
+ async function chooseWorkspaceThenStart(selectedAgentsDir = agentsDir) {
37982
+ const serveRoot = selectedAgentsDir || "./agents";
37983
+ setAgentsDir(serveRoot);
37984
+ setPendingAgentsDir(serveRoot);
37985
+ setStartupError("");
37986
+ setLoadingWorkspaces(true);
37987
+ try {
37988
+ const result = await listCloudWorkspaces({ env: env3, platform: platform2 });
37989
+ if (!result.ok) {
37990
+ setStartupError(result.error || "Could not list workspaces.");
37991
+ return;
37992
+ }
37993
+ if (result.workspaces.length > 1) {
37994
+ setWorkspaceChoices(result.workspaces);
37995
+ return;
37996
+ }
37997
+ await start(serveRoot, result.workspaces[0]?.id || "");
37998
+ } catch (error) {
37999
+ setStartupError(errorMessage2(error));
38000
+ } finally {
38001
+ setLoadingWorkspaces(false);
38002
+ }
38003
+ }
37502
38004
  if (!started) {
38005
+ if (workspaceChoices.length > 1) {
38006
+ return h(
38007
+ Box2,
38008
+ { flexDirection: "column" },
38009
+ h(Text2, { bold: true }, "Select workspace:"),
38010
+ h(SelectInput2, {
38011
+ items: [
38012
+ ...workspaceChoices.map((workspace) => ({
38013
+ label: workspaceLabel(workspace),
38014
+ value: workspace.id
38015
+ })),
38016
+ { label: "Back", value: "__back__" }
38017
+ ],
38018
+ onSelect: (item) => {
38019
+ if (item.value === "__back__") {
38020
+ setWorkspaceChoices([]);
38021
+ return;
38022
+ }
38023
+ start(pendingAgentsDir || agentsDir, item.value);
38024
+ }
38025
+ }),
38026
+ h(Text2, null, ""),
38027
+ h(FooterBar, { items: [
38028
+ { key: "\u2191\u2193", label: "Navigate" },
38029
+ { key: "\u23CE", label: "Select" },
38030
+ { key: "esc", label: "Back" }
38031
+ ] })
38032
+ );
38033
+ }
37503
38034
  const items = [
37504
38035
  ...agentDirCandidates.map((candidate) => ({
37505
38036
  label: `${candidate.path} (${candidate.agentCount} ${candidate.agentCount === 1 ? "agent" : "agents"})`,
@@ -37511,7 +38042,7 @@ function ServeScreen({ env: env3, platform: platform2, onBack, onState }) {
37511
38042
  return h(
37512
38043
  Box2,
37513
38044
  { flexDirection: "column" },
37514
- h(Text2, { bold: true }, "Serve Agents"),
38045
+ h(Text2, { bold: true }, "Select agent directory:"),
37515
38046
  selectingDir && h(SelectInput2, {
37516
38047
  items,
37517
38048
  onSelect: (item) => {
@@ -37525,77 +38056,378 @@ function ServeScreen({ env: env3, platform: platform2, onBack, onState }) {
37525
38056
  return;
37526
38057
  }
37527
38058
  setAgentsDir(item.value);
37528
- start(item.value);
38059
+ chooseWorkspaceThenStart(item.value);
37529
38060
  }
37530
38061
  }),
37531
38062
  !selectingDir && h(TextInput2, {
37532
38063
  value: agentsDir,
37533
38064
  placeholder: "./agents",
37534
38065
  onChange: setAgentsDir,
37535
- onSubmit: (value) => start(value)
38066
+ onSubmit: (value) => chooseWorkspaceThenStart(value)
37536
38067
  }),
37537
- h(BackHint, { onBack })
38068
+ loadingWorkspaces && h(
38069
+ Box2,
38070
+ null,
38071
+ h(Text2, { color: colors.brand }, " "),
38072
+ h(Spinner2, { type: "dots" }),
38073
+ h(Text2, { color: colors.brand }, " Loading workspaces")
38074
+ ),
38075
+ startupError && h(Text2, { color: colors.error }, ` ${fig.cross} ${startupError}`),
38076
+ h(Text2, null, ""),
38077
+ h(FooterBar, { items: [
38078
+ { key: "\u2191\u2193", label: "Navigate" },
38079
+ { key: "\u23CE", label: "Select" },
38080
+ { key: "esc", label: "Back" }
38081
+ ] })
37538
38082
  );
37539
38083
  }
37540
38084
  const summary = agentSummary(state.agents);
38085
+ if (logMode !== null) {
38086
+ return h(
38087
+ Box2,
38088
+ { flexDirection: "column" },
38089
+ h(AgentLogViewer, {
38090
+ agents: state.agents,
38091
+ agentLogs: state.agentLogs,
38092
+ logMode,
38093
+ onSelectAgent: (path7) => setLogMode(path7),
38094
+ onBack: () => {
38095
+ if (logMode === "picker") {
38096
+ setLogMode(null);
38097
+ } else {
38098
+ setLogMode("picker");
38099
+ }
38100
+ }
38101
+ })
38102
+ );
38103
+ }
37541
38104
  return h(
37542
38105
  Box2,
37543
38106
  { flexDirection: "column" },
37544
- h(Text2, { bold: true }, "Serve Agents"),
37545
- startupError ? h(Text2, { color: "red" }, startupError) : h(Box2, null, h(Spinner2, { type: "dots" }), h(Text2, null, ` ${state.activity}`)),
37546
- h(Text2, null, `Agents: ${summary.total} total | ${summary.ready} ready | ${summary.needsSetup} needs setup | ${summary.errors} errors`),
38107
+ startupError ? h(Text2, { color: colors.error }, ` ${fig.cross} ${startupError}`) : h(
38108
+ Box2,
38109
+ null,
38110
+ h(Text2, { color: colors.brand }, " "),
38111
+ h(Spinner2, { type: "dots" }),
38112
+ h(Text2, { color: colors.brand }, ` ${state.activity}`)
38113
+ ),
38114
+ h(Text2, null, ""),
38115
+ h(
38116
+ Text2,
38117
+ null,
38118
+ h(Text2, { color: colors.muted }, " Agents: "),
38119
+ h(Text2, { color: colors.success, bold: true }, `${summary.ready} ready`),
38120
+ summary.needsSetup > 0 && h(Text2, { color: colors.warning }, ` ${fig.dot} ${summary.needsSetup} needs setup`),
38121
+ summary.errors > 0 && h(Text2, { color: colors.error }, ` ${fig.dot} ${summary.errors} errors`),
38122
+ h(Text2, { color: colors.muted }, ` ${fig.dot} ${summary.total} total`)
38123
+ ),
38124
+ h(Text2, null, ""),
38125
+ state.newAgents?.length > 0 && h(
38126
+ Text2,
38127
+ { color: colors.success },
38128
+ ` ${fig.check} New agents synced automatically: ${state.newAgents.map((agent) => agent.relativePath || agent.manifestName || agent.path || "agent").join(", ")}`
38129
+ ),
37547
38130
  h(AgentTable, { agents: state.agents }),
37548
- state.retry && h(Text2, { color: "yellow" }, `Retry: ${state.retry.activity} in ${Number(state.retry.delaySeconds || 0).toFixed(1)}s`),
37549
- state.errors.slice(-3).map((item, index) => h(Text2, { key: `error-${index}`, color: "red" }, `${item.scope}: ${item.message}`)),
37550
- showLogs && h(LogPane, { logs: state.logs }),
37551
- confirmExit && h(Text2, { color: "yellow" }, "Stop local serve? y/Enter confirms, n/Esc cancels."),
37552
- h(Text2, { dimColor: true }, "l toggles details | q exits serve")
38131
+ state.retry && h(Text2, { color: colors.warning }, ` ${fig.warning} Retry: ${state.retry.activity} in ${Number(state.retry.delaySeconds || 0).toFixed(1)}s`),
38132
+ state.errors.length > 0 && h(
38133
+ Box2,
38134
+ { flexDirection: "column", marginTop: 1 },
38135
+ ...state.errors.slice(-3).map((item, index) => h(Text2, { key: `error-${index}`, color: colors.error }, ` ${fig.cross} ${item.scope}: ${item.message}`))
38136
+ ),
38137
+ h(Text2, null, ""),
38138
+ confirmExit ? h(
38139
+ Text2,
38140
+ { color: colors.warning, bold: true },
38141
+ " Stop local serve? ",
38142
+ h(Text2, { color: colors.accent }, "y"),
38143
+ h(Text2, { color: colors.muted }, "/"),
38144
+ h(Text2, { color: colors.accent }, "n")
38145
+ ) : h(FooterBar, { items: [
38146
+ { key: "l", label: "Agent logs" },
38147
+ { key: "q", label: "Stop" },
38148
+ { key: "^c ^c", label: "Exit" }
38149
+ ] }),
38150
+ exitArmed && h(Text2, { color: colors.warning, bold: true }, " Press Ctrl+C again to exit.")
37553
38151
  );
37554
38152
  }
37555
38153
  function AgentTable({ agents }) {
37556
38154
  if (!agents.length) {
37557
- return h(Text2, { dimColor: true }, "No agents discovered yet.");
38155
+ return h(Text2, { color: colors.muted }, ` ${fig.dotEmpty} No agents discovered yet.`);
38156
+ }
38157
+ const termWidth = Math.min(process.stdout.columns || 80, 100);
38158
+ const pathW = Math.min(22, Math.floor(termWidth * 0.25));
38159
+ const nameW = Math.min(28, Math.floor(termWidth * 0.32));
38160
+ const credW = 10;
38161
+ const statusW = 10;
38162
+ const headerLine = ` ${"Path".padEnd(pathW)} ${"Name".padEnd(nameW)} ${"Creds".padEnd(credW)} ${"Status".padEnd(statusW)}`;
38163
+ const separator = ` ${fig.horizontal.repeat(pathW + nameW + credW + statusW + 3)}`;
38164
+ const rows = agents.slice(0, 16).map((agent) => {
38165
+ const path7 = clip(agent.relativePath || ".", pathW);
38166
+ const name = clip(agent.manifestName || agent.manifest?.name || "-", nameW);
38167
+ const creds = credentialLabel(agent);
38168
+ const status = String(agent.status || "-");
38169
+ const statusColor = status === "online" ? colors.success : status === "error" ? colors.error : colors.warning;
38170
+ const statusIcon = status === "online" ? fig.dot : status === "error" ? fig.cross : fig.dotEmpty;
38171
+ return h(
38172
+ Text2,
38173
+ { key: agent.localAgentId || agent.relativePath },
38174
+ h(Text2, { color: colors.muted }, " "),
38175
+ h(Text2, null, path7.padEnd(pathW)),
38176
+ h(Text2, null, " "),
38177
+ h(Text2, null, name.padEnd(nameW)),
38178
+ h(Text2, null, " "),
38179
+ h(Text2, { color: colors.muted }, creds.padEnd(credW)),
38180
+ h(Text2, null, " "),
38181
+ h(Text2, { color: statusColor }, `${statusIcon} ${status}`)
38182
+ );
38183
+ });
38184
+ const hiddenCount = agents.length - 16;
38185
+ return h(
38186
+ Box2,
38187
+ { flexDirection: "column" },
38188
+ h(Text2, { color: colors.muted, bold: true }, headerLine),
38189
+ h(Text2, { color: colors.muted }, separator),
38190
+ ...rows,
38191
+ hiddenCount > 0 && h(Text2, { color: colors.muted }, ` ${fig.ellipsis} and ${hiddenCount} more`)
38192
+ );
38193
+ }
38194
+ function FooterBar({ items }) {
38195
+ if (!items || !items.length) return null;
38196
+ return h(
38197
+ Text2,
38198
+ { color: colors.muted },
38199
+ " ",
38200
+ ...items.map((item, i) => [
38201
+ h(Text2, { key: `k-${i}`, color: colors.accent, bold: true }, item.key),
38202
+ h(Text2, { key: `l-${i}`, color: colors.muted }, ` ${item.label}`),
38203
+ i < items.length - 1 ? h(Text2, { key: `s-${i}` }, " ") : null
38204
+ ]).flat().filter(Boolean)
38205
+ );
38206
+ }
38207
+ function AgentLogViewer({ agents, agentLogs, logMode, onSelectAgent, onBack }) {
38208
+ if (logMode === "picker") {
38209
+ return h(AgentLogPicker, { agents, agentLogs, onSelectAgent, onBack });
38210
+ }
38211
+ return h(AgentLogDetail, { relativePath: logMode, agentLogs, agents, onBack });
38212
+ }
38213
+ function AgentLogPicker({ agents, agentLogs, onSelectAgent, onBack }) {
38214
+ const [selectedIndex, setSelectedIndex] = useState5(0);
38215
+ const agentList = agents.map((agent) => {
38216
+ const path7 = agent.relativePath || ".";
38217
+ const logs = agentLogEntries(agentLogs, path7);
38218
+ const lastLog = logs.length > 0 ? logs[logs.length - 1] : null;
38219
+ const hasErrors = logs.some((l) => l.level === "error" || l.eventType === "completion" && l.success === false);
38220
+ return {
38221
+ path: path7,
38222
+ name: agent.manifestName || agent.manifest?.name || path7,
38223
+ logCount: logs.length,
38224
+ lastLog,
38225
+ hasErrors,
38226
+ status: agent.status || "-"
38227
+ };
38228
+ });
38229
+ const inventoryPaths = new Set(agentList.map((a) => a.path));
38230
+ for (const path7 of Object.keys(agentLogs || {})) {
38231
+ if (!inventoryPaths.has(path7)) {
38232
+ const logs = agentLogEntries(agentLogs, path7);
38233
+ agentList.push({
38234
+ path: path7,
38235
+ name: path7,
38236
+ logCount: logs.length,
38237
+ lastLog: logs.length > 0 ? logs[logs.length - 1] : null,
38238
+ hasErrors: logs.some((l) => l.level === "error"),
38239
+ status: "offline"
38240
+ });
38241
+ }
37558
38242
  }
38243
+ useInput2((input, key) => {
38244
+ if (key.upArrow) {
38245
+ setSelectedIndex((i) => (i - 1 + agentList.length) % agentList.length);
38246
+ } else if (key.downArrow) {
38247
+ setSelectedIndex((i) => (i + 1) % agentList.length);
38248
+ } else if (key.return) {
38249
+ if (agentList.length > 0) {
38250
+ onSelectAgent(agentList[selectedIndex].path);
38251
+ }
38252
+ } else if (key.escape) {
38253
+ onBack();
38254
+ }
38255
+ });
38256
+ if (agentList.length === 0) {
38257
+ return h(
38258
+ Box2,
38259
+ { flexDirection: "column" },
38260
+ h(Text2, { bold: true, color: colors.brand }, ` ${fig.horizontal.repeat(3)} Agent Logs`),
38261
+ h(Text2, null, ""),
38262
+ h(Text2, { color: colors.muted }, " No agent logs yet. Logs appear when agents run."),
38263
+ h(Text2, null, ""),
38264
+ h(FooterBar, { items: [{ key: "esc", label: "Back to serve" }] })
38265
+ );
38266
+ }
38267
+ const pathW = 24;
38268
+ const nameW = 26;
37559
38269
  return h(
37560
38270
  Box2,
37561
38271
  { flexDirection: "column" },
37562
- h(Text2, { dimColor: true }, "Path Name Credentials Status Error"),
37563
- ...agents.slice(0, 12).map((agent) => h(
37564
- Text2,
37565
- { key: agent.localAgentId || agent.relativePath },
37566
- `${clip(agent.relativePath || ".", 20).padEnd(20)} ${clip(agent.manifestName || agent.manifest?.name || "-", 28).padEnd(28)} ${credentialLabel(agent).padEnd(12)} ${String(agent.status || "-").padEnd(13)} ${clip(agent.errorMessage || "", 36)}`
37567
- ))
38272
+ h(Text2, { bold: true, color: colors.brand }, ` ${fig.horizontal.repeat(3)} Agent Logs ${fig.horizontal.repeat(20)}`),
38273
+ h(Text2, null, ""),
38274
+ h(Text2, { color: colors.muted }, ` ${"Agent".padEnd(nameW)} ${"Logs".padEnd(6)} ${"Last event"}`),
38275
+ h(Text2, { color: colors.muted }, ` ${fig.horizontal.repeat(nameW + 6 + 30)}`),
38276
+ ...agentList.map((agent, i) => {
38277
+ const isActive = i === selectedIndex;
38278
+ const logCountColor = agent.hasErrors ? colors.error : agent.logCount > 0 ? colors.success : colors.muted;
38279
+ const lastEvent = agent.lastLog ? formatAgentLogEntry(agent.lastLog) : "No logs";
38280
+ return h(
38281
+ Text2,
38282
+ { key: agent.path },
38283
+ h(Text2, { color: isActive ? colors.accent : colors.muted }, isActive ? ` ${fig.arrow} ` : " "),
38284
+ h(Text2, { bold: isActive }, clip(agent.name, nameW - 2).padEnd(nameW)),
38285
+ h(Text2, { color: logCountColor }, String(agent.logCount).padEnd(6)),
38286
+ h(Text2, { color: colors.muted }, clip(lastEvent, 36))
38287
+ );
38288
+ }),
38289
+ h(Text2, null, ""),
38290
+ h(FooterBar, { items: [
38291
+ { key: "\u2191\u2193", label: "Navigate" },
38292
+ { key: "\u23CE", label: "View logs" },
38293
+ { key: "esc", label: "Back to serve" }
38294
+ ] })
37568
38295
  );
37569
38296
  }
37570
- function LogPane({ logs }) {
38297
+ function AgentLogDetail({ relativePath, agentLogs, agents, onBack }) {
38298
+ const logs = agentLogEntries(agentLogs, relativePath);
38299
+ const agent = agents.find((a) => (a.relativePath || ".") === relativePath);
38300
+ const agentName = agent?.manifestName || agent?.manifest?.name || relativePath;
38301
+ const [scrollOffset, setScrollOffset] = useState5(0);
38302
+ const visibleCount = Math.min(20, Math.max(8, (process.stdout.rows || 24) - 10));
38303
+ useInput2((input, key) => {
38304
+ if (key.escape) {
38305
+ onBack();
38306
+ } else if (key.upArrow) {
38307
+ setScrollOffset((o) => Math.max(0, o - 1));
38308
+ } else if (key.downArrow) {
38309
+ setScrollOffset((o) => Math.min(Math.max(0, logs.length - visibleCount), o + 1));
38310
+ } else if (key.pageDown || input === " ") {
38311
+ setScrollOffset((o) => Math.min(Math.max(0, logs.length - visibleCount), o + visibleCount));
38312
+ } else if (key.pageUp) {
38313
+ setScrollOffset((o) => Math.max(0, o - visibleCount));
38314
+ }
38315
+ });
38316
+ const effectiveOffset = logs.length <= visibleCount ? 0 : scrollOffset >= logs.length - visibleCount - 2 ? Math.max(0, logs.length - visibleCount) : scrollOffset;
38317
+ const visibleLogs = logs.slice(effectiveOffset, effectiveOffset + visibleCount);
37571
38318
  return h(
37572
38319
  Box2,
37573
- { flexDirection: "column", marginTop: 1 },
37574
- h(Text2, { bold: true }, "Details"),
37575
- ...logs.slice(-8).map((event, index) => h(
37576
- Text2,
37577
- { key: `${event.type}-${index}`, dimColor: true },
37578
- `${event.type} ${JSON.stringify(event)}`
37579
- ))
38320
+ { flexDirection: "column" },
38321
+ h(Text2, { bold: true, color: colors.brand }, ` ${fig.horizontal.repeat(3)} ${agentName}`),
38322
+ h(Text2, { color: colors.muted }, ` ${relativePath} ${fig.dash} ${logs.length} log entries`),
38323
+ h(Text2, null, ""),
38324
+ logs.length === 0 ? h(Text2, { color: colors.muted }, " No logs recorded yet for this agent. Logs appear during runs.") : h(
38325
+ Box2,
38326
+ { flexDirection: "column" },
38327
+ ...visibleLogs.map((entry, i) => {
38328
+ const levelColor = entry.level === "error" ? colors.error : entry.level === "warning" ? colors.warning : entry.level === "debug" ? colors.dim : colors.muted;
38329
+ const timeStr = formatLogTime(entry.timestamp);
38330
+ const icon = entry.eventType === "completion" ? entry.success ? fig.check : fig.cross : entry.eventType === "step_event" ? fig.arrow : entry.eventType === "progress" ? fig.dotEmpty : entry.level === "error" ? fig.cross : fig.dash;
38331
+ const iconColor = entry.eventType === "completion" ? entry.success ? colors.success : colors.error : levelColor;
38332
+ return h(
38333
+ Text2,
38334
+ { key: `${effectiveOffset + i}` },
38335
+ h(Text2, { color: colors.dim }, ` ${timeStr} `),
38336
+ h(Text2, { color: iconColor }, `${icon} `),
38337
+ h(
38338
+ Text2,
38339
+ { color: entry.level === "error" ? colors.error : colors.text },
38340
+ clip(formatAgentLogEntry(entry), 60)
38341
+ )
38342
+ );
38343
+ }),
38344
+ logs.length > visibleCount && h(
38345
+ Text2,
38346
+ { color: colors.muted },
38347
+ ` ${fig.ellipsis} Showing ${effectiveOffset + 1}-${effectiveOffset + visibleLogs.length} of ${logs.length}`
38348
+ )
38349
+ ),
38350
+ h(Text2, null, ""),
38351
+ h(FooterBar, { items: [
38352
+ { key: "\u2191\u2193", label: "Scroll" },
38353
+ { key: "PgUp/Dn", label: "Page" },
38354
+ { key: "esc", label: "Back to agents" }
38355
+ ] })
37580
38356
  );
37581
38357
  }
38358
+ function formatAgentLogEntry(entry) {
38359
+ if (!entry) return "";
38360
+ const type = entry.eventType || "";
38361
+ switch (type) {
38362
+ case "log":
38363
+ return entry.message || "";
38364
+ case "progress": {
38365
+ const prog = entry.current != null && entry.total != null ? `${entry.current}/${entry.total}` : "";
38366
+ return `${prog}${entry.message ? ` ${entry.message}` : ""}`.trim() || "progress";
38367
+ }
38368
+ case "completion":
38369
+ if (entry.success) return "Completed successfully";
38370
+ if (entry.error && typeof entry.error === "object") return `Failed: ${entry.error.message || "error"}`;
38371
+ return `Failed: ${entry.error || "unknown error"}`;
38372
+ case "step_event":
38373
+ return `Step: ${entry.stepId || "unknown"}${entry.message ? ` - ${entry.message}` : ""}`;
38374
+ case "step_checkpoint":
38375
+ return `Checkpoint: ${entry.stepId || "unknown"}`;
38376
+ case "step_heartbeat":
38377
+ return `Heartbeat: ${entry.stepId || ""}${entry.message ? ` - ${entry.message}` : ""}`;
38378
+ case "artifact":
38379
+ return `Artifact: ${entry.message || "data"}`;
38380
+ default:
38381
+ return entry.message || type || "event";
38382
+ }
38383
+ }
38384
+ function formatLogTime(timestamp) {
38385
+ if (!timestamp) return " ";
38386
+ try {
38387
+ const d = new Date(timestamp);
38388
+ const h2 = String(d.getHours()).padStart(2, "0");
38389
+ const m = String(d.getMinutes()).padStart(2, "0");
38390
+ const s = String(d.getSeconds()).padStart(2, "0");
38391
+ return `${h2}:${m}:${s}`;
38392
+ } catch {
38393
+ return " ";
38394
+ }
38395
+ }
37582
38396
  function DoctorScreen({ env: env3, platform: platform2, onBack }) {
37583
38397
  const runtime = runtimeSummary({ env: env3, platform: platform2 });
37584
38398
  const pkg = packageAccessSummary({ env: env3, platform: platform2 });
38399
+ const checks = [
38400
+ { label: "Managed Python", passed: runtime.installed, value: runtime.installed ? "Present" : "Missing" },
38401
+ { label: "Managed Env", passed: runtime.installed, value: runtime.detail },
38402
+ { label: "Package Access", passed: pkg.configured, value: pkg.configured ? `Configured (${pkg.source})` : "Missing" }
38403
+ ];
38404
+ const allPassed = checks.every((c) => c.passed);
37585
38405
  return h(
37586
38406
  Box2,
37587
38407
  { flexDirection: "column" },
37588
- h(Text2, { bold: true }, "Doctor"),
37589
- h(Text2, null, `Managed Python: ${runtime.installed ? "present" : "missing"}`),
37590
- h(Text2, null, `Managed environment: ${runtime.detail}`),
37591
- h(Text2, null, `Package access: ${pkg.configured ? `configured (${pkg.source})` : "missing"}`),
38408
+ h(Text2, { bold: true }, " System Health"),
38409
+ h(Text2, null, ""),
38410
+ ...checks.map((check) => h(
38411
+ Text2,
38412
+ { key: check.label },
38413
+ h(Text2, { color: check.passed ? colors.success : colors.error }, ` ${check.passed ? fig.check : fig.cross} `),
38414
+ h(Text2, null, `${check.label.padEnd(18)}${check.value}`)
38415
+ )),
38416
+ h(Text2, null, ""),
38417
+ h(
38418
+ Text2,
38419
+ { color: allPassed ? colors.success : colors.warning, bold: true },
38420
+ allPassed ? ` ${fig.check} All checks passed` : ` ${fig.warning} Some checks need attention`
38421
+ ),
38422
+ h(Text2, null, ""),
37592
38423
  h(SelectInput2, {
37593
- items: [{ label: "Run plain doctor output", value: "run" }, { label: "Back", value: "back" }],
38424
+ items: [{ label: "Run full doctor output", value: "run" }, { label: "Back", value: "back" }],
37594
38425
  onSelect: async (item) => {
37595
38426
  if (item.value === "back") onBack();
37596
38427
  else doctor({ env: env3, platform: platform2 });
37597
38428
  }
37598
- })
38429
+ }),
38430
+ h(FooterBar, { items: [{ key: "\u23CE", label: "Select" }, { key: "esc", label: "Back" }] })
37599
38431
  );
37600
38432
  }
37601
38433
  function UpdateScreen({ env: env3, platform: platform2, onBack }) {
@@ -37603,29 +38435,65 @@ function UpdateScreen({ env: env3, platform: platform2, onBack }) {
37603
38435
  return h(
37604
38436
  Box2,
37605
38437
  { flexDirection: "column" },
37606
- h(Text2, { bold: true }, "Update"),
38438
+ h(Text2, { bold: true }, " Update Runtime"),
38439
+ h(Text2, null, ""),
37607
38440
  h(SelectInput2, {
37608
- items: [{ label: "Update managed runtime", value: "run" }, { label: "Back", value: "back" }],
38441
+ items: [{ label: "Update managed runtime & packages", value: "run" }, { label: "Back", value: "back" }],
37609
38442
  onSelect: async (item) => {
37610
38443
  if (item.value === "back") {
37611
38444
  onBack();
37612
38445
  return;
37613
38446
  }
37614
- setStatus("Updating managed runtime");
38447
+ setStatus(`${fig.dotEmpty} Updating managed runtime...`);
37615
38448
  await setup({ nonInteractive: true, env: env3, platform: platform2 });
37616
- setStatus("Update finished");
38449
+ setStatus(`${fig.check} Update finished`);
37617
38450
  }
37618
38451
  }),
37619
- status && h(Text2, null, status)
38452
+ status && h(Text2, { color: status.includes(fig.check) ? colors.success : colors.muted }, ` ${status}`),
38453
+ h(Text2, null, ""),
38454
+ h(FooterBar, { items: [{ key: "\u23CE", label: "Select" }, { key: "esc", label: "Back" }] }),
38455
+ h(BackHint, { onBack })
37620
38456
  );
37621
38457
  }
37622
38458
  function CommandsScreen({ onBack }) {
38459
+ const grouped = [
38460
+ { section: "Getting Started", commands: [
38461
+ { cmd: "vendian login", desc: "Sign in and prepare runtime" },
38462
+ { cmd: "vendian init --output-dir ./agents", desc: "Initialize docs workspace" }
38463
+ ] },
38464
+ { section: "Agent Development", commands: [
38465
+ { cmd: 'vendian create "My Agent" --output-dir .', desc: "Scaffold new agent" },
38466
+ { cmd: "vendian validate ./agents/my-agent --runtime", desc: "Validate manifest" },
38467
+ { cmd: "vendian test ./agents/my-agent --dry-run", desc: "Test locally" },
38468
+ { cmd: "vendian models", desc: "List available models" }
38469
+ ] },
38470
+ { section: "Cloud & Deploy", commands: [
38471
+ { cmd: "vendian cloud local serve --agents-dir .", desc: "Start cloud-connected serve" },
38472
+ { cmd: "vendian login --backend staging", desc: "Connect to staging" }
38473
+ ] },
38474
+ { section: "Maintenance", commands: [
38475
+ { cmd: "vendian doctor", desc: "Check system health" },
38476
+ { cmd: "vendian update", desc: "Update runtime & packages" }
38477
+ ] }
38478
+ ];
37623
38479
  return h(
37624
38480
  Box2,
37625
38481
  { flexDirection: "column" },
37626
- h(Text2, { bold: true }, "Commands"),
37627
- ...COMMANDS.map((command) => h(Text2, { key: command }, command)),
37628
- h(SelectInput2, { items: [{ label: "Back", value: "back" }], onSelect: onBack })
38482
+ h(Text2, { bold: true }, " Quick Reference"),
38483
+ h(Text2, null, ""),
38484
+ ...grouped.map((group) => [
38485
+ h(Text2, { key: `title-${group.section}`, color: colors.accent, bold: true }, ` ${group.section}`),
38486
+ ...group.commands.map((item) => h(
38487
+ Text2,
38488
+ { key: item.cmd },
38489
+ h(Text2, { color: colors.muted }, " "),
38490
+ h(Text2, { color: colors.brand }, item.cmd.padEnd(44)),
38491
+ h(Text2, { color: colors.muted }, item.desc)
38492
+ )),
38493
+ h(Text2, { key: `gap-${group.section}` }, "")
38494
+ ]).flat(),
38495
+ h(FooterBar, { items: [{ key: "esc", label: "Back" }] }),
38496
+ h(BackHint, { onBack })
37629
38497
  );
37630
38498
  }
37631
38499
  function CommandPromptScreen({ title, label, initialValue, onSubmit, onBack }) {
@@ -37635,17 +38503,20 @@ function CommandPromptScreen({ title, label, initialValue, onSubmit, onBack }) {
37635
38503
  Box2,
37636
38504
  { flexDirection: "column" },
37637
38505
  h(Text2, { bold: true }, title),
38506
+ h(Text2, null, ""),
37638
38507
  h(TextInput2, {
37639
38508
  value,
37640
38509
  placeholder: label,
37641
38510
  onChange: setValue,
37642
38511
  onSubmit: async (submitted) => {
37643
- setStatus("Running command");
38512
+ setStatus(`${fig.dotEmpty} Running...`);
37644
38513
  await onSubmit(submitted);
37645
- setStatus("Command finished");
38514
+ setStatus(`${fig.check} Done`);
37646
38515
  }
37647
38516
  }),
37648
- status && h(Text2, null, status),
38517
+ status && h(Text2, { color: status.includes(fig.check) ? colors.success : colors.muted }, ` ${status}`),
38518
+ h(Text2, null, ""),
38519
+ h(FooterBar, { items: [{ key: "\u23CE", label: "Submit" }, { key: "esc", label: "Back" }] }),
37649
38520
  h(BackHint, { onBack })
37650
38521
  );
37651
38522
  }
@@ -37653,10 +38524,11 @@ function BackHint({ onBack }) {
37653
38524
  useInput2((input, key) => {
37654
38525
  if (key.escape || input === "\x1B") onBack();
37655
38526
  });
37656
- return h(Text2, { dimColor: true }, "Esc returns home");
38527
+ return null;
37657
38528
  }
37658
38529
  function attachServeChild(child, setState, setStartupError, onExit) {
37659
38530
  let buffer = "";
38531
+ const stderrChunks = [];
37660
38532
  child.stdout.setEncoding("utf8");
37661
38533
  child.stdout.on("data", (chunk) => {
37662
38534
  buffer += chunk;
@@ -37677,6 +38549,7 @@ function attachServeChild(child, setState, setStartupError, onExit) {
37677
38549
  child.stderr.on("data", (chunk) => {
37678
38550
  const text = String(chunk).trim();
37679
38551
  if (text) {
38552
+ stderrChunks.push(text);
37680
38553
  setState((current) => ({
37681
38554
  ...current,
37682
38555
  logs: [...current.logs, { type: "stderr", message: text }].slice(-200)
@@ -37684,23 +38557,33 @@ function attachServeChild(child, setState, setStartupError, onExit) {
37684
38557
  }
37685
38558
  });
37686
38559
  child.on("error", (error) => setStartupError(errorMessage2(error)));
37687
- child.on("exit", onExit);
38560
+ child.on("exit", (code, signal) => {
38561
+ const message = serveProcessExitMessage({ stderr: stderrChunks.join("\n"), code, signal });
38562
+ if (message) {
38563
+ setStartupError(message);
38564
+ }
38565
+ onExit({ code, signal, message });
38566
+ });
37688
38567
  }
37689
38568
  function helpText() {
37690
38569
  return [
37691
- "Vendian CLI",
37692
38570
  "",
37693
- "Usage:",
37694
- " vendian Open the interactive shell",
37695
- " vendian login Sign in and prepare the local runtime",
37696
- " vendian doctor Check local bootstrap health",
37697
- " vendian update Update the managed Vendian CLI/runtime",
37698
- " vendian init Write current SDK agent docs into a workspace",
37699
- ' vendian create "My Agent" Scaffold a new agent from SDK templates',
37700
- " vendian <command> Run a managed Python SDK/cloud command",
38571
+ ` ${fig.arrowUp} VENDIAN CLI v${CLI_VERSION}`,
38572
+ "",
38573
+ " Usage:",
38574
+ " vendian Open the interactive shell",
38575
+ " vendian login Sign in and prepare the local runtime",
38576
+ " vendian doctor Check local bootstrap health",
38577
+ " vendian update Update the managed runtime",
38578
+ " vendian init Write SDK agent docs into a workspace",
38579
+ ' vendian create "My Agent" Scaffold a new agent from templates',
38580
+ " vendian <command> Run a managed Python SDK/cloud command",
38581
+ "",
38582
+ " Examples:",
38583
+ ...COMMANDS.map((command) => ` ${command}`),
37701
38584
  "",
37702
- "Examples:",
37703
- ...COMMANDS.map((command) => ` ${command}`)
38585
+ ` Run ${fig.arrowRight} vendian ${fig.arrowRight} to open the interactive TUI`,
38586
+ ""
37704
38587
  ].join("\n");
37705
38588
  }
37706
38589
  function credentialLabel(agent) {