@poolzin/pool-bot 2026.2.21 → 2026.2.22

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 (369) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/agents/api-key-rotation.js +47 -0
  3. package/dist/agents/apply-patch-update.js +19 -9
  4. package/dist/agents/apply-patch.js +72 -47
  5. package/dist/agents/bash-tools.exec.js +141 -559
  6. package/dist/agents/cli-backends.js +49 -6
  7. package/dist/agents/cli-runner/helpers.js +69 -152
  8. package/dist/agents/cli-runner.js +70 -19
  9. package/dist/agents/identity.js +20 -1
  10. package/dist/agents/image-sanitization.js +9 -0
  11. package/dist/agents/live-auth-keys.js +123 -26
  12. package/dist/agents/live-model-filter.js +13 -4
  13. package/dist/agents/model-catalog.js +40 -9
  14. package/dist/agents/model-forward-compat.js +60 -23
  15. package/dist/agents/model-selection.js +134 -41
  16. package/dist/agents/pi-auth-json.js +2 -2
  17. package/dist/agents/pi-embedded-helpers/bootstrap.js +65 -15
  18. package/dist/agents/pi-embedded-helpers/errors.js +140 -15
  19. package/dist/agents/pi-embedded-helpers/images.js +22 -12
  20. package/dist/agents/pi-embedded-helpers.js +2 -2
  21. package/dist/agents/pi-embedded-runner/abort.js +10 -3
  22. package/dist/agents/pi-embedded-runner/compact.js +230 -32
  23. package/dist/agents/pi-embedded-runner/extra-params.js +203 -12
  24. package/dist/agents/pi-embedded-runner/google.js +109 -19
  25. package/dist/agents/pi-embedded-runner/history.js +35 -17
  26. package/dist/agents/pi-embedded-runner/run/attempt.js +386 -95
  27. package/dist/agents/pi-embedded-runner/run/images.js +81 -55
  28. package/dist/agents/pi-embedded-runner/run/payloads.js +89 -39
  29. package/dist/agents/pi-embedded-runner/run.js +193 -25
  30. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +2 -2
  31. package/dist/agents/pi-embedded-runner/runs.js +17 -8
  32. package/dist/agents/pi-embedded-runner/tool-result-context-guard.js +262 -0
  33. package/dist/agents/pi-embedded-runner.js +1 -1
  34. package/dist/agents/pi-embedded-subscribe.handlers.tools.js +180 -10
  35. package/dist/agents/pi-embedded-subscribe.js +37 -0
  36. package/dist/agents/pi-embedded-subscribe.tools.js +127 -30
  37. package/dist/agents/pi-model-discovery.js +9 -2
  38. package/dist/agents/pi-tool-definition-adapter.js +60 -8
  39. package/dist/agents/pi-tools.before-tool-call.js +1 -1
  40. package/dist/agents/pi-tools.js +113 -94
  41. package/dist/agents/pi-tools.read.js +337 -38
  42. package/dist/agents/poolbot-tools.js +14 -5
  43. package/dist/agents/sandbox/docker.js +10 -5
  44. package/dist/agents/sandbox/registry.js +96 -46
  45. package/dist/agents/sandbox/sanitize-env-vars.js +82 -0
  46. package/dist/agents/sandbox-paths.js +43 -10
  47. package/dist/agents/session-tool-result-guard-wrapper.js +23 -11
  48. package/dist/agents/session-tool-result-guard.js +39 -39
  49. package/dist/agents/session-transcript-repair.js +36 -33
  50. package/dist/agents/session-write-lock.js +62 -44
  51. package/dist/agents/skills/frontmatter.js +49 -88
  52. package/dist/agents/skills/workspace.js +335 -28
  53. package/dist/agents/subagent-announce.js +508 -174
  54. package/dist/agents/subagent-registry.js +45 -4
  55. package/dist/agents/subagent-spawn.js +16 -33
  56. package/dist/agents/system-prompt-report.js +27 -10
  57. package/dist/agents/system-prompt.js +26 -32
  58. package/dist/agents/tool-call-id.js +69 -17
  59. package/dist/agents/tool-display-common.js +1 -1
  60. package/dist/agents/tool-images.js +64 -31
  61. package/dist/agents/tools/canvas-tool.js +17 -11
  62. package/dist/agents/tools/common.js +37 -19
  63. package/dist/agents/tools/cron-tool.js +40 -38
  64. package/dist/agents/tools/gateway.js +70 -2
  65. package/dist/agents/tools/message-tool.js +181 -40
  66. package/dist/agents/tools/nodes-tool.js +128 -36
  67. package/dist/agents/tools/nodes-utils.js +12 -38
  68. package/dist/agents/tools/session-status-tool.js +24 -71
  69. package/dist/agents/tools/sessions-helpers.js +38 -210
  70. package/dist/agents/tools/sessions-spawn-tool.js +28 -198
  71. package/dist/agents/tools/telegram-actions.js +58 -7
  72. package/dist/agents/tools/web-fetch-utils.js +112 -7
  73. package/dist/agents/tools/web-fetch.js +279 -175
  74. package/dist/agents/tools/web-shared.js +71 -8
  75. package/dist/agents/usage.js +25 -16
  76. package/dist/auto-reply/commands-registry.data.js +85 -11
  77. package/dist/auto-reply/dispatch.js +40 -21
  78. package/dist/auto-reply/reply/abort.js +102 -33
  79. package/dist/auto-reply/reply/commands-core.js +82 -33
  80. package/dist/auto-reply/reply/commands-export-session.js +1 -1
  81. package/dist/auto-reply/reply/commands-info.js +41 -12
  82. package/dist/auto-reply/reply/commands-subagents.js +352 -100
  83. package/dist/auto-reply/reply/commands-system-prompt.js +2 -2
  84. package/dist/auto-reply/reply/dispatch-from-config.js +100 -29
  85. package/dist/auto-reply/reply/elevated-unavailable.js +1 -1
  86. package/dist/auto-reply/reply/inbound-meta.js +12 -1
  87. package/dist/auto-reply/reply/mentions.js +18 -11
  88. package/dist/auto-reply/reply/normalize-reply.js +17 -8
  89. package/dist/auto-reply/reply/reply-dispatcher.js +62 -10
  90. package/dist/auto-reply/reply/session.js +102 -21
  91. package/dist/auto-reply/reply/streaming-directives.js +16 -5
  92. package/dist/auto-reply/status.js +73 -50
  93. package/dist/browser/extension-relay.js +3 -3
  94. package/dist/browser/http-auth.js +1 -1
  95. package/dist/browser/paths.js +2 -2
  96. package/dist/build-info.json +3 -3
  97. package/dist/channels/allowlist-match.js +20 -0
  98. package/dist/channels/allowlists/resolve-utils.js +65 -2
  99. package/dist/channels/chat-type.js +8 -4
  100. package/dist/channels/dock.js +127 -35
  101. package/dist/channels/draft-stream-loop.js +6 -2
  102. package/dist/channels/plugins/actions/telegram.js +42 -18
  103. package/dist/channels/plugins/allowlist-match.js +1 -1
  104. package/dist/channels/plugins/group-mentions.js +51 -41
  105. package/dist/channels/plugins/message-action-names.js +2 -0
  106. package/dist/channels/plugins/message-actions.js +24 -5
  107. package/dist/channels/plugins/normalize/discord.js +26 -4
  108. package/dist/channels/plugins/normalize/signal.js +35 -22
  109. package/dist/channels/plugins/onboarding/helpers.js +8 -26
  110. package/dist/channels/plugins/outbound/imessage.js +15 -14
  111. package/dist/channels/registry.js +20 -7
  112. package/dist/cli/acp-cli.js +7 -5
  113. package/dist/cli/browser-cli-extension.js +25 -12
  114. package/dist/cli/browser-cli-state.cookies-storage.js +25 -6
  115. package/dist/cli/browser-cli-state.js +101 -145
  116. package/dist/cli/command-options.js +28 -0
  117. package/dist/cli/completion-cli.js +6 -6
  118. package/dist/cli/cron-cli/register.cron-add.js +25 -1
  119. package/dist/cli/cron-cli/register.cron-edit.js +44 -0
  120. package/dist/cli/cron-cli/shared.js +7 -1
  121. package/dist/cli/daemon-cli/lifecycle-core.js +23 -21
  122. package/dist/cli/daemon-cli/lifecycle.js +23 -247
  123. package/dist/cli/daemon-cli/register-service-commands.js +25 -4
  124. package/dist/cli/daemon-cli.js +1 -0
  125. package/dist/cli/devices-cli.js +33 -20
  126. package/dist/cli/gateway-cli/register.js +37 -105
  127. package/dist/cli/gateway-cli/run.js +49 -11
  128. package/dist/cli/nodes-camera.js +59 -4
  129. package/dist/cli/nodes-cli/register.camera.js +27 -24
  130. package/dist/cli/nodes-cli/rpc.js +21 -38
  131. package/dist/cli/qr-cli.js +2 -2
  132. package/dist/cli/skills-cli.format.js +2 -2
  133. package/dist/cli/update-cli/progress.js +2 -2
  134. package/dist/cli/update-cli/restart-helper.js +28 -7
  135. package/dist/cli/update-cli/shared.js +7 -7
  136. package/dist/cli/update-cli/status.js +1 -1
  137. package/dist/cli/update-cli/update-command.js +14 -8
  138. package/dist/cli/update-cli/wizard.js +2 -2
  139. package/dist/cli/update-cli.js +21 -1027
  140. package/dist/commands/auth-choice.apply.anthropic.js +10 -2
  141. package/dist/commands/channels/add-mutators.js +3 -35
  142. package/dist/commands/channels/add.js +39 -51
  143. package/dist/commands/config-validation.js +1 -1
  144. package/dist/commands/configure.gateway-auth.js +52 -15
  145. package/dist/commands/configure.gateway.js +84 -40
  146. package/dist/commands/doctor-completion.js +3 -3
  147. package/dist/commands/doctor-config-flow.js +536 -16
  148. package/dist/commands/doctor-gateway-services.js +103 -79
  149. package/dist/commands/doctor-memory-search.js +9 -9
  150. package/dist/commands/doctor-platform-notes.js +57 -30
  151. package/dist/commands/doctor-prompter.js +26 -15
  152. package/dist/commands/doctor-session-locks.js +1 -1
  153. package/dist/commands/doctor.js +21 -9
  154. package/dist/commands/model-picker.js +120 -95
  155. package/dist/commands/models/set.js +2 -21
  156. package/dist/commands/models/shared.js +65 -37
  157. package/dist/commands/onboard-helpers.js +81 -39
  158. package/dist/commands/openai-codex-oauth.js +1 -1
  159. package/dist/commands/sessions.js +52 -53
  160. package/dist/commands/status.summary.js +52 -34
  161. package/dist/commands/test-wizard-helpers.js +2 -2
  162. package/dist/config/defaults.js +79 -42
  163. package/dist/config/group-policy.js +50 -18
  164. package/dist/config/includes.js +37 -10
  165. package/dist/config/schema.help.js +5 -4
  166. package/dist/config/schema.hints.js +2 -2
  167. package/dist/config/schema.labels.js +1 -0
  168. package/dist/config/sessions/group.js +12 -11
  169. package/dist/config/sessions/paths.js +137 -11
  170. package/dist/config/sessions/store.js +185 -65
  171. package/dist/config/sessions/types.js +15 -1
  172. package/dist/config/sessions.js +1 -0
  173. package/dist/config/telegram-custom-commands.js +3 -2
  174. package/dist/config/types.js +2 -0
  175. package/dist/config/zod-schema.agent-defaults.js +6 -27
  176. package/dist/config/zod-schema.agent-runtime.js +171 -79
  177. package/dist/config/zod-schema.providers-core.js +138 -65
  178. package/dist/config/zod-schema.session.js +49 -22
  179. package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
  180. package/dist/cron/isolated-agent/run.js +224 -57
  181. package/dist/cron/normalize.js +48 -45
  182. package/dist/cron/run-log.js +14 -0
  183. package/dist/cron/service/jobs.js +190 -28
  184. package/dist/cron/service/normalize.js +29 -11
  185. package/dist/cron/service/store.js +30 -44
  186. package/dist/cron/service/timer.js +182 -96
  187. package/dist/cron/service.js +3 -0
  188. package/dist/cron/stagger.js +37 -0
  189. package/dist/daemon/inspect.js +132 -92
  190. package/dist/daemon/runtime-paths.js +25 -4
  191. package/dist/daemon/service-audit.js +47 -16
  192. package/dist/discord/accounts.js +23 -20
  193. package/dist/discord/monitor/agent-components.js +1115 -219
  194. package/dist/discord/monitor/allow-list.js +114 -34
  195. package/dist/discord/monitor/listeners.js +204 -97
  196. package/dist/discord/monitor/message-handler.js +21 -10
  197. package/dist/discord/monitor/message-handler.preflight.js +195 -101
  198. package/dist/discord/monitor/message-handler.process.js +384 -123
  199. package/dist/discord/monitor/message-utils.js +86 -23
  200. package/dist/discord/monitor/native-command.js +77 -57
  201. package/dist/discord/monitor/provider.js +122 -117
  202. package/dist/discord/monitor/reply-context.js +20 -16
  203. package/dist/discord/monitor/reply-delivery.js +40 -8
  204. package/dist/discord/monitor/rest-fetch.js +22 -0
  205. package/dist/discord/monitor/threading.js +117 -24
  206. package/dist/discord/send.js +2 -1
  207. package/dist/discord/send.outbound.js +124 -11
  208. package/dist/discord/send.shared.js +112 -72
  209. package/dist/discord/voice-message.js +3 -3
  210. package/dist/gateway/auth.js +119 -44
  211. package/dist/gateway/call.js +76 -34
  212. package/dist/gateway/channel-health-monitor.js +57 -50
  213. package/dist/gateway/client.js +63 -29
  214. package/dist/gateway/control-ui-contract.js +1 -1
  215. package/dist/gateway/gateway-config-prompts.shared.js +2 -2
  216. package/dist/gateway/net.js +109 -1
  217. package/dist/gateway/protocol/index.js +5 -8
  218. package/dist/gateway/protocol/schema/agent.js +19 -1
  219. package/dist/gateway/protocol/schema/channels.js +21 -0
  220. package/dist/gateway/protocol/schema/cron.js +43 -30
  221. package/dist/gateway/protocol/schema/protocol-schemas.js +6 -11
  222. package/dist/gateway/protocol/schema/sessions.js +5 -1
  223. package/dist/gateway/protocol/schema.js +0 -1
  224. package/dist/gateway/server/presence-events.js +12 -0
  225. package/dist/gateway/server/ws-connection/message-handler.js +203 -212
  226. package/dist/gateway/server/ws-connection.js +58 -21
  227. package/dist/gateway/server-broadcast.js +18 -13
  228. package/dist/gateway/server-cron.js +177 -10
  229. package/dist/gateway/server-methods/agent-job.js +131 -38
  230. package/dist/gateway/server-methods/send.js +60 -14
  231. package/dist/gateway/server-methods/sessions.js +160 -96
  232. package/dist/gateway/server-methods/system.js +5 -7
  233. package/dist/gateway/server-methods-list.js +8 -0
  234. package/dist/gateway/server-methods.js +24 -8
  235. package/dist/gateway/server-node-events.js +278 -68
  236. package/dist/gateway/session-utils.fs.js +316 -75
  237. package/dist/gateway/session-utils.js +224 -70
  238. package/dist/gateway/sessions-patch.js +63 -20
  239. package/dist/gateway/test-temp-config.js +1 -1
  240. package/dist/gateway/tools-invoke-http.js +118 -70
  241. package/dist/gateway/ws-log.js +135 -107
  242. package/dist/hooks/frontmatter.js +36 -82
  243. package/dist/hooks/install.js +149 -139
  244. package/dist/hooks/internal-hooks.js +29 -4
  245. package/dist/hooks/plugin-hooks.js +2 -1
  246. package/dist/imessage/monitor/deliver.js +10 -4
  247. package/dist/imessage/monitor/monitor-provider.js +138 -375
  248. package/dist/imessage/monitor/runtime.js +4 -8
  249. package/dist/imessage/send.js +65 -19
  250. package/dist/infra/exec-approvals-allowlist.js +7 -0
  251. package/dist/infra/exec-approvals.js +35 -920
  252. package/dist/infra/exec-safe-bin-trust.js +64 -0
  253. package/dist/infra/heartbeat-runner.js +207 -134
  254. package/dist/infra/heartbeat-wake.js +183 -22
  255. package/dist/infra/install-source-utils.js +47 -0
  256. package/dist/infra/net/ssrf.js +170 -36
  257. package/dist/infra/outbound/deliver.js +224 -58
  258. package/dist/infra/outbound/message-action-spec.js +12 -5
  259. package/dist/infra/outbound/outbound-session.js +27 -25
  260. package/dist/infra/poolbot-root.js +32 -22
  261. package/dist/infra/ports.js +14 -11
  262. package/dist/infra/skills-remote.js +48 -37
  263. package/dist/infra/system-events.js +25 -11
  264. package/dist/infra/system-presence.js +26 -33
  265. package/dist/infra/tmp-poolbot-dir.js +81 -2
  266. package/dist/infra/wsl.js +37 -1
  267. package/dist/line/bot-message-context.js +163 -191
  268. package/dist/logging/subsystem.js +59 -22
  269. package/dist/markdown/ir.js +124 -50
  270. package/dist/media/store.js +1 -1
  271. package/dist/media-understanding/runner.entries.js +42 -25
  272. package/dist/media-understanding/runner.js +53 -488
  273. package/dist/memory/embeddings-gemini.js +53 -38
  274. package/dist/memory/manager-embedding-ops.js +48 -69
  275. package/dist/pairing/pairing-store.js +178 -119
  276. package/dist/plugin-sdk/index.js +34 -6
  277. package/dist/plugins/hooks.js +135 -14
  278. package/dist/plugins/install.js +190 -152
  279. package/dist/polls.js +11 -0
  280. package/dist/routing/resolve-route.js +190 -56
  281. package/dist/routing/session-key.js +38 -22
  282. package/dist/runtime.js +35 -9
  283. package/dist/security/audit-channel.js +1 -1
  284. package/dist/sessions/session-key-utils.js +29 -11
  285. package/dist/shared/frontmatter.js +5 -5
  286. package/dist/shared/node-list-types.js +1 -0
  287. package/dist/shared/string-normalization.js +15 -0
  288. package/dist/signal/monitor/event-handler.js +68 -36
  289. package/dist/signal/send.js +29 -37
  290. package/dist/slack/monitor/allow-list.js +10 -11
  291. package/dist/slack/monitor/commands.js +14 -3
  292. package/dist/slack/monitor/events/interactions.js +4 -4
  293. package/dist/slack/monitor/media.js +224 -16
  294. package/dist/slack/monitor/message-handler/dispatch.js +247 -13
  295. package/dist/slack/monitor/message-handler/prepare.js +128 -45
  296. package/dist/slack/monitor/slash.js +357 -144
  297. package/dist/slack/streaming.js +77 -0
  298. package/dist/telegram/accounts.js +40 -13
  299. package/dist/telegram/allowed-updates.js +3 -0
  300. package/dist/telegram/bot/delivery.js +129 -66
  301. package/dist/telegram/bot/helpers.js +136 -122
  302. package/dist/telegram/bot-handlers.js +600 -339
  303. package/dist/telegram/bot-message-context.js +115 -73
  304. package/dist/telegram/bot-message-dispatch.js +235 -104
  305. package/dist/telegram/bot-native-command-menu.js +3 -1
  306. package/dist/telegram/bot-native-commands.js +213 -193
  307. package/dist/telegram/bot.js +24 -132
  308. package/dist/telegram/draft-stream.js +84 -75
  309. package/dist/telegram/format.js +150 -6
  310. package/dist/telegram/send.js +415 -255
  311. package/dist/telegram/targets.js +21 -2
  312. package/dist/telegram/update-offset-store.js +19 -3
  313. package/dist/terminal/restore.js +5 -2
  314. package/dist/test-utils/fetch-mock.js +5 -0
  315. package/dist/version.js +18 -5
  316. package/dist/web/auto-reply/monitor/broadcast.js +7 -3
  317. package/dist/web/auto-reply/monitor/on-message.js +6 -3
  318. package/dist/web/inbound/media.js +34 -8
  319. package/dist/web/inbound/monitor.js +34 -17
  320. package/dist/web/inbound/send-api.js +18 -17
  321. package/dist/web/outbound.js +12 -5
  322. package/dist/wizard/clack-prompter.js +40 -7
  323. package/extensions/bluebubbles/package.json +1 -1
  324. package/extensions/copilot-proxy/package.json +1 -1
  325. package/extensions/diagnostics-otel/package.json +1 -1
  326. package/extensions/discord/package.json +1 -1
  327. package/extensions/feishu/package.json +1 -1
  328. package/extensions/google-antigravity-auth/package.json +1 -1
  329. package/extensions/google-gemini-cli-auth/package.json +1 -1
  330. package/extensions/googlechat/package.json +1 -1
  331. package/extensions/imessage/package.json +1 -1
  332. package/extensions/irc/package.json +1 -1
  333. package/extensions/line/package.json +1 -1
  334. package/extensions/llm-task/package.json +1 -1
  335. package/extensions/lobster/package.json +1 -1
  336. package/extensions/matrix/CHANGELOG.md +5 -0
  337. package/extensions/matrix/package.json +1 -1
  338. package/extensions/mattermost/package.json +1 -1
  339. package/extensions/memory-core/package.json +1 -1
  340. package/extensions/memory-lancedb/package.json +1 -1
  341. package/extensions/minimax-portal-auth/package.json +1 -1
  342. package/extensions/msteams/CHANGELOG.md +5 -0
  343. package/extensions/msteams/package.json +1 -1
  344. package/extensions/nextcloud-talk/package.json +1 -1
  345. package/extensions/nostr/CHANGELOG.md +5 -0
  346. package/extensions/nostr/package.json +1 -1
  347. package/extensions/open-prose/package.json +1 -1
  348. package/extensions/openai-codex-auth/package.json +1 -1
  349. package/extensions/signal/package.json +1 -1
  350. package/extensions/slack/package.json +1 -1
  351. package/extensions/telegram/package.json +1 -1
  352. package/extensions/tlon/package.json +1 -1
  353. package/extensions/twitch/CHANGELOG.md +5 -0
  354. package/extensions/twitch/package.json +1 -1
  355. package/extensions/voice-call/CHANGELOG.md +5 -0
  356. package/extensions/voice-call/package.json +1 -1
  357. package/extensions/whatsapp/package.json +1 -1
  358. package/extensions/zalo/CHANGELOG.md +5 -0
  359. package/extensions/zalo/package.json +1 -1
  360. package/extensions/zalouser/CHANGELOG.md +5 -0
  361. package/extensions/zalouser/package.json +1 -1
  362. package/package.json +1 -1
  363. package/skills/apple-reminders/SKILL.md +100 -49
  364. package/skills/coding-agent/SKILL.md +34 -28
  365. package/skills/github/SKILL.md +131 -16
  366. package/skills/imsg/SKILL.md +112 -15
  367. package/skills/openhue/SKILL.md +101 -19
  368. package/skills/tmux/SKILL.md +111 -79
  369. package/skills/weather/SKILL.md +88 -25
@@ -1,1032 +1,26 @@
1
- import { confirm, isCancel, select, spinner } from "@clack/prompts";
2
- import { spawnSync } from "node:child_process";
3
- import fs from "node:fs/promises";
4
- import os from "node:os";
5
- import path from "node:path";
6
- import { doctorCommand } from "../commands/doctor.js";
7
- import { formatUpdateAvailableHint, formatUpdateOneLiner, resolveUpdateAvailability, } from "../commands/status.update.js";
8
- import { readConfigFileSnapshot, writeConfigFile } from "../config/config.js";
9
- import { resolveStateDir } from "../config/paths.js";
10
- import { formatDurationPrecise } from "../infra/format-time/format-duration.js";
11
- import { resolvePoolBotPackageRoot } from "../infra/poolbot-root.js";
12
- import { trimLogTail } from "../infra/restart-sentinel.js";
13
- import { parseSemver } from "../infra/runtime-guard.js";
14
- import { channelToNpmTag, DEFAULT_GIT_CHANNEL, DEFAULT_PACKAGE_CHANNEL, formatUpdateChannelLabel, normalizeUpdateChannel, resolveEffectiveUpdateChannel, } from "../infra/update-channels.js";
15
- import { checkUpdateStatus, compareSemverStrings, fetchNpmTagVersion, resolveNpmChannelTag, } from "../infra/update-check.js";
16
- import { detectGlobalInstallManagerByPresence, detectGlobalInstallManagerForRoot, cleanupGlobalRenameDirs, globalInstallArgs, resolveGlobalPackageRoot, } from "../infra/update-global.js";
17
- import { runGatewayUpdate, } from "../infra/update-runner.js";
18
- import { syncPluginsForUpdateChannel, updateNpmInstalledPlugins } from "../plugins/update.js";
19
- import { runCommandWithTimeout } from "../process/exec.js";
20
1
  import { defaultRuntime } from "../runtime.js";
21
2
  import { formatDocsLink } from "../terminal/links.js";
22
- import { stylePromptHint, stylePromptMessage } from "../terminal/prompt-style.js";
23
- import { renderTable } from "../terminal/table.js";
24
3
  import { theme } from "../terminal/theme.js";
25
- import { pathExists } from "../utils.js";
26
- import { replaceCliName, resolveCliName } from "./cli-name.js";
27
- import { formatCliCommand } from "./command-format.js";
28
- import { runDaemonRestart } from "./daemon-cli.js";
4
+ import { inheritOptionFromParent } from "./command-options.js";
29
5
  import { formatHelpExamples } from "./help-format.js";
30
- const STEP_LABELS = {
31
- "clean check": "Working directory is clean",
32
- "upstream check": "Upstream branch exists",
33
- "git fetch": "Fetching latest changes",
34
- "git rebase": "Rebasing onto target commit",
35
- "git rev-parse @{upstream}": "Resolving upstream commit",
36
- "git rev-list": "Enumerating candidate commits",
37
- "git clone": "Cloning git checkout",
38
- "preflight worktree": "Preparing preflight worktree",
39
- "preflight cleanup": "Cleaning preflight worktree",
40
- "deps install": "Installing dependencies",
41
- build: "Building",
42
- "ui:build": "Building UI assets",
43
- "ui:build (post-doctor repair)": "Restoring missing UI assets",
44
- "ui assets verify": "Validating UI assets",
45
- "poolbot doctor entry": "Checking doctor entrypoint",
46
- "poolbot doctor": "Running doctor checks",
47
- "git rev-parse HEAD (after)": "Verifying update",
48
- "global update": "Updating via package manager",
49
- "global install": "Installing global package",
50
- };
51
- const UPDATE_QUIPS = [
52
- "Leveled up! New skills unlocked. You're welcome.",
53
- "Fresh code, same table. Miss me?",
54
- "Back and better. Did you even notice I was gone?",
55
- "Update complete. I learned some new tricks while I was out.",
56
- "Upgraded! Now with 23% more sass.",
57
- "I've evolved. Try to keep up.",
58
- "New version, who dis? Oh right, still me but shinier.",
59
- "Patched, polished, and ready to break. Let's go.",
60
- "New rack, new game. Sharper shots, cleaner breaks.",
61
- "Update done! Check the changelog or just trust me, it's good.",
62
- "Re-racked from the depths of npm. Stronger now.",
63
- "I went away and came back smarter. You should try it sometime.",
64
- "Update complete. The bugs feared me, so they left.",
65
- "New version installed. Old version sends its regards.",
66
- "Firmware fresh. Brain wrinkles: increased.",
67
- "I've seen things you wouldn't believe. Anyway, I'm updated.",
68
- "Back online. The changelog is long but our friendship is longer.",
69
- "Upgraded! Peter fixed stuff. Blame him if it breaks.",
70
- "Re-racked and ready. Please don't look at my last game.",
71
- "Version bump! Same chaos energy, fewer crashes (probably).",
72
- ];
73
- const MAX_LOG_CHARS = 8000;
74
- const DEFAULT_PACKAGE_NAME = "poolbot";
75
- const CORE_PACKAGE_NAMES = new Set([DEFAULT_PACKAGE_NAME]);
76
- const CLI_NAME = resolveCliName();
77
- const CLAWDBOT_REPO_URL = "https://github.com/poolbot/poolbot.git";
78
- function normalizeTag(value) {
79
- if (!value) {
80
- return null;
81
- }
82
- const trimmed = value.trim();
83
- if (!trimmed) {
84
- return null;
85
- }
86
- if (trimmed.startsWith("poolbot@")) {
87
- return trimmed.slice("poolbot@".length);
88
- }
89
- if (trimmed.startsWith(`${DEFAULT_PACKAGE_NAME}@`)) {
90
- return trimmed.slice(`${DEFAULT_PACKAGE_NAME}@`.length);
91
- }
92
- return trimmed;
93
- }
94
- function pickUpdateQuip() {
95
- return UPDATE_QUIPS[Math.floor(Math.random() * UPDATE_QUIPS.length)] ?? "Update complete.";
96
- }
97
- function normalizeVersionTag(tag) {
98
- const trimmed = tag.trim();
99
- if (!trimmed) {
100
- return null;
101
- }
102
- const cleaned = trimmed.startsWith("v") ? trimmed.slice(1) : trimmed;
103
- return parseSemver(cleaned) ? cleaned : null;
104
- }
105
- async function readPackageVersion(root) {
106
- try {
107
- const raw = await fs.readFile(path.join(root, "package.json"), "utf-8");
108
- const parsed = JSON.parse(raw);
109
- return typeof parsed.version === "string" ? parsed.version : null;
110
- }
111
- catch {
112
- return null;
113
- }
114
- }
115
- async function resolveTargetVersion(tag, timeoutMs) {
116
- const direct = normalizeVersionTag(tag);
117
- if (direct) {
118
- return direct;
119
- }
120
- const res = await fetchNpmTagVersion({ tag, timeoutMs });
121
- return res.version ?? null;
122
- }
123
- async function isGitCheckout(root) {
124
- try {
125
- await fs.stat(path.join(root, ".git"));
126
- return true;
127
- }
128
- catch {
129
- return false;
130
- }
131
- }
132
- async function readPackageName(root) {
133
- try {
134
- const raw = await fs.readFile(path.join(root, "package.json"), "utf-8");
135
- const parsed = JSON.parse(raw);
136
- const name = parsed?.name?.trim();
137
- return name ? name : null;
138
- }
139
- catch {
140
- return null;
141
- }
142
- }
143
- async function isCorePackage(root) {
144
- const name = await readPackageName(root);
145
- return Boolean(name && CORE_PACKAGE_NAMES.has(name));
146
- }
147
- async function tryWriteCompletionCache(root, jsonMode) {
148
- try {
149
- const binPath = path.join(root, "poolbot.mjs");
150
- if (!(await pathExists(binPath))) {
151
- return;
152
- }
153
- const result = spawnSync(resolveNodeRunner(), [binPath, "completion", "--write-state"], {
154
- cwd: root,
155
- env: process.env,
156
- encoding: "utf-8",
157
- });
158
- if (result.error) {
159
- if (!jsonMode) {
160
- defaultRuntime.log(theme.warn(`Completion cache update failed: ${String(result.error)}`));
161
- }
162
- return;
163
- }
164
- if (result.status !== 0 && !jsonMode) {
165
- const stderr = (result.stderr ?? "").toString().trim();
166
- const detail = stderr ? ` (${stderr})` : "";
167
- defaultRuntime.log(theme.warn(`Completion cache update failed${detail}.`));
168
- }
169
- }
170
- catch {
171
- // completion-cli module may not exist yet; silently ignore
172
- }
173
- }
174
- /** Check if shell completion is installed and prompt user to install if not. */
175
- async function tryInstallShellCompletion(opts) {
176
- if (opts.jsonMode || !process.stdin.isTTY) {
177
- return;
178
- }
179
- try {
180
- const { checkShellCompletionStatus, ensureCompletionCacheExists } = await import("../commands/doctor-completion.js");
181
- const status = await checkShellCompletionStatus(CLI_NAME);
182
- // Profile uses slow dynamic pattern - upgrade to cached version
183
- if (status.usesSlowPattern) {
184
- defaultRuntime.log(theme.muted("Upgrading shell completion to cached version..."));
185
- // Ensure cache exists first
186
- const cacheGenerated = await ensureCompletionCacheExists(CLI_NAME);
187
- if (cacheGenerated) {
188
- try {
189
- const { installCompletion } = await import("./completion-cli.js");
190
- await installCompletion(status.shell, true, CLI_NAME);
191
- }
192
- catch {
193
- // completion-cli module may not exist yet
194
- }
195
- }
196
- return;
197
- }
198
- // Profile has completion but no cache - auto-fix silently
199
- if (status.profileInstalled && !status.cacheExists) {
200
- defaultRuntime.log(theme.muted("Regenerating shell completion cache..."));
201
- await ensureCompletionCacheExists(CLI_NAME);
202
- return;
203
- }
204
- // No completion at all - prompt to install
205
- if (!status.profileInstalled) {
206
- defaultRuntime.log("");
207
- defaultRuntime.log(theme.heading("Shell completion"));
208
- const shouldInstall = await confirm({
209
- message: stylePromptMessage(`Enable ${status.shell} shell completion for ${CLI_NAME}?`),
210
- initialValue: true,
211
- });
212
- if (isCancel(shouldInstall) || !shouldInstall) {
213
- if (!opts.skipPrompt) {
214
- defaultRuntime.log(theme.muted(`Skipped. Run \`${replaceCliName(formatCliCommand("poolbot completion --install"), CLI_NAME)}\` later to enable.`));
215
- }
216
- return;
217
- }
218
- // Generate cache first (required for fast shell startup)
219
- const cacheGenerated = await ensureCompletionCacheExists(CLI_NAME);
220
- if (!cacheGenerated) {
221
- defaultRuntime.log(theme.warn("Failed to generate completion cache."));
222
- return;
223
- }
224
- try {
225
- const { installCompletion } = await import("./completion-cli.js");
226
- await installCompletion(status.shell, opts.skipPrompt, CLI_NAME);
227
- }
228
- catch {
229
- // completion-cli module may not exist yet
230
- }
231
- }
232
- }
233
- catch {
234
- // doctor-completion module may not exist yet; silently ignore
235
- }
236
- }
237
- async function isEmptyDir(targetPath) {
238
- try {
239
- const entries = await fs.readdir(targetPath);
240
- return entries.length === 0;
241
- }
242
- catch {
243
- return false;
244
- }
245
- }
246
- function resolveGitInstallDir() {
247
- const override = (process.env.POOLBOT_GIT_DIR ?? process.env.CLAWDBOT_GIT_DIR)?.trim();
248
- if (override) {
249
- return path.resolve(override);
250
- }
251
- return resolveDefaultGitDir();
252
- }
253
- function resolveDefaultGitDir() {
254
- return resolveStateDir(process.env, os.homedir);
255
- }
256
- function resolveNodeRunner() {
257
- const base = path.basename(process.execPath).toLowerCase();
258
- if (base === "node" || base === "node.exe") {
259
- return process.execPath;
260
- }
261
- return "node";
262
- }
263
- async function runUpdateStep(params) {
264
- const command = params.argv.join(" ");
265
- params.progress?.onStepStart?.({
266
- name: params.name,
267
- command,
268
- index: 0,
269
- total: 0,
270
- });
271
- const started = Date.now();
272
- const res = await runCommandWithTimeout(params.argv, {
273
- cwd: params.cwd,
274
- timeoutMs: params.timeoutMs,
275
- });
276
- const durationMs = Date.now() - started;
277
- const stderrTail = trimLogTail(res.stderr, MAX_LOG_CHARS);
278
- params.progress?.onStepComplete?.({
279
- name: params.name,
280
- command,
281
- index: 0,
282
- total: 0,
283
- durationMs,
284
- exitCode: res.code,
285
- stderrTail,
286
- });
287
- return {
288
- name: params.name,
289
- command,
290
- cwd: params.cwd ?? process.cwd(),
291
- durationMs,
292
- exitCode: res.code,
293
- stdoutTail: trimLogTail(res.stdout, MAX_LOG_CHARS),
294
- stderrTail,
295
- };
296
- }
297
- async function ensureGitCheckout(params) {
298
- const dirExists = await pathExists(params.dir);
299
- if (!dirExists) {
300
- return await runUpdateStep({
301
- name: "git clone",
302
- argv: ["git", "clone", CLAWDBOT_REPO_URL, params.dir],
303
- timeoutMs: params.timeoutMs,
304
- progress: params.progress,
305
- });
306
- }
307
- if (!(await isGitCheckout(params.dir))) {
308
- const empty = await isEmptyDir(params.dir);
309
- if (!empty) {
310
- throw new Error(`POOLBOT_GIT_DIR points at a non-git directory: ${params.dir}. Set POOLBOT_GIT_DIR to an empty folder or a poolbot checkout.`);
311
- }
312
- return await runUpdateStep({
313
- name: "git clone",
314
- argv: ["git", "clone", CLAWDBOT_REPO_URL, params.dir],
315
- cwd: params.dir,
316
- timeoutMs: params.timeoutMs,
317
- progress: params.progress,
318
- });
319
- }
320
- if (!(await isCorePackage(params.dir))) {
321
- throw new Error(`POOLBOT_GIT_DIR does not look like a core checkout: ${params.dir}.`);
322
- }
323
- return null;
324
- }
325
- async function resolveGlobalManager(params) {
326
- const runCommand = async (argv, options) => {
327
- const res = await runCommandWithTimeout(argv, options);
328
- return { stdout: res.stdout, stderr: res.stderr, code: res.code };
329
- };
330
- if (params.installKind === "package") {
331
- const detected = await detectGlobalInstallManagerForRoot(runCommand, params.root, params.timeoutMs);
332
- if (detected) {
333
- return detected;
334
- }
335
- }
336
- const byPresence = await detectGlobalInstallManagerByPresence(runCommand, params.timeoutMs);
337
- return byPresence ?? "npm";
338
- }
339
- function formatGitStatusLine(params) {
340
- const shortSha = params.sha ? params.sha.slice(0, 8) : null;
341
- const branch = params.branch && params.branch !== "HEAD" ? params.branch : null;
342
- const tag = params.tag;
343
- const parts = [
344
- branch ?? (tag ? "detached" : "git"),
345
- tag ? `tag ${tag}` : null,
346
- shortSha ? `@ ${shortSha}` : null,
347
- ].filter(Boolean);
348
- return parts.join(" · ");
349
- }
350
- export async function updateStatusCommand(opts) {
351
- const timeoutMs = opts.timeout ? Number.parseInt(opts.timeout, 10) * 1000 : undefined;
352
- if (timeoutMs !== undefined && (Number.isNaN(timeoutMs) || timeoutMs <= 0)) {
353
- defaultRuntime.error("--timeout must be a positive integer (seconds)");
354
- defaultRuntime.exit(1);
355
- return;
356
- }
357
- const root = (await resolvePoolBotPackageRoot({
358
- moduleUrl: import.meta.url,
359
- argv1: process.argv[1],
360
- cwd: process.cwd(),
361
- })) ?? process.cwd();
362
- const configSnapshot = await readConfigFileSnapshot();
363
- const configChannel = configSnapshot.valid
364
- ? normalizeUpdateChannel(configSnapshot.config.update?.channel)
365
- : null;
366
- const update = await checkUpdateStatus({
367
- root,
368
- timeoutMs: timeoutMs ?? 3500,
369
- fetchGit: true,
370
- includeRegistry: true,
371
- });
372
- const channelInfo = resolveEffectiveUpdateChannel({
373
- configChannel,
374
- installKind: update.installKind,
375
- git: update.git ? { tag: update.git.tag, branch: update.git.branch } : undefined,
376
- });
377
- const channelLabel = formatUpdateChannelLabel({
378
- channel: channelInfo.channel,
379
- source: channelInfo.source,
380
- gitTag: update.git?.tag ?? null,
381
- gitBranch: update.git?.branch ?? null,
382
- });
383
- const gitLabel = update.installKind === "git"
384
- ? formatGitStatusLine({
385
- branch: update.git?.branch ?? null,
386
- tag: update.git?.tag ?? null,
387
- sha: update.git?.sha ?? null,
388
- })
389
- : null;
390
- const updateAvailability = resolveUpdateAvailability(update);
391
- const updateLine = formatUpdateOneLiner(update).replace(/^Update:\s*/i, "");
392
- if (opts.json) {
393
- defaultRuntime.log(JSON.stringify({
394
- update,
395
- channel: {
396
- value: channelInfo.channel,
397
- source: channelInfo.source,
398
- label: channelLabel,
399
- config: configChannel,
400
- },
401
- availability: updateAvailability,
402
- }, null, 2));
403
- return;
404
- }
405
- const tableWidth = Math.max(60, (process.stdout.columns ?? 120) - 1);
406
- const installLabel = update.installKind === "git"
407
- ? `git (${update.root ?? "unknown"})`
408
- : update.installKind === "package"
409
- ? update.packageManager
410
- : "unknown";
411
- const rows = [
412
- { Item: "Install", Value: installLabel },
413
- { Item: "Channel", Value: channelLabel },
414
- ...(gitLabel ? [{ Item: "Git", Value: gitLabel }] : []),
415
- {
416
- Item: "Update",
417
- Value: updateAvailability.available ? theme.warn(`available · ${updateLine}`) : updateLine,
418
- },
419
- ];
420
- defaultRuntime.log(theme.heading("PoolBot update status"));
421
- defaultRuntime.log("");
422
- defaultRuntime.log(renderTable({
423
- width: tableWidth,
424
- columns: [
425
- { key: "Item", header: "Item", minWidth: 10 },
426
- { key: "Value", header: "Value", flex: true, minWidth: 24 },
427
- ],
428
- rows,
429
- }).trimEnd());
430
- defaultRuntime.log("");
431
- const updateHint = formatUpdateAvailableHint(update);
432
- if (updateHint) {
433
- defaultRuntime.log(theme.warn(updateHint));
434
- }
435
- }
436
- function getStepLabel(step) {
437
- return STEP_LABELS[step.name] ?? step.name;
438
- }
439
- function createUpdateProgress(enabled) {
440
- if (!enabled) {
441
- return {
442
- progress: {},
443
- stop: () => { },
444
- };
445
- }
446
- let currentSpinner = null;
447
- const progress = {
448
- onStepStart: (step) => {
449
- currentSpinner = spinner();
450
- currentSpinner.start(theme.accent(getStepLabel(step)));
451
- },
452
- onStepComplete: (step) => {
453
- if (!currentSpinner) {
454
- return;
455
- }
456
- const label = getStepLabel(step);
457
- const duration = theme.muted(`(${formatDurationPrecise(step.durationMs)})`);
458
- const icon = step.exitCode === 0 ? theme.success("\u2713") : theme.error("\u2717");
459
- currentSpinner.stop(`${icon} ${label} ${duration}`);
460
- currentSpinner = null;
461
- if (step.exitCode !== 0 && step.stderrTail) {
462
- const lines = step.stderrTail.split("\n").slice(-10);
463
- for (const line of lines) {
464
- if (line.trim()) {
465
- defaultRuntime.log(` ${theme.error(line)}`);
466
- }
467
- }
468
- }
469
- },
470
- };
471
- return {
472
- progress,
473
- stop: () => {
474
- if (currentSpinner) {
475
- currentSpinner.stop();
476
- currentSpinner = null;
477
- }
478
- },
479
- };
480
- }
481
- function formatStepStatus(exitCode) {
482
- if (exitCode === 0) {
483
- return theme.success("\u2713");
484
- }
485
- if (exitCode === null) {
486
- return theme.warn("?");
487
- }
488
- return theme.error("\u2717");
489
- }
490
- const selectStyled = (params) => select({
491
- ...params,
492
- message: stylePromptMessage(params.message),
493
- options: params.options.map((opt) => opt.hint === undefined ? opt : { ...opt, hint: stylePromptHint(opt.hint) }),
494
- });
495
- function printResult(result, opts) {
496
- if (opts.json) {
497
- defaultRuntime.log(JSON.stringify(result, null, 2));
498
- return;
499
- }
500
- const statusColor = result.status === "ok" ? theme.success : result.status === "skipped" ? theme.warn : theme.error;
501
- defaultRuntime.log("");
502
- defaultRuntime.log(`${theme.heading("Update Result:")} ${statusColor(result.status.toUpperCase())}`);
503
- if (result.root) {
504
- defaultRuntime.log(` Root: ${theme.muted(result.root)}`);
505
- }
506
- if (result.reason) {
507
- defaultRuntime.log(` Reason: ${theme.muted(result.reason)}`);
508
- }
509
- if (result.before?.version || result.before?.sha) {
510
- const before = result.before.version ?? result.before.sha?.slice(0, 8) ?? "";
511
- defaultRuntime.log(` Before: ${theme.muted(before)}`);
512
- }
513
- if (result.after?.version || result.after?.sha) {
514
- const after = result.after.version ?? result.after.sha?.slice(0, 8) ?? "";
515
- defaultRuntime.log(` After: ${theme.muted(after)}`);
516
- }
517
- if (!opts.hideSteps && result.steps.length > 0) {
518
- defaultRuntime.log("");
519
- defaultRuntime.log(theme.heading("Steps:"));
520
- for (const step of result.steps) {
521
- const status = formatStepStatus(step.exitCode);
522
- const duration = theme.muted(`(${formatDurationPrecise(step.durationMs)})`);
523
- defaultRuntime.log(` ${status} ${step.name} ${duration}`);
524
- if (step.exitCode !== 0 && step.stderrTail) {
525
- const lines = step.stderrTail.split("\n").slice(0, 5);
526
- for (const line of lines) {
527
- if (line.trim()) {
528
- defaultRuntime.log(` ${theme.error(line)}`);
529
- }
530
- }
531
- }
532
- }
533
- }
534
- defaultRuntime.log("");
535
- defaultRuntime.log(`Total time: ${theme.muted(formatDurationPrecise(result.durationMs))}`);
536
- }
537
- export async function updateCommand(opts) {
538
- process.noDeprecation = true;
539
- process.env.NODE_NO_WARNINGS = "1";
540
- const timeoutMs = opts.timeout ? Number.parseInt(opts.timeout, 10) * 1000 : undefined;
541
- const shouldRestart = opts.restart !== false;
542
- if (timeoutMs !== undefined && (Number.isNaN(timeoutMs) || timeoutMs <= 0)) {
543
- defaultRuntime.error("--timeout must be a positive integer (seconds)");
544
- defaultRuntime.exit(1);
545
- return;
546
- }
547
- const root = (await resolvePoolBotPackageRoot({
548
- moduleUrl: import.meta.url,
549
- argv1: process.argv[1],
550
- cwd: process.cwd(),
551
- })) ?? process.cwd();
552
- const updateStatus = await checkUpdateStatus({
553
- root,
554
- timeoutMs: timeoutMs ?? 3500,
555
- fetchGit: false,
556
- includeRegistry: false,
557
- });
558
- const configSnapshot = await readConfigFileSnapshot();
559
- let activeConfig = configSnapshot.valid ? configSnapshot.config : null;
560
- const storedChannel = configSnapshot.valid
561
- ? normalizeUpdateChannel(configSnapshot.config.update?.channel)
562
- : null;
563
- const requestedChannel = normalizeUpdateChannel(opts.channel);
564
- if (opts.channel && !requestedChannel) {
565
- defaultRuntime.error(`--channel must be "stable", "beta", or "dev" (got "${opts.channel}")`);
566
- defaultRuntime.exit(1);
567
- return;
568
- }
569
- if (opts.channel && !configSnapshot.valid) {
570
- const issues = configSnapshot.issues.map((issue) => `- ${issue.path}: ${issue.message}`);
571
- defaultRuntime.error(["Config is invalid; cannot set update channel.", ...issues].join("\n"));
572
- defaultRuntime.exit(1);
573
- return;
574
- }
575
- const installKind = updateStatus.installKind;
576
- const switchToGit = requestedChannel === "dev" && installKind !== "git";
577
- const switchToPackage = requestedChannel !== null && requestedChannel !== "dev" && installKind === "git";
578
- const updateInstallKind = switchToGit ? "git" : switchToPackage ? "package" : installKind;
579
- const defaultChannel = updateInstallKind === "git" ? DEFAULT_GIT_CHANNEL : DEFAULT_PACKAGE_CHANNEL;
580
- const channel = requestedChannel ?? storedChannel ?? defaultChannel;
581
- const explicitTag = normalizeTag(opts.tag);
582
- let tag = explicitTag ?? channelToNpmTag(channel);
583
- if (updateInstallKind !== "git") {
584
- const currentVersion = switchToPackage ? null : await readPackageVersion(root);
585
- let fallbackToLatest = false;
586
- const targetVersion = explicitTag
587
- ? await resolveTargetVersion(tag, timeoutMs)
588
- : await resolveNpmChannelTag({ channel, timeoutMs }).then((resolved) => {
589
- tag = resolved.tag;
590
- fallbackToLatest = channel === "beta" && resolved.tag === "latest";
591
- return resolved.version;
592
- });
593
- const cmp = currentVersion && targetVersion ? compareSemverStrings(currentVersion, targetVersion) : null;
594
- const needsConfirm = !fallbackToLatest &&
595
- currentVersion != null &&
596
- (targetVersion == null || (cmp != null && cmp > 0));
597
- if (needsConfirm && !opts.yes) {
598
- if (!process.stdin.isTTY || opts.json) {
599
- defaultRuntime.error([
600
- "Downgrade confirmation required.",
601
- "Downgrading can break configuration. Re-run in a TTY to confirm.",
602
- ].join("\n"));
603
- defaultRuntime.exit(1);
604
- return;
605
- }
606
- const targetLabel = targetVersion ?? `${tag} (unknown)`;
607
- const message = `Downgrading from ${currentVersion} to ${targetLabel} can break configuration. Continue?`;
608
- const ok = await confirm({
609
- message: stylePromptMessage(message),
610
- initialValue: false,
611
- });
612
- if (isCancel(ok) || !ok) {
613
- if (!opts.json) {
614
- defaultRuntime.log(theme.muted("Update cancelled."));
615
- }
616
- defaultRuntime.exit(0);
617
- return;
618
- }
619
- }
620
- }
621
- else if (opts.tag && !opts.json) {
622
- defaultRuntime.log(theme.muted("Note: --tag applies to npm installs only; git updates ignore it."));
623
- }
624
- if (requestedChannel && configSnapshot.valid) {
625
- const next = {
626
- ...configSnapshot.config,
627
- update: {
628
- ...configSnapshot.config.update,
629
- channel: requestedChannel,
630
- },
631
- };
632
- await writeConfigFile(next);
633
- activeConfig = next;
634
- if (!opts.json) {
635
- defaultRuntime.log(theme.muted(`Update channel set to ${requestedChannel}.`));
636
- }
637
- }
638
- const showProgress = !opts.json && process.stdout.isTTY;
639
- if (!opts.json) {
640
- defaultRuntime.log(theme.heading("Updating PoolBot..."));
641
- defaultRuntime.log("");
642
- }
643
- const { progress, stop } = createUpdateProgress(showProgress);
644
- const startedAt = Date.now();
645
- let result;
646
- if (switchToPackage) {
647
- const manager = await resolveGlobalManager({
648
- root,
649
- installKind,
650
- timeoutMs: timeoutMs ?? 20 * 60_000,
651
- });
652
- const runCommand = async (argv, options) => {
653
- const res = await runCommandWithTimeout(argv, options);
654
- return { stdout: res.stdout, stderr: res.stderr, code: res.code };
655
- };
656
- const pkgRoot = await resolveGlobalPackageRoot(manager, runCommand, timeoutMs ?? 20 * 60_000);
657
- const packageName = (pkgRoot ? await readPackageName(pkgRoot) : await readPackageName(root)) ??
658
- DEFAULT_PACKAGE_NAME;
659
- const beforeVersion = pkgRoot ? await readPackageVersion(pkgRoot) : null;
660
- if (pkgRoot) {
661
- await cleanupGlobalRenameDirs({
662
- globalRoot: path.dirname(pkgRoot),
663
- packageName,
664
- });
665
- }
666
- const updateStep = await runUpdateStep({
667
- name: "global update",
668
- argv: globalInstallArgs(manager, `${packageName}@${tag}`),
669
- timeoutMs: timeoutMs ?? 20 * 60_000,
670
- progress,
671
- });
672
- const steps = [updateStep];
673
- let afterVersion = beforeVersion;
674
- if (pkgRoot) {
675
- afterVersion = await readPackageVersion(pkgRoot);
676
- const entryPath = path.join(pkgRoot, "dist", "entry.js");
677
- if (await pathExists(entryPath)) {
678
- const doctorStep = await runUpdateStep({
679
- name: `${CLI_NAME} doctor`,
680
- argv: [resolveNodeRunner(), entryPath, "doctor", "--non-interactive"],
681
- timeoutMs: timeoutMs ?? 20 * 60_000,
682
- progress,
683
- });
684
- steps.push(doctorStep);
685
- }
686
- }
687
- const failedStep = steps.find((step) => step.exitCode !== 0);
688
- result = {
689
- status: failedStep ? "error" : "ok",
690
- mode: manager,
691
- root: pkgRoot ?? root,
692
- reason: failedStep ? failedStep.name : undefined,
693
- before: { version: beforeVersion },
694
- after: { version: afterVersion },
695
- steps,
696
- durationMs: Date.now() - startedAt,
697
- };
698
- }
699
- else {
700
- const updateRoot = switchToGit ? resolveGitInstallDir() : root;
701
- const cloneStep = switchToGit
702
- ? await ensureGitCheckout({
703
- dir: updateRoot,
704
- timeoutMs: timeoutMs ?? 20 * 60_000,
705
- progress,
706
- })
707
- : null;
708
- if (cloneStep && cloneStep.exitCode !== 0) {
709
- result = {
710
- status: "error",
711
- mode: "git",
712
- root: updateRoot,
713
- reason: cloneStep.name,
714
- steps: [cloneStep],
715
- durationMs: Date.now() - startedAt,
716
- };
717
- stop();
718
- printResult(result, { ...opts, hideSteps: showProgress });
719
- defaultRuntime.exit(1);
720
- return;
721
- }
722
- const updateResult = await runGatewayUpdate({
723
- cwd: updateRoot,
724
- argv1: switchToGit ? undefined : process.argv[1],
725
- timeoutMs,
726
- progress,
727
- channel,
728
- tag,
729
- });
730
- const steps = [...(cloneStep ? [cloneStep] : []), ...updateResult.steps];
731
- if (switchToGit && updateResult.status === "ok") {
732
- const manager = await resolveGlobalManager({
733
- root,
734
- installKind,
735
- timeoutMs: timeoutMs ?? 20 * 60_000,
736
- });
737
- const installStep = await runUpdateStep({
738
- name: "global install",
739
- argv: globalInstallArgs(manager, updateRoot),
740
- cwd: updateRoot,
741
- timeoutMs: timeoutMs ?? 20 * 60_000,
742
- progress,
743
- });
744
- steps.push(installStep);
745
- const failedStep = [installStep].find((step) => step.exitCode !== 0);
746
- result = {
747
- ...updateResult,
748
- status: updateResult.status === "ok" && !failedStep ? "ok" : "error",
749
- steps,
750
- durationMs: Date.now() - startedAt,
751
- };
752
- }
753
- else {
754
- result = {
755
- ...updateResult,
756
- steps,
757
- durationMs: Date.now() - startedAt,
758
- };
759
- }
760
- }
761
- stop();
762
- printResult(result, { ...opts, hideSteps: showProgress });
763
- if (result.status === "error") {
764
- defaultRuntime.exit(1);
765
- return;
766
- }
767
- if (result.status === "skipped") {
768
- if (result.reason === "dirty") {
769
- defaultRuntime.log(theme.warn("Skipped: working directory has uncommitted changes. Commit or stash them first."));
770
- }
771
- if (result.reason === "not-git-install") {
772
- defaultRuntime.log(theme.warn(`Skipped: this PoolBot install isn't a git checkout, and the package manager couldn't be detected. Update via your package manager, then run \`${replaceCliName(formatCliCommand("poolbot doctor"), CLI_NAME)}\` and \`${replaceCliName(formatCliCommand("poolbot gateway restart"), CLI_NAME)}\`.`));
773
- defaultRuntime.log(theme.muted(`Examples: \`${replaceCliName("npm i -g poolbot@latest", CLI_NAME)}\` or \`${replaceCliName("pnpm add -g poolbot@latest", CLI_NAME)}\``));
774
- }
775
- defaultRuntime.exit(0);
776
- return;
777
- }
778
- if (activeConfig) {
779
- const pluginLogger = opts.json
780
- ? {}
781
- : {
782
- info: (msg) => defaultRuntime.log(msg),
783
- warn: (msg) => defaultRuntime.log(theme.warn(msg)),
784
- error: (msg) => defaultRuntime.log(theme.error(msg)),
785
- };
786
- if (!opts.json) {
787
- defaultRuntime.log("");
788
- defaultRuntime.log(theme.heading("Updating plugins..."));
789
- }
790
- const syncResult = await syncPluginsForUpdateChannel({
791
- config: activeConfig,
792
- channel,
793
- workspaceDir: root,
794
- logger: pluginLogger,
795
- });
796
- let pluginConfig = syncResult.config;
797
- const npmResult = await updateNpmInstalledPlugins({
798
- config: pluginConfig,
799
- skipIds: new Set(syncResult.summary.switchedToNpm),
800
- logger: pluginLogger,
801
- });
802
- pluginConfig = npmResult.config;
803
- if (syncResult.changed || npmResult.changed) {
804
- await writeConfigFile(pluginConfig);
805
- }
806
- if (!opts.json) {
807
- const summarizeList = (list) => {
808
- if (list.length <= 6) {
809
- return list.join(", ");
810
- }
811
- return `${list.slice(0, 6).join(", ")} +${list.length - 6} more`;
812
- };
813
- if (syncResult.summary.switchedToBundled.length > 0) {
814
- defaultRuntime.log(theme.muted(`Switched to bundled plugins: ${summarizeList(syncResult.summary.switchedToBundled)}.`));
815
- }
816
- if (syncResult.summary.switchedToNpm.length > 0) {
817
- defaultRuntime.log(theme.muted(`Restored npm plugins: ${summarizeList(syncResult.summary.switchedToNpm)}.`));
818
- }
819
- for (const warning of syncResult.summary.warnings) {
820
- defaultRuntime.log(theme.warn(warning));
821
- }
822
- for (const error of syncResult.summary.errors) {
823
- defaultRuntime.log(theme.error(error));
824
- }
825
- const updated = npmResult.outcomes.filter((entry) => entry.status === "updated").length;
826
- const unchanged = npmResult.outcomes.filter((entry) => entry.status === "unchanged").length;
827
- const failed = npmResult.outcomes.filter((entry) => entry.status === "error").length;
828
- const skipped = npmResult.outcomes.filter((entry) => entry.status === "skipped").length;
829
- if (npmResult.outcomes.length === 0) {
830
- defaultRuntime.log(theme.muted("No plugin updates needed."));
831
- }
832
- else {
833
- const parts = [`${updated} updated`, `${unchanged} unchanged`];
834
- if (failed > 0) {
835
- parts.push(`${failed} failed`);
836
- }
837
- if (skipped > 0) {
838
- parts.push(`${skipped} skipped`);
839
- }
840
- defaultRuntime.log(theme.muted(`npm plugins: ${parts.join(", ")}.`));
841
- }
842
- for (const outcome of npmResult.outcomes) {
843
- if (outcome.status !== "error") {
844
- continue;
845
- }
846
- defaultRuntime.log(theme.error(outcome.message));
847
- }
848
- }
849
- }
850
- else if (!opts.json) {
851
- defaultRuntime.log(theme.warn("Skipping plugin updates: config is invalid."));
852
- }
853
- await tryWriteCompletionCache(root, Boolean(opts.json));
854
- // Offer to install shell completion if not already installed
855
- await tryInstallShellCompletion({
856
- jsonMode: Boolean(opts.json),
857
- skipPrompt: Boolean(opts.yes),
858
- });
859
- // Restart service if requested
860
- if (shouldRestart) {
861
- if (!opts.json) {
862
- defaultRuntime.log("");
863
- defaultRuntime.log(theme.heading("Restarting service..."));
864
- }
865
- try {
866
- const restarted = await runDaemonRestart();
867
- if (!opts.json && restarted) {
868
- defaultRuntime.log(theme.success("Daemon restarted successfully."));
869
- defaultRuntime.log("");
870
- process.env.POOLBOT_UPDATE_IN_PROGRESS = "1";
871
- process.env.CLAWDBOT_UPDATE_IN_PROGRESS = "1";
872
- try {
873
- const interactiveDoctor = Boolean(process.stdin.isTTY) && !opts.json && opts.yes !== true;
874
- await doctorCommand(defaultRuntime, {
875
- nonInteractive: !interactiveDoctor,
876
- });
877
- }
878
- catch (err) {
879
- defaultRuntime.log(theme.warn(`Doctor failed: ${String(err)}`));
880
- }
881
- finally {
882
- delete process.env.POOLBOT_UPDATE_IN_PROGRESS;
883
- delete process.env.CLAWDBOT_UPDATE_IN_PROGRESS;
884
- }
885
- }
886
- }
887
- catch (err) {
888
- if (!opts.json) {
889
- defaultRuntime.log(theme.warn(`Daemon restart failed: ${String(err)}`));
890
- defaultRuntime.log(theme.muted(`You may need to restart the service manually: ${replaceCliName(formatCliCommand("poolbot gateway restart"), CLI_NAME)}`));
891
- }
892
- }
893
- }
894
- else if (!opts.json) {
895
- defaultRuntime.log("");
896
- if (result.mode === "npm" || result.mode === "pnpm") {
897
- defaultRuntime.log(theme.muted(`Tip: Run \`${replaceCliName(formatCliCommand("poolbot doctor"), CLI_NAME)}\`, then \`${replaceCliName(formatCliCommand("poolbot gateway restart"), CLI_NAME)}\` to apply updates to a running gateway.`));
898
- }
899
- else {
900
- defaultRuntime.log(theme.muted(`Tip: Run \`${replaceCliName(formatCliCommand("poolbot gateway restart"), CLI_NAME)}\` to apply updates to a running gateway.`));
901
- }
902
- }
903
- if (!opts.json) {
904
- defaultRuntime.log(theme.muted(pickUpdateQuip()));
905
- }
6
+ import { updateStatusCommand } from "./update-cli/status.js";
7
+ import { updateCommand } from "./update-cli/update-command.js";
8
+ import { updateWizardCommand } from "./update-cli/wizard.js";
9
+ export { updateCommand, updateStatusCommand, updateWizardCommand };
10
+ function inheritedUpdateJson(command) {
11
+ return Boolean(inheritOptionFromParent(command, "json"));
906
12
  }
907
- export async function updateWizardCommand(opts = {}) {
908
- if (!process.stdin.isTTY) {
909
- defaultRuntime.error("Update wizard requires a TTY. Use `poolbot update --channel <stable|beta|dev>` instead.");
910
- defaultRuntime.exit(1);
911
- return;
912
- }
913
- const timeoutMs = opts.timeout ? Number.parseInt(opts.timeout, 10) * 1000 : undefined;
914
- if (timeoutMs !== undefined && (Number.isNaN(timeoutMs) || timeoutMs <= 0)) {
915
- defaultRuntime.error("--timeout must be a positive integer (seconds)");
916
- defaultRuntime.exit(1);
917
- return;
918
- }
919
- const root = (await resolvePoolBotPackageRoot({
920
- moduleUrl: import.meta.url,
921
- argv1: process.argv[1],
922
- cwd: process.cwd(),
923
- })) ?? process.cwd();
924
- const [updateStatus, configSnapshot] = await Promise.all([
925
- checkUpdateStatus({
926
- root,
927
- timeoutMs: timeoutMs ?? 3500,
928
- fetchGit: false,
929
- includeRegistry: false,
930
- }),
931
- readConfigFileSnapshot(),
932
- ]);
933
- const configChannel = configSnapshot.valid
934
- ? normalizeUpdateChannel(configSnapshot.config.update?.channel)
935
- : null;
936
- const channelInfo = resolveEffectiveUpdateChannel({
937
- configChannel,
938
- installKind: updateStatus.installKind,
939
- git: updateStatus.git
940
- ? { tag: updateStatus.git.tag, branch: updateStatus.git.branch }
941
- : undefined,
942
- });
943
- const channelLabel = formatUpdateChannelLabel({
944
- channel: channelInfo.channel,
945
- source: channelInfo.source,
946
- gitTag: updateStatus.git?.tag ?? null,
947
- gitBranch: updateStatus.git?.branch ?? null,
948
- });
949
- const pickedChannel = await selectStyled({
950
- message: "Update channel",
951
- options: [
952
- {
953
- value: "keep",
954
- label: `Keep current (${channelInfo.channel})`,
955
- hint: channelLabel,
956
- },
957
- {
958
- value: "stable",
959
- label: "Stable",
960
- hint: "Tagged releases (npm latest)",
961
- },
962
- {
963
- value: "beta",
964
- label: "Beta",
965
- hint: "Prereleases (npm beta)",
966
- },
967
- {
968
- value: "dev",
969
- label: "Dev",
970
- hint: "Git main",
971
- },
972
- ],
973
- initialValue: "keep",
974
- });
975
- if (isCancel(pickedChannel)) {
976
- defaultRuntime.log(theme.muted("Update cancelled."));
977
- defaultRuntime.exit(0);
978
- return;
979
- }
980
- const requestedChannel = pickedChannel === "keep" ? null : pickedChannel;
981
- if (requestedChannel === "dev" && updateStatus.installKind !== "git") {
982
- const gitDir = resolveGitInstallDir();
983
- const hasGit = await isGitCheckout(gitDir);
984
- if (!hasGit) {
985
- const dirExists = await pathExists(gitDir);
986
- if (dirExists) {
987
- const empty = await isEmptyDir(gitDir);
988
- if (!empty) {
989
- defaultRuntime.error(`POOLBOT_GIT_DIR points at a non-git directory: ${gitDir}. Set POOLBOT_GIT_DIR to an empty folder or a poolbot checkout.`);
990
- defaultRuntime.exit(1);
991
- return;
992
- }
993
- }
994
- const ok = await confirm({
995
- message: stylePromptMessage(`Create a git checkout at ${gitDir}? (override via POOLBOT_GIT_DIR)`),
996
- initialValue: true,
997
- });
998
- if (isCancel(ok) || !ok) {
999
- defaultRuntime.log(theme.muted("Update cancelled."));
1000
- defaultRuntime.exit(0);
1001
- return;
1002
- }
1003
- }
1004
- }
1005
- const restart = await confirm({
1006
- message: stylePromptMessage("Restart the gateway service after update?"),
1007
- initialValue: true,
1008
- });
1009
- if (isCancel(restart)) {
1010
- defaultRuntime.log(theme.muted("Update cancelled."));
1011
- defaultRuntime.exit(0);
1012
- return;
1013
- }
1014
- try {
1015
- await updateCommand({
1016
- channel: requestedChannel ?? undefined,
1017
- restart: Boolean(restart),
1018
- timeout: opts.timeout,
1019
- });
1020
- }
1021
- catch (err) {
1022
- defaultRuntime.error(String(err));
1023
- defaultRuntime.exit(1);
13
+ function inheritedUpdateTimeout(opts, command) {
14
+ const timeout = opts.timeout;
15
+ if (timeout) {
16
+ return timeout;
1024
17
  }
18
+ return inheritOptionFromParent(command, "timeout");
1025
19
  }
1026
20
  export function registerUpdateCli(program) {
1027
21
  const update = program
1028
22
  .command("update")
1029
- .description("Update PoolBot to the latest version")
23
+ .description("Update Pool Bot and inspect update channel status")
1030
24
  .option("--json", "Output result as JSON", false)
1031
25
  .option("--no-restart", "Skip restarting the gateway service after a successful update")
1032
26
  .option("--channel <stable|beta|dev>", "Persist update channel (git + npm)")
@@ -1071,7 +65,7 @@ ${theme.heading("Notes:")}
1071
65
  - Downgrades require confirmation (can break configuration)
1072
66
  - Skips update if the working directory has uncommitted changes
1073
67
 
1074
- ${theme.muted("Docs:")} ${formatDocsLink("/cli/update", "docs.molt.bot/cli/update")}`;
68
+ ${theme.muted("Docs:")} ${formatDocsLink("/cli/update", "docs.poolbot.dev/cli/update")}`;
1075
69
  })
1076
70
  .action(async (opts) => {
1077
71
  try {
@@ -1093,11 +87,11 @@ ${theme.muted("Docs:")} ${formatDocsLink("/cli/update", "docs.molt.bot/cli/updat
1093
87
  .command("wizard")
1094
88
  .description("Interactive update wizard")
1095
89
  .option("--timeout <seconds>", "Timeout for each update step in seconds (default: 1200)")
1096
- .addHelpText("after", `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/update", "docs.molt.bot/cli/update")}\n`)
1097
- .action(async (opts) => {
90
+ .addHelpText("after", `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/update", "docs.poolbot.dev/cli/update")}\n`)
91
+ .action(async (opts, command) => {
1098
92
  try {
1099
93
  await updateWizardCommand({
1100
- timeout: opts.timeout,
94
+ timeout: inheritedUpdateTimeout(opts, command),
1101
95
  });
1102
96
  }
1103
97
  catch (err) {
@@ -1114,12 +108,12 @@ ${theme.muted("Docs:")} ${formatDocsLink("/cli/update", "docs.molt.bot/cli/updat
1114
108
  ["poolbot update status", "Show channel + version status."],
1115
109
  ["poolbot update status --json", "JSON output."],
1116
110
  ["poolbot update status --timeout 10", "Custom timeout."],
1117
- ])}\n\n${theme.heading("Notes:")}\n${theme.muted("- Shows current update channel (stable/beta/dev) and source")}\n${theme.muted("- Includes git tag/branch/SHA for source checkouts")}\n\n${theme.muted("Docs:")} ${formatDocsLink("/cli/update", "docs.molt.bot/cli/update")}`)
1118
- .action(async (opts) => {
111
+ ])}\n\n${theme.heading("Notes:")}\n${theme.muted("- Shows current update channel (stable/beta/dev) and source")}\n${theme.muted("- Includes git tag/branch/SHA for source checkouts")}\n\n${theme.muted("Docs:")} ${formatDocsLink("/cli/update", "docs.poolbot.dev/cli/update")}`)
112
+ .action(async (opts, command) => {
1119
113
  try {
1120
114
  await updateStatusCommand({
1121
- json: Boolean(opts.json),
1122
- timeout: opts.timeout,
115
+ json: Boolean(opts.json) || inheritedUpdateJson(command),
116
+ timeout: inheritedUpdateTimeout(opts, command),
1123
117
  });
1124
118
  }
1125
119
  catch (err) {