@made-by-moonlight/athene-cli 0.9.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.
Files changed (315) hide show
  1. package/LICENSE +22 -0
  2. package/dist/assets/plugin-registry.json +67 -0
  3. package/dist/assets/scripts/athene-doctor.ps1 +352 -0
  4. package/dist/assets/scripts/athene-doctor.sh +552 -0
  5. package/dist/assets/scripts/athene-update.ps1 +224 -0
  6. package/dist/assets/scripts/athene-update.sh +252 -0
  7. package/dist/commands/completion.d.ts +3 -0
  8. package/dist/commands/completion.d.ts.map +1 -0
  9. package/dist/commands/completion.js +26 -0
  10. package/dist/commands/completion.js.map +1 -0
  11. package/dist/commands/config.d.ts +11 -0
  12. package/dist/commands/config.d.ts.map +1 -0
  13. package/dist/commands/config.js +89 -0
  14. package/dist/commands/config.js.map +1 -0
  15. package/dist/commands/dashboard.d.ts +3 -0
  16. package/dist/commands/dashboard.d.ts.map +1 -0
  17. package/dist/commands/dashboard.js +103 -0
  18. package/dist/commands/dashboard.js.map +1 -0
  19. package/dist/commands/doctor.d.ts +3 -0
  20. package/dist/commands/doctor.d.ts.map +1 -0
  21. package/dist/commands/doctor.js +329 -0
  22. package/dist/commands/doctor.js.map +1 -0
  23. package/dist/commands/events.d.ts +3 -0
  24. package/dist/commands/events.d.ts.map +1 -0
  25. package/dist/commands/events.js +172 -0
  26. package/dist/commands/events.js.map +1 -0
  27. package/dist/commands/migrate-storage.d.ts +3 -0
  28. package/dist/commands/migrate-storage.d.ts.map +1 -0
  29. package/dist/commands/migrate-storage.js +78 -0
  30. package/dist/commands/migrate-storage.js.map +1 -0
  31. package/dist/commands/notify.d.ts +3 -0
  32. package/dist/commands/notify.d.ts.map +1 -0
  33. package/dist/commands/notify.js +143 -0
  34. package/dist/commands/notify.js.map +1 -0
  35. package/dist/commands/open.d.ts +3 -0
  36. package/dist/commands/open.d.ts.map +1 -0
  37. package/dist/commands/open.js +167 -0
  38. package/dist/commands/open.js.map +1 -0
  39. package/dist/commands/plugin.d.ts +3 -0
  40. package/dist/commands/plugin.d.ts.map +1 -0
  41. package/dist/commands/plugin.js +462 -0
  42. package/dist/commands/plugin.js.map +1 -0
  43. package/dist/commands/project.d.ts +3 -0
  44. package/dist/commands/project.d.ts.map +1 -0
  45. package/dist/commands/project.js +143 -0
  46. package/dist/commands/project.js.map +1 -0
  47. package/dist/commands/report.d.ts +19 -0
  48. package/dist/commands/report.d.ts.map +1 -0
  49. package/dist/commands/report.js +114 -0
  50. package/dist/commands/report.js.map +1 -0
  51. package/dist/commands/review-check.d.ts +3 -0
  52. package/dist/commands/review-check.d.ts.map +1 -0
  53. package/dist/commands/review-check.js +122 -0
  54. package/dist/commands/review-check.js.map +1 -0
  55. package/dist/commands/review.d.ts +3 -0
  56. package/dist/commands/review.d.ts.map +1 -0
  57. package/dist/commands/review.js +215 -0
  58. package/dist/commands/review.js.map +1 -0
  59. package/dist/commands/send.d.ts +3 -0
  60. package/dist/commands/send.d.ts.map +1 -0
  61. package/dist/commands/send.js +187 -0
  62. package/dist/commands/send.js.map +1 -0
  63. package/dist/commands/session.d.ts +3 -0
  64. package/dist/commands/session.d.ts.map +1 -0
  65. package/dist/commands/session.js +439 -0
  66. package/dist/commands/session.js.map +1 -0
  67. package/dist/commands/setup.d.ts +5 -0
  68. package/dist/commands/setup.d.ts.map +1 -0
  69. package/dist/commands/setup.js +297 -0
  70. package/dist/commands/setup.js.map +1 -0
  71. package/dist/commands/spawn.d.ts +4 -0
  72. package/dist/commands/spawn.d.ts.map +1 -0
  73. package/dist/commands/spawn.js +436 -0
  74. package/dist/commands/spawn.js.map +1 -0
  75. package/dist/commands/start.d.ts +21 -0
  76. package/dist/commands/start.d.ts.map +1 -0
  77. package/dist/commands/start.js +1836 -0
  78. package/dist/commands/start.js.map +1 -0
  79. package/dist/commands/status.d.ts +3 -0
  80. package/dist/commands/status.d.ts.map +1 -0
  81. package/dist/commands/status.js +556 -0
  82. package/dist/commands/status.js.map +1 -0
  83. package/dist/commands/update.d.ts +15 -0
  84. package/dist/commands/update.d.ts.map +1 -0
  85. package/dist/commands/update.js +652 -0
  86. package/dist/commands/update.js.map +1 -0
  87. package/dist/commands/verify.d.ts +3 -0
  88. package/dist/commands/verify.d.ts.map +1 -0
  89. package/dist/commands/verify.js +131 -0
  90. package/dist/commands/verify.js.map +1 -0
  91. package/dist/index.d.ts +3 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +24 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/lib/bun-tmp-janitor.d.ts +18 -0
  96. package/dist/lib/bun-tmp-janitor.d.ts.map +1 -0
  97. package/dist/lib/bun-tmp-janitor.js +127 -0
  98. package/dist/lib/bun-tmp-janitor.js.map +1 -0
  99. package/dist/lib/caller-context.d.ts +13 -0
  100. package/dist/lib/caller-context.d.ts.map +1 -0
  101. package/dist/lib/caller-context.js +20 -0
  102. package/dist/lib/caller-context.js.map +1 -0
  103. package/dist/lib/cli-errors.d.ts +8 -0
  104. package/dist/lib/cli-errors.d.ts.map +1 -0
  105. package/dist/lib/cli-errors.js +20 -0
  106. package/dist/lib/cli-errors.js.map +1 -0
  107. package/dist/lib/completion.d.ts +13 -0
  108. package/dist/lib/completion.d.ts.map +1 -0
  109. package/dist/lib/completion.js +428 -0
  110. package/dist/lib/completion.js.map +1 -0
  111. package/dist/lib/composio-setup.d.ts +65 -0
  112. package/dist/lib/composio-setup.d.ts.map +1 -0
  113. package/dist/lib/composio-setup.js +3255 -0
  114. package/dist/lib/composio-setup.js.map +1 -0
  115. package/dist/lib/config-instruction.d.ts +2 -0
  116. package/dist/lib/config-instruction.d.ts.map +1 -0
  117. package/dist/lib/config-instruction.js +193 -0
  118. package/dist/lib/config-instruction.js.map +1 -0
  119. package/dist/lib/constants.d.ts +3 -0
  120. package/dist/lib/constants.d.ts.map +1 -0
  121. package/dist/lib/constants.js +3 -0
  122. package/dist/lib/constants.js.map +1 -0
  123. package/dist/lib/create-session-manager.d.ts +26 -0
  124. package/dist/lib/create-session-manager.d.ts.map +1 -0
  125. package/dist/lib/create-session-manager.js +55 -0
  126. package/dist/lib/create-session-manager.js.map +1 -0
  127. package/dist/lib/credential-resolver.d.ts +37 -0
  128. package/dist/lib/credential-resolver.d.ts.map +1 -0
  129. package/dist/lib/credential-resolver.js +105 -0
  130. package/dist/lib/credential-resolver.js.map +1 -0
  131. package/dist/lib/daemon.d.ts +69 -0
  132. package/dist/lib/daemon.d.ts.map +1 -0
  133. package/dist/lib/daemon.js +77 -0
  134. package/dist/lib/daemon.js.map +1 -0
  135. package/dist/lib/dashboard-rebuild.d.ts +53 -0
  136. package/dist/lib/dashboard-rebuild.d.ts.map +1 -0
  137. package/dist/lib/dashboard-rebuild.js +188 -0
  138. package/dist/lib/dashboard-rebuild.js.map +1 -0
  139. package/dist/lib/dashboard-setup.d.ts +14 -0
  140. package/dist/lib/dashboard-setup.d.ts.map +1 -0
  141. package/dist/lib/dashboard-setup.js +192 -0
  142. package/dist/lib/dashboard-setup.js.map +1 -0
  143. package/dist/lib/dashboard-url.d.ts +19 -0
  144. package/dist/lib/dashboard-url.d.ts.map +1 -0
  145. package/dist/lib/dashboard-url.js +25 -0
  146. package/dist/lib/dashboard-url.js.map +1 -0
  147. package/dist/lib/desktop-setup.d.ts +21 -0
  148. package/dist/lib/desktop-setup.d.ts.map +1 -0
  149. package/dist/lib/desktop-setup.js +556 -0
  150. package/dist/lib/desktop-setup.js.map +1 -0
  151. package/dist/lib/detect-agent.d.ts +24 -0
  152. package/dist/lib/detect-agent.d.ts.map +1 -0
  153. package/dist/lib/detect-agent.js +69 -0
  154. package/dist/lib/detect-agent.js.map +1 -0
  155. package/dist/lib/detect-env.d.ts +14 -0
  156. package/dist/lib/detect-env.d.ts.map +1 -0
  157. package/dist/lib/detect-env.js +46 -0
  158. package/dist/lib/detect-env.js.map +1 -0
  159. package/dist/lib/discord-setup.d.ts +20 -0
  160. package/dist/lib/discord-setup.d.ts.map +1 -0
  161. package/dist/lib/discord-setup.js +584 -0
  162. package/dist/lib/discord-setup.js.map +1 -0
  163. package/dist/lib/format.d.ts +11 -0
  164. package/dist/lib/format.d.ts.map +1 -0
  165. package/dist/lib/format.js +116 -0
  166. package/dist/lib/format.js.map +1 -0
  167. package/dist/lib/git-utils.d.ts +14 -0
  168. package/dist/lib/git-utils.d.ts.map +1 -0
  169. package/dist/lib/git-utils.js +45 -0
  170. package/dist/lib/git-utils.js.map +1 -0
  171. package/dist/lib/install-helpers.d.ts +24 -0
  172. package/dist/lib/install-helpers.d.ts.map +1 -0
  173. package/dist/lib/install-helpers.js +76 -0
  174. package/dist/lib/install-helpers.js.map +1 -0
  175. package/dist/lib/lifecycle-service.d.ts +11 -0
  176. package/dist/lib/lifecycle-service.d.ts.map +1 -0
  177. package/dist/lib/lifecycle-service.js +65 -0
  178. package/dist/lib/lifecycle-service.js.map +1 -0
  179. package/dist/lib/notifier-routing.d.ts +35 -0
  180. package/dist/lib/notifier-routing.d.ts.map +1 -0
  181. package/dist/lib/notifier-routing.js +133 -0
  182. package/dist/lib/notifier-routing.js.map +1 -0
  183. package/dist/lib/notify-test.d.ts +72 -0
  184. package/dist/lib/notify-test.d.ts.map +1 -0
  185. package/dist/lib/notify-test.js +674 -0
  186. package/dist/lib/notify-test.js.map +1 -0
  187. package/dist/lib/openclaw-probe.d.ts +38 -0
  188. package/dist/lib/openclaw-probe.d.ts.map +1 -0
  189. package/dist/lib/openclaw-probe.js +146 -0
  190. package/dist/lib/openclaw-probe.js.map +1 -0
  191. package/dist/lib/openclaw-setup.d.ts +19 -0
  192. package/dist/lib/openclaw-setup.d.ts.map +1 -0
  193. package/dist/lib/openclaw-setup.js +684 -0
  194. package/dist/lib/openclaw-setup.js.map +1 -0
  195. package/dist/lib/path-equality.d.ts +29 -0
  196. package/dist/lib/path-equality.d.ts.map +1 -0
  197. package/dist/lib/path-equality.js +52 -0
  198. package/dist/lib/path-equality.js.map +1 -0
  199. package/dist/lib/plugin-marketplace.d.ts +24 -0
  200. package/dist/lib/plugin-marketplace.d.ts.map +1 -0
  201. package/dist/lib/plugin-marketplace.js +175 -0
  202. package/dist/lib/plugin-marketplace.js.map +1 -0
  203. package/dist/lib/plugin-scaffold.d.ts +14 -0
  204. package/dist/lib/plugin-scaffold.d.ts.map +1 -0
  205. package/dist/lib/plugin-scaffold.js +174 -0
  206. package/dist/lib/plugin-scaffold.js.map +1 -0
  207. package/dist/lib/plugin-store.d.ts +9 -0
  208. package/dist/lib/plugin-store.d.ts.map +1 -0
  209. package/dist/lib/plugin-store.js +121 -0
  210. package/dist/lib/plugin-store.js.map +1 -0
  211. package/dist/lib/plugins.d.ts +17 -0
  212. package/dist/lib/plugins.d.ts.map +1 -0
  213. package/dist/lib/plugins.js +65 -0
  214. package/dist/lib/plugins.js.map +1 -0
  215. package/dist/lib/portfolio-display.d.ts +10 -0
  216. package/dist/lib/portfolio-display.d.ts.map +1 -0
  217. package/dist/lib/portfolio-display.js +17 -0
  218. package/dist/lib/portfolio-display.js.map +1 -0
  219. package/dist/lib/preflight.d.ts +27 -0
  220. package/dist/lib/preflight.d.ts.map +1 -0
  221. package/dist/lib/preflight.js +77 -0
  222. package/dist/lib/preflight.js.map +1 -0
  223. package/dist/lib/prevent-sleep.d.ts +34 -0
  224. package/dist/lib/prevent-sleep.d.ts.map +1 -0
  225. package/dist/lib/prevent-sleep.js +65 -0
  226. package/dist/lib/prevent-sleep.js.map +1 -0
  227. package/dist/lib/project-detection.d.ts +11 -0
  228. package/dist/lib/project-detection.d.ts.map +1 -0
  229. package/dist/lib/project-detection.js +206 -0
  230. package/dist/lib/project-detection.js.map +1 -0
  231. package/dist/lib/project-resolution.d.ts +10 -0
  232. package/dist/lib/project-resolution.d.ts.map +1 -0
  233. package/dist/lib/project-resolution.js +17 -0
  234. package/dist/lib/project-resolution.js.map +1 -0
  235. package/dist/lib/project-supervisor.d.ts +28 -0
  236. package/dist/lib/project-supervisor.d.ts.map +1 -0
  237. package/dist/lib/project-supervisor.js +167 -0
  238. package/dist/lib/project-supervisor.js.map +1 -0
  239. package/dist/lib/prompts.d.ts +7 -0
  240. package/dist/lib/prompts.d.ts.map +1 -0
  241. package/dist/lib/prompts.js +37 -0
  242. package/dist/lib/prompts.js.map +1 -0
  243. package/dist/lib/repo-utils.d.ts +16 -0
  244. package/dist/lib/repo-utils.d.ts.map +1 -0
  245. package/dist/lib/repo-utils.js +26 -0
  246. package/dist/lib/repo-utils.js.map +1 -0
  247. package/dist/lib/resolve-project.d.ts +113 -0
  248. package/dist/lib/resolve-project.d.ts.map +1 -0
  249. package/dist/lib/resolve-project.js +433 -0
  250. package/dist/lib/resolve-project.js.map +1 -0
  251. package/dist/lib/routes.d.ts +2 -0
  252. package/dist/lib/routes.d.ts.map +1 -0
  253. package/dist/lib/routes.js +5 -0
  254. package/dist/lib/routes.js.map +1 -0
  255. package/dist/lib/running-state.d.ts +76 -0
  256. package/dist/lib/running-state.d.ts.map +1 -0
  257. package/dist/lib/running-state.js +338 -0
  258. package/dist/lib/running-state.js.map +1 -0
  259. package/dist/lib/script-runner.d.ts +10 -0
  260. package/dist/lib/script-runner.d.ts.map +1 -0
  261. package/dist/lib/script-runner.js +189 -0
  262. package/dist/lib/script-runner.js.map +1 -0
  263. package/dist/lib/session-utils.d.ts +14 -0
  264. package/dist/lib/session-utils.d.ts.map +1 -0
  265. package/dist/lib/session-utils.js +58 -0
  266. package/dist/lib/session-utils.js.map +1 -0
  267. package/dist/lib/shell.d.ts +17 -0
  268. package/dist/lib/shell.d.ts.map +1 -0
  269. package/dist/lib/shell.js +90 -0
  270. package/dist/lib/shell.js.map +1 -0
  271. package/dist/lib/shutdown.d.ts +30 -0
  272. package/dist/lib/shutdown.d.ts.map +1 -0
  273. package/dist/lib/shutdown.js +177 -0
  274. package/dist/lib/shutdown.js.map +1 -0
  275. package/dist/lib/slack-setup.d.ts +17 -0
  276. package/dist/lib/slack-setup.d.ts.map +1 -0
  277. package/dist/lib/slack-setup.js +485 -0
  278. package/dist/lib/slack-setup.js.map +1 -0
  279. package/dist/lib/startup-preflight.d.ts +36 -0
  280. package/dist/lib/startup-preflight.d.ts.map +1 -0
  281. package/dist/lib/startup-preflight.js +273 -0
  282. package/dist/lib/startup-preflight.js.map +1 -0
  283. package/dist/lib/update-channel-onboarding.d.ts +52 -0
  284. package/dist/lib/update-channel-onboarding.d.ts.map +1 -0
  285. package/dist/lib/update-channel-onboarding.js +107 -0
  286. package/dist/lib/update-channel-onboarding.js.map +1 -0
  287. package/dist/lib/update-check.d.ts +161 -0
  288. package/dist/lib/update-check.d.ts.map +1 -0
  289. package/dist/lib/update-check.js +504 -0
  290. package/dist/lib/update-check.js.map +1 -0
  291. package/dist/lib/web-dir.d.ts +47 -0
  292. package/dist/lib/web-dir.d.ts.map +1 -0
  293. package/dist/lib/web-dir.js +179 -0
  294. package/dist/lib/web-dir.js.map +1 -0
  295. package/dist/lib/webhook-setup.d.ts +16 -0
  296. package/dist/lib/webhook-setup.d.ts.map +1 -0
  297. package/dist/lib/webhook-setup.js +383 -0
  298. package/dist/lib/webhook-setup.js.map +1 -0
  299. package/dist/options/version.d.ts +2 -0
  300. package/dist/options/version.d.ts.map +1 -0
  301. package/dist/options/version.js +7 -0
  302. package/dist/options/version.js.map +1 -0
  303. package/dist/program.d.ts +3 -0
  304. package/dist/program.d.ts.map +1 -0
  305. package/dist/program.js +63 -0
  306. package/dist/program.js.map +1 -0
  307. package/package.json +80 -0
  308. package/templates/rules/base.md +4 -0
  309. package/templates/rules/go.md +8 -0
  310. package/templates/rules/javascript.md +4 -0
  311. package/templates/rules/nextjs.md +7 -0
  312. package/templates/rules/pnpm-workspaces.md +4 -0
  313. package/templates/rules/python.md +8 -0
  314. package/templates/rules/react.md +8 -0
  315. package/templates/rules/typescript.md +6 -0
@@ -0,0 +1,187 @@
1
+ import { readFileSync, writeFileSync, unlinkSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import chalk from "chalk";
5
+ import { loadConfig, } from "@made-by-moonlight/athene-core";
6
+ import { exec, tmux } from "../lib/shell.js";
7
+ import { getAgentByName, getAgentByNameFromRegistry } from "../lib/plugins.js";
8
+ import { getPluginRegistry, getSessionManager } from "../lib/create-session-manager.js";
9
+ /**
10
+ * Resolve session context: tmux target name and Agent plugin.
11
+ * Loads config and looks up the session once, avoiding duplicate work.
12
+ */
13
+ async function resolveSessionContext(sessionName) {
14
+ try {
15
+ const config = loadConfig();
16
+ const registry = await getPluginRegistry(config);
17
+ const sm = await getSessionManager(config);
18
+ const session = await sm.get(sessionName);
19
+ if (session) {
20
+ const tmuxTarget = session.runtimeHandle?.id ?? sessionName;
21
+ const project = config.projects[session.projectId];
22
+ const agentName = session.metadata["agent"];
23
+ const runtimeName = session.runtimeHandle?.runtimeName ?? project?.runtime ?? config.defaults.runtime;
24
+ return {
25
+ tmuxTarget,
26
+ runtimeName,
27
+ agent: getAgentByNameFromRegistry(registry, agentName),
28
+ session,
29
+ sessionManager: sm,
30
+ };
31
+ }
32
+ }
33
+ catch {
34
+ // No config or session not found — fall back to defaults
35
+ }
36
+ return {
37
+ tmuxTarget: sessionName,
38
+ runtimeName: "tmux",
39
+ agent: getAgentByName("claude-code"),
40
+ session: null,
41
+ sessionManager: null,
42
+ };
43
+ }
44
+ function isActive(agent, terminalOutput) {
45
+ return agent.detectActivity(terminalOutput) === "active";
46
+ }
47
+ function hasQueuedMessage(terminalOutput) {
48
+ return terminalOutput.includes("Press up to edit queued messages");
49
+ }
50
+ function sleep(ms) {
51
+ return new Promise((resolve) => setTimeout(resolve, ms));
52
+ }
53
+ async function readMessageInput(opts, messageParts) {
54
+ const inlineMessage = messageParts.join(" ");
55
+ if (!opts.file && !inlineMessage) {
56
+ console.error(chalk.red("No message provided"));
57
+ process.exit(1);
58
+ }
59
+ if (!opts.file) {
60
+ return inlineMessage;
61
+ }
62
+ try {
63
+ return readFileSync(opts.file, "utf-8");
64
+ }
65
+ catch (err) {
66
+ console.error(chalk.red(`Cannot read file: ${opts.file} (${err})`));
67
+ process.exit(1);
68
+ }
69
+ }
70
+ async function sendViaTmux(tmuxTarget, message) {
71
+ await exec("tmux", ["send-keys", "-t", tmuxTarget, "C-u"]);
72
+ await sleep(200);
73
+ if (message.includes("\n") || message.length > 200) {
74
+ const tmpFile = join(tmpdir(), `ao-send-${Date.now()}.txt`);
75
+ writeFileSync(tmpFile, message);
76
+ try {
77
+ await exec("tmux", ["load-buffer", tmpFile]);
78
+ await exec("tmux", ["paste-buffer", "-t", tmuxTarget]);
79
+ }
80
+ finally {
81
+ try {
82
+ unlinkSync(tmpFile);
83
+ }
84
+ catch {
85
+ // ignore cleanup failure
86
+ }
87
+ }
88
+ }
89
+ else {
90
+ await exec("tmux", ["send-keys", "-t", tmuxTarget, "-l", message]);
91
+ }
92
+ await sleep(300);
93
+ await exec("tmux", ["send-keys", "-t", tmuxTarget, "Enter"]);
94
+ }
95
+ export function registerSend(program) {
96
+ program
97
+ .command("send")
98
+ .description("Send a message to a session with busy detection and retry")
99
+ .argument("<session>", "Session name")
100
+ .argument("[message...]", "Message to send")
101
+ .option("-f, --file <path>", "Send contents of a file instead")
102
+ .option("--no-wait", "Don't wait for session to become idle before sending")
103
+ .option("--timeout <seconds>", "Max seconds to wait for idle", "600")
104
+ .action(async (session, messageParts, opts) => {
105
+ // Resolve session context once: tmux target, agent plugin, session data
106
+ const { tmuxTarget, runtimeName, agent, session: existingSession, sessionManager, } = await resolveSessionContext(session);
107
+ const rawMessage = await readMessageInput(opts, messageParts);
108
+ // Auto-prefix with the sender's session ID when athene send is invoked
109
+ // from inside an AO session (worker → orchestrator, orchestrator →
110
+ // worker, worker → worker). The receiver gets the message as raw
111
+ // terminal input with no `from:` metadata, so the prefix is the only
112
+ // way to identify who's writing. Humans running athene send from their
113
+ // own terminal have no AO_SESSION_ID and stay unprefixed.
114
+ const senderSessionId = process.env["AO_SESSION_ID"];
115
+ const message = senderSessionId
116
+ ? `[from ${senderSessionId}] ${rawMessage}`
117
+ : rawMessage;
118
+ const parsedTimeout = parseInt(opts.timeout || "600", 10);
119
+ const timeoutMs = (isNaN(parsedTimeout) || parsedTimeout <= 0 ? 600 : parsedTimeout) * 1000;
120
+ const canUseTmux = runtimeName === "tmux";
121
+ if (!existingSession) {
122
+ const exists = await tmux("has-session", "-t", tmuxTarget);
123
+ if (exists === null) {
124
+ console.error(chalk.red(`Session '${session}' does not exist`));
125
+ process.exit(1);
126
+ }
127
+ }
128
+ // Helper to capture output from the resolved tmux target
129
+ async function captureOutput(lines) {
130
+ if (!canUseTmux)
131
+ return "";
132
+ const output = await tmux("capture-pane", "-t", tmuxTarget, "-p", "-S", String(-lines));
133
+ return output || "";
134
+ }
135
+ const delegatesToSessionManager = Boolean(existingSession && sessionManager);
136
+ if (opts.wait !== false && canUseTmux && !delegatesToSessionManager) {
137
+ const start = Date.now();
138
+ let warned = false;
139
+ while (isActive(agent, await captureOutput(5))) {
140
+ if (!warned) {
141
+ console.log(chalk.dim(`Waiting for ${session} to become idle...`));
142
+ warned = true;
143
+ }
144
+ if (Date.now() - start > timeoutMs) {
145
+ console.log(chalk.yellow("Timeout waiting for idle. Sending anyway."));
146
+ break;
147
+ }
148
+ await sleep(5000);
149
+ }
150
+ }
151
+ if (!canUseTmux && !delegatesToSessionManager) {
152
+ console.error(chalk.red(`Session '${session}' is not tmux-backed and cannot be sent without lifecycle routing`));
153
+ process.exit(1);
154
+ }
155
+ if (existingSession && sessionManager) {
156
+ try {
157
+ await sessionManager.send(session, message);
158
+ console.log(chalk.green("Message sent and processing"));
159
+ }
160
+ catch (err) {
161
+ console.error(chalk.red(err instanceof Error ? err.message : String(err)));
162
+ process.exit(1);
163
+ }
164
+ return;
165
+ }
166
+ await sendViaTmux(tmuxTarget, message);
167
+ // Verify delivery with retries
168
+ for (let attempt = 1; attempt <= 3; attempt++) {
169
+ await sleep(2000);
170
+ const output = await captureOutput(10);
171
+ if (isActive(agent, output)) {
172
+ console.log(chalk.green("Message sent and processing"));
173
+ return;
174
+ }
175
+ if (hasQueuedMessage(output)) {
176
+ console.log(chalk.green("Message queued (session finishing previous task)"));
177
+ return;
178
+ }
179
+ if (attempt < 3) {
180
+ await tmux("send-keys", "-t", tmuxTarget, "Enter");
181
+ await sleep(1000);
182
+ }
183
+ }
184
+ console.log(chalk.yellow("Message sent — could not confirm it was received"));
185
+ });
186
+ }
187
+ //# sourceMappingURL=send.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send.js","sourceRoot":"","sources":["../../src/commands/send.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerSession(program: Command): void;
3
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/commands/session.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA8BzC,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAygBtD"}
@@ -0,0 +1,439 @@
1
+ import { spawn } from "node:child_process";
2
+ import { connect as netConnect } from "node:net";
3
+ import chalk from "chalk";
4
+ import { generateConfigHash, isOrchestratorSession, isTerminalSession, isWindows, loadConfig, SessionNotRestorableError, WorkspaceMissingError, } from "@made-by-moonlight/athene-core";
5
+ import { DEFAULT_PORT } from "../lib/constants.js";
6
+ import { git, getTmuxActivity, tmux } from "../lib/shell.js";
7
+ import { formatAge } from "../lib/format.js";
8
+ import { getSessionManager } from "../lib/create-session-manager.js";
9
+ import { isOrchestratorSessionName } from "../lib/session-utils.js";
10
+ import { projectSessionUrl } from "../lib/routes.js";
11
+ export function registerSession(program) {
12
+ const session = program
13
+ .command("session")
14
+ .description("Session management (ls, kill, cleanup, restore, claim-pr)");
15
+ session
16
+ .command("ls")
17
+ .description("List all sessions")
18
+ .option("-p, --project <id>", "Filter by project ID")
19
+ .option("-a, --all", "Include orchestrator sessions")
20
+ .option("--include-terminated", "Include terminated sessions (killed/done/merged/terminated/errored/cleanup)")
21
+ .option("--json", "Output as JSON")
22
+ .action(async (opts) => {
23
+ const config = loadConfig();
24
+ if (opts.project && !config.projects[opts.project]) {
25
+ console.error(chalk.red(`Unknown project: ${opts.project}`));
26
+ process.exit(1);
27
+ }
28
+ const sm = await getSessionManager(config);
29
+ const allSessions = await sm.list(opts.project);
30
+ // Filter out orchestrator sessions unless --all is passed
31
+ const withoutOrchestrators = opts.all
32
+ ? allSessions
33
+ : allSessions.filter((s) => !isOrchestratorSessionName(config, s.id, s.projectId));
34
+ // Count terminal sessions that would be hidden by default, then
35
+ // drop them unless --include-terminated is passed.
36
+ const hiddenTerminatedCount = opts.includeTerminated
37
+ ? 0
38
+ : withoutOrchestrators.filter(isTerminalSession).length;
39
+ const sessions = opts.includeTerminated
40
+ ? withoutOrchestrators
41
+ : withoutOrchestrators.filter((s) => !isTerminalSession(s));
42
+ // Group sessions by project
43
+ const byProject = new Map();
44
+ for (const s of sessions) {
45
+ const list = byProject.get(s.projectId) ?? [];
46
+ list.push(s);
47
+ byProject.set(s.projectId, list);
48
+ }
49
+ // Iterate over all configured projects (not just ones with sessions)
50
+ const projectIds = opts.project ? [opts.project] : Object.keys(config.projects);
51
+ const allSessionPrefixes = Object.entries(config.projects).map(([id, project]) => project.sessionPrefix ?? id);
52
+ const jsonOutput = [];
53
+ for (const projectId of projectIds) {
54
+ const project = config.projects[projectId];
55
+ if (!project)
56
+ continue;
57
+ if (!opts.json) {
58
+ console.log(chalk.bold(`\n${project.name || projectId}:`));
59
+ }
60
+ const projectSessions = (byProject.get(projectId) ?? []).sort((a, b) => a.id.localeCompare(b.id));
61
+ if (projectSessions.length === 0) {
62
+ if (!opts.json) {
63
+ console.log(chalk.dim(" (no active sessions)"));
64
+ }
65
+ continue;
66
+ }
67
+ // Pre-fetch all branches and activities in parallel
68
+ const branches = await Promise.all(projectSessions.map(async (s) => {
69
+ if (s.workspacePath) {
70
+ return git(["branch", "--show-current"], s.workspacePath).catch(() => null);
71
+ }
72
+ return null;
73
+ }));
74
+ const activities = await Promise.all(projectSessions.map((s) => {
75
+ // On Windows, use enriched session lastActivityAt (no tmux available).
76
+ if (isWindows()) {
77
+ return Promise.resolve(s.lastActivityAt ? s.lastActivityAt.getTime() : null);
78
+ }
79
+ const tmuxTarget = s.runtimeHandle?.id ?? s.id;
80
+ return getTmuxActivity(tmuxTarget).catch(() => null);
81
+ }));
82
+ for (let i = 0; i < projectSessions.length; i++) {
83
+ const s = projectSessions[i];
84
+ const liveBranch = branches[i];
85
+ const activityTs = activities[i];
86
+ // Priority: live branch from workspace > metadata branch > empty string
87
+ const branchStr = (s.workspacePath && liveBranch) ? liveBranch : (s.branch || "");
88
+ const prUrl = s.metadata["pr"] ?? null;
89
+ if (opts.json) {
90
+ const role = isOrchestratorSession(s, project.sessionPrefix ?? projectId, allSessionPrefixes)
91
+ ? "orchestrator"
92
+ : "worker";
93
+ jsonOutput.push({
94
+ id: s.id,
95
+ projectId,
96
+ projectName: project.name || projectId,
97
+ role,
98
+ branch: branchStr || null,
99
+ status: s.status,
100
+ issueId: s.issueId,
101
+ pr: prUrl,
102
+ workspacePath: s.workspacePath,
103
+ lastActivityAt: activityTs ? new Date(activityTs).toISOString() : null,
104
+ });
105
+ continue;
106
+ }
107
+ const age = activityTs ? formatAge(activityTs) : "-";
108
+ const parts = [chalk.green(s.id), chalk.dim(`(${age})`)];
109
+ if (branchStr)
110
+ parts.push(chalk.cyan(branchStr));
111
+ if (s.status)
112
+ parts.push(chalk.dim(`[${s.status}]`));
113
+ if (prUrl)
114
+ parts.push(chalk.blue(prUrl));
115
+ console.log(` ${parts.join(" ")}`);
116
+ }
117
+ }
118
+ if (opts.json) {
119
+ console.log(JSON.stringify({ data: jsonOutput, meta: { hiddenTerminatedCount } }, null, 2));
120
+ return;
121
+ }
122
+ if (hiddenTerminatedCount > 0) {
123
+ console.log(chalk.dim(` ${hiddenTerminatedCount} terminated session${hiddenTerminatedCount !== 1 ? "s" : ""} hidden. Use --include-terminated to show.`));
124
+ }
125
+ console.log();
126
+ });
127
+ session
128
+ .command("attach")
129
+ .description("Attach to a session's terminal")
130
+ .argument("<session>", "Session name to attach")
131
+ .action(async (sessionName) => {
132
+ const config = loadConfig();
133
+ const sm = await getSessionManager(config);
134
+ const sessionInfo = await sm.get(sessionName);
135
+ if (isWindows()) {
136
+ // Windows: connect to PTY host named pipe and relay raw terminal I/O
137
+ // Prefer explicit pipePath from runtimeHandle.data if it's a valid string
138
+ const dataPipePath = sessionInfo?.runtimeHandle?.data?.["pipePath"];
139
+ const pipePath = typeof dataPipePath === "string" && dataPipePath
140
+ ? dataPipePath
141
+ : `\\\\.\\pipe\\ao-pty-${sessionInfo?.runtimeHandle?.id ??
142
+ (config.configPath
143
+ ? `${generateConfigHash(config.configPath)}-${sessionName}`
144
+ : sessionName)}`;
145
+ const sock = netConnect(pipePath);
146
+ // Handler refs — set in connect, cleaned up on exit
147
+ let sendResize = null;
148
+ let stdinHandler = null;
149
+ const cleanup = () => {
150
+ if (process.stdin.isTTY)
151
+ process.stdin.setRawMode(false);
152
+ if (sendResize)
153
+ process.stdout.removeListener("resize", sendResize);
154
+ if (stdinHandler)
155
+ process.stdin.removeListener("data", stdinHandler);
156
+ sock.destroy();
157
+ };
158
+ sock.on("error", (err) => {
159
+ cleanup();
160
+ console.error(chalk.red(`Cannot attach to ${sessionName}: ${err.message}`));
161
+ process.exit(1);
162
+ });
163
+ sock.on("connect", () => {
164
+ // Raw mode so keystrokes pass through directly (like tmux attach)
165
+ if (process.stdin.isTTY) {
166
+ process.stdin.setRawMode(true);
167
+ }
168
+ process.stdin.resume();
169
+ // Binary protocol framing buffer
170
+ let buf = Buffer.alloc(0);
171
+ // PTY host → stdout
172
+ sock.on("data", (chunk) => {
173
+ buf = Buffer.concat([buf, chunk]);
174
+ while (buf.length >= 5) {
175
+ const msgType = buf.readUInt8(0);
176
+ const len = buf.readUInt32BE(1);
177
+ if (buf.length < 5 + len)
178
+ break;
179
+ const payload = buf.subarray(5, 5 + len);
180
+ buf = buf.subarray(5 + len);
181
+ // 0x01 = MSG_TERMINAL_DATA
182
+ if (msgType === 0x01) {
183
+ process.stdout.write(payload);
184
+ }
185
+ // 0x07 = MSG_STATUS_RES (PTY exited)
186
+ if (msgType === 0x07) {
187
+ try {
188
+ const status = JSON.parse(payload.toString());
189
+ if (!status.alive) {
190
+ cleanup();
191
+ console.log(`\n[session exited with code ${status.exitCode ?? "unknown"}]`);
192
+ process.exit(status.exitCode ?? 0);
193
+ }
194
+ }
195
+ catch { /* ignore parse errors */ }
196
+ }
197
+ }
198
+ });
199
+ // stdin → PTY host (MSG_TERMINAL_INPUT = 0x02)
200
+ // Ctrl+\ (0x1c) = detach without killing (like tmux Ctrl+B,D)
201
+ stdinHandler = (data) => {
202
+ if (data.length === 1 && data[0] === 0x1c) {
203
+ console.log("\n[detached]");
204
+ cleanup();
205
+ process.exit(0);
206
+ return;
207
+ }
208
+ const header = Buffer.alloc(5);
209
+ header.writeUInt8(0x02, 0);
210
+ header.writeUInt32BE(data.length, 1);
211
+ sock.write(Buffer.concat([header, data]));
212
+ };
213
+ process.stdin.on("data", stdinHandler);
214
+ // Send terminal resize (MSG_RESIZE = 0x03)
215
+ sendResize = () => {
216
+ const payload = Buffer.from(JSON.stringify({ cols: process.stdout.columns, rows: process.stdout.rows }));
217
+ const header = Buffer.alloc(5);
218
+ header.writeUInt8(0x03, 0);
219
+ header.writeUInt32BE(payload.length, 1);
220
+ sock.write(Buffer.concat([header, payload]));
221
+ };
222
+ process.stdout.on("resize", sendResize);
223
+ sendResize(); // send initial size
224
+ sock.on("close", () => {
225
+ cleanup();
226
+ process.exit(0);
227
+ });
228
+ });
229
+ // Keep process alive until pipe closes
230
+ await new Promise(() => { });
231
+ }
232
+ else {
233
+ // Unix: tmux attach (unchanged)
234
+ const tmuxTarget = sessionInfo?.runtimeHandle?.id ?? sessionName;
235
+ const exists = await tmux("has-session", "-t", tmuxTarget);
236
+ if (exists === null) {
237
+ console.error(chalk.red(`Session '${sessionName}' does not exist`));
238
+ process.exit(1);
239
+ }
240
+ await new Promise((resolve, reject) => {
241
+ const child = spawn("tmux", ["attach", "-t", tmuxTarget], { stdio: "inherit" });
242
+ child.once("error", (err) => reject(err));
243
+ child.once("exit", (code) => {
244
+ if (code === 0 || code === null) {
245
+ resolve();
246
+ return;
247
+ }
248
+ reject(new Error(`tmux attach exited with code ${code}`));
249
+ });
250
+ }).catch((err) => {
251
+ console.error(chalk.red(`Failed to attach to session ${sessionName}: ${err}`));
252
+ process.exit(1);
253
+ });
254
+ }
255
+ });
256
+ session
257
+ .command("kill")
258
+ .description("Kill a session and remove its worktree")
259
+ .argument("<session>", "Session name to kill")
260
+ .option("--purge-session", "Delete mapped OpenCode session during kill")
261
+ .action(async (sessionName, opts) => {
262
+ const config = loadConfig();
263
+ const sm = await getSessionManager(config);
264
+ try {
265
+ await sm.kill(sessionName, { purgeOpenCode: opts.purgeSession === true });
266
+ console.log(chalk.green(`\nSession ${sessionName} killed.`));
267
+ }
268
+ catch (err) {
269
+ console.error(chalk.red(`Failed to kill session ${sessionName}: ${err}`));
270
+ process.exit(1);
271
+ }
272
+ });
273
+ session
274
+ .command("cleanup")
275
+ .description("Kill cleanup-eligible sessions with closed work or dead runtimes")
276
+ .option("-p, --project <id>", "Filter by project ID")
277
+ .option("--dry-run", "Show what would be cleaned up without doing it")
278
+ .action(async (opts) => {
279
+ const config = loadConfig();
280
+ if (opts.project && !config.projects[opts.project]) {
281
+ console.error(chalk.red(`Unknown project: ${opts.project}`));
282
+ process.exit(1);
283
+ }
284
+ console.log(chalk.bold("Checking for completed sessions...\n"));
285
+ const sm = await getSessionManager(config);
286
+ const filterCleanupIds = (ids) => ids.filter((entry) => {
287
+ const separator = entry.indexOf(":");
288
+ const entryProjectId = separator === -1 ? opts.project : entry.slice(0, separator);
289
+ const sessionId = separator === -1 ? entry : entry.slice(separator + 1);
290
+ return !isOrchestratorSessionName(config, sessionId, entryProjectId);
291
+ });
292
+ const filterCleanupErrors = (errors) => errors.filter(({ sessionId }) => {
293
+ const separator = sessionId.indexOf(":");
294
+ const entryProjectId = separator === -1 ? opts.project : sessionId.slice(0, separator);
295
+ const normalizedSessionId = separator === -1 ? sessionId : sessionId.slice(separator + 1);
296
+ return !isOrchestratorSessionName(config, normalizedSessionId, entryProjectId);
297
+ });
298
+ if (opts.dryRun) {
299
+ // Dry-run delegates to sm.cleanup() with dryRun flag so it uses the
300
+ // same live checks (PR state, runtime alive, tracker) as actual cleanup.
301
+ const rawResult = await sm.cleanup(opts.project, { dryRun: true });
302
+ const result = {
303
+ ...rawResult,
304
+ killed: filterCleanupIds(rawResult.killed),
305
+ errors: filterCleanupErrors(rawResult.errors),
306
+ };
307
+ if (result.errors.length > 0) {
308
+ for (const { sessionId, error } of result.errors) {
309
+ console.error(chalk.red(` Error checking ${sessionId}: ${error}`));
310
+ }
311
+ }
312
+ if (result.killed.length === 0 && result.errors.length === 0) {
313
+ console.log(chalk.dim(" No sessions to clean up."));
314
+ }
315
+ else {
316
+ for (const id of result.killed) {
317
+ console.log(chalk.yellow(` Would kill ${id}`));
318
+ }
319
+ if (result.killed.length > 0) {
320
+ console.log(chalk.dim(`\nDry run complete. ${result.killed.length} session${result.killed.length !== 1 ? "s" : ""} would be cleaned.`));
321
+ }
322
+ }
323
+ }
324
+ else {
325
+ const rawResult = await sm.cleanup(opts.project);
326
+ const result = {
327
+ ...rawResult,
328
+ killed: filterCleanupIds(rawResult.killed),
329
+ errors: filterCleanupErrors(rawResult.errors),
330
+ };
331
+ if (result.killed.length === 0 && result.errors.length === 0) {
332
+ console.log(chalk.dim(" No sessions to clean up."));
333
+ }
334
+ else {
335
+ if (result.killed.length > 0) {
336
+ for (const id of result.killed) {
337
+ console.log(chalk.green(` Cleaned: ${id}`));
338
+ }
339
+ }
340
+ if (result.errors.length > 0) {
341
+ for (const { sessionId, error } of result.errors) {
342
+ console.error(chalk.red(` Error cleaning ${sessionId}: ${error}`));
343
+ }
344
+ }
345
+ console.log(chalk.green(`\nCleanup complete. ${result.killed.length} sessions cleaned.`));
346
+ }
347
+ }
348
+ });
349
+ session
350
+ .command("claim-pr")
351
+ .description("Attach an existing PR to a session")
352
+ .argument("<pr>", "Pull request number or URL")
353
+ .argument("[session]", "Session name (defaults to AO_SESSION_NAME/AO_SESSION)")
354
+ .option("--assign-on-github", "Assign the PR to the authenticated GitHub user")
355
+ .action(async (prRef, sessionName, opts) => {
356
+ const config = loadConfig();
357
+ const resolvedSession = sessionName ?? process.env["AO_SESSION_NAME"] ?? process.env["AO_SESSION"];
358
+ if (!resolvedSession) {
359
+ console.error(chalk.red("No session provided. Pass a session name or run this inside a managed AO session."));
360
+ process.exit(1);
361
+ }
362
+ const sm = await getSessionManager(config);
363
+ try {
364
+ const result = await sm.claimPR(resolvedSession, prRef, {
365
+ assignOnGithub: opts.assignOnGithub,
366
+ });
367
+ console.log(chalk.green(`\nSession ${resolvedSession} claimed PR #${result.pr.number}.`));
368
+ console.log(chalk.dim(` PR: ${result.pr.url}`));
369
+ console.log(chalk.dim(` Branch: ${result.pr.branch}`));
370
+ console.log(chalk.dim(` Checkout: ${result.branchChanged ? "switched to PR branch" : "already on PR branch"}`));
371
+ if (result.takenOverFrom.length > 0) {
372
+ console.log(chalk.dim(` Took over from: ${result.takenOverFrom.join(", ")}`));
373
+ }
374
+ if (opts.assignOnGithub) {
375
+ if (result.githubAssigned) {
376
+ console.log(chalk.dim(" GitHub assignee: updated"));
377
+ }
378
+ else if (result.githubAssignmentError) {
379
+ console.log(chalk.yellow(` GitHub assignee: ${result.githubAssignmentError}`));
380
+ }
381
+ }
382
+ }
383
+ catch (err) {
384
+ console.error(chalk.red(`Failed to claim PR for session ${resolvedSession}: ${err}`));
385
+ process.exit(1);
386
+ }
387
+ });
388
+ session
389
+ .command("restore")
390
+ .description("Restore a terminated/crashed session in-place")
391
+ .argument("<session>", "Session name to restore")
392
+ .action(async (sessionName) => {
393
+ const config = loadConfig();
394
+ const sm = await getSessionManager(config);
395
+ try {
396
+ const restored = await sm.restore(sessionName);
397
+ console.log(chalk.green(`\nSession ${sessionName} restored.`));
398
+ if (restored.workspacePath) {
399
+ console.log(chalk.dim(` Worktree: ${restored.workspacePath}`));
400
+ }
401
+ if (restored.branch) {
402
+ console.log(chalk.dim(` Branch: ${restored.branch}`));
403
+ }
404
+ const port = config.port ?? DEFAULT_PORT;
405
+ console.log(chalk.dim(` View: ${projectSessionUrl(port, restored.projectId, sessionName)}`));
406
+ }
407
+ catch (err) {
408
+ if (err instanceof SessionNotRestorableError) {
409
+ console.error(chalk.red(`Cannot restore: ${err.reason}`));
410
+ }
411
+ else if (err instanceof WorkspaceMissingError) {
412
+ console.error(chalk.red(`Workspace missing: ${err.message}`));
413
+ }
414
+ else {
415
+ console.error(chalk.red(`Failed to restore session ${sessionName}: ${err}`));
416
+ }
417
+ process.exit(1);
418
+ }
419
+ });
420
+ session
421
+ .command("remap")
422
+ .description("Re-discover and persist OpenCode session mapping for an AO session")
423
+ .argument("<session>", "Session name to remap")
424
+ .option("-f, --force", "Force fresh remap by re-discovering the OpenCode session")
425
+ .action(async (sessionName, opts) => {
426
+ const config = loadConfig();
427
+ const sm = await getSessionManager(config);
428
+ try {
429
+ const mapped = await sm.remap(sessionName, opts.force === true);
430
+ console.log(chalk.green(`\nSession ${sessionName} remapped.`));
431
+ console.log(chalk.dim(` OpenCode session: ${mapped}`));
432
+ }
433
+ catch (err) {
434
+ console.error(chalk.red(`Failed to remap session ${sessionName}: ${err}`));
435
+ process.exit(1);
436
+ }
437
+ });
438
+ }
439
+ //# sourceMappingURL=session.js.map