@qduc/term2 0.1.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 (621) hide show
  1. package/LICENSE +21 -0
  2. package/dist/agent.d.ts +19 -0
  3. package/dist/agent.d.ts.map +1 -0
  4. package/dist/agent.js +143 -0
  5. package/dist/agent.js.map +1 -0
  6. package/dist/app.d.ts +22 -0
  7. package/dist/app.d.ts.map +1 -0
  8. package/dist/app.js +403 -0
  9. package/dist/app.js.map +1 -0
  10. package/dist/app.model-command-feedback.test.d.ts +2 -0
  11. package/dist/app.model-command-feedback.test.d.ts.map +1 -0
  12. package/dist/app.model-command-feedback.test.js +19 -0
  13. package/dist/app.model-command-feedback.test.js.map +1 -0
  14. package/dist/app.parseInput.test.d.ts +2 -0
  15. package/dist/app.parseInput.test.d.ts.map +1 -0
  16. package/dist/app.parseInput.test.js +97 -0
  17. package/dist/app.parseInput.test.js.map +1 -0
  18. package/dist/cli.d.ts +3 -0
  19. package/dist/cli.d.ts.map +1 -0
  20. package/dist/cli.js +241 -0
  21. package/dist/cli.js.map +1 -0
  22. package/dist/components/ApprovalPrompt.d.ts +10 -0
  23. package/dist/components/ApprovalPrompt.d.ts.map +1 -0
  24. package/dist/components/ApprovalPrompt.js +163 -0
  25. package/dist/components/ApprovalPrompt.js.map +1 -0
  26. package/dist/components/Banner.d.ts +9 -0
  27. package/dist/components/Banner.d.ts.map +1 -0
  28. package/dist/components/Banner.js +86 -0
  29. package/dist/components/Banner.js.map +1 -0
  30. package/dist/components/BottomArea.d.ts +33 -0
  31. package/dist/components/BottomArea.d.ts.map +1 -0
  32. package/dist/components/BottomArea.js +31 -0
  33. package/dist/components/BottomArea.js.map +1 -0
  34. package/dist/components/BottomArea.test.d.ts +2 -0
  35. package/dist/components/BottomArea.test.d.ts.map +1 -0
  36. package/dist/components/BottomArea.test.js +73 -0
  37. package/dist/components/BottomArea.test.js.map +1 -0
  38. package/dist/components/ChatMessage.d.ts +7 -0
  39. package/dist/components/ChatMessage.d.ts.map +1 -0
  40. package/dist/components/ChatMessage.js +10 -0
  41. package/dist/components/ChatMessage.js.map +1 -0
  42. package/dist/components/CommandMessage.d.ts +15 -0
  43. package/dist/components/CommandMessage.d.ts.map +1 -0
  44. package/dist/components/CommandMessage.js +188 -0
  45. package/dist/components/CommandMessage.js.map +1 -0
  46. package/dist/components/CommandMessage.test.d.ts +2 -0
  47. package/dist/components/CommandMessage.test.d.ts.map +1 -0
  48. package/dist/components/CommandMessage.test.js +35 -0
  49. package/dist/components/CommandMessage.test.js.map +1 -0
  50. package/dist/components/ErrorBoundary.d.ts +27 -0
  51. package/dist/components/ErrorBoundary.d.ts.map +1 -0
  52. package/dist/components/ErrorBoundary.js +77 -0
  53. package/dist/components/ErrorBoundary.js.map +1 -0
  54. package/dist/components/ErrorBoundary.test.d.ts +2 -0
  55. package/dist/components/ErrorBoundary.test.d.ts.map +1 -0
  56. package/dist/components/ErrorBoundary.test.js +32 -0
  57. package/dist/components/ErrorBoundary.test.js.map +1 -0
  58. package/dist/components/Input/PopupManager.d.ts +42 -0
  59. package/dist/components/Input/PopupManager.d.ts.map +1 -0
  60. package/dist/components/Input/PopupManager.js +13 -0
  61. package/dist/components/Input/PopupManager.js.map +1 -0
  62. package/dist/components/InputBox.d.ts +18 -0
  63. package/dist/components/InputBox.d.ts.map +1 -0
  64. package/dist/components/InputBox.js +384 -0
  65. package/dist/components/InputBox.js.map +1 -0
  66. package/dist/components/InputBox.menu-logic.test.d.ts +2 -0
  67. package/dist/components/InputBox.menu-logic.test.d.ts.map +1 -0
  68. package/dist/components/InputBox.menu-logic.test.js +151 -0
  69. package/dist/components/InputBox.menu-logic.test.js.map +1 -0
  70. package/dist/components/InputBox.test.d.ts +2 -0
  71. package/dist/components/InputBox.test.d.ts.map +1 -0
  72. package/dist/components/InputBox.test.js +91 -0
  73. package/dist/components/InputBox.test.js.map +1 -0
  74. package/dist/components/LiveResponse.d.ts +13 -0
  75. package/dist/components/LiveResponse.d.ts.map +1 -0
  76. package/dist/components/LiveResponse.js +16 -0
  77. package/dist/components/LiveResponse.js.map +1 -0
  78. package/dist/components/MarkdownRenderer.d.ts +8 -0
  79. package/dist/components/MarkdownRenderer.d.ts.map +1 -0
  80. package/dist/components/MarkdownRenderer.js +225 -0
  81. package/dist/components/MarkdownRenderer.js.map +1 -0
  82. package/dist/components/MentorMode.test.d.ts +2 -0
  83. package/dist/components/MentorMode.test.d.ts.map +1 -0
  84. package/dist/components/MentorMode.test.js.map +1 -0
  85. package/dist/components/MessageList.d.ts +7 -0
  86. package/dist/components/MessageList.d.ts.map +1 -0
  87. package/dist/components/MessageList.js +29 -0
  88. package/dist/components/MessageList.js.map +1 -0
  89. package/dist/components/MessageList.test.d.ts +2 -0
  90. package/dist/components/MessageList.test.d.ts.map +1 -0
  91. package/dist/components/MessageList.test.js +15 -0
  92. package/dist/components/MessageList.test.js.map +1 -0
  93. package/dist/components/ModelSelectionMenu.d.ts +18 -0
  94. package/dist/components/ModelSelectionMenu.d.ts.map +1 -0
  95. package/dist/components/ModelSelectionMenu.js +91 -0
  96. package/dist/components/ModelSelectionMenu.js.map +1 -0
  97. package/dist/components/ModelSelectionMenu.test.d.ts +2 -0
  98. package/dist/components/ModelSelectionMenu.test.d.ts.map +1 -0
  99. package/dist/components/ModelSelectionMenu.test.js +83 -0
  100. package/dist/components/ModelSelectionMenu.test.js.map +1 -0
  101. package/dist/components/PathSelectionMenu.d.ts +12 -0
  102. package/dist/components/PathSelectionMenu.d.ts.map +1 -0
  103. package/dist/components/PathSelectionMenu.js +42 -0
  104. package/dist/components/PathSelectionMenu.js.map +1 -0
  105. package/dist/components/SettingsSelectionMenu.d.ts +9 -0
  106. package/dist/components/SettingsSelectionMenu.d.ts.map +1 -0
  107. package/dist/components/SettingsSelectionMenu.js +21 -0
  108. package/dist/components/SettingsSelectionMenu.js.map +1 -0
  109. package/dist/components/SlashCommandMenu.d.ts +15 -0
  110. package/dist/components/SlashCommandMenu.d.ts.map +1 -0
  111. package/dist/components/SlashCommandMenu.js +20 -0
  112. package/dist/components/SlashCommandMenu.js.map +1 -0
  113. package/dist/components/StatusBar.d.ts +11 -0
  114. package/dist/components/StatusBar.d.ts.map +1 -0
  115. package/dist/components/StatusBar.js +59 -0
  116. package/dist/components/StatusBar.js.map +1 -0
  117. package/dist/components/TextInput.d.ts +42 -0
  118. package/dist/components/TextInput.d.ts.map +1 -0
  119. package/dist/components/TextInput.js +397 -0
  120. package/dist/components/TextInput.js.map +1 -0
  121. package/dist/components/TextInput.test.d.ts +2 -0
  122. package/dist/components/TextInput.test.d.ts.map +1 -0
  123. package/dist/components/TextInput.test.js +75 -0
  124. package/dist/components/TextInput.test.js.map +1 -0
  125. package/dist/context/InputContext.d.ts +31 -0
  126. package/dist/context/InputContext.d.ts.map +1 -0
  127. package/dist/context/InputContext.js +36 -0
  128. package/dist/context/InputContext.js.map +1 -0
  129. package/dist/context/InputContext.stability.test.d.ts +2 -0
  130. package/dist/context/InputContext.stability.test.d.ts.map +1 -0
  131. package/dist/context/InputContext.stability.test.js +28 -0
  132. package/dist/context/InputContext.stability.test.js.map +1 -0
  133. package/dist/context/InputContext.test.d.ts +2 -0
  134. package/dist/context/InputContext.test.d.ts.map +1 -0
  135. package/dist/context/InputContext.test.js +168 -0
  136. package/dist/context/InputContext.test.js.map +1 -0
  137. package/dist/debug-schema.d.ts +2 -0
  138. package/dist/debug-schema.d.ts.map +1 -0
  139. package/dist/debug-schema.js +22 -0
  140. package/dist/debug-schema.js.map +1 -0
  141. package/dist/hooks/use-conversation.d.ts +78 -0
  142. package/dist/hooks/use-conversation.d.ts.map +1 -0
  143. package/dist/hooks/use-conversation.js +1017 -0
  144. package/dist/hooks/use-conversation.js.map +1 -0
  145. package/dist/hooks/use-input-history.d.ts +16 -0
  146. package/dist/hooks/use-input-history.d.ts.map +1 -0
  147. package/dist/hooks/use-input-history.js +71 -0
  148. package/dist/hooks/use-input-history.js.map +1 -0
  149. package/dist/hooks/use-model-selection.d.ts +27 -0
  150. package/dist/hooks/use-model-selection.d.ts.map +1 -0
  151. package/dist/hooks/use-model-selection.js +187 -0
  152. package/dist/hooks/use-model-selection.js.map +1 -0
  153. package/dist/hooks/use-model-selection.test.d.ts +2 -0
  154. package/dist/hooks/use-model-selection.test.d.ts.map +1 -0
  155. package/dist/hooks/use-model-selection.test.js +28 -0
  156. package/dist/hooks/use-model-selection.test.js.map +1 -0
  157. package/dist/hooks/use-path-completion.d.ts +22 -0
  158. package/dist/hooks/use-path-completion.d.ts.map +1 -0
  159. package/dist/hooks/use-path-completion.js +153 -0
  160. package/dist/hooks/use-path-completion.js.map +1 -0
  161. package/dist/hooks/use-path-completion.test.d.ts +2 -0
  162. package/dist/hooks/use-path-completion.test.d.ts.map +1 -0
  163. package/dist/hooks/use-path-completion.test.js +29 -0
  164. package/dist/hooks/use-path-completion.test.js.map +1 -0
  165. package/dist/hooks/use-setting.d.ts +7 -0
  166. package/dist/hooks/use-setting.d.ts.map +1 -0
  167. package/dist/hooks/use-setting.js +35 -0
  168. package/dist/hooks/use-setting.js.map +1 -0
  169. package/dist/hooks/use-settings-completion.d.ts +23 -0
  170. package/dist/hooks/use-settings-completion.d.ts.map +1 -0
  171. package/dist/hooks/use-settings-completion.js +164 -0
  172. package/dist/hooks/use-settings-completion.js.map +1 -0
  173. package/dist/hooks/use-settings-completion.test.d.ts +2 -0
  174. package/dist/hooks/use-settings-completion.test.d.ts.map +1 -0
  175. package/dist/hooks/use-settings-completion.test.js +334 -0
  176. package/dist/hooks/use-settings-completion.test.js.map +1 -0
  177. package/dist/hooks/use-slash-commands.d.ts +21 -0
  178. package/dist/hooks/use-slash-commands.d.ts.map +1 -0
  179. package/dist/hooks/use-slash-commands.js +87 -0
  180. package/dist/hooks/use-slash-commands.js.map +1 -0
  181. package/dist/hooks/use-slash-commands.test.d.ts +2 -0
  182. package/dist/hooks/use-slash-commands.test.d.ts.map +1 -0
  183. package/dist/hooks/use-slash-commands.test.js +246 -0
  184. package/dist/hooks/use-slash-commands.test.js.map +1 -0
  185. package/dist/lib/editor-impl.d.ts +23 -0
  186. package/dist/lib/editor-impl.d.ts.map +1 -0
  187. package/dist/lib/editor-impl.js +235 -0
  188. package/dist/lib/editor-impl.js.map +1 -0
  189. package/dist/lib/openai-agent-client.chat.test.d.ts +2 -0
  190. package/dist/lib/openai-agent-client.chat.test.d.ts.map +1 -0
  191. package/dist/lib/openai-agent-client.chat.test.js +68 -0
  192. package/dist/lib/openai-agent-client.chat.test.js.map +1 -0
  193. package/dist/lib/openai-agent-client.d.ts +48 -0
  194. package/dist/lib/openai-agent-client.d.ts.map +1 -0
  195. package/dist/lib/openai-agent-client.js +653 -0
  196. package/dist/lib/openai-agent-client.js.map +1 -0
  197. package/dist/lib/openai-agent-client.test.d.ts +2 -0
  198. package/dist/lib/openai-agent-client.test.d.ts.map +1 -0
  199. package/dist/lib/openai-agent-client.test.js +181 -0
  200. package/dist/lib/openai-agent-client.test.js.map +1 -0
  201. package/dist/lib/shell.d.ts +7 -0
  202. package/dist/lib/shell.d.ts.map +1 -0
  203. package/dist/lib/shell.js +56 -0
  204. package/dist/lib/shell.js.map +1 -0
  205. package/dist/lib/tool-invoke.d.ts +4 -0
  206. package/dist/lib/tool-invoke.d.ts.map +1 -0
  207. package/dist/lib/tool-invoke.js +26 -0
  208. package/dist/lib/tool-invoke.js.map +1 -0
  209. package/dist/lib/tool-invoke.test.d.ts +2 -0
  210. package/dist/lib/tool-invoke.test.d.ts.map +1 -0
  211. package/dist/lib/tool-invoke.test.js +19 -0
  212. package/dist/lib/tool-invoke.test.js.map +1 -0
  213. package/dist/no-singleton-imports.test.d.ts +2 -0
  214. package/dist/no-singleton-imports.test.d.ts.map +1 -0
  215. package/dist/no-singleton-imports.test.js +30 -0
  216. package/dist/no-singleton-imports.test.js.map +1 -0
  217. package/dist/prompts/anthropic.md +79 -0
  218. package/dist/prompts/codex.md +97 -0
  219. package/dist/prompts/default.md +77 -0
  220. package/dist/prompts/default.md.bak +77 -0
  221. package/dist/prompts/gpt-5.md +318 -0
  222. package/dist/prompts/lite.md +29 -0
  223. package/dist/prompts/simple-mentor.md +207 -0
  224. package/dist/prompts/simple.md +189 -0
  225. package/dist/providers/index.d.ts +5 -0
  226. package/dist/providers/index.d.ts.map +1 -0
  227. package/dist/providers/index.js +8 -0
  228. package/dist/providers/index.js.map +1 -0
  229. package/dist/providers/openai-compatible/api.d.ts +17 -0
  230. package/dist/providers/openai-compatible/api.d.ts.map +1 -0
  231. package/dist/providers/openai-compatible/api.js +58 -0
  232. package/dist/providers/openai-compatible/api.js.map +1 -0
  233. package/dist/providers/openai-compatible/model.d.ts +17 -0
  234. package/dist/providers/openai-compatible/model.d.ts.map +1 -0
  235. package/dist/providers/openai-compatible/model.js +435 -0
  236. package/dist/providers/openai-compatible/model.js.map +1 -0
  237. package/dist/providers/openai-compatible/provider.d.ts +22 -0
  238. package/dist/providers/openai-compatible/provider.d.ts.map +1 -0
  239. package/dist/providers/openai-compatible/provider.js +43 -0
  240. package/dist/providers/openai-compatible/provider.js.map +1 -0
  241. package/dist/providers/openai-compatible/utils.d.ts +3 -0
  242. package/dist/providers/openai-compatible/utils.d.ts.map +1 -0
  243. package/dist/providers/openai-compatible/utils.js +11 -0
  244. package/dist/providers/openai-compatible/utils.js.map +1 -0
  245. package/dist/providers/openai-compatible.provider.d.ts +8 -0
  246. package/dist/providers/openai-compatible.provider.d.ts.map +1 -0
  247. package/dist/providers/openai-compatible.provider.js +71 -0
  248. package/dist/providers/openai-compatible.provider.js.map +1 -0
  249. package/dist/providers/openai.provider.d.ts +2 -0
  250. package/dist/providers/openai.provider.d.ts.map +1 -0
  251. package/dist/providers/openai.provider.js +36 -0
  252. package/dist/providers/openai.provider.js.map +1 -0
  253. package/dist/providers/openrouter/api.d.ts +39 -0
  254. package/dist/providers/openrouter/api.d.ts.map +1 -0
  255. package/dist/providers/openrouter/api.js +172 -0
  256. package/dist/providers/openrouter/api.js.map +1 -0
  257. package/dist/providers/openrouter/converters.d.ts +8 -0
  258. package/dist/providers/openrouter/converters.d.ts.map +1 -0
  259. package/dist/providers/openrouter/converters.js +382 -0
  260. package/dist/providers/openrouter/converters.js.map +1 -0
  261. package/dist/providers/openrouter/converters.test.d.ts +2 -0
  262. package/dist/providers/openrouter/converters.test.d.ts.map +1 -0
  263. package/dist/providers/openrouter/converters.test.js +158 -0
  264. package/dist/providers/openrouter/converters.test.js.map +1 -0
  265. package/dist/providers/openrouter/index.d.ts +4 -0
  266. package/dist/providers/openrouter/index.d.ts.map +1 -0
  267. package/dist/providers/openrouter/index.js +4 -0
  268. package/dist/providers/openrouter/index.js.map +1 -0
  269. package/dist/providers/openrouter/model.d.ts +14 -0
  270. package/dist/providers/openrouter/model.d.ts.map +1 -0
  271. package/dist/providers/openrouter/model.js +485 -0
  272. package/dist/providers/openrouter/model.js.map +1 -0
  273. package/dist/providers/openrouter/provider.d.ts +15 -0
  274. package/dist/providers/openrouter/provider.d.ts.map +1 -0
  275. package/dist/providers/openrouter/provider.js +21 -0
  276. package/dist/providers/openrouter/provider.js.map +1 -0
  277. package/dist/providers/openrouter/utils.d.ts +10 -0
  278. package/dist/providers/openrouter/utils.d.ts.map +1 -0
  279. package/dist/providers/openrouter/utils.js +27 -0
  280. package/dist/providers/openrouter/utils.js.map +1 -0
  281. package/dist/providers/openrouter.api.retry.test.d.ts +2 -0
  282. package/dist/providers/openrouter.api.retry.test.d.ts.map +1 -0
  283. package/dist/providers/openrouter.api.retry.test.js +148 -0
  284. package/dist/providers/openrouter.api.retry.test.js.map +1 -0
  285. package/dist/providers/openrouter.d.ts +2 -0
  286. package/dist/providers/openrouter.d.ts.map +1 -0
  287. package/dist/providers/openrouter.history.test.d.ts +2 -0
  288. package/dist/providers/openrouter.history.test.d.ts.map +1 -0
  289. package/dist/providers/openrouter.history.test.js +533 -0
  290. package/dist/providers/openrouter.history.test.js.map +1 -0
  291. package/dist/providers/openrouter.js +4 -0
  292. package/dist/providers/openrouter.js.map +1 -0
  293. package/dist/providers/openrouter.provider.createRunner.test.d.ts +2 -0
  294. package/dist/providers/openrouter.provider.createRunner.test.d.ts.map +1 -0
  295. package/dist/providers/openrouter.provider.createRunner.test.js +23 -0
  296. package/dist/providers/openrouter.provider.createRunner.test.js.map +1 -0
  297. package/dist/providers/openrouter.provider.d.ts +2 -0
  298. package/dist/providers/openrouter.provider.d.ts.map +1 -0
  299. package/dist/providers/openrouter.provider.js +56 -0
  300. package/dist/providers/openrouter.provider.js.map +1 -0
  301. package/dist/providers/openrouter.test.d.ts +2 -0
  302. package/dist/providers/openrouter.test.d.ts.map +1 -0
  303. package/dist/providers/openrouter.test.js +1382 -0
  304. package/dist/providers/openrouter.test.js.map +1 -0
  305. package/dist/providers/registry.d.ts +65 -0
  306. package/dist/providers/registry.d.ts.map +1 -0
  307. package/dist/providers/registry.js +44 -0
  308. package/dist/providers/registry.js.map +1 -0
  309. package/dist/providers/registry.test.d.ts +2 -0
  310. package/dist/providers/registry.test.d.ts.map +1 -0
  311. package/dist/providers/registry.test.js +76 -0
  312. package/dist/providers/registry.test.js.map +1 -0
  313. package/dist/providers/web-search/index.d.ts +8 -0
  314. package/dist/providers/web-search/index.d.ts.map +1 -0
  315. package/dist/providers/web-search/index.js +9 -0
  316. package/dist/providers/web-search/index.js.map +1 -0
  317. package/dist/providers/web-search/registry.d.ts +35 -0
  318. package/dist/providers/web-search/registry.d.ts.map +1 -0
  319. package/dist/providers/web-search/registry.js +56 -0
  320. package/dist/providers/web-search/registry.js.map +1 -0
  321. package/dist/providers/web-search/registry.test.d.ts +2 -0
  322. package/dist/providers/web-search/registry.test.d.ts.map +1 -0
  323. package/dist/providers/web-search/registry.test.js +105 -0
  324. package/dist/providers/web-search/registry.test.js.map +1 -0
  325. package/dist/providers/web-search/tavily.provider.d.ts +15 -0
  326. package/dist/providers/web-search/tavily.provider.d.ts.map +1 -0
  327. package/dist/providers/web-search/tavily.provider.js +69 -0
  328. package/dist/providers/web-search/tavily.provider.js.map +1 -0
  329. package/dist/providers/web-search/tavily.provider.test.d.ts +2 -0
  330. package/dist/providers/web-search/tavily.provider.test.d.ts.map +1 -0
  331. package/dist/providers/web-search/tavily.provider.test.js +67 -0
  332. package/dist/providers/web-search/tavily.provider.test.js.map +1 -0
  333. package/dist/providers/web-search/types.d.ts +55 -0
  334. package/dist/providers/web-search/types.d.ts.map +1 -0
  335. package/dist/providers/web-search/types.js +6 -0
  336. package/dist/providers/web-search/types.js.map +1 -0
  337. package/dist/safety-checker.js +57 -0
  338. package/dist/services/conversation-events.d.ts +76 -0
  339. package/dist/services/conversation-events.d.ts.map +1 -0
  340. package/dist/services/conversation-events.js +2 -0
  341. package/dist/services/conversation-events.js.map +1 -0
  342. package/dist/services/conversation-service.d.ts +31 -0
  343. package/dist/services/conversation-service.d.ts.map +1 -0
  344. package/dist/services/conversation-service.js +46 -0
  345. package/dist/services/conversation-service.js.map +1 -0
  346. package/dist/services/conversation-service.test.js +190 -0
  347. package/dist/services/conversation-session.d.ts +99 -0
  348. package/dist/services/conversation-session.d.ts.map +1 -0
  349. package/dist/services/conversation-session.js +978 -0
  350. package/dist/services/conversation-session.js.map +1 -0
  351. package/dist/services/conversation-store.d.ts +24 -0
  352. package/dist/services/conversation-store.d.ts.map +1 -0
  353. package/dist/services/conversation-store.js +216 -0
  354. package/dist/services/conversation-store.js.map +1 -0
  355. package/dist/services/conversation-store.test.d.ts +2 -0
  356. package/dist/services/conversation-store.test.d.ts.map +1 -0
  357. package/dist/services/conversation-store.test.js +167 -0
  358. package/dist/services/conversation-store.test.js.map +1 -0
  359. package/dist/services/execution-context.d.ts +10 -0
  360. package/dist/services/execution-context.d.ts.map +1 -0
  361. package/dist/services/execution-context.js +22 -0
  362. package/dist/services/execution-context.js.map +1 -0
  363. package/dist/services/execution-context.test.d.ts +2 -0
  364. package/dist/services/execution-context.test.d.ts.map +1 -0
  365. package/dist/services/execution-context.test.js +49 -0
  366. package/dist/services/execution-context.test.js.map +1 -0
  367. package/dist/services/file-service.d.ts +12 -0
  368. package/dist/services/file-service.d.ts.map +1 -0
  369. package/dist/services/file-service.js +90 -0
  370. package/dist/services/file-service.js.map +1 -0
  371. package/dist/services/history-service.d.ts +39 -0
  372. package/dist/services/history-service.d.ts.map +1 -0
  373. package/dist/services/history-service.js +152 -0
  374. package/dist/services/history-service.js.map +1 -0
  375. package/dist/services/logging-service.d.ts +75 -0
  376. package/dist/services/logging-service.d.ts.map +1 -0
  377. package/dist/services/logging-service.js +343 -0
  378. package/dist/services/logging-service.js.map +1 -0
  379. package/dist/services/model-service.d.ts +15 -0
  380. package/dist/services/model-service.d.ts.map +1 -0
  381. package/dist/services/model-service.js +46 -0
  382. package/dist/services/model-service.js.map +1 -0
  383. package/dist/services/model-service.test.d.ts +2 -0
  384. package/dist/services/model-service.test.d.ts.map +1 -0
  385. package/dist/services/model-service.test.js +128 -0
  386. package/dist/services/model-service.test.js.map +1 -0
  387. package/dist/services/service-interfaces.d.ts +33 -0
  388. package/dist/services/service-interfaces.d.ts.map +1 -0
  389. package/dist/services/service-interfaces.js +2 -0
  390. package/dist/services/service-interfaces.js.map +1 -0
  391. package/dist/services/settings-service.d.ts +316 -0
  392. package/dist/services/settings-service.d.ts.map +1 -0
  393. package/dist/services/settings-service.js +1128 -0
  394. package/dist/services/settings-service.js.map +1 -0
  395. package/dist/services/settings-service.mock.d.ts +20 -0
  396. package/dist/services/settings-service.mock.d.ts.map +1 -0
  397. package/dist/services/settings-service.mock.js +55 -0
  398. package/dist/services/settings-service.mock.js.map +1 -0
  399. package/dist/services/singleton-deprecation.test.d.ts +2 -0
  400. package/dist/services/singleton-deprecation.test.d.ts.map +1 -0
  401. package/dist/services/singleton-deprecation.test.js +59 -0
  402. package/dist/services/singleton-deprecation.test.js.map +1 -0
  403. package/dist/services/ssh-service.d.ts +32 -0
  404. package/dist/services/ssh-service.d.ts.map +1 -0
  405. package/dist/services/ssh-service.js +119 -0
  406. package/dist/services/ssh-service.js.map +1 -0
  407. package/dist/services/ssh-service.test.d.ts +2 -0
  408. package/dist/services/ssh-service.test.d.ts.map +1 -0
  409. package/dist/services/ssh-service.test.js +269 -0
  410. package/dist/services/ssh-service.test.js.map +1 -0
  411. package/dist/test-search-tool.d.ts +2 -0
  412. package/dist/test-search-tool.d.ts.map +1 -0
  413. package/dist/test-search-tool.js +36 -0
  414. package/dist/test-search-tool.js.map +1 -0
  415. package/dist/tools/apply-patch.d.ts +28 -0
  416. package/dist/tools/apply-patch.d.ts.map +1 -0
  417. package/dist/tools/apply-patch.js +399 -0
  418. package/dist/tools/apply-patch.js.map +1 -0
  419. package/dist/tools/apply-patch.test.d.ts +2 -0
  420. package/dist/tools/apply-patch.test.d.ts.map +1 -0
  421. package/dist/tools/apply-patch.test.js +155 -0
  422. package/dist/tools/apply-patch.test.js.map +1 -0
  423. package/dist/tools/ask-mentor.d.ts +11 -0
  424. package/dist/tools/ask-mentor.d.ts.map +1 -0
  425. package/dist/tools/ask-mentor.js +52 -0
  426. package/dist/tools/ask-mentor.js.map +1 -0
  427. package/dist/tools/ask-mentor.test.d.ts +2 -0
  428. package/dist/tools/ask-mentor.test.d.ts.map +1 -0
  429. package/dist/tools/ask-mentor.test.js +47 -0
  430. package/dist/tools/ask-mentor.test.js.map +1 -0
  431. package/dist/tools/bash.d.ts +10 -0
  432. package/dist/tools/bash.d.ts.map +1 -0
  433. package/dist/tools/bash.js +55 -0
  434. package/dist/tools/bash.js.map +1 -0
  435. package/dist/tools/find-files.d.ts +15 -0
  436. package/dist/tools/find-files.d.ts.map +1 -0
  437. package/dist/tools/find-files.js +179 -0
  438. package/dist/tools/find-files.js.map +1 -0
  439. package/dist/tools/find-files.test.d.ts +2 -0
  440. package/dist/tools/find-files.test.d.ts.map +1 -0
  441. package/dist/tools/find-files.test.js +131 -0
  442. package/dist/tools/find-files.test.js.map +1 -0
  443. package/dist/tools/format-helpers.d.ts +34 -0
  444. package/dist/tools/format-helpers.d.ts.map +1 -0
  445. package/dist/tools/format-helpers.js +131 -0
  446. package/dist/tools/format-helpers.js.map +1 -0
  447. package/dist/tools/grep.d.ts +16 -0
  448. package/dist/tools/grep.d.ts.map +1 -0
  449. package/dist/tools/grep.js +211 -0
  450. package/dist/tools/grep.js.map +1 -0
  451. package/dist/tools/read-file.d.ts +15 -0
  452. package/dist/tools/read-file.d.ts.map +1 -0
  453. package/dist/tools/read-file.js +114 -0
  454. package/dist/tools/read-file.js.map +1 -0
  455. package/dist/tools/read-file.test.d.ts +2 -0
  456. package/dist/tools/read-file.test.d.ts.map +1 -0
  457. package/dist/tools/read-file.test.js +122 -0
  458. package/dist/tools/read-file.test.js.map +1 -0
  459. package/dist/tools/search-replace.d.ts +19 -0
  460. package/dist/tools/search-replace.d.ts.map +1 -0
  461. package/dist/tools/search-replace.js +411 -0
  462. package/dist/tools/search-replace.js.map +1 -0
  463. package/dist/tools/search-replace.test.d.ts +2 -0
  464. package/dist/tools/search-replace.test.d.ts.map +1 -0
  465. package/dist/tools/search-replace.test.js +302 -0
  466. package/dist/tools/search-replace.test.js.map +1 -0
  467. package/dist/tools/search.d.ts +15 -0
  468. package/dist/tools/search.d.ts.map +1 -0
  469. package/dist/tools/search.js +143 -0
  470. package/dist/tools/search.js.map +1 -0
  471. package/dist/tools/shell.d.ts +19 -0
  472. package/dist/tools/shell.d.ts.map +1 -0
  473. package/dist/tools/shell.js +278 -0
  474. package/dist/tools/shell.js.map +1 -0
  475. package/dist/tools/tool-execution-context.d.ts +7 -0
  476. package/dist/tools/tool-execution-context.d.ts.map +1 -0
  477. package/dist/tools/tool-execution-context.js +7 -0
  478. package/dist/tools/tool-execution-context.js.map +1 -0
  479. package/dist/tools/types.d.ts +30 -0
  480. package/dist/tools/types.d.ts.map +1 -0
  481. package/dist/tools/types.js +2 -0
  482. package/dist/tools/types.js.map +1 -0
  483. package/dist/tools/utils.d.ts +12 -0
  484. package/dist/tools/utils.d.ts.map +1 -0
  485. package/dist/tools/utils.js +19 -0
  486. package/dist/tools/utils.js.map +1 -0
  487. package/dist/tools/web-search.d.ts +29 -0
  488. package/dist/tools/web-search.d.ts.map +1 -0
  489. package/dist/tools/web-search.js +106 -0
  490. package/dist/tools/web-search.js.map +1 -0
  491. package/dist/tools/web-search.test.d.ts +2 -0
  492. package/dist/tools/web-search.test.d.ts.map +1 -0
  493. package/dist/tools/web-search.test.js +176 -0
  494. package/dist/tools/web-search.test.js.map +1 -0
  495. package/dist/utils/command-logger.d.ts +11 -0
  496. package/dist/utils/command-logger.d.ts.map +1 -0
  497. package/dist/utils/command-logger.js +34 -0
  498. package/dist/utils/command-logger.js.map +1 -0
  499. package/dist/utils/command-safety/constants.d.ts +21 -0
  500. package/dist/utils/command-safety/constants.d.ts.map +1 -0
  501. package/dist/utils/command-safety/constants.js +245 -0
  502. package/dist/utils/command-safety/constants.js.map +1 -0
  503. package/dist/utils/command-safety/find-helpers.d.ts +15 -0
  504. package/dist/utils/command-safety/find-helpers.d.ts.map +1 -0
  505. package/dist/utils/command-safety/find-helpers.js +218 -0
  506. package/dist/utils/command-safety/find-helpers.js.map +1 -0
  507. package/dist/utils/command-safety/handlers/find-handler.d.ts +6 -0
  508. package/dist/utils/command-safety/handlers/find-handler.d.ts.map +1 -0
  509. package/dist/utils/command-safety/handlers/find-handler.js +113 -0
  510. package/dist/utils/command-safety/handlers/find-handler.js.map +1 -0
  511. package/dist/utils/command-safety/handlers/git-handler.d.ts +6 -0
  512. package/dist/utils/command-safety/handlers/git-handler.d.ts.map +1 -0
  513. package/dist/utils/command-safety/handlers/git-handler.js +68 -0
  514. package/dist/utils/command-safety/handlers/git-handler.js.map +1 -0
  515. package/dist/utils/command-safety/handlers/index.d.ts +13 -0
  516. package/dist/utils/command-safety/handlers/index.d.ts.map +1 -0
  517. package/dist/utils/command-safety/handlers/index.js +20 -0
  518. package/dist/utils/command-safety/handlers/index.js.map +1 -0
  519. package/dist/utils/command-safety/handlers/sed-handler.d.ts +6 -0
  520. package/dist/utils/command-safety/handlers/sed-handler.d.ts.map +1 -0
  521. package/dist/utils/command-safety/handlers/sed-handler.js +94 -0
  522. package/dist/utils/command-safety/handlers/sed-handler.js.map +1 -0
  523. package/dist/utils/command-safety/handlers/types.d.ts +36 -0
  524. package/dist/utils/command-safety/handlers/types.d.ts.map +1 -0
  525. package/dist/utils/command-safety/handlers/types.js +2 -0
  526. package/dist/utils/command-safety/handlers/types.js.map +1 -0
  527. package/dist/utils/command-safety/index.d.ts +14 -0
  528. package/dist/utils/command-safety/index.d.ts.map +1 -0
  529. package/dist/utils/command-safety/index.js +183 -0
  530. package/dist/utils/command-safety/index.js.map +1 -0
  531. package/dist/utils/command-safety/path-analysis.d.ts +4 -0
  532. package/dist/utils/command-safety/path-analysis.d.ts.map +1 -0
  533. package/dist/utils/command-safety/path-analysis.js +153 -0
  534. package/dist/utils/command-safety/path-analysis.js.map +1 -0
  535. package/dist/utils/command-safety/utils.d.ts +2 -0
  536. package/dist/utils/command-safety/utils.d.ts.map +1 -0
  537. package/dist/utils/command-safety/utils.js +22 -0
  538. package/dist/utils/command-safety/utils.js.map +1 -0
  539. package/dist/utils/command-safety.d.ts +21 -0
  540. package/dist/utils/command-safety.d.ts.map +1 -0
  541. package/dist/utils/command-safety.find.test.d.ts +2 -0
  542. package/dist/utils/command-safety.find.test.d.ts.map +1 -0
  543. package/dist/utils/command-safety.find.test.js +342 -0
  544. package/dist/utils/command-safety.find.test.js.map +1 -0
  545. package/dist/utils/command-safety.js +702 -0
  546. package/dist/utils/command-safety.js.map +1 -0
  547. package/dist/utils/command-safety.path.test.d.ts +2 -0
  548. package/dist/utils/command-safety.path.test.d.ts.map +1 -0
  549. package/dist/utils/command-safety.path.test.js +360 -0
  550. package/dist/utils/command-safety.path.test.js.map +1 -0
  551. package/dist/utils/diff.d.ts +2 -0
  552. package/dist/utils/diff.d.ts.map +1 -0
  553. package/dist/utils/diff.js +44 -0
  554. package/dist/utils/diff.js.map +1 -0
  555. package/dist/utils/diff.test.d.ts +2 -0
  556. package/dist/utils/diff.test.d.ts.map +1 -0
  557. package/dist/utils/diff.test.js +85 -0
  558. package/dist/utils/diff.test.js.map +1 -0
  559. package/dist/utils/error-helpers.d.ts +6 -0
  560. package/dist/utils/error-helpers.d.ts.map +1 -0
  561. package/dist/utils/error-helpers.js +46 -0
  562. package/dist/utils/error-helpers.js.map +1 -0
  563. package/dist/utils/error-helpers.test.d.ts +2 -0
  564. package/dist/utils/error-helpers.test.d.ts.map +1 -0
  565. package/dist/utils/error-helpers.test.js +152 -0
  566. package/dist/utils/error-helpers.test.js.map +1 -0
  567. package/dist/utils/execute-shell.d.ts +15 -0
  568. package/dist/utils/execute-shell.d.ts.map +1 -0
  569. package/dist/utils/execute-shell.js +34 -0
  570. package/dist/utils/execute-shell.js.map +1 -0
  571. package/dist/utils/execute-shell.test.d.ts +2 -0
  572. package/dist/utils/execute-shell.test.d.ts.map +1 -0
  573. package/dist/utils/execute-shell.test.js +20 -0
  574. package/dist/utils/execute-shell.test.js.map +1 -0
  575. package/dist/utils/extract-command-messages.d.ts +5 -0
  576. package/dist/utils/extract-command-messages.d.ts.map +1 -0
  577. package/dist/utils/extract-command-messages.js +140 -0
  578. package/dist/utils/extract-command-messages.js.map +1 -0
  579. package/dist/utils/extract-command-messages.repro.test.d.ts +2 -0
  580. package/dist/utils/extract-command-messages.repro.test.d.ts.map +1 -0
  581. package/dist/utils/extract-command-messages.repro.test.js +31 -0
  582. package/dist/utils/extract-command-messages.repro.test.js.map +1 -0
  583. package/dist/utils/extract-command-messages.test.js +57 -0
  584. package/dist/utils/message-buffer.d.ts +2 -0
  585. package/dist/utils/message-buffer.d.ts.map +1 -0
  586. package/dist/utils/message-buffer.js +15 -0
  587. package/dist/utils/message-buffer.js.map +1 -0
  588. package/dist/utils/message-buffer.test.d.ts +2 -0
  589. package/dist/utils/message-buffer.test.d.ts.map +1 -0
  590. package/dist/utils/message-buffer.test.js +17 -0
  591. package/dist/utils/message-buffer.test.js.map +1 -0
  592. package/dist/utils/output-trim.d.ts +31 -0
  593. package/dist/utils/output-trim.d.ts.map +1 -0
  594. package/dist/utils/output-trim.js +71 -0
  595. package/dist/utils/output-trim.js.map +1 -0
  596. package/dist/utils/provider-credentials.d.ts +10 -0
  597. package/dist/utils/provider-credentials.d.ts.map +1 -0
  598. package/dist/utils/provider-credentials.js +22 -0
  599. package/dist/utils/provider-credentials.js.map +1 -0
  600. package/dist/utils/settings-command.d.ts +13 -0
  601. package/dist/utils/settings-command.d.ts.map +1 -0
  602. package/dist/utils/settings-command.js +173 -0
  603. package/dist/utils/settings-command.js.map +1 -0
  604. package/dist/utils/ssh-config-parser.d.ts +21 -0
  605. package/dist/utils/ssh-config-parser.d.ts.map +1 -0
  606. package/dist/utils/ssh-config-parser.js +89 -0
  607. package/dist/utils/ssh-config-parser.js.map +1 -0
  608. package/dist/utils/ssh-config-parser.test.d.ts +2 -0
  609. package/dist/utils/ssh-config-parser.test.d.ts.map +1 -0
  610. package/dist/utils/ssh-config-parser.test.js +153 -0
  611. package/dist/utils/ssh-config-parser.test.js.map +1 -0
  612. package/dist/utils/streaming-updater.d.ts +7 -0
  613. package/dist/utils/streaming-updater.d.ts.map +1 -0
  614. package/dist/utils/streaming-updater.js +41 -0
  615. package/dist/utils/streaming-updater.js.map +1 -0
  616. package/dist/utils/throttle.d.ts +7 -0
  617. package/dist/utils/throttle.d.ts.map +1 -0
  618. package/dist/utils/throttle.js +49 -0
  619. package/dist/utils/throttle.js.map +1 -0
  620. package/package.json +108 -0
  621. package/readme.md +428 -0
@@ -0,0 +1,1128 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import envPaths from 'env-paths';
4
+ import { z } from 'zod';
5
+ import deepEqual from 'fast-deep-equal';
6
+ import { LoggingService } from './logging-service.js';
7
+ // Import providers to ensure they're registered before schema construction
8
+ import '../providers/index.js';
9
+ import { getAllProviders, getProvider, upsertProvider, } from '../providers/index.js';
10
+ import { getAllWebSearchProviders } from '../providers/web-search/index.js';
11
+ import { createOpenAICompatibleProviderDefinition } from '../providers/openai-compatible.provider.js';
12
+ const paths = envPaths('term2');
13
+ // Define schemas for validation
14
+ const AgentSettingsSchema = z.object({
15
+ model: z.string().min(1).default('gpt-5.1'),
16
+ // 'default' signals we should *not* explicitly pass a reasoningEffort
17
+ // to the API, allowing it to decide what to use.
18
+ reasoningEffort: z
19
+ .enum(['default', 'none', 'minimal', 'low', 'medium', 'high'])
20
+ .default('default'),
21
+ // Temperature controls randomness. We keep it optional so providers/models
22
+ // can use their own defaults when unset.
23
+ temperature: z.number().min(0).max(2).optional(),
24
+ maxTurns: z.number().int().positive().default(100),
25
+ retryAttempts: z.number().int().nonnegative().default(2),
26
+ // NOTE: We do NOT validate provider existence here because the provider
27
+ // registry can be extended at runtime from settings.json (custom providers).
28
+ // We validate/fallback after SettingsService loads and registers runtime providers.
29
+ provider: z
30
+ .string()
31
+ .min(1)
32
+ .default('openai')
33
+ .describe('Provider to use for the agent'),
34
+ openrouter: z
35
+ .object({
36
+ apiKey: z.string().optional(),
37
+ baseUrl: z.string().url().optional(),
38
+ referrer: z.string().optional(),
39
+ title: z.string().optional(),
40
+ })
41
+ .optional(),
42
+ mentorModel: z.string().optional().describe('Model to use as a mentor'),
43
+ mentorReasoningEffort: z
44
+ .enum(['default', 'none', 'minimal', 'low', 'medium', 'high'])
45
+ .default('default')
46
+ .describe('Reasoning effort for the mentor model'),
47
+ });
48
+ const ShellSettingsSchema = z.object({
49
+ timeout: z.number().int().positive().default(120000),
50
+ maxOutputLines: z.number().int().positive().default(1000),
51
+ maxOutputChars: z.number().int().positive().default(10000),
52
+ });
53
+ const UISettingsSchema = z.object({
54
+ historySize: z.number().int().positive().default(1000),
55
+ });
56
+ const LoggingSettingsSchema = z.object({
57
+ logLevel: z
58
+ .enum(['error', 'warn', 'info', 'security', 'debug'])
59
+ .default('info'),
60
+ disableLogging: z.boolean().optional().default(false),
61
+ debugLogging: z.boolean().optional().default(false),
62
+ suppressConsoleOutput: z.boolean().optional().default(true),
63
+ });
64
+ const EnvironmentSettingsSchema = z.object({
65
+ nodeEnv: z.string().optional(),
66
+ });
67
+ const AppSettingsSchema = z.object({
68
+ shellPath: z.string().optional(),
69
+ // Independent mode flags that can be enabled together and persist across sessions
70
+ // mentorMode: uses simplified mentor prompt and enables ask_mentor tool (if mentorModel configured)
71
+ // editMode: auto-approves apply_patch operations within cwd for faster file editing
72
+ // liteMode: minimal context for general terminal assistance (no codebase tools/prompts)
73
+ mentorMode: z.boolean().optional().default(false),
74
+ editMode: z.boolean().optional().default(false),
75
+ liteMode: z.boolean().optional().default(false),
76
+ });
77
+ const ToolsSettingsSchema = z.object({
78
+ logFileOperations: z.boolean().optional().default(true),
79
+ });
80
+ const DebugSettingsSchema = z.object({
81
+ debugBashTool: z.boolean().optional().default(false),
82
+ });
83
+ const SSHSettingsSchema = z.object({
84
+ enabled: z.boolean().default(false),
85
+ host: z.string().optional(),
86
+ port: z.number().int().positive().default(22),
87
+ username: z.string().optional(),
88
+ remoteDir: z.string().optional(),
89
+ });
90
+ const WebSearchSettingsSchema = z.object({
91
+ provider: z.string().optional(),
92
+ tavily: z.object({
93
+ apiKey: z.string().optional(),
94
+ }).optional(),
95
+ });
96
+ const CustomProviderSchema = z.object({
97
+ name: z.string().min(1),
98
+ baseUrl: z.string().url(),
99
+ apiKey: z.string().optional(),
100
+ });
101
+ /**
102
+ * Settings that are sensitive and should NEVER be saved to disk.
103
+ * These are only loaded from environment variables.
104
+ */
105
+ function getSensitiveSettingKeys() {
106
+ const keys = new Set(['app.shellPath']);
107
+ // Add provider-specific sensitive keys
108
+ for (const provider of getAllProviders()) {
109
+ if (provider.sensitiveSettingKeys) {
110
+ for (const key of provider.sensitiveSettingKeys) {
111
+ keys.add(key);
112
+ }
113
+ }
114
+ }
115
+ // Add web search provider-specific sensitive keys
116
+ for (const provider of getAllWebSearchProviders()) {
117
+ if (provider.sensitiveSettingKeys) {
118
+ for (const key of provider.sensitiveSettingKeys) {
119
+ keys.add(key);
120
+ }
121
+ }
122
+ }
123
+ return keys;
124
+ }
125
+ const SENSITIVE_SETTING_KEYS = getSensitiveSettingKeys();
126
+ const SettingsSchema = z.object({
127
+ providers: z.array(CustomProviderSchema).optional().default([]),
128
+ agent: AgentSettingsSchema.optional(),
129
+ shell: ShellSettingsSchema.optional(),
130
+ ui: UISettingsSchema.optional(),
131
+ logging: LoggingSettingsSchema.optional(),
132
+ environment: EnvironmentSettingsSchema.optional(),
133
+ app: AppSettingsSchema.optional(),
134
+ tools: ToolsSettingsSchema.optional(),
135
+ debug: DebugSettingsSchema.optional(),
136
+ ssh: SSHSettingsSchema.optional(),
137
+ webSearch: WebSearchSettingsSchema.optional(),
138
+ });
139
+ /**
140
+ * Centralized list of all setting keys for consistency across the app.
141
+ * Used by settings command UI and other components to avoid duplication.
142
+ */
143
+ export const SETTING_KEYS = {
144
+ AGENT_MODEL: 'agent.model',
145
+ AGENT_REASONING_EFFORT: 'agent.reasoningEffort',
146
+ AGENT_TEMPERATURE: 'agent.temperature',
147
+ AGENT_PROVIDER: 'agent.provider',
148
+ AGENT_MAX_TURNS: 'agent.maxTurns',
149
+ AGENT_RETRY_ATTEMPTS: 'agent.retryAttempts',
150
+ AGENT_OPENROUTER_API_KEY: 'agent.openrouter.apiKey', // Sensitive - env only
151
+ AGENT_OPENROUTER_BASE_URL: 'agent.openrouter.baseUrl', // Sensitive - env only
152
+ AGENT_OPENROUTER_REFERRER: 'agent.openrouter.referrer', // Sensitive - env only
153
+ AGENT_OPENROUTER_TITLE: 'agent.openrouter.title', // Sensitive - env only
154
+ AGENT_MENTOR_MODEL: 'agent.mentorModel',
155
+ AGENT_MENTOR_REASONING_EFFORT: 'agent.mentorReasoningEffort',
156
+ SHELL_TIMEOUT: 'shell.timeout',
157
+ SHELL_MAX_OUTPUT_LINES: 'shell.maxOutputLines',
158
+ SHELL_MAX_OUTPUT_CHARS: 'shell.maxOutputChars',
159
+ UI_HISTORY_SIZE: 'ui.historySize',
160
+ LOGGING_LOG_LEVEL: 'logging.logLevel',
161
+ LOGGING_DISABLE: 'logging.disableLogging',
162
+ LOGGING_DEBUG: 'logging.debugLogging',
163
+ LOGGING_SUPPRESS_CONSOLE: 'logging.suppressConsoleOutput',
164
+ ENV_NODE_ENV: 'environment.nodeEnv',
165
+ APP_SHELL_PATH: 'app.shellPath', // Sensitive - env only
166
+ APP_MENTOR_MODE: 'app.mentorMode',
167
+ APP_EDIT_MODE: 'app.editMode',
168
+ APP_LITE_MODE: 'app.liteMode',
169
+ TOOLS_LOG_FILE_OPS: 'tools.logFileOperations',
170
+ DEBUG_BASH_TOOL: 'debug.debugBashTool',
171
+ SSH_ENABLED: 'ssh.enabled',
172
+ SSH_HOST: 'ssh.host',
173
+ SSH_PORT: 'ssh.port',
174
+ SSH_USERNAME: 'ssh.username',
175
+ SSH_REMOTE_DIR: 'ssh.remoteDir',
176
+ WEB_SEARCH_PROVIDER: 'webSearch.provider',
177
+ WEB_SEARCH_TAVILY_API_KEY: 'webSearch.tavily.apiKey', // Sensitive - env only
178
+ };
179
+ // Define which settings are modifiable at runtime
180
+ const RUNTIME_MODIFIABLE_SETTINGS = new Set([
181
+ SETTING_KEYS.AGENT_MODEL,
182
+ SETTING_KEYS.AGENT_REASONING_EFFORT,
183
+ SETTING_KEYS.AGENT_TEMPERATURE,
184
+ SETTING_KEYS.AGENT_PROVIDER,
185
+ SETTING_KEYS.AGENT_MENTOR_MODEL,
186
+ SETTING_KEYS.AGENT_MENTOR_REASONING_EFFORT,
187
+ SETTING_KEYS.SHELL_TIMEOUT,
188
+ SETTING_KEYS.SHELL_MAX_OUTPUT_LINES,
189
+ SETTING_KEYS.SHELL_MAX_OUTPUT_CHARS,
190
+ SETTING_KEYS.LOGGING_LOG_LEVEL,
191
+ SETTING_KEYS.LOGGING_SUPPRESS_CONSOLE,
192
+ SETTING_KEYS.APP_MENTOR_MODE,
193
+ SETTING_KEYS.APP_EDIT_MODE,
194
+ SETTING_KEYS.APP_LITE_MODE,
195
+ ]);
196
+ // Note: Sensitive settings are NOT in RUNTIME_MODIFIABLE_SETTINGS because they
197
+ // cannot be modified at all - they can only be set via environment variables at startup.
198
+ // app.mentorMode and app.editMode are runtime-modifiable AND persisted to disk so they
199
+ // survive across sessions (user's mode preference is preserved).
200
+ // Some settings with default values are optional to persist
201
+ const OPTIONAL_DEFAULT_KEYS = new Set([]);
202
+ // Default settings
203
+ const DEFAULT_SETTINGS = {
204
+ providers: [],
205
+ agent: {
206
+ model: 'gpt-5.1',
207
+ reasoningEffort: 'default',
208
+ maxTurns: 100,
209
+ retryAttempts: 2,
210
+ provider: 'openai',
211
+ openrouter: {
212
+ // defaults empty; can be provided via env or config
213
+ // defaults empty; can be provided via env or config
214
+ },
215
+ mentorModel: undefined,
216
+ mentorReasoningEffort: 'default',
217
+ },
218
+ shell: {
219
+ timeout: 120000,
220
+ maxOutputLines: 1000,
221
+ maxOutputChars: 10000,
222
+ },
223
+ ui: {
224
+ historySize: 1000,
225
+ },
226
+ logging: {
227
+ logLevel: 'info',
228
+ disableLogging: false,
229
+ debugLogging: false,
230
+ suppressConsoleOutput: true,
231
+ },
232
+ environment: {
233
+ nodeEnv: undefined,
234
+ },
235
+ app: {
236
+ shellPath: undefined,
237
+ mentorMode: false,
238
+ editMode: false,
239
+ liteMode: false,
240
+ },
241
+ tools: {
242
+ logFileOperations: true,
243
+ },
244
+ debug: {
245
+ debugBashTool: false,
246
+ },
247
+ ssh: {
248
+ enabled: false,
249
+ port: 22,
250
+ },
251
+ webSearch: {
252
+ provider: 'tavily',
253
+ tavily: {},
254
+ },
255
+ };
256
+ /**
257
+ * Service for managing application settings.
258
+ * Follows singleton pattern and supports:
259
+ * - XDG-compliant storage
260
+ * - Hierarchical precedence (CLI > Env > Config > Defaults)
261
+ * - Zod validation with graceful degradation
262
+ * - Runtime-modifiable vs startup-only settings
263
+ * - Setting source tracking
264
+ */
265
+ export class SettingsService {
266
+ settings;
267
+ sources;
268
+ settingsDir;
269
+ disableLogging;
270
+ disableFilePersistence;
271
+ listeners = new Set();
272
+ loggingService;
273
+ // Detect if running in test environment
274
+ //
275
+ // We intentionally use a broad set of signals because different test runners
276
+ // set different environment variables. This prevents unit/integration tests
277
+ // from writing to disk by default (which can cause flaky tests and polluted
278
+ // developer machines/CI workspaces).
279
+ isTestEnvironment() {
280
+ return (process.env.NODE_ENV === 'test' ||
281
+ process.env.VITEST !== undefined ||
282
+ process.env.AVA_PATH !== undefined ||
283
+ process.env.JEST_WORKER_ID !== undefined ||
284
+ process.env.TERM2_TEST_MODE === 'true');
285
+ }
286
+ constructor(options) {
287
+ const { settingsDir = path.join(paths.log), disableLogging = false, disableFilePersistence, cli = {}, env = {}, loggingService, } = options ?? {};
288
+ const resolvedDisableLogging = disableLogging || parseBooleanEnv(process.env.DISABLE_LOGGING);
289
+ this.settingsDir = settingsDir;
290
+ this.disableLogging = resolvedDisableLogging;
291
+ this.sources = new Map();
292
+ // Use injected LoggingService or create a new one if not provided
293
+ this.loggingService =
294
+ loggingService ||
295
+ new LoggingService({
296
+ disableLogging: this.disableLogging,
297
+ });
298
+ // Disk persistence can be explicitly disabled (e.g., for tests), and is
299
+ // also automatically disabled when running under a known test runner.
300
+ this.disableFilePersistence =
301
+ disableFilePersistence ?? this.isTestEnvironment();
302
+ // Ensure settings directory exists
303
+ if (!fs.existsSync(this.settingsDir)) {
304
+ try {
305
+ fs.mkdirSync(this.settingsDir, { recursive: true });
306
+ }
307
+ catch (error) {
308
+ if (!this.disableLogging) {
309
+ this.loggingService.error('Failed to create settings directory', {
310
+ error: error instanceof Error
311
+ ? error.message
312
+ : String(error),
313
+ path: this.settingsDir,
314
+ });
315
+ }
316
+ }
317
+ }
318
+ // Load settings with precedence: CLI > Env > Config > Default
319
+ const settingsFilePath = path.join(this.settingsDir, 'settings.json');
320
+ const configFileExisted = fs.existsSync(settingsFilePath);
321
+ const { validated: fileConfig, raw: rawFileConfig } = this.loadFromFile();
322
+ this.settings = this.merge(DEFAULT_SETTINGS, fileConfig, env, cli);
323
+ this.trackSources(DEFAULT_SETTINGS, fileConfig, env, cli);
324
+ // Register any runtime-defined providers from settings.json so they appear
325
+ // in the model selection menu and can be selected as agent.provider.
326
+ this.registerRuntimeProviders();
327
+ // Validate selected provider and fall back if invalid (without rejecting the
328
+ // entire settings file).
329
+ this.validateSelectedProvider();
330
+ // Apply logging level from settings to the logging service so it respects settings
331
+ try {
332
+ this.loggingService.setLogLevel(this.settings.logging.logLevel);
333
+ this.loggingService.setSuppressConsoleOutput(this.settings.logging.suppressConsoleOutput);
334
+ }
335
+ catch (error) {
336
+ if (!this.disableLogging) {
337
+ this.loggingService.warn('Failed to apply logging level from settings', {
338
+ error: error instanceof Error
339
+ ? error.message
340
+ : String(error),
341
+ loggingLevel: this.settings.logging.logLevel,
342
+ });
343
+ }
344
+ }
345
+ if (!this.disableLogging) {
346
+ this.loggingService.info('SettingsService initialized', {
347
+ cliOverrides: Object.keys(this.flattenSettings(cli)).length > 0,
348
+ envOverrides: Object.keys(this.flattenSettings(env)).length > 0,
349
+ configOverrides: Object.keys(this.flattenSettings(fileConfig)).length > 0,
350
+ });
351
+ }
352
+ // Check if file config is missing any keys that exist in defaults
353
+ // Use raw file config (pre-Zod) to detect missing keys since Zod adds defaults
354
+ const shouldUpdateFile = configFileExisted &&
355
+ this.hasMissingKeys(rawFileConfig, DEFAULT_SETTINGS);
356
+ // If there was no config file on disk, persist the current merged settings so
357
+ // users get a settings.json created at startup (rather than waiting for a
358
+ // manual change). saveToFile is safe and handles errors/logging internally.
359
+ // Also update the file if new settings have been added since the file was created.
360
+ if (!configFileExisted) {
361
+ if (!this.disableFilePersistence) {
362
+ this.saveToFile();
363
+ if (!this.disableLogging) {
364
+ this.loggingService.info('Created settings file at startup', {
365
+ settingsFile: settingsFilePath,
366
+ });
367
+ }
368
+ }
369
+ }
370
+ else if (shouldUpdateFile) {
371
+ if (!this.disableFilePersistence) {
372
+ this.saveToFile();
373
+ if (!this.disableLogging) {
374
+ this.loggingService.info('Updated settings file with new default values', {
375
+ settingsFile: settingsFilePath,
376
+ });
377
+ }
378
+ }
379
+ }
380
+ }
381
+ registerRuntimeProviders() {
382
+ const configured = this.settings?.providers;
383
+ if (!Array.isArray(configured) || configured.length === 0)
384
+ return;
385
+ for (const p of configured) {
386
+ const providerId = p?.name;
387
+ const baseUrl = p?.baseUrl;
388
+ if (!providerId || !baseUrl)
389
+ continue;
390
+ const existing = getProvider(providerId);
391
+ if (existing && !existing.isRuntimeDefined) {
392
+ if (!this.disableLogging) {
393
+ this.loggingService.warn('Skipping custom provider because it conflicts with a built-in provider id', { providerId });
394
+ }
395
+ continue;
396
+ }
397
+ try {
398
+ upsertProvider(createOpenAICompatibleProviderDefinition({
399
+ name: String(providerId),
400
+ baseUrl: String(baseUrl),
401
+ apiKey: p?.apiKey
402
+ ? String(p.apiKey)
403
+ : undefined,
404
+ }));
405
+ }
406
+ catch (error) {
407
+ if (!this.disableLogging) {
408
+ this.loggingService.warn('Failed to register custom provider', {
409
+ providerId,
410
+ error: error instanceof Error
411
+ ? error.message
412
+ : String(error),
413
+ });
414
+ }
415
+ }
416
+ }
417
+ }
418
+ validateSelectedProvider() {
419
+ const current = this.settings?.agent?.provider || 'openai';
420
+ if (getProvider(current))
421
+ return;
422
+ if (!this.disableLogging) {
423
+ this.loggingService.warn('Configured agent.provider is not registered; falling back to openai', { provider: current });
424
+ }
425
+ this.settings.agent.provider = 'openai';
426
+ this.sources.set('agent.provider', 'default');
427
+ }
428
+ /**
429
+ * Get a setting value by dot-notation key (e.g., 'agent.model')
430
+ */
431
+ get(key) {
432
+ const keys = key.split('.');
433
+ let value = this.settings;
434
+ for (const k of keys) {
435
+ if (value && typeof value === 'object') {
436
+ value = value[k];
437
+ }
438
+ else {
439
+ return undefined;
440
+ }
441
+ }
442
+ return value;
443
+ }
444
+ /**
445
+ * Get the source of a setting
446
+ */
447
+ getSource(key) {
448
+ return this.sources.get(key) || 'default';
449
+ }
450
+ /**
451
+ * Check if a setting is runtime-modifiable
452
+ */
453
+ isRuntimeModifiable(key) {
454
+ return RUNTIME_MODIFIABLE_SETTINGS.has(key);
455
+ }
456
+ /**
457
+ * Check if a setting is sensitive and should not be saved to disk
458
+ */
459
+ isSensitive(key) {
460
+ return SENSITIVE_SETTING_KEYS.has(key);
461
+ }
462
+ /**
463
+ * Set a setting value (runtime modification)
464
+ * Only runtime-modifiable settings can be changed.
465
+ * Sensitive settings cannot be modified (must come from environment).
466
+ */
467
+ set(key, value) {
468
+ if (this.isSensitive(key)) {
469
+ throw new Error(`Cannot modify '${key}' - it is a sensitive setting that can only be configured via environment variables.`);
470
+ }
471
+ if (!this.isRuntimeModifiable(key)) {
472
+ throw new Error(`Cannot modify '${key}' at runtime. Requires restart.`);
473
+ }
474
+ const keys = key.split('.');
475
+ let obj = this.settings;
476
+ // Navigate to parent
477
+ for (let i = 0; i < keys.length - 1; i++) {
478
+ if (!obj[keys[i]]) {
479
+ obj[keys[i]] = {};
480
+ }
481
+ obj = obj[keys[i]];
482
+ }
483
+ // Set value
484
+ const lastKey = keys[keys.length - 1];
485
+ obj[lastKey] = value;
486
+ // Track source as 'cli' for runtime-set values
487
+ this.sources.set(key, 'cli');
488
+ // If we're changing the logging level, update the logging service runtime
489
+ if (key === 'logging.logLevel') {
490
+ try {
491
+ this.loggingService.setLogLevel(value);
492
+ }
493
+ catch (err) {
494
+ if (!this.disableLogging) {
495
+ this.loggingService.warn('Failed to update logging level at runtime', {
496
+ error: err instanceof Error
497
+ ? err.message
498
+ : String(err),
499
+ loggingLevel: value,
500
+ });
501
+ }
502
+ }
503
+ }
504
+ if (key === 'logging.suppressConsoleOutput') {
505
+ try {
506
+ this.loggingService.setSuppressConsoleOutput(Boolean(value));
507
+ }
508
+ catch (err) {
509
+ if (!this.disableLogging) {
510
+ this.loggingService.warn('Failed to update console output suppression at runtime', {
511
+ error: err instanceof Error
512
+ ? err.message
513
+ : String(err),
514
+ suppressConsoleOutput: value,
515
+ });
516
+ }
517
+ }
518
+ }
519
+ // Persist to file
520
+ if (!this.disableFilePersistence) {
521
+ this.saveToFile();
522
+ }
523
+ this.notifyChange(key);
524
+ }
525
+ /**
526
+ * Reset a setting to its default value.
527
+ * Sensitive settings cannot be reset as they should only come from env.
528
+ */
529
+ reset(key) {
530
+ if (key && this.isSensitive(key)) {
531
+ throw new Error(`Cannot reset '${key}' - it is a sensitive setting that can only be configured via environment variables.`);
532
+ }
533
+ if (key) {
534
+ // Reset specific setting
535
+ const keys = key.split('.');
536
+ let obj = this.settings;
537
+ // Navigate to parent
538
+ for (let i = 0; i < keys.length - 1; i++) {
539
+ if (!obj[keys[i]]) {
540
+ obj[keys[i]] = {};
541
+ }
542
+ obj = obj[keys[i]];
543
+ }
544
+ // Reset to default
545
+ const lastKey = keys[keys.length - 1];
546
+ const defaultKeys = key.split('.');
547
+ let defaultValue = DEFAULT_SETTINGS;
548
+ for (const k of defaultKeys) {
549
+ defaultValue = defaultValue[k];
550
+ }
551
+ obj[lastKey] = defaultValue;
552
+ this.sources.set(key, 'default');
553
+ }
554
+ else {
555
+ // Reset all settings
556
+ this.settings = JSON.parse(JSON.stringify(DEFAULT_SETTINGS));
557
+ this.sources.clear();
558
+ }
559
+ if (!this.disableFilePersistence) {
560
+ this.saveToFile();
561
+ }
562
+ this.notifyChange(key);
563
+ }
564
+ /**
565
+ * Subscribe to changes; returns an unsubscribe function.
566
+ */
567
+ onChange(listener) {
568
+ this.listeners.add(listener);
569
+ return () => {
570
+ this.listeners.delete(listener);
571
+ };
572
+ }
573
+ notifyChange(changedKey) {
574
+ for (const listener of this.listeners) {
575
+ try {
576
+ listener(changedKey);
577
+ }
578
+ catch (error) {
579
+ if (!this.disableLogging) {
580
+ this.loggingService.warn('Settings change listener threw', {
581
+ error: error instanceof Error
582
+ ? error.message
583
+ : String(error),
584
+ changedKey,
585
+ });
586
+ }
587
+ }
588
+ }
589
+ }
590
+ /**
591
+ * Get all settings with their sources
592
+ */
593
+ getAll() {
594
+ return {
595
+ agent: {
596
+ model: {
597
+ value: this.settings.agent.model,
598
+ source: this.getSource('agent.model'),
599
+ },
600
+ reasoningEffort: {
601
+ value: this.settings.agent.reasoningEffort,
602
+ source: this.getSource('agent.reasoningEffort'),
603
+ },
604
+ temperature: {
605
+ value: this.settings.agent.temperature,
606
+ source: this.getSource('agent.temperature'),
607
+ },
608
+ maxTurns: {
609
+ value: this.settings.agent.maxTurns,
610
+ source: this.getSource('agent.maxTurns'),
611
+ },
612
+ retryAttempts: {
613
+ value: this.settings.agent.retryAttempts,
614
+ source: this.getSource('agent.retryAttempts'),
615
+ },
616
+ provider: {
617
+ value: this.settings.agent.provider,
618
+ source: this.getSource('agent.provider'),
619
+ },
620
+ openrouter: {
621
+ value: this.settings.agent.openrouter,
622
+ source: this.getSource('agent.openrouter'),
623
+ },
624
+ mentorModel: {
625
+ value: this.settings.agent.mentorModel,
626
+ source: this.getSource('agent.mentorModel'),
627
+ },
628
+ mentorReasoningEffort: {
629
+ value: this.settings.agent.mentorReasoningEffort,
630
+ source: this.getSource('agent.mentorReasoningEffort'),
631
+ },
632
+ },
633
+ shell: {
634
+ timeout: {
635
+ value: this.settings.shell.timeout,
636
+ source: this.getSource('shell.timeout'),
637
+ },
638
+ maxOutputLines: {
639
+ value: this.settings.shell.maxOutputLines,
640
+ source: this.getSource('shell.maxOutputLines'),
641
+ },
642
+ maxOutputChars: {
643
+ value: this.settings.shell.maxOutputChars,
644
+ source: this.getSource('shell.maxOutputChars'),
645
+ },
646
+ },
647
+ ui: {
648
+ historySize: {
649
+ value: this.settings.ui.historySize,
650
+ source: this.getSource('ui.historySize'),
651
+ },
652
+ },
653
+ logging: {
654
+ logLevel: {
655
+ value: this.settings.logging.logLevel,
656
+ source: this.getSource('logging.logLevel'),
657
+ },
658
+ disableLogging: {
659
+ value: this.settings.logging.disableLogging,
660
+ source: this.getSource('logging.disableLogging'),
661
+ },
662
+ debugLogging: {
663
+ value: this.settings.logging.debugLogging,
664
+ source: this.getSource('logging.debugLogging'),
665
+ },
666
+ suppressConsoleOutput: {
667
+ value: this.settings.logging.suppressConsoleOutput,
668
+ source: this.getSource('logging.suppressConsoleOutput'),
669
+ },
670
+ },
671
+ environment: {
672
+ nodeEnv: {
673
+ value: this.settings.environment.nodeEnv,
674
+ source: this.getSource('environment.nodeEnv'),
675
+ },
676
+ },
677
+ app: {
678
+ shellPath: {
679
+ value: this.settings.app.shellPath,
680
+ source: this.getSource('app.shellPath'),
681
+ },
682
+ mentorMode: {
683
+ value: this.settings.app.mentorMode,
684
+ source: this.getSource('app.mentorMode'),
685
+ },
686
+ editMode: {
687
+ value: this.settings.app.editMode,
688
+ source: this.getSource('app.editMode'),
689
+ },
690
+ liteMode: {
691
+ value: this.settings.app.liteMode,
692
+ source: this.getSource('app.liteMode'),
693
+ },
694
+ },
695
+ tools: {
696
+ logFileOperations: {
697
+ value: this.settings.tools.logFileOperations,
698
+ source: this.getSource('tools.logFileOperations'),
699
+ },
700
+ },
701
+ debug: {
702
+ debugBashTool: {
703
+ value: this.settings.debug.debugBashTool,
704
+ source: this.getSource('debug.debugBashTool'),
705
+ },
706
+ },
707
+ ssh: {
708
+ enabled: {
709
+ value: this.settings.ssh.enabled,
710
+ source: this.getSource('ssh.enabled'),
711
+ },
712
+ host: {
713
+ value: this.settings.ssh.host,
714
+ source: this.getSource('ssh.host'),
715
+ },
716
+ port: {
717
+ value: this.settings.ssh.port,
718
+ source: this.getSource('ssh.port'),
719
+ },
720
+ username: {
721
+ value: this.settings.ssh.username,
722
+ source: this.getSource('ssh.username'),
723
+ },
724
+ remoteDir: {
725
+ value: this.settings.ssh.remoteDir,
726
+ source: this.getSource('ssh.remoteDir'),
727
+ },
728
+ },
729
+ webSearch: {
730
+ provider: {
731
+ value: this.settings.webSearch?.provider,
732
+ source: this.getSource('webSearch.provider'),
733
+ },
734
+ tavily: {
735
+ value: this.settings.webSearch?.tavily,
736
+ source: this.getSource('webSearch.tavily'),
737
+ },
738
+ },
739
+ };
740
+ }
741
+ /**
742
+ * Load settings from file
743
+ * Returns both raw (pre-Zod) and validated data
744
+ */
745
+ loadFromFile() {
746
+ try {
747
+ const settingsFile = path.join(this.settingsDir, 'settings.json');
748
+ if (!fs.existsSync(settingsFile)) {
749
+ return { validated: {}, raw: {} };
750
+ }
751
+ const content = fs.readFileSync(settingsFile, 'utf-8');
752
+ const parsed = JSON.parse(content);
753
+ // Validate and parse with Zod
754
+ const validated = SettingsSchema.safeParse(parsed);
755
+ if (!validated.success) {
756
+ if (!this.disableLogging) {
757
+ this.loggingService.warn('Settings file contains invalid values', {
758
+ errors: validated.error.issues.map(issue => ({
759
+ path: issue.path.join('.'),
760
+ message: issue.message,
761
+ })),
762
+ });
763
+ }
764
+ // Return empty object to trigger defaults
765
+ return { validated: {}, raw: parsed };
766
+ }
767
+ return { validated: validated.data, raw: parsed };
768
+ }
769
+ catch (error) {
770
+ if (!this.disableLogging) {
771
+ this.loggingService.error('Failed to load settings file', {
772
+ error: error instanceof Error ? error.message : String(error),
773
+ settingsFile: path.join(this.settingsDir, 'settings.json'),
774
+ });
775
+ }
776
+ return { validated: {}, raw: {} };
777
+ }
778
+ }
779
+ /**
780
+ * Save settings to file, excluding sensitive values
781
+ */
782
+ saveToFile() {
783
+ if (this.disableFilePersistence) {
784
+ return;
785
+ }
786
+ try {
787
+ const settingsFile = path.join(this.settingsDir, 'settings.json');
788
+ // Ensure directory exists
789
+ if (!fs.existsSync(this.settingsDir)) {
790
+ fs.mkdirSync(this.settingsDir, { recursive: true });
791
+ }
792
+ // Filter out sensitive settings before saving to disk
793
+ const settingsToSave = this.stripSensitiveSettings(this.settings);
794
+ const newContent = JSON.stringify(settingsToSave, null, 2);
795
+ // Only write if file doesn't exist or content has changed
796
+ // Compare parsed objects rather than string content to avoid false positives
797
+ // from formatting differences
798
+ if (fs.existsSync(settingsFile)) {
799
+ try {
800
+ const existingContent = fs.readFileSync(settingsFile, 'utf-8');
801
+ const existingParsed = JSON.parse(existingContent);
802
+ // Deep equality check that ignores formatting and key order
803
+ // Uses fast-deep-equal library for robust comparison
804
+ if (deepEqual(existingParsed, settingsToSave)) {
805
+ return; // No changes, don't write
806
+ }
807
+ }
808
+ catch (parseError) {
809
+ // If we can't parse the existing file, write the new content anyway
810
+ // This handles corrupted files gracefully
811
+ }
812
+ }
813
+ fs.writeFileSync(settingsFile, newContent, 'utf-8');
814
+ }
815
+ catch (error) {
816
+ if (!this.disableLogging) {
817
+ this.loggingService.error('Failed to save settings file', {
818
+ error: error instanceof Error ? error.message : String(error),
819
+ settingsFile: path.join(this.settingsDir, 'settings.json'),
820
+ });
821
+ }
822
+ }
823
+ }
824
+ /**
825
+ * Remove sensitive settings that should never be persisted to disk
826
+ */
827
+ stripSensitiveSettings(settings) {
828
+ const cleaned = JSON.parse(JSON.stringify(settings));
829
+ // Remove sensitive openrouter fields (keep non-secret config)
830
+ if (cleaned.agent?.openrouter) {
831
+ delete cleaned.agent.openrouter.apiKey;
832
+ delete cleaned.agent.openrouter.baseUrl;
833
+ delete cleaned.agent.openrouter.referrer;
834
+ delete cleaned.agent.openrouter.title;
835
+ // Only keep model if it's set (it's not sensitive)
836
+ if (Object.keys(cleaned.agent.openrouter).length === 0) {
837
+ delete cleaned.agent.openrouter;
838
+ }
839
+ }
840
+ // Remove sensitive app settings
841
+ if (cleaned.app) {
842
+ delete cleaned.app.shellPath;
843
+ // mentorMode and editMode are persisted so they survive across sessions
844
+ }
845
+ return cleaned;
846
+ }
847
+ /**
848
+ * Check if target object is missing any keys that exist in source
849
+ */
850
+ hasMissingKeys(target, source, prefix = '') {
851
+ for (const key in source) {
852
+ if (!source.hasOwnProperty(key))
853
+ continue;
854
+ const pathKey = prefix ? `${prefix}.${key}` : key;
855
+ const sourceValue = source[key];
856
+ if (!(key in target)) {
857
+ // Skip optional default keys when deciding whether to rewrite file
858
+ if (OPTIONAL_DEFAULT_KEYS.has(pathKey)) {
859
+ continue;
860
+ }
861
+ // If the default value is undefined, treat it as optional for persistence
862
+ if (typeof sourceValue === 'undefined') {
863
+ continue;
864
+ }
865
+ return true;
866
+ }
867
+ const targetValue = target[key];
868
+ // Recursively check nested objects
869
+ if (sourceValue &&
870
+ typeof sourceValue === 'object' &&
871
+ !Array.isArray(sourceValue) &&
872
+ targetValue &&
873
+ typeof targetValue === 'object' &&
874
+ !Array.isArray(targetValue)) {
875
+ if (this.hasMissingKeys(targetValue, sourceValue, pathKey)) {
876
+ return true;
877
+ }
878
+ }
879
+ }
880
+ return false;
881
+ }
882
+ /**
883
+ * Flatten nested object to dot notation
884
+ */
885
+ flattenSettings(obj, prefix = '') {
886
+ const result = {};
887
+ for (const key in obj) {
888
+ if (!obj.hasOwnProperty(key))
889
+ continue;
890
+ const value = obj[key];
891
+ const newKey = prefix ? `${prefix}.${key}` : key;
892
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
893
+ Object.assign(result, this.flattenSettings(value, newKey));
894
+ }
895
+ else {
896
+ result[newKey] = value;
897
+ }
898
+ }
899
+ return result;
900
+ }
901
+ /**
902
+ * Merge multiple settings sources with proper precedence
903
+ */
904
+ merge(defaults, fileConfig, env, cli) {
905
+ // Deep merge starting with defaults
906
+ const result = JSON.parse(JSON.stringify(defaults));
907
+ // Merge file config
908
+ this.deepMerge(result, fileConfig);
909
+ // Merge env
910
+ this.deepMerge(result, env);
911
+ // Merge cli (highest priority)
912
+ this.deepMerge(result, cli);
913
+ // Ensure all required fields are present
914
+ const merged = {
915
+ providers: result.providers ||
916
+ JSON.parse(JSON.stringify(defaults.providers)),
917
+ agent: result.agent || JSON.parse(JSON.stringify(defaults.agent)),
918
+ shell: result.shell || JSON.parse(JSON.stringify(defaults.shell)),
919
+ ui: result.ui || JSON.parse(JSON.stringify(defaults.ui)),
920
+ logging: result.logging || JSON.parse(JSON.stringify(defaults.logging)),
921
+ environment: result.environment ||
922
+ JSON.parse(JSON.stringify(defaults.environment)),
923
+ app: result.app || JSON.parse(JSON.stringify(defaults.app)),
924
+ tools: result.tools || JSON.parse(JSON.stringify(defaults.tools)),
925
+ debug: result.debug || JSON.parse(JSON.stringify(defaults.debug)),
926
+ ssh: result.ssh || JSON.parse(JSON.stringify(defaults.ssh)),
927
+ webSearch: result.webSearch ||
928
+ JSON.parse(JSON.stringify(defaults.webSearch)),
929
+ };
930
+ // Validate final result
931
+ const validated = SettingsSchema.safeParse(merged);
932
+ if (validated.success) {
933
+ // Ensure we return a complete SettingsData object
934
+ return {
935
+ providers: merged.providers,
936
+ agent: merged.agent,
937
+ shell: merged.shell,
938
+ ui: merged.ui,
939
+ logging: merged.logging,
940
+ environment: merged.environment,
941
+ app: merged.app,
942
+ tools: merged.tools,
943
+ debug: merged.debug,
944
+ ssh: merged.ssh,
945
+ webSearch: merged.webSearch,
946
+ };
947
+ }
948
+ // If validation fails, return defaults
949
+ if (!this.disableLogging) {
950
+ this.loggingService.warn('Final merged settings failed validation, using defaults', {
951
+ errors: validated.error.issues.map(issue => ({
952
+ path: issue.path.join('.'),
953
+ message: issue.message,
954
+ })),
955
+ });
956
+ }
957
+ return defaults;
958
+ }
959
+ /**
960
+ * Deep merge source into target
961
+ */
962
+ deepMerge(target, source) {
963
+ for (const key in source) {
964
+ if (!source.hasOwnProperty(key))
965
+ continue;
966
+ const sourceValue = source[key];
967
+ if (sourceValue &&
968
+ typeof sourceValue === 'object' &&
969
+ !Array.isArray(sourceValue)) {
970
+ if (!target[key] || typeof target[key] !== 'object') {
971
+ target[key] = {};
972
+ }
973
+ this.deepMerge(target[key], sourceValue);
974
+ }
975
+ else {
976
+ target[key] = sourceValue;
977
+ }
978
+ }
979
+ }
980
+ /**
981
+ * Track the source of each setting
982
+ */
983
+ trackSources(defaults, fileConfig, env, cli) {
984
+ const flatDefaults = this.flattenSettings(defaults);
985
+ const flatFileConfig = this.flattenSettings(fileConfig);
986
+ const flatEnv = this.flattenSettings(env);
987
+ const flatCli = this.flattenSettings(cli);
988
+ // For each possible setting key, determine its source
989
+ for (const key in flatDefaults) {
990
+ if (flatCli.hasOwnProperty(key)) {
991
+ this.sources.set(key, 'cli');
992
+ }
993
+ else if (flatEnv.hasOwnProperty(key)) {
994
+ this.sources.set(key, 'env');
995
+ }
996
+ else if (flatFileConfig.hasOwnProperty(key)) {
997
+ this.sources.set(key, 'config');
998
+ }
999
+ else {
1000
+ this.sources.set(key, 'default');
1001
+ }
1002
+ }
1003
+ }
1004
+ }
1005
+ /**
1006
+ * Build environment-derived overrides from process.env
1007
+ * Exported for use in CLI initialization
1008
+ */
1009
+ export function buildEnvOverrides() {
1010
+ const env = (typeof process !== 'undefined' ? process.env : {});
1011
+ const openrouter = {};
1012
+ if (env.OPENROUTER_API_KEY)
1013
+ openrouter.apiKey = env.OPENROUTER_API_KEY;
1014
+ if (env.OPENROUTER_MODEL)
1015
+ openrouter.model = env.OPENROUTER_MODEL;
1016
+ if (env.OPENROUTER_BASE_URL)
1017
+ openrouter.baseUrl = env.OPENROUTER_BASE_URL;
1018
+ if (env.OPENROUTER_REFERRER)
1019
+ openrouter.referrer = env.OPENROUTER_REFERRER;
1020
+ if (env.OPENROUTER_TITLE)
1021
+ openrouter.title = env.OPENROUTER_TITLE;
1022
+ const logging = {};
1023
+ if (env.LOG_LEVEL)
1024
+ logging.logLevel = env.LOG_LEVEL;
1025
+ if (env.DISABLE_LOGGING !== undefined)
1026
+ logging.disableLogging = String(env.DISABLE_LOGGING) === 'true';
1027
+ if (env.DEBUG_LOGGING !== undefined)
1028
+ logging.debugLogging = true;
1029
+ const environment = {
1030
+ nodeEnv: env.NODE_ENV,
1031
+ };
1032
+ const app = {
1033
+ shellPath: env.SHELL || env.COMSPEC,
1034
+ };
1035
+ const tools = {};
1036
+ if (env.LOG_FILE_OPERATIONS !== undefined)
1037
+ tools.logFileOperations = String(env.LOG_FILE_OPERATIONS) !== 'false';
1038
+ const debug = {};
1039
+ if (env.DEBUG_BASH_TOOL !== undefined)
1040
+ debug.debugBashTool = true;
1041
+ const webSearch = {};
1042
+ if (env.TAVILY_API_KEY) {
1043
+ webSearch.tavily = { apiKey: env.TAVILY_API_KEY };
1044
+ }
1045
+ if (env.WEB_SEARCH_PROVIDER) {
1046
+ webSearch.provider = env.WEB_SEARCH_PROVIDER;
1047
+ }
1048
+ const agent = { openrouter };
1049
+ return {
1050
+ agent,
1051
+ logging,
1052
+ environment,
1053
+ app,
1054
+ tools,
1055
+ debug,
1056
+ webSearch,
1057
+ };
1058
+ }
1059
+ const parseBooleanEnv = (value) => {
1060
+ if (typeof value !== 'string') {
1061
+ return false;
1062
+ }
1063
+ const normalized = value.trim().toLowerCase();
1064
+ return normalized === '1' || normalized === 'true' || normalized === 'yes';
1065
+ };
1066
+ const isTestEnvironment = () => {
1067
+ return (process.env.NODE_ENV === 'test' ||
1068
+ process.env.VITEST !== undefined ||
1069
+ process.env.AVA_PATH !== undefined ||
1070
+ process.env.JEST_WORKER_ID !== undefined ||
1071
+ process.env.TERM2_TEST_MODE === 'true');
1072
+ };
1073
+ /**
1074
+ * @deprecated DO NOT USE - Singleton pattern is deprecated
1075
+ *
1076
+ * This singleton is deprecated and should not be used in application code.
1077
+ * Instead, pass the SettingsService instance via dependency injection:
1078
+ *
1079
+ * - In App component: Accept as a prop from cli.tsx
1080
+ * - In services/tools: Accept via constructor deps parameter
1081
+ * - In hooks: Use a context provider or accept as parameter
1082
+ *
1083
+ * This export now throws an error when accessed to catch deprecated usage.
1084
+ * It's only allowed in test files for backwards compatibility.
1085
+ */
1086
+ const _settingsServiceInstance = new SettingsService({
1087
+ env: buildEnvOverrides(),
1088
+ loggingService: new LoggingService({
1089
+ disableLogging: parseBooleanEnv(process.env.DISABLE_LOGGING) ||
1090
+ isTestEnvironment(),
1091
+ debugLogging: parseBooleanEnv(process.env.DEBUG_LOGGING),
1092
+ }),
1093
+ });
1094
+ export const settingsService = new Proxy(_settingsServiceInstance, {
1095
+ get(target, prop) {
1096
+ // Allow access in test environment for backwards compatibility
1097
+ if (isTestEnvironment()) {
1098
+ const value = target[prop];
1099
+ // Bind methods to the original target to preserve 'this' context
1100
+ if (typeof value === 'function') {
1101
+ return value.bind(target);
1102
+ }
1103
+ return value;
1104
+ }
1105
+ // Get the caller's stack trace to show where the deprecated usage is
1106
+ const stack = new Error().stack || '';
1107
+ const callerLine = stack.split('\n')[2] || 'unknown location';
1108
+ throw new Error(`DEPRECATED: Direct use of settingsService singleton is not allowed.\n` +
1109
+ `Called from: ${callerLine.trim()}\n\n` +
1110
+ `Instead, pass SettingsService via dependency injection:\n` +
1111
+ ` - In App component: Accept as prop from cli.tsx\n` +
1112
+ ` - In services/tools: Accept via 'deps' constructor parameter\n` +
1113
+ ` - In hooks: Accept as parameter or use a context provider\n\n` +
1114
+ `See source/app.tsx for an example of proper dependency injection.`);
1115
+ },
1116
+ });
1117
+ /**
1118
+ * Publicly exported list of sensitive settings for UI/CLI components to use.
1119
+ * These settings should only be configured via environment variables.
1120
+ */
1121
+ export const SENSITIVE_SETTINGS = {
1122
+ AGENT_OPENROUTER_API_KEY: 'agent.openrouter.apiKey',
1123
+ AGENT_OPENROUTER_BASE_URL: 'agent.openrouter.baseUrl',
1124
+ AGENT_OPENROUTER_REFERRER: 'agent.openrouter.referrer',
1125
+ AGENT_OPENROUTER_TITLE: 'agent.openrouter.title',
1126
+ APP_SHELL_PATH: 'app.shellPath',
1127
+ };
1128
+ //# sourceMappingURL=settings-service.js.map