fss-link 1.0.40 → 1.0.45

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 (428) hide show
  1. package/dist/commands/mcp/add.test.ts +122 -0
  2. package/dist/commands/mcp/add.ts +222 -0
  3. package/dist/commands/mcp/list.test.ts +154 -0
  4. package/dist/commands/mcp/list.ts +139 -0
  5. package/dist/commands/mcp/remove.test.ts +69 -0
  6. package/dist/commands/mcp/remove.ts +60 -0
  7. package/dist/commands/mcp.test.ts +55 -0
  8. package/dist/commands/mcp.ts +27 -0
  9. package/dist/config/apiValidation.test.ts +118 -0
  10. package/dist/config/auth.test.ts +79 -0
  11. package/dist/config/auth.ts +100 -0
  12. package/dist/config/config.integration.test.ts +407 -0
  13. package/dist/config/config.test.ts +1952 -0
  14. package/dist/config/config.ts +690 -0
  15. package/dist/config/database.test.ts +96 -0
  16. package/dist/config/database.ts +752 -0
  17. package/dist/config/extension.test.ts +236 -0
  18. package/dist/config/extension.ts +180 -0
  19. package/dist/config/keyBindings.test.ts +62 -0
  20. package/dist/config/keyBindings.ts +184 -0
  21. package/dist/config/modelManager.ts +275 -0
  22. package/dist/config/providerManager.ts +244 -0
  23. package/dist/config/providerPersistence.test.ts +377 -0
  24. package/dist/config/providerPersistence.ts +105 -0
  25. package/dist/config/sandboxConfig.ts +107 -0
  26. package/dist/config/settings.test.ts +1424 -0
  27. package/dist/config/settings.ts +517 -0
  28. package/dist/config/settingsSchema.test.ts +252 -0
  29. package/dist/config/settingsSchema.ts +728 -0
  30. package/dist/config/trustedFolders.test.ts +208 -0
  31. package/dist/config/trustedFolders.ts +167 -0
  32. package/dist/gemini.test.tsx +252 -0
  33. package/dist/gemini.tsx +357 -0
  34. package/dist/generated/git-commit.ts +10 -0
  35. package/dist/index.ts +21 -0
  36. package/dist/nonInteractiveCli.test.ts +276 -0
  37. package/dist/nonInteractiveCli.ts +143 -0
  38. package/dist/package.json +87 -87
  39. package/dist/patches/is-in-ci.ts +17 -0
  40. package/dist/services/BuiltinCommandLoader.test.ts +127 -0
  41. package/dist/services/BuiltinCommandLoader.ts +95 -0
  42. package/dist/services/CommandService.test.ts +352 -0
  43. package/dist/services/CommandService.ts +103 -0
  44. package/dist/services/FileCommandLoader.test.ts +1002 -0
  45. package/dist/services/FileCommandLoader.ts +289 -0
  46. package/dist/services/McpPromptLoader.ts +231 -0
  47. package/dist/services/SearchEngineConfigProvider.ts +100 -0
  48. package/dist/services/prompt-processors/argumentProcessor.test.ts +41 -0
  49. package/dist/services/prompt-processors/argumentProcessor.ts +23 -0
  50. package/dist/services/prompt-processors/shellProcessor.test.ts +709 -0
  51. package/dist/services/prompt-processors/shellProcessor.ts +248 -0
  52. package/dist/services/prompt-processors/types.ts +44 -0
  53. package/dist/services/types.ts +24 -0
  54. package/dist/src/config/apiValidation.test.d.ts +6 -0
  55. package/dist/src/config/apiValidation.test.js +99 -0
  56. package/dist/src/config/apiValidation.test.js.map +1 -0
  57. package/dist/src/config/database.d.ts +32 -0
  58. package/dist/src/config/database.js +281 -2
  59. package/dist/src/config/database.js.map +1 -1
  60. package/dist/src/config/database.test.d.ts +6 -0
  61. package/dist/src/config/database.test.js +80 -0
  62. package/dist/src/config/database.test.js.map +1 -0
  63. package/dist/src/config/providerManager.d.ts +74 -0
  64. package/dist/src/config/providerManager.js +203 -0
  65. package/dist/src/config/providerManager.js.map +1 -0
  66. package/dist/src/config/providerPersistence.d.ts +75 -0
  67. package/dist/src/config/providerPersistence.js +55 -0
  68. package/dist/src/config/providerPersistence.js.map +1 -0
  69. package/dist/src/config/providerPersistence.test.d.ts +6 -0
  70. package/dist/src/config/providerPersistence.test.js +283 -0
  71. package/dist/src/config/providerPersistence.test.js.map +1 -0
  72. package/dist/src/config/settingsSchema.d.ts +9 -0
  73. package/dist/src/config/settingsSchema.js +9 -0
  74. package/dist/src/config/settingsSchema.js.map +1 -1
  75. package/dist/src/generated/git-commit.d.ts +1 -1
  76. package/dist/src/generated/git-commit.js +1 -1
  77. package/dist/src/services/BuiltinCommandLoader.js +2 -0
  78. package/dist/src/services/BuiltinCommandLoader.js.map +1 -1
  79. package/dist/src/ui/App.js +14 -2
  80. package/dist/src/ui/App.js.map +1 -1
  81. package/dist/src/ui/commands/contextCommand.d.ts +7 -0
  82. package/dist/src/ui/commands/contextCommand.js +115 -0
  83. package/dist/src/ui/commands/contextCommand.js.map +1 -0
  84. package/dist/src/ui/components/ContextUsageDisplay.d.ts +3 -1
  85. package/dist/src/ui/components/ContextUsageDisplay.js +43 -3
  86. package/dist/src/ui/components/ContextUsageDisplay.js.map +1 -1
  87. package/dist/src/ui/components/Footer.d.ts +1 -0
  88. package/dist/src/ui/components/Footer.js +2 -2
  89. package/dist/src/ui/components/Footer.js.map +1 -1
  90. package/dist/src/ui/components/GeminiKeyDialog.d.ts +11 -0
  91. package/dist/src/ui/components/GeminiKeyDialog.js +156 -0
  92. package/dist/src/ui/components/GeminiKeyDialog.js.map +1 -0
  93. package/dist/src/ui/components/OpenAIEndpointDialog.d.ts +19 -0
  94. package/dist/src/ui/components/OpenAIEndpointDialog.js +163 -0
  95. package/dist/src/ui/components/OpenAIEndpointDialog.js.map +1 -0
  96. package/dist/src/ui/components/WelcomeBackDialog.d.ts +36 -0
  97. package/dist/src/ui/components/WelcomeBackDialog.js +109 -0
  98. package/dist/src/ui/components/WelcomeBackDialog.js.map +1 -0
  99. package/dist/src/ui/hooks/useWelcomeBack.d.ts +52 -0
  100. package/dist/src/ui/hooks/useWelcomeBack.js +214 -0
  101. package/dist/src/ui/hooks/useWelcomeBack.js.map +1 -0
  102. package/dist/src/zed-integration/schema.d.ts +1516 -1516
  103. package/dist/test-setup.ts +12 -0
  104. package/dist/test-utils/customMatchers.ts +65 -0
  105. package/dist/test-utils/mockCommandContext.test.ts +62 -0
  106. package/dist/test-utils/mockCommandContext.ts +105 -0
  107. package/dist/test-utils/render.tsx +18 -0
  108. package/dist/tsconfig.tsbuildinfo +1 -1
  109. package/dist/ui/App.test.tsx +2181 -0
  110. package/dist/ui/App.tsx +1344 -0
  111. package/dist/ui/IdeIntegrationNudge.tsx +98 -0
  112. package/dist/ui/__snapshots__/App.test.tsx.snap +124 -0
  113. package/dist/ui/colors.ts +56 -0
  114. package/dist/ui/commands/aboutCommand.test.ts +153 -0
  115. package/dist/ui/commands/aboutCommand.ts +49 -0
  116. package/dist/ui/commands/authCommand.test.ts +36 -0
  117. package/dist/ui/commands/authCommand.ts +17 -0
  118. package/dist/ui/commands/bugCommand.test.ts +114 -0
  119. package/dist/ui/commands/bugCommand.ts +92 -0
  120. package/dist/ui/commands/chatCommand.test.ts +414 -0
  121. package/dist/ui/commands/chatCommand.ts +280 -0
  122. package/dist/ui/commands/clearCommand.test.ts +100 -0
  123. package/dist/ui/commands/clearCommand.ts +29 -0
  124. package/dist/ui/commands/compressCommand.test.ts +129 -0
  125. package/dist/ui/commands/compressCommand.ts +78 -0
  126. package/dist/ui/commands/contextCommand.ts +132 -0
  127. package/dist/ui/commands/copyCommand.test.ts +296 -0
  128. package/dist/ui/commands/copyCommand.ts +67 -0
  129. package/dist/ui/commands/corgiCommand.test.ts +34 -0
  130. package/dist/ui/commands/corgiCommand.ts +16 -0
  131. package/dist/ui/commands/directoryCommand.test.tsx +185 -0
  132. package/dist/ui/commands/directoryCommand.tsx +179 -0
  133. package/dist/ui/commands/docsCommand.test.ts +99 -0
  134. package/dist/ui/commands/docsCommand.ts +42 -0
  135. package/dist/ui/commands/editorCommand.test.ts +30 -0
  136. package/dist/ui/commands/editorCommand.ts +21 -0
  137. package/dist/ui/commands/extensionsCommand.test.ts +67 -0
  138. package/dist/ui/commands/extensionsCommand.ts +46 -0
  139. package/dist/ui/commands/helpCommand.test.ts +52 -0
  140. package/dist/ui/commands/helpCommand.ts +23 -0
  141. package/dist/ui/commands/ideCommand.test.ts +255 -0
  142. package/dist/ui/commands/ideCommand.ts +283 -0
  143. package/dist/ui/commands/initCommand.test.ts +127 -0
  144. package/dist/ui/commands/initCommand.ts +117 -0
  145. package/dist/ui/commands/mcpCommand.test.ts +1057 -0
  146. package/dist/ui/commands/mcpCommand.ts +531 -0
  147. package/dist/ui/commands/memoryCommand.test.ts +344 -0
  148. package/dist/ui/commands/memoryCommand.ts +305 -0
  149. package/dist/ui/commands/privacyCommand.test.ts +38 -0
  150. package/dist/ui/commands/privacyCommand.ts +17 -0
  151. package/dist/ui/commands/quitCommand.test.ts +55 -0
  152. package/dist/ui/commands/quitCommand.ts +36 -0
  153. package/dist/ui/commands/restoreCommand.test.ts +250 -0
  154. package/dist/ui/commands/restoreCommand.ts +157 -0
  155. package/dist/ui/commands/searchEngineSetupCommand.ts +18 -0
  156. package/dist/ui/commands/settingsCommand.test.ts +36 -0
  157. package/dist/ui/commands/settingsCommand.ts +17 -0
  158. package/dist/ui/commands/setupGithubCommand.test.ts +238 -0
  159. package/dist/ui/commands/setupGithubCommand.ts +212 -0
  160. package/dist/ui/commands/speakCommand.ts +175 -0
  161. package/dist/ui/commands/statsCommand.test.ts +78 -0
  162. package/dist/ui/commands/statsCommand.ts +70 -0
  163. package/dist/ui/commands/terminalSetupCommand.test.ts +85 -0
  164. package/dist/ui/commands/terminalSetupCommand.ts +45 -0
  165. package/dist/ui/commands/themeCommand.test.ts +38 -0
  166. package/dist/ui/commands/themeCommand.ts +17 -0
  167. package/dist/ui/commands/toolsCommand.test.ts +105 -0
  168. package/dist/ui/commands/toolsCommand.ts +71 -0
  169. package/dist/ui/commands/ttsCommand.ts +143 -0
  170. package/dist/ui/commands/types.ts +204 -0
  171. package/dist/ui/commands/vimCommand.ts +25 -0
  172. package/dist/ui/commands/voiceCommand.ts +125 -0
  173. package/dist/ui/components/AboutBox.tsx +133 -0
  174. package/dist/ui/components/AsciiArt.ts +54 -0
  175. package/dist/ui/components/AuthDialog.test.tsx +334 -0
  176. package/dist/ui/components/AuthDialog.tsx +289 -0
  177. package/dist/ui/components/AuthInProgress.tsx +62 -0
  178. package/dist/ui/components/AutoAcceptIndicator.tsx +47 -0
  179. package/dist/ui/components/ConsoleSummaryDisplay.tsx +35 -0
  180. package/dist/ui/components/ContextSummaryDisplay.test.tsx +85 -0
  181. package/dist/ui/components/ContextSummaryDisplay.tsx +120 -0
  182. package/dist/ui/components/ContextUsageDisplay.tsx +77 -0
  183. package/dist/ui/components/DebugProfiler.tsx +36 -0
  184. package/dist/ui/components/DetailedMessagesDisplay.tsx +82 -0
  185. package/dist/ui/components/EditorSettingsDialog.tsx +172 -0
  186. package/dist/ui/components/FolderTrustDialog.test.tsx +36 -0
  187. package/dist/ui/components/FolderTrustDialog.tsx +74 -0
  188. package/dist/ui/components/Footer.test.tsx +159 -0
  189. package/dist/ui/components/Footer.tsx +158 -0
  190. package/dist/ui/components/GeminiKeyDialog.tsx +252 -0
  191. package/dist/ui/components/GeminiRespondingSpinner.tsx +34 -0
  192. package/dist/ui/components/Header.test.tsx +44 -0
  193. package/dist/ui/components/Header.tsx +70 -0
  194. package/dist/ui/components/Help.tsx +174 -0
  195. package/dist/ui/components/HistoryItemDisplay.test.tsx +125 -0
  196. package/dist/ui/components/HistoryItemDisplay.tsx +98 -0
  197. package/dist/ui/components/InputPrompt.test.tsx +1467 -0
  198. package/dist/ui/components/InputPrompt.tsx +641 -0
  199. package/dist/ui/components/LMStudioModelPrompt.tsx +215 -0
  200. package/dist/ui/components/LoadingIndicator.test.tsx +296 -0
  201. package/dist/ui/components/LoadingIndicator.tsx +82 -0
  202. package/dist/ui/components/MemoryUsageDisplay.tsx +36 -0
  203. package/dist/ui/components/ModelStatsDisplay.test.tsx +252 -0
  204. package/dist/ui/components/ModelStatsDisplay.tsx +197 -0
  205. package/dist/ui/components/OllamaModelPrompt.tsx +206 -0
  206. package/dist/ui/components/OpenAIEndpointDialog.tsx +261 -0
  207. package/dist/ui/components/OpenAIKeyPrompt.test.tsx +64 -0
  208. package/dist/ui/components/OpenAIKeyPrompt.tsx +197 -0
  209. package/dist/ui/components/PrepareLabel.tsx +48 -0
  210. package/dist/ui/components/SearchEngineConfigDialog.tsx +280 -0
  211. package/dist/ui/components/SessionSummaryDisplay.test.tsx +75 -0
  212. package/dist/ui/components/SessionSummaryDisplay.tsx +18 -0
  213. package/dist/ui/components/SettingsDialog.test.tsx +865 -0
  214. package/dist/ui/components/SettingsDialog.tsx +753 -0
  215. package/dist/ui/components/ShellConfirmationDialog.test.tsx +53 -0
  216. package/dist/ui/components/ShellConfirmationDialog.tsx +103 -0
  217. package/dist/ui/components/ShellModeIndicator.tsx +18 -0
  218. package/dist/ui/components/ShowMoreLines.tsx +40 -0
  219. package/dist/ui/components/StatsDisplay.test.tsx +401 -0
  220. package/dist/ui/components/StatsDisplay.tsx +273 -0
  221. package/dist/ui/components/SuggestionsDisplay.tsx +102 -0
  222. package/dist/ui/components/ThemeDialog.tsx +310 -0
  223. package/dist/ui/components/Tips.tsx +45 -0
  224. package/dist/ui/components/TodoDisplay.test.tsx +97 -0
  225. package/dist/ui/components/TodoDisplay.tsx +72 -0
  226. package/dist/ui/components/ToolStatsDisplay.test.tsx +180 -0
  227. package/dist/ui/components/ToolStatsDisplay.tsx +208 -0
  228. package/dist/ui/components/UpdateNotification.tsx +23 -0
  229. package/dist/ui/components/WelcomeBackDialog.tsx +290 -0
  230. package/dist/ui/components/__snapshots__/IDEContextDetailDisplay.test.tsx.snap +24 -0
  231. package/dist/ui/components/__snapshots__/ModelStatsDisplay.test.tsx.snap +121 -0
  232. package/dist/ui/components/__snapshots__/SessionSummaryDisplay.test.tsx.snap +30 -0
  233. package/dist/ui/components/__snapshots__/ShellConfirmationDialog.test.tsx.snap +21 -0
  234. package/dist/ui/components/__snapshots__/StatsDisplay.test.tsx.snap +264 -0
  235. package/dist/ui/components/__snapshots__/ToolStatsDisplay.test.tsx.snap +91 -0
  236. package/dist/ui/components/messages/CompressionMessage.tsx +49 -0
  237. package/dist/ui/components/messages/DiffRenderer.test.tsx +365 -0
  238. package/dist/ui/components/messages/DiffRenderer.tsx +358 -0
  239. package/dist/ui/components/messages/ErrorMessage.tsx +31 -0
  240. package/dist/ui/components/messages/GeminiMessage.tsx +43 -0
  241. package/dist/ui/components/messages/GeminiMessageContent.tsx +43 -0
  242. package/dist/ui/components/messages/InfoMessage.tsx +32 -0
  243. package/dist/ui/components/messages/ToolConfirmationMessage.test.tsx +58 -0
  244. package/dist/ui/components/messages/ToolConfirmationMessage.tsx +297 -0
  245. package/dist/ui/components/messages/ToolGroupMessage.tsx +126 -0
  246. package/dist/ui/components/messages/ToolMessage.test.tsx +183 -0
  247. package/dist/ui/components/messages/ToolMessage.tsx +296 -0
  248. package/dist/ui/components/messages/UserMessage.tsx +43 -0
  249. package/dist/ui/components/messages/UserShellMessage.tsx +25 -0
  250. package/dist/ui/components/shared/MaxSizedBox.test.tsx +425 -0
  251. package/dist/ui/components/shared/MaxSizedBox.tsx +624 -0
  252. package/dist/ui/components/shared/RadioButtonSelect.test.tsx +181 -0
  253. package/dist/ui/components/shared/RadioButtonSelect.tsx +234 -0
  254. package/dist/ui/components/shared/__snapshots__/RadioButtonSelect.test.tsx.snap +47 -0
  255. package/dist/ui/components/shared/text-buffer.test.ts +1728 -0
  256. package/dist/ui/components/shared/text-buffer.ts +2227 -0
  257. package/dist/ui/components/shared/vim-buffer-actions.test.ts +1119 -0
  258. package/dist/ui/components/shared/vim-buffer-actions.ts +814 -0
  259. package/dist/ui/constants.ts +17 -0
  260. package/dist/ui/contexts/KeypressContext.test.tsx +391 -0
  261. package/dist/ui/contexts/KeypressContext.tsx +440 -0
  262. package/dist/ui/contexts/OverflowContext.tsx +87 -0
  263. package/dist/ui/contexts/SessionContext.test.tsx +132 -0
  264. package/dist/ui/contexts/SessionContext.tsx +143 -0
  265. package/dist/ui/contexts/SettingsContext.tsx +20 -0
  266. package/dist/ui/contexts/StreamingContext.tsx +22 -0
  267. package/dist/ui/contexts/VimModeContext.tsx +79 -0
  268. package/dist/ui/editors/editorSettingsManager.ts +66 -0
  269. package/dist/ui/hooks/atCommandProcessor.test.ts +1102 -0
  270. package/dist/ui/hooks/atCommandProcessor.ts +485 -0
  271. package/dist/ui/hooks/shellCommandProcessor.test.ts +481 -0
  272. package/dist/ui/hooks/shellCommandProcessor.ts +314 -0
  273. package/dist/ui/hooks/slashCommandProcessor.test.ts +1044 -0
  274. package/dist/ui/hooks/slashCommandProcessor.ts +595 -0
  275. package/dist/ui/hooks/useAtCompletion.test.ts +497 -0
  276. package/dist/ui/hooks/useAtCompletion.ts +244 -0
  277. package/dist/ui/hooks/useAuthCommand.ts +129 -0
  278. package/dist/ui/hooks/useAutoAcceptIndicator.test.ts +300 -0
  279. package/dist/ui/hooks/useAutoAcceptIndicator.ts +52 -0
  280. package/dist/ui/hooks/useBracketedPaste.ts +37 -0
  281. package/dist/ui/hooks/useCommandCompletion.test.ts +518 -0
  282. package/dist/ui/hooks/useCommandCompletion.tsx +238 -0
  283. package/dist/ui/hooks/useCompletion.ts +128 -0
  284. package/dist/ui/hooks/useConsoleMessages.test.ts +147 -0
  285. package/dist/ui/hooks/useConsoleMessages.ts +110 -0
  286. package/dist/ui/hooks/useEditorSettings.test.ts +283 -0
  287. package/dist/ui/hooks/useEditorSettings.ts +75 -0
  288. package/dist/ui/hooks/useFocus.test.ts +119 -0
  289. package/dist/ui/hooks/useFocus.ts +48 -0
  290. package/dist/ui/hooks/useFolderTrust.test.ts +159 -0
  291. package/dist/ui/hooks/useFolderTrust.ts +72 -0
  292. package/dist/ui/hooks/useGeminiStream.test.tsx +1998 -0
  293. package/dist/ui/hooks/useGeminiStream.ts +1017 -0
  294. package/dist/ui/hooks/useGitBranchName.test.ts +280 -0
  295. package/dist/ui/hooks/useGitBranchName.ts +79 -0
  296. package/dist/ui/hooks/useHistoryManager.test.ts +202 -0
  297. package/dist/ui/hooks/useHistoryManager.ts +111 -0
  298. package/dist/ui/hooks/useInputHistory.test.ts +261 -0
  299. package/dist/ui/hooks/useInputHistory.ts +111 -0
  300. package/dist/ui/hooks/useKeypress.test.ts +280 -0
  301. package/dist/ui/hooks/useKeypress.ts +39 -0
  302. package/dist/ui/hooks/useKittyKeyboardProtocol.ts +31 -0
  303. package/dist/ui/hooks/useLoadingIndicator.test.ts +139 -0
  304. package/dist/ui/hooks/useLoadingIndicator.ts +57 -0
  305. package/dist/ui/hooks/useLogger.ts +32 -0
  306. package/dist/ui/hooks/useMessageQueue.test.ts +226 -0
  307. package/dist/ui/hooks/useMessageQueue.ts +69 -0
  308. package/dist/ui/hooks/usePhraseCycler.test.ts +145 -0
  309. package/dist/ui/hooks/usePhraseCycler.ts +198 -0
  310. package/dist/ui/hooks/usePrivacySettings.test.ts +242 -0
  311. package/dist/ui/hooks/usePrivacySettings.ts +150 -0
  312. package/dist/ui/hooks/useReactToolScheduler.ts +309 -0
  313. package/dist/ui/hooks/useRefreshMemoryCommand.ts +7 -0
  314. package/dist/ui/hooks/useReverseSearchCompletion.test.tsx +260 -0
  315. package/dist/ui/hooks/useReverseSearchCompletion.tsx +95 -0
  316. package/dist/ui/hooks/useSettingsCommand.ts +25 -0
  317. package/dist/ui/hooks/useShellHistory.test.ts +219 -0
  318. package/dist/ui/hooks/useShellHistory.ts +133 -0
  319. package/dist/ui/hooks/useShowMemoryCommand.ts +75 -0
  320. package/dist/ui/hooks/useSlashCompletion.test.ts +434 -0
  321. package/dist/ui/hooks/useSlashCompletion.ts +187 -0
  322. package/dist/ui/hooks/useStateAndRef.ts +36 -0
  323. package/dist/ui/hooks/useTerminalSize.ts +32 -0
  324. package/dist/ui/hooks/useThemeCommand.ts +110 -0
  325. package/dist/ui/hooks/useTimer.test.ts +120 -0
  326. package/dist/ui/hooks/useTimer.ts +65 -0
  327. package/dist/ui/hooks/useToolScheduler.test.ts +1123 -0
  328. package/dist/ui/hooks/useWelcomeBack.ts +253 -0
  329. package/dist/ui/hooks/vim.test.ts +1691 -0
  330. package/dist/ui/hooks/vim.ts +784 -0
  331. package/dist/ui/keyMatchers.test.ts +337 -0
  332. package/dist/ui/keyMatchers.ts +105 -0
  333. package/dist/ui/privacy/CloudFreePrivacyNotice.tsx +117 -0
  334. package/dist/ui/privacy/CloudPaidPrivacyNotice.tsx +59 -0
  335. package/dist/ui/privacy/GeminiPrivacyNotice.tsx +62 -0
  336. package/dist/ui/privacy/PrivacyNotice.tsx +42 -0
  337. package/dist/ui/semantic-colors.ts +26 -0
  338. package/dist/ui/themes/ansi-light.ts +150 -0
  339. package/dist/ui/themes/ansi.ts +159 -0
  340. package/dist/ui/themes/atom-one-dark.ts +147 -0
  341. package/dist/ui/themes/ayu-light.ts +139 -0
  342. package/dist/ui/themes/ayu.ts +113 -0
  343. package/dist/ui/themes/color-utils.test.ts +221 -0
  344. package/dist/ui/themes/color-utils.ts +231 -0
  345. package/dist/ui/themes/default-light.ts +108 -0
  346. package/dist/ui/themes/default.ts +151 -0
  347. package/dist/ui/themes/dracula.ts +124 -0
  348. package/dist/ui/themes/fss-code-dark.ts +156 -0
  349. package/dist/ui/themes/fss-dark.ts +113 -0
  350. package/dist/ui/themes/fss-light.ts +139 -0
  351. package/dist/ui/themes/github-dark.ts +147 -0
  352. package/dist/ui/themes/github-light.ts +149 -0
  353. package/dist/ui/themes/googlecode.ts +146 -0
  354. package/dist/ui/themes/no-color.ts +125 -0
  355. package/dist/ui/themes/qwen-dark.ts +118 -0
  356. package/dist/ui/themes/qwen-light.ts +144 -0
  357. package/dist/ui/themes/semantic-tokens.ts +127 -0
  358. package/dist/ui/themes/shades-of-purple.ts +352 -0
  359. package/dist/ui/themes/theme-manager.test.ts +99 -0
  360. package/dist/ui/themes/theme-manager.ts +257 -0
  361. package/dist/ui/themes/theme.test.ts +97 -0
  362. package/dist/ui/themes/theme.ts +451 -0
  363. package/dist/ui/themes/xcode.ts +154 -0
  364. package/dist/ui/types.ts +255 -0
  365. package/dist/ui/utils/CodeColorizer.tsx +217 -0
  366. package/dist/ui/utils/ConsolePatcher.ts +71 -0
  367. package/dist/ui/utils/InlineMarkdownRenderer.tsx +173 -0
  368. package/dist/ui/utils/MarkdownDisplay.test.tsx +244 -0
  369. package/dist/ui/utils/MarkdownDisplay.tsx +415 -0
  370. package/dist/ui/utils/TableRenderer.tsx +159 -0
  371. package/dist/ui/utils/__snapshots__/MarkdownDisplay.test.tsx.snap +93 -0
  372. package/dist/ui/utils/clipboardUtils.test.ts +76 -0
  373. package/dist/ui/utils/clipboardUtils.ts +149 -0
  374. package/dist/ui/utils/commandUtils.test.ts +384 -0
  375. package/dist/ui/utils/commandUtils.ts +106 -0
  376. package/dist/ui/utils/computeStats.test.ts +292 -0
  377. package/dist/ui/utils/computeStats.ts +86 -0
  378. package/dist/ui/utils/displayUtils.test.ts +58 -0
  379. package/dist/ui/utils/displayUtils.ts +32 -0
  380. package/dist/ui/utils/formatters.test.ts +72 -0
  381. package/dist/ui/utils/formatters.ts +63 -0
  382. package/dist/ui/utils/isNarrowWidth.ts +9 -0
  383. package/dist/ui/utils/kittyProtocolDetector.ts +105 -0
  384. package/dist/ui/utils/markdownUtilities.test.ts +50 -0
  385. package/dist/ui/utils/markdownUtilities.ts +125 -0
  386. package/dist/ui/utils/platformConstants.ts +52 -0
  387. package/dist/ui/utils/terminalSetup.ts +342 -0
  388. package/dist/ui/utils/textUtils.ts +40 -0
  389. package/dist/ui/utils/updateCheck.test.ts +163 -0
  390. package/dist/ui/utils/updateCheck.ts +100 -0
  391. package/dist/utils/checks.ts +28 -0
  392. package/dist/utils/cleanup.test.ts +68 -0
  393. package/dist/utils/cleanup.ts +36 -0
  394. package/dist/utils/dialogScopeUtils.ts +64 -0
  395. package/dist/utils/events.ts +14 -0
  396. package/dist/utils/gitUtils.test.ts +149 -0
  397. package/dist/utils/gitUtils.ts +116 -0
  398. package/dist/utils/handleAutoUpdate.test.ts +272 -0
  399. package/dist/utils/handleAutoUpdate.ts +145 -0
  400. package/dist/utils/installationInfo.test.ts +315 -0
  401. package/dist/utils/installationInfo.ts +176 -0
  402. package/dist/utils/package.ts +38 -0
  403. package/dist/utils/readStdin.ts +51 -0
  404. package/dist/utils/resolvePath.ts +21 -0
  405. package/dist/utils/sandbox-macos-permissive-closed.sb +32 -0
  406. package/dist/utils/sandbox-macos-permissive-open.sb +25 -0
  407. package/dist/utils/sandbox-macos-permissive-proxied.sb +37 -0
  408. package/dist/utils/sandbox-macos-restrictive-closed.sb +93 -0
  409. package/dist/utils/sandbox-macos-restrictive-open.sb +96 -0
  410. package/dist/utils/sandbox-macos-restrictive-proxied.sb +98 -0
  411. package/dist/utils/sandbox.ts +962 -0
  412. package/dist/utils/settingsUtils.test.ts +797 -0
  413. package/dist/utils/settingsUtils.ts +489 -0
  414. package/dist/utils/spawnWrapper.ts +9 -0
  415. package/dist/utils/startupWarnings.test.ts +83 -0
  416. package/dist/utils/startupWarnings.ts +40 -0
  417. package/dist/utils/updateEventEmitter.ts +13 -0
  418. package/dist/utils/userStartupWarnings.test.ts +87 -0
  419. package/dist/utils/userStartupWarnings.ts +69 -0
  420. package/dist/utils/version.ts +12 -0
  421. package/dist/validateNonInterActiveAuth.test.ts +260 -0
  422. package/dist/validateNonInterActiveAuth.ts +51 -0
  423. package/dist/vitest.config.ts +37 -0
  424. package/dist/zed-integration/acp.ts +366 -0
  425. package/dist/zed-integration/fileSystemService.ts +47 -0
  426. package/dist/zed-integration/schema.ts +466 -0
  427. package/dist/zed-integration/zedIntegration.ts +944 -0
  428. package/package.json +2 -2
@@ -0,0 +1,752 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import initSqlJs from 'sql.js';
8
+ import type { Database } from 'sql.js';
9
+ import * as path from 'path';
10
+ import * as fs from 'fs';
11
+ import { USER_SETTINGS_DIR } from './settings.js';
12
+ import type { ModelConfig } from 'fss-link-core';
13
+
14
+ export interface UserPreference {
15
+ key: string;
16
+ value: string;
17
+ }
18
+
19
+ /**
20
+ * FSS Link Database Manager
21
+ * Handles all persistent state for model configurations and user preferences
22
+ */
23
+ export class FSSLinkDatabase {
24
+ private db: Database | null = null;
25
+ private dbPath: string;
26
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
+ private SQL: any; // SQLite database handle
28
+ private initialized = false;
29
+
30
+ constructor() {
31
+ // Ensure FSS Link settings directory exists
32
+ if (!fs.existsSync(USER_SETTINGS_DIR)) {
33
+ fs.mkdirSync(USER_SETTINGS_DIR, { recursive: true });
34
+ }
35
+
36
+ this.dbPath = path.join(USER_SETTINGS_DIR, 'fss-link.db');
37
+ }
38
+
39
+ /**
40
+ * Initialize the database - must be called before any operations
41
+ */
42
+ async initialize(): Promise<void> {
43
+ if (this.initialized) return;
44
+
45
+ this.SQL = await initSqlJs();
46
+
47
+ // Load existing database or create new one
48
+ if (fs.existsSync(this.dbPath)) {
49
+ const data = fs.readFileSync(this.dbPath);
50
+ this.db = new this.SQL.Database(data);
51
+ } else {
52
+ this.db = new this.SQL.Database();
53
+ }
54
+
55
+ await this.initializeSchema();
56
+ this.initialized = true;
57
+ }
58
+
59
+ /**
60
+ * Ensure database is initialized
61
+ */
62
+ private async ensureInitialized(): Promise<void> {
63
+ if (!this.initialized) {
64
+ await this.initialize();
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Save database to disk
70
+ */
71
+ private save(): void {
72
+ if (!this.db) return;
73
+ const data = this.db.export();
74
+ fs.writeFileSync(this.dbPath, Buffer.from(data));
75
+ }
76
+
77
+ /**
78
+ * Initialize database schema
79
+ */
80
+ private async initializeSchema(): Promise<void> {
81
+ if (!this.db) return;
82
+
83
+ // Model configurations table
84
+ this.db.exec(`
85
+ CREATE TABLE IF NOT EXISTS model_configs (
86
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
87
+ auth_type TEXT NOT NULL,
88
+ model_name TEXT NOT NULL,
89
+ endpoint_url TEXT,
90
+ api_key TEXT,
91
+ display_name TEXT,
92
+ is_favorite BOOLEAN DEFAULT 0,
93
+ is_active BOOLEAN DEFAULT 0,
94
+ last_used DATETIME,
95
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
96
+ UNIQUE(auth_type, model_name, endpoint_url)
97
+ )
98
+ `);
99
+
100
+ // User preferences table
101
+ this.db.exec(`
102
+ CREATE TABLE IF NOT EXISTS user_preferences (
103
+ key TEXT PRIMARY KEY,
104
+ value TEXT NOT NULL,
105
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
106
+ )
107
+ `);
108
+
109
+ // Custom endpoints table
110
+ this.db.exec(`
111
+ CREATE TABLE IF NOT EXISTS custom_endpoints (
112
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
113
+ provider_id INTEGER NOT NULL,
114
+ name TEXT NOT NULL,
115
+ url TEXT NOT NULL,
116
+ description TEXT,
117
+ is_default BOOLEAN DEFAULT 0,
118
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
119
+ FOREIGN KEY (provider_id) REFERENCES model_configs (id)
120
+ )
121
+ `);
122
+
123
+ // Provider usage tracking table
124
+ this.db.exec(`
125
+ CREATE TABLE IF NOT EXISTS provider_usage (
126
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
127
+ provider_id INTEGER NOT NULL,
128
+ session_date DATE DEFAULT CURRENT_DATE,
129
+ session_count INTEGER DEFAULT 1,
130
+ tokens_used INTEGER DEFAULT 0,
131
+ session_duration INTEGER DEFAULT 0,
132
+ FOREIGN KEY (provider_id) REFERENCES model_configs (id),
133
+ UNIQUE(provider_id, session_date)
134
+ )
135
+ `);
136
+
137
+ // Provider settings table (for additional configuration like sampling params)
138
+ this.db.exec(`
139
+ CREATE TABLE IF NOT EXISTS provider_settings (
140
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
141
+ provider_id INTEGER NOT NULL,
142
+ setting_key TEXT NOT NULL,
143
+ setting_value TEXT NOT NULL,
144
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
145
+ FOREIGN KEY (provider_id) REFERENCES model_configs (id),
146
+ UNIQUE(provider_id, setting_key)
147
+ )
148
+ `);
149
+
150
+ // Create indexes for performance
151
+ this.db.exec(`
152
+ CREATE INDEX IF NOT EXISTS idx_model_configs_active ON model_configs(is_active);
153
+ CREATE INDEX IF NOT EXISTS idx_model_configs_favorite ON model_configs(is_favorite);
154
+ CREATE INDEX IF NOT EXISTS idx_model_configs_last_used ON model_configs(last_used DESC);
155
+ CREATE INDEX IF NOT EXISTS idx_model_configs_auth_type ON model_configs(auth_type);
156
+ CREATE INDEX IF NOT EXISTS idx_custom_endpoints_provider ON custom_endpoints(provider_id);
157
+ CREATE INDEX IF NOT EXISTS idx_provider_usage_date ON provider_usage(session_date DESC);
158
+ CREATE INDEX IF NOT EXISTS idx_provider_settings_key ON provider_settings(provider_id, setting_key);
159
+ `);
160
+
161
+ // Insert default configurations if none exist
162
+ await this.ensureDefaultConfigurations();
163
+
164
+ // Save initial schema
165
+ this.save();
166
+ }
167
+
168
+ /**
169
+ * Ensure default model configurations exist for new installations
170
+ */
171
+ private async ensureDefaultConfigurations(): Promise<void> {
172
+ if (!this.db) return;
173
+
174
+ // Check if any configurations exist
175
+ const stmt = this.db.prepare('SELECT COUNT(*) as count FROM model_configs');
176
+ const result = stmt.get() as { count: number };
177
+
178
+ if (result.count === 0) {
179
+ // Insert default Ollama configuration for new users (no API key needed)
180
+ const defaultOllamaConfig = {
181
+ auth_type: 'ollama',
182
+ model_name: 'qwen3-coder:latest',
183
+ endpoint_url: 'http://localhost:11434/v1',
184
+ api_key: null,
185
+ display_name: 'Qwen3 Coder (Ollama)',
186
+ is_favorite: 1, // Mark as favorite so it's selected by default
187
+ is_active: 1, // Mark as active
188
+ last_used: null,
189
+ created_at: new Date().toISOString()
190
+ };
191
+
192
+ const insertStmt = this.db.prepare(`
193
+ INSERT INTO model_configs (
194
+ auth_type, model_name, endpoint_url, api_key, display_name,
195
+ is_favorite, is_active, last_used, created_at
196
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
197
+ `);
198
+
199
+ insertStmt.run(
200
+ defaultOllamaConfig.auth_type,
201
+ defaultOllamaConfig.model_name,
202
+ defaultOllamaConfig.endpoint_url,
203
+ defaultOllamaConfig.api_key,
204
+ defaultOllamaConfig.display_name,
205
+ defaultOllamaConfig.is_favorite,
206
+ defaultOllamaConfig.is_active,
207
+ defaultOllamaConfig.last_used,
208
+ defaultOllamaConfig.created_at
209
+ );
210
+
211
+ // Also insert a default Gemini configuration (inactive) for when users want to configure API keys later
212
+ const defaultGeminiConfig = {
213
+ auth_type: 'use_gemini',
214
+ model_name: 'gemini-2.5-flash-thinking',
215
+ endpoint_url: 'https://generativelanguage.googleapis.com/v1',
216
+ api_key: null,
217
+ display_name: 'Gemini 2.5 Flash Thinking (Requires API Key)',
218
+ is_favorite: 0,
219
+ is_active: 0, // Inactive until user configures API key
220
+ last_used: null,
221
+ created_at: new Date().toISOString()
222
+ };
223
+
224
+ insertStmt.run(
225
+ defaultGeminiConfig.auth_type,
226
+ defaultGeminiConfig.model_name,
227
+ defaultGeminiConfig.endpoint_url,
228
+ defaultGeminiConfig.api_key,
229
+ defaultGeminiConfig.display_name,
230
+ defaultGeminiConfig.is_favorite,
231
+ defaultGeminiConfig.is_active,
232
+ defaultGeminiConfig.last_used,
233
+ defaultGeminiConfig.created_at
234
+ );
235
+
236
+ // Add default LM Studio configuration (active, optional API key)
237
+ const defaultLMStudioConfig = {
238
+ auth_type: 'lm_studio',
239
+ model_name: 'qwen3-coder:latest',
240
+ endpoint_url: 'http://localhost:1234/v1',
241
+ api_key: null, // API key is optional for LM Studio
242
+ display_name: 'Qwen3 Coder (LM Studio)',
243
+ is_favorite: 0,
244
+ is_active: 1, // Active by default since no API key needed
245
+ last_used: null,
246
+ created_at: new Date().toISOString()
247
+ };
248
+
249
+ insertStmt.run(
250
+ defaultLMStudioConfig.auth_type,
251
+ defaultLMStudioConfig.model_name,
252
+ defaultLMStudioConfig.endpoint_url,
253
+ defaultLMStudioConfig.api_key,
254
+ defaultLMStudioConfig.display_name,
255
+ defaultLMStudioConfig.is_favorite,
256
+ defaultLMStudioConfig.is_active,
257
+ defaultLMStudioConfig.last_used,
258
+ defaultLMStudioConfig.created_at
259
+ );
260
+
261
+ // Add default OpenAI configuration (inactive, requires API key)
262
+ const defaultOpenAIConfig = {
263
+ auth_type: 'use_openai',
264
+ model_name: 'gpt-4o',
265
+ endpoint_url: 'https://api.openai.com/v1',
266
+ api_key: null, // API key required but not set by default
267
+ display_name: 'GPT-4o (Requires API Key)',
268
+ is_favorite: 0,
269
+ is_active: 0, // Inactive until user configures API key
270
+ last_used: null,
271
+ created_at: new Date().toISOString()
272
+ };
273
+
274
+ insertStmt.run(
275
+ defaultOpenAIConfig.auth_type,
276
+ defaultOpenAIConfig.model_name,
277
+ defaultOpenAIConfig.endpoint_url,
278
+ defaultOpenAIConfig.api_key,
279
+ defaultOpenAIConfig.display_name,
280
+ defaultOpenAIConfig.is_favorite,
281
+ defaultOpenAIConfig.is_active,
282
+ defaultOpenAIConfig.last_used,
283
+ defaultOpenAIConfig.created_at
284
+ );
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Get the currently active model configuration
290
+ */
291
+ async getActiveModel(): Promise<ModelConfig | null> {
292
+ await this.ensureInitialized();
293
+ if (!this.db) return null;
294
+
295
+ const stmt = this.db.prepare(`
296
+ SELECT id, auth_type, model_name, endpoint_url, api_key, display_name,
297
+ is_favorite, is_active, last_used, created_at
298
+ FROM model_configs
299
+ WHERE is_active = 1
300
+ LIMIT 1
301
+ `);
302
+
303
+ const result = stmt.getAsObject({});
304
+ // Check if we actually got a valid row by looking for the id field
305
+ // SQLite can return an object with undefined values when no rows match
306
+ return result && result['id'] ? this.mapRowToModelConfig(result) : null;
307
+ }
308
+
309
+ /**
310
+ * Set a model as active (and deactivate all others)
311
+ */
312
+ async setActiveModel(id: number): Promise<void> {
313
+ await this.ensureInitialized();
314
+ if (!this.db) return;
315
+
316
+ // Deactivate all models
317
+ this.db.run('UPDATE model_configs SET is_active = 0');
318
+
319
+ // Activate the selected model and update last_used
320
+ this.db.run(`
321
+ UPDATE model_configs
322
+ SET is_active = 1, last_used = CURRENT_TIMESTAMP
323
+ WHERE id = ?
324
+ `, [id]);
325
+
326
+ this.save();
327
+ }
328
+
329
+ /**
330
+ * Add or update a model configuration
331
+ */
332
+ async upsertModelConfig(config: ModelConfig): Promise<number> {
333
+ await this.ensureInitialized();
334
+ if (!this.db) return -1;
335
+
336
+ const stmt = this.db.prepare(`
337
+ INSERT OR REPLACE INTO model_configs
338
+ (auth_type, model_name, endpoint_url, api_key, display_name, is_favorite, is_active, last_used)
339
+ VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
340
+ `);
341
+
342
+ stmt.run([
343
+ config.authType,
344
+ config.modelName,
345
+ config.endpointUrl || null,
346
+ config.apiKey || null,
347
+ config.displayName || null,
348
+ config.isFavorite ? 1 : 0,
349
+ config.isActive ? 1 : 0
350
+ ]);
351
+
352
+ this.save();
353
+
354
+ // Get the last inserted row ID
355
+ const result = this.db.exec('SELECT last_insert_rowid() as id');
356
+ return result[0]?.values[0]?.[0] as number || -1;
357
+ }
358
+
359
+ /**
360
+ * Get all model configurations
361
+ */
362
+ async getAllModels(): Promise<ModelConfig[]> {
363
+ await this.ensureInitialized();
364
+ if (!this.db) return [];
365
+
366
+ const stmt = this.db.prepare(`
367
+ SELECT id, auth_type, model_name, endpoint_url, api_key, display_name,
368
+ is_favorite, is_active, last_used, created_at
369
+ FROM model_configs
370
+ ORDER BY is_favorite DESC, last_used DESC, created_at DESC
371
+ `);
372
+
373
+ const results = [];
374
+ while (stmt.step()) {
375
+ results.push(this.mapRowToModelConfig(stmt.getAsObject()));
376
+ }
377
+
378
+ return results;
379
+ }
380
+
381
+ /**
382
+ * Get favorite models
383
+ */
384
+ async getFavoriteModels(): Promise<ModelConfig[]> {
385
+ await this.ensureInitialized();
386
+ if (!this.db) return [];
387
+
388
+ const stmt = this.db.prepare(`
389
+ SELECT id, auth_type, model_name, endpoint_url, api_key, display_name,
390
+ is_favorite, is_active, last_used, created_at
391
+ FROM model_configs
392
+ WHERE is_favorite = 1
393
+ ORDER BY last_used DESC, created_at DESC
394
+ `);
395
+
396
+ const results = [];
397
+ while (stmt.step()) {
398
+ results.push(this.mapRowToModelConfig(stmt.getAsObject()));
399
+ }
400
+
401
+ return results;
402
+ }
403
+
404
+ /**
405
+ * Get recently used models
406
+ */
407
+ async getRecentModels(limit: number = 10): Promise<ModelConfig[]> {
408
+ await this.ensureInitialized();
409
+ if (!this.db) return [];
410
+
411
+ const stmt = this.db.prepare(`
412
+ SELECT id, auth_type, model_name, endpoint_url, api_key, display_name,
413
+ is_favorite, is_active, last_used, created_at
414
+ FROM model_configs
415
+ WHERE last_used IS NOT NULL
416
+ ORDER BY last_used DESC
417
+ LIMIT ?
418
+ `, [limit]);
419
+
420
+ const results = [];
421
+ while (stmt.step()) {
422
+ results.push(this.mapRowToModelConfig(stmt.getAsObject()));
423
+ }
424
+
425
+ return results;
426
+ }
427
+
428
+ /**
429
+ * Toggle favorite status for a model
430
+ */
431
+ async toggleModelFavorite(id: number): Promise<void> {
432
+ await this.ensureInitialized();
433
+ if (!this.db) return;
434
+
435
+ this.db.run(`
436
+ UPDATE model_configs
437
+ SET is_favorite = CASE WHEN is_favorite = 1 THEN 0 ELSE 1 END
438
+ WHERE id = ?
439
+ `, [id]);
440
+
441
+ this.save();
442
+ }
443
+
444
+ /**
445
+ * Delete a model configuration
446
+ */
447
+ async deleteModel(id: number): Promise<void> {
448
+ await this.ensureInitialized();
449
+ if (!this.db) return;
450
+
451
+ this.db.run('DELETE FROM model_configs WHERE id = ?', [id]);
452
+ this.save();
453
+ }
454
+
455
+ /**
456
+ * Set user preference
457
+ */
458
+ async setUserPreference(key: string, value: string): Promise<void> {
459
+ await this.ensureInitialized();
460
+ if (!this.db) return;
461
+
462
+ this.db.run(`
463
+ INSERT OR REPLACE INTO user_preferences (key, value, updated_at)
464
+ VALUES (?, ?, CURRENT_TIMESTAMP)
465
+ `, [key, value]);
466
+
467
+ this.save();
468
+ }
469
+
470
+ /**
471
+ * Get user preference
472
+ */
473
+ async getUserPreference(key: string): Promise<string | null> {
474
+ await this.ensureInitialized();
475
+ if (!this.db) return null;
476
+
477
+ const stmt = this.db.prepare('SELECT value FROM user_preferences WHERE key = ?', [key]);
478
+
479
+ if (stmt.step()) {
480
+ const result = stmt.getAsObject() as { value: string };
481
+ return result.value || null;
482
+ }
483
+
484
+ return null;
485
+ }
486
+
487
+ /**
488
+ * Get all user preferences
489
+ */
490
+ async getAllUserPreferences(): Promise<Record<string, string>> {
491
+ await this.ensureInitialized();
492
+ if (!this.db) return {};
493
+
494
+ const stmt = this.db.prepare('SELECT key, value FROM user_preferences');
495
+ const prefs: Record<string, string> = {};
496
+
497
+ while (stmt.step()) {
498
+ const row = stmt.getAsObject() as { key: string; value: string };
499
+ prefs[row.key] = row.value;
500
+ }
501
+
502
+ return prefs;
503
+ }
504
+
505
+ /**
506
+ * Close database connection
507
+ */
508
+ close(): void {
509
+ if (this.db) {
510
+ this.save();
511
+ this.db.close();
512
+ this.db = null;
513
+ this.initialized = false;
514
+ }
515
+ }
516
+
517
+ /**
518
+ * Get database file path (for debugging)
519
+ */
520
+ getDatabasePath(): string {
521
+ return this.dbPath;
522
+ }
523
+
524
+ /**
525
+ * Map database row to ModelConfig interface
526
+ */
527
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
528
+ private mapRowToModelConfig(row: any): ModelConfig {
529
+ return {
530
+ id: row.id,
531
+ authType: row.auth_type,
532
+ modelName: row.model_name,
533
+ endpointUrl: row.endpoint_url,
534
+ apiKey: row.api_key,
535
+ displayName: row.display_name,
536
+ isFavorite: Boolean(row.is_favorite),
537
+ isActive: Boolean(row.is_active),
538
+ lastUsed: row.last_used ? new Date(row.last_used) : undefined,
539
+ createdAt: row.created_at ? new Date(row.created_at) : undefined,
540
+ };
541
+ }
542
+
543
+ // ============= ENHANCED PROVIDER MANAGEMENT =============
544
+
545
+ /**
546
+ * Record provider usage for intelligent default selection
547
+ */
548
+ async recordProviderUsage(providerId: number, tokensUsed: number, sessionDuration: number): Promise<void> {
549
+ await this.ensureInitialized();
550
+ if (!this.db) return;
551
+
552
+ // Update or insert daily usage statistics
553
+ this.db.exec(`
554
+ INSERT INTO provider_usage (provider_id, tokens_used, session_duration)
555
+ VALUES (?, ?, ?)
556
+ ON CONFLICT(provider_id, session_date)
557
+ DO UPDATE SET
558
+ session_count = session_count + 1,
559
+ tokens_used = tokens_used + excluded.tokens_used,
560
+ session_duration = session_duration + excluded.session_duration
561
+ `, [providerId, tokensUsed, sessionDuration]);
562
+
563
+ // Update last_used timestamp in model_configs
564
+ this.db.exec(`
565
+ UPDATE model_configs
566
+ SET last_used = CURRENT_TIMESTAMP
567
+ WHERE id = ?
568
+ `, [providerId]);
569
+
570
+ this.save();
571
+ }
572
+
573
+ /**
574
+ * Save custom endpoint for a provider
575
+ */
576
+ async saveCustomEndpoint(providerId: number, name: string, url: string, description?: string, isDefault = false): Promise<number> {
577
+ await this.ensureInitialized();
578
+ if (!this.db) return -1;
579
+
580
+ // If setting as default, unset other defaults for this provider
581
+ if (isDefault) {
582
+ this.db.exec(`
583
+ UPDATE custom_endpoints
584
+ SET is_default = 0
585
+ WHERE provider_id = ?
586
+ `, [providerId]);
587
+ }
588
+
589
+ this.db.exec(`
590
+ INSERT OR REPLACE INTO custom_endpoints (provider_id, name, url, description, is_default)
591
+ VALUES (?, ?, ?, ?, ?)
592
+ `, [providerId, name, url, description || null, isDefault ? 1 : 0]);
593
+
594
+ this.save();
595
+
596
+ const result = this.db.exec('SELECT last_insert_rowid() as id');
597
+ return result[0]?.values[0]?.[0] as number || -1;
598
+ }
599
+
600
+ /**
601
+ * Get custom endpoints for a provider
602
+ */
603
+ async getCustomEndpoints(providerId: number): Promise<Array<Record<string, unknown>>> {
604
+ await this.ensureInitialized();
605
+ if (!this.db) return [];
606
+
607
+ const stmt = this.db.prepare(`
608
+ SELECT id, provider_id, name, url, description, is_default, created_at
609
+ FROM custom_endpoints
610
+ WHERE provider_id = ?
611
+ ORDER BY is_default DESC, created_at DESC
612
+ `);
613
+
614
+ return stmt.all(providerId) || [];
615
+ }
616
+
617
+ /**
618
+ * Save provider-specific settings (like sampling params)
619
+ */
620
+ async saveProviderSetting(providerId: number, key: string, value: string): Promise<void> {
621
+ await this.ensureInitialized();
622
+ if (!this.db) return;
623
+
624
+ const stmt = this.db.prepare(`
625
+ INSERT OR REPLACE INTO provider_settings (provider_id, setting_key, setting_value)
626
+ VALUES (?, ?, ?)
627
+ `);
628
+ stmt.run(providerId, key, value);
629
+
630
+ this.save();
631
+ }
632
+
633
+ /**
634
+ * Get provider-specific settings
635
+ */
636
+ async getProviderSettings(providerId: number): Promise<Record<string, string>> {
637
+ await this.ensureInitialized();
638
+ if (!this.db) return {};
639
+
640
+ const stmt = this.db.prepare(`
641
+ SELECT setting_key, setting_value
642
+ FROM provider_settings
643
+ WHERE provider_id = ?
644
+ `);
645
+
646
+ const results = stmt.all(providerId) || [];
647
+ const settings: Record<string, string> = {};
648
+
649
+ for (const row of results) {
650
+ settings[row.setting_key] = row.setting_value;
651
+ }
652
+
653
+ return settings;
654
+ }
655
+
656
+ /**
657
+ * Get smart default provider based on usage and preferences
658
+ */
659
+ async getSmartDefaultProvider(): Promise<ModelConfig | null> {
660
+ await this.ensureInitialized();
661
+ if (!this.db) return null;
662
+
663
+ // Priority order:
664
+ // 1. User-marked favorite (is_favorite = 1)
665
+ // 2. Most used provider in last 30 days
666
+ // 3. Most recently used provider
667
+ const stmt = this.db.prepare(`
668
+ SELECT
669
+ m.id, m.auth_type, m.model_name, m.endpoint_url, m.api_key,
670
+ m.display_name, m.is_favorite, m.is_active, m.last_used, m.created_at,
671
+ COALESCE(u.recent_usage_score, 0) as usage_score
672
+ FROM model_configs m
673
+ LEFT JOIN (
674
+ SELECT
675
+ provider_id,
676
+ SUM(session_count * (1.0 / (julianday('now') - julianday(session_date) + 1))) as recent_usage_score
677
+ FROM provider_usage
678
+ WHERE session_date >= date('now', '-30 days')
679
+ GROUP BY provider_id
680
+ ) u ON m.id = u.provider_id
681
+ ORDER BY
682
+ m.is_favorite DESC,
683
+ usage_score DESC,
684
+ m.last_used DESC,
685
+ m.created_at DESC
686
+ LIMIT 1
687
+ `);
688
+
689
+ const result = stmt.get([]);
690
+ if (!result) return null;
691
+
692
+ return {
693
+ authType: result.auth_type,
694
+ modelName: result.model_name,
695
+ endpointUrl: result.endpoint_url,
696
+ apiKey: result.api_key,
697
+ displayName: result.display_name,
698
+ isFavorite: Boolean(result.is_favorite),
699
+ isActive: Boolean(result.is_active)
700
+ };
701
+ }
702
+
703
+ /**
704
+ * Get a specific model configuration by ID
705
+ */
706
+ async getModelById(modelId: number): Promise<ModelConfig | null> {
707
+ await this.ensureInitialized();
708
+ if (!this.db) return null;
709
+
710
+ const stmt = this.db.prepare(`
711
+ SELECT * FROM models WHERE id = ?
712
+ `);
713
+
714
+ const result = stmt.get(modelId);
715
+ if (!result) return null;
716
+
717
+ return {
718
+ id: result.id,
719
+ authType: result.auth_type,
720
+ modelName: result.model_name,
721
+ endpointUrl: result.endpoint_url,
722
+ apiKey: result.api_key,
723
+ displayName: result.display_name,
724
+ isFavorite: Boolean(result.is_favorite),
725
+ isActive: Boolean(result.is_active)
726
+ };
727
+ }
728
+ }
729
+
730
+ // Singleton instance for global access
731
+ let databaseInstance: FSSLinkDatabase | null = null;
732
+
733
+ /**
734
+ * Get the global FSS Link database instance
735
+ */
736
+ export async function getFSSLinkDatabase(): Promise<FSSLinkDatabase> {
737
+ if (!databaseInstance) {
738
+ databaseInstance = new FSSLinkDatabase();
739
+ await databaseInstance.initialize();
740
+ }
741
+ return databaseInstance;
742
+ }
743
+
744
+ /**
745
+ * Close the global database instance (for cleanup)
746
+ */
747
+ export function closeFSSLinkDatabase(): void {
748
+ if (databaseInstance) {
749
+ databaseInstance.close();
750
+ databaseInstance = null;
751
+ }
752
+ }