praxis-agent 0.1.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 (395) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +534 -0
  3. package/dist/application/background-agent-manager.d.ts +66 -0
  4. package/dist/application/background-agent-manager.d.ts.map +1 -0
  5. package/dist/application/background-agent-manager.js +362 -0
  6. package/dist/application/background-agent-manager.js.map +1 -0
  7. package/dist/application/background-bash-manager.d.ts +54 -0
  8. package/dist/application/background-bash-manager.d.ts.map +1 -0
  9. package/dist/application/background-bash-manager.js +354 -0
  10. package/dist/application/background-bash-manager.js.map +1 -0
  11. package/dist/application/background-task-id.d.ts +5 -0
  12. package/dist/application/background-task-id.d.ts.map +1 -0
  13. package/dist/application/background-task-id.js +25 -0
  14. package/dist/application/background-task-id.js.map +1 -0
  15. package/dist/application/claude-project-purge.d.ts +50 -0
  16. package/dist/application/claude-project-purge.d.ts.map +1 -0
  17. package/dist/application/claude-project-purge.js +459 -0
  18. package/dist/application/claude-project-purge.js.map +1 -0
  19. package/dist/application/managed-worktree.d.ts +15 -0
  20. package/dist/application/managed-worktree.d.ts.map +1 -0
  21. package/dist/application/managed-worktree.js +127 -0
  22. package/dist/application/managed-worktree.js.map +1 -0
  23. package/dist/application/model-compactor.d.ts +8 -0
  24. package/dist/application/model-compactor.d.ts.map +1 -0
  25. package/dist/application/model-compactor.js +70 -0
  26. package/dist/application/model-compactor.js.map +1 -0
  27. package/dist/application/scheduled-prompt-manager.d.ts +69 -0
  28. package/dist/application/scheduled-prompt-manager.d.ts.map +1 -0
  29. package/dist/application/scheduled-prompt-manager.js +387 -0
  30. package/dist/application/scheduled-prompt-manager.js.map +1 -0
  31. package/dist/application/session-service.d.ts +142 -0
  32. package/dist/application/session-service.d.ts.map +1 -0
  33. package/dist/application/session-service.js +1690 -0
  34. package/dist/application/session-service.js.map +1 -0
  35. package/dist/application/session-worktree.d.ts +55 -0
  36. package/dist/application/session-worktree.d.ts.map +1 -0
  37. package/dist/application/session-worktree.js +311 -0
  38. package/dist/application/session-worktree.js.map +1 -0
  39. package/dist/application/subagent-service.d.ts +98 -0
  40. package/dist/application/subagent-service.d.ts.map +1 -0
  41. package/dist/application/subagent-service.js +1305 -0
  42. package/dist/application/subagent-service.js.map +1 -0
  43. package/dist/application/tool-use-summary.d.ts +8 -0
  44. package/dist/application/tool-use-summary.d.ts.map +1 -0
  45. package/dist/application/tool-use-summary.js +53 -0
  46. package/dist/application/tool-use-summary.js.map +1 -0
  47. package/dist/application/top-level-agent-manager.d.ts +58 -0
  48. package/dist/application/top-level-agent-manager.d.ts.map +1 -0
  49. package/dist/application/top-level-agent-manager.js +907 -0
  50. package/dist/application/top-level-agent-manager.js.map +1 -0
  51. package/dist/application/workflow-manager.d.ts +61 -0
  52. package/dist/application/workflow-manager.d.ts.map +1 -0
  53. package/dist/application/workflow-manager.js +582 -0
  54. package/dist/application/workflow-manager.js.map +1 -0
  55. package/dist/application/workflow-meta.d.ts +18 -0
  56. package/dist/application/workflow-meta.d.ts.map +1 -0
  57. package/dist/application/workflow-meta.js +146 -0
  58. package/dist/application/workflow-meta.js.map +1 -0
  59. package/dist/application/workflow-runtime.d.ts +29 -0
  60. package/dist/application/workflow-runtime.d.ts.map +1 -0
  61. package/dist/application/workflow-runtime.js +176 -0
  62. package/dist/application/workflow-runtime.js.map +1 -0
  63. package/dist/application/workflow-worktree.d.ts +9 -0
  64. package/dist/application/workflow-worktree.d.ts.map +1 -0
  65. package/dist/application/workflow-worktree.js +11 -0
  66. package/dist/application/workflow-worktree.js.map +1 -0
  67. package/dist/cli/agents-dashboard.d.ts +37 -0
  68. package/dist/cli/agents-dashboard.d.ts.map +1 -0
  69. package/dist/cli/agents-dashboard.js +408 -0
  70. package/dist/cli/agents-dashboard.js.map +1 -0
  71. package/dist/cli/controls.d.ts +15 -0
  72. package/dist/cli/controls.d.ts.map +1 -0
  73. package/dist/cli/controls.js +219 -0
  74. package/dist/cli/controls.js.map +1 -0
  75. package/dist/cli/debug.d.ts +13 -0
  76. package/dist/cli/debug.d.ts.map +1 -0
  77. package/dist/cli/debug.js +37 -0
  78. package/dist/cli/debug.js.map +1 -0
  79. package/dist/cli/interactive.d.ts +63 -0
  80. package/dist/cli/interactive.d.ts.map +1 -0
  81. package/dist/cli/interactive.js +600 -0
  82. package/dist/cli/interactive.js.map +1 -0
  83. package/dist/cli/protocol.d.ts +251 -0
  84. package/dist/cli/protocol.d.ts.map +1 -0
  85. package/dist/cli/protocol.js +2146 -0
  86. package/dist/cli/protocol.js.map +1 -0
  87. package/dist/cli.d.ts +116 -0
  88. package/dist/cli.d.ts.map +1 -0
  89. package/dist/cli.js +3901 -0
  90. package/dist/cli.js.map +1 -0
  91. package/dist/compatibility/claude/compaction.d.ts +19 -0
  92. package/dist/compatibility/claude/compaction.d.ts.map +1 -0
  93. package/dist/compatibility/claude/compaction.js +82 -0
  94. package/dist/compatibility/claude/compaction.js.map +1 -0
  95. package/dist/compatibility/claude/context.d.ts +24 -0
  96. package/dist/compatibility/claude/context.d.ts.map +1 -0
  97. package/dist/compatibility/claude/context.js +92 -0
  98. package/dist/compatibility/claude/context.js.map +1 -0
  99. package/dist/compatibility/claude/dynamic-context.d.ts +20 -0
  100. package/dist/compatibility/claude/dynamic-context.d.ts.map +1 -0
  101. package/dist/compatibility/claude/dynamic-context.js +168 -0
  102. package/dist/compatibility/claude/dynamic-context.js.map +1 -0
  103. package/dist/compatibility/claude/file-history.d.ts +14 -0
  104. package/dist/compatibility/claude/file-history.d.ts.map +1 -0
  105. package/dist/compatibility/claude/file-history.js +340 -0
  106. package/dist/compatibility/claude/file-history.js.map +1 -0
  107. package/dist/compatibility/claude/file-resources.d.ts +28 -0
  108. package/dist/compatibility/claude/file-resources.d.ts.map +1 -0
  109. package/dist/compatibility/claude/file-resources.js +168 -0
  110. package/dist/compatibility/claude/file-resources.js.map +1 -0
  111. package/dist/compatibility/claude/fork.d.ts +9 -0
  112. package/dist/compatibility/claude/fork.d.ts.map +1 -0
  113. package/dist/compatibility/claude/fork.js +225 -0
  114. package/dist/compatibility/claude/fork.js.map +1 -0
  115. package/dist/compatibility/claude/history.d.ts +4 -0
  116. package/dist/compatibility/claude/history.d.ts.map +1 -0
  117. package/dist/compatibility/claude/history.js +86 -0
  118. package/dist/compatibility/claude/history.js.map +1 -0
  119. package/dist/compatibility/claude/ownership.d.ts +87 -0
  120. package/dist/compatibility/claude/ownership.d.ts.map +1 -0
  121. package/dist/compatibility/claude/ownership.js +100 -0
  122. package/dist/compatibility/claude/ownership.js.map +1 -0
  123. package/dist/compatibility/claude/paths.d.ts +17 -0
  124. package/dist/compatibility/claude/paths.d.ts.map +1 -0
  125. package/dist/compatibility/claude/paths.js +54 -0
  126. package/dist/compatibility/claude/paths.js.map +1 -0
  127. package/dist/compatibility/claude/pr-links.d.ts +25 -0
  128. package/dist/compatibility/claude/pr-links.d.ts.map +1 -0
  129. package/dist/compatibility/claude/pr-links.js +80 -0
  130. package/dist/compatibility/claude/pr-links.js.map +1 -0
  131. package/dist/compatibility/claude/projection.d.ts +11 -0
  132. package/dist/compatibility/claude/projection.d.ts.map +1 -0
  133. package/dist/compatibility/claude/projection.js +325 -0
  134. package/dist/compatibility/claude/projection.js.map +1 -0
  135. package/dist/compatibility/claude/schema.d.ts +18 -0
  136. package/dist/compatibility/claude/schema.d.ts.map +1 -0
  137. package/dist/compatibility/claude/schema.js +983 -0
  138. package/dist/compatibility/claude/schema.js.map +1 -0
  139. package/dist/compatibility/claude/shared-resources.d.ts +51 -0
  140. package/dist/compatibility/claude/shared-resources.d.ts.map +1 -0
  141. package/dist/compatibility/claude/shared-resources.js +382 -0
  142. package/dist/compatibility/claude/shared-resources.js.map +1 -0
  143. package/dist/compatibility/claude/sidechain.d.ts +50 -0
  144. package/dist/compatibility/claude/sidechain.d.ts.map +1 -0
  145. package/dist/compatibility/claude/sidechain.js +90 -0
  146. package/dist/compatibility/claude/sidechain.js.map +1 -0
  147. package/dist/compatibility/claude/tool-links.d.ts +13 -0
  148. package/dist/compatibility/claude/tool-links.d.ts.map +1 -0
  149. package/dist/compatibility/claude/tool-links.js +61 -0
  150. package/dist/compatibility/claude/tool-links.js.map +1 -0
  151. package/dist/compatibility/claude/translation.d.ts +71 -0
  152. package/dist/compatibility/claude/translation.d.ts.map +1 -0
  153. package/dist/compatibility/claude/translation.js +443 -0
  154. package/dist/compatibility/claude/translation.js.map +1 -0
  155. package/dist/compatibility/claude/workflow-replay.d.ts +10 -0
  156. package/dist/compatibility/claude/workflow-replay.d.ts.map +1 -0
  157. package/dist/compatibility/claude/workflow-replay.js +66 -0
  158. package/dist/compatibility/claude/workflow-replay.js.map +1 -0
  159. package/dist/compatibility/claude/workflow.d.ts +33 -0
  160. package/dist/compatibility/claude/workflow.d.ts.map +1 -0
  161. package/dist/compatibility/claude/workflow.js +76 -0
  162. package/dist/compatibility/claude/workflow.js.map +1 -0
  163. package/dist/core/compaction.d.ts +16 -0
  164. package/dist/core/compaction.d.ts.map +1 -0
  165. package/dist/core/compaction.js +2 -0
  166. package/dist/core/compaction.js.map +1 -0
  167. package/dist/core/context-budget.d.ts +28 -0
  168. package/dist/core/context-budget.d.ts.map +1 -0
  169. package/dist/core/context-budget.js +108 -0
  170. package/dist/core/context-budget.js.map +1 -0
  171. package/dist/core/context.d.ts +16 -0
  172. package/dist/core/context.d.ts.map +1 -0
  173. package/dist/core/context.js +13 -0
  174. package/dist/core/context.js.map +1 -0
  175. package/dist/core/runtime.d.ts +394 -0
  176. package/dist/core/runtime.d.ts.map +1 -0
  177. package/dist/core/runtime.js +507 -0
  178. package/dist/core/runtime.js.map +1 -0
  179. package/dist/core/usage.d.ts +26 -0
  180. package/dist/core/usage.d.ts.map +1 -0
  181. package/dist/core/usage.js +133 -0
  182. package/dist/core/usage.js.map +1 -0
  183. package/dist/extensions/claude-extension-tools.d.ts +16 -0
  184. package/dist/extensions/claude-extension-tools.d.ts.map +1 -0
  185. package/dist/extensions/claude-extension-tools.js +78 -0
  186. package/dist/extensions/claude-extension-tools.js.map +1 -0
  187. package/dist/extensions/claude-extensions.d.ts +41 -0
  188. package/dist/extensions/claude-extensions.d.ts.map +1 -0
  189. package/dist/extensions/claude-extensions.js +270 -0
  190. package/dist/extensions/claude-extensions.js.map +1 -0
  191. package/dist/hooks/claude-hook-tools.d.ts +26 -0
  192. package/dist/hooks/claude-hook-tools.d.ts.map +1 -0
  193. package/dist/hooks/claude-hook-tools.js +129 -0
  194. package/dist/hooks/claude-hook-tools.js.map +1 -0
  195. package/dist/hooks/claude-hooks.d.ts +87 -0
  196. package/dist/hooks/claude-hooks.d.ts.map +1 -0
  197. package/dist/hooks/claude-hooks.js +411 -0
  198. package/dist/hooks/claude-hooks.js.map +1 -0
  199. package/dist/maintenance/doctor.d.ts +33 -0
  200. package/dist/maintenance/doctor.d.ts.map +1 -0
  201. package/dist/maintenance/doctor.js +391 -0
  202. package/dist/maintenance/doctor.js.map +1 -0
  203. package/dist/maintenance/self-update.d.ts +31 -0
  204. package/dist/maintenance/self-update.d.ts.map +1 -0
  205. package/dist/maintenance/self-update.js +81 -0
  206. package/dist/maintenance/self-update.js.map +1 -0
  207. package/dist/mcp/claude-mcp-management.d.ts +28 -0
  208. package/dist/mcp/claude-mcp-management.d.ts.map +1 -0
  209. package/dist/mcp/claude-mcp-management.js +169 -0
  210. package/dist/mcp/claude-mcp-management.js.map +1 -0
  211. package/dist/mcp/claude-mcp-oauth.d.ts +113 -0
  212. package/dist/mcp/claude-mcp-oauth.d.ts.map +1 -0
  213. package/dist/mcp/claude-mcp-oauth.js +1053 -0
  214. package/dist/mcp/claude-mcp-oauth.js.map +1 -0
  215. package/dist/mcp/claude-mcp-tools.d.ts +98 -0
  216. package/dist/mcp/claude-mcp-tools.d.ts.map +1 -0
  217. package/dist/mcp/claude-mcp-tools.js +1246 -0
  218. package/dist/mcp/claude-mcp-tools.js.map +1 -0
  219. package/dist/mcp/praxis-mcp-server.d.ts +35 -0
  220. package/dist/mcp/praxis-mcp-server.d.ts.map +1 -0
  221. package/dist/mcp/praxis-mcp-server.js +520 -0
  222. package/dist/mcp/praxis-mcp-server.js.map +1 -0
  223. package/dist/permissions/claude-auto-classifier.d.ts +20 -0
  224. package/dist/permissions/claude-auto-classifier.d.ts.map +1 -0
  225. package/dist/permissions/claude-auto-classifier.js +140 -0
  226. package/dist/permissions/claude-auto-classifier.js.map +1 -0
  227. package/dist/permissions/claude-permission-resolver.d.ts +30 -0
  228. package/dist/permissions/claude-permission-resolver.d.ts.map +1 -0
  229. package/dist/permissions/claude-permission-resolver.js +334 -0
  230. package/dist/permissions/claude-permission-resolver.js.map +1 -0
  231. package/dist/persistence/claude-job-store.d.ts +68 -0
  232. package/dist/persistence/claude-job-store.d.ts.map +1 -0
  233. package/dist/persistence/claude-job-store.js +311 -0
  234. package/dist/persistence/claude-job-store.js.map +1 -0
  235. package/dist/persistence/claude-scheduled-task-store.d.ts +29 -0
  236. package/dist/persistence/claude-scheduled-task-store.d.ts.map +1 -0
  237. package/dist/persistence/claude-scheduled-task-store.js +163 -0
  238. package/dist/persistence/claude-scheduled-task-store.js.map +1 -0
  239. package/dist/persistence/claude-sidechain-store.d.ts +18 -0
  240. package/dist/persistence/claude-sidechain-store.d.ts.map +1 -0
  241. package/dist/persistence/claude-sidechain-store.js +107 -0
  242. package/dist/persistence/claude-sidechain-store.js.map +1 -0
  243. package/dist/persistence/claude-task-store.d.ts +72 -0
  244. package/dist/persistence/claude-task-store.d.ts.map +1 -0
  245. package/dist/persistence/claude-task-store.js +440 -0
  246. package/dist/persistence/claude-task-store.js.map +1 -0
  247. package/dist/persistence/claude-transcript-store.d.ts +85 -0
  248. package/dist/persistence/claude-transcript-store.d.ts.map +1 -0
  249. package/dist/persistence/claude-transcript-store.js +385 -0
  250. package/dist/persistence/claude-transcript-store.js.map +1 -0
  251. package/dist/persistence/claude-workflow-store.d.ts +41 -0
  252. package/dist/persistence/claude-workflow-store.d.ts.map +1 -0
  253. package/dist/persistence/claude-workflow-store.js +263 -0
  254. package/dist/persistence/claude-workflow-store.js.map +1 -0
  255. package/dist/persistence/in-memory-sidechain-store.d.ts +12 -0
  256. package/dist/persistence/in-memory-sidechain-store.d.ts.map +1 -0
  257. package/dist/persistence/in-memory-sidechain-store.js +30 -0
  258. package/dist/persistence/in-memory-sidechain-store.js.map +1 -0
  259. package/dist/persistence/in-memory-transcript-store.d.ts +15 -0
  260. package/dist/persistence/in-memory-transcript-store.d.ts.map +1 -0
  261. package/dist/persistence/in-memory-transcript-store.js +95 -0
  262. package/dist/persistence/in-memory-transcript-store.js.map +1 -0
  263. package/dist/platform/atomic-write.d.ts +5 -0
  264. package/dist/platform/atomic-write.d.ts.map +1 -0
  265. package/dist/platform/atomic-write.js +25 -0
  266. package/dist/platform/atomic-write.js.map +1 -0
  267. package/dist/platform/bounded-process-runner.d.ts +28 -0
  268. package/dist/platform/bounded-process-runner.d.ts.map +1 -0
  269. package/dist/platform/bounded-process-runner.js +209 -0
  270. package/dist/platform/bounded-process-runner.js.map +1 -0
  271. package/dist/platform/claude-version.d.ts +5 -0
  272. package/dist/platform/claude-version.d.ts.map +1 -0
  273. package/dist/platform/claude-version.js +17 -0
  274. package/dist/platform/claude-version.js.map +1 -0
  275. package/dist/platform/command-shell.d.ts +3 -0
  276. package/dist/platform/command-shell.d.ts.map +1 -0
  277. package/dist/platform/command-shell.js +15 -0
  278. package/dist/platform/command-shell.js.map +1 -0
  279. package/dist/platform/exclusive-file-lease.d.ts +15 -0
  280. package/dist/platform/exclusive-file-lease.d.ts.map +1 -0
  281. package/dist/platform/exclusive-file-lease.js +265 -0
  282. package/dist/platform/exclusive-file-lease.js.map +1 -0
  283. package/dist/platform/sensitive-data.d.ts +8 -0
  284. package/dist/platform/sensitive-data.d.ts.map +1 -0
  285. package/dist/platform/sensitive-data.js +138 -0
  286. package/dist/platform/sensitive-data.js.map +1 -0
  287. package/dist/platform/tmux-worktree.d.ts +30 -0
  288. package/dist/platform/tmux-worktree.d.ts.map +1 -0
  289. package/dist/platform/tmux-worktree.js +138 -0
  290. package/dist/platform/tmux-worktree.js.map +1 -0
  291. package/dist/plugins/claude-plugin-eval-discovery.d.ts +20 -0
  292. package/dist/plugins/claude-plugin-eval-discovery.d.ts.map +1 -0
  293. package/dist/plugins/claude-plugin-eval-discovery.js +128 -0
  294. package/dist/plugins/claude-plugin-eval-discovery.js.map +1 -0
  295. package/dist/plugins/claude-plugin-eval-graders.d.ts +49 -0
  296. package/dist/plugins/claude-plugin-eval-graders.d.ts.map +1 -0
  297. package/dist/plugins/claude-plugin-eval-graders.js +137 -0
  298. package/dist/plugins/claude-plugin-eval-graders.js.map +1 -0
  299. package/dist/plugins/claude-plugin-eval-runner.d.ts +50 -0
  300. package/dist/plugins/claude-plugin-eval-runner.d.ts.map +1 -0
  301. package/dist/plugins/claude-plugin-eval-runner.js +189 -0
  302. package/dist/plugins/claude-plugin-eval-runner.js.map +1 -0
  303. package/dist/plugins/claude-plugin-eval-schema.d.ts +80 -0
  304. package/dist/plugins/claude-plugin-eval-schema.d.ts.map +1 -0
  305. package/dist/plugins/claude-plugin-eval-schema.js +436 -0
  306. package/dist/plugins/claude-plugin-eval-schema.js.map +1 -0
  307. package/dist/plugins/claude-plugin-eval.d.ts +66 -0
  308. package/dist/plugins/claude-plugin-eval.d.ts.map +1 -0
  309. package/dist/plugins/claude-plugin-eval.js +438 -0
  310. package/dist/plugins/claude-plugin-eval.js.map +1 -0
  311. package/dist/plugins/claude-plugin-maintenance.d.ts +39 -0
  312. package/dist/plugins/claude-plugin-maintenance.d.ts.map +1 -0
  313. package/dist/plugins/claude-plugin-maintenance.js +336 -0
  314. package/dist/plugins/claude-plugin-maintenance.js.map +1 -0
  315. package/dist/plugins/claude-plugin-marketplace.d.ts +97 -0
  316. package/dist/plugins/claude-plugin-marketplace.d.ts.map +1 -0
  317. package/dist/plugins/claude-plugin-marketplace.js +1433 -0
  318. package/dist/plugins/claude-plugin-marketplace.js.map +1 -0
  319. package/dist/plugins/claude-plugin-mcpb.d.ts +71 -0
  320. package/dist/plugins/claude-plugin-mcpb.d.ts.map +1 -0
  321. package/dist/plugins/claude-plugin-mcpb.js +885 -0
  322. package/dist/plugins/claude-plugin-mcpb.js.map +1 -0
  323. package/dist/plugins/claude-plugin-runtime.d.ts +120 -0
  324. package/dist/plugins/claude-plugin-runtime.d.ts.map +1 -0
  325. package/dist/plugins/claude-plugin-runtime.js +1263 -0
  326. package/dist/plugins/claude-plugin-runtime.js.map +1 -0
  327. package/dist/providers/anthropic-compatible.d.ts +35 -0
  328. package/dist/providers/anthropic-compatible.d.ts.map +1 -0
  329. package/dist/providers/anthropic-compatible.js +733 -0
  330. package/dist/providers/anthropic-compatible.js.map +1 -0
  331. package/dist/providers/environment.d.ts +14 -0
  332. package/dist/providers/environment.d.ts.map +1 -0
  333. package/dist/providers/environment.js +67 -0
  334. package/dist/providers/environment.js.map +1 -0
  335. package/dist/providers/fallback-provider.d.ts +17 -0
  336. package/dist/providers/fallback-provider.d.ts.map +1 -0
  337. package/dist/providers/fallback-provider.js +70 -0
  338. package/dist/providers/fallback-provider.js.map +1 -0
  339. package/dist/providers/openai-compatible.d.ts +30 -0
  340. package/dist/providers/openai-compatible.d.ts.map +1 -0
  341. package/dist/providers/openai-compatible.js +409 -0
  342. package/dist/providers/openai-compatible.js.map +1 -0
  343. package/dist/tools/claude-interactive-tools.d.ts +58 -0
  344. package/dist/tools/claude-interactive-tools.d.ts.map +1 -0
  345. package/dist/tools/claude-interactive-tools.js +390 -0
  346. package/dist/tools/claude-interactive-tools.js.map +1 -0
  347. package/dist/tools/claude-lsp-formatters.d.ts +2 -0
  348. package/dist/tools/claude-lsp-formatters.d.ts.map +1 -0
  349. package/dist/tools/claude-lsp-formatters.js +291 -0
  350. package/dist/tools/claude-lsp-formatters.js.map +1 -0
  351. package/dist/tools/claude-lsp-tool.d.ts +22 -0
  352. package/dist/tools/claude-lsp-tool.d.ts.map +1 -0
  353. package/dist/tools/claude-lsp-tool.js +744 -0
  354. package/dist/tools/claude-lsp-tool.js.map +1 -0
  355. package/dist/tools/claude-scheduled-tools.d.ts +20 -0
  356. package/dist/tools/claude-scheduled-tools.d.ts.map +1 -0
  357. package/dist/tools/claude-scheduled-tools.js +344 -0
  358. package/dist/tools/claude-scheduled-tools.js.map +1 -0
  359. package/dist/tools/claude-task-tools.d.ts +30 -0
  360. package/dist/tools/claude-task-tools.d.ts.map +1 -0
  361. package/dist/tools/claude-task-tools.js +503 -0
  362. package/dist/tools/claude-task-tools.js.map +1 -0
  363. package/dist/tools/claude-user-message.d.ts +16 -0
  364. package/dist/tools/claude-user-message.d.ts.map +1 -0
  365. package/dist/tools/claude-user-message.js +98 -0
  366. package/dist/tools/claude-user-message.js.map +1 -0
  367. package/dist/tools/claude-workflow-tools.d.ts +27 -0
  368. package/dist/tools/claude-workflow-tools.d.ts.map +1 -0
  369. package/dist/tools/claude-workflow-tools.js +257 -0
  370. package/dist/tools/claude-workflow-tools.js.map +1 -0
  371. package/dist/tools/claude-worktree-tools.d.ts +17 -0
  372. package/dist/tools/claude-worktree-tools.d.ts.map +1 -0
  373. package/dist/tools/claude-worktree-tools.js +135 -0
  374. package/dist/tools/claude-worktree-tools.js.map +1 -0
  375. package/dist/tools/filtered-tool-registry.d.ts +16 -0
  376. package/dist/tools/filtered-tool-registry.d.ts.map +1 -0
  377. package/dist/tools/filtered-tool-registry.js +51 -0
  378. package/dist/tools/filtered-tool-registry.js.map +1 -0
  379. package/dist/tools/glob.d.ts +9 -0
  380. package/dist/tools/glob.d.ts.map +1 -0
  381. package/dist/tools/glob.js +88 -0
  382. package/dist/tools/glob.js.map +1 -0
  383. package/dist/tools/local-tools.d.ts +47 -0
  384. package/dist/tools/local-tools.d.ts.map +1 -0
  385. package/dist/tools/local-tools.js +1082 -0
  386. package/dist/tools/local-tools.js.map +1 -0
  387. package/dist/tools/notebook.d.ts +22 -0
  388. package/dist/tools/notebook.d.ts.map +1 -0
  389. package/dist/tools/notebook.js +154 -0
  390. package/dist/tools/notebook.js.map +1 -0
  391. package/dist/tools/web.d.ts +48 -0
  392. package/dist/tools/web.d.ts.map +1 -0
  393. package/dist/tools/web.js +726 -0
  394. package/dist/tools/web.js.map +1 -0
  395. package/package.json +145 -0
package/dist/cli.js ADDED
@@ -0,0 +1,3901 @@
1
+ #!/usr/bin/env node
2
+ import { realpathSync } from 'node:fs';
3
+ import { access, copyFile, mkdir, readFile } from 'node:fs/promises';
4
+ import { homedir } from 'node:os';
5
+ import { dirname, join, resolve } from 'node:path';
6
+ import { fileURLToPath, pathToFileURL } from 'node:url';
7
+ import { randomUUID } from 'node:crypto';
8
+ import { ClaudeSessionService, } from './application/session-service.js';
9
+ import { ClaudeConditionalRuleResolver, ClaudeContextAssembler, } from './compatibility/claude/context.js';
10
+ import { loadClaudeDynamicContext } from './compatibility/claude/dynamic-context.js';
11
+ import { createClaudePrSessionFilter, filterClaudePrLinkedSessions, } from './compatibility/claude/pr-links.js';
12
+ import { isClaudeSessionId, resolveClaudePaths, } from './compatibility/claude/paths.js';
13
+ import { loadClaudeContextResources, loadClaudeSettings, loadClaudeSharedResources, resolveClaudeProjectMemoryDirectory, } from './compatibility/claude/shared-resources.js';
14
+ import { AgentRunCancelledError, } from './core/runtime.js';
15
+ import { runInteractive as renderInteractive, } from './cli/interactive.js';
16
+ import { runAgentsDashboard as renderAgentsDashboard } from './cli/agents-dashboard.js';
17
+ import { DEFAULT_CLI_CONTROLS, resolveCliControls } from './cli/controls.js';
18
+ import { createCliDebugSink } from './cli/debug.js';
19
+ import { ClaudePermissionResolver, } from './permissions/claude-permission-resolver.js';
20
+ import { createClaudeModelAutoClassifier, defaultClaudeAutoModeConfig, loadClaudeAutoModeConfig, } from './permissions/claude-auto-classifier.js';
21
+ import { ClaudeExtensionPermissionResolver, ClaudeExtensionToolRegistry, } from './extensions/claude-extension-tools.js';
22
+ import { ClaudeExtensionCatalog } from './extensions/claude-extensions.js';
23
+ import { ClaudeHookRunner } from './hooks/claude-hooks.js';
24
+ import { ClaudeMcpToolRegistry } from './mcp/claude-mcp-tools.js';
25
+ import { ClaudeMcpManagement, mcpScope, } from './mcp/claude-mcp-management.js';
26
+ import { authenticateMcpServer, ClaudeMcpOAuthStore, mcpOAuthServerIdentity, readMcpClientSecret, } from './mcp/claude-mcp-oauth.js';
27
+ import { servePraxisMcpStdio } from './mcp/praxis-mcp-server.js';
28
+ import { detectInstalledClaudeVersion } from './platform/claude-version.js';
29
+ import { redactSensitiveText, sensitiveEnvironmentValues, } from './platform/sensitive-data.js';
30
+ import { AnthropicCompatibleProvider } from './providers/anthropic-compatible.js';
31
+ import { FallbackModelProvider } from './providers/fallback-provider.js';
32
+ import { OpenAICompatibleProvider } from './providers/openai-compatible.js';
33
+ import { parseContextEnvironment, parseProviderEnvironment, } from './providers/environment.js';
34
+ import { ModelPricingRegistry, usageCostUsd } from './core/usage.js';
35
+ import { LocalToolRegistry } from './tools/local-tools.js';
36
+ import { ClaudeLspToolManager } from './tools/claude-lsp-tool.js';
37
+ import { ClaudeInteractiveToolManager, } from './tools/claude-interactive-tools.js';
38
+ import { claudeBackgroundTaskParent } from './application/background-bash-manager.js';
39
+ import { runTopLevelAgentWorker, TopLevelAgentManager, } from './application/top-level-agent-manager.js';
40
+ import { FilteredToolRegistry } from './tools/filtered-tool-registry.js';
41
+ import { WebToolRegistry } from './tools/web.js';
42
+ import { WorkspaceContext } from './application/session-worktree.js';
43
+ import { launchTmuxWorktree } from './platform/tmux-worktree.js';
44
+ import { createErrorResult, createSuccessResult, parseCliInvocation, readStreamJsonMessages, StreamJsonOutput, } from './cli/protocol.js';
45
+ import { describeClaudePlugin, initClaudePlugin, installClaudePlugin, loadClaudePlugins, readPluginRegistry, setClaudePluginEnabled, uninstallClaudePlugin, updateClaudePlugin, validateClaudePlugin, } from './plugins/claude-plugin-runtime.js';
46
+ import { addClaudeMarketplace, disableAllNativePlugins, installClaudeMarketplacePlugin, listClaudeMarketplaceAvailablePlugins, listNativePluginRecords, readClaudeKnownMarketplaces, removeClaudeMarketplace, setNativePluginEnabled, saveClaudePluginConfig, uninstallNativePlugin, updateClaudeMarketplace, updateNativePlugin, validateClaudeMarketplace, } from './plugins/claude-plugin-marketplace.js';
47
+ import { executeClaudePluginEvalCommand, PLUGIN_EVAL_HELP, } from './plugins/claude-plugin-eval.js';
48
+ import { CLAUDE_PLUGIN_PRUNE_HELP, CLAUDE_PLUGIN_TAG_HELP, executeClaudePluginPrune, planClaudePluginPrune, tagClaudePlugin, } from './plugins/claude-plugin-maintenance.js';
49
+ import { formatDoctorReport, runDoctor } from './maintenance/doctor.js';
50
+ import { runSelfUpdate, } from './maintenance/self-update.js';
51
+ import { executeClaudeProjectPurge, planClaudeProjectPurge, } from './application/claude-project-purge.js';
52
+ const VERSION = '0.1.0';
53
+ function fileResourceBaseUrl(environment, providerEnvironment) {
54
+ return (environment.PRAXIS_FILES_BASE_URL ??
55
+ providerEnvironment?.baseUrl ??
56
+ environment.PRAXIS_BASE_URL ??
57
+ (environment.PRAXIS_PROVIDER === 'anthropic'
58
+ ? 'https://api.anthropic.com/v1'
59
+ : 'https://api.openai.com/v1'));
60
+ }
61
+ function fileResourceHeaders(environment, providerEnvironment, credential) {
62
+ const anthropic = providerEnvironment?.provider === 'anthropic';
63
+ const bearer = Boolean(environment.PRAXIS_FILES_BEARER_TOKEN);
64
+ const authentication = bearer
65
+ ? { Authorization: `Bearer ${credential}` }
66
+ : anthropic
67
+ ? { 'x-api-key': credential }
68
+ : { Authorization: `Bearer ${credential}` };
69
+ return {
70
+ ...authentication,
71
+ ...(anthropic
72
+ ? {
73
+ 'anthropic-version': '2023-06-01',
74
+ 'anthropic-beta': `files-api-2025-04-14${bearer ? ',oauth-2025-04-20' : ''}`,
75
+ }
76
+ : {}),
77
+ };
78
+ }
79
+ function createProviderForModel(apiKey, providerEnvironment, context, controls) {
80
+ return (selectedModel) => {
81
+ const providerOptions = {
82
+ apiKey,
83
+ model: selectedModel,
84
+ baseUrl: providerEnvironment.baseUrl,
85
+ ...('contextWindowTokens' in context
86
+ ? { contextWindowTokens: context.contextWindowTokens }
87
+ : {}),
88
+ };
89
+ return providerEnvironment.provider === 'anthropic'
90
+ ? new AnthropicCompatibleProvider({
91
+ ...providerOptions,
92
+ thinking: {
93
+ mode: controls.thinking ?? 'enabled',
94
+ ...(controls.maxThinkingTokens === undefined
95
+ ? {}
96
+ : { maxTokens: controls.maxThinkingTokens }),
97
+ },
98
+ ...('maxOutputTokens' in providerEnvironment
99
+ ? { maxOutputTokens: providerEnvironment.maxOutputTokens }
100
+ : {}),
101
+ ...('anthropicVersion' in providerEnvironment
102
+ ? { anthropicVersion: providerEnvironment.anthropicVersion }
103
+ : {}),
104
+ ...('webSearch' in providerEnvironment
105
+ ? { webSearch: providerEnvironment.webSearch }
106
+ : {}),
107
+ })
108
+ : new OpenAICompatibleProvider({
109
+ ...providerOptions,
110
+ ...(controls.thinking === undefined &&
111
+ controls.maxThinkingTokens === undefined
112
+ ? {}
113
+ : {
114
+ thinking: {
115
+ mode: controls.thinking ?? 'enabled',
116
+ ...(controls.maxThinkingTokens === undefined
117
+ ? {}
118
+ : { maxTokens: controls.maxThinkingTokens }),
119
+ },
120
+ }),
121
+ });
122
+ };
123
+ }
124
+ const HELP = `Praxis — local-first general agent
125
+
126
+ Usage:
127
+ praxis
128
+ praxis [options] [prompt]
129
+ praxis -p [options] [prompt]
130
+ praxis --resume [session-id|title|search] [options] [prompt]
131
+ praxis run [options] <prompt>
132
+ praxis resume [options] <session-id> <prompt>
133
+ praxis fork [--json] <session-id>
134
+ praxis sessions [--json]
135
+ praxis inspect [--json] <session-id>
136
+ praxis export [--json] <session-id>
137
+ praxis --bg [options] <prompt>
138
+ praxis agents [--json] [--all] [--cwd <path>]
139
+ praxis attach <agent-id>
140
+ praxis logs <agent-id>
141
+ praxis stop <agent-id>
142
+ praxis mcp <list|get|add|add-json|remove|reset-project-choices|login|logout|serve> ...
143
+ praxis auto-mode <config|defaults|critique>
144
+ praxis plugin|plugins <details|list|install|uninstall|enable|disable|update|init|prune|tag|validate|marketplace> ...
145
+ praxis doctor [--json]
146
+ praxis install [--force] [stable|latest|version]
147
+ praxis update|upgrade
148
+ praxis project purge [options] [path]
149
+
150
+ Options:
151
+ -p, --print Print response and exit
152
+ --bg, --background Run as a persistent background agent
153
+ -r, --resume [session] Resume by ID/title, or select interactively
154
+ --from-pr [number-or-url] Resume a session linked to a GitHub PR
155
+ -c, --continue Continue latest session in this directory
156
+ --fork-session Fork when resuming or continuing
157
+ --resume-session-at <message-id> Resume at an active conversation message
158
+ --session-id <uuid> Use an explicit ID for a new session
159
+ -n, --name <name> Set session display name
160
+ --model <model> Select model for this session
161
+ --effort <level> low, medium, high, xhigh, or max
162
+ --thinking <mode> enabled, adaptive, or disabled
163
+ --max-thinking-tokens <tokens> Cap extended-thinking tokens
164
+ --fallback-model <models> Comma-separated print-mode fallbacks
165
+ --json-schema <schema> Print-mode JSON Schema for structured output
166
+ --max-budget-usd <amount> Maximum print-mode API spend
167
+ --prefill <text> Accepted as a Claude 2.1.208-compatible no-op
168
+ --prompt-suggestions [value] Enable prompt suggestions. In print/SDK mode, emits a
169
+ prompt_suggestion message after each turn with a predicted
170
+ next user prompt (choices: "true", "false", "1", "0", "yes",
171
+ "no", "on", "off", preset: "true")
172
+ --scope <scope> MCP scope: local, project, or user
173
+ --no-browser Print MCP OAuth URL without opening a browser
174
+ --no-session-persistence Keep print-mode session in memory only
175
+ --agent <name> Select a shared agent definition
176
+ --max-turns <turns> Limit print-mode model round trips
177
+ --betas <betas...> Include Anthropic beta headers
178
+ --brief Enable SendUserMessage communication
179
+ --ax-screen-reader Render flat screen-reader output
180
+ -d, --debug [filter] Enable debug mode with optional category filtering
181
+ (e.g., "api,hooks" or "!1p,!file")
182
+ --debug-file <path> Write runtime diagnostics to a file
183
+ --file <specs...> Download file resources at startup (file_id:relative_path)
184
+ --agents <json> Define inline agents for this session
185
+ --mcp-config <configs...> Load MCP server JSON files or objects
186
+ --strict-mcp-config Ignore configured MCP servers
187
+ --disable-slash-commands Disable skills and slash commands
188
+ --settings <file-or-json> Load additional settings
189
+ --setting-sources <sources> user, project, local, or an empty list
190
+ --safe-mode Disable shared customizations
191
+ --bare Use only explicitly supplied context
192
+ --system-prompt <prompt> Set system prompt
193
+ --append-system-prompt <prompt> Append system prompt
194
+ --exclude-dynamic-system-prompt-sections
195
+ Move cwd, environment, memory path, and git status into first user message
196
+ (default system prompt only; ignored with --system-prompt)
197
+ --add-dir <directories...> Allow access to additional directories
198
+ --plugin-dir <path> Load a local plugin directory or .zip for this session (repeatable)
199
+ --plugin-url <url> Load a plugin .zip URL for this session (repeatable)
200
+ --tools <tools...> Select available tools; empty disables all
201
+ --allowedTools, --allowed-tools <tools...>
202
+ Add permission allow rules
203
+ --disallowedTools, --disallowed-tools <tools...>
204
+ Add permission deny rules
205
+ --permission-mode <mode> Set permission behavior
206
+ --dangerously-skip-permissions Bypass checks except explicit deny rules
207
+ --allow-dangerously-skip-permissions
208
+ Allow bypass mode without enabling it
209
+ --input-format <format> text (default) or stream-json
210
+ --output-format <format> text (default), json, or stream-json
211
+ --include-partial-messages Emit stream_event records
212
+ --include-hook-events Emit hook_started/progress/response records
213
+ --replay-user-messages Echo stream-json user records
214
+ --retry-interrupted-tools Approve prepared interrupted tools
215
+ --verbose Required for stream-json output
216
+ --json Legacy Praxis runtime NDJSON output
217
+ -h, --help Show help
218
+ -v, --version Show version
219
+ -w, --worktree [name] Start in an isolated Git worktree
220
+ --tmux[=classic] Launch worktree in an iTerm2 native pane when available; classic forces tmux
221
+
222
+ Provider environment:
223
+ PRAXIS_PROVIDER=openai|anthropic, PRAXIS_API_KEY, PRAXIS_MODEL
224
+ PRAXIS_BASE_URL, PRAXIS_MAX_OUTPUT_TOKENS, PRAXIS_ANTHROPIC_VERSION
225
+ PRAXIS_ANTHROPIC_WEB_SEARCH=true|false
226
+ PRAXIS_CONTEXT_WINDOW_TOKENS, PRAXIS_CONTEXT_RESERVE_TOKENS
227
+ `;
228
+ const AGENTS_HELP = `Usage: praxis agents [options]
229
+
230
+ Manage background agents
231
+
232
+ Options:
233
+ --add-dir <directory> Additional directory to allow tool
234
+ access to in dispatched sessions
235
+ (repeatable)
236
+ --agent <agent> Default agent for sessions dispatched
237
+ from agent view. Overrides the 'agent'
238
+ setting.
239
+ --all With --json: include completed sessions
240
+ (the full agent view list)
241
+ --allow-dangerously-skip-permissions Make bypass-permissions mode available
242
+ to dispatched sessions without
243
+ defaulting to it
244
+ --cwd <path> Show only background sessions started
245
+ under <path>
246
+ --dangerously-skip-permissions Alias for --permission-mode
247
+ bypassPermissions
248
+ --effort <level> Default effort level for sessions
249
+ dispatched from agent view
250
+ -h, --help Display help for command
251
+ --json Print active sessions as a JSON array
252
+ and exit (for scripting; does not
253
+ require a TTY)
254
+ --mcp-config <config> MCP server configuration to apply to
255
+ dispatched sessions (repeatable)
256
+ --model <model> Default model for sessions dispatched
257
+ from agent view
258
+ --permission-mode <mode> Default permission mode for sessions
259
+ dispatched from agent view
260
+ --plugin-dir <path> Load plugins from specified directory
261
+ for the agent view and dispatched
262
+ sessions (repeatable)
263
+ --setting-sources <sources> Comma-separated list of setting sources
264
+ to load (user, project, local).
265
+ --settings <file-or-json> Settings file or JSON string to apply to
266
+ the agent view and dispatched sessions
267
+ --strict-mcp-config Only use MCP servers from --mcp-config
268
+ in dispatched sessions
269
+ `;
270
+ const MCP_HELP = `Usage: praxis mcp [options] [command]
271
+
272
+ Configure and manage Model Context Protocol servers.
273
+
274
+ Options:
275
+ -h, --help Display help for command
276
+
277
+ Commands:
278
+ list [options] List configured MCP servers
279
+ get [options] <name> Get an MCP server configuration
280
+ add [options] <name> <commandOrUrl> [args...] Add an MCP server
281
+ add-json [options] <name> <json> Add an MCP server from JSON
282
+ remove [options] <name> Remove an MCP server
283
+ reset-project-choices Reset project MCP approvals
284
+ login [options] <name> Authenticate with an MCP server
285
+ logout [options] <name> Clear MCP OAuth credentials
286
+ serve [options] Start Praxis MCP server over stdio
287
+ `;
288
+ const MCP_LIST_HELP = `Usage: praxis mcp list [options]
289
+
290
+ List configured MCP servers. With --scope, list only servers in that scope.
291
+
292
+ Options:
293
+ --scope <scope> Configuration scope: local, project, or user
294
+ --json Print a machine-readable mcp-list result
295
+ -h, --help Display help for command
296
+ `;
297
+ const MCP_GET_HELP = `Usage: praxis mcp get [options] <name>
298
+
299
+ Print configuration for one MCP server.
300
+
301
+ Options:
302
+ --scope <scope> Read from local, project, or user scope
303
+ --json Print a machine-readable mcp-server result
304
+ -h, --help Display help for command
305
+ `;
306
+ const MCP_ADD_HELP = `Usage: praxis mcp add [options] <name> <commandOrUrl> [args...]
307
+
308
+ Add an MCP server to Praxis.
309
+
310
+ Examples:
311
+ # Add HTTP server:
312
+ praxis mcp add --transport http sentry https://mcp.sentry.dev/mcp
313
+
314
+ # Add HTTP server with headers:
315
+ praxis mcp add --transport http corridor https://app.corridor.dev/api/mcp --header "Authorization: Bearer ..."
316
+
317
+ # Add stdio server with environment variables:
318
+ praxis mcp add my-server -e API_KEY=xxx -- npx my-mcp-server
319
+
320
+ # Add stdio server with subprocess flags:
321
+ praxis mcp add my-server -- my-command --some-flag arg1
322
+
323
+ Options:
324
+ --callback-port <port> Fixed port for OAuth callback (for servers requiring pre-registered redirect URIs)
325
+ --client-id <clientId> OAuth client ID for HTTP/SSE servers
326
+ --client-secret Prompt for OAuth client secret (or set MCP_CLIENT_SECRET env var)
327
+ -e, --env <env...> Set environment variables (e.g. -e KEY=value)
328
+ -H, --header <header...> Set WebSocket headers (e.g. -H "X-Api-Key: abc123" -H "X-Custom: value")
329
+ -h, --help Display help for command
330
+ -s, --scope <scope> Configuration scope (local, user, or project) (default: "local")
331
+ -t, --transport <transport> Transport type (stdio, sse, http). Defaults to stdio if not specified.
332
+ `;
333
+ const MCP_ADD_JSON_HELP = `Usage: praxis mcp add-json [options] <name> <json>
334
+
335
+ Add an MCP server from a JSON configuration object. Supported configurations
336
+ include stdio and HTTP server records accepted by Praxis.
337
+
338
+ Options:
339
+ --client-secret Prompt for OAuth client secret (or set MCP_CLIENT_SECRET env var)
340
+ -s, --scope <scope> Configuration scope: local, project, or user (default: local)
341
+ --json Print a machine-readable mcp-added result
342
+ -h, --help Display help for command
343
+ `;
344
+ const MCP_REMOVE_HELP = `Usage: praxis mcp remove [options] <name>
345
+
346
+ Remove an MCP server. Without --scope, Praxis resolves the configured server
347
+ across available scopes.
348
+
349
+ Options:
350
+ -s, --scope <scope> Remove from local, project, or user scope
351
+ --json Print a machine-readable mcp-removed result
352
+ -h, --help Display help for command
353
+ `;
354
+ const MCP_RESET_PROJECT_CHOICES_HELP = `Usage: praxis mcp reset-project-choices [options]
355
+
356
+ Reset approved and rejected project-scoped MCP server choices.
357
+
358
+ Options:
359
+ --json Print a machine-readable confirmation
360
+ -h, --help Display help for command
361
+ `;
362
+ const MCP_LOGIN_HELP = `Usage: praxis mcp login [options] <name>
363
+
364
+ Authenticate with an OAuth-enabled MCP server.
365
+
366
+ Options:
367
+ --no-browser Print authorization URL instead of opening a browser
368
+ --scope <scope> Read server from local, project, or user scope
369
+ -h, --help Display help for command
370
+ `;
371
+ const MCP_LOGOUT_HELP = `Usage: praxis mcp logout [options] <name>
372
+
373
+ Clear stored OAuth credentials for an MCP server.
374
+
375
+ Options:
376
+ --scope <scope> Read server from local, project, or user scope
377
+ -h, --help Display help for command
378
+ `;
379
+ const MCP_SERVE_HELP = `Usage: praxis mcp serve [options]
380
+
381
+ Start the Praxis MCP server over stdio.
382
+
383
+ Options:
384
+ -d, --debug Enable MCP server debug logging
385
+ --verbose Enable verbose MCP server output
386
+ -h, --help Display help for command
387
+ `;
388
+ const MCP_ACTION_HELP = {
389
+ list: MCP_LIST_HELP,
390
+ get: MCP_GET_HELP,
391
+ add: MCP_ADD_HELP,
392
+ 'add-json': MCP_ADD_JSON_HELP,
393
+ remove: MCP_REMOVE_HELP,
394
+ 'reset-project-choices': MCP_RESET_PROJECT_CHOICES_HELP,
395
+ login: MCP_LOGIN_HELP,
396
+ logout: MCP_LOGOUT_HELP,
397
+ serve: MCP_SERVE_HELP,
398
+ };
399
+ const PLUGIN_HELP = `Usage: praxis plugin|plugins [options] [command]
400
+
401
+ Manage Praxis-compatible Claude plugins and marketplaces.
402
+
403
+ Options:
404
+ --json Print machine-readable command output when supported
405
+ --scope <scope> Plugin configuration scope: local, project, or user
406
+ -h, --help Display help for command
407
+
408
+ Commands:
409
+ details <name> Show component inventory and projected token cost
410
+ list [options] List installed or available plugins
411
+ install|i [options] <source> Install a local plugin, URL, or plugin@marketplace
412
+ uninstall|remove [options] <id> Uninstall a plugin
413
+ enable [options] <id> Enable a plugin
414
+ disable [options] [id] Disable one plugin, or all with --all
415
+ update [options] <id> Update a plugin
416
+ init|new [options] <name> Scaffold ~/.claude/skills/<name>
417
+ prune|autoremove [options] Remove unused auto-installed dependencies
418
+ tag [options] [path] Create a validated plugin release tag
419
+ validate [options] <path> Validate a plugin or marketplace manifest
420
+ eval [options] [target] Evaluate plugin behavior
421
+ marketplace [command] Manage configured marketplaces
422
+ `;
423
+ const PLUGIN_DETAILS_HELP = `Usage: praxis plugin details <name>
424
+
425
+ Show a plugin's component inventory and projected token cost.
426
+
427
+ Options:
428
+ -h, --help Display help for command
429
+ `;
430
+ const PLUGIN_LIST_HELP = `Usage: praxis plugin list [options]
431
+
432
+ List installed native and local plugins with enabled and validation status.
433
+
434
+ Options:
435
+ --available List marketplace plugins that are not installed
436
+ --json Print plugin records as JSON (default output is JSON as well)
437
+ -h, --help Display help for command
438
+ `;
439
+ const PLUGIN_INSTALL_HELP = `Usage: praxis plugin install [options] <path-or-url-or-plugin@marketplace>
440
+
441
+ Install a plugin from a local directory, URL, or configured marketplace. A
442
+ marketplace plugin identifier uses the form plugin@marketplace.
443
+
444
+ Options:
445
+ --config <key=value> Set userConfig; use server.key=value for MCPB (repeatable)
446
+ -s, --scope <scope> Install native marketplace plugin at local, project, or user scope (default: user)
447
+ --json Print a machine-readable plugin-installed result
448
+ -h, --help Display help for command
449
+ `;
450
+ const PLUGIN_UNINSTALL_HELP = `Usage: praxis plugin uninstall [options] <name-or-plugin@marketplace>
451
+
452
+ Uninstall a local plugin or a native marketplace plugin.
453
+
454
+ Options:
455
+ --keep-data Preserve the plugin's persistent data directory
456
+ --prune Also remove unused auto-installed dependencies
457
+ -s, --scope <scope> Select native plugin scope: local, project, or user
458
+ -y, --yes Skip the --prune confirmation prompt
459
+ --json Print a machine-readable plugin-uninstalled result
460
+ -h, --help Display help for command
461
+ `;
462
+ const PLUGIN_ENABLE_HELP = `Usage: praxis plugin enable [options] <name-or-plugin@marketplace>
463
+
464
+ Enable a disabled local plugin or native marketplace plugin.
465
+
466
+ Options:
467
+ -s, --scope <scope> Select native plugin scope: local, project, or user
468
+ --json Print a machine-readable plugin-enabled result
469
+ -h, --help Display help for command
470
+ `;
471
+ const PLUGIN_DISABLE_HELP = `Usage: praxis plugin disable [options] [name-or-plugin@marketplace]
472
+
473
+ Disable an enabled local plugin or native marketplace plugin.
474
+
475
+ Options:
476
+ -a, --all Disable all enabled plugins
477
+ -s, --scope <scope> Select native plugin scope: local, project, or user
478
+ --json Print a machine-readable plugin-disabled result
479
+ -h, --help Display help for command
480
+ `;
481
+ const PLUGIN_UPDATE_HELP = `Usage: praxis plugin update [options] <name-or-plugin@marketplace>
482
+
483
+ Update a local plugin or native marketplace plugin from its configured source.
484
+
485
+ Options:
486
+ -s, --scope <scope> Select native plugin scope: local, project, or user
487
+ --json Print a machine-readable plugin-updated result
488
+ -h, --help Display help for command
489
+ `;
490
+ const PLUGIN_INIT_HELP = `Usage: praxis plugin init|new [options] <name>
491
+
492
+ Scaffold a plugin at ~/.claude/skills/<name>. The legacy
493
+ plugin init <directory> <name> form remains available without native options.
494
+
495
+ Options:
496
+ --author <name> Author name (default: git config user.name)
497
+ --author-email <email> Author email (default: git config user.email)
498
+ --description <text> Manifest description
499
+ -f, --force Overwrite an existing .claude-plugin at the target
500
+ --with <components...> Also scaffold: skills, agents, hooks, mcp, lsp, output-style, channel
501
+ -h, --help Display help for command
502
+ `;
503
+ const PLUGIN_VALIDATE_HELP = `Usage: praxis plugin validate <directory>
504
+
505
+ Validate a plugin manifest and report its parsed metadata.
506
+
507
+ Options:
508
+ --strict Treat validation warnings as errors
509
+ --json Print a machine-readable plugin-valid result
510
+ -h, --help Display help for command
511
+ `;
512
+ const PLUGIN_ACTION_HELP = {
513
+ details: PLUGIN_DETAILS_HELP,
514
+ list: PLUGIN_LIST_HELP,
515
+ install: PLUGIN_INSTALL_HELP,
516
+ i: PLUGIN_INSTALL_HELP,
517
+ uninstall: PLUGIN_UNINSTALL_HELP,
518
+ remove: PLUGIN_UNINSTALL_HELP,
519
+ enable: PLUGIN_ENABLE_HELP,
520
+ disable: PLUGIN_DISABLE_HELP,
521
+ update: PLUGIN_UPDATE_HELP,
522
+ init: PLUGIN_INIT_HELP,
523
+ new: PLUGIN_INIT_HELP,
524
+ prune: CLAUDE_PLUGIN_PRUNE_HELP,
525
+ autoremove: CLAUDE_PLUGIN_PRUNE_HELP,
526
+ tag: CLAUDE_PLUGIN_TAG_HELP,
527
+ validate: PLUGIN_VALIDATE_HELP,
528
+ eval: PLUGIN_EVAL_HELP,
529
+ };
530
+ const PLUGIN_MARKETPLACE_HELP = `Usage: praxis plugin marketplace [options] [command]
531
+
532
+ Manage configured plugin marketplaces.
533
+
534
+ Options:
535
+ --scope <scope> Scope for marketplace additions: local, project, or user (default: user)
536
+ --json Print machine-readable command output when supported
537
+ -h, --help Display help for command
538
+
539
+ Commands:
540
+ list List configured marketplaces
541
+ add <source> Add marketplace from a local path or URL
542
+ remove|rm <name> Remove a configured marketplace
543
+ update [name] Update one marketplace, or all when name is omitted
544
+ `;
545
+ const PLUGIN_MARKETPLACE_LIST_HELP = `Usage: praxis plugin marketplace list [options]
546
+
547
+ List configured marketplaces.
548
+
549
+ Options:
550
+ --json Print marketplace records as JSON (default output is JSON as well)
551
+ -h, --help Display help for command
552
+ `;
553
+ const PLUGIN_MARKETPLACE_ADD_HELP = `Usage: praxis plugin marketplace add [options] <source>
554
+
555
+ Add a marketplace from a local path, URL, or supported repository source.
556
+
557
+ Options:
558
+ --scope <scope> Configuration scope: local, project, or user (default: user)
559
+ --sparse <paths...> Limit a Git checkout to specific directories
560
+ --json Print a machine-readable plugin-marketplace-added result
561
+ -h, --help Display help for command
562
+ `;
563
+ const PLUGIN_MARKETPLACE_REMOVE_HELP = `Usage: praxis plugin marketplace remove [options] <name>
564
+
565
+ Remove a configured marketplace.
566
+
567
+ Options:
568
+ --scope <scope> Select marketplace scope: local, project, or user
569
+ --json Print a machine-readable plugin-marketplace-removed result
570
+ -h, --help Display help for command
571
+ `;
572
+ const PLUGIN_MARKETPLACE_UPDATE_HELP = `Usage: praxis plugin marketplace update [options] [name]
573
+
574
+ Update one marketplace, or all configured marketplaces when name is omitted.
575
+
576
+ Options:
577
+ --json Print a machine-readable plugin-marketplace-updated result
578
+ -h, --help Display help for command
579
+ `;
580
+ const PLUGIN_MARKETPLACE_ACTION_HELP = {
581
+ list: PLUGIN_MARKETPLACE_LIST_HELP,
582
+ add: PLUGIN_MARKETPLACE_ADD_HELP,
583
+ remove: PLUGIN_MARKETPLACE_REMOVE_HELP,
584
+ rm: PLUGIN_MARKETPLACE_REMOVE_HELP,
585
+ update: PLUGIN_MARKETPLACE_UPDATE_HELP,
586
+ };
587
+ const AUTO_MODE_HELP = `Usage: praxis auto-mode [options] [command]
588
+
589
+ Inspect auto mode classifier configuration and critique custom rules.
590
+
591
+ Options:
592
+ -h, --help Display help for command
593
+
594
+ Commands:
595
+ config Print effective auto mode config as JSON
596
+ defaults Print default environment, allow, soft_deny, and hard_deny rules as JSON
597
+ critique [options] Get provider-backed feedback on custom auto mode rules
598
+ `;
599
+ const AUTO_MODE_CONFIG_HELP = `Usage: praxis auto-mode config
600
+
601
+ Print effective auto mode configuration as JSON. Settings values override
602
+ defaults; omitted rule lists retain their default values.
603
+
604
+ Options:
605
+ -h, --help Display help for command
606
+ `;
607
+ const AUTO_MODE_DEFAULTS_HELP = `Usage: praxis auto-mode defaults [options]
608
+
609
+ Print default auto mode environment, allow, soft_deny, and hard_deny rules as
610
+ JSON.
611
+
612
+ Options:
613
+ --label <prefix> Show only rules whose label starts with this prefix (case-insensitive)
614
+ -h, --help Display help for command
615
+ `;
616
+ const AUTO_MODE_CRITIQUE_HELP = `Usage: praxis auto-mode critique [options]
617
+
618
+ Get provider-backed feedback on custom auto mode rules. With no custom rule
619
+ lists configured, Praxis prints guidance without creating a provider request.
620
+
621
+ Options:
622
+ --model <model> Override model used for critique (default: PRAXIS_MODEL)
623
+ -h, --help Display help for command
624
+ `;
625
+ const AUTO_MODE_ACTION_HELP = {
626
+ config: AUTO_MODE_CONFIG_HELP,
627
+ defaults: AUTO_MODE_DEFAULTS_HELP,
628
+ critique: AUTO_MODE_CRITIQUE_HELP,
629
+ };
630
+ const PROJECT_HELP = `Usage: praxis project [options] [command]
631
+
632
+ Manage Praxis-compatible Claude project state.
633
+
634
+ Options:
635
+ -h, --help Display help for command
636
+
637
+ Commands:
638
+ purge [options] [path] Delete Claude project state for a path (default: current project)
639
+ `;
640
+ const DOCTOR_HELP = `Usage: praxis doctor [options]
641
+
642
+ Check Praxis installation and local single-user configuration health.
643
+
644
+ Options:
645
+ --json Output a machine-readable report
646
+ -h, --help Show help
647
+ `;
648
+ const PROJECT_PURGE_HELP = `Usage: praxis project purge [options] [path]
649
+
650
+ Delete all Claude Code state for a project (transcripts, tasks, file history,
651
+ config entry)
652
+
653
+ Options:
654
+ --all Purge state for every project (mutually exclusive with [path])
655
+ --dry-run List what would be deleted without deleting anything
656
+ -i, --interactive Prompt for each item before deleting
657
+ --json Print purge plan and result as JSON
658
+ -y, --yes Skip confirmation prompt
659
+ -h, --help Show help
660
+
661
+ Default: [path] is current project; Praxis requests confirmation unless --yes
662
+ is supplied.
663
+ `;
664
+ const INSTALL_HELP = `Usage: praxis install [options] [target]
665
+
666
+ Install Praxis global npm package. Target may be stable, latest, next, beta,
667
+ canary, or an exact semantic version.
668
+
669
+ Options:
670
+ --force Force installation even if already installed
671
+ --json Output a machine-readable result
672
+ -h, --help Show help
673
+ `;
674
+ const UPDATE_HELP = `Usage: praxis update|upgrade [options]
675
+
676
+ Install latest Praxis global npm package.
677
+
678
+ Options:
679
+ --json Output a machine-readable result
680
+ -h, --help Show help
681
+ `;
682
+ export { parseContextEnvironment, parseProviderEnvironment };
683
+ const consoleIO = {
684
+ stdout: (message) => process.stdout.write(message),
685
+ stderr: (message) => process.stderr.write(message),
686
+ isTTY: Boolean(process.stdin.isTTY && process.stdout.isTTY),
687
+ readStdinLines: () => process.stdin,
688
+ };
689
+ const createDefaultService = async ({ eventSink, requireProvider, approveRecovery, approveTool, agent, controls = DEFAULT_CLI_CONTROLS, interactive = false, sessionKind, signal, exposeToolRegistry = false, onElicitation, askUser, approvePlan, emitToolUseSummaries = false, cwd: requestedCwd, configRoot: requestedConfigRoot, environment, providerEnvironment: requestedProviderEnvironment, }) => {
690
+ const runtimeEnvironment = requestedProviderEnvironment ?? process.env;
691
+ const claudeVersion = await detectInstalledClaudeVersion();
692
+ const cwd = requestedCwd ?? process.cwd();
693
+ const workspace = new WorkspaceContext(cwd);
694
+ const configuredRoot = runtimeEnvironment.CLAUDE_CONFIG_DIR || undefined;
695
+ const configRoot = resolve(requestedConfigRoot ?? configuredRoot ?? resolve(homedir(), '.claude'));
696
+ const claudeStatePath = requestedConfigRoot || configuredRoot
697
+ ? join(configRoot, '.claude.json')
698
+ : resolve(homedir(), '.claude.json');
699
+ const cli = await resolveCliControls(controls, cwd);
700
+ const debug = cli.debug !== undefined || cli.debugFile !== undefined
701
+ ? createCliDebugSink(eventSink, {
702
+ cwd,
703
+ ...(cli.debug === undefined ? {} : { filter: cli.debug }),
704
+ ...(cli.debugFile === undefined ? {} : { file: cli.debugFile }),
705
+ ...(cli.debugFile === undefined
706
+ ? { stderr: (message) => process.stderr.write(message) }
707
+ : {}),
708
+ })
709
+ : undefined;
710
+ const runtimeEventSink = debug?.eventSink ?? eventSink;
711
+ let provider;
712
+ let providerForModel;
713
+ const context = parseContextEnvironment(runtimeEnvironment);
714
+ const apiKey = runtimeEnvironment.PRAXIS_API_KEY;
715
+ const model = cli.model ?? runtimeEnvironment.PRAXIS_MODEL;
716
+ const providerEnvironment = apiKey && model
717
+ ? parseProviderEnvironment(runtimeEnvironment)
718
+ : cli.fileResources.length > 0
719
+ ? parseProviderEnvironment(runtimeEnvironment)
720
+ : undefined;
721
+ const fileCredential = runtimeEnvironment.PRAXIS_FILES_BEARER_TOKEN ??
722
+ runtimeEnvironment.PRAXIS_FILES_API_KEY ??
723
+ apiKey;
724
+ if (cli.fileResources.length > 0 && !fileCredential) {
725
+ throw new Error('--file requires PRAXIS_FILES_BEARER_TOKEN, PRAXIS_FILES_API_KEY, or PRAXIS_API_KEY');
726
+ }
727
+ if (requireProvider && (!apiKey || !model)) {
728
+ throw new Error('PRAXIS_API_KEY and a model (--model or PRAXIS_MODEL) are required');
729
+ }
730
+ if (apiKey && model) {
731
+ if (!providerEnvironment) {
732
+ throw new Error('Provider environment is unavailable');
733
+ }
734
+ providerForModel = createProviderForModel(apiKey, providerEnvironment, context, cli);
735
+ const models = [model, ...(cli.fallbackModels ?? [])].filter((candidate, index, all) => all.indexOf(candidate) === index);
736
+ const createProvider = providerForModel;
737
+ const providers = models.map((candidate) => createProvider(candidate));
738
+ provider =
739
+ providers.length > 1
740
+ ? new FallbackModelProvider({ providers })
741
+ : providers[0];
742
+ }
743
+ const options = {
744
+ configRoot,
745
+ cwd,
746
+ claudeVersion,
747
+ eventSink: runtimeEventSink,
748
+ sessionPersistence: cli.sessionPersistence,
749
+ effort: cli.effort ?? 'high',
750
+ ...(cli.maxTurns === undefined ? {} : { maxModelTurns: cli.maxTurns }),
751
+ ...(cli.brief && !cli.disallowedTools.includes('SendUserMessage')
752
+ ? { brief: true }
753
+ : {}),
754
+ ...(cli.betas.length ? { betas: cli.betas } : {}),
755
+ ...(cli.jsonSchema ? { structuredOutputSchema: cli.jsonSchema } : {}),
756
+ ...(cli.maxBudgetUsd === undefined
757
+ ? {}
758
+ : { maxBudgetUsd: cli.maxBudgetUsd }),
759
+ pricing: ModelPricingRegistry.fromEnvironment(runtimeEnvironment.PRAXIS_PRICING_JSON),
760
+ collectMetrics: true,
761
+ ...(sessionKind === undefined ? {} : { sessionKind }),
762
+ workspace,
763
+ ...(cli.worktreeRequested
764
+ ? {
765
+ initialWorktree: true,
766
+ ...(cli.worktreeName === undefined
767
+ ? {}
768
+ : { initialWorktreeName: cli.worktreeName }),
769
+ }
770
+ : {}),
771
+ ...(cli.fileResources.length > 0
772
+ ? {
773
+ fileResources: cli.fileResources,
774
+ fileResourceConfig: {
775
+ cwd,
776
+ apiKey: fileCredential ?? '',
777
+ baseUrl: runtimeEnvironment.PRAXIS_FILES_BASE_URL ??
778
+ fileResourceBaseUrl(runtimeEnvironment, providerEnvironment),
779
+ headers: fileResourceHeaders(runtimeEnvironment, providerEnvironment, fileCredential ?? ''),
780
+ },
781
+ }
782
+ : {}),
783
+ };
784
+ if (!provider && !exposeToolRegistry)
785
+ return new ClaudeSessionService(options);
786
+ const toolProvider = provider ?? {
787
+ model: 'praxis/provider',
788
+ capabilities: {
789
+ streaming: true,
790
+ usage: true,
791
+ tools: true,
792
+ images: true,
793
+ documents: true,
794
+ webSearch: true,
795
+ },
796
+ complete: () => ({
797
+ [Symbol.asyncIterator]: () => ({
798
+ next: async () => {
799
+ throw new Error('A model provider is required to execute model-backed tools');
800
+ },
801
+ }),
802
+ }),
803
+ };
804
+ const hostedToolProvider = exposeToolRegistry && !toolProvider.capabilities.webSearch
805
+ ? {
806
+ ...(toolProvider.model === undefined
807
+ ? {}
808
+ : { model: toolProvider.model }),
809
+ capabilities: { ...toolProvider.capabilities, webSearch: true },
810
+ complete: (request) => toolProvider.complete(request),
811
+ }
812
+ : toolProvider;
813
+ const automaticSettingSources = cli.safeMode || cli.bare ? [] : cli.settingSources;
814
+ const settings = [
815
+ ...(await loadClaudeSettings({
816
+ configRoot,
817
+ cwd,
818
+ ...(cli.bare
819
+ ? { settingSources: [] }
820
+ : cli.settingSources === undefined
821
+ ? {}
822
+ : { settingSources: cli.settingSources }),
823
+ })),
824
+ ...(cli.additionalSettings ? [cli.additionalSettings] : []),
825
+ ];
826
+ const loadedResources = await loadClaudeSharedResources({
827
+ configRoot,
828
+ cwd,
829
+ claudeStatePath,
830
+ ...(automaticSettingSources === undefined
831
+ ? {}
832
+ : { settingSources: automaticSettingSources }),
833
+ });
834
+ const pluginResources = await loadClaudePlugins({
835
+ configRoot,
836
+ cwd,
837
+ pluginDirectories: cli.pluginDirectories,
838
+ pluginUrls: cli.pluginUrls,
839
+ strictPluginDirectories: cli.pluginDirectories.length + cli.pluginUrls.length > 0,
840
+ loadInstalled: !cli.safeMode && !cli.bare,
841
+ environment: runtimeEnvironment,
842
+ });
843
+ for (const plugin of pluginResources.plugins) {
844
+ for (const error of plugin.errors) {
845
+ runtimeEventSink({
846
+ type: 'warning',
847
+ message: `Plugin ${plugin.name} could not be loaded: ${error}`,
848
+ });
849
+ }
850
+ }
851
+ settings.push(...pluginResources.settings);
852
+ const resources = {
853
+ ...loadedResources,
854
+ commands: [...loadedResources.commands, ...pluginResources.commands],
855
+ skills: [...loadedResources.skills, ...pluginResources.skills],
856
+ agents: [
857
+ ...loadedResources.agents,
858
+ ...pluginResources.agents,
859
+ ...cli.inlineAgents,
860
+ ],
861
+ settings: [
862
+ ...loadedResources.settings,
863
+ ...pluginResources.settings,
864
+ ...(cli.additionalSettings ? [cli.additionalSettings] : []),
865
+ ],
866
+ mcp: cli.strictMcpConfig
867
+ ? cli.mcpResources
868
+ : [...loadedResources.mcp, ...pluginResources.mcp, ...cli.mcpResources],
869
+ };
870
+ const extensions = new ClaudeExtensionCatalog(resources, {
871
+ disableSlashCommands: cli.disableSlashCommands,
872
+ });
873
+ const memoryDirectory = cli.safeMode || cli.bare
874
+ ? undefined
875
+ : await resolveClaudeProjectMemoryDirectory({ configRoot, cwd });
876
+ if (memoryDirectory)
877
+ await mkdir(memoryDirectory, { recursive: true });
878
+ const loadContextResources = (runtimeCwd = workspace.cwd()) => loadClaudeContextResources({
879
+ configRoot,
880
+ cwd: runtimeCwd,
881
+ ...(automaticSettingSources === undefined
882
+ ? {}
883
+ : { settingSources: automaticSettingSources }),
884
+ });
885
+ const permissionResolverForMode = (permissionMode) => new ClaudeExtensionPermissionResolver(new ClaudePermissionResolver({
886
+ cwd,
887
+ cwdProvider: () => workspace.cwd(),
888
+ settings,
889
+ allowedTools: cli.allowedTools,
890
+ disallowedTools: cli.disallowedTools,
891
+ permissionMode,
892
+ ...(permissionMode === 'auto'
893
+ ? {
894
+ autoClassifier: createClaudeModelAutoClassifier(hostedToolProvider),
895
+ }
896
+ : {}),
897
+ }));
898
+ const permissions = permissionResolverForMode(cli.dangerouslySkipPermissions ? 'bypassPermissions' : cli.permissionMode);
899
+ const exposePlanDirectory = interactive &&
900
+ askUser !== undefined &&
901
+ approvePlan !== undefined &&
902
+ ['EnterPlanMode', 'ExitPlanMode'].some((name) => (cli.tools === undefined ||
903
+ cli.tools.includes('default') ||
904
+ cli.tools.includes(name)) &&
905
+ !cli.disallowedTools.includes(name));
906
+ const localTools = new LocalToolRegistry({
907
+ cwd,
908
+ cwdProvider: () => workspace.cwd(),
909
+ enableReportFindings: exposeToolRegistry,
910
+ ...(memoryDirectory ? { sharedMemoryDirectory: memoryDirectory } : {}),
911
+ additionalDirectories: [
912
+ ...cli.additionalDirectories,
913
+ ...(exposePlanDirectory ? [resolve(configRoot, 'plans')] : []),
914
+ ],
915
+ additionalReadDirectories: [claudeBackgroundTaskParent(cwd)],
916
+ ...(environment ? { environment } : {}),
917
+ });
918
+ const mcpTools = await ClaudeMcpToolRegistry.connect({
919
+ base: cli.bare
920
+ ? localTools
921
+ : new WebToolRegistry({
922
+ base: localTools,
923
+ provider: hostedToolProvider,
924
+ }),
925
+ resources: resources.mcp,
926
+ cwd,
927
+ configRoot,
928
+ onWarning: (message) => runtimeEventSink({ type: 'warning', message }),
929
+ onPromptsChanged: (prompts) => extensions.setMcpPrompts(prompts),
930
+ ...(onElicitation ? { onElicitation } : {}),
931
+ eventSink: runtimeEventSink,
932
+ ...(signal ? { signal } : {}),
933
+ });
934
+ let lspTools;
935
+ try {
936
+ const permissionApprover = cli.permissionPromptTool
937
+ ? mcpTools.permissionPrompt(cli.permissionPromptTool)
938
+ : approveTool;
939
+ const extensionTools = new ClaudeExtensionToolRegistry(mcpTools, extensions);
940
+ const lspEnabled = interactive &&
941
+ !cli.safeMode &&
942
+ !cli.bare &&
943
+ pluginResources.lsp.length > 0;
944
+ lspTools = lspEnabled
945
+ ? new ClaudeLspToolManager({
946
+ servers: pluginResources.lsp,
947
+ cwdProvider: () => workspace.cwd(),
948
+ roots: () => [
949
+ workspace.cwd(),
950
+ ...cli.additionalDirectories,
951
+ ...(memoryDirectory ? [memoryDirectory] : []),
952
+ ],
953
+ environment: runtimeEnvironment,
954
+ })
955
+ : undefined;
956
+ const extensionAndLspTools = lspTools
957
+ ? lspTools.registry(extensionTools)
958
+ : extensionTools;
959
+ const agentToolNames = ['Agent', 'SendMessage'];
960
+ const taskToolNames = [
961
+ 'TaskCreate',
962
+ 'TaskGet',
963
+ 'TaskList',
964
+ 'TaskOutput',
965
+ 'TaskStop',
966
+ 'TaskUpdate',
967
+ ];
968
+ const scheduledToolNames = [
969
+ 'CronCreate',
970
+ 'CronDelete',
971
+ 'CronList',
972
+ 'ScheduleWakeup',
973
+ ];
974
+ const workflowToolNames = ['Workflow'];
975
+ const worktreeToolNames = ['EnterWorktree', 'ExitWorktree'];
976
+ const interactiveToolNames = [
977
+ 'AskUserQuestion',
978
+ 'EnterPlanMode',
979
+ 'ExitPlanMode',
980
+ ];
981
+ const selectedAgentTools = agentToolNames.filter((name) => (cli.tools === undefined ||
982
+ cli.tools.includes('default') ||
983
+ cli.tools.includes(name)) &&
984
+ !cli.disallowedTools.includes(name));
985
+ const selectedTaskTools = taskToolNames.filter((name) => cli.sessionPersistence &&
986
+ !cli.bare &&
987
+ (cli.tools === undefined ||
988
+ cli.tools.includes('default') ||
989
+ cli.tools.includes(name)) &&
990
+ !cli.disallowedTools.includes(name));
991
+ const selectedScheduledTools = scheduledToolNames.filter((name) => !cli.bare &&
992
+ (cli.tools === undefined ||
993
+ cli.tools.includes('default') ||
994
+ cli.tools.includes(name)) &&
995
+ !cli.disallowedTools.includes(name));
996
+ const selectedWorkflowTools = workflowToolNames.filter((name) => cli.sessionPersistence &&
997
+ !cli.bare &&
998
+ (cli.tools === undefined ||
999
+ cli.tools.includes('default') ||
1000
+ cli.tools.includes(name)) &&
1001
+ !cli.disallowedTools.includes(name));
1002
+ const selectedWorktreeTools = worktreeToolNames.filter((name) => !cli.bare &&
1003
+ (cli.tools === undefined ||
1004
+ cli.tools.includes('default') ||
1005
+ cli.tools.includes(name)) &&
1006
+ !cli.disallowedTools.includes(name));
1007
+ const selectedInteractiveTools = interactiveToolNames.filter((name) => interactive &&
1008
+ (cli.tools === undefined ||
1009
+ cli.tools.includes('default') ||
1010
+ cli.tools.includes(name)) &&
1011
+ !cli.disallowedTools.includes(name));
1012
+ const enableBackgroundBash = cli.sessionPersistence &&
1013
+ !cli.bare &&
1014
+ (cli.tools === undefined ||
1015
+ cli.tools.includes('default') ||
1016
+ cli.tools.includes('Bash')) &&
1017
+ !cli.disallowedTools.includes('Bash');
1018
+ const selectedTaskRuntimeTools = [
1019
+ ...selectedTaskTools,
1020
+ ...(enableBackgroundBash ? ['Bash'] : []),
1021
+ ];
1022
+ const routedSubagentTools = [
1023
+ ...selectedAgentTools,
1024
+ ...selectedTaskTools.filter((name) => name === 'TaskOutput' || name === 'TaskStop'),
1025
+ ];
1026
+ const selectedBaseTools = cli.tools?.filter((name) => !agentToolNames.includes(name) &&
1027
+ !taskToolNames.includes(name) &&
1028
+ !scheduledToolNames.includes(name) &&
1029
+ !workflowToolNames.includes(name) &&
1030
+ !worktreeToolNames.includes(name) &&
1031
+ !interactiveToolNames.includes(name) &&
1032
+ (!cli.bare || (name !== 'WebFetch' && name !== 'WebSearch')));
1033
+ const filteredTools = new FilteredToolRegistry(extensionAndLspTools, {
1034
+ ...(cli.tools === undefined
1035
+ ? cli.bare
1036
+ ? { tools: ['Bash', 'Edit', 'Read'] }
1037
+ : {}
1038
+ : { tools: selectedBaseTools ?? [] }),
1039
+ disallowedTools: cli.disallowedTools,
1040
+ });
1041
+ const enableSubagents = !cli.bare && selectedAgentTools.length > 0;
1042
+ const hooks = cli.safeMode || cli.bare
1043
+ ? undefined
1044
+ : new ClaudeHookRunner({
1045
+ settings,
1046
+ cwd,
1047
+ onEvent: (event) => runtimeEventSink({ type: 'hook', event }),
1048
+ });
1049
+ const interactiveTools = selectedInteractiveTools.length > 0 && askUser && approvePlan
1050
+ ? new ClaudeInteractiveToolManager({
1051
+ configRoot,
1052
+ initialMode: cli.dangerouslySkipPermissions
1053
+ ? 'bypassPermissions'
1054
+ : cli.permissionMode,
1055
+ enabledTools: selectedInteractiveTools,
1056
+ callbacks: { askUser, approvePlan },
1057
+ permissionResolverForMode,
1058
+ })
1059
+ : undefined;
1060
+ const service = new ClaudeSessionService({
1061
+ ...options,
1062
+ provider: hostedToolProvider,
1063
+ ...(providerForModel ? { providerForModel } : {}),
1064
+ tools: filteredTools,
1065
+ permissions,
1066
+ permissionResolverForMode,
1067
+ extensions,
1068
+ enableSubagents,
1069
+ subagentToolNames: routedSubagentTools,
1070
+ taskToolNames: selectedTaskRuntimeTools,
1071
+ scheduledToolNames: selectedScheduledTools,
1072
+ enableDynamicWakeups: interactive,
1073
+ enableWorkflows: selectedWorkflowTools.length > 0,
1074
+ emitToolUseSummaries,
1075
+ enableWorktrees: cli.worktreeRequested || selectedWorktreeTools.length > 0,
1076
+ worktreeToolNames: selectedWorktreeTools,
1077
+ ...(interactiveTools ? { interactiveTools } : {}),
1078
+ ...(hooks ? { hooks } : {}),
1079
+ ...(agent ? { agent } : {}),
1080
+ contextAssembler: new ClaudeContextAssembler({
1081
+ loadResources: loadContextResources,
1082
+ loadDynamicContext: (runtimeCwd = workspace.cwd()) => loadClaudeDynamicContext({
1083
+ cwd: runtimeCwd,
1084
+ ...(memoryDirectory ? { memoryDirectory } : {}),
1085
+ }),
1086
+ excludeDynamicSystemPromptSections: cli.excludeDynamicSystemPromptSections,
1087
+ ...(cli.systemPrompt === undefined
1088
+ ? {}
1089
+ : { systemPrompt: cli.systemPrompt }),
1090
+ ...(cli.appendSystemPrompt === undefined
1091
+ ? {}
1092
+ : { appendSystemPrompt: cli.appendSystemPrompt }),
1093
+ }),
1094
+ conditionalRuleResolver: new ClaudeConditionalRuleResolver({
1095
+ loadResources: loadContextResources,
1096
+ }),
1097
+ ...('contextReserveTokens' in context
1098
+ ? { contextReserveTokens: context.contextReserveTokens }
1099
+ : {}),
1100
+ ...(permissionApprover ? { approveTool: permissionApprover } : {}),
1101
+ ...(approveRecovery ? { approveRecovery } : {}),
1102
+ fileCheckpointing: runtimeEnvironment.CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING === 'true',
1103
+ fileRewindRoots: [
1104
+ ...cli.additionalDirectories,
1105
+ ...(memoryDirectory ? [memoryDirectory] : []),
1106
+ ],
1107
+ });
1108
+ const toolNames = [
1109
+ ...filteredTools.definitions().map((definition) => definition.name),
1110
+ ...(cli.brief && !cli.disallowedTools.includes('SendUserMessage')
1111
+ ? ['SendUserMessage']
1112
+ : []),
1113
+ ...selectedTaskTools,
1114
+ ...selectedScheduledTools,
1115
+ ...selectedWorkflowTools,
1116
+ ...selectedWorktreeTools,
1117
+ ...selectedInteractiveTools,
1118
+ ...(enableSubagents ? selectedAgentTools : []),
1119
+ ];
1120
+ const runtimeInfo = {
1121
+ cwd: workspace.cwd(),
1122
+ model: provider?.model ?? runtimeEnvironment.PRAXIS_MODEL ?? 'unknown',
1123
+ tools: toolNames,
1124
+ mcpServers: mcpTools.serverStatuses(),
1125
+ permissionMode: cli.dangerouslySkipPermissions
1126
+ ? 'bypassPermissions'
1127
+ : cli.permissionMode,
1128
+ slashCommands: extensions
1129
+ .modelInvocableSkills()
1130
+ .map((definition) => definition.name)
1131
+ .concat(extensions.mcpPromptNames()),
1132
+ agents: extensions.agentNames(),
1133
+ skills: extensions
1134
+ .modelInvocableSkills()
1135
+ .map((definition) => definition.name),
1136
+ plugins: pluginResources.plugins
1137
+ .filter((plugin) => plugin.enabled && plugin.errors.length === 0)
1138
+ .map((plugin) => ({ name: plugin.name, path: plugin.path })),
1139
+ claudeCodeVersion: claudeVersion,
1140
+ };
1141
+ const toolRegistry = exposeToolRegistry
1142
+ ? service.createHostedToolRegistry(randomUUID())
1143
+ : filteredTools;
1144
+ let pendingResumeSessionAt = cli.resumeSessionAt;
1145
+ return {
1146
+ toolRegistry,
1147
+ run: (prompt, signal, sessionId, name, images, documents) => service.run(prompt, signal, sessionId, name ?? cli.name, images, documents),
1148
+ resume: (sessionId, prompt, signal, name, images, documents) => {
1149
+ const resumeSessionAt = pendingResumeSessionAt;
1150
+ pendingResumeSessionAt = undefined;
1151
+ return service.resume(sessionId, prompt, signal, name ?? cli.name, images, documents, resumeSessionAt);
1152
+ },
1153
+ fork: (sessionId, targetSessionId) => {
1154
+ const resumeSessionAt = pendingResumeSessionAt;
1155
+ pendingResumeSessionAt = undefined;
1156
+ return service.fork(sessionId, targetSessionId, resumeSessionAt);
1157
+ },
1158
+ rewindFiles: (sessionId, userMessageId) => service.rewindFiles(sessionId, userMessageId),
1159
+ lifecycle: async (trigger, lifecycleOptions = {}) => {
1160
+ if (!hooks)
1161
+ return;
1162
+ const sessionId = lifecycleOptions.sessionId ?? randomUUID();
1163
+ const runtimeCwd = workspace.cwd();
1164
+ const hookSession = {
1165
+ session_id: sessionId,
1166
+ transcript_path: resolveClaudePaths({
1167
+ cwd: runtimeCwd,
1168
+ sessionId,
1169
+ configDir: configRoot,
1170
+ }).sessionFile,
1171
+ cwd: runtimeCwd,
1172
+ permission_mode: cli.dangerouslySkipPermissions
1173
+ ? 'bypassPermissions'
1174
+ : cli.permissionMode,
1175
+ };
1176
+ const setup = await hooks.run({ ...hookSession, hook_event_name: 'Setup', trigger }, trigger, signal);
1177
+ if (setup.blockedReason) {
1178
+ throw new Error(`Setup hook error: ${setup.blockedReason}`);
1179
+ }
1180
+ if (!lifecycleOptions.sessionStart)
1181
+ return;
1182
+ const startup = await hooks.run({
1183
+ ...hookSession,
1184
+ hook_event_name: 'SessionStart',
1185
+ source: 'startup',
1186
+ }, 'startup', signal);
1187
+ if (startup.blockedReason) {
1188
+ throw new Error(`SessionStart hook error: ${startup.blockedReason}`);
1189
+ }
1190
+ },
1191
+ sessions: () => service.sessions(),
1192
+ inspect: (sessionId) => service.inspect(sessionId),
1193
+ export: (sessionId) => service.export(sessionId),
1194
+ nextScheduledPrompt: (signal) => service.nextScheduledPrompt(signal),
1195
+ close: async () => {
1196
+ let failure;
1197
+ try {
1198
+ await service.close();
1199
+ }
1200
+ catch (error) {
1201
+ failure = error;
1202
+ }
1203
+ try {
1204
+ await mcpTools.close();
1205
+ }
1206
+ catch (error) {
1207
+ failure ??= error;
1208
+ }
1209
+ try {
1210
+ await lspTools?.close();
1211
+ }
1212
+ catch (error) {
1213
+ failure ??= error;
1214
+ }
1215
+ await debug?.close();
1216
+ if (failure !== undefined)
1217
+ throw failure;
1218
+ },
1219
+ runtimeInfo: () => ({
1220
+ ...runtimeInfo,
1221
+ cwd: workspace.cwd(),
1222
+ model: provider?.model ?? process.env.PRAXIS_MODEL ?? 'unknown',
1223
+ }),
1224
+ promptSuggestion: (sessionId, suggestionSignal) => service.promptSuggestion(sessionId, suggestionSignal),
1225
+ };
1226
+ }
1227
+ catch (error) {
1228
+ try {
1229
+ await lspTools?.close();
1230
+ }
1231
+ catch {
1232
+ // Preserve the service-construction failure as the primary error.
1233
+ }
1234
+ try {
1235
+ await mcpTools.close();
1236
+ }
1237
+ catch {
1238
+ // Preserve the service-construction failure as the primary error.
1239
+ }
1240
+ finally {
1241
+ await debug?.close();
1242
+ }
1243
+ throw error;
1244
+ }
1245
+ };
1246
+ const createDefaultAutoModeCritic = async ({ model }) => {
1247
+ const apiKey = process.env.PRAXIS_API_KEY;
1248
+ const selectedModel = model ?? process.env.PRAXIS_MODEL;
1249
+ if (!apiKey || !selectedModel) {
1250
+ throw new Error('PRAXIS_API_KEY and a model (--model or PRAXIS_MODEL) are required');
1251
+ }
1252
+ return createProviderForModel(apiKey, parseProviderEnvironment(process.env), parseContextEnvironment(process.env), {})(selectedModel);
1253
+ };
1254
+ const defaultPluginEvalRuntimeFactory = {
1255
+ create: async (options) => {
1256
+ let turns = 0;
1257
+ let historySessionId;
1258
+ if (options.historyFile) {
1259
+ const source = await readFile(options.historyFile, 'utf8');
1260
+ for (const line of source.split(/\r?\n/u)) {
1261
+ if (!line.trim())
1262
+ continue;
1263
+ let entry;
1264
+ try {
1265
+ entry = JSON.parse(line);
1266
+ }
1267
+ catch (error) {
1268
+ throw new Error(`Invalid history_file JSONL: ${options.historyFile}`, {
1269
+ cause: error,
1270
+ });
1271
+ }
1272
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry))
1273
+ throw new Error(`Invalid history_file entry: ${options.historyFile}`);
1274
+ const candidate = entry.sessionId;
1275
+ if (typeof candidate === 'string' && isClaudeSessionId(candidate)) {
1276
+ historySessionId ??= candidate;
1277
+ }
1278
+ }
1279
+ if (!historySessionId)
1280
+ throw new Error('history_file must contain a Claude sessionId');
1281
+ const sessionFile = resolveClaudePaths({
1282
+ cwd: options.cwd,
1283
+ sessionId: historySessionId,
1284
+ configDir: options.configRoot,
1285
+ }).sessionFile;
1286
+ await mkdir(dirname(sessionFile), { recursive: true });
1287
+ await copyFile(options.historyFile, sessionFile);
1288
+ }
1289
+ const service = await createDefaultService({
1290
+ eventSink: (event) => {
1291
+ if (event.type === 'state' && event.state === 'awaiting-model')
1292
+ turns += 1;
1293
+ options.eventSink(event);
1294
+ },
1295
+ requireProvider: true,
1296
+ cwd: options.cwd,
1297
+ configRoot: options.configRoot,
1298
+ environment: {
1299
+ ...options.env,
1300
+ HOME: options.home,
1301
+ USERPROFILE: options.home,
1302
+ },
1303
+ providerEnvironment: process.env,
1304
+ controls: {
1305
+ ...DEFAULT_CLI_CONTROLS,
1306
+ sessionPersistence: false,
1307
+ maxTurns: options.maxTurns,
1308
+ pluginDirectories: [...options.pluginDirectories],
1309
+ addDirectories: [...options.addDirs],
1310
+ allowedTools: [...options.allowedTools],
1311
+ disallowedTools: [],
1312
+ tools: [...options.allowedTools],
1313
+ permissionMode: 'dontAsk',
1314
+ ...(options.model ? { model: options.model } : {}),
1315
+ ...(options.appendSystemPrompt
1316
+ ? { appendSystemPrompt: options.appendSystemPrompt }
1317
+ : {}),
1318
+ },
1319
+ });
1320
+ return {
1321
+ run: async (prompt, signal) => {
1322
+ const result = historySessionId
1323
+ ? await service.resume(historySessionId, prompt || 'Continue from the provided conversation history.', signal)
1324
+ : await service.run(prompt, signal);
1325
+ return {
1326
+ text: result.text,
1327
+ turns,
1328
+ ...(result.costUsd === undefined
1329
+ ? {}
1330
+ : { costUsd: result.costUsd }),
1331
+ };
1332
+ },
1333
+ close: () => service.close?.() ?? Promise.resolve(),
1334
+ };
1335
+ },
1336
+ };
1337
+ const defaultPluginEvalJudge = {
1338
+ vote: async ({ criteria, focus, baseline, model, signal }) => {
1339
+ const environment = process.env;
1340
+ const apiKey = environment.PRAXIS_API_KEY;
1341
+ if (!apiKey)
1342
+ throw new Error('PRAXIS_API_KEY is required for paid eval graders');
1343
+ const provider = createProviderForModel(apiKey, parseProviderEnvironment(environment), parseContextEnvironment(environment), {})(model);
1344
+ const prompt = `You are an eval judge. Return only JSON matching {"passed":boolean,"explanation":string}.
1345
+
1346
+ Criteria:
1347
+ ${criteria}
1348
+
1349
+ Candidate:
1350
+ ${focus}${baseline === undefined ? '' : `\n\nBaseline:\n${baseline}`}`;
1351
+ let text = '';
1352
+ let usage;
1353
+ for await (const event of provider.complete({
1354
+ messages: [{ role: 'user', content: prompt }],
1355
+ ...(signal ? { signal } : {}),
1356
+ })) {
1357
+ if (event.type === 'text-delta')
1358
+ text += event.delta;
1359
+ else if (event.type === 'usage')
1360
+ usage = event.usage;
1361
+ }
1362
+ const object = /\{[\s\S]*\}/u.exec(text)?.[0];
1363
+ if (!object)
1364
+ throw new Error('Eval judge returned no JSON object');
1365
+ let parsed;
1366
+ try {
1367
+ parsed = JSON.parse(object);
1368
+ }
1369
+ catch (error) {
1370
+ throw new Error('Eval judge returned invalid JSON', { cause: error });
1371
+ }
1372
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
1373
+ throw new Error('Eval judge returned an invalid result');
1374
+ const result = parsed;
1375
+ if (typeof result.passed !== 'boolean' ||
1376
+ (result.explanation !== undefined &&
1377
+ typeof result.explanation !== 'string'))
1378
+ throw new Error('Eval judge result must contain passed and explanation');
1379
+ if (!usage)
1380
+ throw new Error('Eval judge provider returned no usage');
1381
+ const pricing = ModelPricingRegistry.fromEnvironment(environment.PRAXIS_PRICING_JSON).resolve(model);
1382
+ if (!pricing)
1383
+ throw new Error(`No pricing configured for eval judge model ${model}`);
1384
+ return {
1385
+ passed: result.passed,
1386
+ ...(typeof result.explanation === 'string'
1387
+ ? { explanation: result.explanation }
1388
+ : {}),
1389
+ costUsd: usageCostUsd(usage, pricing),
1390
+ };
1391
+ },
1392
+ };
1393
+ const defaultDependencies = {
1394
+ createService: createDefaultService,
1395
+ createAutoModeCritic: createDefaultAutoModeCritic,
1396
+ pluginEval: {
1397
+ runtimeFactory: defaultPluginEvalRuntimeFactory,
1398
+ judge: defaultPluginEvalJudge,
1399
+ },
1400
+ runInteractive: ({ agent, controls, initialPrompt, resume, signal }) => renderInteractive({
1401
+ factory: {
1402
+ createService: (options) => createDefaultService({
1403
+ ...options,
1404
+ ...(agent === undefined ? {} : { agent }),
1405
+ ...(controls === undefined ? {} : { controls }),
1406
+ interactive: true,
1407
+ }),
1408
+ scheduledPrompts: true,
1409
+ },
1410
+ ...(signal ? { signal } : {}),
1411
+ ...(initialPrompt === undefined ? {} : { initialPrompt }),
1412
+ ...(controls?.axScreenReader ? { axScreenReader: true } : {}),
1413
+ ...(resume === undefined ? {} : { resume }),
1414
+ ...(resume?.fromPr !== undefined
1415
+ ? {
1416
+ sessionFilter: createClaudePrSessionFilter(resume.fromPr),
1417
+ requireSession: true,
1418
+ }
1419
+ : resume?.sessionSelector === undefined
1420
+ ? {}
1421
+ : {
1422
+ sessionFilter: createResumeSessionFilter(resume.sessionSelector),
1423
+ requireSession: true,
1424
+ missingSessionMessage: isClaudeSessionId(resume.sessionSelector)
1425
+ ? `No conversation found with session ID: ${resume.sessionSelector}`
1426
+ : `No conversation found matching: ${resume.sessionSelector}`,
1427
+ }),
1428
+ ...(resume?.requireSession ? { requireSession: true } : {}),
1429
+ }),
1430
+ runAgentsDashboard: ({ manager, defaults, signal }) => renderAgentsDashboard({
1431
+ manager,
1432
+ defaults,
1433
+ ...(signal ? { signal } : {}),
1434
+ }),
1435
+ topLevelAgents: new TopLevelAgentManager({
1436
+ configRoot: resolve(process.env.CLAUDE_CONFIG_DIR ?? resolve(homedir(), '.claude')),
1437
+ cwd: process.cwd(),
1438
+ cliPath: fileURLToPath(import.meta.url),
1439
+ version: VERSION,
1440
+ }),
1441
+ launchTmux: launchTmuxWorktree,
1442
+ selfUpdate: runSelfUpdate,
1443
+ };
1444
+ export async function createBackgroundWorkerRuntime(workerSink, dispatch, createService = createDefaultService) {
1445
+ const invocation = parseCliInvocation(dispatch.argv);
1446
+ return createService({
1447
+ eventSink: workerSink,
1448
+ requireProvider: true,
1449
+ ...(invocation.retryInterruptedTools
1450
+ ? { approveRecovery: () => true }
1451
+ : {}),
1452
+ ...(invocation.agent ? { agent: invocation.agent } : {}),
1453
+ controls: invocation,
1454
+ sessionKind: 'bg',
1455
+ });
1456
+ }
1457
+ async function runBackgroundWorker(id) {
1458
+ const configRoot = resolve(process.env.CLAUDE_CONFIG_DIR ?? resolve(homedir(), '.claude'));
1459
+ await runTopLevelAgentWorker({
1460
+ configRoot,
1461
+ id,
1462
+ createRuntime: (workerSink, dispatch) => createBackgroundWorkerRuntime(workerSink, dispatch),
1463
+ });
1464
+ }
1465
+ function writeJson(io, value) {
1466
+ io.stdout(`${JSON.stringify(value)}\n`);
1467
+ }
1468
+ function formatSessionIssue(issue) {
1469
+ return issue
1470
+ ? `line ${issue.lineNumber}, byte ${issue.byteOffset}: ${issue.message}`
1471
+ : '';
1472
+ }
1473
+ function selectPrLinkedSession(sessions, selector) {
1474
+ const matches = filterClaudePrLinkedSessions(sessions, selector);
1475
+ const label = selector === true ? 'a pull request' : `PR ${selector}`;
1476
+ if (matches.length === 0) {
1477
+ throw new Error(`No conversation linked to ${label} in this project`);
1478
+ }
1479
+ if (matches.length > 1) {
1480
+ throw new Error(`Multiple conversations are linked to ${label}; use --resume with one of: ${matches
1481
+ .map((session) => session.sessionId)
1482
+ .join(', ')}`);
1483
+ }
1484
+ return matches[0];
1485
+ }
1486
+ function selectImplicitResumeSession(sessions, fromPr) {
1487
+ if (fromPr !== undefined)
1488
+ return selectPrLinkedSession(sessions, fromPr);
1489
+ const latest = sessions[0];
1490
+ if (!latest)
1491
+ throw new Error('No conversation found to continue');
1492
+ return latest;
1493
+ }
1494
+ function missingResumeSelectorMessage(invocation) {
1495
+ if (invocation.print) {
1496
+ return '--resume requires a valid session ID or session title when used with --print. Usage: praxis -p --resume <session-id|title>';
1497
+ }
1498
+ if (invocation.background) {
1499
+ return '--resume requires a valid session ID or session title when used with --background';
1500
+ }
1501
+ return '--resume requires a valid session ID or session title outside an interactive terminal';
1502
+ }
1503
+ function selectResumeSession(sessions, selector, invocation) {
1504
+ const normalized = selector.toLowerCase();
1505
+ const idMatch = sessions.find((session) => session.sessionId.toLowerCase() === normalized);
1506
+ if (idMatch)
1507
+ return idMatch;
1508
+ if (isClaudeSessionId(selector)) {
1509
+ throw new Error(`No conversation found with session ID: ${selector}`);
1510
+ }
1511
+ const titleMatches = sessions.filter((session) => session.name?.toLowerCase() === normalized);
1512
+ if (titleMatches.length === 1)
1513
+ return titleMatches[0];
1514
+ if (titleMatches.length > 1) {
1515
+ throw new Error(`--resume "${selector}" matches ${titleMatches.length} sessions. Pass one of these session IDs to disambiguate:\n${titleMatches
1516
+ .map((session) => ` ${session.sessionId} (modified ${session.updatedAt})`)
1517
+ .join('\n')}`);
1518
+ }
1519
+ throw new Error(`${missingResumeSelectorMessage(invocation)}. Provided value "${selector}" is not a UUID and does not match any session title.`);
1520
+ }
1521
+ function createResumeSessionFilter(selector) {
1522
+ const normalized = selector.toLowerCase();
1523
+ if (isClaudeSessionId(selector)) {
1524
+ return (session) => session.sessionId.toLowerCase() === normalized;
1525
+ }
1526
+ return (session) => [session.name, session.lastPrompt, session.sessionId].some((value) => value?.toLowerCase().includes(normalized));
1527
+ }
1528
+ function mcpOutput(io, invocation, value) {
1529
+ if (invocation.legacyJson || invocation.outputFormat !== 'text') {
1530
+ writeJson(io, value);
1531
+ }
1532
+ else if (typeof value === 'string') {
1533
+ io.stdout(`${value}\n`);
1534
+ }
1535
+ else {
1536
+ io.stdout(`${JSON.stringify(value)}\n`);
1537
+ }
1538
+ }
1539
+ function mcpEnvironment(values) {
1540
+ const environment = {};
1541
+ for (const value of values) {
1542
+ const [name, ...parts] = value.split('=');
1543
+ if (!name || parts.length === 0) {
1544
+ throw new Error(`Invalid environment variable format: ${value}, environment variables should be added as: -e KEY1=value1 -e KEY2=value2`);
1545
+ }
1546
+ environment[name] = parts.join('=');
1547
+ }
1548
+ return environment;
1549
+ }
1550
+ function mcpHeaders(values) {
1551
+ if (values.length === 0)
1552
+ return undefined;
1553
+ const headers = {};
1554
+ for (const value of values) {
1555
+ const separator = value.indexOf(':');
1556
+ if (separator < 0) {
1557
+ throw new Error(`Invalid header format: "${value}". Expected format: "Header-Name: value"`);
1558
+ }
1559
+ const name = value.slice(0, separator).trim();
1560
+ if (!name) {
1561
+ throw new Error(`Invalid header: "${value}". Header name cannot be empty.`);
1562
+ }
1563
+ headers[name] = value.slice(separator + 1).trim();
1564
+ }
1565
+ return headers;
1566
+ }
1567
+ function mcpCallbackPort(value) {
1568
+ if (value === undefined)
1569
+ return undefined;
1570
+ const port = Number.parseInt(value, 10);
1571
+ if (!port)
1572
+ return undefined;
1573
+ if (port < 0)
1574
+ throw new Error('Invalid configuration: : Invalid input');
1575
+ return port;
1576
+ }
1577
+ function mcpOauthOptions(config) {
1578
+ const oauth = config.oauth;
1579
+ if (!oauth || typeof oauth !== 'object' || Array.isArray(oauth))
1580
+ return {};
1581
+ const values = oauth;
1582
+ return {
1583
+ ...(typeof values.clientId === 'string'
1584
+ ? { clientId: values.clientId }
1585
+ : {}),
1586
+ ...(typeof values.callbackPort === 'number'
1587
+ ? { callbackPort: values.callbackPort }
1588
+ : {}),
1589
+ };
1590
+ }
1591
+ function mcpConfigPath(record) {
1592
+ return record.scope === 'local'
1593
+ ? `${record.path} [project: ${process.cwd()}]`
1594
+ : record.path;
1595
+ }
1596
+ function redactMcpHeaders(headers) {
1597
+ return Object.fromEntries(Object.entries(headers).map(([name, value]) => [
1598
+ name,
1599
+ /authorization|api[-_]?key|token|secret|cookie/iu.test(name)
1600
+ ? '[REDACTED]'
1601
+ : value,
1602
+ ]));
1603
+ }
1604
+ function isLikelyMcpUrl(value) {
1605
+ return (value.startsWith('http://') ||
1606
+ value.startsWith('https://') ||
1607
+ value.startsWith('localhost') ||
1608
+ value.endsWith('/sse') ||
1609
+ value.endsWith('/mcp'));
1610
+ }
1611
+ function mcpAddedOutput(io, invocation, record, transport, commandOrUrl, args, headers) {
1612
+ if (invocation.legacyJson || invocation.outputFormat !== 'text') {
1613
+ mcpOutput(io, invocation, {
1614
+ type: 'mcp-added',
1615
+ server: mcpRecordJson(record),
1616
+ });
1617
+ return;
1618
+ }
1619
+ if (transport === 'stdio') {
1620
+ io.stdout(`Added stdio MCP server ${record.name} with command: ${commandOrUrl} ${args.join(' ')} to ${record.scope} config\n`);
1621
+ }
1622
+ else {
1623
+ io.stdout(`Added ${transport.toUpperCase()} MCP server ${record.name} with URL: ${commandOrUrl} to ${record.scope} config\n`);
1624
+ if (headers) {
1625
+ io.stdout(`Headers: ${JSON.stringify(redactMcpHeaders(headers), null, 2)}\n`);
1626
+ }
1627
+ }
1628
+ io.stdout(`File modified: ${mcpConfigPath(record)}\n`);
1629
+ }
1630
+ async function existingMcpServer(management, name, scope) {
1631
+ try {
1632
+ return await management.get(name, scope);
1633
+ }
1634
+ catch (error) {
1635
+ if (error instanceof Error &&
1636
+ error.message === `MCP server not found: ${name}`) {
1637
+ return undefined;
1638
+ }
1639
+ throw error;
1640
+ }
1641
+ }
1642
+ function mcpRecordJson(record) {
1643
+ return {
1644
+ name: record.name,
1645
+ scope: record.scope,
1646
+ path: record.path,
1647
+ config: record.config,
1648
+ };
1649
+ }
1650
+ function autoModeJson(config) {
1651
+ return {
1652
+ allow: config.allow,
1653
+ soft_deny: config.softDeny,
1654
+ hard_deny: config.hardDeny,
1655
+ environment: config.environment,
1656
+ classifyAllShell: config.classifyAllShell,
1657
+ };
1658
+ }
1659
+ function filterAutoModeRules(config, label) {
1660
+ if (label === undefined)
1661
+ return config;
1662
+ const prefix = label.toLowerCase();
1663
+ const filter = (rules) => rules.filter((rule) => rule.toLowerCase().startsWith(prefix));
1664
+ return {
1665
+ ...config,
1666
+ allow: filter(config.allow),
1667
+ soft_deny: filter(config.soft_deny),
1668
+ hard_deny: filter(config.hard_deny),
1669
+ environment: filter(config.environment),
1670
+ };
1671
+ }
1672
+ function hasCustomAutoModeRules(settings) {
1673
+ return settings.some((setting) => {
1674
+ if (!setting.value ||
1675
+ typeof setting.value !== 'object' ||
1676
+ Array.isArray(setting.value)) {
1677
+ return false;
1678
+ }
1679
+ const autoMode = setting.value.autoMode;
1680
+ if (!autoMode || typeof autoMode !== 'object' || Array.isArray(autoMode)) {
1681
+ return false;
1682
+ }
1683
+ const rules = autoMode;
1684
+ return ['allow', 'soft_deny', 'hard_deny', 'environment'].some((key) => Array.isArray(rules[key]) && rules[key].length > 0);
1685
+ });
1686
+ }
1687
+ const AUTO_MODE_CRITIQUE_SYSTEM = `Praxis auto-mode critique. Review custom auto mode rules for ambiguity, coverage gaps, conflicts, risk, and actionable wording. Return concise Markdown only; do not claim to have changed settings.`;
1688
+ const NO_CUSTOM_AUTO_MODE_RULES = `No custom auto mode rules found.
1689
+
1690
+ Add rules to your settings file under autoMode.{allow, soft_deny, hard_deny,
1691
+ environment}.
1692
+ Run \`praxis auto-mode defaults\` to see the default rules for reference.
1693
+ `;
1694
+ async function executeAutoModeCommand(args, invocation, io, dependencies, signal) {
1695
+ const action = args[1];
1696
+ if (!action || action === 'help') {
1697
+ io.stdout(AUTO_MODE_HELP);
1698
+ return 0;
1699
+ }
1700
+ if (invocation.autoModeLabel !== undefined && action !== 'defaults') {
1701
+ throw new Error('--label is only valid with auto-mode defaults');
1702
+ }
1703
+ if (args.length !== 2) {
1704
+ throw new Error(`auto-mode ${action} takes no operands`);
1705
+ }
1706
+ if (action === 'defaults') {
1707
+ const output = filterAutoModeRules(autoModeJson(defaultClaudeAutoModeConfig()), invocation.autoModeLabel);
1708
+ if (invocation.legacyJson || invocation.outputFormat !== 'text')
1709
+ writeJson(io, output);
1710
+ else
1711
+ io.stdout(`${JSON.stringify(output)}\n`);
1712
+ return 0;
1713
+ }
1714
+ if (action === 'critique') {
1715
+ const configRoot = resolve(process.env.CLAUDE_CONFIG_DIR ?? resolve(homedir(), '.claude'));
1716
+ const settings = await loadClaudeSettings({
1717
+ configRoot,
1718
+ cwd: process.cwd(),
1719
+ });
1720
+ if (!hasCustomAutoModeRules(settings)) {
1721
+ io.stdout(NO_CUSTOM_AUTO_MODE_RULES);
1722
+ return 0;
1723
+ }
1724
+ const config = autoModeJson(loadClaudeAutoModeConfig(settings));
1725
+ io.stdout('Analyzing your auto mode rules…\n\n');
1726
+ const provider = await (dependencies.createAutoModeCritic ?? createDefaultAutoModeCritic)({
1727
+ ...(invocation.model === undefined ? {} : { model: invocation.model }),
1728
+ });
1729
+ let critique = '';
1730
+ for await (const event of provider.complete({
1731
+ messages: [
1732
+ { role: 'system', content: AUTO_MODE_CRITIQUE_SYSTEM },
1733
+ {
1734
+ role: 'user',
1735
+ content: `Review this effective auto mode configuration:\n\n${JSON.stringify(config, null, 2)}`,
1736
+ },
1737
+ ],
1738
+ thinking: { mode: 'disabled' },
1739
+ ...(signal ? { signal } : {}),
1740
+ })) {
1741
+ if (event.type !== 'text-delta')
1742
+ continue;
1743
+ critique += event.delta;
1744
+ io.stdout(event.delta);
1745
+ }
1746
+ if (!critique.trim()) {
1747
+ io.stdout('No critique was generated. Please try again.\n');
1748
+ }
1749
+ else if (!critique.endsWith('\n')) {
1750
+ io.stdout('\n');
1751
+ }
1752
+ return 0;
1753
+ }
1754
+ if (action === 'config') {
1755
+ const configRoot = resolve(process.env.CLAUDE_CONFIG_DIR ?? resolve(homedir(), '.claude'));
1756
+ const settings = await loadClaudeSettings({
1757
+ configRoot,
1758
+ cwd: process.cwd(),
1759
+ });
1760
+ const output = autoModeJson(loadClaudeAutoModeConfig(settings));
1761
+ if (invocation.legacyJson || invocation.outputFormat !== 'text')
1762
+ writeJson(io, output);
1763
+ else
1764
+ io.stdout(`${JSON.stringify(output)}\n`);
1765
+ return 0;
1766
+ }
1767
+ throw new Error(`Unknown auto-mode command: ${action}`);
1768
+ }
1769
+ async function executeDoctorCommand(args, invocation, io) {
1770
+ if (args.length !== 1)
1771
+ throw new Error('doctor takes no operands');
1772
+ if (invocation.outputFormat !== 'text' &&
1773
+ invocation.outputFormat !== 'json' &&
1774
+ !invocation.legacyJson) {
1775
+ throw new Error('doctor output format must be text or json');
1776
+ }
1777
+ const configuredRoot = process.env.CLAUDE_CONFIG_DIR || undefined;
1778
+ const configRoot = resolve(configuredRoot ?? resolve(homedir(), '.claude'));
1779
+ const claudeStatePath = configuredRoot
1780
+ ? join(configRoot, '.claude.json')
1781
+ : resolve(homedir(), '.claude.json');
1782
+ const report = await runDoctor({
1783
+ version: VERSION,
1784
+ executablePath: fileURLToPath(import.meta.url),
1785
+ nodeExecutablePath: process.execPath,
1786
+ nodeVersion: process.version,
1787
+ configRoot,
1788
+ claudeStatePath,
1789
+ cwd: process.cwd(),
1790
+ environment: process.env,
1791
+ });
1792
+ if (invocation.legacyJson || invocation.outputFormat === 'json') {
1793
+ writeJson(io, report);
1794
+ }
1795
+ else {
1796
+ io.stdout(formatDoctorReport(report));
1797
+ }
1798
+ return report.ok ? 0 : 1;
1799
+ }
1800
+ async function executeSelfUpdateCommand(argv, io, dependencies) {
1801
+ const command = argv[0];
1802
+ const values = argv.slice(1);
1803
+ if (values.includes('--help') || values.includes('-h')) {
1804
+ io.stdout(command === 'install' ? INSTALL_HELP : UPDATE_HELP);
1805
+ return 0;
1806
+ }
1807
+ const json = values.includes('--json');
1808
+ const operands = values.filter((value) => value !== '--json');
1809
+ if (command === 'update' || command === 'upgrade') {
1810
+ if (operands.length > 0) {
1811
+ throw new Error(`${command} takes no operands`);
1812
+ }
1813
+ const result = await dependencies.selfUpdate?.({ operation: 'update' });
1814
+ if (!result)
1815
+ throw new Error('Self-update unavailable');
1816
+ if (json)
1817
+ writeJson(io, result);
1818
+ else
1819
+ io.stdout(`Praxis update completed: ${result.output}\n`);
1820
+ return 0;
1821
+ }
1822
+ let force = false;
1823
+ const targets = [];
1824
+ for (const value of operands) {
1825
+ if (value === '--force')
1826
+ force = true;
1827
+ else
1828
+ targets.push(value);
1829
+ }
1830
+ if (targets.length > 1)
1831
+ throw new Error('install accepts at most one target');
1832
+ const result = await dependencies.selfUpdate?.({
1833
+ operation: 'install',
1834
+ force,
1835
+ ...(targets[0] === undefined ? {} : { target: targets[0] }),
1836
+ });
1837
+ if (!result)
1838
+ throw new Error('Self-update unavailable');
1839
+ if (json)
1840
+ writeJson(io, result);
1841
+ else
1842
+ io.stdout(`Praxis install completed: ${result.output}\n`);
1843
+ return 0;
1844
+ }
1845
+ async function executeProjectPurgeCommand(argv, io) {
1846
+ let all = false;
1847
+ let dryRun = false;
1848
+ let interactive = false;
1849
+ let yes = false;
1850
+ let help = false;
1851
+ let json = false;
1852
+ const passthrough = [];
1853
+ let path;
1854
+ let optionsEnded = false;
1855
+ for (let index = 2; index < argv.length; index += 1) {
1856
+ const value = argv[index];
1857
+ if (value === undefined)
1858
+ continue;
1859
+ if (!optionsEnded && value === '--') {
1860
+ optionsEnded = true;
1861
+ continue;
1862
+ }
1863
+ if (value === '--all') {
1864
+ all = true;
1865
+ continue;
1866
+ }
1867
+ if (value === '--dry-run') {
1868
+ dryRun = true;
1869
+ continue;
1870
+ }
1871
+ if (value === '-i' || value === '--interactive') {
1872
+ interactive = true;
1873
+ continue;
1874
+ }
1875
+ if (value === '-y' || value === '--yes') {
1876
+ yes = true;
1877
+ continue;
1878
+ }
1879
+ if (value === '--json') {
1880
+ json = true;
1881
+ continue;
1882
+ }
1883
+ if (value === '-h' || value === '--help') {
1884
+ help = true;
1885
+ continue;
1886
+ }
1887
+ if (!optionsEnded && value.startsWith('-')) {
1888
+ passthrough.push(value);
1889
+ continue;
1890
+ }
1891
+ if (path !== undefined)
1892
+ throw new Error('project purge accepts one path');
1893
+ path = value;
1894
+ }
1895
+ if (help) {
1896
+ io.stdout(PROJECT_PURGE_HELP);
1897
+ return 0;
1898
+ }
1899
+ if (passthrough.length > 0) {
1900
+ throw new Error(`Unknown project purge option: ${passthrough[0]}`);
1901
+ }
1902
+ if (all && path !== undefined) {
1903
+ throw new Error('Cannot specify both a path and --all');
1904
+ }
1905
+ if (interactive && yes) {
1906
+ throw new Error('--interactive cannot be combined with --yes');
1907
+ }
1908
+ const configuredRoot = process.env.CLAUDE_CONFIG_DIR || undefined;
1909
+ const configRoot = resolve(configuredRoot ?? resolve(homedir(), '.claude'));
1910
+ const statePath = configuredRoot
1911
+ ? join(configRoot, '.claude.json')
1912
+ : resolve(homedir(), '.claude.json');
1913
+ const plan = await planClaudeProjectPurge({
1914
+ cwd: process.cwd(),
1915
+ ...(path === undefined ? {} : { path }),
1916
+ ...(all ? { all: true } : {}),
1917
+ configRoot,
1918
+ statePath,
1919
+ });
1920
+ const jsonValue = (result) => ({
1921
+ type: 'project-purge',
1922
+ plan,
1923
+ result: {
1924
+ ...result,
1925
+ failures: result.failures.map((failure) => ({
1926
+ item: failure.item,
1927
+ error: redactSensitiveText(failure.error.message, sensitiveEnvironmentValues(process.env)),
1928
+ })),
1929
+ },
1930
+ });
1931
+ if (dryRun) {
1932
+ const result = await executeClaudeProjectPurge(plan, { dryRun: true });
1933
+ if (json)
1934
+ writeJson(io, jsonValue(result));
1935
+ else {
1936
+ io.stdout(`${plan.items.length} item(s) would be deleted for ${plan.mode === 'all' ? 'all projects' : plan.targetPath}\n`);
1937
+ for (const item of plan.items)
1938
+ io.stdout(` ${item.kind}\t${item.path}\n`);
1939
+ }
1940
+ return 0;
1941
+ }
1942
+ if (plan.items.length === 0) {
1943
+ if (json) {
1944
+ writeJson(io, jsonValue({
1945
+ dryRun: false,
1946
+ aborted: false,
1947
+ deleted: [],
1948
+ skipped: [],
1949
+ failures: [],
1950
+ }));
1951
+ }
1952
+ else
1953
+ io.stdout('No project state found.\n');
1954
+ return 0;
1955
+ }
1956
+ let inputIterator;
1957
+ if (!yes) {
1958
+ const input = io.readStdinLines?.();
1959
+ if (!input)
1960
+ throw new Error('project purge requires --yes without stdin');
1961
+ inputIterator = input[Symbol.asyncIterator]();
1962
+ }
1963
+ const nextAnswer = async () => {
1964
+ const next = await inputIterator?.next();
1965
+ return typeof next?.value === 'string'
1966
+ ? next.value.trim().toLowerCase()
1967
+ : next?.value instanceof Uint8Array
1968
+ ? Buffer.from(next.value).toString('utf8').trim().toLowerCase()
1969
+ : '';
1970
+ };
1971
+ let confirmed = yes;
1972
+ if (!yes && !interactive) {
1973
+ io.stderr(`Delete ${plan.items.length} item(s) for ${plan.mode === 'all' ? 'all projects' : plan.targetPath}? [y/N] `);
1974
+ confirmed = ['y', 'yes'].includes(await nextAnswer());
1975
+ if (!confirmed) {
1976
+ io.stderr('Purge cancelled.\n');
1977
+ return 1;
1978
+ }
1979
+ }
1980
+ const selectItem = interactive
1981
+ ? async (item) => {
1982
+ io.stderr(`Delete ${item.description} (${item.path})? [y/N/a/q] `);
1983
+ const answer = await nextAnswer();
1984
+ if (answer === 'a' || answer === 'all')
1985
+ return 'delete-all';
1986
+ if (answer === 'q' || answer === 'quit')
1987
+ return 'abort';
1988
+ return answer === 'y' || answer === 'yes' ? 'delete' : 'skip';
1989
+ }
1990
+ : undefined;
1991
+ const result = await executeClaudeProjectPurge(plan, {
1992
+ ...(selectItem ? { selectItem } : {}),
1993
+ });
1994
+ if (json) {
1995
+ writeJson(io, jsonValue(result));
1996
+ }
1997
+ else {
1998
+ for (const item of result.deleted)
1999
+ io.stdout(`deleted\t${item.path}\n`);
2000
+ for (const item of result.skipped)
2001
+ io.stdout(`skipped\t${item.path}\n`);
2002
+ for (const failure of result.failures) {
2003
+ io.stderr(`failed\t${failure.item.path}\t${failure.error.message}\n`);
2004
+ }
2005
+ if (result.aborted)
2006
+ io.stderr('Purge cancelled.\n');
2007
+ }
2008
+ return result.failures.length === 0 && !result.aborted ? 0 : 1;
2009
+ }
2010
+ function pluginOutput(io, invocation, value) {
2011
+ if (invocation.legacyJson || invocation.outputFormat !== 'text') {
2012
+ writeJson(io, value);
2013
+ }
2014
+ else if (typeof value === 'string') {
2015
+ io.stdout(`${value}\n`);
2016
+ }
2017
+ else {
2018
+ io.stdout(`${JSON.stringify(value)}\n`);
2019
+ }
2020
+ }
2021
+ function pluginPruneSummary(plan) {
2022
+ const count = plan.candidates.length;
2023
+ return `${count} auto-installed plugin${count === 1 ? '' : 's'} no longer needed at ${plan.scope} scope:\n${plan.candidates.map((plugin) => ` ${plugin.id} (${plugin.version})`).join('\n')}\n`;
2024
+ }
2025
+ async function pluginConfirmation(io) {
2026
+ const input = io.readStdinLines?.();
2027
+ if (!input)
2028
+ return false;
2029
+ const next = await input[Symbol.asyncIterator]().next();
2030
+ const value = typeof next.value === 'string'
2031
+ ? next.value
2032
+ : next.value instanceof Uint8Array
2033
+ ? Buffer.from(next.value).toString('utf8')
2034
+ : '';
2035
+ return ['y', 'yes'].includes(value.trim().toLowerCase());
2036
+ }
2037
+ async function executePluginPruneFlow(configRoot, cwd, scope, invocation, io, uninstalledName) {
2038
+ const structured = invocation.legacyJson || invocation.outputFormat !== 'text';
2039
+ const structuredOutput = (status, value = {}) => {
2040
+ pluginOutput(io, invocation, {
2041
+ type: uninstalledName === undefined
2042
+ ? 'plugin-pruned'
2043
+ : 'plugin-uninstalled-and-pruned',
2044
+ ...(uninstalledName === undefined ? {} : { name: uninstalledName }),
2045
+ scope,
2046
+ status,
2047
+ ...value,
2048
+ });
2049
+ };
2050
+ const plan = await planClaudePluginPrune(configRoot, cwd, scope);
2051
+ if (plan.failedPluginIds) {
2052
+ const message = `Skipped — cannot determine orphans: ${plan.failedPluginIds.join(', ')} failed to load. Fix or uninstall, then retry.`;
2053
+ if (structured) {
2054
+ structuredOutput('skipped', {
2055
+ message,
2056
+ failedPluginIds: plan.failedPluginIds,
2057
+ });
2058
+ }
2059
+ else
2060
+ io.stdout(`${message}\n`);
2061
+ return 0;
2062
+ }
2063
+ if (plan.candidates.length === 0) {
2064
+ const message = plan.autoCount === 0
2065
+ ? `Nothing to prune (no auto-installed plugins at ${scope} scope).`
2066
+ : `Nothing to prune (${plan.autoCount} auto-installed plugin${plan.autoCount === 1 ? '' : 's'} at ${scope} scope, all still needed).`;
2067
+ if (structured) {
2068
+ structuredOutput('complete', {
2069
+ dryRun: invocation.pluginDryRun,
2070
+ removed: [],
2071
+ });
2072
+ }
2073
+ else
2074
+ io.stdout(`${message}\n`);
2075
+ return 0;
2076
+ }
2077
+ if (structured) {
2078
+ if (invocation.pluginDryRun) {
2079
+ structuredOutput('complete', {
2080
+ dryRun: true,
2081
+ removed: [],
2082
+ candidates: plan.candidates,
2083
+ });
2084
+ return 0;
2085
+ }
2086
+ }
2087
+ else {
2088
+ io.stdout(pluginPruneSummary(plan));
2089
+ if (invocation.pluginDryRun) {
2090
+ io.stdout('(dry run — nothing removed)\n');
2091
+ return 0;
2092
+ }
2093
+ }
2094
+ if (!invocation.pluginYes && io.isTTY !== true) {
2095
+ if (structured) {
2096
+ structuredOutput('confirmation-required', {
2097
+ dryRun: false,
2098
+ removed: [],
2099
+ candidates: plan.candidates,
2100
+ });
2101
+ }
2102
+ else {
2103
+ io.stdout('Not a TTY — run `praxis plugin prune -y` to remove.\n');
2104
+ }
2105
+ return 0;
2106
+ }
2107
+ if (!invocation.pluginYes) {
2108
+ if (structured)
2109
+ io.stderr('Remove? [y/N] ');
2110
+ else
2111
+ io.stdout('Remove? [y/N] ');
2112
+ if (!(await pluginConfirmation(io))) {
2113
+ if (structured) {
2114
+ structuredOutput('cancelled', {
2115
+ dryRun: false,
2116
+ removed: [],
2117
+ candidates: plan.candidates,
2118
+ });
2119
+ }
2120
+ else
2121
+ io.stdout('Cancelled.\n');
2122
+ return 0;
2123
+ }
2124
+ }
2125
+ const removed = await executeClaudePluginPrune(configRoot, cwd, plan);
2126
+ if (structured) {
2127
+ structuredOutput('complete', {
2128
+ dryRun: false,
2129
+ removed,
2130
+ });
2131
+ }
2132
+ else {
2133
+ io.stdout(`Removed ${removed.length} auto-installed plugin${removed.length === 1 ? '' : 's'}: ${removed.map((plugin) => plugin.id.split('@')[0]).join(', ')}\n`);
2134
+ }
2135
+ return 0;
2136
+ }
2137
+ async function executePluginTag(path, invocation, io) {
2138
+ try {
2139
+ const result = await tagClaudePlugin({
2140
+ path,
2141
+ ...(invocation.pluginDryRun ? { dryRun: true } : {}),
2142
+ ...(invocation.pluginForce ? { force: true } : {}),
2143
+ ...(invocation.pluginMessage === undefined
2144
+ ? {}
2145
+ : { message: invocation.pluginMessage }),
2146
+ ...(invocation.pluginPush ? { push: true } : {}),
2147
+ ...(invocation.pluginRemote === undefined
2148
+ ? {}
2149
+ : { remote: invocation.pluginRemote }),
2150
+ });
2151
+ if (invocation.legacyJson || invocation.outputFormat !== 'text') {
2152
+ pluginOutput(io, invocation, { type: 'plugin-tagged', ...result });
2153
+ return 0;
2154
+ }
2155
+ for (const warning of result.warnings)
2156
+ io.stdout(`⚠ ${warning}\n`);
2157
+ io.stdout(`Plugin: ${result.name}\nVersion: ${result.version} (from plugin.json)\n${result.marketplaceEntry === undefined ? '' : `Marketplace entry: plugins[${result.marketplaceEntry.index}] in ${result.marketplaceEntry.path}${result.marketplaceEntry.version === undefined ? '' : ` (version: ${result.marketplaceEntry.version})`}\n`}Tag: ${result.tag}\n\n`);
2158
+ const forceFlag = result.force ? ' --force' : '';
2159
+ if (result.dryRun) {
2160
+ io.stdout(`✔ Dry run — would create tag ${result.tag} at HEAD in ${result.repository}\n git -C ${result.repository} tag${forceFlag} -a ${result.tag} -m ${JSON.stringify(result.message)}\n git -C ${result.repository} push${forceFlag} ${result.remote} refs/tags/${result.tag}\n`);
2161
+ }
2162
+ else {
2163
+ io.stdout(`✔ Created tag ${result.tag}\n`);
2164
+ io.stdout(result.pushed
2165
+ ? `✔ Pushed to ${result.remote}\n`
2166
+ : ` Push with: git -C ${result.repository} push${forceFlag} ${result.remote} refs/tags/${result.tag}\n`);
2167
+ }
2168
+ return 0;
2169
+ }
2170
+ catch (error) {
2171
+ io.stdout(`✘ ${error instanceof Error ? error.message : String(error)}\n`);
2172
+ return 1;
2173
+ }
2174
+ }
2175
+ function isClaudeMarketplacePluginId(value) {
2176
+ const separator = value.lastIndexOf('@');
2177
+ return (separator > 0 &&
2178
+ separator < value.length - 1 &&
2179
+ !value.includes('/') &&
2180
+ !value.includes('\\'));
2181
+ }
2182
+ function formatPluginDetails(details) {
2183
+ const { plugin, components, tokenEstimate } = details;
2184
+ const lines = [
2185
+ `${plugin.name}${plugin.version ? ` ${plugin.version}` : ''}`,
2186
+ ...(plugin.description ? [` ${plugin.description}`] : []),
2187
+ ` Source: ${plugin.source}`,
2188
+ '',
2189
+ 'Component inventory',
2190
+ ];
2191
+ const groups = [
2192
+ ['Skills', [...components.skills, ...components.commands].sort(), ''],
2193
+ ['Agents', components.agents, ''],
2194
+ ['Hooks', components.hooks, ' (harness-only — no model context cost)'],
2195
+ [
2196
+ 'MCP servers',
2197
+ components.mcpServers,
2198
+ ' (tool schemas resolved at runtime; not counted)',
2199
+ ],
2200
+ [
2201
+ 'LSP servers',
2202
+ components.lspServers,
2203
+ ' (out-of-process tooling; no model context cost)',
2204
+ ],
2205
+ ];
2206
+ for (const [label, values, annotation] of groups) {
2207
+ lines.push(` ${label} (${values.length})${values.length === 0 ? '' : ` ${values.join(', ')}`}${values.length > 0 ? annotation : ''}`);
2208
+ }
2209
+ lines.push('', 'Projected token cost', ` Always-on: ~${tokenEstimate.alwaysOn.toLocaleString()} tok added to every session`);
2210
+ const textComponents = details.componentCosts;
2211
+ if (textComponents.length === 0)
2212
+ return lines.join('\n');
2213
+ lines.push('', 'Per-component (rounded)', ' component always-on on-invoke');
2214
+ const width = Math.max(9, ...textComponents.map((component) => component.name.length));
2215
+ for (const component of textComponents) {
2216
+ lines.push(` ${component.name.padEnd(width)} ${formatPluginTokenEstimate(component.alwaysOn).padStart(9)} ${formatPluginTokenEstimate(component.onInvoke).padStart(9)}`);
2217
+ }
2218
+ lines.push('', ' On-invoke cost is paid each time a skill or agent fires.', ' Token counts are estimates and may differ from actual usage.');
2219
+ return lines.join('\n');
2220
+ }
2221
+ function formatPluginTokenEstimate(value) {
2222
+ if (value < 20)
2223
+ return '< 20';
2224
+ return `~${Math.round(value / 10) * 10}`;
2225
+ }
2226
+ async function pluginDetailsForName(configRoot, cwd, name) {
2227
+ const [native, local] = await Promise.all([
2228
+ listNativePluginRecords(configRoot, cwd),
2229
+ readPluginRegistry(configRoot),
2230
+ ]);
2231
+ const entries = [...native, ...local];
2232
+ const entry = entries.find((candidate) => candidate.name === name) ??
2233
+ entries.find((candidate) => candidate.name.startsWith(`${name}@`));
2234
+ if (!entry) {
2235
+ throw new Error(`Plugin "${name}" not found. Run \`praxis plugin list\` to see installed plugins.`);
2236
+ }
2237
+ return describeClaudePlugin(entry.path, entry.source);
2238
+ }
2239
+ async function executePluginCommand(args, invocation, io) {
2240
+ const requestedAction = args[1];
2241
+ const action = requestedAction === 'i'
2242
+ ? 'install'
2243
+ : requestedAction === 'new'
2244
+ ? 'init'
2245
+ : requestedAction === 'remove'
2246
+ ? 'uninstall'
2247
+ : requestedAction;
2248
+ const cwd = process.cwd();
2249
+ const configRoot = resolve(process.env.CLAUDE_CONFIG_DIR ?? resolve(homedir(), '.claude'));
2250
+ const requestedScope = invocation.mcpScope;
2251
+ const installScope = requestedScope ?? 'user';
2252
+ if (!action || action === 'help') {
2253
+ io.stdout(PLUGIN_HELP);
2254
+ return 0;
2255
+ }
2256
+ if (invocation.pluginAvailable && action !== 'list') {
2257
+ throw new Error('--available is only valid with plugin list');
2258
+ }
2259
+ if (invocation.pluginAll || invocation.agentsAll) {
2260
+ if (action !== 'disable') {
2261
+ throw new Error('--all is only valid with plugin disable');
2262
+ }
2263
+ }
2264
+ if ((invocation.pluginKeepData || invocation.pluginPrune) &&
2265
+ action !== 'uninstall') {
2266
+ throw new Error('--keep-data and --prune are only valid with plugin uninstall');
2267
+ }
2268
+ if (invocation.pluginYes &&
2269
+ !['uninstall', 'prune', 'autoremove'].includes(action)) {
2270
+ throw new Error('--yes is only valid with plugin uninstall or prune');
2271
+ }
2272
+ if (invocation.pluginStrict && action !== 'validate') {
2273
+ throw new Error('--strict is only valid with plugin validate');
2274
+ }
2275
+ if (invocation.pluginConfig.length > 0 && action !== 'install') {
2276
+ throw new Error('--config is only valid with plugin install');
2277
+ }
2278
+ if ((invocation.pluginAuthor !== undefined ||
2279
+ invocation.pluginAuthorEmail !== undefined ||
2280
+ invocation.pluginDescription !== undefined ||
2281
+ invocation.pluginWith.length > 0) &&
2282
+ action !== 'init') {
2283
+ throw new Error('--author, --author-email, --description, and --with are only valid with plugin init');
2284
+ }
2285
+ if (invocation.pluginForce && !['init', 'tag'].includes(action)) {
2286
+ throw new Error('--force is only valid with plugin init or tag');
2287
+ }
2288
+ if (invocation.pluginDryRun &&
2289
+ !['prune', 'autoremove', 'tag'].includes(action)) {
2290
+ throw new Error('--dry-run is only valid with plugin prune or tag');
2291
+ }
2292
+ if ((invocation.pluginMessage !== undefined ||
2293
+ invocation.pluginPush ||
2294
+ invocation.pluginRemote !== undefined) &&
2295
+ action !== 'tag') {
2296
+ throw new Error('--message, --push, and --remote are only valid with plugin tag');
2297
+ }
2298
+ if (requestedScope !== undefined &&
2299
+ ![
2300
+ 'install',
2301
+ 'uninstall',
2302
+ 'enable',
2303
+ 'disable',
2304
+ 'update',
2305
+ 'marketplace',
2306
+ 'prune',
2307
+ 'autoremove',
2308
+ ].includes(action)) {
2309
+ throw new Error(`--scope is not valid with plugin ${action}`);
2310
+ }
2311
+ if (action === 'marketplace') {
2312
+ const requestedMarketplaceAction = args[2];
2313
+ const marketplaceAction = requestedMarketplaceAction === 'rm'
2314
+ ? 'remove'
2315
+ : requestedMarketplaceAction;
2316
+ if (!marketplaceAction || marketplaceAction === 'help') {
2317
+ io.stdout(PLUGIN_MARKETPLACE_HELP);
2318
+ return 0;
2319
+ }
2320
+ if (marketplaceAction === 'list') {
2321
+ if (args.length !== 3)
2322
+ throw new Error('plugin marketplace list takes no operands');
2323
+ if (requestedScope !== undefined) {
2324
+ throw new Error('--scope is not valid with plugin marketplace list');
2325
+ }
2326
+ pluginOutput(io, invocation, await readClaudeKnownMarketplaces(configRoot));
2327
+ return 0;
2328
+ }
2329
+ if (marketplaceAction === 'add') {
2330
+ if (args.length !== 4)
2331
+ throw new Error('plugin marketplace add requires a source');
2332
+ pluginOutput(io, invocation, {
2333
+ type: 'plugin-marketplace-added',
2334
+ marketplace: await addClaudeMarketplace(configRoot, cwd, args[3], installScope, invocation.pluginSparsePaths),
2335
+ });
2336
+ return 0;
2337
+ }
2338
+ if (marketplaceAction === 'remove') {
2339
+ if (args.length !== 4)
2340
+ throw new Error('plugin marketplace remove requires a name');
2341
+ await removeClaudeMarketplace(configRoot, cwd, args[3], requestedScope);
2342
+ pluginOutput(io, invocation, {
2343
+ type: 'plugin-marketplace-removed',
2344
+ name: args[3],
2345
+ });
2346
+ return 0;
2347
+ }
2348
+ if (marketplaceAction === 'update') {
2349
+ if (args.length > 4)
2350
+ throw new Error('plugin marketplace update accepts at most one name');
2351
+ if (requestedScope !== undefined) {
2352
+ throw new Error('--scope is not valid with plugin marketplace update');
2353
+ }
2354
+ pluginOutput(io, invocation, {
2355
+ type: 'plugin-marketplace-updated',
2356
+ marketplaces: await updateClaudeMarketplace(configRoot, args[3]),
2357
+ });
2358
+ return 0;
2359
+ }
2360
+ throw new Error(`Unknown plugin marketplace command: ${marketplaceAction}`);
2361
+ }
2362
+ if (action === 'list') {
2363
+ if (args.length !== 2)
2364
+ throw new Error('plugin list takes no operands');
2365
+ const registry = await readPluginRegistry(configRoot);
2366
+ const native = await listNativePluginRecords(configRoot, cwd);
2367
+ const installed = await Promise.all([...native, ...registry].map(async (entry) => {
2368
+ try {
2369
+ const record = await validateClaudePlugin(entry.path);
2370
+ return {
2371
+ ...entry,
2372
+ status: entry.enabled ? 'enabled' : 'disabled',
2373
+ valid: true,
2374
+ version: record.version ?? entry.version,
2375
+ };
2376
+ }
2377
+ catch (error) {
2378
+ return {
2379
+ ...entry,
2380
+ status: entry.enabled ? 'enabled' : 'disabled',
2381
+ valid: false,
2382
+ error: error instanceof Error ? error.message : String(error),
2383
+ };
2384
+ }
2385
+ }));
2386
+ const includeAvailable = invocation.pluginAvailable;
2387
+ pluginOutput(io, invocation, includeAvailable
2388
+ ? {
2389
+ installed,
2390
+ available: await listClaudeMarketplaceAvailablePlugins(configRoot, cwd),
2391
+ }
2392
+ : installed);
2393
+ return 0;
2394
+ }
2395
+ if (action === 'details') {
2396
+ if (args.length !== 3)
2397
+ throw new Error('plugin details requires a plugin name');
2398
+ const details = await pluginDetailsForName(configRoot, cwd, args[2]);
2399
+ pluginOutput(io, invocation, invocation.legacyJson || invocation.outputFormat !== 'text'
2400
+ ? details
2401
+ : formatPluginDetails(details));
2402
+ return 0;
2403
+ }
2404
+ if (action === 'install') {
2405
+ if (args.length !== 3)
2406
+ throw new Error('plugin install requires a plugin path, URL, or plugin@marketplace id');
2407
+ const source = args[2];
2408
+ if (isClaudeMarketplacePluginId(source)) {
2409
+ const plugin = await installClaudeMarketplacePlugin(configRoot, cwd, source, installScope);
2410
+ const config = await saveClaudePluginConfig(configRoot, cwd, installScope, plugin.id, plugin.installPath, invocation.pluginConfig);
2411
+ pluginOutput(io, invocation, {
2412
+ type: 'plugin-installed',
2413
+ plugin,
2414
+ ...(config.warnings.length === 0 ? {} : { warnings: config.warnings }),
2415
+ });
2416
+ return 0;
2417
+ }
2418
+ if (requestedScope !== undefined) {
2419
+ throw new Error('--scope is only supported when installing plugin@marketplace');
2420
+ }
2421
+ const plugin = await installClaudePlugin(configRoot, source);
2422
+ const config = await saveClaudePluginConfig(configRoot, cwd, installScope, plugin.name, plugin.path, invocation.pluginConfig);
2423
+ pluginOutput(io, invocation, {
2424
+ type: 'plugin-installed',
2425
+ plugin,
2426
+ ...(config.warnings.length === 0 ? {} : { warnings: config.warnings }),
2427
+ });
2428
+ return 0;
2429
+ }
2430
+ if (action === 'uninstall') {
2431
+ if (args.length !== 3)
2432
+ throw new Error('plugin uninstall requires a plugin name');
2433
+ const name = args[2];
2434
+ if (isClaudeMarketplacePluginId(name))
2435
+ await uninstallNativePlugin(configRoot, cwd, name, installScope, !invocation.pluginKeepData);
2436
+ else
2437
+ await uninstallClaudePlugin(configRoot, name, !invocation.pluginKeepData);
2438
+ if (!invocation.pluginPrune) {
2439
+ pluginOutput(io, invocation, { type: 'plugin-uninstalled', name });
2440
+ return 0;
2441
+ }
2442
+ const structured = invocation.legacyJson || invocation.outputFormat !== 'text';
2443
+ if (!structured) {
2444
+ pluginOutput(io, invocation, { type: 'plugin-uninstalled', name });
2445
+ }
2446
+ return executePluginPruneFlow(configRoot, cwd, installScope, invocation, io, structured ? name : undefined);
2447
+ }
2448
+ if (action === 'enable' || action === 'disable') {
2449
+ if (action === 'disable' && invocation.pluginAll) {
2450
+ if (args.length !== 2) {
2451
+ throw new Error('plugin disable --all takes no plugin name');
2452
+ }
2453
+ if (requestedScope !== undefined) {
2454
+ throw new Error('--scope cannot be used with plugin disable --all');
2455
+ }
2456
+ const native = await disableAllNativePlugins(configRoot, cwd);
2457
+ const local = (await readPluginRegistry(configRoot)).filter((plugin) => plugin.enabled);
2458
+ const disabledLocal = await Promise.all(local.map((plugin) => setClaudePluginEnabled(configRoot, plugin.name, false)));
2459
+ pluginOutput(io, invocation, {
2460
+ type: 'plugins-disabled',
2461
+ plugins: [...native, ...disabledLocal],
2462
+ });
2463
+ return 0;
2464
+ }
2465
+ if (args.length !== 3)
2466
+ throw new Error(`plugin ${action} requires a plugin name`);
2467
+ const name = args[2];
2468
+ const plugin = isClaudeMarketplacePluginId(name)
2469
+ ? await setNativePluginEnabled(configRoot, cwd, name, action === 'enable', requestedScope)
2470
+ : await setClaudePluginEnabled(configRoot, name, action === 'enable');
2471
+ pluginOutput(io, invocation, {
2472
+ type: action === 'enable' ? 'plugin-enabled' : 'plugin-disabled',
2473
+ plugin,
2474
+ });
2475
+ return 0;
2476
+ }
2477
+ if (action === 'update') {
2478
+ if (args.length !== 3)
2479
+ throw new Error('plugin update requires a plugin name');
2480
+ const name = args[2];
2481
+ pluginOutput(io, invocation, {
2482
+ type: 'plugin-updated',
2483
+ plugin: isClaudeMarketplacePluginId(name)
2484
+ ? await updateNativePlugin(configRoot, cwd, name, installScope)
2485
+ : await updateClaudePlugin(configRoot, name),
2486
+ });
2487
+ return 0;
2488
+ }
2489
+ if (action === 'prune' || action === 'autoremove') {
2490
+ if (args.length !== 2)
2491
+ throw new Error(`plugin ${action} takes no operands`);
2492
+ return executePluginPruneFlow(configRoot, cwd, installScope, invocation, io);
2493
+ }
2494
+ if (action === 'tag') {
2495
+ if (args.length > 3)
2496
+ throw new Error('plugin tag accepts at most one path');
2497
+ return executePluginTag(args[2] ?? cwd, invocation, io);
2498
+ }
2499
+ if (action === 'validate') {
2500
+ if (args.length !== 3)
2501
+ throw new Error('plugin validate requires a plugin directory');
2502
+ const path = resolve(args[2]);
2503
+ let marketplace = false;
2504
+ try {
2505
+ await access(join(path, '.claude-plugin', 'marketplace.json'));
2506
+ marketplace = true;
2507
+ }
2508
+ catch {
2509
+ // A plugin manifest remains the default validation target.
2510
+ }
2511
+ pluginOutput(io, invocation, {
2512
+ type: 'plugin-valid',
2513
+ ...(marketplace
2514
+ ? {
2515
+ marketplace: await validateClaudeMarketplace(path, {
2516
+ strict: invocation.pluginStrict,
2517
+ }),
2518
+ }
2519
+ : {
2520
+ plugin: await validateClaudePlugin(path, {
2521
+ strict: invocation.pluginStrict,
2522
+ }),
2523
+ }),
2524
+ });
2525
+ return 0;
2526
+ }
2527
+ if (action === 'init') {
2528
+ if (args.length < 3 || args.length > 4) {
2529
+ throw new Error('plugin init requires a name');
2530
+ }
2531
+ const nativeOptionsUsed = invocation.pluginAuthor !== undefined ||
2532
+ invocation.pluginAuthorEmail !== undefined ||
2533
+ invocation.pluginDescription !== undefined ||
2534
+ invocation.pluginForce ||
2535
+ invocation.pluginWith.length > 0;
2536
+ if (args.length === 4 && nativeOptionsUsed) {
2537
+ throw new Error('plugin init native syntax accepts one name; use plugin init <directory> <name> without native init options for legacy scaffolding');
2538
+ }
2539
+ const options = {
2540
+ ...(invocation.pluginAuthor === undefined
2541
+ ? {}
2542
+ : { author: invocation.pluginAuthor }),
2543
+ ...(invocation.pluginAuthorEmail === undefined
2544
+ ? {}
2545
+ : { authorEmail: invocation.pluginAuthorEmail }),
2546
+ ...(invocation.pluginDescription === undefined
2547
+ ? {}
2548
+ : { description: invocation.pluginDescription }),
2549
+ ...(invocation.pluginForce ? { force: true } : {}),
2550
+ ...(invocation.pluginWith.length === 0
2551
+ ? {}
2552
+ : {
2553
+ with: invocation.pluginWith,
2554
+ }),
2555
+ };
2556
+ const nativeLayout = args.length === 3;
2557
+ const path = nativeLayout
2558
+ ? join(configRoot, 'skills', args[2])
2559
+ : args[2];
2560
+ const name = nativeLayout ? args[2] : args[3];
2561
+ const plugin = await initClaudePlugin(path, name, {
2562
+ ...options,
2563
+ nativeLayout,
2564
+ });
2565
+ pluginOutput(io, invocation, {
2566
+ type: 'plugin-initialized',
2567
+ path: resolve(path),
2568
+ ...(name ? { name } : {}),
2569
+ plugin,
2570
+ });
2571
+ return 0;
2572
+ }
2573
+ throw new Error(`Unknown plugin command: ${action}`);
2574
+ }
2575
+ async function executeMcpCommand(args, invocation, io, dependencies, signal) {
2576
+ const action = args[1];
2577
+ if (!action || action === 'help') {
2578
+ io.stdout(MCP_HELP);
2579
+ return 0;
2580
+ }
2581
+ const configRoot = resolve(process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude'));
2582
+ const management = new ClaudeMcpManagement({
2583
+ configRoot,
2584
+ cwd: process.cwd(),
2585
+ });
2586
+ const scope = invocation.mcpScope ? mcpScope(invocation.mcpScope) : undefined;
2587
+ if (action === 'login') {
2588
+ if (args.length !== 3)
2589
+ throw new Error('mcp login requires a server name');
2590
+ const record = await management.get(args[2], scope);
2591
+ const server = mcpOAuthServerIdentity(record.name, record.config);
2592
+ const oauth = mcpOauthOptions(record.config);
2593
+ const clientSecret = oauth.clientId
2594
+ ? await new ClaudeMcpOAuthStore({ configRoot }).readClientSecret(server)
2595
+ : undefined;
2596
+ await (dependencies.mcpAuthenticate ?? authenticateMcpServer)({
2597
+ configRoot,
2598
+ server,
2599
+ ...oauth,
2600
+ ...(clientSecret ? { clientSecret } : {}),
2601
+ noBrowser: invocation.mcpNoBrowser,
2602
+ write: (message) => io.stdout(message),
2603
+ });
2604
+ io.stdout(`Authenticated with "${record.name}". Its tools are now available in Praxis.\n`);
2605
+ return 0;
2606
+ }
2607
+ if (action === 'logout') {
2608
+ if (args.length !== 3)
2609
+ throw new Error('mcp logout requires a server name');
2610
+ const record = await management.get(args[2], scope);
2611
+ const server = mcpOAuthServerIdentity(record.name, record.config);
2612
+ await new ClaudeMcpOAuthStore({ configRoot }).clear(server);
2613
+ io.stdout(`Signed out of "${record.name}". Run \`praxis mcp login ${record.name}\` to authenticate again.\n`);
2614
+ return 0;
2615
+ }
2616
+ if (action === 'serve') {
2617
+ if (args.length !== 2)
2618
+ throw new Error('mcp serve takes no operands');
2619
+ if (scope)
2620
+ throw new Error('--scope is not valid with mcp serve');
2621
+ await (dependencies.mcpServe ?? servePraxisMcpStdio)({
2622
+ cwd: process.cwd(),
2623
+ debug: invocation.mcpDebug,
2624
+ verbose: invocation.verbose,
2625
+ ...(signal ? { signal } : {}),
2626
+ writeError: (message) => io.stderr(message),
2627
+ createToolRegistry: async () => {
2628
+ const service = await dependencies.createService({
2629
+ eventSink: () => undefined,
2630
+ requireProvider: false,
2631
+ exposeToolRegistry: true,
2632
+ approveTool: async () => true,
2633
+ controls: invocation,
2634
+ ...(signal ? { signal } : {}),
2635
+ });
2636
+ const registry = service.toolRegistry;
2637
+ if (!registry) {
2638
+ await service.close?.();
2639
+ throw new Error('MCP tool registry is unavailable');
2640
+ }
2641
+ return {
2642
+ definitions: registry.definitions.bind(registry),
2643
+ prepare: registry.prepare.bind(registry),
2644
+ execute: registry.execute.bind(registry),
2645
+ close: async () => service.close?.(),
2646
+ };
2647
+ },
2648
+ createAgentService: ({ agent, model, permissionMode, worktree, eventSink: agentEventSink, }) => dependencies.createService({
2649
+ eventSink: agentEventSink,
2650
+ requireProvider: true,
2651
+ approveTool: async () => true,
2652
+ ...(agent ? { agent } : {}),
2653
+ controls: {
2654
+ ...invocation,
2655
+ ...(model ? { model } : {}),
2656
+ ...(permissionMode ? { permissionMode } : {}),
2657
+ ...(worktree ? { worktreeRequested: true } : {}),
2658
+ },
2659
+ ...(signal ? { signal } : {}),
2660
+ }),
2661
+ });
2662
+ return 0;
2663
+ }
2664
+ if (action === 'list') {
2665
+ if (args.length !== 2)
2666
+ throw new Error('mcp list takes no operands');
2667
+ const servers = await management.list(scope);
2668
+ if (invocation.legacyJson || invocation.outputFormat !== 'text') {
2669
+ mcpOutput(io, invocation, {
2670
+ type: 'mcp-list',
2671
+ servers: servers.map(mcpRecordJson),
2672
+ });
2673
+ }
2674
+ else {
2675
+ for (const server of servers) {
2676
+ io.stdout(`${server.name}\t${server.scope}\t${JSON.stringify(server.config)}\n`);
2677
+ }
2678
+ }
2679
+ return 0;
2680
+ }
2681
+ if (action === 'get') {
2682
+ if (args.length !== 3)
2683
+ throw new Error('mcp get requires a server name');
2684
+ const server = await management.get(args[2], scope);
2685
+ mcpOutput(io, invocation, {
2686
+ type: 'mcp-server',
2687
+ server: mcpRecordJson(server),
2688
+ });
2689
+ return 0;
2690
+ }
2691
+ if (action === 'add-json') {
2692
+ if (args.length !== 4)
2693
+ throw new Error('mcp add-json requires name and JSON');
2694
+ let config;
2695
+ try {
2696
+ config = JSON.parse(args[3]);
2697
+ }
2698
+ catch (error) {
2699
+ throw new Error('mcp add-json requires valid JSON', { cause: error });
2700
+ }
2701
+ if (!config || typeof config !== 'object' || Array.isArray(config)) {
2702
+ throw new Error('mcp add-json server config must be an object');
2703
+ }
2704
+ const name = args[2];
2705
+ const serverConfig = config;
2706
+ const oauth = serverConfig.oauth;
2707
+ const hasClientId = oauth !== null &&
2708
+ typeof oauth === 'object' &&
2709
+ !Array.isArray(oauth) &&
2710
+ typeof oauth.clientId === 'string' &&
2711
+ oauth.clientId !== '';
2712
+ const clientSecret = invocation.mcpClientSecret && hasClientId
2713
+ ? await readMcpClientSecret()
2714
+ : undefined;
2715
+ const targetScope = scope ?? 'local';
2716
+ const previous = clientSecret
2717
+ ? await existingMcpServer(management, name, targetScope)
2718
+ : undefined;
2719
+ const server = await management.add(name, serverConfig, targetScope);
2720
+ if (clientSecret) {
2721
+ try {
2722
+ await new ClaudeMcpOAuthStore({ configRoot }).saveClientSecret(mcpOAuthServerIdentity(name, serverConfig), clientSecret);
2723
+ }
2724
+ catch (error) {
2725
+ try {
2726
+ if (previous)
2727
+ await management.add(name, previous.config, targetScope);
2728
+ else
2729
+ await management.remove(name, targetScope);
2730
+ }
2731
+ catch {
2732
+ throw new Error('MCP client secret could not be stored and configuration rollback failed', { cause: error });
2733
+ }
2734
+ throw error;
2735
+ }
2736
+ }
2737
+ mcpOutput(io, invocation, {
2738
+ type: 'mcp-added',
2739
+ server: mcpRecordJson(server),
2740
+ });
2741
+ return 0;
2742
+ }
2743
+ if (action === 'add') {
2744
+ if (args.length < 3)
2745
+ throw new Error("missing required argument 'name'");
2746
+ if (args.length < 4)
2747
+ throw new Error("missing required argument 'commandOrUrl'");
2748
+ const name = args[2];
2749
+ const commandOrUrl = args[3];
2750
+ const transport = invocation.mcpTransport ?? 'stdio';
2751
+ const commandArgs = args.slice(4);
2752
+ if (transport === 'stdio') {
2753
+ if (invocation.mcpClientId ||
2754
+ invocation.mcpClientSecret ||
2755
+ invocation.mcpCallbackPort) {
2756
+ io.stderr('Warning: --client-id, --client-secret, and --callback-port are only supported for HTTP/SSE transports and will be ignored for stdio.\n');
2757
+ }
2758
+ if (!invocation.mcpTransport && isLikelyMcpUrl(commandOrUrl)) {
2759
+ io.stderr(`\nWarning: The command "${commandOrUrl}" looks like a URL, but is being interpreted as a stdio server as --transport was not specified.\n`);
2760
+ io.stderr(`If this is an HTTP server, use: praxis mcp add --transport http ${name} ${commandOrUrl}\n`);
2761
+ io.stderr(`If this is an SSE server, use: praxis mcp add --transport sse ${name} ${commandOrUrl}\n`);
2762
+ }
2763
+ const server = await management.add(name, {
2764
+ type: 'stdio',
2765
+ command: commandOrUrl,
2766
+ args: commandArgs,
2767
+ env: mcpEnvironment(invocation.mcpEnv),
2768
+ }, scope ?? 'local');
2769
+ mcpAddedOutput(io, invocation, server, transport, commandOrUrl, commandArgs, undefined);
2770
+ return 0;
2771
+ }
2772
+ const headers = mcpHeaders(invocation.mcpHeaders);
2773
+ const callbackPort = mcpCallbackPort(invocation.mcpCallbackPort);
2774
+ const oauth = invocation.mcpClientId || callbackPort
2775
+ ? {
2776
+ ...(invocation.mcpClientId
2777
+ ? { clientId: invocation.mcpClientId }
2778
+ : {}),
2779
+ ...(callbackPort ? { callbackPort } : {}),
2780
+ }
2781
+ : undefined;
2782
+ const clientSecret = invocation.mcpClientSecret && invocation.mcpClientId
2783
+ ? await readMcpClientSecret()
2784
+ : undefined;
2785
+ const config = {
2786
+ type: transport,
2787
+ url: commandOrUrl,
2788
+ ...(headers ? { headers } : {}),
2789
+ ...(oauth ? { oauth } : {}),
2790
+ };
2791
+ const targetScope = scope ?? 'local';
2792
+ const previous = clientSecret
2793
+ ? await existingMcpServer(management, name, targetScope)
2794
+ : undefined;
2795
+ const server = await management.add(name, config, targetScope);
2796
+ if (clientSecret) {
2797
+ try {
2798
+ await new ClaudeMcpOAuthStore({ configRoot }).saveClientSecret(mcpOAuthServerIdentity(name, config), clientSecret);
2799
+ }
2800
+ catch (error) {
2801
+ try {
2802
+ if (previous) {
2803
+ await management.add(name, previous.config, targetScope);
2804
+ }
2805
+ else {
2806
+ await management.remove(name, targetScope);
2807
+ }
2808
+ }
2809
+ catch {
2810
+ throw new Error('MCP client secret could not be stored and configuration rollback failed', { cause: error });
2811
+ }
2812
+ throw error;
2813
+ }
2814
+ }
2815
+ mcpAddedOutput(io, invocation, server, transport, commandOrUrl, commandArgs, headers);
2816
+ return 0;
2817
+ }
2818
+ if (action === 'remove') {
2819
+ if (args.length !== 3)
2820
+ throw new Error('mcp remove requires a server name');
2821
+ const server = await management.remove(args[2], scope);
2822
+ mcpOutput(io, invocation, {
2823
+ type: 'mcp-removed',
2824
+ server: mcpRecordJson(server),
2825
+ });
2826
+ return 0;
2827
+ }
2828
+ if (action === 'reset-project-choices') {
2829
+ if (args.length !== 2) {
2830
+ throw new Error('mcp reset-project-choices takes no operands');
2831
+ }
2832
+ await management.resetProjectChoices();
2833
+ mcpOutput(io, invocation, 'Reset project MCP choices');
2834
+ return 0;
2835
+ }
2836
+ throw new Error(`Unknown mcp command: ${action}`);
2837
+ }
2838
+ function eventSink(io, outputFormat, legacyJson = false) {
2839
+ const sensitiveValues = sensitiveEnvironmentValues(process.env);
2840
+ if (legacyJson) {
2841
+ return (event) => writeJson(io, event.type === 'warning' || event.type === 'failed'
2842
+ ? {
2843
+ ...event,
2844
+ message: redactSensitiveText(event.message, sensitiveValues),
2845
+ }
2846
+ : event);
2847
+ }
2848
+ if (outputFormat !== 'text')
2849
+ return () => undefined;
2850
+ return (event) => {
2851
+ if (event.type === 'text-delta')
2852
+ io.stdout(event.delta);
2853
+ if (event.type === 'user-message')
2854
+ io.stdout(`\n${event.message}\n`);
2855
+ if (event.type === 'warning') {
2856
+ io.stderr(`Warning: ${redactSensitiveText(event.message, sensitiveValues)}\n`);
2857
+ }
2858
+ };
2859
+ }
2860
+ function requireValue(value, label) {
2861
+ if (!value)
2862
+ throw new Error(`${label} is required`);
2863
+ return value;
2864
+ }
2865
+ function promptFrom(values) {
2866
+ const prompt = values.join(' ').trim();
2867
+ if (prompt.length === 0)
2868
+ throw new Error('Prompt is required');
2869
+ return prompt;
2870
+ }
2871
+ function backgroundWorkerArgv(argv) {
2872
+ const filtered = [];
2873
+ let optionsEnded = false;
2874
+ for (let index = 0; index < argv.length; index += 1) {
2875
+ const value = argv[index];
2876
+ if (value === undefined)
2877
+ continue;
2878
+ if (!optionsEnded && value === '--') {
2879
+ optionsEnded = true;
2880
+ filtered.push(value);
2881
+ continue;
2882
+ }
2883
+ if (!optionsEnded && (value === '--bg' || value === '--background')) {
2884
+ continue;
2885
+ }
2886
+ if (!optionsEnded && value === '--session-id') {
2887
+ index += 1;
2888
+ continue;
2889
+ }
2890
+ if (!optionsEnded && value.startsWith('--session-id='))
2891
+ continue;
2892
+ filtered.push(value);
2893
+ }
2894
+ return filtered;
2895
+ }
2896
+ function agentDashboardWorkerArgv(invocation) {
2897
+ const argv = [];
2898
+ if (invocation.model !== undefined)
2899
+ argv.push('--model', invocation.model);
2900
+ if (invocation.effort !== undefined)
2901
+ argv.push('--effort', invocation.effort);
2902
+ if (invocation.permissionMode !== 'default') {
2903
+ argv.push('--permission-mode', invocation.permissionMode);
2904
+ }
2905
+ if (invocation.dangerouslySkipPermissions) {
2906
+ argv.push('--dangerously-skip-permissions');
2907
+ }
2908
+ if (invocation.allowDangerouslySkipPermissions) {
2909
+ argv.push('--allow-dangerously-skip-permissions');
2910
+ }
2911
+ if (invocation.agent !== undefined)
2912
+ argv.push('--agent', invocation.agent);
2913
+ for (const directory of invocation.addDirectories) {
2914
+ argv.push('--add-dir', directory);
2915
+ }
2916
+ for (const config of invocation.mcpConfigs) {
2917
+ argv.push('--mcp-config', config);
2918
+ }
2919
+ if (invocation.strictMcpConfig)
2920
+ argv.push('--strict-mcp-config');
2921
+ if (invocation.settings !== undefined) {
2922
+ argv.push('--settings', invocation.settings);
2923
+ }
2924
+ if (invocation.settingSources !== undefined) {
2925
+ argv.push('--setting-sources', invocation.settingSources.join(','));
2926
+ }
2927
+ for (const directory of invocation.pluginDirectories) {
2928
+ argv.push('--plugin-dir', directory);
2929
+ }
2930
+ return argv;
2931
+ }
2932
+ function requireTopLevelAgentManager(dependencies) {
2933
+ if (!dependencies.topLevelAgents) {
2934
+ throw new Error('Top-level agent manager unavailable');
2935
+ }
2936
+ return dependencies.topLevelAgents;
2937
+ }
2938
+ function assertAgentsOptionAllowlist(argv) {
2939
+ const valued = new Set([
2940
+ '--add-dir',
2941
+ '--agent',
2942
+ '--cwd',
2943
+ '--effort',
2944
+ '--mcp-config',
2945
+ '--model',
2946
+ '--permission-mode',
2947
+ '--plugin-dir',
2948
+ '--setting-sources',
2949
+ '--settings',
2950
+ ]);
2951
+ const flags = new Set([
2952
+ '--all',
2953
+ '--allow-dangerously-skip-permissions',
2954
+ '--dangerously-skip-permissions',
2955
+ '--json',
2956
+ '--strict-mcp-config',
2957
+ ]);
2958
+ for (let index = 0; index < argv.length; index += 1) {
2959
+ const value = argv[index];
2960
+ if (value === 'agents')
2961
+ continue;
2962
+ if (value === '--')
2963
+ throw new Error('Unexpected operand for agents');
2964
+ const option = value?.split('=', 1)[0];
2965
+ if (option === '-h' || option === '--help')
2966
+ continue;
2967
+ if (option && flags.has(option)) {
2968
+ if (value.includes('='))
2969
+ throw new Error(`Option does not take a value: ${option}`);
2970
+ continue;
2971
+ }
2972
+ if (option && valued.has(option)) {
2973
+ if (!value.includes('=')) {
2974
+ index += 1;
2975
+ if (argv[index] === undefined)
2976
+ throw new Error(`Missing value for ${option}`);
2977
+ }
2978
+ continue;
2979
+ }
2980
+ if (value?.startsWith('-'))
2981
+ throw new Error(`${value} is not valid with agents`);
2982
+ throw new Error(`Unexpected operand for agents: ${value}`);
2983
+ }
2984
+ }
2985
+ function isCancellation(error, signal) {
2986
+ return (error instanceof AgentRunCancelledError ||
2987
+ signal?.aborted === true ||
2988
+ (error instanceof DOMException && error.name === 'AbortError'));
2989
+ }
2990
+ function helpActionAt(argv, commandIndex, actions) {
2991
+ for (let index = commandIndex + 1; index < argv.length; index += 1) {
2992
+ const value = argv[index];
2993
+ if (value === undefined || value === '--')
2994
+ return undefined;
2995
+ if (actions.includes(value))
2996
+ return { value, index };
2997
+ }
2998
+ return undefined;
2999
+ }
3000
+ function pluginActionHelp(action, marketplaceAction) {
3001
+ if (action === 'marketplace') {
3002
+ return (PLUGIN_MARKETPLACE_ACTION_HELP[marketplaceAction ?? ''] ??
3003
+ PLUGIN_MARKETPLACE_HELP);
3004
+ }
3005
+ return PLUGIN_ACTION_HELP[action ?? ''] ?? PLUGIN_HELP;
3006
+ }
3007
+ function printCommandHelp(argv, io) {
3008
+ const hasHelpFlag = argv.includes('--help') || argv.includes('-h');
3009
+ const commandIndex = argv.findIndex((value) => ['agents', 'mcp', 'plugin', 'plugins', 'auto-mode', 'project'].includes(value));
3010
+ if (commandIndex < 0)
3011
+ return false;
3012
+ const command = argv[commandIndex];
3013
+ if (command === 'agents') {
3014
+ if (!hasHelpFlag)
3015
+ return false;
3016
+ io.stdout(AGENTS_HELP);
3017
+ return true;
3018
+ }
3019
+ if (command === 'mcp') {
3020
+ const action = helpActionAt(argv, commandIndex, [
3021
+ 'help',
3022
+ ...Object.keys(MCP_ACTION_HELP),
3023
+ ]);
3024
+ if (!hasHelpFlag && action?.value !== 'help')
3025
+ return false;
3026
+ const target = action?.value === 'help'
3027
+ ? helpActionAt(argv, action.index, Object.keys(MCP_ACTION_HELP))
3028
+ : action;
3029
+ io.stdout(MCP_ACTION_HELP[target?.value ?? ''] ?? MCP_HELP);
3030
+ return true;
3031
+ }
3032
+ if (command === 'plugin' || command === 'plugins') {
3033
+ const initialAction = helpActionAt(argv, commandIndex, [
3034
+ 'help',
3035
+ 'marketplace',
3036
+ ...Object.keys(PLUGIN_ACTION_HELP),
3037
+ ]);
3038
+ let requestedHelp = hasHelpFlag || initialAction?.value === 'help';
3039
+ if (!requestedHelp && initialAction?.value !== 'marketplace') {
3040
+ return false;
3041
+ }
3042
+ const action = initialAction?.value === 'help'
3043
+ ? helpActionAt(argv, initialAction.index, [
3044
+ 'marketplace',
3045
+ ...Object.keys(PLUGIN_ACTION_HELP),
3046
+ ])
3047
+ : initialAction;
3048
+ if (action?.value === 'marketplace') {
3049
+ const initialMarketplaceAction = helpActionAt(argv, action.index, [
3050
+ 'help',
3051
+ ...Object.keys(PLUGIN_MARKETPLACE_ACTION_HELP),
3052
+ ]);
3053
+ const marketplaceAction = initialMarketplaceAction?.value === 'help'
3054
+ ? helpActionAt(argv, initialMarketplaceAction.index, Object.keys(PLUGIN_MARKETPLACE_ACTION_HELP))
3055
+ : initialMarketplaceAction;
3056
+ requestedHelp ||= initialMarketplaceAction?.value === 'help';
3057
+ if (!requestedHelp)
3058
+ return false;
3059
+ io.stdout(pluginActionHelp(action.value, marketplaceAction?.value));
3060
+ return true;
3061
+ }
3062
+ if (!requestedHelp)
3063
+ return false;
3064
+ io.stdout(pluginActionHelp(action?.value));
3065
+ return true;
3066
+ }
3067
+ if (command === 'auto-mode') {
3068
+ const action = helpActionAt(argv, commandIndex, [
3069
+ 'help',
3070
+ ...Object.keys(AUTO_MODE_ACTION_HELP),
3071
+ ]);
3072
+ if (!hasHelpFlag && action?.value !== 'help')
3073
+ return false;
3074
+ const target = action?.value === 'help'
3075
+ ? helpActionAt(argv, action.index, Object.keys(AUTO_MODE_ACTION_HELP))
3076
+ : action;
3077
+ io.stdout(AUTO_MODE_ACTION_HELP[target?.value ?? ''] ?? AUTO_MODE_HELP);
3078
+ return true;
3079
+ }
3080
+ if (command === 'project') {
3081
+ const action = helpActionAt(argv, commandIndex, ['help', 'purge']);
3082
+ if (!hasHelpFlag && action?.value !== 'help')
3083
+ return false;
3084
+ const target = action?.value === 'help'
3085
+ ? helpActionAt(argv, action.index, ['purge'])
3086
+ : action;
3087
+ io.stdout(target?.value === 'purge' ? PROJECT_PURGE_HELP : PROJECT_HELP);
3088
+ return true;
3089
+ }
3090
+ return false;
3091
+ }
3092
+ async function execute(argv, io, dependencies, signal) {
3093
+ if (argv[0] === '__background-worker') {
3094
+ await runBackgroundWorker(requireValue(argv[1], 'Agent ID'));
3095
+ return 0;
3096
+ }
3097
+ if (argv.length === 0 && io.isTTY && dependencies.runInteractive) {
3098
+ return dependencies.runInteractive(signal ? { signal } : {});
3099
+ }
3100
+ if ((argv[0] === 'plugin' || argv[0] === 'plugins') && argv[1] === 'eval') {
3101
+ if (!dependencies.pluginEval)
3102
+ throw new Error('Plugin eval unavailable');
3103
+ const pluginEvalDependencies = dependencies.pluginEval.claudeVersion
3104
+ ? dependencies.pluginEval
3105
+ : {
3106
+ ...dependencies.pluginEval,
3107
+ claudeVersion: await detectInstalledClaudeVersion(),
3108
+ };
3109
+ return executeClaudePluginEvalCommand(argv.slice(2), io, pluginEvalDependencies, signal);
3110
+ }
3111
+ if (printCommandHelp(argv, io))
3112
+ return 0;
3113
+ if (argv[0] === 'doctor' &&
3114
+ (argv.includes('--help') || argv.includes('-h'))) {
3115
+ io.stdout(DOCTOR_HELP);
3116
+ return 0;
3117
+ }
3118
+ if (argv[0] === 'mcp' &&
3119
+ argv.includes('add') &&
3120
+ (argv.includes('--help') || argv.includes('-h'))) {
3121
+ io.stdout(MCP_ADD_HELP);
3122
+ return 0;
3123
+ }
3124
+ if (argv[0] === 'project' && argv[1] === 'purge') {
3125
+ return executeProjectPurgeCommand(argv, io);
3126
+ }
3127
+ if (['install', 'update', 'upgrade'].includes(argv[0] ?? '')) {
3128
+ return executeSelfUpdateCommand(argv, io, dependencies);
3129
+ }
3130
+ if (argv[0]?.startsWith('-') &&
3131
+ argv[argv.indexOf('project') + 1] === 'purge') {
3132
+ const commandIndex = argv.indexOf('project');
3133
+ return executeProjectPurgeCommand([
3134
+ 'project',
3135
+ 'purge',
3136
+ ...argv.slice(0, commandIndex),
3137
+ ...argv.slice(commandIndex + 2),
3138
+ ], io);
3139
+ }
3140
+ if (argv.length === 0 || argv.includes('--help') || argv.includes('-h')) {
3141
+ io.stdout(HELP);
3142
+ return 0;
3143
+ }
3144
+ if (argv.includes('--version') || argv.includes('-v')) {
3145
+ io.stdout(`${VERSION}\n`);
3146
+ return 0;
3147
+ }
3148
+ const invocation = parseCliInvocation(argv);
3149
+ const { agent, args, outputFormat, inputFormat, includePartialMessages } = invocation;
3150
+ const command = args[0];
3151
+ const knownCommand = [
3152
+ 'run',
3153
+ 'resume',
3154
+ 'fork',
3155
+ 'sessions',
3156
+ 'inspect',
3157
+ 'export',
3158
+ 'agents',
3159
+ 'attach',
3160
+ 'logs',
3161
+ 'stop',
3162
+ 'mcp',
3163
+ 'auto-mode',
3164
+ 'plugin',
3165
+ 'plugins',
3166
+ 'doctor',
3167
+ 'install',
3168
+ 'update',
3169
+ 'upgrade',
3170
+ 'project',
3171
+ ].includes(command ?? '');
3172
+ if (args[0] === 'agents')
3173
+ assertAgentsOptionAllowlist(argv);
3174
+ const interactiveResume = invocation.resumeSelector !== undefined &&
3175
+ args.length === (typeof invocation.resumeSelector === 'string' ? 2 : 1);
3176
+ const interactivePromptArgs = command === 'resume' && invocation.resumeSelector !== undefined
3177
+ ? args.slice(2)
3178
+ : !knownCommand
3179
+ ? args
3180
+ : [];
3181
+ const interactivePrompt = interactivePromptArgs.length > 0
3182
+ ? promptFrom(interactivePromptArgs)
3183
+ : undefined;
3184
+ if ((invocation.fallbackModels !== undefined ||
3185
+ invocation.jsonSchema !== undefined ||
3186
+ invocation.maxBudgetUsd !== undefined) &&
3187
+ !invocation.print) {
3188
+ throw new Error('--fallback-model, --json-schema, and --max-budget-usd require --print');
3189
+ }
3190
+ if (invocation.tmux) {
3191
+ if (!dependencies.launchTmux)
3192
+ throw new Error('tmux launcher unavailable');
3193
+ const launched = await dependencies.launchTmux({
3194
+ argv,
3195
+ cwd: process.cwd(),
3196
+ cliPath: fileURLToPath(import.meta.url),
3197
+ ...(invocation.worktreeName === undefined
3198
+ ? {}
3199
+ : { worktreeName: invocation.worktreeName }),
3200
+ mode: invocation.tmux,
3201
+ attach: Boolean(io.isTTY),
3202
+ });
3203
+ if (!io.isTTY)
3204
+ io.stdout(launched.kind === 'iterm'
3205
+ ? `Started iTerm2 pane ${launched.sessionName}\n`
3206
+ : `Started tmux session ${launched.sessionName}\n`);
3207
+ return 0;
3208
+ }
3209
+ if (invocation.rewindFiles !== undefined && args.length > 2) {
3210
+ throw new Error('--rewind-files is a standalone operation and cannot be used with a prompt');
3211
+ }
3212
+ if (io.isTTY &&
3213
+ dependencies.runInteractive &&
3214
+ (args.length === 0 ||
3215
+ interactiveResume ||
3216
+ interactivePrompt !== undefined) &&
3217
+ !invocation.print &&
3218
+ !invocation.background &&
3219
+ !invocation.initOnly &&
3220
+ inputFormat === 'text' &&
3221
+ outputFormat === 'text') {
3222
+ return dependencies.runInteractive({
3223
+ ...(agent === undefined ? {} : { agent }),
3224
+ controls: invocation,
3225
+ ...(interactivePrompt === undefined
3226
+ ? {}
3227
+ : { initialPrompt: interactivePrompt }),
3228
+ resume: {
3229
+ ...(typeof invocation.resumeSelector === 'string'
3230
+ ? {
3231
+ sessionSelector: invocation.resumeSelector,
3232
+ ...(isClaudeSessionId(invocation.resumeSelector)
3233
+ ? { sessionId: invocation.resumeSelector }
3234
+ : {}),
3235
+ }
3236
+ : {}),
3237
+ ...(invocation.resumeSelector === undefined
3238
+ ? {}
3239
+ : { requireSession: true }),
3240
+ ...(invocation.fromPr === undefined
3241
+ ? {}
3242
+ : { fromPr: invocation.fromPr }),
3243
+ ...(invocation.forkSession ? { forkSession: true } : {}),
3244
+ ...(invocation.sessionId === undefined
3245
+ ? {}
3246
+ : { forkSessionId: invocation.sessionId }),
3247
+ ...(invocation.retryInterruptedTools
3248
+ ? { retryInterruptedTools: true }
3249
+ : {}),
3250
+ },
3251
+ ...(signal ? { signal } : {}),
3252
+ });
3253
+ }
3254
+ if (invocation.initOnly) {
3255
+ const lifecycleService = await dependencies.createService({
3256
+ eventSink: () => undefined,
3257
+ requireProvider: false,
3258
+ exposeToolRegistry: true,
3259
+ ...(signal ? { signal } : {}),
3260
+ controls: invocation,
3261
+ });
3262
+ try {
3263
+ await lifecycleService.lifecycle?.('init', {
3264
+ sessionStart: true,
3265
+ ...(invocation.sessionId === undefined
3266
+ ? {}
3267
+ : { sessionId: invocation.sessionId }),
3268
+ });
3269
+ return 0;
3270
+ }
3271
+ finally {
3272
+ await lifecycleService.close?.();
3273
+ }
3274
+ }
3275
+ const { retryInterruptedTools } = invocation;
3276
+ if (command === 'resume' && args[1] === undefined) {
3277
+ if (invocation.resumeSelector === true) {
3278
+ throw new Error(missingResumeSelectorMessage(invocation));
3279
+ }
3280
+ requireValue(args[1], 'Session ID');
3281
+ }
3282
+ if (retryInterruptedTools &&
3283
+ command !== 'resume' &&
3284
+ invocation.fromPr === undefined) {
3285
+ throw new Error('--retry-interrupted-tools is only valid with resume or --from-pr');
3286
+ }
3287
+ if (agent &&
3288
+ knownCommand &&
3289
+ !['run', 'resume', 'agents'].includes(command ?? 'run')) {
3290
+ throw new Error('--agent is only valid with run or resume');
3291
+ }
3292
+ if ((invocation.agentsAll || invocation.agentsCwd !== undefined) &&
3293
+ command !== 'agents') {
3294
+ throw new Error('--all and --cwd are only valid with agents');
3295
+ }
3296
+ if (invocation.mcpScope !== undefined && command !== 'mcp') {
3297
+ if (command !== 'plugin' && command !== 'plugins') {
3298
+ throw new Error('--scope is only valid with mcp or plugin commands');
3299
+ }
3300
+ }
3301
+ const mcpAction = command === 'mcp' ? args[1] : undefined;
3302
+ if ((invocation.mcpTransport !== undefined ||
3303
+ invocation.mcpEnv.length > 0 ||
3304
+ invocation.mcpHeaders.length > 0 ||
3305
+ invocation.mcpCallbackPort !== undefined ||
3306
+ invocation.mcpClientId !== undefined) &&
3307
+ mcpAction !== 'add') {
3308
+ throw new Error('--transport, --env, --header, --callback-port, and --client-id are only valid with mcp add');
3309
+ }
3310
+ if (invocation.mcpClientSecret &&
3311
+ mcpAction !== 'add' &&
3312
+ mcpAction !== 'add-json') {
3313
+ throw new Error('--client-secret is only valid with mcp add or add-json');
3314
+ }
3315
+ if (invocation.mcpNoBrowser && mcpAction !== 'login') {
3316
+ throw new Error('--no-browser is only valid with mcp login');
3317
+ }
3318
+ if (invocation.mcpDebug && mcpAction !== 'serve') {
3319
+ throw new Error('--debug is only valid with mcp serve');
3320
+ }
3321
+ if (command === 'mcp') {
3322
+ return executeMcpCommand(args, invocation, io, dependencies, signal);
3323
+ }
3324
+ if (command === 'auto-mode') {
3325
+ return executeAutoModeCommand(args, invocation, io, dependencies, signal);
3326
+ }
3327
+ if (invocation.autoModeLabel !== undefined &&
3328
+ !(command === 'auto-mode' && args[1] === 'defaults')) {
3329
+ throw new Error('--label is only valid with auto-mode defaults');
3330
+ }
3331
+ if (invocation.pluginSparsePaths.length > 0 &&
3332
+ !((command === 'plugin' || command === 'plugins') &&
3333
+ args[1] === 'marketplace' &&
3334
+ args[2] === 'add')) {
3335
+ throw new Error('--sparse is only valid with plugin marketplace add');
3336
+ }
3337
+ if (command === 'plugin' || command === 'plugins') {
3338
+ return executePluginCommand(command === 'plugins' ? ['plugin', ...args.slice(1)] : args, invocation, io);
3339
+ }
3340
+ if (command === 'doctor') {
3341
+ return executeDoctorCommand(args, invocation, io);
3342
+ }
3343
+ const expectedOperands = command === 'sessions' || command === 'agents' ? 1 : 2;
3344
+ if ([
3345
+ 'sessions',
3346
+ 'fork',
3347
+ 'inspect',
3348
+ 'export',
3349
+ 'agents',
3350
+ 'attach',
3351
+ 'logs',
3352
+ 'stop',
3353
+ ].includes(command ?? '') &&
3354
+ args.length > expectedOperands) {
3355
+ throw new Error(`Unexpected operand for ${command}: ${args[expectedOperands]}`);
3356
+ }
3357
+ if (command === 'agents') {
3358
+ if (invocation.legacyJson) {
3359
+ const agents = await requireTopLevelAgentManager(dependencies).list({
3360
+ ...(invocation.agentsCwd === undefined
3361
+ ? {}
3362
+ : { cwd: invocation.agentsCwd }),
3363
+ all: invocation.agentsAll,
3364
+ });
3365
+ writeJson(io, agents);
3366
+ return 0;
3367
+ }
3368
+ if (!io.isTTY) {
3369
+ throw new Error("'praxis agents' requires an interactive terminal (stdout is not a TTY) — use 'praxis agents --json' for a machine-readable listing.");
3370
+ }
3371
+ const manager = requireTopLevelAgentManager(dependencies);
3372
+ if (!dependencies.runAgentsDashboard) {
3373
+ throw new Error('Agents dashboard unavailable');
3374
+ }
3375
+ return dependencies.runAgentsDashboard({
3376
+ manager,
3377
+ defaults: {
3378
+ argv: agentDashboardWorkerArgv(invocation),
3379
+ ...(invocation.agentsCwd === undefined
3380
+ ? {}
3381
+ : { cwd: invocation.agentsCwd }),
3382
+ },
3383
+ ...(signal ? { signal } : {}),
3384
+ });
3385
+ }
3386
+ if (command === 'logs') {
3387
+ io.stdout(await requireTopLevelAgentManager(dependencies).logs(requireValue(args[1], 'Agent ID')));
3388
+ return 0;
3389
+ }
3390
+ if (command === 'stop') {
3391
+ const id = requireValue(args[1], 'Agent ID');
3392
+ await requireTopLevelAgentManager(dependencies).stop(id);
3393
+ io.stdout(`stopped ${id}\n`);
3394
+ return 0;
3395
+ }
3396
+ if (command === 'attach') {
3397
+ const input = io.readStdinLines?.();
3398
+ if (!input)
3399
+ throw new Error('attach requires stdin support');
3400
+ await requireTopLevelAgentManager(dependencies).attach(requireValue(args[1], 'Agent ID'), input, (text) => io.stdout(text), signal);
3401
+ return 0;
3402
+ }
3403
+ if (invocation.background) {
3404
+ if (!invocation.sessionPersistence) {
3405
+ throw new Error('--bg requires session persistence');
3406
+ }
3407
+ if (inputFormat !== 'text' || outputFormat !== 'text') {
3408
+ throw new Error('--bg only supports text input and output');
3409
+ }
3410
+ if (command && knownCommand && command !== 'run' && command !== 'resume') {
3411
+ throw new Error(`--bg cannot be combined with ${command}`);
3412
+ }
3413
+ const prompt = promptFrom(command === 'resume'
3414
+ ? args.slice(2)
3415
+ : command === 'run'
3416
+ ? args.slice(1)
3417
+ : args);
3418
+ const explicitResumeSelector = command === 'resume' ? requireValue(args[1], 'Session ID') : undefined;
3419
+ let resumeSessionId;
3420
+ let usedExplicitSessionId = false;
3421
+ if (explicitResumeSelector !== undefined ||
3422
+ invocation.continueSession ||
3423
+ invocation.fromPr !== undefined) {
3424
+ const sessionService = await dependencies.createService({
3425
+ eventSink: () => undefined,
3426
+ requireProvider: false,
3427
+ controls: invocation,
3428
+ });
3429
+ try {
3430
+ const sessions = await sessionService.sessions();
3431
+ if (explicitResumeSelector !== undefined) {
3432
+ resumeSessionId = selectResumeSession(sessions, explicitResumeSelector, invocation).sessionId;
3433
+ }
3434
+ else {
3435
+ resumeSessionId = selectImplicitResumeSession(sessions, invocation.fromPr).sessionId;
3436
+ }
3437
+ if (invocation.forkSession) {
3438
+ resumeSessionId = (await sessionService.fork(resumeSessionId, invocation.sessionId)).sessionId;
3439
+ usedExplicitSessionId = invocation.sessionId !== undefined;
3440
+ }
3441
+ }
3442
+ finally {
3443
+ await sessionService.close?.();
3444
+ }
3445
+ }
3446
+ if (invocation.sessionId && !usedExplicitSessionId) {
3447
+ io.stderr('warning: --bg manages the session id; ignoring --session-id (use --resume <id> to continue an existing session)\n');
3448
+ }
3449
+ const launched = await requireTopLevelAgentManager(dependencies).launch({
3450
+ prompt,
3451
+ argv: backgroundWorkerArgv(argv),
3452
+ ...(resumeSessionId === undefined ? {} : { resumeSessionId }),
3453
+ });
3454
+ io.stdout(`backgrounded · ${launched.id}\n praxis agents list sessions\n praxis attach ${launched.id} open in this terminal\n praxis logs ${launched.id} show recent output\n praxis stop ${launched.id} stop this session\n`);
3455
+ return 0;
3456
+ }
3457
+ let streamOutput;
3458
+ let jsonModelTurns = 0;
3459
+ const pendingEvents = [];
3460
+ let streamIterator;
3461
+ let firstStreamMessage;
3462
+ const queuedStreamUsers = [];
3463
+ const earlyControlResponses = new Map();
3464
+ let currentTurnAbort;
3465
+ if (inputFormat === 'stream-json' && !invocation.legacyJson) {
3466
+ const input = io.readStdinLines?.();
3467
+ if (!input)
3468
+ throw new Error('stream-json input requires stdin support');
3469
+ streamIterator = readStreamJsonMessages(input);
3470
+ }
3471
+ const receiveStreamControl = (message) => {
3472
+ if (!('type' in message)) {
3473
+ queuedStreamUsers.push(message);
3474
+ return;
3475
+ }
3476
+ if (message.type === 'control_request') {
3477
+ if (message.request.subtype === 'interrupt')
3478
+ currentTurnAbort?.abort();
3479
+ return;
3480
+ }
3481
+ if (message.type === 'control_response') {
3482
+ earlyControlResponses.set(message.response.request_id, message);
3483
+ return;
3484
+ }
3485
+ if (message.type === 'control_cancel_request') {
3486
+ earlyControlResponses.set(message.request_id, {
3487
+ type: 'control_response',
3488
+ response: {
3489
+ subtype: 'error',
3490
+ request_id: message.request_id,
3491
+ error: 'Control request cancelled',
3492
+ },
3493
+ });
3494
+ }
3495
+ };
3496
+ const nextStreamMessage = async () => {
3497
+ if (!streamIterator)
3498
+ return null;
3499
+ const next = await streamIterator.next();
3500
+ return next.done ? null : next.value;
3501
+ };
3502
+ const nextStreamUser = async () => {
3503
+ const queued = queuedStreamUsers.shift();
3504
+ if (queued)
3505
+ return queued;
3506
+ for (;;) {
3507
+ const message = await nextStreamMessage();
3508
+ if (!message)
3509
+ return null;
3510
+ if (!('type' in message))
3511
+ return message;
3512
+ receiveStreamControl(message);
3513
+ }
3514
+ };
3515
+ const awaitControlResponse = async (requestId) => {
3516
+ const early = earlyControlResponses.get(requestId);
3517
+ if (early) {
3518
+ earlyControlResponses.delete(requestId);
3519
+ return early;
3520
+ }
3521
+ for (;;) {
3522
+ const message = await nextStreamMessage();
3523
+ if (!message)
3524
+ return null;
3525
+ if (!('type' in message)) {
3526
+ queuedStreamUsers.push(message);
3527
+ continue;
3528
+ }
3529
+ if (message.type === 'control_response') {
3530
+ if (message.response.request_id === requestId)
3531
+ return message;
3532
+ earlyControlResponses.set(message.response.request_id, message);
3533
+ continue;
3534
+ }
3535
+ if (message.type === 'control_cancel_request' &&
3536
+ message.request_id === requestId) {
3537
+ return {
3538
+ type: 'control_response',
3539
+ response: {
3540
+ subtype: 'error',
3541
+ request_id: requestId,
3542
+ error: 'Control request cancelled',
3543
+ },
3544
+ };
3545
+ }
3546
+ receiveStreamControl(message);
3547
+ if (currentTurnAbort?.signal.aborted) {
3548
+ return {
3549
+ type: 'control_response',
3550
+ response: {
3551
+ subtype: 'error',
3552
+ request_id: requestId,
3553
+ error: 'Control request interrupted',
3554
+ },
3555
+ };
3556
+ }
3557
+ }
3558
+ };
3559
+ const approveStreamTool = async (call) => {
3560
+ if (!streamOutput || !streamIterator)
3561
+ return false;
3562
+ const requestId = randomUUID();
3563
+ streamOutput.controlRequest({
3564
+ request_id: requestId,
3565
+ request: {
3566
+ subtype: 'can_use_tool',
3567
+ tool_name: call.name,
3568
+ input: call.input,
3569
+ tool_use_id: call.id,
3570
+ },
3571
+ });
3572
+ const response = await awaitControlResponse(requestId);
3573
+ if (!response || response.response.subtype === 'error')
3574
+ return false;
3575
+ const result = response.response.response;
3576
+ if (!result || result.behavior !== 'allow') {
3577
+ if (result?.interrupt === true)
3578
+ currentTurnAbort?.abort();
3579
+ return false;
3580
+ }
3581
+ return true;
3582
+ };
3583
+ const respondStreamElicitation = async (request) => {
3584
+ if (!streamOutput || !streamIterator)
3585
+ return { action: 'decline' };
3586
+ const requestId = randomUUID();
3587
+ streamOutput.controlRequest({
3588
+ request_id: requestId,
3589
+ request: {
3590
+ subtype: 'elicitation',
3591
+ mcp_server_name: request.serverName,
3592
+ message: request.message,
3593
+ ...(request.mode === undefined ? {} : { mode: request.mode }),
3594
+ ...(request.url === undefined ? {} : { url: request.url }),
3595
+ ...(request.elicitationId === undefined
3596
+ ? {}
3597
+ : { elicitation_id: request.elicitationId }),
3598
+ ...(request.requestedSchema === undefined
3599
+ ? {}
3600
+ : { requested_schema: request.requestedSchema }),
3601
+ },
3602
+ });
3603
+ const response = await awaitControlResponse(requestId);
3604
+ if (!response || response.response.subtype === 'error')
3605
+ return { action: 'decline' };
3606
+ const result = response.response.response;
3607
+ if (!result ||
3608
+ !['accept', 'decline', 'cancel'].includes(String(result.action)))
3609
+ return { action: 'decline' };
3610
+ return {
3611
+ action: result.action,
3612
+ ...(result.content && typeof result.content === 'object'
3613
+ ? {
3614
+ content: result.content,
3615
+ }
3616
+ : {}),
3617
+ };
3618
+ };
3619
+ const service = await dependencies.createService({
3620
+ eventSink: outputFormat === 'stream-json' && !invocation.legacyJson
3621
+ ? (event) => {
3622
+ const safeEvent = event.type === 'warning' || event.type === 'failed'
3623
+ ? {
3624
+ ...event,
3625
+ message: redactSensitiveText(event.message, sensitiveEnvironmentValues(process.env)),
3626
+ }
3627
+ : event;
3628
+ if (streamOutput)
3629
+ streamOutput.sink(safeEvent);
3630
+ else
3631
+ pendingEvents.push(safeEvent);
3632
+ }
3633
+ : outputFormat === 'json'
3634
+ ? (event) => {
3635
+ if (event.type === 'state' && event.state === 'awaiting-model') {
3636
+ jsonModelTurns += 1;
3637
+ }
3638
+ if (event.type === 'warning') {
3639
+ io.stderr(`Warning: ${redactSensitiveText(event.message, sensitiveEnvironmentValues(process.env))}\n`);
3640
+ }
3641
+ }
3642
+ : eventSink(io, outputFormat, invocation.legacyJson),
3643
+ requireProvider: invocation.rewindFiles === undefined &&
3644
+ !['fork', 'sessions', 'inspect', 'export'].includes(command ?? 'run'),
3645
+ ...(retryInterruptedTools ? { approveRecovery: () => true } : {}),
3646
+ ...(streamIterator ? { approveTool: approveStreamTool } : {}),
3647
+ ...(streamIterator ? { onElicitation: respondStreamElicitation } : {}),
3648
+ ...(streamIterator ? { emitToolUseSummaries: true } : {}),
3649
+ ...(signal ? { signal } : {}),
3650
+ ...(agent ? { agent } : {}),
3651
+ controls: invocation,
3652
+ });
3653
+ try {
3654
+ const lifecycleTrigger = invocation.init
3655
+ ? 'init'
3656
+ : invocation.maintenance
3657
+ ? 'maintenance'
3658
+ : undefined;
3659
+ if (lifecycleTrigger !== undefined) {
3660
+ await service.lifecycle?.(lifecycleTrigger, {
3661
+ ...(invocation.sessionId === undefined
3662
+ ? {}
3663
+ : { sessionId: invocation.sessionId }),
3664
+ });
3665
+ }
3666
+ if (invocation.rewindFiles !== undefined) {
3667
+ const sessionId = requireValue(args[1], 'Session ID');
3668
+ if (!service.rewindFiles)
3669
+ throw new Error('File rewinding is unavailable');
3670
+ await service.rewindFiles(sessionId, invocation.rewindFiles);
3671
+ io.stdout(`Files rewound to state at message ${invocation.rewindFiles}\n`);
3672
+ return 0;
3673
+ }
3674
+ if (command === 'sessions') {
3675
+ const sessions = await service.sessions();
3676
+ if (outputFormat === 'json' || invocation.legacyJson) {
3677
+ writeJson(io, { type: 'sessions', sessions });
3678
+ }
3679
+ else if (outputFormat === 'stream-json') {
3680
+ for (const session of sessions)
3681
+ writeJson(io, { type: 'session', session });
3682
+ }
3683
+ else {
3684
+ for (const session of sessions) {
3685
+ io.stdout(`${session.sessionId}\t${session.updatedAt}\t${session.lastPrompt ?? ''}\t${session.status}\t${formatSessionIssue(session.issue)}\n`);
3686
+ }
3687
+ }
3688
+ return 0;
3689
+ }
3690
+ if (command === 'fork') {
3691
+ const result = await service.fork(requireValue(args[1], 'Session ID'));
3692
+ if (outputFormat !== 'text')
3693
+ writeJson(io, { type: 'forked', ...result });
3694
+ else
3695
+ io.stdout(`${result.sessionId}\n`);
3696
+ return 0;
3697
+ }
3698
+ if (command === 'inspect') {
3699
+ const session = await service.inspect(requireValue(args[1], 'Session ID'));
3700
+ if (outputFormat !== 'text')
3701
+ writeJson(io, { type: 'session', session });
3702
+ else {
3703
+ io.stdout(`${session.sessionId}\t${session.status}\t${session.writeMode}\t${session.updatedAt}\t${session.entryCount}\t${session.byteLength}\t${session.newlineTerminated}\t${session.lastPrompt ?? ''}\t${formatSessionIssue(session.issue)}\n`);
3704
+ }
3705
+ return 0;
3706
+ }
3707
+ if (command === 'export') {
3708
+ const sessionId = requireValue(args[1], 'Session ID');
3709
+ const transcript = await service.export(sessionId);
3710
+ if (outputFormat !== 'text') {
3711
+ writeJson(io, {
3712
+ type: 'session-export',
3713
+ sessionId,
3714
+ encoding: 'base64',
3715
+ transcript: transcript.toString('base64'),
3716
+ });
3717
+ }
3718
+ else
3719
+ io.stdout(transcript);
3720
+ return 0;
3721
+ }
3722
+ const runtimeInfo = service.runtimeInfo?.() ?? {
3723
+ cwd: process.cwd(),
3724
+ model: process.env.PRAXIS_MODEL ?? 'unknown',
3725
+ tools: [],
3726
+ mcpServers: [],
3727
+ permissionMode: 'default',
3728
+ slashCommands: [],
3729
+ agents: [],
3730
+ skills: [],
3731
+ claudeCodeVersion: 'unknown',
3732
+ };
3733
+ const explicitResumeSelector = command === 'resume' ? requireValue(args[1], 'Session ID') : undefined;
3734
+ let existingSessionId;
3735
+ if (explicitResumeSelector !== undefined ||
3736
+ invocation.continueSession ||
3737
+ invocation.fromPr !== undefined) {
3738
+ const sessions = await service.sessions();
3739
+ existingSessionId =
3740
+ explicitResumeSelector === undefined
3741
+ ? selectImplicitResumeSession(sessions, invocation.fromPr).sessionId
3742
+ : selectResumeSession(sessions, explicitResumeSelector, invocation)
3743
+ .sessionId;
3744
+ }
3745
+ if (existingSessionId && invocation.forkSession) {
3746
+ existingSessionId = (await service.fork(existingSessionId, invocation.sessionId)).sessionId;
3747
+ }
3748
+ let activeSessionId = existingSessionId ?? invocation.sessionId ?? randomUUID();
3749
+ if (outputFormat === 'stream-json' && !invocation.legacyJson) {
3750
+ streamOutput = new StreamJsonOutput((value) => writeJson(io, value), runtimeInfo, activeSessionId, includePartialMessages, invocation.includeHookEvents);
3751
+ }
3752
+ if (streamIterator) {
3753
+ const first = await nextStreamUser();
3754
+ if (!first)
3755
+ return 0;
3756
+ firstStreamMessage = first;
3757
+ }
3758
+ const initialPrompt = firstStreamMessage?.prompt ??
3759
+ promptFrom(command === 'resume'
3760
+ ? args.slice(2)
3761
+ : knownCommand
3762
+ ? args.slice(1)
3763
+ : args);
3764
+ let isFirstTurn = true;
3765
+ const runTurn = async (prompt, streamMessage) => {
3766
+ const startedAt = Date.now();
3767
+ const turnAbort = new AbortController();
3768
+ const forwardAbort = () => turnAbort.abort();
3769
+ if (signal?.aborted)
3770
+ turnAbort.abort();
3771
+ signal?.addEventListener('abort', forwardAbort, { once: true });
3772
+ currentTurnAbort = streamIterator ? turnAbort : undefined;
3773
+ const runSignal = streamIterator ? turnAbort.signal : signal;
3774
+ if (streamOutput) {
3775
+ streamOutput.init();
3776
+ if (isFirstTurn) {
3777
+ for (const event of pendingEvents)
3778
+ streamOutput.sink(event);
3779
+ }
3780
+ if (invocation.replayUserMessages && streamMessage)
3781
+ streamOutput.replayUser(streamMessage.message);
3782
+ }
3783
+ let result;
3784
+ try {
3785
+ result =
3786
+ existingSessionId !== undefined || !isFirstTurn
3787
+ ? runSignal
3788
+ ? await service.resume(activeSessionId, prompt, runSignal, undefined, streamMessage?.images, streamMessage?.documents)
3789
+ : await service.resume(activeSessionId, prompt, undefined, undefined, streamMessage?.images, streamMessage?.documents)
3790
+ : runSignal
3791
+ ? await service.run(prompt, runSignal, activeSessionId, undefined, streamMessage?.images, streamMessage?.documents)
3792
+ : await service.run(prompt, undefined, activeSessionId, undefined, streamMessage?.images, streamMessage?.documents);
3793
+ }
3794
+ catch (error) {
3795
+ if (isCancellation(error, turnAbort.signal)) {
3796
+ if (currentTurnAbort === turnAbort)
3797
+ currentTurnAbort = undefined;
3798
+ signal?.removeEventListener('abort', forwardAbort);
3799
+ throw error;
3800
+ }
3801
+ const message = redactSensitiveText(error instanceof Error ? error.message : String(error), sensitiveEnvironmentValues(process.env));
3802
+ if (streamOutput)
3803
+ streamOutput.error(message, startedAt);
3804
+ else if (outputFormat === 'json') {
3805
+ writeJson(io, createErrorResult(message, activeSessionId, startedAt, jsonModelTurns));
3806
+ }
3807
+ else {
3808
+ if (currentTurnAbort === turnAbort)
3809
+ currentTurnAbort = undefined;
3810
+ signal?.removeEventListener('abort', forwardAbort);
3811
+ throw error;
3812
+ }
3813
+ if (currentTurnAbort === turnAbort)
3814
+ currentTurnAbort = undefined;
3815
+ signal?.removeEventListener('abort', forwardAbort);
3816
+ return false;
3817
+ }
3818
+ activeSessionId = result.sessionId;
3819
+ if (streamOutput)
3820
+ streamOutput.result(result, startedAt);
3821
+ else if (outputFormat === 'json') {
3822
+ const resultRuntimeInfo = service.runtimeInfo?.() ?? runtimeInfo;
3823
+ writeJson(io, createSuccessResult(result, resultRuntimeInfo, startedAt, Math.max(1, jsonModelTurns)));
3824
+ }
3825
+ else if (outputFormat !== 'text')
3826
+ writeJson(io, { type: 'result', ...result });
3827
+ else
3828
+ io.stdout('\n');
3829
+ if (streamOutput && invocation.promptSuggestions) {
3830
+ try {
3831
+ const suggestion = await service.promptSuggestion?.(activeSessionId, runSignal);
3832
+ if (suggestion)
3833
+ streamOutput.promptSuggestion(suggestion);
3834
+ }
3835
+ catch {
3836
+ // Prompt suggestions are auxiliary and must not change turn success.
3837
+ }
3838
+ }
3839
+ isFirstTurn = false;
3840
+ if (currentTurnAbort === turnAbort)
3841
+ currentTurnAbort = undefined;
3842
+ signal?.removeEventListener('abort', forwardAbort);
3843
+ return true;
3844
+ };
3845
+ if (!(await runTurn(initialPrompt, firstStreamMessage)))
3846
+ return 1;
3847
+ if (streamIterator) {
3848
+ for (;;) {
3849
+ const message = await nextStreamUser();
3850
+ if (!message)
3851
+ break;
3852
+ if (!(await runTurn(message.prompt, message)))
3853
+ return 1;
3854
+ }
3855
+ }
3856
+ return 0;
3857
+ }
3858
+ finally {
3859
+ try {
3860
+ await service.close?.();
3861
+ }
3862
+ catch (error) {
3863
+ io.stderr(`Warning: ${redactSensitiveText(error instanceof Error ? error.message : String(error), sensitiveEnvironmentValues(process.env))}\n`);
3864
+ }
3865
+ }
3866
+ }
3867
+ export async function run(argv, io = consoleIO, dependencies = defaultDependencies, signal) {
3868
+ try {
3869
+ return await execute(argv, io, dependencies, signal);
3870
+ }
3871
+ catch (error) {
3872
+ if (isCancellation(error, signal)) {
3873
+ io.stderr('Praxis run cancelled.\n');
3874
+ return 130;
3875
+ }
3876
+ const message = redactSensitiveText(error instanceof Error ? error.message : String(error), sensitiveEnvironmentValues(process.env));
3877
+ if (argv.includes('--json'))
3878
+ writeJson(io, { type: 'error', message });
3879
+ else
3880
+ io.stderr(`${message}\n`);
3881
+ return 1;
3882
+ }
3883
+ }
3884
+ function isDirectExecution(moduleUrl, argvPath) {
3885
+ if (!argvPath)
3886
+ return false;
3887
+ try {
3888
+ return moduleUrl === pathToFileURL(realpathSync(argvPath)).href;
3889
+ }
3890
+ catch {
3891
+ return false;
3892
+ }
3893
+ }
3894
+ if (isDirectExecution(import.meta.url, process.argv[1])) {
3895
+ const controller = new AbortController();
3896
+ const cancel = () => controller.abort();
3897
+ process.once('SIGINT', cancel);
3898
+ process.exitCode = await run(process.argv.slice(2), consoleIO, defaultDependencies, controller.signal);
3899
+ process.removeListener('SIGINT', cancel);
3900
+ }
3901
+ //# sourceMappingURL=cli.js.map