@tonyclaw/agent-inspector 2.0.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 (400) hide show
  1. package/.output/cli.js +1611 -0
  2. package/.output/nitro.json +17 -0
  3. package/.output/public/assets/CompareDrawer-CU5ZrWcL.js +1 -0
  4. package/.output/public/assets/ProxyViewerContainer-pEBqVp1d.js +101 -0
  5. package/.output/public/assets/ReplayDialog-F58yNg5j.js +1 -0
  6. package/.output/public/assets/RequestAnatomy-C9lT0qE_.js +1 -0
  7. package/.output/public/assets/ResponseView-DHJq6bnz.js +1 -0
  8. package/.output/public/assets/StreamingChunkSequence-BTgfpFUT.js +1 -0
  9. package/.output/public/assets/_sessionId-DsNRbnNm.js +1 -0
  10. package/.output/public/assets/alibaba-TTwafVwX.svg +1 -0
  11. package/.output/public/assets/index-CpWG2hFn.css +1 -0
  12. package/.output/public/assets/index-DmBV8Gve.js +1 -0
  13. package/.output/public/assets/json-viewer-CZVYLR8j.js +14 -0
  14. package/.output/public/assets/main-DHs7FBK3.js +18 -0
  15. package/.output/public/assets/minimax-BPMzvuL-.jpeg +0 -0
  16. package/.output/public/assets/qwen-CONDcHqt.png +0 -0
  17. package/.output/public/assets/zhipuai-BPNAnxo-.svg +219 -0
  18. package/.output/server/_chunks/ssr-renderer.mjs +22 -0
  19. package/.output/server/_libs/@radix-ui/react-accessible-icon+[...].mjs +1 -0
  20. package/.output/server/_libs/@radix-ui/react-dismissable-layer+[...].mjs +210 -0
  21. package/.output/server/_libs/@radix-ui/react-navigation-menu+[...].mjs +2 -0
  22. package/.output/server/_libs/@radix-ui/react-one-time-password-field+[...].mjs +2 -0
  23. package/.output/server/_libs/@radix-ui/react-password-toggle-field+[...].mjs +2 -0
  24. package/.output/server/_libs/@radix-ui/react-use-callback-ref+[...].mjs +11 -0
  25. package/.output/server/_libs/@radix-ui/react-use-controllable-state+[...].mjs +69 -0
  26. package/.output/server/_libs/@radix-ui/react-use-effect-event+[...].mjs +1 -0
  27. package/.output/server/_libs/@radix-ui/react-use-escape-keydown+[...].mjs +17 -0
  28. package/.output/server/_libs/@radix-ui/react-use-is-hydrated+[...].mjs +1 -0
  29. package/.output/server/_libs/@radix-ui/react-use-layout-effect+[...].mjs +6 -0
  30. package/.output/server/_libs/@radix-ui/react-visually-hidden+[...].mjs +34 -0
  31. package/.output/server/_libs/ajv-formats.mjs +330 -0
  32. package/.output/server/_libs/ajv.mjs +11444 -0
  33. package/.output/server/_libs/aria-hidden.mjs +122 -0
  34. package/.output/server/_libs/atomically.mjs +152 -0
  35. package/.output/server/_libs/bail.mjs +8 -0
  36. package/.output/server/_libs/cfworker__json-schema.mjs +1 -0
  37. package/.output/server/_libs/character-entities.mjs +2130 -0
  38. package/.output/server/_libs/class-variance-authority.mjs +44 -0
  39. package/.output/server/_libs/clsx.mjs +16 -0
  40. package/.output/server/_libs/comma-separated-tokens.mjs +10 -0
  41. package/.output/server/_libs/conf.mjs +635 -0
  42. package/.output/server/_libs/cookie-es.mjs +58 -0
  43. package/.output/server/_libs/core-util-is.mjs +75 -0
  44. package/.output/server/_libs/croner.mjs +1 -0
  45. package/.output/server/_libs/crossws.mjs +1 -0
  46. package/.output/server/_libs/debounce-fn.mjs +69 -0
  47. package/.output/server/_libs/decode-named-character-reference+[...].mjs +8 -0
  48. package/.output/server/_libs/dequal.mjs +27 -0
  49. package/.output/server/_libs/detect-node-es.mjs +1 -0
  50. package/.output/server/_libs/devlop.mjs +8 -0
  51. package/.output/server/_libs/diff.mjs +320 -0
  52. package/.output/server/_libs/dot-prop.mjs +265 -0
  53. package/.output/server/_libs/env-paths.mjs +57 -0
  54. package/.output/server/_libs/estree-util-is-identifier-name.mjs +11 -0
  55. package/.output/server/_libs/extend.mjs +97 -0
  56. package/.output/server/_libs/fast-deep-equal.mjs +38 -0
  57. package/.output/server/_libs/fast-uri.mjs +812 -0
  58. package/.output/server/_libs/floating-ui__core.mjs +725 -0
  59. package/.output/server/_libs/floating-ui__dom.mjs +622 -0
  60. package/.output/server/_libs/floating-ui__react-dom.mjs +292 -0
  61. package/.output/server/_libs/floating-ui__utils.mjs +320 -0
  62. package/.output/server/_libs/get-nonce.mjs +9 -0
  63. package/.output/server/_libs/h3-v2.mjs +276 -0
  64. package/.output/server/_libs/h3.mjs +408 -0
  65. package/.output/server/_libs/hast-util-to-jsx-runtime.mjs +388 -0
  66. package/.output/server/_libs/hast-util-whitespace.mjs +10 -0
  67. package/.output/server/_libs/hookable.mjs +1 -0
  68. package/.output/server/_libs/html-url-attributes.mjs +26 -0
  69. package/.output/server/_libs/immediate.mjs +74 -0
  70. package/.output/server/_libs/inherits.mjs +50 -0
  71. package/.output/server/_libs/inline-style-parser.mjs +142 -0
  72. package/.output/server/_libs/is-plain-obj.mjs +10 -0
  73. package/.output/server/_libs/isarray.mjs +14 -0
  74. package/.output/server/_libs/isbot.mjs +20 -0
  75. package/.output/server/_libs/json-schema-traverse.mjs +180 -0
  76. package/.output/server/_libs/jszip.mjs +3051 -0
  77. package/.output/server/_libs/lie.mjs +273 -0
  78. package/.output/server/_libs/lucide-react.mjs +492 -0
  79. package/.output/server/_libs/mdast-util-from-markdown.mjs +717 -0
  80. package/.output/server/_libs/mdast-util-to-hast.mjs +710 -0
  81. package/.output/server/_libs/mdast-util-to-string.mjs +38 -0
  82. package/.output/server/_libs/micromark-core-commonmark.mjs +2259 -0
  83. package/.output/server/_libs/micromark-factory-destination.mjs +94 -0
  84. package/.output/server/_libs/micromark-factory-label.mjs +63 -0
  85. package/.output/server/_libs/micromark-factory-space.mjs +24 -0
  86. package/.output/server/_libs/micromark-factory-title.mjs +65 -0
  87. package/.output/server/_libs/micromark-factory-whitespace.mjs +22 -0
  88. package/.output/server/_libs/micromark-util-character.mjs +44 -0
  89. package/.output/server/_libs/micromark-util-chunked.mjs +36 -0
  90. package/.output/server/_libs/micromark-util-classify-character+[...].mjs +12 -0
  91. package/.output/server/_libs/micromark-util-combine-extensions+[...].mjs +41 -0
  92. package/.output/server/_libs/micromark-util-decode-numeric-character-reference+[...].mjs +19 -0
  93. package/.output/server/_libs/micromark-util-decode-string.mjs +21 -0
  94. package/.output/server/_libs/micromark-util-encode.mjs +1 -0
  95. package/.output/server/_libs/micromark-util-html-tag-name.mjs +69 -0
  96. package/.output/server/_libs/micromark-util-normalize-identifier+[...].mjs +6 -0
  97. package/.output/server/_libs/micromark-util-resolve-all.mjs +15 -0
  98. package/.output/server/_libs/micromark-util-sanitize-uri.mjs +41 -0
  99. package/.output/server/_libs/micromark-util-subtokenize.mjs +346 -0
  100. package/.output/server/_libs/micromark.mjs +906 -0
  101. package/.output/server/_libs/mimic-function.mjs +47 -0
  102. package/.output/server/_libs/modelcontextprotocol__server.mjs +9738 -0
  103. package/.output/server/_libs/ocache.mjs +1 -0
  104. package/.output/server/_libs/ohash.mjs +1 -0
  105. package/.output/server/_libs/pako.mjs +4223 -0
  106. package/.output/server/_libs/process-nextick-args.mjs +48 -0
  107. package/.output/server/_libs/property-information.mjs +1209 -0
  108. package/.output/server/_libs/radix-ui.mjs +1 -0
  109. package/.output/server/_libs/radix-ui__number.mjs +6 -0
  110. package/.output/server/_libs/radix-ui__primitive.mjs +11 -0
  111. package/.output/server/_libs/radix-ui__react-accordion.mjs +1 -0
  112. package/.output/server/_libs/radix-ui__react-alert-dialog.mjs +1 -0
  113. package/.output/server/_libs/radix-ui__react-arrow.mjs +23 -0
  114. package/.output/server/_libs/radix-ui__react-aspect-ratio.mjs +1 -0
  115. package/.output/server/_libs/radix-ui__react-avatar.mjs +1 -0
  116. package/.output/server/_libs/radix-ui__react-checkbox.mjs +1 -0
  117. package/.output/server/_libs/radix-ui__react-collapsible.mjs +144 -0
  118. package/.output/server/_libs/radix-ui__react-collection.mjs +69 -0
  119. package/.output/server/_libs/radix-ui__react-compose-refs.mjs +39 -0
  120. package/.output/server/_libs/radix-ui__react-context-menu.mjs +1 -0
  121. package/.output/server/_libs/radix-ui__react-context.mjs +78 -0
  122. package/.output/server/_libs/radix-ui__react-dialog.mjs +325 -0
  123. package/.output/server/_libs/radix-ui__react-direction.mjs +9 -0
  124. package/.output/server/_libs/radix-ui__react-dropdown-menu.mjs +1 -0
  125. package/.output/server/_libs/radix-ui__react-focus-guards.mjs +29 -0
  126. package/.output/server/_libs/radix-ui__react-focus-scope.mjs +206 -0
  127. package/.output/server/_libs/radix-ui__react-form.mjs +1 -0
  128. package/.output/server/_libs/radix-ui__react-hover-card.mjs +1 -0
  129. package/.output/server/_libs/radix-ui__react-id.mjs +14 -0
  130. package/.output/server/_libs/radix-ui__react-label.mjs +1 -0
  131. package/.output/server/_libs/radix-ui__react-menu.mjs +1 -0
  132. package/.output/server/_libs/radix-ui__react-menubar.mjs +1 -0
  133. package/.output/server/_libs/radix-ui__react-popover.mjs +1 -0
  134. package/.output/server/_libs/radix-ui__react-popper.mjs +286 -0
  135. package/.output/server/_libs/radix-ui__react-portal.mjs +16 -0
  136. package/.output/server/_libs/radix-ui__react-presence.mjs +128 -0
  137. package/.output/server/_libs/radix-ui__react-primitive.mjs +42 -0
  138. package/.output/server/_libs/radix-ui__react-progress.mjs +1 -0
  139. package/.output/server/_libs/radix-ui__react-radio-group.mjs +1 -0
  140. package/.output/server/_libs/radix-ui__react-roving-focus.mjs +224 -0
  141. package/.output/server/_libs/radix-ui__react-scroll-area.mjs +721 -0
  142. package/.output/server/_libs/radix-ui__react-select.mjs +1163 -0
  143. package/.output/server/_libs/radix-ui__react-separator.mjs +28 -0
  144. package/.output/server/_libs/radix-ui__react-slider.mjs +1 -0
  145. package/.output/server/_libs/radix-ui__react-slot.mjs +99 -0
  146. package/.output/server/_libs/radix-ui__react-switch.mjs +1 -0
  147. package/.output/server/_libs/radix-ui__react-tabs.mjs +189 -0
  148. package/.output/server/_libs/radix-ui__react-toast.mjs +2 -0
  149. package/.output/server/_libs/radix-ui__react-toggle-group.mjs +1 -0
  150. package/.output/server/_libs/radix-ui__react-toggle.mjs +1 -0
  151. package/.output/server/_libs/radix-ui__react-toolbar.mjs +1 -0
  152. package/.output/server/_libs/radix-ui__react-tooltip.mjs +495 -0
  153. package/.output/server/_libs/radix-ui__react-use-previous.mjs +14 -0
  154. package/.output/server/_libs/radix-ui__react-use-size.mjs +39 -0
  155. package/.output/server/_libs/react-dom.mjs +10781 -0
  156. package/.output/server/_libs/react-markdown.mjs +147 -0
  157. package/.output/server/_libs/react-remove-scroll-bar.mjs +82 -0
  158. package/.output/server/_libs/react-remove-scroll.mjs +328 -0
  159. package/.output/server/_libs/react-style-singleton.mjs +69 -0
  160. package/.output/server/_libs/react.mjs +515 -0
  161. package/.output/server/_libs/readable-stream.mjs +1518 -0
  162. package/.output/server/_libs/remark-parse.mjs +19 -0
  163. package/.output/server/_libs/remark-rehype.mjs +21 -0
  164. package/.output/server/_libs/rou3.mjs +14 -0
  165. package/.output/server/_libs/safe-buffer.mjs +64 -0
  166. package/.output/server/_libs/semver.mjs +1938 -0
  167. package/.output/server/_libs/seroval-plugins.mjs +58 -0
  168. package/.output/server/_libs/seroval.mjs +1765 -0
  169. package/.output/server/_libs/setimmediate.mjs +152 -0
  170. package/.output/server/_libs/space-separated-tokens.mjs +6 -0
  171. package/.output/server/_libs/srvx.mjs +1029 -0
  172. package/.output/server/_libs/stubborn-fs.mjs +91 -0
  173. package/.output/server/_libs/stubborn-utils.mjs +66 -0
  174. package/.output/server/_libs/style-to-js.mjs +72 -0
  175. package/.output/server/_libs/style-to-object.mjs +38 -0
  176. package/.output/server/_libs/swr.mjs +939 -0
  177. package/.output/server/_libs/tailwind-merge.mjs +3010 -0
  178. package/.output/server/_libs/tanstack__history.mjs +217 -0
  179. package/.output/server/_libs/tanstack__react-router.mjs +1480 -0
  180. package/.output/server/_libs/tanstack__react-store.mjs +1 -0
  181. package/.output/server/_libs/tanstack__react-virtual.mjs +44 -0
  182. package/.output/server/_libs/tanstack__router-core.mjs +4827 -0
  183. package/.output/server/_libs/tanstack__store.mjs +1 -0
  184. package/.output/server/_libs/tanstack__virtual-core.mjs +1225 -0
  185. package/.output/server/_libs/tiny-invariant.mjs +12 -0
  186. package/.output/server/_libs/tiny-warning.mjs +5 -0
  187. package/.output/server/_libs/trim-lines.mjs +41 -0
  188. package/.output/server/_libs/trough.mjs +85 -0
  189. package/.output/server/_libs/tslib.mjs +1 -0
  190. package/.output/server/_libs/ufo.mjs +54 -0
  191. package/.output/server/_libs/uint8array-extras.mjs +69 -0
  192. package/.output/server/_libs/ungap__structured-clone.mjs +212 -0
  193. package/.output/server/_libs/unified.mjs +661 -0
  194. package/.output/server/_libs/unist-util-is.mjs +100 -0
  195. package/.output/server/_libs/unist-util-position.mjs +27 -0
  196. package/.output/server/_libs/unist-util-stringify-position.mjs +27 -0
  197. package/.output/server/_libs/unist-util-visit-parents.mjs +82 -0
  198. package/.output/server/_libs/unist-util-visit.mjs +24 -0
  199. package/.output/server/_libs/unstorage.mjs +1 -0
  200. package/.output/server/_libs/use-callback-ref.mjs +66 -0
  201. package/.output/server/_libs/use-sidecar.mjs +106 -0
  202. package/.output/server/_libs/use-sync-external-store.mjs +64 -0
  203. package/.output/server/_libs/util-deprecate.mjs +12 -0
  204. package/.output/server/_libs/vfile-message.mjs +138 -0
  205. package/.output/server/_libs/vfile.mjs +467 -0
  206. package/.output/server/_libs/when-exit.mjs +53 -0
  207. package/.output/server/_libs/zod.mjs +4524 -0
  208. package/.output/server/_sessionId-wMLPvC5g.mjs +123 -0
  209. package/.output/server/_ssr/CompareDrawer-BU4V0uVf.mjs +1041 -0
  210. package/.output/server/_ssr/ProxyViewerContainer-BnRwFEnn.mjs +5972 -0
  211. package/.output/server/_ssr/ReplayDialog-C7dn9pd_.mjs +322 -0
  212. package/.output/server/_ssr/RequestAnatomy-C1rWpe9-.mjs +353 -0
  213. package/.output/server/_ssr/ResponseView-hGpPaYsf.mjs +602 -0
  214. package/.output/server/_ssr/StreamingChunkSequence-BRWI1r_G.mjs +302 -0
  215. package/.output/server/_ssr/index-BKURLVPz.mjs +118 -0
  216. package/.output/server/_ssr/index.mjs +1184 -0
  217. package/.output/server/_ssr/json-viewer-BBd2DtQP.mjs +515 -0
  218. package/.output/server/_ssr/router-BcZ0D6AB.mjs +6317 -0
  219. package/.output/server/_ssr/start-HYkvq4Ni.mjs +4 -0
  220. package/.output/server/_tanstack-start-manifest_v-1y8ZVxRI.mjs +4 -0
  221. package/.output/server/index.mjs +436 -0
  222. package/.output/server/node_modules/tslib/modules/index.js +70 -0
  223. package/.output/server/node_modules/tslib/modules/package.json +3 -0
  224. package/.output/server/node_modules/tslib/package.json +47 -0
  225. package/.output/server/node_modules/tslib/tslib.js +484 -0
  226. package/.output/server/package.json +9 -0
  227. package/LICENSE +21 -0
  228. package/README.md +52 -0
  229. package/package.json +110 -0
  230. package/src/assets/favicon.svg +31 -0
  231. package/src/assets/logos/alibaba.svg +1 -0
  232. package/src/assets/logos/anthropic.svg +1 -0
  233. package/src/assets/logos/claude-code.svg +4 -0
  234. package/src/assets/logos/deepseek.svg +1 -0
  235. package/src/assets/logos/mcp.png +0 -0
  236. package/src/assets/logos/minimax.jpeg +0 -0
  237. package/src/assets/logos/openai.svg +1 -0
  238. package/src/assets/logos/opencode.svg +4 -0
  239. package/src/assets/logos/qwen.png +0 -0
  240. package/src/assets/logos/zhipuai.svg +219 -0
  241. package/src/cli/detect-tools.ts +147 -0
  242. package/src/cli/doctor.ts +521 -0
  243. package/src/cli/onboard.ts +224 -0
  244. package/src/cli/templates/command-onboard.ts +17 -0
  245. package/src/cli/templates/skill-onboard.ts +547 -0
  246. package/src/cli.ts +345 -0
  247. package/src/components/OnboardingBanner.tsx +67 -0
  248. package/src/components/ProxyViewer.tsx +545 -0
  249. package/src/components/ProxyViewerContainer.tsx +363 -0
  250. package/src/components/providers/ImportWizardDialog.tsx +349 -0
  251. package/src/components/providers/ProviderCard.tsx +474 -0
  252. package/src/components/providers/ProviderForm.tsx +494 -0
  253. package/src/components/providers/ProviderLogo.tsx +117 -0
  254. package/src/components/providers/ProvidersPanel.tsx +619 -0
  255. package/src/components/providers/SettingsDialog.tsx +202 -0
  256. package/src/components/proxy-viewer/CompareDrawer.tsx +893 -0
  257. package/src/components/proxy-viewer/ConversationGroup.tsx +107 -0
  258. package/src/components/proxy-viewer/ConversationHeader.tsx +300 -0
  259. package/src/components/proxy-viewer/LogEntry.tsx +543 -0
  260. package/src/components/proxy-viewer/LogEntryHeader.tsx +501 -0
  261. package/src/components/proxy-viewer/ReplayDialog.tsx +218 -0
  262. package/src/components/proxy-viewer/ResponseView.tsx +171 -0
  263. package/src/components/proxy-viewer/StreamingChunkSequence.tsx +188 -0
  264. package/src/components/proxy-viewer/ThreadConnector.tsx +136 -0
  265. package/src/components/proxy-viewer/TurnGroup.tsx +337 -0
  266. package/src/components/proxy-viewer/anatomy/RequestAnatomy.tsx +98 -0
  267. package/src/components/proxy-viewer/anatomy/SegmentBar.tsx +196 -0
  268. package/src/components/proxy-viewer/anatomy/tokenEstimate.ts +53 -0
  269. package/src/components/proxy-viewer/anatomy/types.ts +39 -0
  270. package/src/components/proxy-viewer/anatomy/useAnatomyJump.ts +114 -0
  271. package/src/components/proxy-viewer/cacheTrend.ts +50 -0
  272. package/src/components/proxy-viewer/diff/DiffView.tsx +321 -0
  273. package/src/components/proxy-viewer/diff/computeDiff.ts +178 -0
  274. package/src/components/proxy-viewer/diff/index.ts +3 -0
  275. package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +157 -0
  276. package/src/components/proxy-viewer/formats/anthropic/ResponseView.tsx +66 -0
  277. package/src/components/proxy-viewer/formats/anthropic/thinkingExtract.ts +21 -0
  278. package/src/components/proxy-viewer/formats/index.tsx +33 -0
  279. package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +170 -0
  280. package/src/components/proxy-viewer/index.ts +9 -0
  281. package/src/components/proxy-viewer/lazy.ts +37 -0
  282. package/src/components/proxy-viewer/log-formats/anthropic.ts +194 -0
  283. package/src/components/proxy-viewer/log-formats/index.ts +23 -0
  284. package/src/components/proxy-viewer/log-formats/openai.ts +167 -0
  285. package/src/components/proxy-viewer/log-formats/types.ts +40 -0
  286. package/src/components/proxy-viewer/log-formats/unknown.ts +18 -0
  287. package/src/components/proxy-viewer/logEntryVisibility.ts +39 -0
  288. package/src/components/proxy-viewer/requestDiff.ts +277 -0
  289. package/src/components/proxy-viewer/useCopyFeedback.ts +36 -0
  290. package/src/components/proxy-viewer/useKeyboardNavigation.ts +190 -0
  291. package/src/components/proxy-viewer/viewerState.ts +66 -0
  292. package/src/components/ui/badge.tsx +47 -0
  293. package/src/components/ui/button.tsx +47 -0
  294. package/src/components/ui/collapsible.tsx +21 -0
  295. package/src/components/ui/confirm-dialog.tsx +51 -0
  296. package/src/components/ui/crab-logo.tsx +95 -0
  297. package/src/components/ui/crab-variants.tsx +467 -0
  298. package/src/components/ui/dialog.tsx +129 -0
  299. package/src/components/ui/json-expansion-button.tsx +56 -0
  300. package/src/components/ui/json-viewer-bulk.ts +97 -0
  301. package/src/components/ui/json-viewer.tsx +494 -0
  302. package/src/components/ui/mcp-logo.tsx +20 -0
  303. package/src/components/ui/scroll-area.tsx +54 -0
  304. package/src/components/ui/select.tsx +178 -0
  305. package/src/components/ui/separator.tsx +28 -0
  306. package/src/components/ui/tabs.tsx +88 -0
  307. package/src/components/ui/tooltip.tsx +51 -0
  308. package/src/index.css +11 -0
  309. package/src/knowledge/candidateStore.ts +63 -0
  310. package/src/knowledge/distiller.ts +98 -0
  311. package/src/knowledge/openclawClient.ts +118 -0
  312. package/src/knowledge/redactor.ts +80 -0
  313. package/src/knowledge/types.ts +84 -0
  314. package/src/lib/apiClient.ts +49 -0
  315. package/src/lib/export-logs.ts +51 -0
  316. package/src/lib/mask.ts +4 -0
  317. package/src/lib/objectUtils.ts +22 -0
  318. package/src/lib/providerContract.ts +26 -0
  319. package/src/lib/providerTestContract.ts +107 -0
  320. package/src/lib/runtimeConfig.ts +25 -0
  321. package/src/lib/serverPort.ts +41 -0
  322. package/src/lib/sessionUrl.ts +44 -0
  323. package/src/lib/stopReason.ts +58 -0
  324. package/src/lib/useOnboarding.ts +80 -0
  325. package/src/lib/useProviders.ts +30 -0
  326. package/src/lib/useStripConfig.ts +108 -0
  327. package/src/lib/utils.ts +21 -0
  328. package/src/mcp/loopback.ts +76 -0
  329. package/src/mcp/previewExtractor.ts +166 -0
  330. package/src/mcp/server.ts +396 -0
  331. package/src/mcp/toolHandlers.ts +341 -0
  332. package/src/proxy/chunkStorage.ts +112 -0
  333. package/src/proxy/claudeCodeStrip.ts +99 -0
  334. package/src/proxy/config.ts +172 -0
  335. package/src/proxy/constants.ts +47 -0
  336. package/src/proxy/dataDir.ts +86 -0
  337. package/src/proxy/formats/anthropic/anthropicProvider.ts +75 -0
  338. package/src/proxy/formats/anthropic/handler.ts +71 -0
  339. package/src/proxy/formats/anthropic/index.ts +14 -0
  340. package/src/proxy/formats/anthropic/register.ts +4 -0
  341. package/src/proxy/formats/anthropic/schemas.ts +237 -0
  342. package/src/proxy/formats/anthropic/stream.ts +205 -0
  343. package/src/proxy/formats/handler.ts +46 -0
  344. package/src/proxy/formats/index.ts +12 -0
  345. package/src/proxy/formats/jsonSchema.ts +36 -0
  346. package/src/proxy/formats/openai/alibabaProvider.ts +38 -0
  347. package/src/proxy/formats/openai/handler.ts +96 -0
  348. package/src/proxy/formats/openai/index.ts +25 -0
  349. package/src/proxy/formats/openai/provider.ts +50 -0
  350. package/src/proxy/formats/openai/register.ts +4 -0
  351. package/src/proxy/formats/openai/schemas.ts +187 -0
  352. package/src/proxy/formats/openai/stream.ts +206 -0
  353. package/src/proxy/formats/protocol.ts +50 -0
  354. package/src/proxy/formats/providerRegistry.ts +51 -0
  355. package/src/proxy/formats/providers/index.ts +3 -0
  356. package/src/proxy/formats/registry.ts +66 -0
  357. package/src/proxy/handler.ts +334 -0
  358. package/src/proxy/logFinalizer.ts +305 -0
  359. package/src/proxy/logFinalizer.worker.ts +24 -0
  360. package/src/proxy/logIndex.ts +268 -0
  361. package/src/proxy/logger.ts +179 -0
  362. package/src/proxy/openaiOrphanToolStrip.ts +142 -0
  363. package/src/proxy/providerImporters.ts +491 -0
  364. package/src/proxy/providers.ts +613 -0
  365. package/src/proxy/schemas.ts +209 -0
  366. package/src/proxy/sessionProcess.ts +140 -0
  367. package/src/proxy/sessionRuntime.ts +85 -0
  368. package/src/proxy/sessionSupervisor.ts +283 -0
  369. package/src/proxy/sessionWorkerEntry.ts +26 -0
  370. package/src/proxy/socketTracker.ts +255 -0
  371. package/src/proxy/store.ts +412 -0
  372. package/src/proxy/upstream.ts +90 -0
  373. package/src/router.tsx +16 -0
  374. package/src/routes/__root.tsx +45 -0
  375. package/src/routes/api/config.paths.ts +14 -0
  376. package/src/routes/api/config.ts +53 -0
  377. package/src/routes/api/health.ts +15 -0
  378. package/src/routes/api/knowledge.candidates.$candidateId.promote.ts +32 -0
  379. package/src/routes/api/knowledge.candidates.ts +10 -0
  380. package/src/routes/api/knowledge.project-context.ts +18 -0
  381. package/src/routes/api/knowledge.search.ts +31 -0
  382. package/src/routes/api/knowledge.sessions.$sessionId.candidates.ts +16 -0
  383. package/src/routes/api/logs.$id.chunks.ts +36 -0
  384. package/src/routes/api/logs.$id.replay.ts +191 -0
  385. package/src/routes/api/logs.$id.ts +22 -0
  386. package/src/routes/api/logs.stream.ts +74 -0
  387. package/src/routes/api/logs.ts +59 -0
  388. package/src/routes/api/mcp.ts +25 -0
  389. package/src/routes/api/models.ts +10 -0
  390. package/src/routes/api/providers.$providerId.test.log.ts +293 -0
  391. package/src/routes/api/providers.$providerId.ts +50 -0
  392. package/src/routes/api/providers.export.ts +26 -0
  393. package/src/routes/api/providers.import.ts +47 -0
  394. package/src/routes/api/providers.scan.ts +23 -0
  395. package/src/routes/api/providers.ts +45 -0
  396. package/src/routes/api/sessions.ts +17 -0
  397. package/src/routes/index.tsx +6 -0
  398. package/src/routes/proxy/$.ts +15 -0
  399. package/src/routes/session/$sessionId.tsx +23 -0
  400. package/styles/globals.css +188 -0
@@ -0,0 +1,619 @@
1
+ import { type JSX, useState, useEffect, useCallback, useMemo, useRef } from "react";
2
+ import { z } from "zod";
3
+ import { Button } from "../ui/button";
4
+ import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "../ui/tooltip";
5
+ import { Plus, AlertCircle, Copy, Check, Download, Upload, Scan } from "lucide-react";
6
+ import { ImportWizardDialog } from "./ImportWizardDialog";
7
+ import { ConfirmDialog } from "../ui/confirm-dialog";
8
+ import { ProviderCard } from "./ProviderCard";
9
+ import { ProviderForm } from "./ProviderForm";
10
+ import { parseJsonResponse, readApiError } from "../../lib/apiClient";
11
+ import { ProviderConfigSchema, type ProviderConfig } from "../../lib/providerContract";
12
+ import {
13
+ createFailedProviderTestResults,
14
+ createPendingProviderTestResults,
15
+ ProviderTestResultsSchema,
16
+ type ProviderTestResults as TestResults,
17
+ } from "../../lib/providerTestContract";
18
+
19
+ export type { ProviderTestResults as TestResults } from "../../lib/providerTestContract";
20
+
21
+ const ConfigPathsResponseSchema = z.object({
22
+ providerConfig: z.string(),
23
+ });
24
+
25
+ const ImportResponseSchema = z.object({
26
+ success: z.boolean().optional(),
27
+ imported: z.number().optional(),
28
+ message: z.string().optional(),
29
+ errors: z.array(z.string()).optional(),
30
+ });
31
+
32
+ const TEST_TIMEOUT_SECONDS = 30;
33
+ const NETWORK_ERROR_MESSAGE = "Network error: could not reach the server. Is the proxy running?";
34
+
35
+ type ProviderFormData = {
36
+ name: string;
37
+ apiKey: string;
38
+ models: string[];
39
+ anthropicBaseUrl?: string;
40
+ openaiBaseUrl?: string;
41
+ apiDocsUrl?: string;
42
+ source?: "company" | "personal";
43
+ };
44
+
45
+ function createProviderPayload(data: ProviderFormData) {
46
+ return {
47
+ name: data.name,
48
+ apiKey: data.apiKey,
49
+ models: data.models,
50
+ anthropicBaseUrl: (data.anthropicBaseUrl?.length ?? 0) > 0 ? data.anthropicBaseUrl : undefined,
51
+ openaiBaseUrl: (data.openaiBaseUrl?.length ?? 0) > 0 ? data.openaiBaseUrl : undefined,
52
+ apiDocsUrl: (data.apiDocsUrl?.length ?? 0) > 0 ? data.apiDocsUrl : undefined,
53
+ source: data.source,
54
+ };
55
+ }
56
+
57
+ type ProvidersPanelProps = {
58
+ externalProviders?: ProviderConfig[];
59
+ isLoading?: boolean;
60
+ externalTestResults?: Record<string, TestResults>;
61
+ externalTestingProviders?: Set<string>;
62
+ externalTestingTimeLeft?: Record<string, number>;
63
+ onProvidersMutate?: () => Promise<unknown>;
64
+ onTestResultsChange?: (providerId: string, results: TestResults) => void;
65
+ onTestingProvidersChange?: (providerId: string, isTesting: boolean) => void;
66
+ onTestingTimeLeftChange?: (providerId: string, seconds: number | undefined) => void;
67
+ };
68
+
69
+ export function ProvidersPanel({
70
+ externalProviders,
71
+ isLoading = false,
72
+ externalTestResults,
73
+ externalTestingProviders,
74
+ externalTestingTimeLeft,
75
+ onProvidersMutate,
76
+ onTestResultsChange,
77
+ onTestingProvidersChange,
78
+ onTestingTimeLeftChange,
79
+ }: ProvidersPanelProps): JSX.Element {
80
+ const [showForm, setShowForm] = useState(false);
81
+ const [editingProvider, setEditingProvider] = useState<ProviderConfig | undefined>();
82
+ const [error, setError] = useState<string | null>(null);
83
+ const [internalTestResults, setInternalTestResults] = useState<Record<string, TestResults>>({});
84
+ const [internalTestingProviders, setInternalTestingProviders] = useState<Set<string>>(new Set());
85
+ const [internalTestingTimeLeft, setInternalTestingTimeLeft] = useState<Record<string, number>>(
86
+ {},
87
+ );
88
+ const [configPath, setConfigPath] = useState<string | null>(null);
89
+ const [configPathCopied, setConfigPathCopied] = useState(false);
90
+ const [highlightedProviderId, setHighlightedProviderId] = useState<string | null>(null);
91
+ const [showImportWizard, setShowImportWizard] = useState(false);
92
+ const [confirmOpen, setConfirmOpen] = useState(false);
93
+ const [deletingProviderId, setDeletingProviderId] = useState<string | null>(null);
94
+ const [sourceFilter, setSourceFilter] = useState<"all" | "personal" | "company">("all");
95
+ const listScrollRef = useRef<HTMLDivElement>(null);
96
+ const highlightTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
97
+
98
+ // Use external state if provided (from SWR), otherwise use internal state
99
+ const providers = externalProviders ?? [];
100
+ const filteredProviders = useMemo(() => {
101
+ const filtered =
102
+ sourceFilter === "all" ? providers : providers.filter((p) => p.source === sourceFilter);
103
+ if (sourceFilter === "all") {
104
+ return [...filtered].sort((a, b) => {
105
+ const order: Record<string, number> = { personal: 0, company: 1 };
106
+ return (order[a.source ?? ""] ?? 2) - (order[b.source ?? ""] ?? 2);
107
+ });
108
+ }
109
+ return filtered;
110
+ }, [providers, sourceFilter]);
111
+ const testResults = externalTestResults ?? internalTestResults;
112
+ const testingProviders = externalTestingProviders ?? internalTestingProviders;
113
+ const testingTimeLeft = externalTestingTimeLeft ?? internalTestingTimeLeft;
114
+ const setTestingTimeLeft = onTestingTimeLeftChange
115
+ ? (id: string, seconds: number | undefined) => onTestingTimeLeftChange(id, seconds)
116
+ : (id: string, seconds: number | undefined) => {
117
+ if (seconds === undefined) {
118
+ setInternalTestingTimeLeft((prev) => {
119
+ const next = { ...prev };
120
+ delete next[id];
121
+ return next;
122
+ });
123
+ } else {
124
+ setInternalTestingTimeLeft((prev) => ({ ...prev, [id]: seconds }));
125
+ }
126
+ };
127
+
128
+ useEffect(() => {
129
+ void (async () => {
130
+ try {
131
+ const res = await fetch("/api/config/paths");
132
+ if (res.ok) {
133
+ const data = await parseJsonResponse(res, ConfigPathsResponseSchema);
134
+ setConfigPath(data.providerConfig);
135
+ }
136
+ } catch {
137
+ // Ignore
138
+ }
139
+ })();
140
+ }, []);
141
+
142
+ const triggerHighlight = useCallback((providerId: string) => {
143
+ // Cancel any existing highlight timeout
144
+ if (highlightTimeoutRef.current !== null) {
145
+ clearTimeout(highlightTimeoutRef.current);
146
+ }
147
+ setHighlightedProviderId(providerId);
148
+ // Scroll the list to bring the card into view
149
+ listScrollRef.current?.scrollTo({
150
+ top: listScrollRef.current.scrollHeight,
151
+ behavior: "smooth",
152
+ });
153
+ // Clear highlight after 2 seconds
154
+ highlightTimeoutRef.current = setTimeout(() => {
155
+ setHighlightedProviderId(null);
156
+ highlightTimeoutRef.current = null;
157
+ }, 2000);
158
+ }, []);
159
+
160
+ useEffect(
161
+ () => () => {
162
+ if (highlightTimeoutRef.current !== null) {
163
+ clearTimeout(highlightTimeoutRef.current);
164
+ }
165
+ },
166
+ [],
167
+ );
168
+
169
+ const refreshProviders = useCallback((): void => {
170
+ onProvidersMutate?.().catch(() => {});
171
+ }, [onProvidersMutate]);
172
+
173
+ const updateTestResults = useCallback(
174
+ (providerId: string, results: TestResults): void => {
175
+ if (onTestResultsChange) {
176
+ onTestResultsChange(providerId, results);
177
+ } else {
178
+ setInternalTestResults((prev) => ({ ...prev, [providerId]: results }));
179
+ }
180
+ },
181
+ [onTestResultsChange],
182
+ );
183
+
184
+ const updateTestingState = useCallback(
185
+ (providerId: string, isTesting: boolean): void => {
186
+ if (onTestingProvidersChange) {
187
+ onTestingProvidersChange(providerId, isTesting);
188
+ return;
189
+ }
190
+ setInternalTestingProviders((prev) => {
191
+ const next = new Set(prev);
192
+ if (isTesting) next.add(providerId);
193
+ else next.delete(providerId);
194
+ return next;
195
+ });
196
+ },
197
+ [onTestingProvidersChange],
198
+ );
199
+
200
+ const runTest = useCallback(
201
+ async (providerId: string): Promise<void> => {
202
+ // Clear previous test results when starting a new test
203
+ updateTestResults(providerId, createPendingProviderTestResults());
204
+ updateTestingState(providerId, true);
205
+
206
+ // Create abort controller for this test request
207
+ const controller = new AbortController();
208
+
209
+ // Start countdown
210
+ let remaining = TEST_TIMEOUT_SECONDS;
211
+ setTestingTimeLeft(providerId, remaining);
212
+ const intervalId = setInterval(() => {
213
+ remaining--;
214
+ setTestingTimeLeft(providerId, remaining);
215
+ if (remaining <= 0) {
216
+ clearInterval(intervalId);
217
+ // Abort the fetch request when time runs out
218
+ controller.abort();
219
+ }
220
+ }, 1000);
221
+
222
+ try {
223
+ const res = await fetch(`/api/providers/${providerId}/test`, {
224
+ method: "POST",
225
+ signal: controller.signal,
226
+ });
227
+ if (res.ok) {
228
+ const results = await parseJsonResponse(res, ProviderTestResultsSchema);
229
+ updateTestResults(providerId, results);
230
+ } else {
231
+ updateTestResults(
232
+ providerId,
233
+ createFailedProviderTestResults(
234
+ `HTTP ${res.status}: ${res.statusText}`,
235
+ "server_error",
236
+ ),
237
+ );
238
+ }
239
+ } catch (err) {
240
+ // Check if this was an abort (timeout)
241
+ const isAbort = err instanceof Error && err.name === "AbortError";
242
+ if (isAbort) {
243
+ updateTestResults(
244
+ providerId,
245
+ createFailedProviderTestResults("Request timed out", "timeout"),
246
+ );
247
+ }
248
+ // If it's not an abort error, the test results won't be updated
249
+ // which means the previous results will persist (or it will show "Not configured" reset state)
250
+ } finally {
251
+ // Always clear the countdown and testing state, even if an error occurs
252
+ clearInterval(intervalId);
253
+ setTestingTimeLeft(providerId, undefined);
254
+ updateTestingState(providerId, false);
255
+ }
256
+ },
257
+ [setTestingTimeLeft, updateTestResults, updateTestingState],
258
+ );
259
+
260
+ function handleAddProvider(data: ProviderFormData): void {
261
+ void (async () => {
262
+ try {
263
+ const res = await fetch("/api/providers", {
264
+ method: "POST",
265
+ headers: { "Content-Type": "application/json" },
266
+ body: JSON.stringify(createProviderPayload(data)),
267
+ });
268
+ if (!res.ok) {
269
+ setError(await readApiError(res, "Failed to add provider"));
270
+ return;
271
+ }
272
+ const newProvider = await parseJsonResponse(res, ProviderConfigSchema);
273
+ setShowForm(false);
274
+ triggerHighlight(newProvider.id);
275
+ refreshProviders();
276
+ await runTest(newProvider.id);
277
+ } catch {
278
+ setError(NETWORK_ERROR_MESSAGE);
279
+ }
280
+ })();
281
+ }
282
+
283
+ function handleUpdateProvider(data: ProviderFormData): void {
284
+ if (!editingProvider) return;
285
+ void (async () => {
286
+ try {
287
+ const res = await fetch(`/api/providers/${editingProvider.id}`, {
288
+ method: "PUT",
289
+ headers: { "Content-Type": "application/json" },
290
+ body: JSON.stringify(createProviderPayload(data)),
291
+ });
292
+ if (!res.ok) {
293
+ setError(await readApiError(res, "Failed to update provider"));
294
+ return;
295
+ }
296
+ const updated = await parseJsonResponse(res, ProviderConfigSchema);
297
+ setEditingProvider(undefined);
298
+ triggerHighlight(updated.id);
299
+ refreshProviders();
300
+ // Only run connection test when critical fields (apiKey, model, base URLs) changed
301
+ const criticalFieldsChanged =
302
+ (data.apiKey ?? "") !== (editingProvider.apiKey ?? "") ||
303
+ JSON.stringify(data.models) !== JSON.stringify(editingProvider.models) ||
304
+ (data.anthropicBaseUrl ?? "") !== (editingProvider.anthropicBaseUrl ?? "") ||
305
+ (data.openaiBaseUrl ?? "") !== (editingProvider.openaiBaseUrl ?? "");
306
+ if (criticalFieldsChanged) {
307
+ await runTest(updated.id);
308
+ }
309
+ } catch {
310
+ setError(NETWORK_ERROR_MESSAGE);
311
+ }
312
+ })();
313
+ }
314
+
315
+ function handleDeleteProvider(providerId: string): void {
316
+ setDeletingProviderId(providerId);
317
+ setConfirmOpen(true);
318
+ }
319
+
320
+ function executeDelete(): void {
321
+ const providerId = deletingProviderId;
322
+ if (providerId === null) return;
323
+ void (async () => {
324
+ let res: Response;
325
+ try {
326
+ res = await fetch(`/api/providers/${providerId}`, {
327
+ method: "DELETE",
328
+ });
329
+ } catch {
330
+ setError(NETWORK_ERROR_MESSAGE);
331
+ return;
332
+ }
333
+ if (!res.ok) {
334
+ setError(await readApiError(res, "Failed to delete provider"));
335
+ return;
336
+ }
337
+ refreshProviders();
338
+ })();
339
+ }
340
+
341
+ const fileInputRef = useRef<HTMLInputElement>(null);
342
+
343
+ function handleExport(includeKeys: boolean): void {
344
+ const url = `/api/providers/export${includeKeys ? "?includeKeys=true" : ""}`;
345
+ void (async () => {
346
+ try {
347
+ const res = await fetch(url);
348
+ if (!res.ok) {
349
+ setError("Failed to export providers");
350
+ return;
351
+ }
352
+ const blob = await res.blob();
353
+ const downloadUrl = URL.createObjectURL(blob);
354
+ const a = document.createElement("a");
355
+ a.href = downloadUrl;
356
+ a.download =
357
+ res.headers.get("Content-Disposition")?.match(/filename="(.+)"/)?.[1] ?? "providers.json";
358
+ document.body.appendChild(a);
359
+ a.click();
360
+ document.body.removeChild(a);
361
+ URL.revokeObjectURL(downloadUrl);
362
+ } catch {
363
+ setError("Failed to export providers");
364
+ }
365
+ })();
366
+ }
367
+
368
+ function handleImportClick(): void {
369
+ fileInputRef.current?.click();
370
+ }
371
+
372
+ function handleFileChange(e: React.ChangeEvent<HTMLInputElement>): void {
373
+ const file = e.target.files?.[0];
374
+ if (!file) return;
375
+
376
+ void (async () => {
377
+ try {
378
+ const text = await file.text();
379
+ const res = await fetch("/api/providers/import", {
380
+ method: "POST",
381
+ headers: { "Content-Type": "application/json" },
382
+ body: JSON.stringify(text),
383
+ });
384
+ const data = await parseJsonResponse(res, ImportResponseSchema);
385
+ if (res.ok && data.imported !== undefined && data.imported > 0) {
386
+ refreshProviders();
387
+ // Show success message via error state temporarily
388
+ setError(null);
389
+ // Use a ref or state to show success - for now just refresh list
390
+ } else if (data.errors && data.errors.length > 0) {
391
+ setError(data.errors.join("; "));
392
+ } else {
393
+ setError(data.message ?? "Import failed");
394
+ }
395
+ } catch {
396
+ setError("Failed to import providers");
397
+ }
398
+ // Reset file input
399
+ e.target.value = "";
400
+ })();
401
+ }
402
+
403
+ // Only show loading if we are actually loading (prevents flashing when reopening Settings during test)
404
+ if (isLoading) {
405
+ return (
406
+ <div className="flex items-center justify-center py-8">
407
+ <p className="text-sm text-muted-foreground">Loading providers...</p>
408
+ </div>
409
+ );
410
+ }
411
+
412
+ if (showForm || editingProvider) {
413
+ return (
414
+ <div className="space-y-4">
415
+ <div className="flex items-center justify-between sticky top-0 bg-background z-10 pb-2">
416
+ <h3 className="text-lg font-medium">
417
+ {editingProvider ? "Edit Provider" : "Add New Provider"}
418
+ </h3>
419
+ </div>
420
+ <ProviderForm
421
+ provider={editingProvider}
422
+ onSubmit={editingProvider ? handleUpdateProvider : handleAddProvider}
423
+ onCancel={() => {
424
+ setShowForm(false);
425
+ setEditingProvider(undefined);
426
+ }}
427
+ />
428
+ </div>
429
+ );
430
+ }
431
+
432
+ return (
433
+ <div className="space-y-4">
434
+ <div className="flex items-center justify-between sticky top-0 z-10 bg-background pb-2">
435
+ <h3 className="text-lg font-medium">Providers</h3>
436
+ <div className="flex items-center gap-2">
437
+ <TooltipProvider>
438
+ <Tooltip>
439
+ <TooltipTrigger asChild>
440
+ <Button
441
+ variant="outline"
442
+ size="sm"
443
+ onClick={() => handleExport(false)}
444
+ className="gap-1 hover:bg-muted"
445
+ >
446
+ <Download className="size-3" />
447
+ Export
448
+ </Button>
449
+ </TooltipTrigger>
450
+ <TooltipContent>Download providers as JSON for backup or sharing</TooltipContent>
451
+ </Tooltip>
452
+ </TooltipProvider>
453
+ <TooltipProvider>
454
+ <Tooltip>
455
+ <TooltipTrigger asChild>
456
+ <Button
457
+ variant="outline"
458
+ size="sm"
459
+ onClick={handleImportClick}
460
+ className="gap-1 hover:bg-muted"
461
+ >
462
+ <Upload className="size-3" />
463
+ Import
464
+ </Button>
465
+ </TooltipTrigger>
466
+ <TooltipContent>Import providers from an exported JSON file</TooltipContent>
467
+ </Tooltip>
468
+ </TooltipProvider>
469
+ <input
470
+ type="file"
471
+ ref={fileInputRef}
472
+ accept=".json"
473
+ onChange={handleFileChange}
474
+ style={{ display: "none" }}
475
+ />
476
+ <TooltipProvider>
477
+ <Tooltip>
478
+ <TooltipTrigger asChild>
479
+ <Button
480
+ variant="outline"
481
+ size="sm"
482
+ onClick={() => setShowImportWizard(true)}
483
+ className="gap-1"
484
+ >
485
+ <Scan className="size-3" />
486
+ Scan
487
+ </Button>
488
+ </TooltipTrigger>
489
+ <TooltipContent>
490
+ Detect and import provider configs from Claude Code and OpenCode
491
+ </TooltipContent>
492
+ </Tooltip>
493
+ </TooltipProvider>
494
+ <Button onClick={() => setShowForm(true)} size="sm" className="gap-1">
495
+ <Plus className="size-4" />
496
+ Add Provider
497
+ </Button>
498
+ </div>
499
+ </div>
500
+
501
+ {configPath !== null && (
502
+ <div className="flex items-center gap-2 text-xs text-muted-foreground bg-muted/30 rounded-md px-3 py-2">
503
+ <span className="shrink-0">Config:</span>
504
+ <span className="font-mono truncate" title={configPath}>
505
+ {configPath}
506
+ </span>
507
+ <TooltipProvider>
508
+ <Tooltip>
509
+ <TooltipTrigger asChild>
510
+ <button
511
+ type="button"
512
+ onClick={() => {
513
+ void window.navigator.clipboard.writeText(configPath).then(() => {
514
+ setConfigPathCopied(true);
515
+ setTimeout(() => setConfigPathCopied(false), 2000);
516
+ });
517
+ }}
518
+ className="shrink-0 ml-auto text-muted-foreground hover:text-foreground transition-colors"
519
+ >
520
+ {configPathCopied ? (
521
+ <Check className="size-3 text-green-500" />
522
+ ) : (
523
+ <Copy className="size-3" />
524
+ )}
525
+ </button>
526
+ </TooltipTrigger>
527
+ <TooltipContent>Copy config file path to clipboard</TooltipContent>
528
+ </Tooltip>
529
+ </TooltipProvider>
530
+ </div>
531
+ )}
532
+
533
+ {error !== null && (
534
+ <div className="flex items-center gap-2 text-sm text-destructive bg-destructive/10 rounded-md px-3 py-2">
535
+ <AlertCircle className="size-4 shrink-0" />
536
+ {error}
537
+ </div>
538
+ )}
539
+
540
+ {providers.length === 0 ? (
541
+ <div className="text-center py-12 space-y-3">
542
+ <p className="text-sm text-muted-foreground">No providers configured yet.</p>
543
+ <Button onClick={() => setShowForm(true)} variant="outline" size="sm">
544
+ <Plus className="size-4" />
545
+ Add Your First Provider
546
+ </Button>
547
+ </div>
548
+ ) : (
549
+ <div className="space-y-3">
550
+ <div className="flex gap-1 border-b border-border">
551
+ {(["all", "personal", "company"] as const).map((tab) => (
552
+ <TooltipProvider key={tab}>
553
+ <Tooltip>
554
+ <TooltipTrigger asChild>
555
+ <button
556
+ type="button"
557
+ onClick={() => setSourceFilter(tab)}
558
+ className={`px-3 py-2 text-sm font-medium border-b-2 transition-colors ${
559
+ sourceFilter === tab
560
+ ? "border-primary text-primary"
561
+ : "border-transparent text-muted-foreground hover:text-foreground"
562
+ }`}
563
+ >
564
+ {tab === "all" ? "All" : tab === "personal" ? "Personal" : "Company"}
565
+ </button>
566
+ </TooltipTrigger>
567
+ <TooltipContent>
568
+ {tab === "all"
569
+ ? "Show all providers"
570
+ : tab === "personal"
571
+ ? "Providers you configured yourself"
572
+ : "Providers set by your organization"}
573
+ </TooltipContent>
574
+ </Tooltip>
575
+ </TooltipProvider>
576
+ ))}
577
+ </div>
578
+ <div ref={listScrollRef} className="space-y-3">
579
+ {filteredProviders.map((provider) => (
580
+ <ProviderCard
581
+ key={provider.id}
582
+ provider={provider}
583
+ testResults={testResults[provider.id]}
584
+ isTesting={testingProviders.has(provider.id)}
585
+ testingTimeLeft={testingTimeLeft[provider.id]}
586
+ isHighlighted={provider.id === highlightedProviderId}
587
+ onEdit={(p) => setEditingProvider(p)}
588
+ onDelete={handleDeleteProvider}
589
+ onTest={(id: string) => {
590
+ void runTest(id);
591
+ }}
592
+ />
593
+ ))}
594
+ </div>
595
+ </div>
596
+ )}
597
+
598
+ <ImportWizardDialog
599
+ open={showImportWizard}
600
+ onOpenChange={setShowImportWizard}
601
+ onImportComplete={() => {
602
+ if (onProvidersMutate !== undefined) {
603
+ void onProvidersMutate();
604
+ }
605
+ }}
606
+ />
607
+
608
+ <ConfirmDialog
609
+ open={confirmOpen}
610
+ onOpenChange={setConfirmOpen}
611
+ title="Delete Provider"
612
+ description="Are you sure you want to delete this provider? This action cannot be undone."
613
+ confirmLabel="Delete"
614
+ variant="destructive"
615
+ onConfirm={executeDelete}
616
+ />
617
+ </div>
618
+ );
619
+ }