@neotx/cli 0.1.0-alpha.0

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 (38) hide show
  1. package/LICENSE +21 -0
  2. package/dist/agents-Y6LREFXP.js +58 -0
  3. package/dist/agents-Y6LREFXP.js.map +1 -0
  4. package/dist/chunk-CP54H7WA.js +85 -0
  5. package/dist/chunk-CP54H7WA.js.map +1 -0
  6. package/dist/chunk-EZAJLAUF.js +40 -0
  7. package/dist/chunk-EZAJLAUF.js.map +1 -0
  8. package/dist/chunk-TNJOG54I.js +16 -0
  9. package/dist/chunk-TNJOG54I.js.map +1 -0
  10. package/dist/chunk-YQIWMDXL.js +33 -0
  11. package/dist/chunk-YQIWMDXL.js.map +1 -0
  12. package/dist/cost-DNGKT4UC.js +134 -0
  13. package/dist/cost-DNGKT4UC.js.map +1 -0
  14. package/dist/daemon/supervisor-worker.js +28 -0
  15. package/dist/daemon/supervisor-worker.js.map +1 -0
  16. package/dist/daemon/worker.js +95 -0
  17. package/dist/daemon/worker.js.map +1 -0
  18. package/dist/doctor-CPVIT7IP.js +198 -0
  19. package/dist/doctor-CPVIT7IP.js.map +1 -0
  20. package/dist/index.js +24 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/init-YNSPTCA3.js +74 -0
  23. package/dist/init-YNSPTCA3.js.map +1 -0
  24. package/dist/logs-AWNAMMJC.js +200 -0
  25. package/dist/logs-AWNAMMJC.js.map +1 -0
  26. package/dist/mcp-LC5VU65M.js +217 -0
  27. package/dist/mcp-LC5VU65M.js.map +1 -0
  28. package/dist/repos-GI6F72NO.js +111 -0
  29. package/dist/repos-GI6F72NO.js.map +1 -0
  30. package/dist/run-KIU2ZE72.js +231 -0
  31. package/dist/run-KIU2ZE72.js.map +1 -0
  32. package/dist/runs-CHA2JM5K.js +176 -0
  33. package/dist/runs-CHA2JM5K.js.map +1 -0
  34. package/dist/supervise-7ZITWRSL.js +298 -0
  35. package/dist/supervise-7ZITWRSL.js.map +1 -0
  36. package/dist/tui-W2FHMMMN.js +489 -0
  37. package/dist/tui-W2FHMMMN.js.map +1 -0
  38. package/package.json +53 -0
@@ -0,0 +1,198 @@
1
+ import {
2
+ resolveAgentsDir
3
+ } from "./chunk-TNJOG54I.js";
4
+ import {
5
+ printError,
6
+ printJson,
7
+ printSuccess
8
+ } from "./chunk-YQIWMDXL.js";
9
+
10
+ // src/commands/doctor.ts
11
+ import { execFile } from "child_process";
12
+ import { existsSync } from "fs";
13
+ import { access, constants } from "fs/promises";
14
+ import path from "path";
15
+ import { promisify } from "util";
16
+ import {
17
+ AgentRegistry,
18
+ getDataDir,
19
+ getJournalsDir,
20
+ listReposFromGlobalConfig,
21
+ loadGlobalConfig,
22
+ toRepoSlug
23
+ } from "@neotx/core";
24
+ import { defineCommand } from "citty";
25
+ var execFileAsync = promisify(execFile);
26
+ async function checkNodeVersion() {
27
+ const version = process.versions.node;
28
+ const major = Number.parseInt(version.split(".")[0] ?? "0", 10);
29
+ if (major >= 22) {
30
+ return { name: "Node.js", status: "pass", message: `v${version}` };
31
+ }
32
+ return { name: "Node.js", status: "fail", message: `v${version} (requires >= 22)` };
33
+ }
34
+ async function checkGit() {
35
+ try {
36
+ const { stdout } = await execFileAsync("git", ["--version"]);
37
+ const match = stdout.match(/(\d+\.\d+)/);
38
+ const version = match?.[1] ?? "unknown";
39
+ const [major, minor] = version.split(".").map(Number);
40
+ if ((major ?? 0) > 2 || (major ?? 0) === 2 && (minor ?? 0) >= 20) {
41
+ return { name: "git", status: "pass", message: `v${version}` };
42
+ }
43
+ return { name: "git", status: "fail", message: `v${version} (requires >= 2.20)` };
44
+ } catch {
45
+ return { name: "git", status: "fail", message: "not installed" };
46
+ }
47
+ }
48
+ async function checkGlobalConfig() {
49
+ try {
50
+ const config = await loadGlobalConfig();
51
+ const globalDir = getDataDir();
52
+ const repoCount = config.repos.length;
53
+ return {
54
+ name: "Global config",
55
+ status: "pass",
56
+ message: `${globalDir}/config.yml (budget: $${config.budget.dailyCapUsd}/day, ${repoCount} repos)`
57
+ };
58
+ } catch (error) {
59
+ return {
60
+ name: "Global config",
61
+ status: "fail",
62
+ message: `Invalid: ${error instanceof Error ? error.message : String(error)}`
63
+ };
64
+ }
65
+ }
66
+ async function checkRepoRegistered() {
67
+ const cwd = process.cwd();
68
+ const repos = await listReposFromGlobalConfig();
69
+ const match = repos.find((r) => path.resolve(r.path) === cwd);
70
+ if (match) {
71
+ return {
72
+ name: "Repo registered",
73
+ status: "pass",
74
+ message: `"${toRepoSlug(match)}" (branch: ${match.defaultBranch})`
75
+ };
76
+ }
77
+ return {
78
+ name: "Repo registered",
79
+ status: "info",
80
+ message: "CWD not registered. Run 'neo init' or 'neo repos add'. Zero-config mode works without registration."
81
+ };
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
+ async function checkClaudeCli() {
107
+ try {
108
+ const { stdout } = await execFileAsync("claude", ["--version"]);
109
+ return { name: "Claude CLI", status: "pass", message: stdout.trim() };
110
+ } catch {
111
+ return { name: "Claude CLI", status: "fail", message: "not installed or not in PATH" };
112
+ }
113
+ }
114
+ async function checkAgents() {
115
+ try {
116
+ const agentsDir = resolveAgentsDir();
117
+ if (!existsSync(agentsDir)) {
118
+ return { name: "Agents", status: "fail", message: "Agent definitions not found" };
119
+ }
120
+ const registry = new AgentRegistry(agentsDir);
121
+ await registry.load();
122
+ const count = registry.list().length;
123
+ return { name: "Agents", status: "pass", message: `${count} agents loaded` };
124
+ } catch (error) {
125
+ return {
126
+ name: "Agents",
127
+ status: "fail",
128
+ message: error instanceof Error ? error.message : String(error)
129
+ };
130
+ }
131
+ }
132
+ async function checkJournalDirs() {
133
+ const journalDir = getJournalsDir();
134
+ if (!existsSync(journalDir)) {
135
+ return {
136
+ name: "Journals",
137
+ status: "pass",
138
+ message: "Directory will be created on first write"
139
+ };
140
+ }
141
+ try {
142
+ await access(journalDir, constants.W_OK);
143
+ return { name: "Journals", status: "pass", message: journalDir };
144
+ } catch {
145
+ return { name: "Journals", status: "fail", message: `${journalDir} is not writable` };
146
+ }
147
+ }
148
+ var doctor_default = defineCommand({
149
+ meta: {
150
+ name: "doctor",
151
+ description: "Check environment prerequisites (Node.js, git, config, Claude CLI)"
152
+ },
153
+ args: {
154
+ output: {
155
+ type: "string",
156
+ description: "Output format: json"
157
+ }
158
+ },
159
+ async run({ args }) {
160
+ const jsonOutput = args.output === "json";
161
+ const checks = (await Promise.all([
162
+ checkNodeVersion(),
163
+ checkGit(),
164
+ checkGlobalConfig(),
165
+ checkRepoRegistered(),
166
+ checkLegacyConfig(),
167
+ checkTmux(),
168
+ checkClaudeCli(),
169
+ checkAgents(),
170
+ checkJournalDirs()
171
+ ])).filter((c) => c !== null);
172
+ if (jsonOutput) {
173
+ printJson({ checks });
174
+ if (checks.some((c) => c.status === "fail")) {
175
+ process.exitCode = 1;
176
+ }
177
+ return;
178
+ }
179
+ let hasFailure = false;
180
+ for (const check of checks) {
181
+ if (check.status === "pass") {
182
+ printSuccess(`${check.name}: ${check.message ?? "OK"}`);
183
+ } else if (check.status === "info") {
184
+ console.log(` ${check.name}: ${check.message ?? ""}`);
185
+ } else {
186
+ printError(`${check.name}: ${check.message ?? "FAILED"}`);
187
+ hasFailure = true;
188
+ }
189
+ }
190
+ if (hasFailure) {
191
+ process.exitCode = 1;
192
+ }
193
+ }
194
+ });
195
+ export {
196
+ doctor_default as default
197
+ };
198
+ //# sourceMappingURL=doctor-CPVIT7IP.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 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":[]}
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ // src/index.ts
3
+ import { defineCommand, runMain } from "citty";
4
+ var main = defineCommand({
5
+ meta: {
6
+ name: "neo",
7
+ version: "0.1.0",
8
+ description: "Orchestrate autonomous developer agents with worktree isolation, budget guards, and 3-level recovery. Run 'neo init' to get started."
9
+ },
10
+ subCommands: {
11
+ init: () => import("./init-YNSPTCA3.js").then((m) => m.default),
12
+ run: () => import("./run-KIU2ZE72.js").then((m) => m.default),
13
+ runs: () => import("./runs-CHA2JM5K.js").then((m) => m.default),
14
+ logs: () => import("./logs-AWNAMMJC.js").then((m) => m.default),
15
+ cost: () => import("./cost-DNGKT4UC.js").then((m) => m.default),
16
+ repos: () => import("./repos-GI6F72NO.js").then((m) => m.default),
17
+ agents: () => import("./agents-Y6LREFXP.js").then((m) => m.default),
18
+ supervise: () => import("./supervise-7ZITWRSL.js").then((m) => m.default),
19
+ mcp: () => import("./mcp-LC5VU65M.js").then((m) => m.default),
20
+ doctor: () => import("./doctor-CPVIT7IP.js").then((m) => m.default)
21
+ }
22
+ });
23
+ runMain(main);
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { defineCommand, runMain } from \"citty\";\n\nconst main = defineCommand({\n meta: {\n name: \"neo\",\n version: \"0.1.0\",\n description:\n \"Orchestrate autonomous developer agents with worktree isolation, budget guards, and 3-level recovery. Run 'neo init' to get started.\",\n },\n subCommands: {\n init: () => import(\"./commands/init.js\").then((m) => m.default),\n run: () => import(\"./commands/run.js\").then((m) => m.default),\n runs: () => import(\"./commands/runs.js\").then((m) => m.default),\n logs: () => import(\"./commands/logs.js\").then((m) => m.default),\n cost: () => import(\"./commands/cost.js\").then((m) => m.default),\n repos: () => import(\"./commands/repos.js\").then((m) => m.default),\n agents: () => import(\"./commands/agents.js\").then((m) => m.default),\n supervise: () => import(\"./commands/supervise.js\").then((m) => m.default),\n mcp: () => import(\"./commands/mcp.js\").then((m) => m.default),\n doctor: () => import(\"./commands/doctor.js\").then((m) => m.default),\n },\n});\n\nrunMain(main);\n"],"mappings":";AAAA,SAAS,eAAe,eAAe;AAEvC,IAAM,OAAO,cAAc;AAAA,EACzB,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,aACE;AAAA,EACJ;AAAA,EACA,aAAa;AAAA,IACX,MAAM,MAAM,OAAO,oBAAoB,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO;AAAA,IAC9D,KAAK,MAAM,OAAO,mBAAmB,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO;AAAA,IAC5D,MAAM,MAAM,OAAO,oBAAoB,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO;AAAA,IAC9D,MAAM,MAAM,OAAO,oBAAoB,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO;AAAA,IAC9D,MAAM,MAAM,OAAO,oBAAoB,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO;AAAA,IAC9D,OAAO,MAAM,OAAO,qBAAqB,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO;AAAA,IAChE,QAAQ,MAAM,OAAO,sBAAsB,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO;AAAA,IAClE,WAAW,MAAM,OAAO,yBAAyB,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO;AAAA,IACxE,KAAK,MAAM,OAAO,mBAAmB,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO;AAAA,IAC5D,QAAQ,MAAM,OAAO,sBAAsB,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO;AAAA,EACpE;AACF,CAAC;AAED,QAAQ,IAAI;","names":[]}
@@ -0,0 +1,74 @@
1
+ import {
2
+ detectDefaultBranch
3
+ } from "./chunk-EZAJLAUF.js";
4
+ import {
5
+ printError,
6
+ printSuccess
7
+ } from "./chunk-YQIWMDXL.js";
8
+
9
+ // src/commands/init.ts
10
+ import { existsSync } from "fs";
11
+ import { appendFile, mkdir, readFile } from "fs/promises";
12
+ import path from "path";
13
+ import { addRepoToGlobalConfig, getDataDir, loadGlobalConfig } from "@neotx/core";
14
+ import { defineCommand } from "citty";
15
+ var GITIGNORE_ENTRY = ".neo/worktrees/";
16
+ async function ensureGitignore() {
17
+ const gitignorePath = path.resolve(".gitignore");
18
+ if (existsSync(gitignorePath)) {
19
+ const content = await readFile(gitignorePath, "utf-8");
20
+ if (content.includes(GITIGNORE_ENTRY)) return false;
21
+ await appendFile(gitignorePath, `
22
+ # neo worktrees (ephemeral)
23
+ ${GITIGNORE_ENTRY}
24
+ `);
25
+ } else {
26
+ await appendFile(gitignorePath, `# neo worktrees (ephemeral)
27
+ ${GITIGNORE_ENTRY}
28
+ `);
29
+ }
30
+ return true;
31
+ }
32
+ var init_default = defineCommand({
33
+ meta: {
34
+ name: "init",
35
+ description: "Initialize a .neo/ project directory and register the repo"
36
+ },
37
+ args: {
38
+ force: {
39
+ type: "boolean",
40
+ description: "Re-register even if already initialized",
41
+ default: false
42
+ }
43
+ },
44
+ async run({ args }) {
45
+ const agentsDir = path.resolve(".neo/agents");
46
+ if (existsSync(agentsDir) && !args.force) {
47
+ printError(".neo/agents/ already exists. Use --force to re-register.");
48
+ process.exitCode = 1;
49
+ return;
50
+ }
51
+ await mkdir(agentsDir, { recursive: true });
52
+ printSuccess("Created .neo/agents/");
53
+ if (await ensureGitignore()) {
54
+ printSuccess(`Added ${GITIGNORE_ENTRY} to .gitignore`);
55
+ }
56
+ const branch = await detectDefaultBranch();
57
+ const repoPath = path.resolve(".");
58
+ await addRepoToGlobalConfig({
59
+ path: repoPath,
60
+ defaultBranch: branch
61
+ });
62
+ printSuccess(`Registered repo in global config (branch: ${branch})`);
63
+ const globalConfig = await loadGlobalConfig();
64
+ const globalDir = getDataDir();
65
+ printSuccess(
66
+ `Global config at ${globalDir}/config.yml (budget: $${globalConfig.budget.dailyCapUsd}/day)`
67
+ );
68
+ printSuccess("neo initialized. Run 'neo doctor' to verify setup.");
69
+ }
70
+ });
71
+ export {
72
+ init_default as default
73
+ };
74
+ //# sourceMappingURL=init-YNSPTCA3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/init.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { appendFile, mkdir, readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { addRepoToGlobalConfig, getDataDir, loadGlobalConfig } from \"@neotx/core\";\nimport { defineCommand } from \"citty\";\nimport { detectDefaultBranch } from \"../git-utils.js\";\nimport { printError, printSuccess } from \"../output.js\";\n\nconst GITIGNORE_ENTRY = \".neo/worktrees/\";\n\nasync function ensureGitignore(): Promise<boolean> {\n const gitignorePath = path.resolve(\".gitignore\");\n\n if (existsSync(gitignorePath)) {\n const content = await readFile(gitignorePath, \"utf-8\");\n if (content.includes(GITIGNORE_ENTRY)) return false;\n await appendFile(gitignorePath, `\\n# neo worktrees (ephemeral)\\n${GITIGNORE_ENTRY}\\n`);\n } else {\n await appendFile(gitignorePath, `# neo worktrees (ephemeral)\\n${GITIGNORE_ENTRY}\\n`);\n }\n\n return true;\n}\n\nexport default defineCommand({\n meta: {\n name: \"init\",\n description: \"Initialize a .neo/ project directory and register the repo\",\n },\n args: {\n force: {\n type: \"boolean\",\n description: \"Re-register even if already initialized\",\n default: false,\n },\n },\n async run({ args }) {\n const agentsDir = path.resolve(\".neo/agents\");\n\n if (existsSync(agentsDir) && !args.force) {\n printError(\".neo/agents/ already exists. Use --force to re-register.\");\n process.exitCode = 1;\n return;\n }\n\n // Create .neo/agents/ for project-local agent definitions\n await mkdir(agentsDir, { recursive: true });\n printSuccess(\"Created .neo/agents/\");\n\n // Ensure .neo/worktrees/ is in .gitignore\n if (await ensureGitignore()) {\n printSuccess(`Added ${GITIGNORE_ENTRY} to .gitignore`);\n }\n\n // Detect default branch and register repo in global config\n const branch = await detectDefaultBranch();\n const repoPath = path.resolve(\".\");\n\n await addRepoToGlobalConfig({\n path: repoPath,\n defaultBranch: branch,\n });\n printSuccess(`Registered repo in global config (branch: ${branch})`);\n\n // Ensure global config exists (creates with defaults if missing)\n const globalConfig = await loadGlobalConfig();\n const globalDir = getDataDir();\n printSuccess(\n `Global config at ${globalDir}/config.yml (budget: $${globalConfig.budget.dailyCapUsd}/day)`,\n );\n\n printSuccess(\"neo initialized. Run 'neo doctor' to verify setup.\");\n },\n});\n"],"mappings":";;;;;;;;;AAAA,SAAS,kBAAkB;AAC3B,SAAS,YAAY,OAAO,gBAAgB;AAC5C,OAAO,UAAU;AACjB,SAAS,uBAAuB,YAAY,wBAAwB;AACpE,SAAS,qBAAqB;AAI9B,IAAM,kBAAkB;AAExB,eAAe,kBAAoC;AACjD,QAAM,gBAAgB,KAAK,QAAQ,YAAY;AAE/C,MAAI,WAAW,aAAa,GAAG;AAC7B,UAAM,UAAU,MAAM,SAAS,eAAe,OAAO;AACrD,QAAI,QAAQ,SAAS,eAAe,EAAG,QAAO;AAC9C,UAAM,WAAW,eAAe;AAAA;AAAA,EAAkC,eAAe;AAAA,CAAI;AAAA,EACvF,OAAO;AACL,UAAM,WAAW,eAAe;AAAA,EAAgC,eAAe;AAAA,CAAI;AAAA,EACrF;AAEA,SAAO;AACT;AAEA,IAAO,eAAQ,cAAc;AAAA,EAC3B,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,MAAM;AAAA,IACJ,OAAO;AAAA,MACL,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,IACX;AAAA,EACF;AAAA,EACA,MAAM,IAAI,EAAE,KAAK,GAAG;AAClB,UAAM,YAAY,KAAK,QAAQ,aAAa;AAE5C,QAAI,WAAW,SAAS,KAAK,CAAC,KAAK,OAAO;AACxC,iBAAW,0DAA0D;AACrE,cAAQ,WAAW;AACnB;AAAA,IACF;AAGA,UAAM,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;AAC1C,iBAAa,sBAAsB;AAGnC,QAAI,MAAM,gBAAgB,GAAG;AAC3B,mBAAa,SAAS,eAAe,gBAAgB;AAAA,IACvD;AAGA,UAAM,SAAS,MAAM,oBAAoB;AACzC,UAAM,WAAW,KAAK,QAAQ,GAAG;AAEjC,UAAM,sBAAsB;AAAA,MAC1B,MAAM;AAAA,MACN,eAAe;AAAA,IACjB,CAAC;AACD,iBAAa,6CAA6C,MAAM,GAAG;AAGnE,UAAM,eAAe,MAAM,iBAAiB;AAC5C,UAAM,YAAY,WAAW;AAC7B;AAAA,MACE,oBAAoB,SAAS,yBAAyB,aAAa,OAAO,WAAW;AAAA,IACvF;AAEA,iBAAa,oDAAoD;AAAA,EACnE;AACF,CAAC;","names":[]}
@@ -0,0 +1,200 @@
1
+ import {
2
+ printError,
3
+ printJson
4
+ } from "./chunk-YQIWMDXL.js";
5
+
6
+ // src/commands/logs.ts
7
+ import { existsSync, watch } from "fs";
8
+ import { open, readdir, readFile } from "fs/promises";
9
+ import path from "path";
10
+ import { getJournalsDir, getRepoRunsDir, getRunLogPath, getRunsDir } from "@neotx/core";
11
+ import { defineCommand } from "citty";
12
+ async function readJournalLines(journalDir, filePrefix) {
13
+ if (!existsSync(journalDir)) return [];
14
+ const files = await readdir(journalDir);
15
+ const matching = files.filter((f) => f.startsWith(filePrefix)).sort().reverse();
16
+ const events = [];
17
+ for (const file of matching) {
18
+ const content = await readFile(path.join(journalDir, file), "utf-8");
19
+ for (const line of content.trim().split("\n")) {
20
+ if (!line.trim()) continue;
21
+ events.push(JSON.parse(line));
22
+ }
23
+ }
24
+ events.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
25
+ return events;
26
+ }
27
+ async function findRunLogPath(runId) {
28
+ const runsDir = getRunsDir();
29
+ if (!existsSync(runsDir)) return null;
30
+ const slugs = await readdir(runsDir, { withFileTypes: true });
31
+ for (const entry of slugs) {
32
+ if (!entry.isDirectory()) continue;
33
+ const logPath = getRunLogPath(entry.name, runId);
34
+ if (existsSync(logPath)) return logPath;
35
+ const runFile = path.join(getRepoRunsDir(entry.name), `${runId}.json`);
36
+ if (existsSync(runFile)) return logPath;
37
+ }
38
+ return null;
39
+ }
40
+ async function followRunLog(runId) {
41
+ const logPath = await findRunLogPath(runId);
42
+ if (!logPath) {
43
+ printError(`No log file found for run ${runId}. Is it a detached run?`);
44
+ process.exitCode = 1;
45
+ return;
46
+ }
47
+ if (existsSync(logPath)) {
48
+ const existing = await readFile(logPath, "utf-8");
49
+ if (existing) process.stdout.write(existing);
50
+ }
51
+ const runDir = path.dirname(logPath);
52
+ const runJsonPath = path.join(runDir, `${runId}.json`);
53
+ if (existsSync(runJsonPath)) {
54
+ const runData = JSON.parse(await readFile(runJsonPath, "utf-8"));
55
+ if (runData.status === "completed" || runData.status === "failed") {
56
+ return;
57
+ }
58
+ }
59
+ let offset = existsSync(logPath) ? await open(logPath, "r").then(async (fh) => {
60
+ const stat = await fh.stat();
61
+ await fh.close();
62
+ return stat.size;
63
+ }) : 0;
64
+ const ac = new AbortController();
65
+ process.on("SIGINT", () => ac.abort());
66
+ try {
67
+ const watcher = watch(logPath, { signal: ac.signal });
68
+ watcher.on("change", async () => {
69
+ try {
70
+ const fh = await open(logPath, "r");
71
+ const stat = await fh.stat();
72
+ if (stat.size > offset) {
73
+ const buf = Buffer.alloc(stat.size - offset);
74
+ await fh.read(buf, 0, buf.length, offset);
75
+ process.stdout.write(buf);
76
+ offset = stat.size;
77
+ }
78
+ await fh.close();
79
+ if (existsSync(runJsonPath)) {
80
+ const runData = JSON.parse(await readFile(runJsonPath, "utf-8"));
81
+ if (runData.status === "completed" || runData.status === "failed") {
82
+ ac.abort();
83
+ }
84
+ }
85
+ } catch {
86
+ }
87
+ });
88
+ } catch (err) {
89
+ if (err instanceof Error && err.name === "AbortError") return;
90
+ throw err;
91
+ }
92
+ }
93
+ function formatEvent(event, short) {
94
+ const ts = event.timestamp.slice(11, 19);
95
+ const type = event.type ?? "cost";
96
+ if (short) {
97
+ switch (type) {
98
+ case "session:start":
99
+ return `${ts} START ${event.agent} ${event.runId?.slice(0, 8) ?? ""}`;
100
+ case "session:complete":
101
+ return `${ts} OK $${(event.costUsd ?? 0).toFixed(4)} ${event.durationMs ?? 0}ms`;
102
+ case "session:fail":
103
+ return `${ts} FAIL ${event.error?.slice(0, 80) ?? ""}`;
104
+ case "cost:update":
105
+ return `${ts} COST today=$${event.todayTotal?.toFixed(4) ?? "?"} remaining=${event.budgetRemainingPct?.toFixed(0) ?? "?"}%`;
106
+ case "budget:alert":
107
+ return `${ts} ALERT budget=${event.utilizationPct?.toFixed(0) ?? "?"}%`;
108
+ default:
109
+ return `${ts} ${type}`;
110
+ }
111
+ }
112
+ switch (type) {
113
+ case "session:start":
114
+ return `[${ts}] session:start agent=${event.agent} run=${event.runId?.slice(0, 8) ?? "?"}`;
115
+ case "session:complete":
116
+ return `[${ts}] session:done cost=$${(event.costUsd ?? 0).toFixed(4)} duration=${event.durationMs ?? 0}ms`;
117
+ case "session:fail":
118
+ return `[${ts}] session:fail error=${event.error ?? "unknown"}`;
119
+ case "cost:update":
120
+ return `[${ts}] cost:update today=$${event.todayTotal?.toFixed(4) ?? "?"} remaining=${event.budgetRemainingPct?.toFixed(0) ?? "?"}%`;
121
+ case "budget:alert":
122
+ return `[${ts}] budget:alert utilization=${event.utilizationPct?.toFixed(0) ?? "?"}%`;
123
+ default:
124
+ return `[${ts}] ${type}`;
125
+ }
126
+ }
127
+ var logs_default = defineCommand({
128
+ meta: {
129
+ name: "logs",
130
+ description: "Show event logs from journals (session starts, completions, failures, costs)"
131
+ },
132
+ args: {
133
+ last: {
134
+ type: "string",
135
+ description: "Show only the last N events (default: 20)",
136
+ default: "20"
137
+ },
138
+ type: {
139
+ type: "string",
140
+ description: "Filter by event type: session:start, session:complete, session:fail, cost:update, budget:alert"
141
+ },
142
+ run: {
143
+ type: "string",
144
+ description: "Filter by run ID (prefix match)"
145
+ },
146
+ short: {
147
+ type: "boolean",
148
+ description: "Compact output for supervisor agents (saves tokens)",
149
+ default: false
150
+ },
151
+ follow: {
152
+ type: "boolean",
153
+ alias: "f",
154
+ description: "Follow a detached run log in real time (requires --run)",
155
+ default: false
156
+ },
157
+ output: {
158
+ type: "string",
159
+ description: "Output format: json"
160
+ }
161
+ },
162
+ async run({ args }) {
163
+ if (args.follow) {
164
+ if (!args.run) {
165
+ printError("--follow requires --run <runId>");
166
+ process.exitCode = 1;
167
+ return;
168
+ }
169
+ await followRunLog(args.run);
170
+ return;
171
+ }
172
+ const jsonOutput = args.output === "json";
173
+ const journalDir = getJournalsDir();
174
+ let events = await readJournalLines(journalDir, "events-");
175
+ if (events.length === 0) {
176
+ printError("No event logs found.");
177
+ process.exitCode = 1;
178
+ return;
179
+ }
180
+ if (args.type) {
181
+ events = events.filter((e) => e.type === args.type);
182
+ }
183
+ if (args.run) {
184
+ events = events.filter((e) => e.runId?.startsWith(args.run));
185
+ }
186
+ const limit = Number(args.last);
187
+ events = events.slice(0, limit);
188
+ if (jsonOutput) {
189
+ printJson(events);
190
+ return;
191
+ }
192
+ for (const event of events.reverse()) {
193
+ console.log(formatEvent(event, args.short));
194
+ }
195
+ }
196
+ });
197
+ export {
198
+ logs_default as default
199
+ };
200
+ //# sourceMappingURL=logs-AWNAMMJC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/logs.ts"],"sourcesContent":["import { existsSync, watch } from \"node:fs\";\nimport { open, readdir, readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { PersistedRun } from \"@neotx/core\";\nimport { getJournalsDir, getRepoRunsDir, getRunLogPath, getRunsDir } from \"@neotx/core\";\nimport { defineCommand } from \"citty\";\nimport { printError, printJson } from \"../output.js\";\n\ninterface JournalEvent {\n type?: string;\n timestamp: string;\n runId?: string;\n sessionId?: string;\n agent?: string;\n error?: string;\n costUsd?: number;\n durationMs?: number;\n [key: string]: unknown;\n}\n\nasync function readJournalLines(journalDir: string, filePrefix: string): Promise<JournalEvent[]> {\n if (!existsSync(journalDir)) return [];\n const files = await readdir(journalDir);\n const matching = files\n .filter((f) => f.startsWith(filePrefix))\n .sort()\n .reverse();\n const events: JournalEvent[] = [];\n\n for (const file of matching) {\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 events.push(JSON.parse(line) as JournalEvent);\n }\n }\n\n // Sort by timestamp descending (most recent first)\n events.sort((a, b) => b.timestamp.localeCompare(a.timestamp));\n return events;\n}\n\n/**\n * Find the log file for a run by searching all repo slug directories.\n */\nasync function findRunLogPath(runId: string): Promise<string | null> {\n const runsDir = getRunsDir();\n if (!existsSync(runsDir)) return null;\n\n const slugs = await readdir(runsDir, { withFileTypes: true });\n for (const entry of slugs) {\n if (!entry.isDirectory()) continue;\n const logPath = getRunLogPath(entry.name, runId);\n if (existsSync(logPath)) return logPath;\n // Also try prefix match on runId\n const runFile = path.join(getRepoRunsDir(entry.name), `${runId}.json`);\n if (existsSync(runFile)) return logPath;\n }\n return null;\n}\n\n/**\n * Tail a log file, printing new content as it appears.\n * Stops when the run completes or the user presses Ctrl+C.\n */\nasync function followRunLog(runId: string): Promise<void> {\n const logPath = await findRunLogPath(runId);\n if (!logPath) {\n printError(`No log file found for run ${runId}. Is it a detached run?`);\n process.exitCode = 1;\n return;\n }\n\n // Print existing content\n if (existsSync(logPath)) {\n const existing = await readFile(logPath, \"utf-8\");\n if (existing) process.stdout.write(existing);\n }\n\n // Find persisted run to check status\n const runDir = path.dirname(logPath);\n const runJsonPath = path.join(runDir, `${runId}.json`);\n\n // Check if already finished\n if (existsSync(runJsonPath)) {\n const runData = JSON.parse(await readFile(runJsonPath, \"utf-8\")) as PersistedRun;\n if (runData.status === \"completed\" || runData.status === \"failed\") {\n return;\n }\n }\n\n // Watch for changes\n let offset = existsSync(logPath)\n ? await open(logPath, \"r\").then(async (fh) => {\n const stat = await fh.stat();\n await fh.close();\n return stat.size;\n })\n : 0;\n\n const ac = new AbortController();\n process.on(\"SIGINT\", () => ac.abort());\n\n try {\n const watcher = watch(logPath, { signal: ac.signal });\n watcher.on(\"change\", async () => {\n try {\n const fh = await open(logPath, \"r\");\n const stat = await fh.stat();\n if (stat.size > offset) {\n const buf = Buffer.alloc(stat.size - offset);\n await fh.read(buf, 0, buf.length, offset);\n process.stdout.write(buf);\n offset = stat.size;\n }\n await fh.close();\n\n // Check if run is done\n if (existsSync(runJsonPath)) {\n const runData = JSON.parse(await readFile(runJsonPath, \"utf-8\")) as PersistedRun;\n if (runData.status === \"completed\" || runData.status === \"failed\") {\n ac.abort();\n }\n }\n } catch {\n // Ignore read errors during follow\n }\n });\n } catch (err) {\n if (err instanceof Error && err.name === \"AbortError\") return;\n throw err;\n }\n}\n\nfunction formatEvent(event: JournalEvent, short: boolean): string {\n const ts = event.timestamp.slice(11, 19);\n const type = event.type ?? \"cost\";\n\n if (short) {\n // Ultra compact: one line, minimal tokens\n switch (type) {\n case \"session:start\":\n return `${ts} START ${event.agent} ${event.runId?.slice(0, 8) ?? \"\"}`;\n case \"session:complete\":\n return `${ts} OK $${(event.costUsd ?? 0).toFixed(4)} ${event.durationMs ?? 0}ms`;\n case \"session:fail\":\n return `${ts} FAIL ${event.error?.slice(0, 80) ?? \"\"}`;\n case \"cost:update\":\n return `${ts} COST today=$${(event.todayTotal as number | undefined)?.toFixed(4) ?? \"?\"} remaining=${(event.budgetRemainingPct as number | undefined)?.toFixed(0) ?? \"?\"}%`;\n case \"budget:alert\":\n return `${ts} ALERT budget=${(event.utilizationPct as number | undefined)?.toFixed(0) ?? \"?\"}%`;\n default:\n return `${ts} ${type}`;\n }\n }\n\n switch (type) {\n case \"session:start\":\n return `[${ts}] session:start agent=${event.agent} run=${event.runId?.slice(0, 8) ?? \"?\"}`;\n case \"session:complete\":\n return `[${ts}] session:done cost=$${(event.costUsd ?? 0).toFixed(4)} duration=${event.durationMs ?? 0}ms`;\n case \"session:fail\":\n return `[${ts}] session:fail error=${event.error ?? \"unknown\"}`;\n case \"cost:update\":\n return `[${ts}] cost:update today=$${(event.todayTotal as number | undefined)?.toFixed(4) ?? \"?\"} remaining=${(event.budgetRemainingPct as number | undefined)?.toFixed(0) ?? \"?\"}%`;\n case \"budget:alert\":\n return `[${ts}] budget:alert utilization=${(event.utilizationPct as number | undefined)?.toFixed(0) ?? \"?\"}%`;\n default:\n return `[${ts}] ${type}`;\n }\n}\n\nexport default defineCommand({\n meta: {\n name: \"logs\",\n description: \"Show event logs from journals (session starts, completions, failures, costs)\",\n },\n args: {\n last: {\n type: \"string\",\n description: \"Show only the last N events (default: 20)\",\n default: \"20\",\n },\n type: {\n type: \"string\",\n description:\n \"Filter by event type: session:start, session:complete, session:fail, cost:update, budget:alert\",\n },\n run: {\n type: \"string\",\n description: \"Filter by run ID (prefix match)\",\n },\n short: {\n type: \"boolean\",\n description: \"Compact output for supervisor agents (saves tokens)\",\n default: false,\n },\n follow: {\n type: \"boolean\",\n alias: \"f\",\n description: \"Follow a detached run log in real time (requires --run)\",\n default: false,\n },\n output: {\n type: \"string\",\n description: \"Output format: json\",\n },\n },\n async run({ args }) {\n // Follow mode: tail a detached run's log file\n if (args.follow) {\n if (!args.run) {\n printError(\"--follow requires --run <runId>\");\n process.exitCode = 1;\n return;\n }\n await followRunLog(args.run);\n return;\n }\n\n const jsonOutput = args.output === \"json\";\n const journalDir = getJournalsDir();\n\n let events = await readJournalLines(journalDir, \"events-\");\n\n if (events.length === 0) {\n printError(\"No event logs found.\");\n process.exitCode = 1;\n return;\n }\n\n // Filter by type\n if (args.type) {\n events = events.filter((e) => e.type === args.type);\n }\n\n // Filter by run\n if (args.run) {\n events = events.filter((e) => e.runId?.startsWith(args.run as string));\n }\n\n // Limit\n const limit = Number(args.last);\n events = events.slice(0, limit);\n\n if (jsonOutput) {\n printJson(events);\n return;\n }\n\n for (const event of events.reverse()) {\n console.log(formatEvent(event, args.short));\n }\n },\n});\n"],"mappings":"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","names":[]}