@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,652 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import chalk from "chalk";
4
+ import { getGlobalConfigPath, isCanonicalGlobalConfigPath, isWindows, loadConfig, loadGlobalConfig, recordActivityEvent, } from "@made-by-moonlight/athene-core";
5
+ import { runRepoScript } from "../lib/script-runner.js";
6
+ import { checkForUpdate, detectInstallMethod, getCurrentVersion, getUpdateCommand, invalidateCache, readCachedUpdateInfo, resolveUpdateChannel, } from "../lib/update-check.js";
7
+ import { promptConfirm } from "../lib/prompts.js";
8
+ import { getSessionManager } from "../lib/create-session-manager.js";
9
+ import { getRunning } from "../lib/running-state.js";
10
+ /** Inline check instead of module-level constant so tests can control TTY state. */
11
+ function isTTY() {
12
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
13
+ }
14
+ /**
15
+ * The dashboard's POST /api/update spawns `athene update` with `stdio: "ignore"`,
16
+ * which makes `isTTY()` return false. That used to be fatal: handleNpmUpdate
17
+ * fell into the "non-interactive — print and return" branch and never invoked
18
+ * the install. The route would respond 202 "started" and absolutely nothing
19
+ * would happen.
20
+ *
21
+ * /api/update sets `AO_NON_INTERACTIVE_INSTALL=1` on the spawn env so we can
22
+ * distinguish "API kicked this off, please install without prompting" from
23
+ * "user piped output and we shouldn't surprise-install."
24
+ */
25
+ export const NON_INTERACTIVE_INSTALL_ENV = "AO_NON_INTERACTIVE_INSTALL";
26
+ function isApiInvoked() {
27
+ return process.env[NON_INTERACTIVE_INSTALL_ENV] === "1";
28
+ }
29
+ /**
30
+ * Statuses that mean "the agent is doing real work right now and updating
31
+ * `ao` would yank the rug out from under it."
32
+ *
33
+ * Mirrors the design doc (release-process.html §07): refuse, never auto-stop.
34
+ */
35
+ const ACTIVE_SESSION_STATUSES = new Set([
36
+ "working",
37
+ "idle",
38
+ "needs_input",
39
+ "stuck",
40
+ ]);
41
+ export function registerUpdate(program) {
42
+ program
43
+ .command("update")
44
+ .description("Check for updates and upgrade AO to the latest version")
45
+ .option("--skip-smoke", "Skip smoke tests after rebuilding (git installs only)")
46
+ .option("--smoke-only", "Run smoke tests without fetching or rebuilding (git installs only)")
47
+ .option("--check", "Print version info as JSON without upgrading")
48
+ .option("--no-restore", "Restart AO after updating but do not restore stopped sessions")
49
+ .action(async (opts) => {
50
+ if (opts.skipSmoke && opts.smokeOnly) {
51
+ console.error("`athene update` does not allow `--skip-smoke` together with `--smoke-only`.");
52
+ process.exit(1);
53
+ }
54
+ if (opts.check) {
55
+ await handleCheck();
56
+ return;
57
+ }
58
+ const method = detectInstallMethod();
59
+ recordActivityEvent({
60
+ source: "cli",
61
+ kind: "cli.update_invoked",
62
+ level: "info",
63
+ summary: `athene update invoked (method: ${method})`,
64
+ data: { method, options: opts },
65
+ });
66
+ // Reject git-only flags up front when the install isn't a git source.
67
+ // Without this, users copy/pasting `athene update --skip-smoke` from older
68
+ // docs would silently no-op on npm/pnpm/bun installs (the flag would be
69
+ // accepted, ignored, and the user would never know why smoke tests
70
+ // didn't run — because they never ran on these install methods anyway).
71
+ if ((opts.skipSmoke || opts.smokeOnly) && method !== "git") {
72
+ const flag = opts.skipSmoke ? "--skip-smoke" : "--smoke-only";
73
+ console.error(`${flag} only applies to git installs (current install: ${method}).`);
74
+ process.exit(1);
75
+ }
76
+ switch (method) {
77
+ case "git":
78
+ await handleGitUpdate(opts);
79
+ break;
80
+ case "homebrew":
81
+ await handleHomebrewUpdate();
82
+ break;
83
+ case "npm-global":
84
+ case "pnpm-global":
85
+ case "bun-global":
86
+ await handleNpmUpdate(method, { restore: opts.restore !== false });
87
+ break;
88
+ case "unknown":
89
+ await handleUnknownUpdate();
90
+ break;
91
+ }
92
+ });
93
+ }
94
+ // ---------------------------------------------------------------------------
95
+ // --check
96
+ // ---------------------------------------------------------------------------
97
+ async function handleCheck() {
98
+ const info = await checkForUpdate({ force: true });
99
+ console.log(JSON.stringify(info, null, 2));
100
+ }
101
+ async function getUpdateLifecyclePlan() {
102
+ let sessions;
103
+ let configPath;
104
+ let primaryProjectId;
105
+ let runningBeforeUpdate = false;
106
+ try {
107
+ // Live signal first: running.json lists whichever projects the active
108
+ // `athene start` daemon is currently polling. That can include local-only
109
+ // projects whose `agent-orchestrator.yaml` is NOT in the global registry
110
+ // (Dhruv edge case: user runs `athene start` from a repo with a local config
111
+ // and no global registration — sessions live on disk, would be clobbered
112
+ // if `athene update` proceeded).
113
+ //
114
+ // If a daemon is running, trust its configPath — it's the source of
115
+ // truth for "which sessions does the running ao instance own?"
116
+ const running = await getRunning();
117
+ if (running && running.projects.length > 0) {
118
+ runningBeforeUpdate = true;
119
+ configPath = running.configPath;
120
+ primaryProjectId = running.projects[0];
121
+ // running.configPath could be local-wrapped (a project's
122
+ // agent-orchestrator.yaml) OR the canonical global path. loadConfig
123
+ // dispatches based on the path shape — both cases produce a full
124
+ // OrchestratorConfig the SessionManager can enumerate.
125
+ const config = loadConfig(running.configPath);
126
+ const sm = await getSessionManager(config);
127
+ sessions = await sm.list();
128
+ }
129
+ else {
130
+ // No live daemon. Fall back to the global registry — covers the case
131
+ // where the user ran `athene stop` (running.json gone) but stale sessions
132
+ // sit on disk under ~/.agent-orchestrator/{hash}-{projectId}/. The
133
+ // SessionManager's enrichment will reconcile any stale-runtime
134
+ // sessions to `killed`, so terminal statuses don't block the update.
135
+ const globalPath = getGlobalConfigPath();
136
+ if (!existsSync(globalPath)) {
137
+ return { runningBeforeUpdate, configPath, primaryProjectId, activeSessions: [] };
138
+ }
139
+ const globalConfig = loadGlobalConfig(globalPath);
140
+ if (!globalConfig || Object.keys(globalConfig.projects).length === 0) {
141
+ return { runningBeforeUpdate, configPath, primaryProjectId, activeSessions: [] };
142
+ }
143
+ if (!isCanonicalGlobalConfigPath(globalPath)) {
144
+ return { runningBeforeUpdate, configPath, primaryProjectId, activeSessions: [] };
145
+ }
146
+ configPath = globalPath;
147
+ const config = loadConfig(globalPath);
148
+ primaryProjectId = Object.keys(config.projects)[0];
149
+ const sm = await getSessionManager(config);
150
+ sessions = await sm.list();
151
+ }
152
+ }
153
+ catch {
154
+ // If we can't enumerate sessions, don't pretend there are zero — but
155
+ // also don't block the upgrade indefinitely. Surface a soft warning.
156
+ console.error(chalk.yellow("⚠ Could not check for active sessions before updating. Proceeding anyway."));
157
+ return { runningBeforeUpdate, configPath, primaryProjectId, activeSessions: [] };
158
+ }
159
+ const active = sessions.filter((s) => ACTIVE_SESSION_STATUSES.has(s.status));
160
+ return { runningBeforeUpdate, configPath, primaryProjectId, activeSessions: active };
161
+ }
162
+ async function pauseAoForUpdate(plan) {
163
+ const shouldStop = plan.runningBeforeUpdate || plan.activeSessions.length > 0;
164
+ if (!shouldStop)
165
+ return false;
166
+ if (plan.activeSessions.length > 0) {
167
+ const noun = plan.activeSessions.length === 1 ? "session" : "sessions";
168
+ console.log(chalk.yellow(`\n${plan.activeSessions.length} active ${noun} will be paused and restored after the update.`));
169
+ for (const s of plan.activeSessions.slice(0, 5)) {
170
+ console.log(chalk.dim(` • ${s.id} (${s.status})`));
171
+ }
172
+ if (plan.activeSessions.length > 5) {
173
+ console.log(chalk.dim(` … and ${plan.activeSessions.length - 5} more`));
174
+ }
175
+ }
176
+ else {
177
+ console.log(chalk.dim("\nAO is running; it will be restarted after the update."));
178
+ }
179
+ const stopExit = await runAoLifecycleCommand(["stop", "--yes"], {
180
+ configPath: plan.configPath,
181
+ });
182
+ if (stopExit !== 0) {
183
+ recordActivityEvent({
184
+ source: "cli",
185
+ kind: "cli.update_failed",
186
+ level: "error",
187
+ summary: `athene update failed: internal athene stop exited non-zero`,
188
+ data: { exitCode: stopExit },
189
+ });
190
+ console.error(chalk.red(`\nAO update could not stop the running daemon (exit ${stopExit}).`));
191
+ process.exit(stopExit);
192
+ }
193
+ const afterStop = await getUpdateLifecyclePlan();
194
+ if (afterStop.runningBeforeUpdate || afterStop.activeSessions.length > 0) {
195
+ recordActivityEvent({
196
+ source: "cli",
197
+ kind: "cli.update_failed",
198
+ level: "error",
199
+ summary: `athene update failed: AO still appears active after internal athene stop`,
200
+ data: {
201
+ runningAfterStop: afterStop.runningBeforeUpdate,
202
+ activeSessionCount: afterStop.activeSessions.length,
203
+ activeSessionIds: afterStop.activeSessions.map((s) => s.id).slice(0, 20),
204
+ },
205
+ });
206
+ console.error(chalk.red("\nAO update stopped before installing because AO still appears to be running after `athene stop --yes`."));
207
+ if (afterStop.activeSessions.length > 0) {
208
+ console.error(chalk.dim("Still-active sessions:"));
209
+ for (const s of afterStop.activeSessions.slice(0, 5)) {
210
+ console.error(chalk.dim(` • ${s.id} (${s.status})`));
211
+ }
212
+ if (afterStop.activeSessions.length > 5) {
213
+ console.error(chalk.dim(` … and ${afterStop.activeSessions.length - 5} more`));
214
+ }
215
+ }
216
+ console.error(chalk.dim("Run `athene stop` and retry `athene update` after AO is fully stopped."));
217
+ process.exit(1);
218
+ }
219
+ return plan.runningBeforeUpdate;
220
+ }
221
+ async function restartAoAfterUpdate(plan, opts) {
222
+ const args = ["start"];
223
+ if (plan.primaryProjectId)
224
+ args.push(plan.primaryProjectId);
225
+ args.push(opts.restore ? "--restore" : "--no-restore");
226
+ console.log(chalk.dim(`\nRestarting AO: ao ${args.join(" ")}`));
227
+ const exitCode = await runAoLifecycleCommand(args, { configPath: plan.configPath });
228
+ if (exitCode !== 0) {
229
+ recordActivityEvent({
230
+ source: "cli",
231
+ kind: "cli.update_restart_failed",
232
+ level: "error",
233
+ summary: `athene update could not restart AO after install`,
234
+ data: { exitCode, args },
235
+ });
236
+ console.error(chalk.yellow(`\nAO was updated, but \`ao ${args.join(" ")}\` failed with exit ${exitCode}. ` +
237
+ `Run it manually to restore your sessions.`));
238
+ process.exit(exitCode);
239
+ }
240
+ }
241
+ function runAoLifecycleCommand(args, opts = {}) {
242
+ return new Promise((resolveExit) => {
243
+ const child = spawn("athene", args, {
244
+ stdio: "inherit",
245
+ shell: isWindows(),
246
+ windowsHide: true,
247
+ env: opts.configPath ? { ...process.env, AO_CONFIG_PATH: opts.configPath } : process.env,
248
+ });
249
+ child.on("error", (error) => {
250
+ console.error(chalk.yellow(`Could not run ao ${args.join(" ")}: ${error.message}`));
251
+ resolveExit(1);
252
+ });
253
+ child.on("exit", (code, signal) => {
254
+ resolveExit(signal ? 1 : (code ?? 1));
255
+ });
256
+ });
257
+ }
258
+ // ---------------------------------------------------------------------------
259
+ // git install
260
+ // ---------------------------------------------------------------------------
261
+ async function handleGitUpdate(opts) {
262
+ const lifecyclePlan = await getUpdateLifecyclePlan();
263
+ const shouldRestart = await pauseAoForUpdate(lifecyclePlan);
264
+ const args = [];
265
+ if (opts.skipSmoke)
266
+ args.push("--skip-smoke");
267
+ if (opts.smokeOnly)
268
+ args.push("--smoke-only");
269
+ try {
270
+ const exitCode = await runRepoScript("athene-update.sh", args);
271
+ if (exitCode !== 0) {
272
+ recordActivityEvent({
273
+ source: "cli",
274
+ kind: "cli.update_failed",
275
+ level: "error",
276
+ summary: `athene update (git) failed: athene-update.sh exited non-zero`,
277
+ data: { method: "git", exitCode },
278
+ });
279
+ if (shouldRestart) {
280
+ await restartAoAfterUpdate(lifecyclePlan, { restore: opts.restore !== false });
281
+ }
282
+ process.exit(exitCode);
283
+ }
284
+ invalidateCache();
285
+ if (shouldRestart) {
286
+ await restartAoAfterUpdate(lifecyclePlan, { restore: opts.restore !== false });
287
+ }
288
+ }
289
+ catch (error) {
290
+ if (error instanceof Error && error.message.includes("Script not found: athene-update.sh")) {
291
+ recordActivityEvent({
292
+ source: "cli",
293
+ kind: "cli.update_failed",
294
+ level: "error",
295
+ summary: `athene update (git) failed: athene-update.sh missing from bundled assets`,
296
+ data: { method: "git", reason: "script_missing" },
297
+ });
298
+ console.error(chalk.red("athene-update.sh is missing from the bundled assets. " +
299
+ "If you're running from a source checkout, rebuild with `pnpm --filter @made-by-moonlight/athene-cli build`. " +
300
+ "If you're on a package install, reinstall the package."));
301
+ if (shouldRestart) {
302
+ await restartAoAfterUpdate(lifecyclePlan, { restore: opts.restore !== false });
303
+ }
304
+ process.exit(1);
305
+ }
306
+ recordActivityEvent({
307
+ source: "cli",
308
+ kind: "cli.update_failed",
309
+ level: "error",
310
+ summary: `athene update (git) failed`,
311
+ data: {
312
+ method: "git",
313
+ errorMessage: error instanceof Error ? error.message : String(error),
314
+ },
315
+ });
316
+ console.error(error instanceof Error ? error.message : String(error));
317
+ if (shouldRestart) {
318
+ await restartAoAfterUpdate(lifecyclePlan, { restore: opts.restore !== false });
319
+ }
320
+ process.exit(1);
321
+ }
322
+ }
323
+ // ---------------------------------------------------------------------------
324
+ // npm / pnpm / bun global install
325
+ // ---------------------------------------------------------------------------
326
+ async function handleNpmUpdate(method, opts) {
327
+ const channel = resolveUpdateChannel();
328
+ // Snapshot the previously cached channel BEFORE we force a refresh, so we
329
+ // can detect a channel switch (stable→nightly or vice versa). force:true
330
+ // would overwrite cache.channel before we can read it.
331
+ const previousChannel = readCachedUpdateInfo(method)?.channel;
332
+ let info;
333
+ try {
334
+ info = await checkForUpdate({ force: true, channel });
335
+ }
336
+ catch (error) {
337
+ recordActivityEvent({
338
+ source: "cli",
339
+ kind: "cli.update_failed",
340
+ level: "error",
341
+ summary: `athene update (${method}) failed: npm registry lookup threw`,
342
+ data: {
343
+ method,
344
+ channel,
345
+ reason: "registry_lookup_threw",
346
+ errorMessage: error instanceof Error ? error.message : String(error),
347
+ },
348
+ });
349
+ console.error(chalk.red("Could not reach npm registry. Check your network and try again."));
350
+ process.exit(1);
351
+ }
352
+ if (!info.latestVersion) {
353
+ recordActivityEvent({
354
+ source: "cli",
355
+ kind: "cli.update_failed",
356
+ level: "error",
357
+ summary: `athene update (${method}) failed: npm registry lookup returned no version`,
358
+ data: { method, channel, reason: "registry_unreachable" },
359
+ });
360
+ console.error(chalk.red("Could not reach npm registry. Check your network and try again."));
361
+ process.exit(1);
362
+ }
363
+ // Detect a channel switch. When stable=0.5.0 and nightly=0.5.0-nightly-abc,
364
+ // isVersionOutdated returns false (per semver, prerelease < stable on equal
365
+ // base), so a stable→nightly user would see "Already on latest nightly"
366
+ // until the next numeric bump. Force the prompt instead — explicit consent
367
+ // is the right UX for a channel transition, and the install command we'd
368
+ // run is genuinely different even if the version-compare says "no".
369
+ const isChannelSwitch = !info.isOutdated && previousChannel !== undefined && previousChannel !== channel;
370
+ // First-channel opt-in. previousChannel === undefined means we've never
371
+ // installed via the auto-updater. A user who just ran `athene config set
372
+ // updateChannel nightly` (after a manual install) would otherwise see
373
+ // "Already on latest nightly" because semver says prerelease < stable.
374
+ // Treat any version mismatch as install-worthy in that case.
375
+ const isFirstChannelOptIn = !info.isOutdated &&
376
+ !isChannelSwitch &&
377
+ previousChannel === undefined &&
378
+ info.currentVersion !== info.latestVersion;
379
+ const needsInstall = info.isOutdated || isChannelSwitch || isFirstChannelOptIn;
380
+ if (!needsInstall) {
381
+ console.log(chalk.green(`Already on latest ${channel === "nightly" ? "nightly" : "version"} (${info.currentVersion}).`));
382
+ return;
383
+ }
384
+ console.log(`Current version: ${chalk.dim(info.currentVersion)}`);
385
+ console.log(`Latest version: ${chalk.green(info.latestVersion)}`);
386
+ console.log(`Channel: ${chalk.cyan(channel)}`);
387
+ if (isChannelSwitch) {
388
+ console.log(chalk.yellow(`\nChannel switch detected: was on ${previousChannel}, now ${channel}.`));
389
+ console.log(chalk.dim(" The version compare says you're current, but the install command picks a different dist-tag."));
390
+ }
391
+ else if (isFirstChannelOptIn) {
392
+ console.log(chalk.yellow(`\nFirst install via the ${channel} channel — installing the channel's current build.`));
393
+ }
394
+ console.log();
395
+ const command = getUpdateCommand(method, channel);
396
+ const apiInvoked = isApiInvoked();
397
+ const interactive = isTTY() && !apiInvoked;
398
+ const lifecyclePlan = await getUpdateLifecyclePlan();
399
+ // Non-interactive path: API-invoked OR piped output. We still plan the
400
+ // stop/start lifecycle, but we never bail out just because there's no
401
+ // terminal — the dashboard's "Update" click must actually install. The
402
+ // only thing we skip is the confirm prompt.
403
+ if (interactive) {
404
+ // Soft auto-install: when the user has opted into stable or nightly we
405
+ // skip the confirm prompt — they've already said "keep me on this channel."
406
+ // Manual users (and explicit channel switches / first opt-ins) still see
407
+ // the confirm so an unintended `athene update` doesn't wipe the version they
408
+ // pinned to.
409
+ if (channel === "manual" || isChannelSwitch || isFirstChannelOptIn) {
410
+ const promptText = isChannelSwitch || isFirstChannelOptIn
411
+ ? `Switch to ${channel} via ${chalk.cyan(command)}?`
412
+ : `Run ${chalk.cyan(command)}?`;
413
+ const confirmed = await promptConfirm(promptText, !(isChannelSwitch || isFirstChannelOptIn));
414
+ if (!confirmed)
415
+ return;
416
+ }
417
+ else {
418
+ console.log(chalk.dim(`Updating: ${command}`));
419
+ }
420
+ }
421
+ else if (apiInvoked) {
422
+ console.log(chalk.dim(`Updating (api-invoked): ${command}`));
423
+ }
424
+ else {
425
+ // Non-TTY but also not API-invoked (piped output). Keep the old
426
+ // "print the command and let the user run it" behavior so a script
427
+ // running `athene update | tee` doesn't get a surprise install.
428
+ console.log(`Run: ${chalk.cyan(command)}`);
429
+ return;
430
+ }
431
+ const shouldRestart = await pauseAoForUpdate(lifecyclePlan);
432
+ const installResult = await runNpmInstall(command);
433
+ if (installResult.exitCode !== 0) {
434
+ recordActivityEvent({
435
+ source: "cli",
436
+ kind: "cli.update_failed",
437
+ level: "error",
438
+ summary: `athene update (${method}) failed: install command exited non-zero`,
439
+ data: {
440
+ method,
441
+ command,
442
+ exitCode: installResult.exitCode,
443
+ classification: classifyInstallFailure(installResult.output).kind,
444
+ },
445
+ });
446
+ printInstallFailure({
447
+ method,
448
+ command,
449
+ channel,
450
+ currentVersion: info.currentVersion,
451
+ exitCode: installResult.exitCode,
452
+ output: installResult.output,
453
+ });
454
+ if (shouldRestart) {
455
+ console.log(chalk.dim("\nRestarting AO with the existing installation..."));
456
+ await restartAoAfterUpdate(lifecyclePlan, opts);
457
+ }
458
+ process.exit(1);
459
+ }
460
+ const verification = await verifyInstalledVersion(info.latestVersion, info.currentVersion);
461
+ if (!verification.ok) {
462
+ recordActivityEvent({
463
+ source: "cli",
464
+ kind: "cli.update_failed",
465
+ level: "error",
466
+ summary: `athene update (${method}) failed: installed version verification failed`,
467
+ data: {
468
+ method,
469
+ command,
470
+ expectedVersion: info.latestVersion,
471
+ actualVersion: verification.actualVersion,
472
+ output: verification.output,
473
+ },
474
+ });
475
+ console.error(chalk.red(`\nAO was not verified after install.`));
476
+ console.error(chalk.yellow(verification.message));
477
+ console.error(chalk.dim(`Expected: ${info.latestVersion}`));
478
+ console.error(chalk.dim(`Current before update: ${info.currentVersion}`));
479
+ if (shouldRestart) {
480
+ console.log(chalk.dim("\nRestarting AO before exiting..."));
481
+ await restartAoAfterUpdate(lifecyclePlan, opts);
482
+ }
483
+ process.exit(1);
484
+ }
485
+ invalidateCache();
486
+ if (shouldRestart) {
487
+ await restartAoAfterUpdate(lifecyclePlan, opts);
488
+ }
489
+ console.log(chalk.green(`\nUpdate complete: ${info.currentVersion} → ${verification.actualVersion}.` +
490
+ (shouldRestart ? " AO restarted." : "")));
491
+ }
492
+ function runNpmInstall(command) {
493
+ const [cmd, ...args] = command.split(" ");
494
+ return runCommandCapture(cmd, args, { echo: true }).then((result) => {
495
+ if (result.exitCode !== 0) {
496
+ console.error(chalk.yellow(`\n${cmd} exited with code ${result.exitCode}.`));
497
+ }
498
+ return result;
499
+ });
500
+ }
501
+ function runCommandCapture(cmd, args, opts = {}) {
502
+ return new Promise((resolveExit) => {
503
+ // `shell: isWindows()` is required so PATHEXT gets consulted on Windows —
504
+ // npm/pnpm/bun install as `*.cmd` shims, and Node.js does not look at
505
+ // PATHEXT for non-shell spawns, so a bare `npm` / `pnpm` / `bun` lookup
506
+ // would silently ENOENT on every Windows install. `windowsHide: true`
507
+ // keeps the shell window from flashing. Same fix that landed for the
508
+ // dashboard's /api/update spawn in commit 9f29131d.
509
+ const child = spawn(cmd, args, {
510
+ stdio: ["inherit", "pipe", "pipe"],
511
+ shell: isWindows(),
512
+ windowsHide: true,
513
+ });
514
+ let output = "";
515
+ const collect = (chunk, stream) => {
516
+ const text = chunk.toString();
517
+ output += text;
518
+ if (opts.echo)
519
+ stream.write(chunk);
520
+ };
521
+ child.stdout?.on("data", (chunk) => collect(chunk, process.stdout));
522
+ child.stderr?.on("data", (chunk) => collect(chunk, process.stderr));
523
+ child.on("error", (error) => {
524
+ output += `${error.name}: ${error.message}`;
525
+ resolveExit({ exitCode: 1, output });
526
+ });
527
+ child.on("exit", (code, signal) => {
528
+ if (signal) {
529
+ resolveExit({ exitCode: 1, output: `${output}\nTerminated by signal ${signal}` });
530
+ return;
531
+ }
532
+ resolveExit({ exitCode: code ?? 1, output });
533
+ });
534
+ });
535
+ }
536
+ async function verifyInstalledVersion(expectedVersion, previousVersion) {
537
+ const result = await runCommandCapture("athene", ["--version"]);
538
+ const output = result.output.trim();
539
+ const actualVersion = parseAoVersion(output);
540
+ if (result.exitCode !== 0) {
541
+ return {
542
+ ok: false,
543
+ output,
544
+ message: `\`athene --version\` failed with exit ${result.exitCode}.`,
545
+ };
546
+ }
547
+ if (!actualVersion) {
548
+ return {
549
+ ok: false,
550
+ output,
551
+ message: `Could not parse \`athene --version\` output: ${output || "<empty>"}`,
552
+ };
553
+ }
554
+ if (actualVersion !== expectedVersion) {
555
+ return {
556
+ ok: false,
557
+ actualVersion,
558
+ output,
559
+ message: actualVersion === previousVersion
560
+ ? `The install command exited successfully, but AO is still on ${previousVersion}.`
561
+ : `The install command exited successfully, but AO reports ${actualVersion}.`,
562
+ };
563
+ }
564
+ return { ok: true, actualVersion, output, message: "verified" };
565
+ }
566
+ function parseAoVersion(output) {
567
+ const match = output.match(/\b(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\b/);
568
+ return match?.[1];
569
+ }
570
+ function classifyInstallFailure(output) {
571
+ if (/ERR_PNPM_UNEXPECTED_VIRTUAL_STORE/i.test(output)) {
572
+ return {
573
+ kind: "pnpm_virtual_store",
574
+ guidance: "pnpm's global store metadata is inconsistent. Try `pnpm store prune`, then retry `athene update`. " +
575
+ "If pnpm remains stuck, use the npm fallback below.",
576
+ };
577
+ }
578
+ if (/(?:EACCES|EPERM|permission denied|access denied)/i.test(output)) {
579
+ return {
580
+ kind: "permission",
581
+ guidance: "The package manager could not write to the global install location. Fix your npm/pnpm global prefix permissions, or retry from a shell with access to that directory.",
582
+ };
583
+ }
584
+ if (/(?:ENETUNREACH|ECONNRESET|ETIMEDOUT|EAI_AGAIN|network|socket hang up)/i.test(output)) {
585
+ return {
586
+ kind: "network",
587
+ guidance: "The registry request failed due to a network error. Check connectivity/VPN/proxy settings and retry `athene update`.",
588
+ };
589
+ }
590
+ if (/(?:lockfile|ERR_PNPM_LOCKFILE|ERR_PNPM_OUTDATED_LOCKFILE|ERR_PNPM_BROKEN_LOCKFILE)/i.test(output)) {
591
+ return {
592
+ kind: "lockfile",
593
+ guidance: "pnpm reported lockfile state problems. Clear the affected global install metadata or retry with the npm fallback below.",
594
+ };
595
+ }
596
+ if (/(?:registry|ERR_PNPM_FETCH|ERR_PNPM_META_FETCH_FAIL|E401|E403|E404|404 Not Found|401 Unauthorized|403 Forbidden)/i.test(output)) {
597
+ return {
598
+ kind: "registry",
599
+ guidance: "The npm registry rejected or failed the package request. Check registry configuration, auth tokens, and the selected AO update channel.",
600
+ };
601
+ }
602
+ return {
603
+ kind: "unknown",
604
+ guidance: "The package manager failed before AO could verify the new version. Retry `athene update` after addressing the package-manager error below.",
605
+ };
606
+ }
607
+ function printInstallFailure(opts) {
608
+ const classification = classifyInstallFailure(opts.output);
609
+ const fallbackCommand = getUpdateCommand("npm-global", opts.channel);
610
+ console.error(chalk.red(`\nAO was not updated. You are still on version ${opts.currentVersion}.`));
611
+ console.error(chalk.yellow(`The package manager (${opts.method.replace("-global", "")}) failed with exit ${opts.exitCode}.`));
612
+ console.error(chalk.yellow(classification.guidance));
613
+ console.error(chalk.dim(`\nTo retry: athene update`));
614
+ if (opts.command !== fallbackCommand) {
615
+ console.error(chalk.dim(`You can also try: ${fallbackCommand}`));
616
+ }
617
+ console.error(chalk.dim("\nPackage manager output:"));
618
+ console.error(opts.output.trim() || "<no output>");
619
+ }
620
+ // ---------------------------------------------------------------------------
621
+ // homebrew install (notice only)
622
+ // ---------------------------------------------------------------------------
623
+ async function handleHomebrewUpdate() {
624
+ const channel = resolveUpdateChannel();
625
+ const info = await checkForUpdate({ force: true, channel });
626
+ console.log(`Installed via: ${chalk.yellow("Homebrew")}`);
627
+ console.log(`Current version: ${chalk.dim(info.currentVersion)}`);
628
+ if (info.latestVersion) {
629
+ console.log(`Latest version: ${chalk.green(info.latestVersion)}`);
630
+ }
631
+ console.log();
632
+ console.log(`Homebrew installs are managed by brew. Run:\n ${chalk.cyan("brew upgrade ao")}`);
633
+ console.log(chalk.dim(" (AO does not auto-install for brew installs because it would clobber brew's symlinks.)"));
634
+ }
635
+ // ---------------------------------------------------------------------------
636
+ // unknown install
637
+ // ---------------------------------------------------------------------------
638
+ async function handleUnknownUpdate() {
639
+ const version = getCurrentVersion();
640
+ const channel = resolveUpdateChannel();
641
+ const info = await checkForUpdate({ force: true, channel });
642
+ console.log(`Installed version: ${chalk.dim(version)}`);
643
+ if (info.latestVersion) {
644
+ console.log(`Latest version: ${chalk.green(info.latestVersion)}`);
645
+ }
646
+ console.log(`Install method: ${chalk.yellow("unknown")}`);
647
+ console.log(`Channel: ${chalk.cyan(channel)}`);
648
+ console.log();
649
+ console.log(`Could not detect install method. If you installed via npm, run:\n ${chalk.cyan(getUpdateCommand("npm-global", channel))}`);
650
+ console.log(chalk.dim(` Override detection in ~/.agent-orchestrator/config.yaml:\n installMethod: pnpm-global # or bun-global, npm-global, homebrew, git`));
651
+ }
652
+ //# sourceMappingURL=update.js.map