@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.
- package/dist/{doctor-CPVIT7IP.js → doctor-GC4NH7H6.js} +1 -26
- package/dist/doctor-GC4NH7H6.js.map +1 -0
- package/dist/index.js +2 -2
- package/dist/{supervise-UF5WHQG3.js → supervise-KIB2EYY4.js} +25 -90
- package/dist/supervise-KIB2EYY4.js.map +1 -0
- package/package.json +3 -3
- package/dist/doctor-CPVIT7IP.js.map +0 -1
- package/dist/supervise-UF5WHQG3.js.map +0 -1
|
@@ -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-
|
|
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-
|
|
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-
|
|
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
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
|
227
|
+
await startDaemon(name);
|
|
291
228
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
292
229
|
}
|
|
293
|
-
|
|
294
|
-
|
|
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-
|
|
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.
|
|
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.
|
|
39
|
-
"@neotx/core": "0.1.0-alpha.
|
|
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":";;;;;;AAAA,SAAS,aAAa;AACtB,SAAS,kBAAkB;AAC3B,SAAS,WAAW,YAAY,gBAAgB;AAChD,SAAS,YAAY,OAAO,UAAU,UAAU;AAChD,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAC9B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,OACK;AACP,SAAS,qBAAqB;;;ACf9B,SAAS,UAAU,iBAAiB;AACpC,SAAS,iBAAiB;AAE1B,IAAM,gBAAgB,UAAU,QAAQ;AAGxC,eAAsB,kBAAoC;AACxD,MAAI;AACF,UAAM,cAAc,QAAQ,CAAC,IAAI,CAAC;AAClC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAaA,eAAsB,kBAAkB,MAAgC;AACtE,MAAI;AACF,UAAM,cAAc,QAAQ,CAAC,eAAe,MAAM,IAAI,CAAC;AACvD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAIA,eAAsB,eACpB,MACA,SACA,MACA,KACe;AACf,QAAM,cAAc,QAAQ,CAAC,eAAe,MAAM,MAAM,MAAM,MAAM,KAAK,SAAS,GAAG,IAAI,CAAC;AAE1F,QAAM,cAAc,QAAQ,CAAC,cAAc,MAAM,MAAM,kBAAkB,IAAI,CAAC;AAChF;AAiCA,eAAsB,SAAS,MAA6B;AAC1D,QAAM,cAAc,QAAQ,CAAC,gBAAgB,MAAM,IAAI,CAAC;AAC1D;;;AD9DA,IAAM,eAAe;AAErB,eAAe,UAAU,MAAqD;AAC5E,QAAM,YAAY,uBAAuB,IAAI;AAC7C,MAAI,CAAC,WAAW,SAAS,EAAG,QAAO;AACnC,MAAI;AACF,UAAM,MAAM,MAAM,SAAS,WAAW,OAAO;AAC7C,WAAO,4BAA4B,MAAM,KAAK,MAAM,GAAG,CAAC;AAAA,EAC1D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,eAAe,KAAsB;AAC5C,MAAI;AACF,YAAQ,KAAK,KAAK,CAAC;AACnB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,gBAAgB,MAAqD;AAClF,QAAM,QAAQ,MAAM,UAAU,IAAI;AAClC,MAAI,CAAC,SAAS,MAAM,WAAW,UAAW,QAAO;AACjD,MAAI,CAAC,eAAe,MAAM,GAAG,EAAG,QAAO;AACvC,SAAO;AACT;AAEA,eAAe,aAAa,MAA6B;AACvD,QAAM,QAAQ,MAAM,gBAAgB,IAAI;AACxC,MAAI,CAAC,OAAO;AACV,YAAQ,IAAI,uCAAuC,IAAI,IAAI;AAC3D;AAAA,EACF;AAEA,QAAM,SAAS,MAAM,iBAAiB;AACtC,eAAa,eAAe,IAAI,WAAW;AAC3C,UAAQ,IAAI,iBAAiB,MAAM,GAAG,EAAE;AACxC,UAAQ,IAAI,iBAAiB,MAAM,IAAI,EAAE;AACzC,UAAQ,IAAI,iBAAiB,MAAM,SAAS,EAAE;AAC9C,UAAQ,IAAI,iBAAiB,MAAM,SAAS,EAAE;AAC9C,UAAQ;AAAA,IACN,iBAAiB,OAAO,WAAW,iBAAiB,GAAI,iBAAiB,OAAO,WAAW,WAAW;AAAA,EACxG;AACA,UAAQ,IAAI,iBAAiB,MAAM,cAAc,EAAE;AACnD,MAAI,MAAM,eAAe;AACvB,YAAQ,IAAI,iBAAiB,MAAM,aAAa,EAAE;AAAA,EACpD;AACA,UAAQ,IAAI,kBAAkB,MAAM,cAAc,QAAQ,CAAC,KAAK,MAAM,EAAE;AACxE,UAAQ,IAAI,kBAAkB,MAAM,cAAc,QAAQ,CAAC,KAAK,MAAM,EAAE;AACxE,UAAQ,IAAI,iBAAiB,MAAM,MAAM,EAAE;AAC3C,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,8BAA8B,MAAM,IAAI,SAAS;AAC7D,UAAQ,IAAI,oCAAoC;AAChD,UAAQ,IAAI,kCAAkC;AAChD;AAEA,eAAe,WAAW,MAA6B;AACrD,QAAM,QAAQ,MAAM,gBAAgB,IAAI;AACxC,MAAI,CAAC,OAAO;AACV,eAAW,uCAAuC,IAAI,IAAI;AAG1D,UAAMA,YAAW,sBAAsB,IAAI;AAC3C,QAAI,WAAWA,SAAQ,GAAG;AACxB,YAAM,GAAGA,WAAU,EAAE,OAAO,KAAK,CAAC;AAAA,IACpC;AACA,YAAQ,WAAW;AACnB;AAAA,EACF;AAGA,QAAM,MAAM,MAAM;AAClB,MAAI;AACF,YAAQ,KAAK,KAAK,SAAS;AAC3B,iBAAa,+BAA+B,IAAI,UAAU,GAAG,GAAG;AAAA,EAClE,QAAQ;AACN,eAAW,gCAAgC,GAAG,gBAAgB;AAC9D,UAAMA,YAAW,sBAAsB,IAAI;AAC3C,UAAM,GAAGA,WAAU,EAAE,OAAO,KAAK,CAAC;AAClC;AAAA,EACF;AAGA,QAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,SAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,UAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAC3C,QAAI,CAAC,eAAe,GAAG,GAAG;AACxB,mBAAa,iBAAiB;AAC9B;AAAA,IACF;AAAA,EACF;AAGA,MAAI;AACF,YAAQ,KAAK,KAAK,SAAS;AAC3B,iBAAa,wDAAmD,GAAG,IAAI;AAAA,EACzE,QAAQ;AAAA,EAER;AAGA,QAAM,WAAW,sBAAsB,IAAI;AAC3C,QAAM,GAAG,UAAU,EAAE,OAAO,KAAK,CAAC;AAGlC,QAAM,WAAW,OAAO,IAAI;AAC5B,QAAM,aAAa,MAAM,kBAAkB,QAAQ;AACnD,MAAI,YAAY;AACd,UAAM,SAAS,QAAQ;AAAA,EACzB;AACF;AAEA,eAAe,YAAY,MAAc,SAAiC;AACxE,QAAM,UAAU,MAAM,gBAAgB,IAAI;AAC1C,MAAI,SAAS;AACX,eAAW,eAAe,IAAI,6BAA6B,QAAQ,GAAG,IAAI;AAC1E,eAAW,2CAA2C;AACtD,YAAQ,WAAW;AACnB;AAAA,EACF;AAGA,QAAM,WAAW,sBAAsB,IAAI;AAC3C,MAAI,WAAW,QAAQ,GAAG;AACxB,UAAM,GAAG,UAAU,EAAE,OAAO,KAAK,CAAC;AAAA,EACpC;AAGA,QAAM,YAAY,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AAC7D,QAAM,aAAa,KAAK,KAAK,WAAW,UAAU,sBAAsB;AACxE,QAAM,cAAc,KAAK,QAAQ,WAAW,IAAI;AAEhD,MAAI,SAAS;AACX,UAAM,gBAAgB,MAAM,gBAAgB;AAC5C,QAAI,CAAC,eAAe;AAClB,iBAAW,4CAA4C;AACvD,iBAAW,sCAAsC;AACjD,cAAQ,WAAW;AACnB;AAAA,IACF;AAEA,UAAM,WAAW,OAAO,IAAI;AAC5B,UAAM,SAAS,MAAM,kBAAkB,QAAQ;AAC/C,QAAI,QAAQ;AACV,YAAM,SAAS,QAAQ;AAAA,IACzB;AAGA,UAAM,eAAe,UAAU,QAAQ,CAAC,YAAY,IAAI,GAAG,WAAW;AAEtE,UAAM,SAAS,MAAM,iBAAiB;AACtC,iBAAa,eAAe,IAAI,iCAAiC,IAAI,EAAE;AACvE,YAAQ,IAAI,eAAe,OAAO,WAAW,IAAI,EAAE;AACnD,YAAQ,IAAI,8BAA8B,OAAO,WAAW,IAAI,SAAS;AACzE,YAAQ,IAAI,sCAAsC,OAAO,WAAW,IAAI,kBAAkB;AAC1F,YAAQ,IAAI,eAAe,iBAAiB,IAAI,CAAC,aAAa;AAC9D,YAAQ,IAAI,oCAAoC;AAChD,YAAQ,IAAI,oCAAoC;AAChD,YAAQ,IAAI,kCAAkC;AAAA,EAChD,OAAO;AAEL,UAAM,SAAS,iBAAiB,IAAI;AACpC,UAAM,MAAM,QAAQ,EAAE,WAAW,KAAK,CAAC;AACvC,UAAM,QAAQ,SAAS,KAAK,KAAK,QAAQ,YAAY,GAAG,GAAG;AAC3D,UAAM,QAAQ,MAAM,QAAQ,UAAU,CAAC,YAAY,IAAI,GAAG;AAAA,MACxD,UAAU;AAAA,MACV,OAAO,CAAC,UAAU,OAAO,KAAK;AAAA,MAC9B,KAAK;AAAA,IACP,CAAC;AACD,UAAM,MAAM;AACZ,cAAU,KAAK;AAEf,UAAM,SAAS,MAAM,iBAAiB;AACtC,iBAAa,eAAe,IAAI,kBAAkB,MAAM,GAAG,GAAG;AAC9D,YAAQ,IAAI,eAAe,OAAO,WAAW,IAAI,EAAE;AACnD,YAAQ,IAAI,8BAA8B,OAAO,WAAW,IAAI,SAAS;AACzE,YAAQ,IAAI,sCAAsC,OAAO,WAAW,IAAI,kBAAkB;AAC1F,YAAQ,IAAI,eAAe,iBAAiB,IAAI,CAAC,aAAa;AAC9D,YAAQ,IAAI,oCAAoC;AAChD,YAAQ,IAAI,kCAAkC;AAAA,EAChD;AACF;AAEA,eAAe,aAAa,MAA6B;AACvD,QAAM,UAAU,MAAM,gBAAgB,IAAI;AAC1C,MAAI,CAAC,SAAS;AACZ,eAAW,uCAAuC,IAAI,IAAI;AAC1D,eAAW,2BAA2B;AACtC,YAAQ,WAAW;AACnB;AAAA,EACF;AAEA,QAAM,EAAE,oBAAoB,IAAI,MAAM,OAAO,mBAAiB;AAC9D,QAAM,oBAAoB,IAAI;AAChC;AAEA,eAAe,cAAc,MAAc,MAA6B;AACtE,QAAM,UAAU,MAAM,gBAAgB,IAAI;AAC1C,MAAI,CAAC,SAAS;AACZ,eAAW,uCAAuC,IAAI,IAAI;AAC1D,YAAQ,WAAW;AACnB;AAAA,EACF;AAEA,QAAM,YAAY,uBAAuB,IAAI;AAC7C,QAAM,UAAU;AAAA,IACd,IAAI,WAAW;AAAA,IACf,MAAM;AAAA,IACN;AAAA,IACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,EACpC;AAEA,QAAM,WAAW,WAAW,GAAG,KAAK,UAAU,OAAO,CAAC;AAAA,GAAM,OAAO;AACnE,eAAa,+BAA+B,IAAI,IAAI;AACtD;AAEA,IAAO,oBAAQ,cAAc;AAAA,EAC3B,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,MAAM;AAAA,IACJ,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,IACX;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,IACX;AAAA,IACA,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,IACX;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,IACX;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,IACX;AAAA,IACA,SAAS;AAAA,MACP,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA,MAAM,IAAI,EAAE,KAAK,GAAG;AAClB,UAAM,OAAO,KAAK;AAElB,QAAI,KAAK,QAAQ;AACf,YAAM,aAAa,IAAI;AACvB;AAAA,IACF;AAEA,QAAI,KAAK,MAAM;AACb,YAAM,WAAW,IAAI;AACrB;AAAA,IACF;AAEA,QAAI,KAAK,QAAQ;AACf,YAAM,aAAa,IAAI;AACvB;AAAA,IACF;AAEA,QAAI,KAAK,SAAS;AAChB,YAAM,cAAc,MAAM,KAAK,OAAO;AACtC;AAAA,IACF;AAGA,UAAM,iBAAiB,MAAM,gBAAgB,IAAI;AACjD,QAAI,CAAC,gBAAgB;AACnB,YAAM,YAAY,MAAM,CAAC,KAAK,MAAM;AAEpC,YAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,GAAK,CAAC;AAAA,IAC/C;AAGA,QAAI,CAAC,KAAK,QAAQ;AAChB,YAAM,EAAE,oBAAoB,IAAI,MAAM,OAAO,mBAAiB;AAC9D,YAAM,oBAAoB,IAAI;AAAA,IAChC;AAAA,EACF;AACF,CAAC;","names":["lockPath"]}
|