@thaut/claude-ide 0.0.0-stage → 1.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 (559) hide show
  1. package/bin/claude-ide.js +23 -0
  2. package/package.json +75 -4
  3. package/server/add-dirs.ts +126 -0
  4. package/server/auth.test.ts +148 -0
  5. package/server/auth.ts +235 -0
  6. package/server/bang.ts +54 -0
  7. package/server/btw-instructions.ts +19 -0
  8. package/server/btw-policy.ts +17 -0
  9. package/server/btw.ts +143 -0
  10. package/server/child-env.ts +29 -0
  11. package/server/cli-control.ts +255 -0
  12. package/server/cli-files.ts +55 -0
  13. package/server/cli-info.ts +73 -0
  14. package/server/config/account.ts +65 -0
  15. package/server/config/agents.ts +186 -0
  16. package/server/config/mcp.ts +86 -0
  17. package/server/config/memory.ts +193 -0
  18. package/server/config/paths.ts +156 -0
  19. package/server/config/plugins.ts +204 -0
  20. package/server/config/routes.ts +95 -0
  21. package/server/config/settings.ts +246 -0
  22. package/server/config.ts +46 -0
  23. package/server/db.ts +121 -0
  24. package/server/directories.test.ts +180 -0
  25. package/server/directories.ts +87 -0
  26. package/server/dist-version.ts +69 -0
  27. package/server/durable-tasks.ts +61 -0
  28. package/server/file-transfers.ts +79 -0
  29. package/server/files.test.ts +127 -0
  30. package/server/files.ts +386 -0
  31. package/server/fs-queue.ts +14 -0
  32. package/server/fsutil.ts +144 -0
  33. package/server/git.ts +86 -0
  34. package/server/goal-state.test.ts +64 -0
  35. package/server/goal-state.ts +78 -0
  36. package/server/history-pages.test.ts +619 -0
  37. package/server/history-pages.ts +513 -0
  38. package/server/http-channel-manager.ts +468 -0
  39. package/server/http-channel-pull.test.ts +314 -0
  40. package/server/http-channel-push.test.ts +364 -0
  41. package/server/http-channel-registration.test.ts +291 -0
  42. package/server/http-channel-routes.ts +73 -0
  43. package/server/http-channel-store.ts +142 -0
  44. package/server/http-channel-test-fixture.ts +103 -0
  45. package/server/ide-messages-mcp.test.ts +82 -0
  46. package/server/ide-messages-mcp.ts +47 -0
  47. package/server/idecron-mcp.ts +65 -0
  48. package/server/idecron.test.ts +79 -0
  49. package/server/idecron.ts +271 -0
  50. package/server/index.ts +336 -0
  51. package/server/live-session.test.ts +1126 -0
  52. package/server/live-session.ts +1165 -0
  53. package/server/project-routes.ts +46 -0
  54. package/server/projects.test.ts +28 -0
  55. package/server/push.ts +315 -0
  56. package/server/report-cache.ts +28 -0
  57. package/server/session-aliases.test.ts +57 -0
  58. package/server/session-aliases.ts +50 -0
  59. package/server/session-branch.ts +71 -0
  60. package/server/session-dirs.ts +189 -0
  61. package/server/session-file.test.ts +134 -0
  62. package/server/session-file.ts +113 -0
  63. package/server/session-messaging.test.ts +297 -0
  64. package/server/session-messaging.ts +165 -0
  65. package/server/sessions.ts +638 -0
  66. package/server/subagents.test.ts +43 -0
  67. package/server/subagents.ts +58 -0
  68. package/server/tool-images.test.ts +76 -0
  69. package/server/tool-images.ts +45 -0
  70. package/server/turn-changes.ts +308 -0
  71. package/server/worktree-git.ts +87 -0
  72. package/server/worktrees.ts +138 -0
  73. package/server/ws.test.ts +195 -0
  74. package/server/ws.ts +197 -0
  75. package/shared/agent-model.ts +15 -0
  76. package/shared/agent-tasks.ts +12 -0
  77. package/shared/config.ts +175 -0
  78. package/shared/directories.ts +12 -0
  79. package/shared/files.ts +51 -0
  80. package/shared/http-channels.ts +63 -0
  81. package/shared/message-window.ts +189 -0
  82. package/shared/project.ts +77 -0
  83. package/shared/protocol.ts +203 -0
  84. package/shared/task-types.ts +4 -0
  85. package/shared/todos.ts +40 -0
  86. package/shared/write-policy.ts +9 -0
  87. package/web/dist/assets/AgentView-wrTKgcJ3.js +1 -0
  88. package/web/dist/assets/AnnotationSelection-BZmNmwVW.js +7 -0
  89. package/web/dist/assets/ChangesView-G1WTzh4q.js +2 -0
  90. package/web/dist/assets/FilesView-DPgAbYt_.js +6 -0
  91. package/web/dist/assets/Form-BKTwiYyw.js +1 -0
  92. package/web/dist/assets/HomePage-Dq2apjBe.js +1 -0
  93. package/web/dist/assets/Markdown-IwP-xF29.js +67 -0
  94. package/web/dist/assets/MessageList-DD9GypSy.js +15 -0
  95. package/web/dist/assets/Modal-g2rv8I_n.js +1 -0
  96. package/web/dist/assets/NotificationSettings-DpuLO_Bv.js +1 -0
  97. package/web/dist/assets/OverviewPage-Dx6miabE.js +1 -0
  98. package/web/dist/assets/ProjectHome-CousyTLL.js +1 -0
  99. package/web/dist/assets/SessionView-CPVL7_HO.js +2 -0
  100. package/web/dist/assets/SettingsPage-D_oYSnW2.js +7 -0
  101. package/web/dist/assets/WorktreesView-C7e0HV__.js +1 -0
  102. package/web/dist/assets/abap-CLvhMVsD.js +1 -0
  103. package/web/dist/assets/actionscript-3-Cs6JJXT5.js +1 -0
  104. package/web/dist/assets/ada-X3Y2mlq2.js +1 -0
  105. package/web/dist/assets/ahk-DwADkxyd.js +1 -0
  106. package/web/dist/assets/ahk2-_6rzW9BS.js +1 -0
  107. package/web/dist/assets/andromeeda-vGVdxbeo.js +1 -0
  108. package/web/dist/assets/angular-html-Cw29lun8.js +1 -0
  109. package/web/dist/assets/angular-ts-BXz-O3SJ.js +1 -0
  110. package/web/dist/assets/apache-U0d_L8uA.js +1 -0
  111. package/web/dist/assets/apex-KFGFyKwG.js +1 -0
  112. package/web/dist/assets/apl-xBchdRhy.js +1 -0
  113. package/web/dist/assets/applescript-CCn79oCD.js +1 -0
  114. package/web/dist/assets/ara-4CJ0cIlV.js +1 -0
  115. package/web/dist/assets/asciidoc-SCjQUq34.js +1 -0
  116. package/web/dist/assets/asm-Cmm7eHzH.js +1 -0
  117. package/web/dist/assets/astro-1q6DGuN3.js +1 -0
  118. package/web/dist/assets/aurora-x-CDeNXAV0.js +1 -0
  119. package/web/dist/assets/awk-BWXHIvNe.js +1 -0
  120. package/web/dist/assets/ayu-dark-DluEY0Gj.js +1 -0
  121. package/web/dist/assets/ayu-light-C3h-C4tm.js +1 -0
  122. package/web/dist/assets/ayu-mirage-Bqwy1Gya.js +1 -0
  123. package/web/dist/assets/ballerina-B7ZEbQpA.js +1 -0
  124. package/web/dist/assets/bat-qdxAdBWQ.js +1 -0
  125. package/web/dist/assets/beancount-C2IN4Vk8.js +1 -0
  126. package/web/dist/assets/berry-DKpUyyne.js +1 -0
  127. package/web/dist/assets/bibtex-Ci_nEsc7.js +1 -0
  128. package/web/dist/assets/bicep-CUHmPFLl.js +1 -0
  129. package/web/dist/assets/bird2-CYIUgYqB.js +1 -0
  130. package/web/dist/assets/blade-Cqdv4oZN.js +1 -0
  131. package/web/dist/assets/bsl-CVED6lJ1.js +1 -0
  132. package/web/dist/assets/c-D9gIxSfx.js +1 -0
  133. package/web/dist/assets/c3-D0apG41V.js +1 -0
  134. package/web/dist/assets/cadence-CQ2zXKGN.js +1 -0
  135. package/web/dist/assets/cairo-DRAZz75c.js +1 -0
  136. package/web/dist/assets/catppuccin-frappe-C9Q5zVZY.js +1 -0
  137. package/web/dist/assets/catppuccin-latte-CtsyrtPq.js +1 -0
  138. package/web/dist/assets/catppuccin-macchiato-R8nAf1vR.js +1 -0
  139. package/web/dist/assets/catppuccin-mocha-DjuaAJKi.js +1 -0
  140. package/web/dist/assets/chapel-CuseGurG.js +1 -0
  141. package/web/dist/assets/clarity-CtfBBspg.js +1 -0
  142. package/web/dist/assets/clojure-DqKBuwfJ.js +1 -0
  143. package/web/dist/assets/cmake-Bj61d0ZC.js +1 -0
  144. package/web/dist/assets/cobol-Bdpj3Qoh.js +1 -0
  145. package/web/dist/assets/codeowners-C8r90Shi.js +1 -0
  146. package/web/dist/assets/codeql-oeQT6MSM.js +1 -0
  147. package/web/dist/assets/coffee-Sk8pHFjs.js +1 -0
  148. package/web/dist/assets/common-lisp-Cv5bFMCO.js +1 -0
  149. package/web/dist/assets/coq-CGaSfhwR.js +1 -0
  150. package/web/dist/assets/core-BxI4V4-T.js +12 -0
  151. package/web/dist/assets/cpp-BQfBYKx3.js +1 -0
  152. package/web/dist/assets/crystal-B3j7DoOZ.js +1 -0
  153. package/web/dist/assets/csharp-dmMxLVLW.js +1 -0
  154. package/web/dist/assets/css-DpFbYmRh.js +1 -0
  155. package/web/dist/assets/csv-Dx-8-gkx.js +1 -0
  156. package/web/dist/assets/cue-CE9AQfxI.js +1 -0
  157. package/web/dist/assets/cypher-ClKdZ_lG.js +1 -0
  158. package/web/dist/assets/d-qD-0Kul2.js +1 -0
  159. package/web/dist/assets/dark-plus-Cs2F2srj.js +1 -0
  160. package/web/dist/assets/dart-CHZY9Xhz.js +1 -0
  161. package/web/dist/assets/dax-BkyTk9wS.js +1 -0
  162. package/web/dist/assets/desktop-Dlh5hvp9.js +1 -0
  163. package/web/dist/assets/diff-woXpYk--.js +1 -0
  164. package/web/dist/assets/docker-IyjqRm3v.js +1 -0
  165. package/web/dist/assets/dotenv-CuH5my32.js +1 -0
  166. package/web/dist/assets/dracula-BHWKrbxM.js +1 -0
  167. package/web/dist/assets/dracula-soft-5eyTD99u.js +1 -0
  168. package/web/dist/assets/dream-maker-DW3nJb8Q.js +1 -0
  169. package/web/dist/assets/edge-DXQWmXTB.js +1 -0
  170. package/web/dist/assets/elixir-CrDIY2Vo.js +1 -0
  171. package/web/dist/assets/elm-BslFEg8K.js +1 -0
  172. package/web/dist/assets/emacs-lisp-DR2vtWaj.js +1 -0
  173. package/web/dist/assets/engine-javascript-DJg3-Vu2.js +141 -0
  174. package/web/dist/assets/erb-CUYhg0Ws.js +1 -0
  175. package/web/dist/assets/erlang-BXdgHVfi.js +1 -0
  176. package/web/dist/assets/everforest-dark-sB-x3p7T.js +1 -0
  177. package/web/dist/assets/everforest-light-Df2xbC6M.js +1 -0
  178. package/web/dist/assets/fennel-DQxkIbk2.js +1 -0
  179. package/web/dist/assets/fish-BJitypiv.js +1 -0
  180. package/web/dist/assets/fluent-C03EYrpw.js +1 -0
  181. package/web/dist/assets/format-DrBC7w0A.js +1 -0
  182. package/web/dist/assets/fortran-fixed-form-D6mN0kjy.js +1 -0
  183. package/web/dist/assets/fortran-free-form-CYNrtFtB.js +1 -0
  184. package/web/dist/assets/fsharp-CwoEF39o.js +1 -0
  185. package/web/dist/assets/gdresource-CZkFGo3B.js +1 -0
  186. package/web/dist/assets/gdscript-DYvRD3hg.js +1 -0
  187. package/web/dist/assets/gdshader-CBce3t8t.js +1 -0
  188. package/web/dist/assets/genie-CV2tkWYe.js +1 -0
  189. package/web/dist/assets/gherkin-DExj1W_8.js +1 -0
  190. package/web/dist/assets/git-commit-dUGrvm6Q.js +1 -0
  191. package/web/dist/assets/git-rebase-4zCRqF28.js +1 -0
  192. package/web/dist/assets/github-dark-C-LZuMrd.js +1 -0
  193. package/web/dist/assets/github-dark-default-DXG-b-1a.js +1 -0
  194. package/web/dist/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
  195. package/web/dist/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
  196. package/web/dist/assets/github-light-EUqPIrTm.js +1 -0
  197. package/web/dist/assets/github-light-default-BXViO-2h.js +1 -0
  198. package/web/dist/assets/github-light-high-contrast-B68TUdTA.js +1 -0
  199. package/web/dist/assets/gleam-CSRkHgEL.js +1 -0
  200. package/web/dist/assets/glimmer-js-7adTRvH4.js +1 -0
  201. package/web/dist/assets/glimmer-ts-IdSskDYw.js +1 -0
  202. package/web/dist/assets/glsl-Qzy53KDt.js +1 -0
  203. package/web/dist/assets/gn-CAXYAgV-.js +1 -0
  204. package/web/dist/assets/gnuplot-7GGW24-e.js +1 -0
  205. package/web/dist/assets/go-rLFTqkRN.js +1 -0
  206. package/web/dist/assets/graphql-CV_puxbB.js +1 -0
  207. package/web/dist/assets/groovy-CacY0gHj.js +1 -0
  208. package/web/dist/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
  209. package/web/dist/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
  210. package/web/dist/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
  211. package/web/dist/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
  212. package/web/dist/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
  213. package/web/dist/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
  214. package/web/dist/assets/hack-C5GPjiLV.js +1 -0
  215. package/web/dist/assets/haml-BQ5YR1DQ.js +1 -0
  216. package/web/dist/assets/handlebars-DnhRK9A1.js +1 -0
  217. package/web/dist/assets/haskell-D8IpX4py.js +1 -0
  218. package/web/dist/assets/haxe-C8oCnwJJ.js +1 -0
  219. package/web/dist/assets/hcl-Dh228itO.js +1 -0
  220. package/web/dist/assets/highlight.worker-CY-ZeDAl.js +1 -0
  221. package/web/dist/assets/hjson-CxZEssPk.js +1 -0
  222. package/web/dist/assets/hlsl-Cvrh5tZx.js +1 -0
  223. package/web/dist/assets/horizon-CE9ld1lL.js +1 -0
  224. package/web/dist/assets/horizon-bright-DSNQnXHK.js +1 -0
  225. package/web/dist/assets/houston-CsvMBhTu.js +1 -0
  226. package/web/dist/assets/html-CmHiEKV9.js +1 -0
  227. package/web/dist/assets/html-derivative-BXE-rYIK.js +1 -0
  228. package/web/dist/assets/http-DPjjqtcL.js +1 -0
  229. package/web/dist/assets/hurl-AMBpSloO.js +1 -0
  230. package/web/dist/assets/hxml-D7Y24wKF.js +1 -0
  231. package/web/dist/assets/hy-CZbG8q4J.js +1 -0
  232. package/web/dist/assets/imba-DsUTQ-LC.js +1 -0
  233. package/web/dist/assets/index-DNPnl4sh.css +2 -0
  234. package/web/dist/assets/index-DQh72lEx.js +14 -0
  235. package/web/dist/assets/ini-B5eOa1yu.js +1 -0
  236. package/web/dist/assets/java-CEM7Tdw_.js +1 -0
  237. package/web/dist/assets/javascript-CU3PLowd.js +1 -0
  238. package/web/dist/assets/jinja-fR0x_9Xy.js +1 -0
  239. package/web/dist/assets/jison-BoKsxs5M.js +1 -0
  240. package/web/dist/assets/json-BM2FWsRB.js +1 -0
  241. package/web/dist/assets/json5-BR5RXkoi.js +1 -0
  242. package/web/dist/assets/jsonc-CYpm1nAK.js +1 -0
  243. package/web/dist/assets/jsonl-CmCQp5Yx.js +1 -0
  244. package/web/dist/assets/jsonnet-CJTPZ8u_.js +1 -0
  245. package/web/dist/assets/jssm-DXw9l8Rf.js +1 -0
  246. package/web/dist/assets/jsx-B5fTecQP.js +1 -0
  247. package/web/dist/assets/julia-DGyDG39_.js +1 -0
  248. package/web/dist/assets/just-B-RyLcSR.js +1 -0
  249. package/web/dist/assets/kanagawa-dragon-Bozd7klA.js +1 -0
  250. package/web/dist/assets/kanagawa-lotus-BmuOG9ZW.js +1 -0
  251. package/web/dist/assets/kanagawa-wave-BML5mF2J.js +1 -0
  252. package/web/dist/assets/kdl-CsD5j6eV.js +1 -0
  253. package/web/dist/assets/kotlin-DhhofPvG.js +1 -0
  254. package/web/dist/assets/kusto-C7mF5XQf.js +1 -0
  255. package/web/dist/assets/langs-CciPKqaF.js +1 -0
  256. package/web/dist/assets/laserwave-C_8bwKvT.js +1 -0
  257. package/web/dist/assets/latex-CV1pQelm.js +1 -0
  258. package/web/dist/assets/lean-CewbzKMR.js +1 -0
  259. package/web/dist/assets/less-DVTAwKKz.js +1 -0
  260. package/web/dist/assets/lib-DlcVMUqT.js +1 -0
  261. package/web/dist/assets/light-plus-DVQuIRkW.js +1 -0
  262. package/web/dist/assets/liquid-9p05RSzQ.js +1 -0
  263. package/web/dist/assets/llvm-DqHOMS-s.js +1 -0
  264. package/web/dist/assets/log-BNLmms1o.js +1 -0
  265. package/web/dist/assets/logo-Cluzi2Zq.js +1 -0
  266. package/web/dist/assets/lua-CIPVh0S-.js +1 -0
  267. package/web/dist/assets/luau-VGWVLoqX.js +1 -0
  268. package/web/dist/assets/make-Dixweg8N.js +1 -0
  269. package/web/dist/assets/markdown-BYOwaDjH.js +1 -0
  270. package/web/dist/assets/marko-h2h6YUMS.js +1 -0
  271. package/web/dist/assets/material-theme-Bm3Qr25_.js +1 -0
  272. package/web/dist/assets/material-theme-darker-2IIEA8gg.js +1 -0
  273. package/web/dist/assets/material-theme-lighter-uhdI0v04.js +1 -0
  274. package/web/dist/assets/material-theme-ocean-CHQ94UKr.js +1 -0
  275. package/web/dist/assets/material-theme-palenight-B5W6OYN7.js +1 -0
  276. package/web/dist/assets/matlab-D7qyCx1q.js +1 -0
  277. package/web/dist/assets/mdc-yAyZMdoC.js +1 -0
  278. package/web/dist/assets/mdx-DQZ5AkYe.js +1 -0
  279. package/web/dist/assets/mermaid-BxpfTIzt.js +1 -0
  280. package/web/dist/assets/message-font-size-DoaZfwkT.js +1 -0
  281. package/web/dist/assets/min-dark-BSWPekZh.js +1 -0
  282. package/web/dist/assets/min-light-DDpmG2fV.js +1 -0
  283. package/web/dist/assets/mipsasm-BMqwQI7S.js +1 -0
  284. package/web/dist/assets/mojo-fg40_ntQ.js +1 -0
  285. package/web/dist/assets/monokai-BscAjiQZ.js +1 -0
  286. package/web/dist/assets/moonbit-D9W784E_.js +1 -0
  287. package/web/dist/assets/move-BYnGhZoM.js +1 -0
  288. package/web/dist/assets/narrat-_X_XdTYD.js +1 -0
  289. package/web/dist/assets/nextflow-BoTnSBQ_.js +1 -0
  290. package/web/dist/assets/nextflow-groovy-D7IRt6Pp.js +1 -0
  291. package/web/dist/assets/nginx-jWINucNT.js +1 -0
  292. package/web/dist/assets/night-owl-DhmEMT88.js +1 -0
  293. package/web/dist/assets/night-owl-light-eJ-hLW7d.js +1 -0
  294. package/web/dist/assets/nim-Dtt9DZ0t.js +1 -0
  295. package/web/dist/assets/nix-Cl_7024t.js +1 -0
  296. package/web/dist/assets/nord-Cb4Vim4T.js +1 -0
  297. package/web/dist/assets/nsis-CjNLYCLg.js +1 -0
  298. package/web/dist/assets/nushell--0fT3PI1.js +1 -0
  299. package/web/dist/assets/objective-c-D1A_Heim.js +1 -0
  300. package/web/dist/assets/objective-cpp-BsSzOQcm.js +1 -0
  301. package/web/dist/assets/ocaml-O90oeIOV.js +1 -0
  302. package/web/dist/assets/odin-B1RWQWA5.js +1 -0
  303. package/web/dist/assets/one-dark-pro-C6YyR8OD.js +1 -0
  304. package/web/dist/assets/one-light-D7Lr4KcI.js +1 -0
  305. package/web/dist/assets/openscad-BUDT5pXO.js +1 -0
  306. package/web/dist/assets/org-ClD5huOa.js +1 -0
  307. package/web/dist/assets/pascal-4ZHwLPI5.js +1 -0
  308. package/web/dist/assets/perl-BFegxyeJ.js +1 -0
  309. package/web/dist/assets/php-B-qOA3Y4.js +1 -0
  310. package/web/dist/assets/pkl-ot-7Btpt.js +1 -0
  311. package/web/dist/assets/plastic-DQwYfKfQ.js +1 -0
  312. package/web/dist/assets/plsql-DGHpHOYJ.js +1 -0
  313. package/web/dist/assets/po-BiJDBrnU.js +1 -0
  314. package/web/dist/assets/poimandres-DRFjx7u4.js +1 -0
  315. package/web/dist/assets/polar-C7UOKdEL.js +1 -0
  316. package/web/dist/assets/postcss-BXeXVLqQ.js +1 -0
  317. package/web/dist/assets/powerquery-DNMTfnFr.js +1 -0
  318. package/web/dist/assets/powershell-QtSnmXcu.js +1 -0
  319. package/web/dist/assets/prisma-DMgNhnYS.js +1 -0
  320. package/web/dist/assets/prolog-iXnhIJG7.js +1 -0
  321. package/web/dist/assets/proto-DB4EqR-F.js +1 -0
  322. package/web/dist/assets/pug-Cq0NBPu-.js +1 -0
  323. package/web/dist/assets/puppet-CDv2pdJW.js +1 -0
  324. package/web/dist/assets/purescript-9MfHhQsQ.js +1 -0
  325. package/web/dist/assets/python-gzcpVVnB.js +1 -0
  326. package/web/dist/assets/qml-CYUsa87k.js +1 -0
  327. package/web/dist/assets/qmldir-DCQb3MpD.js +1 -0
  328. package/web/dist/assets/qss-Fe1Jh2GI.js +1 -0
  329. package/web/dist/assets/r-4xJhgbOf.js +1 -0
  330. package/web/dist/assets/racket-DcIDlBhZ.js +1 -0
  331. package/web/dist/assets/raku-B3gFvitq.js +1 -0
  332. package/web/dist/assets/razor-BlHaOHx8.js +1 -0
  333. package/web/dist/assets/rbs-C0MpOrj4.js +1 -0
  334. package/web/dist/assets/react-B4UGpcWC.js +1 -0
  335. package/web/dist/assets/red-CYlAq2b0.js +1 -0
  336. package/web/dist/assets/reg-CRGYupPL.js +1 -0
  337. package/web/dist/assets/regexp-BUSxjv2o.js +1 -0
  338. package/web/dist/assets/rel-BtDbiS_P.js +1 -0
  339. package/web/dist/assets/riscv-Ckw8ddFX.js +1 -0
  340. package/web/dist/assets/ron-VUp2lXgN.js +1 -0
  341. package/web/dist/assets/rose-pine-aMsaMD1D.js +1 -0
  342. package/web/dist/assets/rose-pine-dawn-8PwUyIlP.js +1 -0
  343. package/web/dist/assets/rose-pine-moon-DO-qXg-I.js +1 -0
  344. package/web/dist/assets/rosmsg-CAekHB0j.js +1 -0
  345. package/web/dist/assets/rst-zjfguEh7.js +1 -0
  346. package/web/dist/assets/ruby-4oR9waBK.js +1 -0
  347. package/web/dist/assets/rust-DpiIf5JO.js +1 -0
  348. package/web/dist/assets/sas-Do4V_2i9.js +1 -0
  349. package/web/dist/assets/sass-DXrisJhu.js +1 -0
  350. package/web/dist/assets/scala-BiFt_7mu.js +1 -0
  351. package/web/dist/assets/scheme-DQCgrYNe.js +1 -0
  352. package/web/dist/assets/scss-CW6JPtag.js +1 -0
  353. package/web/dist/assets/sdbl-bTVj8UrX.js +1 -0
  354. package/web/dist/assets/shaderlab-C4rnI7x0.js +1 -0
  355. package/web/dist/assets/shellscript-zBmOMFJh.js +1 -0
  356. package/web/dist/assets/shellsession-DxGUTWaf.js +1 -0
  357. package/web/dist/assets/slack-dark-DnToyrRv.js +1 -0
  358. package/web/dist/assets/slack-ochin-B2OO5cIa.js +1 -0
  359. package/web/dist/assets/smalltalk-BHpQb5nM.js +1 -0
  360. package/web/dist/assets/smithy-Dgaz7aur.js +1 -0
  361. package/web/dist/assets/snazzy-light-4G7pJPwS.js +1 -0
  362. package/web/dist/assets/solarized-dark-b97czJOk.js +1 -0
  363. package/web/dist/assets/solarized-light-DFPxpIYQ.js +1 -0
  364. package/web/dist/assets/solidity-Bj7E1mRn.js +1 -0
  365. package/web/dist/assets/soy-DVtw20Yn.js +1 -0
  366. package/web/dist/assets/sparql-C0ia5K60.js +1 -0
  367. package/web/dist/assets/splunk-BC2Px7Mm.js +1 -0
  368. package/web/dist/assets/sql-DGCM-jag.js +1 -0
  369. package/web/dist/assets/ssh-config-BgfXC-Er.js +1 -0
  370. package/web/dist/assets/stata-DfuMOXna.js +1 -0
  371. package/web/dist/assets/stylus-B6D30XZt.js +1 -0
  372. package/web/dist/assets/surrealql-CBoRWxfR.js +1 -0
  373. package/web/dist/assets/svelte-g_uCUem5.js +1 -0
  374. package/web/dist/assets/swift-CyEgAFGc.js +1 -0
  375. package/web/dist/assets/synthwave-84-nFMaYfgc.js +1 -0
  376. package/web/dist/assets/system-verilog-Dh5ihWbf.js +1 -0
  377. package/web/dist/assets/systemd-BxMlprV5.js +1 -0
  378. package/web/dist/assets/talonscript-CohzipZa.js +1 -0
  379. package/web/dist/assets/tasl-DMoTqEGO.js +1 -0
  380. package/web/dist/assets/tcl-CZd0xW_V.js +1 -0
  381. package/web/dist/assets/templ-N5C1hJ4Y.js +1 -0
  382. package/web/dist/assets/terraform-DswuEJGm.js +1 -0
  383. package/web/dist/assets/tex-mx6xDwuK.js +1 -0
  384. package/web/dist/assets/themes-CNElGaXw.js +1 -0
  385. package/web/dist/assets/tokyo-night-oM2G3aXe.js +1 -0
  386. package/web/dist/assets/toml-CcmNWLt0.js +1 -0
  387. package/web/dist/assets/ts-tags-C-j71FRb.js +1 -0
  388. package/web/dist/assets/tsv-sltzmVWM.js +1 -0
  389. package/web/dist/assets/tsx-z04y7qR9.js +1 -0
  390. package/web/dist/assets/turtle-ByJddavk.js +1 -0
  391. package/web/dist/assets/twig-Bz57G_nl.js +1 -0
  392. package/web/dist/assets/typescript-RMqVTmzR.js +1 -0
  393. package/web/dist/assets/typespec-BGhR2SqG.js +1 -0
  394. package/web/dist/assets/typst-BgkqA2Pg.js +1 -0
  395. package/web/dist/assets/v-DBj4K9d_.js +1 -0
  396. package/web/dist/assets/vala-zf12oZj6.js +1 -0
  397. package/web/dist/assets/vb-DOwzaHZ0.js +1 -0
  398. package/web/dist/assets/verilog-B2gwz-sJ.js +1 -0
  399. package/web/dist/assets/vesper-DdrHHSXu.js +1 -0
  400. package/web/dist/assets/vhdl-BroJfC0k.js +1 -0
  401. package/web/dist/assets/viml-DvXPmvsu.js +1 -0
  402. package/web/dist/assets/vitesse-black-fwtXNY1n.js +1 -0
  403. package/web/dist/assets/vitesse-dark-BZCL-v6S.js +1 -0
  404. package/web/dist/assets/vitesse-light-VbXTXTou.js +1 -0
  405. package/web/dist/assets/vue-CQgvQytb.js +1 -0
  406. package/web/dist/assets/vue-html-Dhl6fh9l.js +1 -0
  407. package/web/dist/assets/vue-vine-Dw_uU59K.js +1 -0
  408. package/web/dist/assets/vyper-CgoNMtux.js +1 -0
  409. package/web/dist/assets/wasm-ByWQv1Qj.js +1 -0
  410. package/web/dist/assets/wenyan-C8pVoKbM.js +1 -0
  411. package/web/dist/assets/wgsl-BsKzXJz4.js +1 -0
  412. package/web/dist/assets/wikitext-ClFFjSW2.js +1 -0
  413. package/web/dist/assets/wit-DdvCle-K.js +1 -0
  414. package/web/dist/assets/wolfram-DLL8P-h_.js +1 -0
  415. package/web/dist/assets/xml-B49klYQ3.js +1 -0
  416. package/web/dist/assets/xsl-D7tnD3qq.js +1 -0
  417. package/web/dist/assets/yaml-DNQ5_xkL.js +1 -0
  418. package/web/dist/assets/zenscript-BnlCZFoB.js +1 -0
  419. package/web/dist/assets/zig-CMLA9XwU.js +1 -0
  420. package/web/dist/icons/apple-touch-icon.png +0 -0
  421. package/web/dist/icons/badge-96.png +0 -0
  422. package/web/dist/icons/icon-192.png +0 -0
  423. package/web/dist/icons/icon-512.png +0 -0
  424. package/web/dist/icons/icon.svg +4 -0
  425. package/web/dist/icons/maskable-192.png +0 -0
  426. package/web/dist/icons/maskable-512.png +0 -0
  427. package/web/dist/index.html +30 -0
  428. package/web/dist/manifest.webmanifest +20 -0
  429. package/web/dist/sw.js +176 -0
  430. package/web/dist/version.json +1 -0
  431. package/web/src/App.tsx +97 -0
  432. package/web/src/agent-history-flow.test.ts +191 -0
  433. package/web/src/agent-history-flow.ts +58 -0
  434. package/web/src/agent-owners.ts +30 -0
  435. package/web/src/annotation-selection.test.ts +36 -0
  436. package/web/src/annotation-selection.ts +123 -0
  437. package/web/src/annotations.test.ts +279 -0
  438. package/web/src/annotations.ts +191 -0
  439. package/web/src/api.ts +27 -0
  440. package/web/src/auth-events.ts +11 -0
  441. package/web/src/auth-lifecycle.test.ts +105 -0
  442. package/web/src/clipboard.ts +32 -0
  443. package/web/src/components/ActivityNotifier.tsx +22 -0
  444. package/web/src/components/AnnotationEditor.tsx +135 -0
  445. package/web/src/components/AnnotationSelection.tsx +122 -0
  446. package/web/src/components/Automation.tsx +390 -0
  447. package/web/src/components/CodeView.tsx +121 -0
  448. package/web/src/components/Composer.tsx +552 -0
  449. package/web/src/components/ComposerAnnotations.tsx +45 -0
  450. package/web/src/components/Confirmation.tsx +91 -0
  451. package/web/src/components/CopyButton.tsx +23 -0
  452. package/web/src/components/DiffView.tsx +94 -0
  453. package/web/src/components/DirectoryPicker.tsx +230 -0
  454. package/web/src/components/FileCopyMenu.tsx +58 -0
  455. package/web/src/components/FileEntryActions.tsx +95 -0
  456. package/web/src/components/ImagePreview.tsx +68 -0
  457. package/web/src/components/Items.tsx +608 -0
  458. package/web/src/components/Markdown.tsx +73 -0
  459. package/web/src/components/MessageList.tsx +458 -0
  460. package/web/src/components/Modal.tsx +72 -0
  461. package/web/src/components/NotificationEntry.tsx +31 -0
  462. package/web/src/components/NotificationSettings.tsx +226 -0
  463. package/web/src/components/NotificationToasts.tsx +59 -0
  464. package/web/src/components/OpenTabs.tsx +112 -0
  465. package/web/src/components/Pending.tsx +175 -0
  466. package/web/src/components/ProjectExtras.tsx +299 -0
  467. package/web/src/components/ProjectNav.tsx +47 -0
  468. package/web/src/components/SessionActions.tsx +271 -0
  469. package/web/src/components/SessionChannels.tsx +288 -0
  470. package/web/src/components/SessionExtras.tsx +263 -0
  471. package/web/src/components/SettingsLink.tsx +21 -0
  472. package/web/src/components/Shell.tsx +320 -0
  473. package/web/src/components/SvgPreview.tsx +18 -0
  474. package/web/src/components/TodoPanel.tsx +37 -0
  475. package/web/src/components/VersionNotice.tsx +40 -0
  476. package/web/src/components/icons.tsx +43 -0
  477. package/web/src/diff.ts +62 -0
  478. package/web/src/drafts.test.ts +80 -0
  479. package/web/src/drafts.ts +64 -0
  480. package/web/src/format.ts +5 -0
  481. package/web/src/highlight.worker.ts +49 -0
  482. package/web/src/hooks.ts +59 -0
  483. package/web/src/item-view-state.test.ts +51 -0
  484. package/web/src/item-view-state.ts +43 -0
  485. package/web/src/live-history-flow.test.ts +77 -0
  486. package/web/src/live-merge.test.ts +64 -0
  487. package/web/src/live-merge.ts +50 -0
  488. package/web/src/live-message-hydration.test.ts +253 -0
  489. package/web/src/live.ts +404 -0
  490. package/web/src/main.tsx +27 -0
  491. package/web/src/markdown-assets.test.ts +70 -0
  492. package/web/src/markdown-assets.ts +78 -0
  493. package/web/src/markdown-html.test.ts +84 -0
  494. package/web/src/markdown-html.ts +60 -0
  495. package/web/src/markdown-svg.test.ts +37 -0
  496. package/web/src/markdown-svg.ts +42 -0
  497. package/web/src/markdown-tables.test.ts +34 -0
  498. package/web/src/markdown-tables.ts +17 -0
  499. package/web/src/message-bodies.ts +117 -0
  500. package/web/src/message-cache.test.ts +208 -0
  501. package/web/src/message-cache.ts +158 -0
  502. package/web/src/message-font-size.test.ts +41 -0
  503. package/web/src/message-font-size.ts +57 -0
  504. package/web/src/message-time.test.ts +32 -0
  505. package/web/src/message-time.ts +26 -0
  506. package/web/src/notify.ts +132 -0
  507. package/web/src/open-tabs.test.ts +69 -0
  508. package/web/src/open-tabs.ts +110 -0
  509. package/web/src/pages/AgentView.tsx +138 -0
  510. package/web/src/pages/ChangesView.tsx +193 -0
  511. package/web/src/pages/FilesView.tsx +406 -0
  512. package/web/src/pages/HomePage.tsx +96 -0
  513. package/web/src/pages/LoginPage.tsx +56 -0
  514. package/web/src/pages/OverviewPage.tsx +124 -0
  515. package/web/src/pages/ProjectHome.tsx +136 -0
  516. package/web/src/pages/SessionView.tsx +238 -0
  517. package/web/src/pages/WorktreesView.tsx +197 -0
  518. package/web/src/pages/settings/AccountPage.tsx +149 -0
  519. package/web/src/pages/settings/AgentsPage.tsx +182 -0
  520. package/web/src/pages/settings/ConfigPage.tsx +181 -0
  521. package/web/src/pages/settings/GeneralPage.tsx +27 -0
  522. package/web/src/pages/settings/HooksPage.tsx +244 -0
  523. package/web/src/pages/settings/McpPage.tsx +310 -0
  524. package/web/src/pages/settings/MemoryPage.tsx +309 -0
  525. package/web/src/pages/settings/PermissionsPage.tsx +160 -0
  526. package/web/src/pages/settings/PluginsPage.tsx +251 -0
  527. package/web/src/pages/settings/SettingsPage.tsx +141 -0
  528. package/web/src/pages/settings/SkillsPage.tsx +142 -0
  529. package/web/src/pages/settings/ui.tsx +165 -0
  530. package/web/src/preference-document.ts +133 -0
  531. package/web/src/project-actions.ts +18 -0
  532. package/web/src/project-files-state.test.ts +49 -0
  533. package/web/src/project-files-state.ts +64 -0
  534. package/web/src/project-images.ts +13 -0
  535. package/web/src/pwa.ts +33 -0
  536. package/web/src/queryClient.ts +5 -0
  537. package/web/src/reasoning-preference.test.ts +39 -0
  538. package/web/src/reasoning-preference.ts +42 -0
  539. package/web/src/session-confirmations.ts +36 -0
  540. package/web/src/stream-replay.test.ts +163 -0
  541. package/web/src/styles.css +231 -0
  542. package/web/src/tool-result-body.test.ts +115 -0
  543. package/web/src/transcript.test.ts +186 -0
  544. package/web/src/transcript.ts +583 -0
  545. package/web/src/turns.test.ts +338 -0
  546. package/web/src/turns.ts +178 -0
  547. package/web/src/ui/Button.tsx +60 -0
  548. package/web/src/ui/Controls.tsx +227 -0
  549. package/web/src/ui/Form.tsx +203 -0
  550. package/web/src/ui/MenuPopover.tsx +144 -0
  551. package/web/src/ui/Popover.tsx +147 -0
  552. package/web/src/ui/ResizablePane.tsx +110 -0
  553. package/web/src/ui/Select.tsx +150 -0
  554. package/web/src/ui/icons.tsx +270 -0
  555. package/web/src/unsaved.tsx +99 -0
  556. package/web/src/user-text.test.ts +60 -0
  557. package/web/src/user-text.ts +35 -0
  558. package/web/src/uuid.ts +12 -0
  559. package/README.md +0 -3
@@ -0,0 +1,186 @@
1
+ import fs from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import type { AgentFile, AgentInput } from "../../shared/config.ts";
5
+ import { initInfo, invalidate } from "../cli-control.ts";
6
+ import { ConfigError, configDir, writeFileAtomic } from "./paths.ts";
7
+
8
+ /**
9
+ * Custom subagents (/agents): `~/.claude/agents/*.md` and `<project>/.claude/agents/*.md`,
10
+ * YAML frontmatter (name, description, tools, model, …) + the system prompt as body.
11
+ * Unknown frontmatter keys are kept verbatim.
12
+ */
13
+
14
+ const KNOWN = ["name", "description", "tools", "model"] as const;
15
+
16
+ export function agentsDir(scope: "user" | "project", root: string | null): string {
17
+ if (scope === "user") return path.join(configDir, "agents");
18
+ if (!root) throw new ConfigError(400, "项目级 agent 需要先选择项目");
19
+ return path.join(root, ".claude", "agents");
20
+ }
21
+
22
+ type Entry = { key: string; lines: string[] };
23
+
24
+ /** Split frontmatter into top-level entries; an entry owns its indented continuation lines. */
25
+ export function splitFrontmatter(text: string): { entries: Entry[] | null; body: string } {
26
+ const m = /^---\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/.exec(text);
27
+ if (!m) return { entries: null, body: text };
28
+ const entries: Entry[] = [];
29
+ for (const line of m[1].split(/\r?\n/)) {
30
+ const km = /^([A-Za-z0-9_-]+)\s*:/.exec(line);
31
+ if (km && !/^\s/.test(line)) entries.push({ key: km[1], lines: [line] });
32
+ else if (entries.length) entries[entries.length - 1].lines.push(line);
33
+ else entries.push({ key: "", lines: [line] });
34
+ }
35
+ return { entries, body: text.slice(m[0].length) };
36
+ }
37
+
38
+ function unquote(v: string): string {
39
+ const t = v.trim();
40
+ if (t.startsWith('"') && t.endsWith('"') && t.length >= 2) {
41
+ try {
42
+ return JSON.parse(t) as string;
43
+ } catch {
44
+ return t.slice(1, -1);
45
+ }
46
+ }
47
+ if (t.startsWith("'") && t.endsWith("'") && t.length >= 2) return t.slice(1, -1).replace(/''/g, "'");
48
+ return t;
49
+ }
50
+
51
+ /** Scalar value of an entry; handles `key: value`, block scalars (`|`, `>`) and `- item` lists. */
52
+ function entryValue(e: Entry): string {
53
+ const first = e.lines[0].slice(e.lines[0].indexOf(":") + 1).trim();
54
+ const rest = e.lines.slice(1);
55
+ if (/^[|>][-+]?$/.test(first)) {
56
+ const lines = rest.map((l) => l.replace(/^\s{1,2}/, ""));
57
+ return (first.startsWith(">") ? lines.join(" ") : lines.join("\n")).trim();
58
+ }
59
+ const items = rest.map((l) => /^\s*-\s+(.*)$/.exec(l)?.[1]).filter((x): x is string => x !== undefined);
60
+ if (!first && items.length) return items.map(unquote).join(", ");
61
+ if (first.startsWith("[") && first.endsWith("]")) return first.slice(1, -1).split(",").map(unquote).filter(Boolean).join(", ");
62
+ return [first, ...rest.map((l) => l.trim())].filter(Boolean).map(unquote).join(" ");
63
+ }
64
+
65
+ /** YAML-safe scalar: plain when harmless, else a JSON (= YAML double-quoted) string. */
66
+ function yamlScalar(v: string): string {
67
+ if (/^[\w./@()* ,+-]+$/.test(v) && !/^[-?:,[\]{}#&*!|>'"%@`]/.test(v) && !/:\s|\s#/.test(v) && v.trim() === v) return v;
68
+ return JSON.stringify(v);
69
+ }
70
+
71
+ export function parseAgent(text: string): Omit<AgentFile, "scope" | "file" | "path" | "mtime"> {
72
+ const { entries, body } = splitFrontmatter(text);
73
+ const get = (k: string) => {
74
+ const e = entries?.find((x) => x.key === k);
75
+ return e ? entryValue(e) : "";
76
+ };
77
+ const extra = (entries ?? [])
78
+ .filter((e) => !(KNOWN as readonly string[]).includes(e.key))
79
+ .flatMap((e) => e.lines)
80
+ .join("\n");
81
+ return { name: get("name"), description: get("description"), tools: get("tools"), model: get("model"), extra, prompt: body.replace(/^\r?\n/, "") };
82
+ }
83
+
84
+ export function serializeAgent(a: { name: string; description: string; tools: string; model: string; prompt: string }, original?: string): string {
85
+ const known: Record<string, string> = {
86
+ name: a.name.trim(),
87
+ description: a.description.trim(),
88
+ tools: a.tools
89
+ .split(",")
90
+ .map((t) => t.trim())
91
+ .filter(Boolean)
92
+ .join(", "),
93
+ model: a.model.trim(),
94
+ };
95
+ const prev = original ? splitFrontmatter(original).entries : null;
96
+ const out: string[] = [];
97
+ const written = new Set<string>();
98
+ // Keep the original key order and unknown keys; update or drop the known ones.
99
+ for (const e of prev ?? []) {
100
+ if ((KNOWN as readonly string[]).includes(e.key)) {
101
+ if (known[e.key]) out.push(`${e.key}: ${yamlScalar(known[e.key])}`);
102
+ written.add(e.key);
103
+ } else out.push(...e.lines);
104
+ }
105
+ for (const k of KNOWN) if (!written.has(k) && known[k]) out.push(`${k}: ${yamlScalar(known[k])}`);
106
+ const prompt = a.prompt.replace(/\s+$/, "");
107
+ return `---\n${out.join("\n")}\n---\n\n${prompt}\n`;
108
+ }
109
+
110
+ async function exists(p: string) {
111
+ try {
112
+ await fs.access(p);
113
+ return true;
114
+ } catch {
115
+ return false;
116
+ }
117
+ }
118
+
119
+ async function readAgentFile(scope: "user" | "project", file: string): Promise<AgentFile | null> {
120
+ try {
121
+ const st = await fs.stat(file);
122
+ if (!st.isFile()) return null;
123
+ return { scope, file: path.basename(file, ".md"), path: file, mtime: st.mtimeMs, ...parseAgent(await fs.readFile(file, "utf8")) };
124
+ } catch {
125
+ return null;
126
+ }
127
+ }
128
+
129
+ export async function listAgents(root: string | null) {
130
+ const agents: AgentFile[] = [];
131
+ for (const scope of root ? (["user", "project"] as const) : (["user"] as const)) {
132
+ const dir = agentsDir(scope, root);
133
+ let names: string[] = [];
134
+ try {
135
+ names = (await fs.readdir(dir)).filter((n) => n.endsWith(".md")).sort();
136
+ } catch {
137
+ // no agents dir
138
+ }
139
+ for (const n of names) {
140
+ const a = await readAgentFile(scope, path.join(dir, n));
141
+ if (a) agents.push(a);
142
+ }
143
+ }
144
+ // Agents the CLI actually loads (built-ins, plugins, files), for reference.
145
+ const init = await initInfo(root ?? os.homedir()).catch(() => null);
146
+ return { agents, effective: (init?.agents ?? []).map((a) => ({ name: a.name, description: a.description, model: a.model })) };
147
+ }
148
+
149
+ const FILE_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,80}$/;
150
+
151
+ function agentPath(scope: "user" | "project", root: string | null, file: string): string {
152
+ if (!FILE_RE.test(file) || file.includes("..")) throw new ConfigError(400, "文件名不合法");
153
+ return path.join(agentsDir(scope, root), `${file}.md`);
154
+ }
155
+
156
+ export async function saveAgent(root: string | null, input: AgentInput & { baseMtime?: number }) {
157
+ const scope = input.scope === "project" ? "project" : "user";
158
+ const name = String(input.name ?? "").trim();
159
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(name)) throw new ConfigError(400, "名称只能包含字母、数字、- 和 _");
160
+ if (!String(input.description ?? "").trim()) throw new ConfigError(400, "请填写说明(Claude 据此决定何时使用它)");
161
+ const file = input.file ?? name;
162
+ const p = agentPath(scope, root, file);
163
+ let original: string | undefined;
164
+ if (input.file) {
165
+ try {
166
+ const st = await fs.stat(p);
167
+ if (input.baseMtime !== undefined && Math.abs(st.mtimeMs - input.baseMtime) > 1) throw new ConfigError(409, "文件在你编辑期间被修改了,请重新打开后再保存");
168
+ original = await fs.readFile(p, "utf8");
169
+ } catch (err) {
170
+ if (err instanceof ConfigError) throw err;
171
+ throw new ConfigError(404, "agent 文件不存在");
172
+ }
173
+ } else if (await exists(p)) {
174
+ throw new ConfigError(409, "同名 agent 已存在");
175
+ }
176
+ await writeFileAtomic(p, serializeAgent({ name, description: input.description, tools: input.tools ?? "", model: input.model ?? "", prompt: input.prompt ?? "" }, original));
177
+ invalidate("init:");
178
+ return (await readAgentFile(scope, p))!;
179
+ }
180
+
181
+ export async function deleteAgent(root: string | null, scope: string, file: string) {
182
+ const p = agentPath(scope === "project" ? "project" : "user", root, file);
183
+ if (!(await exists(p))) throw new ConfigError(404, "agent 文件不存在");
184
+ await fs.rm(p);
185
+ invalidate("init:");
186
+ }
@@ -0,0 +1,86 @@
1
+ import os from "node:os";
2
+ import type { McpAddInput, McpServer, SettingsScope } from "../../shared/config.ts";
3
+ import { clearMcpAuth, mcpStatus, toggleMcp } from "../cli-control.ts";
4
+ import { ConfigError, runClaude } from "./paths.ts";
5
+
6
+ /**
7
+ * MCP servers (/mcp). Status comes from the CLI (mcpServerStatus in a probe query);
8
+ * add/remove go through `claude mcp add-json/remove -s <scope>` so the CLI writes its
9
+ * own files (~/.claude.json for local/user, .mcp.json for project).
10
+ */
11
+
12
+ const EDITABLE = new Set(["local", "project", "user"]);
13
+
14
+ export async function listMcp(root: string | null): Promise<McpServer[]> {
15
+ const list = await mcpStatus(root ?? os.homedir());
16
+ return list.map((s) => {
17
+ const scope = s.scope ?? s.source ?? "unknown";
18
+ return {
19
+ name: s.name,
20
+ scope,
21
+ status: s.status,
22
+ ...(s.error ? { error: s.error.length > 500 ? s.error.slice(0, 500) + "…" : s.error } : {}),
23
+ config: s.config ?? {},
24
+ editable: EDITABLE.has(scope) && (scope === "user" || !!root),
25
+ };
26
+ });
27
+ }
28
+
29
+ const NAME_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$/;
30
+
31
+ function cliError(r: { stdout: string; stderr: string }, fallback: string) {
32
+ const text = (r.stderr || r.stdout).trim();
33
+ return new ConfigError(400, text ? text.split("\n").slice(0, 6).join("\n") : fallback);
34
+ }
35
+
36
+ export async function addMcp(root: string | null, input: McpAddInput) {
37
+ const name = String(input.name ?? "").trim();
38
+ if (!NAME_RE.test(name)) throw new ConfigError(400, "名称只能包含字母、数字、-、_ 和 .");
39
+ const scope = input.scope as SettingsScope;
40
+ if (!["local", "project", "user"].includes(scope)) throw new ConfigError(400, "未知的作用域");
41
+ if (scope !== "user" && !root) throw new ConfigError(400, "local / project 作用域需要先选择项目");
42
+ let json: Record<string, unknown>;
43
+ if (input.transport === "stdio") {
44
+ const command = String(input.command ?? "").trim();
45
+ if (!command) throw new ConfigError(400, "请填写命令");
46
+ json = { type: "stdio", command, args: (input.args ?? []).map(String), env: cleanMap(input.env) };
47
+ } else if (input.transport === "http" || input.transport === "sse") {
48
+ const url = String(input.url ?? "").trim();
49
+ if (!/^https?:\/\//.test(url)) throw new ConfigError(400, "请填写 http(s) 地址");
50
+ json = { type: input.transport, url, ...(Object.keys(cleanMap(input.headers)).length ? { headers: cleanMap(input.headers) } : {}) };
51
+ } else throw new ConfigError(400, "未知的传输方式");
52
+ const r = await runClaude(["mcp", "add-json", "-s", scope, name, JSON.stringify(json)], root ?? os.homedir());
53
+ if (r.code !== 0) throw cliError(r, "添加失败");
54
+ return { ok: true, message: r.stdout.trim() };
55
+ }
56
+
57
+ function cleanMap(m: Record<string, string> | undefined): Record<string, string> {
58
+ const out: Record<string, string> = {};
59
+ for (const [k, v] of Object.entries(m ?? {})) if (k.trim()) out[k.trim()] = String(v);
60
+ return out;
61
+ }
62
+
63
+ export async function removeMcp(root: string | null, name: string, scope: string) {
64
+ if (!NAME_RE.test(name)) throw new ConfigError(400, "名称不合法");
65
+ if (!EDITABLE.has(scope)) throw new ConfigError(400, "这个服务器不在可编辑的作用域里");
66
+ const r = await runClaude(["mcp", "remove", "-s", scope, name], root ?? os.homedir());
67
+ if (r.code !== 0) throw cliError(r, "移除失败");
68
+ return { ok: true, message: r.stdout.trim() };
69
+ }
70
+
71
+ export async function setMcpEnabled(root: string | null, name: string, enabled: boolean) {
72
+ const r = await toggleMcp(root ?? os.homedir(), name, enabled);
73
+ return { ok: true, ...r };
74
+ }
75
+
76
+ export async function logoutMcp(root: string | null, name: string) {
77
+ await clearMcpAuth(root ?? os.homedir(), name);
78
+ return { ok: true };
79
+ }
80
+
81
+ /** `claude mcp get <name>`: the CLI's own view of one server (used by tests and the detail view). */
82
+ export async function getMcpText(root: string | null, name: string) {
83
+ if (!NAME_RE.test(name)) throw new ConfigError(400, "名称不合法");
84
+ const r = await runClaude(["mcp", "get", name], root ?? os.homedir());
85
+ return { found: r.code === 0, text: (r.stdout || r.stderr).trim() };
86
+ }
@@ -0,0 +1,193 @@
1
+ import fs from "node:fs/promises";
2
+ import type { Dirent } from "node:fs";
3
+ import path from "node:path";
4
+ import type { MemoryFile, MemoryKind, MemoryListing, SettingsScope, TextFile } from "../../shared/config.ts";
5
+ import { invalidate, memoryDialog } from "../cli-control.ts";
6
+ import { ConfigError, configDir, isInside, readJsonFile, settingsFile, tildify, updateJsonFile, writeFileAtomic } from "./paths.ts";
7
+
8
+ /**
9
+ * CLAUDE.md files, rules and auto memory (/memory). The CLI's memory dialog tells us
10
+ * which files it loads and where the auto-memory folder is (it canonicalizes worktrees
11
+ * to the main repo); the fixed locations are added so missing files can be created.
12
+ */
13
+
14
+ const MAX_EDIT_BYTES = 1024 * 1024;
15
+
16
+ /** Directory name the CLI uses under ~/.claude/projects for a project root. */
17
+ export function encodeProjectDir(root: string): string {
18
+ return root.replace(/[^a-zA-Z0-9]/g, "-");
19
+ }
20
+
21
+ type Targets = {
22
+ fixed: { kind: MemoryKind; path: string; label: string }[];
23
+ dirs: { kind: MemoryKind; path: string; label: string }[];
24
+ other: { path: string; label: string }[];
25
+ auto: { enabled: boolean; dir: string | null };
26
+ };
27
+
28
+ async function targets(root: string | null): Promise<Targets> {
29
+ const fixed: Targets["fixed"] = [{ kind: "user", path: path.join(configDir, "CLAUDE.md"), label: tildify(path.join(configDir, "CLAUDE.md")) }];
30
+ const dirs: Targets["dirs"] = [{ kind: "user-rule", path: path.join(configDir, "rules"), label: tildify(path.join(configDir, "rules")) }];
31
+ const other: Targets["other"] = [];
32
+ let auto: Targets["auto"] = { enabled: true, dir: null };
33
+ if (root) {
34
+ fixed.push(
35
+ { kind: "project", path: path.join(root, "CLAUDE.md"), label: "CLAUDE.md" },
36
+ { kind: "project", path: path.join(root, ".claude", "CLAUDE.md"), label: ".claude/CLAUDE.md" },
37
+ { kind: "local", path: path.join(root, "CLAUDE.local.md"), label: "CLAUDE.local.md" },
38
+ );
39
+ dirs.push({ kind: "project-rule", path: path.join(root, ".claude", "rules"), label: ".claude/rules" });
40
+ let autoDir = path.join(configDir, "projects", encodeProjectDir(root), "memory");
41
+ try {
42
+ const d = await memoryDialog(root);
43
+ const folder = d.folders.find((f) => f.kind === "auto");
44
+ if (folder) autoDir = path.resolve(folder.path);
45
+ auto = { enabled: d.auto_memory.enabled, dir: autoDir };
46
+ const known = new Set([...fixed.map((f) => f.path)]);
47
+ for (const f of d.files) {
48
+ const p = path.resolve(f.path);
49
+ if (known.has(p) || dirs.some((dd) => isInside(dd.path, p))) continue;
50
+ other.push({ path: p, label: tildify(p) });
51
+ }
52
+ } catch {
53
+ auto = { enabled: await autoMemoryFromFiles(root), dir: autoDir };
54
+ }
55
+ dirs.push({ kind: "auto", path: auto.dir!, label: "自动记忆" });
56
+ }
57
+ return { fixed, dirs, other, auto };
58
+ }
59
+
60
+ async function autoMemoryFromFiles(root: string | null): Promise<boolean> {
61
+ for (const scope of ["local", "project", "user"] as const) {
62
+ const f = settingsFile(scope, root);
63
+ if (!f) continue;
64
+ const v = (await readJsonFile(f)).data.autoMemoryEnabled;
65
+ if (typeof v === "boolean") return v;
66
+ }
67
+ return true;
68
+ }
69
+
70
+ async function statFile(p: string): Promise<{ exists: boolean; size?: number; mtime?: number }> {
71
+ try {
72
+ const st = await fs.stat(p);
73
+ return st.isFile() ? { exists: true, size: st.size, mtime: st.mtimeMs } : { exists: false };
74
+ } catch {
75
+ return { exists: false };
76
+ }
77
+ }
78
+
79
+ async function listMarkdown(dir: string, depth = 0): Promise<string[]> {
80
+ if (depth > 4) return [];
81
+ let entries: Dirent[];
82
+ try {
83
+ entries = await fs.readdir(dir, { withFileTypes: true });
84
+ } catch {
85
+ return [];
86
+ }
87
+ const out: string[] = [];
88
+ for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
89
+ const p = path.join(dir, e.name);
90
+ if (e.isDirectory()) out.push(...await listMarkdown(p, depth + 1));
91
+ else if (e.isFile() && e.name.endsWith(".md")) out.push(p);
92
+ }
93
+ return out;
94
+ }
95
+
96
+ export async function listMemory(root: string | null): Promise<MemoryListing> {
97
+ const t = await targets(root);
98
+ const files: MemoryFile[] = [];
99
+ for (const f of t.fixed) {
100
+ const st = await statFile(f.path);
101
+ // .claude/CLAUDE.md is an alternative location; only list it when it exists.
102
+ if (!st.exists && f.label === ".claude/CLAUDE.md") continue;
103
+ files.push({ kind: f.kind, path: f.path, label: f.label, ...st });
104
+ }
105
+ for (const d of t.dirs) {
106
+ const list = await listMarkdown(d.path);
107
+ // MEMORY.md (the index) first.
108
+ if (d.kind === "auto") list.sort((a, b) => Number(path.basename(b) === "MEMORY.md") - Number(path.basename(a) === "MEMORY.md"));
109
+ for (const p of list) files.push({ kind: d.kind, path: p, label: d.kind === "auto" ? path.relative(d.path, p) : `${d.label}/${path.relative(d.path, p)}`, ...await statFile(p) });
110
+ }
111
+ for (const o of t.other) files.push({ kind: "other", path: o.path, label: o.label, ...await statFile(o.path) });
112
+ return {
113
+ files,
114
+ dirs: t.dirs.map((d) => ({ kind: d.kind, path: d.path, label: d.kind === "auto" ? tildify(d.path) : d.label })),
115
+ autoMemory: { enabled: t.auto.enabled, dir: t.auto.dir, scope: await autoMemoryScope(root) },
116
+ };
117
+ }
118
+
119
+ /** Only known memory files, or Markdown files inside the rules / auto-memory dirs, may be touched. */
120
+ async function checkPath(root: string | null, raw: string): Promise<string> {
121
+ if (!raw || raw.includes("\0") || !path.isAbsolute(raw)) throw new ConfigError(400, "非法路径");
122
+ const p = path.resolve(raw);
123
+ const t = await targets(root);
124
+ if (t.fixed.some((f) => f.path === p) || t.other.some((o) => o.path === p)) return p;
125
+ const dir = t.dirs.find((d) => isInside(d.path, p) && p !== d.path);
126
+ if (p.endsWith(".md") && dir) {
127
+ // Symlinks inside the directory must not lead outside it.
128
+ let probe = p;
129
+ for (;;) {
130
+ try {
131
+ await fs.access(probe);
132
+ break;
133
+ } catch {
134
+ const parent = path.dirname(probe);
135
+ if (parent === probe) break;
136
+ probe = parent;
137
+ }
138
+ }
139
+ if (!isInside(dir.path, probe)) return p; // nothing under the directory exists yet
140
+ if (isInside(await fs.realpath(dir.path), await fs.realpath(probe))) return p;
141
+ }
142
+ throw new ConfigError(403, "不是记忆文件");
143
+ }
144
+
145
+ export async function readMemoryFile(root: string | null, raw: string): Promise<TextFile> {
146
+ const p = await checkPath(root, raw);
147
+ const st = await statFile(p);
148
+ if (!st.exists) throw new ConfigError(404, "文件不存在");
149
+ if ((st.size ?? 0) > MAX_EDIT_BYTES) throw new ConfigError(400, "文件太大");
150
+ return { path: p, content: await fs.readFile(p, "utf8"), mtime: st.mtime! };
151
+ }
152
+
153
+ export async function writeMemoryFile(root: string | null, body: { path?: string; content?: string; create?: boolean; baseMtime?: number }): Promise<TextFile> {
154
+ const p = await checkPath(root, String(body.path ?? ""));
155
+ const content = String(body.content ?? "");
156
+ if (Buffer.byteLength(content) > MAX_EDIT_BYTES) throw new ConfigError(400, "内容太大");
157
+ const st = await statFile(p);
158
+ if (body.create && st.exists) throw new ConfigError(409, "文件已存在");
159
+ if (!body.create && !st.exists) throw new ConfigError(404, "文件不存在");
160
+ // Someone (Claude, the CLI) changed it since it was opened: don't overwrite blindly.
161
+ if (body.baseMtime !== undefined && st.exists && Math.abs((st.mtime ?? 0) - body.baseMtime) > 1) {
162
+ throw new ConfigError(409, "文件在你编辑期间被修改了,请重新打开后再保存");
163
+ }
164
+ await writeFileAtomic(p, content);
165
+ if (root) invalidate(`memory:${root}`);
166
+ return { path: p, content, mtime: (await statFile(p)).mtime! };
167
+ }
168
+
169
+ export async function deleteMemoryFile(root: string | null, raw: string) {
170
+ const p = await checkPath(root, raw);
171
+ if (!(await statFile(p)).exists) throw new ConfigError(404, "文件不存在");
172
+ await fs.rm(p);
173
+ if (root) invalidate(`memory:${root}`);
174
+ }
175
+
176
+ /** The highest-priority settings file that sets autoMemoryEnabled (null: default). */
177
+ async function autoMemoryScope(root: string | null): Promise<SettingsScope | null> {
178
+ for (const scope of ["local", "project", "user"] as const) {
179
+ const f = settingsFile(scope, root);
180
+ if (f && typeof (await readJsonFile(f)).data.autoMemoryEnabled === "boolean") return scope;
181
+ }
182
+ return null;
183
+ }
184
+
185
+ /** Toggle auto memory where it is decided: the layer that sets it, else user settings. */
186
+ export async function setAutoMemory(root: string | null, enabled: boolean) {
187
+ const scope = (await autoMemoryScope(root)) ?? "user";
188
+ await updateJsonFile(settingsFile(scope, root)!, (d) => {
189
+ d.autoMemoryEnabled = enabled;
190
+ });
191
+ if (root) invalidate(`memory:${root}`);
192
+ return { scope };
193
+ }
@@ -0,0 +1,156 @@
1
+ import { childEnv } from "../child-env.ts";
2
+ import fs from "node:fs/promises";
3
+ import { withFileLock } from "../fs-queue.ts";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import crypto from "node:crypto";
7
+ import { execFile } from "node:child_process";
8
+ import type { SettingsScope } from "../../shared/config.ts";
9
+
10
+ /**
11
+ * Where the CLI keeps its user-level config. Normally ~/.claude (+ ~/.claude.json).
12
+ * `CLAUDE_IDE_CLAUDE_CONFIG_DIR` points only the settings pages (and the `claude`
13
+ * subcommands / probe queries they run) at another directory — e2e tests use it so
14
+ * they never touch the real config while live sessions keep the real login.
15
+ * An inherited `CLAUDE_CONFIG_DIR` is honoured the same way the CLI honours it.
16
+ */
17
+ const override = process.env.CLAUDE_IDE_CLAUDE_CONFIG_DIR ? path.resolve(process.env.CLAUDE_IDE_CLAUDE_CONFIG_DIR) : undefined;
18
+ const inherited = process.env.CLAUDE_CONFIG_DIR ? path.resolve(process.env.CLAUDE_CONFIG_DIR) : undefined;
19
+
20
+ export const configDir = override ?? inherited ?? path.join(os.homedir(), ".claude");
21
+ if (override) await fs.mkdir(override, { recursive: true });
22
+
23
+ /** The CLI's state file: `$CLAUDE_CONFIG_DIR/.claude.json` when set, else `~/.claude.json`. */
24
+ export const globalStateFile = override || inherited ? path.join(configDir, ".claude.json") : path.join(os.homedir(), ".claude.json");
25
+
26
+ /** Environment for `claude` subcommands and probe queries started by the settings pages. */
27
+ export function claudeEnv(): Record<string, string | undefined> {
28
+ const env = childEnv();
29
+ if (override) env.CLAUDE_CONFIG_DIR = override;
30
+ return env;
31
+ }
32
+
33
+ /** "~/…" for display, relative to the real home. */
34
+ export function tildify(p: string): string {
35
+ const home = os.homedir();
36
+ return p === home || p.startsWith(home + path.sep) ? "~" + p.slice(home.length) : p;
37
+ }
38
+
39
+ export function settingsFile(scope: SettingsScope, projectRoot: string | null): string | null {
40
+ if (scope === "user") return path.join(configDir, "settings.json");
41
+ if (!projectRoot) return null;
42
+ return path.join(projectRoot, ".claude", scope === "project" ? "settings.json" : "settings.local.json");
43
+ }
44
+
45
+ export const isInside = (root: string, p: string) => {
46
+ const rel = path.relative(root, p);
47
+ return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
48
+ };
49
+
50
+ // ---------- atomic writes ----------
51
+
52
+ /** Write a file atomically (temp file in the same directory, then rename), keeping its mode. */
53
+ export async function writeFileAtomic(file: string, content: string) {
54
+ await fs.mkdir(path.dirname(file), { recursive: true });
55
+ let mode: number | undefined;
56
+ try {
57
+ mode = (await fs.stat(file)).mode & 0o777;
58
+ } catch {
59
+ // new file
60
+ }
61
+ const tmp = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${crypto.randomBytes(4).toString("hex")}.tmp`);
62
+ await fs.writeFile(tmp, content, mode !== undefined ? { mode } : {});
63
+ try {
64
+ await fs.rename(tmp, file);
65
+ } catch (err) {
66
+ await fs.rm(tmp, { force: true });
67
+ throw err;
68
+ }
69
+ }
70
+
71
+ type JsonObject = Record<string, unknown>;
72
+
73
+ export class ConfigError extends Error {
74
+ constructor(
75
+ public status: 400 | 403 | 404 | 409 | 500 | 502,
76
+ message: string,
77
+ ) {
78
+ super(message);
79
+ }
80
+ }
81
+
82
+ export async function readJsonFile(file: string): Promise<{ data: JsonObject; exists: boolean; indent: string | number; newline: boolean }> {
83
+ let raw: string;
84
+ try {
85
+ raw = await fs.readFile(file, "utf8");
86
+ } catch {
87
+ return { data: {}, exists: false, indent: 2, newline: true };
88
+ }
89
+ if (!raw.trim()) return { data: {}, exists: true, indent: 2, newline: true };
90
+ let data: unknown;
91
+ try {
92
+ data = JSON.parse(raw);
93
+ } catch {
94
+ throw new ConfigError(409, `${tildify(file)} 不是合法的 JSON,请先手动修复`);
95
+ }
96
+ if (!data || typeof data !== "object" || Array.isArray(data)) throw new ConfigError(409, `${tildify(file)} 的顶层不是对象`);
97
+ // Keep the file's indentation style: the first indented line decides.
98
+ const m = /\n([ \t]+)\S/.exec(raw);
99
+ const indent = m ? (m[1].includes("\t") ? "\t" : m[1].length) : 2;
100
+ return { data: data as JsonObject, exists: true, indent, newline: raw.endsWith("\n") };
101
+ }
102
+
103
+ /**
104
+ * Read-modify-write a JSON config file. Unknown keys are kept as they are, the
105
+ * indentation style is preserved, and the write is atomic.
106
+ */
107
+ export function updateJsonFile(file: string, fn: (data: JsonObject) => void): Promise<JsonObject> {
108
+ return withFileLock(file, async () => {
109
+ const { data, indent, newline } = await readJsonFile(file);
110
+ fn(data);
111
+ await writeFileAtomic(file, JSON.stringify(data, null, indent) + (newline ? "\n" : ""));
112
+ return data;
113
+ });
114
+ }
115
+
116
+ // ---------- running `claude` ----------
117
+
118
+ export const claudeBin = process.env.CLAUDE_IDE_CLAUDE_BIN || "claude";
119
+
120
+ export type CliResult = { code: number; stdout: string; stderr: string };
121
+
122
+ /** Run a `claude` subcommand (never a model call) against the configured config dir. */
123
+ export function runClaude(args: string[], cwd: string, opts: { timeoutMs?: number } = {}): Promise<CliResult> {
124
+ return new Promise((resolve, reject) => {
125
+ execFile(
126
+ claudeBin,
127
+ args,
128
+ { cwd, env: claudeEnv(), timeout: opts.timeoutMs ?? 60_000, maxBuffer: 16 * 1024 * 1024, encoding: "utf8" },
129
+ (err, stdout, stderr) => {
130
+ if (err && typeof (err as NodeJS.ErrnoException).code === "string") return reject(new ConfigError(500, `无法运行 claude:${err.message}`));
131
+ if (err && (err as { killed?: boolean }).killed) return resolve({ code: -1, stdout, stderr: stderr || "超时" });
132
+ const code = err ? ((err as { code?: number }).code ?? 1) : 0;
133
+ resolve({ code: typeof code === "number" ? code : 1, stdout, stderr });
134
+ },
135
+ ).stdin?.end();
136
+ });
137
+ }
138
+
139
+ /** Parse the last JSON value printed on stdout (`--json` prints one line, sometimes after other text). */
140
+ export function parseJsonOutput<T>(stdout: string): T | undefined {
141
+ const t = stdout.trim();
142
+ if (!t) return undefined;
143
+ try {
144
+ return JSON.parse(t) as T;
145
+ } catch {
146
+ const lines = t.split("\n");
147
+ for (let i = lines.length - 1; i >= 0; i--) {
148
+ try {
149
+ return JSON.parse(lines.slice(i).join("\n")) as T;
150
+ } catch {
151
+ // keep looking
152
+ }
153
+ }
154
+ }
155
+ return undefined;
156
+ }