@vendian/cli 0.0.9 → 0.0.11

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 +1505 -167
  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;
@@ -24765,10 +24765,10 @@ var require_react_reconciler_development = __commonJS({
24765
24765
  var setErrorHandler = null;
24766
24766
  var setSuspenseHandler = null;
24767
24767
  {
24768
- var copyWithDeleteImpl = function(obj, path7, index2) {
24769
- var key = path7[index2];
24768
+ var copyWithDeleteImpl = function(obj, path8, index2) {
24769
+ var key = path8[index2];
24770
24770
  var updated = isArray(obj) ? obj.slice() : assign({}, obj);
24771
- if (index2 + 1 === path7.length) {
24771
+ if (index2 + 1 === path8.length) {
24772
24772
  if (isArray(updated)) {
24773
24773
  updated.splice(key, 1);
24774
24774
  } else {
@@ -24776,11 +24776,11 @@ var require_react_reconciler_development = __commonJS({
24776
24776
  }
24777
24777
  return updated;
24778
24778
  }
24779
- updated[key] = copyWithDeleteImpl(obj[key], path7, index2 + 1);
24779
+ updated[key] = copyWithDeleteImpl(obj[key], path8, index2 + 1);
24780
24780
  return updated;
24781
24781
  };
24782
- var copyWithDelete = function(obj, path7) {
24783
- return copyWithDeleteImpl(obj, path7, 0);
24782
+ var copyWithDelete = function(obj, path8) {
24783
+ return copyWithDeleteImpl(obj, path8, 0);
24784
24784
  };
24785
24785
  var copyWithRenameImpl = function(obj, oldPath, newPath, index2) {
24786
24786
  var oldKey = oldPath[index2];
@@ -24818,17 +24818,17 @@ var require_react_reconciler_development = __commonJS({
24818
24818
  }
24819
24819
  return copyWithRenameImpl(obj, oldPath, newPath, 0);
24820
24820
  };
24821
- var copyWithSetImpl = function(obj, path7, index2, value) {
24822
- if (index2 >= path7.length) {
24821
+ var copyWithSetImpl = function(obj, path8, index2, value) {
24822
+ if (index2 >= path8.length) {
24823
24823
  return value;
24824
24824
  }
24825
- var key = path7[index2];
24825
+ var key = path8[index2];
24826
24826
  var updated = isArray(obj) ? obj.slice() : assign({}, obj);
24827
- updated[key] = copyWithSetImpl(obj[key], path7, index2 + 1, value);
24827
+ updated[key] = copyWithSetImpl(obj[key], path8, index2 + 1, value);
24828
24828
  return updated;
24829
24829
  };
24830
- var copyWithSet = function(obj, path7, value) {
24831
- return copyWithSetImpl(obj, path7, 0, value);
24830
+ var copyWithSet = function(obj, path8, value) {
24831
+ return copyWithSetImpl(obj, path8, 0, value);
24832
24832
  };
24833
24833
  var findHook = function(fiber, id) {
24834
24834
  var currentHook2 = fiber.memoizedState;
@@ -24838,10 +24838,10 @@ var require_react_reconciler_development = __commonJS({
24838
24838
  }
24839
24839
  return currentHook2;
24840
24840
  };
24841
- overrideHookState = function(fiber, id, path7, value) {
24841
+ overrideHookState = function(fiber, id, path8, value) {
24842
24842
  var hook = findHook(fiber, id);
24843
24843
  if (hook !== null) {
24844
- var newState = copyWithSet(hook.memoizedState, path7, value);
24844
+ var newState = copyWithSet(hook.memoizedState, path8, value);
24845
24845
  hook.memoizedState = newState;
24846
24846
  hook.baseState = newState;
24847
24847
  fiber.memoizedProps = assign({}, fiber.memoizedProps);
@@ -24851,10 +24851,10 @@ var require_react_reconciler_development = __commonJS({
24851
24851
  }
24852
24852
  }
24853
24853
  };
24854
- overrideHookStateDeletePath = function(fiber, id, path7) {
24854
+ overrideHookStateDeletePath = function(fiber, id, path8) {
24855
24855
  var hook = findHook(fiber, id);
24856
24856
  if (hook !== null) {
24857
- var newState = copyWithDelete(hook.memoizedState, path7);
24857
+ var newState = copyWithDelete(hook.memoizedState, path8);
24858
24858
  hook.memoizedState = newState;
24859
24859
  hook.baseState = newState;
24860
24860
  fiber.memoizedProps = assign({}, fiber.memoizedProps);
@@ -24877,8 +24877,8 @@ var require_react_reconciler_development = __commonJS({
24877
24877
  }
24878
24878
  }
24879
24879
  };
24880
- overrideProps = function(fiber, path7, value) {
24881
- fiber.pendingProps = copyWithSet(fiber.memoizedProps, path7, value);
24880
+ overrideProps = function(fiber, path8, value) {
24881
+ fiber.pendingProps = copyWithSet(fiber.memoizedProps, path8, value);
24882
24882
  if (fiber.alternate) {
24883
24883
  fiber.alternate.pendingProps = fiber.pendingProps;
24884
24884
  }
@@ -24887,8 +24887,8 @@ var require_react_reconciler_development = __commonJS({
24887
24887
  scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
24888
24888
  }
24889
24889
  };
24890
- overridePropsDeletePath = function(fiber, path7) {
24891
- fiber.pendingProps = copyWithDelete(fiber.memoizedProps, path7);
24890
+ overridePropsDeletePath = function(fiber, path8) {
24891
+ fiber.pendingProps = copyWithDelete(fiber.memoizedProps, path8);
24892
24892
  if (fiber.alternate) {
24893
24893
  fiber.alternate.pendingProps = fiber.pendingProps;
24894
24894
  }
@@ -32281,7 +32281,7 @@ var init_Text = __esm({
32281
32281
  });
32282
32282
 
32283
32283
  // node_modules/ink/build/components/ErrorOverview.js
32284
- import * as fs10 from "node:fs";
32284
+ import * as fs11 from "node:fs";
32285
32285
  import { cwd } from "node:process";
32286
32286
  function ErrorOverview({ error }) {
32287
32287
  const stack = error.stack ? error.stack.split("\n").slice(1) : void 0;
@@ -32289,8 +32289,8 @@ function ErrorOverview({ error }) {
32289
32289
  const filePath = cleanupPath(origin?.file);
32290
32290
  let excerpt;
32291
32291
  let lineWidth = 0;
32292
- if (filePath && origin?.line && fs10.existsSync(filePath)) {
32293
- const sourceCode = fs10.readFileSync(filePath, "utf8");
32292
+ if (filePath && origin?.line && fs11.existsSync(filePath)) {
32293
+ const sourceCode = fs11.readFileSync(filePath, "utf8");
32294
32294
  excerpt = dist_default3(sourceCode, origin.line);
32295
32295
  if (excerpt) {
32296
32296
  for (const { line } of excerpt) {
@@ -32385,8 +32385,8 @@ var init_ErrorOverview = __esm({
32385
32385
  init_dist3();
32386
32386
  init_Box();
32387
32387
  init_Text();
32388
- cleanupPath = (path7) => {
32389
- return path7?.replace(`file://${cwd()}/`, "");
32388
+ cleanupPath = (path8) => {
32389
+ return path8?.replace(`file://${cwd()}/`, "");
32390
32390
  };
32391
32391
  stackUtils = new import_stack_utils.default({
32392
32392
  cwd: cwd(),
@@ -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,32 +36720,33 @@ 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
  }
36723
36739
 
36724
36740
  // src/tui.js
36725
- import fs11 from "node:fs";
36741
+ import fs12 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.11" : 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,10 @@ function initialServeState() {
37047
37092
  retry: null,
37048
37093
  errors: [],
37049
37094
  logs: [],
37095
+ agentLogs: {},
37096
+ // per-agent logs keyed by relativePath
37097
+ agentRunState: {},
37098
+ newAgents: [],
37050
37099
  stopped: false,
37051
37100
  jobsRun: 0
37052
37101
  };
@@ -37062,6 +37111,19 @@ function parseServeEventLine(line) {
37062
37111
  }
37063
37112
  return parsed;
37064
37113
  }
37114
+ function serveProcessExitMessage({ stderr = "", code = 0, signal = "" } = {}) {
37115
+ const text = String(stderr || "");
37116
+ if (/unrecognized arguments:\s*--event-stream/i.test(text)) {
37117
+ return "Managed Vendian runtime is too old for the local serve dashboard. Run `vendian update`, then start Serve Agents again.";
37118
+ }
37119
+ if (code && code !== 0) {
37120
+ return `Local serve exited with code ${code}. Toggle details for stderr.`;
37121
+ }
37122
+ if (signal && signal !== "SIGINT" && signal !== "SIGTERM") {
37123
+ return `Local serve exited from ${signal}. Toggle details for stderr.`;
37124
+ }
37125
+ return "";
37126
+ }
37065
37127
  function applyServeEvent(state, event) {
37066
37128
  if (!event || typeof event.type !== "string") {
37067
37129
  return state;
@@ -37086,29 +37148,66 @@ function applyServeEvent(state, event) {
37086
37148
  return { ...next, activity: `Preparing ${agentLabel(event)}` };
37087
37149
  }
37088
37150
  if (event.type === "agent_prepare_completed") {
37151
+ const runState = event.status === "error" ? setAgentRunState(state.agentRunState, agentLabel(event), {
37152
+ status: "error",
37153
+ lastEventAt: event.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
37154
+ errorMessage: stringValue(event.error || event.errorMessage || "Agent setup failed")
37155
+ }) : state.agentRunState;
37089
37156
  return {
37090
37157
  ...next,
37091
37158
  activity: `${agentLabel(event)} ${event.status === "error" ? "needs setup" : "ready"}`,
37159
+ agentRunState: runState,
37092
37160
  errors: event.error ? appendError(state.errors, agentLabel(event), event.error) : state.errors
37093
37161
  };
37094
37162
  }
37095
37163
  if (event.type === "inventory_synced") {
37164
+ const agents = Array.isArray(event.agents) ? event.agents : state.agents;
37165
+ const newAgents = state.agents.length > 0 ? findNewAgents(state.agents, agents) : [];
37096
37166
  return {
37097
37167
  ...next,
37098
- agents: Array.isArray(event.agents) ? event.agents : state.agents,
37099
- activity: `Inventory synced (${Number(event.agentCount ?? event.agents?.length ?? 0)} agent(s))`
37168
+ agents,
37169
+ newAgents,
37170
+ agentRunState: reconcileInventoryRunState(state.agentRunState, agents, event.timestamp),
37171
+ activity: newAgents.length ? `${newAgents.length} new agent${newAgents.length === 1 ? "" : "s"} synced` : `Inventory synced (${Number(event.agentCount ?? agents.length ?? 0)} agent(s))`
37100
37172
  };
37101
37173
  }
37102
37174
  if (event.type === "job_started") {
37175
+ const relativePath = agentLabel(event);
37176
+ const timestamp = event.timestamp || (/* @__PURE__ */ new Date()).toISOString();
37103
37177
  return {
37104
37178
  ...next,
37179
+ agentLogs: appendAgentLog(state.agentLogs, event),
37180
+ agentRunState: setAgentRunState(state.agentRunState, relativePath, {
37181
+ status: "running",
37182
+ runId: stringValue(event.runId || event.deployRequestId),
37183
+ jobType: stringValue(event.jobType || "run"),
37184
+ startedAt: timestamp,
37185
+ completedAt: null,
37186
+ lastEventAt: timestamp,
37187
+ errorMessage: null
37188
+ }),
37105
37189
  currentJob: event,
37106
37190
  activity: `Running ${jobLabel(event)}`
37107
37191
  };
37108
37192
  }
37109
37193
  if (event.type === "job_completed") {
37194
+ const relativePath = agentLabel(event);
37195
+ const timestamp = event.timestamp || (/* @__PURE__ */ new Date()).toISOString();
37196
+ const success = event.success !== false;
37197
+ const runId = stringValue(event.runId || event.deployRequestId);
37198
+ const previous = (state.agentRunState || {})[relativePath];
37199
+ const effectiveSuccess = success && !(previous?.status === "error" && previous?.runId === runId);
37110
37200
  return {
37111
37201
  ...next,
37202
+ agentLogs: appendAgentLog(state.agentLogs, event),
37203
+ agentRunState: setAgentRunState(state.agentRunState, relativePath, {
37204
+ status: effectiveSuccess ? "completed" : "error",
37205
+ runId,
37206
+ jobType: stringValue(event.jobType || "run"),
37207
+ completedAt: timestamp,
37208
+ lastEventAt: timestamp,
37209
+ errorMessage: effectiveSuccess ? null : previous?.errorMessage || stringValue(event.error || "Job failed")
37210
+ }),
37112
37211
  jobsRun: state.jobsRun + 1,
37113
37212
  currentJob: null,
37114
37213
  activity: `${jobLabel(event)} completed`
@@ -37124,8 +37223,20 @@ function applyServeEvent(state, event) {
37124
37223
  return { ...next, retry: event, activity: `Retrying ${stringValue(event.activity)} in ${formatSeconds(event.delaySeconds)}` };
37125
37224
  }
37126
37225
  if (event.type === "error") {
37226
+ const relativePath = event.relativePath || event.path ? agentLabel(event) : "";
37227
+ const timestamp = event.timestamp || (/* @__PURE__ */ new Date()).toISOString();
37228
+ const hasAgentScope = Boolean(relativePath);
37127
37229
  return {
37128
37230
  ...next,
37231
+ agentLogs: hasAgentScope ? appendAgentLog(state.agentLogs, event) : state.agentLogs,
37232
+ agentRunState: hasAgentScope ? setAgentRunState(state.agentRunState, relativePath, {
37233
+ status: "error",
37234
+ runId: stringValue(event.runId || event.deployRequestId),
37235
+ jobType: stringValue(event.jobType || "run"),
37236
+ completedAt: timestamp,
37237
+ lastEventAt: timestamp,
37238
+ errorMessage: stringValue(event.error || event.code || "Unknown error")
37239
+ }) : state.agentRunState,
37129
37240
  activity: "Error",
37130
37241
  errors: appendError(state.errors, jobLabel(event), event.error || event.code || "Unknown error")
37131
37242
  };
@@ -37140,6 +37251,24 @@ function applyServeEvent(state, event) {
37140
37251
  jobsRun: Number(event.jobsRun ?? state.jobsRun)
37141
37252
  };
37142
37253
  }
37254
+ if (event.type === "run_log") {
37255
+ const entry = serveEventAgentLogEntry(event);
37256
+ const nextRunState = entry && entry.entry.eventType === "completion" ? setAgentRunState(state.agentRunState, entry.relativePath, {
37257
+ status: entry.entry.success === false ? "error" : "completed",
37258
+ runId: entry.entry.runId,
37259
+ completedAt: entry.entry.timestamp,
37260
+ lastEventAt: entry.entry.timestamp,
37261
+ errorMessage: entry.entry.success === false ? formatErrorMessage(entry.entry.error) : null
37262
+ }) : entry ? setAgentRunState(state.agentRunState, entry.relativePath, {
37263
+ runId: entry.entry.runId,
37264
+ lastEventAt: entry.entry.timestamp
37265
+ }) : state.agentRunState;
37266
+ return {
37267
+ ...next,
37268
+ agentLogs: appendAgentLog(state.agentLogs, event),
37269
+ agentRunState: nextRunState
37270
+ };
37271
+ }
37143
37272
  return next;
37144
37273
  }
37145
37274
  function agentSummary(agents = []) {
@@ -37161,9 +37290,254 @@ function agentSummary(agents = []) {
37161
37290
  function appendLog(logs, event) {
37162
37291
  return [...logs, event].slice(-200);
37163
37292
  }
37293
+ function appendAgentLog(agentLogs, event) {
37294
+ const normalized = serveEventAgentLogEntry(event);
37295
+ if (!normalized) {
37296
+ return agentLogs || {};
37297
+ }
37298
+ const key = normalized.relativePath;
37299
+ const existing = agentLogs[key] || [];
37300
+ return {
37301
+ ...agentLogs,
37302
+ [key]: [...existing, normalized.entry].slice(-5e3)
37303
+ };
37304
+ }
37305
+ function agentLogEntries(agentLogs, relativePath) {
37306
+ return (agentLogs || {})[relativePath] || [];
37307
+ }
37308
+ function mergeAgentLogRecords(agentLogs, records = []) {
37309
+ let next = agentLogs || {};
37310
+ for (const record of records) {
37311
+ if (!record || typeof record !== "object") continue;
37312
+ const relativePath = stringValue(record.relativePath || ".");
37313
+ const entry = record.entry && typeof record.entry === "object" ? record.entry : record;
37314
+ const existing = next[relativePath] || [];
37315
+ next = {
37316
+ ...next,
37317
+ [relativePath]: [...existing, normalizeStoredAgentLogEntry(entry)].slice(-5e3)
37318
+ };
37319
+ }
37320
+ return next;
37321
+ }
37322
+ function agentRunStateFromLogs(agentLogs, { includeRunning = true } = {}) {
37323
+ let next = {};
37324
+ for (const [relativePath, entries] of Object.entries(agentLogs || {})) {
37325
+ for (const entry of entries || []) {
37326
+ const timestamp = entry.timestamp || (/* @__PURE__ */ new Date()).toISOString();
37327
+ const runId = stringValue(entry.runId);
37328
+ const current = next[relativePath] || {};
37329
+ if (entry.eventType === "job_started") {
37330
+ next = setAgentRunState(next, relativePath, {
37331
+ status: includeRunning ? "running" : current.status,
37332
+ runId,
37333
+ jobType: entry.jobType ? stringValue(entry.jobType) : current.jobType,
37334
+ startedAt: timestamp,
37335
+ lastEventAt: timestamp
37336
+ });
37337
+ continue;
37338
+ }
37339
+ if (entry.eventType === "completion" || entry.eventType === "job_completed") {
37340
+ const failed = entry.success === false || current.status === "error" && current.runId === runId;
37341
+ next = setAgentRunState(next, relativePath, {
37342
+ status: failed ? "error" : "completed",
37343
+ runId,
37344
+ jobType: entry.jobType ? stringValue(entry.jobType) : current.jobType,
37345
+ completedAt: timestamp,
37346
+ lastEventAt: timestamp,
37347
+ errorMessage: failed ? formatErrorMessage(entry.error) || current.errorMessage || stringValue(entry.message || "Job failed") : null
37348
+ });
37349
+ continue;
37350
+ }
37351
+ if (entry.eventType === "error" || entry.level === "error" && entry.success === false) {
37352
+ next = setAgentRunState(next, relativePath, {
37353
+ status: "error",
37354
+ runId,
37355
+ jobType: entry.jobType ? stringValue(entry.jobType) : current.jobType,
37356
+ completedAt: timestamp,
37357
+ lastEventAt: timestamp,
37358
+ errorMessage: formatErrorMessage(entry.error) || stringValue(entry.message || "Job failed")
37359
+ });
37360
+ continue;
37361
+ }
37362
+ if (runId || timestamp) {
37363
+ next = setAgentRunState(next, relativePath, {
37364
+ runId: runId || current.runId,
37365
+ lastEventAt: timestamp
37366
+ });
37367
+ }
37368
+ }
37369
+ }
37370
+ return next;
37371
+ }
37372
+ function serveEventAgentLogEntry(event) {
37373
+ if (!event || typeof event !== "object") {
37374
+ return null;
37375
+ }
37376
+ const type = stringValue(event.type);
37377
+ const relativePath = stringValue(event.relativePath || event.path || ".");
37378
+ const timestamp = event.timestamp || (/* @__PURE__ */ new Date()).toISOString();
37379
+ if (type === "run_log") {
37380
+ return {
37381
+ relativePath,
37382
+ entry: {
37383
+ timestamp,
37384
+ runId: stringValue(event.runId),
37385
+ eventType: stringValue(event.eventType || "log"),
37386
+ level: stringValue(event.level || "info"),
37387
+ message: stringValue(event.message),
37388
+ stepId: event.stepId ? stringValue(event.stepId) : null,
37389
+ current: event.current ?? null,
37390
+ total: event.total ?? null,
37391
+ success: event.success ?? null,
37392
+ error: event.error ?? null,
37393
+ jobType: event.jobType ? stringValue(event.jobType) : null
37394
+ }
37395
+ };
37396
+ }
37397
+ if (type === "job_started") {
37398
+ const jobType = stringValue(event.jobType || "run");
37399
+ return {
37400
+ relativePath,
37401
+ entry: {
37402
+ timestamp,
37403
+ runId: stringValue(event.runId || event.deployRequestId),
37404
+ eventType: "job_started",
37405
+ level: "info",
37406
+ message: `Started ${jobType}`,
37407
+ stepId: null,
37408
+ current: null,
37409
+ total: null,
37410
+ success: null,
37411
+ error: null,
37412
+ jobType
37413
+ }
37414
+ };
37415
+ }
37416
+ if (type === "job_completed") {
37417
+ const success = event.success !== false;
37418
+ const jobType = stringValue(event.jobType || "run");
37419
+ return {
37420
+ relativePath,
37421
+ entry: {
37422
+ timestamp,
37423
+ runId: stringValue(event.runId || event.deployRequestId),
37424
+ eventType: "job_completed",
37425
+ level: success ? "info" : "error",
37426
+ message: success ? "Completed successfully" : stringValue(event.error || "Failed"),
37427
+ stepId: null,
37428
+ current: null,
37429
+ total: null,
37430
+ success,
37431
+ error: event.error ?? null,
37432
+ jobType
37433
+ }
37434
+ };
37435
+ }
37436
+ if (type === "error" && (event.relativePath || event.path)) {
37437
+ return {
37438
+ relativePath,
37439
+ entry: {
37440
+ timestamp,
37441
+ runId: stringValue(event.runId || event.deployRequestId),
37442
+ eventType: "error",
37443
+ level: "error",
37444
+ message: stringValue(event.error || event.code || "Unknown error"),
37445
+ stepId: null,
37446
+ current: null,
37447
+ total: null,
37448
+ success: false,
37449
+ error: event.error ?? event.code ?? null,
37450
+ jobType: event.jobType ? stringValue(event.jobType) : null
37451
+ }
37452
+ };
37453
+ }
37454
+ return null;
37455
+ }
37456
+ function agentRuntimeStatus(agent, agentRunState = {}) {
37457
+ const path8 = stringValue(agent?.relativePath || ".");
37458
+ const run2 = (agentRunState || {})[path8];
37459
+ const inventoryStatus = stringValue(agent?.status);
37460
+ if (run2?.status === "running") {
37461
+ return { status: "running", label: "running", run: run2 };
37462
+ }
37463
+ if (run2?.status === "error" || inventoryStatus === "error") {
37464
+ return { status: "error", label: "error", run: run2 };
37465
+ }
37466
+ if (inventoryStatus === "online") {
37467
+ return { status: run2?.status === "completed" ? "completed" : "ready", label: "ready", run: run2 };
37468
+ }
37469
+ if (run2?.status === "completed") {
37470
+ return { status: "completed", label: "ready", run: run2 };
37471
+ }
37472
+ return { status: inventoryStatus || "unknown", label: inventoryStatus || "unknown", run: run2 };
37473
+ }
37164
37474
  function appendError(errors, scope, message) {
37165
37475
  return [...errors, { scope, message: stringValue(message) }].slice(-20);
37166
37476
  }
37477
+ function setAgentRunState(agentRunState, relativePath, patch) {
37478
+ const key = stringValue(relativePath || ".");
37479
+ return {
37480
+ ...agentRunState || {},
37481
+ [key]: {
37482
+ ...(agentRunState || {})[key] || {},
37483
+ ...patch
37484
+ }
37485
+ };
37486
+ }
37487
+ function reconcileInventoryRunState(agentRunState, agents, timestamp) {
37488
+ let next = agentRunState || {};
37489
+ for (const agent of agents || []) {
37490
+ const path8 = stringValue(agent?.relativePath || ".");
37491
+ const current = next[path8];
37492
+ if (agent?.status === "error") {
37493
+ next = setAgentRunState(next, path8, {
37494
+ status: "error",
37495
+ lastEventAt: timestamp || (/* @__PURE__ */ new Date()).toISOString(),
37496
+ errorMessage: stringValue(agent.errorMessage || agent.error || "Agent setup failed")
37497
+ });
37498
+ } else if (agent?.status === "online" && current?.status === "error" && !current?.runId) {
37499
+ next = setAgentRunState(next, path8, {
37500
+ status: "ready",
37501
+ lastEventAt: timestamp || (/* @__PURE__ */ new Date()).toISOString(),
37502
+ errorMessage: null
37503
+ });
37504
+ }
37505
+ }
37506
+ return next;
37507
+ }
37508
+ function normalizeStoredAgentLogEntry(entry) {
37509
+ return {
37510
+ timestamp: entry.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
37511
+ runId: stringValue(entry.runId),
37512
+ eventType: stringValue(entry.eventType || "log"),
37513
+ level: stringValue(entry.level || "info"),
37514
+ message: stringValue(entry.message),
37515
+ stepId: entry.stepId ? stringValue(entry.stepId) : null,
37516
+ current: entry.current ?? null,
37517
+ total: entry.total ?? null,
37518
+ success: entry.success ?? null,
37519
+ error: entry.error ?? null,
37520
+ jobType: entry.jobType ? stringValue(entry.jobType) : null
37521
+ };
37522
+ }
37523
+ function formatErrorMessage(error) {
37524
+ if (!error) return null;
37525
+ if (typeof error === "object") return stringValue(error.message || "error");
37526
+ return stringValue(error);
37527
+ }
37528
+ function findNewAgents(previous, next) {
37529
+ const seen = new Set(previous.map(agentKey).filter(Boolean));
37530
+ return next.filter((agent) => {
37531
+ const key = agentKey(agent);
37532
+ return key && !seen.has(key);
37533
+ }).slice(0, 8);
37534
+ }
37535
+ function agentKey(agent) {
37536
+ if (!agent || typeof agent !== "object") {
37537
+ return "";
37538
+ }
37539
+ return stringValue(agent.localAgentId || agent.relativePath || agent.path || agent.manifestName || agent.id);
37540
+ }
37167
37541
  function agentLabel(event) {
37168
37542
  return stringValue(event.relativePath || event.path || ".");
37169
37543
  }
@@ -37182,27 +37556,260 @@ function formatSeconds(value) {
37182
37556
  import { spawn as spawn2 } from "node:child_process";
37183
37557
  async function spawnLocalServeEventStream({
37184
37558
  agentsDir = "./agents",
37559
+ collectionId = "",
37185
37560
  env: env3 = process.env,
37186
37561
  platform: platform2 = process.platform
37187
37562
  } = {}) {
37188
- const invocation = await preparePythonVendianInvocation([
37563
+ const invocation = await preparePythonVendianInvocation(buildLocalServeEventStreamArgs({ agentsDir, collectionId }), { env: env3, platform: platform2 });
37564
+ return spawn2(invocation.command, invocation.args, {
37565
+ env: invocation.env,
37566
+ stdio: ["ignore", "pipe", "pipe"],
37567
+ shell: false
37568
+ });
37569
+ }
37570
+ function buildLocalServeEventStreamArgs({ agentsDir = "./agents", collectionId = "" } = {}) {
37571
+ const args = [
37189
37572
  "cloud",
37190
37573
  "local",
37191
37574
  "serve",
37192
37575
  "--agents-dir",
37193
37576
  agentsDir || "./agents",
37194
37577
  "--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
- });
37578
+ ];
37579
+ if (collectionId) {
37580
+ args.push("--collection-id", collectionId);
37581
+ }
37582
+ return args;
37201
37583
  }
37202
37584
 
37585
+ // src/serve-log-store.js
37586
+ import crypto2 from "node:crypto";
37587
+ import fs10 from "node:fs";
37588
+ import path7 from "node:path";
37589
+ var MAX_EVENTS = 5e3;
37590
+ var MAX_RUNS = 50;
37591
+ function createServeLogStore({
37592
+ agentsDir = "./agents",
37593
+ collectionId = "",
37594
+ env: env3 = process.env,
37595
+ platform: platform2 = process.platform
37596
+ } = {}) {
37597
+ const file = serveLogFilePath({ agentsDir, collectionId, env: env3, platform: platform2 });
37598
+ let appendCount = 0;
37599
+ return {
37600
+ file,
37601
+ load() {
37602
+ return readServeLogRecords(file);
37603
+ },
37604
+ append(event) {
37605
+ const normalized = serveEventAgentLogEntry(event);
37606
+ if (!normalized) {
37607
+ return false;
37608
+ }
37609
+ fs10.mkdirSync(path7.dirname(file), { recursive: true });
37610
+ const record = {
37611
+ version: 1,
37612
+ collectionId: collectionId || "",
37613
+ agentsDir: String(agentsDir || "./agents"),
37614
+ relativePath: normalized.relativePath,
37615
+ entry: normalized.entry
37616
+ };
37617
+ fs10.appendFileSync(file, `${JSON.stringify(record)}
37618
+ `, "utf8");
37619
+ appendCount += 1;
37620
+ if (appendCount % 50 === 0) {
37621
+ compactServeLogFile(file);
37622
+ }
37623
+ return true;
37624
+ },
37625
+ compact() {
37626
+ compactServeLogFile(file);
37627
+ }
37628
+ };
37629
+ }
37630
+ function serveLogFilePath({
37631
+ agentsDir = "./agents",
37632
+ collectionId = "",
37633
+ env: env3 = process.env,
37634
+ platform: platform2 = process.platform
37635
+ } = {}) {
37636
+ const root = vendianHome(env3, platform2);
37637
+ const resolvedAgentsDir = path7.resolve(String(agentsDir || "./agents"));
37638
+ const hash = crypto2.createHash("sha256").update(`${collectionId || "default"}\0${resolvedAgentsDir}`).digest("hex").slice(0, 16);
37639
+ return path7.join(root, "serve-logs", `${safePathPart(collectionId || "default")}-${hash}.jsonl`);
37640
+ }
37641
+ function readServeLogRecords(file) {
37642
+ if (!file || !fs10.existsSync(file)) {
37643
+ return [];
37644
+ }
37645
+ const records = [];
37646
+ const lines = fs10.readFileSync(file, "utf8").split(/\r?\n/);
37647
+ for (const line of lines) {
37648
+ const value = line.trim();
37649
+ if (!value) continue;
37650
+ try {
37651
+ const record = JSON.parse(value);
37652
+ if (record && typeof record === "object" && record.entry && record.relativePath) {
37653
+ records.push({ relativePath: String(record.relativePath), entry: record.entry });
37654
+ }
37655
+ } catch {
37656
+ }
37657
+ }
37658
+ return trimServeLogRecords(records);
37659
+ }
37660
+ function compactServeLogFile(file) {
37661
+ if (!file || !fs10.existsSync(file)) {
37662
+ return;
37663
+ }
37664
+ const records = readServeLogRecords(file);
37665
+ const tmp = `${file}.tmp`;
37666
+ fs10.writeFileSync(
37667
+ tmp,
37668
+ records.map((record) => JSON.stringify({ version: 1, ...record })).join("\n") + (records.length ? "\n" : ""),
37669
+ "utf8"
37670
+ );
37671
+ fs10.renameSync(tmp, file);
37672
+ }
37673
+ function trimServeLogRecords(records) {
37674
+ const runIds = [];
37675
+ const seen = /* @__PURE__ */ new Set();
37676
+ for (const record of records) {
37677
+ const runId = String(record.entry?.runId || "");
37678
+ if (runId && !seen.has(runId)) {
37679
+ seen.add(runId);
37680
+ runIds.push(runId);
37681
+ }
37682
+ }
37683
+ const keepRuns = new Set(runIds.slice(-MAX_RUNS));
37684
+ return records.filter((record) => {
37685
+ const runId = String(record.entry?.runId || "");
37686
+ return !runId || keepRuns.has(runId);
37687
+ }).slice(-MAX_EVENTS);
37688
+ }
37689
+ function safePathPart(value) {
37690
+ return String(value || "default").replace(/[^a-zA-Z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "default";
37691
+ }
37692
+
37693
+ // src/workspaces.js
37694
+ async function listCloudWorkspaces({
37695
+ env: env3 = process.env,
37696
+ platform: platform2 = process.platform,
37697
+ prepareInvocation = preparePythonVendianInvocation,
37698
+ run: run2 = runCapture
37699
+ } = {}) {
37700
+ const invocation = await prepareInvocation(["cloud", "collections", "list", "--json"], { env: env3, platform: platform2 });
37701
+ const result = run2(invocation.command, invocation.args, { env: invocation.env });
37702
+ if (!result.ok) {
37703
+ return {
37704
+ ok: false,
37705
+ workspaces: [],
37706
+ error: (result.stderr || result.stdout || `workspace list exited with code ${result.status}`).trim()
37707
+ };
37708
+ }
37709
+ try {
37710
+ return { ok: true, workspaces: normalizeWorkspaceList(JSON.parse(result.stdout)), error: "" };
37711
+ } catch (error) {
37712
+ const message = error && typeof error.message === "string" ? error.message : String(error);
37713
+ return { ok: false, workspaces: [], error: `Could not parse workspace list: ${message}` };
37714
+ }
37715
+ }
37716
+ function normalizeWorkspaceList(payload) {
37717
+ const data = payload && typeof payload === "object" && "data" in payload ? payload.data : payload;
37718
+ 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 : [];
37719
+ return raw.filter((item) => item && typeof item === "object").map((item) => ({
37720
+ id: stringValue2(item.id),
37721
+ name: stringValue2(item.name || item.slug || item.id || "Unnamed workspace"),
37722
+ slug: stringValue2(item.slug)
37723
+ })).filter((item) => item.id);
37724
+ }
37725
+ function workspaceLabel(workspace) {
37726
+ const name = workspace?.name || workspace?.slug || workspace?.id || "Unnamed workspace";
37727
+ const slug = workspace?.slug && workspace.slug !== name ? ` (${workspace.slug})` : "";
37728
+ return `${name}${slug}`;
37729
+ }
37730
+ function stringValue2(value) {
37731
+ return value == null ? "" : String(value).trim();
37732
+ }
37733
+
37734
+ // src/ui/theme.js
37735
+ var colors = {
37736
+ brand: "cyan",
37737
+ brandBold: "cyanBright",
37738
+ success: "green",
37739
+ error: "red",
37740
+ warning: "yellow",
37741
+ muted: "gray",
37742
+ accent: "blueBright",
37743
+ text: "white",
37744
+ dim: "gray"
37745
+ };
37746
+ var spacing = {
37747
+ labelWidth: 14,
37748
+ indent: 2
37749
+ };
37750
+
37751
+ // src/ui/figures.js
37752
+ var supportsUnicode = process.platform !== "win32" || Boolean(
37753
+ process.env.WT_SESSION || // Windows Terminal
37754
+ process.env.TERM_PROGRAM === "vscode" || // VS Code terminal
37755
+ process.env.TERM === "xterm-256color" || process.env.ConEmuTask || process.env.TERMINAL_EMULATOR
37756
+ );
37757
+ var unicode = {
37758
+ dot: "\u25CF",
37759
+ dotEmpty: "\u25CB",
37760
+ check: "\u2714",
37761
+ cross: "\u2716",
37762
+ warning: "\u26A0",
37763
+ arrow: "\u25B6",
37764
+ arrowRight: "\u203A",
37765
+ arrowUp: "\u25B2",
37766
+ dash: "\u2500",
37767
+ ellipsis: "\u2026",
37768
+ topLeft: "\u256D",
37769
+ topRight: "\u256E",
37770
+ bottomLeft: "\u2570",
37771
+ bottomRight: "\u256F",
37772
+ horizontal: "\u2500",
37773
+ vertical: "\u2502",
37774
+ teeRight: "\u251C",
37775
+ teeLeft: "\u2524",
37776
+ teeDown: "\u252C",
37777
+ teeUp: "\u2534",
37778
+ cross_join: "\u253C",
37779
+ bar: "\u2588",
37780
+ barEmpty: "\u2591"
37781
+ };
37782
+ var ascii = {
37783
+ dot: "*",
37784
+ dotEmpty: "o",
37785
+ check: "+",
37786
+ cross: "x",
37787
+ warning: "!",
37788
+ arrow: ">",
37789
+ arrowRight: ">",
37790
+ arrowUp: "^",
37791
+ dash: "-",
37792
+ ellipsis: "...",
37793
+ topLeft: "+",
37794
+ topRight: "+",
37795
+ bottomLeft: "+",
37796
+ bottomRight: "+",
37797
+ horizontal: "-",
37798
+ vertical: "|",
37799
+ teeRight: "+",
37800
+ teeLeft: "+",
37801
+ teeDown: "+",
37802
+ teeUp: "+",
37803
+ cross_join: "+",
37804
+ bar: "#",
37805
+ barEmpty: "."
37806
+ };
37807
+ var fig = supportsUnicode ? unicode : ascii;
37808
+
37203
37809
  // src/tui.js
37204
37810
  var h;
37205
37811
  var useEffect6;
37812
+ var useRef2;
37206
37813
  var useState5;
37207
37814
  var Box2;
37208
37815
  var Text2;
@@ -37219,15 +37826,25 @@ var ENDPOINTS = [
37219
37826
  { key: "prod", label: "Production" }
37220
37827
  ];
37221
37828
  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" }
37829
+ { value: "connect", label: "Connect Endpoint", desc: "Switch between environments" },
37830
+ { value: "init", label: "Initialize Docs", desc: "Set up SDK documentation workspace" },
37831
+ { value: "create", label: "Create Agent", desc: "Scaffold a new agent from templates" },
37832
+ { value: "serve", label: "Serve Agents", desc: "Start local development server" },
37833
+ { value: "doctor", label: "Doctor", desc: "Check system health" },
37834
+ { value: "update", label: "Update", desc: "Update runtime & packages" },
37835
+ { value: "commands", label: "Commands", desc: "Show CLI command reference" },
37836
+ { value: "exit", label: "Exit", desc: "Close interactive shell" }
37230
37837
  ];
37838
+ var SCREEN_TITLES = {
37839
+ home: "Home",
37840
+ connect: "Connect Endpoint",
37841
+ init: "Initialize Docs",
37842
+ create: "Create Agent",
37843
+ serve: "Serve Agents",
37844
+ doctor: "Doctor",
37845
+ update: "Update",
37846
+ commands: "Commands"
37847
+ };
37231
37848
  var COMMANDS = [
37232
37849
  "vendian login",
37233
37850
  "vendian init --output-dir ./agents",
@@ -37247,6 +37864,7 @@ async function runTui({ env: env3 = process.env, platform: platform2 = process.p
37247
37864
  `);
37248
37865
  return;
37249
37866
  }
37867
+ await refreshInteractiveManagedRuntime({ env: env3, platform: platform2, output });
37250
37868
  await loadInkRuntime();
37251
37869
  const app = renderInk(h(VendianShell, { env: env3, platform: platform2, input, output }), {
37252
37870
  stdin: input,
@@ -37255,6 +37873,25 @@ async function runTui({ env: env3 = process.env, platform: platform2 = process.p
37255
37873
  });
37256
37874
  await app.waitUntilExit();
37257
37875
  }
37876
+ async function refreshInteractiveManagedRuntime({
37877
+ env: env3 = process.env,
37878
+ platform: platform2 = process.platform,
37879
+ output = process.stderr,
37880
+ refreshPackageAccess = refreshPackageAccessFromCloudAuth,
37881
+ autoUpdate = maybeAutoUpdateManagedEnv
37882
+ } = {}) {
37883
+ if (env3.VENDIAN_SKIP_AUTO_UPDATE === "1") {
37884
+ return false;
37885
+ }
37886
+ try {
37887
+ await refreshPackageAccess({ env: env3, platform: platform2 });
37888
+ } catch (error) {
37889
+ const message = errorMessage2(error);
37890
+ output.write(`[vendian] Package access refresh failed; continuing with installed runtime. ${message}
37891
+ `);
37892
+ }
37893
+ return autoUpdate({ env: env3, platform: platform2, force: true });
37894
+ }
37258
37895
  async function loadInkRuntime() {
37259
37896
  if (h) {
37260
37897
  return;
@@ -37268,6 +37905,7 @@ async function loadInkRuntime() {
37268
37905
  ]);
37269
37906
  h = reactModule.default.createElement;
37270
37907
  useEffect6 = reactModule.useEffect;
37908
+ useRef2 = reactModule.useRef;
37271
37909
  useState5 = reactModule.useState;
37272
37910
  Box2 = inkModule.Box;
37273
37911
  Text2 = inkModule.Text;
@@ -37278,11 +37916,25 @@ async function loadInkRuntime() {
37278
37916
  TextInput2 = textInputModule.default;
37279
37917
  Spinner2 = spinnerModule.default;
37280
37918
  }
37919
+ function endpointRows({ env: env3 = process.env, platform: platform2 = process.platform } = {}) {
37920
+ return ENDPOINTS.map((endpoint) => {
37921
+ const status = cloudAuthStatus({ backend: endpoint.key, env: env3, platform: platform2 });
37922
+ const active = status.activeApiUrl === status.apiUrl;
37923
+ return {
37924
+ key: endpoint.key,
37925
+ label: endpoint.label,
37926
+ apiUrl: status.apiUrl,
37927
+ active,
37928
+ status: status.authenticated ? active ? "connected" : "signed in" : "not signed in",
37929
+ detail: status.authenticated ? status.email || status.apiUrl : status.apiUrl
37930
+ };
37931
+ });
37932
+ }
37281
37933
  function runtimeSummary({ env: env3 = process.env, platform: platform2 = process.platform, now = Date.now() } = {}) {
37282
37934
  const config = loadConfig(env3, platform2);
37283
37935
  const venvPath = managedVenvPath(env3, platform2);
37284
37936
  const vendianPath = venvVendian(venvPath, platform2);
37285
- const installed = fs11.existsSync(vendianPath);
37937
+ const installed = fs12.existsSync(vendianPath);
37286
37938
  const lastUpdate = Date.parse(config.lastManagedUpdateAt || "");
37287
37939
  const stale = !Number.isFinite(lastUpdate) || now - lastUpdate > 24 * 60 * 60 * 1e3;
37288
37940
  return {
@@ -37298,37 +37950,92 @@ function packageAccessSummary({ env: env3 = process.env, platform: platform2 = p
37298
37950
  source: registry.tokenSource || "local config"
37299
37951
  };
37300
37952
  }
37953
+ function isCtrlCInput(input = "", key = {}) {
37954
+ return input === "" || input === "" || Boolean(key?.ctrl && (key.name === "c" || input === "c"));
37955
+ }
37301
37956
  function VendianShell({ env: env3, platform: platform2, input, output }) {
37302
37957
  const app = useApp2();
37303
37958
  const [screen, setScreen] = useState5("home");
37304
37959
  const [serveState, setServeState] = useState5(null);
37305
- useInput2((_, key) => {
37306
- if (key.ctrl && key.name === "c" && screen !== "serve") {
37960
+ const [exitArmed, setExitArmed] = useState5(false);
37961
+ const exitTimer = useRef2(null);
37962
+ const lastInterruptAt = useRef2(0);
37963
+ function armExit() {
37964
+ if (exitArmed) {
37307
37965
  app.exit();
37966
+ return;
37967
+ }
37968
+ setExitArmed(true);
37969
+ if (exitTimer.current) {
37970
+ clearTimeout(exitTimer.current);
37971
+ }
37972
+ exitTimer.current = setTimeout(() => setExitArmed(false), 1500);
37973
+ }
37974
+ function handleInterrupt() {
37975
+ const now = Date.now();
37976
+ if (now - lastInterruptAt.current < 75) {
37977
+ return;
37978
+ }
37979
+ lastInterruptAt.current = now;
37980
+ if (screen === "serve") {
37981
+ return;
37982
+ }
37983
+ armExit();
37984
+ }
37985
+ useEffect6(() => () => {
37986
+ if (exitTimer.current) {
37987
+ clearTimeout(exitTimer.current);
37988
+ }
37989
+ }, []);
37990
+ useEffect6(() => {
37991
+ const onData = (chunk) => {
37992
+ if (isCtrlCInput(String(chunk || ""))) {
37993
+ handleInterrupt();
37994
+ }
37995
+ };
37996
+ input?.on?.("data", onData);
37997
+ process.on("SIGINT", handleInterrupt);
37998
+ return () => {
37999
+ input?.off?.("data", onData);
38000
+ process.off("SIGINT", handleInterrupt);
38001
+ };
38002
+ }, [input, screen, exitArmed]);
38003
+ useInput2((typedInput, key) => {
38004
+ if (isCtrlCInput(typedInput, key)) {
38005
+ handleInterrupt();
37308
38006
  }
37309
38007
  });
37310
38008
  const goHome = () => setScreen("home");
38009
+ const breadcrumb = screen !== "home" ? ["Home", SCREEN_TITLES[screen] || screen] : ["Home"];
37311
38010
  return h(
37312
38011
  Box2,
37313
38012
  { flexDirection: "column" },
37314
- h(Header, { env: env3, platform: platform2, serveState }),
38013
+ h(Header, { env: env3, platform: platform2, serveState, screen }),
38014
+ h(
38015
+ Text2,
38016
+ { color: colors.muted },
38017
+ breadcrumb.map(
38018
+ (part, i) => i < breadcrumb.length - 1 ? `${part} ${fig.arrowRight} ` : part
38019
+ ).join("")
38020
+ ),
38021
+ h(Text2, null, ""),
37315
38022
  screen === "home" && h(HomeScreen, { onSelect: (value) => value === "exit" ? app.exit() : setScreen(value) }),
37316
38023
  screen === "connect" && h(ConnectScreen, { env: env3, platform: platform2, onBack: goHome }),
37317
38024
  screen === "init" && h(InitScreen, { env: env3, platform: platform2, onBack: goHome }),
37318
38025
  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 }),
38026
+ screen === "serve" && h(ServeScreen, { env: env3, platform: platform2, input, output, onBack: goHome, onState: setServeState, onExitApp: app.exit }),
37320
38027
  screen === "doctor" && h(DoctorScreen, { env: env3, platform: platform2, onBack: goHome }),
37321
38028
  screen === "update" && h(UpdateScreen, { env: env3, platform: platform2, onBack: goHome }),
37322
- screen === "commands" && h(CommandsScreen, { onBack: goHome })
38029
+ screen === "commands" && h(CommandsScreen, { onBack: goHome }),
38030
+ exitArmed && screen !== "serve" && h(Text2, { color: colors.warning, bold: true }, " Press Ctrl+C again to exit.")
37323
38031
  );
37324
38032
  }
37325
- function Header({ env: env3, platform: platform2, serveState }) {
38033
+ function Header({ env: env3, platform: platform2, serveState, screen }) {
37326
38034
  const active = activeCloudAuthStatus({ env: env3, platform: platform2 });
37327
38035
  const runtime = runtimeSummary({ env: env3, platform: platform2 });
37328
38036
  const [npmUpdate, setNpmUpdate] = useState5(() => npmPackageUpdateSummary({ config: loadConfig(env3, platform2) }));
37329
38037
  const [npmCheckStarted, setNpmCheckStarted] = useState5(false);
37330
38038
  const pkg = packageAccessSummary({ env: env3, platform: platform2 });
37331
- const daemon = serveState?.daemonId ? `Daemon ${serveState.daemonId} | ${serveState.activity}` : "Daemon idle";
37332
38039
  useEffect6(() => {
37333
38040
  let cancelled = false;
37334
38041
  if (!npmUpdate.stale || npmCheckStarted) {
@@ -37344,29 +38051,111 @@ function Header({ env: env3, platform: platform2, serveState }) {
37344
38051
  cancelled = true;
37345
38052
  };
37346
38053
  }, [env3, platform2, npmCheckStarted, npmUpdate.stale]);
38054
+ const connStatus = active.authenticated ? "ok" : "error";
38055
+ const connValue = active.authenticated ? `${active.email || "Authenticated"} ${fig.dash} ${active.apiUrl}` : "Not connected \u2014 run vendian login";
38056
+ const rtStatus = runtime.installed ? "ok" : "error";
38057
+ const rtValue = runtime.installed ? "Ready" : "Missing \u2014 run vendian login";
38058
+ const pkgStatus = pkg.configured ? "ok" : "warning";
38059
+ const pkgValue = pkg.configured ? `Configured (${pkg.source})` : "Missing";
38060
+ const updValue = updateLabel({ runtime, npmUpdate });
38061
+ const updStatus = updValue.includes("available") ? "warning" : "ok";
38062
+ const w = Math.min(process.stdout.columns || 80, 90);
38063
+ const inner = w - 2;
38064
+ const titleLeft = `${fig.vertical} ${fig.arrowUp} VENDIAN`;
38065
+ const titleRight = `v${CLI_VERSION} ${fig.vertical}`;
38066
+ const titlePad = Math.max(0, inner - titleLeft.length - titleRight.length + 2);
37347
38067
  return h(
37348
38068
  Box2,
37349
38069
  { 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)
38070
+ h(Text2, { color: colors.muted }, `${fig.topLeft}${fig.horizontal.repeat(inner)}${fig.topRight}`),
38071
+ h(
38072
+ Text2,
38073
+ null,
38074
+ h(Text2, { color: colors.muted }, fig.vertical),
38075
+ h(Text2, { bold: true, color: colors.brand }, ` ${fig.arrowUp} VENDIAN`),
38076
+ h(Text2, null, " ".repeat(titlePad)),
38077
+ h(Text2, { color: colors.muted }, `v${CLI_VERSION} `),
38078
+ h(Text2, { color: colors.muted }, fig.vertical)
38079
+ ),
38080
+ h(Text2, { color: colors.muted }, `${fig.teeRight}${fig.horizontal.repeat(inner)}${fig.teeLeft}`),
38081
+ h(StatusRow, { status: connStatus, label: "Endpoint", value: connValue, width: inner }),
38082
+ h(StatusRow, { status: rtStatus, label: "Runtime", value: rtValue, width: inner }),
38083
+ h(StatusRow, { status: pkgStatus, label: "Packages", value: pkgValue, width: inner }),
38084
+ h(StatusRow, { status: updStatus, label: "Updates", value: updValue, width: inner }),
38085
+ serveState?.daemonId && h(StatusRow, {
38086
+ status: serveState.connected ? "ok" : "warning",
38087
+ label: "Daemon",
38088
+ value: serveState.activity || "Running",
38089
+ width: inner
38090
+ }),
38091
+ h(Text2, { color: colors.muted }, `${fig.bottomLeft}${fig.horizontal.repeat(inner)}${fig.bottomRight}`)
38092
+ );
38093
+ }
38094
+ function StatusRow({ status, label, value, width }) {
38095
+ const dotColor = status === "ok" ? colors.success : status === "warning" ? colors.warning : colors.error;
38096
+ const dot = status === "ok" ? fig.dot : status === "warning" ? fig.dot : fig.dot;
38097
+ const paddedLabel = String(label).padEnd(spacing.labelWidth);
38098
+ const maxVal = Math.max(0, (width || 70) - spacing.labelWidth - 6);
38099
+ const clipped = clip(value || "", maxVal);
38100
+ return h(
38101
+ Text2,
38102
+ null,
38103
+ h(Text2, { color: colors.muted }, `${fig.vertical} `),
38104
+ h(Text2, { color: dotColor }, `${dot} `),
38105
+ h(Text2, { bold: true }, paddedLabel),
38106
+ h(Text2, null, clipped),
38107
+ h(Text2, { color: colors.muted }, ` ${fig.vertical}`)
37354
38108
  );
37355
38109
  }
37356
38110
  function HomeScreen({ onSelect }) {
38111
+ const [activeIndex, setActiveIndex] = useState5(0);
38112
+ useInput2((input, key) => {
38113
+ if (key.upArrow) {
38114
+ setActiveIndex((i) => (i - 1 + ACTIONS.length) % ACTIONS.length);
38115
+ } else if (key.downArrow) {
38116
+ setActiveIndex((i) => (i + 1) % ACTIONS.length);
38117
+ } else if (key.return) {
38118
+ onSelect(ACTIONS[activeIndex].value);
38119
+ }
38120
+ });
37357
38121
  return h(
37358
38122
  Box2,
37359
38123
  { flexDirection: "column" },
37360
- h(Text2, { bold: true }, "Home"),
37361
- h(SelectInput2, { items: ACTIONS, onSelect: (item) => onSelect(item.value) })
38124
+ ...ACTIONS.map((action, i) => {
38125
+ const isActive = i === activeIndex;
38126
+ const isSeparator = action.value === "doctor";
38127
+ return h(
38128
+ Box2,
38129
+ { key: action.value, flexDirection: "column" },
38130
+ isSeparator && h(Text2, { color: colors.muted }, ` ${fig.horizontal.repeat(40)}`),
38131
+ h(
38132
+ Text2,
38133
+ null,
38134
+ h(Text2, { color: isActive ? colors.accent : colors.muted }, isActive ? ` ${fig.arrow} ` : " "),
38135
+ h(Text2, { bold: isActive, color: isActive ? colors.text : colors.muted }, action.label.padEnd(22)),
38136
+ h(Text2, { color: colors.dim }, action.desc)
38137
+ )
38138
+ );
38139
+ }),
38140
+ h(Text2, null, ""),
38141
+ h(FooterBar, { items: [
38142
+ { key: "\u2191\u2193", label: "Navigate" },
38143
+ { key: "\u23CE", label: "Select" },
38144
+ { key: "^c ^c", label: "Exit" }
38145
+ ] })
37362
38146
  );
37363
38147
  }
37364
38148
  function ConnectScreen({ env: env3, platform: platform2, onBack }) {
37365
38149
  const [mode, setMode] = useState5("select");
37366
38150
  const [apiUrl, setApiUrl] = useState5("");
37367
38151
  const [status, setStatus] = useState5("");
38152
+ const rows = endpointRows({ env: env3, platform: platform2 });
37368
38153
  const items = [
37369
- ...ENDPOINTS.map((endpoint) => ({ label: `${endpoint.label} ${BACKEND_TARGETS[endpoint.key]}`, value: endpoint.key })),
38154
+ ...ENDPOINTS.map((endpoint) => {
38155
+ const row = rows.find((r) => r.key === endpoint.key);
38156
+ const statusLabel = row ? row.active ? "\u25CF Connected" : row.status : "";
38157
+ return { label: `${endpoint.label.padEnd(12)} ${BACKEND_TARGETS[endpoint.key]} ${statusLabel}`, value: endpoint.key };
38158
+ }),
37370
38159
  { label: "Custom API URL", value: "custom" },
37371
38160
  { label: "Back", value: "back" }
37372
38161
  ];
@@ -37379,41 +38168,47 @@ function ConnectScreen({ env: env3, platform: platform2, onBack }) {
37379
38168
  setMode("custom");
37380
38169
  return;
37381
38170
  }
37382
- setStatus(`Connecting ${value}`);
38171
+ setStatus(`Connecting to ${value}...`);
37383
38172
  await setup({ backend: value, forceAuth: true, env: env3, platform: platform2 });
37384
- setStatus("Connected");
38173
+ setStatus(`${fig.check} Connected`);
37385
38174
  }
37386
38175
  if (mode === "custom") {
37387
38176
  return h(
37388
38177
  Box2,
37389
38178
  { flexDirection: "column" },
37390
- h(Text2, { bold: true }, "Connect Endpoint"),
38179
+ h(Text2, { bold: true }, " Enter custom API URL:"),
38180
+ h(Text2, null, ""),
37391
38181
  h(TextInput2, {
37392
38182
  value: apiUrl,
37393
38183
  placeholder: "https://api.example.test",
37394
38184
  onChange: setApiUrl,
37395
38185
  onSubmit: async (value) => {
37396
38186
  if (!value) return;
37397
- setStatus("Connecting custom endpoint");
38187
+ setStatus("Connecting...");
37398
38188
  await setup({ apiUrl: value, forceAuth: true, env: env3, platform: platform2 });
37399
- setStatus("Connected");
38189
+ setStatus(`${fig.check} Connected`);
37400
38190
  }
37401
38191
  }),
37402
- h(Text2, null, status),
38192
+ status && h(Text2, { color: status.includes(fig.check) ? colors.success : colors.muted }, ` ${status}`),
38193
+ h(Text2, null, ""),
38194
+ h(FooterBar, { items: [{ key: "\u23CE", label: "Submit" }, { key: "esc", label: "Back" }] }),
37403
38195
  h(BackHint, { onBack })
37404
38196
  );
37405
38197
  }
37406
38198
  return h(
37407
38199
  Box2,
37408
38200
  { flexDirection: "column" },
37409
- h(Text2, { bold: true }, "Connect Endpoint"),
38201
+ h(Text2, { bold: true }, " Select environment:"),
38202
+ h(Text2, null, ""),
37410
38203
  h(SelectInput2, { items, onSelect: (item) => connect(item.value) }),
37411
- status && h(Text2, null, status)
38204
+ status && h(Text2, { color: status.includes(fig.check) ? colors.success : colors.muted }, ` ${status}`),
38205
+ h(Text2, null, ""),
38206
+ h(FooterBar, { items: [{ key: "\u2191\u2193", label: "Navigate" }, { key: "\u23CE", label: "Select" }, { key: "esc", label: "Back" }] })
37412
38207
  );
37413
38208
  }
37414
38209
  function InitScreen({ env: env3, platform: platform2, onBack }) {
37415
38210
  return h(CommandPromptScreen, {
37416
- title: "Initialize Docs",
38211
+ title: " Initialize Docs",
37417
38212
  label: "Workspace directory",
37418
38213
  initialValue: ".",
37419
38214
  onBack,
@@ -37453,53 +38248,180 @@ function CreateScreen({ env: env3, platform: platform2, onBack }) {
37453
38248
  h(BackHint, { onBack })
37454
38249
  );
37455
38250
  }
37456
- function ServeScreen({ env: env3, platform: platform2, onBack, onState }) {
38251
+ function ServeScreen({ env: env3, platform: platform2, input, onBack, onState, onExitApp }) {
37457
38252
  const [agentDirCandidates] = useState5(() => findAgentDirectoryCandidates());
37458
38253
  const [agentsDir, setAgentsDir] = useState5(() => defaultAgentsDir(agentDirCandidates));
37459
38254
  const [selectingDir, setSelectingDir] = useState5(() => agentDirCandidates.length > 0);
38255
+ const [workspaceChoices, setWorkspaceChoices] = useState5([]);
38256
+ const [pendingAgentsDir, setPendingAgentsDir] = useState5("");
38257
+ const [loadingWorkspaces, setLoadingWorkspaces] = useState5(false);
37460
38258
  const [started, setStarted] = useState5(false);
37461
38259
  const [state, setState] = useState5(initialServeState());
37462
38260
  const [child, setChild] = useState5(null);
37463
- const [showLogs, setShowLogs] = useState5(false);
38261
+ const [logMode, setLogMode] = useState5(null);
37464
38262
  const [confirmExit, setConfirmExit] = useState5(false);
38263
+ const [exitArmed, setExitArmed] = useState5(false);
37465
38264
  const [startupError, setStartupError] = useState5("");
38265
+ const exitTimer = useRef2(null);
38266
+ const lastInterruptAt = useRef2(0);
37466
38267
  useEffect6(() => {
37467
38268
  onState(state);
37468
38269
  }, [state, onState]);
37469
- useInput2((input, key) => {
38270
+ useEffect6(() => () => {
38271
+ if (exitTimer.current) {
38272
+ clearTimeout(exitTimer.current);
38273
+ }
38274
+ }, []);
38275
+ function armExit() {
38276
+ if (exitArmed) {
38277
+ child?.kill("SIGINT");
38278
+ onState(null);
38279
+ onExitApp();
38280
+ return;
38281
+ }
38282
+ setExitArmed(true);
38283
+ setConfirmExit(false);
38284
+ if (exitTimer.current) {
38285
+ clearTimeout(exitTimer.current);
38286
+ }
38287
+ exitTimer.current = setTimeout(() => setExitArmed(false), 1500);
38288
+ }
38289
+ function handleInterrupt() {
38290
+ const now = Date.now();
38291
+ if (now - lastInterruptAt.current < 75) {
38292
+ return;
38293
+ }
38294
+ lastInterruptAt.current = now;
38295
+ armExit();
38296
+ }
38297
+ useEffect6(() => {
38298
+ const onData = (chunk) => {
38299
+ if (isCtrlCInput(String(chunk || ""))) {
38300
+ handleInterrupt();
38301
+ }
38302
+ };
38303
+ input?.on?.("data", onData);
38304
+ process.on("SIGINT", handleInterrupt);
38305
+ return () => {
38306
+ input?.off?.("data", onData);
38307
+ process.off("SIGINT", handleInterrupt);
38308
+ };
38309
+ }, [input, exitArmed, child]);
38310
+ useInput2((input2, key) => {
38311
+ if (isCtrlCInput(input2, key)) {
38312
+ handleInterrupt();
38313
+ return;
38314
+ }
37470
38315
  if (!started) return;
38316
+ if (logMode !== null) {
38317
+ if (key.escape) {
38318
+ if (logMode === "picker") {
38319
+ setLogMode(null);
38320
+ } else {
38321
+ setLogMode("picker");
38322
+ }
38323
+ }
38324
+ return;
38325
+ }
37471
38326
  if (confirmExit) {
37472
- if (input.toLowerCase() === "y" || key.return) {
38327
+ if (input2.toLowerCase() === "y" || key.return) {
37473
38328
  child?.kill("SIGINT");
37474
38329
  onState(null);
37475
38330
  onBack();
37476
- } else if (input.toLowerCase() === "n" || key.escape) {
38331
+ } else if (input2.toLowerCase() === "n" || key.escape) {
37477
38332
  setConfirmExit(false);
37478
38333
  }
37479
38334
  return;
37480
38335
  }
37481
- if (input === "l") {
37482
- setShowLogs((value) => !value);
37483
- } else if (input === "q" || key.ctrl && key.name === "c") {
38336
+ if (input2 === "l") {
38337
+ setLogMode("picker");
38338
+ } else if (input2 === "q") {
37484
38339
  setConfirmExit(true);
37485
38340
  }
37486
38341
  });
37487
- async function start(selectedAgentsDir = agentsDir) {
38342
+ async function start(selectedAgentsDir = agentsDir, collectionId = "") {
37488
38343
  const serveRoot = selectedAgentsDir || "./agents";
37489
38344
  setAgentsDir(serveRoot);
37490
38345
  setStarted(true);
37491
38346
  setStartupError("");
37492
38347
  try {
37493
- const nextChild = await spawnLocalServeEventStream({ agentsDir: serveRoot, env: env3, platform: platform2 });
38348
+ const logStore = createServeLogStore({ agentsDir: serveRoot, collectionId, env: env3, platform: platform2 });
38349
+ const historicalLogs = logStore.load();
38350
+ if (historicalLogs.length > 0) {
38351
+ setState((current) => {
38352
+ const agentLogs = mergeAgentLogRecords(current.agentLogs, historicalLogs);
38353
+ return {
38354
+ ...current,
38355
+ agentLogs,
38356
+ agentRunState: {
38357
+ ...agentRunStateFromLogs(agentLogs, { includeRunning: false }),
38358
+ ...current.agentRunState
38359
+ }
38360
+ };
38361
+ });
38362
+ }
38363
+ const nextChild = await spawnLocalServeEventStream({ agentsDir: serveRoot, collectionId, env: env3, platform: platform2 });
37494
38364
  setChild(nextChild);
37495
- attachServeChild(nextChild, setState, setStartupError, () => {
37496
- setState((current) => current.stopped ? current : { ...current, stopped: true, activity: "Process exited" });
37497
- });
38365
+ attachServeChild(nextChild, setState, setStartupError, ({ message } = {}) => {
38366
+ setState((current) => current.stopped ? current : { ...current, stopped: true, activity: message ? "Runtime update required" : "Process exited" });
38367
+ }, logStore);
38368
+ } catch (error) {
38369
+ setStartupError(errorMessage2(error));
38370
+ }
38371
+ }
38372
+ async function chooseWorkspaceThenStart(selectedAgentsDir = agentsDir) {
38373
+ const serveRoot = selectedAgentsDir || "./agents";
38374
+ setAgentsDir(serveRoot);
38375
+ setPendingAgentsDir(serveRoot);
38376
+ setStartupError("");
38377
+ setLoadingWorkspaces(true);
38378
+ try {
38379
+ const result = await listCloudWorkspaces({ env: env3, platform: platform2 });
38380
+ if (!result.ok) {
38381
+ setStartupError(result.error || "Could not list workspaces.");
38382
+ return;
38383
+ }
38384
+ if (result.workspaces.length > 1) {
38385
+ setWorkspaceChoices(result.workspaces);
38386
+ return;
38387
+ }
38388
+ await start(serveRoot, result.workspaces[0]?.id || "");
37498
38389
  } catch (error) {
37499
38390
  setStartupError(errorMessage2(error));
38391
+ } finally {
38392
+ setLoadingWorkspaces(false);
37500
38393
  }
37501
38394
  }
37502
38395
  if (!started) {
38396
+ if (workspaceChoices.length > 1) {
38397
+ return h(
38398
+ Box2,
38399
+ { flexDirection: "column" },
38400
+ h(Text2, { bold: true }, "Select workspace:"),
38401
+ h(SelectInput2, {
38402
+ items: [
38403
+ ...workspaceChoices.map((workspace) => ({
38404
+ label: workspaceLabel(workspace),
38405
+ value: workspace.id
38406
+ })),
38407
+ { label: "Back", value: "__back__" }
38408
+ ],
38409
+ onSelect: (item) => {
38410
+ if (item.value === "__back__") {
38411
+ setWorkspaceChoices([]);
38412
+ return;
38413
+ }
38414
+ start(pendingAgentsDir || agentsDir, item.value);
38415
+ }
38416
+ }),
38417
+ h(Text2, null, ""),
38418
+ h(FooterBar, { items: [
38419
+ { key: "\u2191\u2193", label: "Navigate" },
38420
+ { key: "\u23CE", label: "Select" },
38421
+ { key: "esc", label: "Back" }
38422
+ ] })
38423
+ );
38424
+ }
37503
38425
  const items = [
37504
38426
  ...agentDirCandidates.map((candidate) => ({
37505
38427
  label: `${candidate.path} (${candidate.agentCount} ${candidate.agentCount === 1 ? "agent" : "agents"})`,
@@ -37511,7 +38433,7 @@ function ServeScreen({ env: env3, platform: platform2, onBack, onState }) {
37511
38433
  return h(
37512
38434
  Box2,
37513
38435
  { flexDirection: "column" },
37514
- h(Text2, { bold: true }, "Serve Agents"),
38436
+ h(Text2, { bold: true }, "Select agent directory:"),
37515
38437
  selectingDir && h(SelectInput2, {
37516
38438
  items,
37517
38439
  onSelect: (item) => {
@@ -37525,77 +38447,434 @@ function ServeScreen({ env: env3, platform: platform2, onBack, onState }) {
37525
38447
  return;
37526
38448
  }
37527
38449
  setAgentsDir(item.value);
37528
- start(item.value);
38450
+ chooseWorkspaceThenStart(item.value);
37529
38451
  }
37530
38452
  }),
37531
38453
  !selectingDir && h(TextInput2, {
37532
38454
  value: agentsDir,
37533
38455
  placeholder: "./agents",
37534
38456
  onChange: setAgentsDir,
37535
- onSubmit: (value) => start(value)
38457
+ onSubmit: (value) => chooseWorkspaceThenStart(value)
37536
38458
  }),
37537
- h(BackHint, { onBack })
38459
+ loadingWorkspaces && h(
38460
+ Box2,
38461
+ null,
38462
+ h(Text2, { color: colors.brand }, " "),
38463
+ h(Spinner2, { type: "dots" }),
38464
+ h(Text2, { color: colors.brand }, " Loading workspaces")
38465
+ ),
38466
+ startupError && h(Text2, { color: colors.error }, ` ${fig.cross} ${startupError}`),
38467
+ h(Text2, null, ""),
38468
+ h(FooterBar, { items: [
38469
+ { key: "\u2191\u2193", label: "Navigate" },
38470
+ { key: "\u23CE", label: "Select" },
38471
+ { key: "esc", label: "Back" }
38472
+ ] })
37538
38473
  );
37539
38474
  }
37540
38475
  const summary = agentSummary(state.agents);
38476
+ if (logMode !== null) {
38477
+ return h(
38478
+ Box2,
38479
+ { flexDirection: "column" },
38480
+ h(AgentLogViewer, {
38481
+ agents: state.agents,
38482
+ agentLogs: state.agentLogs,
38483
+ agentRunState: state.agentRunState,
38484
+ logMode,
38485
+ onSelectAgent: (path8) => setLogMode(path8),
38486
+ onBack: () => {
38487
+ if (logMode === "picker") {
38488
+ setLogMode(null);
38489
+ } else {
38490
+ setLogMode("picker");
38491
+ }
38492
+ }
38493
+ })
38494
+ );
38495
+ }
37541
38496
  return h(
37542
38497
  Box2,
37543
38498
  { 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`),
37547
- 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")
38499
+ startupError ? h(Text2, { color: colors.error }, ` ${fig.cross} ${startupError}`) : h(
38500
+ Box2,
38501
+ null,
38502
+ h(Text2, { color: colors.brand }, " "),
38503
+ h(Spinner2, { type: "dots" }),
38504
+ h(Text2, { color: colors.brand }, ` ${state.activity}`)
38505
+ ),
38506
+ h(Text2, null, ""),
38507
+ h(
38508
+ Text2,
38509
+ null,
38510
+ h(Text2, { color: colors.muted }, " Agents: "),
38511
+ h(Text2, { color: colors.success, bold: true }, `${summary.ready} ready`),
38512
+ summary.needsSetup > 0 && h(Text2, { color: colors.warning }, ` ${fig.dot} ${summary.needsSetup} needs setup`),
38513
+ summary.errors > 0 && h(Text2, { color: colors.error }, ` ${fig.dot} ${summary.errors} errors`),
38514
+ h(Text2, { color: colors.muted }, ` ${fig.dot} ${summary.total} total`)
38515
+ ),
38516
+ h(Text2, null, ""),
38517
+ state.newAgents?.length > 0 && h(
38518
+ Text2,
38519
+ { color: colors.success },
38520
+ ` ${fig.check} New agents synced automatically: ${state.newAgents.map((agent) => agent.relativePath || agent.manifestName || agent.path || "agent").join(", ")}`
38521
+ ),
38522
+ h(AgentTable, { agents: state.agents, agentRunState: state.agentRunState }),
38523
+ state.retry && h(Text2, { color: colors.warning }, ` ${fig.warning} Retry: ${state.retry.activity} in ${Number(state.retry.delaySeconds || 0).toFixed(1)}s`),
38524
+ state.errors.length > 0 && h(
38525
+ Box2,
38526
+ { flexDirection: "column", marginTop: 1 },
38527
+ ...state.errors.slice(-3).map((item, index) => h(Text2, { key: `error-${index}`, color: colors.error }, ` ${fig.cross} ${item.scope}: ${item.message}`))
38528
+ ),
38529
+ h(Text2, null, ""),
38530
+ confirmExit ? h(
38531
+ Text2,
38532
+ { color: colors.warning, bold: true },
38533
+ " Stop local serve? ",
38534
+ h(Text2, { color: colors.accent }, "y"),
38535
+ h(Text2, { color: colors.muted }, "/"),
38536
+ h(Text2, { color: colors.accent }, "n")
38537
+ ) : h(FooterBar, { items: [
38538
+ { key: "l", label: "Agent logs" },
38539
+ { key: "q", label: "Stop" },
38540
+ { key: "^c ^c", label: "Exit" }
38541
+ ] }),
38542
+ exitArmed && h(Text2, { color: colors.warning, bold: true }, " Press Ctrl+C again to exit.")
37553
38543
  );
37554
38544
  }
37555
- function AgentTable({ agents }) {
38545
+ function AgentTable({ agents, agentRunState = {} }) {
37556
38546
  if (!agents.length) {
37557
- return h(Text2, { dimColor: true }, "No agents discovered yet.");
38547
+ return h(Text2, { color: colors.muted }, ` ${fig.dotEmpty} No agents discovered yet.`);
38548
+ }
38549
+ const termWidth = Math.min(process.stdout.columns || 80, 100);
38550
+ const pathW = Math.min(22, Math.floor(termWidth * 0.25));
38551
+ const nameW = Math.min(28, Math.floor(termWidth * 0.32));
38552
+ const credW = 10;
38553
+ const statusW = 10;
38554
+ const headerLine = ` ${"Path".padEnd(pathW)} ${"Name".padEnd(nameW)} ${"Creds".padEnd(credW)} ${"Status".padEnd(statusW)}`;
38555
+ const separator = ` ${fig.horizontal.repeat(pathW + nameW + credW + statusW + 3)}`;
38556
+ const rows = agents.slice(0, 16).map((agent) => {
38557
+ const path8 = clip(agent.relativePath || ".", pathW);
38558
+ const name = clip(agent.manifestName || agent.manifest?.name || "-", nameW);
38559
+ const creds = credentialLabel(agent);
38560
+ const runtime = agentRuntimeStatus(agent, agentRunState);
38561
+ const status = runtime.label || "-";
38562
+ const statusColor = runtimeStatusColor(runtime.status);
38563
+ const statusIcon = runtimeStatusIcon(runtime.status);
38564
+ return h(
38565
+ Text2,
38566
+ { key: agent.localAgentId || agent.relativePath },
38567
+ h(Text2, { color: colors.muted }, " "),
38568
+ h(Text2, null, path8.padEnd(pathW)),
38569
+ h(Text2, null, " "),
38570
+ h(Text2, null, name.padEnd(nameW)),
38571
+ h(Text2, null, " "),
38572
+ h(Text2, { color: colors.muted }, creds.padEnd(credW)),
38573
+ h(Text2, null, " "),
38574
+ h(Text2, { color: statusColor }, `${statusIcon} ${status}`)
38575
+ );
38576
+ });
38577
+ const hiddenCount = agents.length - 16;
38578
+ return h(
38579
+ Box2,
38580
+ { flexDirection: "column" },
38581
+ h(Text2, { color: colors.muted, bold: true }, headerLine),
38582
+ h(Text2, { color: colors.muted }, separator),
38583
+ ...rows,
38584
+ hiddenCount > 0 && h(Text2, { color: colors.muted }, ` ${fig.ellipsis} and ${hiddenCount} more`)
38585
+ );
38586
+ }
38587
+ function runtimeStatusColor(status) {
38588
+ if (status === "running") return colors.accent;
38589
+ if (status === "ready" || status === "completed") return colors.success;
38590
+ if (status === "error") return colors.error;
38591
+ return colors.warning;
38592
+ }
38593
+ function runtimeStatusIcon(status) {
38594
+ if (status === "running") return fig.arrow;
38595
+ if (status === "ready" || status === "completed") return fig.dot;
38596
+ if (status === "error") return fig.cross;
38597
+ return fig.dotEmpty;
38598
+ }
38599
+ function FooterBar({ items }) {
38600
+ if (!items || !items.length) return null;
38601
+ return h(
38602
+ Text2,
38603
+ { color: colors.muted },
38604
+ " ",
38605
+ ...items.map((item, i) => [
38606
+ h(Text2, { key: `k-${i}`, color: colors.accent, bold: true }, item.key),
38607
+ h(Text2, { key: `l-${i}`, color: colors.muted }, ` ${item.label}`),
38608
+ i < items.length - 1 ? h(Text2, { key: `s-${i}` }, " ") : null
38609
+ ]).flat().filter(Boolean)
38610
+ );
38611
+ }
38612
+ function AgentLogViewer({ agents, agentLogs, agentRunState, logMode, onSelectAgent, onBack }) {
38613
+ if (logMode === "picker") {
38614
+ return h(AgentLogPicker, { agents, agentLogs, agentRunState, onSelectAgent, onBack });
38615
+ }
38616
+ return h(AgentLogDetail, { relativePath: logMode, agentLogs, agentRunState, agents, onBack });
38617
+ }
38618
+ function AgentLogPicker({ agents, agentLogs, agentRunState = {}, onSelectAgent, onBack }) {
38619
+ const [selectedIndex, setSelectedIndex] = useState5(0);
38620
+ const agentList = agents.map((agent) => {
38621
+ const path8 = agent.relativePath || ".";
38622
+ const logs = agentLogEntries(agentLogs, path8);
38623
+ const lastLog = logs.length > 0 ? logs[logs.length - 1] : null;
38624
+ const hasErrors = logs.some((l) => l.level === "error" || l.eventType === "completion" && l.success === false);
38625
+ return {
38626
+ path: path8,
38627
+ name: agent.manifestName || agent.manifest?.name || path8,
38628
+ logCount: logs.length,
38629
+ lastLog,
38630
+ hasErrors,
38631
+ status: agentRuntimeStatus(agent, agentRunState)
38632
+ };
38633
+ });
38634
+ const inventoryPaths = new Set(agentList.map((a) => a.path));
38635
+ for (const path8 of Object.keys(agentLogs || {})) {
38636
+ if (!inventoryPaths.has(path8)) {
38637
+ const logs = agentLogEntries(agentLogs, path8);
38638
+ agentList.push({
38639
+ path: path8,
38640
+ name: path8,
38641
+ logCount: logs.length,
38642
+ lastLog: logs.length > 0 ? logs[logs.length - 1] : null,
38643
+ hasErrors: logs.some((l) => l.level === "error"),
38644
+ status: { status: "unknown", label: "offline" }
38645
+ });
38646
+ }
37558
38647
  }
38648
+ useInput2((input, key) => {
38649
+ if (key.upArrow) {
38650
+ setSelectedIndex((i) => (i - 1 + agentList.length) % agentList.length);
38651
+ } else if (key.downArrow) {
38652
+ setSelectedIndex((i) => (i + 1) % agentList.length);
38653
+ } else if (key.return) {
38654
+ if (agentList.length > 0) {
38655
+ onSelectAgent(agentList[selectedIndex].path);
38656
+ }
38657
+ } else if (key.escape) {
38658
+ onBack();
38659
+ }
38660
+ });
38661
+ if (agentList.length === 0) {
38662
+ return h(
38663
+ Box2,
38664
+ { flexDirection: "column" },
38665
+ h(Text2, { bold: true, color: colors.brand }, ` ${fig.horizontal.repeat(3)} Agent Logs`),
38666
+ h(Text2, null, ""),
38667
+ h(Text2, { color: colors.muted }, " No agent logs yet. Logs appear when agents run."),
38668
+ h(Text2, null, ""),
38669
+ h(FooterBar, { items: [{ key: "esc", label: "Back to serve" }] })
38670
+ );
38671
+ }
38672
+ const pathW = 24;
38673
+ const nameW = 24;
38674
+ const statusW = 10;
37559
38675
  return h(
37560
38676
  Box2,
37561
38677
  { 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
- ))
38678
+ h(Text2, { bold: true, color: colors.brand }, ` ${fig.horizontal.repeat(3)} Agent Logs ${fig.horizontal.repeat(20)}`),
38679
+ h(Text2, null, ""),
38680
+ h(Text2, { color: colors.muted }, ` ${"Agent".padEnd(nameW)} ${"Status".padEnd(statusW)} ${"Logs".padEnd(6)} ${"Last event"}`),
38681
+ h(Text2, { color: colors.muted }, ` ${fig.horizontal.repeat(nameW + statusW + 6 + 32)}`),
38682
+ ...agentList.map((agent, i) => {
38683
+ const isActive = i === selectedIndex;
38684
+ const logCountColor = agent.hasErrors ? colors.error : agent.logCount > 0 ? colors.success : colors.muted;
38685
+ const lastEvent = agent.lastLog ? formatAgentLogEntry(agent.lastLog) : "No logs";
38686
+ return h(
38687
+ Text2,
38688
+ { key: agent.path },
38689
+ h(Text2, { color: isActive ? colors.accent : colors.muted }, isActive ? ` ${fig.arrow} ` : " "),
38690
+ h(Text2, { bold: isActive }, clip(agent.name, nameW - 2).padEnd(nameW)),
38691
+ h(Text2, { color: runtimeStatusColor(agent.status.status) }, `${runtimeStatusIcon(agent.status.status)} ${agent.status.label}`.padEnd(statusW)),
38692
+ h(Text2, { color: logCountColor }, String(agent.logCount).padEnd(6)),
38693
+ h(Text2, { color: colors.muted }, clip(lastEvent, 36))
38694
+ );
38695
+ }),
38696
+ h(Text2, null, ""),
38697
+ h(FooterBar, { items: [
38698
+ { key: "\u2191\u2193", label: "Navigate" },
38699
+ { key: "\u23CE", label: "View logs" },
38700
+ { key: "esc", label: "Back to serve" }
38701
+ ] })
37568
38702
  );
37569
38703
  }
37570
- function LogPane({ logs }) {
38704
+ function AgentLogDetail({ relativePath, agentLogs, agentRunState = {}, agents, onBack }) {
38705
+ const logs = agentLogEntries(agentLogs, relativePath);
38706
+ const agent = agents.find((a) => (a.relativePath || ".") === relativePath);
38707
+ const agentName = agent?.manifestName || agent?.manifest?.name || relativePath;
38708
+ const runtime = agent ? agentRuntimeStatus(agent, agentRunState) : { status: agentRunState?.[relativePath]?.status || "unknown", label: agentRunState?.[relativePath]?.status || "offline" };
38709
+ const [scrollOffset, setScrollOffset] = useState5(0);
38710
+ const visibleCount = Math.min(20, Math.max(8, (process.stdout.rows || 24) - 10));
38711
+ useInput2((input, key) => {
38712
+ if (key.escape) {
38713
+ onBack();
38714
+ } else if (key.upArrow) {
38715
+ setScrollOffset((o) => Math.max(0, o - 1));
38716
+ } else if (key.downArrow) {
38717
+ setScrollOffset((o) => Math.min(Math.max(0, logs.length - visibleCount), o + 1));
38718
+ } else if (key.pageDown || input === " ") {
38719
+ setScrollOffset((o) => Math.min(Math.max(0, logs.length - visibleCount), o + visibleCount));
38720
+ } else if (key.pageUp) {
38721
+ setScrollOffset((o) => Math.max(0, o - visibleCount));
38722
+ }
38723
+ });
38724
+ const effectiveOffset = logs.length <= visibleCount ? 0 : scrollOffset >= logs.length - visibleCount - 2 ? Math.max(0, logs.length - visibleCount) : scrollOffset;
38725
+ const visibleLogs = logs.slice(effectiveOffset, effectiveOffset + visibleCount);
38726
+ const visibleRows = groupedLogRows(visibleLogs, logs[effectiveOffset - 1]);
38727
+ const messageWidth = Math.max(50, Math.min((process.stdout.columns || 80) - 18, 120));
37571
38728
  return h(
37572
38729
  Box2,
37573
- { flexDirection: "column", marginTop: 1 },
37574
- h(Text2, { bold: true }, "Details"),
37575
- ...logs.slice(-8).map((event, index) => h(
38730
+ { flexDirection: "column" },
38731
+ h(Text2, { bold: true, color: colors.brand }, ` ${fig.horizontal.repeat(3)} ${agentName}`),
38732
+ h(
37576
38733
  Text2,
37577
- { key: `${event.type}-${index}`, dimColor: true },
37578
- `${event.type} ${JSON.stringify(event)}`
37579
- ))
38734
+ null,
38735
+ h(Text2, { color: colors.muted }, ` ${relativePath} ${fig.dash} ${logs.length} log entries ${fig.dash} `),
38736
+ h(Text2, { color: runtimeStatusColor(runtime.status) }, `${runtimeStatusIcon(runtime.status)} ${runtime.label}`)
38737
+ ),
38738
+ h(Text2, null, ""),
38739
+ logs.length === 0 ? h(Text2, { color: colors.muted }, " No logs recorded yet for this agent. Logs appear during runs.") : h(
38740
+ Box2,
38741
+ { flexDirection: "column" },
38742
+ ...visibleRows.map((row, i) => {
38743
+ if (row.type === "divider") {
38744
+ return h(
38745
+ Text2,
38746
+ { key: `divider-${effectiveOffset}-${i}`, color: colors.dim },
38747
+ ` ${fig.horizontal.repeat(3)} run ${clip(row.runId, 24)} ${fig.horizontal.repeat(12)}`
38748
+ );
38749
+ }
38750
+ const entry = row.entry;
38751
+ const levelColor = entry.level === "error" ? colors.error : entry.level === "warning" ? colors.warning : entry.level === "debug" ? colors.dim : colors.muted;
38752
+ const timeStr = formatLogTime(entry.timestamp);
38753
+ const icon = entry.eventType === "completion" ? entry.success ? fig.check : fig.cross : entry.eventType === "job_started" ? fig.arrow : entry.eventType === "job_completed" ? entry.success === false ? fig.cross : fig.check : entry.eventType === "step_event" ? fig.arrow : entry.eventType === "progress" ? fig.dotEmpty : entry.eventType === "error" || entry.level === "error" ? fig.cross : fig.dash;
38754
+ const iconColor = entry.eventType === "completion" ? entry.success ? colors.success : colors.error : entry.eventType === "job_started" ? colors.accent : entry.eventType === "job_completed" ? entry.success === false ? colors.error : colors.success : levelColor;
38755
+ return h(
38756
+ Text2,
38757
+ { key: `entry-${effectiveOffset}-${i}` },
38758
+ h(Text2, { color: colors.dim }, ` ${timeStr} `),
38759
+ h(Text2, { color: iconColor }, `${icon} `),
38760
+ h(
38761
+ Text2,
38762
+ { color: entry.level === "error" ? colors.error : colors.text },
38763
+ clip(formatAgentLogEntry(entry), messageWidth)
38764
+ )
38765
+ );
38766
+ }),
38767
+ logs.length > visibleCount && h(
38768
+ Text2,
38769
+ { color: colors.muted },
38770
+ ` ${fig.ellipsis} Showing ${effectiveOffset + 1}-${effectiveOffset + visibleLogs.length} of ${logs.length}`
38771
+ )
38772
+ ),
38773
+ h(Text2, null, ""),
38774
+ h(FooterBar, { items: [
38775
+ { key: "\u2191\u2193", label: "Scroll" },
38776
+ { key: "PgUp/Dn", label: "Page" },
38777
+ { key: "esc", label: "Back to agents" }
38778
+ ] })
37580
38779
  );
37581
38780
  }
38781
+ function formatAgentLogEntry(entry) {
38782
+ if (!entry) return "";
38783
+ const type = entry.eventType || "";
38784
+ switch (type) {
38785
+ case "job_started":
38786
+ return entry.message || `Started ${entry.jobType || "run"}`;
38787
+ case "job_completed":
38788
+ return entry.success === false ? `Failed: ${formatEntryError(entry.error) || entry.message || "error"}` : entry.message || "Completed successfully";
38789
+ case "error":
38790
+ return `Error: ${entry.message || formatEntryError(entry.error) || "unknown error"}`;
38791
+ case "log":
38792
+ return entry.message || "";
38793
+ case "progress": {
38794
+ const prog = entry.current != null && entry.total != null ? `${entry.current}/${entry.total}` : "";
38795
+ return `${prog}${entry.message ? ` ${entry.message}` : ""}`.trim() || "progress";
38796
+ }
38797
+ case "completion":
38798
+ if (entry.success) return "Completed successfully";
38799
+ if (entry.error && typeof entry.error === "object") return `Failed: ${entry.error.message || "error"}`;
38800
+ return `Failed: ${entry.error || "unknown error"}`;
38801
+ case "step_event":
38802
+ return `Step: ${entry.stepId || "unknown"}${entry.message ? ` - ${entry.message}` : ""}`;
38803
+ case "step_checkpoint":
38804
+ return `Checkpoint: ${entry.stepId || "unknown"}`;
38805
+ case "step_heartbeat":
38806
+ return `Heartbeat: ${entry.stepId || ""}${entry.message ? ` - ${entry.message}` : ""}`;
38807
+ case "artifact":
38808
+ return `Artifact: ${entry.message || "data"}`;
38809
+ default:
38810
+ return entry.message || type || "event";
38811
+ }
38812
+ }
38813
+ function groupedLogRows(logs, previousEntry) {
38814
+ const rows = [];
38815
+ let currentRunId = previousEntry?.runId || "";
38816
+ for (const entry of logs) {
38817
+ const runId = entry.runId || "";
38818
+ if (runId && runId !== currentRunId) {
38819
+ rows.push({ type: "divider", runId });
38820
+ currentRunId = runId;
38821
+ }
38822
+ rows.push({ type: "entry", entry });
38823
+ }
38824
+ return rows;
38825
+ }
38826
+ function formatEntryError(error) {
38827
+ if (!error) return "";
38828
+ if (typeof error === "object") return error.message || "error";
38829
+ return String(error);
38830
+ }
38831
+ function formatLogTime(timestamp) {
38832
+ if (!timestamp) return " ";
38833
+ try {
38834
+ const d = new Date(timestamp);
38835
+ const h2 = String(d.getHours()).padStart(2, "0");
38836
+ const m = String(d.getMinutes()).padStart(2, "0");
38837
+ const s = String(d.getSeconds()).padStart(2, "0");
38838
+ return `${h2}:${m}:${s}`;
38839
+ } catch {
38840
+ return " ";
38841
+ }
38842
+ }
37582
38843
  function DoctorScreen({ env: env3, platform: platform2, onBack }) {
37583
38844
  const runtime = runtimeSummary({ env: env3, platform: platform2 });
37584
38845
  const pkg = packageAccessSummary({ env: env3, platform: platform2 });
38846
+ const checks = [
38847
+ { label: "Managed Python", passed: runtime.installed, value: runtime.installed ? "Present" : "Missing" },
38848
+ { label: "Managed Env", passed: runtime.installed, value: runtime.detail },
38849
+ { label: "Package Access", passed: pkg.configured, value: pkg.configured ? `Configured (${pkg.source})` : "Missing" }
38850
+ ];
38851
+ const allPassed = checks.every((c) => c.passed);
37585
38852
  return h(
37586
38853
  Box2,
37587
38854
  { 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"}`),
38855
+ h(Text2, { bold: true }, " System Health"),
38856
+ h(Text2, null, ""),
38857
+ ...checks.map((check) => h(
38858
+ Text2,
38859
+ { key: check.label },
38860
+ h(Text2, { color: check.passed ? colors.success : colors.error }, ` ${check.passed ? fig.check : fig.cross} `),
38861
+ h(Text2, null, `${check.label.padEnd(18)}${check.value}`)
38862
+ )),
38863
+ h(Text2, null, ""),
38864
+ h(
38865
+ Text2,
38866
+ { color: allPassed ? colors.success : colors.warning, bold: true },
38867
+ allPassed ? ` ${fig.check} All checks passed` : ` ${fig.warning} Some checks need attention`
38868
+ ),
38869
+ h(Text2, null, ""),
37592
38870
  h(SelectInput2, {
37593
- items: [{ label: "Run plain doctor output", value: "run" }, { label: "Back", value: "back" }],
38871
+ items: [{ label: "Run full doctor output", value: "run" }, { label: "Back", value: "back" }],
37594
38872
  onSelect: async (item) => {
37595
38873
  if (item.value === "back") onBack();
37596
38874
  else doctor({ env: env3, platform: platform2 });
37597
38875
  }
37598
- })
38876
+ }),
38877
+ h(FooterBar, { items: [{ key: "\u23CE", label: "Select" }, { key: "esc", label: "Back" }] })
37599
38878
  );
37600
38879
  }
37601
38880
  function UpdateScreen({ env: env3, platform: platform2, onBack }) {
@@ -37603,29 +38882,65 @@ function UpdateScreen({ env: env3, platform: platform2, onBack }) {
37603
38882
  return h(
37604
38883
  Box2,
37605
38884
  { flexDirection: "column" },
37606
- h(Text2, { bold: true }, "Update"),
38885
+ h(Text2, { bold: true }, " Update Runtime"),
38886
+ h(Text2, null, ""),
37607
38887
  h(SelectInput2, {
37608
- items: [{ label: "Update managed runtime", value: "run" }, { label: "Back", value: "back" }],
38888
+ items: [{ label: "Update managed runtime & packages", value: "run" }, { label: "Back", value: "back" }],
37609
38889
  onSelect: async (item) => {
37610
38890
  if (item.value === "back") {
37611
38891
  onBack();
37612
38892
  return;
37613
38893
  }
37614
- setStatus("Updating managed runtime");
38894
+ setStatus(`${fig.dotEmpty} Updating managed runtime...`);
37615
38895
  await setup({ nonInteractive: true, env: env3, platform: platform2 });
37616
- setStatus("Update finished");
38896
+ setStatus(`${fig.check} Update finished`);
37617
38897
  }
37618
38898
  }),
37619
- status && h(Text2, null, status)
38899
+ status && h(Text2, { color: status.includes(fig.check) ? colors.success : colors.muted }, ` ${status}`),
38900
+ h(Text2, null, ""),
38901
+ h(FooterBar, { items: [{ key: "\u23CE", label: "Select" }, { key: "esc", label: "Back" }] }),
38902
+ h(BackHint, { onBack })
37620
38903
  );
37621
38904
  }
37622
38905
  function CommandsScreen({ onBack }) {
38906
+ const grouped = [
38907
+ { section: "Getting Started", commands: [
38908
+ { cmd: "vendian login", desc: "Sign in and prepare runtime" },
38909
+ { cmd: "vendian init --output-dir ./agents", desc: "Initialize docs workspace" }
38910
+ ] },
38911
+ { section: "Agent Development", commands: [
38912
+ { cmd: 'vendian create "My Agent" --output-dir .', desc: "Scaffold new agent" },
38913
+ { cmd: "vendian validate ./agents/my-agent --runtime", desc: "Validate manifest" },
38914
+ { cmd: "vendian test ./agents/my-agent --dry-run", desc: "Test locally" },
38915
+ { cmd: "vendian models", desc: "List available models" }
38916
+ ] },
38917
+ { section: "Cloud & Deploy", commands: [
38918
+ { cmd: "vendian cloud local serve --agents-dir .", desc: "Start cloud-connected serve" },
38919
+ { cmd: "vendian login --backend staging", desc: "Connect to staging" }
38920
+ ] },
38921
+ { section: "Maintenance", commands: [
38922
+ { cmd: "vendian doctor", desc: "Check system health" },
38923
+ { cmd: "vendian update", desc: "Update runtime & packages" }
38924
+ ] }
38925
+ ];
37623
38926
  return h(
37624
38927
  Box2,
37625
38928
  { 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 })
38929
+ h(Text2, { bold: true }, " Quick Reference"),
38930
+ h(Text2, null, ""),
38931
+ ...grouped.map((group) => [
38932
+ h(Text2, { key: `title-${group.section}`, color: colors.accent, bold: true }, ` ${group.section}`),
38933
+ ...group.commands.map((item) => h(
38934
+ Text2,
38935
+ { key: item.cmd },
38936
+ h(Text2, { color: colors.muted }, " "),
38937
+ h(Text2, { color: colors.brand }, item.cmd.padEnd(44)),
38938
+ h(Text2, { color: colors.muted }, item.desc)
38939
+ )),
38940
+ h(Text2, { key: `gap-${group.section}` }, "")
38941
+ ]).flat(),
38942
+ h(FooterBar, { items: [{ key: "esc", label: "Back" }] }),
38943
+ h(BackHint, { onBack })
37629
38944
  );
37630
38945
  }
37631
38946
  function CommandPromptScreen({ title, label, initialValue, onSubmit, onBack }) {
@@ -37635,17 +38950,20 @@ function CommandPromptScreen({ title, label, initialValue, onSubmit, onBack }) {
37635
38950
  Box2,
37636
38951
  { flexDirection: "column" },
37637
38952
  h(Text2, { bold: true }, title),
38953
+ h(Text2, null, ""),
37638
38954
  h(TextInput2, {
37639
38955
  value,
37640
38956
  placeholder: label,
37641
38957
  onChange: setValue,
37642
38958
  onSubmit: async (submitted) => {
37643
- setStatus("Running command");
38959
+ setStatus(`${fig.dotEmpty} Running...`);
37644
38960
  await onSubmit(submitted);
37645
- setStatus("Command finished");
38961
+ setStatus(`${fig.check} Done`);
37646
38962
  }
37647
38963
  }),
37648
- status && h(Text2, null, status),
38964
+ status && h(Text2, { color: status.includes(fig.check) ? colors.success : colors.muted }, ` ${status}`),
38965
+ h(Text2, null, ""),
38966
+ h(FooterBar, { items: [{ key: "\u23CE", label: "Submit" }, { key: "esc", label: "Back" }] }),
37649
38967
  h(BackHint, { onBack })
37650
38968
  );
37651
38969
  }
@@ -37653,10 +38971,11 @@ function BackHint({ onBack }) {
37653
38971
  useInput2((input, key) => {
37654
38972
  if (key.escape || input === "\x1B") onBack();
37655
38973
  });
37656
- return h(Text2, { dimColor: true }, "Esc returns home");
38974
+ return null;
37657
38975
  }
37658
- function attachServeChild(child, setState, setStartupError, onExit) {
38976
+ function attachServeChild(child, setState, setStartupError, onExit, logStore = null) {
37659
38977
  let buffer = "";
38978
+ const stderrChunks = [];
37660
38979
  child.stdout.setEncoding("utf8");
37661
38980
  child.stdout.on("data", (chunk) => {
37662
38981
  buffer += chunk;
@@ -37666,6 +38985,10 @@ function attachServeChild(child, setState, setStartupError, onExit) {
37666
38985
  try {
37667
38986
  const event = parseServeEventLine(line);
37668
38987
  if (event) {
38988
+ try {
38989
+ logStore?.append(event);
38990
+ } catch {
38991
+ }
37669
38992
  setState((current) => applyServeEvent(current, event));
37670
38993
  }
37671
38994
  } catch (error) {
@@ -37677,6 +39000,7 @@ function attachServeChild(child, setState, setStartupError, onExit) {
37677
39000
  child.stderr.on("data", (chunk) => {
37678
39001
  const text = String(chunk).trim();
37679
39002
  if (text) {
39003
+ stderrChunks.push(text);
37680
39004
  setState((current) => ({
37681
39005
  ...current,
37682
39006
  logs: [...current.logs, { type: "stderr", message: text }].slice(-200)
@@ -37684,23 +39008,37 @@ function attachServeChild(child, setState, setStartupError, onExit) {
37684
39008
  }
37685
39009
  });
37686
39010
  child.on("error", (error) => setStartupError(errorMessage2(error)));
37687
- child.on("exit", onExit);
39011
+ child.on("exit", (code, signal) => {
39012
+ try {
39013
+ logStore?.compact();
39014
+ } catch {
39015
+ }
39016
+ const message = serveProcessExitMessage({ stderr: stderrChunks.join("\n"), code, signal });
39017
+ if (message) {
39018
+ setStartupError(message);
39019
+ }
39020
+ onExit({ code, signal, message });
39021
+ });
37688
39022
  }
37689
39023
  function helpText() {
37690
39024
  return [
37691
- "Vendian CLI",
37692
39025
  "",
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",
39026
+ ` ${fig.arrowUp} VENDIAN CLI v${CLI_VERSION}`,
39027
+ "",
39028
+ " Usage:",
39029
+ " vendian Open the interactive shell",
39030
+ " vendian login Sign in and prepare the local runtime",
39031
+ " vendian doctor Check local bootstrap health",
39032
+ " vendian update Update the managed runtime",
39033
+ " vendian init Write SDK agent docs into a workspace",
39034
+ ' vendian create "My Agent" Scaffold a new agent from templates',
39035
+ " vendian <command> Run a managed Python SDK/cloud command",
39036
+ "",
39037
+ " Examples:",
39038
+ ...COMMANDS.map((command) => ` ${command}`),
37701
39039
  "",
37702
- "Examples:",
37703
- ...COMMANDS.map((command) => ` ${command}`)
39040
+ ` Run ${fig.arrowRight} vendian ${fig.arrowRight} to open the interactive TUI`,
39041
+ ""
37704
39042
  ].join("\n");
37705
39043
  }
37706
39044
  function credentialLabel(agent) {