@poolzin/pool-bot 2026.2.20 → 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 (388) hide show
  1. package/CHANGELOG.md +25 -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-auth.js +12 -0
  14. package/dist/agents/model-catalog.js +40 -9
  15. package/dist/agents/model-fallback.js +24 -0
  16. package/dist/agents/model-forward-compat.js +60 -23
  17. package/dist/agents/model-selection.js +134 -41
  18. package/dist/agents/pi-auth-json.js +2 -2
  19. package/dist/agents/pi-embedded-helpers/bootstrap.js +65 -15
  20. package/dist/agents/pi-embedded-helpers/errors.js +140 -15
  21. package/dist/agents/pi-embedded-helpers/images.js +22 -12
  22. package/dist/agents/pi-embedded-helpers.js +2 -2
  23. package/dist/agents/pi-embedded-runner/abort.js +10 -3
  24. package/dist/agents/pi-embedded-runner/compact.js +230 -32
  25. package/dist/agents/pi-embedded-runner/extra-params.js +203 -12
  26. package/dist/agents/pi-embedded-runner/google.js +109 -19
  27. package/dist/agents/pi-embedded-runner/history.js +35 -17
  28. package/dist/agents/pi-embedded-runner/run/attempt.js +386 -80
  29. package/dist/agents/pi-embedded-runner/run/images.js +81 -55
  30. package/dist/agents/pi-embedded-runner/run/payloads.js +89 -39
  31. package/dist/agents/pi-embedded-runner/run.js +193 -25
  32. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +2 -2
  33. package/dist/agents/pi-embedded-runner/runs.js +17 -8
  34. package/dist/agents/pi-embedded-runner/tool-result-context-guard.js +262 -0
  35. package/dist/agents/pi-embedded-runner.js +1 -1
  36. package/dist/agents/pi-embedded-subscribe.handlers.tools.js +180 -10
  37. package/dist/agents/pi-embedded-subscribe.js +37 -0
  38. package/dist/agents/pi-embedded-subscribe.tools.js +127 -30
  39. package/dist/agents/pi-model-discovery.js +9 -2
  40. package/dist/agents/pi-tool-definition-adapter.js +60 -8
  41. package/dist/agents/pi-tools.before-tool-call.js +1 -1
  42. package/dist/agents/pi-tools.js +113 -94
  43. package/dist/agents/pi-tools.read.js +337 -38
  44. package/dist/agents/poolbot-tools.js +14 -5
  45. package/dist/agents/provider/config-loader.js +76 -0
  46. package/dist/agents/provider/index.js +15 -0
  47. package/dist/agents/provider/integration.js +136 -0
  48. package/dist/agents/provider/models-dev.js +129 -0
  49. package/dist/agents/provider/rate-limits.js +458 -0
  50. package/dist/agents/provider/request-monitor.js +449 -0
  51. package/dist/agents/provider/session-binding.js +376 -0
  52. package/dist/agents/provider/token-pool.js +541 -0
  53. package/dist/agents/sandbox/docker.js +10 -5
  54. package/dist/agents/sandbox/registry.js +96 -46
  55. package/dist/agents/sandbox/sanitize-env-vars.js +82 -0
  56. package/dist/agents/sandbox-paths.js +43 -10
  57. package/dist/agents/session-tool-result-guard-wrapper.js +23 -11
  58. package/dist/agents/session-tool-result-guard.js +39 -39
  59. package/dist/agents/session-transcript-repair.js +36 -33
  60. package/dist/agents/session-write-lock.js +62 -44
  61. package/dist/agents/skills/frontmatter.js +49 -88
  62. package/dist/agents/skills/workspace.js +335 -28
  63. package/dist/agents/subagent-announce.js +508 -174
  64. package/dist/agents/subagent-registry.js +45 -4
  65. package/dist/agents/subagent-spawn.js +16 -33
  66. package/dist/agents/system-prompt-report.js +27 -10
  67. package/dist/agents/system-prompt.js +26 -32
  68. package/dist/agents/tool-call-id.js +69 -17
  69. package/dist/agents/tool-display-common.js +1 -1
  70. package/dist/agents/tool-images.js +64 -31
  71. package/dist/agents/tools/canvas-tool.js +17 -11
  72. package/dist/agents/tools/common.js +37 -19
  73. package/dist/agents/tools/cron-tool.js +40 -38
  74. package/dist/agents/tools/gateway.js +70 -2
  75. package/dist/agents/tools/message-tool.js +181 -40
  76. package/dist/agents/tools/nodes-tool.js +128 -36
  77. package/dist/agents/tools/nodes-utils.js +12 -38
  78. package/dist/agents/tools/session-status-tool.js +24 -71
  79. package/dist/agents/tools/sessions-helpers.js +38 -210
  80. package/dist/agents/tools/sessions-spawn-tool.js +28 -198
  81. package/dist/agents/tools/telegram-actions.js +58 -7
  82. package/dist/agents/tools/web-fetch-utils.js +112 -7
  83. package/dist/agents/tools/web-fetch.js +279 -175
  84. package/dist/agents/tools/web-shared.js +71 -8
  85. package/dist/agents/usage.js +25 -16
  86. package/dist/auto-reply/commands-registry.data.js +85 -11
  87. package/dist/auto-reply/dispatch.js +40 -21
  88. package/dist/auto-reply/reply/abort.js +102 -33
  89. package/dist/auto-reply/reply/commands-core.js +82 -33
  90. package/dist/auto-reply/reply/commands-export-session.js +1 -1
  91. package/dist/auto-reply/reply/commands-info.js +41 -12
  92. package/dist/auto-reply/reply/commands-subagents.js +352 -100
  93. package/dist/auto-reply/reply/commands-system-prompt.js +2 -2
  94. package/dist/auto-reply/reply/dispatch-from-config.js +100 -29
  95. package/dist/auto-reply/reply/elevated-unavailable.js +1 -1
  96. package/dist/auto-reply/reply/inbound-meta.js +12 -1
  97. package/dist/auto-reply/reply/mentions.js +18 -11
  98. package/dist/auto-reply/reply/normalize-reply.js +17 -8
  99. package/dist/auto-reply/reply/reply-dispatcher.js +62 -10
  100. package/dist/auto-reply/reply/session.js +102 -21
  101. package/dist/auto-reply/reply/streaming-directives.js +16 -5
  102. package/dist/auto-reply/status.js +73 -50
  103. package/dist/browser/extension-relay.js +3 -3
  104. package/dist/browser/http-auth.js +1 -1
  105. package/dist/browser/paths.js +2 -2
  106. package/dist/build-info.json +3 -3
  107. package/dist/channels/allowlist-match.js +20 -0
  108. package/dist/channels/allowlists/resolve-utils.js +65 -2
  109. package/dist/channels/chat-type.js +8 -4
  110. package/dist/channels/dock.js +127 -35
  111. package/dist/channels/draft-stream-loop.js +6 -2
  112. package/dist/channels/plugins/actions/telegram.js +42 -18
  113. package/dist/channels/plugins/allowlist-match.js +1 -1
  114. package/dist/channels/plugins/group-mentions.js +51 -41
  115. package/dist/channels/plugins/message-action-names.js +2 -0
  116. package/dist/channels/plugins/message-actions.js +24 -5
  117. package/dist/channels/plugins/normalize/discord.js +26 -4
  118. package/dist/channels/plugins/normalize/signal.js +35 -22
  119. package/dist/channels/plugins/onboarding/helpers.js +8 -26
  120. package/dist/channels/plugins/outbound/imessage.js +15 -14
  121. package/dist/channels/registry.js +20 -7
  122. package/dist/cli/acp-cli.js +7 -5
  123. package/dist/cli/browser-cli-extension.js +25 -12
  124. package/dist/cli/browser-cli-state.cookies-storage.js +25 -6
  125. package/dist/cli/browser-cli-state.js +101 -145
  126. package/dist/cli/command-options.js +28 -0
  127. package/dist/cli/completion-cli.js +6 -6
  128. package/dist/cli/cron-cli/register.cron-add.js +25 -1
  129. package/dist/cli/cron-cli/register.cron-edit.js +44 -0
  130. package/dist/cli/cron-cli/shared.js +7 -1
  131. package/dist/cli/daemon-cli/lifecycle-core.js +23 -21
  132. package/dist/cli/daemon-cli/lifecycle.js +23 -247
  133. package/dist/cli/daemon-cli/register-service-commands.js +25 -4
  134. package/dist/cli/daemon-cli.js +1 -0
  135. package/dist/cli/devices-cli.js +33 -20
  136. package/dist/cli/gateway-cli/register.js +37 -105
  137. package/dist/cli/gateway-cli/run.js +49 -11
  138. package/dist/cli/nodes-camera.js +59 -4
  139. package/dist/cli/nodes-cli/register.camera.js +27 -24
  140. package/dist/cli/nodes-cli/rpc.js +21 -38
  141. package/dist/cli/qr-cli.js +2 -2
  142. package/dist/cli/skills-cli.format.js +2 -2
  143. package/dist/cli/update-cli/progress.js +2 -2
  144. package/dist/cli/update-cli/restart-helper.js +28 -7
  145. package/dist/cli/update-cli/shared.js +7 -7
  146. package/dist/cli/update-cli/status.js +1 -1
  147. package/dist/cli/update-cli/update-command.js +14 -8
  148. package/dist/cli/update-cli/wizard.js +2 -2
  149. package/dist/cli/update-cli.js +21 -1027
  150. package/dist/commands/auth-choice.apply.anthropic.js +10 -2
  151. package/dist/commands/channels/add-mutators.js +3 -35
  152. package/dist/commands/channels/add.js +39 -51
  153. package/dist/commands/config-validation.js +1 -1
  154. package/dist/commands/configure.gateway-auth.js +52 -15
  155. package/dist/commands/configure.gateway.js +84 -40
  156. package/dist/commands/doctor-completion.js +3 -3
  157. package/dist/commands/doctor-config-flow.js +536 -16
  158. package/dist/commands/doctor-gateway-services.js +103 -79
  159. package/dist/commands/doctor-memory-search.js +9 -9
  160. package/dist/commands/doctor-platform-notes.js +57 -30
  161. package/dist/commands/doctor-prompter.js +26 -15
  162. package/dist/commands/doctor-session-locks.js +1 -1
  163. package/dist/commands/doctor.js +21 -9
  164. package/dist/commands/model-picker.js +120 -95
  165. package/dist/commands/models/set.js +2 -21
  166. package/dist/commands/models/shared.js +65 -37
  167. package/dist/commands/onboard-helpers.js +81 -39
  168. package/dist/commands/openai-codex-oauth.js +1 -1
  169. package/dist/commands/sessions.js +52 -53
  170. package/dist/commands/status.summary.js +52 -34
  171. package/dist/commands/test-wizard-helpers.js +2 -2
  172. package/dist/config/defaults.js +79 -42
  173. package/dist/config/group-policy.js +50 -18
  174. package/dist/config/includes.js +37 -10
  175. package/dist/config/schema.help.js +5 -4
  176. package/dist/config/schema.hints.js +2 -2
  177. package/dist/config/schema.labels.js +1 -0
  178. package/dist/config/sessions/group.js +12 -11
  179. package/dist/config/sessions/paths.js +137 -11
  180. package/dist/config/sessions/store.js +185 -65
  181. package/dist/config/sessions/types.js +15 -1
  182. package/dist/config/sessions.js +1 -0
  183. package/dist/config/telegram-custom-commands.js +3 -2
  184. package/dist/config/types.js +2 -0
  185. package/dist/config/zod-schema.agent-defaults.js +6 -27
  186. package/dist/config/zod-schema.agent-runtime.js +171 -79
  187. package/dist/config/zod-schema.providers-core.js +138 -65
  188. package/dist/config/zod-schema.session.js +49 -22
  189. package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
  190. package/dist/cron/isolated-agent/run.js +224 -57
  191. package/dist/cron/normalize.js +48 -45
  192. package/dist/cron/run-log.js +14 -0
  193. package/dist/cron/service/jobs.js +190 -28
  194. package/dist/cron/service/normalize.js +29 -11
  195. package/dist/cron/service/store.js +30 -44
  196. package/dist/cron/service/timer.js +182 -96
  197. package/dist/cron/service.js +3 -0
  198. package/dist/cron/stagger.js +37 -0
  199. package/dist/daemon/inspect.js +132 -92
  200. package/dist/daemon/runtime-paths.js +25 -4
  201. package/dist/daemon/service-audit.js +47 -16
  202. package/dist/discord/accounts.js +23 -20
  203. package/dist/discord/monitor/agent-components.js +1115 -219
  204. package/dist/discord/monitor/allow-list.js +114 -34
  205. package/dist/discord/monitor/listeners.js +204 -97
  206. package/dist/discord/monitor/message-handler.js +21 -10
  207. package/dist/discord/monitor/message-handler.preflight.js +195 -101
  208. package/dist/discord/monitor/message-handler.process.js +384 -123
  209. package/dist/discord/monitor/message-utils.js +86 -23
  210. package/dist/discord/monitor/native-command.js +77 -57
  211. package/dist/discord/monitor/provider.js +122 -117
  212. package/dist/discord/monitor/reply-context.js +20 -16
  213. package/dist/discord/monitor/reply-delivery.js +40 -8
  214. package/dist/discord/monitor/rest-fetch.js +22 -0
  215. package/dist/discord/monitor/threading.js +117 -24
  216. package/dist/discord/send.js +2 -1
  217. package/dist/discord/send.outbound.js +124 -11
  218. package/dist/discord/send.shared.js +112 -72
  219. package/dist/discord/voice-message.js +3 -3
  220. package/dist/gateway/auth.js +119 -44
  221. package/dist/gateway/call.js +76 -34
  222. package/dist/gateway/channel-health-monitor.js +57 -50
  223. package/dist/gateway/client.js +63 -29
  224. package/dist/gateway/control-ui-contract.js +1 -1
  225. package/dist/gateway/gateway-config-prompts.shared.js +2 -2
  226. package/dist/gateway/net.js +109 -1
  227. package/dist/gateway/protocol/index.js +5 -8
  228. package/dist/gateway/protocol/schema/agent.js +19 -1
  229. package/dist/gateway/protocol/schema/channels.js +21 -0
  230. package/dist/gateway/protocol/schema/cron.js +43 -30
  231. package/dist/gateway/protocol/schema/protocol-schemas.js +6 -11
  232. package/dist/gateway/protocol/schema/sessions.js +5 -1
  233. package/dist/gateway/protocol/schema.js +0 -1
  234. package/dist/gateway/server/presence-events.js +12 -0
  235. package/dist/gateway/server/ws-connection/message-handler.js +203 -212
  236. package/dist/gateway/server/ws-connection.js +58 -21
  237. package/dist/gateway/server-broadcast.js +18 -13
  238. package/dist/gateway/server-cron.js +177 -10
  239. package/dist/gateway/server-methods/agent-job.js +131 -38
  240. package/dist/gateway/server-methods/send.js +60 -14
  241. package/dist/gateway/server-methods/sessions.js +160 -96
  242. package/dist/gateway/server-methods/system.js +5 -7
  243. package/dist/gateway/server-methods-list.js +8 -0
  244. package/dist/gateway/server-methods.js +24 -8
  245. package/dist/gateway/server-node-events.js +278 -68
  246. package/dist/gateway/session-utils.fs.js +316 -75
  247. package/dist/gateway/session-utils.js +224 -70
  248. package/dist/gateway/sessions-patch.js +63 -20
  249. package/dist/gateway/test-temp-config.js +1 -1
  250. package/dist/gateway/tools-invoke-http.js +118 -70
  251. package/dist/gateway/ws-log.js +135 -107
  252. package/dist/hooks/frontmatter.js +36 -82
  253. package/dist/hooks/install.js +149 -139
  254. package/dist/hooks/internal-hooks.js +29 -4
  255. package/dist/hooks/plugin-hooks.js +2 -1
  256. package/dist/imessage/monitor/deliver.js +10 -4
  257. package/dist/imessage/monitor/monitor-provider.js +138 -375
  258. package/dist/imessage/monitor/runtime.js +4 -8
  259. package/dist/imessage/send.js +65 -19
  260. package/dist/infra/exec-approvals-allowlist.js +7 -0
  261. package/dist/infra/exec-approvals.js +35 -920
  262. package/dist/infra/exec-safe-bin-trust.js +64 -0
  263. package/dist/infra/heartbeat-runner.js +207 -134
  264. package/dist/infra/heartbeat-wake.js +183 -22
  265. package/dist/infra/install-source-utils.js +47 -0
  266. package/dist/infra/net/ssrf.js +170 -36
  267. package/dist/infra/outbound/deliver.js +224 -58
  268. package/dist/infra/outbound/message-action-spec.js +12 -5
  269. package/dist/infra/outbound/outbound-session.js +27 -25
  270. package/dist/infra/poolbot-root.js +32 -22
  271. package/dist/infra/ports.js +14 -11
  272. package/dist/infra/skills-remote.js +48 -37
  273. package/dist/infra/system-events.js +25 -11
  274. package/dist/infra/system-presence.js +26 -33
  275. package/dist/infra/tmp-poolbot-dir.js +81 -2
  276. package/dist/infra/wsl.js +37 -1
  277. package/dist/line/bot-message-context.js +163 -191
  278. package/dist/logging/subsystem.js +59 -22
  279. package/dist/markdown/ir.js +124 -50
  280. package/dist/media/store.js +1 -1
  281. package/dist/media-understanding/runner.entries.js +42 -25
  282. package/dist/media-understanding/runner.js +53 -488
  283. package/dist/memory/embeddings-gemini.js +53 -38
  284. package/dist/memory/manager-embedding-ops.js +48 -69
  285. package/dist/pairing/pairing-store.js +178 -119
  286. package/dist/plugin-sdk/index.js +34 -6
  287. package/dist/plugins/hooks.js +135 -14
  288. package/dist/plugins/install.js +190 -152
  289. package/dist/polls.js +11 -0
  290. package/dist/routing/resolve-route.js +190 -56
  291. package/dist/routing/session-key.js +38 -22
  292. package/dist/runtime.js +35 -9
  293. package/dist/security/audit-channel.js +1 -1
  294. package/dist/sessions/session-key-utils.js +29 -11
  295. package/dist/shared/frontmatter.js +5 -5
  296. package/dist/shared/node-list-types.js +1 -0
  297. package/dist/shared/string-normalization.js +15 -0
  298. package/dist/signal/monitor/event-handler.js +68 -36
  299. package/dist/signal/send.js +29 -37
  300. package/dist/slack/monitor/allow-list.js +10 -11
  301. package/dist/slack/monitor/commands.js +14 -3
  302. package/dist/slack/monitor/events/interactions.js +4 -4
  303. package/dist/slack/monitor/media.js +224 -16
  304. package/dist/slack/monitor/message-handler/dispatch.js +247 -13
  305. package/dist/slack/monitor/message-handler/prepare.js +128 -45
  306. package/dist/slack/monitor/slash.js +357 -144
  307. package/dist/slack/streaming.js +77 -0
  308. package/dist/telegram/accounts.js +40 -13
  309. package/dist/telegram/allowed-updates.js +3 -0
  310. package/dist/telegram/bot/delivery.js +129 -66
  311. package/dist/telegram/bot/helpers.js +136 -122
  312. package/dist/telegram/bot-handlers.js +600 -339
  313. package/dist/telegram/bot-message-context.js +115 -73
  314. package/dist/telegram/bot-message-dispatch.js +235 -104
  315. package/dist/telegram/bot-native-command-menu.js +3 -1
  316. package/dist/telegram/bot-native-commands.js +213 -193
  317. package/dist/telegram/bot.js +24 -132
  318. package/dist/telegram/draft-stream.js +84 -75
  319. package/dist/telegram/format.js +150 -6
  320. package/dist/telegram/send.js +415 -255
  321. package/dist/telegram/targets.js +21 -2
  322. package/dist/telegram/update-offset-store.js +19 -3
  323. package/dist/terminal/restore.js +5 -2
  324. package/dist/test-utils/fetch-mock.js +5 -0
  325. package/dist/version.js +18 -5
  326. package/dist/web/auto-reply/monitor/broadcast.js +7 -3
  327. package/dist/web/auto-reply/monitor/on-message.js +6 -3
  328. package/dist/web/inbound/media.js +34 -8
  329. package/dist/web/inbound/monitor.js +34 -17
  330. package/dist/web/inbound/send-api.js +18 -17
  331. package/dist/web/outbound.js +12 -5
  332. package/dist/wizard/clack-prompter.js +40 -7
  333. package/extensions/bluebubbles/package.json +1 -1
  334. package/extensions/copilot-proxy/package.json +1 -1
  335. package/extensions/diagnostics-otel/package.json +1 -1
  336. package/extensions/discord/package.json +1 -1
  337. package/extensions/feishu/package.json +1 -1
  338. package/extensions/google-antigravity-auth/package.json +1 -1
  339. package/extensions/google-gemini-cli-auth/package.json +1 -1
  340. package/extensions/googlechat/package.json +1 -1
  341. package/extensions/imessage/package.json +1 -1
  342. package/extensions/irc/package.json +1 -1
  343. package/extensions/line/package.json +1 -1
  344. package/extensions/llm-task/package.json +1 -1
  345. package/extensions/lobster/package.json +1 -1
  346. package/extensions/matrix/CHANGELOG.md +5 -0
  347. package/extensions/matrix/package.json +1 -1
  348. package/extensions/mattermost/package.json +1 -1
  349. package/extensions/memory-core/package.json +1 -1
  350. package/extensions/memory-lancedb/package.json +1 -1
  351. package/extensions/minimax-portal-auth/package.json +1 -1
  352. package/extensions/msteams/CHANGELOG.md +5 -0
  353. package/extensions/msteams/package.json +1 -1
  354. package/extensions/nextcloud-talk/package.json +1 -1
  355. package/extensions/nostr/CHANGELOG.md +5 -0
  356. package/extensions/nostr/package.json +1 -1
  357. package/extensions/open-prose/package.json +1 -1
  358. package/extensions/openai-codex-auth/package.json +1 -1
  359. package/extensions/signal/package.json +1 -1
  360. package/extensions/slack/package.json +1 -1
  361. package/extensions/telegram/package.json +1 -1
  362. package/extensions/tlon/package.json +1 -1
  363. package/extensions/twitch/CHANGELOG.md +5 -0
  364. package/extensions/twitch/package.json +1 -1
  365. package/extensions/voice-call/CHANGELOG.md +5 -0
  366. package/extensions/voice-call/package.json +1 -1
  367. package/extensions/whatsapp/package.json +1 -1
  368. package/extensions/zalo/CHANGELOG.md +5 -0
  369. package/extensions/zalo/package.json +1 -1
  370. package/extensions/zalouser/CHANGELOG.md +5 -0
  371. package/extensions/zalouser/package.json +1 -1
  372. package/package.json +1 -1
  373. package/skills/apple-reminders/SKILL.md +100 -49
  374. package/skills/coding-agent/SKILL.md +34 -28
  375. package/skills/github/SKILL.md +131 -16
  376. package/skills/imsg/SKILL.md +112 -15
  377. package/skills/openhue/SKILL.md +101 -19
  378. package/skills/plcode-controller/SKILL.md +156 -0
  379. package/skills/plcode-controller/assets/operator-prompts.md +65 -0
  380. package/skills/plcode-controller/references/command-cheatsheet.md +53 -0
  381. package/skills/plcode-controller/references/failure-handling.md +60 -0
  382. package/skills/plcode-controller/references/model-selection.md +57 -0
  383. package/skills/plcode-controller/references/plan-vs-build.md +52 -0
  384. package/skills/plcode-controller/references/question-handling.md +40 -0
  385. package/skills/plcode-controller/references/session-management.md +63 -0
  386. package/skills/plcode-controller/references/workflow.md +35 -0
  387. package/skills/tmux/SKILL.md +111 -79
  388. package/skills/weather/SKILL.md +88 -25
@@ -4,50 +4,18 @@ import { loadConfig } from "../../config/config.js";
4
4
  import { discoverGatewayBeacons } from "../../infra/bonjour-discovery.js";
5
5
  import { resolveWideAreaDiscoveryDomain } from "../../infra/widearea-dns.js";
6
6
  import { defaultRuntime } from "../../runtime.js";
7
+ import { styleHealthChannelLine } from "../../terminal/health-style.js";
7
8
  import { formatDocsLink } from "../../terminal/links.js";
8
9
  import { colorize, isRich, theme } from "../../terminal/theme.js";
9
10
  import { formatTokenCount, formatUsd } from "../../utils/usage-format.js";
10
11
  import { runCommandWithRuntime } from "../cli-utils.js";
11
- import { runDaemonInstall, runDaemonRestart, runDaemonStart, runDaemonStatus, runDaemonStop, runDaemonUninstall, } from "../daemon-cli.js";
12
+ import { inheritOptionFromParent } from "../command-options.js";
13
+ import { addGatewayServiceCommands } from "../daemon-cli.js";
14
+ import { formatHelpExamples } from "../help-format.js";
12
15
  import { withProgress } from "../progress.js";
13
16
  import { callGatewayCli, gatewayCallOpts } from "./call.js";
14
17
  import { dedupeBeacons, parseDiscoverTimeoutMs, pickBeaconHost, pickGatewayPort, renderBeaconLines, } from "./discover.js";
15
18
  import { addGatewayRunCommand } from "./run.js";
16
- function styleHealthChannelLine(line, rich) {
17
- if (!rich) {
18
- return line;
19
- }
20
- const colon = line.indexOf(":");
21
- if (colon === -1) {
22
- return line;
23
- }
24
- const label = line.slice(0, colon + 1);
25
- const detail = line.slice(colon + 1).trimStart();
26
- const normalized = detail.toLowerCase();
27
- const applyPrefix = (prefix, color) => `${label} ${color(detail.slice(0, prefix.length))}${detail.slice(prefix.length)}`;
28
- if (normalized.startsWith("failed")) {
29
- return applyPrefix("failed", theme.error);
30
- }
31
- if (normalized.startsWith("ok")) {
32
- return applyPrefix("ok", theme.success);
33
- }
34
- if (normalized.startsWith("linked")) {
35
- return applyPrefix("linked", theme.success);
36
- }
37
- if (normalized.startsWith("configured")) {
38
- return applyPrefix("configured", theme.success);
39
- }
40
- if (normalized.startsWith("not linked")) {
41
- return applyPrefix("not linked", theme.warn);
42
- }
43
- if (normalized.startsWith("not configured")) {
44
- return applyPrefix("not configured", theme.muted);
45
- }
46
- if (normalized.startsWith("unknown")) {
47
- return applyPrefix("unknown", theme.warn);
48
- }
49
- return line;
50
- }
51
19
  function runGatewayCommand(action, label) {
52
20
  return runCommandWithRuntime(defaultRuntime, action, (err) => {
53
21
  const message = String(err);
@@ -67,6 +35,15 @@ function parseDaysOption(raw, fallback = 30) {
67
35
  }
68
36
  return fallback;
69
37
  }
38
+ function resolveGatewayRpcOptions(opts, command) {
39
+ const parentToken = inheritOptionFromParent(command, "token");
40
+ const parentPassword = inheritOptionFromParent(command, "password");
41
+ return {
42
+ ...opts,
43
+ token: opts.token ?? parentToken,
44
+ password: opts.password ?? parentPassword,
45
+ };
46
+ }
70
47
  function renderCostUsageSummary(summary, days, rich) {
71
48
  const totalCost = formatUsd(summary.totals.totalCost) ?? "$0.00";
72
49
  const totalTokens = formatTokenCount(summary.totals.totalTokens) ?? "0";
@@ -88,76 +65,28 @@ function renderCostUsageSummary(summary, days, rich) {
88
65
  export function registerGatewayCli(program) {
89
66
  const gateway = addGatewayRunCommand(program
90
67
  .command("gateway")
91
- .description("Run the WebSocket Gateway")
92
- .addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/gateway", "docs.molt.bot/cli/gateway")}\n`));
68
+ .description("Run, inspect, and query the WebSocket Gateway")
69
+ .addHelpText("after", () => `\n${theme.heading("Examples:")}\n${formatHelpExamples([
70
+ ["poolbot gateway run", "Run the gateway in the foreground."],
71
+ ["poolbot gateway status", "Show service status and probe reachability."],
72
+ ["poolbot gateway discover", "Find local and wide-area gateway beacons."],
73
+ ["poolbot gateway call health", "Call a gateway RPC method directly."],
74
+ ])}\n\n${theme.muted("Docs:")} ${formatDocsLink("/cli/gateway", "docs.poolbot.ai/cli/gateway")}\n`));
93
75
  addGatewayRunCommand(gateway.command("run").description("Run the WebSocket Gateway (foreground)"));
94
- gateway
95
- .command("status")
96
- .description("Show gateway service status + probe the Gateway")
97
- .option("--url <url>", "Gateway WebSocket URL (defaults to config/remote/local)")
98
- .option("--token <token>", "Gateway token (if required)")
99
- .option("--password <password>", "Gateway password (password auth)")
100
- .option("--timeout <ms>", "Timeout in ms", "10000")
101
- .option("--no-probe", "Skip RPC probe")
102
- .option("--deep", "Scan system-level services", false)
103
- .option("--json", "Output JSON", false)
104
- .action(async (opts) => {
105
- await runDaemonStatus({
106
- rpc: opts,
107
- probe: Boolean(opts.probe),
108
- deep: Boolean(opts.deep),
109
- json: Boolean(opts.json),
110
- });
111
- });
112
- gateway
113
- .command("install")
114
- .description("Install the Gateway service (launchd/systemd/schtasks)")
115
- .option("--port <port>", "Gateway port")
116
- .option("--runtime <runtime>", "Daemon runtime (node|bun). Default: node")
117
- .option("--token <token>", "Gateway token (token auth)")
118
- .option("--force", "Reinstall/overwrite if already installed", false)
119
- .option("--json", "Output JSON", false)
120
- .action(async (opts) => {
121
- await runDaemonInstall(opts);
122
- });
123
- gateway
124
- .command("uninstall")
125
- .description("Uninstall the Gateway service (launchd/systemd/schtasks)")
126
- .option("--json", "Output JSON", false)
127
- .action(async (opts) => {
128
- await runDaemonUninstall(opts);
129
- });
130
- gateway
131
- .command("start")
132
- .description("Start the Gateway service (launchd/systemd/schtasks)")
133
- .option("--json", "Output JSON", false)
134
- .action(async (opts) => {
135
- await runDaemonStart(opts);
136
- });
137
- gateway
138
- .command("stop")
139
- .description("Stop the Gateway service (launchd/systemd/schtasks)")
140
- .option("--json", "Output JSON", false)
141
- .action(async (opts) => {
142
- await runDaemonStop(opts);
143
- });
144
- gateway
145
- .command("restart")
146
- .description("Restart the Gateway service (launchd/systemd/schtasks)")
147
- .option("--json", "Output JSON", false)
148
- .action(async (opts) => {
149
- await runDaemonRestart(opts);
76
+ addGatewayServiceCommands(gateway, {
77
+ statusDescription: "Show gateway service status + probe the Gateway",
150
78
  });
151
79
  gatewayCallOpts(gateway
152
80
  .command("call")
153
81
  .description("Call a Gateway method")
154
82
  .argument("<method>", "Method name (health/status/system-presence/cron.*)")
155
83
  .option("--params <json>", "JSON object string for params", "{}")
156
- .action(async (method, opts) => {
84
+ .action(async (method, opts, command) => {
157
85
  await runGatewayCommand(async () => {
86
+ const rpcOpts = resolveGatewayRpcOptions(opts, command);
158
87
  const params = JSON.parse(String(opts.params ?? "{}"));
159
- const result = await callGatewayCli(method, opts, params);
160
- if (opts.json) {
88
+ const result = await callGatewayCli(method, rpcOpts, params);
89
+ if (rpcOpts.json) {
161
90
  defaultRuntime.log(JSON.stringify(result, null, 2));
162
91
  return;
163
92
  }
@@ -170,11 +99,12 @@ export function registerGatewayCli(program) {
170
99
  .command("usage-cost")
171
100
  .description("Fetch usage cost summary from session logs")
172
101
  .option("--days <days>", "Number of days to include", "30")
173
- .action(async (opts) => {
102
+ .action(async (opts, command) => {
174
103
  await runGatewayCommand(async () => {
104
+ const rpcOpts = resolveGatewayRpcOptions(opts, command);
175
105
  const days = parseDaysOption(opts.days);
176
- const result = await callGatewayCli("usage.cost", opts, { days });
177
- if (opts.json) {
106
+ const result = await callGatewayCli("usage.cost", rpcOpts, { days });
107
+ if (rpcOpts.json) {
178
108
  defaultRuntime.log(JSON.stringify(result, null, 2));
179
109
  return;
180
110
  }
@@ -188,10 +118,11 @@ export function registerGatewayCli(program) {
188
118
  gatewayCallOpts(gateway
189
119
  .command("health")
190
120
  .description("Fetch Gateway health")
191
- .action(async (opts) => {
121
+ .action(async (opts, command) => {
192
122
  await runGatewayCommand(async () => {
193
- const result = await callGatewayCli("health", opts);
194
- if (opts.json) {
123
+ const rpcOpts = resolveGatewayRpcOptions(opts, command);
124
+ const result = await callGatewayCli("health", rpcOpts);
125
+ if (rpcOpts.json) {
195
126
  defaultRuntime.log(JSON.stringify(result, null, 2));
196
127
  return;
197
128
  }
@@ -218,9 +149,10 @@ export function registerGatewayCli(program) {
218
149
  .option("--password <password>", "Gateway password (applies to all probes)")
219
150
  .option("--timeout <ms>", "Overall probe budget in ms", "3000")
220
151
  .option("--json", "Output JSON", false)
221
- .action(async (opts) => {
152
+ .action(async (opts, command) => {
222
153
  await runGatewayCommand(async () => {
223
- await gatewayStatusCommand(opts, defaultRuntime);
154
+ const rpcOpts = resolveGatewayRpcOptions(opts, command);
155
+ await gatewayStatusCommand(rpcOpts, defaultRuntime);
224
156
  });
225
157
  });
226
158
  gateway
@@ -1,5 +1,6 @@
1
1
  import fs from "node:fs";
2
- import { CONFIG_PATH, loadConfig, readConfigFileSnapshot, resolveGatewayPort, } from "../../config/config.js";
2
+ import path from "node:path";
3
+ import { CONFIG_PATH, loadConfig, readConfigFileSnapshot, resolveStateDir, resolveGatewayPort, } from "../../config/config.js";
3
4
  import { resolveGatewayAuth } from "../../gateway/auth.js";
4
5
  import { startGatewayServer } from "../../gateway/server.js";
5
6
  import { setGatewayWsLogStyle } from "../../gateway/ws-logging.js";
@@ -10,13 +11,52 @@ import { setConsoleSubsystemFilter, setConsoleTimestampPrefix } from "../../logg
10
11
  import { createSubsystemLogger } from "../../logging/subsystem.js";
11
12
  import { defaultRuntime } from "../../runtime.js";
12
13
  import { formatCliCommand } from "../command-format.js";
14
+ import { inheritOptionFromParent } from "../command-options.js";
13
15
  import { forceFreePortAndWait } from "../ports.js";
14
16
  import { ensureDevGatewayConfig } from "./dev.js";
15
17
  import { runGatewayLoop } from "./run-loop.js";
16
18
  import { describeUnknownError, extractGatewayMiskeys, maybeExplainGatewayServiceStop, parsePort, toOptionString, } from "./shared.js";
17
19
  const gatewayLog = createSubsystemLogger("gateway");
20
+ const GATEWAY_RUN_VALUE_KEYS = [
21
+ "port",
22
+ "bind",
23
+ "token",
24
+ "auth",
25
+ "password",
26
+ "tailscale",
27
+ "wsLog",
28
+ "rawStreamPath",
29
+ ];
30
+ const GATEWAY_RUN_BOOLEAN_KEYS = [
31
+ "tailscaleResetOnExit",
32
+ "allowUnconfigured",
33
+ "dev",
34
+ "reset",
35
+ "force",
36
+ "verbose",
37
+ "claudeCliLogs",
38
+ "compact",
39
+ "rawStream",
40
+ ];
41
+ function resolveGatewayRunOptions(opts, command) {
42
+ const resolved = { ...opts };
43
+ for (const key of GATEWAY_RUN_VALUE_KEYS) {
44
+ const inherited = inheritOptionFromParent(command, key);
45
+ if (key === "wsLog") {
46
+ // wsLog has a child default ("auto"), so prefer inherited parent CLI value when present.
47
+ resolved[key] = inherited ?? resolved[key];
48
+ continue;
49
+ }
50
+ resolved[key] = resolved[key] ?? inherited;
51
+ }
52
+ for (const key of GATEWAY_RUN_BOOLEAN_KEYS) {
53
+ const inherited = inheritOptionFromParent(command, key);
54
+ resolved[key] = Boolean(resolved[key] || inherited);
55
+ }
56
+ return resolved;
57
+ }
18
58
  async function runGatewayCommand(opts) {
19
- const isDevProfile = (process.env.POOLBOT_PROFILE || process.env.CLAWDBOT_PROFILE)?.trim().toLowerCase() === "dev";
59
+ const isDevProfile = process.env.POOLBOT_PROFILE?.trim().toLowerCase() === "dev";
20
60
  const devMode = Boolean(opts.dev) || isDevProfile;
21
61
  if (opts.reset && !devMode) {
22
62
  defaultRuntime.error("Use --reset with --dev.");
@@ -28,7 +68,6 @@ async function runGatewayCommand(opts) {
28
68
  if (opts.claudeCliLogs) {
29
69
  setConsoleSubsystemFilter(["agent/claude-cli"]);
30
70
  process.env.POOLBOT_CLAUDE_CLI_LOG_OUTPUT = "1";
31
- process.env.CLAWDBOT_CLAUDE_CLI_LOG_OUTPUT = "1";
32
71
  }
33
72
  const wsLogRaw = (opts.compact ? "compact" : opts.wsLog);
34
73
  const wsLogStyle = wsLogRaw === "compact" ? "compact" : wsLogRaw === "full" ? "full" : "auto";
@@ -42,12 +81,10 @@ async function runGatewayCommand(opts) {
42
81
  setGatewayWsLogStyle(wsLogStyle);
43
82
  if (opts.rawStream) {
44
83
  process.env.POOLBOT_RAW_STREAM = "1";
45
- process.env.CLAWDBOT_RAW_STREAM = "1";
46
84
  }
47
85
  const rawStreamPath = toOptionString(opts.rawStreamPath);
48
86
  if (rawStreamPath) {
49
87
  process.env.POOLBOT_RAW_STREAM_PATH = rawStreamPath;
50
- process.env.CLAWDBOT_RAW_STREAM_PATH = rawStreamPath;
51
88
  }
52
89
  if (devMode) {
53
90
  await ensureDevGatewayConfig({ reset: Boolean(opts.reset) });
@@ -95,7 +132,6 @@ async function runGatewayCommand(opts) {
95
132
  const token = toOptionString(opts.token);
96
133
  if (token) {
97
134
  process.env.POOLBOT_GATEWAY_TOKEN = token;
98
- process.env.CLAWDBOT_GATEWAY_TOKEN = token;
99
135
  }
100
136
  }
101
137
  const authModeRaw = toOptionString(opts.auth);
@@ -116,7 +152,9 @@ async function runGatewayCommand(opts) {
116
152
  }
117
153
  const passwordRaw = toOptionString(opts.password);
118
154
  const tokenRaw = toOptionString(opts.token);
119
- const configExists = fs.existsSync(CONFIG_PATH);
155
+ const snapshot = await readConfigFileSnapshot().catch(() => null);
156
+ const configExists = snapshot?.exists ?? fs.existsSync(CONFIG_PATH);
157
+ const configAuditPath = path.join(resolveStateDir(process.env), "logs", "config-audit.jsonl");
120
158
  const mode = cfg.gateway?.mode;
121
159
  if (!opts.allowUnconfigured && mode !== "local") {
122
160
  if (!configExists) {
@@ -124,6 +162,7 @@ async function runGatewayCommand(opts) {
124
162
  }
125
163
  else {
126
164
  defaultRuntime.error(`Gateway start blocked: set gateway.mode=local (current: ${mode ?? "unset"}) or pass --allow-unconfigured.`);
165
+ defaultRuntime.error(`Config write audit: ${configAuditPath}`);
127
166
  }
128
167
  defaultRuntime.exit(1);
129
168
  return;
@@ -141,7 +180,6 @@ async function runGatewayCommand(opts) {
141
180
  defaultRuntime.exit(1);
142
181
  return;
143
182
  }
144
- const snapshot = await readConfigFileSnapshot().catch(() => null);
145
183
  const miskeys = extractGatewayMiskeys(snapshot?.parsed);
146
184
  const authConfig = {
147
185
  ...cfg.gateway?.auth,
@@ -189,7 +227,7 @@ async function runGatewayCommand(opts) {
189
227
  defaultRuntime.exit(1);
190
228
  return;
191
229
  }
192
- if (bind !== "loopback" && !hasSharedSecret) {
230
+ if (bind !== "loopback" && !hasSharedSecret && resolvedAuthMode !== "trusted-proxy") {
193
231
  defaultRuntime.error([
194
232
  `Refusing to bind gateway to ${bind} without auth.`,
195
233
  "Set gateway.auth.token/password (or POOLBOT_GATEWAY_TOKEN/POOLBOT_GATEWAY_PASSWORD) or pass --token/--password.",
@@ -264,7 +302,7 @@ export function addGatewayRunCommand(cmd) {
264
302
  .option("--compact", 'Alias for "--ws-log compact"', false)
265
303
  .option("--raw-stream", "Log raw model stream events to jsonl", false)
266
304
  .option("--raw-stream-path <path>", "Raw stream jsonl path")
267
- .action(async (opts) => {
268
- await runGatewayCommand(opts);
305
+ .action(async (opts, command) => {
306
+ await runGatewayCommand(resolveGatewayRunOptions(opts, command));
269
307
  });
270
308
  }
@@ -3,6 +3,7 @@ import * as fs from "node:fs/promises";
3
3
  import * as os from "node:os";
4
4
  import * as path from "node:path";
5
5
  import { resolveCliName } from "./cli-name.js";
6
+ const MAX_CAMERA_URL_DOWNLOAD_BYTES = 250 * 1024 * 1024;
6
7
  function asRecord(value) {
7
8
  return typeof value === "object" && value !== null ? value : {};
8
9
  }
@@ -19,23 +20,25 @@ export function parseCameraSnapPayload(value) {
19
20
  const obj = asRecord(value);
20
21
  const format = asString(obj.format);
21
22
  const base64 = asString(obj.base64);
23
+ const url = asString(obj.url);
22
24
  const width = asNumber(obj.width);
23
25
  const height = asNumber(obj.height);
24
- if (!format || !base64 || width === undefined || height === undefined) {
26
+ if (!format || (!base64 && !url) || width === undefined || height === undefined) {
25
27
  throw new Error("invalid camera.snap payload");
26
28
  }
27
- return { format, base64, width, height };
29
+ return { format, ...(base64 ? { base64 } : {}), ...(url ? { url } : {}), width, height };
28
30
  }
29
31
  export function parseCameraClipPayload(value) {
30
32
  const obj = asRecord(value);
31
33
  const format = asString(obj.format);
32
34
  const base64 = asString(obj.base64);
35
+ const url = asString(obj.url);
33
36
  const durationMs = asNumber(obj.durationMs);
34
37
  const hasAudio = asBoolean(obj.hasAudio);
35
- if (!format || !base64 || durationMs === undefined || hasAudio === undefined) {
38
+ if (!format || (!base64 && !url) || durationMs === undefined || hasAudio === undefined) {
36
39
  throw new Error("invalid camera.clip payload");
37
40
  }
38
- return { format, base64, durationMs, hasAudio };
41
+ return { format, ...(base64 ? { base64 } : {}), ...(url ? { url } : {}), durationMs, hasAudio };
39
42
  }
40
43
  export function cameraTempPath(opts) {
41
44
  const tmpDir = opts.tmpDir ?? os.tmpdir();
@@ -45,6 +48,58 @@ export function cameraTempPath(opts) {
45
48
  const cliName = resolveCliName();
46
49
  return path.join(tmpDir, `${cliName}-camera-${opts.kind}${facingPart}-${id}${ext}`);
47
50
  }
51
+ export async function writeUrlToFile(filePath, url) {
52
+ const parsed = new URL(url);
53
+ if (parsed.protocol !== "https:") {
54
+ throw new Error(`writeUrlToFile: only https URLs are allowed, got ${parsed.protocol}`);
55
+ }
56
+ const res = await fetch(url);
57
+ if (!res.ok) {
58
+ throw new Error(`failed to download ${url}: ${res.status} ${res.statusText}`);
59
+ }
60
+ const contentLengthRaw = res.headers.get("content-length");
61
+ const contentLength = contentLengthRaw ? Number.parseInt(contentLengthRaw, 10) : undefined;
62
+ if (typeof contentLength === "number" &&
63
+ Number.isFinite(contentLength) &&
64
+ contentLength > MAX_CAMERA_URL_DOWNLOAD_BYTES) {
65
+ throw new Error(`writeUrlToFile: content-length ${contentLength} exceeds max ${MAX_CAMERA_URL_DOWNLOAD_BYTES}`);
66
+ }
67
+ const body = res.body;
68
+ if (!body) {
69
+ throw new Error(`failed to download ${url}: empty response body`);
70
+ }
71
+ const fileHandle = await fs.open(filePath, "w");
72
+ let bytes = 0;
73
+ let thrown;
74
+ try {
75
+ const reader = body.getReader();
76
+ while (true) {
77
+ const { done, value } = await reader.read();
78
+ if (done) {
79
+ break;
80
+ }
81
+ if (!value || value.byteLength === 0) {
82
+ continue;
83
+ }
84
+ bytes += value.byteLength;
85
+ if (bytes > MAX_CAMERA_URL_DOWNLOAD_BYTES) {
86
+ throw new Error(`writeUrlToFile: downloaded ${bytes} bytes, exceeds max ${MAX_CAMERA_URL_DOWNLOAD_BYTES}`);
87
+ }
88
+ await fileHandle.write(value);
89
+ }
90
+ }
91
+ catch (err) {
92
+ thrown = err;
93
+ }
94
+ finally {
95
+ await fileHandle.close();
96
+ }
97
+ if (thrown) {
98
+ await fs.unlink(filePath).catch(() => { });
99
+ throw thrown;
100
+ }
101
+ return { path: filePath, bytes };
102
+ }
48
103
  export async function writeBase64ToFile(filePath, base64) {
49
104
  const buf = Buffer.from(base64, "base64");
50
105
  await fs.writeFile(filePath, buf);
@@ -1,17 +1,17 @@
1
- import { randomIdempotencyKey } from "../../gateway/call.js";
2
1
  import { defaultRuntime } from "../../runtime.js";
3
- import { cameraTempPath, parseCameraClipPayload, parseCameraSnapPayload, writeBase64ToFile, } from "../nodes-camera.js";
4
- import { parseDurationMs } from "../parse-duration.js";
5
- import { getNodesTheme, runNodesCommand } from "./cli-utils.js";
6
- import { callGatewayCli, nodesCallOpts, resolveNodeId } from "./rpc.js";
7
2
  import { renderTable } from "../../terminal/table.js";
8
3
  import { shortenHomePath } from "../../utils.js";
4
+ import { cameraTempPath, parseCameraClipPayload, parseCameraSnapPayload, writeBase64ToFile, writeUrlToFile, } from "../nodes-camera.js";
5
+ import { parseDurationMs } from "../parse-duration.js";
6
+ import { getNodesTheme, runNodesCommand } from "./cli-utils.js";
7
+ import { buildNodeInvokeParams, callGatewayCli, nodesCallOpts, resolveNodeId } from "./rpc.js";
9
8
  const parseFacing = (value) => {
10
9
  const v = String(value ?? "")
11
10
  .trim()
12
11
  .toLowerCase();
13
- if (v === "front" || v === "back")
12
+ if (v === "front" || v === "back") {
14
13
  return v;
14
+ }
15
15
  throw new Error(`invalid facing: ${value} (expected front|back)`);
16
16
  };
17
17
  export function registerNodesCameraCommands(nodes) {
@@ -23,11 +23,10 @@ export function registerNodesCameraCommands(nodes) {
23
23
  .action(async (opts) => {
24
24
  await runNodesCommand("camera list", async () => {
25
25
  const nodeId = await resolveNodeId(opts, String(opts.node ?? ""));
26
- const raw = (await callGatewayCli("node.invoke", opts, {
26
+ const raw = await callGatewayCli("node.invoke", opts, buildNodeInvokeParams({
27
27
  nodeId,
28
28
  command: "camera.list",
29
29
  params: {},
30
- idempotencyKey: randomIdempotencyKey(),
31
30
  }));
32
31
  const res = typeof raw === "object" && raw !== null ? raw : {};
33
32
  const payload = typeof res.payload === "object" && res.payload !== null
@@ -94,7 +93,7 @@ export function registerNodesCameraCommands(nodes) {
94
93
  : undefined;
95
94
  const results = [];
96
95
  for (const facing of facings) {
97
- const invokeParams = {
96
+ const invokeParams = buildNodeInvokeParams({
98
97
  nodeId,
99
98
  command: "camera.snap",
100
99
  params: {
@@ -105,12 +104,9 @@ export function registerNodesCameraCommands(nodes) {
105
104
  delayMs: Number.isFinite(delayMs) ? delayMs : undefined,
106
105
  deviceId: deviceId || undefined,
107
106
  },
108
- idempotencyKey: randomIdempotencyKey(),
109
- };
110
- if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs)) {
111
- invokeParams.timeoutMs = timeoutMs;
112
- }
113
- const raw = (await callGatewayCli("node.invoke", opts, invokeParams));
107
+ timeoutMs,
108
+ });
109
+ const raw = await callGatewayCli("node.invoke", opts, invokeParams);
114
110
  const res = typeof raw === "object" && raw !== null ? raw : {};
115
111
  const payload = parseCameraSnapPayload(res.payload);
116
112
  const filePath = cameraTempPath({
@@ -118,7 +114,12 @@ export function registerNodesCameraCommands(nodes) {
118
114
  facing,
119
115
  ext: payload.format === "jpeg" ? "jpg" : payload.format,
120
116
  });
121
- await writeBase64ToFile(filePath, payload.base64);
117
+ if (payload.url) {
118
+ await writeUrlToFile(filePath, payload.url);
119
+ }
120
+ else if (payload.base64) {
121
+ await writeBase64ToFile(filePath, payload.base64);
122
+ }
122
123
  results.push({
123
124
  facing,
124
125
  path: filePath,
@@ -152,7 +153,7 @@ export function registerNodesCameraCommands(nodes) {
152
153
  ? Number.parseInt(String(opts.invokeTimeout), 10)
153
154
  : undefined;
154
155
  const deviceId = opts.deviceId ? String(opts.deviceId).trim() : undefined;
155
- const invokeParams = {
156
+ const invokeParams = buildNodeInvokeParams({
156
157
  nodeId,
157
158
  command: "camera.clip",
158
159
  params: {
@@ -162,12 +163,9 @@ export function registerNodesCameraCommands(nodes) {
162
163
  format: "mp4",
163
164
  deviceId: deviceId || undefined,
164
165
  },
165
- idempotencyKey: randomIdempotencyKey(),
166
- };
167
- if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs)) {
168
- invokeParams.timeoutMs = timeoutMs;
169
- }
170
- const raw = (await callGatewayCli("node.invoke", opts, invokeParams));
166
+ timeoutMs,
167
+ });
168
+ const raw = await callGatewayCli("node.invoke", opts, invokeParams);
171
169
  const res = typeof raw === "object" && raw !== null ? raw : {};
172
170
  const payload = parseCameraClipPayload(res.payload);
173
171
  const filePath = cameraTempPath({
@@ -175,7 +173,12 @@ export function registerNodesCameraCommands(nodes) {
175
173
  facing,
176
174
  ext: payload.format,
177
175
  });
178
- await writeBase64ToFile(filePath, payload.base64);
176
+ if (payload.url) {
177
+ await writeUrlToFile(filePath, payload.url);
178
+ }
179
+ else if (payload.base64) {
180
+ await writeBase64ToFile(filePath, payload.base64);
181
+ }
179
182
  if (opts.json) {
180
183
  defaultRuntime.log(JSON.stringify({
181
184
  file: {
@@ -1,4 +1,5 @@
1
- import { callGateway } from "../../gateway/call.js";
1
+ import { callGateway, randomIdempotencyKey } from "../../gateway/call.js";
2
+ import { resolveNodeIdFromCandidates } from "../../shared/node-match.js";
2
3
  import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../../utils/message-channel.js";
3
4
  import { withProgress } from "../progress.js";
4
5
  import { parseNodeList, parsePairingList } from "./format.js";
@@ -7,7 +8,7 @@ export const nodesCallOpts = (cmd, defaults) => cmd
7
8
  .option("--token <token>", "Gateway token (if required)")
8
9
  .option("--timeout <ms>", "Timeout in ms", String(defaults?.timeoutMs ?? 10_000))
9
10
  .option("--json", "Output JSON", false);
10
- export const callGatewayCli = async (method, opts, params) => withProgress({
11
+ export const callGatewayCli = async (method, opts, params, callOpts) => withProgress({
11
12
  label: `Nodes ${method}`,
12
13
  indeterminate: true,
13
14
  enabled: opts.json !== true,
@@ -16,10 +17,22 @@ export const callGatewayCli = async (method, opts, params) => withProgress({
16
17
  token: opts.token,
17
18
  method,
18
19
  params,
19
- timeoutMs: Number(opts.timeout ?? 10_000),
20
+ timeoutMs: callOpts?.transportTimeoutMs ?? Number(opts.timeout ?? 10_000),
20
21
  clientName: GATEWAY_CLIENT_NAMES.CLI,
21
22
  mode: GATEWAY_CLIENT_MODES.CLI,
22
23
  }));
24
+ export function buildNodeInvokeParams(params) {
25
+ const invokeParams = {
26
+ nodeId: params.nodeId,
27
+ command: params.command,
28
+ params: params.params,
29
+ idempotencyKey: params.idempotencyKey ?? randomIdempotencyKey(),
30
+ };
31
+ if (typeof params.timeoutMs === "number" && Number.isFinite(params.timeoutMs)) {
32
+ invokeParams.timeoutMs = params.timeoutMs;
33
+ }
34
+ return invokeParams;
35
+ }
23
36
  export function unauthorizedHintForMessage(message) {
24
37
  const haystack = message.toLowerCase();
25
38
  if (haystack.includes("unauthorizedclient") ||
@@ -33,24 +46,18 @@ export function unauthorizedHintForMessage(message) {
33
46
  }
34
47
  return null;
35
48
  }
36
- function normalizeNodeKey(value) {
37
- return value
38
- .toLowerCase()
39
- .replace(/[^a-z0-9]+/g, "-")
40
- .replace(/^-+/, "")
41
- .replace(/-+$/, "");
42
- }
43
49
  export async function resolveNodeId(opts, query) {
44
50
  const q = String(query ?? "").trim();
45
- if (!q)
51
+ if (!q) {
46
52
  throw new Error("node required");
53
+ }
47
54
  let nodes = [];
48
55
  try {
49
- const res = (await callGatewayCli("node.list", opts, {}));
56
+ const res = await callGatewayCli("node.list", opts, {});
50
57
  nodes = parseNodeList(res);
51
58
  }
52
59
  catch {
53
- const res = (await callGatewayCli("node.pair.list", opts, {}));
60
+ const res = await callGatewayCli("node.pair.list", opts, {});
54
61
  const { paired } = parsePairingList(res);
55
62
  nodes = paired.map((n) => ({
56
63
  nodeId: n.nodeId,
@@ -60,29 +67,5 @@ export async function resolveNodeId(opts, query) {
60
67
  remoteIp: n.remoteIp,
61
68
  }));
62
69
  }
63
- const qNorm = normalizeNodeKey(q);
64
- const matches = nodes.filter((n) => {
65
- if (n.nodeId === q)
66
- return true;
67
- if (typeof n.remoteIp === "string" && n.remoteIp === q)
68
- return true;
69
- const name = typeof n.displayName === "string" ? n.displayName : "";
70
- if (name && normalizeNodeKey(name) === qNorm)
71
- return true;
72
- if (q.length >= 6 && n.nodeId.startsWith(q))
73
- return true;
74
- return false;
75
- });
76
- if (matches.length === 1)
77
- return matches[0].nodeId;
78
- if (matches.length === 0) {
79
- const known = nodes
80
- .map((n) => n.displayName || n.remoteIp || n.nodeId)
81
- .filter(Boolean)
82
- .join(", ");
83
- throw new Error(`unknown node: ${q}${known ? ` (known: ${known})` : ""}`);
84
- }
85
- throw new Error(`ambiguous node: ${q} (matches: ${matches
86
- .map((n) => n.displayName || n.remoteIp || n.nodeId)
87
- .join(", ")})`);
70
+ return resolveNodeIdFromCandidates(nodes, q);
88
71
  }
@@ -114,14 +114,14 @@ export function registerQrCli(program) {
114
114
  }
115
115
  const lines = [
116
116
  theme.heading("Pairing QR"),
117
- "Scan this with the OpenClaw iOS app (Onboarding -> Scan QR).",
117
+ "Scan this with the Pool Bot iOS app (Onboarding -> Scan QR).",
118
118
  "",
119
119
  ];
120
120
  if (opts.ascii !== false) {
121
121
  const qrAscii = await renderQrAscii(setupCode);
122
122
  lines.push(qrAscii.trimEnd(), "");
123
123
  }
124
- lines.push(`${theme.muted("Setup code:")} ${setupCode}`, `${theme.muted("Gateway:")} ${resolved.payload.url}`, `${theme.muted("Auth:")} ${resolved.authLabel}`, `${theme.muted("Source:")} ${resolved.urlSource}`, "", "Approve after scan with:", ` ${theme.command("openclaw devices list")}`, ` ${theme.command("openclaw devices approve <requestId>")}`);
124
+ lines.push(`${theme.muted("Setup code:")} ${setupCode}`, `${theme.muted("Gateway:")} ${resolved.payload.url}`, `${theme.muted("Auth:")} ${resolved.authLabel}`, `${theme.muted("Source:")} ${resolved.urlSource}`, "", "Approve after scan with:", ` ${theme.command("poolbot devices list")}`, ` ${theme.command("poolbot devices approve <requestId>")}`);
125
125
  defaultRuntime.log(lines.join("\n"));
126
126
  }
127
127
  catch (err) {