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

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.
@@ -80,29 +80,6 @@ async function checkRepoRegistered() {
80
80
  message: "CWD not registered. Run 'neo init' or 'neo repos add'. Zero-config mode works without registration."
81
81
  };
82
82
  }
83
- async function checkLegacyConfig() {
84
- const legacyPath = path.resolve(".neo/config.yml");
85
- if (existsSync(legacyPath)) {
86
- return {
87
- name: "Legacy config",
88
- status: "info",
89
- message: ".neo/config.yml detected \u2014 this file is no longer needed. Config is now in ~/.neo/config.yml."
90
- };
91
- }
92
- return null;
93
- }
94
- async function checkTmux() {
95
- try {
96
- const { stdout } = await execFileAsync("tmux", ["-V"]);
97
- return { name: "tmux", status: "pass", message: stdout.trim() };
98
- } catch {
99
- return {
100
- name: "tmux",
101
- status: "info",
102
- message: "not installed (required for neo supervise)"
103
- };
104
- }
105
- }
106
83
  async function checkClaudeCli() {
107
84
  try {
108
85
  const { stdout } = await execFileAsync("claude", ["--version"]);
@@ -163,8 +140,6 @@ var doctor_default = defineCommand({
163
140
  checkGit(),
164
141
  checkGlobalConfig(),
165
142
  checkRepoRegistered(),
166
- checkLegacyConfig(),
167
- checkTmux(),
168
143
  checkClaudeCli(),
169
144
  checkAgents(),
170
145
  checkJournalDirs()
@@ -195,4 +170,4 @@ var doctor_default = defineCommand({
195
170
  export {
196
171
  doctor_default as default
197
172
  };
198
- //# sourceMappingURL=doctor-CPVIT7IP.js.map
173
+ //# sourceMappingURL=doctor-GC4NH7H6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/doctor.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { access, constants } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { promisify } from \"node:util\";\nimport {\n AgentRegistry,\n getDataDir,\n getJournalsDir,\n listReposFromGlobalConfig,\n loadGlobalConfig,\n toRepoSlug,\n} from \"@neotx/core\";\nimport { defineCommand } from \"citty\";\nimport { printError, printJson, printSuccess } from \"../output.js\";\nimport { resolveAgentsDir } from \"../resolve.js\";\n\nconst execFileAsync = promisify(execFile);\n\ninterface CheckResult {\n name: string;\n status: \"pass\" | \"fail\" | \"info\";\n message?: string;\n}\n\nasync function checkNodeVersion(): Promise<CheckResult> {\n const version = process.versions.node;\n const major = Number.parseInt(version.split(\".\")[0] ?? \"0\", 10);\n if (major >= 22) {\n return { name: \"Node.js\", status: \"pass\", message: `v${version}` };\n }\n return { name: \"Node.js\", status: \"fail\", message: `v${version} (requires >= 22)` };\n}\n\nasync function checkGit(): Promise<CheckResult> {\n try {\n const { stdout } = await execFileAsync(\"git\", [\"--version\"]);\n const match = stdout.match(/(\\d+\\.\\d+)/);\n const version = match?.[1] ?? \"unknown\";\n const [major, minor] = version.split(\".\").map(Number);\n if ((major ?? 0) > 2 || ((major ?? 0) === 2 && (minor ?? 0) >= 20)) {\n return { name: \"git\", status: \"pass\", message: `v${version}` };\n }\n return { name: \"git\", status: \"fail\", message: `v${version} (requires >= 2.20)` };\n } catch {\n return { name: \"git\", status: \"fail\", message: \"not installed\" };\n }\n}\n\nasync function checkGlobalConfig(): Promise<CheckResult> {\n try {\n const config = await loadGlobalConfig();\n const globalDir = getDataDir();\n const repoCount = config.repos.length;\n return {\n name: \"Global config\",\n status: \"pass\",\n message: `${globalDir}/config.yml (budget: $${config.budget.dailyCapUsd}/day, ${repoCount} repos)`,\n };\n } catch (error) {\n return {\n name: \"Global config\",\n status: \"fail\",\n message: `Invalid: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n}\n\nasync function checkRepoRegistered(): Promise<CheckResult> {\n const cwd = process.cwd();\n const repos = await listReposFromGlobalConfig();\n const match = repos.find((r) => path.resolve(r.path) === cwd);\n\n if (match) {\n return {\n name: \"Repo registered\",\n status: \"pass\",\n message: `\"${toRepoSlug(match)}\" (branch: ${match.defaultBranch})`,\n };\n }\n\n return {\n name: \"Repo registered\",\n status: \"info\",\n message:\n \"CWD not registered. Run 'neo init' or 'neo repos add'. Zero-config mode works without registration.\",\n };\n}\n\nasync function checkClaudeCli(): Promise<CheckResult> {\n try {\n const { stdout } = await execFileAsync(\"claude\", [\"--version\"]);\n return { name: \"Claude CLI\", status: \"pass\", message: stdout.trim() };\n } catch {\n return { name: \"Claude CLI\", status: \"fail\", message: \"not installed or not in PATH\" };\n }\n}\n\nasync function checkAgents(): Promise<CheckResult> {\n try {\n const agentsDir = resolveAgentsDir();\n if (!existsSync(agentsDir)) {\n return { name: \"Agents\", status: \"fail\", message: \"Agent definitions not found\" };\n }\n const registry = new AgentRegistry(agentsDir);\n await registry.load();\n const count = registry.list().length;\n return { name: \"Agents\", status: \"pass\", message: `${count} agents loaded` };\n } catch (error) {\n return {\n name: \"Agents\",\n status: \"fail\",\n message: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\nasync function checkJournalDirs(): Promise<CheckResult> {\n const journalDir = getJournalsDir();\n if (!existsSync(journalDir)) {\n return {\n name: \"Journals\",\n status: \"pass\",\n message: \"Directory will be created on first write\",\n };\n }\n try {\n await access(journalDir, constants.W_OK);\n return { name: \"Journals\", status: \"pass\", message: journalDir };\n } catch {\n return { name: \"Journals\", status: \"fail\", message: `${journalDir} is not writable` };\n }\n}\n\nexport default defineCommand({\n meta: {\n name: \"doctor\",\n description: \"Check environment prerequisites (Node.js, git, config, Claude CLI)\",\n },\n args: {\n output: {\n type: \"string\",\n description: \"Output format: json\",\n },\n },\n async run({ args }) {\n const jsonOutput = args.output === \"json\";\n\n const checks = (\n await Promise.all([\n checkNodeVersion(),\n checkGit(),\n checkGlobalConfig(),\n checkRepoRegistered(),\n checkClaudeCli(),\n checkAgents(),\n checkJournalDirs(),\n ])\n ).filter((c): c is CheckResult => c !== null);\n\n if (jsonOutput) {\n printJson({ checks });\n if (checks.some((c) => c.status === \"fail\")) {\n process.exitCode = 1;\n }\n return;\n }\n\n let hasFailure = false;\n for (const check of checks) {\n if (check.status === \"pass\") {\n printSuccess(`${check.name}: ${check.message ?? \"OK\"}`);\n } else if (check.status === \"info\") {\n console.log(` ${check.name}: ${check.message ?? \"\"}`);\n } else {\n printError(`${check.name}: ${check.message ?? \"FAILED\"}`);\n hasFailure = true;\n }\n }\n\n if (hasFailure) {\n process.exitCode = 1;\n }\n },\n});\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -15,9 +15,9 @@ var main = defineCommand({
15
15
  cost: () => import("./cost-DNGKT4UC.js").then((m) => m.default),
16
16
  repos: () => import("./repos-GI6F72NO.js").then((m) => m.default),
17
17
  agents: () => import("./agents-Y6LREFXP.js").then((m) => m.default),
18
- supervise: () => import("./supervise-UF5WHQG3.js").then((m) => m.default),
18
+ supervise: () => import("./supervise-KIB2EYY4.js").then((m) => m.default),
19
19
  mcp: () => import("./mcp-GH6CCW7A.js").then((m) => m.default),
20
- doctor: () => import("./doctor-CPVIT7IP.js").then((m) => m.default)
20
+ doctor: () => import("./doctor-GC4NH7H6.js").then((m) => m.default)
21
21
  }
22
22
  });
23
23
  runMain(main);
@@ -19,36 +19,6 @@ import {
19
19
  supervisorDaemonStateSchema
20
20
  } from "@neotx/core";
21
21
  import { defineCommand } from "citty";
22
-
23
- // src/tmux.ts
24
- import { execFile, spawnSync } from "child_process";
25
- import { promisify } from "util";
26
- var execFileAsync = promisify(execFile);
27
- async function isTmuxInstalled() {
28
- try {
29
- await execFileAsync("tmux", ["-V"]);
30
- return true;
31
- } catch {
32
- return false;
33
- }
34
- }
35
- async function tmuxSessionExists(name) {
36
- try {
37
- await execFileAsync("tmux", ["has-session", "-t", name]);
38
- return true;
39
- } catch {
40
- return false;
41
- }
42
- }
43
- async function tmuxNewSession(name, command, args, cwd) {
44
- await execFileAsync("tmux", ["new-session", "-d", "-s", name, "-c", cwd, command, ...args]);
45
- await execFileAsync("tmux", ["set-option", "-t", name, "remain-on-exit", "on"]);
46
- }
47
- async function tmuxKill(name) {
48
- await execFileAsync("tmux", ["kill-session", "-t", name]);
49
- }
50
-
51
- // src/commands/supervise.ts
52
22
  var DEFAULT_NAME = "supervisor";
53
23
  async function readState(name) {
54
24
  const statePath = getSupervisorStatePath(name);
@@ -137,13 +107,8 @@ async function handleKill(name) {
137
107
  }
138
108
  const lockPath = getSupervisorLockPath(name);
139
109
  await rm(lockPath, { force: true });
140
- const tmuxName = `neo-${name}`;
141
- const tmuxExists = await tmuxSessionExists(tmuxName);
142
- if (tmuxExists) {
143
- await tmuxKill(tmuxName);
144
- }
145
110
  }
146
- async function startDaemon(name, useTmux) {
111
+ async function startDaemon(name) {
147
112
  const running = await isDaemonRunning(name);
148
113
  if (running) {
149
114
  printError(`Supervisor "${name}" is already running (PID ${running.pid}).`);
@@ -158,49 +123,26 @@ async function startDaemon(name, useTmux) {
158
123
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
159
124
  const workerPath = path.join(__dirname, "daemon", "supervisor-worker.js");
160
125
  const packageRoot = path.resolve(__dirname, "..");
161
- if (useTmux) {
162
- const tmuxAvailable = await isTmuxInstalled();
163
- if (!tmuxAvailable) {
164
- printError("tmux not found. Install: brew install tmux");
165
- printError("Or use --daemon to run without tmux.");
166
- process.exitCode = 1;
167
- return;
168
- }
169
- const tmuxName = `neo-${name}`;
170
- const exists = await tmuxSessionExists(tmuxName);
171
- if (exists) {
172
- await tmuxKill(tmuxName);
173
- }
174
- await tmuxNewSession(tmuxName, "node", [workerPath, name], packageRoot);
175
- const config = await loadGlobalConfig();
176
- printSuccess(`Supervisor "${name}" started in tmux session neo-${name}`);
177
- console.log(` Port: ${config.supervisor.port}`);
178
- console.log(` Health: curl localhost:${config.supervisor.port}/health`);
179
- console.log(` Webhook: curl -X POST localhost:${config.supervisor.port}/webhook -d '{}'`);
180
- console.log(` Logs: ${getSupervisorDir(name)}/daemon.log`);
181
- console.log(` Attach: neo supervise --attach`);
182
- console.log(` Status: neo supervise --status`);
183
- console.log(` Stop: neo supervise --kill`);
184
- } else {
185
- const logDir = getSupervisorDir(name);
186
- await mkdir(logDir, { recursive: true });
187
- const logFd = openSync(path.join(logDir, "daemon.log"), "a");
188
- const child = spawn(process.execPath, [workerPath, name], {
189
- detached: true,
190
- stdio: ["ignore", logFd, logFd],
191
- cwd: packageRoot
192
- });
193
- child.unref();
194
- closeSync(logFd);
195
- const config = await loadGlobalConfig();
196
- printSuccess(`Supervisor "${name}" started (PID ${child.pid})`);
197
- console.log(` Port: ${config.supervisor.port}`);
198
- console.log(` Health: curl localhost:${config.supervisor.port}/health`);
199
- console.log(` Webhook: curl -X POST localhost:${config.supervisor.port}/webhook -d '{}'`);
200
- console.log(` Logs: ${getSupervisorDir(name)}/daemon.log`);
201
- console.log(` Status: neo supervise --status`);
202
- console.log(` Stop: neo supervise --kill`);
203
- }
126
+ const logDir = getSupervisorDir(name);
127
+ await mkdir(logDir, { recursive: true });
128
+ const logFd = openSync(path.join(logDir, "daemon.log"), "a");
129
+ const child = spawn(process.execPath, [workerPath, name], {
130
+ detached: true,
131
+ stdio: ["ignore", logFd, logFd],
132
+ cwd: packageRoot,
133
+ env: process.env
134
+ });
135
+ child.unref();
136
+ closeSync(logFd);
137
+ const config = await loadGlobalConfig();
138
+ printSuccess(`Supervisor "${name}" started (PID ${child.pid})`);
139
+ console.log(` Port: ${config.supervisor.port}`);
140
+ console.log(` Health: curl localhost:${config.supervisor.port}/health`);
141
+ console.log(` Webhook: curl -X POST localhost:${config.supervisor.port}/webhook -d '{}'`);
142
+ console.log(` Logs: ${getSupervisorDir(name)}/daemon.log`);
143
+ console.log(` Attach: neo supervise --attach`);
144
+ console.log(` Status: neo supervise --status`);
145
+ console.log(` Stop: neo supervise --kill`);
204
146
  }
205
147
  async function handleAttach(name) {
206
148
  const running = await isDaemonRunning(name);
@@ -252,11 +194,6 @@ var supervise_default = defineCommand({
252
194
  description: "Stop the running supervisor",
253
195
  default: false
254
196
  },
255
- daemon: {
256
- type: "boolean",
257
- description: "Start daemon without tmux (detached process)",
258
- default: false
259
- },
260
197
  attach: {
261
198
  type: "boolean",
262
199
  description: "Open the TUI for a running supervisor",
@@ -287,16 +224,14 @@ var supervise_default = defineCommand({
287
224
  }
288
225
  const alreadyRunning = await isDaemonRunning(name);
289
226
  if (!alreadyRunning) {
290
- await startDaemon(name, !args.daemon);
227
+ await startDaemon(name);
291
228
  await new Promise((r) => setTimeout(r, 1e3));
292
229
  }
293
- if (!args.daemon) {
294
- const { renderSupervisorTui } = await import("./tui-QS3RPHKH.js");
295
- await renderSupervisorTui(name);
296
- }
230
+ const { renderSupervisorTui } = await import("./tui-QS3RPHKH.js");
231
+ await renderSupervisorTui(name);
297
232
  }
298
233
  });
299
234
  export {
300
235
  supervise_default as default
301
236
  };
302
- //# sourceMappingURL=supervise-UF5WHQG3.js.map
237
+ //# sourceMappingURL=supervise-KIB2EYY4.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 getSupervisorDir,\n getSupervisorInboxPath,\n getSupervisorLockPath,\n getSupervisorStatePath,\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\nfunction isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\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(\n ` Interval: ${config.supervisor.idleIntervalMs / 1000}s (skip up to ${config.supervisor.idleSkipMax} idle)`,\n );\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(\" Attach: neo supervise --attach\");\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 --attach to connect.\");\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(` Attach: neo supervise --attach`);\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 inboxPath = getSupervisorInboxPath(name);\n const message = {\n id: randomUUID(),\n from: \"api\" as const,\n text,\n timestamp: new Date().toISOString(),\n };\n\n await appendFile(inboxPath, `${JSON.stringify(message)}\\n`, \"utf-8\");\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\",\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 // Default: start daemon + open TUI\n const alreadyRunning = await isDaemonRunning(name);\n if (!alreadyRunning) {\n await startDaemon(name);\n // Give daemon a moment to initialize\n await new Promise((r) => setTimeout(r, 1_000));\n }\n\n // Open TUI\n const { renderSupervisorTui } = await import(\"../tui/index.js\");\n await renderSupervisorTui(name);\n },\n});\n"],"mappings":"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","names":["lockPath"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neotx/cli",
3
- "version": "0.1.0-alpha.1",
3
+ "version": "0.1.0-alpha.2",
4
4
  "description": "CLI for the Neo orchestration framework",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -35,8 +35,8 @@
35
35
  "ink-text-input": "^6.0.0",
36
36
  "react": "^19.2.4",
37
37
  "yaml": "^2.8.2",
38
- "@neotx/agents": "0.1.0-alpha.1",
39
- "@neotx/core": "0.1.0-alpha.1"
38
+ "@neotx/agents": "0.1.0-alpha.2",
39
+ "@neotx/core": "0.1.0-alpha.2"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@anthropic-ai/claude-agent-sdk": "^0.1.0",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/doctor.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { access, constants } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { promisify } from \"node:util\";\nimport {\n AgentRegistry,\n getDataDir,\n getJournalsDir,\n listReposFromGlobalConfig,\n loadGlobalConfig,\n toRepoSlug,\n} from \"@neotx/core\";\nimport { defineCommand } from \"citty\";\nimport { printError, printJson, printSuccess } from \"../output.js\";\nimport { resolveAgentsDir } from \"../resolve.js\";\n\nconst execFileAsync = promisify(execFile);\n\ninterface CheckResult {\n name: string;\n status: \"pass\" | \"fail\" | \"info\";\n message?: string;\n}\n\nasync function checkNodeVersion(): Promise<CheckResult> {\n const version = process.versions.node;\n const major = Number.parseInt(version.split(\".\")[0] ?? \"0\", 10);\n if (major >= 22) {\n return { name: \"Node.js\", status: \"pass\", message: `v${version}` };\n }\n return { name: \"Node.js\", status: \"fail\", message: `v${version} (requires >= 22)` };\n}\n\nasync function checkGit(): Promise<CheckResult> {\n try {\n const { stdout } = await execFileAsync(\"git\", [\"--version\"]);\n const match = stdout.match(/(\\d+\\.\\d+)/);\n const version = match?.[1] ?? \"unknown\";\n const [major, minor] = version.split(\".\").map(Number);\n if ((major ?? 0) > 2 || ((major ?? 0) === 2 && (minor ?? 0) >= 20)) {\n return { name: \"git\", status: \"pass\", message: `v${version}` };\n }\n return { name: \"git\", status: \"fail\", message: `v${version} (requires >= 2.20)` };\n } catch {\n return { name: \"git\", status: \"fail\", message: \"not installed\" };\n }\n}\n\nasync function checkGlobalConfig(): Promise<CheckResult> {\n try {\n const config = await loadGlobalConfig();\n const globalDir = getDataDir();\n const repoCount = config.repos.length;\n return {\n name: \"Global config\",\n status: \"pass\",\n message: `${globalDir}/config.yml (budget: $${config.budget.dailyCapUsd}/day, ${repoCount} repos)`,\n };\n } catch (error) {\n return {\n name: \"Global config\",\n status: \"fail\",\n message: `Invalid: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n}\n\nasync function checkRepoRegistered(): Promise<CheckResult> {\n const cwd = process.cwd();\n const repos = await listReposFromGlobalConfig();\n const match = repos.find((r) => path.resolve(r.path) === cwd);\n\n if (match) {\n return {\n name: \"Repo registered\",\n status: \"pass\",\n message: `\"${toRepoSlug(match)}\" (branch: ${match.defaultBranch})`,\n };\n }\n\n return {\n name: \"Repo registered\",\n status: \"info\",\n message:\n \"CWD not registered. Run 'neo init' or 'neo repos add'. Zero-config mode works without registration.\",\n };\n}\n\nasync function checkLegacyConfig(): Promise<CheckResult | null> {\n const legacyPath = path.resolve(\".neo/config.yml\");\n if (existsSync(legacyPath)) {\n return {\n name: \"Legacy config\",\n status: \"info\",\n message:\n \".neo/config.yml detected — this file is no longer needed. Config is now in ~/.neo/config.yml.\",\n };\n }\n return null;\n}\n\nasync function checkTmux(): Promise<CheckResult> {\n try {\n const { stdout } = await execFileAsync(\"tmux\", [\"-V\"]);\n return { name: \"tmux\", status: \"pass\", message: stdout.trim() };\n } catch {\n return {\n name: \"tmux\",\n status: \"info\",\n message: \"not installed (required for neo supervise)\",\n };\n }\n}\n\nasync function checkClaudeCli(): Promise<CheckResult> {\n try {\n const { stdout } = await execFileAsync(\"claude\", [\"--version\"]);\n return { name: \"Claude CLI\", status: \"pass\", message: stdout.trim() };\n } catch {\n return { name: \"Claude CLI\", status: \"fail\", message: \"not installed or not in PATH\" };\n }\n}\n\nasync function checkAgents(): Promise<CheckResult> {\n try {\n const agentsDir = resolveAgentsDir();\n if (!existsSync(agentsDir)) {\n return { name: \"Agents\", status: \"fail\", message: \"Agent definitions not found\" };\n }\n const registry = new AgentRegistry(agentsDir);\n await registry.load();\n const count = registry.list().length;\n return { name: \"Agents\", status: \"pass\", message: `${count} agents loaded` };\n } catch (error) {\n return {\n name: \"Agents\",\n status: \"fail\",\n message: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\nasync function checkJournalDirs(): Promise<CheckResult> {\n const journalDir = getJournalsDir();\n if (!existsSync(journalDir)) {\n return {\n name: \"Journals\",\n status: \"pass\",\n message: \"Directory will be created on first write\",\n };\n }\n try {\n await access(journalDir, constants.W_OK);\n return { name: \"Journals\", status: \"pass\", message: journalDir };\n } catch {\n return { name: \"Journals\", status: \"fail\", message: `${journalDir} is not writable` };\n }\n}\n\nexport default defineCommand({\n meta: {\n name: \"doctor\",\n description: \"Check environment prerequisites (Node.js, git, config, Claude CLI)\",\n },\n args: {\n output: {\n type: \"string\",\n description: \"Output format: json\",\n },\n },\n async run({ args }) {\n const jsonOutput = args.output === \"json\";\n\n const checks = (\n await Promise.all([\n checkNodeVersion(),\n checkGit(),\n checkGlobalConfig(),\n checkRepoRegistered(),\n checkLegacyConfig(),\n checkTmux(),\n checkClaudeCli(),\n checkAgents(),\n checkJournalDirs(),\n ])\n ).filter((c): c is CheckResult => c !== null);\n\n if (jsonOutput) {\n printJson({ checks });\n if (checks.some((c) => c.status === \"fail\")) {\n process.exitCode = 1;\n }\n return;\n }\n\n let hasFailure = false;\n for (const check of checks) {\n if (check.status === \"pass\") {\n printSuccess(`${check.name}: ${check.message ?? \"OK\"}`);\n } else if (check.status === \"info\") {\n console.log(` ${check.name}: ${check.message ?? \"\"}`);\n } else {\n printError(`${check.name}: ${check.message ?? \"FAILED\"}`);\n hasFailure = true;\n }\n }\n\n if (hasFailure) {\n process.exitCode = 1;\n }\n },\n});\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/supervise.ts","../src/tmux.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 getSupervisorDir,\n getSupervisorInboxPath,\n getSupervisorLockPath,\n getSupervisorStatePath,\n loadGlobalConfig,\n type SupervisorDaemonState,\n supervisorDaemonStateSchema,\n} from \"@neotx/core\";\nimport { defineCommand } from \"citty\";\nimport { printError, printSuccess } from \"../output.js\";\nimport { isTmuxInstalled, tmuxKill, tmuxNewSession, tmuxSessionExists } from \"../tmux.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\nfunction isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\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(\n ` Interval: ${config.supervisor.idleIntervalMs / 1000}s (skip up to ${config.supervisor.idleSkipMax} idle)`,\n );\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(\" Attach: neo supervise --attach\");\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 // Also kill tmux session if it exists\n const tmuxName = `neo-${name}`;\n const tmuxExists = await tmuxSessionExists(tmuxName);\n if (tmuxExists) {\n await tmuxKill(tmuxName);\n }\n}\n\nasync function startDaemon(name: string, useTmux: boolean): 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 --attach to connect.\");\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 if (useTmux) {\n const tmuxAvailable = await isTmuxInstalled();\n if (!tmuxAvailable) {\n printError(\"tmux not found. Install: brew install tmux\");\n printError(\"Or use --daemon to run without tmux.\");\n process.exitCode = 1;\n return;\n }\n\n const tmuxName = `neo-${name}`;\n const exists = await tmuxSessionExists(tmuxName);\n if (exists) {\n await tmuxKill(tmuxName);\n }\n\n // Launch via tmux so it persists after terminal closes\n await tmuxNewSession(tmuxName, \"node\", [workerPath, name], packageRoot);\n\n const config = await loadGlobalConfig();\n printSuccess(`Supervisor \"${name}\" started in tmux session neo-${name}`);\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(` Attach: neo supervise --attach`);\n console.log(` Status: neo supervise --status`);\n console.log(` Stop: neo supervise --kill`);\n } else {\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 });\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(` Status: neo supervise --status`);\n console.log(` Stop: neo supervise --kill`);\n }\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 inboxPath = getSupervisorInboxPath(name);\n const message = {\n id: randomUUID(),\n from: \"api\" as const,\n text,\n timestamp: new Date().toISOString(),\n };\n\n await appendFile(inboxPath, `${JSON.stringify(message)}\\n`, \"utf-8\");\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 daemon: {\n type: \"boolean\",\n description: \"Start daemon without tmux (detached process)\",\n default: false,\n },\n attach: {\n type: \"boolean\",\n description: \"Open the TUI for a running supervisor\",\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 // Default: start daemon + open TUI\n const alreadyRunning = await isDaemonRunning(name);\n if (!alreadyRunning) {\n await startDaemon(name, !args.daemon);\n // Give daemon a moment to initialize\n await new Promise((r) => setTimeout(r, 1_000));\n }\n\n // Open TUI unless --daemon (headless mode)\n if (!args.daemon) {\n const { renderSupervisorTui } = await import(\"../tui/index.js\");\n await renderSupervisorTui(name);\n }\n },\n});\n","import { execFile, spawnSync } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\n\n/** Check if tmux binary is available. */\nexport async function isTmuxInstalled(): Promise<boolean> {\n try {\n await execFileAsync(\"tmux\", [\"-V\"]);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Get tmux version string, or null if not installed. */\nexport async function getTmuxVersion(): Promise<string | null> {\n try {\n const { stdout } = await execFileAsync(\"tmux\", [\"-V\"]);\n return stdout.trim();\n } catch {\n return null;\n }\n}\n\n/** Check if a tmux session with the given name exists. */\nexport async function tmuxSessionExists(name: string): Promise<boolean> {\n try {\n await execFileAsync(\"tmux\", [\"has-session\", \"-t\", name]);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Create a new detached tmux session running the given command.\n * Sets remain-on-exit so the pane stays alive when the process exits. */\nexport async function tmuxNewSession(\n name: string,\n command: string,\n args: string[],\n cwd: string,\n): Promise<void> {\n await execFileAsync(\"tmux\", [\"new-session\", \"-d\", \"-s\", name, \"-c\", cwd, command, ...args]);\n // Keep the pane alive when the process exits so we can respawn it\n await execFileAsync(\"tmux\", [\"set-option\", \"-t\", name, \"remain-on-exit\", \"on\"]);\n}\n\n/** Check if the first pane in the session has a dead process. */\nexport async function tmuxPaneDead(name: string): Promise<boolean> {\n try {\n const { stdout } = await execFileAsync(\"tmux\", [\n \"list-panes\",\n \"-t\",\n name,\n \"-F\",\n \"#{pane_dead}\",\n ]);\n return stdout.trim() === \"1\";\n } catch {\n return false;\n }\n}\n\n/** Respawn the dead pane in a session with a new command. */\nexport async function tmuxRespawnPane(\n name: string,\n command: string,\n args: string[],\n): Promise<void> {\n await execFileAsync(\"tmux\", [\"respawn-pane\", \"-t\", name, \"-k\", command, ...args]);\n}\n\n/** Attach the current terminal to an existing tmux session. */\nexport function tmuxAttach(name: string): void {\n spawnSync(\"tmux\", [\"attach-session\", \"-t\", name], { stdio: \"inherit\" });\n}\n\n/** Kill a tmux session by name. */\nexport async function tmuxKill(name: string): Promise<void> {\n await execFileAsync(\"tmux\", [\"kill-session\", \"-t\", name]);\n}\n\nexport interface TmuxSessionInfo {\n created: string;\n windows: number;\n}\n\n/** Get session info, or null if the session does not exist. */\nexport async function tmuxSessionInfo(name: string): Promise<TmuxSessionInfo | null> {\n try {\n const { stdout } = await execFileAsync(\"tmux\", [\n \"list-sessions\",\n \"-F\",\n \"#{session_created} #{session_windows}\",\n \"-f\",\n `#{==:#{session_name},${name}}`,\n ]);\n const line = stdout.trim();\n if (!line) return null;\n\n const [timestamp, windows] = line.split(\" \");\n const epoch = Number(timestamp);\n return {\n created: Number.isNaN(epoch) ? \"unknown\" : new Date(epoch * 1000).toISOString(),\n windows: Number(windows) || 1,\n };\n } catch {\n return null;\n }\n}\n"],"mappings":"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","names":["lockPath"]}