@phuetz/code-buddy 2.0.0 → 2.2.0

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 (421) hide show
  1. package/README.fr.md +56 -0
  2. package/README.md +74 -16
  3. package/codebuddy-runtime.json +5 -5
  4. package/dist/agent/codebuddy-agent.d.ts +18 -0
  5. package/dist/agent/codebuddy-agent.js +45 -8
  6. package/dist/agent/execution/agent-executor.d.ts +0 -9
  7. package/dist/agent/execution/agent-executor.js +176 -12
  8. package/dist/agent/execution/context-pipeline.d.ts +9 -0
  9. package/dist/agent/execution/context-pipeline.js +64 -2
  10. package/dist/agent/execution/tool-loop-guard.d.ts +76 -0
  11. package/dist/agent/execution/tool-loop-guard.js +165 -0
  12. package/dist/agent/execution/tool-selection-strategy.d.ts +7 -0
  13. package/dist/agent/execution/tool-selection-strategy.js +32 -2
  14. package/dist/agent/facades/session-facade.d.ts +0 -6
  15. package/dist/agent/facades/session-facade.js +0 -45
  16. package/dist/agent/hermes-claw-migrate.d.ts +0 -21
  17. package/dist/agent/hermes-claw-migrate.js +9 -4
  18. package/dist/agent/hermes-protocol-gateways.js +8 -1
  19. package/dist/agent/repo-profiler.js +17 -4
  20. package/dist/agent/self-improvement/authored-tool-runtime.js +10 -2
  21. package/dist/agent/self-improvement/evolution/ast-novelty.d.ts +5 -0
  22. package/dist/agent/self-improvement/evolution/ast-novelty.js +58 -23
  23. package/dist/agent/self-improvement/evolution/code-variant-store.d.ts +3 -0
  24. package/dist/agent/self-improvement/evolution/evolution-engine.js +2 -0
  25. package/dist/agent/self-improvement/evolution/protected-paths.js +6 -0
  26. package/dist/agent/self-improvement/evolution/variant-fitness.d.ts +13 -2
  27. package/dist/agent/self-improvement/evolution/variant-fitness.js +184 -58
  28. package/dist/agent/self-improvement/evolution/worktree-scorer.d.ts +2 -0
  29. package/dist/agent/self-improvement/evolution/worktree-scorer.js +15 -0
  30. package/dist/agent/self-improvement/evolutionary-archive.d.ts +2 -1
  31. package/dist/agent/self-improvement/evolutionary-archive.js +34 -5
  32. package/dist/agent/self-improvement/proposal-store.d.ts +1 -0
  33. package/dist/agent/self-improvement/proposal-store.js +79 -17
  34. package/dist/agent/self-improvement/skill-apply-journal.d.ts +44 -0
  35. package/dist/agent/self-improvement/skill-apply-journal.js +178 -0
  36. package/dist/agent/self-improvement/skill-behavior-benchmark.d.ts +3 -0
  37. package/dist/agent/self-improvement/skill-behavior-benchmark.js +19 -0
  38. package/dist/agent/self-improvement/skill-behavior-gate.d.ts +34 -0
  39. package/dist/agent/self-improvement/skill-behavior-gate.js +96 -0
  40. package/dist/agent/self-improvement/skill-engine.d.ts +22 -0
  41. package/dist/agent/self-improvement/skill-engine.js +385 -13
  42. package/dist/agent/self-improvement/skill-gate.d.ts +2 -0
  43. package/dist/agent/self-improvement/skill-gate.js +17 -2
  44. package/dist/agent/self-improvement/skill-mutator.d.ts +6 -1
  45. package/dist/agent/self-improvement/skill-mutator.js +51 -15
  46. package/dist/agent/self-improvement/skill-types.d.ts +2 -1
  47. package/dist/agent/self-improvement/types.d.ts +11 -0
  48. package/dist/agent/tool-handler.d.ts +1 -1
  49. package/dist/agent/tool-handler.js +28 -9
  50. package/dist/auth/profile-manager.js +1 -1
  51. package/dist/channels/companion-channel-turn.d.ts +53 -4
  52. package/dist/channels/companion-channel-turn.js +218 -16
  53. package/dist/channels/slash-parity.d.ts +4 -6
  54. package/dist/channels/slash-parity.js +9 -44
  55. package/dist/cli/session-commands.d.ts +9 -0
  56. package/dist/cli/session-commands.js +45 -5
  57. package/dist/cli/session-picker.d.ts +43 -0
  58. package/dist/cli/session-picker.js +128 -0
  59. package/dist/codebuddy/a2a-call-tool-defs.d.ts +26 -0
  60. package/dist/codebuddy/a2a-call-tool-defs.js +12 -0
  61. package/dist/codebuddy/fleet-tool-defs.d.ts +2 -0
  62. package/dist/codebuddy/fleet-tool-defs.js +55 -0
  63. package/dist/codebuddy/providers/chatgpt-headers.d.ts +18 -0
  64. package/dist/codebuddy/providers/chatgpt-headers.js +37 -0
  65. package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +2 -1
  66. package/dist/codebuddy/providers/provider-chatgpt-responses.js +10 -23
  67. package/dist/codebuddy/providers/provider-openai-compat.d.ts +7 -0
  68. package/dist/codebuddy/providers/provider-openai-compat.js +29 -1
  69. package/dist/codebuddy/ragchat-tool-defs.d.ts +31 -0
  70. package/dist/codebuddy/ragchat-tool-defs.js +19 -0
  71. package/dist/codebuddy/resource-catalog-tool-defs.d.ts +28 -0
  72. package/dist/codebuddy/resource-catalog-tool-defs.js +18 -0
  73. package/dist/codebuddy/tool-definitions/agent-tools.js +2 -2
  74. package/dist/codebuddy/tool-definitions/code-exec-tools.js +5 -1
  75. package/dist/codebuddy/tool-definitions/cron-tools.js +6 -0
  76. package/dist/codebuddy/tool-definitions/self-describe-tools.js +6 -1
  77. package/dist/codebuddy/tools.d.ts +0 -6
  78. package/dist/codebuddy/tools.js +25 -3
  79. package/dist/commands/cli/daemon-commands.js +3 -1
  80. package/dist/commands/cli/evolve-command.js +21 -5
  81. package/dist/commands/cli/fleet-collaboration-commands.d.ts +2 -0
  82. package/dist/commands/cli/fleet-collaboration-commands.js +57 -0
  83. package/dist/commands/cli/fleet-commands.js +29 -0
  84. package/dist/commands/cli/fleet-mission-commands.d.ts +3 -0
  85. package/dist/commands/cli/fleet-mission-commands.js +72 -0
  86. package/dist/commands/cli/fleet-rooms-commands.d.ts +10 -0
  87. package/dist/commands/cli/fleet-rooms-commands.js +289 -0
  88. package/dist/commands/cli/tools-commands.js +2 -1
  89. package/dist/commands/cli/triage-command.d.ts +8 -0
  90. package/dist/commands/cli/triage-command.js +72 -0
  91. package/dist/commands/cli/utility-commands.js +25 -1
  92. package/dist/commands/client-dispatcher.d.ts +7 -0
  93. package/dist/commands/client-dispatcher.js +73 -21
  94. package/dist/commands/cron-cli/index.d.ts +3 -1
  95. package/dist/commands/cron-cli/index.js +25 -0
  96. package/dist/commands/device-auth.d.ts +10 -0
  97. package/dist/commands/device-auth.js +91 -0
  98. package/dist/commands/enhanced-command-handler.d.ts +9 -0
  99. package/dist/commands/enhanced-command-handler.js +28 -6
  100. package/dist/commands/handlers/agent-handlers.d.ts +1 -1
  101. package/dist/commands/handlers/agent-handlers.js +4 -4
  102. package/dist/commands/handlers/channel-handlers.js +53 -2
  103. package/dist/commands/handlers/export-handlers.d.ts +3 -2
  104. package/dist/commands/handlers/export-handlers.js +55 -28
  105. package/dist/commands/handlers/extra-handlers.js +5 -1
  106. package/dist/commands/handlers/grill-me-handler.js +26 -18
  107. package/dist/commands/handlers/memory-handlers.d.ts +6 -2
  108. package/dist/commands/handlers/memory-handlers.js +35 -15
  109. package/dist/commands/handlers/missing-handlers.d.ts +2 -2
  110. package/dist/commands/handlers/missing-handlers.js +12 -4
  111. package/dist/commands/handlers/resources-handler.d.ts +12 -0
  112. package/dist/commands/handlers/resources-handler.js +52 -0
  113. package/dist/commands/handlers/starter-handlers.d.ts +1 -1
  114. package/dist/commands/handlers/starter-handlers.js +32 -2
  115. package/dist/commands/handlers/switch-handler.d.ts +1 -1
  116. package/dist/commands/handlers/switch-handler.js +15 -0
  117. package/dist/commands/handlers/test-handlers.js +4 -86
  118. package/dist/commands/handlers/ui-handlers.js +11 -5
  119. package/dist/commands/login-chatgpt.d.ts +8 -0
  120. package/dist/commands/login-chatgpt.js +17 -0
  121. package/dist/commands/mcp-import.d.ts +11 -0
  122. package/dist/commands/mcp-import.js +67 -0
  123. package/dist/commands/mcp-invoke.d.ts +2 -0
  124. package/dist/commands/mcp-invoke.js +68 -0
  125. package/dist/commands/mcp.d.ts +13 -0
  126. package/dist/commands/mcp.js +66 -21
  127. package/dist/commands/pipeline.d.ts +7 -1
  128. package/dist/commands/pipeline.js +104 -7
  129. package/dist/commands/resources.d.ts +2 -0
  130. package/dist/commands/resources.js +64 -0
  131. package/dist/commands/skills-cli/index.js +35 -12
  132. package/dist/commands/slash/builtin-commands.js +16 -3
  133. package/dist/commands/slash/index.d.ts +3 -1
  134. package/dist/commands/slash/index.js +2 -0
  135. package/dist/commands/slash/surfaces.d.ts +53 -0
  136. package/dist/commands/slash/surfaces.js +177 -0
  137. package/dist/commands/slash/types.d.ts +17 -0
  138. package/dist/commands/try.d.ts +1 -1
  139. package/dist/commands/try.js +1 -1
  140. package/dist/companion/companion-identity.d.ts +43 -0
  141. package/dist/companion/companion-identity.js +136 -0
  142. package/dist/companion/companion-toolset.d.ts +66 -0
  143. package/dist/companion/companion-toolset.js +368 -0
  144. package/dist/companion/companion-turn.d.ts +10 -2
  145. package/dist/companion/companion-turn.js +38 -1
  146. package/dist/config/code-exec-policy.d.ts +25 -0
  147. package/dist/config/code-exec-policy.js +39 -0
  148. package/dist/config/model-tools.d.ts +9 -0
  149. package/dist/config/model-tools.js +103 -0
  150. package/dist/context/context-manager-v2.js +2 -2
  151. package/dist/context/lm-resizer-compressor.js +3 -2
  152. package/dist/context/lm-resizer-diagnostics.d.ts +9 -0
  153. package/dist/context/lm-resizer-diagnostics.js +30 -0
  154. package/dist/context/tool-output-masking.js +3 -4
  155. package/dist/curator/curator.d.ts +6 -0
  156. package/dist/curator/curator.js +53 -3
  157. package/dist/daemon/cron-agent-bridge.d.ts +6 -0
  158. package/dist/daemon/cron-agent-bridge.js +31 -4
  159. package/dist/doctor/index.d.ts +11 -2
  160. package/dist/doctor/index.js +84 -21
  161. package/dist/doctor/integrations.d.ts +197 -0
  162. package/dist/doctor/integrations.js +241 -0
  163. package/dist/doctor/triage.d.ts +108 -0
  164. package/dist/doctor/triage.js +304 -0
  165. package/dist/errors/crash-handler.js +2 -0
  166. package/dist/fleet/collaboration.d.ts +44 -0
  167. package/dist/fleet/collaboration.js +170 -0
  168. package/dist/fleet/fleet-listener.d.ts +1 -0
  169. package/dist/fleet/fleet-listener.js +87 -46
  170. package/dist/fleet/fleet-registry.d.ts +2 -0
  171. package/dist/fleet/resource-catalog.d.ts +245 -0
  172. package/dist/fleet/resource-catalog.js +211 -0
  173. package/dist/fleet/rooms/room-access.d.ts +138 -0
  174. package/dist/fleet/rooms/room-access.js +196 -0
  175. package/dist/fleet/rooms/room-client.d.ts +158 -0
  176. package/dist/fleet/rooms/room-client.js +685 -0
  177. package/dist/fleet/rooms/room-event.d.ts +139 -0
  178. package/dist/fleet/rooms/room-event.js +330 -0
  179. package/dist/fleet/rooms/room-filter.d.ts +48 -0
  180. package/dist/fleet/rooms/room-filter.js +152 -0
  181. package/dist/fleet/rooms/room-hub.d.ts +154 -0
  182. package/dist/fleet/rooms/room-hub.js +363 -0
  183. package/dist/fleet/rooms/room-identity.d.ts +26 -0
  184. package/dist/fleet/rooms/room-identity.js +82 -0
  185. package/dist/fleet/rooms/room-lock.d.ts +34 -0
  186. package/dist/fleet/rooms/room-lock.js +180 -0
  187. package/dist/fleet/rooms/room-observations.d.ts +7 -0
  188. package/dist/fleet/rooms/room-observations.js +61 -0
  189. package/dist/fleet/rooms/room-server.d.ts +28 -0
  190. package/dist/fleet/rooms/room-server.js +75 -0
  191. package/dist/fleet/rooms/room-store.d.ts +119 -0
  192. package/dist/fleet/rooms/room-store.js +448 -0
  193. package/dist/fleet/rooms/room-ws-bridge.d.ts +27 -0
  194. package/dist/fleet/rooms/room-ws-bridge.js +137 -0
  195. package/dist/fleet/sensory-mission-bridge.d.ts +42 -0
  196. package/dist/fleet/sensory-mission-bridge.js +136 -0
  197. package/dist/git/worktree-sessions.js +1 -0
  198. package/dist/harness/contract.d.ts +4 -4
  199. package/dist/harness/fleet-supervisor.d.ts +16 -0
  200. package/dist/harness/fleet-supervisor.js +64 -0
  201. package/dist/harness/index.d.ts +4 -0
  202. package/dist/harness/index.js +4 -0
  203. package/dist/harness/mission-runner.d.ts +3 -0
  204. package/dist/harness/mission-runner.js +53 -0
  205. package/dist/harness/mission-store.d.ts +93 -0
  206. package/dist/harness/mission-store.js +297 -0
  207. package/dist/harness/tool-harness.d.ts +56 -0
  208. package/dist/harness/tool-harness.js +153 -0
  209. package/dist/hooks/use-enhanced-input.js +48 -4
  210. package/dist/hooks/use-input-handler.d.ts +1 -0
  211. package/dist/hooks/use-input-handler.js +82 -22
  212. package/dist/hooks/use-input-history.js +1 -0
  213. package/dist/identity/lisa-introspection.js +5 -2
  214. package/dist/identity/operational-self-model.d.ts +2 -0
  215. package/dist/identity/operational-self-model.js +4 -0
  216. package/dist/index.js +52 -6
  217. package/dist/mcp/client.d.ts +11 -0
  218. package/dist/mcp/client.js +68 -3
  219. package/dist/mcp/config.d.ts +20 -0
  220. package/dist/mcp/config.js +121 -8
  221. package/dist/mcp/import-normalize.d.ts +12 -0
  222. package/dist/mcp/import-normalize.js +114 -0
  223. package/dist/mcp/mcp-oauth-constants.d.ts +8 -0
  224. package/dist/mcp/mcp-oauth-constants.js +9 -0
  225. package/dist/mcp/mcp-oauth-provider.d.ts +80 -0
  226. package/dist/mcp/mcp-oauth-provider.js +241 -0
  227. package/dist/mcp/mcp-oauth.d.ts +60 -1
  228. package/dist/mcp/mcp-oauth.js +241 -65
  229. package/dist/mcp/transports.d.ts +14 -5
  230. package/dist/mcp/transports.js +112 -101
  231. package/dist/mcp/types.d.ts +5 -0
  232. package/dist/media/comfyui-recipe-contract.d.ts +7 -7
  233. package/dist/memory/facts-memory.d.ts +11 -1
  234. package/dist/memory/facts-memory.js +47 -8
  235. package/dist/memory/memory-candidate-queue.d.ts +2 -2
  236. package/dist/memory/memory-candidate-queue.js +6 -6
  237. package/dist/memory/persistent-memory.d.ts +13 -8
  238. package/dist/memory/persistent-memory.js +77 -30
  239. package/dist/nodes/device-node.js +5 -9
  240. package/dist/observability/mobile-supervision-gateway-contract.js +20 -7
  241. package/dist/observability/run-event-writer.d.ts +21 -0
  242. package/dist/observability/run-event-writer.js +81 -0
  243. package/dist/observability/run-store.d.ts +5 -0
  244. package/dist/observability/run-store.js +18 -13
  245. package/dist/observability/run-trajectory.js +1 -1
  246. package/dist/optimization/prompt-cache.d.ts +19 -2
  247. package/dist/optimization/prompt-cache.js +41 -6
  248. package/dist/orchestration/orchestrator.d.ts +21 -0
  249. package/dist/orchestration/orchestrator.js +122 -15
  250. package/dist/persistence/session-content.d.ts +8 -0
  251. package/dist/persistence/session-content.js +76 -0
  252. package/dist/persistence/session-handoff.d.ts +64 -0
  253. package/dist/persistence/session-handoff.js +80 -0
  254. package/dist/persistence/session-history.d.ts +4 -0
  255. package/dist/persistence/session-history.js +38 -0
  256. package/dist/persistence/session-store.d.ts +9 -0
  257. package/dist/persistence/session-store.js +48 -20
  258. package/dist/personas/persona-manager.js +17 -1
  259. package/dist/plugins/code-explorer/CodeExplorerManager.d.ts +10 -4
  260. package/dist/plugins/code-explorer/CodeExplorerManager.js +69 -16
  261. package/dist/plugins/plugin-manager.js +10 -1
  262. package/dist/prompts/prompt-manager.js +11 -4
  263. package/dist/prompts/system-base.js +6 -2
  264. package/dist/protocols/a2a/codebuddy-executor.d.ts +11 -2
  265. package/dist/protocols/a2a/codebuddy-executor.js +9 -8
  266. package/dist/protocols/a2a/index.d.ts +1 -1
  267. package/dist/protocols/a2a/jsonrpc-v1.d.ts +41 -0
  268. package/dist/protocols/a2a/jsonrpc-v1.js +145 -0
  269. package/dist/protocols/a2a/peer-config.d.ts +20 -0
  270. package/dist/protocols/a2a/peer-config.js +55 -0
  271. package/dist/providers/codex-oauth.js +30 -6
  272. package/dist/providers/model-provider-compat.js +2 -1
  273. package/dist/sandbox/os-sandbox.d.ts +1 -1
  274. package/dist/scheduler/cron-scheduler.d.ts +15 -2
  275. package/dist/scheduler/cron-scheduler.js +35 -4
  276. package/dist/scheduler/job-notepad.d.ts +69 -0
  277. package/dist/scheduler/job-notepad.js +339 -0
  278. package/dist/security/audit-logger.d.ts +1 -1
  279. package/dist/security/compute-confinement.d.ts +4 -0
  280. package/dist/security/compute-confinement.js +89 -0
  281. package/dist/security/session-encryption.d.ts +4 -0
  282. package/dist/security/session-encryption.js +31 -0
  283. package/dist/sensory/voice-loop.d.ts +17 -0
  284. package/dist/sensory/voice-loop.js +72 -15
  285. package/dist/server/auth/device-session-context.d.ts +9 -0
  286. package/dist/server/auth/device-session-context.js +13 -0
  287. package/dist/server/auth/device-store.d.ts +89 -0
  288. package/dist/server/auth/device-store.js +261 -0
  289. package/dist/server/auth/device-token.d.ts +3 -0
  290. package/dist/server/auth/device-token.js +6 -0
  291. package/dist/server/auth/jwt.js +4 -0
  292. package/dist/server/index.d.ts +0 -10
  293. package/dist/server/index.js +28 -0
  294. package/dist/server/middleware/auth.js +3 -2
  295. package/dist/server/routes/a2a-jsonrpc.d.ts +9 -0
  296. package/dist/server/routes/a2a-jsonrpc.js +66 -0
  297. package/dist/server/routes/a2a-protocol.d.ts +12 -0
  298. package/dist/server/routes/a2a-protocol.js +1 -1
  299. package/dist/server/routes/device-auth.d.ts +4 -0
  300. package/dist/server/routes/device-auth.js +40 -0
  301. package/dist/server/types.d.ts +4 -0
  302. package/dist/server/websocket/handler.d.ts +13 -0
  303. package/dist/server/websocket/handler.js +102 -17
  304. package/dist/services/prompt-builder.d.ts +2 -0
  305. package/dist/services/prompt-builder.js +16 -6
  306. package/dist/services/runtime-settings-context.d.ts +81 -0
  307. package/dist/services/runtime-settings-context.js +79 -0
  308. package/dist/skills/executor.js +2 -0
  309. package/dist/skills/hub.js +16 -14
  310. package/dist/skills/local-inventory.d.ts +21 -0
  311. package/dist/skills/local-inventory.js +72 -0
  312. package/dist/skills/skill-loader.js +2 -2
  313. package/dist/skills/skill-usage-store.d.ts +49 -0
  314. package/dist/skills/skill-usage-store.js +121 -0
  315. package/dist/testing/ai-integration-tests.js +8 -5
  316. package/dist/themes/theme-manager.d.ts +12 -0
  317. package/dist/themes/theme-manager.js +29 -0
  318. package/dist/themes/theme-schema.d.ts +24 -24
  319. package/dist/tools/a2a-call-tool.d.ts +34 -0
  320. package/dist/tools/a2a-call-tool.js +75 -0
  321. package/dist/tools/bash/streaming-executor.d.ts +1 -0
  322. package/dist/tools/bash/streaming-executor.js +78 -90
  323. package/dist/tools/code-exec-preflight-runner.d.ts +41 -0
  324. package/dist/tools/code-exec-preflight-runner.js +254 -0
  325. package/dist/tools/code-exec-preflight.d.ts +44 -0
  326. package/dist/tools/code-exec-preflight.js +501 -0
  327. package/dist/tools/code-exec-tool.d.ts +48 -1
  328. package/dist/tools/code-exec-tool.js +191 -47
  329. package/dist/tools/comment-watcher.js +29 -12
  330. package/dist/tools/core-code-inspection.d.ts +56 -0
  331. package/dist/tools/core-code-inspection.js +86 -0
  332. package/dist/tools/create-skill-tool.d.ts +2 -3
  333. package/dist/tools/create-skill-tool.js +16 -75
  334. package/dist/tools/cronjob-tool.d.ts +1 -0
  335. package/dist/tools/cronjob-tool.js +1 -0
  336. package/dist/tools/env-doctor-tool.d.ts +2 -0
  337. package/dist/tools/env-doctor-tool.js +4 -3
  338. package/dist/tools/execute-code-rpc-invoker.d.ts +1 -1
  339. package/dist/tools/execute-code-rpc-invoker.js +35 -19
  340. package/dist/tools/execute-code-runner.d.ts +2 -0
  341. package/dist/tools/execute-code-runner.js +52 -17
  342. package/dist/tools/fleet-room-tool.d.ts +104 -0
  343. package/dist/tools/fleet-room-tool.js +124 -0
  344. package/dist/tools/integration-tool-hints.d.ts +4 -0
  345. package/dist/tools/integration-tool-hints.js +19 -0
  346. package/dist/tools/list-peers-tool.d.ts +3 -0
  347. package/dist/tools/list-peers-tool.js +11 -4
  348. package/dist/tools/media-generation-tool.d.ts +16 -1
  349. package/dist/tools/media-generation-tool.js +341 -9
  350. package/dist/tools/merge-conflict-tool.js +53 -25
  351. package/dist/tools/metadata.d.ts +1 -5
  352. package/dist/tools/metadata.js +30 -28
  353. package/dist/tools/peer-tool-invoke-tool.d.ts +61 -0
  354. package/dist/tools/peer-tool-invoke-tool.js +396 -0
  355. package/dist/tools/ragchat-tool.d.ts +39 -0
  356. package/dist/tools/ragchat-tool.js +105 -0
  357. package/dist/tools/registry/fleet-tools.d.ts +21 -3
  358. package/dist/tools/registry/fleet-tools.js +137 -4
  359. package/dist/tools/registry/index.d.ts +1 -1
  360. package/dist/tools/registry/index.js +2 -2
  361. package/dist/tools/registry/knowledge-tools.d.ts +2 -2
  362. package/dist/tools/registry/knowledge-tools.js +2 -2
  363. package/dist/tools/registry/lessons-tools.d.ts +7 -0
  364. package/dist/tools/registry/lessons-tools.js +43 -3
  365. package/dist/tools/registry/memory-tools.d.ts +1 -1
  366. package/dist/tools/registry/memory-tools.js +8 -8
  367. package/dist/tools/registry/research-tools.d.ts +1 -0
  368. package/dist/tools/registry/research-tools.js +4 -13
  369. package/dist/tools/registry/self-describe-tools.d.ts +1 -1
  370. package/dist/tools/registry/self-describe-tools.js +43 -30
  371. package/dist/tools/resource-catalog-tool.d.ts +38 -0
  372. package/dist/tools/resource-catalog-tool.js +46 -0
  373. package/dist/tools/skills-inspection-tool.js +30 -1
  374. package/dist/tools/stream-tool-output.d.ts +3 -0
  375. package/dist/tools/stream-tool-output.js +33 -0
  376. package/dist/tools/tool-call-scheduler.d.ts +10 -0
  377. package/dist/tools/tool-call-scheduler.js +58 -0
  378. package/dist/tools/tool-effect.d.ts +5 -0
  379. package/dist/tools/tool-effect.js +26 -0
  380. package/dist/tools/tool-search.d.ts +12 -11
  381. package/dist/tools/tool-search.js +67 -26
  382. package/dist/tools/types.d.ts +6 -0
  383. package/dist/ui/components/ChatHistory.d.ts +1 -1
  384. package/dist/ui/components/ChatHistory.js +2 -2
  385. package/dist/ui/components/ChatInput.d.ts +2 -1
  386. package/dist/ui/components/ChatInput.js +42 -88
  387. package/dist/ui/components/ChatInterface.d.ts +4 -2
  388. package/dist/ui/components/ChatInterface.js +27 -63
  389. package/dist/ui/components/CommandSuggestions.d.ts +0 -1
  390. package/dist/ui/components/CommandSuggestions.js +4 -6
  391. package/dist/ui/components/FileAutocomplete.js +1 -1
  392. package/dist/ui/components/KeyboardHelp.js +8 -6
  393. package/dist/ui/components/ModelSelection.js +6 -5
  394. package/dist/ui/components/StatusBar.d.ts +2 -1
  395. package/dist/ui/components/StatusBar.js +8 -7
  396. package/dist/ui/context/theme-context.js +3 -2
  397. package/dist/ui/index.d.ts +1 -1
  398. package/dist/ui/index.js +1 -1
  399. package/dist/ui/utils/markdown-renderer.js +4 -13
  400. package/dist/utils/atomic-write.d.ts +22 -0
  401. package/dist/utils/atomic-write.js +29 -0
  402. package/dist/utils/bounded-output.d.ts +16 -0
  403. package/dist/utils/bounded-output.js +63 -0
  404. package/dist/utils/config-validation/schema.d.ts +2 -2
  405. package/dist/utils/device-store-file.d.ts +4 -0
  406. package/dist/utils/device-store-file.js +40 -0
  407. package/dist/utils/export-manager.d.ts +9 -0
  408. package/dist/utils/export-manager.js +19 -0
  409. package/dist/utils/first-use-hints.d.ts +25 -0
  410. package/dist/utils/first-use-hints.js +58 -0
  411. package/dist/utils/graceful-shutdown.d.ts +8 -0
  412. package/dist/utils/graceful-shutdown.js +23 -7
  413. package/dist/utils/history-manager.js +1 -1
  414. package/dist/utils/update-notifier.js +16 -18
  415. package/dist/wizard/environment-detection.d.ts +5 -1
  416. package/dist/wizard/environment-detection.js +49 -7
  417. package/dist/wizard/onboarding.d.ts +5 -1
  418. package/dist/wizard/onboarding.js +90 -30
  419. package/dist/wizard/provider-onboarding.d.ts +2 -0
  420. package/dist/wizard/provider-onboarding.js +55 -17
  421. package/package.json +7 -3
@@ -94,6 +94,15 @@ export declare class ExportManager extends EventEmitter {
94
94
  filePath?: string;
95
95
  error?: string;
96
96
  }>;
97
+ /**
98
+ * Export an in-memory conversation (e.g. the live CLI transcript) to a file,
99
+ * with the same redaction and formatting as session exports.
100
+ */
101
+ exportConversationData(data: ConversationExport, format: ExportFormat, options?: Partial<ExportOptions>, prefix?: string): Promise<{
102
+ success: boolean;
103
+ filePath?: string;
104
+ error?: string;
105
+ }>;
97
106
  /**
98
107
  * Export session to string
99
108
  */
@@ -511,6 +511,25 @@ export class ExportManager extends EventEmitter {
511
511
  return { success: false, error: errorMsg };
512
512
  }
513
513
  }
514
+ /**
515
+ * Export an in-memory conversation (e.g. the live CLI transcript) to a file,
516
+ * with the same redaction and formatting as session exports.
517
+ */
518
+ async exportConversationData(data, format, options = {}, prefix = 'conversation') {
519
+ try {
520
+ const content = this.formatExport(data, format, options);
521
+ const filePath = path.join(this.outputDir, this.generateFilename(prefix, format));
522
+ await this.ensureOutputDir();
523
+ await fs.writeFile(filePath, content, 'utf-8');
524
+ this.emit('conversation:exported', { filePath, format });
525
+ return { success: true, filePath };
526
+ }
527
+ catch (error) {
528
+ const errorMsg = error instanceof Error ? error.message : 'Unknown error';
529
+ this.emit('error', error);
530
+ return { success: false, error: errorMsg };
531
+ }
532
+ }
514
533
  /**
515
534
  * Export session to string
516
535
  */
@@ -0,0 +1,25 @@
1
+ /**
2
+ * First-use hints (comparatif plan P4, optional part).
3
+ *
4
+ * A short contextual tip shown ONCE per profile, the first time the user meets
5
+ * a surprising behaviour: a command unavailable on this surface, a message
6
+ * queued behind a running turn, a tool output shortened for the model (exact
7
+ * output recoverable through restore_context).
8
+ *
9
+ * Persistence: one marker file per hint under ~/.codebuddy/hints/ created with
10
+ * O_EXCL, so concurrent processes (CLI + Cowork) show a hint at most once.
11
+ * `CODEBUDDY_HINTS=off` disables every hint. Never throws: an unwritable
12
+ * profile means no hint rather than a repeated one.
13
+ */
14
+ export type FirstUseHintId = 'surface_unavailable' | 'message_queued' | 'restore_context';
15
+ export declare const FIRST_USE_HINTS: Readonly<Record<FirstUseHintId, {
16
+ en: string;
17
+ fr: string;
18
+ }>>;
19
+ export declare function firstUseHintsDir(): string;
20
+ /**
21
+ * Return the hint text the first time `id` is requested in this profile, then
22
+ * null forever. The marker is created before the text is returned.
23
+ */
24
+ export declare function takeFirstUseHint(id: FirstUseHintId, lang?: 'en' | 'fr', dir?: string): string | null;
25
+ export declare function resetFirstUseHints(dir?: string): void;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * First-use hints (comparatif plan P4, optional part).
3
+ *
4
+ * A short contextual tip shown ONCE per profile, the first time the user meets
5
+ * a surprising behaviour: a command unavailable on this surface, a message
6
+ * queued behind a running turn, a tool output shortened for the model (exact
7
+ * output recoverable through restore_context).
8
+ *
9
+ * Persistence: one marker file per hint under ~/.codebuddy/hints/ created with
10
+ * O_EXCL, so concurrent processes (CLI + Cowork) show a hint at most once.
11
+ * `CODEBUDDY_HINTS=off` disables every hint. Never throws: an unwritable
12
+ * profile means no hint rather than a repeated one.
13
+ */
14
+ import fs from 'node:fs';
15
+ import os from 'node:os';
16
+ import path from 'node:path';
17
+ export const FIRST_USE_HINTS = Object.freeze({
18
+ surface_unavailable: {
19
+ en: 'Tip: greyed-out commands are not available on this surface yet; run them in the terminal with `buddy`.',
20
+ fr: 'Astuce : les commandes grisées ne sont pas encore pilotables ici ; lance-les dans un terminal avec `buddy`.',
21
+ },
22
+ message_queued: {
23
+ en: 'Tip: this message was queued while Buddy was busy and runs now; messages sent during a turn always run in order.',
24
+ fr: 'Astuce : un message envoyé pendant un tour est mis en file et partira à la fin du tour en cours.',
25
+ },
26
+ restore_context: {
27
+ en: 'Tip: long tool outputs are shortened for the model; the exact output stays recoverable with restore_context.',
28
+ fr: 'Astuce : les longues sorties d’outils sont raccourcies pour le modèle ; la sortie exacte reste récupérable avec restore_context.',
29
+ },
30
+ });
31
+ export function firstUseHintsDir() {
32
+ return process.env.CODEBUDDY_HINTS_DIR || path.join(os.homedir(), '.codebuddy', 'hints');
33
+ }
34
+ /**
35
+ * Return the hint text the first time `id` is requested in this profile, then
36
+ * null forever. The marker is created before the text is returned.
37
+ */
38
+ export function takeFirstUseHint(id, lang = 'en', dir = firstUseHintsDir()) {
39
+ if ((process.env.CODEBUDDY_HINTS ?? '').toLowerCase() === 'off')
40
+ return null;
41
+ const hint = FIRST_USE_HINTS[id];
42
+ if (!hint)
43
+ return null;
44
+ try {
45
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
46
+ fs.writeFileSync(path.join(dir, id), `${new Date().toISOString()}\n`, { flag: 'wx', mode: 0o600 });
47
+ return hint[lang];
48
+ }
49
+ catch {
50
+ return null;
51
+ }
52
+ }
53
+ export function resetFirstUseHints(dir = firstUseHintsDir()) {
54
+ for (const id of Object.keys(FIRST_USE_HINTS)) {
55
+ fs.rmSync(path.join(dir, id), { force: true });
56
+ }
57
+ }
58
+ //# sourceMappingURL=first-use-hints.js.map
@@ -11,6 +11,14 @@
11
11
  * Intercepts signals: SIGINT (Ctrl+C), SIGTERM (kill), SIGHUP (terminal closed)
12
12
  */
13
13
  import { type Disposable } from './disposable.js';
14
+ /**
15
+ * Exit code after a shutdown signal. Only an explicit headless run (`-p`, loop,
16
+ * try: CODEBUDDY_HEADLESS=true) reports SIGINT as 130 (USER_CANCELLED), so a
17
+ * script can tell an interruption from success. Servers and daemons whose stdout
18
+ * is merely not a TTY (systemd) keep exit 0: a normal SIGTERM stop must not be
19
+ * reported as a failure and trigger Restart=on-failure.
20
+ */
21
+ export declare function signalExitCode(signal: string, env?: NodeJS.ProcessEnv): number;
14
22
  export interface ShutdownOptions {
15
23
  /** Maximum time to wait for shutdown in milliseconds (default: 30000) */
16
24
  timeoutMs: number;
@@ -12,6 +12,22 @@
12
12
  */
13
13
  import { logger, getLogger } from './logger.js';
14
14
  import { DisposableManager, registerDisposable } from './disposable.js';
15
+ import { EXIT_CODES } from './exit-codes.js';
16
+ /**
17
+ * Exit code after a shutdown signal. Only an explicit headless run (`-p`, loop,
18
+ * try: CODEBUDDY_HEADLESS=true) reports SIGINT as 130 (USER_CANCELLED), so a
19
+ * script can tell an interruption from success. Servers and daemons whose stdout
20
+ * is merely not a TTY (systemd) keep exit 0: a normal SIGTERM stop must not be
21
+ * reported as a failure and trigger Restart=on-failure.
22
+ */
23
+ export function signalExitCode(signal, env = process.env) {
24
+ if (env.CODEBUDDY_HEADLESS !== 'true')
25
+ return 0;
26
+ return signal === 'SIGINT' ? EXIT_CODES.USER_CANCELLED : 0;
27
+ }
28
+ function shutdownProgressStream() {
29
+ return process.stdout.isTTY ? process.stdout : process.stderr;
30
+ }
15
31
  const DEFAULT_OPTIONS = {
16
32
  timeoutMs: 30000, // 30 seconds max before force exit
17
33
  forceExitOnTimeout: true,
@@ -69,7 +85,7 @@ export class GracefulShutdownManager {
69
85
  // Show user-friendly message
70
86
  logger.info(`Received ${signal}. Shutting down gracefully...`);
71
87
  try {
72
- await this.shutdown({ exitCode: 0 });
88
+ await this.shutdown({ exitCode: signalExitCode(signal) });
73
89
  }
74
90
  catch (error) {
75
91
  logger.error('Error during shutdown', error instanceof Error ? error : new Error(String(error)));
@@ -232,7 +248,7 @@ export class GracefulShutdownManager {
232
248
  for (const handler of this.handlers) {
233
249
  try {
234
250
  if (showProgress) {
235
- process.stdout.write(` [shutdown] ${handler.name}...`);
251
+ shutdownProgressStream().write(` [shutdown] ${handler.name}...`);
236
252
  }
237
253
  logger.debug(`Running shutdown handler: ${handler.name}`);
238
254
  const result = handler.handler();
@@ -241,14 +257,14 @@ export class GracefulShutdownManager {
241
257
  }
242
258
  this.completedHandlers.add(handler.name);
243
259
  if (showProgress) {
244
- process.stdout.write(' done\n');
260
+ shutdownProgressStream().write(' done\n');
245
261
  }
246
262
  logger.debug(`Completed shutdown handler: ${handler.name}`);
247
263
  }
248
264
  catch (error) {
249
265
  this.completedHandlers.add(handler.name); // Mark as completed even on error
250
266
  if (showProgress) {
251
- process.stdout.write(' failed\n');
267
+ shutdownProgressStream().write(' failed\n');
252
268
  }
253
269
  logger.error(`Error in shutdown handler ${handler.name}:`, error instanceof Error ? error : new Error(String(error)));
254
270
  // Continue with other handlers even if one fails
@@ -257,18 +273,18 @@ export class GracefulShutdownManager {
257
273
  // Then, dispose all registered disposables
258
274
  try {
259
275
  if (showProgress) {
260
- process.stdout.write(' [shutdown] disposing resources...');
276
+ shutdownProgressStream().write(' [shutdown] disposing resources...');
261
277
  }
262
278
  logger.debug('Disposing all registered resources...');
263
279
  await DisposableManager.getInstance().disposeAll();
264
280
  if (showProgress) {
265
- process.stdout.write(' done\n');
281
+ shutdownProgressStream().write(' done\n');
266
282
  }
267
283
  logger.debug('All resources disposed');
268
284
  }
269
285
  catch (error) {
270
286
  if (showProgress) {
271
- process.stdout.write(' failed\n');
287
+ shutdownProgressStream().write(' failed\n');
272
288
  }
273
289
  logger.error('Error disposing resources:', error instanceof Error ? error : new Error(String(error)));
274
290
  }
@@ -29,7 +29,7 @@ export class HistoryManager {
29
29
  };
30
30
  static DEFAULT_CONFIG = {
31
31
  maxEntries: 1000, // Increased default limit
32
- historyFile: path.join(os.homedir(), '.codebuddy', 'history.json'),
32
+ get historyFile() { return path.join(os.homedir(), '.codebuddy', 'history.json'); },
33
33
  excludePrefixes: [], // Include all commands by default
34
34
  includeSlashCommands: true, // Include slash commands
35
35
  };
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Checks for new versions and notifies users (mistral-vibe style).
5
5
  */
6
- import { existsSync, readFileSync } from 'fs';
6
+ import { readFileSync } from 'fs';
7
7
  import { join } from 'path';
8
8
  import { homedir } from 'os';
9
9
  import https from 'https';
@@ -24,27 +24,17 @@ const DEFAULT_CONFIG = {
24
24
  */
25
25
  function getCurrentVersion() {
26
26
  try {
27
- // Try to find package.json from various locations
28
- // Works in both local dev and npm global install
29
- const possiblePaths = [
30
- // Local development or npx
31
- join(process.cwd(), 'package.json'),
32
- // npm global install (node_modules/@phuetz/code-buddy/package.json)
33
- join(process.execPath, '..', '..', 'lib', 'node_modules', '@phuetz', 'code-buddy', 'package.json'),
34
- ];
35
- for (const pkgPath of possiblePaths) {
36
- if (existsSync(pkgPath)) {
37
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
38
- if (pkg.name?.includes('code-buddy')) {
39
- return pkg.version || '0.0.0';
40
- }
41
- }
27
+ // Resolve our own manifest in both src/utils and dist/utils. The caller's
28
+ // working directory and Node executable do not identify a global npm install.
29
+ const pkg = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf-8'));
30
+ if (pkg.name === '@phuetz/code-buddy' && typeof pkg.version === 'string' && pkg.version.trim()) {
31
+ return pkg.version;
42
32
  }
43
- return '0.0.0';
44
33
  }
45
34
  catch {
46
- return '0.0.0';
35
+ // An unknown installed version must never produce a fictitious upgrade.
47
36
  }
37
+ return null;
48
38
  }
49
39
  /**
50
40
  * Fetch latest version from npm registry
@@ -147,6 +137,10 @@ export class UpdateNotifier {
147
137
  return null;
148
138
  }
149
139
  const currentVersion = getCurrentVersion();
140
+ if (!currentVersion) {
141
+ this.updateInfo = null;
142
+ return null;
143
+ }
150
144
  // Check cache first
151
145
  const cache = loadCache();
152
146
  if (cache && !shouldCheckForUpdates(this.config)) {
@@ -184,6 +178,10 @@ export class UpdateNotifier {
184
178
  */
185
179
  async forceCheck() {
186
180
  const currentVersion = getCurrentVersion();
181
+ if (!currentVersion) {
182
+ this.updateInfo = null;
183
+ return null;
184
+ }
187
185
  const latestVersion = await fetchLatestVersion(this.config.packageName, this.config.registryUrl);
188
186
  if (!latestVersion) {
189
187
  return null;
@@ -54,6 +54,8 @@ export declare function detectOllama(): Promise<DetectedCapability>;
54
54
  export declare function detectLmStudio(): Promise<DetectedCapability>;
55
55
  /** Check the ChatGPT (Codex) OAuth credential file. */
56
56
  export declare function detectChatGptOAuth(): Promise<DetectedCapability>;
57
+ /** Offline variant: credential presence only, never refreshes tokens. */
58
+ export declare function detectChatGptOAuthOffline(): Promise<DetectedCapability>;
57
59
  /** Check the xAI (Grok) OAuth credential file. */
58
60
  export declare function detectXaiOAuth(): Promise<DetectedCapability>;
59
61
  /** Report API keys present in the environment (not billed to us, so free:false). */
@@ -62,6 +64,8 @@ export declare function detectApiKeys(): DetectedCapability[];
62
64
  * One call to learn everything the setup surfaces need. Free local/OAuth probes
63
65
  * run in parallel; the API-key scan is synchronous.
64
66
  */
65
- export declare function detectEnvironment(): Promise<EnvironmentSnapshot>;
67
+ export declare function detectEnvironment(options?: {
68
+ offline?: boolean;
69
+ }): Promise<EnvironmentSnapshot>;
66
70
  /** Compact human summary of what was detected — used by wizard + doctor. */
67
71
  export declare function renderDetectionSummary(snapshot: EnvironmentSnapshot): string;
@@ -121,6 +121,40 @@ export async function detectChatGptOAuth() {
121
121
  return base;
122
122
  }
123
123
  }
124
+ /** Offline variant: credential presence only, never refreshes tokens. */
125
+ export async function detectChatGptOAuthOffline() {
126
+ const base = {
127
+ id: 'chatgpt',
128
+ label: 'ChatGPT subscription (OAuth, $0)',
129
+ kind: 'oauth',
130
+ free: true,
131
+ available: false,
132
+ detail: 'not signed in',
133
+ setupCommand: 'buddy login',
134
+ };
135
+ try {
136
+ const { hasCodexCredentials } = await import('../providers/codex-oauth.js');
137
+ if (!hasCodexCredentials())
138
+ return base;
139
+ return { ...base, available: true, detail: 'credentials present (not verified offline)', setupCommand: undefined };
140
+ }
141
+ catch {
142
+ return base;
143
+ }
144
+ }
145
+ function notProbedLocalRuntime(id, label, rawHost, setupCommand) {
146
+ const host = normalizeHost(rawHost);
147
+ return Promise.resolve({
148
+ id,
149
+ label,
150
+ kind: 'local',
151
+ free: true,
152
+ available: false,
153
+ detail: 'not probed (--offline)',
154
+ baseURL: `${host}/v1`,
155
+ setupCommand,
156
+ });
157
+ }
124
158
  /** Check the xAI (Grok) OAuth credential file. */
125
159
  export async function detectXaiOAuth() {
126
160
  const base = {
@@ -167,13 +201,21 @@ export function detectApiKeys() {
167
201
  * One call to learn everything the setup surfaces need. Free local/OAuth probes
168
202
  * run in parallel; the API-key scan is synchronous.
169
203
  */
170
- export async function detectEnvironment() {
171
- const [ollama, lmstudio, chatgpt, xai] = await Promise.all([
172
- detectOllama(),
173
- detectLmStudio(),
174
- detectChatGptOAuth(),
175
- detectXaiOAuth(),
176
- ]);
204
+ export async function detectEnvironment(options = {}) {
205
+ // Offline: no HTTP probe and no OAuth refresh — only local credential files are read.
206
+ const [ollama, lmstudio, chatgpt, xai] = await Promise.all(options.offline
207
+ ? [
208
+ notProbedLocalRuntime('ollama', 'Ollama (local, $0)', process.env.OLLAMA_HOST || OLLAMA_DEFAULT_HOST, 'ollama serve'),
209
+ notProbedLocalRuntime('lmstudio', 'LM Studio (local, $0)', process.env.LMSTUDIO_HOST || LMSTUDIO_DEFAULT_HOST, 'Start the LM Studio local server'),
210
+ detectChatGptOAuthOffline(),
211
+ detectXaiOAuth(),
212
+ ]
213
+ : [
214
+ detectOllama(),
215
+ detectLmStudio(),
216
+ detectChatGptOAuth(),
217
+ detectXaiOAuth(),
218
+ ]);
177
219
  const apiKeys = detectApiKeys();
178
220
  const capabilities = [ollama, lmstudio, chatgpt, xai, ...apiKeys];
179
221
  // Free path priority: a local Ollama that actually has a model → ChatGPT
@@ -1,3 +1,4 @@
1
+ import * as readline from 'readline';
1
2
  import { type EnvironmentSnapshot } from './environment-detection.js';
2
3
  export interface OnboardingResult {
3
4
  provider: string;
@@ -47,7 +48,7 @@ export declare const ONBOARDING_PHASES: OnboardingPhase[];
47
48
  * - an entered API key → encrypted credential store (never plaintext in settings),
48
49
  * plus `process.env[envVar]` so the current process resolves immediately.
49
50
  * - ollama → default `OLLAMA_HOST` so auto-detection picks it up.
50
- * All writes are best-effort; the wizard summary still prints on failure.
51
+ * Persistence errors propagate so the wizard cannot report a false success.
51
52
  */
52
53
  export declare function persistProviderSelection(guide: OnboardingProviderGuide, model: string, apiKey: string): Promise<void>;
53
54
  export declare const PROJECT_FOLDER_QUESTION = "Which project folder should Code Buddy use?";
@@ -67,4 +68,7 @@ export declare function renderCapabilitiesFooter(): string;
67
68
  * recommended free path at index 0. Availability annotations are appended to
68
69
  * the label so the newcomer sees "(detected)" without extra prose. */
69
70
  export declare function orderGuidesByDetection(snapshot: EnvironmentSnapshot): OnboardingProviderGuide[];
71
+ /** Offer to run the isolated 60-second demo right after setup — ending the
72
+ * wizard on proof (a green test), not prose. */
73
+ export declare function offerOnboardingDemo(rl: readline.Interface, result: OnboardingResult): Promise<void>;
70
74
  export declare function runOnboarding(): Promise<OnboardingResult | null>;
@@ -1,6 +1,7 @@
1
1
  import * as readline from 'readline';
2
2
  import { spawn } from 'child_process';
3
- import { mkdirSync, writeFileSync } from 'fs';
3
+ import { existsSync, readFileSync, mkdirSync } from 'fs';
4
+ import { writeJsonAtomicSync } from '../utils/atomic-write.js';
4
5
  import { isAbsolute, join, resolve } from 'path';
5
6
  import { getValidationConfigForGuide, validateProviderKey, } from './provider-onboarding.js';
6
7
  import { detectEnvironment, renderDetectionSummary, } from './environment-detection.js';
@@ -165,31 +166,39 @@ export const ONBOARDING_PHASES = [
165
166
  successCheck: 'the selected layer has a doctor/status command or a focused smoke prompt.',
166
167
  },
167
168
  ];
168
- const TTS_PROVIDERS = ['pocket', 'elevenlabs', 'piper'];
169
+ const TTS_PROVIDERS = ['piper', 'espeak', 'say', 'audioreader'];
170
+ class OnboardingCancelled extends Error {
171
+ }
169
172
  function ask(rl, question, defaultValue) {
170
- return new Promise((resolve) => {
173
+ return new Promise((resolve, reject) => {
174
+ const onClose = () => reject(new OnboardingCancelled('Setup cancelled.'));
175
+ rl.once('close', onClose);
171
176
  const suffix = defaultValue ? ` [${defaultValue}]` : '';
172
177
  rl.question(` ${question}${suffix}: `, (answer) => {
178
+ rl.removeListener('close', onClose);
173
179
  resolve(answer.trim() || defaultValue || '');
174
180
  });
175
181
  });
176
182
  }
177
- function askChoice(rl, question, choices, defaultIdx) {
178
- return new Promise((resolve) => {
179
- console.log(`\n ${question}`);
180
- choices.forEach((c, i) => console.log(` ${i + 1}. ${c}${i === defaultIdx ? ' (default)' : ''}`));
181
- rl.question(` Choice [${defaultIdx + 1}]: `, (answer) => {
182
- const idx = parseInt(answer) - 1;
183
- const selectedIdx = idx >= 0 && idx < choices.length ? idx : defaultIdx;
184
- resolve(choices[selectedIdx] ?? choices[0] ?? '');
185
- });
186
- });
183
+ async function askChoice(rl, question, choices, defaultIdx) {
184
+ console.log(`\n ${question}`);
185
+ choices.forEach((c, i) => console.log(` ${i + 1}. ${c}${i === defaultIdx ? ' (default)' : ''}`));
186
+ while (true) {
187
+ const answer = await ask(rl, 'Choice', String(defaultIdx + 1));
188
+ const idx = Number(answer) - 1;
189
+ if (/^\d+$/.test(answer) && Number.isInteger(idx) && idx >= 0 && idx < choices.length) {
190
+ return choices[idx];
191
+ }
192
+ console.log(` Please enter a number from 1 to ${choices.length}.`);
193
+ }
187
194
  }
188
195
  /** Read a secret without echoing it (masks keystrokes with '*'). The captured
189
196
  * value is always correct regardless of echo; masking is cosmetic, and falls
190
197
  * back to a visible prompt if the terminal doesn't support muting. */
191
198
  function askSecret(rl, question) {
192
- return new Promise((resolve) => {
199
+ return new Promise((resolve, reject) => {
200
+ const onClose = () => reject(new OnboardingCancelled('Setup cancelled.'));
201
+ rl.once('close', onClose);
193
202
  const rlAny = rl;
194
203
  const original = rlAny._writeToOutput?.bind(rl);
195
204
  let muted = false;
@@ -202,6 +211,7 @@ function askSecret(rl, question) {
202
211
  };
203
212
  }
204
213
  rl.question(` ${question}: `, (answer) => {
214
+ rl.removeListener('close', onClose);
205
215
  if (typeof original === 'function')
206
216
  rlAny._writeToOutput = original;
207
217
  resolve(answer.trim());
@@ -216,10 +226,10 @@ function askSecret(rl, question) {
216
226
  * - an entered API key → encrypted credential store (never plaintext in settings),
217
227
  * plus `process.env[envVar]` so the current process resolves immediately.
218
228
  * - ollama → default `OLLAMA_HOST` so auto-detection picks it up.
219
- * All writes are best-effort; the wizard summary still prints on failure.
229
+ * Persistence errors propagate so the wizard cannot report a false success.
220
230
  */
221
231
  export async function persistProviderSelection(guide, model, apiKey) {
222
- try {
232
+ {
223
233
  const { getSettingsManager } = await import('../utils/settings-manager.js');
224
234
  getSettingsManager().saveUserSettings({
225
235
  provider: guide.id,
@@ -229,13 +239,11 @@ export async function persistProviderSelection(guide, model, apiKey) {
229
239
  ...(guide.baseURL ? { baseURL: guide.baseURL } : {}),
230
240
  });
231
241
  }
232
- catch { /* non-fatal */ }
233
242
  if (guide.authMode === 'api-key' && apiKey) {
234
- try {
243
+ {
235
244
  const { getCredentialManager } = await import('../security/credential-manager.js');
236
245
  getCredentialManager().setApiKey(apiKey);
237
246
  }
238
- catch { /* non-fatal */ }
239
247
  if (guide.envVar)
240
248
  process.env[guide.envVar] = apiKey;
241
249
  }
@@ -255,7 +263,13 @@ export function writeConfig(configDir, result) {
255
263
  mkdirSync(configDir, { recursive: true });
256
264
  const authMode = result.authMode ?? getProviderGuide(result.provider).authMode;
257
265
  const recommendedNextCommands = result.recommendedNextCommands ?? buildRecommendedNextCommands(result);
266
+ const configPath = join(configDir, 'config.json');
267
+ const previous = existsSync(configPath) ? JSON.parse(readFileSync(configPath, 'utf8')) : {};
268
+ if (!previous || typeof previous !== 'object' || Array.isArray(previous)) {
269
+ throw new Error('Existing project config must be a JSON object; it was not overwritten.');
270
+ }
258
271
  const config = {
272
+ ...previous,
259
273
  provider: result.provider,
260
274
  model: result.model,
261
275
  authMode,
@@ -269,7 +283,9 @@ export function writeConfig(configDir, result) {
269
283
  if (result.ttsProvider) {
270
284
  config.ttsProvider = result.ttsProvider;
271
285
  }
272
- writeFileSync(join(configDir, 'config.json'), JSON.stringify(config, null, 2) + '\n');
286
+ if (!result.ttsEnabled)
287
+ delete config.ttsProvider;
288
+ writeJsonAtomicSync(configPath, config);
273
289
  }
274
290
  export function getProviderGuide(provider) {
275
291
  return (PROVIDER_GUIDES.find((guide) => guide.id === provider)
@@ -460,7 +476,7 @@ async function runQuickStart(rl, snapshot, projectDir) {
460
476
  const free = snapshot.recommendedFree;
461
477
  const ollama = snapshot.capabilities.find((c) => c.id === 'ollama');
462
478
  // Case A: a ready free path exists — offer it directly.
463
- if (free) {
479
+ if (free && PROVIDER_GUIDES.some((guide) => guide.id === free.id)) {
464
480
  const answer = (await ask(rl, `Start now on ${free.label} — free, no API key needed? (Y/n)`, 'y')).toLowerCase();
465
481
  if (answer === 'y' || answer === 'yes' || answer === '') {
466
482
  const guide = getProviderGuide(free.id);
@@ -514,17 +530,40 @@ function finishQuickStart(provider, model, baseURL, projectDir) {
514
530
  }
515
531
  /** Offer to run the isolated 60-second demo right after setup — ending the
516
532
  * wizard on proof (a green test), not prose. */
517
- async function offerTry(rl) {
533
+ export async function offerOnboardingDemo(rl, result) {
534
+ if (!['chatgpt', 'ollama', 'lmstudio'].includes(result.provider)) {
535
+ console.log(' Verify this provider with the first-chat command below.');
536
+ return;
537
+ }
518
538
  const answer = (await ask(rl, 'Run the 60-second demo now to confirm it works? (Y/n)', 'y')).toLowerCase();
519
539
  if (answer === 'y' || answer === 'yes' || answer === '') {
520
540
  try {
521
541
  const { runTryDemo } = await import('../commands/try.js');
522
542
  rl.pause();
523
- await runTryDemo();
524
- rl.resume();
543
+ const { getSettingsManager } = await import('../utils/settings-manager.js');
544
+ const code = await runTryDemo({
545
+ verbose: false,
546
+ resolveProvider: async () => ({
547
+ kind: result.provider === 'chatgpt' ? 'chatgpt' : 'ollama',
548
+ label: `${result.provider} (${result.model})`,
549
+ apiKey: result.provider === 'chatgpt' ? 'oauth-chatgpt' : 'local',
550
+ baseURL: result.provider === 'chatgpt'
551
+ ? 'https://chatgpt.com/backend-api/codex'
552
+ : getSettingsManager().getBaseURL(),
553
+ model: result.model,
554
+ }),
555
+ });
556
+ if (code !== 0) {
557
+ process.exitCode = code;
558
+ console.log(' Setup saved, but the demo failed. First chat is not verified.');
559
+ }
525
560
  }
526
561
  catch {
527
- console.log('\n Run it yourself anytime with: buddy try\n');
562
+ process.exitCode = 1;
563
+ console.log('\n The demo failed. First chat is not verified.\n');
564
+ }
565
+ finally {
566
+ rl.resume();
528
567
  }
529
568
  }
530
569
  else {
@@ -562,7 +601,7 @@ export async function runOnboarding() {
562
601
  if (quick) {
563
602
  console.log(renderCapabilitiesFooter());
564
603
  console.log('');
565
- await offerTry(rl);
604
+ await offerOnboardingDemo(rl, quick);
566
605
  return quick;
567
606
  }
568
607
  console.log(renderOnboardingRoadmap());
@@ -571,8 +610,15 @@ export async function runOnboarding() {
571
610
  // default lands on the recommended free path when there is one.
572
611
  const orderedGuides = orderGuidesByDetection(snapshot);
573
612
  const provider = await askChoice(rl, 'Which AI provider do you want to use?', orderedGuides.map((guide) => `${guide.id} — ${guide.label}`), 0).then((choice) => choice.split(/\s+—\s+/)[0] ?? choice);
574
- const guide = getProviderGuide(provider);
575
- const validationConfig = getValidationConfigForGuide(provider);
613
+ const selectedGuide = getProviderGuide(provider);
614
+ const detected = snapshot.capabilities.find((capability) => capability.id === provider);
615
+ const guide = selectedGuide.authMode === 'local' && detected?.baseURL
616
+ ? { ...selectedGuide, baseURL: detected.baseURL }
617
+ : selectedGuide;
618
+ const defaultValidation = getValidationConfigForGuide(provider);
619
+ const validationConfig = guide.authMode === 'local' && guide.baseURL && defaultValidation
620
+ ? { ...defaultValidation, baseUrl: guide.baseURL.replace(/\/v1\/?$/, '') }
621
+ : defaultValidation;
576
622
  // 2. Authentication — captured AND verified inline so you leave the wizard
577
623
  // ready to chat. A rejected key never gets persisted; a successful probe
578
624
  // also hands us the provider's real model list for step 3.
@@ -651,10 +697,18 @@ export async function runOnboarding() {
651
697
  recommendedNextCommands: buildRecommendedNextCommands({ provider, apiKey, model }),
652
698
  ...(ttsProvider ? { ttsProvider } : {}),
653
699
  };
700
+ {
701
+ const { getTTSManager } = await import('../input/text-to-speech.js');
702
+ getTTSManager().updateConfig({
703
+ enabled: ttsEnabled,
704
+ autoSpeak: ttsEnabled,
705
+ ...(ttsProvider ? { provider: ttsProvider } : {}),
706
+ });
707
+ }
654
708
  // 6. Write project config into the folder the user chose
655
709
  applyOnboardingProjectConfig(projectDir, result);
656
710
  // 7. Summary
657
- const ready = verified || (guide.authMode === 'api-key' && Boolean(apiKey));
711
+ const ready = verified;
658
712
  console.log('');
659
713
  console.log(` ${ready ? 'Setup complete — you\'re ready to go!' : 'Setup saved.'}`);
660
714
  console.log('');
@@ -688,10 +742,16 @@ export async function runOnboarding() {
688
742
  console.log('');
689
743
  // Close with a real, runnable test so the wizard ends on proof, not prose.
690
744
  if (ready) {
691
- await offerTry(rl);
745
+ await offerOnboardingDemo(rl, result);
692
746
  }
693
747
  return result;
694
748
  }
749
+ catch (error) {
750
+ if (!(error instanceof OnboardingCancelled))
751
+ throw error;
752
+ console.log('\n Setup cancelled.');
753
+ return null;
754
+ }
695
755
  finally {
696
756
  rl.close();
697
757
  }