@neotx/cli 0.1.0-alpha.2 → 0.1.0-alpha.4

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 (46) hide show
  1. package/README.md +381 -0
  2. package/dist/{agents-Y6LREFXP.js → agents-PH3P7G7E.js} +2 -2
  3. package/dist/{chunk-CP54H7WA.js → chunk-3ZP3BQXB.js} +6 -11
  4. package/dist/chunk-3ZP3BQXB.js.map +1 -0
  5. package/dist/{chunk-TNJOG54I.js → chunk-F622JUDY.js} +6 -2
  6. package/dist/{chunk-TNJOG54I.js.map → chunk-F622JUDY.js.map} +1 -1
  7. package/dist/{cost-DNGKT4UC.js → cost-OQGFNBBG.js} +3 -8
  8. package/dist/cost-OQGFNBBG.js.map +1 -0
  9. package/dist/daemon/supervisor-worker.js +7 -1
  10. package/dist/daemon/supervisor-worker.js.map +1 -1
  11. package/dist/daemon/worker.js +25 -0
  12. package/dist/daemon/worker.js.map +1 -1
  13. package/dist/doctor-ZBO73UID.js +337 -0
  14. package/dist/doctor-ZBO73UID.js.map +1 -0
  15. package/dist/index.js +12 -9
  16. package/dist/index.js.map +1 -1
  17. package/dist/{init-YNSPTCA3.js → init-UYS6KS5U.js} +4 -20
  18. package/dist/init-UYS6KS5U.js.map +1 -0
  19. package/dist/log-PTHLI7ZN.js +141 -0
  20. package/dist/log-PTHLI7ZN.js.map +1 -0
  21. package/dist/{mcp-GH6CCW7A.js → mcp-XHZND5A4.js} +6 -1
  22. package/dist/mcp-XHZND5A4.js.map +1 -0
  23. package/dist/memory-6R22DFS7.js +292 -0
  24. package/dist/memory-6R22DFS7.js.map +1 -0
  25. package/dist/{run-KIU2ZE72.js → run-GJLDWPUE.js} +32 -9
  26. package/dist/run-GJLDWPUE.js.map +1 -0
  27. package/dist/{runs-CHA2JM5K.js → runs-LOYOWU55.js} +9 -10
  28. package/dist/runs-LOYOWU55.js.map +1 -0
  29. package/dist/{supervise-KIB2EYY4.js → supervise-LVCGVYA4.js} +33 -28
  30. package/dist/supervise-LVCGVYA4.js.map +1 -0
  31. package/dist/{tui-QS3RPHKH.js → tui-6USVDV75.js} +100 -33
  32. package/dist/tui-6USVDV75.js.map +1 -0
  33. package/dist/version-XVOAMGDD.js +26 -0
  34. package/dist/version-XVOAMGDD.js.map +1 -0
  35. package/package.json +22 -4
  36. package/dist/chunk-CP54H7WA.js.map +0 -1
  37. package/dist/cost-DNGKT4UC.js.map +0 -1
  38. package/dist/doctor-GC4NH7H6.js +0 -173
  39. package/dist/doctor-GC4NH7H6.js.map +0 -1
  40. package/dist/init-YNSPTCA3.js.map +0 -1
  41. package/dist/mcp-GH6CCW7A.js.map +0 -1
  42. package/dist/run-KIU2ZE72.js.map +0 -1
  43. package/dist/runs-CHA2JM5K.js.map +0 -1
  44. package/dist/supervise-KIB2EYY4.js.map +0 -1
  45. package/dist/tui-QS3RPHKH.js.map +0 -1
  46. /package/dist/{agents-Y6LREFXP.js.map → agents-PH3P7G7E.js.map} +0 -0
@@ -11,10 +11,12 @@ import { appendFile, mkdir, readFile, rm } from "fs/promises";
11
11
  import path from "path";
12
12
  import { fileURLToPath } from "url";
13
13
  import {
14
+ getSupervisorActivityPath,
14
15
  getSupervisorDir,
15
16
  getSupervisorInboxPath,
16
17
  getSupervisorLockPath,
17
18
  getSupervisorStatePath,
19
+ isProcessAlive,
18
20
  loadGlobalConfig,
19
21
  supervisorDaemonStateSchema
20
22
  } from "@neotx/core";
@@ -30,14 +32,6 @@ async function readState(name) {
30
32
  return null;
31
33
  }
32
34
  }
33
- function isProcessAlive(pid) {
34
- try {
35
- process.kill(pid, 0);
36
- return true;
37
- } catch {
38
- return false;
39
- }
40
- }
41
35
  async function isDaemonRunning(name) {
42
36
  const state = await readState(name);
43
37
  if (!state || state.status === "stopped") return null;
@@ -56,9 +50,7 @@ async function handleStatus(name) {
56
50
  console.log(` Port: ${state.port}`);
57
51
  console.log(` Session: ${state.sessionId}`);
58
52
  console.log(` Started: ${state.startedAt}`);
59
- console.log(
60
- ` Interval: ${config.supervisor.idleIntervalMs / 1e3}s (skip up to ${config.supervisor.idleSkipMax} idle)`
61
- );
53
+ console.log(` Timeout: ${config.supervisor.eventTimeoutMs / 1e3}s`);
62
54
  console.log(` Heartbeats: ${state.heartbeatCount}`);
63
55
  if (state.lastHeartbeat) {
64
56
  console.log(` Last beat: ${state.lastHeartbeat}`);
@@ -68,7 +60,7 @@ async function handleStatus(name) {
68
60
  console.log(` Status: ${state.status}`);
69
61
  console.log("");
70
62
  console.log(` Health: curl localhost:${state.port}/health`);
71
- console.log(" Attach: neo supervise --attach");
63
+ console.log(" TUI: neo supervise");
72
64
  console.log(" Stop: neo supervise --kill");
73
65
  }
74
66
  async function handleKill(name) {
@@ -112,7 +104,7 @@ async function startDaemon(name) {
112
104
  const running = await isDaemonRunning(name);
113
105
  if (running) {
114
106
  printError(`Supervisor "${name}" is already running (PID ${running.pid}).`);
115
- printError("Use --kill first, or --attach to connect.");
107
+ printError("Use --kill first, or run neo supervise to open TUI.");
116
108
  process.exitCode = 1;
117
109
  return;
118
110
  }
@@ -140,7 +132,7 @@ async function startDaemon(name) {
140
132
  console.log(` Health: curl localhost:${config.supervisor.port}/health`);
141
133
  console.log(` Webhook: curl -X POST localhost:${config.supervisor.port}/webhook -d '{}'`);
142
134
  console.log(` Logs: ${getSupervisorDir(name)}/daemon.log`);
143
- console.log(` Attach: neo supervise --attach`);
135
+ console.log(` TUI: neo supervise`);
144
136
  console.log(` Status: neo supervise --status`);
145
137
  console.log(` Stop: neo supervise --kill`);
146
138
  }
@@ -152,7 +144,7 @@ async function handleAttach(name) {
152
144
  process.exitCode = 1;
153
145
  return;
154
146
  }
155
- const { renderSupervisorTui } = await import("./tui-QS3RPHKH.js");
147
+ const { renderSupervisorTui } = await import("./tui-6USVDV75.js");
156
148
  await renderSupervisorTui(name);
157
149
  }
158
150
  async function handleMessage(name, text) {
@@ -162,14 +154,13 @@ async function handleMessage(name, text) {
162
154
  process.exitCode = 1;
163
155
  return;
164
156
  }
165
- const inboxPath = getSupervisorInboxPath(name);
166
- const message = {
167
- id: randomUUID(),
168
- from: "api",
169
- text,
170
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
171
- };
172
- await appendFile(inboxPath, `${JSON.stringify(message)}
157
+ const id = randomUUID();
158
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
159
+ const message = { id, from: "api", text, timestamp };
160
+ await appendFile(getSupervisorInboxPath(name), `${JSON.stringify(message)}
161
+ `, "utf-8");
162
+ const activityEntry = { id, type: "message", summary: text, timestamp };
163
+ await appendFile(getSupervisorActivityPath(name), `${JSON.stringify(activityEntry)}
173
164
  `, "utf-8");
174
165
  printSuccess(`Message sent to supervisor "${name}".`);
175
166
  }
@@ -196,7 +187,13 @@ var supervise_default = defineCommand({
196
187
  },
197
188
  attach: {
198
189
  type: "boolean",
199
- description: "Open the TUI for a running supervisor",
190
+ description: "Open the TUI for a running supervisor (default when no flags given)",
191
+ default: false
192
+ },
193
+ detach: {
194
+ type: "boolean",
195
+ alias: "d",
196
+ description: "Start daemon in the background without opening the TUI",
200
197
  default: false
201
198
  },
202
199
  message: {
@@ -222,16 +219,24 @@ var supervise_default = defineCommand({
222
219
  await handleMessage(name, args.message);
223
220
  return;
224
221
  }
222
+ if (args.detach) {
223
+ const alreadyRunning2 = await isDaemonRunning(name);
224
+ if (alreadyRunning2) {
225
+ printSuccess(`Supervisor "${name}" already running (PID ${alreadyRunning2.pid}).`);
226
+ return;
227
+ }
228
+ await startDaemon(name);
229
+ return;
230
+ }
225
231
  const alreadyRunning = await isDaemonRunning(name);
226
232
  if (!alreadyRunning) {
227
233
  await startDaemon(name);
228
- await new Promise((r) => setTimeout(r, 1e3));
234
+ await new Promise((r) => setTimeout(r, 1500));
229
235
  }
230
- const { renderSupervisorTui } = await import("./tui-QS3RPHKH.js");
231
- await renderSupervisorTui(name);
236
+ await handleAttach(name);
232
237
  }
233
238
  });
234
239
  export {
235
240
  supervise_default as default
236
241
  };
237
- //# sourceMappingURL=supervise-KIB2EYY4.js.map
242
+ //# sourceMappingURL=supervise-LVCGVYA4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/supervise.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { randomUUID } from \"node:crypto\";\nimport { closeSync, existsSync, openSync } from \"node:fs\";\nimport { appendFile, mkdir, readFile, rm } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n getSupervisorActivityPath,\n getSupervisorDir,\n getSupervisorInboxPath,\n getSupervisorLockPath,\n getSupervisorStatePath,\n isProcessAlive,\n loadGlobalConfig,\n type SupervisorDaemonState,\n supervisorDaemonStateSchema,\n} from \"@neotx/core\";\nimport { defineCommand } from \"citty\";\nimport { printError, printSuccess } from \"../output.js\";\n\nconst DEFAULT_NAME = \"supervisor\";\n\nasync function readState(name: string): Promise<SupervisorDaemonState | null> {\n const statePath = getSupervisorStatePath(name);\n if (!existsSync(statePath)) return null;\n try {\n const raw = await readFile(statePath, \"utf-8\");\n return supervisorDaemonStateSchema.parse(JSON.parse(raw));\n } catch {\n return null;\n }\n}\n\nasync function isDaemonRunning(name: string): Promise<SupervisorDaemonState | null> {\n const state = await readState(name);\n if (!state || state.status === \"stopped\") return null;\n if (!isProcessAlive(state.pid)) return null;\n return state;\n}\n\nasync function handleStatus(name: string): Promise<void> {\n const state = await isDaemonRunning(name);\n if (!state) {\n console.log(`No supervisor daemon running (name: ${name}).`);\n return;\n }\n\n const config = await loadGlobalConfig();\n printSuccess(`Supervisor \"${name}\" running`);\n console.log(` PID: ${state.pid}`);\n console.log(` Port: ${state.port}`);\n console.log(` Session: ${state.sessionId}`);\n console.log(` Started: ${state.startedAt}`);\n console.log(` Timeout: ${config.supervisor.eventTimeoutMs / 1000}s`);\n console.log(` Heartbeats: ${state.heartbeatCount}`);\n if (state.lastHeartbeat) {\n console.log(` Last beat: ${state.lastHeartbeat}`);\n }\n console.log(` Cost today: $${state.todayCostUsd?.toFixed(2) ?? \"0.00\"}`);\n console.log(` Cost total: $${state.totalCostUsd?.toFixed(2) ?? \"0.00\"}`);\n console.log(` Status: ${state.status}`);\n console.log(\"\");\n console.log(` Health: curl localhost:${state.port}/health`);\n console.log(\" TUI: neo supervise\");\n console.log(\" Stop: neo supervise --kill\");\n}\n\nasync function handleKill(name: string): Promise<void> {\n const state = await isDaemonRunning(name);\n if (!state) {\n printError(`No supervisor daemon running (name: ${name}).`);\n\n // Clean up stale lock if exists\n const lockPath = getSupervisorLockPath(name);\n if (existsSync(lockPath)) {\n await rm(lockPath, { force: true });\n }\n process.exitCode = 1;\n return;\n }\n\n // Send SIGTERM for graceful shutdown, then SIGKILL after 10s\n const pid = state.pid;\n try {\n process.kill(pid, \"SIGTERM\");\n printSuccess(`Sent SIGTERM to supervisor \"${name}\" (PID ${pid})`);\n } catch {\n printError(`Failed to send signal to PID ${pid}. Cleaning up.`);\n const lockPath = getSupervisorLockPath(name);\n await rm(lockPath, { force: true });\n return;\n }\n\n // Wait up to 10s for graceful exit, then force kill\n const deadline = Date.now() + 10_000;\n while (Date.now() < deadline) {\n await new Promise((r) => setTimeout(r, 500));\n if (!isProcessAlive(pid)) {\n printSuccess(\"Daemon stopped.\");\n return;\n }\n }\n\n // Force kill\n try {\n process.kill(pid, \"SIGKILL\");\n printSuccess(`Daemon did not exit in time — sent SIGKILL (PID ${pid}).`);\n } catch {\n // Already dead\n }\n\n // Clean up lock\n const lockPath = getSupervisorLockPath(name);\n await rm(lockPath, { force: true });\n}\n\nasync function startDaemon(name: string): Promise<void> {\n const running = await isDaemonRunning(name);\n if (running) {\n printError(`Supervisor \"${name}\" is already running (PID ${running.pid}).`);\n printError(\"Use --kill first, or run neo supervise to open TUI.\");\n process.exitCode = 1;\n return;\n }\n\n // Clean up stale lock\n const lockPath = getSupervisorLockPath(name);\n if (existsSync(lockPath)) {\n await rm(lockPath, { force: true });\n }\n\n // Resolve the worker script path and package root (for module resolution)\n const __dirname = path.dirname(fileURLToPath(import.meta.url));\n const workerPath = path.join(__dirname, \"daemon\", \"supervisor-worker.js\");\n const packageRoot = path.resolve(__dirname, \"..\");\n\n // Spawn as detached child process with stdio to log file\n const logDir = getSupervisorDir(name);\n await mkdir(logDir, { recursive: true });\n const logFd = openSync(path.join(logDir, \"daemon.log\"), \"a\");\n const child = spawn(process.execPath, [workerPath, name], {\n detached: true,\n stdio: [\"ignore\", logFd, logFd],\n cwd: packageRoot,\n env: process.env,\n });\n child.unref();\n closeSync(logFd);\n\n const config = await loadGlobalConfig();\n printSuccess(`Supervisor \"${name}\" started (PID ${child.pid})`);\n console.log(` Port: ${config.supervisor.port}`);\n console.log(` Health: curl localhost:${config.supervisor.port}/health`);\n console.log(` Webhook: curl -X POST localhost:${config.supervisor.port}/webhook -d '{}'`);\n console.log(` Logs: ${getSupervisorDir(name)}/daemon.log`);\n console.log(` TUI: neo supervise`);\n console.log(` Status: neo supervise --status`);\n console.log(` Stop: neo supervise --kill`);\n}\n\nasync function handleAttach(name: string): Promise<void> {\n const running = await isDaemonRunning(name);\n if (!running) {\n printError(`No supervisor daemon running (name: ${name}).`);\n printError(\"Start with: neo supervise\");\n process.exitCode = 1;\n return;\n }\n\n const { renderSupervisorTui } = await import(\"../tui/index.js\");\n await renderSupervisorTui(name);\n}\n\nasync function handleMessage(name: string, text: string): Promise<void> {\n const running = await isDaemonRunning(name);\n if (!running) {\n printError(`No supervisor daemon running (name: ${name}).`);\n process.exitCode = 1;\n return;\n }\n\n const id = randomUUID();\n const timestamp = new Date().toISOString();\n\n const message = { id, from: \"api\" as const, text, timestamp };\n await appendFile(getSupervisorInboxPath(name), `${JSON.stringify(message)}\\n`, \"utf-8\");\n\n // Write to activity.jsonl so the message appears in the TUI conversation\n const activityEntry = { id, type: \"message\", summary: text, timestamp };\n await appendFile(getSupervisorActivityPath(name), `${JSON.stringify(activityEntry)}\\n`, \"utf-8\");\n\n printSuccess(`Message sent to supervisor \"${name}\".`);\n}\n\nexport default defineCommand({\n meta: {\n name: \"supervise\",\n description: \"Manage the autonomous supervisor daemon\",\n },\n args: {\n name: {\n type: \"string\",\n description: \"Supervisor instance name\",\n default: DEFAULT_NAME,\n },\n status: {\n type: \"boolean\",\n description: \"Show supervisor status\",\n default: false,\n },\n kill: {\n type: \"boolean\",\n description: \"Stop the running supervisor\",\n default: false,\n },\n attach: {\n type: \"boolean\",\n description: \"Open the TUI for a running supervisor (default when no flags given)\",\n default: false,\n },\n detach: {\n type: \"boolean\",\n alias: \"d\",\n description: \"Start daemon in the background without opening the TUI\",\n default: false,\n },\n message: {\n type: \"string\",\n description: \"Send a message to the supervisor inbox\",\n },\n },\n async run({ args }) {\n const name = args.name;\n\n if (args.status) {\n await handleStatus(name);\n return;\n }\n\n if (args.kill) {\n await handleKill(name);\n return;\n }\n\n if (args.attach) {\n await handleAttach(name);\n return;\n }\n\n if (args.message) {\n await handleMessage(name, args.message);\n return;\n }\n\n // --detach: start daemon headless (no TUI)\n if (args.detach) {\n const alreadyRunning = await isDaemonRunning(name);\n if (alreadyRunning) {\n printSuccess(`Supervisor \"${name}\" already running (PID ${alreadyRunning.pid}).`);\n return;\n }\n await startDaemon(name);\n return;\n }\n\n // Default: start daemon if needed, then open TUI\n const alreadyRunning = await isDaemonRunning(name);\n if (!alreadyRunning) {\n await startDaemon(name);\n // Wait briefly for daemon to initialize before attaching\n await new Promise((r) => setTimeout(r, 1500));\n }\n await handleAttach(name);\n },\n});\n"],"mappings":"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","names":["lockPath","alreadyRunning"]}
@@ -5,10 +5,13 @@ import React from "react";
5
5
  // src/tui/supervisor-tui.tsx
6
6
  import { randomUUID } from "crypto";
7
7
  import { appendFile, readFile } from "fs/promises";
8
+ import path from "path";
8
9
  import {
9
10
  getSupervisorActivityPath,
11
+ getSupervisorDir,
10
12
  getSupervisorInboxPath,
11
- getSupervisorStatePath
13
+ getSupervisorStatePath,
14
+ MemoryStore
12
15
  } from "@neotx/core";
13
16
  import { Box, Text, useApp, useInput, useStdout } from "ink";
14
17
  import TextInput from "ink-text-input";
@@ -283,39 +286,80 @@ function ActivityRow({
283
286
  /* @__PURE__ */ jsx(Text, { dimColor: isOld, children: formatTime(entry.timestamp) }),
284
287
  /* @__PURE__ */ jsx(Text, { color, dimColor: isOld, bold: isLatest, children: icon }),
285
288
  /* @__PURE__ */ jsx(Text, { color, dimColor: isOld, bold: true, children: label }),
286
- /* @__PURE__ */ jsx(Text, { dimColor: isOld, bold: isLatest, wrap: "truncate", children: entry.summary })
289
+ /* @__PURE__ */ jsx(Text, { dimColor: isOld, bold: isLatest, children: entry.summary })
287
290
  ] });
288
291
  }
289
- function ThinkingPanel({ entries }) {
290
- const latest = [...entries].reverse().find((e) => {
291
- const type = e.type;
292
- return type === "thinking" || type === "plan";
293
- });
294
- if (!latest) return null;
295
- const icon = TYPE_ICONS[latest.type] ?? "\xB7";
296
- const color = TYPE_COLORS[latest.type] ?? "#9ca3af";
297
- const label = latest.type === "thinking" ? "THINKING" : "PLANNING";
298
- const text = latest.summary.length > 600 ? `${latest.summary.slice(0, 600)}...` : latest.summary;
292
+ var TASK_STATUS_COLORS = {
293
+ in_progress: "#60a5fa",
294
+ blocked: "#f87171",
295
+ pending: "#6b7280",
296
+ done: "#4ade80"
297
+ };
298
+ var TASK_STATUS_LABELS = {
299
+ in_progress: "ACTIVE",
300
+ blocked: "BLOCK",
301
+ pending: "\xB7"
302
+ };
303
+ function TaskPanel({ tasks }) {
304
+ const active = tasks.filter((t) => t.outcome !== "done" && t.outcome !== "abandoned");
305
+ const doneCount = tasks.filter((t) => t.outcome === "done").length;
306
+ if (tasks.length === 0) return null;
307
+ const MAX_VISIBLE = 6;
308
+ const visible = active.slice(0, MAX_VISIBLE);
309
+ const overflow = active.length - visible.length;
299
310
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
300
311
  /* @__PURE__ */ jsxs(Box, { paddingX: 2, gap: 1, children: [
301
312
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u251C" }),
302
- /* @__PURE__ */ jsxs(Text, { color, bold: true, children: [
303
- icon,
304
- " ",
305
- label
313
+ /* @__PURE__ */ jsx(Text, { dimColor: true, bold: true, children: "TASKS" }),
314
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
315
+ "(",
316
+ active.length,
317
+ " active, ",
318
+ doneCount,
319
+ " done)"
306
320
  ] }),
307
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(36) })
321
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(30) })
308
322
  ] }),
309
- /* @__PURE__ */ jsxs(Box, { paddingX: 2, children: [
310
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502 " }),
311
- /* @__PURE__ */ jsx(Text, { color, wrap: "truncate-end", children: text })
312
- ] }),
313
- /* @__PURE__ */ jsx(Box, { paddingX: 2, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }) })
323
+ visible.map((t) => {
324
+ const status = t.outcome ?? "pending";
325
+ const color = TASK_STATUS_COLORS[status] ?? "#6b7280";
326
+ const label = (TASK_STATUS_LABELS[status] ?? "\xB7").padEnd(6);
327
+ const prio = t.severity ? `[${t.severity.slice(0, 3)}] ` : "";
328
+ const repo = t.scope !== "global" ? path.basename(t.scope) : "";
329
+ const run = t.runId ? `run:${t.runId.slice(0, 4)}` : "";
330
+ const meta = [repo, run].filter(Boolean).join(" ");
331
+ return /* @__PURE__ */ jsxs(Box, { gap: 1, paddingX: 2, children: [
332
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
333
+ /* @__PURE__ */ jsx(Text, { color, bold: true, children: label }),
334
+ prio && /* @__PURE__ */ jsx(Text, { dimColor: true, children: prio.padEnd(5) }),
335
+ /* @__PURE__ */ jsx(Text, { wrap: "truncate", children: t.content }),
336
+ meta && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
337
+ "(",
338
+ meta,
339
+ ")"
340
+ ] })
341
+ ] }, t.id);
342
+ }),
343
+ overflow > 0 && /* @__PURE__ */ jsx(Box, { paddingX: 2, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
344
+ "\u2502 ... +",
345
+ overflow,
346
+ " more pending"
347
+ ] }) })
314
348
  ] });
315
349
  }
350
+ var ACTIVITY_TYPES = /* @__PURE__ */ new Set([
351
+ "heartbeat",
352
+ "decision",
353
+ "action",
354
+ "dispatch",
355
+ "error",
356
+ "event",
357
+ "message"
358
+ ]);
316
359
  function ActivityPanel({ entries, termHeight }) {
317
- const maxVisible = Math.max(5, Math.min(MAX_VISIBLE_ENTRIES, termHeight - 14));
318
- const visible = entries.slice(-maxVisible);
360
+ const maxVisible = Math.max(5, Math.min(MAX_VISIBLE_ENTRIES, termHeight - 10));
361
+ const filtered = entries.filter((e) => ACTIVITY_TYPES.has(e.type));
362
+ const visible = filtered.slice(-maxVisible);
319
363
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
320
364
  /* @__PURE__ */ jsxs(Box, { paddingX: 2, gap: 1, children: [
321
365
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u251C" }),
@@ -404,15 +448,27 @@ async function readActivity(name, maxEntries) {
404
448
  return [];
405
449
  }
406
450
  }
451
+ function readTasks(name) {
452
+ try {
453
+ const dir = getSupervisorDir(name);
454
+ const store = new MemoryStore(path.join(dir, "memory.sqlite"));
455
+ const tasks = store.query({ types: ["task"], limit: 20, sortBy: "createdAt" });
456
+ store.close();
457
+ return tasks;
458
+ } catch {
459
+ return [];
460
+ }
461
+ }
407
462
  async function sendMessage(name, text) {
408
- const message = {
409
- id: randomUUID(),
410
- from: "tui",
411
- text,
412
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
413
- };
463
+ const id = randomUUID();
464
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
465
+ const message = { id, from: "tui", text, timestamp };
414
466
  const inboxPath = getSupervisorInboxPath(name);
415
467
  await appendFile(inboxPath, `${JSON.stringify(message)}
468
+ `, "utf-8");
469
+ const activityEntry = { id, type: "message", summary: text, timestamp };
470
+ const activityPath = getSupervisorActivityPath(name);
471
+ await appendFile(activityPath, `${JSON.stringify(activityEntry)}
416
472
  `, "utf-8");
417
473
  }
418
474
  function SupervisorTui({ name }) {
@@ -422,6 +478,7 @@ function SupervisorTui({ name }) {
422
478
  const clock = useClock();
423
479
  const [state, setState] = useState(null);
424
480
  const [entries, setEntries] = useState([]);
481
+ const [tasks, setTasks] = useState([]);
425
482
  const [input, setInput] = useState("");
426
483
  const [lastSent, setLastSent] = useState("");
427
484
  const [termHeight, setTermHeight] = useState(stdout?.rows ?? 30);
@@ -445,6 +502,7 @@ function SupervisorTui({ name }) {
445
502
  if (!active) return;
446
503
  setState(newState);
447
504
  setEntries(newEntries);
505
+ setTasks(readTasks(name));
448
506
  }
449
507
  poll();
450
508
  const interval = setInterval(poll, POLL_INTERVAL_MS);
@@ -471,8 +529,17 @@ function SupervisorTui({ name }) {
471
529
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
472
530
  /* @__PURE__ */ jsx(HeaderBar, { state, name, frame, clock }),
473
531
  /* @__PURE__ */ jsx(BudgetPanel, { state, dailyCap: 50, costHistory }),
474
- /* @__PURE__ */ jsx(ThinkingPanel, { entries }),
475
- /* @__PURE__ */ jsx(ActivityPanel, { entries, termHeight }),
532
+ /* @__PURE__ */ jsx(TaskPanel, { tasks }),
533
+ /* @__PURE__ */ jsx(
534
+ ActivityPanel,
535
+ {
536
+ entries,
537
+ termHeight: termHeight - (tasks.length > 0 ? Math.min(
538
+ tasks.filter((t) => t.outcome !== "done" && t.outcome !== "abandoned").length,
539
+ 6
540
+ ) + 2 : 0)
541
+ }
542
+ ),
476
543
  /* @__PURE__ */ jsx(InputPanel, { value: input, onChange: setInput, onSubmit: handleSubmit, lastSent }),
477
544
  /* @__PURE__ */ jsx(Footer, {})
478
545
  ] });
@@ -486,4 +553,4 @@ async function renderSupervisorTui(name) {
486
553
  export {
487
554
  renderSupervisorTui
488
555
  };
489
- //# sourceMappingURL=tui-QS3RPHKH.js.map
556
+ //# sourceMappingURL=tui-6USVDV75.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tui/index.ts","../src/tui/supervisor-tui.tsx"],"sourcesContent":["import { render } from \"ink\";\nimport React from \"react\";\nimport { SupervisorTui } from \"./supervisor-tui.js\";\n\n/**\n * Render the supervisor TUI. Returns a promise that resolves when the user exits.\n */\nexport async function renderSupervisorTui(name: string): Promise<void> {\n const { waitUntilExit } = render(React.createElement(SupervisorTui, { name }));\n await waitUntilExit();\n}\n","import { randomUUID } from \"node:crypto\";\nimport { appendFile, readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { ActivityEntry, MemoryEntry, SupervisorDaemonState } from \"@neotx/core\";\nimport {\n getSupervisorActivityPath,\n getSupervisorDir,\n getSupervisorInboxPath,\n getSupervisorStatePath,\n MemoryStore,\n} from \"@neotx/core\";\nimport { Box, Text, useApp, useInput, useStdout } from \"ink\";\nimport TextInput from \"ink-text-input\";\nimport { useCallback, useEffect, useState } from \"react\";\n\n// ─── Constants ───────────────────────────────────────────\n\nconst MAX_VISIBLE_ENTRIES = 24;\nconst POLL_INTERVAL_MS = 1_500;\nconst ANIMATION_TICK_MS = 400;\n\n// ─── Unicode Visual Elements ─────────────────────────────\n\nconst SPARK_CHARS = [\"▁\", \"▂\", \"▃\", \"▄\", \"▅\", \"▆\", \"▇\", \"█\"];\nconst BLOCK_FULL = \"█\";\nconst BLOCK_EMPTY = \"░\";\nconst PULSE_FRAMES = [\"◉\", \"◎\", \"○\", \"◎\"];\nconst IDLE_FRAMES = [\"◌\", \"◌\", \"◌\", \"◌\"];\n\nconst TYPE_ICONS: Record<string, string> = {\n heartbeat: \"♥\",\n decision: \"★\",\n action: \"⚡\",\n error: \"✖\",\n event: \"◆\",\n message: \"✉\",\n thinking: \"◇\",\n plan: \"▸\",\n dispatch: \"↗\",\n tool_use: \"⊘\",\n};\n\nconst TYPE_COLORS: Record<string, string> = {\n heartbeat: \"#6ee7b7\",\n decision: \"#fbbf24\",\n action: \"#60a5fa\",\n error: \"#f87171\",\n event: \"#c084fc\",\n message: \"#67e8f9\",\n thinking: \"#a78bfa\",\n plan: \"#34d399\",\n dispatch: \"#f472b6\",\n tool_use: \"#38bdf8\",\n};\n\nconst TYPE_LABELS: Record<string, string> = {\n heartbeat: \"BEAT\",\n decision: \"DECIDE\",\n action: \"ACTION\",\n error: \"ERROR\",\n event: \"EVENT\",\n message: \"MSG\",\n thinking: \"THINK\",\n plan: \"PLAN\",\n dispatch: \"SEND\",\n tool_use: \"TOOL\",\n};\n\n// ─── Helpers ─────────────────────────────────────────────\n\nfunction formatTime(timestamp: string): string {\n return timestamp.slice(11, 19);\n}\n\nfunction formatUptime(startedAt: string): string {\n const ms = Date.now() - new Date(startedAt).getTime();\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n const days = Math.floor(hours / 24);\n if (days > 0) return `${days}d ${hours % 24}h`;\n if (hours > 0) return `${hours}h ${minutes % 60}m`;\n if (minutes > 0) return `${minutes}m ${seconds % 60}s`;\n return `${seconds}s`;\n}\n\nfunction formatTimeAgo(timestamp: string): string {\n const ms = Date.now() - new Date(timestamp).getTime();\n const seconds = Math.floor(ms / 1000);\n if (seconds < 60) return `${seconds}s ago`;\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m ago`;\n const hours = Math.floor(minutes / 60);\n return `${hours}h ago`;\n}\n\nfunction buildProgressBar(ratio: number, width: number): { filled: string; empty: string } {\n const clamped = Math.max(0, Math.min(1, ratio));\n const filledCount = Math.round(clamped * width);\n return {\n filled: BLOCK_FULL.repeat(filledCount),\n empty: BLOCK_EMPTY.repeat(width - filledCount),\n };\n}\n\nfunction buildSparkline(values: number[], width: number): string {\n if (values.length === 0) return \"▁\".repeat(width);\n const recent = values.slice(-width);\n const max = Math.max(...recent, 0.001);\n return recent\n .map((v) => {\n const idx = Math.min(\n Math.floor((v / max) * (SPARK_CHARS.length - 1)),\n SPARK_CHARS.length - 1,\n );\n return SPARK_CHARS[idx];\n })\n .join(\"\");\n}\n\nfunction extractCostHistory(entries: ActivityEntry[]): number[] {\n return entries\n .filter((e) => e.type === \"heartbeat\" && e.summary.includes(\"complete\"))\n .map((e) => {\n const detail = e.detail as Record<string, unknown> | undefined;\n return typeof detail?.costUsd === \"number\" ? detail.costUsd : 0;\n });\n}\n\n// ─── Animated Hooks ──────────────────────────────────────\n\nfunction useAnimationFrame(): number {\n const [frame, setFrame] = useState(0);\n useEffect(() => {\n const interval = setInterval(() => setFrame((f) => f + 1), ANIMATION_TICK_MS);\n return () => clearInterval(interval);\n }, []);\n return frame;\n}\n\nfunction useClock(): string {\n const [time, setTime] = useState(() => new Date().toLocaleTimeString());\n useEffect(() => {\n const interval = setInterval(() => setTime(new Date().toLocaleTimeString()), 1000);\n return () => clearInterval(interval);\n }, []);\n return time;\n}\n\n// ─── Components ──────────────────────────────────────────\n\nfunction Logo() {\n return (\n <Box paddingX={1} gap={1}>\n <Text color=\"#c084fc\" bold>\n ◆\n </Text>\n <Text bold>\n <Text color=\"#c084fc\">N</Text>\n <Text color=\"#a78bfa\">E</Text>\n <Text color=\"#818cf8\">O</Text>\n </Text>\n <Text dimColor>SUPERVISOR</Text>\n </Box>\n );\n}\n\nfunction LiveIndicator({ frame, isRunning }: { frame: number; isRunning: boolean }) {\n const frames = isRunning ? PULSE_FRAMES : IDLE_FRAMES;\n const dot = frames[frame % frames.length];\n return (\n <Box paddingX={1}>\n <Text color={isRunning ? \"#4ade80\" : \"#6b7280\"} bold>\n {dot}\n </Text>\n <Text color={isRunning ? \"#4ade80\" : \"#6b7280\"} bold>\n {\" \"}\n {isRunning ? \"LIVE\" : \"IDLE\"}\n </Text>\n </Box>\n );\n}\n\nfunction HeaderBar({\n state,\n name,\n frame,\n clock,\n}: {\n state: SupervisorDaemonState | null;\n name: string;\n frame: number;\n clock: string;\n}) {\n if (!state) {\n return (\n <Box borderStyle=\"round\" borderColor=\"#6b7280\" paddingX={1} flexDirection=\"column\">\n <Box justifyContent=\"space-between\">\n <Logo />\n <Box paddingX={1}>\n <Text dimColor>{clock}</Text>\n </Box>\n </Box>\n <Box paddingX={1}>\n <Text color=\"#fbbf24\">⟳ Connecting to \"{name}\"...</Text>\n </Box>\n </Box>\n );\n }\n\n const isRunning = state.status === \"running\";\n\n return (\n <Box\n borderStyle=\"round\"\n borderColor={isRunning ? \"#6ee7b7\" : \"#f87171\"}\n paddingX={0}\n flexDirection=\"column\"\n >\n <Box justifyContent=\"space-between\">\n <Logo />\n <Box gap={2}>\n <LiveIndicator frame={frame} isRunning={isRunning} />\n <Box paddingX={1}>\n <Text dimColor>{clock}</Text>\n </Box>\n </Box>\n </Box>\n\n <Box paddingX={1} gap={1}>\n <Text dimColor>│</Text>\n <Text>\n <Text dimColor>pid</Text> <Text bold>{state.pid}</Text>\n </Text>\n <Text dimColor>·</Text>\n <Text>\n <Text dimColor>port</Text> <Text bold>:{state.port}</Text>\n </Text>\n <Text dimColor>·</Text>\n <Text>\n <Text dimColor>beats</Text>{\" \"}\n <Text bold color=\"#6ee7b7\">\n ▲{state.heartbeatCount}\n </Text>\n </Text>\n {state.lastHeartbeat && (\n <>\n <Text dimColor>·</Text>\n <Text>\n <Text dimColor>last</Text> <Text>{formatTimeAgo(state.lastHeartbeat)}</Text>\n </Text>\n </>\n )}\n <Text dimColor>·</Text>\n <Text>\n <Text dimColor>up</Text> <Text>{formatUptime(state.startedAt)}</Text>\n </Text>\n </Box>\n </Box>\n );\n}\n\nfunction BudgetPanel({\n state,\n dailyCap,\n costHistory,\n}: {\n state: SupervisorDaemonState | null;\n dailyCap: number;\n costHistory: number[];\n}) {\n if (!state) return null;\n\n const todayCost = state.todayCostUsd ?? 0;\n const totalCost = state.totalCostUsd ?? 0;\n const ratio = dailyCap > 0 ? todayCost / dailyCap : 0;\n const barWidth = 20;\n const bar = buildProgressBar(ratio, barWidth);\n const pct = Math.round(ratio * 100);\n\n const barColor = pct < 50 ? \"#4ade80\" : pct < 80 ? \"#fbbf24\" : \"#f87171\";\n\n const sparkline = buildSparkline(costHistory, 12);\n\n return (\n <Box paddingX={2} gap={2}>\n <Box gap={1}>\n <Text dimColor>budget</Text>\n <Text color={barColor}>{bar.filled}</Text>\n <Text dimColor>{bar.empty}</Text>\n <Text bold color={barColor}>\n {pct}%\n </Text>\n <Text dimColor>\n (${todayCost.toFixed(2)}/${dailyCap})\n </Text>\n </Box>\n <Text dimColor>│</Text>\n <Box gap={1}>\n <Text dimColor>total</Text>\n <Text bold>${totalCost.toFixed(2)}</Text>\n </Box>\n <Text dimColor>│</Text>\n <Box gap={1}>\n <Text dimColor>cost/beat</Text>\n <Text color=\"#818cf8\">{sparkline}</Text>\n </Box>\n </Box>\n );\n}\n\nfunction ActivityRow({\n entry,\n isLatest,\n isOld,\n}: {\n entry: ActivityEntry;\n isLatest: boolean;\n isOld: boolean;\n}) {\n const icon = TYPE_ICONS[entry.type] ?? \"·\";\n const color = TYPE_COLORS[entry.type] ?? \"#9ca3af\";\n const label = (TYPE_LABELS[entry.type] ?? (entry.type as string).toUpperCase()).padEnd(7);\n\n return (\n <Box gap={1} paddingX={2}>\n <Text dimColor={isOld}>{isLatest ? \"│\" : \"│\"}</Text>\n <Text dimColor={isOld}>{formatTime(entry.timestamp)}</Text>\n <Text color={color} dimColor={isOld} bold={isLatest}>\n {icon}\n </Text>\n <Text color={color} dimColor={isOld} bold>\n {label}\n </Text>\n <Text dimColor={isOld} bold={isLatest}>\n {entry.summary}\n </Text>\n </Box>\n );\n}\n\nconst TASK_STATUS_COLORS: Record<string, string> = {\n in_progress: \"#60a5fa\",\n blocked: \"#f87171\",\n pending: \"#6b7280\",\n done: \"#4ade80\",\n};\n\nconst TASK_STATUS_LABELS: Record<string, string> = {\n in_progress: \"ACTIVE\",\n blocked: \"BLOCK\",\n pending: \"·\",\n};\n\nfunction TaskPanel({ tasks }: { tasks: MemoryEntry[] }) {\n const active = tasks.filter((t) => t.outcome !== \"done\" && t.outcome !== \"abandoned\");\n const doneCount = tasks.filter((t) => t.outcome === \"done\").length;\n\n if (tasks.length === 0) return null;\n\n const MAX_VISIBLE = 6;\n const visible = active.slice(0, MAX_VISIBLE);\n const overflow = active.length - visible.length;\n\n return (\n <Box flexDirection=\"column\">\n <Box paddingX={2} gap={1}>\n <Text dimColor>├</Text>\n <Text dimColor bold>\n TASKS\n </Text>\n <Text dimColor>\n ({active.length} active, {doneCount} done)\n </Text>\n <Text dimColor>{\"─\".repeat(30)}</Text>\n </Box>\n {visible.map((t) => {\n const status = t.outcome ?? \"pending\";\n const color = TASK_STATUS_COLORS[status] ?? \"#6b7280\";\n const label = (TASK_STATUS_LABELS[status] ?? \"·\").padEnd(6);\n const prio = t.severity ? `[${t.severity.slice(0, 3)}] ` : \"\";\n const repo = t.scope !== \"global\" ? path.basename(t.scope) : \"\";\n const run = t.runId ? `run:${t.runId.slice(0, 4)}` : \"\";\n const meta = [repo, run].filter(Boolean).join(\" \");\n\n return (\n <Box key={t.id} gap={1} paddingX={2}>\n <Text dimColor>│</Text>\n <Text color={color} bold>\n {label}\n </Text>\n {prio && <Text dimColor>{prio.padEnd(5)}</Text>}\n <Text wrap=\"truncate\">{t.content}</Text>\n {meta && <Text dimColor>({meta})</Text>}\n </Box>\n );\n })}\n {overflow > 0 && (\n <Box paddingX={2}>\n <Text dimColor>│ ... +{overflow} more pending</Text>\n </Box>\n )}\n </Box>\n );\n}\n\n/** Types shown in the activity feed — plan/thinking are internal, not shown */\nconst ACTIVITY_TYPES = new Set([\n \"heartbeat\",\n \"decision\",\n \"action\",\n \"dispatch\",\n \"error\",\n \"event\",\n \"message\",\n]);\n\nfunction ActivityPanel({ entries, termHeight }: { entries: ActivityEntry[]; termHeight: number }) {\n // Reserve lines for header (5), budget (1), separator (1), input (2), footer (1) = 10\n const maxVisible = Math.max(5, Math.min(MAX_VISIBLE_ENTRIES, termHeight - 10));\n const filtered = entries.filter((e) => ACTIVITY_TYPES.has(e.type));\n const visible = filtered.slice(-maxVisible);\n\n return (\n <Box flexDirection=\"column\">\n <Box paddingX={2} gap={1}>\n <Text dimColor>├</Text>\n <Text dimColor bold>\n ACTIVITY\n </Text>\n <Text dimColor>{\"─\".repeat(40)}</Text>\n </Box>\n\n {visible.length === 0 ? (\n <Box paddingX={2}>\n <Text dimColor>│ Waiting for heartbeats...</Text>\n </Box>\n ) : (\n visible.map((entry, idx) => (\n <ActivityRow\n key={entry.id}\n entry={entry}\n isLatest={idx === visible.length - 1}\n isOld={idx < visible.length - 5}\n />\n ))\n )}\n\n <Box paddingX={2}>\n <Text dimColor>│</Text>\n </Box>\n </Box>\n );\n}\n\nfunction InputPanel({\n value,\n onChange,\n onSubmit,\n lastSent,\n}: {\n value: string;\n onChange: (v: string) => void;\n onSubmit: (v: string) => void;\n lastSent: string;\n}) {\n return (\n <Box flexDirection=\"column\">\n <Box paddingX={2} gap={1}>\n <Text dimColor>└</Text>\n <Text bold color=\"#60a5fa\">\n ❯\n </Text>\n <TextInput\n value={value}\n onChange={onChange}\n onSubmit={onSubmit}\n placeholder=\"message the supervisor...\"\n />\n </Box>\n <Box paddingX={2} gap={1}>\n <Text dimColor> </Text>\n {lastSent ? <Text color=\"#6b7280\">✓ \"{lastSent}\"</Text> : null}\n </Box>\n </Box>\n );\n}\n\nfunction Footer() {\n return (\n <Box paddingX={2} gap={1} justifyContent=\"center\">\n <Text dimColor>\n <Text bold>esc</Text> quit\n </Text>\n <Text dimColor>·</Text>\n <Text dimColor>\n <Text bold>enter</Text> send\n </Text>\n <Text dimColor>·</Text>\n <Text dimColor>daemon keeps running</Text>\n </Box>\n );\n}\n\n// ─── Data Fetching ───────────────────────────────────────\n\nasync function readState(name: string): Promise<SupervisorDaemonState | null> {\n try {\n const raw = await readFile(getSupervisorStatePath(name), \"utf-8\");\n return JSON.parse(raw) as SupervisorDaemonState;\n } catch {\n return null;\n }\n}\n\nasync function readActivity(name: string, maxEntries: number): Promise<ActivityEntry[]> {\n try {\n const content = await readFile(getSupervisorActivityPath(name), \"utf-8\");\n const lines = content.trim().split(\"\\n\").filter(Boolean);\n const lastLines = lines.slice(-maxEntries);\n const entries: ActivityEntry[] = [];\n for (const line of lastLines) {\n try {\n entries.push(JSON.parse(line) as ActivityEntry);\n } catch {\n // Skip malformed\n }\n }\n return entries;\n } catch {\n return [];\n }\n}\n\nfunction readTasks(name: string): MemoryEntry[] {\n try {\n const dir = getSupervisorDir(name);\n const store = new MemoryStore(path.join(dir, \"memory.sqlite\"));\n const tasks = store.query({ types: [\"task\"], limit: 20, sortBy: \"createdAt\" });\n store.close();\n return tasks;\n } catch {\n return [];\n }\n}\n\nasync function sendMessage(name: string, text: string): Promise<void> {\n const id = randomUUID();\n const timestamp = new Date().toISOString();\n\n const message = { id, from: \"tui\" as const, text, timestamp };\n const inboxPath = getSupervisorInboxPath(name);\n await appendFile(inboxPath, `${JSON.stringify(message)}\\n`, \"utf-8\");\n\n // Write to activity.jsonl so the message appears in the TUI conversation\n const activityEntry = { id, type: \"message\", summary: text, timestamp };\n const activityPath = getSupervisorActivityPath(name);\n await appendFile(activityPath, `${JSON.stringify(activityEntry)}\\n`, \"utf-8\");\n}\n\n// ─── Main Component ──────────────────────────────────────\n\nexport function SupervisorTui({ name }: { name: string }) {\n const { exit } = useApp();\n const { stdout } = useStdout();\n const frame = useAnimationFrame();\n const clock = useClock();\n\n const [state, setState] = useState<SupervisorDaemonState | null>(null);\n const [entries, setEntries] = useState<ActivityEntry[]>([]);\n const [tasks, setTasks] = useState<MemoryEntry[]>([]);\n const [input, setInput] = useState(\"\");\n const [lastSent, setLastSent] = useState(\"\");\n const [termHeight, setTermHeight] = useState(stdout?.rows ?? 30);\n\n // Track terminal resize\n useEffect(() => {\n function onResize() {\n if (stdout) setTermHeight(stdout.rows);\n }\n stdout?.on(\"resize\", onResize);\n return () => {\n stdout?.off(\"resize\", onResize);\n };\n }, [stdout]);\n\n // Poll state and activity\n useEffect(() => {\n let active = true;\n\n async function poll() {\n if (!active) return;\n const [newState, newEntries] = await Promise.all([\n readState(name),\n readActivity(name, MAX_VISIBLE_ENTRIES),\n ]);\n if (!active) return;\n setState(newState);\n setEntries(newEntries);\n setTasks(readTasks(name));\n }\n\n poll();\n const interval = setInterval(poll, POLL_INTERVAL_MS);\n return () => {\n active = false;\n clearInterval(interval);\n };\n }, [name]);\n\n useInput((_input, key) => {\n if (key.escape) {\n exit();\n }\n });\n\n const handleSubmit = useCallback(\n (text: string) => {\n if (!text.trim()) return;\n sendMessage(name, text.trim());\n setLastSent(text.trim());\n setInput(\"\");\n },\n [name],\n );\n\n const costHistory = extractCostHistory(entries);\n\n return (\n <Box flexDirection=\"column\">\n <HeaderBar state={state} name={name} frame={frame} clock={clock} />\n <BudgetPanel state={state} dailyCap={50} costHistory={costHistory} />\n <TaskPanel tasks={tasks} />\n <ActivityPanel\n entries={entries}\n termHeight={\n termHeight -\n (tasks.length > 0\n ? Math.min(\n tasks.filter((t) => t.outcome !== \"done\" && t.outcome !== \"abandoned\").length,\n 6,\n ) + 2\n : 0)\n }\n />\n <InputPanel value={input} onChange={setInput} onSubmit={handleSubmit} lastSent={lastSent} />\n <Footer />\n </Box>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,26 @@
1
+ // src/commands/version.ts
2
+ import { readFile } from "fs/promises";
3
+ import { dirname, join } from "path";
4
+ import { fileURLToPath } from "url";
5
+ import { defineCommand } from "citty";
6
+ async function getVersion() {
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const pkgPath = join(__dirname, "..", "..", "package.json");
9
+ const content = await readFile(pkgPath, "utf-8");
10
+ const pkg = JSON.parse(content);
11
+ return pkg.version;
12
+ }
13
+ var version_default = defineCommand({
14
+ meta: {
15
+ name: "version",
16
+ description: "Display the current neo version"
17
+ },
18
+ async run() {
19
+ const version = await getVersion();
20
+ console.log(`neo v${version}`);
21
+ }
22
+ });
23
+ export {
24
+ version_default as default
25
+ };
26
+ //# sourceMappingURL=version-XVOAMGDD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/version.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { defineCommand } from \"citty\";\n\nasync function getVersion(): Promise<string> {\n const __dirname = dirname(fileURLToPath(import.meta.url));\n const pkgPath = join(__dirname, \"..\", \"..\", \"package.json\");\n const content = await readFile(pkgPath, \"utf-8\");\n const pkg = JSON.parse(content) as { version: string };\n return pkg.version;\n}\n\nexport default defineCommand({\n meta: {\n name: \"version\",\n description: \"Display the current neo version\",\n },\n async run() {\n const version = await getVersion();\n console.log(`neo v${version}`);\n },\n});\n"],"mappings":";AAAA,SAAS,gBAAgB;AACzB,SAAS,SAAS,YAAY;AAC9B,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAE9B,eAAe,aAA8B;AAC3C,QAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AACxD,QAAM,UAAU,KAAK,WAAW,MAAM,MAAM,cAAc;AAC1D,QAAM,UAAU,MAAM,SAAS,SAAS,OAAO;AAC/C,QAAM,MAAM,KAAK,MAAM,OAAO;AAC9B,SAAO,IAAI;AACb;AAEA,IAAO,kBAAQ,cAAc;AAAA,EAC3B,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,MAAM,MAAM;AACV,UAAM,UAAU,MAAM,WAAW;AACjC,YAAQ,IAAI,QAAQ,OAAO,EAAE;AAAA,EAC/B;AACF,CAAC;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neotx/cli",
3
- "version": "0.1.0-alpha.2",
3
+ "version": "0.1.0-alpha.4",
4
4
  "description": "CLI for the Neo orchestration framework",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -29,25 +29,43 @@
29
29
  "engines": {
30
30
  "node": ">=22"
31
31
  },
32
+ "man": [
33
+ "../../man/man1/neo.1",
34
+ "../../man/man1/neo-agents.1",
35
+ "../../man/man1/neo-cost.1",
36
+ "../../man/man1/neo-doctor.1",
37
+ "../../man/man1/neo-init.1",
38
+ "../../man/man1/neo-log.1",
39
+ "../../man/man1/neo-logs.1",
40
+ "../../man/man1/neo-mcp.1",
41
+ "../../man/man1/neo-memory.1",
42
+ "../../man/man1/neo-repos.1",
43
+ "../../man/man1/neo-run.1",
44
+ "../../man/man1/neo-runs.1",
45
+ "../../man/man1/neo-supervise.1",
46
+ "../../man/man1/neo-version.1"
47
+ ],
32
48
  "dependencies": {
33
49
  "citty": "^0.2.1",
34
50
  "ink": "^6.8.0",
35
51
  "ink-text-input": "^6.0.0",
36
52
  "react": "^19.2.4",
37
53
  "yaml": "^2.8.2",
38
- "@neotx/agents": "0.1.0-alpha.2",
39
- "@neotx/core": "0.1.0-alpha.2"
54
+ "@neotx/agents": "0.1.0-alpha.4",
55
+ "@neotx/core": "0.1.0-alpha.4"
40
56
  },
41
57
  "devDependencies": {
42
58
  "@anthropic-ai/claude-agent-sdk": "^0.1.0",
43
59
  "@types/node": "^25.5.0",
44
60
  "@types/react": "^19.2.14",
61
+ "@vitest/coverage-v8": "^3.0.0",
45
62
  "tsup": "^8.5.1"
46
63
  },
47
64
  "scripts": {
48
65
  "build": "tsup",
49
66
  "typecheck": "tsc --noEmit",
50
67
  "lint": "biome check src/",
51
- "test": "vitest run"
68
+ "test": "vitest run",
69
+ "install-man": "mkdir -p /usr/local/share/man/man1 && cp ../../man/man1/*.1 /usr/local/share/man/man1/ && mandb 2>/dev/null || true"
52
70
  }
53
71
  }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/repo-filter.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { readdir, readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { PersistedRun } from \"@neotx/core\";\nimport { getRunsDir, listReposFromGlobalConfig, toRepoSlug } from \"@neotx/core\";\n\nexport interface RepoFilter {\n mode: \"cwd\" | \"all\" | \"named\";\n repoSlug?: string;\n repoPath?: string;\n}\n\n/**\n * Resolve which repos to query based on --all / --repo flags.\n * Default: CWD-based (finds the matching registered repo slug, or uses basename).\n */\nexport async function resolveRepoFilter(args: {\n all?: boolean | undefined;\n repo?: string | undefined;\n}): Promise<RepoFilter> {\n if (args.all) return { mode: \"all\" };\n\n if (args.repo) {\n const repo = args.repo;\n // Could be a name/slug or a path\n const repos = await listReposFromGlobalConfig();\n const match = repos.find(\n (r) => toRepoSlug(r) === repo || path.resolve(r.path) === path.resolve(repo),\n );\n if (match) {\n return { mode: \"named\", repoSlug: toRepoSlug(match), repoPath: match.path };\n }\n // Treat as path, derive slug\n return { mode: \"named\", repoSlug: toRepoSlug({ path: repo }), repoPath: repo };\n }\n\n // Default: CWD\n const cwd = process.cwd();\n const repos = await listReposFromGlobalConfig();\n const match = repos.find((r) => path.resolve(r.path) === cwd);\n const slug = match ? toRepoSlug(match) : toRepoSlug({ path: cwd });\n return { mode: \"cwd\", repoSlug: slug, repoPath: cwd };\n}\n\n/**\n * Load persisted runs, filtered by RepoFilter.\n */\nexport async function loadRunsFiltered(filter: RepoFilter): Promise<PersistedRun[]> {\n const runsDir = getRunsDir();\n if (!existsSync(runsDir)) return [];\n\n const runs: PersistedRun[] = [];\n\n if (filter.mode === \"all\") {\n // Scan all slug subdirs + legacy flat files\n const entries = await readdir(runsDir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory()) {\n await loadRunsFromDir(path.join(runsDir, entry.name), runs);\n } else if (entry.name.endsWith(\".json\")) {\n await loadRunFile(path.join(runsDir, entry.name), runs);\n }\n }\n } else {\n // Specific slug dir\n const slugDir = path.join(runsDir, filter.repoSlug ?? \"unknown\");\n await loadRunsFromDir(slugDir, runs);\n // Also check legacy flat files matching this repo\n await loadLegacyRuns(runsDir, filter.repoPath, runs);\n }\n\n runs.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));\n return runs;\n}\n\nasync function loadRunsFromDir(dir: string, runs: PersistedRun[]): Promise<void> {\n if (!existsSync(dir)) return;\n const files = await readdir(dir);\n for (const file of files) {\n if (!file.endsWith(\".json\")) continue;\n await loadRunFile(path.join(dir, file), runs);\n }\n}\n\nasync function loadRunFile(filePath: string, runs: PersistedRun[]): Promise<void> {\n try {\n const content = await readFile(filePath, \"utf-8\");\n runs.push(JSON.parse(content) as PersistedRun);\n } catch {\n // Skip corrupt files\n }\n}\n\nasync function loadLegacyRuns(\n runsDir: string,\n repoPath: string | undefined,\n runs: PersistedRun[],\n): Promise<void> {\n if (!repoPath) return;\n const resolvedRepo = path.resolve(repoPath);\n\n try {\n const entries = await readdir(runsDir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith(\".json\")) continue;\n const filePath = path.join(runsDir, entry.name);\n const content = await readFile(filePath, \"utf-8\");\n const run = JSON.parse(content) as PersistedRun;\n if (path.resolve(run.repo) === resolvedRepo) {\n // Avoid duplicates\n if (!runs.some((r) => r.runId === run.runId)) {\n runs.push(run);\n }\n }\n }\n } catch {\n // Non-critical\n }\n}\n"],"mappings":"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","names":["repos","match"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/cost.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { readdir, readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { CostEntry } from \"@neotx/core\";\nimport { getJournalsDir, toRepoSlug } from \"@neotx/core\";\nimport { defineCommand } from \"citty\";\nimport { printError, printJson, printTable } from \"../output.js\";\nimport { resolveRepoFilter } from \"../repo-filter.js\";\n\nasync function readCostEntries(journalDir: string): Promise<CostEntry[]> {\n if (!existsSync(journalDir)) return [];\n const files = await readdir(journalDir);\n const costFiles = files\n .filter((f) => f.startsWith(\"cost-\"))\n .sort()\n .reverse();\n const entries: CostEntry[] = [];\n\n for (const file of costFiles) {\n const content = await readFile(path.join(journalDir, file), \"utf-8\");\n for (const line of content.trim().split(\"\\n\")) {\n if (!line.trim()) continue;\n entries.push(JSON.parse(line) as CostEntry);\n }\n }\n\n return entries;\n}\n\nfunction isToday(timestamp: string): boolean {\n const d = new Date(timestamp);\n const now = new Date();\n return (\n d.getUTCFullYear() === now.getUTCFullYear() &&\n d.getUTCMonth() === now.getUTCMonth() &&\n d.getUTCDate() === now.getUTCDate()\n );\n}\n\nexport default defineCommand({\n meta: {\n name: \"cost\",\n description: \"Show cost breakdown from journals (today, by agent, by run)\",\n },\n args: {\n all: {\n type: \"boolean\",\n description: \"Show costs from all repos\",\n default: false,\n },\n repo: {\n type: \"string\",\n description: \"Filter by repo name or path\",\n },\n short: {\n type: \"boolean\",\n description: \"Compact output for supervisor agents (saves tokens)\",\n default: false,\n },\n output: {\n type: \"string\",\n description: \"Output format: json\",\n },\n },\n async run({ args }) {\n const jsonOutput = args.output === \"json\";\n const journalDir = getJournalsDir();\n let entries = await readCostEntries(journalDir);\n\n if (entries.length === 0) {\n printError(\"No cost data found.\");\n process.exitCode = 1;\n return;\n }\n\n // Filter by repo unless --all\n const filter = await resolveRepoFilter({ all: args.all, repo: args.repo });\n if (filter.mode !== \"all\") {\n const slug = filter.repoSlug;\n entries = entries.filter((e) => {\n if (!e.repo) return false;\n return toRepoSlug({ path: e.repo }) === slug;\n });\n }\n\n const todayEntries = entries.filter((e) => isToday(e.timestamp));\n const todayTotal = todayEntries.reduce((sum, e) => sum + e.costUsd, 0);\n const allTimeTotal = entries.reduce((sum, e) => sum + e.costUsd, 0);\n\n // Breakdown by agent (today)\n const byAgent = new Map<string, { cost: number; runs: number }>();\n for (const e of todayEntries) {\n const prev = byAgent.get(e.agent) ?? { cost: 0, runs: 0 };\n byAgent.set(e.agent, { cost: prev.cost + e.costUsd, runs: prev.runs + 1 });\n }\n\n // Breakdown by repo (today, only in --all mode)\n const byRepo = new Map<string, { cost: number; runs: number }>();\n if (filter.mode === \"all\") {\n for (const e of todayEntries) {\n const repo = e.repo ?? \"unknown\";\n const prev = byRepo.get(repo) ?? { cost: 0, runs: 0 };\n byRepo.set(repo, { cost: prev.cost + e.costUsd, runs: prev.runs + 1 });\n }\n }\n\n if (jsonOutput) {\n printJson({\n today: {\n total: todayTotal,\n sessions: todayEntries.length,\n byAgent: Object.fromEntries(byAgent),\n ...(byRepo.size > 0 ? { byRepo: Object.fromEntries(byRepo) } : {}),\n },\n allTime: {\n total: allTimeTotal,\n sessions: entries.length,\n },\n });\n return;\n }\n\n if (args.short) {\n const agents = [...byAgent.entries()]\n .map(([name, data]) => `${name}=$${data.cost.toFixed(4)}`)\n .join(\" \");\n console.log(`today=$${todayTotal.toFixed(4)} sessions=${todayEntries.length} ${agents}`);\n return;\n }\n\n console.log(`Today: $${todayTotal.toFixed(4)} (${todayEntries.length} sessions)`);\n console.log(`All time: $${allTimeTotal.toFixed(4)} (${entries.length} sessions)`);\n\n if (byAgent.size > 0) {\n console.log(\"\");\n printTable(\n [\"AGENT\", \"COST TODAY\", \"SESSIONS\"],\n [...byAgent.entries()]\n .sort((a, b) => b[1].cost - a[1].cost)\n .map(([name, data]) => [name, `$${data.cost.toFixed(4)}`, String(data.runs)]),\n );\n }\n\n if (byRepo.size > 0) {\n console.log(\"\");\n printTable(\n [\"REPO\", \"COST TODAY\", \"SESSIONS\"],\n [...byRepo.entries()]\n .sort((a, b) => b[1].cost - a[1].cost)\n .map(([repo, data]) => [repo, `$${data.cost.toFixed(4)}`, String(data.runs)]),\n );\n }\n },\n});\n"],"mappings":"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","names":[]}