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
|
@@ -0,0 +1,885 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { chmod, lstat, mkdir, mkdtemp, readFile, readdir, realpath, rename, rm, stat, writeFile, } from 'node:fs/promises';
|
|
3
|
+
import { closeSync, openSync, writeSync } from 'node:fs';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep, } from 'node:path';
|
|
6
|
+
import { Unzip, UnzipInflate } from 'fflate';
|
|
7
|
+
import { ExclusiveFileLease, } from '../platform/exclusive-file-lease.js';
|
|
8
|
+
const DEFAULT_ARCHIVE_BYTES = 512 * 1024 * 1024;
|
|
9
|
+
const DEFAULT_EXTRACTED_BYTES = 1024 * 1024 * 1024;
|
|
10
|
+
const DEFAULT_FILE_BYTES = 512 * 1024 * 1024;
|
|
11
|
+
const DEFAULT_FILES = 100_000;
|
|
12
|
+
const MAX_COMPRESSION_RATIO = 50;
|
|
13
|
+
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
14
|
+
const MANIFEST_FILE = 'manifest.json';
|
|
15
|
+
function isRecord(value) {
|
|
16
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
17
|
+
}
|
|
18
|
+
function safeRelativePath(value, label) {
|
|
19
|
+
const normalized = value.replaceAll('\\', '/');
|
|
20
|
+
if (normalized.length === 0 ||
|
|
21
|
+
normalized.startsWith('/') ||
|
|
22
|
+
/^[A-Za-z]:\//u.test(normalized) ||
|
|
23
|
+
normalized.includes('\0') ||
|
|
24
|
+
normalized.split('/').some((segment) => segment === '..')) {
|
|
25
|
+
throw new Error(`${label} must stay inside bundle root`);
|
|
26
|
+
}
|
|
27
|
+
const stripped = normalized.replace(/^\.\//u, '');
|
|
28
|
+
const segments = stripped.endsWith('/')
|
|
29
|
+
? stripped.slice(0, -1).split('/')
|
|
30
|
+
: stripped.split('/');
|
|
31
|
+
if (stripped.length === 0 ||
|
|
32
|
+
segments.some((segment) => segment.length === 0 || segment === '.')) {
|
|
33
|
+
throw new Error(`${label} must stay inside bundle root`);
|
|
34
|
+
}
|
|
35
|
+
return stripped;
|
|
36
|
+
}
|
|
37
|
+
function isValueForType(value, definition) {
|
|
38
|
+
if (Array.isArray(value)) {
|
|
39
|
+
return (definition.type === 'string' &&
|
|
40
|
+
definition.multiple === true &&
|
|
41
|
+
value.every((item) => typeof item === 'string'));
|
|
42
|
+
}
|
|
43
|
+
if (definition.type === 'boolean')
|
|
44
|
+
return typeof value === 'boolean';
|
|
45
|
+
if (definition.type === 'number')
|
|
46
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
47
|
+
return typeof value === 'string';
|
|
48
|
+
}
|
|
49
|
+
async function validateManifest(value) {
|
|
50
|
+
const { vAny } = await import('@anthropic-ai/mcpb/schemas');
|
|
51
|
+
const parsed = vAny.McpbManifestSchema.safeParse(value);
|
|
52
|
+
if (!parsed.success) {
|
|
53
|
+
const detail = parsed.error.issues
|
|
54
|
+
.map((issue) => `${issue.path.join('.') || 'manifest'}: ${issue.message}`)
|
|
55
|
+
.join('; ');
|
|
56
|
+
throw new Error(`Invalid MCPB manifest: ${detail}`);
|
|
57
|
+
}
|
|
58
|
+
const manifest = parsed.data;
|
|
59
|
+
const manifestVersion = manifest.manifest_version ?? manifest.dxt_version;
|
|
60
|
+
return {
|
|
61
|
+
...manifest,
|
|
62
|
+
manifest_version: manifestVersion,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function resolveUserConfig(manifest, supplied) {
|
|
66
|
+
const definitions = manifest.user_config ?? {};
|
|
67
|
+
const unknown = Object.keys(supplied).find((key) => definitions[key] === undefined);
|
|
68
|
+
if (unknown !== undefined)
|
|
69
|
+
throw new Error(`Unknown MCPB user_config value: ${unknown}`);
|
|
70
|
+
const values = {};
|
|
71
|
+
const sensitive = [];
|
|
72
|
+
for (const [key, definition] of Object.entries(definitions)) {
|
|
73
|
+
const rawSupplied = supplied[key];
|
|
74
|
+
const suppliedValue = typeof rawSupplied === 'string' && definition.multiple === true
|
|
75
|
+
? rawSupplied
|
|
76
|
+
.split(',')
|
|
77
|
+
.map((item) => item.trim())
|
|
78
|
+
.filter((item) => item.length > 0)
|
|
79
|
+
: typeof rawSupplied === 'string' && definition.type === 'boolean'
|
|
80
|
+
? rawSupplied === 'true'
|
|
81
|
+
? true
|
|
82
|
+
: rawSupplied === 'false'
|
|
83
|
+
? false
|
|
84
|
+
: rawSupplied
|
|
85
|
+
: typeof rawSupplied === 'string' && definition.type === 'number'
|
|
86
|
+
? rawSupplied.trim().length > 0 &&
|
|
87
|
+
Number.isFinite(Number(rawSupplied))
|
|
88
|
+
? Number(rawSupplied)
|
|
89
|
+
: rawSupplied
|
|
90
|
+
: rawSupplied;
|
|
91
|
+
if (definition.required === true &&
|
|
92
|
+
(suppliedValue === undefined ||
|
|
93
|
+
suppliedValue === '' ||
|
|
94
|
+
(Array.isArray(suppliedValue) &&
|
|
95
|
+
(suppliedValue.length === 0 ||
|
|
96
|
+
suppliedValue.some((item) => item === ''))))) {
|
|
97
|
+
throw new Error(`Required MCPB user_config is missing: ${key}`);
|
|
98
|
+
}
|
|
99
|
+
const value = suppliedValue ?? definition.default;
|
|
100
|
+
if (value === undefined) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (suppliedValue !== undefined && !isValueForType(value, definition))
|
|
104
|
+
throw new Error(`MCPB user_config ${key} has invalid value type`);
|
|
105
|
+
if (suppliedValue !== undefined &&
|
|
106
|
+
typeof value === 'number' &&
|
|
107
|
+
((definition.min !== undefined && value < definition.min) ||
|
|
108
|
+
(definition.max !== undefined && value > definition.max))) {
|
|
109
|
+
throw new Error(`MCPB user_config ${key} is outside its allowed range`);
|
|
110
|
+
}
|
|
111
|
+
values[key] = value;
|
|
112
|
+
if (definition.sensitive === true) {
|
|
113
|
+
const rendered = Array.isArray(value)
|
|
114
|
+
? [...value, value.join(',')]
|
|
115
|
+
: [String(value)];
|
|
116
|
+
sensitive.push(...rendered.filter((item) => item.length > 0));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return { values, sensitive };
|
|
120
|
+
}
|
|
121
|
+
function renderUserValue(value) {
|
|
122
|
+
return Array.isArray(value) ? value.join(',') : String(value);
|
|
123
|
+
}
|
|
124
|
+
function expandString(value, pluginRoot, extractedPath, data, environment, userConfig) {
|
|
125
|
+
return value.replace(/\$\{([^}]+)\}/gu, (placeholder, expression) => {
|
|
126
|
+
if (expression === '__dirname' ||
|
|
127
|
+
expression === 'MCPB_ROOT' ||
|
|
128
|
+
expression === 'DXT_ROOT')
|
|
129
|
+
return extractedPath;
|
|
130
|
+
if (expression === 'CLAUDE_PLUGIN_ROOT')
|
|
131
|
+
return pluginRoot;
|
|
132
|
+
if (expression === 'CLAUDE_PLUGIN_DATA')
|
|
133
|
+
return data;
|
|
134
|
+
if (expression === 'pathSeparator' || expression === '/')
|
|
135
|
+
return sep;
|
|
136
|
+
const home = homedir();
|
|
137
|
+
const systemDirectory = {
|
|
138
|
+
HOME: home,
|
|
139
|
+
DESKTOP: join(home, 'Desktop'),
|
|
140
|
+
DOCUMENTS: join(home, 'Documents'),
|
|
141
|
+
DOWNLOADS: join(home, 'Downloads'),
|
|
142
|
+
}[expression];
|
|
143
|
+
if (systemDirectory !== undefined)
|
|
144
|
+
return systemDirectory;
|
|
145
|
+
const userMatch = /^(?:user_config[.:])([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?$/u.exec(expression);
|
|
146
|
+
if (userMatch) {
|
|
147
|
+
const configured = userConfig[userMatch[1] ?? ''];
|
|
148
|
+
if (configured !== undefined) {
|
|
149
|
+
return Array.isArray(configured)
|
|
150
|
+
? placeholder
|
|
151
|
+
: renderUserValue(configured);
|
|
152
|
+
}
|
|
153
|
+
if (userMatch[2] !== undefined)
|
|
154
|
+
return userMatch[2];
|
|
155
|
+
return '';
|
|
156
|
+
}
|
|
157
|
+
const envMatch = /^(?:env[.:])?([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?$/u.exec(expression);
|
|
158
|
+
if (envMatch)
|
|
159
|
+
return environment[envMatch[1] ?? ''] ?? envMatch[2] ?? placeholder;
|
|
160
|
+
return placeholder;
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
async function serverConfig(manifest, pluginRoot, extractedPath, data, environment, userConfig) {
|
|
164
|
+
const { getMcpConfigForManifest } = await import('@anthropic-ai/mcpb');
|
|
165
|
+
const home = homedir();
|
|
166
|
+
const generated = await getMcpConfigForManifest({
|
|
167
|
+
manifest: manifest,
|
|
168
|
+
extensionPath: extractedPath,
|
|
169
|
+
systemDirs: {
|
|
170
|
+
HOME: home,
|
|
171
|
+
DESKTOP: join(home, 'Desktop'),
|
|
172
|
+
DOCUMENTS: join(home, 'Documents'),
|
|
173
|
+
DOWNLOADS: join(home, 'Downloads'),
|
|
174
|
+
},
|
|
175
|
+
userConfig: userConfig,
|
|
176
|
+
pathSeparator: sep,
|
|
177
|
+
});
|
|
178
|
+
if (generated === undefined || typeof generated.command !== 'string') {
|
|
179
|
+
throw new Error('MCPB manifest did not produce an MCP server config');
|
|
180
|
+
}
|
|
181
|
+
const command = generated.command;
|
|
182
|
+
const args = Array.isArray(generated.args) ? generated.args : [];
|
|
183
|
+
const configuredEnvironment = isRecord(generated.env) ? generated.env : {};
|
|
184
|
+
const expand = (value) => expandString(value, pluginRoot, extractedPath, data, environment, userConfig);
|
|
185
|
+
return {
|
|
186
|
+
command: expand(command),
|
|
187
|
+
args: args.flatMap((argument) => {
|
|
188
|
+
if (typeof argument !== 'string') {
|
|
189
|
+
throw new Error('MCPB generated args must contain strings');
|
|
190
|
+
}
|
|
191
|
+
const match = /^\$\{user_config\.([A-Za-z_][A-Za-z0-9_]*)\}$/u.exec(argument);
|
|
192
|
+
const value = match ? userConfig[match[1] ?? ''] : undefined;
|
|
193
|
+
return Array.isArray(value) ? [...value] : [expand(argument)];
|
|
194
|
+
}),
|
|
195
|
+
env: {
|
|
196
|
+
CLAUDE_PLUGIN_ROOT: pluginRoot,
|
|
197
|
+
CLAUDE_PLUGIN_DATA: data,
|
|
198
|
+
...Object.fromEntries(Object.entries(configuredEnvironment).map(([key, value]) => {
|
|
199
|
+
if (typeof value !== 'string') {
|
|
200
|
+
throw new Error('MCPB generated env must contain strings');
|
|
201
|
+
}
|
|
202
|
+
return [key, expand(value)];
|
|
203
|
+
})),
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function uint16(bytes, offset) {
|
|
208
|
+
return (bytes.at(offset) ?? 0) | ((bytes.at(offset + 1) ?? 0) << 8);
|
|
209
|
+
}
|
|
210
|
+
function uint32(bytes, offset) {
|
|
211
|
+
return (((bytes.at(offset) ?? 0) |
|
|
212
|
+
((bytes.at(offset + 1) ?? 0) << 8) |
|
|
213
|
+
((bytes.at(offset + 2) ?? 0) << 16) |
|
|
214
|
+
((bytes.at(offset + 3) ?? 0) << 24)) >>>
|
|
215
|
+
0);
|
|
216
|
+
}
|
|
217
|
+
function archiveEntries(bytes, limits) {
|
|
218
|
+
let eocd = -1;
|
|
219
|
+
for (let offset = bytes.length - 22; offset >= Math.max(0, bytes.length - 65_557); offset -= 1) {
|
|
220
|
+
if (uint32(bytes, offset) === 0x06054b50) {
|
|
221
|
+
eocd = offset;
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (eocd < 0)
|
|
226
|
+
throw new Error('Invalid MCPB archive: end record not found');
|
|
227
|
+
const count = uint16(bytes, eocd + 10);
|
|
228
|
+
const centralOffset = uint32(bytes, eocd + 16);
|
|
229
|
+
if (count === 0xffff || centralOffset === 0xffffffff)
|
|
230
|
+
throw new Error('ZIP64 MCPB archives are not supported');
|
|
231
|
+
if (count > limits.files)
|
|
232
|
+
throw new Error(`MCPB archive exceeds ${limits.files} files`);
|
|
233
|
+
const decoder = new TextDecoder('utf-8', { fatal: true });
|
|
234
|
+
const seen = new Set();
|
|
235
|
+
const result = [];
|
|
236
|
+
let extractedBytes = 0;
|
|
237
|
+
let offset = centralOffset;
|
|
238
|
+
for (let index = 0; index < count; index += 1) {
|
|
239
|
+
if (offset + 46 > bytes.length || uint32(bytes, offset) !== 0x02014b50)
|
|
240
|
+
throw new Error('Invalid MCPB archive central directory');
|
|
241
|
+
const flags = uint16(bytes, offset + 8);
|
|
242
|
+
if ((flags & 1) !== 0)
|
|
243
|
+
throw new Error('Encrypted MCPB archives are not supported');
|
|
244
|
+
const compressedSize = uint32(bytes, offset + 20);
|
|
245
|
+
const size = uint32(bytes, offset + 24);
|
|
246
|
+
const nameLength = uint16(bytes, offset + 28);
|
|
247
|
+
const extraLength = uint16(bytes, offset + 30);
|
|
248
|
+
const commentLength = uint16(bytes, offset + 32);
|
|
249
|
+
const attrs = uint32(bytes, offset + 38);
|
|
250
|
+
const nameEnd = offset + 46 + nameLength;
|
|
251
|
+
if (nameEnd + extraLength + commentLength > bytes.length)
|
|
252
|
+
throw new Error('Invalid MCPB archive entry');
|
|
253
|
+
const rawName = decoder.decode(bytes.subarray(offset + 46, nameEnd));
|
|
254
|
+
const name = safeRelativePath(rawName, 'MCPB archive path');
|
|
255
|
+
if (seen.has(name))
|
|
256
|
+
throw new Error(`MCPB archive has duplicate path: ${name}`);
|
|
257
|
+
seen.add(name);
|
|
258
|
+
const mode = (attrs >>> 16) & 0xffff;
|
|
259
|
+
if ((mode & 0o170000) === 0o120000)
|
|
260
|
+
throw new Error(`MCPB archive contains symlink: ${name}`);
|
|
261
|
+
const directory = rawName.replaceAll('\\', '/').endsWith('/') ||
|
|
262
|
+
(mode & 0o170000) === 0o040000;
|
|
263
|
+
if (!directory) {
|
|
264
|
+
if (size > DEFAULT_FILE_BYTES) {
|
|
265
|
+
throw new Error(`MCPB archive file exceeds ${DEFAULT_FILE_BYTES} bytes`);
|
|
266
|
+
}
|
|
267
|
+
if (size > 0 &&
|
|
268
|
+
(compressedSize === 0 || size / compressedSize > MAX_COMPRESSION_RATIO)) {
|
|
269
|
+
throw new Error(`MCPB archive compression ratio exceeds ${MAX_COMPRESSION_RATIO}`);
|
|
270
|
+
}
|
|
271
|
+
extractedBytes += size;
|
|
272
|
+
if (extractedBytes > limits.extractedBytes)
|
|
273
|
+
throw new Error(`MCPB archive exceeds ${limits.extractedBytes} extracted bytes`);
|
|
274
|
+
}
|
|
275
|
+
result.push({
|
|
276
|
+
name,
|
|
277
|
+
size,
|
|
278
|
+
compressedSize,
|
|
279
|
+
...(mode === 0 ? {} : { mode }),
|
|
280
|
+
directory,
|
|
281
|
+
});
|
|
282
|
+
offset = nameEnd + extraLength + commentLength;
|
|
283
|
+
}
|
|
284
|
+
return result;
|
|
285
|
+
}
|
|
286
|
+
async function extractArchive(bytes, destination, limits, signal) {
|
|
287
|
+
signal.throwIfAborted();
|
|
288
|
+
const entries = archiveEntries(bytes, limits);
|
|
289
|
+
const entryByName = new Map(entries.map((entry) => [entry.name, entry]));
|
|
290
|
+
await mkdir(destination, { recursive: true });
|
|
291
|
+
for (const entry of entries) {
|
|
292
|
+
signal.throwIfAborted();
|
|
293
|
+
const target = join(destination, entry.name);
|
|
294
|
+
await mkdir(entry.directory ? target : dirname(target), {
|
|
295
|
+
recursive: true,
|
|
296
|
+
mode: 0o755,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
const openFiles = new Map();
|
|
300
|
+
const completed = new Set();
|
|
301
|
+
let totalBytes = 0;
|
|
302
|
+
let extractionError;
|
|
303
|
+
const unzip = new Unzip((file) => {
|
|
304
|
+
let name;
|
|
305
|
+
let entry;
|
|
306
|
+
try {
|
|
307
|
+
name = safeRelativePath(file.name, 'MCPB archive path');
|
|
308
|
+
entry = entryByName.get(name);
|
|
309
|
+
if (entry === undefined)
|
|
310
|
+
throw new Error(`Invalid MCPB archive entry: ${name}`);
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
extractionError =
|
|
314
|
+
error instanceof Error ? error : new Error(String(error));
|
|
315
|
+
file.terminate();
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
file.ondata = (error, chunk, final) => {
|
|
319
|
+
try {
|
|
320
|
+
if (extractionError !== undefined) {
|
|
321
|
+
file.terminate();
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
signal.throwIfAborted();
|
|
325
|
+
if (error !== null)
|
|
326
|
+
throw error;
|
|
327
|
+
if (entry.directory) {
|
|
328
|
+
if (chunk.byteLength !== 0)
|
|
329
|
+
throw new Error(`Invalid MCPB archive directory: ${name}`);
|
|
330
|
+
if (final)
|
|
331
|
+
completed.add(name);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
let state = openFiles.get(name);
|
|
335
|
+
if (state === undefined) {
|
|
336
|
+
state = {
|
|
337
|
+
descriptor: openSync(join(destination, name), 'wx', 0o600),
|
|
338
|
+
length: 0,
|
|
339
|
+
};
|
|
340
|
+
openFiles.set(name, state);
|
|
341
|
+
}
|
|
342
|
+
const nextLength = state.length + chunk.byteLength;
|
|
343
|
+
const ratioLimit = entry.compressedSize * MAX_COMPRESSION_RATIO;
|
|
344
|
+
if (nextLength > entry.size ||
|
|
345
|
+
(nextLength > 0 &&
|
|
346
|
+
(entry.compressedSize === 0 || nextLength > ratioLimit)) ||
|
|
347
|
+
totalBytes + chunk.byteLength > limits.extractedBytes) {
|
|
348
|
+
throw new Error(`MCPB archive exceeds ${limits.extractedBytes} extracted bytes or compression ratio ${MAX_COMPRESSION_RATIO}`);
|
|
349
|
+
}
|
|
350
|
+
let written = 0;
|
|
351
|
+
while (written < chunk.byteLength) {
|
|
352
|
+
const count = writeSync(state.descriptor, chunk, written, chunk.byteLength - written, state.length + written);
|
|
353
|
+
if (count === 0)
|
|
354
|
+
throw new Error(`Unable to write MCPB archive content: ${name}`);
|
|
355
|
+
written += count;
|
|
356
|
+
}
|
|
357
|
+
state.length = nextLength;
|
|
358
|
+
totalBytes += chunk.byteLength;
|
|
359
|
+
if (!final)
|
|
360
|
+
return;
|
|
361
|
+
if (state.length !== entry.size)
|
|
362
|
+
throw new Error(`Invalid MCPB archive content: ${name}`);
|
|
363
|
+
closeSync(state.descriptor);
|
|
364
|
+
openFiles.delete(name);
|
|
365
|
+
completed.add(name);
|
|
366
|
+
}
|
|
367
|
+
catch (caught) {
|
|
368
|
+
extractionError =
|
|
369
|
+
caught instanceof Error ? caught : new Error(String(caught));
|
|
370
|
+
file.terminate();
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
file.start();
|
|
374
|
+
});
|
|
375
|
+
unzip.register(UnzipInflate);
|
|
376
|
+
try {
|
|
377
|
+
for (let offset = 0; offset < bytes.byteLength; offset += 4 * 1024) {
|
|
378
|
+
signal.throwIfAborted();
|
|
379
|
+
unzip.push(bytes.subarray(offset, Math.min(offset + 4 * 1024, bytes.byteLength)), offset + 4 * 1024 >= bytes.byteLength);
|
|
380
|
+
if (extractionError !== undefined)
|
|
381
|
+
throw extractionError;
|
|
382
|
+
}
|
|
383
|
+
if (extractionError !== undefined)
|
|
384
|
+
throw extractionError;
|
|
385
|
+
}
|
|
386
|
+
finally {
|
|
387
|
+
for (const { descriptor } of openFiles.values()) {
|
|
388
|
+
try {
|
|
389
|
+
closeSync(descriptor);
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
/* preserve extraction failure */
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
for (const entry of entries) {
|
|
397
|
+
signal.throwIfAborted();
|
|
398
|
+
if (!completed.has(entry.name))
|
|
399
|
+
throw new Error(`Invalid MCPB archive content: ${entry.name}`);
|
|
400
|
+
if (entry.directory)
|
|
401
|
+
continue;
|
|
402
|
+
const target = join(destination, entry.name);
|
|
403
|
+
await chmod(target, entry.mode === undefined
|
|
404
|
+
? 0o644
|
|
405
|
+
: (entry.mode & 0o111) === 0
|
|
406
|
+
? 0o644
|
|
407
|
+
: 0o755);
|
|
408
|
+
}
|
|
409
|
+
signal.throwIfAborted();
|
|
410
|
+
}
|
|
411
|
+
function safeSourceLabel(source) {
|
|
412
|
+
try {
|
|
413
|
+
const url = new URL(source);
|
|
414
|
+
return `${url.origin}${url.pathname}`;
|
|
415
|
+
}
|
|
416
|
+
catch {
|
|
417
|
+
return basename(source);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
function redactedErrorMessage(error, source, userConfig) {
|
|
421
|
+
let message = error instanceof Error ? error.message : String(error);
|
|
422
|
+
message = message.replaceAll(source, safeSourceLabel(source));
|
|
423
|
+
for (const value of Object.values(userConfig)) {
|
|
424
|
+
const rendered = Array.isArray(value)
|
|
425
|
+
? [...value, renderUserValue(value)]
|
|
426
|
+
: [renderUserValue(value)];
|
|
427
|
+
for (const candidate of rendered) {
|
|
428
|
+
if (candidate.length >= 3)
|
|
429
|
+
message = message.replaceAll(candidate, '[REDACTED]');
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return message;
|
|
433
|
+
}
|
|
434
|
+
function mcpbLoadError(error, source, userConfig) {
|
|
435
|
+
return new Error(`Unable to load MCPB bundle ${safeSourceLabel(source)}: ${redactedErrorMessage(error, source, userConfig)}`);
|
|
436
|
+
}
|
|
437
|
+
async function configuredUserValues(options, manifest) {
|
|
438
|
+
const resolved = await options.resolveUserConfig?.(manifest);
|
|
439
|
+
return { ...(options.userConfig ?? {}), ...(resolved ?? {}) };
|
|
440
|
+
}
|
|
441
|
+
function hash(value) {
|
|
442
|
+
return createHash('sha256').update(value).digest('hex');
|
|
443
|
+
}
|
|
444
|
+
function isWellFormedLease(value) {
|
|
445
|
+
if (!isRecord(value))
|
|
446
|
+
return false;
|
|
447
|
+
return (value.version === 1 &&
|
|
448
|
+
Number.isSafeInteger(value.pid) &&
|
|
449
|
+
Number(value.pid) > 0 &&
|
|
450
|
+
typeof value.token === 'string' &&
|
|
451
|
+
/^[A-Za-z0-9_-]{1,128}$/u.test(value.token) &&
|
|
452
|
+
typeof value.createdAt === 'string');
|
|
453
|
+
}
|
|
454
|
+
async function readCached(entry, source, cacheRoot) {
|
|
455
|
+
try {
|
|
456
|
+
const entryStat = await lstat(entry);
|
|
457
|
+
if (!entryStat.isDirectory() || entryStat.isSymbolicLink())
|
|
458
|
+
return undefined;
|
|
459
|
+
const canonicalEntry = await realpath(entry);
|
|
460
|
+
if (!pathIsWithin(cacheRoot, canonicalEntry, false))
|
|
461
|
+
return undefined;
|
|
462
|
+
const extracted = join(entry, 'extracted');
|
|
463
|
+
const extractedStat = await lstat(extracted);
|
|
464
|
+
if (!extractedStat.isDirectory() || extractedStat.isSymbolicLink())
|
|
465
|
+
return undefined;
|
|
466
|
+
const canonicalExtracted = await realpath(extracted);
|
|
467
|
+
if (!pathIsWithin(canonicalEntry, canonicalExtracted, false))
|
|
468
|
+
return undefined;
|
|
469
|
+
const metadataPath = join(entry, 'metadata.json');
|
|
470
|
+
const metadataStat = await lstat(metadataPath);
|
|
471
|
+
if (!metadataStat.isFile() || metadataStat.isSymbolicLink())
|
|
472
|
+
return undefined;
|
|
473
|
+
if (!pathIsWithin(canonicalEntry, await realpath(metadataPath), false))
|
|
474
|
+
return undefined;
|
|
475
|
+
const manifestPath = join(extracted, MANIFEST_FILE);
|
|
476
|
+
const manifestStat = await lstat(manifestPath);
|
|
477
|
+
if (!manifestStat.isFile() || manifestStat.isSymbolicLink())
|
|
478
|
+
return undefined;
|
|
479
|
+
if (!pathIsWithin(canonicalExtracted, await realpath(manifestPath), false))
|
|
480
|
+
return undefined;
|
|
481
|
+
const metadata = JSON.parse(await readFile(metadataPath, 'utf8'));
|
|
482
|
+
if (!isRecord(metadata) ||
|
|
483
|
+
metadata.source !== source ||
|
|
484
|
+
typeof metadata.fingerprint !== 'string')
|
|
485
|
+
return undefined;
|
|
486
|
+
const manifest = await validateManifest(JSON.parse(await readFile(manifestPath, 'utf8')));
|
|
487
|
+
return { metadata: metadata, manifest };
|
|
488
|
+
}
|
|
489
|
+
catch {
|
|
490
|
+
return undefined;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
function pathIsWithin(root, candidate, allowSame) {
|
|
494
|
+
const rel = relative(root, candidate);
|
|
495
|
+
return ((allowSame || rel.length > 0) &&
|
|
496
|
+
rel !== '..' &&
|
|
497
|
+
!rel.startsWith('../') &&
|
|
498
|
+
!isAbsolute(rel));
|
|
499
|
+
}
|
|
500
|
+
async function fetchArchive(source, cached, options, signal, archiveLimit) {
|
|
501
|
+
const headers = {};
|
|
502
|
+
if (!options.refresh && cached?.etag)
|
|
503
|
+
headers['if-none-match'] = cached.etag;
|
|
504
|
+
if (!options.refresh && cached?.lastModified)
|
|
505
|
+
headers['if-modified-since'] = cached.lastModified;
|
|
506
|
+
let response;
|
|
507
|
+
try {
|
|
508
|
+
let requestUrl = source;
|
|
509
|
+
let redirects = 0;
|
|
510
|
+
for (;;) {
|
|
511
|
+
response = await (options.fetch ?? fetch)(requestUrl, {
|
|
512
|
+
redirect: 'manual',
|
|
513
|
+
headers,
|
|
514
|
+
signal,
|
|
515
|
+
});
|
|
516
|
+
if (![301, 302, 303, 307, 308].includes(response.status))
|
|
517
|
+
break;
|
|
518
|
+
const location = response.headers.get('location');
|
|
519
|
+
if (location === null) {
|
|
520
|
+
await response.body?.cancel();
|
|
521
|
+
throw new Error('MCPB download redirect is missing Location');
|
|
522
|
+
}
|
|
523
|
+
const redirected = new URL(location, requestUrl);
|
|
524
|
+
if (redirected.protocol !== 'http:' && redirected.protocol !== 'https:') {
|
|
525
|
+
await response.body?.cancel();
|
|
526
|
+
throw new Error('MCPB download redirect must use HTTP or HTTPS');
|
|
527
|
+
}
|
|
528
|
+
await response.body?.cancel();
|
|
529
|
+
if (++redirects > 5) {
|
|
530
|
+
throw new Error('MCPB download exceeded 5 redirects');
|
|
531
|
+
}
|
|
532
|
+
requestUrl = redirected.href;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
catch (error) {
|
|
536
|
+
throw new Error(`MCPB download failed: ${safeSourceLabel(source)}: ${redactedErrorMessage(error, source, {})}`);
|
|
537
|
+
}
|
|
538
|
+
if (response.status === 304)
|
|
539
|
+
return {};
|
|
540
|
+
if (!response.ok) {
|
|
541
|
+
await response.body?.cancel();
|
|
542
|
+
throw new Error(`MCPB download failed with HTTP ${response.status}: ${safeSourceLabel(source)}`);
|
|
543
|
+
}
|
|
544
|
+
const declared = Number(response.headers.get('content-length'));
|
|
545
|
+
if (Number.isFinite(declared) && declared > archiveLimit) {
|
|
546
|
+
await response.body?.cancel();
|
|
547
|
+
throw new Error(`MCPB download exceeds ${archiveLimit} bytes`);
|
|
548
|
+
}
|
|
549
|
+
if (!response.body)
|
|
550
|
+
throw new Error('MCPB download returned no body');
|
|
551
|
+
const chunks = [];
|
|
552
|
+
let length = 0;
|
|
553
|
+
for await (const raw of response.body) {
|
|
554
|
+
signal.throwIfAborted();
|
|
555
|
+
const chunk = raw instanceof Uint8Array ? raw : new Uint8Array(raw);
|
|
556
|
+
length += chunk.byteLength;
|
|
557
|
+
if (length > archiveLimit)
|
|
558
|
+
throw new Error(`MCPB download exceeds ${archiveLimit} bytes`);
|
|
559
|
+
chunks.push(chunk);
|
|
560
|
+
}
|
|
561
|
+
const bytes = new Uint8Array(length);
|
|
562
|
+
let offset = 0;
|
|
563
|
+
for (const chunk of chunks) {
|
|
564
|
+
bytes.set(chunk, offset);
|
|
565
|
+
offset += chunk.byteLength;
|
|
566
|
+
}
|
|
567
|
+
const responseEtag = response.headers.get('etag');
|
|
568
|
+
const responseLastModified = response.headers.get('last-modified');
|
|
569
|
+
return {
|
|
570
|
+
bytes,
|
|
571
|
+
...(responseEtag === null ? {} : { etag: responseEtag }),
|
|
572
|
+
...(responseLastModified === null
|
|
573
|
+
? {}
|
|
574
|
+
: { lastModified: responseLastModified }),
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
async function replaceCache(staging, target) {
|
|
578
|
+
const backup = `${target}.${process.pid}.${randomUUID()}.bak`;
|
|
579
|
+
let backedUp = false;
|
|
580
|
+
try {
|
|
581
|
+
try {
|
|
582
|
+
await rename(target, backup);
|
|
583
|
+
backedUp = true;
|
|
584
|
+
}
|
|
585
|
+
catch (error) {
|
|
586
|
+
if (error.code !== 'ENOENT')
|
|
587
|
+
throw error;
|
|
588
|
+
}
|
|
589
|
+
await rename(staging, target);
|
|
590
|
+
if (backedUp)
|
|
591
|
+
await rm(backup, { recursive: true, force: true });
|
|
592
|
+
}
|
|
593
|
+
catch (error) {
|
|
594
|
+
await rm(staging, { recursive: true, force: true });
|
|
595
|
+
if (backedUp) {
|
|
596
|
+
try {
|
|
597
|
+
await rename(backup, target);
|
|
598
|
+
}
|
|
599
|
+
catch {
|
|
600
|
+
/* preserve original failure */
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
throw error;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
async function resolveMcpbSource(options, signal) {
|
|
607
|
+
signal.throwIfAborted();
|
|
608
|
+
const pluginRoot = await realpath(resolve(options.pluginRoot));
|
|
609
|
+
signal.throwIfAborted();
|
|
610
|
+
const remote = /^https?:\/\//u.test(options.source);
|
|
611
|
+
if (!remote && /^[a-z][a-z0-9+.-]*:\/\//iu.test(options.source))
|
|
612
|
+
throw new Error('MCPB source URL must use HTTP or HTTPS');
|
|
613
|
+
if (!['.mcpb', '.dxt'].some((extension) => options.source.endsWith(extension))) {
|
|
614
|
+
throw new Error('MCPB source must end in .mcpb or .dxt');
|
|
615
|
+
}
|
|
616
|
+
if (remote)
|
|
617
|
+
return { pluginRoot, source: options.source, remote: true };
|
|
618
|
+
const requestedSource = resolve(pluginRoot, options.source);
|
|
619
|
+
const requestedRelative = relative(pluginRoot, requestedSource);
|
|
620
|
+
if (requestedRelative === '..' ||
|
|
621
|
+
requestedRelative.startsWith('../') ||
|
|
622
|
+
isAbsolute(requestedRelative))
|
|
623
|
+
throw new Error('Local MCPB source escapes plugin root');
|
|
624
|
+
const source = await realpath(requestedSource);
|
|
625
|
+
signal.throwIfAborted();
|
|
626
|
+
const canonicalRelative = relative(pluginRoot, source);
|
|
627
|
+
if (canonicalRelative === '..' ||
|
|
628
|
+
canonicalRelative.startsWith('../') ||
|
|
629
|
+
isAbsolute(canonicalRelative))
|
|
630
|
+
throw new Error('Local MCPB source escapes plugin root');
|
|
631
|
+
return { pluginRoot, source, remote: false };
|
|
632
|
+
}
|
|
633
|
+
async function cacheEntryIsRealDirectory(cacheEntry) {
|
|
634
|
+
try {
|
|
635
|
+
const entryStat = await lstat(cacheEntry);
|
|
636
|
+
return entryStat.isDirectory() && !entryStat.isSymbolicLink();
|
|
637
|
+
}
|
|
638
|
+
catch (error) {
|
|
639
|
+
if (error.code === 'ENOENT')
|
|
640
|
+
return false;
|
|
641
|
+
throw error;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
async function canonicalCacheRoot(pluginRoot, signal) {
|
|
645
|
+
const requested = join(pluginRoot, '.mcpb-cache');
|
|
646
|
+
await mkdir(requested, { recursive: true });
|
|
647
|
+
signal.throwIfAborted();
|
|
648
|
+
const requestedStat = await lstat(requested);
|
|
649
|
+
if (!requestedStat.isDirectory() || requestedStat.isSymbolicLink())
|
|
650
|
+
throw new Error('MCPB cache root must be a real directory');
|
|
651
|
+
const canonical = await realpath(requested);
|
|
652
|
+
const rel = relative(pluginRoot, canonical);
|
|
653
|
+
if (rel === '..' || rel.startsWith('../') || isAbsolute(rel))
|
|
654
|
+
throw new Error('MCPB cache root escapes plugin root');
|
|
655
|
+
signal.throwIfAborted();
|
|
656
|
+
return canonical;
|
|
657
|
+
}
|
|
658
|
+
async function cleanupCacheArtifacts(cacheRoot, cacheEntry, cacheSource) {
|
|
659
|
+
await mkdir(cacheRoot, { recursive: true });
|
|
660
|
+
const cacheName = basename(cacheEntry);
|
|
661
|
+
const stagingPrefix = `.${cacheName}.`;
|
|
662
|
+
const backupPattern = new RegExp(`^${cacheName}\\.[0-9]+\\.[A-Za-z0-9-]+\\.bak$`, 'u');
|
|
663
|
+
const entries = await readdir(cacheRoot, { withFileTypes: true });
|
|
664
|
+
const backups = [];
|
|
665
|
+
for (const entry of entries) {
|
|
666
|
+
const artifact = join(cacheRoot, entry.name);
|
|
667
|
+
if (entry.name.startsWith(stagingPrefix) && entry.name.includes('.tmp-')) {
|
|
668
|
+
await rm(artifact, { recursive: true, force: true });
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
if (!backupPattern.test(entry.name))
|
|
672
|
+
continue;
|
|
673
|
+
let artifactStat;
|
|
674
|
+
try {
|
|
675
|
+
artifactStat = await lstat(artifact);
|
|
676
|
+
}
|
|
677
|
+
catch (error) {
|
|
678
|
+
if (error.code === 'ENOENT')
|
|
679
|
+
continue;
|
|
680
|
+
throw error;
|
|
681
|
+
}
|
|
682
|
+
if (!artifactStat.isDirectory()) {
|
|
683
|
+
await rm(artifact, { recursive: true, force: true });
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
backups.push({ path: artifact, modified: artifactStat.mtimeMs });
|
|
687
|
+
}
|
|
688
|
+
if (backups.length === 0)
|
|
689
|
+
return;
|
|
690
|
+
backups.sort((left, right) => right.modified - left.modified);
|
|
691
|
+
if (!(await cacheEntryIsRealDirectory(cacheEntry))) {
|
|
692
|
+
await rm(cacheEntry, { recursive: true, force: true });
|
|
693
|
+
for (let index = 0; index < backups.length; index += 1) {
|
|
694
|
+
const candidate = backups[index];
|
|
695
|
+
if (candidate === undefined ||
|
|
696
|
+
(await readCached(candidate.path, cacheSource, cacheRoot)) === undefined)
|
|
697
|
+
continue;
|
|
698
|
+
await rename(candidate.path, cacheEntry);
|
|
699
|
+
backups.splice(index, 1);
|
|
700
|
+
break;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
await Promise.all(backups.map((backup) => rm(backup.path, { recursive: true, force: true })));
|
|
704
|
+
}
|
|
705
|
+
async function waitForLeaseRetry(signal) {
|
|
706
|
+
await new Promise((resolveWait, rejectWait) => {
|
|
707
|
+
const timer = setTimeout(finish, 10);
|
|
708
|
+
const abort = () => finish(signal.reason);
|
|
709
|
+
function finish(reason) {
|
|
710
|
+
clearTimeout(timer);
|
|
711
|
+
signal.removeEventListener('abort', abort);
|
|
712
|
+
if (reason === undefined)
|
|
713
|
+
resolveWait();
|
|
714
|
+
else
|
|
715
|
+
rejectWait(reason);
|
|
716
|
+
}
|
|
717
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
718
|
+
if (signal.aborted)
|
|
719
|
+
abort();
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
async function cacheLockIsWellFormed(lockFile) {
|
|
723
|
+
try {
|
|
724
|
+
const lockStat = await lstat(lockFile);
|
|
725
|
+
if (!lockStat.isFile() || lockStat.size > 4 * 1024)
|
|
726
|
+
return false;
|
|
727
|
+
return isWellFormedLease(JSON.parse(await readFile(lockFile, 'utf8')));
|
|
728
|
+
}
|
|
729
|
+
catch (error) {
|
|
730
|
+
if (error instanceof SyntaxError)
|
|
731
|
+
return false;
|
|
732
|
+
if (error.code === 'ENOENT')
|
|
733
|
+
return false;
|
|
734
|
+
throw error;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
async function waitForCacheLease(cacheEntry, recoveryLockFile, signal) {
|
|
738
|
+
const lockFile = `${cacheEntry}.lock`;
|
|
739
|
+
const lease = new ExclusiveFileLease(lockFile);
|
|
740
|
+
const recoveryLease = new ExclusiveFileLease(recoveryLockFile);
|
|
741
|
+
for (;;) {
|
|
742
|
+
signal.throwIfAborted();
|
|
743
|
+
const recovery = await recoveryLease.tryAcquire();
|
|
744
|
+
if (recovery === null) {
|
|
745
|
+
await waitForLeaseRetry(signal);
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
try {
|
|
749
|
+
if (!(await cacheLockIsWellFormed(lockFile))) {
|
|
750
|
+
await rm(lockFile, { recursive: true, force: true });
|
|
751
|
+
}
|
|
752
|
+
const acquired = await lease.tryAcquire();
|
|
753
|
+
if (acquired !== null)
|
|
754
|
+
return acquired;
|
|
755
|
+
}
|
|
756
|
+
finally {
|
|
757
|
+
await recovery.release();
|
|
758
|
+
}
|
|
759
|
+
await waitForLeaseRetry(signal);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Loads one MCPB/DXT server referenced by a plugin manifest.
|
|
764
|
+
*
|
|
765
|
+
* Local sources must resolve inside `pluginRoot`; URL caches remain sticky
|
|
766
|
+
* until `refresh` is set. A missing required user_config value rejects with
|
|
767
|
+
* `Required MCPB user_config is missing: <key>` so callers can isolate only
|
|
768
|
+
* that server. Valid cache entries persist across calls and failed refreshes.
|
|
769
|
+
*/
|
|
770
|
+
async function loadClaudePluginMcpbUnlocked(options, resolvedSource, cacheRoot, signal) {
|
|
771
|
+
const { pluginRoot, source, remote } = resolvedSource;
|
|
772
|
+
const pluginData = resolve(options.pluginData);
|
|
773
|
+
const limits = {
|
|
774
|
+
archiveBytes: options.limits?.archiveBytes ?? DEFAULT_ARCHIVE_BYTES,
|
|
775
|
+
extractedBytes: options.limits?.extractedBytes ?? DEFAULT_EXTRACTED_BYTES,
|
|
776
|
+
files: options.limits?.files ?? DEFAULT_FILES,
|
|
777
|
+
};
|
|
778
|
+
signal.throwIfAborted();
|
|
779
|
+
const cacheEntry = join(cacheRoot, hash(source));
|
|
780
|
+
const cacheSource = remote ? `url:${hash(source)}` : source;
|
|
781
|
+
const cached = await readCached(cacheEntry, cacheSource, cacheRoot);
|
|
782
|
+
let effectiveUserConfig = options.userConfig ?? {};
|
|
783
|
+
let bytes;
|
|
784
|
+
let fingerprint;
|
|
785
|
+
let etag;
|
|
786
|
+
let lastModified;
|
|
787
|
+
if (remote) {
|
|
788
|
+
if (cached === undefined || options.refresh === true) {
|
|
789
|
+
const downloaded = await fetchArchive(source, options.refresh === true ? cached?.metadata : undefined, options, signal, limits.archiveBytes);
|
|
790
|
+
bytes = downloaded.bytes;
|
|
791
|
+
etag = downloaded.etag ?? cached?.metadata.etag;
|
|
792
|
+
lastModified = downloaded.lastModified ?? cached?.metadata.lastModified;
|
|
793
|
+
}
|
|
794
|
+
fingerprint =
|
|
795
|
+
bytes === undefined ? cached?.metadata.fingerprint : hash(bytes);
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
const sourceStat = await stat(source, { bigint: true });
|
|
799
|
+
fingerprint = `${sourceStat.size}:${sourceStat.mtimeNs}`;
|
|
800
|
+
if (options.refresh ||
|
|
801
|
+
cached === undefined ||
|
|
802
|
+
cached.metadata.fingerprint !== fingerprint) {
|
|
803
|
+
const local = await readFile(source);
|
|
804
|
+
if (local.byteLength > limits.archiveBytes)
|
|
805
|
+
throw new Error(`MCPB archive exceeds ${limits.archiveBytes} bytes`);
|
|
806
|
+
bytes = local;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
if (bytes === undefined && cached !== undefined) {
|
|
810
|
+
try {
|
|
811
|
+
effectiveUserConfig = await configuredUserValues(options, cached.manifest);
|
|
812
|
+
const resolved = resolveUserConfig(cached.manifest, effectiveUserConfig);
|
|
813
|
+
return {
|
|
814
|
+
name: cached.manifest.name,
|
|
815
|
+
config: await serverConfig(cached.manifest, pluginRoot, join(cacheEntry, 'extracted'), pluginData, options.environment ?? process.env, resolved.values),
|
|
816
|
+
manifest: cached.manifest,
|
|
817
|
+
sensitiveValues: resolved.sensitive,
|
|
818
|
+
extractedPath: join(cacheEntry, 'extracted'),
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
catch (error) {
|
|
822
|
+
throw mcpbLoadError(error, source, effectiveUserConfig);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
if (bytes === undefined || fingerprint === undefined)
|
|
826
|
+
throw new Error('MCPB cache could not be refreshed');
|
|
827
|
+
let staging;
|
|
828
|
+
try {
|
|
829
|
+
await mkdir(cacheRoot, { recursive: true });
|
|
830
|
+
staging = await mkdtemp(join(cacheRoot, `.${hash(source)}.${process.pid}.tmp-`));
|
|
831
|
+
await writeFile(join(staging, 'bundle.mcpb'), bytes, {
|
|
832
|
+
flag: 'wx',
|
|
833
|
+
mode: 0o600,
|
|
834
|
+
});
|
|
835
|
+
await extractArchive(bytes, join(staging, 'extracted'), limits, signal);
|
|
836
|
+
const manifest = await validateManifest(JSON.parse(await readFile(join(staging, 'extracted', MANIFEST_FILE), 'utf8')));
|
|
837
|
+
const metadata = {
|
|
838
|
+
source: cacheSource,
|
|
839
|
+
fingerprint,
|
|
840
|
+
...(etag === undefined ? {} : { etag }),
|
|
841
|
+
...(lastModified === undefined ? {} : { lastModified }),
|
|
842
|
+
};
|
|
843
|
+
await writeFile(join(staging, 'metadata.json'), `${JSON.stringify(metadata, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
|
|
844
|
+
await replaceCache(staging, cacheEntry);
|
|
845
|
+
effectiveUserConfig = await configuredUserValues(options, manifest);
|
|
846
|
+
const resolved = resolveUserConfig(manifest, effectiveUserConfig);
|
|
847
|
+
return {
|
|
848
|
+
name: manifest.name,
|
|
849
|
+
config: await serverConfig(manifest, pluginRoot, join(cacheEntry, 'extracted'), pluginData, options.environment ?? process.env, resolved.values),
|
|
850
|
+
manifest,
|
|
851
|
+
sensitiveValues: resolved.sensitive,
|
|
852
|
+
extractedPath: join(cacheEntry, 'extracted'),
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
catch (error) {
|
|
856
|
+
if (staging !== undefined)
|
|
857
|
+
await rm(staging, { recursive: true, force: true });
|
|
858
|
+
throw mcpbLoadError(error, source, effectiveUserConfig);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
export async function loadClaudePluginMcpb(options) {
|
|
862
|
+
const timeout = AbortSignal.timeout(options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
863
|
+
const signal = options.signal === undefined
|
|
864
|
+
? timeout
|
|
865
|
+
: AbortSignal.any([options.signal, timeout]);
|
|
866
|
+
const resolvedSource = await resolveMcpbSource(options, signal);
|
|
867
|
+
const cacheRoot = await canonicalCacheRoot(resolvedSource.pluginRoot, signal);
|
|
868
|
+
const cacheEntry = join(cacheRoot, hash(resolvedSource.source));
|
|
869
|
+
const cacheSource = resolvedSource.remote
|
|
870
|
+
? `url:${hash(resolvedSource.source)}`
|
|
871
|
+
: resolvedSource.source;
|
|
872
|
+
const configRoot = resolve(process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), '.claude'));
|
|
873
|
+
const recoveryLockFile = join(configRoot, 'praxis', 'locks', 'mcpb', `${hash(cacheEntry)}.lock`);
|
|
874
|
+
const lease = await waitForCacheLease(cacheEntry, recoveryLockFile, signal);
|
|
875
|
+
try {
|
|
876
|
+
signal.throwIfAborted();
|
|
877
|
+
await cleanupCacheArtifacts(cacheRoot, cacheEntry, cacheSource);
|
|
878
|
+
signal.throwIfAborted();
|
|
879
|
+
return await loadClaudePluginMcpbUnlocked(options, resolvedSource, cacheRoot, signal);
|
|
880
|
+
}
|
|
881
|
+
finally {
|
|
882
|
+
await lease.release();
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
//# sourceMappingURL=claude-plugin-mcpb.js.map
|