@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
package/.output/cli.js ADDED
@@ -0,0 +1,1611 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // src/cli/detect-tools.ts
13
+ import { execFileSync } from "node:child_process";
14
+ import { existsSync } from "node:fs";
15
+ import { join } from "node:path";
16
+ import { homedir } from "node:os";
17
+ function which(bin) {
18
+ try {
19
+ if (process.platform === "win32") {
20
+ const out2 = execFileSync("where", [bin], {
21
+ encoding: "utf8",
22
+ timeout: 3e3,
23
+ windowsHide: true,
24
+ stdio: ["ignore", "pipe", "ignore"]
25
+ });
26
+ const first = out2.split(/\r?\n/).find((line) => line.trim().length > 0);
27
+ return first?.trim() ?? null;
28
+ }
29
+ const out = execFileSync("sh", ["-c", `command -v ${bin}`], {
30
+ encoding: "utf8",
31
+ timeout: 3e3,
32
+ stdio: ["ignore", "pipe", "ignore"]
33
+ });
34
+ return out.trim() || null;
35
+ } catch {
36
+ return null;
37
+ }
38
+ }
39
+ function tryDir(path) {
40
+ return existsSync(path) ? path : null;
41
+ }
42
+ function detectClaudeCode() {
43
+ const configDir = tryDir(join(homedir(), ".claude"));
44
+ const bin = which("claude");
45
+ if (configDir === null && bin === null) return { found: false };
46
+ return { found: true, path: configDir ?? bin ?? "" };
47
+ }
48
+ function detectOpenCode() {
49
+ const configDir = tryDir(join(homedir(), ".config", "opencode"));
50
+ const bin = which("opencode");
51
+ if (configDir === null && bin === null) return { found: false };
52
+ return { found: true, path: configDir ?? bin ?? "" };
53
+ }
54
+ function detectMiMo() {
55
+ const configDir = tryDir(join(homedir(), ".mimo"));
56
+ const bin = which("mimo");
57
+ if (configDir === null && bin === null) return { found: false };
58
+ return { found: true, path: configDir ?? bin ?? "" };
59
+ }
60
+ function detectCursor() {
61
+ const configDir = tryDir(join(homedir(), ".cursor"));
62
+ const bin = which("cursor");
63
+ if (configDir === null && bin === null) return { found: false };
64
+ return { found: true, path: configDir ?? bin ?? "" };
65
+ }
66
+ function detectCody() {
67
+ const configDir = tryDir(join(homedir(), ".config", "cody"));
68
+ const bin = which("cody");
69
+ if (configDir === null && bin === null) return { found: false };
70
+ return { found: true, path: configDir ?? bin ?? "" };
71
+ }
72
+ function detectAll() {
73
+ return DETECTORS.map(({ id, displayName, detect }) => ({
74
+ id,
75
+ displayName,
76
+ result: detect()
77
+ }));
78
+ }
79
+ function detectFirst() {
80
+ for (const entry of DETECTORS) {
81
+ const result = entry.detect();
82
+ if (result.found) return { id: entry.id, displayName: entry.displayName, result };
83
+ }
84
+ return null;
85
+ }
86
+ var DETECTORS;
87
+ var init_detect_tools = __esm({
88
+ "src/cli/detect-tools.ts"() {
89
+ "use strict";
90
+ DETECTORS = [
91
+ { id: "claude-code", displayName: "Claude Code", detect: detectClaudeCode },
92
+ { id: "opencode", displayName: "OpenCode", detect: detectOpenCode },
93
+ { id: "mimo", displayName: "MiMo Code", detect: detectMiMo },
94
+ { id: "cursor", displayName: "Cursor", detect: detectCursor },
95
+ { id: "cody", displayName: "Cody", detect: detectCody }
96
+ ];
97
+ }
98
+ });
99
+
100
+ // src/cli/templates/command-onboard.ts
101
+ function renderCommandOnboard() {
102
+ return `---
103
+ description: Walk through agent-inspector setup \u2014 start the proxy, wire your AI tool, capture your first request.
104
+ ---
105
+
106
+ Invoke the \`agent-inspector-onboard\` skill and follow its phases.
107
+
108
+ The user wants to set up agent-inspector. If they have specific context (e.g. "I'm on Windows", "I already added my API key"), honor it \u2014 but otherwise just run the skill end-to-end and let the \`EXPLAIN / DO / PAUSE\` markers drive the conversation.
109
+ `;
110
+ }
111
+ var init_command_onboard = __esm({
112
+ "src/cli/templates/command-onboard.ts"() {
113
+ "use strict";
114
+ }
115
+ });
116
+
117
+ // src/cli/templates/skill-onboard.ts
118
+ function renderSkillOnboard(ctx) {
119
+ const { version, port, detectedSummary } = ctx;
120
+ return `---
121
+ name: agent-inspector-onboard
122
+ description: Guided setup for Agent Inspector v${version}: start the proxy, wire your AI coding tool, capture your first request, and learn the Web UI.
123
+ metadata:
124
+ author: agent-inspector
125
+ version: ${version}
126
+ ---
127
+
128
+ # Agent Inspector onboard
129
+
130
+ Guide the user from "I just installed Agent Inspector" to "I can see my AI tool's traffic in the Web UI". This is a teaching experience: you'll do real work in their environment while explaining each step.
131
+
132
+ Environment detected by the installer:
133
+ ${detectedSummary || " (no known AI tool detected \u2014 the user can still use the generic curl example in Phase 4)"}
134
+
135
+ Default proxy port: \`${port}\` (override with \`PORT=<n> agent-inspector\` or \`--port <n>\`).
136
+
137
+ > **PAUSE protocol.** Every \`**PAUSE**\` marker in this skill is a real stop.
138
+ > Use the \`AskUserQuestion\` tool to actually wait for the user before
139
+ > continuing. Do not stream past a PAUSE based on context \u2014 the user has
140
+ > not seen your output yet. Each PAUSE in the body below includes a sample
141
+ > question you can adapt.
142
+
143
+ ---
144
+
145
+ ## Phase 0: Idempotency check
146
+
147
+ **EXPLAIN:** "Before we do anything, let me see what's already set up. If some of the steps are already done, we can skip them."
148
+
149
+ **DO:** Probe the three pieces of state this skill touches. Use targeted checks \u2014 do **not** read large JSON files into the conversation.
150
+
151
+ \`\`\`bash
152
+ # 1. Is the proxy already up?
153
+ curl -fsS "http://localhost:${port}/api/health" 2>/dev/null && echo "PROXY: up" || echo "PROXY: down"
154
+
155
+ # 2. Does the provider config have a real provider key?
156
+ agent_inspector_data_dir() {
157
+ base="\${HOME:-/tmp}"
158
+ if [ -n "\${AGENT_INSPECTOR_DATA_DIR:-}" ]; then
159
+ case "$AGENT_INSPECTOR_DATA_DIR" in /*) printf '%s
160
+ ' "$AGENT_INSPECTOR_DATA_DIR" ;; *) printf '%s
161
+ ' "$base/$AGENT_INSPECTOR_DATA_DIR" ;; esac
162
+ elif [ -n "\${AGENT_INSPECTOR_CONFIG_DIR:-}" ]; then
163
+ case "$AGENT_INSPECTOR_CONFIG_DIR" in /*) printf '%s
164
+ ' "$AGENT_INSPECTOR_CONFIG_DIR" ;; *) printf '%s
165
+ ' "$base/$AGENT_INSPECTOR_CONFIG_DIR" ;; esac
166
+ elif { [ ! -e "$base/.agent-inspector/providers.json" ] && [ ! -e "$base/.agent-inspector/config.json" ] && [ ! -e "$base/.agent-inspector/logs" ] && [ ! -e "$base/.agent-inspector/chunks" ]; } && { [ -e "$base/.llm-inspector/providers.json" ] || [ -e "$base/.llm-inspector/config.json" ] || [ -e "$base/.llm-inspector/logs" ] || [ -e "$base/.llm-inspector/chunks" ]; }; then
167
+ printf '%s
168
+ ' "$base/.llm-inspector"
169
+ else
170
+ printf '%s
171
+ ' "$base/.agent-inspector"
172
+ fi
173
+ }
174
+ DATA_DIR="$(agent_inspector_data_dir)"
175
+ PROVIDERS="$DATA_DIR/providers.json"
176
+ LEGACY_CONFIG="$DATA_DIR/config.json"
177
+ if [ -f "$PROVIDERS" ] || [ -f "$LEGACY_CONFIG" ]; then
178
+ if grep -qE '"apiKey"[[:space:]]*:[[:space:]]*"sk-[^"]+"' "$PROVIDERS" "$LEGACY_CONFIG" 2>/dev/null; then
179
+ echo "CONFIG: has key (no REPLACE placeholder)"
180
+ elif grep -qE '"apiKey"[[:space:]]*:[[:space:]]*"REPLACE' "$PROVIDERS" "$LEGACY_CONFIG" 2>/dev/null; then
181
+ echo "CONFIG: missing or has placeholder key"
182
+ else
183
+ echo "CONFIG: missing or has placeholder key"
184
+ fi
185
+ else
186
+ echo "CONFIG: file does not exist"
187
+ fi
188
+
189
+ # 3. Is the MCP server already wired? (project .mcp.json wins)
190
+ PROJ_MCP=".mcp.json"
191
+ HOME_MCP="$HOME/.claude.json"
192
+ if [ -f "$PROJ_MCP" ] && grep -q '"agent-inspector"' "$PROJ_MCP"; then
193
+ echo "MCP: wired in $PROJ_MCP"
194
+ elif [ -f "$HOME_MCP" ] && grep -q '"agent-inspector"' "$HOME_MCP"; then
195
+ echo "MCP: wired in $HOME_MCP"
196
+ else
197
+ echo "MCP: not wired"
198
+ fi
199
+ \`\`\`
200
+
201
+ \`\`\`powershell
202
+ # Windows PowerShell \u2014 single-quoted so $env: expands correctly
203
+ $port = ${port}
204
+ function Resolve-AgentInspectorDataDir {
205
+ $base = $env:USERPROFILE
206
+ if (-not $base) { $base = $env:APPDATA }
207
+ if (-not $base) { $base = 'C:\\' }
208
+ if ($env:AGENT_INSPECTOR_DATA_DIR) {
209
+ if ([System.IO.Path]::IsPathRooted($env:AGENT_INSPECTOR_DATA_DIR)) { return $env:AGENT_INSPECTOR_DATA_DIR }
210
+ return (Join-Path $base $env:AGENT_INSPECTOR_DATA_DIR)
211
+ }
212
+ if ($env:AGENT_INSPECTOR_CONFIG_DIR) {
213
+ if ([System.IO.Path]::IsPathRooted($env:AGENT_INSPECTOR_CONFIG_DIR)) { return $env:AGENT_INSPECTOR_CONFIG_DIR }
214
+ return (Join-Path $base $env:AGENT_INSPECTOR_CONFIG_DIR)
215
+ }
216
+ $current = Join-Path $base '.agent-inspector'
217
+ $legacy = Join-Path $base '.llm-inspector'
218
+ $currentHasState = (Test-Path (Join-Path $current 'providers.json')) -or (Test-Path (Join-Path $current 'config.json')) -or (Test-Path (Join-Path $current 'logs')) -or (Test-Path (Join-Path $current 'chunks'))
219
+ $legacyHasState = (Test-Path (Join-Path $legacy 'providers.json')) -or (Test-Path (Join-Path $legacy 'config.json')) -or (Test-Path (Join-Path $legacy 'logs')) -or (Test-Path (Join-Path $legacy 'chunks'))
220
+ if (-not $currentHasState -and $legacyHasState) {
221
+ return $legacy
222
+ }
223
+ return $current
224
+ }
225
+ $dataDir = Resolve-AgentInspectorDataDir
226
+ $providers = Join-Path $dataDir 'providers.json'
227
+ $legacyConfig = Join-Path $dataDir 'config.json'
228
+
229
+ # 1. Is the proxy already up?
230
+ try {
231
+ $null = Invoke-RestMethod -Uri "http://localhost:$port/api/health" -TimeoutSec 2 -ErrorAction Stop
232
+ Write-Host 'PROXY: up'
233
+ } catch {
234
+ Write-Host 'PROXY: down'
235
+ }
236
+
237
+ # 2. Does the config have a real provider key?
238
+ if ((Test-Path $providers) -or (Test-Path $legacyConfig)) {
239
+ $content = ''
240
+ if (Test-Path $providers) { $content += Get-Content $providers -Raw }
241
+ if (Test-Path $legacyConfig) { $content += Get-Content $legacyConfig -Raw }
242
+ if ($content -match '"apiKey"[[:space:]]*:[[:space:]]*"(sk-[^"]+)"') {
243
+ Write-Host 'CONFIG: has key'
244
+ } elseif ($content -match 'REPLACE') {
245
+ Write-Host 'CONFIG: has placeholder key'
246
+ } else {
247
+ Write-Host 'CONFIG: missing key'
248
+ }
249
+ } else {
250
+ Write-Host 'CONFIG: file does not exist'
251
+ }
252
+
253
+ # 3. Is the MCP server already wired? (project .mcp.json wins)
254
+ $projMcp = Join-Path (Get-Location) '.mcp.json'
255
+ $homeMcp = Join-Path $env:USERPROFILE '.claude.json'
256
+ if ((Test-Path $projMcp) -and (Select-String -Path $projMcp -Pattern 'agent-inspector' -Quiet)) {
257
+ Write-Host "MCP: wired in $projMcp"
258
+ } elseif ((Test-Path $homeMcp) -and (Select-String -Path $homeMcp -Pattern 'agent-inspector' -Quiet)) {
259
+ Write-Host "MCP: wired in $homeMcp"
260
+ } else {
261
+ Write-Host 'MCP: not wired'
262
+ }
263
+ \`\`\`
264
+
265
+ **DO:** Summarize the three checks in one line, then use \`AskUserQuestion\` to ask whether to skip the corresponding phases.
266
+
267
+ > **PAUSE** \u2014 call \`AskUserQuestion\` with:
268
+ > - header: \`Skip done\`
269
+ > - question: \`Proxy/CONFIG/MCP state: <summary>. Skip the phases that are already done?\`
270
+ > - options: \`["Yes, skip what's done", "No, walk me through everything again"]\`
271
+ > Wait for the answer before moving to Preflight.
272
+
273
+ ---
274
+
275
+ ## Preflight
276
+
277
+ **EXPLAIN:** "Quick env sanity check \u2014 make sure Node and Claude Code are present."
278
+
279
+ **DO:** Run the platform-appropriate commands below.
280
+
281
+ \`\`\`bash
282
+ # Unix / macOS / WSL
283
+ node --version # expect >= 18
284
+ command -v claude >/dev/null && echo "claude-code: present" || echo "claude-code: not detected"
285
+ \`\`\`
286
+
287
+ \`\`\`powershell
288
+ # Windows PowerShell \u2014 single-quoted so $env: expands correctly
289
+ node --version # expect >= 18
290
+ $null = Get-Command claude -ErrorAction SilentlyContinue
291
+ if ($null) { Write-Host 'claude-code: present' } else { Write-Host 'claude-code: not detected' }
292
+ \`\`\`
293
+
294
+ > **PAUSE** \u2014 if Node is older than 18, ask the user to install a newer version (https://nodejs.org) before continuing. Use \`AskUserQuestion\` with header \`Node version\`.
295
+
296
+ ---
297
+
298
+ ## Phase 1: Welcome
299
+
300
+ **EXPLAIN:**
301
+
302
+ \`\`\`
303
+ ## Welcome to Agent Inspector!
304
+
305
+ Agent Inspector is an agent observability and knowledge-capture platform for AI coding tools. The \`agent-inspector\` CLI runs a transparent local proxy + Web UI, so you can see every model request and response: system prompts, tool definitions, message history, SSE streaming chunks, and token counts captured live in a browser tab.
306
+
307
+ **What we'll do in the next ~10 minutes:**
308
+ 1. Add your first LLM provider (Anthropic or OpenAI key)
309
+ 2. Start the proxy
310
+ 3. Wire your AI tool to use it
311
+ 4. Capture a first request end-to-end
312
+ 5. Tour the key UI affordances
313
+
314
+ Ready? Let's start with the provider.
315
+ \`\`\`
316
+
317
+ > **PAUSE** \u2014 use \`AskUserQuestion\` with header \`Ready?\` and options \`["Yes, let's go", "Wait, I have a question"]\`. Wait for the user before continuing.
318
+
319
+ ---
320
+
321
+ ## Phase 2: Provider setup
322
+
323
+ **EXPLAIN:** "A 'provider' is an upstream LLM endpoint \u2014 Anthropic, OpenAI, MiniMax, etc. agent-inspector routes each request to the right upstream based on the model name. You need at least one provider configured for the proxy to forward traffic."
324
+
325
+ **DO:** First, re-check whether \`<dataDir>/providers.json\` already has a real key (Phase 0 may have raced with a manual edit). Use the same data-dir resolution and \`grep -qE '"apiKey":"sk-'\` (bash) or \`Select-String\` (PowerShell) check from Phase 0. If a real key is present, skip the rest of this phase.
326
+
327
+ **DO:** If no real key, ask the user for the provider type and API key via \`AskUserQuestion\`. The question should be a free-form text field (no fixed options) \u2014 the API key is a secret, so don't echo it back in the question UI.
328
+
329
+ \`\`\`bash
330
+ # After collecting the key, write the config
331
+ agent_inspector_data_dir() {
332
+ base="\${HOME:-/tmp}"
333
+ if [ -n "\${AGENT_INSPECTOR_DATA_DIR:-}" ]; then
334
+ case "$AGENT_INSPECTOR_DATA_DIR" in /*) printf '%s
335
+ ' "$AGENT_INSPECTOR_DATA_DIR" ;; *) printf '%s
336
+ ' "$base/$AGENT_INSPECTOR_DATA_DIR" ;; esac
337
+ elif [ -n "\${AGENT_INSPECTOR_CONFIG_DIR:-}" ]; then
338
+ case "$AGENT_INSPECTOR_CONFIG_DIR" in /*) printf '%s
339
+ ' "$AGENT_INSPECTOR_CONFIG_DIR" ;; *) printf '%s
340
+ ' "$base/$AGENT_INSPECTOR_CONFIG_DIR" ;; esac
341
+ elif { [ ! -e "$base/.agent-inspector/providers.json" ] && [ ! -e "$base/.agent-inspector/config.json" ] && [ ! -e "$base/.agent-inspector/logs" ] && [ ! -e "$base/.agent-inspector/chunks" ]; } && { [ -e "$base/.llm-inspector/providers.json" ] || [ -e "$base/.llm-inspector/config.json" ] || [ -e "$base/.llm-inspector/logs" ] || [ -e "$base/.llm-inspector/chunks" ]; }; then
342
+ printf '%s
343
+ ' "$base/.llm-inspector"
344
+ else
345
+ printf '%s
346
+ ' "$base/.agent-inspector"
347
+ fi
348
+ }
349
+ DATA_DIR="$(agent_inspector_data_dir)"
350
+ mkdir -p "$DATA_DIR"
351
+ PROVIDERS="$DATA_DIR/providers.json"
352
+ NOW="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
353
+ cat > "$PROVIDERS" <<JSON
354
+ {
355
+ "providers": [
356
+ {
357
+ "id": "onboard-anthropic",
358
+ "name": "Anthropic",
359
+ "apiKey": "REPLACE_ME_BEFORE_WRITING",
360
+ "models": ["claude-sonnet-4-5-20250929"],
361
+ "format": "anthropic",
362
+ "anthropicBaseUrl": "https://api.anthropic.com",
363
+ "openaiBaseUrl": "",
364
+ "authHeader": "bearer",
365
+ "createdAt": "$NOW",
366
+ "updatedAt": "$NOW"
367
+ }
368
+ ]
369
+ }
370
+ JSON
371
+ # Then patch the apiKey with the user-provided value (use jq if available)
372
+ \`\`\`
373
+
374
+ \`\`\`powershell
375
+ # Windows PowerShell \u2014 single-quoted so $env: expands correctly
376
+ function Resolve-AgentInspectorDataDir {
377
+ $base = $env:USERPROFILE
378
+ if (-not $base) { $base = $env:APPDATA }
379
+ if (-not $base) { $base = 'C:\\' }
380
+ if ($env:AGENT_INSPECTOR_DATA_DIR) {
381
+ if ([System.IO.Path]::IsPathRooted($env:AGENT_INSPECTOR_DATA_DIR)) { return $env:AGENT_INSPECTOR_DATA_DIR }
382
+ return (Join-Path $base $env:AGENT_INSPECTOR_DATA_DIR)
383
+ }
384
+ if ($env:AGENT_INSPECTOR_CONFIG_DIR) {
385
+ if ([System.IO.Path]::IsPathRooted($env:AGENT_INSPECTOR_CONFIG_DIR)) { return $env:AGENT_INSPECTOR_CONFIG_DIR }
386
+ return (Join-Path $base $env:AGENT_INSPECTOR_CONFIG_DIR)
387
+ }
388
+ $current = Join-Path $base '.agent-inspector'
389
+ $legacy = Join-Path $base '.llm-inspector'
390
+ $currentHasState = (Test-Path (Join-Path $current 'providers.json')) -or (Test-Path (Join-Path $current 'config.json')) -or (Test-Path (Join-Path $current 'logs')) -or (Test-Path (Join-Path $current 'chunks'))
391
+ $legacyHasState = (Test-Path (Join-Path $legacy 'providers.json')) -or (Test-Path (Join-Path $legacy 'config.json')) -or (Test-Path (Join-Path $legacy 'logs')) -or (Test-Path (Join-Path $legacy 'chunks'))
392
+ if (-not $currentHasState -and $legacyHasState) {
393
+ return $legacy
394
+ }
395
+ return $current
396
+ }
397
+ $dir = Resolve-AgentInspectorDataDir
398
+ $file = Join-Path $dir 'providers.json'
399
+ New-Item -ItemType Directory -Force -Path $dir | Out-Null
400
+ $now = (Get-Date).ToUniversalTime().ToString('o')
401
+ $json = @{
402
+ providers = @(
403
+ @{
404
+ id = 'onboard-anthropic'
405
+ name = 'Anthropic'
406
+ apiKey = 'REPLACE_ME_BEFORE_WRITING'
407
+ models = @('claude-sonnet-4-5-20250929')
408
+ format = 'anthropic'
409
+ anthropicBaseUrl = 'https://api.anthropic.com'
410
+ openaiBaseUrl = ''
411
+ authHeader = 'bearer'
412
+ createdAt = $now
413
+ updatedAt = $now
414
+ }
415
+ )
416
+ } | ConvertTo-Json -Depth 5
417
+ $json | Set-Content -Path $file -Encoding UTF8
418
+ # Then patch the apiKey with the user-provided value
419
+ \`\`\`
420
+
421
+ **DO:** Patch the placeholder with the actual key using \`jq\` (preferred) or a simple \`sed\`. Then read the file back to confirm the key is no longer \`REPLACE_ME_BEFORE_WRITING\`.
422
+
423
+ **DO:** If the user declines to provide a key in the AskUserQuestion (selects "Skip for now"), do **not** write a placeholder config. Tell the user that Phase 5 (First capture) will be skipped, and that they can re-run the skill after adding a key.
424
+
425
+ > **PAUSE** \u2014 use \`AskUserQuestion\` with header \`Provider key\` and options \`["Key is in, continue", "Skip for now, I'll add it later"]\`. Wait for the answer.
426
+
427
+ ---
428
+
429
+ ## Phase 3: Start proxy
430
+
431
+ **EXPLAIN:** "Time to start the proxy. It binds to port ${port} by default, reuses an already-running healthy agent-inspector, and prints the URL. Use \`--force-restart\` only when you intentionally want to replace the existing process."
432
+
433
+ **DO:** Skip this phase entirely if the Phase 0 health check already reported \`PROXY: up\` and the user opted to skip done phases.
434
+
435
+ **DO:** Otherwise, start the proxy with the explicit \`--background --no-open\` flags. On Windows, resolve the npm shim first and launch it through \`Start-Process -WindowStyle Hidden\` so setup does not flash a command window.
436
+
437
+ \`\`\`bash
438
+ # Unix / macOS / WSL
439
+ agent-inspector --background --no-open > /tmp/agent-inspector.log 2>&1
440
+ \`\`\`
441
+
442
+ \`\`\`powershell
443
+ # Windows PowerShell \u2014 single-quoted so $env: expands correctly.
444
+ # Locate the binary on PATH first (works for npm, pnpm, yarn, volta, fnm).
445
+ # If not on PATH, fall back to the common npm global shim at $env:APPDATA.
446
+ # As a last resort, let cmd /c resolve it through PATHEXT.
447
+ $log = Join-Path $env:TEMP 'agent-inspector.log'
448
+ $err = Join-Path $env:TEMP 'agent-inspector.err.log'
449
+ $found = Get-Command agent-inspector -ErrorAction SilentlyContinue
450
+ if ($found) {
451
+ $shim = $found.Source
452
+ $args = '--background','--no-open'
453
+ } elseif (Test-Path (Join-Path $env:APPDATA 'npm/agent-inspector.cmd')) {
454
+ $shim = Join-Path $env:APPDATA 'npm/agent-inspector.cmd'
455
+ $args = '--background','--no-open'
456
+ } else {
457
+ # bin not on PATH and not at the default npm prefix \u2014 let cmd resolve it
458
+ $shim = 'cmd.exe'
459
+ $args = '/c','agent-inspector','--background','--no-open'
460
+ }
461
+ Start-Process -FilePath $shim -ArgumentList $args -RedirectStandardOutput $log -RedirectStandardError $err -WindowStyle Hidden
462
+ \`\`\`
463
+
464
+ Then wait for the port to be ready:
465
+
466
+ \`\`\`bash
467
+ for i in $(seq 1 20); do
468
+ curl -fsS "http://localhost:${port}/api/health" >/dev/null 2>&1 && echo "ready" && break
469
+ sleep 0.5
470
+ done
471
+ \`\`\`
472
+
473
+ **DO:** Hit the health endpoint to confirm the proxy is alive:
474
+
475
+ \`\`\`bash
476
+ curl -sS "http://localhost:${port}/api/health"
477
+ \`\`\`
478
+
479
+ > **PAUSE** \u2014 if the health check fails, show the user the log file (\`/tmp/agent-inspector.log\` or \`%TEMP%\\agent-inspector.log\`) and diagnose. Common issues: another process on the port, firewall, missing providers. Use \`AskUserQuestion\` with header \`Proxy up?\` and options \`["Yes, proxy is up", "No, I see an error in the log"]\`. Wait for the answer.
480
+
481
+ ---
482
+
483
+ ## Phase 4: Wire tool
484
+
485
+ **EXPLAIN:** "Now we tell your AI tool to send traffic through the proxy instead of directly to the upstream API. The exact env var depends on which tool you have."
486
+
487
+ **DO:** Based on the \`Environment detected\` block at the top of this skill, print the matching wiring command. Examples for each supported tool:
488
+
489
+ \`\`\`bash
490
+ # Claude Code
491
+ export ANTHROPIC_BASE_URL=http://localhost:${port}/proxy
492
+ claude
493
+
494
+ # OpenCode
495
+ export LLM_BASE_URL=http://localhost:${port}/proxy
496
+ opencode
497
+
498
+ # MiMo Code
499
+ export OPENAI_BASE_URL=http://localhost:${port}/proxy
500
+ mimo
501
+
502
+ # Cursor / Cody \u2014 set the OpenAI base URL in each tool's settings panel to http://localhost:${port}/proxy
503
+ \`\`\`
504
+
505
+ For a tool that wasn't auto-detected, fall through to the generic curl test in the next phase \u2014 the user can wire their tool later.
506
+
507
+ > **PAUSE** \u2014 use \`AskUserQuestion\` with header \`Tool wired?\` and options \`["Yes, env var is set, claude is running", "No, I'm going to use the curl test instead"]\`. Wait for the answer.
508
+
509
+ ---
510
+
511
+ ## Phase 4.5: Wire MCP server
512
+
513
+ **EXPLAIN:** "The proxy also exposes an MCP server at \`http://localhost:${port}/api/mcp\`. Your AI agent can query logs, replay requests, and test providers through it \u2014 no need to leave the editor."
514
+
515
+ **DO:** Skip this phase if Phase 0 reported \`MCP: wired in <path>\` and the user opted to skip done phases.
516
+
517
+ **DO:** Otherwise, check the project-level \`.mcp.json\` first (preferred \u2014 modern Claude Code convention), then fall back to \`~/.claude.json\`. Use the \`Read\` tool to inspect; do **not** \`cat\` a 40 KB file into the conversation.
518
+
519
+ If neither has an \`agent-inspector\` entry, add one. The simplest path is to write to project \`.mcp.json\` (create it if missing):
520
+
521
+ \`\`\`json
522
+ // .mcp.json (project root)
523
+ {
524
+ "mcpServers": {
525
+ "agent-inspector": {
526
+ "type": "http",
527
+ "url": "http://localhost:${port}/api/mcp"
528
+ }
529
+ }
530
+ }
531
+ \`\`\`
532
+
533
+ If \`mcpServers\` already exists in \`.mcp.json\`, merge the \`agent-inspector\` key into it via the \`Edit\` tool \u2014 do not overwrite other entries. If you can't create a project \`.mcp.json\` (no project root, permission, etc.), fall back to merging into \`~/.claude.json\` using the same \`Read\`/\`Edit\` pattern.
534
+
535
+ **DO:** Verify the handshake. The MCP \`initialize\` request should return 200 with a \`serverInfo\` payload \u2014 that proves the server is mounted and reachable:
536
+
537
+ \`\`\`bash
538
+ curl -sS -X POST "http://localhost:${port}/api/mcp" \\
539
+ -H "Content-Type: application/json" \\
540
+ -H "Accept: application/json, text/event-stream" \\
541
+ -d '{
542
+ "jsonrpc": "2.0",
543
+ "id": 1,
544
+ "method": "initialize",
545
+ "params": {
546
+ "protocolVersion": "2025-03-26",
547
+ "capabilities": {},
548
+ "clientInfo": { "name": "onboard-check", "version": "0" }
549
+ }
550
+ }' | grep -o '"name":"agent-inspector"' && echo "handshake OK"
551
+ \`\`\`
552
+
553
+ The \`grep -o '"name":"agent-inspector"'\` extracts only the serverInfo name \u2014 do not dump the full response. If the server returns session IDs, store the \`mcp-session-id\` header from the first response and use it for the follow-up \`tools/list\` call.
554
+
555
+ > **PAUSE** \u2014 use \`AskUserQuestion\` with header \`MCP OK?\` and options \`["Yes, handshake returned 200", "No, the call failed"]\`. Wait for the answer.
556
+
557
+ ---
558
+
559
+ ## Phase 5: First capture
560
+
561
+ **EXPLAIN:** "Let's prove the proxy works end-to-end. We'll send one real request through it and confirm the log shows up in the API."
562
+
563
+ **DO:** First, re-check the config. If the \`apiKey\` is still a \`REPLACE_ME_BEFORE_WRITING\` placeholder (user opted out in Phase 2), **skip the capture test** and tell the user to fill in their key and re-run the skill. A 401 from the upstream is fine if they did provide a real key \u2014 the proxy will still log the request.
564
+
565
+ Fire a minimal Anthropic-format request through the proxy:
566
+
567
+ \`\`\`bash
568
+ curl -sS -X POST "http://localhost:${port}/proxy/v1/messages" \\
569
+ -H "Content-Type: application/json" \\
570
+ -H "anthropic-version: 2023-06-01" \\
571
+ -H "x-api-key: \${AGENT_INSPECTOR_API_KEY:-sk-no-key-needed-for-routing}" \\
572
+ -d '{"model":"claude-3-5-sonnet-20241022","max_tokens":1,"messages":[{"role":"user","content":"ping"}]}' \\
573
+ -o /tmp/agent-inspector-capture.json -w 'STATUS:%{http_code}\\n'
574
+ \`\`\`
575
+
576
+ **DO:** Poll the logs API for up to 5 seconds. A 200 with at least one entry means the request reached the proxy:
577
+
578
+ \`\`\`bash
579
+ for i in $(seq 1 10); do
580
+ resp=$(curl -sS "http://localhost:${port}/api/logs?limit=1")
581
+ count=$(echo "$resp" | grep -o '"total":[0-9]*' | head -1 | grep -o '[0-9]*$')
582
+ if [ "\${count:-0}" -ge 1 ]; then
583
+ echo "captured"
584
+ echo "$resp" | head -c 400
585
+ break
586
+ fi
587
+ sleep 0.5
588
+ done
589
+ \`\`\`
590
+
591
+ **DO:** Diagnose the response based on the actual status and body. **Do not** default to "auth failure" for every 4xx.
592
+
593
+ | Status | Body hint | Meaning |
594
+ |--------|-----------|---------|
595
+ | 200 | normal | Real success \u2014 the upstream returned data |
596
+ | 401 | \`"unauthorized"\` or similar | Upstream rejected the key (expected with a test key) |
597
+ | 403 | \`"Request not allowed"\` | **Proxy's allowlist** \u2014 not an auth failure, the proxy rejected the model/config. Show the user the proxy log. |
598
+ | 403 | other text | Could be upstream ACL \u2014 different problem |
599
+ | 5xx | anything | Upstream network error |
600
+
601
+ > **PAUSE** \u2014 use \`AskUserQuestion\` with header \`Captured?\` and options matching the diagnosis above. Wait for the answer.
602
+
603
+ ---
604
+
605
+ ## Phase 6: Tour & wrap
606
+
607
+ **EXPLAIN:** "Everything's working. Here's the cheat sheet for the Web UI and the supporting surfaces:"
608
+
609
+ - **Web UI**: \`http://localhost:${port}/\` \u2014 collapsible log rows, per-tab Copy/Expand in the log header, Diff with Raw (request body), Diff with Previous (compare adjacent requests), Replay (re-send a request), Export (JSON ZIP).
610
+ - **MCP server**: \`http://localhost:${port}/api/mcp\` \u2014 connect from your coding agent to query logs, replay, and test providers without leaving the editor. Look for the "MCP Ready" badge in the Web UI header.
611
+ - **REST API**: \`/api/logs\`, \`/api/sessions\`, \`/api/providers\` \u2014 for scripting and shell-based inspection.
612
+ - **Stop the proxy**:
613
+
614
+ \`\`\`bash
615
+ # Unix / macOS
616
+ lsof -ti:${port} | xargs -r kill -9
617
+
618
+ # Windows PowerShell \u2014 single-quoted so $env: expands correctly
619
+ Get-NetTCPConnection -LocalPort $port | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force }
620
+ \`\`\`
621
+
622
+ - **Re-run onboard**: \`agent-inspector onboard --force\` refreshes this skill.
623
+ - **Full docs**: see the project README (linked from the Web UI footer).
624
+
625
+ > **PAUSE** \u2014 use \`AskUserQuestion\` with header \`All set?\` and options \`["All set, I'm done", "Wait, I want to revisit a phase"]\`. Wait for the answer.
626
+
627
+ You're done. Happy inspecting.
628
+ `;
629
+ }
630
+ var REQUIRED_PHASE_HEADINGS;
631
+ var init_skill_onboard = __esm({
632
+ "src/cli/templates/skill-onboard.ts"() {
633
+ "use strict";
634
+ REQUIRED_PHASE_HEADINGS = [
635
+ "Phase 0: Idempotency check",
636
+ "Preflight",
637
+ "Phase 1: Welcome",
638
+ "Phase 2: Provider setup",
639
+ "Phase 3: Start proxy",
640
+ "Phase 4: Wire tool",
641
+ "Phase 4.5: Wire MCP server",
642
+ "Phase 5: First capture",
643
+ "Phase 6: Tour & wrap"
644
+ ];
645
+ }
646
+ });
647
+
648
+ // src/cli/onboard.ts
649
+ var onboard_exports = {};
650
+ __export(onboard_exports, {
651
+ runOnboard: () => runOnboard
652
+ });
653
+ import { mkdirSync, writeFileSync, existsSync as existsSync2, readFileSync } from "node:fs";
654
+ import { homedir as homedir2 } from "node:os";
655
+ import { dirname, join as join2 } from "node:path";
656
+ import { fileURLToPath } from "node:url";
657
+ function parseFlags(argv) {
658
+ const flags = {
659
+ force: false,
660
+ dryRun: false,
661
+ skipProvider: false,
662
+ skipToolWire: false,
663
+ skillDir: null
664
+ };
665
+ for (let i = 0; i < argv.length; i++) {
666
+ const arg = argv[i];
667
+ switch (arg) {
668
+ case void 0:
669
+ continue;
670
+ case "--force":
671
+ flags.force = true;
672
+ break;
673
+ case "--dry-run":
674
+ flags.dryRun = true;
675
+ break;
676
+ case "--skip-provider":
677
+ flags.skipProvider = true;
678
+ break;
679
+ case "--skip-tool-wire":
680
+ flags.skipToolWire = true;
681
+ break;
682
+ case "--skill-dir": {
683
+ const next = argv[i + 1];
684
+ if (next === void 0) {
685
+ process.stderr.write("agent-inspector onboard: --skill-dir requires a path argument\n");
686
+ process.exit(2);
687
+ }
688
+ flags.skillDir = next;
689
+ i++;
690
+ break;
691
+ }
692
+ case "--help":
693
+ case "-h":
694
+ printHelp();
695
+ process.exit(0);
696
+ break;
697
+ default:
698
+ process.stderr.write(`agent-inspector onboard: unknown flag: ${arg}
699
+ `);
700
+ process.exit(2);
701
+ }
702
+ }
703
+ return flags;
704
+ }
705
+ function printHelp() {
706
+ process.stdout.write(`agent-inspector onboard \u2014 install the agent-inspector Claude Code skill
707
+
708
+ Usage:
709
+ agent-inspector onboard [options]
710
+
711
+ Options:
712
+ --force Overwrite the existing skill and slash command if they exist
713
+ --dry-run Print target paths and a template preview, write nothing
714
+ --skip-provider Skip the provider-setup phase in the skill body
715
+ --skip-tool-wire Skip the wire-tool phase in the skill body
716
+ --skill-dir <path> Override the target skill directory (default: ~/.claude/skills)
717
+ -h, --help Show this help
718
+
719
+ Exit codes:
720
+ 0 success (or already installed)
721
+ 2 invalid arguments
722
+ 1 write failed
723
+ `);
724
+ }
725
+ function resolveTargets(flags) {
726
+ const claudeRoot = flags.skillDir ?? join2(homedir2(), ".claude");
727
+ const skillDir = join2(claudeRoot, "skills", SKILL_DIR_NAME);
728
+ const commandsDir = join2(claudeRoot, "commands");
729
+ return {
730
+ skillFile: join2(skillDir, SKILL_FILE_NAME),
731
+ commandFile: join2(commandsDir, COMMAND_FILE_NAME)
732
+ };
733
+ }
734
+ function isObject(value) {
735
+ return typeof value === "object" && value !== null;
736
+ }
737
+ function buildDetectedSummary() {
738
+ const entries = detectAll();
739
+ const present = entries.filter((e) => e.result.found);
740
+ const absent = entries.filter((e) => !e.result.found);
741
+ const presentList = present.map((e) => e.displayName).join(", ") || "(none)";
742
+ const absentList = absent.map((e) => e.displayName).join(", ") || "(none)";
743
+ return ` - Detected: ${presentList}
744
+ - Not detected: ${absentList}`;
745
+ }
746
+ function runOnboard(argv) {
747
+ try {
748
+ return Promise.resolve(runOnboardSync(argv));
749
+ } catch (err) {
750
+ const msg = err instanceof Error ? err.message : String(err);
751
+ process.stderr.write(`agent-inspector onboard: ${msg}
752
+ `);
753
+ process.stderr.write("(run `agent-inspector onboard` again after fixing the issue)\n");
754
+ return Promise.resolve(0);
755
+ }
756
+ }
757
+ function runOnboardSync(argv) {
758
+ const flags = parseFlags(argv);
759
+ const { skillFile, commandFile } = resolveTargets(flags);
760
+ if (!flags.force && existsSync2(skillFile)) {
761
+ process.stdout.write(`agent-inspector onboard: already installed at ${skillFile}
762
+ `);
763
+ process.stdout.write("Re-run with --force to refresh.\n");
764
+ return 0;
765
+ }
766
+ let version = "0.0.0";
767
+ try {
768
+ const raw = JSON.parse(readFileSync(join2(__dirname, "..", "package.json"), "utf8"));
769
+ if (isObject(raw) && typeof raw["version"] === "string") {
770
+ version = raw["version"];
771
+ }
772
+ } catch {
773
+ }
774
+ const detectedSummary = buildDetectedSummary();
775
+ const skillBody = renderSkillOnboard({
776
+ version,
777
+ port: DEFAULT_PORT,
778
+ detectedSummary
779
+ });
780
+ const commandBody = renderCommandOnboard();
781
+ if (flags.dryRun) {
782
+ process.stdout.write(`agent-inspector onboard --dry-run
783
+
784
+ `);
785
+ process.stdout.write(`Skill target: ${skillFile}
786
+ `);
787
+ process.stdout.write(`Command target: ${commandFile}
788
+
789
+ `);
790
+ process.stdout.write(`Skill preview (first 5 lines + headings):
791
+ `);
792
+ const previewLines = skillBody.split("\n").slice(0, 5);
793
+ process.stdout.write(`${previewLines.join("\n")}
794
+ `);
795
+ process.stdout.write(`...
796
+ `);
797
+ for (const heading of REQUIRED_PHASE_HEADINGS) {
798
+ process.stdout.write(` - ${heading}
799
+ `);
800
+ }
801
+ process.stdout.write(`
802
+ Detected tools:
803
+ ${detectedSummary}
804
+ `);
805
+ process.stdout.write(`
806
+ No files were written.
807
+ `);
808
+ return 0;
809
+ }
810
+ mkdirSync(join2(skillFile, ".."), { recursive: true });
811
+ mkdirSync(join2(commandFile, ".."), { recursive: true });
812
+ try {
813
+ writeFileSync(skillFile, skillBody, "utf8");
814
+ writeFileSync(commandFile, commandBody, "utf8");
815
+ } catch (err) {
816
+ const msg = err instanceof Error ? err.message : String(err);
817
+ process.stderr.write(`agent-inspector onboard: failed to write files: ${msg}
818
+ `);
819
+ return 1;
820
+ }
821
+ const firstTool = detectFirst();
822
+ const toolHint = firstTool !== null ? firstTool.displayName : "no known tool detected";
823
+ process.stdout.write(`Installed skill to: ${skillFile}
824
+ `);
825
+ process.stdout.write(`Installed command to: ${commandFile}
826
+ `);
827
+ process.stdout.write(`
828
+ Next steps:
829
+ `);
830
+ process.stdout.write(` - Open Claude Code and run: /agent-inspector:onboard
831
+ `);
832
+ process.stdout.write(` - Or refresh later: agent-inspector onboard --force
833
+ `);
834
+ process.stdout.write(` - Detected primary tool: ${toolHint}
835
+ `);
836
+ return 0;
837
+ }
838
+ var __filename, __dirname, DEFAULT_PORT, SKILL_DIR_NAME, SKILL_FILE_NAME, COMMAND_FILE_NAME;
839
+ var init_onboard = __esm({
840
+ "src/cli/onboard.ts"() {
841
+ "use strict";
842
+ init_detect_tools();
843
+ init_command_onboard();
844
+ init_skill_onboard();
845
+ __filename = fileURLToPath(import.meta.url);
846
+ __dirname = dirname(__filename);
847
+ DEFAULT_PORT = 25947;
848
+ SKILL_DIR_NAME = "agent-inspector-onboard";
849
+ SKILL_FILE_NAME = "SKILL.md";
850
+ COMMAND_FILE_NAME = process.platform === "win32" ? "agent-inspector-onboard.md" : "agent-inspector:onboard.md";
851
+ }
852
+ });
853
+
854
+ // src/proxy/dataDir.ts
855
+ import { isAbsolute, join as join3 } from "node:path";
856
+ function hasDataDirState(pathExists, dir) {
857
+ return pathExists(join3(dir, "providers.json")) || pathExists(join3(dir, "config.json")) || pathExists(join3(dir, "logs")) || pathExists(join3(dir, "chunks"));
858
+ }
859
+ function resolveDataDir(pathExists, env = process.env, platform = process.platform) {
860
+ const isWindows = platform === "win32";
861
+ const base = isWindows ? env["USERPROFILE"] ?? env["APPDATA"] ?? "C:\\" : env["HOME"] ?? "/tmp";
862
+ const dirEnv = env["AGENT_INSPECTOR_DATA_DIR"];
863
+ if (dirEnv !== void 0 && dirEnv !== "") {
864
+ return isAbsolute(dirEnv) ? dirEnv : join3(base, dirEnv);
865
+ }
866
+ const legacyDirEnv = env["AGENT_INSPECTOR_CONFIG_DIR"];
867
+ if (legacyDirEnv !== void 0 && legacyDirEnv !== "") {
868
+ return isAbsolute(legacyDirEnv) ? legacyDirEnv : join3(base, legacyDirEnv);
869
+ }
870
+ const currentDir = join3(base, CURRENT_DATA_DIR_NAME);
871
+ const legacyDir = join3(base, LEGACY_DATA_DIR_NAME);
872
+ return !hasDataDirState(pathExists, currentDir) && hasDataDirState(pathExists, legacyDir) ? legacyDir : currentDir;
873
+ }
874
+ var CURRENT_DATA_DIR_NAME, LEGACY_DATA_DIR_NAME;
875
+ var init_dataDir = __esm({
876
+ "src/proxy/dataDir.ts"() {
877
+ "use strict";
878
+ CURRENT_DATA_DIR_NAME = ".agent-inspector";
879
+ LEGACY_DATA_DIR_NAME = ".llm-inspector";
880
+ }
881
+ });
882
+
883
+ // src/cli/doctor.ts
884
+ var doctor_exports = {};
885
+ __export(doctor_exports, {
886
+ buildDoctorReport: () => buildDoctorReport,
887
+ createDoctorDeps: () => createDoctorDeps,
888
+ doctorHelp: () => doctorHelp,
889
+ formatDoctorReport: () => formatDoctorReport,
890
+ parseDoctorArgs: () => parseDoctorArgs,
891
+ runDoctor: () => runDoctor
892
+ });
893
+ import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync } from "node:fs";
894
+ import { isAbsolute as isAbsolute2, join as join4, resolve } from "node:path";
895
+ import { createConnection } from "node:net";
896
+ function check(name, severity, message, hint = "") {
897
+ return {
898
+ name,
899
+ severity,
900
+ message,
901
+ hint: hint === "" ? null : hint
902
+ };
903
+ }
904
+ function parsePort(raw) {
905
+ if (raw === void 0 || raw.trim() === "") return null;
906
+ const port = Number(raw);
907
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) return null;
908
+ return port;
909
+ }
910
+ function parseDoctorArgs(argv, env = process.env) {
911
+ const envPort = parsePort(env["PORT"]);
912
+ const options = {
913
+ port: envPort ?? DEFAULT_PORT2,
914
+ configDir: null,
915
+ providersJson: null,
916
+ help: false
917
+ };
918
+ for (let i = 0; i < argv.length; i++) {
919
+ const arg = argv[i];
920
+ switch (arg) {
921
+ case void 0:
922
+ continue;
923
+ case "--help":
924
+ case "-h":
925
+ options.help = true;
926
+ break;
927
+ case "--port":
928
+ case "-p": {
929
+ const parsed = parsePort(argv[i + 1]);
930
+ if (parsed === null) {
931
+ return { kind: "error", message: "doctor: --port must be an integer from 1 to 65535" };
932
+ }
933
+ options.port = parsed;
934
+ i++;
935
+ break;
936
+ }
937
+ case "--config-dir": {
938
+ const next = argv[i + 1];
939
+ if (next === void 0 || next.trim() === "") {
940
+ return { kind: "error", message: "doctor: --config-dir requires a path argument" };
941
+ }
942
+ options.configDir = next;
943
+ i++;
944
+ break;
945
+ }
946
+ case "--providers": {
947
+ const next = argv[i + 1];
948
+ if (next === void 0 || next.trim() === "") {
949
+ return { kind: "error", message: "doctor: --providers requires a JSON argument" };
950
+ }
951
+ options.providersJson = next;
952
+ i++;
953
+ break;
954
+ }
955
+ default:
956
+ return { kind: "error", message: `doctor: unknown option ${arg}` };
957
+ }
958
+ }
959
+ return { kind: "ok", options };
960
+ }
961
+ function isObject2(value) {
962
+ return typeof value === "object" && value !== null && !Array.isArray(value);
963
+ }
964
+ function providerCountFromParsed(value) {
965
+ if (Array.isArray(value)) return value.length;
966
+ if (isObject2(value)) {
967
+ const providers = value["providers"];
968
+ if (Array.isArray(providers)) return providers.length;
969
+ }
970
+ return null;
971
+ }
972
+ function providerCountFromJson(raw) {
973
+ try {
974
+ const parsed = JSON.parse(raw);
975
+ return providerCountFromParsed(parsed);
976
+ } catch {
977
+ return "invalid";
978
+ }
979
+ }
980
+ function resolveMaybeRelative(base, value) {
981
+ return isAbsolute2(value) ? value : resolve(base, value);
982
+ }
983
+ function resolveDefaultDataDir(env, platform, exists) {
984
+ return resolveDataDir(exists, env, platform);
985
+ }
986
+ function providerConfigCandidates(options, deps) {
987
+ const explicitPath = deps.env["AGENT_INSPECTOR_CONFIG_PATH"];
988
+ if (explicitPath !== void 0 && explicitPath !== "") {
989
+ return [resolveMaybeRelative(deps.cwd, explicitPath)];
990
+ }
991
+ if (options.configDir !== null) {
992
+ const dir = resolveMaybeRelative(deps.cwd, options.configDir);
993
+ return [join4(dir, "providers.json"), join4(dir, "config.json")];
994
+ }
995
+ const dataDir = resolveDefaultDataDir(deps.env, deps.platform, deps.exists);
996
+ return [join4(dataDir, "providers.json"), join4(dataDir, "config.json")];
997
+ }
998
+ function checkProviderConfig(options, deps) {
999
+ const providersJson = options.providersJson ?? deps.env["AGENT_INSPECTOR_PROVIDERS_JSON"] ?? null;
1000
+ if (providersJson !== null) {
1001
+ const count = providerCountFromJson(providersJson);
1002
+ if (count === "invalid") {
1003
+ return check(
1004
+ "Provider config",
1005
+ "fail",
1006
+ "Provider JSON override is not valid JSON.",
1007
+ "Fix --providers or AGENT_INSPECTOR_PROVIDERS_JSON; doctor never prints the raw value."
1008
+ );
1009
+ }
1010
+ if (count !== null) {
1011
+ return check("Provider config", "pass", `${count} provider(s) found in JSON override.`);
1012
+ }
1013
+ return check(
1014
+ "Provider config",
1015
+ "fail",
1016
+ "Provider JSON override does not look like a provider array or { providers } object.",
1017
+ "Use an exported providers JSON payload or configure providers in the UI."
1018
+ );
1019
+ }
1020
+ const candidates = providerConfigCandidates(options, deps);
1021
+ for (const candidate of candidates) {
1022
+ if (!deps.exists(candidate)) continue;
1023
+ const text = deps.readText(candidate);
1024
+ if (text === null) {
1025
+ return check(
1026
+ "Provider config",
1027
+ "fail",
1028
+ `Provider config exists but could not be read: ${candidate}`
1029
+ );
1030
+ }
1031
+ const count = providerCountFromJson(text);
1032
+ if (count === "invalid") {
1033
+ return check("Provider config", "fail", `Provider config is invalid JSON: ${candidate}`);
1034
+ }
1035
+ if (count !== null && count > 0) {
1036
+ return check("Provider config", "pass", `${count} provider(s) configured.`);
1037
+ }
1038
+ return check(
1039
+ "Provider config",
1040
+ "warn",
1041
+ `Provider config exists but no providers were found: ${candidate}`,
1042
+ "Add a provider in Settings or import provider settings."
1043
+ );
1044
+ }
1045
+ return check(
1046
+ "Provider config",
1047
+ "warn",
1048
+ "No provider config was found.",
1049
+ "Add a provider in the web UI, import providers, or pass --providers for this check."
1050
+ );
1051
+ }
1052
+ async function checkProxyHealth(port, deps) {
1053
+ const health = await deps.fetchHealth(port);
1054
+ const url = `http://localhost:${port}`;
1055
+ if (health.ok) {
1056
+ return check(
1057
+ "Proxy health",
1058
+ "pass",
1059
+ `agent-inspector is healthy at ${url}.`,
1060
+ `Proxy URL: ${url}/proxy`
1061
+ );
1062
+ }
1063
+ if (await deps.isPortOpen(port)) {
1064
+ const suffix = health.status === null ? "" : ` (HTTP ${health.status})`;
1065
+ return check(
1066
+ "Proxy health",
1067
+ "fail",
1068
+ `Port ${port} is accepting connections, but /api/health is not healthy${suffix}.`,
1069
+ "Stop that process, choose --port <n>, or start agent-inspector with --force-restart."
1070
+ );
1071
+ }
1072
+ return check(
1073
+ "Proxy health",
1074
+ "warn",
1075
+ `No process is listening on port ${port}.`,
1076
+ "Start with `agent-inspector`, `agent-inspector --background`, or `bun run dev`."
1077
+ );
1078
+ }
1079
+ function findProjectRoot(startDir, deps) {
1080
+ let current = resolve(startDir);
1081
+ for (let i = 0; i < 6; i++) {
1082
+ if (deps.exists(join4(current, "package.json"))) return current;
1083
+ const next = resolve(current, "..");
1084
+ if (next === current) return startDir;
1085
+ current = next;
1086
+ }
1087
+ return startDir;
1088
+ }
1089
+ function checkPackage(rootDir, deps) {
1090
+ const packagePath = join4(rootDir, "package.json");
1091
+ const text = deps.readText(packagePath);
1092
+ if (text === null) {
1093
+ return check("Package metadata", "warn", "package.json was not found near the CLI bundle.");
1094
+ }
1095
+ try {
1096
+ const parsed = JSON.parse(text);
1097
+ if (!isObject2(parsed)) {
1098
+ return check("Package metadata", "warn", "package.json is not an object.");
1099
+ }
1100
+ const name = parsed["name"];
1101
+ const version = parsed["version"];
1102
+ if (typeof name === "string" && typeof version === "string") {
1103
+ return check("Package metadata", "pass", `${name}@${version}`);
1104
+ }
1105
+ return check("Package metadata", "warn", "package.json is missing name or version.");
1106
+ } catch {
1107
+ return check("Package metadata", "warn", "package.json is not valid JSON.");
1108
+ }
1109
+ }
1110
+ function checkExtensionSource(rootDir, deps) {
1111
+ const extensionDir = join4(rootDir, "extensions", "chrome");
1112
+ const missing = EXTENSION_REQUIRED_FILES.filter((file) => !deps.exists(join4(extensionDir, file)));
1113
+ if (missing.length === 0) {
1114
+ return check(
1115
+ "Chrome extension source",
1116
+ "pass",
1117
+ "Manifest, side panel, service worker, and icons are present."
1118
+ );
1119
+ }
1120
+ return check(
1121
+ "Chrome extension source",
1122
+ "fail",
1123
+ `Missing ${missing.length} Chrome extension file(s): ${missing.join(", ")}`,
1124
+ "Restore the extension files before packaging or loading unpacked."
1125
+ );
1126
+ }
1127
+ function extensionManifestVersion(rootDir, deps) {
1128
+ const manifestText = deps.readText(join4(rootDir, "extensions", "chrome", "manifest.json"));
1129
+ if (manifestText === null) return null;
1130
+ try {
1131
+ const manifest = JSON.parse(manifestText);
1132
+ if (!isObject2(manifest)) return null;
1133
+ const version = manifest["version"];
1134
+ return typeof version === "string" && version !== "" ? version : null;
1135
+ } catch {
1136
+ return null;
1137
+ }
1138
+ }
1139
+ function checkExtensionPackage(rootDir, deps) {
1140
+ const distDir = join4(rootDir, "dist", "chrome-extension");
1141
+ const files = deps.listDir(distDir);
1142
+ if (files === null) {
1143
+ return check(
1144
+ "Chrome extension package",
1145
+ "warn",
1146
+ "No packaged Chrome extension artifact was found.",
1147
+ "Run `npm run extension:zip` before Chrome Web Store upload."
1148
+ );
1149
+ }
1150
+ const manifestVersion = extensionManifestVersion(rootDir, deps);
1151
+ if (manifestVersion !== null) {
1152
+ const zipName = `agent-inspector-companion-v${manifestVersion}.zip`;
1153
+ const checksumName = `${zipName}.sha256`;
1154
+ const hasZip = files.includes(zipName);
1155
+ const hasChecksum = files.includes(checksumName);
1156
+ if (hasZip && hasChecksum) {
1157
+ return check("Chrome extension package", "pass", `${zipName} with checksum is present.`);
1158
+ }
1159
+ if (hasZip) {
1160
+ return check(
1161
+ "Chrome extension package",
1162
+ "warn",
1163
+ `${zipName} exists but no .sha256 checksum was found.`,
1164
+ "Run `npm run extension:zip` to regenerate the zip and checksum."
1165
+ );
1166
+ }
1167
+ return check(
1168
+ "Chrome extension package",
1169
+ "warn",
1170
+ `Current Chrome extension package was not found: ${zipName}`,
1171
+ "Run `npm run extension:zip` before Chrome Web Store upload."
1172
+ );
1173
+ }
1174
+ const zip = files.find((file) => file.endsWith(".zip"));
1175
+ const checksum = files.find((file) => file.endsWith(".zip.sha256"));
1176
+ if (zip !== void 0 && checksum !== void 0) {
1177
+ return check("Chrome extension package", "pass", `${zip} with checksum is present.`);
1178
+ }
1179
+ if (zip !== void 0) {
1180
+ return check(
1181
+ "Chrome extension package",
1182
+ "warn",
1183
+ `${zip} exists but no .sha256 checksum was found.`,
1184
+ "Run `npm run extension:zip` to regenerate the zip and checksum."
1185
+ );
1186
+ }
1187
+ return check(
1188
+ "Chrome extension package",
1189
+ "warn",
1190
+ "No Chrome extension zip artifact was found.",
1191
+ "Run `npm run extension:zip` before Chrome Web Store upload."
1192
+ );
1193
+ }
1194
+ async function buildDoctorReport(options, deps = createDoctorDeps()) {
1195
+ const rootDir = findProjectRoot(deps.cwd, deps);
1196
+ const checks = [
1197
+ await checkProxyHealth(options.port, deps),
1198
+ checkProviderConfig(options, deps),
1199
+ checkPackage(rootDir, deps),
1200
+ checkExtensionSource(rootDir, deps),
1201
+ checkExtensionPackage(rootDir, deps)
1202
+ ];
1203
+ const passCount = checks.filter((item) => item.severity === "pass").length;
1204
+ const warnCount = checks.filter((item) => item.severity === "warn").length;
1205
+ const failCount = checks.filter((item) => item.severity === "fail").length;
1206
+ return {
1207
+ checks,
1208
+ passCount,
1209
+ warnCount,
1210
+ failCount,
1211
+ exitCode: failCount > 0 ? 1 : 0
1212
+ };
1213
+ }
1214
+ function formatDoctorReport(report) {
1215
+ const lines = ["agent-inspector doctor", ""];
1216
+ for (const item of report.checks) {
1217
+ lines.push(`${item.severity.toUpperCase().padEnd(4)} ${item.name}: ${item.message}`);
1218
+ if (item.hint !== null) {
1219
+ lines.push(` ${item.hint}`);
1220
+ }
1221
+ }
1222
+ lines.push("");
1223
+ lines.push(
1224
+ `Summary: ${report.passCount} pass, ${report.warnCount} warn, ${report.failCount} fail`
1225
+ );
1226
+ return `${lines.join("\n")}
1227
+ `;
1228
+ }
1229
+ function doctorHelp() {
1230
+ return [
1231
+ "agent-inspector doctor",
1232
+ "",
1233
+ "Usage:",
1234
+ " agent-inspector doctor [--port <port>] [--config-dir <dir>] [--providers <json>]",
1235
+ "",
1236
+ "Checks local proxy health, provider config presence, package metadata, and Chrome companion readiness.",
1237
+ ""
1238
+ ].join("\n");
1239
+ }
1240
+ async function fetchHealth(port) {
1241
+ const controller = new AbortController();
1242
+ const timeout = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);
1243
+ try {
1244
+ const response = await fetch(`http://127.0.0.1:${port}/api/health`, {
1245
+ cache: "no-store",
1246
+ signal: controller.signal
1247
+ });
1248
+ return { ok: response.ok, status: response.status };
1249
+ } catch {
1250
+ return { ok: false, status: null };
1251
+ } finally {
1252
+ clearTimeout(timeout);
1253
+ }
1254
+ }
1255
+ function isPortOpen(port) {
1256
+ return new Promise((resolveOpen) => {
1257
+ const socket = createConnection({ host: "127.0.0.1", port });
1258
+ const finish = (value) => {
1259
+ socket.removeAllListeners();
1260
+ socket.destroy();
1261
+ resolveOpen(value);
1262
+ };
1263
+ socket.setTimeout(PROBE_TIMEOUT_MS);
1264
+ socket.once("connect", () => finish(true));
1265
+ socket.once("timeout", () => finish(false));
1266
+ socket.once("error", () => finish(false));
1267
+ });
1268
+ }
1269
+ function readText(path) {
1270
+ try {
1271
+ return readFileSync2(path, "utf8");
1272
+ } catch {
1273
+ return null;
1274
+ }
1275
+ }
1276
+ function listDir(path) {
1277
+ try {
1278
+ return readdirSync(path);
1279
+ } catch {
1280
+ return null;
1281
+ }
1282
+ }
1283
+ function createDoctorDeps() {
1284
+ return {
1285
+ env: process.env,
1286
+ platform: process.platform,
1287
+ cwd: process.cwd(),
1288
+ exists: existsSync3,
1289
+ readText,
1290
+ listDir,
1291
+ fetchHealth,
1292
+ isPortOpen
1293
+ };
1294
+ }
1295
+ async function runDoctor(argv) {
1296
+ const parsed = parseDoctorArgs(argv);
1297
+ if (parsed.kind === "error") {
1298
+ process.stderr.write(`${parsed.message}
1299
+ `);
1300
+ return 2;
1301
+ }
1302
+ if (parsed.options.help) {
1303
+ process.stdout.write(doctorHelp());
1304
+ return 0;
1305
+ }
1306
+ const report = await buildDoctorReport(parsed.options);
1307
+ process.stdout.write(formatDoctorReport(report));
1308
+ return report.exitCode;
1309
+ }
1310
+ var DEFAULT_PORT2, PROBE_TIMEOUT_MS, EXTENSION_REQUIRED_FILES;
1311
+ var init_doctor = __esm({
1312
+ "src/cli/doctor.ts"() {
1313
+ "use strict";
1314
+ init_dataDir();
1315
+ DEFAULT_PORT2 = 25947;
1316
+ PROBE_TIMEOUT_MS = 2e3;
1317
+ EXTENSION_REQUIRED_FILES = [
1318
+ "manifest.json",
1319
+ "sidepanel.html",
1320
+ "sidepanel.js",
1321
+ "sidepanel.css",
1322
+ "service-worker.js",
1323
+ "icons/icon.svg",
1324
+ "icons/icon-16.png",
1325
+ "icons/icon-32.png",
1326
+ "icons/icon-48.png",
1327
+ "icons/icon-128.png"
1328
+ ];
1329
+ }
1330
+ });
1331
+
1332
+ // src/cli.ts
1333
+ import { spawn, execSync } from "node:child_process";
1334
+ import { createConnection as createConnection2 } from "node:net";
1335
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
1336
+ import { dirname as dirname2, join as join5, resolve as resolvePath } from "node:path";
1337
+ var __filename2 = fileURLToPath2(import.meta.url);
1338
+ var __dirname2 = dirname2(__filename2);
1339
+ var DEFAULT_PORT3 = 25947;
1340
+ var LOCAL_PROBE_TIMEOUT_MS = 2e3;
1341
+ var subcommand = process.argv[2];
1342
+ if (subcommand === "onboard") {
1343
+ const { runOnboard: runOnboard2 } = await Promise.resolve().then(() => (init_onboard(), onboard_exports));
1344
+ const code = await runOnboard2(process.argv.slice(3));
1345
+ process.exit(code);
1346
+ }
1347
+ if (subcommand === "doctor") {
1348
+ const { runDoctor: runDoctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
1349
+ const code = await runDoctor2(process.argv.slice(3));
1350
+ process.exit(code);
1351
+ }
1352
+ await runStart(process.argv.slice(2));
1353
+ async function isInspectorHealthy(port) {
1354
+ const controller = new AbortController();
1355
+ const timeout = setTimeout(() => controller.abort(), LOCAL_PROBE_TIMEOUT_MS);
1356
+ try {
1357
+ const response = await fetch(`http://127.0.0.1:${port}/api/health`, {
1358
+ cache: "no-store",
1359
+ signal: controller.signal
1360
+ });
1361
+ return response.ok;
1362
+ } catch {
1363
+ return false;
1364
+ } finally {
1365
+ clearTimeout(timeout);
1366
+ }
1367
+ }
1368
+ function isPortAcceptingConnections(port) {
1369
+ return new Promise((resolve2) => {
1370
+ const socket = createConnection2({ host: "127.0.0.1", port });
1371
+ const finish = (value) => {
1372
+ socket.removeAllListeners();
1373
+ socket.destroy();
1374
+ resolve2(value);
1375
+ };
1376
+ socket.setTimeout(LOCAL_PROBE_TIMEOUT_MS);
1377
+ socket.once("connect", () => finish(true));
1378
+ socket.once("timeout", () => finish(false));
1379
+ socket.once("error", () => finish(false));
1380
+ });
1381
+ }
1382
+ function sleep(ms) {
1383
+ return new Promise((resolve2) => {
1384
+ setTimeout(resolve2, ms);
1385
+ });
1386
+ }
1387
+ async function waitForInspectorHealthy(port, timeoutMs) {
1388
+ const start = Date.now();
1389
+ while (Date.now() - start < timeoutMs) {
1390
+ if (await isInspectorHealthy(port)) return true;
1391
+ await sleep(250);
1392
+ }
1393
+ return false;
1394
+ }
1395
+ function openBrowser(targetUrl) {
1396
+ let command;
1397
+ switch (process.platform) {
1398
+ case "darwin":
1399
+ command = ["open", targetUrl];
1400
+ break;
1401
+ case "linux":
1402
+ command = ["xdg-open", targetUrl];
1403
+ break;
1404
+ case "win32":
1405
+ command = ["cmd", "/c", "start", "", targetUrl];
1406
+ break;
1407
+ default:
1408
+ break;
1409
+ }
1410
+ if (command === void 0) return;
1411
+ const [bin, ...cmdArgs] = command;
1412
+ if (bin === void 0) return;
1413
+ const browserProcess = spawn(bin, cmdArgs, {
1414
+ stdio: "ignore",
1415
+ detached: true,
1416
+ windowsHide: true
1417
+ });
1418
+ browserProcess.unref();
1419
+ }
1420
+ function waitForProcessExit(child) {
1421
+ return new Promise((resolve2) => {
1422
+ child.once("exit", (code) => {
1423
+ resolve2(code ?? 1);
1424
+ });
1425
+ child.once("error", () => {
1426
+ resolve2(1);
1427
+ });
1428
+ });
1429
+ }
1430
+ async function runStart(args) {
1431
+ const envPort = process.env["PORT"];
1432
+ const portDefault = envPort !== void 0 ? Number(envPort) : DEFAULT_PORT3;
1433
+ let port = portDefault;
1434
+ let open = true;
1435
+ let openWasSpecified = false;
1436
+ let background = false;
1437
+ let forceRestart = false;
1438
+ let configDir;
1439
+ let providersJson;
1440
+ for (let i = 0; i < args.length; i++) {
1441
+ const arg = args[i] ?? "";
1442
+ switch (arg) {
1443
+ case "--port":
1444
+ case "-p":
1445
+ port = Number(args[i + 1]);
1446
+ i++;
1447
+ break;
1448
+ case "--no-open":
1449
+ open = false;
1450
+ openWasSpecified = true;
1451
+ break;
1452
+ case "--open":
1453
+ open = true;
1454
+ openWasSpecified = true;
1455
+ break;
1456
+ case "--force-restart":
1457
+ case "--restart":
1458
+ forceRestart = true;
1459
+ break;
1460
+ case "--background":
1461
+ background = true;
1462
+ break;
1463
+ case "--config-dir":
1464
+ configDir = args[i + 1];
1465
+ i++;
1466
+ break;
1467
+ case "--providers":
1468
+ providersJson = args[i + 1];
1469
+ i++;
1470
+ break;
1471
+ default:
1472
+ break;
1473
+ }
1474
+ }
1475
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) {
1476
+ console.error(`Invalid port: ${String(port)}. Use --port <1-65535>.`);
1477
+ process.exitCode = 1;
1478
+ return;
1479
+ }
1480
+ function killProcessOnPort(targetPort) {
1481
+ const platform = process.platform;
1482
+ try {
1483
+ let pids = [];
1484
+ if (platform === "win32") {
1485
+ const output = execSync(`netstat -ano | findstr :${targetPort}`, {
1486
+ encoding: "utf8",
1487
+ timeout: 5e3,
1488
+ windowsHide: true
1489
+ });
1490
+ const lines = output.trim().split("\n");
1491
+ for (const line of lines) {
1492
+ const parts = line.trim().split(/\s+/);
1493
+ if (parts.length >= 5) {
1494
+ const localAddress = parts[1] ?? "";
1495
+ const pidStr = parts[4] ?? "";
1496
+ if (localAddress !== "" && pidStr !== "" && localAddress.includes(`:${targetPort}`)) {
1497
+ const pid = parseInt(pidStr, 10);
1498
+ if (!isNaN(pid) && pid > 0) {
1499
+ pids.push(pid);
1500
+ }
1501
+ }
1502
+ }
1503
+ }
1504
+ pids = [...new Set(pids)];
1505
+ for (const pid of pids) {
1506
+ try {
1507
+ console.log(`Killing process ${pid} on port ${targetPort}...`);
1508
+ execSync(`taskkill /PID ${pid} /F`, {
1509
+ encoding: "utf8",
1510
+ timeout: 5e3,
1511
+ windowsHide: true
1512
+ });
1513
+ } catch {
1514
+ }
1515
+ }
1516
+ } else {
1517
+ const output = execSync(`lsof -ti:${targetPort}`, { encoding: "utf8", timeout: 5e3 });
1518
+ const lines = output.trim().split("\n");
1519
+ for (const line of lines) {
1520
+ const pid = parseInt(line.trim(), 10);
1521
+ if (!isNaN(pid) && pid > 0) {
1522
+ pids.push(pid);
1523
+ }
1524
+ }
1525
+ pids = [...new Set(pids)];
1526
+ for (const pid of pids) {
1527
+ try {
1528
+ console.log(`Killing process ${pid} on port ${targetPort}...`);
1529
+ execSync(`kill -9 ${pid}`, { encoding: "utf8", timeout: 5e3 });
1530
+ } catch {
1531
+ }
1532
+ }
1533
+ }
1534
+ } catch {
1535
+ }
1536
+ }
1537
+ process.env["PORT"] = String(port);
1538
+ const url = `http://localhost:${port}`;
1539
+ if (!forceRestart && await isInspectorHealthy(port)) {
1540
+ console.log(`agent-inspector is already running at ${url}`);
1541
+ console.log(`Use --force-restart to restart the existing instance.`);
1542
+ if (open && openWasSpecified) {
1543
+ openBrowser(url);
1544
+ }
1545
+ return;
1546
+ }
1547
+ if (!forceRestart && await isPortAcceptingConnections(port)) {
1548
+ console.error(`Port ${port} is already in use, but it is not a healthy agent-inspector.`);
1549
+ console.error(`Stop that process, choose --port <n>, or re-run with --force-restart.`);
1550
+ process.exitCode = 1;
1551
+ return;
1552
+ }
1553
+ if (forceRestart) {
1554
+ killProcessOnPort(port);
1555
+ }
1556
+ console.log(`Server running at ${url}`);
1557
+ console.log(` Proxy: ${url}/proxy`);
1558
+ console.log(``);
1559
+ console.log(`Route AI coding tools through the proxy:`);
1560
+ console.log(` Claude Code: ANTHROPIC_BASE_URL=${url}/proxy claude`);
1561
+ console.log(` OpenCode: LLM_BASE_URL=${url}/proxy opencode`);
1562
+ console.log(` MiMo Code: OPENAI_BASE_URL=${url}/proxy mimo`);
1563
+ console.log(
1564
+ ` Direct HTTP: curl ${url}/proxy/v1/messages -d '{"model":"...","messages":[...]}'`
1565
+ );
1566
+ console.log(``);
1567
+ console.log(`Routing environment variables:`);
1568
+ console.log(` ROUTES JSON map of model prefix -> upstream URL`);
1569
+ console.log(` DEFAULT_UPSTREAM Fallback upstream for unmatched models`);
1570
+ console.log(
1571
+ ` Example: ROUTES='{"claude-":"https://api.anthropic.com","MiniMax":"https://api.minimaxi.com/anthropic"}'`
1572
+ );
1573
+ if (open) {
1574
+ openBrowser(url);
1575
+ }
1576
+ const outputDir = __dirname2;
1577
+ const serverPath = join5(outputDir, "../.output/server/index.mjs");
1578
+ const serverEnv = { ...process.env };
1579
+ if (configDir !== void 0) {
1580
+ let resolvedPath = join5(configDir);
1581
+ const msysMatch = /^\\([a-z])\\(.*)$/i.exec(resolvedPath);
1582
+ if (msysMatch !== null) {
1583
+ const drive = (msysMatch[1] ?? "").toUpperCase();
1584
+ const rest = msysMatch[2] ?? "";
1585
+ resolvedPath = `${drive}:\\${rest}`;
1586
+ } else {
1587
+ resolvedPath = resolvePath(resolvedPath);
1588
+ }
1589
+ serverEnv["AGENT_INSPECTOR_CONFIG_DIR"] = resolvedPath;
1590
+ }
1591
+ if (providersJson !== void 0) {
1592
+ serverEnv["AGENT_INSPECTOR_PROVIDERS_JSON"] = providersJson;
1593
+ }
1594
+ const serverProcess = spawn(process.execPath, [serverPath], {
1595
+ stdio: background ? ["ignore", "ignore", "ignore"] : "inherit",
1596
+ detached: background,
1597
+ env: serverEnv,
1598
+ windowsHide: background
1599
+ });
1600
+ if (background) {
1601
+ serverProcess.unref();
1602
+ if (await waitForInspectorHealthy(port, 5e3)) {
1603
+ console.log(`agent-inspector background server is ready at ${url}`);
1604
+ return;
1605
+ }
1606
+ console.error(`agent-inspector background server did not become ready at ${url}.`);
1607
+ process.exitCode = 1;
1608
+ return;
1609
+ }
1610
+ process.exitCode = await waitForProcessExit(serverProcess);
1611
+ }