@vendian/cli 0.0.8 → 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 +1022 -137
  2. package/package.json +2 -1
package/cli-wrapper.mjs CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire as __vendianCreateRequire } from 'node:module';
3
+ const require = __vendianCreateRequire(import.meta.url);
2
4
 
3
5
  var __create = Object.create;
4
6
  var __defProp = Object.defineProperty;
@@ -1379,7 +1381,7 @@ var require_react_development = __commonJS({
1379
1381
  var dispatcher = resolveDispatcher();
1380
1382
  return dispatcher.useReducer(reducer, initialArg, init);
1381
1383
  }
1382
- function useRef2(initialValue) {
1384
+ function useRef3(initialValue) {
1383
1385
  var dispatcher = resolveDispatcher();
1384
1386
  return dispatcher.useRef(initialValue);
1385
1387
  }
@@ -2173,7 +2175,7 @@ var require_react_development = __commonJS({
2173
2175
  exports.useLayoutEffect = useLayoutEffect2;
2174
2176
  exports.useMemo = useMemo3;
2175
2177
  exports.useReducer = useReducer;
2176
- exports.useRef = useRef2;
2178
+ exports.useRef = useRef3;
2177
2179
  exports.useState = useState6;
2178
2180
  exports.useSyncExternalStore = useSyncExternalStore;
2179
2181
  exports.useTransition = useTransition;
@@ -36689,16 +36691,27 @@ function packageIndexEnv(config = {}, env3 = process.env, platform2 = process.pl
36689
36691
  function joinIndexUrls(urls) {
36690
36692
  return urls.flatMap((value) => String(value || "").split(/\s+/)).map((value) => value.trim()).filter(Boolean).filter((value, index, all) => all.indexOf(value) === index).join(" ");
36691
36693
  }
36692
- 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
+ } = {}) {
36693
36706
  if (env3.VENDIAN_SKIP_AUTO_UPDATE === "1") {
36694
36707
  return false;
36695
36708
  }
36696
- const config = loadConfig(env3, platform2);
36709
+ const config = load(env3, platform2);
36697
36710
  const lastUpdate = Date.parse(config.lastManagedUpdateAt || "");
36698
- if (Number.isFinite(lastUpdate) && Date.now() - lastUpdate < AUTO_UPDATE_INTERVAL_MS) {
36711
+ if (!force && Number.isFinite(lastUpdate) && now - lastUpdate < AUTO_UPDATE_INTERVAL_MS) {
36699
36712
  return false;
36700
36713
  }
36701
- const registry = registryConfig(config, env3);
36714
+ const registry = registryConfig(config, env3, platform2);
36702
36715
  if (!registry.token) {
36703
36716
  return false;
36704
36717
  }
@@ -36707,14 +36720,19 @@ function maybeAutoUpdateManagedEnv({ env: env3 = process.env, platform: platform
36707
36720
  return false;
36708
36721
  }
36709
36722
  try {
36710
- console.error("[vendian] Checking managed CLI/runtime updates...");
36711
- installVendianPackages({ pythonPath, venvPath, config, env: env3 });
36712
- const next = { ...config, lastManagedUpdateAt: (/* @__PURE__ */ new Date()).toISOString() };
36713
- 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 });
36714
36730
  return true;
36715
36731
  } catch (error) {
36716
36732
  const message = error && typeof error.message === "string" ? error.message : String(error);
36717
- 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
+ }
36718
36736
  return false;
36719
36737
  }
36720
36738
  }
@@ -36723,16 +36741,12 @@ function maybeAutoUpdateManagedEnv({ env: env3 = process.env, platform: platform
36723
36741
  import fs11 from "node:fs";
36724
36742
  import readlinePromises from "node:readline/promises";
36725
36743
 
36726
- // src/npm-update.js
36727
- import { execFile } from "node:child_process";
36728
- import { promisify } from "node:util";
36729
-
36730
36744
  // src/version.js
36731
- var CLI_VERSION = true ? "0.0.8" : 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";
36732
36746
 
36733
36747
  // src/npm-update.js
36734
- var execFileAsync = promisify(execFile);
36735
36748
  var NPM_CHECK_INTERVAL_MS = 30 * 60 * 1e3;
36749
+ var CLI_PACKAGE_NAME = "@vendian/cli";
36736
36750
  function npmPackageUpdateSummary({
36737
36751
  config = {},
36738
36752
  currentVersion = CLI_VERSION,
@@ -36755,21 +36769,14 @@ function npmPackageUpdateSummary({
36755
36769
  async function checkNpmPackageUpdate({
36756
36770
  env: env3 = process.env,
36757
36771
  platform: platform2 = process.platform,
36758
- packageName = "@vendian/cli",
36772
+ packageName = CLI_PACKAGE_NAME,
36759
36773
  registry = "https://registry.npmjs.org/",
36760
36774
  currentVersion = CLI_VERSION,
36761
- timeoutMs = 4e3
36775
+ timeoutMs = 4e3,
36776
+ fetchImpl = globalThis.fetch
36762
36777
  } = {}) {
36763
- const npm = platform2 === "win32" ? "npm.cmd" : "npm";
36764
- const args = ["view", packageName, "dist-tags.latest", "--json", `--registry=${registry}`];
36765
36778
  try {
36766
- const { stdout } = await execFileAsync(npm, args, {
36767
- env: env3,
36768
- encoding: "utf8",
36769
- timeout: timeoutMs,
36770
- windowsHide: true
36771
- });
36772
- const latestVersion = parseNpmLatest(stdout);
36779
+ const latestVersion = await latestNpmPackageVersion({ packageName, registry, timeoutMs, fetchImpl });
36773
36780
  const config = {
36774
36781
  ...loadConfig(env3, platform2),
36775
36782
  latestNpmCliVersion: latestVersion,
@@ -36784,6 +36791,33 @@ async function checkNpmPackageUpdate({
36784
36791
  };
36785
36792
  }
36786
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
+ }
36787
36821
  function updateLabel({ runtime, npmUpdate }) {
36788
36822
  if (npmUpdate?.updateAvailable) {
36789
36823
  return `${npmUpdate.label} (current ${npmUpdate.currentVersion})`;
@@ -36811,6 +36845,19 @@ function parseNpmLatest(stdout) {
36811
36845
  return raw.replace(/^"|"$/g, "").trim();
36812
36846
  }
36813
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
+ }
36814
36861
  function compareSemver(left, right) {
36815
36862
  const leftParts = semverParts(left);
36816
36863
  const rightParts = semverParts(right);
@@ -37045,6 +37092,9 @@ function initialServeState() {
37045
37092
  retry: null,
37046
37093
  errors: [],
37047
37094
  logs: [],
37095
+ agentLogs: {},
37096
+ // per-agent logs keyed by relativePath
37097
+ newAgents: [],
37048
37098
  stopped: false,
37049
37099
  jobsRun: 0
37050
37100
  };
@@ -37060,6 +37110,19 @@ function parseServeEventLine(line) {
37060
37110
  }
37061
37111
  return parsed;
37062
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
+ }
37063
37126
  function applyServeEvent(state, event) {
37064
37127
  if (!event || typeof event.type !== "string") {
37065
37128
  return state;
@@ -37091,10 +37154,13 @@ function applyServeEvent(state, event) {
37091
37154
  };
37092
37155
  }
37093
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) : [];
37094
37159
  return {
37095
37160
  ...next,
37096
- agents: Array.isArray(event.agents) ? event.agents : state.agents,
37097
- 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))`
37098
37164
  };
37099
37165
  }
37100
37166
  if (event.type === "job_started") {
@@ -37138,6 +37204,12 @@ function applyServeEvent(state, event) {
37138
37204
  jobsRun: Number(event.jobsRun ?? state.jobsRun)
37139
37205
  };
37140
37206
  }
37207
+ if (event.type === "run_log") {
37208
+ return {
37209
+ ...next,
37210
+ agentLogs: appendAgentLog(state.agentLogs, event)
37211
+ };
37212
+ }
37141
37213
  return next;
37142
37214
  }
37143
37215
  function agentSummary(agents = []) {
@@ -37159,9 +37231,45 @@ function agentSummary(agents = []) {
37159
37231
  function appendLog(logs, event) {
37160
37232
  return [...logs, event].slice(-200);
37161
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
+ }
37162
37257
  function appendError(errors, scope, message) {
37163
37258
  return [...errors, { scope, message: stringValue(message) }].slice(-20);
37164
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
+ }
37165
37273
  function agentLabel(event) {
37166
37274
  return stringValue(event.relativePath || event.path || ".");
37167
37275
  }
@@ -37180,27 +37288,152 @@ function formatSeconds(value) {
37180
37288
  import { spawn as spawn2 } from "node:child_process";
37181
37289
  async function spawnLocalServeEventStream({
37182
37290
  agentsDir = "./agents",
37291
+ collectionId = "",
37183
37292
  env: env3 = process.env,
37184
37293
  platform: platform2 = process.platform
37185
37294
  } = {}) {
37186
- 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 = [
37187
37304
  "cloud",
37188
37305
  "local",
37189
37306
  "serve",
37190
37307
  "--agents-dir",
37191
37308
  agentsDir || "./agents",
37192
37309
  "--event-stream"
37193
- ], { env: env3, platform: platform2 });
37194
- return spawn2(invocation.command, invocation.args, {
37195
- env: invocation.env,
37196
- stdio: ["ignore", "pipe", "pipe"],
37197
- shell: false
37198
- });
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
+ }
37199
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;
37200
37432
 
37201
37433
  // src/tui.js
37202
37434
  var h;
37203
37435
  var useEffect6;
37436
+ var useRef2;
37204
37437
  var useState5;
37205
37438
  var Box2;
37206
37439
  var Text2;
@@ -37217,15 +37450,25 @@ var ENDPOINTS = [
37217
37450
  { key: "prod", label: "Production" }
37218
37451
  ];
37219
37452
  var ACTIONS = [
37220
- { value: "connect", label: "Connect Endpoint" },
37221
- { value: "init", label: "Initialize Docs" },
37222
- { value: "create", label: "Create Agent" },
37223
- { value: "serve", label: "Serve Agents" },
37224
- { value: "doctor", label: "Doctor" },
37225
- { value: "update", label: "Update" },
37226
- { value: "commands", label: "Commands" },
37227
- { 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" }
37228
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
+ };
37229
37472
  var COMMANDS = [
37230
37473
  "vendian login",
37231
37474
  "vendian init --output-dir ./agents",
@@ -37245,6 +37488,7 @@ async function runTui({ env: env3 = process.env, platform: platform2 = process.p
37245
37488
  `);
37246
37489
  return;
37247
37490
  }
37491
+ await refreshInteractiveManagedRuntime({ env: env3, platform: platform2, output });
37248
37492
  await loadInkRuntime();
37249
37493
  const app = renderInk(h(VendianShell, { env: env3, platform: platform2, input, output }), {
37250
37494
  stdin: input,
@@ -37253,6 +37497,25 @@ async function runTui({ env: env3 = process.env, platform: platform2 = process.p
37253
37497
  });
37254
37498
  await app.waitUntilExit();
37255
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
+ }
37256
37519
  async function loadInkRuntime() {
37257
37520
  if (h) {
37258
37521
  return;
@@ -37266,6 +37529,7 @@ async function loadInkRuntime() {
37266
37529
  ]);
37267
37530
  h = reactModule.default.createElement;
37268
37531
  useEffect6 = reactModule.useEffect;
37532
+ useRef2 = reactModule.useRef;
37269
37533
  useState5 = reactModule.useState;
37270
37534
  Box2 = inkModule.Box;
37271
37535
  Text2 = inkModule.Text;
@@ -37276,6 +37540,20 @@ async function loadInkRuntime() {
37276
37540
  TextInput2 = textInputModule.default;
37277
37541
  Spinner2 = spinnerModule.default;
37278
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
+ }
37279
37557
  function runtimeSummary({ env: env3 = process.env, platform: platform2 = process.platform, now = Date.now() } = {}) {
37280
37558
  const config = loadConfig(env3, platform2);
37281
37559
  const venvPath = managedVenvPath(env3, platform2);
@@ -37296,37 +37574,92 @@ function packageAccessSummary({ env: env3 = process.env, platform: platform2 = p
37296
37574
  source: registry.tokenSource || "local config"
37297
37575
  };
37298
37576
  }
37577
+ function isCtrlCInput(input = "", key = {}) {
37578
+ return input === "" || input === "" || Boolean(key?.ctrl && (key.name === "c" || input === "c"));
37579
+ }
37299
37580
  function VendianShell({ env: env3, platform: platform2, input, output }) {
37300
37581
  const app = useApp2();
37301
37582
  const [screen, setScreen] = useState5("home");
37302
37583
  const [serveState, setServeState] = useState5(null);
37303
- useInput2((_, key) => {
37304
- 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) {
37305
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();
37306
37630
  }
37307
37631
  });
37308
37632
  const goHome = () => setScreen("home");
37633
+ const breadcrumb = screen !== "home" ? ["Home", SCREEN_TITLES[screen] || screen] : ["Home"];
37309
37634
  return h(
37310
37635
  Box2,
37311
37636
  { flexDirection: "column" },
37312
- 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, ""),
37313
37646
  screen === "home" && h(HomeScreen, { onSelect: (value) => value === "exit" ? app.exit() : setScreen(value) }),
37314
37647
  screen === "connect" && h(ConnectScreen, { env: env3, platform: platform2, onBack: goHome }),
37315
37648
  screen === "init" && h(InitScreen, { env: env3, platform: platform2, onBack: goHome }),
37316
37649
  screen === "create" && h(CreateScreen, { env: env3, platform: platform2, onBack: goHome }),
37317
- 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 }),
37318
37651
  screen === "doctor" && h(DoctorScreen, { env: env3, platform: platform2, onBack: goHome }),
37319
37652
  screen === "update" && h(UpdateScreen, { env: env3, platform: platform2, onBack: goHome }),
37320
- 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.")
37321
37655
  );
37322
37656
  }
37323
- function Header({ env: env3, platform: platform2, serveState }) {
37657
+ function Header({ env: env3, platform: platform2, serveState, screen }) {
37324
37658
  const active = activeCloudAuthStatus({ env: env3, platform: platform2 });
37325
37659
  const runtime = runtimeSummary({ env: env3, platform: platform2 });
37326
37660
  const [npmUpdate, setNpmUpdate] = useState5(() => npmPackageUpdateSummary({ config: loadConfig(env3, platform2) }));
37327
37661
  const [npmCheckStarted, setNpmCheckStarted] = useState5(false);
37328
37662
  const pkg = packageAccessSummary({ env: env3, platform: platform2 });
37329
- const daemon = serveState?.daemonId ? `Daemon ${serveState.daemonId} | ${serveState.activity}` : "Daemon idle";
37330
37663
  useEffect6(() => {
37331
37664
  let cancelled = false;
37332
37665
  if (!npmUpdate.stale || npmCheckStarted) {
@@ -37342,29 +37675,111 @@ function Header({ env: env3, platform: platform2, serveState }) {
37342
37675
  cancelled = true;
37343
37676
  };
37344
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);
37345
37691
  return h(
37346
37692
  Box2,
37347
37693
  { flexDirection: "column", marginBottom: 1 },
37348
- h(Text2, { bold: true, color: "cyan" }, `VENDIAN CLI ${CLI_VERSION}`),
37349
- h(Text2, null, `Endpoint: ${active.authenticated ? `${active.apiUrl}${active.email ? ` (${active.email})` : ""}` : "not connected"}`),
37350
- h(Text2, null, `Runtime: ${runtime.installed ? "ready" : "missing"} | Package access: ${pkg.configured ? `configured (${pkg.source})` : "missing"} | Update: ${updateLabel({ runtime, npmUpdate })}`),
37351
- 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}`)
37352
37732
  );
37353
37733
  }
37354
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
+ });
37355
37745
  return h(
37356
37746
  Box2,
37357
37747
  { flexDirection: "column" },
37358
- h(Text2, { bold: true }, "Home"),
37359
- 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
+ ] })
37360
37770
  );
37361
37771
  }
37362
37772
  function ConnectScreen({ env: env3, platform: platform2, onBack }) {
37363
37773
  const [mode, setMode] = useState5("select");
37364
37774
  const [apiUrl, setApiUrl] = useState5("");
37365
37775
  const [status, setStatus] = useState5("");
37776
+ const rows = endpointRows({ env: env3, platform: platform2 });
37366
37777
  const items = [
37367
- ...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
+ }),
37368
37783
  { label: "Custom API URL", value: "custom" },
37369
37784
  { label: "Back", value: "back" }
37370
37785
  ];
@@ -37377,41 +37792,47 @@ function ConnectScreen({ env: env3, platform: platform2, onBack }) {
37377
37792
  setMode("custom");
37378
37793
  return;
37379
37794
  }
37380
- setStatus(`Connecting ${value}`);
37795
+ setStatus(`Connecting to ${value}...`);
37381
37796
  await setup({ backend: value, forceAuth: true, env: env3, platform: platform2 });
37382
- setStatus("Connected");
37797
+ setStatus(`${fig.check} Connected`);
37383
37798
  }
37384
37799
  if (mode === "custom") {
37385
37800
  return h(
37386
37801
  Box2,
37387
37802
  { flexDirection: "column" },
37388
- h(Text2, { bold: true }, "Connect Endpoint"),
37803
+ h(Text2, { bold: true }, " Enter custom API URL:"),
37804
+ h(Text2, null, ""),
37389
37805
  h(TextInput2, {
37390
37806
  value: apiUrl,
37391
37807
  placeholder: "https://api.example.test",
37392
37808
  onChange: setApiUrl,
37393
37809
  onSubmit: async (value) => {
37394
37810
  if (!value) return;
37395
- setStatus("Connecting custom endpoint");
37811
+ setStatus("Connecting...");
37396
37812
  await setup({ apiUrl: value, forceAuth: true, env: env3, platform: platform2 });
37397
- setStatus("Connected");
37813
+ setStatus(`${fig.check} Connected`);
37398
37814
  }
37399
37815
  }),
37400
- 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" }] }),
37401
37819
  h(BackHint, { onBack })
37402
37820
  );
37403
37821
  }
37404
37822
  return h(
37405
37823
  Box2,
37406
37824
  { flexDirection: "column" },
37407
- h(Text2, { bold: true }, "Connect Endpoint"),
37825
+ h(Text2, { bold: true }, " Select environment:"),
37826
+ h(Text2, null, ""),
37408
37827
  h(SelectInput2, { items, onSelect: (item) => connect(item.value) }),
37409
- 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" }] })
37410
37831
  );
37411
37832
  }
37412
37833
  function InitScreen({ env: env3, platform: platform2, onBack }) {
37413
37834
  return h(CommandPromptScreen, {
37414
- title: "Initialize Docs",
37835
+ title: " Initialize Docs",
37415
37836
  label: "Workspace directory",
37416
37837
  initialValue: ".",
37417
37838
  onBack,
@@ -37451,53 +37872,165 @@ function CreateScreen({ env: env3, platform: platform2, onBack }) {
37451
37872
  h(BackHint, { onBack })
37452
37873
  );
37453
37874
  }
37454
- function ServeScreen({ env: env3, platform: platform2, onBack, onState }) {
37875
+ function ServeScreen({ env: env3, platform: platform2, input, onBack, onState, onExitApp }) {
37455
37876
  const [agentDirCandidates] = useState5(() => findAgentDirectoryCandidates());
37456
37877
  const [agentsDir, setAgentsDir] = useState5(() => defaultAgentsDir(agentDirCandidates));
37457
37878
  const [selectingDir, setSelectingDir] = useState5(() => agentDirCandidates.length > 0);
37879
+ const [workspaceChoices, setWorkspaceChoices] = useState5([]);
37880
+ const [pendingAgentsDir, setPendingAgentsDir] = useState5("");
37881
+ const [loadingWorkspaces, setLoadingWorkspaces] = useState5(false);
37458
37882
  const [started, setStarted] = useState5(false);
37459
37883
  const [state, setState] = useState5(initialServeState());
37460
37884
  const [child, setChild] = useState5(null);
37461
- const [showLogs, setShowLogs] = useState5(false);
37885
+ const [logMode, setLogMode] = useState5(null);
37462
37886
  const [confirmExit, setConfirmExit] = useState5(false);
37887
+ const [exitArmed, setExitArmed] = useState5(false);
37463
37888
  const [startupError, setStartupError] = useState5("");
37889
+ const exitTimer = useRef2(null);
37890
+ const lastInterruptAt = useRef2(0);
37464
37891
  useEffect6(() => {
37465
37892
  onState(state);
37466
37893
  }, [state, onState]);
37467
- 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
+ }
37468
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
+ }
37469
37950
  if (confirmExit) {
37470
- if (input.toLowerCase() === "y" || key.return) {
37951
+ if (input2.toLowerCase() === "y" || key.return) {
37471
37952
  child?.kill("SIGINT");
37472
37953
  onState(null);
37473
37954
  onBack();
37474
- } else if (input.toLowerCase() === "n" || key.escape) {
37955
+ } else if (input2.toLowerCase() === "n" || key.escape) {
37475
37956
  setConfirmExit(false);
37476
37957
  }
37477
37958
  return;
37478
37959
  }
37479
- if (input === "l") {
37480
- setShowLogs((value) => !value);
37481
- } else if (input === "q" || key.ctrl && key.name === "c") {
37960
+ if (input2 === "l") {
37961
+ setLogMode("picker");
37962
+ } else if (input2 === "q") {
37482
37963
  setConfirmExit(true);
37483
37964
  }
37484
37965
  });
37485
- async function start(selectedAgentsDir = agentsDir) {
37966
+ async function start(selectedAgentsDir = agentsDir, collectionId = "") {
37486
37967
  const serveRoot = selectedAgentsDir || "./agents";
37487
37968
  setAgentsDir(serveRoot);
37488
37969
  setStarted(true);
37489
37970
  setStartupError("");
37490
37971
  try {
37491
- const nextChild = await spawnLocalServeEventStream({ agentsDir: serveRoot, env: env3, platform: platform2 });
37972
+ const nextChild = await spawnLocalServeEventStream({ agentsDir: serveRoot, collectionId, env: env3, platform: platform2 });
37492
37973
  setChild(nextChild);
37493
- attachServeChild(nextChild, setState, setStartupError, () => {
37494
- 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" });
37495
37976
  });
37496
37977
  } catch (error) {
37497
37978
  setStartupError(errorMessage2(error));
37498
37979
  }
37499
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
+ }
37500
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
+ }
37501
38034
  const items = [
37502
38035
  ...agentDirCandidates.map((candidate) => ({
37503
38036
  label: `${candidate.path} (${candidate.agentCount} ${candidate.agentCount === 1 ? "agent" : "agents"})`,
@@ -37509,7 +38042,7 @@ function ServeScreen({ env: env3, platform: platform2, onBack, onState }) {
37509
38042
  return h(
37510
38043
  Box2,
37511
38044
  { flexDirection: "column" },
37512
- h(Text2, { bold: true }, "Serve Agents"),
38045
+ h(Text2, { bold: true }, "Select agent directory:"),
37513
38046
  selectingDir && h(SelectInput2, {
37514
38047
  items,
37515
38048
  onSelect: (item) => {
@@ -37523,77 +38056,378 @@ function ServeScreen({ env: env3, platform: platform2, onBack, onState }) {
37523
38056
  return;
37524
38057
  }
37525
38058
  setAgentsDir(item.value);
37526
- start(item.value);
38059
+ chooseWorkspaceThenStart(item.value);
37527
38060
  }
37528
38061
  }),
37529
38062
  !selectingDir && h(TextInput2, {
37530
38063
  value: agentsDir,
37531
38064
  placeholder: "./agents",
37532
38065
  onChange: setAgentsDir,
37533
- onSubmit: (value) => start(value)
38066
+ onSubmit: (value) => chooseWorkspaceThenStart(value)
37534
38067
  }),
37535
- 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
+ ] })
37536
38082
  );
37537
38083
  }
37538
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
+ }
37539
38104
  return h(
37540
38105
  Box2,
37541
38106
  { flexDirection: "column" },
37542
- h(Text2, { bold: true }, "Serve Agents"),
37543
- startupError ? h(Text2, { color: "red" }, startupError) : h(Box2, null, h(Spinner2, { type: "dots" }), h(Text2, null, ` ${state.activity}`)),
37544
- 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
+ ),
37545
38130
  h(AgentTable, { agents: state.agents }),
37546
- state.retry && h(Text2, { color: "yellow" }, `Retry: ${state.retry.activity} in ${Number(state.retry.delaySeconds || 0).toFixed(1)}s`),
37547
- state.errors.slice(-3).map((item, index) => h(Text2, { key: `error-${index}`, color: "red" }, `${item.scope}: ${item.message}`)),
37548
- showLogs && h(LogPane, { logs: state.logs }),
37549
- confirmExit && h(Text2, { color: "yellow" }, "Stop local serve? y/Enter confirms, n/Esc cancels."),
37550
- 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.")
37551
38151
  );
37552
38152
  }
37553
38153
  function AgentTable({ agents }) {
37554
38154
  if (!agents.length) {
37555
- 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
+ }
37556
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;
37557
38269
  return h(
37558
38270
  Box2,
37559
38271
  { flexDirection: "column" },
37560
- h(Text2, { dimColor: true }, "Path Name Credentials Status Error"),
37561
- ...agents.slice(0, 12).map((agent) => h(
37562
- Text2,
37563
- { key: agent.localAgentId || agent.relativePath },
37564
- `${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)}`
37565
- ))
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
+ ] })
37566
38295
  );
37567
38296
  }
37568
- 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);
37569
38318
  return h(
37570
38319
  Box2,
37571
- { flexDirection: "column", marginTop: 1 },
37572
- h(Text2, { bold: true }, "Details"),
37573
- ...logs.slice(-8).map((event, index) => h(
37574
- Text2,
37575
- { key: `${event.type}-${index}`, dimColor: true },
37576
- `${event.type} ${JSON.stringify(event)}`
37577
- ))
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
+ ] })
37578
38356
  );
37579
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
+ }
37580
38396
  function DoctorScreen({ env: env3, platform: platform2, onBack }) {
37581
38397
  const runtime = runtimeSummary({ env: env3, platform: platform2 });
37582
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);
37583
38405
  return h(
37584
38406
  Box2,
37585
38407
  { flexDirection: "column" },
37586
- h(Text2, { bold: true }, "Doctor"),
37587
- h(Text2, null, `Managed Python: ${runtime.installed ? "present" : "missing"}`),
37588
- h(Text2, null, `Managed environment: ${runtime.detail}`),
37589
- 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, ""),
37590
38423
  h(SelectInput2, {
37591
- 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" }],
37592
38425
  onSelect: async (item) => {
37593
38426
  if (item.value === "back") onBack();
37594
38427
  else doctor({ env: env3, platform: platform2 });
37595
38428
  }
37596
- })
38429
+ }),
38430
+ h(FooterBar, { items: [{ key: "\u23CE", label: "Select" }, { key: "esc", label: "Back" }] })
37597
38431
  );
37598
38432
  }
37599
38433
  function UpdateScreen({ env: env3, platform: platform2, onBack }) {
@@ -37601,29 +38435,65 @@ function UpdateScreen({ env: env3, platform: platform2, onBack }) {
37601
38435
  return h(
37602
38436
  Box2,
37603
38437
  { flexDirection: "column" },
37604
- h(Text2, { bold: true }, "Update"),
38438
+ h(Text2, { bold: true }, " Update Runtime"),
38439
+ h(Text2, null, ""),
37605
38440
  h(SelectInput2, {
37606
- items: [{ label: "Update managed runtime", value: "run" }, { label: "Back", value: "back" }],
38441
+ items: [{ label: "Update managed runtime & packages", value: "run" }, { label: "Back", value: "back" }],
37607
38442
  onSelect: async (item) => {
37608
38443
  if (item.value === "back") {
37609
38444
  onBack();
37610
38445
  return;
37611
38446
  }
37612
- setStatus("Updating managed runtime");
38447
+ setStatus(`${fig.dotEmpty} Updating managed runtime...`);
37613
38448
  await setup({ nonInteractive: true, env: env3, platform: platform2 });
37614
- setStatus("Update finished");
38449
+ setStatus(`${fig.check} Update finished`);
37615
38450
  }
37616
38451
  }),
37617
- 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 })
37618
38456
  );
37619
38457
  }
37620
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
+ ];
37621
38479
  return h(
37622
38480
  Box2,
37623
38481
  { flexDirection: "column" },
37624
- h(Text2, { bold: true }, "Commands"),
37625
- ...COMMANDS.map((command) => h(Text2, { key: command }, command)),
37626
- 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 })
37627
38497
  );
37628
38498
  }
37629
38499
  function CommandPromptScreen({ title, label, initialValue, onSubmit, onBack }) {
@@ -37633,17 +38503,20 @@ function CommandPromptScreen({ title, label, initialValue, onSubmit, onBack }) {
37633
38503
  Box2,
37634
38504
  { flexDirection: "column" },
37635
38505
  h(Text2, { bold: true }, title),
38506
+ h(Text2, null, ""),
37636
38507
  h(TextInput2, {
37637
38508
  value,
37638
38509
  placeholder: label,
37639
38510
  onChange: setValue,
37640
38511
  onSubmit: async (submitted) => {
37641
- setStatus("Running command");
38512
+ setStatus(`${fig.dotEmpty} Running...`);
37642
38513
  await onSubmit(submitted);
37643
- setStatus("Command finished");
38514
+ setStatus(`${fig.check} Done`);
37644
38515
  }
37645
38516
  }),
37646
- 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" }] }),
37647
38520
  h(BackHint, { onBack })
37648
38521
  );
37649
38522
  }
@@ -37651,10 +38524,11 @@ function BackHint({ onBack }) {
37651
38524
  useInput2((input, key) => {
37652
38525
  if (key.escape || input === "\x1B") onBack();
37653
38526
  });
37654
- return h(Text2, { dimColor: true }, "Esc returns home");
38527
+ return null;
37655
38528
  }
37656
38529
  function attachServeChild(child, setState, setStartupError, onExit) {
37657
38530
  let buffer = "";
38531
+ const stderrChunks = [];
37658
38532
  child.stdout.setEncoding("utf8");
37659
38533
  child.stdout.on("data", (chunk) => {
37660
38534
  buffer += chunk;
@@ -37675,6 +38549,7 @@ function attachServeChild(child, setState, setStartupError, onExit) {
37675
38549
  child.stderr.on("data", (chunk) => {
37676
38550
  const text = String(chunk).trim();
37677
38551
  if (text) {
38552
+ stderrChunks.push(text);
37678
38553
  setState((current) => ({
37679
38554
  ...current,
37680
38555
  logs: [...current.logs, { type: "stderr", message: text }].slice(-200)
@@ -37682,23 +38557,33 @@ function attachServeChild(child, setState, setStartupError, onExit) {
37682
38557
  }
37683
38558
  });
37684
38559
  child.on("error", (error) => setStartupError(errorMessage2(error)));
37685
- 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
+ });
37686
38567
  }
37687
38568
  function helpText() {
37688
38569
  return [
37689
- "Vendian CLI",
37690
38570
  "",
37691
- "Usage:",
37692
- " vendian Open the interactive shell",
37693
- " vendian login Sign in and prepare the local runtime",
37694
- " vendian doctor Check local bootstrap health",
37695
- " vendian update Update the managed Vendian CLI/runtime",
37696
- " vendian init Write current SDK agent docs into a workspace",
37697
- ' vendian create "My Agent" Scaffold a new agent from SDK templates',
37698
- " 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}`),
37699
38584
  "",
37700
- "Examples:",
37701
- ...COMMANDS.map((command) => ` ${command}`)
38585
+ ` Run ${fig.arrowRight} vendian ${fig.arrowRight} to open the interactive TUI`,
38586
+ ""
37702
38587
  ].join("\n");
37703
38588
  }
37704
38589
  function credentialLabel(agent) {