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.
- package/LICENSE +21 -0
- package/README.md +534 -0
- package/dist/application/background-agent-manager.d.ts +66 -0
- package/dist/application/background-agent-manager.d.ts.map +1 -0
- package/dist/application/background-agent-manager.js +362 -0
- package/dist/application/background-agent-manager.js.map +1 -0
- package/dist/application/background-bash-manager.d.ts +54 -0
- package/dist/application/background-bash-manager.d.ts.map +1 -0
- package/dist/application/background-bash-manager.js +354 -0
- package/dist/application/background-bash-manager.js.map +1 -0
- package/dist/application/background-task-id.d.ts +5 -0
- package/dist/application/background-task-id.d.ts.map +1 -0
- package/dist/application/background-task-id.js +25 -0
- package/dist/application/background-task-id.js.map +1 -0
- package/dist/application/claude-project-purge.d.ts +50 -0
- package/dist/application/claude-project-purge.d.ts.map +1 -0
- package/dist/application/claude-project-purge.js +459 -0
- package/dist/application/claude-project-purge.js.map +1 -0
- package/dist/application/managed-worktree.d.ts +15 -0
- package/dist/application/managed-worktree.d.ts.map +1 -0
- package/dist/application/managed-worktree.js +127 -0
- package/dist/application/managed-worktree.js.map +1 -0
- package/dist/application/model-compactor.d.ts +8 -0
- package/dist/application/model-compactor.d.ts.map +1 -0
- package/dist/application/model-compactor.js +70 -0
- package/dist/application/model-compactor.js.map +1 -0
- package/dist/application/scheduled-prompt-manager.d.ts +69 -0
- package/dist/application/scheduled-prompt-manager.d.ts.map +1 -0
- package/dist/application/scheduled-prompt-manager.js +387 -0
- package/dist/application/scheduled-prompt-manager.js.map +1 -0
- package/dist/application/session-service.d.ts +142 -0
- package/dist/application/session-service.d.ts.map +1 -0
- package/dist/application/session-service.js +1690 -0
- package/dist/application/session-service.js.map +1 -0
- package/dist/application/session-worktree.d.ts +55 -0
- package/dist/application/session-worktree.d.ts.map +1 -0
- package/dist/application/session-worktree.js +311 -0
- package/dist/application/session-worktree.js.map +1 -0
- package/dist/application/subagent-service.d.ts +98 -0
- package/dist/application/subagent-service.d.ts.map +1 -0
- package/dist/application/subagent-service.js +1305 -0
- package/dist/application/subagent-service.js.map +1 -0
- package/dist/application/tool-use-summary.d.ts +8 -0
- package/dist/application/tool-use-summary.d.ts.map +1 -0
- package/dist/application/tool-use-summary.js +53 -0
- package/dist/application/tool-use-summary.js.map +1 -0
- package/dist/application/top-level-agent-manager.d.ts +58 -0
- package/dist/application/top-level-agent-manager.d.ts.map +1 -0
- package/dist/application/top-level-agent-manager.js +907 -0
- package/dist/application/top-level-agent-manager.js.map +1 -0
- package/dist/application/workflow-manager.d.ts +61 -0
- package/dist/application/workflow-manager.d.ts.map +1 -0
- package/dist/application/workflow-manager.js +582 -0
- package/dist/application/workflow-manager.js.map +1 -0
- package/dist/application/workflow-meta.d.ts +18 -0
- package/dist/application/workflow-meta.d.ts.map +1 -0
- package/dist/application/workflow-meta.js +146 -0
- package/dist/application/workflow-meta.js.map +1 -0
- package/dist/application/workflow-runtime.d.ts +29 -0
- package/dist/application/workflow-runtime.d.ts.map +1 -0
- package/dist/application/workflow-runtime.js +176 -0
- package/dist/application/workflow-runtime.js.map +1 -0
- package/dist/application/workflow-worktree.d.ts +9 -0
- package/dist/application/workflow-worktree.d.ts.map +1 -0
- package/dist/application/workflow-worktree.js +11 -0
- package/dist/application/workflow-worktree.js.map +1 -0
- package/dist/cli/agents-dashboard.d.ts +37 -0
- package/dist/cli/agents-dashboard.d.ts.map +1 -0
- package/dist/cli/agents-dashboard.js +408 -0
- package/dist/cli/agents-dashboard.js.map +1 -0
- package/dist/cli/controls.d.ts +15 -0
- package/dist/cli/controls.d.ts.map +1 -0
- package/dist/cli/controls.js +219 -0
- package/dist/cli/controls.js.map +1 -0
- package/dist/cli/debug.d.ts +13 -0
- package/dist/cli/debug.d.ts.map +1 -0
- package/dist/cli/debug.js +37 -0
- package/dist/cli/debug.js.map +1 -0
- package/dist/cli/interactive.d.ts +63 -0
- package/dist/cli/interactive.d.ts.map +1 -0
- package/dist/cli/interactive.js +600 -0
- package/dist/cli/interactive.js.map +1 -0
- package/dist/cli/protocol.d.ts +251 -0
- package/dist/cli/protocol.d.ts.map +1 -0
- package/dist/cli/protocol.js +2146 -0
- package/dist/cli/protocol.js.map +1 -0
- package/dist/cli.d.ts +116 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +3901 -0
- package/dist/cli.js.map +1 -0
- package/dist/compatibility/claude/compaction.d.ts +19 -0
- package/dist/compatibility/claude/compaction.d.ts.map +1 -0
- package/dist/compatibility/claude/compaction.js +82 -0
- package/dist/compatibility/claude/compaction.js.map +1 -0
- package/dist/compatibility/claude/context.d.ts +24 -0
- package/dist/compatibility/claude/context.d.ts.map +1 -0
- package/dist/compatibility/claude/context.js +92 -0
- package/dist/compatibility/claude/context.js.map +1 -0
- package/dist/compatibility/claude/dynamic-context.d.ts +20 -0
- package/dist/compatibility/claude/dynamic-context.d.ts.map +1 -0
- package/dist/compatibility/claude/dynamic-context.js +168 -0
- package/dist/compatibility/claude/dynamic-context.js.map +1 -0
- package/dist/compatibility/claude/file-history.d.ts +14 -0
- package/dist/compatibility/claude/file-history.d.ts.map +1 -0
- package/dist/compatibility/claude/file-history.js +340 -0
- package/dist/compatibility/claude/file-history.js.map +1 -0
- package/dist/compatibility/claude/file-resources.d.ts +28 -0
- package/dist/compatibility/claude/file-resources.d.ts.map +1 -0
- package/dist/compatibility/claude/file-resources.js +168 -0
- package/dist/compatibility/claude/file-resources.js.map +1 -0
- package/dist/compatibility/claude/fork.d.ts +9 -0
- package/dist/compatibility/claude/fork.d.ts.map +1 -0
- package/dist/compatibility/claude/fork.js +225 -0
- package/dist/compatibility/claude/fork.js.map +1 -0
- package/dist/compatibility/claude/history.d.ts +4 -0
- package/dist/compatibility/claude/history.d.ts.map +1 -0
- package/dist/compatibility/claude/history.js +86 -0
- package/dist/compatibility/claude/history.js.map +1 -0
- package/dist/compatibility/claude/ownership.d.ts +87 -0
- package/dist/compatibility/claude/ownership.d.ts.map +1 -0
- package/dist/compatibility/claude/ownership.js +100 -0
- package/dist/compatibility/claude/ownership.js.map +1 -0
- package/dist/compatibility/claude/paths.d.ts +17 -0
- package/dist/compatibility/claude/paths.d.ts.map +1 -0
- package/dist/compatibility/claude/paths.js +54 -0
- package/dist/compatibility/claude/paths.js.map +1 -0
- package/dist/compatibility/claude/pr-links.d.ts +25 -0
- package/dist/compatibility/claude/pr-links.d.ts.map +1 -0
- package/dist/compatibility/claude/pr-links.js +80 -0
- package/dist/compatibility/claude/pr-links.js.map +1 -0
- package/dist/compatibility/claude/projection.d.ts +11 -0
- package/dist/compatibility/claude/projection.d.ts.map +1 -0
- package/dist/compatibility/claude/projection.js +325 -0
- package/dist/compatibility/claude/projection.js.map +1 -0
- package/dist/compatibility/claude/schema.d.ts +18 -0
- package/dist/compatibility/claude/schema.d.ts.map +1 -0
- package/dist/compatibility/claude/schema.js +983 -0
- package/dist/compatibility/claude/schema.js.map +1 -0
- package/dist/compatibility/claude/shared-resources.d.ts +51 -0
- package/dist/compatibility/claude/shared-resources.d.ts.map +1 -0
- package/dist/compatibility/claude/shared-resources.js +382 -0
- package/dist/compatibility/claude/shared-resources.js.map +1 -0
- package/dist/compatibility/claude/sidechain.d.ts +50 -0
- package/dist/compatibility/claude/sidechain.d.ts.map +1 -0
- package/dist/compatibility/claude/sidechain.js +90 -0
- package/dist/compatibility/claude/sidechain.js.map +1 -0
- package/dist/compatibility/claude/tool-links.d.ts +13 -0
- package/dist/compatibility/claude/tool-links.d.ts.map +1 -0
- package/dist/compatibility/claude/tool-links.js +61 -0
- package/dist/compatibility/claude/tool-links.js.map +1 -0
- package/dist/compatibility/claude/translation.d.ts +71 -0
- package/dist/compatibility/claude/translation.d.ts.map +1 -0
- package/dist/compatibility/claude/translation.js +443 -0
- package/dist/compatibility/claude/translation.js.map +1 -0
- package/dist/compatibility/claude/workflow-replay.d.ts +10 -0
- package/dist/compatibility/claude/workflow-replay.d.ts.map +1 -0
- package/dist/compatibility/claude/workflow-replay.js +66 -0
- package/dist/compatibility/claude/workflow-replay.js.map +1 -0
- package/dist/compatibility/claude/workflow.d.ts +33 -0
- package/dist/compatibility/claude/workflow.d.ts.map +1 -0
- package/dist/compatibility/claude/workflow.js +76 -0
- package/dist/compatibility/claude/workflow.js.map +1 -0
- package/dist/core/compaction.d.ts +16 -0
- package/dist/core/compaction.d.ts.map +1 -0
- package/dist/core/compaction.js +2 -0
- package/dist/core/compaction.js.map +1 -0
- package/dist/core/context-budget.d.ts +28 -0
- package/dist/core/context-budget.d.ts.map +1 -0
- package/dist/core/context-budget.js +108 -0
- package/dist/core/context-budget.js.map +1 -0
- package/dist/core/context.d.ts +16 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/context.js +13 -0
- package/dist/core/context.js.map +1 -0
- package/dist/core/runtime.d.ts +394 -0
- package/dist/core/runtime.d.ts.map +1 -0
- package/dist/core/runtime.js +507 -0
- package/dist/core/runtime.js.map +1 -0
- package/dist/core/usage.d.ts +26 -0
- package/dist/core/usage.d.ts.map +1 -0
- package/dist/core/usage.js +133 -0
- package/dist/core/usage.js.map +1 -0
- package/dist/extensions/claude-extension-tools.d.ts +16 -0
- package/dist/extensions/claude-extension-tools.d.ts.map +1 -0
- package/dist/extensions/claude-extension-tools.js +78 -0
- package/dist/extensions/claude-extension-tools.js.map +1 -0
- package/dist/extensions/claude-extensions.d.ts +41 -0
- package/dist/extensions/claude-extensions.d.ts.map +1 -0
- package/dist/extensions/claude-extensions.js +270 -0
- package/dist/extensions/claude-extensions.js.map +1 -0
- package/dist/hooks/claude-hook-tools.d.ts +26 -0
- package/dist/hooks/claude-hook-tools.d.ts.map +1 -0
- package/dist/hooks/claude-hook-tools.js +129 -0
- package/dist/hooks/claude-hook-tools.js.map +1 -0
- package/dist/hooks/claude-hooks.d.ts +87 -0
- package/dist/hooks/claude-hooks.d.ts.map +1 -0
- package/dist/hooks/claude-hooks.js +411 -0
- package/dist/hooks/claude-hooks.js.map +1 -0
- package/dist/maintenance/doctor.d.ts +33 -0
- package/dist/maintenance/doctor.d.ts.map +1 -0
- package/dist/maintenance/doctor.js +391 -0
- package/dist/maintenance/doctor.js.map +1 -0
- package/dist/maintenance/self-update.d.ts +31 -0
- package/dist/maintenance/self-update.d.ts.map +1 -0
- package/dist/maintenance/self-update.js +81 -0
- package/dist/maintenance/self-update.js.map +1 -0
- package/dist/mcp/claude-mcp-management.d.ts +28 -0
- package/dist/mcp/claude-mcp-management.d.ts.map +1 -0
- package/dist/mcp/claude-mcp-management.js +169 -0
- package/dist/mcp/claude-mcp-management.js.map +1 -0
- package/dist/mcp/claude-mcp-oauth.d.ts +113 -0
- package/dist/mcp/claude-mcp-oauth.d.ts.map +1 -0
- package/dist/mcp/claude-mcp-oauth.js +1053 -0
- package/dist/mcp/claude-mcp-oauth.js.map +1 -0
- package/dist/mcp/claude-mcp-tools.d.ts +98 -0
- package/dist/mcp/claude-mcp-tools.d.ts.map +1 -0
- package/dist/mcp/claude-mcp-tools.js +1246 -0
- package/dist/mcp/claude-mcp-tools.js.map +1 -0
- package/dist/mcp/praxis-mcp-server.d.ts +35 -0
- package/dist/mcp/praxis-mcp-server.d.ts.map +1 -0
- package/dist/mcp/praxis-mcp-server.js +520 -0
- package/dist/mcp/praxis-mcp-server.js.map +1 -0
- package/dist/permissions/claude-auto-classifier.d.ts +20 -0
- package/dist/permissions/claude-auto-classifier.d.ts.map +1 -0
- package/dist/permissions/claude-auto-classifier.js +140 -0
- package/dist/permissions/claude-auto-classifier.js.map +1 -0
- package/dist/permissions/claude-permission-resolver.d.ts +30 -0
- package/dist/permissions/claude-permission-resolver.d.ts.map +1 -0
- package/dist/permissions/claude-permission-resolver.js +334 -0
- package/dist/permissions/claude-permission-resolver.js.map +1 -0
- package/dist/persistence/claude-job-store.d.ts +68 -0
- package/dist/persistence/claude-job-store.d.ts.map +1 -0
- package/dist/persistence/claude-job-store.js +311 -0
- package/dist/persistence/claude-job-store.js.map +1 -0
- package/dist/persistence/claude-scheduled-task-store.d.ts +29 -0
- package/dist/persistence/claude-scheduled-task-store.d.ts.map +1 -0
- package/dist/persistence/claude-scheduled-task-store.js +163 -0
- package/dist/persistence/claude-scheduled-task-store.js.map +1 -0
- package/dist/persistence/claude-sidechain-store.d.ts +18 -0
- package/dist/persistence/claude-sidechain-store.d.ts.map +1 -0
- package/dist/persistence/claude-sidechain-store.js +107 -0
- package/dist/persistence/claude-sidechain-store.js.map +1 -0
- package/dist/persistence/claude-task-store.d.ts +72 -0
- package/dist/persistence/claude-task-store.d.ts.map +1 -0
- package/dist/persistence/claude-task-store.js +440 -0
- package/dist/persistence/claude-task-store.js.map +1 -0
- package/dist/persistence/claude-transcript-store.d.ts +85 -0
- package/dist/persistence/claude-transcript-store.d.ts.map +1 -0
- package/dist/persistence/claude-transcript-store.js +385 -0
- package/dist/persistence/claude-transcript-store.js.map +1 -0
- package/dist/persistence/claude-workflow-store.d.ts +41 -0
- package/dist/persistence/claude-workflow-store.d.ts.map +1 -0
- package/dist/persistence/claude-workflow-store.js +263 -0
- package/dist/persistence/claude-workflow-store.js.map +1 -0
- package/dist/persistence/in-memory-sidechain-store.d.ts +12 -0
- package/dist/persistence/in-memory-sidechain-store.d.ts.map +1 -0
- package/dist/persistence/in-memory-sidechain-store.js +30 -0
- package/dist/persistence/in-memory-sidechain-store.js.map +1 -0
- package/dist/persistence/in-memory-transcript-store.d.ts +15 -0
- package/dist/persistence/in-memory-transcript-store.d.ts.map +1 -0
- package/dist/persistence/in-memory-transcript-store.js +95 -0
- package/dist/persistence/in-memory-transcript-store.js.map +1 -0
- package/dist/platform/atomic-write.d.ts +5 -0
- package/dist/platform/atomic-write.d.ts.map +1 -0
- package/dist/platform/atomic-write.js +25 -0
- package/dist/platform/atomic-write.js.map +1 -0
- package/dist/platform/bounded-process-runner.d.ts +28 -0
- package/dist/platform/bounded-process-runner.d.ts.map +1 -0
- package/dist/platform/bounded-process-runner.js +209 -0
- package/dist/platform/bounded-process-runner.js.map +1 -0
- package/dist/platform/claude-version.d.ts +5 -0
- package/dist/platform/claude-version.d.ts.map +1 -0
- package/dist/platform/claude-version.js +17 -0
- package/dist/platform/claude-version.js.map +1 -0
- package/dist/platform/command-shell.d.ts +3 -0
- package/dist/platform/command-shell.d.ts.map +1 -0
- package/dist/platform/command-shell.js +15 -0
- package/dist/platform/command-shell.js.map +1 -0
- package/dist/platform/exclusive-file-lease.d.ts +15 -0
- package/dist/platform/exclusive-file-lease.d.ts.map +1 -0
- package/dist/platform/exclusive-file-lease.js +265 -0
- package/dist/platform/exclusive-file-lease.js.map +1 -0
- package/dist/platform/sensitive-data.d.ts +8 -0
- package/dist/platform/sensitive-data.d.ts.map +1 -0
- package/dist/platform/sensitive-data.js +138 -0
- package/dist/platform/sensitive-data.js.map +1 -0
- package/dist/platform/tmux-worktree.d.ts +30 -0
- package/dist/platform/tmux-worktree.d.ts.map +1 -0
- package/dist/platform/tmux-worktree.js +138 -0
- package/dist/platform/tmux-worktree.js.map +1 -0
- package/dist/plugins/claude-plugin-eval-discovery.d.ts +20 -0
- package/dist/plugins/claude-plugin-eval-discovery.d.ts.map +1 -0
- package/dist/plugins/claude-plugin-eval-discovery.js +128 -0
- package/dist/plugins/claude-plugin-eval-discovery.js.map +1 -0
- package/dist/plugins/claude-plugin-eval-graders.d.ts +49 -0
- package/dist/plugins/claude-plugin-eval-graders.d.ts.map +1 -0
- package/dist/plugins/claude-plugin-eval-graders.js +137 -0
- package/dist/plugins/claude-plugin-eval-graders.js.map +1 -0
- package/dist/plugins/claude-plugin-eval-runner.d.ts +50 -0
- package/dist/plugins/claude-plugin-eval-runner.d.ts.map +1 -0
- package/dist/plugins/claude-plugin-eval-runner.js +189 -0
- package/dist/plugins/claude-plugin-eval-runner.js.map +1 -0
- package/dist/plugins/claude-plugin-eval-schema.d.ts +80 -0
- package/dist/plugins/claude-plugin-eval-schema.d.ts.map +1 -0
- package/dist/plugins/claude-plugin-eval-schema.js +436 -0
- package/dist/plugins/claude-plugin-eval-schema.js.map +1 -0
- package/dist/plugins/claude-plugin-eval.d.ts +66 -0
- package/dist/plugins/claude-plugin-eval.d.ts.map +1 -0
- package/dist/plugins/claude-plugin-eval.js +438 -0
- package/dist/plugins/claude-plugin-eval.js.map +1 -0
- package/dist/plugins/claude-plugin-maintenance.d.ts +39 -0
- package/dist/plugins/claude-plugin-maintenance.d.ts.map +1 -0
- package/dist/plugins/claude-plugin-maintenance.js +336 -0
- package/dist/plugins/claude-plugin-maintenance.js.map +1 -0
- package/dist/plugins/claude-plugin-marketplace.d.ts +97 -0
- package/dist/plugins/claude-plugin-marketplace.d.ts.map +1 -0
- package/dist/plugins/claude-plugin-marketplace.js +1433 -0
- package/dist/plugins/claude-plugin-marketplace.js.map +1 -0
- package/dist/plugins/claude-plugin-mcpb.d.ts +71 -0
- package/dist/plugins/claude-plugin-mcpb.d.ts.map +1 -0
- package/dist/plugins/claude-plugin-mcpb.js +885 -0
- package/dist/plugins/claude-plugin-mcpb.js.map +1 -0
- package/dist/plugins/claude-plugin-runtime.d.ts +120 -0
- package/dist/plugins/claude-plugin-runtime.d.ts.map +1 -0
- package/dist/plugins/claude-plugin-runtime.js +1263 -0
- package/dist/plugins/claude-plugin-runtime.js.map +1 -0
- package/dist/providers/anthropic-compatible.d.ts +35 -0
- package/dist/providers/anthropic-compatible.d.ts.map +1 -0
- package/dist/providers/anthropic-compatible.js +733 -0
- package/dist/providers/anthropic-compatible.js.map +1 -0
- package/dist/providers/environment.d.ts +14 -0
- package/dist/providers/environment.d.ts.map +1 -0
- package/dist/providers/environment.js +67 -0
- package/dist/providers/environment.js.map +1 -0
- package/dist/providers/fallback-provider.d.ts +17 -0
- package/dist/providers/fallback-provider.d.ts.map +1 -0
- package/dist/providers/fallback-provider.js +70 -0
- package/dist/providers/fallback-provider.js.map +1 -0
- package/dist/providers/openai-compatible.d.ts +30 -0
- package/dist/providers/openai-compatible.d.ts.map +1 -0
- package/dist/providers/openai-compatible.js +409 -0
- package/dist/providers/openai-compatible.js.map +1 -0
- package/dist/tools/claude-interactive-tools.d.ts +58 -0
- package/dist/tools/claude-interactive-tools.d.ts.map +1 -0
- package/dist/tools/claude-interactive-tools.js +390 -0
- package/dist/tools/claude-interactive-tools.js.map +1 -0
- package/dist/tools/claude-lsp-formatters.d.ts +2 -0
- package/dist/tools/claude-lsp-formatters.d.ts.map +1 -0
- package/dist/tools/claude-lsp-formatters.js +291 -0
- package/dist/tools/claude-lsp-formatters.js.map +1 -0
- package/dist/tools/claude-lsp-tool.d.ts +22 -0
- package/dist/tools/claude-lsp-tool.d.ts.map +1 -0
- package/dist/tools/claude-lsp-tool.js +744 -0
- package/dist/tools/claude-lsp-tool.js.map +1 -0
- package/dist/tools/claude-scheduled-tools.d.ts +20 -0
- package/dist/tools/claude-scheduled-tools.d.ts.map +1 -0
- package/dist/tools/claude-scheduled-tools.js +344 -0
- package/dist/tools/claude-scheduled-tools.js.map +1 -0
- package/dist/tools/claude-task-tools.d.ts +30 -0
- package/dist/tools/claude-task-tools.d.ts.map +1 -0
- package/dist/tools/claude-task-tools.js +503 -0
- package/dist/tools/claude-task-tools.js.map +1 -0
- package/dist/tools/claude-user-message.d.ts +16 -0
- package/dist/tools/claude-user-message.d.ts.map +1 -0
- package/dist/tools/claude-user-message.js +98 -0
- package/dist/tools/claude-user-message.js.map +1 -0
- package/dist/tools/claude-workflow-tools.d.ts +27 -0
- package/dist/tools/claude-workflow-tools.d.ts.map +1 -0
- package/dist/tools/claude-workflow-tools.js +257 -0
- package/dist/tools/claude-workflow-tools.js.map +1 -0
- package/dist/tools/claude-worktree-tools.d.ts +17 -0
- package/dist/tools/claude-worktree-tools.d.ts.map +1 -0
- package/dist/tools/claude-worktree-tools.js +135 -0
- package/dist/tools/claude-worktree-tools.js.map +1 -0
- package/dist/tools/filtered-tool-registry.d.ts +16 -0
- package/dist/tools/filtered-tool-registry.d.ts.map +1 -0
- package/dist/tools/filtered-tool-registry.js +51 -0
- package/dist/tools/filtered-tool-registry.js.map +1 -0
- package/dist/tools/glob.d.ts +9 -0
- package/dist/tools/glob.d.ts.map +1 -0
- package/dist/tools/glob.js +88 -0
- package/dist/tools/glob.js.map +1 -0
- package/dist/tools/local-tools.d.ts +47 -0
- package/dist/tools/local-tools.d.ts.map +1 -0
- package/dist/tools/local-tools.js +1082 -0
- package/dist/tools/local-tools.js.map +1 -0
- package/dist/tools/notebook.d.ts +22 -0
- package/dist/tools/notebook.d.ts.map +1 -0
- package/dist/tools/notebook.js +154 -0
- package/dist/tools/notebook.js.map +1 -0
- package/dist/tools/web.d.ts +48 -0
- package/dist/tools/web.d.ts.map +1 -0
- package/dist/tools/web.js +726 -0
- package/dist/tools/web.js.map +1 -0
- 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
|