@phuetz/code-buddy 1.1.0 → 1.3.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 (381) hide show
  1. package/README.md +211 -163
  2. package/dist/agent/autonomous/agentic-coding-contract.d.ts +12 -12
  3. package/dist/agent/base-agent.d.ts +4 -0
  4. package/dist/agent/base-agent.js +6 -0
  5. package/dist/agent/codebuddy-agent.d.ts +1 -1
  6. package/dist/agent/codebuddy-agent.js +69 -14
  7. package/dist/agent/execution/agent-executor.js +122 -12
  8. package/dist/agent/execution/tool-dependency-graph.js +7 -0
  9. package/dist/agent/execution/tool-hooks.d.ts +6 -0
  10. package/dist/agent/execution/tool-hooks.js +13 -5
  11. package/dist/agent/execution/tool-selection-strategy.d.ts +11 -2
  12. package/dist/agent/execution/tool-selection-strategy.js +67 -10
  13. package/dist/agent/extended-thinking.d.ts +8 -0
  14. package/dist/agent/extended-thinking.js +27 -0
  15. package/dist/agent/facades/infrastructure-facade.d.ts +9 -2
  16. package/dist/agent/facades/infrastructure-facade.js +15 -6
  17. package/dist/agent/hermes-browser-backends.js +151 -11
  18. package/dist/agent/hermes-claw-migrate.d.ts +59 -0
  19. package/dist/agent/hermes-claw-migrate.js +427 -26
  20. package/dist/agent/hermes-parity-manifest.js +78 -71
  21. package/dist/agent/hermes-runtime-lifecycle.d.ts +67 -0
  22. package/dist/agent/hermes-runtime-lifecycle.js +468 -0
  23. package/dist/agent/middleware/index.d.ts +1 -0
  24. package/dist/agent/middleware/index.js +1 -0
  25. package/dist/agent/middleware/visual-validation-middleware.d.ts +17 -0
  26. package/dist/agent/middleware/visual-validation-middleware.js +67 -0
  27. package/dist/agent/model-benchmark.d.ts +77 -0
  28. package/dist/agent/model-benchmark.js +309 -0
  29. package/dist/agent/model-tier.d.ts +14 -0
  30. package/dist/agent/model-tier.js +71 -0
  31. package/dist/agent/multi-agent/agents/orchestrator-agent.js +17 -6
  32. package/dist/agent/multi-agent/multi-agent-system.js +3 -1
  33. package/dist/agent/multi-agent/session-fleet-bridge.d.ts +2 -0
  34. package/dist/agent/multi-agent/session-fleet-bridge.js +9 -1
  35. package/dist/agent/multi-agent/session-tools.js +22 -5
  36. package/dist/agent/reasoning/mcts.js +20 -6
  37. package/dist/agent/self-improvement/authored-artifact-gate.d.ts +18 -0
  38. package/dist/agent/self-improvement/authored-artifact-gate.js +42 -0
  39. package/dist/agent/self-improvement/authored-tool-runtime.d.ts +27 -0
  40. package/dist/agent/self-improvement/authored-tool-runtime.js +57 -0
  41. package/dist/agent/self-improvement/authored-tool-store.d.ts +24 -0
  42. package/dist/agent/self-improvement/authored-tool-store.js +57 -0
  43. package/dist/agent/self-improvement/llm-tool-proposer.d.ts +41 -0
  44. package/dist/agent/self-improvement/llm-tool-proposer.js +136 -0
  45. package/dist/agent/self-improvement/sandbox-scorer.d.ts +17 -0
  46. package/dist/agent/self-improvement/sandbox-scorer.js +43 -0
  47. package/dist/agent/self-improvement/self-knowledge.d.ts +8 -0
  48. package/dist/agent/self-improvement/self-knowledge.js +24 -0
  49. package/dist/agent/self-improvement/skill-benchmark.d.ts +9 -0
  50. package/dist/agent/self-improvement/skill-benchmark.js +22 -0
  51. package/dist/agent/self-improvement/skill-consolidator.d.ts +71 -0
  52. package/dist/agent/self-improvement/skill-consolidator.js +137 -0
  53. package/dist/agent/self-improvement/skill-engine.d.ts +42 -0
  54. package/dist/agent/self-improvement/skill-engine.js +87 -0
  55. package/dist/agent/self-improvement/skill-gate.d.ts +19 -0
  56. package/dist/agent/self-improvement/skill-gate.js +62 -0
  57. package/dist/agent/self-improvement/skill-mutator.d.ts +74 -0
  58. package/dist/agent/self-improvement/skill-mutator.js +223 -0
  59. package/dist/agent/self-improvement/skill-proposer.d.ts +40 -0
  60. package/dist/agent/self-improvement/skill-proposer.js +82 -0
  61. package/dist/agent/self-improvement/skill-types.d.ts +41 -0
  62. package/dist/agent/self-improvement/skill-types.js +13 -0
  63. package/dist/agent/self-improvement/tool-benchmark.d.ts +10 -0
  64. package/dist/agent/self-improvement/tool-benchmark.js +37 -0
  65. package/dist/agent/self-improvement/tool-engine.d.ts +54 -0
  66. package/dist/agent/self-improvement/tool-engine.js +101 -0
  67. package/dist/agent/self-improvement/tool-gate.d.ts +20 -0
  68. package/dist/agent/self-improvement/tool-gate.js +78 -0
  69. package/dist/agent/self-improvement/tool-proposer.d.ts +31 -0
  70. package/dist/agent/self-improvement/tool-proposer.js +34 -0
  71. package/dist/agent/self-improvement/tool-skill-mutator.d.ts +40 -0
  72. package/dist/agent/self-improvement/tool-skill-mutator.js +79 -0
  73. package/dist/agent/self-improvement/tool-types.d.ts +48 -0
  74. package/dist/agent/self-improvement/tool-types.js +9 -0
  75. package/dist/agent/self-improvement/types.d.ts +3 -1
  76. package/dist/agent/session-end-flush.d.ts +6 -1
  77. package/dist/agent/session-end-flush.js +20 -4
  78. package/dist/agent/streaming/streaming-handler.d.ts +7 -0
  79. package/dist/agent/streaming/streaming-handler.js +8 -1
  80. package/dist/agent/tool-executor.js +1 -0
  81. package/dist/agent/tool-handler.d.ts +5 -0
  82. package/dist/agent/tool-handler.js +133 -4
  83. package/dist/browser-automation/browser-operator-executor.js +15 -0
  84. package/dist/browser-automation/browser-use-runner.d.ts +96 -0
  85. package/dist/browser-automation/browser-use-runner.js +492 -0
  86. package/dist/browser-automation/camofox-runner.d.ts +107 -0
  87. package/dist/browser-automation/camofox-runner.js +287 -0
  88. package/dist/channels/dingtalk/index.js +3 -0
  89. package/dist/channels/discord/client.d.ts +9 -0
  90. package/dist/channels/discord/client.js +12 -0
  91. package/dist/channels/feishu/index.d.ts +167 -1
  92. package/dist/channels/feishu/index.js +432 -7
  93. package/dist/channels/gateway-lifecycle.d.ts +132 -0
  94. package/dist/channels/gateway-lifecycle.js +219 -0
  95. package/dist/channels/google-chat/index.d.ts +5 -1
  96. package/dist/channels/google-chat/index.js +5 -1
  97. package/dist/channels/imessage/index.d.ts +14 -0
  98. package/dist/channels/imessage/index.js +64 -17
  99. package/dist/channels/index.d.ts +4 -0
  100. package/dist/channels/index.js +4 -0
  101. package/dist/channels/irc/index.d.ts +73 -3
  102. package/dist/channels/irc/index.js +446 -11
  103. package/dist/channels/line/index.js +3 -0
  104. package/dist/channels/mattermost/index.d.ts +51 -4
  105. package/dist/channels/mattermost/index.js +303 -20
  106. package/dist/channels/nextcloud-talk/index.d.ts +89 -8
  107. package/dist/channels/nextcloud-talk/index.js +367 -16
  108. package/dist/channels/nostr/index.d.ts +121 -1
  109. package/dist/channels/nostr/index.js +396 -8
  110. package/dist/channels/ntfy/index.js +3 -0
  111. package/dist/channels/qq/index.js +3 -0
  112. package/dist/channels/slash-parity.d.ts +117 -0
  113. package/dist/channels/slash-parity.js +185 -0
  114. package/dist/channels/synology-chat/index.js +3 -0
  115. package/dist/channels/teams/index.d.ts +3 -0
  116. package/dist/channels/teams/index.js +3 -0
  117. package/dist/channels/telegram/client.d.ts +9 -0
  118. package/dist/channels/telegram/client.js +12 -0
  119. package/dist/channels/twilio-voice/index.js +3 -0
  120. package/dist/channels/webchat/index.d.ts +7 -1
  121. package/dist/channels/webchat/index.js +7 -1
  122. package/dist/channels/wecom/index.js +3 -0
  123. package/dist/channels/weixin/index.js +3 -0
  124. package/dist/channels/zalo/index.js +3 -0
  125. package/dist/cli/config-loader.js +1 -1
  126. package/dist/codebuddy/client.d.ts +30 -1
  127. package/dist/codebuddy/client.js +167 -20
  128. package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +5 -0
  129. package/dist/codebuddy/providers/provider-chatgpt-responses.js +7 -1
  130. package/dist/codebuddy/providers/provider-gemini-native.js +1 -1
  131. package/dist/codebuddy/providers/provider-openai-compat.d.ts +5 -0
  132. package/dist/codebuddy/providers/provider-openai-compat.js +82 -2
  133. package/dist/codebuddy/tool-definitions/computer-control-tools.js +32 -2
  134. package/dist/codebuddy/tool-definitions/core-tools.js +3 -3
  135. package/dist/codebuddy/tool-definitions/index.d.ts +2 -1
  136. package/dist/codebuddy/tool-definitions/index.js +2 -1
  137. package/dist/codebuddy/tool-definitions/multimodal-tools.d.ts +1 -0
  138. package/dist/codebuddy/tool-definitions/multimodal-tools.js +44 -1
  139. package/dist/codebuddy/tool-definitions/windows-tools.d.ts +2 -0
  140. package/dist/codebuddy/tool-definitions/windows-tools.js +34 -0
  141. package/dist/codebuddy/tools.d.ts +8 -1
  142. package/dist/codebuddy/tools.js +67 -24
  143. package/dist/commands/cli/hermes-commands.js +108 -0
  144. package/dist/commands/cli/improve-command.js +123 -0
  145. package/dist/commands/cli/native-engine-commands.js +102 -3
  146. package/dist/commands/cli/utility-commands.js +45 -0
  147. package/dist/commands/enhanced-command-handler.js +2 -2
  148. package/dist/commands/goal-cli.d.ts +30 -0
  149. package/dist/commands/goal-cli.js +210 -27
  150. package/dist/commands/handlers/goal-handler.d.ts +9 -2
  151. package/dist/commands/handlers/goal-handler.js +36 -9
  152. package/dist/commands/handlers/infra-handlers.js +36 -0
  153. package/dist/commands/handlers/memory-handlers.js +96 -1
  154. package/dist/commands/handlers/missing-handlers.d.ts +1 -1
  155. package/dist/commands/handlers/missing-handlers.js +26 -3
  156. package/dist/commands/headless-slash.d.ts +2 -0
  157. package/dist/commands/headless-slash.js +12 -0
  158. package/dist/commands/llm-provider-resolution.d.ts +5 -3
  159. package/dist/commands/llm-provider-resolution.js +87 -33
  160. package/dist/commands/ollama.d.ts +25 -0
  161. package/dist/commands/ollama.js +100 -0
  162. package/dist/commands/provider.d.ts +5 -0
  163. package/dist/commands/provider.js +106 -55
  164. package/dist/commands/skills-cli/index.js +123 -0
  165. package/dist/commands/slash/builtin-commands.js +1 -1
  166. package/dist/commands/spec-next.js +2 -1
  167. package/dist/commands/spec-plan.js +5 -16
  168. package/dist/commands/tunnel.d.ts +2 -0
  169. package/dist/commands/tunnel.js +48 -0
  170. package/dist/companion/percepts.js +11 -1
  171. package/dist/config/config-resolver.d.ts +2 -1
  172. package/dist/config/config-resolver.js +54 -41
  173. package/dist/config/constants.d.ts +28 -0
  174. package/dist/config/constants.js +7 -0
  175. package/dist/config/env-schema.js +756 -0
  176. package/dist/config/model-tools.js +4 -4
  177. package/dist/config/toml-config.d.ts +16 -0
  178. package/dist/config/toml-config.js +3 -0
  179. package/dist/context/bootstrap-loader.js +6 -23
  180. package/dist/context/context-manager-v2.js +1 -0
  181. package/dist/context/import-directive-parser.d.ts +4 -0
  182. package/dist/context/import-directive-parser.js +51 -6
  183. package/dist/context/instruction-excludes.d.ts +30 -1
  184. package/dist/context/instruction-excludes.js +71 -1
  185. package/dist/context/jit-context.d.ts +8 -10
  186. package/dist/context/jit-context.js +28 -106
  187. package/dist/context/project-context.d.ts +90 -0
  188. package/dist/context/project-context.js +295 -0
  189. package/dist/daemon/autonomous-daemon.d.ts +1 -1
  190. package/dist/daemon/autonomous-daemon.js +3 -3
  191. package/dist/daemon/autonomous-loop.d.ts +31 -1
  192. package/dist/daemon/autonomous-loop.js +91 -4
  193. package/dist/daemon/colab-goal.js +9 -1
  194. package/dist/daemon/cron-agent-bridge.d.ts +12 -3
  195. package/dist/daemon/cron-agent-bridge.js +25 -9
  196. package/dist/desktop/codebuddy-engine-adapter.d.ts +32 -0
  197. package/dist/desktop/codebuddy-engine-adapter.js +257 -80
  198. package/dist/desktop/engine-adapter.d.ts +14 -0
  199. package/dist/desktop-automation/automation-manager.js +16 -0
  200. package/dist/desktop-automation/omniparser-runner.d.ts +63 -0
  201. package/dist/desktop-automation/omniparser-runner.js +115 -0
  202. package/dist/desktop-automation/smart-snapshot.d.ts +11 -0
  203. package/dist/desktop-automation/smart-snapshot.js +72 -5
  204. package/dist/doctor/index.js +40 -23
  205. package/dist/events/types.d.ts +11 -0
  206. package/dist/fleet/colab-store.js +14 -2
  207. package/dist/fleet/peer-session-bridge.js +50 -12
  208. package/dist/fleet/peer-tool-bridge.js +14 -0
  209. package/dist/goals/goal-decomposer.d.ts +27 -0
  210. package/dist/goals/goal-decomposer.js +293 -0
  211. package/dist/goals/goal-judge-client.d.ts +15 -0
  212. package/dist/goals/goal-judge-client.js +45 -0
  213. package/dist/goals/goal-judge.d.ts +2 -0
  214. package/dist/goals/goal-judge.js +35 -6
  215. package/dist/goals/goal-loop.d.ts +14 -0
  216. package/dist/goals/goal-loop.js +47 -2
  217. package/dist/goals/goal-manager.d.ts +6 -0
  218. package/dist/goals/goal-manager.js +67 -14
  219. package/dist/goals/goal-state.d.ts +13 -0
  220. package/dist/goals/goal-state.js +83 -13
  221. package/dist/goals/goal-store.d.ts +3 -0
  222. package/dist/goals/goal-store.js +31 -15
  223. package/dist/harness/contract.d.ts +28 -28
  224. package/dist/hooks/user-hooks.js +17 -3
  225. package/dist/identity/identity-manager.js +3 -2
  226. package/dist/index.js +481 -26
  227. package/dist/input/text-to-speech.d.ts +2 -6
  228. package/dist/input/text-to-speech.js +2 -27
  229. package/dist/integrations/screenpipe/screenpipe-client.d.ts +10 -0
  230. package/dist/integrations/screenpipe/screenpipe-client.js +12 -2
  231. package/dist/integrations/tailscale.d.ts +13 -0
  232. package/dist/integrations/tailscale.js +87 -34
  233. package/dist/knowledge/workspace-indexer.js +53 -9
  234. package/dist/mcp/client.js +1 -0
  235. package/dist/mcp/mcp-desktop-tools.d.ts +23 -0
  236. package/dist/mcp/mcp-desktop-tools.js +158 -0
  237. package/dist/mcp/mcp-resources.js +2 -3
  238. package/dist/mcp/mcp-server.js +2 -0
  239. package/dist/memory/enhanced-memory.d.ts +10 -0
  240. package/dist/memory/enhanced-memory.js +33 -2
  241. package/dist/memory/index.d.ts +3 -1
  242. package/dist/memory/index.js +4 -1
  243. package/dist/memory/memory-auto-proposer.d.ts +23 -0
  244. package/dist/memory/memory-auto-proposer.js +308 -0
  245. package/dist/memory/memory-candidate-queue.d.ts +92 -0
  246. package/dist/memory/memory-candidate-queue.js +261 -0
  247. package/dist/memory/persistent-memory.d.ts +45 -2
  248. package/dist/memory/persistent-memory.js +236 -40
  249. package/dist/ml/bayesian-qualifier.d.ts +1 -1
  250. package/dist/ml/bayesian-qualifier.js +35 -2
  251. package/dist/openclaw/gateway-bridge.js +5 -0
  252. package/dist/plugins/marketplace.d.ts +1 -0
  253. package/dist/plugins/marketplace.js +7 -0
  254. package/dist/prompts/execution-discipline.d.ts +14 -0
  255. package/dist/prompts/execution-discipline.js +29 -0
  256. package/dist/prompts/variation-injector.js +9 -2
  257. package/dist/protocols/acp/acp-agentic-runner.d.ts +7 -6
  258. package/dist/protocols/acp/acp-agentic-runner.js +115 -18
  259. package/dist/protocols/acp/acp-session-store.d.ts +23 -0
  260. package/dist/protocols/acp/acp-session-store.js +77 -0
  261. package/dist/protocols/acp/acp-stdio-server.d.ts +7 -0
  262. package/dist/protocols/acp/acp-stdio-server.js +71 -16
  263. package/dist/providers/active-llm-registry.d.ts +37 -0
  264. package/dist/providers/active-llm-registry.js +186 -0
  265. package/dist/providers/auxiliary-provider.d.ts +25 -0
  266. package/dist/providers/auxiliary-provider.js +192 -0
  267. package/dist/providers/codex-oauth.d.ts +1 -1
  268. package/dist/providers/codex-oauth.js +27 -4
  269. package/dist/providers/index.d.ts +3 -0
  270. package/dist/providers/index.js +4 -0
  271. package/dist/providers/provider-catalog.d.ts +70 -0
  272. package/dist/providers/provider-catalog.js +738 -0
  273. package/dist/providers/provider-fallback.d.ts +35 -0
  274. package/dist/providers/provider-fallback.js +171 -0
  275. package/dist/providers/xai-oauth.d.ts +128 -0
  276. package/dist/providers/xai-oauth.js +735 -0
  277. package/dist/scheduler/cron-scheduler.d.ts +15 -3
  278. package/dist/scheduler/cron-scheduler.js +53 -7
  279. package/dist/scheduler/script-runner.d.ts +2 -0
  280. package/dist/scheduler/script-runner.js +19 -4
  281. package/dist/scheduler/watchdog-handlers.js +7 -20
  282. package/dist/search/usearch-index.js +7 -2
  283. package/dist/security/tool-policy/tool-groups.js +2 -0
  284. package/dist/sensory/dreaming.d.ts +45 -0
  285. package/dist/sensory/dreaming.js +114 -0
  286. package/dist/sensory/heartbeat-scheduler.d.ts +38 -0
  287. package/dist/sensory/heartbeat-scheduler.js +72 -0
  288. package/dist/sensory/reactions.d.ts +24 -0
  289. package/dist/sensory/reactions.js +31 -0
  290. package/dist/sensory/screen-reaction.d.ts +23 -0
  291. package/dist/sensory/screen-reaction.js +59 -0
  292. package/dist/sensory/sensory-bridge.d.ts +23 -0
  293. package/dist/sensory/sensory-bridge.js +85 -0
  294. package/dist/sensory/sensory-memory.d.ts +20 -0
  295. package/dist/sensory/sensory-memory.js +39 -0
  296. package/dist/sensory/speech-reaction.d.ts +21 -0
  297. package/dist/sensory/speech-reaction.js +83 -0
  298. package/dist/sensory/vision-reaction.d.ts +31 -0
  299. package/dist/sensory/vision-reaction.js +74 -0
  300. package/dist/server/index.js +106 -2
  301. package/dist/server/routes/mobile.d.ts +12 -4
  302. package/dist/server/routes/mobile.js +116 -1
  303. package/dist/server/tls-config.d.ts +35 -0
  304. package/dist/server/tls-config.js +142 -0
  305. package/dist/server/tunnel-manager.d.ts +20 -0
  306. package/dist/server/tunnel-manager.js +58 -0
  307. package/dist/server/websocket/fleet-bridge.js +5 -0
  308. package/dist/services/prompt-builder.d.ts +11 -0
  309. package/dist/services/prompt-builder.js +124 -17
  310. package/dist/shared/engine-types.d.ts +15 -1
  311. package/dist/sidecar/sidecar-bridge.d.ts +1 -0
  312. package/dist/sidecar/sidecar-bridge.js +21 -10
  313. package/dist/skills/parser.js +3 -0
  314. package/dist/skills/skill-importer.d.ts +58 -0
  315. package/dist/skills/skill-importer.js +261 -0
  316. package/dist/skills/skill-sources.d.ts +20 -0
  317. package/dist/skills/skill-sources.js +102 -0
  318. package/dist/skills/types.d.ts +6 -0
  319. package/dist/spec/spec-store.js +8 -1
  320. package/dist/talk-mode/providers/audioreader-tts.d.ts +1 -1
  321. package/dist/talk-mode/providers/audioreader-tts.js +8 -8
  322. package/dist/templates/project-scaffolding.js +3 -2
  323. package/dist/themes/theme-schema.d.ts +10 -10
  324. package/dist/tools/application-profiles.js +38 -0
  325. package/dist/tools/bash/bash-tool.d.ts +1 -0
  326. package/dist/tools/bash/bash-tool.js +16 -2
  327. package/dist/tools/bash/command-validator.js +3 -0
  328. package/dist/tools/bash/rtk-rewrite.d.ts +16 -0
  329. package/dist/tools/bash/rtk-rewrite.js +100 -0
  330. package/dist/tools/bash/streaming-executor.js +14 -1
  331. package/dist/tools/computer-control-tool.d.ts +16 -1
  332. package/dist/tools/computer-control-tool.js +317 -5
  333. package/dist/tools/document-generator.d.ts +14 -0
  334. package/dist/tools/document-generator.js +79 -0
  335. package/dist/tools/execute-code-rpc-invoker.d.ts +26 -2
  336. package/dist/tools/execute-code-rpc-invoker.js +46 -5
  337. package/dist/tools/metadata.js +28 -0
  338. package/dist/tools/office-macro-tool.d.ts +10 -0
  339. package/dist/tools/office-macro-tool.js +93 -0
  340. package/dist/tools/register-tool-handler.d.ts +25 -0
  341. package/dist/tools/register-tool-handler.js +100 -0
  342. package/dist/tools/registry/index.d.ts +2 -1
  343. package/dist/tools/registry/index.js +2 -1
  344. package/dist/tools/registry/memory-tools.d.ts +20 -0
  345. package/dist/tools/registry/memory-tools.js +225 -3
  346. package/dist/tools/registry/multimodal-tools.js +2 -2
  347. package/dist/tools/registry/vision-tools.d.ts +33 -0
  348. package/dist/tools/registry/vision-tools.js +308 -6
  349. package/dist/tools/registry/windows-tools.d.ts +2 -0
  350. package/dist/tools/registry/windows-tools.js +47 -0
  351. package/dist/tools/registry.d.ts +6 -0
  352. package/dist/tools/registry.js +8 -0
  353. package/dist/tools/route-peer-tool.js +14 -0
  354. package/dist/tools/screenshot-tool.js +14 -2
  355. package/dist/tools/text-to-speech-tool.d.ts +1 -1
  356. package/dist/tools/text-to-speech-tool.js +2 -12
  357. package/dist/tools/tool-selector.js +3 -59
  358. package/dist/utils/config-validation/schema.d.ts +40 -7
  359. package/dist/utils/config-validation/schema.js +130 -1
  360. package/dist/utils/cost-tracker.js +21 -1
  361. package/dist/utils/disk-guard.d.ts +180 -0
  362. package/dist/utils/disk-guard.js +404 -0
  363. package/dist/utils/init-project.d.ts +7 -0
  364. package/dist/utils/init-project.js +37 -0
  365. package/dist/utils/interactive-setup.js +35 -2
  366. package/dist/utils/model-utils.js +11 -2
  367. package/dist/utils/output-sanitizer.js +11 -0
  368. package/dist/utils/provider-detector.d.ts +8 -7
  369. package/dist/utils/provider-detector.js +19 -77
  370. package/dist/utils/settings-manager.d.ts +19 -0
  371. package/dist/utils/token-display.d.ts +3 -3
  372. package/dist/utils/token-display.js +22 -4
  373. package/dist/wizard/onboarding.d.ts +39 -0
  374. package/dist/wizard/onboarding.js +433 -21
  375. package/dist/wizard/provider-onboarding.d.ts +8 -23
  376. package/dist/wizard/provider-onboarding.js +39 -158
  377. package/package.json +4 -2
  378. package/dist/plugins/provider-onboarding.d.ts +0 -23
  379. package/dist/plugins/provider-onboarding.js +0 -116
  380. package/dist/utils/kokoro-tts.d.ts +0 -8
  381. package/dist/utils/kokoro-tts.js +0 -67
@@ -0,0 +1,404 @@
1
+ /**
2
+ * disk-guard — the single place that bounds disk access.
3
+ *
4
+ * Motivation: on 2026-06-17 a tight build/Electron restart loop leaked temp
5
+ * dirs until the disk filled mid-run, corrupting command-output capture
6
+ * (ENOSPC). The fix at the time guarded ONE call site (the Cowork pilot). This
7
+ * module generalizes that lesson into reusable primitives so the failure class
8
+ * — "unbounded resource accumulation in a tight loop without monitoring or
9
+ * cleanup" — is handled in one place:
10
+ *
11
+ * - free-space preflight → {@link getFreeSpaceInfo}, {@link ensureFreeSpace}
12
+ * - managed temp lifecycle → {@link createManagedTempDir} (auto-cleaned on exit)
13
+ * - orphan sweep backstop → {@link sweepOrphans} (catches SIGKILL'd leaks)
14
+ * - byte budget → {@link DiskBudget}
15
+ * - bounded writes / logs → {@link boundedWriteFile}, {@link boundedAppendFile},
16
+ * {@link rotateIfLarge}
17
+ *
18
+ * Scope is deliberately focused. It is adopted at the highest-risk sites
19
+ * (disk-space checks, leaky /tmp dirs, unbounded append logs) rather than
20
+ * routing every fs write through it. It deliberately does NOT bound:
21
+ * - general / user-intentional workspace writes (the user asked for them),
22
+ * - third-party writers (Electron / Docker / Playwright) — covered instead by
23
+ * the startup {@link sweepOrphans} backstop,
24
+ * - low-traffic logs (add a one-line {@link rotateIfLarge} as needed).
25
+ *
26
+ * Config (read from the environment at call time, validated, with fallbacks —
27
+ * see {@link DISK_GUARD_DEFAULTS}; registered in `src/config/env-schema.ts`):
28
+ * CODEBUDDY_MIN_FREE_MB, CODEBUDDY_DISK_QUOTA_MB, CODEBUDDY_TEMP_MAX_AGE_MS,
29
+ * CODEBUDDY_LOG_MAX_MB, CODEBUDDY_LOG_MAX_FILES.
30
+ */
31
+ import { statfsSync, readdirSync, statSync, existsSync, renameSync, unlinkSync, rmSync, } from 'node:fs';
32
+ import { mkdtemp, rm, writeFile, appendFile } from 'node:fs/promises';
33
+ import { tmpdir } from 'node:os';
34
+ import { join, dirname, basename, extname } from 'node:path';
35
+ import { logger } from './logger.js';
36
+ import { CodeBuddyError } from '../errors/base-error.js';
37
+ import { registerDisposable, unregisterDisposable } from './disposable.js';
38
+ import { GracefulShutdownManager } from './graceful-shutdown.js';
39
+ const MB = 1024 * 1024;
40
+ /** Built-in fallbacks, used when the matching env var is unset/invalid. */
41
+ export const DISK_GUARD_DEFAULTS = {
42
+ /** Min free space (MB) before a guarded write/launch is refused. Matches the pilot's 500MB. */
43
+ minFreeMb: 500,
44
+ /** Per-session byte budget (MB); 0 = unlimited. */
45
+ quotaMb: 0,
46
+ /** Orphan-sweep age cutoff (ms); 0 = presence-based (remove any match). */
47
+ tempMaxAgeMs: 0,
48
+ /** Append-log rotation size (MB). Matches logger.ts. */
49
+ logMaxMb: 10,
50
+ /** Append-log retention count. Matches logger.ts. */
51
+ logMaxFiles: 5,
52
+ };
53
+ /** Parse a non-negative integer env var with range validation + fallback (mirrors AutoCompactConfig.fromEnv). */
54
+ function readIntEnv(name, fallback, min, max) {
55
+ const raw = process.env[name];
56
+ if (raw !== undefined && raw !== '') {
57
+ const parsed = parseInt(raw, 10);
58
+ if (!Number.isNaN(parsed) && parsed >= min && parsed <= max) {
59
+ return parsed;
60
+ }
61
+ }
62
+ return fallback;
63
+ }
64
+ function defaultMinFreeBytes() {
65
+ return readIntEnv('CODEBUDDY_MIN_FREE_MB', DISK_GUARD_DEFAULTS.minFreeMb, 0, 1_000_000) * MB;
66
+ }
67
+ function defaultQuotaBytes() {
68
+ return readIntEnv('CODEBUDDY_DISK_QUOTA_MB', DISK_GUARD_DEFAULTS.quotaMb, 0, 100_000_000) * MB;
69
+ }
70
+ function defaultTempMaxAgeMs() {
71
+ return readIntEnv('CODEBUDDY_TEMP_MAX_AGE_MS', DISK_GUARD_DEFAULTS.tempMaxAgeMs, 0, Number.MAX_SAFE_INTEGER);
72
+ }
73
+ function defaultLogMaxBytes() {
74
+ return readIntEnv('CODEBUDDY_LOG_MAX_MB', DISK_GUARD_DEFAULTS.logMaxMb, 1, 100_000) * MB;
75
+ }
76
+ function defaultLogMaxFiles() {
77
+ return readIntEnv('CODEBUDDY_LOG_MAX_FILES', DISK_GUARD_DEFAULTS.logMaxFiles, 1, 1000);
78
+ }
79
+ function formatMb(bytes) {
80
+ return `${Math.round(bytes / MB)}MB`;
81
+ }
82
+ function byteLength(data) {
83
+ return typeof data === 'string' ? Buffer.byteLength(data) : data.byteLength;
84
+ }
85
+ // ---------------------------------------------------------------------------
86
+ // Errors
87
+ // ---------------------------------------------------------------------------
88
+ /** Thrown by {@link ensureFreeSpace} when free disk space is below the threshold. */
89
+ export class DiskSpaceError extends CodeBuddyError {
90
+ constructor(message, context) {
91
+ super('DISK_SPACE_LOW', message, { context });
92
+ this.name = 'DiskSpaceError';
93
+ }
94
+ }
95
+ /** Thrown by {@link boundedWriteFile} when a {@link DiskBudget} reservation is refused. */
96
+ export class DiskBudgetError extends CodeBuddyError {
97
+ constructor(message, context) {
98
+ super('DISK_BUDGET_EXCEEDED', message, { context });
99
+ this.name = 'DiskBudgetError';
100
+ }
101
+ }
102
+ /**
103
+ * Free-space info for the filesystem containing `targetPath`, or `null` when
104
+ * `fs.statfsSync` is unavailable / the path can't be stat'd (never throws).
105
+ * Uses `bavail` (blocks available to non-root) — the honest free figure a
106
+ * normal process can actually use — not `bfree`.
107
+ */
108
+ export function getFreeSpaceInfo(targetPath) {
109
+ try {
110
+ const st = statfsSync(targetPath);
111
+ const bsize = Number(st.bsize);
112
+ const totalBytes = Number(st.blocks) * bsize;
113
+ const freeBytes = Number(st.bavail) * bsize;
114
+ const freePercent = totalBytes > 0 ? (freeBytes / totalBytes) * 100 : 0;
115
+ return { freeBytes, totalBytes, freePercent };
116
+ }
117
+ catch {
118
+ return null;
119
+ }
120
+ }
121
+ /** Free bytes for the filesystem containing `targetPath`, or `null` if unavailable. */
122
+ export function getFreeBytes(targetPath) {
123
+ return getFreeSpaceInfo(targetPath)?.freeBytes ?? null;
124
+ }
125
+ /**
126
+ * Throw {@link DiskSpaceError} if free space on `targetPath`'s filesystem is
127
+ * below `minBytes` (default {@link DISK_GUARD_DEFAULTS.minFreeMb}). No-op when
128
+ * statfs is unavailable — we can't guard, so we don't block (matches the
129
+ * platform-resilient behavior of the original pilot guard).
130
+ */
131
+ export function ensureFreeSpace(targetPath, minBytes, opts = {}) {
132
+ const min = minBytes ?? defaultMinFreeBytes();
133
+ const info = getFreeSpaceInfo(targetPath);
134
+ if (info === null)
135
+ return;
136
+ if (info.freeBytes < min) {
137
+ const label = opts.label ? `${opts.label}: ` : '';
138
+ throw new DiskSpaceError(`${label}only ${formatMb(info.freeBytes)} free on ${targetPath} — refusing to proceed ` +
139
+ `(needs ${formatMb(min)} headroom; free disk space first).`, { path: targetPath, freeBytes: info.freeBytes, minBytes: min });
140
+ }
141
+ }
142
+ /**
143
+ * Remove orphaned entries named `<prefix>*` directly under `root`. This is the
144
+ * durable backstop for the leak class: managed temp dirs are auto-disposed on a
145
+ * graceful exit, but a SIGKILL'd / crashed run skips cleanup and leaks its dir
146
+ * until the next startup sweep.
147
+ *
148
+ * - `maxAgeMs: 0` (default) → presence-based: remove every match (the original
149
+ * pilot semantics — safe when only one instance runs at a time).
150
+ * - `maxAgeMs > 0` → age-safe: only remove matches whose mtime is older than
151
+ * `maxAgeMs`, so a concurrently-running peer's fresh dir is never nuked.
152
+ */
153
+ export function sweepOrphans(root, prefix, opts = {}) {
154
+ const maxAgeMs = opts.maxAgeMs ?? defaultTempMaxAgeMs();
155
+ const dryRun = opts.dryRun ?? false;
156
+ const removed = [];
157
+ const skipped = [];
158
+ let names;
159
+ try {
160
+ names = readdirSync(root);
161
+ }
162
+ catch {
163
+ return { removed, skipped };
164
+ }
165
+ const now = Date.now();
166
+ for (const name of names) {
167
+ if (!name.startsWith(prefix))
168
+ continue;
169
+ const full = join(root, name);
170
+ if (maxAgeMs > 0) {
171
+ try {
172
+ const st = statSync(full);
173
+ if (now - st.mtimeMs < maxAgeMs) {
174
+ skipped.push(full);
175
+ continue;
176
+ }
177
+ }
178
+ catch {
179
+ skipped.push(full);
180
+ continue;
181
+ }
182
+ }
183
+ if (dryRun) {
184
+ removed.push(full);
185
+ continue;
186
+ }
187
+ try {
188
+ rmSync(full, { recursive: true, force: true });
189
+ removed.push(full);
190
+ }
191
+ catch {
192
+ skipped.push(full);
193
+ }
194
+ }
195
+ return { removed, skipped };
196
+ }
197
+ /**
198
+ * One-shot startup janitor: remove our own stale scratch dirs/files left in the
199
+ * system temp dir by crashed or SIGKILL'd runs (the durable backstop for the
200
+ * leak class). Age-gated (default 24h) so a concurrently-running session's fresh
201
+ * scratch dirs are never touched. Matches both `codebuddy-*` and `codebuddy_*`.
202
+ * Never throws.
203
+ */
204
+ export function sweepStaleCodebuddyTemp(maxAgeMs = 24 * 60 * 60 * 1000) {
205
+ return sweepOrphans(tmpdir(), 'codebuddy', { maxAgeMs });
206
+ }
207
+ const managedTempDirs = new Set();
208
+ let shutdownHandlerRegistered = false;
209
+ class ManagedTempDirImpl {
210
+ path;
211
+ disposed = false;
212
+ constructor(path) {
213
+ this.path = path;
214
+ }
215
+ async dispose() {
216
+ if (this.disposed)
217
+ return;
218
+ this.disposed = true;
219
+ managedTempDirs.delete(this);
220
+ unregisterDisposable(this);
221
+ try {
222
+ await rm(this.path, { recursive: true, force: true });
223
+ }
224
+ catch (err) {
225
+ logger.warn(`disk-guard: failed to remove managed temp dir ${this.path}`, {
226
+ error: err instanceof Error ? err.message : String(err),
227
+ });
228
+ }
229
+ }
230
+ }
231
+ /** Lazily wire a single shutdown handler the first time a managed temp dir is created. */
232
+ function ensureShutdownHandler() {
233
+ if (shutdownHandlerRegistered)
234
+ return;
235
+ shutdownHandlerRegistered = true;
236
+ try {
237
+ GracefulShutdownManager.getInstance().registerHandler({
238
+ name: 'disk-guard:temp-cleanup',
239
+ priority: 100,
240
+ handler: async () => {
241
+ await disposeAllManagedTempDirs();
242
+ },
243
+ });
244
+ }
245
+ catch (err) {
246
+ logger.debug('disk-guard: could not register shutdown handler', {
247
+ error: err instanceof Error ? err.message : String(err),
248
+ });
249
+ }
250
+ }
251
+ /** Dispose every still-tracked managed temp dir (invoked on graceful shutdown). */
252
+ export async function disposeAllManagedTempDirs() {
253
+ await Promise.allSettled([...managedTempDirs].map((d) => d.dispose()));
254
+ }
255
+ /**
256
+ * Create a temp directory that is tracked and auto-removed on graceful exit
257
+ * (and via the {@link Disposable} registry). Preflights {@link ensureFreeSpace}
258
+ * on the parent so we fail fast instead of mkdtemp'ing into a full disk.
259
+ *
260
+ * Pair this with a startup {@link sweepOrphans} on the same `parentDir`/`prefix`
261
+ * as the durable backstop for crashed runs that never reach `dispose()`.
262
+ */
263
+ export async function createManagedTempDir(prefix, opts = {}) {
264
+ const parent = opts.parentDir ?? tmpdir();
265
+ ensureFreeSpace(parent, opts.minFreeBytes, { label: `createManagedTempDir(${prefix})` });
266
+ const dir = await mkdtemp(join(parent, prefix));
267
+ const handle = new ManagedTempDirImpl(dir);
268
+ managedTempDirs.add(handle);
269
+ registerDisposable(handle);
270
+ ensureShutdownHandler();
271
+ return handle;
272
+ }
273
+ /**
274
+ * Tracks cumulative bytes written against a quota
275
+ * (default {@link DISK_GUARD_DEFAULTS.quotaMb} → `CODEBUDDY_DISK_QUOTA_MB`).
276
+ * A quota of 0 means unlimited — every reservation is allowed.
277
+ */
278
+ export class DiskBudget {
279
+ quotaBytes;
280
+ usedBytes = 0;
281
+ constructor(quotaBytes) {
282
+ this.quotaBytes = quotaBytes ?? defaultQuotaBytes();
283
+ }
284
+ /** Check (without recording) whether `bytes` more would fit. */
285
+ reserve(bytes) {
286
+ if (this.quotaBytes <= 0)
287
+ return { allowed: true };
288
+ if (this.usedBytes + bytes > this.quotaBytes) {
289
+ return {
290
+ allowed: false,
291
+ reason: `disk budget exceeded: ${formatMb(this.usedBytes + bytes)} would exceed ` +
292
+ `quota ${formatMb(this.quotaBytes)}`,
293
+ };
294
+ }
295
+ return { allowed: true };
296
+ }
297
+ record(bytes) {
298
+ this.usedBytes += bytes;
299
+ }
300
+ release(bytes) {
301
+ this.usedBytes = Math.max(0, this.usedBytes - bytes);
302
+ }
303
+ used() {
304
+ return this.usedBytes;
305
+ }
306
+ remaining() {
307
+ return this.quotaBytes <= 0 ? Infinity : Math.max(0, this.quotaBytes - this.usedBytes);
308
+ }
309
+ }
310
+ // ---------------------------------------------------------------------------
311
+ // Bounded writes + log rotation
312
+ // ---------------------------------------------------------------------------
313
+ function rotatedPath(filePath, index) {
314
+ const ext = extname(filePath);
315
+ const base = filePath.slice(0, filePath.length - ext.length);
316
+ return `${base}.${index}${ext}`;
317
+ }
318
+ /**
319
+ * Rotate `filePath` when it reaches `maxBytes`, keeping at most `maxFiles`
320
+ * generations (`name.1.ext` … `name.N.ext`, oldest dropped). Ports the
321
+ * algorithm in logger.ts so unbounded append logs can't fill the disk.
322
+ * Returns true if a rotation happened. Never throws.
323
+ */
324
+ export function rotateIfLarge(filePath, maxBytes, maxFiles) {
325
+ const max = maxBytes ?? defaultLogMaxBytes();
326
+ const keep = maxFiles ?? defaultLogMaxFiles();
327
+ try {
328
+ const st = statSync(filePath);
329
+ if (st.size < max)
330
+ return false;
331
+ }
332
+ catch {
333
+ return false; // file doesn't exist yet → nothing to rotate
334
+ }
335
+ // Shift existing rotated files (N -> N+1), dropping anything beyond `keep`.
336
+ for (let i = keep - 1; i >= 1; i--) {
337
+ const src = rotatedPath(filePath, i);
338
+ const dst = rotatedPath(filePath, i + 1);
339
+ try {
340
+ if (existsSync(src)) {
341
+ if (i + 1 > keep)
342
+ unlinkSync(src);
343
+ else
344
+ renameSync(src, dst);
345
+ }
346
+ }
347
+ catch {
348
+ /* ignore individual file rotation errors */
349
+ }
350
+ }
351
+ try {
352
+ const overflow = rotatedPath(filePath, keep + 1);
353
+ if (existsSync(overflow))
354
+ unlinkSync(overflow);
355
+ }
356
+ catch {
357
+ /* ignore */
358
+ }
359
+ try {
360
+ renameSync(filePath, rotatedPath(filePath, 1));
361
+ }
362
+ catch {
363
+ /* if rename fails, continue appending to the existing file */
364
+ }
365
+ return true;
366
+ }
367
+ /**
368
+ * Write a file after preflighting free space (and, if a {@link DiskBudget} is
369
+ * supplied, reserving + recording its size). Throws {@link DiskSpaceError} or
370
+ * {@link DiskBudgetError} before touching disk when bounds would be breached.
371
+ */
372
+ export async function boundedWriteFile(filePath, data, opts = {}) {
373
+ ensureFreeSpace(dirname(filePath), opts.minFreeBytes, { label: `write ${basename(filePath)}` });
374
+ const size = byteLength(data);
375
+ if (opts.budget) {
376
+ const res = opts.budget.reserve(size);
377
+ if (!res.allowed) {
378
+ throw new DiskBudgetError(res.reason ?? 'disk budget exceeded', {
379
+ usedBytes: opts.budget.used(),
380
+ quotaBytes: opts.budget.used() + opts.budget.remaining(),
381
+ requestedBytes: size,
382
+ });
383
+ }
384
+ }
385
+ await writeFile(filePath, data);
386
+ opts.budget?.record(size);
387
+ }
388
+ /**
389
+ * Append to a file after optionally rotating it ({@link rotateIfLarge}) and
390
+ * preflighting free space. The append-log counterpart to {@link boundedWriteFile}.
391
+ */
392
+ export async function boundedAppendFile(filePath, data, opts = {}) {
393
+ if (opts.rotate) {
394
+ rotateIfLarge(filePath, opts.rotate.maxBytes, opts.rotate.maxFiles);
395
+ }
396
+ ensureFreeSpace(dirname(filePath), opts.minFreeBytes, { label: `append ${basename(filePath)}` });
397
+ await appendFile(filePath, data);
398
+ }
399
+ /** Test helper: clear tracked temp dirs + reset the lazy shutdown-handler flag. */
400
+ export function resetDiskGuardForTests() {
401
+ managedTempDirs.clear();
402
+ shutdownHandlerRegistered = false;
403
+ }
404
+ //# sourceMappingURL=disk-guard.js.map
@@ -81,4 +81,11 @@ export declare function initCodeBuddyProject(workingDirectory?: string, options?
81
81
  * Format init result for display (ASCII markers, no emojis)
82
82
  */
83
83
  export declare function formatInitResult(result: InitResult): string;
84
+ /**
85
+ * Build the prompt for the agent-driven `/init`. Instructs the model to analyze
86
+ * the repository and write/improve a tailored root `AGENTS.md`, grounded on the
87
+ * RepoProfiler's verified facts so it cannot hallucinate build/test commands.
88
+ * Modeled on Codex's `prompt_for_init_command.md`.
89
+ */
90
+ export declare function buildInitPrompt(contextPack: string): string;
84
91
  export {};
@@ -495,6 +495,13 @@ ${archLines.join('\n')}
495
495
  ## More Context
496
496
  ${moreLines.join('\n')}
497
497
 
498
+ <sub>**How instruction files load:** Code Buddy reads from \`~/.codebuddy/\` (global), the
499
+ project root, and subdirectories — the file closest to what you're editing wins.
500
+ Accepted names compose in order: **AGENTS.md** (this file, primary), then \`CODEBUDDY.md\`,
501
+ \`CLAUDE.md\`, \`GEMINI.md\`, \`CONTEXT.md\`, \`INSTRUCTIONS.md\`. Drop \`@./path.md\` to import
502
+ another file. Override a file in the same directory with \`AGENTS.override.md\` (committed)
503
+ or \`AGENTS.local.md\` (gitignored).</sub>
504
+
498
505
  ---
499
506
  *Auto-generated by \`buddy init\`. Re-run with \`--force\` to refresh, or edit by hand — \`buddy init\` won't overwrite without explicit force.*
500
507
  `;
@@ -772,6 +779,8 @@ Safety checks:
772
779
  .codebuddy/tool-results/
773
780
  .codebuddy/cache/
774
781
  .codebuddy/user-settings.json
782
+ # Personal, uncommitted instruction overrides (AGENTS.local.md, CODEBUDDY.local.md, …)
783
+ *.local.md
775
784
  `;
776
785
  if (fs.existsSync(gitignorePath)) {
777
786
  const currentContent = fs.readFileSync(gitignorePath, 'utf-8');
@@ -897,6 +906,34 @@ export function formatInitResult(result) {
897
906
  output += ' 5. Use /starter to browse framework starter packs for new projects\n';
898
907
  return output;
899
908
  }
909
+ /**
910
+ * Build the prompt for the agent-driven `/init`. Instructs the model to analyze
911
+ * the repository and write/improve a tailored root `AGENTS.md`, grounded on the
912
+ * RepoProfiler's verified facts so it cannot hallucinate build/test commands.
913
+ * Modeled on Codex's `prompt_for_init_command.md`.
914
+ */
915
+ export function buildInitPrompt(contextPack) {
916
+ const facts = contextPack && contextPack.trim()
917
+ ? `\nVERIFIED FACTS about this repository (auto-detected — use these, do not invent commands):\n${contextPack.trim()}\n`
918
+ : '';
919
+ return `Analyze THIS repository and write a tailored \`AGENTS.md\` at the project root — a concise contributor/agent guide (the cross-CLI standard read by Claude Code, Cursor, Codex, Gemini CLI and Code Buddy).
920
+ ${facts}
921
+ How to do it:
922
+ 1. Explore the real code first — use \`list_directory\`, \`view_file\`, \`search\` (and \`code_graph\` if available) to understand the actual structure, entry points, scripts and conventions. Read \`package.json\` / \`pyproject.toml\` / \`Cargo.toml\`, the README, and a few representative source files.
923
+ 2. Trust any auto-detected facts shown above for build/test/lint commands, languages and directories — do NOT invent commands. Confirm anything else by reading the config files.
924
+ 3. Write a clear, concise document (aim for 200–400 words, Markdown headings), adapting or omitting sections as appropriate:
925
+ - **Project Structure & Module Organization** — where source, tests and assets live.
926
+ - **Build, Test & Dev Commands** — the real commands, each briefly explained.
927
+ - **Coding Style & Naming Conventions** — indentation, language style, naming, lint/format tools.
928
+ - **Testing Guidelines** — framework, how to run, naming.
929
+ - **Commit & Pull Request Guidelines** — infer conventions from the git history.
930
+ - *(optional)* **Architecture Overview** — for non-obvious design.
931
+
932
+ Important:
933
+ - Write ONLY \`AGENTS.md\` at the project root. Do NOT modify anything under \`.codebuddy/\` — that config was just scaffolded deterministically.
934
+ - If \`AGENTS.md\` already has meaningful content, IMPROVE it in place: preserve the user's good wording, refine and fill gaps; never blank-overwrite.
935
+ - Keep it specific to THIS repository and actionable. No filler.`;
936
+ }
900
937
  // ============================================================================
901
938
  // Data flow renderer — shows how key components connect
902
939
  // ============================================================================
@@ -12,6 +12,7 @@ import { logger } from "./logger.js";
12
12
  import path from 'path';
13
13
  import readline from 'readline';
14
14
  import { getCodeBuddyHome, ensureCodeBuddyHome } from './codebuddy-home.js';
15
+ import { getApiKey as getStoredApiKey, setApiKey as setStoredApiKey, } from '../security/credential-manager.js';
15
16
  // ============================================================================
16
17
  // Readline Utilities
17
18
  // ============================================================================
@@ -208,6 +209,15 @@ export async function runSetup() {
208
209
  * Load existing API key from settings
209
210
  */
210
211
  function loadExistingApiKey() {
212
+ // Encrypted credential store first; fall back to a legacy plaintext key.
213
+ try {
214
+ const stored = getStoredApiKey();
215
+ if (stored)
216
+ return stored;
217
+ }
218
+ catch {
219
+ // Ignore — fall through to the legacy settings file.
220
+ }
211
221
  try {
212
222
  const settingsPath = path.join(getCodeBuddyHome(), 'user-settings.json');
213
223
  if (fs.existsSync(settingsPath)) {
@@ -232,9 +242,25 @@ async function saveConfig(config) {
232
242
  if (fs.existsSync(settingsPath)) {
233
243
  settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
234
244
  }
235
- // Update settings
245
+ // Update settings.
246
+ // API keys are secrets: persist them to the encrypted credential store,
247
+ // NEVER plaintext in user-settings.json. Mirrors the onboarding wizard
248
+ // (src/wizard/onboarding.ts → persistProviderSelection).
236
249
  if (config.apiKey) {
237
- settings.apiKey = config.apiKey;
250
+ try {
251
+ setStoredApiKey(config.apiKey);
252
+ }
253
+ catch (err) {
254
+ logger.warn('Could not persist API key to the credential store', {
255
+ error: err instanceof Error ? err.message : String(err),
256
+ });
257
+ }
258
+ if (!process.env.GROK_API_KEY)
259
+ process.env.GROK_API_KEY = config.apiKey;
260
+ }
261
+ // Migrate any legacy plaintext key out of the settings file.
262
+ if (typeof settings.apiKey === 'string') {
263
+ delete settings.apiKey;
238
264
  }
239
265
  if (config.baseURL) {
240
266
  settings.baseURL = config.baseURL;
@@ -263,6 +289,13 @@ export function needsSetup() {
263
289
  if (process.env.GROK_API_KEY) {
264
290
  return false;
265
291
  }
292
+ try {
293
+ if (getStoredApiKey())
294
+ return false;
295
+ }
296
+ catch {
297
+ // Ignore — fall through to the legacy settings file.
298
+ }
266
299
  try {
267
300
  const settingsPath = path.join(getCodeBuddyHome(), 'user-settings.json');
268
301
  if (fs.existsSync(settingsPath)) {
@@ -9,13 +9,22 @@ import { ValidationError } from './errors.js';
9
9
  export function isSupportedModel(model) {
10
10
  return model in SUPPORTED_MODELS;
11
11
  }
12
+ function resolveSupportedModelName(model) {
13
+ if (isSupportedModel(model))
14
+ return model;
15
+ const withoutLatest = model.endsWith(':latest') ? model.slice(0, -':latest'.length) : '';
16
+ if (withoutLatest && isSupportedModel(withoutLatest))
17
+ return withoutLatest;
18
+ return null;
19
+ }
12
20
  /**
13
21
  * Get information about a model
14
22
  */
15
23
  export function getModelInfo(model) {
16
- if (isSupportedModel(model)) {
24
+ const supportedModel = resolveSupportedModelName(model);
25
+ if (supportedModel) {
17
26
  return {
18
- ...SUPPORTED_MODELS[model],
27
+ ...SUPPORTED_MODELS[supportedModel],
19
28
  isSupported: true,
20
29
  };
21
30
  }
@@ -30,6 +30,14 @@ const CHATML_TOKEN_PATTERN = /<\|(?:im_start|im_end|endoftext|assistant|user|sys
30
30
  * Kept as a separate pass to avoid over-matching legitimate content.
31
31
  */
32
32
  const GENERIC_CONTROL_TOKEN_PATTERN = /<\|[^|>]+\|>/g;
33
+ /**
34
+ * Some local OpenAI-compatible runtimes leak malformed channel tokens that
35
+ * miss one side of the ChatML pipe pair, e.g. `thought\n<channel|>42` or
36
+ * `<|tool_call>...`. Require at least one pipe so legitimate XML/HTML tags
37
+ * such as `<message>` are preserved.
38
+ */
39
+ const LOCAL_CHANNEL_HEADER_PATTERN = /^(?:thought|analysis|commentary)\s*\n\s*(?=<(?:\|(?:channel|message|tool_call|constrain)\|?|(?:channel|message|tool_call|constrain)\|)>)/i;
40
+ const MALFORMED_PIPE_CONTROL_TOKEN_PATTERN = /<(?:\|(?:channel|message|tool_call|constrain)\|?|(?:channel|message|tool_call|constrain)\|)>/gi;
33
41
  /**
34
42
  * JSON-escaped control tokens: \u003c|token|\u003e
35
43
  */
@@ -67,6 +75,9 @@ const SANITIZATION_RULES = [
67
75
  { pattern: CHATML_TOKEN_PATTERN, replacement: '' },
68
76
  // Generic <|...|> control tokens
69
77
  { pattern: GENERIC_CONTROL_TOKEN_PATTERN, replacement: '' },
78
+ // Local malformed channel/control markers
79
+ { pattern: LOCAL_CHANNEL_HEADER_PATTERN, replacement: '' },
80
+ { pattern: MALFORMED_PIPE_CONTROL_TOKEN_PATTERN, replacement: '' },
70
81
  // JSON-escaped control tokens
71
82
  { pattern: JSON_ESCAPED_CONTROL_TOKEN_PATTERN, replacement: '' },
72
83
  // LLaMA system prompt blocks
@@ -5,21 +5,22 @@
5
5
  * a Code Buddy session. Pure function (no side effects beyond stat/read
6
6
  * on the OAuth file) so it's unit-testable.
7
7
  *
8
- * Priority order:
8
+ * Priority order is defined in `src/providers/provider-catalog.ts`:
9
9
  * 0. CODEBUDDY_PROVIDER override (always wins when set + valid)
10
10
  * 1. ChatGPT OAuth credentials present (~/.codebuddy/codex-auth.json)
11
11
  * → explicit "I logged in" act beats ambient env vars
12
- * 2. OLLAMA_HOST → ollama (local, free, unlimited)
13
- * 3. GROK_API_KEY → grok / OpenAI-compat (incl. xAI)
14
- * 4. GEMINI/GOOGLE → gemini
15
- * 5. OPENAI → openai
16
- * 6. ANTHROPIC → anthropic
12
+ * 2. Local providers (Ollama / LM Studio / vLLM)
13
+ * 3. Cloud providers in catalog priority order
17
14
  * else null (no provider available)
18
15
  */
16
+ import { type RuntimeProviderId, type ProviderApiMode, type ProviderAuthMode } from '../providers/provider-catalog.js';
19
17
  export interface DetectedProvider {
20
- provider: 'gemini' | 'grok' | 'openai' | 'anthropic' | 'ollama' | 'chatgpt' | 'unknown';
18
+ provider: RuntimeProviderId | 'unknown';
21
19
  apiKey: string;
22
20
  baseURL: string;
23
21
  defaultModel: string;
22
+ apiMode?: ProviderApiMode;
23
+ authMode?: ProviderAuthMode;
24
+ source?: 'oauth' | 'environment' | 'override';
24
25
  }
25
26
  export declare function detectProviderFromEnv(): DetectedProvider | null;