agim-cli 1.0.1

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 (453) hide show
  1. package/CHANGELOG.md +1234 -0
  2. package/LICENSE +21 -0
  3. package/README.md +422 -0
  4. package/README.zh-CN.md +414 -0
  5. package/dist/cli-ui/cmd-handlers.d.ts +11 -0
  6. package/dist/cli-ui/cmd-handlers.d.ts.map +1 -0
  7. package/dist/cli-ui/cmd-handlers.js +240 -0
  8. package/dist/cli-ui/cmd-handlers.js.map +1 -0
  9. package/dist/cli-ui/config-wizard.d.ts +3 -0
  10. package/dist/cli-ui/config-wizard.d.ts.map +1 -0
  11. package/dist/cli-ui/config-wizard.js +851 -0
  12. package/dist/cli-ui/config-wizard.js.map +1 -0
  13. package/dist/cli-ui/entry-menu.d.ts +28 -0
  14. package/dist/cli-ui/entry-menu.d.ts.map +1 -0
  15. package/dist/cli-ui/entry-menu.js +50 -0
  16. package/dist/cli-ui/entry-menu.js.map +1 -0
  17. package/dist/cli-ui/env-file.d.ts +35 -0
  18. package/dist/cli-ui/env-file.d.ts.map +1 -0
  19. package/dist/cli-ui/env-file.js +163 -0
  20. package/dist/cli-ui/env-file.js.map +1 -0
  21. package/dist/cli-ui/i18n.d.ts +204 -0
  22. package/dist/cli-ui/i18n.d.ts.map +1 -0
  23. package/dist/cli-ui/i18n.js +455 -0
  24. package/dist/cli-ui/i18n.js.map +1 -0
  25. package/dist/cli-ui/lang-picker.d.ts +10 -0
  26. package/dist/cli-ui/lang-picker.d.ts.map +1 -0
  27. package/dist/cli-ui/lang-picker.js +33 -0
  28. package/dist/cli-ui/lang-picker.js.map +1 -0
  29. package/dist/cli-ui/paths.d.ts +4 -0
  30. package/dist/cli-ui/paths.d.ts.map +1 -0
  31. package/dist/cli-ui/paths.js +11 -0
  32. package/dist/cli-ui/paths.js.map +1 -0
  33. package/dist/cli-ui/prompts.d.ts +65 -0
  34. package/dist/cli-ui/prompts.d.ts.map +1 -0
  35. package/dist/cli-ui/prompts.js +125 -0
  36. package/dist/cli-ui/prompts.js.map +1 -0
  37. package/dist/cli-ui/service.d.ts +41 -0
  38. package/dist/cli-ui/service.d.ts.map +1 -0
  39. package/dist/cli-ui/service.js +241 -0
  40. package/dist/cli-ui/service.js.map +1 -0
  41. package/dist/cli.d.ts +3 -0
  42. package/dist/cli.d.ts.map +1 -0
  43. package/dist/cli.js +1143 -0
  44. package/dist/cli.js.map +1 -0
  45. package/dist/core/acp-server.d.ts +8 -0
  46. package/dist/core/acp-server.d.ts.map +1 -0
  47. package/dist/core/acp-server.js +266 -0
  48. package/dist/core/acp-server.js.map +1 -0
  49. package/dist/core/agent-base.d.ts +94 -0
  50. package/dist/core/agent-base.d.ts.map +1 -0
  51. package/dist/core/agent-base.js +373 -0
  52. package/dist/core/agent-base.js.map +1 -0
  53. package/dist/core/agent-cwd.d.ts +48 -0
  54. package/dist/core/agent-cwd.d.ts.map +1 -0
  55. package/dist/core/agent-cwd.js +181 -0
  56. package/dist/core/agent-cwd.js.map +1 -0
  57. package/dist/core/agent-helper.d.ts +65 -0
  58. package/dist/core/agent-helper.d.ts.map +1 -0
  59. package/dist/core/agent-helper.js +150 -0
  60. package/dist/core/agent-helper.js.map +1 -0
  61. package/dist/core/agim-paths.d.ts +10 -0
  62. package/dist/core/agim-paths.d.ts.map +1 -0
  63. package/dist/core/agim-paths.js +64 -0
  64. package/dist/core/agim-paths.js.map +1 -0
  65. package/dist/core/approval-bus.d.ts +300 -0
  66. package/dist/core/approval-bus.d.ts.map +1 -0
  67. package/dist/core/approval-bus.js +990 -0
  68. package/dist/core/approval-bus.js.map +1 -0
  69. package/dist/core/approval-router.d.ts +101 -0
  70. package/dist/core/approval-router.d.ts.map +1 -0
  71. package/dist/core/approval-router.js +540 -0
  72. package/dist/core/approval-router.js.map +1 -0
  73. package/dist/core/audit-log.d.ts +55 -0
  74. package/dist/core/audit-log.d.ts.map +1 -0
  75. package/dist/core/audit-log.js +203 -0
  76. package/dist/core/audit-log.js.map +1 -0
  77. package/dist/core/bgjob-reader.d.ts +65 -0
  78. package/dist/core/bgjob-reader.d.ts.map +1 -0
  79. package/dist/core/bgjob-reader.js +212 -0
  80. package/dist/core/bgjob-reader.js.map +1 -0
  81. package/dist/core/circuit-breaker.d.ts +37 -0
  82. package/dist/core/circuit-breaker.d.ts.map +1 -0
  83. package/dist/core/circuit-breaker.js +115 -0
  84. package/dist/core/circuit-breaker.js.map +1 -0
  85. package/dist/core/commands/agent.d.ts +4 -0
  86. package/dist/core/commands/agent.d.ts.map +1 -0
  87. package/dist/core/commands/agent.js +40 -0
  88. package/dist/core/commands/agent.js.map +1 -0
  89. package/dist/core/commands/approval.d.ts +3 -0
  90. package/dist/core/commands/approval.d.ts.map +1 -0
  91. package/dist/core/commands/approval.js +85 -0
  92. package/dist/core/commands/approval.js.map +1 -0
  93. package/dist/core/commands/audit.d.ts +3 -0
  94. package/dist/core/commands/audit.d.ts.map +1 -0
  95. package/dist/core/commands/audit.js +84 -0
  96. package/dist/core/commands/audit.js.map +1 -0
  97. package/dist/core/commands/builtin.d.ts +3 -0
  98. package/dist/core/commands/builtin.d.ts.map +1 -0
  99. package/dist/core/commands/builtin.js +304 -0
  100. package/dist/core/commands/builtin.js.map +1 -0
  101. package/dist/core/commands/cron.d.ts +3 -0
  102. package/dist/core/commands/cron.d.ts.map +1 -0
  103. package/dist/core/commands/cron.js +128 -0
  104. package/dist/core/commands/cron.js.map +1 -0
  105. package/dist/core/commands/job.d.ts +3 -0
  106. package/dist/core/commands/job.d.ts.map +1 -0
  107. package/dist/core/commands/job.js +195 -0
  108. package/dist/core/commands/job.js.map +1 -0
  109. package/dist/core/commands/memo.d.ts +3 -0
  110. package/dist/core/commands/memo.d.ts.map +1 -0
  111. package/dist/core/commands/memo.js +151 -0
  112. package/dist/core/commands/memo.js.map +1 -0
  113. package/dist/core/commands/model.d.ts +9 -0
  114. package/dist/core/commands/model.d.ts.map +1 -0
  115. package/dist/core/commands/model.js +183 -0
  116. package/dist/core/commands/model.js.map +1 -0
  117. package/dist/core/commands/plan.d.ts +3 -0
  118. package/dist/core/commands/plan.d.ts.map +1 -0
  119. package/dist/core/commands/plan.js +75 -0
  120. package/dist/core/commands/plan.js.map +1 -0
  121. package/dist/core/commands/remind.d.ts +3 -0
  122. package/dist/core/commands/remind.d.ts.map +1 -0
  123. package/dist/core/commands/remind.js +271 -0
  124. package/dist/core/commands/remind.js.map +1 -0
  125. package/dist/core/commands/router.d.ts +3 -0
  126. package/dist/core/commands/router.d.ts.map +1 -0
  127. package/dist/core/commands/router.js +71 -0
  128. package/dist/core/commands/router.js.map +1 -0
  129. package/dist/core/commands/sessions.d.ts +3 -0
  130. package/dist/core/commands/sessions.d.ts.map +1 -0
  131. package/dist/core/commands/sessions.js +88 -0
  132. package/dist/core/commands/sessions.js.map +1 -0
  133. package/dist/core/commands/stats.d.ts +3 -0
  134. package/dist/core/commands/stats.d.ts.map +1 -0
  135. package/dist/core/commands/stats.js +73 -0
  136. package/dist/core/commands/stats.js.map +1 -0
  137. package/dist/core/commands/think.d.ts +3 -0
  138. package/dist/core/commands/think.d.ts.map +1 -0
  139. package/dist/core/commands/think.js +28 -0
  140. package/dist/core/commands/think.js.map +1 -0
  141. package/dist/core/commands/workspaces.d.ts +3 -0
  142. package/dist/core/commands/workspaces.d.ts.map +1 -0
  143. package/dist/core/commands/workspaces.js +47 -0
  144. package/dist/core/commands/workspaces.js.map +1 -0
  145. package/dist/core/config-schema.d.ts +60 -0
  146. package/dist/core/config-schema.d.ts.map +1 -0
  147. package/dist/core/config-schema.js +75 -0
  148. package/dist/core/config-schema.js.map +1 -0
  149. package/dist/core/coord-systems.d.ts +65 -0
  150. package/dist/core/coord-systems.d.ts.map +1 -0
  151. package/dist/core/coord-systems.js +229 -0
  152. package/dist/core/coord-systems.js.map +1 -0
  153. package/dist/core/cron.d.ts +29 -0
  154. package/dist/core/cron.d.ts.map +1 -0
  155. package/dist/core/cron.js +184 -0
  156. package/dist/core/cron.js.map +1 -0
  157. package/dist/core/event-bus.d.ts +80 -0
  158. package/dist/core/event-bus.d.ts.map +1 -0
  159. package/dist/core/event-bus.js +62 -0
  160. package/dist/core/event-bus.js.map +1 -0
  161. package/dist/core/intent-llm.d.ts +27 -0
  162. package/dist/core/intent-llm.d.ts.map +1 -0
  163. package/dist/core/intent-llm.js +170 -0
  164. package/dist/core/intent-llm.js.map +1 -0
  165. package/dist/core/intent.d.ts +12 -0
  166. package/dist/core/intent.d.ts.map +1 -0
  167. package/dist/core/intent.js +187 -0
  168. package/dist/core/intent.js.map +1 -0
  169. package/dist/core/job-board.d.ts +82 -0
  170. package/dist/core/job-board.d.ts.map +1 -0
  171. package/dist/core/job-board.js +379 -0
  172. package/dist/core/job-board.js.map +1 -0
  173. package/dist/core/location-context.d.ts +32 -0
  174. package/dist/core/location-context.d.ts.map +1 -0
  175. package/dist/core/location-context.js +69 -0
  176. package/dist/core/location-context.js.map +1 -0
  177. package/dist/core/location-token.d.ts +57 -0
  178. package/dist/core/location-token.d.ts.map +1 -0
  179. package/dist/core/location-token.js +128 -0
  180. package/dist/core/location-token.js.map +1 -0
  181. package/dist/core/logger.d.ts +6 -0
  182. package/dist/core/logger.d.ts.map +1 -0
  183. package/dist/core/logger.js +54 -0
  184. package/dist/core/logger.js.map +1 -0
  185. package/dist/core/memo-rpc.d.ts +13 -0
  186. package/dist/core/memo-rpc.d.ts.map +1 -0
  187. package/dist/core/memo-rpc.js +288 -0
  188. package/dist/core/memo-rpc.js.map +1 -0
  189. package/dist/core/memos.d.ts +163 -0
  190. package/dist/core/memos.d.ts.map +1 -0
  191. package/dist/core/memos.js +502 -0
  192. package/dist/core/memos.js.map +1 -0
  193. package/dist/core/metrics.d.ts +55 -0
  194. package/dist/core/metrics.d.ts.map +1 -0
  195. package/dist/core/metrics.js +291 -0
  196. package/dist/core/metrics.js.map +1 -0
  197. package/dist/core/onboarding.d.ts +99 -0
  198. package/dist/core/onboarding.d.ts.map +1 -0
  199. package/dist/core/onboarding.js +426 -0
  200. package/dist/core/onboarding.js.map +1 -0
  201. package/dist/core/pending-reminder.d.ts +25 -0
  202. package/dist/core/pending-reminder.d.ts.map +1 -0
  203. package/dist/core/pending-reminder.js +53 -0
  204. package/dist/core/pending-reminder.js.map +1 -0
  205. package/dist/core/rate-limiter.d.ts +44 -0
  206. package/dist/core/rate-limiter.d.ts.map +1 -0
  207. package/dist/core/rate-limiter.js +115 -0
  208. package/dist/core/rate-limiter.js.map +1 -0
  209. package/dist/core/registry.d.ts +32 -0
  210. package/dist/core/registry.d.ts.map +1 -0
  211. package/dist/core/registry.js +126 -0
  212. package/dist/core/registry.js.map +1 -0
  213. package/dist/core/remind-intent.d.ts +25 -0
  214. package/dist/core/remind-intent.d.ts.map +1 -0
  215. package/dist/core/remind-intent.js +196 -0
  216. package/dist/core/remind-intent.js.map +1 -0
  217. package/dist/core/reminder-rpc.d.ts +17 -0
  218. package/dist/core/reminder-rpc.d.ts.map +1 -0
  219. package/dist/core/reminder-rpc.js +169 -0
  220. package/dist/core/reminder-rpc.js.map +1 -0
  221. package/dist/core/reminders.d.ts +159 -0
  222. package/dist/core/reminders.d.ts.map +1 -0
  223. package/dist/core/reminders.js +977 -0
  224. package/dist/core/reminders.js.map +1 -0
  225. package/dist/core/router.d.ts +55 -0
  226. package/dist/core/router.d.ts.map +1 -0
  227. package/dist/core/router.js +497 -0
  228. package/dist/core/router.js.map +1 -0
  229. package/dist/core/schedule.d.ts +65 -0
  230. package/dist/core/schedule.d.ts.map +1 -0
  231. package/dist/core/schedule.js +323 -0
  232. package/dist/core/schedule.js.map +1 -0
  233. package/dist/core/session.d.ts +182 -0
  234. package/dist/core/session.d.ts.map +1 -0
  235. package/dist/core/session.js +807 -0
  236. package/dist/core/session.js.map +1 -0
  237. package/dist/core/sqlite-helper.d.ts +37 -0
  238. package/dist/core/sqlite-helper.d.ts.map +1 -0
  239. package/dist/core/sqlite-helper.js +79 -0
  240. package/dist/core/sqlite-helper.js.map +1 -0
  241. package/dist/core/transcribe.d.ts +25 -0
  242. package/dist/core/transcribe.d.ts.map +1 -0
  243. package/dist/core/transcribe.js +217 -0
  244. package/dist/core/transcribe.js.map +1 -0
  245. package/dist/core/types.d.ts +360 -0
  246. package/dist/core/types.d.ts.map +1 -0
  247. package/dist/core/types.js +3 -0
  248. package/dist/core/types.js.map +1 -0
  249. package/dist/core/workspace.d.ts +67 -0
  250. package/dist/core/workspace.d.ts.map +1 -0
  251. package/dist/core/workspace.js +113 -0
  252. package/dist/core/workspace.js.map +1 -0
  253. package/dist/index.d.ts +5 -0
  254. package/dist/index.d.ts.map +1 -0
  255. package/dist/index.js +6 -0
  256. package/dist/index.js.map +1 -0
  257. package/dist/plugins/agents/acp/acp-adapter.d.ts +16 -0
  258. package/dist/plugins/agents/acp/acp-adapter.d.ts.map +1 -0
  259. package/dist/plugins/agents/acp/acp-adapter.js +49 -0
  260. package/dist/plugins/agents/acp/acp-adapter.js.map +1 -0
  261. package/dist/plugins/agents/acp/acp-client.d.ts +32 -0
  262. package/dist/plugins/agents/acp/acp-client.d.ts.map +1 -0
  263. package/dist/plugins/agents/acp/acp-client.js +177 -0
  264. package/dist/plugins/agents/acp/acp-client.js.map +1 -0
  265. package/dist/plugins/agents/acp/discovery.d.ts +19 -0
  266. package/dist/plugins/agents/acp/discovery.d.ts.map +1 -0
  267. package/dist/plugins/agents/acp/discovery.js +111 -0
  268. package/dist/plugins/agents/acp/discovery.js.map +1 -0
  269. package/dist/plugins/agents/acp/index.d.ts +4 -0
  270. package/dist/plugins/agents/acp/index.d.ts.map +1 -0
  271. package/dist/plugins/agents/acp/index.js +4 -0
  272. package/dist/plugins/agents/acp/index.js.map +1 -0
  273. package/dist/plugins/agents/acp/types.d.ts +62 -0
  274. package/dist/plugins/agents/acp/types.d.ts.map +1 -0
  275. package/dist/plugins/agents/acp/types.js +5 -0
  276. package/dist/plugins/agents/acp/types.js.map +1 -0
  277. package/dist/plugins/agents/claude-code/index.d.ts +25 -0
  278. package/dist/plugins/agents/claude-code/index.d.ts.map +1 -0
  279. package/dist/plugins/agents/claude-code/index.js +184 -0
  280. package/dist/plugins/agents/claude-code/index.js.map +1 -0
  281. package/dist/plugins/agents/claude-code/mcp-approval-server.d.ts +59 -0
  282. package/dist/plugins/agents/claude-code/mcp-approval-server.d.ts.map +1 -0
  283. package/dist/plugins/agents/claude-code/mcp-approval-server.js +645 -0
  284. package/dist/plugins/agents/claude-code/mcp-approval-server.js.map +1 -0
  285. package/dist/plugins/agents/codex/build-mcp-cli-args.d.ts +28 -0
  286. package/dist/plugins/agents/codex/build-mcp-cli-args.d.ts.map +1 -0
  287. package/dist/plugins/agents/codex/build-mcp-cli-args.js +74 -0
  288. package/dist/plugins/agents/codex/build-mcp-cli-args.js.map +1 -0
  289. package/dist/plugins/agents/codex/index.d.ts +53 -0
  290. package/dist/plugins/agents/codex/index.d.ts.map +1 -0
  291. package/dist/plugins/agents/codex/index.js +341 -0
  292. package/dist/plugins/agents/codex/index.js.map +1 -0
  293. package/dist/plugins/agents/copilot/index.d.ts +35 -0
  294. package/dist/plugins/agents/copilot/index.d.ts.map +1 -0
  295. package/dist/plugins/agents/copilot/index.js +182 -0
  296. package/dist/plugins/agents/copilot/index.js.map +1 -0
  297. package/dist/plugins/agents/opencode/ensure-mcp-config.d.ts +11 -0
  298. package/dist/plugins/agents/opencode/ensure-mcp-config.d.ts.map +1 -0
  299. package/dist/plugins/agents/opencode/ensure-mcp-config.js +100 -0
  300. package/dist/plugins/agents/opencode/ensure-mcp-config.js.map +1 -0
  301. package/dist/plugins/agents/opencode/index.d.ts +5 -0
  302. package/dist/plugins/agents/opencode/index.d.ts.map +1 -0
  303. package/dist/plugins/agents/opencode/index.js +30 -0
  304. package/dist/plugins/agents/opencode/index.js.map +1 -0
  305. package/dist/plugins/agents/opencode/opencode-http-adapter.d.ts +166 -0
  306. package/dist/plugins/agents/opencode/opencode-http-adapter.d.ts.map +1 -0
  307. package/dist/plugins/agents/opencode/opencode-http-adapter.js +682 -0
  308. package/dist/plugins/agents/opencode/opencode-http-adapter.js.map +1 -0
  309. package/dist/plugins/agents/opencode/opencode-stdio-adapter.d.ts +32 -0
  310. package/dist/plugins/agents/opencode/opencode-stdio-adapter.d.ts.map +1 -0
  311. package/dist/plugins/agents/opencode/opencode-stdio-adapter.js +137 -0
  312. package/dist/plugins/agents/opencode/opencode-stdio-adapter.js.map +1 -0
  313. package/dist/plugins/agents/opencode/serve-manager.d.ts +27 -0
  314. package/dist/plugins/agents/opencode/serve-manager.d.ts.map +1 -0
  315. package/dist/plugins/agents/opencode/serve-manager.js +194 -0
  316. package/dist/plugins/agents/opencode/serve-manager.js.map +1 -0
  317. package/dist/plugins/messengers/dingtalk/dingtalk-adapter.d.ts +57 -0
  318. package/dist/plugins/messengers/dingtalk/dingtalk-adapter.d.ts.map +1 -0
  319. package/dist/plugins/messengers/dingtalk/dingtalk-adapter.js +409 -0
  320. package/dist/plugins/messengers/dingtalk/dingtalk-adapter.js.map +1 -0
  321. package/dist/plugins/messengers/dingtalk/dingtalk-client.d.ts +48 -0
  322. package/dist/plugins/messengers/dingtalk/dingtalk-client.d.ts.map +1 -0
  323. package/dist/plugins/messengers/dingtalk/dingtalk-client.js +236 -0
  324. package/dist/plugins/messengers/dingtalk/dingtalk-client.js.map +1 -0
  325. package/dist/plugins/messengers/dingtalk/index.d.ts +3 -0
  326. package/dist/plugins/messengers/dingtalk/index.d.ts.map +1 -0
  327. package/dist/plugins/messengers/dingtalk/index.js +3 -0
  328. package/dist/plugins/messengers/dingtalk/index.js.map +1 -0
  329. package/dist/plugins/messengers/dingtalk/link-coords.d.ts +23 -0
  330. package/dist/plugins/messengers/dingtalk/link-coords.d.ts.map +1 -0
  331. package/dist/plugins/messengers/dingtalk/link-coords.js +89 -0
  332. package/dist/plugins/messengers/dingtalk/link-coords.js.map +1 -0
  333. package/dist/plugins/messengers/dingtalk/media-store.d.ts +16 -0
  334. package/dist/plugins/messengers/dingtalk/media-store.d.ts.map +1 -0
  335. package/dist/plugins/messengers/dingtalk/media-store.js +77 -0
  336. package/dist/plugins/messengers/dingtalk/media-store.js.map +1 -0
  337. package/dist/plugins/messengers/dingtalk/types.d.ts +82 -0
  338. package/dist/plugins/messengers/dingtalk/types.d.ts.map +1 -0
  339. package/dist/plugins/messengers/dingtalk/types.js +14 -0
  340. package/dist/plugins/messengers/dingtalk/types.js.map +1 -0
  341. package/dist/plugins/messengers/discord/discord-adapter.d.ts +21 -0
  342. package/dist/plugins/messengers/discord/discord-adapter.d.ts.map +1 -0
  343. package/dist/plugins/messengers/discord/discord-adapter.js +238 -0
  344. package/dist/plugins/messengers/discord/discord-adapter.js.map +1 -0
  345. package/dist/plugins/messengers/discord/index.d.ts +4 -0
  346. package/dist/plugins/messengers/discord/index.d.ts.map +1 -0
  347. package/dist/plugins/messengers/discord/index.js +4 -0
  348. package/dist/plugins/messengers/discord/index.js.map +1 -0
  349. package/dist/plugins/messengers/discord/markdown-to-discord.d.ts +11 -0
  350. package/dist/plugins/messengers/discord/markdown-to-discord.d.ts.map +1 -0
  351. package/dist/plugins/messengers/discord/markdown-to-discord.js +59 -0
  352. package/dist/plugins/messengers/discord/markdown-to-discord.js.map +1 -0
  353. package/dist/plugins/messengers/discord/types.d.ts +9 -0
  354. package/dist/plugins/messengers/discord/types.d.ts.map +1 -0
  355. package/dist/plugins/messengers/discord/types.js +3 -0
  356. package/dist/plugins/messengers/discord/types.js.map +1 -0
  357. package/dist/plugins/messengers/email/email-adapter.d.ts +33 -0
  358. package/dist/plugins/messengers/email/email-adapter.d.ts.map +1 -0
  359. package/dist/plugins/messengers/email/email-adapter.js +137 -0
  360. package/dist/plugins/messengers/email/email-adapter.js.map +1 -0
  361. package/dist/plugins/messengers/feishu/card-builder.d.ts +23 -0
  362. package/dist/plugins/messengers/feishu/card-builder.d.ts.map +1 -0
  363. package/dist/plugins/messengers/feishu/card-builder.js +89 -0
  364. package/dist/plugins/messengers/feishu/card-builder.js.map +1 -0
  365. package/dist/plugins/messengers/feishu/feishu-adapter.d.ts +23 -0
  366. package/dist/plugins/messengers/feishu/feishu-adapter.d.ts.map +1 -0
  367. package/dist/plugins/messengers/feishu/feishu-adapter.js +250 -0
  368. package/dist/plugins/messengers/feishu/feishu-adapter.js.map +1 -0
  369. package/dist/plugins/messengers/feishu/feishu-client.d.ts +43 -0
  370. package/dist/plugins/messengers/feishu/feishu-client.d.ts.map +1 -0
  371. package/dist/plugins/messengers/feishu/feishu-client.js +118 -0
  372. package/dist/plugins/messengers/feishu/feishu-client.js.map +1 -0
  373. package/dist/plugins/messengers/feishu/index.d.ts +4 -0
  374. package/dist/plugins/messengers/feishu/index.d.ts.map +1 -0
  375. package/dist/plugins/messengers/feishu/index.js +4 -0
  376. package/dist/plugins/messengers/feishu/index.js.map +1 -0
  377. package/dist/plugins/messengers/feishu/types.d.ts +113 -0
  378. package/dist/plugins/messengers/feishu/types.d.ts.map +1 -0
  379. package/dist/plugins/messengers/feishu/types.js +4 -0
  380. package/dist/plugins/messengers/feishu/types.js.map +1 -0
  381. package/dist/plugins/messengers/telegram/index.d.ts +4 -0
  382. package/dist/plugins/messengers/telegram/index.d.ts.map +1 -0
  383. package/dist/plugins/messengers/telegram/index.js +4 -0
  384. package/dist/plugins/messengers/telegram/index.js.map +1 -0
  385. package/dist/plugins/messengers/telegram/markdown-to-html.d.ts +5 -0
  386. package/dist/plugins/messengers/telegram/markdown-to-html.d.ts.map +1 -0
  387. package/dist/plugins/messengers/telegram/markdown-to-html.js +186 -0
  388. package/dist/plugins/messengers/telegram/markdown-to-html.js.map +1 -0
  389. package/dist/plugins/messengers/telegram/media-download.d.ts +59 -0
  390. package/dist/plugins/messengers/telegram/media-download.d.ts.map +1 -0
  391. package/dist/plugins/messengers/telegram/media-download.js +228 -0
  392. package/dist/plugins/messengers/telegram/media-download.js.map +1 -0
  393. package/dist/plugins/messengers/telegram/telegram-adapter.d.ts +77 -0
  394. package/dist/plugins/messengers/telegram/telegram-adapter.d.ts.map +1 -0
  395. package/dist/plugins/messengers/telegram/telegram-adapter.js +880 -0
  396. package/dist/plugins/messengers/telegram/telegram-adapter.js.map +1 -0
  397. package/dist/plugins/messengers/telegram/types.d.ts +47 -0
  398. package/dist/plugins/messengers/telegram/types.d.ts.map +1 -0
  399. package/dist/plugins/messengers/telegram/types.js +3 -0
  400. package/dist/plugins/messengers/telegram/types.js.map +1 -0
  401. package/dist/plugins/messengers/wechat/context-store.d.ts +18 -0
  402. package/dist/plugins/messengers/wechat/context-store.d.ts.map +1 -0
  403. package/dist/plugins/messengers/wechat/context-store.js +105 -0
  404. package/dist/plugins/messengers/wechat/context-store.js.map +1 -0
  405. package/dist/plugins/messengers/wechat/ilink-adapter.d.ts +71 -0
  406. package/dist/plugins/messengers/wechat/ilink-adapter.d.ts.map +1 -0
  407. package/dist/plugins/messengers/wechat/ilink-adapter.js +664 -0
  408. package/dist/plugins/messengers/wechat/ilink-adapter.js.map +1 -0
  409. package/dist/plugins/messengers/wechat/ilink-client.d.ts +75 -0
  410. package/dist/plugins/messengers/wechat/ilink-client.d.ts.map +1 -0
  411. package/dist/plugins/messengers/wechat/ilink-client.js +331 -0
  412. package/dist/plugins/messengers/wechat/ilink-client.js.map +1 -0
  413. package/dist/plugins/messengers/wechat/ilink-types.d.ts +181 -0
  414. package/dist/plugins/messengers/wechat/ilink-types.d.ts.map +1 -0
  415. package/dist/plugins/messengers/wechat/ilink-types.js +22 -0
  416. package/dist/plugins/messengers/wechat/ilink-types.js.map +1 -0
  417. package/dist/plugins/messengers/wechat/media-download.d.ts +32 -0
  418. package/dist/plugins/messengers/wechat/media-download.d.ts.map +1 -0
  419. package/dist/plugins/messengers/wechat/media-download.js +78 -0
  420. package/dist/plugins/messengers/wechat/media-download.js.map +1 -0
  421. package/dist/scripts/migrate-gcj02-to-wgs84.d.ts +3 -0
  422. package/dist/scripts/migrate-gcj02-to-wgs84.d.ts.map +1 -0
  423. package/dist/scripts/migrate-gcj02-to-wgs84.js +52 -0
  424. package/dist/scripts/migrate-gcj02-to-wgs84.js.map +1 -0
  425. package/dist/utils/backoff.d.ts +35 -0
  426. package/dist/utils/backoff.d.ts.map +1 -0
  427. package/dist/utils/backoff.js +59 -0
  428. package/dist/utils/backoff.js.map +1 -0
  429. package/dist/utils/cross-platform.d.ts +26 -0
  430. package/dist/utils/cross-platform.d.ts.map +1 -0
  431. package/dist/utils/cross-platform.js +58 -0
  432. package/dist/utils/cross-platform.js.map +1 -0
  433. package/dist/utils/message-split.d.ts +14 -0
  434. package/dist/utils/message-split.d.ts.map +1 -0
  435. package/dist/utils/message-split.js +65 -0
  436. package/dist/utils/message-split.js.map +1 -0
  437. package/dist/utils/safe-equal.d.ts +2 -0
  438. package/dist/utils/safe-equal.d.ts.map +1 -0
  439. package/dist/utils/safe-equal.js +11 -0
  440. package/dist/utils/safe-equal.js.map +1 -0
  441. package/dist/web/public/_app.js +196 -0
  442. package/dist/web/public/index.html +936 -0
  443. package/dist/web/public/loc.html +305 -0
  444. package/dist/web/public/login.html +106 -0
  445. package/dist/web/public/memos.html +271 -0
  446. package/dist/web/public/reminders.html +234 -0
  447. package/dist/web/public/settings.html +1355 -0
  448. package/dist/web/public/tasks.html +1835 -0
  449. package/dist/web/server.d.ts +12 -0
  450. package/dist/web/server.d.ts.map +1 -0
  451. package/dist/web/server.js +2399 -0
  452. package/dist/web/server.js.map +1 -0
  453. package/package.json +92 -0
package/dist/cli.js ADDED
@@ -0,0 +1,1143 @@
1
+ #!/usr/bin/env node
2
+ // Agim (阿吉姆) CLI — rebranded from im-hub-pro at v1.0.0.
3
+ //
4
+ // Brand: "Agim" (English) / "阿吉姆" (Chinese). Binary is `agim`; the
5
+ // legacy `im-hub-pro` bin name is kept as an alias for systemd units +
6
+ // shell aliases users have already wired up.
7
+ //
8
+ // On-disk paths (~/.im-hub/, ~/.im-hub-workspaces/), env vars (IMHUB_*),
9
+ // HTTP headers (X-IM-Hub-Token), and the systemd unit `im-hub.service`
10
+ // keep their legacy "im-hub" identifier for drop-in compatibility with
11
+ // existing installations. Do not "fix" them — millions of bytes of user
12
+ // data + every existing deployment's systemd config depend on those
13
+ // names being stable. The rename is a brand surface only.
14
+ import { program } from 'commander';
15
+ import { dirname, join } from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
17
+ import { readFileSync } from 'node:fs';
18
+ import { randomUUID } from 'node:crypto';
19
+ import { registry } from './core/registry.js';
20
+ import { sessionManager } from './core/session.js';
21
+ import { parseMessage, routeMessage } from './core/router.js';
22
+ import { crossSpawn, isMac, isWindows } from './utils/cross-platform.js';
23
+ import { generateTraceId, createLogger } from './core/logger.js';
24
+ import { validateConfig } from './core/config-schema.js';
25
+ import { workspaceRegistry } from './core/workspace.js';
26
+ import { bootstrapAgentWorkspaces } from './core/agent-cwd.js';
27
+ import { approvalBus, threadKey } from './core/approval-bus.js';
28
+ import { install as installApprovalRouter, tryHandleApprovalReply, platformToMessengerName } from './core/approval-router.js';
29
+ import { tryConsumeReply as tryConsumePendingReminderReply } from './core/pending-reminder.js';
30
+ import { consumeLocationContext, formatLocationAnnotation } from './core/location-context.js';
31
+ import { tryDetectReminderIntent } from './core/remind-intent.js';
32
+ import { createReminder } from './core/reminders.js';
33
+ import { setReminderConfirmNotifier } from './core/reminder-rpc.js';
34
+ import { checkMessengerConfig, checkAgentAvailability, runMessengerOnboarding, formatAgentInstallHint, formatMessengerStartError, loadConfig as loadOnboardingConfig, saveConfig as saveOnboardingConfig, } from './core/onboarding.js';
35
+ import { startWebServer } from './web/server.js';
36
+ import { startACPServer } from './core/acp-server.js';
37
+ import { AGIM_HOME } from './core/agim-paths.js';
38
+ // Helper to format agent install hint for missing agents
39
+ function _formatMissingAgentHint(missing) {
40
+ return formatAgentInstallHint(missing);
41
+ }
42
+ const CONFIG_DIR = AGIM_HOME;
43
+ const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
44
+ const PACKAGE_JSON = join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
45
+ async function loadConfig() {
46
+ return loadOnboardingConfig();
47
+ }
48
+ async function saveConfig(config) {
49
+ return saveOnboardingConfig(config);
50
+ }
51
+ function readPackageVersion() {
52
+ try {
53
+ const parsed = JSON.parse(readFileSync(PACKAGE_JSON, 'utf-8'));
54
+ if (typeof parsed.version === 'string' && parsed.version.trim()) {
55
+ return parsed.version;
56
+ }
57
+ }
58
+ catch {
59
+ // Keep --help/--version usable even in unusual source-tree layouts.
60
+ }
61
+ return '0.0.0';
62
+ }
63
+ program
64
+ .name('agim')
65
+ .description('Universal messenger-to-agent bridge')
66
+ .version(readPackageVersion());
67
+ program
68
+ .command('start')
69
+ .description('Start the Agim server (foreground by default; --bg for detached daemon)')
70
+ .option('--bg, --background', 'Start as a detached background daemon (writes pid file + log to ~/.im-hub/)')
71
+ .action(async (opts) => {
72
+ // `--bg` (or its alias `--background`): hand off to the bg-spawner.
73
+ // It re-execs us with `start` (no flag) inside a detached child so
74
+ // the actual foreground start logic below runs in that child.
75
+ if (opts.bg || opts.background) {
76
+ const { cmdStart } = await import('./cli-ui/cmd-handlers.js');
77
+ await cmdStart(true, async () => { });
78
+ return;
79
+ }
80
+ console.log('🚀 Starting Agim (阿吉姆)…');
81
+ let config = await loadConfig();
82
+ console.log(`Config loaded from ${CONFIG_FILE}`);
83
+ // Validate config schema — always use the zod-validated output (which
84
+ // applies defaults and strips invalid fields) instead of the raw config.
85
+ const validation = validateConfig(config);
86
+ if (!validation.ok) {
87
+ console.warn('⚠️ Config schema issues detected:');
88
+ for (const err of validation.errors) {
89
+ console.warn(` - ${err}`);
90
+ }
91
+ console.warn(' Agim will continue with defaults for invalid fields.\n');
92
+ }
93
+ config = validation.config;
94
+ // Initialize workspace registry
95
+ const rawConfig = config;
96
+ const workspaces = rawConfig.workspaces;
97
+ workspaceRegistry.load({ workspaces: workspaces });
98
+ const wsCount = workspaces?.length || 1;
99
+ console.log(`Workspaces loaded: ${wsCount} workspace(s)`);
100
+ // Initialize session manager
101
+ await sessionManager.start();
102
+ // Start approval-bus before any agent can spawn — failure here is
103
+ // non-fatal: claude-code falls back to legacy --permission-mode dontAsk.
104
+ if (process.env.IMHUB_APPROVAL_DISABLED === '1') {
105
+ console.log('🛑 Approval bus disabled via IMHUB_APPROVAL_DISABLED=1');
106
+ }
107
+ else {
108
+ try {
109
+ const sockPath = await approvalBus.start();
110
+ console.log(`✅ Approval bus listening on ${sockPath}`);
111
+ // Expose the path through env so long-lived child processes
112
+ // (notably `opencode serve`, which spawns the MCP sidecar with
113
+ // `env: process.env`) can connect without per-spawn config.
114
+ // claude-code and opencode stdio still set this per-spawn via
115
+ // extraEnv — that override wins inside their spawn scope.
116
+ process.env.IMHUB_APPROVAL_SOCK = sockPath;
117
+ }
118
+ catch (err) {
119
+ const msg = err instanceof Error ? err.message : String(err);
120
+ console.warn(`⚠️ Approval bus failed to start (${msg}); claude-code will fall back to dontAsk mode`);
121
+ }
122
+ }
123
+ // Wire the bus's lifetime counters into /api/metrics (M14). Done even
124
+ // when the bus failed to start — getMetrics() returns zeros, which is
125
+ // the correct Prometheus snapshot for "no approvals processed".
126
+ const { setApprovalBusSnapshotProvider } = await import('./core/metrics.js');
127
+ setApprovalBusSnapshotProvider(() => approvalBus.getMetrics());
128
+ // Load plugins FIRST (agents won't be registered until this runs)
129
+ await registry.loadBuiltInPlugins();
130
+ // Bootstrap per-agent IM workspaces. Idempotent — creates
131
+ // ~/.im-hub-workspaces/<agent>/ and seeds CLAUDE.md / AGENTS.md only if
132
+ // they don't exist yet. See docs/architecture/agent-cwd-and-memory.md.
133
+ try {
134
+ const bootstrapped = await bootstrapAgentWorkspaces();
135
+ for (const { agent, dir } of bootstrapped) {
136
+ console.log(`📁 Agent workspace: ${agent} → ${dir}`);
137
+ }
138
+ }
139
+ catch (err) {
140
+ const msg = err instanceof Error ? err.message : String(err);
141
+ console.warn(`⚠️ Agent workspace bootstrap failed: ${msg}`);
142
+ console.warn(' Agents will fall back to Agim cwd ("/" under systemd)');
143
+ }
144
+ // Register the imhub MCP server in opencode's global config so opencode
145
+ // (stdio mode) can see the reminder tools. Idempotent; quietly skips if
146
+ // opencode isn't installed / no config dir / write fails. HTTP mode
147
+ // can't use this — see ensure-mcp-config.ts comments.
148
+ try {
149
+ const { ensureOpencodeMcpRegistered } = await import('./plugins/agents/opencode/ensure-mcp-config.js');
150
+ const wrote = await ensureOpencodeMcpRegistered();
151
+ if (wrote)
152
+ console.log('🔧 Registered imhub MCP in opencode global config');
153
+ }
154
+ catch (err) {
155
+ const msg = err instanceof Error ? err.message : String(err);
156
+ console.warn(`⚠️ opencode MCP registration skipped: ${msg}`);
157
+ }
158
+ // Load ACP (remote) agents from config
159
+ if (config.acpAgents?.length) {
160
+ await registry.loadACPAgents(config.acpAgents);
161
+ }
162
+ // Discover ACP agents via .well-known/acp on configured base URLs
163
+ const discoveryUrls = config.acpDiscoveryUrls;
164
+ if (discoveryUrls?.length) {
165
+ await registry.loadDiscoveredACPAgents(discoveryUrls);
166
+ }
167
+ // Start the scheduler (runs cron-due schedules every 30s)
168
+ const { startScheduler } = await import('./core/schedule.js');
169
+ startScheduler();
170
+ // Start the reminder engine (one-shot lightweight timers, 5s tick)
171
+ const { startReminderEngine } = await import('./core/reminders.js');
172
+ startReminderEngine();
173
+ // Start the memo expiry sweeper (every 5 min — purges memos with
174
+ // expires_at <= now). Idempotent: safe even if startSweepTimer is
175
+ // called twice. Lazy import keeps memos.ts out of the cold-start path
176
+ // until it's actually needed.
177
+ const { startSweepTimer: startMemoSweep } = await import('./core/memos.js');
178
+ startMemoSweep();
179
+ // ============================================
180
+ // ONBOARDING CHECKS (before default fill!)
181
+ // ============================================
182
+ // Check messengers BEFORE the default fill
183
+ const onboardingResult = checkMessengerConfig(config);
184
+ if (onboardingResult.needsOnboarding) {
185
+ console.log('👋 No messengers configured.\n');
186
+ const newConfig = await runMessengerOnboarding(config);
187
+ if (!newConfig) {
188
+ console.log('\n❌ Onboarding cancelled.');
189
+ console.log('Run "agim config <messenger>" to configure manually.');
190
+ process.exit(1);
191
+ }
192
+ config = newConfig;
193
+ await saveConfig(config);
194
+ }
195
+ // Check agents (async, AFTER plugins loaded)
196
+ const agentResult = await checkAgentAvailability();
197
+ if (agentResult.allMissing) {
198
+ console.log('\n⚠️ No coding agents found!');
199
+ console.log(formatAgentInstallHint(agentResult.missing));
200
+ console.log('\nInstall at least one agent, then run agim start again.');
201
+ process.exit(1);
202
+ }
203
+ else if (agentResult.missing.length > 0) {
204
+ console.log('⚠️ Some agents not available:', agentResult.missing.join(', '));
205
+ console.log(` ${formatAgentInstallHint(agentResult.missing)}`);
206
+ console.log('');
207
+ }
208
+ // Set defaultAgent to first available installed agent
209
+ if (agentResult.available.length > 0) {
210
+ config.defaultAgent = agentResult.available[0];
211
+ }
212
+ // ============================================
213
+ // START MESSENGERS
214
+ // ============================================
215
+ // Get messengers to start (now config.messengers is populated)
216
+ const messengersToStart = config.messengers.length > 0
217
+ ? [...config.messengers]
218
+ : ['wechat-ilink']; // Fallback default
219
+ // Auto-include the email adapter so /remind email ... works without
220
+ // explicit `agim config email`. The adapter's start() is fail-soft
221
+ // when SMTP env vars are missing — it stays "registered but disabled"
222
+ // and any actual send call throws a clear "not configured" error.
223
+ if (!messengersToStart.includes('email')) {
224
+ messengersToStart.push('email');
225
+ }
226
+ // Start messenger adapters
227
+ for (const name of messengersToStart) {
228
+ const messenger = registry.getMessenger(name);
229
+ if (!messenger) {
230
+ console.warn(`⚠️ Messenger "${name}" not found, skipping`);
231
+ continue;
232
+ }
233
+ // Set up message handler
234
+ messenger.onMessage(async (ctx) => {
235
+ const traceId = generateTraceId();
236
+ ctx.traceId = traceId;
237
+ ctx.logger = createLogger({ traceId, platform: ctx.platform, component: 'cli' });
238
+ ctx.logger.info({ event: 'message.received', text: ctx.message.text.substring(0, 120), userId: ctx.message.userId });
239
+ // Approval interception comes BEFORE the agent router. If a pending
240
+ // approval exists for this thread and the message is a y/n-style
241
+ // reply, we resolve the approval and stop. Anything else routes
242
+ // normally (with the side effect of auto-denying any stale pending —
243
+ // see approval-router.ts).
244
+ if (tryHandleApprovalReply(threadKey(ctx.platform, ctx.channelId, ctx.message.threadId), ctx.message.text)) {
245
+ ctx.logger.info({ event: 'message.consumed_by_approval' });
246
+ return;
247
+ }
248
+ // Reminder-intent confirmation reply ("y"/"n" to a previously-
249
+ // proposed reminder card). Must come BEFORE the detector so a
250
+ // user replying "y" doesn't get re-detected as another reminder.
251
+ const tk = `${ctx.platform}:${ctx.channelId}:${ctx.message.threadId}`;
252
+ // Location-context injection: if the user shared a native pin /
253
+ // venue on Telegram or Feishu within the last 5 min, the adapter
254
+ // stashed the WGS-84 coords in core/location-context. Prepend a
255
+ // structured annotation to the user's text so the agent can decide
256
+ // what to do (save_memo with these coords, ignore, ask follow-up).
257
+ // Single-shot: consuming clears the stash so the next turn doesn't
258
+ // accidentally re-inject. Matches the WeChat H5-flow architecture:
259
+ // the agent is the decision-maker; adapters just surface data.
260
+ const locCtxStash = consumeLocationContext(tk);
261
+ if (locCtxStash) {
262
+ const annotation = formatLocationAnnotation(locCtxStash);
263
+ ctx.message.text = `${annotation}\n\n${ctx.message.text}`;
264
+ ctx.logger.info({
265
+ event: 'message.location_context_injected',
266
+ lat: locCtxStash.lat,
267
+ lng: locCtxStash.lng,
268
+ hasVenue: !!locCtxStash.venueLabel,
269
+ });
270
+ }
271
+ const replyDecision = tryConsumePendingReminderReply(tk, ctx.message.text);
272
+ if (replyDecision) {
273
+ if (replyDecision.decision === 'cancel') {
274
+ await messenger.sendMessage(ctx.message.threadId, '✅ 已忽略这条提醒建议');
275
+ }
276
+ else {
277
+ const p = replyDecision.pending;
278
+ try {
279
+ const id = createReminder({
280
+ fireAt: p.fireAt,
281
+ text: p.text,
282
+ platform: ctx.platform,
283
+ channelId: ctx.channelId,
284
+ threadId: ctx.message.threadId,
285
+ userId: ctx.message.userId || '',
286
+ source: 'slash',
287
+ recurrence: p.recurrence,
288
+ });
289
+ const recurLine = p.recurrence ? `\n 循环:${p.recurrence}` : '';
290
+ await messenger.sendMessage(ctx.message.threadId, `✅ 已创建提醒 #${id}\n 触发:${p.fireAt.toLocaleString()}${recurLine}\n 内容:${p.text}`);
291
+ }
292
+ catch (err) {
293
+ const msg = err instanceof Error ? err.message : String(err);
294
+ await messenger.sendMessage(ctx.message.threadId, `❌ 创建失败:${msg}`);
295
+ }
296
+ }
297
+ ctx.logger.info({ event: 'message.consumed_by_pending_reminder', decision: replyDecision.decision });
298
+ return;
299
+ }
300
+ // Reminder-intent detector: only on non-slash messages and only
301
+ // when the user has aiwatch on. Cheap regex pre-filter inside the
302
+ // detector skips most messages without a time hint, so the LLM
303
+ // call only fires on plausible candidates.
304
+ if (!ctx.message.text.trim().startsWith('/')) {
305
+ try {
306
+ const detected = await tryDetectReminderIntent({
307
+ platform: ctx.platform,
308
+ channelId: ctx.channelId,
309
+ threadId: ctx.message.threadId,
310
+ userId: ctx.message.userId || '',
311
+ text: ctx.message.text,
312
+ });
313
+ if (detected.handled && detected.reply) {
314
+ await messenger.sendMessage(ctx.message.threadId, detected.reply);
315
+ ctx.logger.info({ event: 'message.proposed_reminder' });
316
+ return;
317
+ }
318
+ }
319
+ catch (err) {
320
+ // Detector must never break the message pipeline. Log and
321
+ // fall through to normal agent dispatch.
322
+ ctx.logger.warn({
323
+ event: 'remind-intent.crashed',
324
+ err: err instanceof Error ? err.message : String(err),
325
+ }, 'detector crashed — falling through to normal agent flow');
326
+ }
327
+ }
328
+ await handleMessage(ctx, config.defaultAgent);
329
+ });
330
+ try {
331
+ await messenger.start();
332
+ console.log(`✅ Started messenger: ${name}`);
333
+ }
334
+ catch (error) {
335
+ const errMsg = error instanceof Error ? error.message : String(error);
336
+ console.error(`❌ Failed to start messenger ${name}:`);
337
+ console.error(` ${errMsg}`);
338
+ // Show actionable next step, not stack trace
339
+ const hint = formatMessengerStartError(name, error);
340
+ if (hint !== errMsg) {
341
+ console.error(` ${hint}`);
342
+ }
343
+ }
344
+ }
345
+ // ============================================
346
+ // WIRE APPROVAL ROUTER (after messengers are up)
347
+ // ============================================
348
+ if (approvalBus.getSocketPath()) {
349
+ installApprovalRouter({
350
+ resolveMessenger: (platform) => registry.getMessenger(platformToMessengerName(platform)),
351
+ });
352
+ console.log('✅ Approval router wired to messengers');
353
+ }
354
+ // Reminder confirm notifier — used by reminder-rpc.ts when an agent
355
+ // creates a high-frequency + LLM-polish reminder (those go through a
356
+ // y/n card before landing in the DB). Plain message via the same
357
+ // messenger the agent is talking on.
358
+ setReminderConfirmNotifier(async (ctx, message) => {
359
+ const messenger = registry.getMessenger(platformToMessengerName(ctx.platform))
360
+ ?? registry.getMessenger(ctx.platform);
361
+ if (!messenger) {
362
+ console.warn(`reminder-confirm: no messenger for platform "${ctx.platform}"`);
363
+ return;
364
+ }
365
+ await messenger.sendMessage(ctx.threadId, message);
366
+ });
367
+ // ============================================
368
+ // START WEB CHAT SERVER
369
+ // ============================================
370
+ let webServer;
371
+ try {
372
+ webServer = await startWebServer({
373
+ port: config.webPort,
374
+ defaultAgent: config.defaultAgent,
375
+ });
376
+ }
377
+ catch (err) {
378
+ const errMsg = err instanceof Error ? err.message : String(err);
379
+ console.warn(`⚠️ Web chat server failed to start: ${errMsg}`);
380
+ }
381
+ // ============================================
382
+ // START ACP SERVER
383
+ // ============================================
384
+ let acpServer;
385
+ const acpPort = config.acpPort || undefined;
386
+ try {
387
+ acpServer = await startACPServer({
388
+ port: acpPort,
389
+ defaultAgent: config.defaultAgent,
390
+ });
391
+ }
392
+ catch (err) {
393
+ const errMsg = err instanceof Error ? err.message : String(err);
394
+ console.warn(`⚠️ ACP server failed to start: ${errMsg}`);
395
+ }
396
+ console.log('\n✅ IM hub is running!');
397
+ if (webServer) {
398
+ console.log(` Chat UI: http://localhost:${webServer.port}`);
399
+ }
400
+ if (acpServer) {
401
+ console.log(` ACP Endpoint: http://localhost:${acpServer.port}`);
402
+ }
403
+ console.log('Press Ctrl+C to stop');
404
+ // Keep process alive
405
+ process.on('SIGINT', async () => {
406
+ console.log('\n👋 Shutting down...');
407
+ sessionManager.stop();
408
+ webServer?.close();
409
+ acpServer?.close();
410
+ // Stop all messengers
411
+ for (const name of registry.listMessengers()) {
412
+ const messenger = registry.getMessenger(name);
413
+ if (messenger) {
414
+ await messenger.stop();
415
+ }
416
+ }
417
+ // Stop approval bus last — denies any in-flight approvals so sidecar
418
+ // processes don't hang. Always called even if start() failed earlier.
419
+ try {
420
+ await approvalBus.stop();
421
+ }
422
+ catch { /* ignore */ }
423
+ // L2: stop background sweepers and checkpoint SQLite WAL files before
424
+ // exiting. Without these, the next startup has to recover the WAL
425
+ // (cheap but visible in logs and slows boot).
426
+ try {
427
+ const { stopRetentionSweep, closeAuditDb } = await import('./core/audit-log.js');
428
+ stopRetentionSweep();
429
+ closeAuditDb();
430
+ }
431
+ catch { /* ignore */ }
432
+ try {
433
+ const { stopJobRetentionSweep, closeJobBoardDb } = await import('./core/job-board.js');
434
+ stopJobRetentionSweep();
435
+ closeJobBoardDb();
436
+ }
437
+ catch { /* ignore */ }
438
+ try {
439
+ const { stopScheduler, closeScheduleDb } = await import('./core/schedule.js');
440
+ stopScheduler();
441
+ closeScheduleDb();
442
+ }
443
+ catch { /* ignore */ }
444
+ try {
445
+ const { stopReminderEngine, closeReminderDb } = await import('./core/reminders.js');
446
+ stopReminderEngine();
447
+ closeReminderDb();
448
+ }
449
+ catch { /* ignore */ }
450
+ try {
451
+ const { closeMemosDb } = await import('./core/memos.js');
452
+ closeMemosDb(); // also stops the sweep timer
453
+ }
454
+ catch { /* ignore */ }
455
+ process.exit(0);
456
+ });
457
+ // Wait forever
458
+ await new Promise(() => { });
459
+ });
460
+ /**
461
+ * Handle incoming message from any messenger
462
+ */
463
+ async function handleMessage(ctx, defaultAgent) {
464
+ const { message, platform } = ctx;
465
+ const traceId = ctx.traceId || generateTraceId();
466
+ const logger = ctx.logger || createLogger({ traceId, platform, component: 'cli' });
467
+ const messengerName = platform === 'wechat' ? 'wechat-ilink' : platform;
468
+ const messenger = registry.getMessenger(messengerName);
469
+ if (!messenger) {
470
+ console.error(`No messenger found for platform: ${platform}`);
471
+ return;
472
+ }
473
+ // Agent-name prefix removed — historically we prepended "[opencode]\n\n"
474
+ // / "[codex]\n\n" / etc. so users could see which agent answered when
475
+ // they were on a non-default sticky session. In practice it cluttered
476
+ // every reply (most users only ever stick with one agent per channel)
477
+ // and made memo/reminder confirmations look noisy. If the user wants to
478
+ // know the active agent they can run `/status` or `/router status`.
479
+ const maybePrefix = async (text) => text;
480
+ const stopTyping = async () => {
481
+ if (messenger.sendTyping) {
482
+ try {
483
+ await messenger.sendTyping(message.threadId, false);
484
+ }
485
+ catch {
486
+ // Ignore typing errors
487
+ }
488
+ }
489
+ };
490
+ // Build route context with trace
491
+ const routeCtx = {
492
+ threadId: message.threadId,
493
+ channelId: ctx.channelId,
494
+ platform,
495
+ defaultAgent,
496
+ traceId,
497
+ logger,
498
+ userId: message.userId,
499
+ };
500
+ // Thinking placeholder — sent if the adapter supports it, dismissed (when
501
+ // the adapter knows how) just before the real response goes out. Skip for
502
+ // y/n-style approval replies which already get a same-thread effect from
503
+ // the resolved approval card; sending "🤔 思考中…" there would just add
504
+ // noise. We approximate that filter by skipping placeholders for messages
505
+ // that look like single-token approval words.
506
+ let dismissThinking;
507
+ const looksLikeApproval = /^\s*[yn]\s*$/i.test(message.text) ||
508
+ /^\s*(批准|拒绝|同意|不同意|通过|可以|不可以|不行|✅|❌)\s*$/.test(message.text);
509
+ try {
510
+ if (messenger.sendTyping) {
511
+ messenger.sendTyping(message.threadId, true).catch(() => { });
512
+ }
513
+ const parsed = parseMessage(message.text);
514
+ logger.debug({ event: 'router.parse', parsed: parsed.type });
515
+ // Only show "🤔 思考中…" for messages that will actually go through the
516
+ // agent — built-in/system commands (/help /status /audit /router etc.)
517
+ // respond instantly so a placeholder would race the real reply.
518
+ const willInvokeAgent = (parsed.type === 'default' || parsed.type === 'agent' || parsed.type === 'agentCommand') &&
519
+ !looksLikeApproval;
520
+ // Prepare native agent sessions lazily — the router calls
521
+ // onAgentResolved once intent classification picks the final agent,
522
+ // so we never allocate a Claude UUID for an opencode run (or vice versa).
523
+ //
524
+ // Important: claude-code writes its session jsonl as soon as the run
525
+ // starts, so markClaudeSessionPrimed MUST happen before the adapter is
526
+ // invoked. If the run later errors, the next turn must use --resume or
527
+ // claude refuses with "session already exists". We therefore mark
528
+ // primed inside onAgentResolved (right after we wire agentSessionId)
529
+ // rather than after routeMessage returns.
530
+ if (willInvokeAgent) {
531
+ routeCtx.onAgentResolved = async (resolvedAgent) => {
532
+ const stickySession = await sessionManager.getExistingSession(platform, ctx.channelId, message.threadId);
533
+ if (resolvedAgent === 'claude-code') {
534
+ let claudeId = stickySession?.claudeSessionId;
535
+ if (!claudeId) {
536
+ claudeId = randomUUID();
537
+ await sessionManager.getOrCreateSession(platform, ctx.channelId, message.threadId, resolvedAgent);
538
+ await sessionManager.setClaudeSessionId(platform, ctx.channelId, message.threadId, claudeId);
539
+ }
540
+ routeCtx.agentSessionId = claudeId;
541
+ const willResume = !!stickySession?.claudeSessionPrimed;
542
+ routeCtx.agentSessionResume = willResume;
543
+ if (!willResume) {
544
+ try {
545
+ await sessionManager.markClaudeSessionPrimed(platform, ctx.channelId, message.threadId);
546
+ }
547
+ catch (err) {
548
+ logger.debug({ err: String(err) }, 'markClaudeSessionPrimed failed');
549
+ }
550
+ }
551
+ }
552
+ else if (resolvedAgent === 'opencode') {
553
+ const ocId = stickySession?.opencodeSessionId;
554
+ if (ocId) {
555
+ routeCtx.agentSessionId = ocId;
556
+ routeCtx.agentSessionResume = true;
557
+ }
558
+ await sessionManager.getOrCreateSession(platform, ctx.channelId, message.threadId, resolvedAgent);
559
+ }
560
+ else if (resolvedAgent === 'codex') {
561
+ const cxId = stickySession?.codexSessionId;
562
+ if (cxId) {
563
+ routeCtx.agentSessionId = cxId;
564
+ routeCtx.agentSessionResume = true;
565
+ }
566
+ await sessionManager.getOrCreateSession(platform, ctx.channelId, message.threadId, resolvedAgent);
567
+ }
568
+ };
569
+ }
570
+ if (willInvokeAgent && messenger.sendThinking) {
571
+ try {
572
+ dismissThinking = await messenger.sendThinking(message.threadId, '🤔 思考中…');
573
+ }
574
+ catch (err) {
575
+ logger.debug({ err: String(err) }, 'sendThinking failed');
576
+ }
577
+ }
578
+ const result = await routeMessage(parsed, routeCtx);
579
+ const dismiss = async () => {
580
+ if (dismissThinking) {
581
+ try {
582
+ await dismissThinking();
583
+ }
584
+ catch { /* ignore */ }
585
+ dismissThinking = undefined;
586
+ }
587
+ };
588
+ // Handle response (string or async generator)
589
+ if (typeof result === 'string') {
590
+ await stopTyping();
591
+ await dismiss();
592
+ await messenger.sendMessage(message.threadId, await maybePrefix(result));
593
+ logger.info({ event: 'message.sent', responseLen: result.length });
594
+ }
595
+ else {
596
+ // Stream response chunks
597
+ let fullResponse = '';
598
+ for await (const chunk of result) {
599
+ fullResponse += chunk;
600
+ }
601
+ await stopTyping();
602
+ await dismiss();
603
+ if (fullResponse) {
604
+ await messenger.sendMessage(message.threadId, await maybePrefix(fullResponse));
605
+ logger.info({ event: 'message.sent', responseLen: fullResponse.length });
606
+ }
607
+ else {
608
+ logger.warn({ event: 'message.empty_response' });
609
+ }
610
+ }
611
+ }
612
+ catch (error) {
613
+ const errMsg = error instanceof Error ? error.message : String(error);
614
+ logger.error({ event: 'message.error', error: errMsg });
615
+ await stopTyping();
616
+ if (dismissThinking) {
617
+ try {
618
+ await dismissThinking();
619
+ }
620
+ catch { /* ignore */ }
621
+ }
622
+ try {
623
+ await messenger.sendMessage(message.threadId, '❌ An error occurred processing your message.');
624
+ }
625
+ catch {
626
+ // Ignore
627
+ }
628
+ }
629
+ }
630
+ program
631
+ .command('config [component]')
632
+ .description('Interactive config wizard (or `config <component>` for the per-component legacy flow)')
633
+ .action(async (component) => {
634
+ if (!component) {
635
+ // New interactive wizard (since v0.5.0). Falls through to the
636
+ // per-component switch below when an explicit `config wechat` etc.
637
+ // is passed — keeps scripted setups working.
638
+ const { pickLang } = await import('./cli-ui/lang-picker.js');
639
+ const { runConfigWizard } = await import('./cli-ui/config-wizard.js');
640
+ const lang = await pickLang();
641
+ await runConfigWizard(lang);
642
+ return;
643
+ }
644
+ const config = await loadConfig();
645
+ switch (component) {
646
+ case 'wechat': {
647
+ console.log('📱 Configuring WeChat adapter...');
648
+ console.log('Fetching QR code...\n');
649
+ // Import the iLink adapter for QR login
650
+ const { ILinkWeChatAdapter } = await import('./plugins/messengers/wechat/ilink-adapter.js');
651
+ const adapter = new ILinkWeChatAdapter();
652
+ try {
653
+ // Get QR code URL and token
654
+ const { qrUrl, qrToken } = await adapter.startQRLogin();
655
+ console.log('📱 Scan this QR code with WeChat:\n');
656
+ console.log(qrUrl);
657
+ console.log('\n');
658
+ // Poll for login status
659
+ const credentials = await adapter.waitForQRLogin(qrToken, (status) => {
660
+ console.log(`[${new Date().toLocaleTimeString()}] ${status}`);
661
+ });
662
+ if (credentials) {
663
+ console.log(`\n✅ Logged in as ${credentials.userId}`);
664
+ console.log(` Bot ID: ${credentials.accountId}`);
665
+ // Add wechat-ilink to config
666
+ if (!config.messengers.includes('wechat-ilink')) {
667
+ config.messengers.push('wechat-ilink');
668
+ }
669
+ }
670
+ else {
671
+ console.log('\n❌ Login failed or timed out');
672
+ return;
673
+ }
674
+ }
675
+ catch (error) {
676
+ console.error('\n❌ Failed to configure WeChat:', error);
677
+ return;
678
+ }
679
+ break;
680
+ }
681
+ case 'claude': {
682
+ console.log('🤖 Configuring Claude Code agent...');
683
+ // Check if claude CLI is available
684
+ const checkClaude = crossSpawn('claude', ['--version'], { stdio: 'ignore' });
685
+ checkClaude.on('close', (code) => {
686
+ if (code === 0) {
687
+ console.log('✅ Claude Code CLI found!');
688
+ }
689
+ else {
690
+ console.log('❌ Claude Code CLI not found.');
691
+ console.log('Install with: npm install -g @anthropic-ai/claude-code');
692
+ }
693
+ });
694
+ if (!config.agents.includes('claude-code')) {
695
+ config.agents.push('claude-code');
696
+ }
697
+ config.defaultAgent = 'claude-code';
698
+ break;
699
+ }
700
+ case 'codex': {
701
+ console.log('🤖 Configuring Codex agent...');
702
+ const checkCodex = crossSpawn('codex', ['--version'], { stdio: 'ignore' });
703
+ checkCodex.on('close', (code) => {
704
+ if (code === 0) {
705
+ console.log('✅ Codex CLI found!');
706
+ }
707
+ else {
708
+ console.log('❌ Codex CLI not found.');
709
+ console.log('Install with: npm install -g @openai/codex');
710
+ }
711
+ });
712
+ if (!config.agents.includes('codex')) {
713
+ config.agents.push('codex');
714
+ }
715
+ config.defaultAgent = 'codex';
716
+ break;
717
+ }
718
+ case 'telegram': {
719
+ console.log('📱 Configuring Telegram adapter...');
720
+ console.log('To get a bot token:');
721
+ console.log('1. Open Telegram and search for @BotFather');
722
+ console.log('2. Send /newbot and follow instructions');
723
+ console.log('3. Copy the bot token\n');
724
+ const { createInterface } = await import('node:readline');
725
+ const rl = createInterface({
726
+ input: process.stdin,
727
+ output: process.stdout,
728
+ });
729
+ const token = await new Promise((resolve) => {
730
+ rl.question('Enter your bot token: ', (answer) => {
731
+ resolve(answer.trim());
732
+ });
733
+ });
734
+ if (!token) {
735
+ console.log('❌ Bot token is required');
736
+ return;
737
+ }
738
+ const channelId = await new Promise((resolve) => {
739
+ rl.question('Enter channel ID (optional, press Enter for default): ', (answer) => {
740
+ resolve(answer.trim() || 'default');
741
+ });
742
+ });
743
+ rl.close();
744
+ config.telegram = { botToken: token, channelId };
745
+ if (!config.messengers.includes('telegram')) {
746
+ config.messengers.push('telegram');
747
+ }
748
+ console.log('✅ Telegram bot token saved');
749
+ console.log(` Channel ID: ${channelId}`);
750
+ break;
751
+ }
752
+ case 'feishu': {
753
+ console.log('📱 Configuring Feishu adapter (WebSocket long polling mode)...');
754
+ console.log('To create a Feishu bot:');
755
+ console.log('1. Go to https://open.feishu.cn/app');
756
+ console.log('2. Create a custom bot app');
757
+ console.log('3. Enable Bot capability');
758
+ console.log('4. Configure event subscriptions (Subscribe to "im.message.receive_v1" event)');
759
+ console.log('5. Go to Permissions management and enable: im:message, im:message.p2p_msg:readonly, im:message:send_as_bot');
760
+ console.log('6. Create a version and publish it');
761
+ console.log('7. Copy App ID and App Secret\n');
762
+ const { createInterface: createRl } = await import('node:readline');
763
+ const feishuRl = createRl({
764
+ input: process.stdin,
765
+ output: process.stdout,
766
+ });
767
+ const appId = await new Promise((resolve) => {
768
+ feishuRl.question('Enter App ID: ', (answer) => {
769
+ resolve(answer.trim());
770
+ });
771
+ });
772
+ const appSecret = await new Promise((resolve) => {
773
+ feishuRl.question('Enter App Secret: ', (answer) => {
774
+ resolve(answer.trim());
775
+ });
776
+ });
777
+ feishuRl.close();
778
+ if (!appId || !appSecret) {
779
+ console.log('❌ App ID and App Secret are required');
780
+ return;
781
+ }
782
+ config.feishu = {
783
+ appId,
784
+ appSecret
785
+ };
786
+ if (!config.messengers.includes('feishu')) {
787
+ config.messengers.push('feishu');
788
+ }
789
+ console.log('✅ Feishu bot credentials saved');
790
+ console.log(`\n✅ Using WebSocket long polling mode - no webhook configuration needed!`);
791
+ console.log(` The bot will automatically connect to Feishu servers.`);
792
+ break;
793
+ }
794
+ case 'discord': {
795
+ console.log('📱 Configuring Discord adapter...');
796
+ console.log('To create a Discord bot:');
797
+ console.log('1. Go to https://discord.com/developers/applications');
798
+ console.log('2. Click "New Application" and give it a name');
799
+ console.log('3. Go to "Bot" tab and click "Add Bot"');
800
+ console.log('4. IMPORTANT: Enable "MESSAGE CONTENT INTENT" under Privileged Gateway Intents');
801
+ console.log('5. Click "Reset Token" to get your bot token');
802
+ console.log('6. Use OAuth2 URL Generator to invite the bot to your server\n');
803
+ const { createInterface: createDiscordRl } = await import('node:readline');
804
+ const discordRl = createDiscordRl({
805
+ input: process.stdin,
806
+ output: process.stdout,
807
+ });
808
+ const discordToken = await new Promise((resolve) => {
809
+ discordRl.question('Enter your bot token: ', (answer) => {
810
+ resolve(answer.trim());
811
+ });
812
+ });
813
+ if (!discordToken) {
814
+ console.log('❌ Bot token is required');
815
+ discordRl.close();
816
+ return;
817
+ }
818
+ const discordChannelId = await new Promise((resolve) => {
819
+ discordRl.question('Enter channel ID (optional, press Enter for default): ', (answer) => {
820
+ resolve(answer.trim() || 'default');
821
+ });
822
+ });
823
+ const allowedGuilds = await new Promise((resolve) => {
824
+ discordRl.question('Allowed guild IDs (comma-separated, optional): ', (answer) => {
825
+ resolve(answer.trim());
826
+ });
827
+ });
828
+ const allowedChannels = await new Promise((resolve) => {
829
+ discordRl.question('Allowed channel IDs (comma-separated, optional): ', (answer) => {
830
+ resolve(answer.trim());
831
+ });
832
+ });
833
+ discordRl.close();
834
+ config.discord = {
835
+ botToken: discordToken,
836
+ channelId: discordChannelId,
837
+ allowedGuilds: allowedGuilds ? allowedGuilds.split(',').map(s => s.trim()).filter(Boolean) : undefined,
838
+ allowedChannels: allowedChannels ? allowedChannels.split(',').map(s => s.trim()).filter(Boolean) : undefined,
839
+ };
840
+ if (!config.messengers.includes('discord')) {
841
+ config.messengers.push('discord');
842
+ }
843
+ console.log('✅ Discord bot token saved');
844
+ console.log(` Channel ID: ${discordChannelId}`);
845
+ if (config.discord.allowedGuilds?.length) {
846
+ console.log(` Allowed guilds: ${config.discord.allowedGuilds.join(', ')}`);
847
+ }
848
+ if (config.discord.allowedChannels?.length) {
849
+ console.log(` Allowed channels: ${config.discord.allowedChannels.join(', ')}`);
850
+ }
851
+ break;
852
+ }
853
+ case 'opencode': {
854
+ console.log('🤖 Configuring OpenCode agent...');
855
+ // Check if opencode CLI is available
856
+ const openCodeAvailable = await new Promise((resolve) => {
857
+ const proc = crossSpawn('opencode', ['--version'], { stdio: 'ignore' });
858
+ proc.on('error', () => resolve(false));
859
+ proc.on('close', (code) => resolve(code === 0));
860
+ });
861
+ if (openCodeAvailable) {
862
+ console.log('✅ OpenCode CLI found!');
863
+ console.log('\nTo authenticate, run: opencode auth login');
864
+ }
865
+ else {
866
+ console.log('❌ OpenCode CLI not found.');
867
+ console.log('Install with: npm i -g opencode-ai@latest');
868
+ console.log('Or visit: https://github.com/anomalyco/opencode');
869
+ }
870
+ if (!config.agents.includes('opencode')) {
871
+ config.agents.push('opencode');
872
+ }
873
+ config.defaultAgent = 'opencode';
874
+ break;
875
+ }
876
+ case 'copilot': {
877
+ console.log('🤖 Configuring GitHub Copilot CLI agent...');
878
+ // Check if copilot CLI is available (multiple installation methods)
879
+ const { copilotAdapter } = await import('./plugins/agents/copilot/index.js');
880
+ const copilotAvailable = await copilotAdapter.isAvailable();
881
+ if (copilotAvailable) {
882
+ console.log('✅ GitHub Copilot CLI found!');
883
+ }
884
+ else {
885
+ console.log('❌ GitHub Copilot CLI not found.');
886
+ console.log('\n安装方式 (选择其一):');
887
+ console.log(' npm i -g @github/copilot');
888
+ console.log(' gh extension install github/gh-copilot');
889
+ if (isMac) {
890
+ console.log(' brew install copilot-cli');
891
+ }
892
+ if (isWindows) {
893
+ console.log(' winget install GitHub.Copilot');
894
+ }
895
+ console.log(' 或安装 VS Code Copilot Chat 扩展');
896
+ console.log('\n详情: https://github.com/features/copilot/cli');
897
+ }
898
+ if (!config.agents.includes('copilot')) {
899
+ config.agents.push('copilot');
900
+ }
901
+ config.defaultAgent = 'copilot';
902
+ break;
903
+ }
904
+ case 'agent': {
905
+ console.log('🔌 Configuring remote ACP agent...');
906
+ console.log('This adds a remote agent that speaks the Agent Communication Protocol.');
907
+ console.log('ACP is an open standard (https://agentcommunicationprotocol.dev)\n');
908
+ const { createInterface: createAgentRl } = await import('node:readline');
909
+ const agentRl = createAgentRl({ input: process.stdin, output: process.stdout });
910
+ const agentName = await new Promise((resolve) => {
911
+ agentRl.question('Agent name (e.g. openclaw-dev): ', (answer) => resolve(answer.trim()));
912
+ });
913
+ if (!agentName) {
914
+ console.log('❌ Name is required');
915
+ agentRl.close();
916
+ return;
917
+ }
918
+ const agentAlias = await new Promise((resolve) => {
919
+ agentRl.question('Aliases, comma-separated (optional): ', (answer) => resolve(answer.trim()));
920
+ });
921
+ const endpoint = await new Promise((resolve) => {
922
+ agentRl.question('ACP endpoint URL (e.g. http://localhost:8080): ', (answer) => resolve(answer.trim()));
923
+ });
924
+ if (!endpoint) {
925
+ console.log('❌ Endpoint is required');
926
+ agentRl.close();
927
+ return;
928
+ }
929
+ console.log('\nAuthentication type:');
930
+ console.log(' 1. none');
931
+ console.log(' 2. apikey');
932
+ console.log(' 3. bearer');
933
+ const authTypeInput = await new Promise((resolve) => {
934
+ agentRl.question('Choose (1-3, default: none): ', (answer) => resolve(answer.trim() || '1'));
935
+ });
936
+ const authTypeMap = { '1': 'none', '2': 'apikey', '3': 'bearer' };
937
+ const authType = authTypeMap[authTypeInput] || 'none';
938
+ let auth;
939
+ if (authType !== 'none') {
940
+ const token = await new Promise((resolve) => {
941
+ agentRl.question('Auth token: ', (answer) => resolve(answer.trim()));
942
+ });
943
+ if (!token) {
944
+ console.log('❌ Token is required when auth is enabled');
945
+ agentRl.close();
946
+ return;
947
+ }
948
+ auth = { type: authType, token };
949
+ }
950
+ agentRl.close();
951
+ // Validate connection
952
+ console.log('\n🔍 Testing connection...');
953
+ const { ACPClient } = await import('./plugins/agents/acp/acp-client.js');
954
+ const testClient = new ACPClient({ name: agentName, endpoint, auth });
955
+ try {
956
+ const manifest = await testClient.fetchManifest();
957
+ console.log(`✅ Connected! Agent: ${manifest.name}`);
958
+ if (manifest.description)
959
+ console.log(` ${manifest.description}`);
960
+ }
961
+ catch (e) {
962
+ const msg = e instanceof Error ? e.message : String(e);
963
+ console.log(`⚠️ Connection failed: ${msg}`);
964
+ console.log(' Agent will be saved but may not work until endpoint is available.');
965
+ }
966
+ // Save
967
+ if (!config.acpAgents)
968
+ config.acpAgents = [];
969
+ const existing = config.acpAgents.findIndex(a => a.name === agentName);
970
+ const agentConfig = {
971
+ name: agentName,
972
+ aliases: agentAlias?.split(',').map(s => s.trim()).filter(Boolean) || [],
973
+ endpoint,
974
+ auth,
975
+ enabled: true
976
+ };
977
+ if (existing >= 0) {
978
+ config.acpAgents[existing] = agentConfig;
979
+ }
980
+ else {
981
+ config.acpAgents.push(agentConfig);
982
+ }
983
+ break;
984
+ }
985
+ default:
986
+ console.log(`Unknown component: ${component}`);
987
+ console.log('Run "agim config" to see available components.');
988
+ return;
989
+ }
990
+ await saveConfig(config);
991
+ console.log(`\n✅ Configuration saved to ${CONFIG_FILE}`);
992
+ });
993
+ program
994
+ .command('agents')
995
+ .description('List available agents')
996
+ .action(async () => {
997
+ await registry.loadBuiltInPlugins();
998
+ const config = await loadConfig();
999
+ if (config.acpAgents?.length) {
1000
+ await registry.loadACPAgents(config.acpAgents);
1001
+ }
1002
+ const agents = registry.listAgents();
1003
+ if (agents.length === 0) {
1004
+ console.log('No agents registered yet.');
1005
+ console.log('Run "agim config claude" to configure Claude Code.');
1006
+ console.log('Run "agim config agent" to add a remote ACP agent.');
1007
+ return;
1008
+ }
1009
+ console.log('🤖 Checking agents...\n');
1010
+ // Check all agents in parallel to avoid slow sequential timeouts
1011
+ const results = await Promise.allSettled(agents.map(async (name) => {
1012
+ const agent = registry.getAgent(name);
1013
+ const available = await agent?.isAvailable().catch(() => false);
1014
+ // Check if this is an ACP agent with extra info
1015
+ let info = '';
1016
+ try {
1017
+ const { ACPAdapter } = await import('./plugins/agents/acp/acp-adapter.js');
1018
+ if (agent instanceof ACPAdapter) {
1019
+ const manifest = await agent.getManifest().catch(() => undefined);
1020
+ if (manifest) {
1021
+ info = ` — ${manifest.description || 'Remote ACP agent'}`;
1022
+ }
1023
+ }
1024
+ }
1025
+ catch { /* not an ACP agent */ }
1026
+ return { name, available, aliases: agent?.aliases || [], info };
1027
+ }));
1028
+ for (const result of results) {
1029
+ if (result.status === 'fulfilled') {
1030
+ const { name, available, aliases, info } = result.value;
1031
+ const aliasStr = aliases.length ? ` (${aliases.join(', ')})` : '';
1032
+ console.log(` ${available ? '✅' : '❌'} ${name}${aliasStr}${info}`);
1033
+ }
1034
+ }
1035
+ });
1036
+ program
1037
+ .command('messengers')
1038
+ .description('List available messengers')
1039
+ .action(async () => {
1040
+ await registry.loadBuiltInPlugins();
1041
+ const messengers = registry.listMessengers();
1042
+ if (messengers.length === 0) {
1043
+ console.log('No messengers registered yet.');
1044
+ console.log('Run "agim config wechat" to configure WeChat.');
1045
+ return;
1046
+ }
1047
+ console.log('📱 Available Messengers:\n');
1048
+ for (const name of messengers) {
1049
+ console.log(` ${name}`);
1050
+ }
1051
+ });
1052
+ // ─── service-management commands (v0.5.0) ──────────────────────────────
1053
+ // status / restart / stop / uninstall — all delegate to cli-ui/cmd-handlers.ts.
1054
+ program
1055
+ .command('status')
1056
+ .description('Show whether the bridge is running (systemd / background / foreground)')
1057
+ .action(async () => {
1058
+ const { cmdStatus } = await import('./cli-ui/cmd-handlers.js');
1059
+ cmdStatus();
1060
+ });
1061
+ program
1062
+ .command('restart')
1063
+ .description('Restart the bridge (autodetects systemd vs background daemon)')
1064
+ .action(async () => {
1065
+ const { cmdRestart } = await import('./cli-ui/cmd-handlers.js');
1066
+ await cmdRestart();
1067
+ });
1068
+ program
1069
+ .command('stop')
1070
+ .description('Stop the bridge (systemd unit or background daemon)')
1071
+ .action(async () => {
1072
+ const { cmdStop } = await import('./cli-ui/cmd-handlers.js');
1073
+ await cmdStop();
1074
+ });
1075
+ program
1076
+ .command('uninstall')
1077
+ .description('Stop service, remove npm install + config; keeps ~/.im-hub-workspaces/')
1078
+ .action(async () => {
1079
+ const { cmdUninstall } = await import('./cli-ui/cmd-handlers.js');
1080
+ await cmdUninstall();
1081
+ });
1082
+ // ─── bare `agim` → entry menu (v0.5.0+) ─────────────────────────────
1083
+ // If the user invoked us with no args (just `agim`), drop them at
1084
+ // the language picker + maintenance menu instead of dumping --help text.
1085
+ //
1086
+ // This is intentionally BEFORE program.parse() so the menu runs even
1087
+ // though commander would otherwise fall through to its default no-command
1088
+ // branch. We check process.argv length to detect "no subcommand passed".
1089
+ async function maybeRunEntryMenu() {
1090
+ // argv layout: [node, /path/to/cli.js, ...rest]. No subcommand = length 2.
1091
+ if (process.argv.length > 2)
1092
+ return false;
1093
+ if (!process.stdin.isTTY)
1094
+ return false; // piped / non-interactive — fall back to commander's help
1095
+ const { runEntryMenu } = await import('./cli-ui/entry-menu.js');
1096
+ const { cmdStatus, cmdStart, cmdStop, cmdRestart, cmdUninstall } = await import('./cli-ui/cmd-handlers.js');
1097
+ const { action, lang } = await runEntryMenu();
1098
+ switch (action.kind) {
1099
+ case 'quit':
1100
+ return true;
1101
+ case 'status':
1102
+ cmdStatus();
1103
+ return true;
1104
+ case 'config': {
1105
+ // Reuse the lang the user just picked in runEntryMenu — re-prompting
1106
+ // here was a v0.5.1 regression (user reported: "已经选过语言不要再问").
1107
+ const { runConfigWizard } = await import('./cli-ui/config-wizard.js');
1108
+ await runConfigWizard(lang);
1109
+ return true;
1110
+ }
1111
+ case 'start':
1112
+ // For foreground start from the menu, re-exec ourselves with the
1113
+ // proper subcommand so commander's `start` action runs. Easier than
1114
+ // duplicating the 500-line start body.
1115
+ if (action.bg) {
1116
+ await cmdStart(true, async () => { });
1117
+ }
1118
+ else {
1119
+ // Re-exec in the same shell so Ctrl-C maps cleanly. Dynamic
1120
+ // `await import` works in ESM (require() doesn't).
1121
+ const { spawn } = await import('node:child_process');
1122
+ await cmdStart(false, () => new Promise((resolve) => {
1123
+ const child = spawn(process.execPath, [process.argv[1], 'start'], { stdio: 'inherit' });
1124
+ child.on('exit', () => resolve());
1125
+ }));
1126
+ }
1127
+ return true;
1128
+ case 'restart':
1129
+ await cmdRestart();
1130
+ return true;
1131
+ case 'stop':
1132
+ await cmdStop();
1133
+ return true;
1134
+ case 'uninstall':
1135
+ await cmdUninstall();
1136
+ return true;
1137
+ }
1138
+ }
1139
+ void maybeRunEntryMenu().then((handled) => {
1140
+ if (!handled)
1141
+ program.parse();
1142
+ });
1143
+ //# sourceMappingURL=cli.js.map