cartethyia 2.7.5 → 2.7.7

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 (300) hide show
  1. package/.env.example +8 -3
  2. package/README.md +26 -2
  3. package/dist/dashboard/assets/BannedPage-B05aF8Dg.js +1 -0
  4. package/dist/dashboard/assets/BannedPage-B05aF8Dg.js.gz +0 -0
  5. package/dist/dashboard/assets/CliToolDetailPage-CLUhmjUy.js +3 -0
  6. package/dist/dashboard/assets/CliToolDetailPage-CLUhmjUy.js.gz +0 -0
  7. package/dist/dashboard/assets/CliToolsPage-CQmrqqrk.js +1 -0
  8. package/dist/dashboard/assets/CliToolsPage-CQmrqqrk.js.gz +0 -0
  9. package/dist/dashboard/assets/CombosPage-CHG_1Wr3.js +1 -0
  10. package/dist/dashboard/assets/CombosPage-CHG_1Wr3.js.gz +0 -0
  11. package/dist/dashboard/assets/{ConsoleLogPage-D5rol2j2.js → ConsoleLogPage-DPgbklad.js} +1 -1
  12. package/dist/dashboard/assets/ConsoleLogPage-DPgbklad.js.gz +0 -0
  13. package/dist/dashboard/assets/{CustomizationPage-BKj_r0rp.js → CustomizationPage-pnfAqr_-.js} +1 -1
  14. package/dist/dashboard/assets/CustomizationPage-pnfAqr_-.js.gz +0 -0
  15. package/dist/dashboard/assets/HealthEventsModal-DGJuU2A6.js +1 -0
  16. package/dist/dashboard/assets/HealthEventsModal-DGJuU2A6.js.gz +0 -0
  17. package/dist/dashboard/assets/{InjectSystemPromptPanel-B5nZvmf0.js → InjectSystemPromptPanel-C-e2AMz6.js} +245 -2
  18. package/dist/dashboard/assets/InjectSystemPromptPanel-C-e2AMz6.js.gz +0 -0
  19. package/dist/dashboard/assets/LoginPage-J8wPbmfT.js +1 -0
  20. package/dist/dashboard/assets/LoginPage-J8wPbmfT.js.gz +0 -0
  21. package/dist/dashboard/assets/{ModelPicker-DnQPSIA8.js → ModelPicker-C6WlpU5E.js} +1 -1
  22. package/dist/dashboard/assets/ModelPicker-C6WlpU5E.js.gz +0 -0
  23. package/dist/dashboard/assets/OverviewPage-BSQnF2Lp.js +1 -0
  24. package/dist/dashboard/assets/OverviewPage-BSQnF2Lp.js.gz +0 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-DK_e0FJz.js +7 -0
  26. package/dist/dashboard/assets/ProviderDetailPage-DK_e0FJz.js.gz +0 -0
  27. package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js +1 -0
  28. package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js.gz +0 -0
  29. package/dist/dashboard/assets/ProvidersPage-D_a3r6Gc.js +1 -0
  30. package/dist/dashboard/assets/ProvidersPage-D_a3r6Gc.js.gz +0 -0
  31. package/dist/dashboard/assets/{ProxyPage-5SnUSy3j.js → ProxyPage-BBTRBAQ3.js} +1 -1
  32. package/dist/dashboard/assets/ProxyPage-BBTRBAQ3.js.gz +0 -0
  33. package/dist/dashboard/assets/QuotaPage-B2WqMSG1.js +1 -0
  34. package/dist/dashboard/assets/QuotaPage-B2WqMSG1.js.gz +0 -0
  35. package/dist/dashboard/assets/{SettingsPage-BJtRZ4qD.js → SettingsPage-D7xp5Ij_.js} +1 -1
  36. package/dist/dashboard/assets/SettingsPage-D7xp5Ij_.js.gz +0 -0
  37. package/dist/dashboard/assets/SetupPage-LND6_Ss7.js +1 -0
  38. package/dist/dashboard/assets/SetupPage-LND6_Ss7.js.gz +0 -0
  39. package/dist/dashboard/assets/{StudioPage-BfAb5-o_.js → StudioPage-BzNnm6Bi.js} +4 -4
  40. package/dist/dashboard/assets/StudioPage-BzNnm6Bi.js.gz +0 -0
  41. package/dist/dashboard/assets/ToolIcon-CfeLA-Z_.js +1 -0
  42. package/dist/dashboard/assets/ToolIcon-CfeLA-Z_.js.gz +0 -0
  43. package/dist/dashboard/assets/{UsagePage-BNzeZ2Vk.js → UsagePage-C1TbL24N.js} +13 -13
  44. package/dist/dashboard/assets/UsagePage-C1TbL24N.js.gz +0 -0
  45. package/dist/dashboard/assets/api-keys-KvX83mRe.js +1 -0
  46. package/dist/dashboard/assets/api-keys-KvX83mRe.js.gz +0 -0
  47. package/dist/dashboard/assets/app-DRnQLhsc.js +9 -0
  48. package/dist/dashboard/assets/app-DRnQLhsc.js.gz +0 -0
  49. package/dist/dashboard/assets/app-mweRcsKp.css +2 -0
  50. package/dist/dashboard/assets/app-mweRcsKp.css.gz +0 -0
  51. package/dist/dashboard/assets/{assert-C0K-c4eo.js → assert-CidQVoNo.js} +1 -1
  52. package/dist/dashboard/assets/assert-CidQVoNo.js.gz +0 -0
  53. package/dist/dashboard/assets/{badge-DfbhQU4k.js → badge-BaX2GTrn.js} +1 -1
  54. package/dist/dashboard/assets/badge-BaX2GTrn.js.gz +0 -0
  55. package/dist/dashboard/assets/{banner-BllT4LcF.js → banner-DFH4Bkte.js} +1 -1
  56. package/dist/dashboard/assets/banner-DFH4Bkte.js.gz +0 -0
  57. package/dist/dashboard/assets/{card-CtkX66Wd.js → card-CxDw0lK6.js} +1 -1
  58. package/dist/dashboard/assets/card-CxDw0lK6.js.gz +0 -0
  59. package/dist/dashboard/assets/circle-question-mark-Cz4un8sH.js +1 -0
  60. package/dist/dashboard/assets/circle-question-mark-Cz4un8sH.js.gz +0 -0
  61. package/dist/dashboard/assets/{cli-tools-CQXVMn4P.js → cli-tools-Bwv5-zGZ.js} +1 -1
  62. package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js.gz +0 -0
  63. package/dist/dashboard/assets/{clipboard-button-BQqBQd2n.js → clipboard-button-DfS1ntiq.js} +1 -1
  64. package/dist/dashboard/assets/clipboard-button-DfS1ntiq.js.gz +0 -0
  65. package/dist/dashboard/assets/dialog-BK-NRr5u.js +1 -0
  66. package/dist/dashboard/assets/dialog-BK-NRr5u.js.gz +0 -0
  67. package/dist/dashboard/assets/{drawer-BrTEZ8zu.js → drawer-QqPul6sC.js} +1 -1
  68. package/dist/dashboard/assets/drawer-QqPul6sC.js.gz +0 -0
  69. package/dist/dashboard/assets/entry-BMlYJVFj.js +1 -0
  70. package/dist/dashboard/assets/entry-BMlYJVFj.js.gz +0 -0
  71. package/dist/dashboard/assets/entry-Dmx7gBiN.js +1 -0
  72. package/dist/dashboard/assets/entry-Dmx7gBiN.js.gz +0 -0
  73. package/dist/dashboard/assets/entry-vqGb8yVz.js +4 -0
  74. package/dist/dashboard/assets/entry-vqGb8yVz.js.gz +0 -0
  75. package/dist/dashboard/assets/formatters-DP-5E01-.js +1 -0
  76. package/dist/dashboard/assets/formatters-DP-5E01-.js.gz +0 -0
  77. package/dist/dashboard/assets/house-h9AZlJuT.js +1 -0
  78. package/dist/dashboard/assets/house-h9AZlJuT.js.gz +0 -0
  79. package/dist/dashboard/assets/{index-FQlj0HUT.js → index-D36vCZht.js} +2 -2
  80. package/dist/dashboard/assets/index-D36vCZht.js.gz +0 -0
  81. package/dist/dashboard/assets/{inline-C4gU5iOL.js → inline-CHJQwxLa.js} +1 -1
  82. package/dist/dashboard/assets/inline-CHJQwxLa.js.gz +0 -0
  83. package/dist/dashboard/assets/{input-BHHX_Tmm.js → input-BlmlizVV.js} +1 -1
  84. package/dist/dashboard/assets/input-BlmlizVV.js.gz +0 -0
  85. package/dist/dashboard/assets/{layout-CajrYUnD.js → layout-Bz93I-yE.js} +1 -1
  86. package/dist/dashboard/assets/layout-Bz93I-yE.js.gz +0 -0
  87. package/dist/dashboard/assets/{list-row-Bx2I7XUe.js → list-row-Ml16KCfu.js} +1 -1
  88. package/dist/dashboard/assets/list-row-Ml16KCfu.js.gz +0 -0
  89. package/dist/dashboard/assets/{live-DNNqRbj2.js → live-7_QpnYGu.js} +1 -1
  90. package/dist/dashboard/assets/live-7_QpnYGu.js.gz +0 -0
  91. package/dist/dashboard/assets/{mutation-Rr0TIvcj.js → mutation-GU7XdEjz.js} +1 -1
  92. package/dist/dashboard/assets/mutation-GU7XdEjz.js.gz +0 -0
  93. package/dist/dashboard/assets/{network-C5UBACrJ.js → network-CuMDaTzT.js} +1 -1
  94. package/dist/dashboard/assets/network-CuMDaTzT.js.gz +0 -0
  95. package/dist/dashboard/assets/numbers-CoqpaDMx.js +2 -0
  96. package/dist/dashboard/assets/numbers-CoqpaDMx.js.gz +0 -0
  97. package/dist/dashboard/assets/{pagination-Xsi9Qx04.js → pagination-B5aOoweZ.js} +1 -1
  98. package/dist/dashboard/assets/pagination-B5aOoweZ.js.gz +0 -0
  99. package/dist/dashboard/assets/providers/zai.webp +0 -0
  100. package/dist/dashboard/assets/providers/zcode.webp +0 -0
  101. package/dist/dashboard/assets/{query-keys-BloKtaXJ.js → query-keys-YSoQOUh6.js} +1 -1
  102. package/dist/dashboard/assets/{query-keys-BloKtaXJ.js.gz → query-keys-YSoQOUh6.js.gz} +0 -0
  103. package/dist/dashboard/assets/quota-DJ1QlAOq.js +1 -0
  104. package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
  105. package/dist/dashboard/assets/{routing-in0f_IfK.js → routing-fnCsxgNO.js} +1 -1
  106. package/dist/dashboard/assets/routing-fnCsxgNO.js.gz +0 -0
  107. package/dist/dashboard/assets/{select-BLULNqTp.js → select-C9Ssle9b.js} +2 -2
  108. package/dist/dashboard/assets/select-C9Ssle9b.js.gz +0 -0
  109. package/dist/dashboard/assets/{settings-CcGczj6I.js → settings-BxslsErn.js} +1 -1
  110. package/dist/dashboard/assets/settings-BxslsErn.js.gz +0 -0
  111. package/dist/dashboard/assets/{stack-D2lIR0tg.js → stack-j7cXjgYX.js} +1 -1
  112. package/dist/dashboard/assets/stack-j7cXjgYX.js.gz +0 -0
  113. package/dist/dashboard/assets/{state-DTARjfiW.js → state-DHmEnLa2.js} +1 -1
  114. package/dist/dashboard/assets/state-DHmEnLa2.js.gz +0 -0
  115. package/dist/dashboard/assets/{switch-c0oPSnqW.js → switch-DmZcyTIC.js} +1 -1
  116. package/dist/dashboard/assets/switch-DmZcyTIC.js.gz +0 -0
  117. package/dist/dashboard/assets/{useMutation-m8WGNPrz.js → useMutation-CbCP4w1o.js} +1 -1
  118. package/dist/dashboard/assets/useMutation-CbCP4w1o.js.gz +0 -0
  119. package/dist/dashboard/index.html +2 -2
  120. package/dist/dashboard/index.html.gz +0 -0
  121. package/dist/dashboard/precompressed-assets.json +57 -57
  122. package/migrations/full-postgres/0042_add_telemetry_degenerate_stop.sql +14 -0
  123. package/migrations/full-postgres/0043_add_provider_custom_user_agent.sql +14 -0
  124. package/migrations/lite-sqlite3/0042_add_telemetry_degenerate_stop.sql +12 -0
  125. package/migrations/lite-sqlite3/0043_add_provider_custom_user_agent.sql +10 -0
  126. package/package.json +3 -1
  127. package/src/boot/dependencies.ts +10 -2
  128. package/src/config/providers.ts +13 -7
  129. package/src/config/security.ts +1 -1
  130. package/src/config/spec.ts +31 -17
  131. package/src/config/usage.ts +27 -0
  132. package/src/console/api-keys/routes.ts +19 -23
  133. package/src/console/cli-tools/cartethyia-websearch-plugin.ts +186 -0
  134. package/src/console/cli-tools/contracts.ts +39 -295
  135. package/src/console/cli-tools/fs-ops.ts +29 -0
  136. package/src/console/cli-tools/injectors/claude.ts +3 -3
  137. package/src/console/cli-tools/injectors/codex.ts +6 -6
  138. package/src/console/cli-tools/injectors/driver.ts +8 -105
  139. package/src/console/cli-tools/injectors/kilo.ts +146 -21
  140. package/src/console/cli-tools/injectors/opencode.ts +68 -17
  141. package/src/console/cli-tools/routes.ts +6 -0
  142. package/src/console/cli-tools/service.ts +7 -6
  143. package/src/console/observability/contracts.ts +6 -0
  144. package/src/console/observability/store.ts +3 -0
  145. package/src/console/providers/catalog/contracts.ts +4 -0
  146. package/src/console/providers/catalog/routes.ts +1 -0
  147. package/src/console/providers/detail/routes.ts +8 -0
  148. package/src/console/providers/detail/store.ts +4 -0
  149. package/src/console/quota/account-quota-tenant.ts +74 -0
  150. package/src/console/share/share-stats.ts +7 -9
  151. package/src/observability/event-log.ts +7 -0
  152. package/src/observability/event-query.ts +2 -0
  153. package/src/observability/telemetry-buffer.ts +9 -1
  154. package/src/persistence/boot.ts +13 -4
  155. package/src/persistence/schema.ts +15 -1
  156. package/src/persistence/stores/api-key-store.ts +19 -1
  157. package/src/protocol/canonical-model.ts +7 -0
  158. package/src/protocol/dsml.ts +5 -76
  159. package/src/protocol/request/gemini.ts +20 -1
  160. package/src/protocol/tool-identity.ts +15 -1
  161. package/src/providers/adapters/compatible-adapter.ts +29 -1
  162. package/src/providers/default-registry.ts +20 -4
  163. package/src/providers/discovery/discovery-types.ts +18 -0
  164. package/src/providers/discovery/probe-phases.ts +45 -0
  165. package/src/providers/discovery/probing-service.ts +41 -6
  166. package/src/providers/integrations/antigravity/antigravity-protocol.ts +108 -16
  167. package/src/providers/integrations/antigravity/antigravity-quota.ts +18 -6
  168. package/src/providers/integrations/antigravity/antigravity.ts +44 -11
  169. package/src/providers/integrations/codex/codex.ts +28 -1
  170. package/src/providers/integrations/cursor/cursor.ts +2 -3
  171. package/src/providers/integrations/github/github-discovery.ts +15 -2
  172. package/src/providers/integrations/github/github-oauth.ts +178 -9
  173. package/src/providers/integrations/github/github.ts +95 -14
  174. package/src/providers/integrations/kiro/kiro.ts +1 -2
  175. package/src/providers/integrations/qoder/chat-proto.ts +328 -0
  176. package/src/providers/integrations/qoder/claim-qoder-credits.ts +258 -0
  177. package/src/providers/integrations/qoder/qoder-adapter.ts +275 -0
  178. package/src/providers/integrations/qoder/qoder-auth.ts +201 -0
  179. package/src/providers/integrations/qoder/qoder-body.ts +78 -0
  180. package/src/providers/integrations/qoder/qoder-catalog.ts +14 -4
  181. package/src/providers/integrations/qoder/qoder-chat.ts +514 -0
  182. package/src/providers/integrations/qoder/qoder-cosy.ts +162 -0
  183. package/src/providers/integrations/qoder/qoder-grpc.ts +667 -0
  184. package/src/providers/integrations/qoder/qoder-ide.ts +584 -0
  185. package/src/providers/integrations/qoder/qoder-oauth.ts +4 -4
  186. package/src/providers/integrations/qoder/qoder-quota.ts +1 -1
  187. package/src/providers/integrations/qoder/qoder-realm.ts +40 -4
  188. package/src/providers/integrations/qoder/qoder-types.ts +51 -0
  189. package/src/providers/integrations/qoder/qoder-umid.ts +123 -8
  190. package/src/providers/integrations/qoder/qoder.ts +147 -1830
  191. package/src/providers/integrations/trae/trae.ts +2 -2
  192. package/src/providers/operations/account-error-classification.ts +67 -17
  193. package/src/providers/operations/byok-wire-profile.ts +19 -15
  194. package/src/providers/provider-contracts.ts +7 -0
  195. package/src/providers/provider-usage.ts +24 -55
  196. package/src/security/abuse.ts +5 -5
  197. package/src/security/admission/service.ts +11 -4
  198. package/src/security/api-key-auth.ts +5 -3
  199. package/src/transport/dispatch/attempt-finalize.ts +69 -21
  200. package/src/transport/dispatch/attempt-loop.ts +22 -0
  201. package/src/transport/dispatch/upstream.ts +4 -2
  202. package/src/transport/failure-policy.ts +15 -1
  203. package/src/transport/guards/error-lifecycle.ts +4 -0
  204. package/src/transport/request/state.ts +6 -0
  205. package/src/transport/routing/route-catalog.ts +8 -0
  206. package/src/transport/routing/route-model.ts +4 -0
  207. package/src/transport/routing/router.ts +8 -110
  208. package/src/transport/surface/reasoning-summary.ts +9 -14
  209. package/src/transport/vision-handoff.ts +83 -28
  210. package/dist/dashboard/assets/BannedPage-Cj15GsMp.js +0 -1
  211. package/dist/dashboard/assets/BannedPage-Cj15GsMp.js.gz +0 -0
  212. package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js +0 -1
  213. package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js.gz +0 -0
  214. package/dist/dashboard/assets/CliToolDetailPage-DZKmVuJN.js +0 -1
  215. package/dist/dashboard/assets/CliToolDetailPage-DZKmVuJN.js.gz +0 -0
  216. package/dist/dashboard/assets/CliToolsPage-BIccZmeL.js +0 -1
  217. package/dist/dashboard/assets/CliToolsPage-BIccZmeL.js.gz +0 -0
  218. package/dist/dashboard/assets/CombosPage-DEEKkYD-.js +0 -1
  219. package/dist/dashboard/assets/CombosPage-DEEKkYD-.js.gz +0 -0
  220. package/dist/dashboard/assets/ConsoleLogPage-D5rol2j2.js.gz +0 -0
  221. package/dist/dashboard/assets/CustomizationPage-BKj_r0rp.js.gz +0 -0
  222. package/dist/dashboard/assets/InjectSystemPromptPanel-B5nZvmf0.js.gz +0 -0
  223. package/dist/dashboard/assets/LoginPage-DkXb6dx-.js +0 -1
  224. package/dist/dashboard/assets/LoginPage-DkXb6dx-.js.gz +0 -0
  225. package/dist/dashboard/assets/ModelPicker-DnQPSIA8.js.gz +0 -0
  226. package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js +0 -1
  227. package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js.gz +0 -0
  228. package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js +0 -5
  229. package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js.gz +0 -0
  230. package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js +0 -1
  231. package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js.gz +0 -0
  232. package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js +0 -1
  233. package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js.gz +0 -0
  234. package/dist/dashboard/assets/ProxyPage-5SnUSy3j.js.gz +0 -0
  235. package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js +0 -1
  236. package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js.gz +0 -0
  237. package/dist/dashboard/assets/SettingsPage-BJtRZ4qD.js.gz +0 -0
  238. package/dist/dashboard/assets/SetupPage-WTa0sktC.js +0 -1
  239. package/dist/dashboard/assets/SetupPage-WTa0sktC.js.gz +0 -0
  240. package/dist/dashboard/assets/StudioPage-BfAb5-o_.js.gz +0 -0
  241. package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js +0 -1
  242. package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js.gz +0 -0
  243. package/dist/dashboard/assets/UsagePage-BNzeZ2Vk.js.gz +0 -0
  244. package/dist/dashboard/assets/api-keys-l29JROPv.js +0 -1
  245. package/dist/dashboard/assets/api-keys-l29JROPv.js.gz +0 -0
  246. package/dist/dashboard/assets/app--PJWq8_C.css +0 -2
  247. package/dist/dashboard/assets/app--PJWq8_C.css.gz +0 -0
  248. package/dist/dashboard/assets/app-Bgd3zANQ.js +0 -9
  249. package/dist/dashboard/assets/app-Bgd3zANQ.js.gz +0 -0
  250. package/dist/dashboard/assets/assert-C0K-c4eo.js.gz +0 -0
  251. package/dist/dashboard/assets/badge-DfbhQU4k.js.gz +0 -0
  252. package/dist/dashboard/assets/banner-BllT4LcF.js.gz +0 -0
  253. package/dist/dashboard/assets/card-CtkX66Wd.js.gz +0 -0
  254. package/dist/dashboard/assets/cli-tools-CQXVMn4P.js.gz +0 -0
  255. package/dist/dashboard/assets/clipboard-button-BQqBQd2n.js.gz +0 -0
  256. package/dist/dashboard/assets/dialog-BEK7KP0H.js +0 -1
  257. package/dist/dashboard/assets/dialog-BEK7KP0H.js.gz +0 -0
  258. package/dist/dashboard/assets/drawer-BrTEZ8zu.js.gz +0 -0
  259. package/dist/dashboard/assets/entry-CHWxbS56.js +0 -1
  260. package/dist/dashboard/assets/entry-CHWxbS56.js.gz +0 -0
  261. package/dist/dashboard/assets/entry-EBHJ3DrE.js +0 -1
  262. package/dist/dashboard/assets/entry-EBHJ3DrE.js.gz +0 -0
  263. package/dist/dashboard/assets/entry-LCS85hiq.js +0 -4
  264. package/dist/dashboard/assets/entry-LCS85hiq.js.gz +0 -0
  265. package/dist/dashboard/assets/formatters-BAACA727.js +0 -1
  266. package/dist/dashboard/assets/formatters-BAACA727.js.gz +0 -0
  267. package/dist/dashboard/assets/github-badge-YSCE4UC2.js +0 -1
  268. package/dist/dashboard/assets/github-badge-YSCE4UC2.js.gz +0 -0
  269. package/dist/dashboard/assets/index-FQlj0HUT.js.gz +0 -0
  270. package/dist/dashboard/assets/inline-C4gU5iOL.js.gz +0 -0
  271. package/dist/dashboard/assets/input-BHHX_Tmm.js.gz +0 -0
  272. package/dist/dashboard/assets/layout-CajrYUnD.js.gz +0 -0
  273. package/dist/dashboard/assets/list-row-Bx2I7XUe.js.gz +0 -0
  274. package/dist/dashboard/assets/live-DNNqRbj2.js.gz +0 -0
  275. package/dist/dashboard/assets/mutation-Rr0TIvcj.js.gz +0 -0
  276. package/dist/dashboard/assets/network-C5UBACrJ.js.gz +0 -0
  277. package/dist/dashboard/assets/numbers-CUWAZK4J.js +0 -2
  278. package/dist/dashboard/assets/numbers-CUWAZK4J.js.gz +0 -0
  279. package/dist/dashboard/assets/pagination-Xsi9Qx04.js.gz +0 -0
  280. package/dist/dashboard/assets/providers/zai-coding-plan.svg +0 -6
  281. package/dist/dashboard/assets/providers/zai-coding-plan.svg.gz +0 -0
  282. package/dist/dashboard/assets/quota-DvQbD_sr.js +0 -1
  283. package/dist/dashboard/assets/quota-DvQbD_sr.js.gz +0 -0
  284. package/dist/dashboard/assets/routing-in0f_IfK.js.gz +0 -0
  285. package/dist/dashboard/assets/select-BLULNqTp.js.gz +0 -0
  286. package/dist/dashboard/assets/settings-CcGczj6I.js.gz +0 -0
  287. package/dist/dashboard/assets/stack-D2lIR0tg.js.gz +0 -0
  288. package/dist/dashboard/assets/state-DTARjfiW.js.gz +0 -0
  289. package/dist/dashboard/assets/switch-c0oPSnqW.js.gz +0 -0
  290. package/dist/dashboard/assets/useMutation-m8WGNPrz.js.gz +0 -0
  291. package/src/console/cli-tools/injectors/cline.ts +0 -110
  292. package/src/console/cli-tools/injectors/copilot.ts +0 -108
  293. package/src/console/cli-tools/injectors/cowork.ts +0 -64
  294. package/src/console/cli-tools/injectors/deepseek-tui.ts +0 -85
  295. package/src/console/cli-tools/injectors/droid.ts +0 -96
  296. package/src/console/cli-tools/injectors/grok-build.ts +0 -70
  297. package/src/console/cli-tools/injectors/hermes.ts +0 -101
  298. package/src/console/cli-tools/injectors/jcode.ts +0 -115
  299. package/src/console/cli-tools/injectors/openclaw.ts +0 -161
  300. package/src/providers/integrations/qoder/qoder-text-tools.ts +0 -277
@@ -0,0 +1,186 @@
1
+ /**
2
+ * OpenCode websearch provider plugin generation.
3
+ *
4
+ * The Console writes `websearch.provider = "<provider>"` into the user's
5
+ * opencode.jsonc. That value resolves to a WebSearch provider the local plugin
6
+ * below registers with OpenCode. The plugin reads the gateway URL and API key
7
+ * from the matching provider entry in opencode.jsonc — the same provider block
8
+ * the injector writes — so the search route follows the configured provider.
9
+ *
10
+ * OpenCode loads every `.js` file under `~/.config/opencode/plugins/`
11
+ * (or `<config-dir>/plugins/`) as a plugin; no registration step is needed
12
+ * beyond writing the file.
13
+ */
14
+ import { ensureDir, fileExists, join, removeFile, writeTextFileWithBackup } from "./fs-ops";
15
+
16
+ const PLUGIN_FILENAME = (providerName: string) => `${providerName}-websearch.js`;
17
+
18
+ /** Escapes the provider name for use inside a JS double-quoted string. */
19
+ function jsStringLiteral(value: string): string {
20
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
21
+ }
22
+
23
+ /**
24
+ * Renders the websearch plugin source for one provider name.
25
+ *
26
+ * `providerName` is the exact provider key written into opencode.jsonc (e.g.
27
+ * `Cartethyia`, `CarteLocal`). The plugin reads that block's `baseURL` and
28
+ * `apiKey` as the single source of truth, so no secret is duplicated anywhere.
29
+ */
30
+ export function renderWebsearchPlugin(providerName: string): string {
31
+ const quoted = jsStringLiteral(providerName);
32
+ return `// ${quoted}-websearch — route OpenCode's built-in websearch tool through the
33
+ // Cartethyia gateway's /v1/search native route, using the "${quoted}"
34
+ // provider block in opencode.jsonc as the single source of truth.
35
+ //
36
+ // Generated by the Cartethyia console (CLI tools → OpenCode). Do not edit by
37
+ // hand; re-apply the tool config to regenerate.
38
+ //
39
+ // Env overrides (all optional):
40
+ // CARTETHYIA_SEARCH_URL (default: the provider's baseURL + /search)
41
+ // CARTETHYIA_SEARCH_API_KEY (default: the provider's apiKey)
42
+ // CARTETHYIA_SEARCH_BACKEND (default: "exa"; "parallel" also works)
43
+
44
+ import { Plugin } from "@opencode/plugin";
45
+ import { readFileSync } from "node:fs";
46
+ import { homedir } from "node:os";
47
+ import { join } from "node:path";
48
+
49
+ function pick(obj, keys) {
50
+ for (const k of keys) {
51
+ const v = obj?.[k];
52
+ if (v !== undefined && v !== null) return v;
53
+ }
54
+ return undefined;
55
+ }
56
+
57
+ // Pull the "${quoted}" provider's baseURL/apiKey out of opencode.jsonc.
58
+ // The file is JSONC (comments allowed) so search for the provider block
59
+ // loosely rather than parsing strictly.
60
+ function readProviderSettings() {
61
+ const candidates = [
62
+ process.env.OPENCODE_CONFIG,
63
+ join(homedir(), ".config", "opencode", "opencode.jsonc"),
64
+ join(homedir(), ".config", "opencode", "opencode.json"),
65
+ ];
66
+ for (const path of candidates) {
67
+ if (!path) continue;
68
+ let text;
69
+ try {
70
+ text = readFileSync(path, "utf8");
71
+ } catch {
72
+ continue;
73
+ }
74
+ // Find the provider block, then the next provider block or closing brace.
75
+ const providerStart = text.indexOf(\`"\${PROVIDER_NAME}"\`);
76
+ if (providerStart < 0) continue;
77
+ const settingsMatch = text.slice(providerStart).match(/"settings"\\s*:\\s*\\{/);
78
+ if (!settingsMatch) continue;
79
+ const settingsStart = providerStart + settingsMatch.index + settingsMatch[0].length - 1;
80
+ let depth = 0;
81
+ let end = -1;
82
+ for (let i = settingsStart; i < text.length; i++) {
83
+ const ch = text[i];
84
+ if (ch === "{") depth += 1;
85
+ else if (ch === "}") {
86
+ depth -= 1;
87
+ if (depth === 0) {
88
+ end = i;
89
+ break;
90
+ }
91
+ }
92
+ }
93
+ if (end < 0) continue;
94
+ const settings = text.slice(settingsStart, end + 1);
95
+ const baseURL = settings.match(/"baseURL"\\s*:\\s*"([^"]+)"/)?.[1];
96
+ const apiKey = settings.match(/"apiKey"\\s*:\\s*"([^"]+)"/)?.[1];
97
+ if (baseURL) {
98
+ return { baseURL, apiKey };
99
+ }
100
+ }
101
+ return { baseURL: undefined, apiKey: undefined };
102
+ }
103
+
104
+ const PROVIDER_NAME = "${quoted}";
105
+ const local = readProviderSettings();
106
+ const BASE_URL = (process.env.CARTETHYIA_SEARCH_URL || (local.baseURL ? \`\${local.baseURL.replace(/\\/+$/, "")}/search\` : "")).replace(/\\/+$/, "");
107
+ const API_KEY = process.env.CARTETHYIA_SEARCH_API_KEY || local.apiKey || "";
108
+ const BACKEND = process.env.CARTETHYIA_SEARCH_BACKEND || "exa";
109
+
110
+ export default Plugin.define({
111
+ id: "${quoted}-websearch",
112
+ async setup(ctx) {
113
+ await ctx.websearch.transform((editor) => {
114
+ editor.add({
115
+ id: "${quoted}",
116
+ name: "Cartethyia Search",
117
+ execute: async ({ query }, { signal }) => {
118
+ if (!BASE_URL) {
119
+ throw new Error(
120
+ "Cartethyia search gateway is not configured: set CARTETHYIA_SEARCH_URL or add a ${quoted} provider with baseURL in opencode.jsonc",
121
+ );
122
+ }
123
+ const response = await fetch(BASE_URL, {
124
+ method: "POST",
125
+ headers: {
126
+ "content-type": "application/json",
127
+ ...(API_KEY ? { authorization: \`Bearer \${API_KEY}\` } : {}),
128
+ },
129
+ body: JSON.stringify({
130
+ model: BACKEND,
131
+ query,
132
+ max_results: 8,
133
+ }),
134
+ signal,
135
+ });
136
+ if (!response.ok) {
137
+ const text = await response.text().catch(() => "");
138
+ throw new Error(
139
+ \`Cartethyia search failed (HTTP \${response.status}): \${text.slice(0, 200)}\`,
140
+ );
141
+ }
142
+ const data = await response.json();
143
+ const results = Array.isArray(data?.results) ? data.results : [];
144
+ return results.map((item) => ({
145
+ url: String(item?.url ?? ""),
146
+ title: typeof item?.title === "string" && item.title ? item.title : undefined,
147
+ content: typeof item?.snippet === "string" && item.snippet ? item.snippet : undefined,
148
+ time:
149
+ typeof item?.published_at === "string" && item.published_at
150
+ ? { published: Date.parse(item.published_at) || undefined }
151
+ : {},
152
+ }));
153
+ },
154
+ });
155
+ editor.default.set("${quoted}");
156
+ });
157
+ },
158
+ });
159
+ `;
160
+ }
161
+
162
+ /** Writes the websearch plugin for a provider into the OpenCode config dir. */
163
+ export async function writeWebsearchPlugin(
164
+ configDir: string,
165
+ providerName: string,
166
+ ): Promise<string> {
167
+ const pluginsDir = join(configDir, "plugins");
168
+ await ensureDir(pluginsDir);
169
+ const filename = PLUGIN_FILENAME(providerName);
170
+ const target = join(pluginsDir, filename);
171
+ await writeTextFileWithBackup(target, renderWebsearchPlugin(providerName));
172
+ return target;
173
+ }
174
+
175
+ /** Removes the websearch plugin for a provider from the OpenCode config dir. */
176
+ export async function removeWebsearchPlugin(
177
+ configDir: string,
178
+ providerName: string,
179
+ ): Promise<boolean> {
180
+ const target = join(configDir, "plugins", PLUGIN_FILENAME(providerName));
181
+ if (await fileExists(target)) {
182
+ await removeFile(target);
183
+ return true;
184
+ }
185
+ return false;
186
+ }
@@ -6,12 +6,11 @@ import { ensureV1Suffix } from "./fs-ops";
6
6
  * CLI Tools type contracts — shared across registry, injectors, service,
7
7
  * dashboard, and API routes.
8
8
  *
9
- * Each supported CLI coding agent ([CC], Codex, Cline, etc.) has a `ToolDef`
10
- * with static metadata and (when `configType !== "guide"`) a paired
11
- * `ToolInjector` that reads and writes the tool's config files on the host
12
- * filesystem. The dashboard picks an API key + models, POSTs to the backend,
13
- * and the injector merges Cartethyia-specific fields into the tool's config
14
- * without clobbering user settings.
9
+ * Each supported CLI coding agent ([CC], Codex, Kilo, OpenCode) has a `ToolDef`
10
+ * with static metadata and a paired `ToolInjector` that reads and writes the
11
+ * tool's config files on the host filesystem. The dashboard picks an API key +
12
+ * models, POSTs to the backend, and the injector merges Cartethyia-specific
13
+ * fields into the tool's config without clobbering user settings.
15
14
  */
16
15
 
17
16
  /**
@@ -22,7 +21,7 @@ import { ensureV1Suffix } from "./fs-ops";
22
21
  type CliToolSurface = "chat" | "responses" | "messages";
23
22
 
24
23
  /** How the tool's config is managed. */
25
- type ConfigType = "env" | "custom" | "guide";
24
+ type ConfigType = "env" | "custom";
26
25
 
27
26
  /** A native model slot exposed by a CLI tool. */
28
27
  interface ToolModelDef {
@@ -53,6 +52,13 @@ export interface CliMappingInput {
53
52
  /** The user-facing routing mode for a CLI tool: `remote` routes are resolved by the gateway, `local` routes are baked into the tool's own config file. */
54
53
  export type CliMappingMode = "remote" | "local";
55
54
 
55
+ /**
56
+ * The default runtime SDK package for OpenCode: its native
57
+ * `@opencode/ai/providers/openai-compatible` package (bundled with OpenCode
58
+ * itself, not the AI SDK).
59
+ */
60
+ export const DEFAULT_SDK: string = "@opencode/ai/providers/openai-compatible";
61
+
56
62
  /** Persisted mapping settings for one CLI tool + API key pair. */
57
63
  export interface CliMappingSettings {
58
64
  readonly toolId: string;
@@ -68,21 +74,6 @@ interface ToolNote {
68
74
  readonly text: string;
69
75
  }
70
76
 
71
- /** A guide step for guide-only tools. */
72
- interface GuideStep {
73
- readonly step: number;
74
- readonly title: string;
75
- readonly desc?: string;
76
- readonly value?: string;
77
- readonly type?: "apiKeySelector" | "modelSelector";
78
- }
79
-
80
- /** A code block for guide-only tools. */
81
- interface GuideCodeBlock {
82
- readonly language: string;
83
- readonly code: string;
84
- }
85
-
86
77
  /** Static metadata for a CLI tool — consumed by both backend and frontend. */
87
78
  export interface ToolDef {
88
79
  readonly id: string;
@@ -95,8 +86,6 @@ export interface ToolDef {
95
86
  readonly settingsFile?: string;
96
87
  readonly docsUrl?: string;
97
88
  readonly notes?: readonly ToolNote[];
98
- readonly guideSteps?: readonly GuideStep[];
99
- readonly codeBlock?: GuideCodeBlock;
100
89
  /**
101
90
  * How this tool's slot→route mapping behaves, which also decides whether the
102
91
  * tool has a mapping surface at all.
@@ -109,9 +98,7 @@ export interface ToolDef {
109
98
  * so a slot has one value (written to the file) rather than a separate
110
99
  * target. There is nothing to persist; the model is baked into the file.
111
100
  *
112
- * Absent means the tool exposes no configurable model slots at all (a
113
- * guide-only tool). This replaces a separate `mappingSupported` flag that
114
- * had to be kept consistent with the mode by hand — the mode already says it.
101
+ * Absent means the tool exposes no configurable model slots at all.
115
102
  */
116
103
  readonly mappingMode?: CliMappingMode;
117
104
  }
@@ -137,8 +124,6 @@ export type ToolRegistryEntry = Pick<
137
124
  | "settingsFile"
138
125
  | "docsUrl"
139
126
  | "notes"
140
- | "guideSteps"
141
- | "codeBlock"
142
127
  > & { readonly mappingSupported: boolean };
143
128
 
144
129
  /** Runtime status of a CLI tool on the host. */
@@ -175,6 +160,17 @@ export interface ApplyInput {
175
160
  readonly mapping?: CliMappingInput;
176
161
  /** Enables [CC]'s bypass-permissions (YOLO) mode when explicitly selected. */
177
162
  readonly bypassPermissions?: boolean;
163
+ /**
164
+ * The runtime SDK package to write into the tool's config (e.g. OpenCode's
165
+ * `provider.*.npm`). Absent = the tool's default.
166
+ */
167
+ readonly sdk?: string;
168
+ /**
169
+ * WebSearch provider selection written into OpenCode's config. "carte"
170
+ * routes search through the Cartethyia gateway; "native" (or absent) leaves
171
+ * OpenCode's own provider selection in place.
172
+ */
173
+ readonly websearch?: string;
178
174
  }
179
175
 
180
176
  /** Result of an apply or reset operation. */
@@ -182,6 +178,8 @@ export interface ApplyResult {
182
178
  readonly success: boolean;
183
179
  readonly settingsPath?: string;
184
180
  readonly message: string;
181
+ /** Non-fatal notes for the operator, e.g. preserved pre-existing providers. */
182
+ readonly warnings?: readonly string[];
185
183
  }
186
184
 
187
185
  /**
@@ -195,9 +193,9 @@ export interface ApplyResult {
195
193
  * persisted routing rows on each `/v1/*` call. Works when the gateway is
196
194
  * remote, containerised, or the CLI lives on another machine.
197
195
  * - `both` — the tool's config was written and a remote route was recorded.
198
- * - `none` — nothing ran: the tool has no injectable config (a guide tool) and
199
- * the request asked for no remote route. Reported rather than folded into
200
- * `file`, so the UI never claims a write that did not happen.
196
+ * - `none` — nothing ran: the tool has no injectable config and the request
197
+ * asked for no remote route. Reported rather than folded into `file`, so
198
+ * the UI never claims a write that did not happen.
201
199
  */
202
200
  export type ApplyOutcome = "file" | "remote" | "both" | "none";
203
201
 
@@ -210,6 +208,8 @@ export interface ApplyConfigResult {
210
208
  readonly savedRemoteRoute: boolean;
211
209
  readonly settingsPath?: string;
212
210
  readonly message: string;
211
+ /** Non-fatal notes for the operator (see `ApplyResult.warnings`). */
212
+ readonly warnings?: readonly string[];
213
213
  }
214
214
 
215
215
  /** Result of a download-config operation. */
@@ -220,8 +220,7 @@ export interface DownloadResult {
220
220
  }
221
221
 
222
222
  /**
223
- * Injector contract — one per file-based tool.
224
- * Guide-only tools share a single GuideInjector that generates config text.
223
+ * Injector contract — one per tool.
225
224
  */
226
225
  export interface ToolInjector {
227
226
  readonly toolId: string;
@@ -325,8 +324,12 @@ export interface InjectorSpec {
325
324
  /** Extract status details from existing settings file(s) */
326
325
  readonly readStatus: (path: string) => Promise<StatusDetails> | StatusDetails;
327
326
 
328
- /** Apply Cartethyia configuration to the settings file(s) */
329
- readonly apply: (input: ApplyInput, path: string) => Promise<void> | void;
327
+ /**
328
+ * Apply Cartethyia configuration to the settings file(s). Return an array of
329
+ * non-fatal warning strings to surface them to the operator (e.g. preserved
330
+ * pre-existing providers).
331
+ */
332
+ readonly apply: (input: ApplyInput, path: string) => Promise<readonly string[] | void> | readonly string[] | void;
330
333
 
331
334
  /**
332
335
  * Reset Cartethyia configuration from settings file(s).
@@ -449,32 +452,6 @@ export const TOOL_REGISTRY = {
449
452
  ],
450
453
  },
451
454
 
452
- cline: {
453
- id: "cline",
454
- name: "Cline",
455
- color: "#00D1B2",
456
- description: "Cline AI Coding Assistant (VS Code)",
457
- configType: "custom" as const,
458
- surface: "chat" as const,
459
- settingsFile: "~/.cline/data/globalState.json",
460
- defaultModels: [
461
- {
462
- id: "claude/claude-sonnet-5",
463
- name: "Claude Sonnet 5",
464
- alias: "sonnet",
465
- roleLabel: "Primary Agent",
466
- roleKind: "primary",
467
- },
468
- {
469
- id: "claude/claude-opus-4-8",
470
- name: "Claude Opus",
471
- alias: "opus",
472
- roleLabel: "Advanced Agent",
473
- roleKind: "secondary",
474
- },
475
- ],
476
- },
477
-
478
455
  opencode: {
479
456
  id: "opencode",
480
457
  name: "OpenCode",
@@ -501,86 +478,6 @@ export const TOOL_REGISTRY = {
501
478
  ],
502
479
  },
503
480
 
504
- droid: {
505
- id: "droid",
506
- name: "Factory Droid",
507
- color: "#00D4FF",
508
- description: "Factory Droid AI Assistant",
509
- configType: "custom" as const,
510
- surface: "chat" as const,
511
- settingsFile: "~/.factory/settings.json",
512
- defaultModels: [
513
- { id: "claude/claude-sonnet-5", name: "Claude Sonnet 5", alias: "sonnet" },
514
- { id: "gpt-5.6-sol", name: "GPT-5.6 Sol", alias: "gpt-5.6-sol" },
515
- ],
516
- },
517
-
518
- hermes: {
519
- id: "hermes",
520
- name: "Hermes Agent",
521
- color: "#8B5CF6",
522
- description: "Nous Research self-improving AI agent",
523
- configType: "custom" as const,
524
- surface: "chat" as const,
525
- settingsFile: "~/.hermes/config.yaml",
526
- defaultModels: [{ id: "claude/claude-sonnet-5", name: "Claude Sonnet 5", alias: "sonnet" }],
527
- },
528
-
529
- "grok-build": {
530
- id: "grok-build",
531
- name: "Grok Build",
532
- color: "#1DA1F2",
533
- description: "xAI Grok Build CLI",
534
- configType: "custom" as const,
535
- surface: "chat" as const,
536
- settingsFile: "~/.grok/config.toml",
537
- defaultModels: [{ id: "grok/grok-4", name: "Grok 4", alias: "grok-4" }],
538
- },
539
-
540
- copilot: {
541
- id: "copilot",
542
- name: "GitHub Copilot",
543
- color: "#1F6FEB",
544
- description: "GitHub Copilot Chat (VS Code chatLanguageModels.json)",
545
- configType: "custom" as const,
546
- surface: "chat" as const,
547
- settingsFile: "Code/User/chatLanguageModels.json",
548
- defaultModels: [
549
- { id: "gpt-5.6-sol", name: "GPT-5.6 Sol", alias: "gpt-5.6-sol" },
550
- { id: "claude/claude-sonnet-5", name: "Claude Sonnet 5", alias: "sonnet" },
551
- ],
552
- },
553
-
554
- "deepseek-tui": {
555
- id: "deepseek-tui",
556
- name: "DeepSeek TUI",
557
- color: "#4D6BFE",
558
- description: "DeepSeek Terminal Coding Agent (Rust TUI)",
559
- configType: "custom" as const,
560
- surface: "chat" as const,
561
- settingsFile: "~/.deepseek/config.toml",
562
- defaultModels: [
563
- { id: "deepseek/deepseek-chat", name: "DeepSeek Chat", alias: "deepseek-chat" },
564
- {
565
- id: "deepseek/deepseek-reasoner",
566
- name: "DeepSeek Reasoner",
567
- alias: "deepseek-reasoner",
568
- },
569
- ],
570
- },
571
-
572
- jcode: {
573
- id: "jcode",
574
- name: "jcode",
575
- color: "#FF6B35",
576
- description: "High-performance Rust-based coding agent harness",
577
- configType: "custom" as const,
578
- surface: "chat" as const,
579
- settingsFile: "~/.jcode/config.toml",
580
- docsUrl: "https://github.com/1jehuang/jcode",
581
- defaultModels: [{ id: "claude/claude-sonnet-5", name: "Claude Sonnet 5", alias: "sonnet" }],
582
- },
583
-
584
481
  kilo: {
585
482
  id: "kilo",
586
483
  name: "Kilo Code",
@@ -591,159 +488,6 @@ export const TOOL_REGISTRY = {
591
488
  settingsFile: "~/.local/share/kilo/auth.json",
592
489
  defaultModels: [{ id: "claude/claude-sonnet-5", name: "Claude Sonnet 5", alias: "sonnet" }],
593
490
  },
594
-
595
- openclaw: {
596
- id: "openclaw",
597
- name: "Open Claw",
598
- color: "#FF6B35",
599
- description: "Open Claw AI Assistant",
600
- configType: "custom" as const,
601
- surface: "messages" as const,
602
- settingsFile: "~/.openclaw/openclaw.json",
603
- defaultModels: [{ id: "claude/claude-sonnet-5", name: "Claude Sonnet 5", alias: "sonnet" }],
604
- },
605
-
606
- cowork: {
607
- id: "cowork",
608
- name: "Claude Cowork",
609
- color: "#D97757",
610
- description: "Claude Desktop Cowork (third-party inference)",
611
- configType: "custom" as const,
612
- surface: "messages" as const,
613
- settingsFile: "Claude/configLibrary/_meta.json",
614
- defaultModels: [{ id: "claude/claude-sonnet-5", name: "Claude Sonnet 5", alias: "sonnet" }],
615
- },
616
- // ── Guide-only tools (no fs injection — show steps + downloadable config) ──
617
- roo: {
618
- id: "roo",
619
- name: "Roo",
620
- color: "#FF6B6B",
621
- description: "Roo AI Assistant",
622
- configType: "guide" as const,
623
- surface: "chat" as const,
624
- defaultModels: [{ id: "claude/claude-sonnet-5", name: "Claude Sonnet 5", alias: "sonnet" }],
625
- guideSteps: [
626
- { step: 1, title: "Open Settings", desc: "Go to Roo Settings panel" },
627
- { step: 2, title: "Select Provider", desc: "Choose API Provider → Ollama" },
628
- { step: 3, title: "Base URL", value: "{{baseUrl}}" },
629
- { step: 4, title: "API Key", type: "apiKeySelector" },
630
- { step: 5, title: "Select Model", type: "modelSelector" },
631
- ],
632
- },
633
-
634
- continue: {
635
- id: "continue",
636
- name: "Continue",
637
- color: "#7C3AED",
638
- description: "Continue AI Assistant",
639
- configType: "guide" as const,
640
- surface: "chat" as const,
641
- defaultModels: [{ id: "claude/claude-sonnet-5", name: "Claude Sonnet 5", alias: "sonnet" }],
642
- guideSteps: [
643
- { step: 1, title: "Open Config", desc: "Open Continue configuration file" },
644
- { step: 2, title: "API Key", type: "apiKeySelector" },
645
- { step: 3, title: "Select Model", type: "modelSelector" },
646
- {
647
- step: 4,
648
- title: "Add Model Config",
649
- desc: "Add the following configuration to your models array:",
650
- },
651
- ],
652
- codeBlock: {
653
- language: "json",
654
- code: `{
655
- "apiBase": "{{baseUrl}}",
656
- "title": "{{model}}",
657
- "model": "{{model}}",
658
- "provider": "openai",
659
- "apiKey": "{{apiKey}}"
660
- }`,
661
- },
662
- },
663
-
664
- amp: {
665
- id: "amp",
666
- name: "Amp CLI",
667
- color: "#F97316",
668
- description: "Sourcegraph Amp coding assistant CLI",
669
- configType: "guide" as const,
670
- surface: "chat" as const,
671
- docsUrl: "/docs?section=cli-tools&tool=amp",
672
- defaultModels: [{ id: "claude/claude-sonnet-5", name: "Claude Sonnet 5", alias: "sonnet" }],
673
- notes: [
674
- {
675
- type: "info",
676
- text: "Use Cartethyia model aliases to keep Amp shorthand mappings stable across provider updates.",
677
- },
678
- ],
679
- guideSteps: [
680
- {
681
- step: 1,
682
- title: "Install Amp",
683
- desc: "Install the Amp CLI using the package manager supported by your environment.",
684
- },
685
- { step: 2, title: "API Key", type: "apiKeySelector" },
686
- { step: 3, title: "Base URL", value: "{{baseUrl}}" },
687
- { step: 4, title: "Select Model", type: "modelSelector" },
688
- {
689
- step: 5,
690
- title: "Add Shorthands",
691
- desc: "Map Amp shorthand names to Cartethyia aliases in your local config.",
692
- },
693
- ],
694
- codeBlock: {
695
- language: "bash",
696
- code: `export OPENAI_API_KEY="{{apiKey}}"
697
- export OPENAI_BASE_URL="{{baseUrl}}"
698
- amp --model "{{model}}"`,
699
- },
700
- },
701
-
702
- qwen: {
703
- id: "qwen",
704
- name: "Qwen Code",
705
- color: "#10B981",
706
- description: "Alibaba Qwen Code CLI — OpenAI-compatible via Cartethyia",
707
- configType: "guide" as const,
708
- surface: "chat" as const,
709
- docsUrl: "https://qwenlm.github.io/qwen-code-docs/en/users/configuration/model-providers/",
710
- defaultModels: [
711
- { id: "claude/claude-sonnet-5", name: "Claude Sonnet 5", alias: "sonnet" },
712
- { id: "gpt-5.6-sol", name: "GPT-5.6 Sol", alias: "gpt-5.6-sol" },
713
- ],
714
- notes: [
715
- {
716
- type: "warning",
717
- text: "Config path: Linux/macOS ~/.qwen/settings.json - Windows %USERPROFILE%\\.qwen\\settings.json",
718
- },
719
- ],
720
- guideSteps: [
721
- { step: 1, title: "Install Qwen Code", desc: "npm install -g @qwen-code/qwen-code" },
722
- { step: 2, title: "API Key", type: "apiKeySelector" },
723
- { step: 3, title: "Base URL", value: "{{baseUrl}}" },
724
- { step: 4, title: "Select Model", type: "modelSelector" },
725
- {
726
- step: 5,
727
- title: "Save Config",
728
- desc: "Copy the JSON below to your ~/.qwen/settings.json file.",
729
- },
730
- ],
731
- codeBlock: {
732
- language: "json",
733
- code: `{
734
- "security": {
735
- "auth": {
736
- "selectedType": "openai",
737
- "apiKey": "{{apiKey}}",
738
- "baseUrl": "{{baseUrl}}"
739
- }
740
- },
741
- "model": {
742
- "name": "{{model}}"
743
- }
744
- }`,
745
- },
746
- },
747
491
  } satisfies Record<string, ToolDef>;
748
492
 
749
493
  export type ToolId = keyof typeof TOOL_REGISTRY;
@@ -54,6 +54,17 @@ export async function writeJsonFile(path: string, data: unknown): Promise<void>
54
54
  await Bun.write(path, JSON.stringify(data, null, 2));
55
55
  }
56
56
 
57
+ /**
58
+ * Write JSON to a file, but first preserve the current contents at `path.bak`
59
+ * when the file already exists. Callers use this for tools whose config is
60
+ * precious (anything the user hand-edited): a broken merge must never destroy
61
+ * the only copy of that file, and the operator can always restore `.bak`.
62
+ */
63
+ export async function writeJsonFileWithBackup(path: string, data: unknown): Promise<void> {
64
+ await backupExistingFile(path);
65
+ await writeJsonFile(path, data);
66
+ }
67
+
57
68
  /** Read a text file, returning null if it doesn't exist. */
58
69
  export async function readTextFile(path: string): Promise<string | null> {
59
70
  try {
@@ -68,6 +79,24 @@ export async function writeTextFile(path: string, content: string): Promise<void
68
79
  await Bun.write(path, content);
69
80
  }
70
81
 
82
+ /**
83
+ * Write text to a file, preserving the current contents at `path.bak` first.
84
+ * See `writeJsonFileWithBackup`.
85
+ */
86
+ export async function writeTextFileWithBackup(path: string, content: string): Promise<void> {
87
+ await backupExistingFile(path);
88
+ await writeTextFile(path, content);
89
+ }
90
+
91
+ /**
92
+ * Copy an existing file to `path.bak` (overwriting an old backup). Missing
93
+ * source files are skipped — there is nothing to protect yet.
94
+ */
95
+ async function backupExistingFile(path: string): Promise<void> {
96
+ if (!(await fileExists(path))) return;
97
+ await Bun.write(`${path}.bak`, await Bun.file(path).text());
98
+ }
99
+
71
100
  /** Remove a file if it exists. */
72
101
  export async function removeFile(path: string): Promise<void> {
73
102
  await rm(path, { force: true });
@@ -1,6 +1,6 @@
1
1
  import type {} from "../contracts";
2
2
  import { jsonDownload } from "../contracts";
3
- import { homeDir, isLocalEndpoint, readJsonFile, stripV1Suffix, writeJsonFile } from "../fs-ops";
3
+ import { homeDir, isLocalEndpoint, readJsonFile, stripV1Suffix, writeJsonFileWithBackup } from "../fs-ops";
4
4
  import type { ApplyInput, InjectorSpec } from "../contracts";
5
5
 
6
6
 
@@ -94,7 +94,7 @@ export const claudeSpec: InjectorSpec = {
94
94
  settings.env = env;
95
95
  settings.model = claudeModel(input);
96
96
  for (const [key, value] of Object.entries(CLAUDE_BASELINE)) settings[key] = value;
97
- await writeJsonFile(path, settings);
97
+ await writeJsonFileWithBackup(path, settings);
98
98
  },
99
99
 
100
100
  async reset(path) {
@@ -106,7 +106,7 @@ export const claudeSpec: InjectorSpec = {
106
106
  }
107
107
  delete settings.model;
108
108
  delete settings.smallModel;
109
- await writeJsonFile(path, settings);
109
+ await writeJsonFileWithBackup(path, settings);
110
110
  },
111
111
 
112
112
  download(input) {