cartethyia 2.7.8 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (519) hide show
  1. package/.env.example +3 -3
  2. package/README.md +29 -59
  3. package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js +1 -0
  4. package/dist/dashboard/assets/BannedPage-Cc0vKpMG.js.gz +0 -0
  5. package/dist/dashboard/assets/{CliToolDetailPage-BRWKPQPf.js → CliToolDetailPage-Bqj2aa1T.js} +1 -1
  6. package/dist/dashboard/assets/CliToolDetailPage-Bqj2aa1T.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolsPage-BP-Y5iHN.js → CliToolsPage-CXsaOAO2.js} +1 -1
  8. package/dist/dashboard/assets/CliToolsPage-CXsaOAO2.js.gz +0 -0
  9. package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js +1 -0
  10. package/dist/dashboard/assets/CombosPage-BeLpJ4ma.js.gz +0 -0
  11. package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js +1 -0
  12. package/dist/dashboard/assets/ConsoleLogPage-DnV0uZ3m.js.gz +0 -0
  13. package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js +1 -0
  14. package/dist/dashboard/assets/CustomizationPage-CBxAjo63.js.gz +0 -0
  15. package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js +1 -0
  16. package/dist/dashboard/assets/HealthEventsModal-B5yEE0k6.js.gz +0 -0
  17. package/dist/dashboard/assets/{InjectSystemPromptPanel-Cv-2Ol_K.js → InjectSystemPromptPanel-BWFLECmz.js} +2 -2
  18. package/dist/dashboard/assets/InjectSystemPromptPanel-BWFLECmz.js.gz +0 -0
  19. package/dist/dashboard/assets/{LoginPage-bKfz_b1K.js → LoginPage-CgoPc5B9.js} +1 -1
  20. package/dist/dashboard/assets/LoginPage-CgoPc5B9.js.gz +0 -0
  21. package/dist/dashboard/assets/{ModelPicker-B-WQxx_H.js → ModelPicker-D9bFQern.js} +1 -1
  22. package/dist/dashboard/assets/ModelPicker-D9bFQern.js.gz +0 -0
  23. package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js +1 -0
  24. package/dist/dashboard/assets/OverviewPage-B_p4tL8k.js.gz +0 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js +8 -0
  26. package/dist/dashboard/assets/ProviderDetailPage-rs5E2rxv.js.gz +0 -0
  27. package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js +1 -0
  28. package/dist/dashboard/assets/ProviderIcon-D_0PiZtg.js.gz +0 -0
  29. package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js +1 -0
  30. package/dist/dashboard/assets/ProvidersPage-CLI2bJTK.js.gz +0 -0
  31. package/dist/dashboard/assets/{ProxyPage-emlTuxz6.js → ProxyPage-BwNC_-Cx.js} +1 -1
  32. package/dist/dashboard/assets/ProxyPage-BwNC_-Cx.js.gz +0 -0
  33. package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js +1 -0
  34. package/dist/dashboard/assets/QuotaPage-DA-a1iSN.js.gz +0 -0
  35. package/dist/dashboard/assets/SettingsPage-CctTga2C.js +1 -0
  36. package/dist/dashboard/assets/SettingsPage-CctTga2C.js.gz +0 -0
  37. package/dist/dashboard/assets/{SetupPage-CUZrKs9r.js → SetupPage-febLU4r1.js} +1 -1
  38. package/dist/dashboard/assets/SetupPage-febLU4r1.js.gz +0 -0
  39. package/dist/dashboard/assets/{StudioPage-jWmUdDpy.js → StudioPage-M6fvR5Wz.js} +1 -1
  40. package/dist/dashboard/assets/StudioPage-M6fvR5Wz.js.gz +0 -0
  41. package/dist/dashboard/assets/UsagePage-nyeuRqz9.js +56 -0
  42. package/dist/dashboard/assets/UsagePage-nyeuRqz9.js.gz +0 -0
  43. package/dist/dashboard/assets/api-keys-BFOlvrJC.js +1 -0
  44. package/dist/dashboard/assets/api-keys-BFOlvrJC.js.gz +0 -0
  45. package/dist/dashboard/assets/app-Dx2QgsNj.css +2 -0
  46. package/dist/dashboard/assets/app-Dx2QgsNj.css.gz +0 -0
  47. package/dist/dashboard/assets/{assert-CidQVoNo.js → assert-CqTLw5e7.js} +1 -1
  48. package/dist/dashboard/assets/assert-CqTLw5e7.js.gz +0 -0
  49. package/dist/dashboard/assets/{badge-DyI_mJfi.js → badge-C83EqLB0.js} +1 -1
  50. package/dist/dashboard/assets/badge-C83EqLB0.js.gz +0 -0
  51. package/dist/dashboard/assets/ban-durations-B58FyRxC.js +1 -0
  52. package/dist/dashboard/assets/ban-durations-B58FyRxC.js.gz +0 -0
  53. package/dist/dashboard/assets/{banner-B-yXtCqb.js → banner-D1xn7pIe.js} +1 -1
  54. package/dist/dashboard/assets/banner-D1xn7pIe.js.gz +0 -0
  55. package/dist/dashboard/assets/brand/bansos_page.webp +0 -0
  56. package/dist/dashboard/assets/brand/denia_bansos.webp +0 -0
  57. package/dist/dashboard/assets/brand/favicon.webp +0 -0
  58. package/dist/dashboard/assets/brand/login_register.webp +0 -0
  59. package/dist/dashboard/assets/brand/login_register_initial.webp +0 -0
  60. package/dist/dashboard/assets/brand/og_bansos.webp +0 -0
  61. package/dist/dashboard/assets/brand/og_image.webp +0 -0
  62. package/dist/dashboard/assets/brand/sidebar_ico.webp +0 -0
  63. package/dist/dashboard/assets/{card-_HwAEqFF.js → card-DU3Lq5Lm.js} +1 -1
  64. package/dist/dashboard/assets/card-DU3Lq5Lm.js.gz +0 -0
  65. package/dist/dashboard/assets/cli-tools-Dw1U_drK.js +1 -0
  66. package/dist/dashboard/assets/cli-tools-Dw1U_drK.js.gz +0 -0
  67. package/dist/dashboard/assets/{clipboard-button-ByjCk0o9.js → clipboard-button-CEL5Sg_J.js} +1 -1
  68. package/dist/dashboard/assets/clipboard-button-CEL5Sg_J.js.gz +0 -0
  69. package/dist/dashboard/assets/{dialog-BZhOlfz-.js → dialog-BjFR5UHy.js} +1 -1
  70. package/dist/dashboard/assets/dialog-BjFR5UHy.js.gz +0 -0
  71. package/dist/dashboard/assets/{drawer-BADqIRYP.js → drawer-HvBW0AjI.js} +1 -1
  72. package/dist/dashboard/assets/drawer-HvBW0AjI.js.gz +0 -0
  73. package/dist/dashboard/assets/entry-BSu3gFlt.js +4 -0
  74. package/dist/dashboard/assets/entry-BSu3gFlt.js.gz +0 -0
  75. package/dist/dashboard/assets/{entry-COaC-irN.js → entry-CECVELyy.js} +1 -1
  76. package/dist/dashboard/assets/entry-CECVELyy.js.gz +0 -0
  77. package/dist/dashboard/assets/entry-DYYofQ7W.js +1 -0
  78. package/dist/dashboard/assets/entry-DYYofQ7W.js.gz +0 -0
  79. package/dist/dashboard/assets/{formatters-DcHJyRiu.js → formatters-DLW4i8zK.js} +1 -1
  80. package/dist/dashboard/assets/formatters-DLW4i8zK.js.gz +0 -0
  81. package/dist/dashboard/assets/{index-CgKmFngv.js → index-1-fCqe64.js} +2 -2
  82. package/dist/dashboard/assets/index-1-fCqe64.js.gz +0 -0
  83. package/dist/dashboard/assets/{inline-B2BirKry.js → inline-DGvU2oLK.js} +1 -1
  84. package/dist/dashboard/assets/inline-DGvU2oLK.js.gz +0 -0
  85. package/dist/dashboard/assets/{input-D-qV9dKY.js → input-iNnO1X2K.js} +1 -1
  86. package/dist/dashboard/assets/input-iNnO1X2K.js.gz +0 -0
  87. package/dist/dashboard/assets/{layout-B7tVWhtV.js → layout-0q8UGypb.js} +1 -1
  88. package/dist/dashboard/assets/layout-0q8UGypb.js.gz +0 -0
  89. package/dist/dashboard/assets/{list-row-CtkGE3bF.js → list-row-C0w2bQjJ.js} +1 -1
  90. package/dist/dashboard/assets/list-row-C0w2bQjJ.js.gz +0 -0
  91. package/dist/dashboard/assets/{live-DGizF06-.js → live-M5aJ_xCa.js} +1 -1
  92. package/dist/dashboard/assets/live-M5aJ_xCa.js.gz +0 -0
  93. package/dist/dashboard/assets/{mutation-GU7XdEjz.js → mutation-BQ0ccH_A.js} +1 -1
  94. package/dist/dashboard/assets/mutation-BQ0ccH_A.js.gz +0 -0
  95. package/dist/dashboard/assets/{network-C9UE-bOr.js → network-BKeQGT3K.js} +1 -1
  96. package/dist/dashboard/assets/network-BKeQGT3K.js.gz +0 -0
  97. package/dist/dashboard/assets/{numbers-BnAd22a_.js → numbers-CivAk3da.js} +1 -1
  98. package/dist/dashboard/assets/numbers-CivAk3da.js.gz +0 -0
  99. package/dist/dashboard/assets/{pagination-BAywyMzS.js → pagination-CpzvI5ep.js} +1 -1
  100. package/dist/dashboard/assets/pagination-CpzvI5ep.js.gz +0 -0
  101. package/dist/dashboard/assets/providers/devin.svg +9 -0
  102. package/dist/dashboard/assets/providers/devin.svg.gz +0 -0
  103. package/dist/dashboard/assets/providers/windsurf.svg +10 -0
  104. package/dist/dashboard/assets/providers/windsurf.svg.gz +0 -0
  105. package/dist/dashboard/assets/providers/zro.svg +3 -0
  106. package/dist/dashboard/assets/providers/zro.svg.gz +0 -0
  107. package/dist/dashboard/assets/{query-keys-YSoQOUh6.js → query-keys-IKLh0YFd.js} +1 -1
  108. package/dist/dashboard/assets/query-keys-IKLh0YFd.js.gz +0 -0
  109. package/dist/dashboard/assets/quota-DiPD6UKH.js +1 -0
  110. package/dist/dashboard/assets/quota-DiPD6UKH.js.gz +0 -0
  111. package/dist/dashboard/assets/routing-BFQX2VBc.js +1 -0
  112. package/dist/dashboard/assets/routing-BFQX2VBc.js.gz +0 -0
  113. package/dist/dashboard/assets/{select-BCOcv8WK.js → select-CXbck0jG.js} +2 -2
  114. package/dist/dashboard/assets/select-CXbck0jG.js.gz +0 -0
  115. package/dist/dashboard/assets/settings-C_XvMHh9.js +1 -0
  116. package/dist/dashboard/assets/settings-C_XvMHh9.js.gz +0 -0
  117. package/dist/dashboard/assets/{stack-ByKt3Q1g.js → stack-DggRiCxE.js} +1 -1
  118. package/dist/dashboard/assets/stack-DggRiCxE.js.gz +0 -0
  119. package/dist/dashboard/assets/{state-DXbGTx7N.js → state-ClFzfbYp.js} +1 -1
  120. package/dist/dashboard/assets/state-ClFzfbYp.js.gz +0 -0
  121. package/dist/dashboard/assets/story/25817331.webp +0 -0
  122. package/dist/dashboard/assets/story/Shorekeeper.webp +0 -0
  123. package/dist/dashboard/assets/story/cartethyia-god.webp +0 -0
  124. package/dist/dashboard/assets/story/fleurdelys_plus.webp +0 -0
  125. package/dist/dashboard/assets/story/jinhsi-blossom.webp +0 -0
  126. package/dist/dashboard/assets/story/phrolova-a.webp +0 -0
  127. package/dist/dashboard/assets/story/requestdeniawokkjpg.webp +0 -0
  128. package/dist/dashboard/assets/story/wuthering-waves-hiyuki-aemeath-hiyuki-rover.webp +0 -0
  129. package/dist/dashboard/assets/{switch-ZeTsW623.js → switch-CTIFxKqS.js} +1 -1
  130. package/dist/dashboard/assets/switch-CTIFxKqS.js.gz +0 -0
  131. package/dist/dashboard/assets/{useMutation-CbCP4w1o.js → useMutation-BClG3yWj.js} +1 -1
  132. package/dist/dashboard/assets/useMutation-BClG3yWj.js.gz +0 -0
  133. package/dist/dashboard/console-backgrounds.webp +0 -0
  134. package/dist/dashboard/console-backgrounds_initial.webp +0 -0
  135. package/dist/dashboard/donation/qris-risun-marke.webp +0 -0
  136. package/dist/dashboard/index.html +3 -3
  137. package/dist/dashboard/index.html.gz +0 -0
  138. package/dist/dashboard/precompressed-assets.json +59 -56
  139. package/migrations/full-postgres/0044_add_models_warning_metadata.sql +10 -0
  140. package/migrations/full-postgres/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
  141. package/migrations/full-postgres/0046_add_accounts_quota_plan_fetched_at.sql +7 -0
  142. package/migrations/lite-sqlite3/0044_add_models_warning_metadata.sql +11 -0
  143. package/migrations/lite-sqlite3/0045_migrate_workbuddy_to_codebuddy.sql +156 -0
  144. package/migrations/lite-sqlite3/0046_add_accounts_quota_plan_fetched_at.sql +4 -0
  145. package/package.json +1 -1
  146. package/src/boot/app.ts +10 -13
  147. package/src/boot/dependencies.ts +12 -4
  148. package/src/boot/flush.ts +20 -0
  149. package/src/boot/lifecycle.ts +7 -5
  150. package/src/config/cooldowns.ts +1 -1
  151. package/src/config/routing.ts +26 -0
  152. package/src/config/spec-metadata.ts +72 -0
  153. package/src/config/spec.ts +20 -6
  154. package/src/console/api-keys/contracts.ts +12 -1
  155. package/src/console/api-keys/routes.ts +50 -31
  156. package/src/console/auth/account-reset.ts +7 -9
  157. package/src/console/backup/contracts.ts +4 -0
  158. package/src/console/backup/export-endpoint.ts +7 -5
  159. package/src/console/cli-tools/service.ts +6 -12
  160. package/src/console/console-router-guards.ts +23 -16
  161. package/src/console/console-router.ts +7 -0
  162. package/src/console/dashboard-assets.ts +5 -5
  163. package/src/console/domain-registration.ts +27 -2
  164. package/src/console/inferhub/normalize.ts +24 -28
  165. package/src/console/inferhub/types.ts +3 -0
  166. package/src/console/ip-bans/routes.ts +93 -0
  167. package/src/console/observability/store.ts +0 -53
  168. package/src/console/providers/catalog/catalog-projections.ts +5 -2
  169. package/src/console/providers/catalog/contracts.ts +123 -0
  170. package/src/console/providers/catalog/discover-constants.ts +6 -0
  171. package/src/console/providers/catalog/model-operations.ts +122 -10
  172. package/src/console/providers/catalog/provider-operations.ts +116 -8
  173. package/src/console/providers/catalog/public-model-store.ts +0 -0
  174. package/src/console/providers/catalog/routes.ts +65 -4
  175. package/src/console/providers/catalog/store-helpers.ts +30 -1
  176. package/src/console/providers/catalog/store.ts +249 -4
  177. package/src/console/quota/account-quota-global.ts +97 -19
  178. package/src/console/quota/account-quota-shared.ts +49 -0
  179. package/src/console/quota/account-quota-tenant.ts +6 -18
  180. package/src/console/quota/native-identity.ts +2 -0
  181. package/src/console/quota/quota-view.ts +19 -3
  182. package/src/console/routing/pools/operations.ts +31 -46
  183. package/src/console/settings/contracts.ts +18 -0
  184. package/src/console/settings/store.ts +32 -0
  185. package/src/console/shared/errors.ts +33 -6
  186. package/src/console/system-prompt/contracts.ts +0 -1
  187. package/src/main.ts +3 -3
  188. package/src/network/pool/byte-accounting.ts +3 -2
  189. package/src/network/pool/selector.ts +182 -39
  190. package/src/network/pool-cooldown-store.ts +89 -14
  191. package/src/network/pool-health.ts +49 -61
  192. package/src/network/rate-limit-verdict.ts +1 -1
  193. package/src/network/ssrf.ts +6 -6
  194. package/src/observability/event-log.ts +7 -361
  195. package/src/observability/event-query.ts +18 -4
  196. package/src/observability/performance-metrics.ts +9 -0
  197. package/src/observability/runtime-metrics.ts +14 -9
  198. package/src/observability/telemetry-buffer.ts +15 -25
  199. package/src/observability/telemetry-reconciliation.ts +9 -37
  200. package/src/observability/telemetry-row.ts +125 -0
  201. package/src/persistence/db-mode.ts +4 -1
  202. package/src/persistence/dialect.ts +30 -0
  203. package/src/persistence/migrate.ts +6 -3
  204. package/src/persistence/schema.ts +28 -2
  205. package/src/persistence/ttl-cache.ts +20 -2
  206. package/src/protocol/codecs.ts +1 -1
  207. package/src/protocol/dsml.ts +35 -87
  208. package/src/protocol/gateway-error.ts +1 -0
  209. package/src/protocol/markup-stream-parser.ts +195 -0
  210. package/src/protocol/reasoning-text.ts +19 -0
  211. package/src/protocol/reasoning.ts +1 -1
  212. package/src/protocol/request/chat.ts +14 -14
  213. package/src/protocol/request/gemini.ts +8 -2
  214. package/src/protocol/request/messages.ts +4 -5
  215. package/src/protocol/response/chat.ts +12 -20
  216. package/src/protocol/response/gemini.ts +6 -1
  217. package/src/protocol/response/omp-tool-call-markup.ts +43 -86
  218. package/src/protocol/response/responses.ts +18 -2
  219. package/src/protocol/sse-decoder.ts +25 -8
  220. package/src/protocol/stop-reason.ts +23 -1
  221. package/src/protocol/stream-error-frames.ts +13 -3
  222. package/src/protocol/think-split.ts +112 -0
  223. package/src/protocol/translation/dialect/qwen.ts +26 -58
  224. package/src/protocol/translation/quirks.ts +1 -1
  225. package/src/protocol/translation/tool-repair.ts +2 -2
  226. package/src/protocol/upstream/send-claude-messages.ts +15 -4
  227. package/src/protocol/upstream-parsing.ts +1 -1
  228. package/src/providers/adapters/compatible-adapter.ts +78 -17
  229. package/src/providers/authentication/oauth-client.ts +1 -1
  230. package/src/providers/authentication/oauth-device-flow.ts +98 -8
  231. package/src/providers/authentication/oauth-refresh-service.ts +153 -59
  232. package/src/providers/configured-provider.ts +148 -0
  233. package/src/providers/default-registry.ts +117 -27
  234. package/src/providers/discovery/discovery-types.ts +12 -0
  235. package/src/providers/discovery/probe-phases.ts +74 -33
  236. package/src/providers/discovery/probing-service.ts +293 -165
  237. package/src/providers/integrations/alibabacp/alibabacp-models.ts +1 -2
  238. package/src/providers/integrations/{dashscope-models.ts → alibabacp/dashscope-models.ts} +2 -2
  239. package/src/providers/integrations/antigravity/antigravity-protocol.ts +121 -2
  240. package/src/providers/integrations/antigravity/antigravity.ts +13 -2
  241. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +72 -34
  242. package/src/providers/integrations/buddy/buddy-chat-shared.ts +191 -35
  243. package/src/providers/integrations/buddy/buddy-checkin.ts +17 -39
  244. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +2 -2
  245. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +5 -5
  246. package/src/providers/integrations/buddy/buddy-identity.ts +89 -6
  247. package/src/providers/integrations/buddy/buddy-media.ts +4 -5
  248. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +2 -2
  249. package/src/providers/integrations/buddy/buddy-quota-shared.ts +1 -1
  250. package/src/providers/integrations/buddy/buddy-usage-shared.ts +1 -3
  251. package/src/providers/integrations/buddy/capabilities.ts +6 -43
  252. package/src/providers/integrations/buddy/codebuddy/cn-catalog.ts +2 -1
  253. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +23 -1
  254. package/src/providers/integrations/buddy/codebuddy/provider.ts +6 -2
  255. package/src/providers/integrations/buddy/codebuddy/shared.ts +38 -27
  256. package/src/providers/integrations/claude/claude-quota.ts +14 -17
  257. package/src/providers/integrations/cline/cline.ts +2 -2
  258. package/src/providers/integrations/commandcode/quota.ts +22 -28
  259. package/src/providers/integrations/cursor/cursor-auth.ts +0 -8
  260. package/src/providers/integrations/cursor/cursor-catalog.ts +2 -0
  261. package/src/providers/integrations/cursor/cursor-exec.ts +745 -0
  262. package/src/providers/integrations/cursor/cursor-http2.ts +2 -0
  263. package/src/providers/integrations/cursor/cursor-request.ts +35 -20
  264. package/src/providers/integrations/cursor/cursor-shared.ts +26 -5
  265. package/src/providers/integrations/cursor/cursor.ts +139 -70
  266. package/src/providers/integrations/cursor/protocol/messages.ts +22 -29
  267. package/src/providers/integrations/cursor/protocol/protobuf-schema.ts +37 -0
  268. package/src/providers/integrations/cursor/protocol/tool-canonical.ts +26 -100
  269. package/src/providers/integrations/devin/devin-auth.ts +353 -0
  270. package/src/providers/integrations/devin/devin-catalog.ts +207 -0
  271. package/src/providers/integrations/devin/devin-http.ts +91 -0
  272. package/src/providers/integrations/devin/devin-quota.ts +214 -0
  273. package/src/providers/integrations/devin/devin-shared.ts +111 -0
  274. package/src/providers/integrations/devin/devin.ts +539 -0
  275. package/src/providers/integrations/devin/protocol/devin-proto.ts +558 -0
  276. package/src/providers/integrations/devin/protocol/framing.ts +124 -0
  277. package/src/providers/integrations/factory/factory-oauth.ts +17 -44
  278. package/src/providers/integrations/factory/factory.ts +2 -2
  279. package/src/providers/integrations/freebuff/assets/official-system-manager.txt +88 -0
  280. package/src/providers/integrations/freebuff/assets/official-system-worker.txt +57 -0
  281. package/src/providers/integrations/freebuff/assets/official-tools.json +1070 -0
  282. package/src/providers/integrations/freebuff/freebuff-catalog.ts +120 -0
  283. package/src/providers/integrations/freebuff/freebuff-constants.ts +1233 -0
  284. package/src/providers/integrations/freebuff/freebuff-discovery.ts +108 -0
  285. package/src/providers/integrations/freebuff/freebuff-oauth.ts +240 -0
  286. package/src/providers/integrations/freebuff/freebuff-quota.ts +260 -0
  287. package/src/providers/integrations/freebuff/freebuff-virtual.ts +89 -0
  288. package/src/providers/integrations/freebuff/freebuff.ts +682 -0
  289. package/src/providers/integrations/freepi/freepi-catalog.ts +60 -0
  290. package/src/providers/integrations/freepi/freepi-discovery.ts +82 -0
  291. package/src/providers/integrations/freepi/freepi-headers.ts +21 -0
  292. package/src/providers/integrations/freepi/freepi-oauth.ts +142 -0
  293. package/src/providers/integrations/freepi/freepi-protocol.ts +242 -0
  294. package/src/providers/integrations/freepi/freepi-quota.ts +126 -0
  295. package/src/providers/integrations/freepi/freepi-realm.ts +35 -0
  296. package/src/providers/integrations/freepi/freepi.ts +292 -0
  297. package/src/providers/integrations/gemini.ts +2 -2
  298. package/src/providers/integrations/grok/grok-oauth.ts +48 -45
  299. package/src/providers/integrations/grok/grok-ratelimit-store.ts +10 -17
  300. package/src/providers/integrations/grok/grok-turn-index.ts +11 -16
  301. package/src/providers/integrations/hermes/hermes-oauth.ts +94 -0
  302. package/src/providers/integrations/{hermes.ts → hermes/hermes.ts} +13 -21
  303. package/src/providers/integrations/hermes/index.ts +2 -0
  304. package/src/providers/integrations/kiro/kiro-catalog.ts +17 -1
  305. package/src/providers/integrations/kiro/kiro-discovery.ts +10 -12
  306. package/src/providers/integrations/kiro/kiro-quota.ts +8 -4
  307. package/src/providers/integrations/kiro/kiro.ts +15 -19
  308. package/src/providers/integrations/ollamacloud/index.ts +2 -0
  309. package/src/providers/integrations/{ollamacloud-models.ts → ollamacloud/models.ts} +2 -2
  310. package/src/providers/integrations/{ollamacloud-quota.ts → ollamacloud/quota.ts} +3 -3
  311. package/src/providers/integrations/opencode/fingerprint.ts +1 -1
  312. package/src/providers/integrations/opencode/opencode-go-quota.ts +2 -8
  313. package/src/providers/integrations/perch/perch-catalog.ts +105 -0
  314. package/src/providers/integrations/perch/perch-credential.ts +25 -0
  315. package/src/providers/integrations/perch/perch-oauth.ts +170 -0
  316. package/src/providers/integrations/perch/perch-protocol.ts +208 -0
  317. package/src/providers/integrations/perch/perch-quota.ts +123 -0
  318. package/src/providers/integrations/perch/perch-realm.ts +82 -0
  319. package/src/providers/integrations/perch/perch-system-prompt.ts +13 -0
  320. package/src/providers/integrations/perch/perch.ts +305 -0
  321. package/src/providers/integrations/perch/saffron-system-prompt.txt +8 -0
  322. package/src/providers/integrations/qoder/chat-proto.ts +328 -328
  323. package/src/providers/integrations/qoder/claim-qoder-credits.ts +19 -60
  324. package/src/providers/integrations/qoder/qoder-adapter.ts +90 -40
  325. package/src/providers/integrations/qoder/qoder-auth.ts +101 -29
  326. package/src/providers/integrations/qoder/qoder-cache.ts +32 -0
  327. package/src/providers/integrations/qoder/qoder-catalog.ts +26 -6
  328. package/src/providers/integrations/qoder/qoder-chat.ts +7 -21
  329. package/src/providers/integrations/qoder/qoder-checkin.ts +6 -6
  330. package/src/providers/integrations/qoder/qoder-grpc.ts +210 -7
  331. package/src/providers/integrations/qoder/qoder-ide.ts +5 -108
  332. package/src/providers/integrations/qoder/qoder-identity-cache.ts +184 -0
  333. package/src/providers/integrations/qoder/qoder-native-identity-store.ts +83 -18
  334. package/src/providers/integrations/qoder/qoder-realm.ts +9 -6
  335. package/src/providers/integrations/qoder/qoder-umid.ts +109 -342
  336. package/src/providers/integrations/qoder/qoder-virtual.ts +33 -1
  337. package/src/providers/integrations/qoder/qoder.ts +3 -7
  338. package/src/providers/integrations/trae/trae-catalog.ts +9 -14
  339. package/src/providers/integrations/trae/trae-headers.ts +36 -11
  340. package/src/providers/integrations/trae/trae-oauth.ts +14 -17
  341. package/src/providers/integrations/trae/trae-protocol.ts +12 -11
  342. package/src/providers/integrations/trae/trae-quota.ts +96 -36
  343. package/src/providers/integrations/trae/trae-realm.ts +74 -15
  344. package/src/providers/integrations/trae/trae.ts +99 -26
  345. package/src/providers/integrations/zcode-start-plan/provider.ts +226 -23
  346. package/src/providers/integrations/zcode-start-plan/quota.ts +19 -2
  347. package/src/providers/integrations/zro/zro.ts +204 -0
  348. package/src/providers/operations/account-error-classification.ts +235 -278
  349. package/src/providers/operations/account-health-service.ts +273 -26
  350. package/src/providers/operations/account-refresh-status.ts +269 -0
  351. package/src/providers/operations/account-tier.ts +66 -0
  352. package/src/providers/operations/client-version-resolver.ts +1 -1
  353. package/src/providers/operations/client-versions.ts +42 -150
  354. package/src/providers/operations/cooldown-rules.ts +56 -0
  355. package/src/providers/operations/{model-catalog-cache.ts → provider-caches.ts} +68 -16
  356. package/src/providers/operations/provider-catalog-seeder.ts +4 -0
  357. package/src/providers/operations/provider-catalog-service.ts +1 -1
  358. package/src/providers/operations/provider-credential-service.ts +102 -32
  359. package/src/providers/operations/provider-rules/github.ts +48 -0
  360. package/src/providers/operations/provider-rules/grok.ts +33 -0
  361. package/src/providers/operations/provider-rules/inception.ts +49 -0
  362. package/src/providers/operations/provider-rules/index.ts +32 -0
  363. package/src/providers/operations/provider-rules/perch.ts +37 -0
  364. package/src/providers/operations/provider-rules/qoder.ts +70 -0
  365. package/src/providers/operations/provider-rules/trae.ts +81 -0
  366. package/src/providers/provider-contracts.ts +33 -2
  367. package/src/providers/{provider-credential-resolver.ts → provider-credentials.ts} +40 -3
  368. package/src/providers/provider-metadata.ts +99 -14
  369. package/src/providers/provider-model-definition.ts +36 -0
  370. package/src/providers/provider-probe.ts +15 -1
  371. package/src/providers/provider-registry.ts +42 -33
  372. package/src/providers/quota/quota-limit-windows.ts +16 -5
  373. package/src/providers/quota/quota-support.ts +1 -1
  374. package/src/providers/quota/quota-window-parser.ts +90 -18
  375. package/src/providers/quota/refresh.ts +96 -18
  376. package/src/security/abuse.ts +71 -0
  377. package/src/security/api-key-auth-cache.ts +1 -1
  378. package/src/security/api-key-auth.ts +6 -2
  379. package/src/transport/dispatch/attempt-finalize.ts +14 -13
  380. package/src/transport/dispatch/attempt-loop.ts +71 -17
  381. package/src/transport/dispatch/generation.ts +2 -8
  382. package/src/transport/dispatch/native-shared.ts +25 -0
  383. package/src/transport/dispatch/proxy-request.ts +9 -46
  384. package/src/transport/dispatch/responses-compact.ts +2 -8
  385. package/src/transport/dispatch/streaming/finalize.ts +9 -6
  386. package/src/transport/dispatch/streaming/stream.ts +9 -36
  387. package/src/transport/dispatch/systemone.ts +2 -8
  388. package/src/transport/dispatch/token-count.ts +2 -6
  389. package/src/transport/dispatch/upstream.ts +67 -1
  390. package/src/transport/drain-endpoint.ts +31 -4
  391. package/src/transport/failure-policy.ts +129 -37
  392. package/src/transport/guards/body-policy.ts +7 -1
  393. package/src/transport/guards/error-lifecycle.ts +13 -1
  394. package/src/transport/guards/request-context.ts +22 -14
  395. package/src/transport/request/preparer.ts +81 -71
  396. package/src/transport/routing/bounded-lru.ts +14 -0
  397. package/src/transport/routing/drain-reserve.ts +129 -0
  398. package/src/transport/routing/plan-gate.ts +7 -1
  399. package/src/transport/routing/round-robin.ts +60 -0
  400. package/src/transport/routing/route-catalog.ts +123 -111
  401. package/src/transport/routing/route-model.ts +51 -9
  402. package/src/transport/routing/router.ts +418 -33
  403. package/src/transport/surface/adapters.ts +11 -2
  404. package/src/transport/surface/chat/adapter.ts +2 -2
  405. package/src/transport/surface/chat/encode.ts +47 -4
  406. package/src/transport/surface/chat/parse.ts +2 -12
  407. package/src/transport/surface/completion.ts +44 -33
  408. package/src/transport/surface/content-parts.ts +21 -0
  409. package/src/transport/surface/messages/adapter.ts +9 -2
  410. package/src/transport/surface/messages/encode.ts +11 -3
  411. package/src/transport/surface/messages/parse.ts +2 -1
  412. package/src/transport/surface/messages/stream.ts +33 -17
  413. package/src/transport/surface/responses/encode.ts +34 -6
  414. package/src/transport/surface/responses/parse.ts +3 -11
  415. package/src/workers/daily-checkin.ts +1 -1
  416. package/src/workers/quota-refresh-sweep.ts +1 -1
  417. package/src/workers/scheduler.ts +22 -4
  418. package/dist/dashboard/assets/BannedPage-DeIbIiBU.js +0 -1
  419. package/dist/dashboard/assets/BannedPage-DeIbIiBU.js.gz +0 -0
  420. package/dist/dashboard/assets/CliToolDetailPage-BRWKPQPf.js.gz +0 -0
  421. package/dist/dashboard/assets/CliToolsPage-BP-Y5iHN.js.gz +0 -0
  422. package/dist/dashboard/assets/CombosPage-B7JHozVa.js +0 -1
  423. package/dist/dashboard/assets/CombosPage-B7JHozVa.js.gz +0 -0
  424. package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js +0 -1
  425. package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js.gz +0 -0
  426. package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js +0 -1
  427. package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js.gz +0 -0
  428. package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js +0 -1
  429. package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js.gz +0 -0
  430. package/dist/dashboard/assets/InjectSystemPromptPanel-Cv-2Ol_K.js.gz +0 -0
  431. package/dist/dashboard/assets/LoginPage-bKfz_b1K.js.gz +0 -0
  432. package/dist/dashboard/assets/ModelPicker-B-WQxx_H.js.gz +0 -0
  433. package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js +0 -1
  434. package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js.gz +0 -0
  435. package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js +0 -7
  436. package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js.gz +0 -0
  437. package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js +0 -1
  438. package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js.gz +0 -0
  439. package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js +0 -1
  440. package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js.gz +0 -0
  441. package/dist/dashboard/assets/ProxyPage-emlTuxz6.js.gz +0 -0
  442. package/dist/dashboard/assets/QuotaPage-CMJKNYWb.js +0 -1
  443. package/dist/dashboard/assets/QuotaPage-CMJKNYWb.js.gz +0 -0
  444. package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js +0 -1
  445. package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js.gz +0 -0
  446. package/dist/dashboard/assets/SetupPage-CUZrKs9r.js.gz +0 -0
  447. package/dist/dashboard/assets/StudioPage-jWmUdDpy.js.gz +0 -0
  448. package/dist/dashboard/assets/UsagePage-5F9tGVGG.js +0 -56
  449. package/dist/dashboard/assets/UsagePage-5F9tGVGG.js.gz +0 -0
  450. package/dist/dashboard/assets/api-keys-KvX83mRe.js +0 -1
  451. package/dist/dashboard/assets/api-keys-KvX83mRe.js.gz +0 -0
  452. package/dist/dashboard/assets/app-BEsU8hKs.css +0 -2
  453. package/dist/dashboard/assets/app-BEsU8hKs.css.gz +0 -0
  454. package/dist/dashboard/assets/assert-CidQVoNo.js.gz +0 -0
  455. package/dist/dashboard/assets/badge-DyI_mJfi.js.gz +0 -0
  456. package/dist/dashboard/assets/banner-B-yXtCqb.js.gz +0 -0
  457. package/dist/dashboard/assets/brand/bansos_page.png +0 -0
  458. package/dist/dashboard/assets/brand/denia_bansos.jpg +0 -0
  459. package/dist/dashboard/assets/brand/favicon.jpg +0 -0
  460. package/dist/dashboard/assets/brand/login_register.jpg +0 -0
  461. package/dist/dashboard/assets/brand/login_register_initial.jpg +0 -0
  462. package/dist/dashboard/assets/brand/og_bansos.jpg +0 -0
  463. package/dist/dashboard/assets/brand/og_image.jpg +0 -0
  464. package/dist/dashboard/assets/brand/sidebar_ico.jpg +0 -0
  465. package/dist/dashboard/assets/card-_HwAEqFF.js.gz +0 -0
  466. package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js +0 -1
  467. package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js.gz +0 -0
  468. package/dist/dashboard/assets/clipboard-button-ByjCk0o9.js.gz +0 -0
  469. package/dist/dashboard/assets/dialog-BZhOlfz-.js.gz +0 -0
  470. package/dist/dashboard/assets/drawer-BADqIRYP.js.gz +0 -0
  471. package/dist/dashboard/assets/entry-COaC-irN.js.gz +0 -0
  472. package/dist/dashboard/assets/entry-Ca7LSCet.js +0 -4
  473. package/dist/dashboard/assets/entry-Ca7LSCet.js.gz +0 -0
  474. package/dist/dashboard/assets/entry-PDd27uCd.js +0 -1
  475. package/dist/dashboard/assets/entry-PDd27uCd.js.gz +0 -0
  476. package/dist/dashboard/assets/formatters-DcHJyRiu.js.gz +0 -0
  477. package/dist/dashboard/assets/index-CgKmFngv.js.gz +0 -0
  478. package/dist/dashboard/assets/inline-B2BirKry.js.gz +0 -0
  479. package/dist/dashboard/assets/input-D-qV9dKY.js.gz +0 -0
  480. package/dist/dashboard/assets/layout-B7tVWhtV.js.gz +0 -0
  481. package/dist/dashboard/assets/list-row-CtkGE3bF.js.gz +0 -0
  482. package/dist/dashboard/assets/live-DGizF06-.js.gz +0 -0
  483. package/dist/dashboard/assets/minimize-2-BTmMTK4o.js +0 -1
  484. package/dist/dashboard/assets/minimize-2-BTmMTK4o.js.gz +0 -0
  485. package/dist/dashboard/assets/mutation-GU7XdEjz.js.gz +0 -0
  486. package/dist/dashboard/assets/network-C9UE-bOr.js.gz +0 -0
  487. package/dist/dashboard/assets/numbers-BnAd22a_.js.gz +0 -0
  488. package/dist/dashboard/assets/pagination-BAywyMzS.js.gz +0 -0
  489. package/dist/dashboard/assets/query-keys-YSoQOUh6.js.gz +0 -0
  490. package/dist/dashboard/assets/quota-DJ1QlAOq.js +0 -1
  491. package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
  492. package/dist/dashboard/assets/routing-fnCsxgNO.js +0 -1
  493. package/dist/dashboard/assets/routing-fnCsxgNO.js.gz +0 -0
  494. package/dist/dashboard/assets/select-BCOcv8WK.js.gz +0 -0
  495. package/dist/dashboard/assets/settings-BxslsErn.js +0 -1
  496. package/dist/dashboard/assets/settings-BxslsErn.js.gz +0 -0
  497. package/dist/dashboard/assets/stack-ByKt3Q1g.js.gz +0 -0
  498. package/dist/dashboard/assets/state-DXbGTx7N.js.gz +0 -0
  499. package/dist/dashboard/assets/switch-ZeTsW623.js.gz +0 -0
  500. package/dist/dashboard/assets/useMutation-CbCP4w1o.js.gz +0 -0
  501. package/dist/dashboard/console-backgrounds.jpg +0 -0
  502. package/dist/dashboard/console-backgrounds_initial.jpg +0 -0
  503. package/dist/dashboard/donation/qris-risun-marke.png +0 -0
  504. package/src/providers/bundled-discovery-providers.ts +0 -98
  505. package/src/providers/configured-openai-providers.ts +0 -120
  506. package/src/providers/integrations/buddy/turing-device-token.ts +0 -262
  507. package/src/providers/integrations/buddy/workbuddy/cn-oauth.ts +0 -30
  508. package/src/providers/integrations/buddy/workbuddy/cn-provider.ts +0 -58
  509. package/src/providers/integrations/buddy/workbuddy/oauth.ts +0 -31
  510. package/src/providers/integrations/buddy/workbuddy/provider.ts +0 -209
  511. package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -161
  512. package/src/providers/integrations/buddy/workbuddy/shared.ts +0 -285
  513. package/src/providers/operations/model-discovery-cache.ts +0 -34
  514. package/src/providers/operations/probe-fetch.ts +0 -15
  515. package/src/providers/operations/provider-version-cache.ts +0 -30
  516. package/src/providers/provider-capabilities.ts +0 -33
  517. package/src/providers/provider-credential-envelope.ts +0 -43
  518. /package/dist/dashboard/assets/{app-CTxoqyYB.js → app-DIXLjojg.js} +0 -0
  519. /package/dist/dashboard/assets/{app-CTxoqyYB.js.gz → app-DIXLjojg.js.gz} +0 -0
@@ -0,0 +1,745 @@
1
+ // Cursor-native tool executor — runs Cursor's host tools locally instead of
2
+ // relaying them to the client or refusing them.
3
+ //
4
+ // Cursor asks the local client to execute host tools through `ExecServerMessage`
5
+ // frames (read, shell, write, edit, grep, glob, ls, delete, and the modern
6
+ // `pi_*` family). Previously this gateway relayed those to the caller as tool
7
+ // calls or refused them; this module executes them in-process, inside one
8
+ // workspace root, and returns the protobuf result message the wire expects.
9
+ //
10
+ // Safety model:
11
+ // - Every filesystem operation resolves inside `root` (the gateway's
12
+ // workspace). A path that escapes is refused with the tool's `rejected`
13
+ // or `permission_denied` case — never executed.
14
+ // - Shell commands run through `spawn` with `shell: false` on POSIX and a
15
+ // parsed argv on Windows (no `cmd /c` string interpolation); the working
16
+ // directory is pinned to `root`.
17
+ // - `delete` refuses to delete the workspace root itself, and `edit`/`write`
18
+ // refuse to touch paths outside the root.
19
+ //
20
+ // The result shape is keyed by the origin exec frame (which variant asked),
21
+ // so a reply can only ever come back in the arm it arrived on.
22
+
23
+ import { spawn } from "node:child_process";
24
+ import {
25
+ readFileSync,
26
+ writeFileSync,
27
+ existsSync,
28
+ lstatSync,
29
+ readdirSync,
30
+ unlinkSync,
31
+ mkdirSync,
32
+ realpathSync,
33
+ } from "node:fs";
34
+ import { resolve, join, relative, dirname, isAbsolute } from "node:path";
35
+ import { createHash } from "node:crypto";
36
+
37
+ /** Max output bytes a tool result carries before truncation is reported. */
38
+ export const CURSOR_TOOL_MAX_OUTPUT_BYTES = 64 * 1024;
39
+ /** Max lines a read result reports before truncation. */
40
+ export const CURSOR_TOOL_MAX_LINES = 2000;
41
+
42
+ /**
43
+ * The decoded result of one executed tool, ready to be wrapped in the exec
44
+ * frame's result oneof.
45
+ */
46
+ export interface NativeToolResult {
47
+ /** The ExecClientMessage result field name (e.g. `read_result`, `ct_read_result`). */
48
+ readonly field: string;
49
+ /** The protobuf result message object, keyed by its oneof arm. */
50
+ readonly message: Record<string, unknown>;
51
+ }
52
+
53
+ /** One exec-frame → result-field mapping (wire arms, not tool names). */
54
+ const EXEC_RESULT_FIELD: Readonly<Record<string, string>> = {
55
+ shell_args: "shell_result",
56
+ write_args: "write_result",
57
+ delete_args: "delete_result",
58
+ grep_args: "grep_result",
59
+ read_args: "read_result",
60
+ ls_args: "ls_result",
61
+ // Modern exec arms are namespaced `ct_` (cartethyia) on the wire — the
62
+ // upstream calls them `pi_`, but the canonical tool surface is one name per
63
+ // tool, so the wire suffix here is ours alone.
64
+ ct_read_args: "pi_read_result",
65
+ ct_bash_args: "pi_bash_result",
66
+ ct_edit_args: "pi_edit_result",
67
+ ct_write_args: "pi_write_result",
68
+ ct_grep_args: "pi_grep_result",
69
+ ct_find_args: "pi_find_result",
70
+ ct_ls_args: "pi_ls_result",
71
+ };
72
+
73
+ /**
74
+ * Resolves an `ExecServerMessage` frame to `{ frameKey, args }` when the
75
+ * frame carries an executable tool payload. `frameKey` is the wire key
76
+ * (`read_args`, `pi_read_args`, …); `args` are canonicalised snake-case
77
+ * arguments. Returns `undefined` for non-tool frames (id, exec_id,
78
+ * request_context_args, …).
79
+ */
80
+ export function resolveExecFrame(
81
+ execServer: Record<string, unknown>,
82
+ ): { readonly frameKey: string; readonly args: Record<string, unknown> } | undefined {
83
+ for (const [key, value] of Object.entries(execServer)) {
84
+ if (value === null || value === undefined || !key.endsWith("_args")) continue;
85
+ if (!Object.hasOwn(EXEC_RESULT_FIELD, key)) continue;
86
+ return { frameKey: key, args: canonicalToolArgumentsFrom(value) };
87
+ }
88
+ return undefined;
89
+ }
90
+
91
+ function canonicalToolArgumentsFrom(value: unknown): Record<string, unknown> {
92
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return {};
93
+ return Object.fromEntries(
94
+ Object.entries(value as Record<string, unknown>).map(([key, child]) => [
95
+ key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[\s-]+/g, "_").toLowerCase(),
96
+ child,
97
+ ]),
98
+ );
99
+ }
100
+
101
+ /** Resolves a tool path against the workspace root; null when it escapes. */
102
+ function resolveInside(root: string, input: string | undefined): string | null {
103
+ if (input === undefined || input.trim().length === 0) return null;
104
+ const raw = input.trim();
105
+ const abs = isAbsolute(raw) ? raw : resolve(root, raw);
106
+ const rel = relative(root, abs);
107
+ if (rel.startsWith("..") || isAbsolute(rel)) return null;
108
+ return abs;
109
+ }
110
+
111
+ /** Best-effort realpath for exists checks; falls back to the resolved path. */
112
+ function safeRealpath(p: string): string {
113
+ try {
114
+ return realpathSync(p);
115
+ } catch {
116
+ return p;
117
+ }
118
+ }
119
+
120
+ function isFile(p: string): boolean {
121
+ try {
122
+ return lstatSync(p).isFile();
123
+ } catch {
124
+ return false;
125
+ }
126
+ }
127
+
128
+ function isDir(p: string): boolean {
129
+ try {
130
+ return lstatSync(p).isDirectory();
131
+ } catch {
132
+ return false;
133
+ }
134
+ }
135
+
136
+ function truncate(text: string, maxBytes: number): { text: string; truncated: boolean } {
137
+ const bytes = Buffer.byteLength(text, "utf8");
138
+ if (bytes <= maxBytes) return { text, truncated: false };
139
+ // Trim by characters so we never split a UTF-8 sequence.
140
+ let out = text;
141
+ while (Buffer.byteLength(out, "utf8") > maxBytes) {
142
+ out = out.slice(0, -1);
143
+ }
144
+ return { text: out, truncated: true };
145
+ }
146
+
147
+ function shellSuccess(args: Record<string, unknown>, cwd: string, stdout: string, stderr: string, exitCode: number, startedMs: number): Record<string, unknown> {
148
+ return {
149
+ success: {
150
+ command: String(args["command"] ?? ""),
151
+ working_directory: String(args["working_directory"] ?? cwd),
152
+ exit_code: exitCode,
153
+ stdout,
154
+ stderr,
155
+ execution_time: Date.now() - startedMs,
156
+ },
157
+ };
158
+ }
159
+
160
+ function shellFailure(args: Record<string, unknown>, cwd: string, stdout: string, stderr: string, exitCode: number, startedMs: number): Record<string, unknown> {
161
+ return {
162
+ failure: {
163
+ command: String(args["command"] ?? ""),
164
+ working_directory: String(args["working_directory"] ?? cwd),
165
+ exit_code: exitCode,
166
+ stdout,
167
+ stderr,
168
+ execution_time: Date.now() - startedMs,
169
+ },
170
+ };
171
+ }
172
+
173
+ /** Parses a command string into an argv array (Windows-aware). */
174
+ function parseCommand(command: string): string[] {
175
+ const argv: string[] = [];
176
+ let current = "";
177
+ let quote: string | null = null;
178
+ for (let i = 0; i < command.length; i++) {
179
+ const ch = command[i]!;
180
+ if (quote !== null) {
181
+ if (ch === quote) quote = null;
182
+ else current += ch;
183
+ continue;
184
+ }
185
+ if (ch === '"' || ch === "'") {
186
+ quote = ch;
187
+ continue;
188
+ }
189
+ if (ch === " " || ch === "\t") {
190
+ if (current.length > 0) {
191
+ argv.push(current);
192
+ current = "";
193
+ }
194
+ continue;
195
+ }
196
+ current += ch;
197
+ }
198
+ if (current.length > 0) argv.push(current);
199
+ return argv;
200
+ }
201
+
202
+ async function runShell(
203
+ args: Record<string, unknown>,
204
+ root: string,
205
+ timeoutMs: number,
206
+ ): Promise<Record<string, unknown>> {
207
+ const command = String(args["command"] ?? "").trim();
208
+ if (!command) return { rejected: { reason: "empty command" } };
209
+ const cwd = String(args["working_directory"] ?? root).trim() || root;
210
+ const cwdAbs = resolveInside(root, cwd);
211
+ if (cwdAbs === null) return { rejected: { command, working_directory: cwd, reason: "working directory escapes the workspace root" } };
212
+
213
+ const started = Date.now();
214
+ const shellTimeout = timeoutMs > 0 ? timeoutMs : 30_000;
215
+ const argv = parseCommand(command);
216
+ if (argv.length === 0) return { rejected: { command, working_directory: cwd, reason: "empty command" } };
217
+
218
+ return await new Promise<Record<string, unknown>>((resolvePromise) => {
219
+ const child = spawn(argv[0]!, argv.slice(1), {
220
+ cwd: cwdAbs,
221
+ shell: false,
222
+ windowsHide: true,
223
+ env: { ...process.env, CURSOR_WORKSPACE_ROOT: root },
224
+ });
225
+ let stdout = "";
226
+ let stderr = "";
227
+ let settled = false;
228
+ const timer = setTimeout(() => {
229
+ if (settled) return;
230
+ settled = true;
231
+ child.kill("SIGKILL");
232
+ resolvePromise({
233
+ timeout: {
234
+ command,
235
+ working_directory: cwd,
236
+ timeout_ms: shellTimeout,
237
+ },
238
+ });
239
+ }, shellTimeout);
240
+ child.stdout.on("data", (d: Buffer) => {
241
+ stdout += d.toString("utf8");
242
+ if (Buffer.byteLength(stdout) > CURSOR_TOOL_MAX_OUTPUT_BYTES * 2) child.stdout.pause();
243
+ });
244
+ child.stderr.on("data", (d: Buffer) => {
245
+ stderr += d.toString("utf8");
246
+ if (Buffer.byteLength(stderr) > CURSOR_TOOL_MAX_OUTPUT_BYTES * 2) child.stderr.pause();
247
+ });
248
+ child.on("error", (err) => {
249
+ if (settled) return;
250
+ settled = true;
251
+ clearTimeout(timer);
252
+ resolvePromise({
253
+ failure: {
254
+ command,
255
+ working_directory: cwd,
256
+ exit_code: -1,
257
+ stdout,
258
+ stderr: `${stderr}\n${err.message}`.trim(),
259
+ execution_time: Date.now() - started,
260
+ },
261
+ });
262
+ });
263
+ child.on("close", (code) => {
264
+ if (settled) return;
265
+ settled = true;
266
+ clearTimeout(timer);
267
+ const out = truncate(stdout, CURSOR_TOOL_MAX_OUTPUT_BYTES);
268
+ const err = truncate(stderr, CURSOR_TOOL_MAX_OUTPUT_BYTES);
269
+ if (code === 0) {
270
+ resolvePromise(shellSuccess(args, cwd, out.text, err.text, 0, started));
271
+ } else {
272
+ resolvePromise(shellFailure(args, cwd, out.text, err.text, code ?? -1, started));
273
+ }
274
+ });
275
+ });
276
+ }
277
+
278
+ async function readFile(
279
+ args: Record<string, unknown>,
280
+ root: string,
281
+ pi: boolean,
282
+ ): Promise<Record<string, unknown>> {
283
+ const path = resolveInside(root, String(args["path"] ?? ""));
284
+ if (path === null) {
285
+ return pi
286
+ ? { error: { error: "path escapes the workspace root" } }
287
+ : { rejected: { path: String(args["path"] ?? ""), reason: "path escapes the workspace root" } };
288
+ }
289
+ if (!existsSync(path)) {
290
+ return pi
291
+ ? { error: { error: `file not found: ${path}` } }
292
+ : { file_not_found: { path } };
293
+ }
294
+ if (!isFile(path)) {
295
+ return pi
296
+ ? { error: { error: `not a regular file: ${path}` } }
297
+ : { invalid_file: { path, reason: "not a regular file" } };
298
+ }
299
+ try {
300
+ const size = lstatSync(path).size;
301
+ const offset = typeof args["offset"] === "number" ? Math.max(0, args["offset"]) : 0;
302
+ const raw = readFileSync(path, "utf8");
303
+ const lines = raw.split("\n");
304
+ let content: string;
305
+ let truncation: Record<string, unknown> | undefined;
306
+ if (offset > 0 || lines.length > CURSOR_TOOL_MAX_LINES) {
307
+ const slice = lines.slice(offset, offset + CURSOR_TOOL_MAX_LINES).join("\n");
308
+ const out = truncate(slice, CURSOR_TOOL_MAX_OUTPUT_BYTES);
309
+ content = out.text;
310
+ if (out.truncated || lines.length > CURSOR_TOOL_MAX_LINES) {
311
+ truncation = {
312
+ truncated: true,
313
+ truncated_by: "line_limit",
314
+ total_lines: lines.length,
315
+ output_lines: Math.min(lines.length - offset, CURSOR_TOOL_MAX_LINES),
316
+ output_bytes: Buffer.byteLength(content),
317
+ max_lines: CURSOR_TOOL_MAX_LINES,
318
+ };
319
+ }
320
+ } else {
321
+ const out = truncate(raw, CURSOR_TOOL_MAX_OUTPUT_BYTES);
322
+ content = out.text;
323
+ if (out.truncated) {
324
+ truncation = {
325
+ truncated: true,
326
+ truncated_by: "byte_limit",
327
+ total_lines: lines.length,
328
+ output_lines: lines.length,
329
+ output_bytes: Buffer.byteLength(content),
330
+ max_bytes: CURSOR_TOOL_MAX_OUTPUT_BYTES,
331
+ };
332
+ }
333
+ }
334
+ if (pi) {
335
+ return truncation
336
+ ? { success: { output: content, truncation } }
337
+ : { success: { output: content } };
338
+ }
339
+ return {
340
+ success: {
341
+ path,
342
+ content,
343
+ total_lines: lines.length,
344
+ file_size: size,
345
+ truncated: truncation !== undefined,
346
+ },
347
+ };
348
+ } catch (err) {
349
+ const msg = err instanceof Error ? err.message : String(err);
350
+ return pi ? { error: { error: msg } } : { error: { path, error: msg } };
351
+ }
352
+ }
353
+
354
+ function globFiles(args: Record<string, unknown>, root: string): Record<string, unknown> {
355
+ const target = resolveInside(root, String(args["target_directory"] ?? "."));
356
+ if (target === null) return { error: "target directory escapes the workspace root" };
357
+ if (!isDir(target)) return { error: `not a directory: ${target}` };
358
+ const pattern = String(args["glob_pattern"] ?? "**/*");
359
+ const out: string[] = [];
360
+ const walk = (dir: string, depth: number) => {
361
+ if (depth > 12) return;
362
+ let entries: string[];
363
+ try {
364
+ entries = readdirSync(dir);
365
+ } catch {
366
+ return;
367
+ }
368
+ for (const entry of entries) {
369
+ const full = join(dir, entry);
370
+ const rel = relative(target, full);
371
+ let isDirEntry = false;
372
+ try {
373
+ isDirEntry = lstatSync(full).isDirectory();
374
+ } catch {
375
+ continue;
376
+ }
377
+ if (isDirEntry) {
378
+ const match = simpleGlobMatch(pattern, rel);
379
+ if (match) out.push(rel);
380
+ walk(full, depth + 1);
381
+ } else {
382
+ const match = simpleGlobMatch(pattern, rel);
383
+ if (match) out.push(rel);
384
+ }
385
+ }
386
+ };
387
+ walk(target, 0);
388
+ out.sort();
389
+ return { files: out };
390
+ }
391
+
392
+ /**
393
+ * Minimal glob matcher: `*` (within a segment), `**` (any depth), `?`.
394
+ * The value is normalized to forward slashes first, so a Windows `\` path
395
+ * matches exactly like a POSIX `/` path — otherwise `*.ts` matches
396
+ * `src\a.ts` on Windows but not `src/a.ts`, and the same pattern behaves
397
+ * differently depending on which separator `relative()` happened to emit.
398
+ */
399
+ function simpleGlobMatch(pattern: string, value: string): boolean {
400
+ const normalized = value.replace(/\\/g, "/");
401
+ const re = pattern
402
+ .replace(/[.+^${}()|[\]\\]/g, "\\$&")
403
+ .replace(/\*\*/g, "__GLOBSTAR__")
404
+ .replace(/\*/g, "[^/]*")
405
+ .replace(/__GLOBSTAR__/g, ".*")
406
+ .replace(/\?/g, ".");
407
+ return new RegExp(`^${re}$`).test(normalized);
408
+ }
409
+
410
+ function grepFiles(args: Record<string, unknown>, root: string, pi: boolean): Record<string, unknown> {
411
+ const pattern = String(args["pattern"] ?? "");
412
+ if (!pattern) return pi ? { error: { error: "empty pattern" } } : { error: { error: "empty pattern" } };
413
+ const path = String(args["path"] ?? "");
414
+ const searchRoot = path ? resolveInside(root, path) : root;
415
+ if (searchRoot === null) {
416
+ return pi ? { error: { error: "path escapes the workspace root" } } : { error: { error: "path escapes the workspace root" } };
417
+ }
418
+ const glob = String(args["glob"] ?? "");
419
+ const ignoreCase = args["ignore_case"] === true || args["case_insensitive"] === true;
420
+ const literal = args["literal"] === true;
421
+ const limit = typeof args["limit"] === "number" ? args["limit"] : 200;
422
+ const flags = ignoreCase ? "gi" : "g";
423
+ const re = literal ? new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), flags) : new RegExp(pattern, flags);
424
+ const matches: Array<{ file: string; matches: Array<{ lineNumber: number; content: string }> }> = [];
425
+ let totalMatched = 0;
426
+
427
+ const walk = (dir: string, depth: number) => {
428
+ if (depth > 12 || totalMatched >= limit) return;
429
+ let entries: string[];
430
+ try {
431
+ entries = readdirSync(dir);
432
+ } catch {
433
+ return;
434
+ }
435
+ for (const entry of entries) {
436
+ if (totalMatched >= limit) return;
437
+ if (entry.startsWith(".")) continue;
438
+ const full = join(dir, entry);
439
+ const rel = relative(searchRoot, full);
440
+ let isDirEntry = false;
441
+ try {
442
+ isDirEntry = lstatSync(full).isDirectory();
443
+ } catch {
444
+ continue;
445
+ }
446
+ if (isDirEntry) {
447
+ walk(full, depth + 1);
448
+ continue;
449
+ }
450
+ if (glob && !simpleGlobMatch(glob, rel)) continue;
451
+ try {
452
+ const content = readFileSync(full, "utf8");
453
+ const lines = content.split("\n");
454
+ const fileMatches: Array<{ lineNumber: number; content: string }> = [];
455
+ for (let i = 0; i < lines.length && fileMatches.length < 20; i++) {
456
+ re.lastIndex = 0;
457
+ if (re.test(lines[i]!)) {
458
+ fileMatches.push({ lineNumber: i + 1, content: lines[i]!.slice(0, 500) });
459
+ }
460
+ }
461
+ if (fileMatches.length > 0) {
462
+ matches.push({ file: rel, matches: fileMatches });
463
+ totalMatched += fileMatches.length;
464
+ }
465
+ } catch {
466
+ // unreadable file — skip
467
+ }
468
+ }
469
+ };
470
+ walk(searchRoot, 0);
471
+
472
+ if (pi) {
473
+ const lines = matches.flatMap((m) => m.matches.map((mm) => `${m.file}:${mm.lineNumber}:${mm.content}`));
474
+ const out = truncate(lines.join("\n"), CURSOR_TOOL_MAX_OUTPUT_BYTES);
475
+ return { success: { output: out.text, truncation: out.truncated ? { truncated: true, truncated_by: "byte_limit" } : undefined } };
476
+ }
477
+ return {
478
+ success: {
479
+ pattern,
480
+ path: path || root,
481
+ output_mode: "content",
482
+ workspace_results: {
483
+ "": {
484
+ content: {
485
+ matches,
486
+ total_lines: totalMatched,
487
+ total_matched_lines: totalMatched,
488
+ client_truncated: totalMatched >= limit,
489
+ ripgrep_truncated: false,
490
+ },
491
+ },
492
+ },
493
+ },
494
+ };
495
+ }
496
+
497
+ function lsTree(args: Record<string, unknown>, root: string, pi: boolean): Record<string, unknown> {
498
+ const path = resolveInside(root, String(args["path"] ?? "."));
499
+ if (path === null) {
500
+ return pi ? { error: { error: "path escapes the workspace root" } } : { rejected: { path: String(args["path"] ?? ""), reason: "path escapes the workspace root" } };
501
+ }
502
+ if (!isDir(path)) {
503
+ return pi ? { error: { error: `not a directory: ${path}` } } : { error: { path, error: "not a directory" } };
504
+ }
505
+ const buildNode = (dir: string): Record<string, unknown> => {
506
+ const childrenDirs: Record<string, unknown>[] = [];
507
+ const childrenFiles: Array<{ name: string }> = [];
508
+ let entries: string[];
509
+ try {
510
+ entries = readdirSync(dir).sort();
511
+ } catch {
512
+ entries = [];
513
+ }
514
+ for (const entry of entries) {
515
+ const full = join(dir, entry);
516
+ try {
517
+ if (lstatSync(full).isDirectory()) childrenDirs.push(buildNode(full));
518
+ else childrenFiles.push({ name: entry });
519
+ } catch {
520
+ // skip
521
+ }
522
+ }
523
+ return {
524
+ abs_path: dir,
525
+ children_dirs: childrenDirs,
526
+ children_files: childrenFiles,
527
+ num_files: childrenFiles.length,
528
+ };
529
+ };
530
+ const tree = buildNode(path);
531
+ if (pi) {
532
+ const lines: string[] = [];
533
+ const flatten = (node: Record<string, unknown>, prefix: string) => {
534
+ for (const d of (node["children_dirs"] ?? []) as Record<string, unknown>[]) {
535
+ lines.push(`${prefix}${d["abs_path"]}/`);
536
+ flatten(d, `${prefix} `);
537
+ }
538
+ for (const f of (node["children_files"] ?? []) as Array<{ name: string }>) {
539
+ lines.push(`${prefix}${f.name}`);
540
+ }
541
+ };
542
+ flatten(tree, "");
543
+ const out = truncate(lines.join("\n"), CURSOR_TOOL_MAX_OUTPUT_BYTES);
544
+ return { success: { output: out.text, truncation: out.truncated ? { truncated: true, truncated_by: "byte_limit" } : undefined } };
545
+ }
546
+ return { success: { directory_tree_root: tree } };
547
+ }
548
+
549
+ function editFile(args: Record<string, unknown>, root: string, pi: boolean): Record<string, unknown> {
550
+ const path = resolveInside(root, String(args["path"] ?? ""));
551
+ if (path === null) {
552
+ return pi ? { rejected: { reason: "path escapes the workspace root" } } : { rejected: { path: String(args["path"] ?? ""), reason: "path escapes the workspace root" } };
553
+ }
554
+ if (!existsSync(path)) {
555
+ return pi ? { error: { error: `file not found: ${path}` } } : { file_not_found: { path } };
556
+ }
557
+ if (!isFile(path)) {
558
+ return pi ? { error: { error: `not a regular file: ${path}` } } : { invalid_file: { path, reason: "not a regular file" } };
559
+ }
560
+ try {
561
+ const original = readFileSync(path, "utf8");
562
+ let content = original;
563
+ // Legacy edit: stream_content is the *new full content* (old EditToolArgs
564
+ // semantics — the IDE sends the entire replacement file).
565
+ if (typeof args["stream_content"] === "string" && !pi) {
566
+ content = args["stream_content"];
567
+ } else {
568
+ const edits = (args["edits"] ?? []) as Array<{ old_text?: string; new_text?: string }>;
569
+ for (const edit of edits) {
570
+ const oldText = edit.old_text ?? "";
571
+ const newText = edit.new_text ?? "";
572
+ if (!oldText) continue;
573
+ if (!content.includes(oldText)) {
574
+ return pi
575
+ ? { error: { error: `old_text not found in ${path}` } }
576
+ : { error: { path, error: `old_text not found in ${path}` } };
577
+ }
578
+ content = content.replace(oldText, newText);
579
+ }
580
+ }
581
+ writeFileSync(path, content, "utf8");
582
+ const lines = content.split("\n");
583
+ const out = truncate(content, CURSOR_TOOL_MAX_OUTPUT_BYTES);
584
+ if (pi) {
585
+ return {
586
+ success: {
587
+ output: out.text,
588
+ truncation: out.truncated ? { truncated: true, truncated_by: "byte_limit" } : undefined,
589
+ },
590
+ };
591
+ }
592
+ return {
593
+ success: {
594
+ path,
595
+ content: out.text,
596
+ total_lines: lines.length,
597
+ file_size: Buffer.byteLength(content),
598
+ truncated: out.truncated,
599
+ },
600
+ };
601
+ } catch (err) {
602
+ const msg = err instanceof Error ? err.message : String(err);
603
+ return pi ? { error: { error: msg } } : { error: { path, error: msg } };
604
+ }
605
+ }
606
+
607
+ function writeFileTool(args: Record<string, unknown>, root: string, pi: boolean): Record<string, unknown> {
608
+ const path = resolveInside(root, String(args["path"] ?? ""));
609
+ if (path === null) {
610
+ return pi ? { rejected: { reason: "path escapes the workspace root" } } : { rejected: { path: String(args["path"] ?? ""), reason: "path escapes the workspace root" } };
611
+ }
612
+ let content = "";
613
+ if (typeof args["content"] === "string") content = args["content"];
614
+ else if (typeof args["file_text"] === "string") content = args["file_text"];
615
+ else if (args["file_bytes"] instanceof Uint8Array) content = new TextDecoder().decode(args["file_bytes"] as Uint8Array);
616
+ try {
617
+ mkdirSync(dirname(path), { recursive: true });
618
+ writeFileSync(path, content, "utf8");
619
+ const lines = content.split("\n");
620
+ if (pi) {
621
+ const out = truncate(content, CURSOR_TOOL_MAX_OUTPUT_BYTES);
622
+ return {
623
+ success: {
624
+ output: out.text,
625
+ truncation: out.truncated ? { truncated: true, truncated_by: "byte_limit" } : undefined,
626
+ },
627
+ };
628
+ }
629
+ return {
630
+ success: {
631
+ path,
632
+ lines_created: lines.length,
633
+ file_size: Buffer.byteLength(content),
634
+ file_content_after_write: truncate(content, CURSOR_TOOL_MAX_OUTPUT_BYTES).text,
635
+ },
636
+ };
637
+ } catch (err) {
638
+ const msg = err instanceof Error ? err.message : String(err);
639
+ return pi ? { error: { error: msg } } : { error: { path, error: msg } };
640
+ }
641
+ }
642
+
643
+ function deleteFileTool(args: Record<string, unknown>, root: string): Record<string, unknown> {
644
+ const path = resolveInside(root, String(args["path"] ?? ""));
645
+ if (path === null) {
646
+ return { rejected: { path: String(args["path"] ?? ""), reason: "path escapes the workspace root" } };
647
+ }
648
+ if (path === safeRealpath(root)) {
649
+ return { permission_denied: { path, client_visible_error: "refusing to delete the workspace root", is_readonly: true } };
650
+ }
651
+ if (!existsSync(path)) {
652
+ return { error: { path, error: "file not found" } };
653
+ }
654
+ try {
655
+ unlinkSync(path);
656
+ return { success: { path, deleted_file: path } };
657
+ } catch (err) {
658
+ return { error: { path, error: err instanceof Error ? err.message : String(err) } };
659
+ }
660
+ }
661
+
662
+ /**
663
+ * Executes one Cursor-native exec frame. `frameKey` is the exec-frame key
664
+ * (`read_args`, `pi_read_args`, …); `args` are the decoded canonical (snake)
665
+ * arguments. Returns the result message for the frame's result oneof, or
666
+ * `undefined` when the frame is not executable (the caller refuses it).
667
+ */
668
+ export async function executeNativeTool(
669
+ frameKey: string,
670
+ args: Record<string, unknown>,
671
+ root: string,
672
+ timeoutMs = 30_000,
673
+ ): Promise<NativeToolResult | undefined> {
674
+ const field = EXEC_RESULT_FIELD[frameKey];
675
+ if (field === undefined) return undefined;
676
+
677
+ let message: Record<string, unknown>;
678
+ switch (frameKey) {
679
+ case "read_args":
680
+ message = await readFile(args, root, false);
681
+ break;
682
+ case "ct_read_args":
683
+ message = await readFile(args, root, true);
684
+ break;
685
+ case "shell_args":
686
+ message = await runShell(args, root, timeoutMs);
687
+ break;
688
+ case "ct_bash_args":
689
+ message = await runShell(args, root, timeoutMs);
690
+ break;
691
+ case "glob_args":
692
+ message = globFiles(args, root);
693
+ break;
694
+ case "grep_args":
695
+ message = grepFiles(args, root, false);
696
+ break;
697
+ case "ct_grep_args":
698
+ message = grepFiles(args, root, true);
699
+ break;
700
+ case "ls_args":
701
+ message = lsTree(args, root, false);
702
+ break;
703
+ case "ct_ls_args":
704
+ message = lsTree(args, root, true);
705
+ break;
706
+ case "edit_args":
707
+ message = editFile(args, root, false);
708
+ break;
709
+ case "ct_edit_args":
710
+ message = editFile(args, root, true);
711
+ break;
712
+ case "write_args":
713
+ message = writeFileTool(args, root, false);
714
+ break;
715
+ case "ct_write_args":
716
+ message = writeFileTool(args, root, true);
717
+ break;
718
+ case "delete_args":
719
+ message = deleteFileTool(args, root);
720
+ break;
721
+ case "ct_find_args": {
722
+ const path = resolveInside(root, String(args["path"] ?? "."));
723
+ if (path === null) message = { error: { error: "path escapes the workspace root" } };
724
+ else {
725
+ const pattern = String(args["pattern"] ?? "*");
726
+ const files = globFiles({ target_directory: path, glob_pattern: pattern }, root);
727
+ message = { success: { output: (files["files"] as string[] ?? []).join("\n") } };
728
+ }
729
+ break;
730
+ }
731
+ default:
732
+ return undefined;
733
+ }
734
+
735
+ return { field, message };
736
+ }
737
+
738
+ /** Blobs a file for output-blob results (unused here; kept for symmetry). */
739
+ export function fileDigest(path: string): string {
740
+ try {
741
+ return createHash("sha256").update(readFileSync(path)).digest("hex");
742
+ } catch {
743
+ return "";
744
+ }
745
+ }