patchwork-os 0.2.0-alpha.9 → 0.2.0-beta.1
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/README.bridge.md +6 -0
- package/README.md +318 -35
- package/deploy/bootstrap-new-vps.sh +12 -12
- package/deploy/bootstrap-vps.sh +187 -0
- package/deploy/deploy-dashboard.sh +174 -0
- package/deploy/deploy-landing.sh +136 -0
- package/dist/activationMetrics.d.ts +67 -0
- package/dist/activationMetrics.js +255 -0
- package/dist/activationMetrics.js.map +1 -0
- package/dist/activityLog.d.ts +49 -0
- package/dist/activityLog.js +78 -0
- package/dist/activityLog.js.map +1 -1
- package/dist/analyticsAggregator.d.ts +5 -1
- package/dist/analyticsAggregator.js +15 -4
- package/dist/analyticsAggregator.js.map +1 -1
- package/dist/analyticsPrefs.d.ts +11 -0
- package/dist/analyticsPrefs.js +33 -0
- package/dist/analyticsPrefs.js.map +1 -1
- package/dist/approvalHttp.d.ts +49 -2
- package/dist/approvalHttp.js +217 -21
- package/dist/approvalHttp.js.map +1 -1
- package/dist/approvalInsights.d.ts +49 -0
- package/dist/approvalInsights.js +97 -0
- package/dist/approvalInsights.js.map +1 -0
- package/dist/approvalQueue.d.ts +27 -1
- package/dist/approvalQueue.js +123 -3
- package/dist/approvalQueue.js.map +1 -1
- package/dist/approvalSignals.d.ts +124 -0
- package/dist/approvalSignals.js +512 -0
- package/dist/approvalSignals.js.map +1 -0
- package/dist/automation.d.ts +57 -0
- package/dist/automation.js +156 -59
- package/dist/automation.js.map +1 -1
- package/dist/automationSuggestions.d.ts +79 -0
- package/dist/automationSuggestions.js +150 -0
- package/dist/automationSuggestions.js.map +1 -0
- package/dist/bridge.d.ts +3 -0
- package/dist/bridge.js +194 -153
- package/dist/bridge.js.map +1 -1
- package/dist/bridgeToken.js +57 -19
- package/dist/bridgeToken.js.map +1 -1
- package/dist/ccPermissions.d.ts +15 -0
- package/dist/ccPermissions.js +21 -4
- package/dist/ccPermissions.js.map +1 -1
- package/dist/claudeDriver.d.ts +0 -16
- package/dist/claudeDriver.js +93 -36
- package/dist/claudeDriver.js.map +1 -1
- package/dist/claudeMdPatch.d.ts +9 -3
- package/dist/claudeMdPatch.js +79 -13
- package/dist/claudeMdPatch.js.map +1 -1
- package/dist/claudeOrchestrator.d.ts +13 -1
- package/dist/claudeOrchestrator.js +16 -8
- package/dist/claudeOrchestrator.js.map +1 -1
- package/dist/commands/dashboard.js +1 -1
- package/dist/commands/dashboard.js.map +1 -1
- package/dist/commands/launchd.d.ts +2 -0
- package/dist/commands/launchd.js +94 -0
- package/dist/commands/launchd.js.map +1 -0
- package/dist/commands/marketplace.d.ts +15 -10
- package/dist/commands/marketplace.js +27 -115
- package/dist/commands/marketplace.js.map +1 -1
- package/dist/commands/patchworkInit.d.ts +8 -0
- package/dist/commands/patchworkInit.js +77 -11
- package/dist/commands/patchworkInit.js.map +1 -1
- package/dist/commands/recipe.d.ts +289 -0
- package/dist/commands/recipe.js +1359 -0
- package/dist/commands/recipe.js.map +1 -0
- package/dist/commands/recipeInstall.d.ts +150 -0
- package/dist/commands/recipeInstall.js +647 -0
- package/dist/commands/recipeInstall.js.map +1 -0
- package/dist/commands/tracesExport.d.ts +83 -0
- package/dist/commands/tracesExport.js +269 -0
- package/dist/commands/tracesExport.js.map +1 -0
- package/dist/commands/tracesImport.d.ts +56 -0
- package/dist/commands/tracesImport.js +161 -0
- package/dist/commands/tracesImport.js.map +1 -0
- package/dist/commitIssueLinkLog.d.ts +8 -0
- package/dist/commitIssueLinkLog.js +53 -1
- package/dist/commitIssueLinkLog.js.map +1 -1
- package/dist/config.d.ts +23 -2
- package/dist/config.js +119 -9
- package/dist/config.js.map +1 -1
- package/dist/connectorRoutes.d.ts +43 -0
- package/dist/connectorRoutes.js +1300 -0
- package/dist/connectorRoutes.js.map +1 -0
- package/dist/connectors/asana.d.ts +198 -0
- package/dist/connectors/asana.js +679 -0
- package/dist/connectors/asana.js.map +1 -0
- package/dist/connectors/baseConnector.d.ts +153 -0
- package/dist/connectors/baseConnector.js +336 -0
- package/dist/connectors/baseConnector.js.map +1 -0
- package/dist/connectors/confluence.d.ts +111 -0
- package/dist/connectors/confluence.js +406 -0
- package/dist/connectors/confluence.js.map +1 -0
- package/dist/connectors/datadog.d.ts +116 -0
- package/dist/connectors/datadog.js +385 -0
- package/dist/connectors/datadog.js.map +1 -0
- package/dist/connectors/discord.d.ts +150 -0
- package/dist/connectors/discord.js +543 -0
- package/dist/connectors/discord.js.map +1 -0
- package/dist/connectors/fixtureLibrary.d.ts +21 -0
- package/dist/connectors/fixtureLibrary.js +70 -0
- package/dist/connectors/fixtureLibrary.js.map +1 -0
- package/dist/connectors/fixtureRecorder.d.ts +1 -0
- package/dist/connectors/fixtureRecorder.js +35 -0
- package/dist/connectors/fixtureRecorder.js.map +1 -0
- package/dist/connectors/github.js +17 -18
- package/dist/connectors/github.js.map +1 -1
- package/dist/connectors/gitlab.d.ts +180 -0
- package/dist/connectors/gitlab.js +582 -0
- package/dist/connectors/gitlab.js.map +1 -0
- package/dist/connectors/gmail.d.ts +4 -1
- package/dist/connectors/gmail.js +149 -27
- package/dist/connectors/gmail.js.map +1 -1
- package/dist/connectors/googleCalendar.d.ts +4 -1
- package/dist/connectors/googleCalendar.js +88 -25
- package/dist/connectors/googleCalendar.js.map +1 -1
- package/dist/connectors/googleDrive.d.ts +34 -0
- package/dist/connectors/googleDrive.js +321 -0
- package/dist/connectors/googleDrive.js.map +1 -0
- package/dist/connectors/htmlEscape.d.ts +5 -0
- package/dist/connectors/htmlEscape.js +13 -0
- package/dist/connectors/htmlEscape.js.map +1 -0
- package/dist/connectors/hubspot.d.ts +112 -0
- package/dist/connectors/hubspot.js +408 -0
- package/dist/connectors/hubspot.js.map +1 -0
- package/dist/connectors/intercom.d.ts +102 -0
- package/dist/connectors/intercom.js +402 -0
- package/dist/connectors/intercom.js.map +1 -0
- package/dist/connectors/jira.d.ts +98 -0
- package/dist/connectors/jira.js +396 -0
- package/dist/connectors/jira.js.map +1 -0
- package/dist/connectors/linear.js +30 -19
- package/dist/connectors/linear.js.map +1 -1
- package/dist/connectors/mcpOAuth.d.ts +3 -0
- package/dist/connectors/mcpOAuth.js +64 -10
- package/dist/connectors/mcpOAuth.js.map +1 -1
- package/dist/connectors/mockConnector.d.ts +28 -0
- package/dist/connectors/mockConnector.js +81 -0
- package/dist/connectors/mockConnector.js.map +1 -0
- package/dist/connectors/notion.d.ts +143 -0
- package/dist/connectors/notion.js +424 -0
- package/dist/connectors/notion.js.map +1 -0
- package/dist/connectors/oauthStateStore.d.ts +31 -0
- package/dist/connectors/oauthStateStore.js +52 -0
- package/dist/connectors/oauthStateStore.js.map +1 -0
- package/dist/connectors/pagerduty.d.ts +160 -0
- package/dist/connectors/pagerduty.js +464 -0
- package/dist/connectors/pagerduty.js.map +1 -0
- package/dist/connectors/sentry.js +5 -13
- package/dist/connectors/sentry.js.map +1 -1
- package/dist/connectors/slack.d.ts +16 -1
- package/dist/connectors/slack.js +155 -32
- package/dist/connectors/slack.js.map +1 -1
- package/dist/connectors/stripe.d.ts +116 -0
- package/dist/connectors/stripe.js +379 -0
- package/dist/connectors/stripe.js.map +1 -0
- package/dist/connectors/tokenStorage.d.ts +35 -0
- package/dist/connectors/tokenStorage.js +484 -0
- package/dist/connectors/tokenStorage.js.map +1 -0
- package/dist/connectors/zendesk.d.ts +104 -0
- package/dist/connectors/zendesk.js +442 -0
- package/dist/connectors/zendesk.js.map +1 -0
- package/dist/cors.d.ts +10 -0
- package/dist/cors.js +29 -0
- package/dist/cors.js.map +1 -0
- package/dist/decisionReplay.d.ts +72 -0
- package/dist/decisionReplay.js +92 -0
- package/dist/decisionReplay.js.map +1 -0
- package/dist/decisionTraceLog.d.ts +6 -0
- package/dist/decisionTraceLog.js +54 -2
- package/dist/decisionTraceLog.js.map +1 -1
- package/dist/drivers/claude/subprocess.d.ts +12 -2
- package/dist/drivers/claude/subprocess.js +79 -6
- package/dist/drivers/claude/subprocess.js.map +1 -1
- package/dist/drivers/gemini/api.d.ts +18 -0
- package/dist/drivers/gemini/api.js +29 -0
- package/dist/drivers/gemini/api.js.map +1 -0
- package/dist/drivers/gemini/index.d.ts +5 -1
- package/dist/drivers/gemini/index.js +39 -5
- package/dist/drivers/gemini/index.js.map +1 -1
- package/dist/drivers/index.d.ts +8 -1
- package/dist/drivers/index.js +10 -2
- package/dist/drivers/index.js.map +1 -1
- package/dist/drivers/local/index.d.ts +26 -0
- package/dist/drivers/local/index.js +41 -0
- package/dist/drivers/local/index.js.map +1 -0
- package/dist/featureFlags.d.ts +79 -0
- package/dist/featureFlags.js +208 -0
- package/dist/featureFlags.js.map +1 -0
- package/dist/fp/automationInterpreter.js +26 -21
- package/dist/fp/automationInterpreter.js.map +1 -1
- package/dist/fp/automationProgram.d.ts +1 -1
- package/dist/fp/automationProgram.js.map +1 -1
- package/dist/fp/automationState.js +4 -1
- package/dist/fp/automationState.js.map +1 -1
- package/dist/fp/policyParser.js +21 -1
- package/dist/fp/policyParser.js.map +1 -1
- package/dist/httpErrorResponse.d.ts +36 -0
- package/dist/httpErrorResponse.js +46 -0
- package/dist/httpErrorResponse.js.map +1 -0
- package/dist/inboxRoutes.d.ts +22 -0
- package/dist/inboxRoutes.js +193 -0
- package/dist/inboxRoutes.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1403 -203
- package/dist/index.js.map +1 -1
- package/dist/installGuard.d.ts +25 -0
- package/dist/installGuard.js +48 -0
- package/dist/installGuard.js.map +1 -0
- package/dist/mcpRoutes.d.ts +37 -0
- package/dist/mcpRoutes.js +76 -0
- package/dist/mcpRoutes.js.map +1 -0
- package/dist/oauth.d.ts +20 -1
- package/dist/oauth.js +214 -39
- package/dist/oauth.js.map +1 -1
- package/dist/oauthRoutes.d.ts +32 -0
- package/dist/oauthRoutes.js +119 -0
- package/dist/oauthRoutes.js.map +1 -0
- package/dist/orchestrator/orchestratorBridge.js +2 -2
- package/dist/orchestrator/orchestratorBridge.js.map +1 -1
- package/dist/patchworkConfig.d.ts +29 -0
- package/dist/patchworkConfig.js +100 -5
- package/dist/patchworkConfig.js.map +1 -1
- package/dist/pluginLoader.d.ts +28 -0
- package/dist/pluginLoader.js +77 -11
- package/dist/pluginLoader.js.map +1 -1
- package/dist/pluginWatcher.js +8 -3
- package/dist/pluginWatcher.js.map +1 -1
- package/dist/preToolUseHook.d.ts +12 -0
- package/dist/preToolUseHook.js +30 -1
- package/dist/preToolUseHook.js.map +1 -1
- package/dist/prompts.js +4 -0
- package/dist/prompts.js.map +1 -1
- package/dist/recipeOrchestration.d.ts +121 -0
- package/dist/recipeOrchestration.js +965 -0
- package/dist/recipeOrchestration.js.map +1 -0
- package/dist/recipeRoutes.d.ts +185 -0
- package/dist/recipeRoutes.js +1369 -0
- package/dist/recipeRoutes.js.map +1 -0
- package/dist/recipes/RecipeOrchestrator.d.ts +40 -0
- package/dist/recipes/RecipeOrchestrator.js +51 -0
- package/dist/recipes/RecipeOrchestrator.js.map +1 -0
- package/dist/recipes/agentExecutor.d.ts +38 -0
- package/dist/recipes/agentExecutor.js +50 -0
- package/dist/recipes/agentExecutor.js.map +1 -0
- package/dist/recipes/chainedRunner.d.ts +191 -0
- package/dist/recipes/chainedRunner.js +759 -0
- package/dist/recipes/chainedRunner.js.map +1 -0
- package/dist/recipes/compiler.js +3 -3
- package/dist/recipes/compiler.js.map +1 -1
- package/dist/recipes/dependencyGraph.d.ts +39 -0
- package/dist/recipes/dependencyGraph.js +199 -0
- package/dist/recipes/dependencyGraph.js.map +1 -0
- package/dist/recipes/disabledMarkers.d.ts +48 -0
- package/dist/recipes/disabledMarkers.js +52 -0
- package/dist/recipes/disabledMarkers.js.map +1 -0
- package/dist/recipes/installer.js +3 -3
- package/dist/recipes/installer.js.map +1 -1
- package/dist/recipes/legacyRecipeCompat.d.ts +10 -0
- package/dist/recipes/legacyRecipeCompat.js +131 -0
- package/dist/recipes/legacyRecipeCompat.js.map +1 -0
- package/dist/recipes/manifest.d.ts +47 -0
- package/dist/recipes/manifest.js +156 -0
- package/dist/recipes/manifest.js.map +1 -0
- package/dist/recipes/migrationWarnings.d.ts +12 -0
- package/dist/recipes/migrationWarnings.js +44 -0
- package/dist/recipes/migrationWarnings.js.map +1 -0
- package/dist/recipes/migrations/index.d.ts +24 -0
- package/dist/recipes/migrations/index.js +55 -0
- package/dist/recipes/migrations/index.js.map +1 -0
- package/dist/recipes/migrations/types.d.ts +28 -0
- package/dist/recipes/migrations/types.js +2 -0
- package/dist/recipes/migrations/types.js.map +1 -0
- package/dist/recipes/migrations/v1.d.ts +11 -0
- package/dist/recipes/migrations/v1.js +18 -0
- package/dist/recipes/migrations/v1.js.map +1 -0
- package/dist/recipes/names.d.ts +40 -0
- package/dist/recipes/names.js +66 -0
- package/dist/recipes/names.js.map +1 -0
- package/dist/recipes/nestedRecipeStep.d.ts +58 -0
- package/dist/recipes/nestedRecipeStep.js +95 -0
- package/dist/recipes/nestedRecipeStep.js.map +1 -0
- package/dist/recipes/outputRegistry.d.ts +28 -0
- package/dist/recipes/outputRegistry.js +52 -0
- package/dist/recipes/outputRegistry.js.map +1 -0
- package/dist/recipes/parser.js +4 -1
- package/dist/recipes/parser.js.map +1 -1
- package/dist/recipes/replayRun.d.ts +62 -0
- package/dist/recipes/replayRun.js +97 -0
- package/dist/recipes/replayRun.js.map +1 -0
- package/dist/recipes/resolveRecipePath.d.ts +69 -0
- package/dist/recipes/resolveRecipePath.js +202 -0
- package/dist/recipes/resolveRecipePath.js.map +1 -0
- package/dist/recipes/scheduler.d.ts +23 -7
- package/dist/recipes/scheduler.js +225 -45
- package/dist/recipes/scheduler.js.map +1 -1
- package/dist/recipes/schema.d.ts +17 -2
- package/dist/recipes/schemaGenerator.d.ts +28 -0
- package/dist/recipes/schemaGenerator.js +565 -0
- package/dist/recipes/schemaGenerator.js.map +1 -0
- package/dist/recipes/stepObservation.d.ts +44 -0
- package/dist/recipes/stepObservation.js +232 -0
- package/dist/recipes/stepObservation.js.map +1 -0
- package/dist/recipes/templateEngine.d.ts +62 -0
- package/dist/recipes/templateEngine.js +201 -0
- package/dist/recipes/templateEngine.js.map +1 -0
- package/dist/recipes/toolRegistry.d.ts +186 -0
- package/dist/recipes/toolRegistry.js +309 -0
- package/dist/recipes/toolRegistry.js.map +1 -0
- package/dist/recipes/tools/asana.d.ts +16 -0
- package/dist/recipes/tools/asana.js +524 -0
- package/dist/recipes/tools/asana.js.map +1 -0
- package/dist/recipes/tools/calendar.d.ts +6 -0
- package/dist/recipes/tools/calendar.js +61 -0
- package/dist/recipes/tools/calendar.js.map +1 -0
- package/dist/recipes/tools/confluence.d.ts +6 -0
- package/dist/recipes/tools/confluence.js +254 -0
- package/dist/recipes/tools/confluence.js.map +1 -0
- package/dist/recipes/tools/datadog.d.ts +6 -0
- package/dist/recipes/tools/datadog.js +239 -0
- package/dist/recipes/tools/datadog.js.map +1 -0
- package/dist/recipes/tools/diagnostics.d.ts +6 -0
- package/dist/recipes/tools/diagnostics.js +36 -0
- package/dist/recipes/tools/diagnostics.js.map +1 -0
- package/dist/recipes/tools/discord.d.ts +18 -0
- package/dist/recipes/tools/discord.js +254 -0
- package/dist/recipes/tools/discord.js.map +1 -0
- package/dist/recipes/tools/file.d.ts +12 -0
- package/dist/recipes/tools/file.js +174 -0
- package/dist/recipes/tools/file.js.map +1 -0
- package/dist/recipes/tools/git.d.ts +6 -0
- package/dist/recipes/tools/git.js +63 -0
- package/dist/recipes/tools/git.js.map +1 -0
- package/dist/recipes/tools/github.d.ts +6 -0
- package/dist/recipes/tools/github.js +116 -0
- package/dist/recipes/tools/github.js.map +1 -0
- package/dist/recipes/tools/gitlab.d.ts +11 -0
- package/dist/recipes/tools/gitlab.js +285 -0
- package/dist/recipes/tools/gitlab.js.map +1 -0
- package/dist/recipes/tools/gmail.d.ts +6 -0
- package/dist/recipes/tools/gmail.js +451 -0
- package/dist/recipes/tools/gmail.js.map +1 -0
- package/dist/recipes/tools/googleDrive.d.ts +1 -0
- package/dist/recipes/tools/googleDrive.js +55 -0
- package/dist/recipes/tools/googleDrive.js.map +1 -0
- package/dist/recipes/tools/hubspot.d.ts +6 -0
- package/dist/recipes/tools/hubspot.js +232 -0
- package/dist/recipes/tools/hubspot.js.map +1 -0
- package/dist/recipes/tools/index.d.ts +30 -0
- package/dist/recipes/tools/index.js +33 -0
- package/dist/recipes/tools/index.js.map +1 -0
- package/dist/recipes/tools/intercom.d.ts +6 -0
- package/dist/recipes/tools/intercom.js +226 -0
- package/dist/recipes/tools/intercom.js.map +1 -0
- package/dist/recipes/tools/jira.d.ts +14 -0
- package/dist/recipes/tools/jira.js +369 -0
- package/dist/recipes/tools/jira.js.map +1 -0
- package/dist/recipes/tools/linear.d.ts +7 -0
- package/dist/recipes/tools/linear.js +307 -0
- package/dist/recipes/tools/linear.js.map +1 -0
- package/dist/recipes/tools/meetingNotes.d.ts +21 -0
- package/dist/recipes/tools/meetingNotes.js +701 -0
- package/dist/recipes/tools/meetingNotes.js.map +1 -0
- package/dist/recipes/tools/notion.d.ts +6 -0
- package/dist/recipes/tools/notion.js +278 -0
- package/dist/recipes/tools/notion.js.map +1 -0
- package/dist/recipes/tools/pagerduty.d.ts +15 -0
- package/dist/recipes/tools/pagerduty.js +451 -0
- package/dist/recipes/tools/pagerduty.js.map +1 -0
- package/dist/recipes/tools/sentry.d.ts +12 -0
- package/dist/recipes/tools/sentry.js +73 -0
- package/dist/recipes/tools/sentry.js.map +1 -0
- package/dist/recipes/tools/slack.d.ts +6 -0
- package/dist/recipes/tools/slack.js +82 -0
- package/dist/recipes/tools/slack.js.map +1 -0
- package/dist/recipes/tools/stripe.d.ts +6 -0
- package/dist/recipes/tools/stripe.js +265 -0
- package/dist/recipes/tools/stripe.js.map +1 -0
- package/dist/recipes/tools/zendesk.d.ts +6 -0
- package/dist/recipes/tools/zendesk.js +245 -0
- package/dist/recipes/tools/zendesk.js.map +1 -0
- package/dist/recipes/validation.d.ts +13 -0
- package/dist/recipes/validation.js +617 -0
- package/dist/recipes/validation.js.map +1 -0
- package/dist/recipes/yamlRunner.d.ts +130 -2
- package/dist/recipes/yamlRunner.js +1009 -402
- package/dist/recipes/yamlRunner.js.map +1 -1
- package/dist/recipesHttp.d.ts +151 -6
- package/dist/recipesHttp.js +999 -29
- package/dist/recipesHttp.js.map +1 -1
- package/dist/riskTier.js +7 -1
- package/dist/riskTier.js.map +1 -1
- package/dist/runLog.d.ts +100 -1
- package/dist/runLog.js +258 -5
- package/dist/runLog.js.map +1 -1
- package/dist/schemas/dry-run-plan.v1.json +139 -0
- package/dist/schemas/recipe.v1.json +684 -0
- package/dist/server.d.ts +127 -8
- package/dist/server.js +740 -933
- package/dist/server.js.map +1 -1
- package/dist/ssrfGuard.d.ts +54 -0
- package/dist/ssrfGuard.js +122 -0
- package/dist/ssrfGuard.js.map +1 -0
- package/dist/streamableHttp.d.ts +39 -1
- package/dist/streamableHttp.js +128 -17
- package/dist/streamableHttp.js.map +1 -1
- package/dist/tokenUsageTracker.d.ts +33 -0
- package/dist/tokenUsageTracker.js +146 -0
- package/dist/tokenUsageTracker.js.map +1 -0
- package/dist/tools/activityLog.d.ts +2 -0
- package/dist/tools/addLinearComment.d.ts +1 -0
- package/dist/tools/addLinearComment.js +4 -2
- package/dist/tools/addLinearComment.js.map +1 -1
- package/dist/tools/batchLsp.d.ts +3 -0
- package/dist/tools/bridgeDoctor.d.ts +1 -0
- package/dist/tools/bridgeDoctor.js +2 -2
- package/dist/tools/bridgeDoctor.js.map +1 -1
- package/dist/tools/bridgeStatus.d.ts +1 -0
- package/dist/tools/cancelClaudeTask.d.ts +2 -0
- package/dist/tools/cancelClaudeTask.js +1 -0
- package/dist/tools/cancelClaudeTask.js.map +1 -1
- package/dist/tools/checkDocumentDirty.d.ts +1 -0
- package/dist/tools/clipboard.d.ts +2 -0
- package/dist/tools/closeTabs.d.ts +2 -0
- package/dist/tools/codeLens.d.ts +1 -0
- package/dist/tools/contextBundle.d.ts +1 -0
- package/dist/tools/createIssueFromAIComment.d.ts +1 -0
- package/dist/tools/createLinearIssue.d.ts +1 -0
- package/dist/tools/ctxGetTaskContext.d.ts +1 -0
- package/dist/tools/ctxQueryTraces.d.ts +1 -0
- package/dist/tools/ctxSaveTrace.d.ts +1 -0
- package/dist/tools/debug.d.ts +4 -0
- package/dist/tools/decorations.d.ts +2 -0
- package/dist/tools/documentLinks.d.ts +1 -0
- package/dist/tools/editText.d.ts +1 -0
- package/dist/tools/enrichCommit.d.ts +1 -0
- package/dist/tools/enrichStackTrace.d.ts +1 -0
- package/dist/tools/explainDiagnostic.d.ts +1 -0
- package/dist/tools/explainSymbol.d.ts +1 -0
- package/dist/tools/fetchCalendarEvents.d.ts +1 -0
- package/dist/tools/fetchGithubIssue.d.ts +1 -0
- package/dist/tools/fetchGithubPR.d.ts +1 -0
- package/dist/tools/fetchLinearIssue.d.ts +1 -0
- package/dist/tools/fetchSentryIssue.d.ts +1 -0
- package/dist/tools/fetchSlackProfile.d.ts +1 -0
- package/dist/tools/fetchSlackProfile.js +4 -1
- package/dist/tools/fetchSlackProfile.js.map +1 -1
- package/dist/tools/fileOperations.d.ts +3 -0
- package/dist/tools/fileWatcher.d.ts +2 -0
- package/dist/tools/findFiles.d.ts +1 -0
- package/dist/tools/findRelatedTests.d.ts +1 -0
- package/dist/tools/fixAllLintErrors.d.ts +1 -0
- package/dist/tools/foldingRanges.d.ts +1 -0
- package/dist/tools/formatDocument.d.ts +1 -0
- package/dist/tools/generateTests.d.ts +1 -0
- package/dist/tools/getAIComments.d.ts +1 -0
- package/dist/tools/getAnalyticsReport.d.ts +1 -0
- package/dist/tools/getArchitectureContext.d.ts +1 -0
- package/dist/tools/getBufferContent.d.ts +1 -0
- package/dist/tools/getChangeImpact.d.ts +1 -0
- package/dist/tools/getClaudeTaskStatus.d.ts +2 -0
- package/dist/tools/getClaudeTaskStatus.js +1 -0
- package/dist/tools/getClaudeTaskStatus.js.map +1 -1
- package/dist/tools/getCodeCoverage.d.ts +1 -0
- package/dist/tools/getCommitsForIssue.d.ts +1 -0
- package/dist/tools/getConnectorStatus.d.ts +1 -0
- package/dist/tools/getCurrentSelection.d.ts +2 -0
- package/dist/tools/getDebugState.d.ts +1 -0
- package/dist/tools/getDependencyTree.d.ts +1 -0
- package/dist/tools/getDiagnostics.d.ts +1 -0
- package/dist/tools/getDiffFromHandoff.d.ts +1 -0
- package/dist/tools/getDocumentSymbols.d.ts +25 -0
- package/dist/tools/getDocumentSymbols.js +74 -8
- package/dist/tools/getDocumentSymbols.js.map +1 -1
- package/dist/tools/getFileTree.d.ts +1 -0
- package/dist/tools/getGitDiff.d.ts +1 -0
- package/dist/tools/getGitHotspots.d.ts +1 -0
- package/dist/tools/getGitLog.d.ts +1 -0
- package/dist/tools/getGitStatus.d.ts +1 -0
- package/dist/tools/getImportTree.d.ts +1 -0
- package/dist/tools/getImportedSignatures.d.ts +1 -0
- package/dist/tools/getOpenEditors.d.ts +1 -0
- package/dist/tools/getPRTemplate.d.ts +1 -0
- package/dist/tools/getProjectContext.d.ts +1 -0
- package/dist/tools/getProjectInfo.d.ts +1 -0
- package/dist/tools/getSecurityAdvisories.d.ts +1 -0
- package/dist/tools/getSecurityAdvisories.js +10 -1
- package/dist/tools/getSecurityAdvisories.js.map +1 -1
- package/dist/tools/getSessionUsage.d.ts +4 -0
- package/dist/tools/getSessionUsage.js +3 -0
- package/dist/tools/getSessionUsage.js.map +1 -1
- package/dist/tools/getSymbolHistory.d.ts +1 -0
- package/dist/tools/getToolCapabilities.d.ts +1 -0
- package/dist/tools/getTypeSignature.d.ts +1 -0
- package/dist/tools/getWorkspaceFolders.d.ts +1 -0
- package/dist/tools/getWorkspaceSettings.d.ts +1 -0
- package/dist/tools/gitHistory.d.ts +2 -0
- package/dist/tools/gitWrite.d.ts +11 -0
- package/dist/tools/github/actions.d.ts +2 -0
- package/dist/tools/github/actions.js +4 -2
- package/dist/tools/github/actions.js.map +1 -1
- package/dist/tools/github/composite.d.ts +342 -0
- package/dist/tools/github/composite.js +343 -0
- package/dist/tools/github/composite.js.map +1 -0
- package/dist/tools/github/index.d.ts +1 -0
- package/dist/tools/github/index.js +1 -0
- package/dist/tools/github/index.js.map +1 -1
- package/dist/tools/github/issues.d.ts +4 -0
- package/dist/tools/github/issues.js +8 -4
- package/dist/tools/github/issues.js.map +1 -1
- package/dist/tools/github/pr.d.ts +7 -0
- package/dist/tools/github/pr.js +50 -12
- package/dist/tools/github/pr.js.map +1 -1
- package/dist/tools/handoffNote.d.ts +4 -0
- package/dist/tools/handoffNote.js +2 -0
- package/dist/tools/handoffNote.js.map +1 -1
- package/dist/tools/hoverAtCursor.d.ts +1 -0
- package/dist/tools/httpClient.d.ts +2 -0
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.js +47 -8
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/inlayHints.d.ts +1 -0
- package/dist/tools/launchQuickTask.d.ts +2 -0
- package/dist/tools/launchQuickTask.js +1 -0
- package/dist/tools/launchQuickTask.js.map +1 -1
- package/dist/tools/listClaudeTasks.d.ts +2 -0
- package/dist/tools/listClaudeTasks.js +1 -0
- package/dist/tools/listClaudeTasks.js.map +1 -1
- package/dist/tools/listTerminals.d.ts +1 -0
- package/dist/tools/lsp.d.ts +14 -0
- package/dist/tools/navigateToSymbolByName.d.ts +1 -0
- package/dist/tools/openDiff.d.ts +1 -0
- package/dist/tools/openFile.d.ts +1 -0
- package/dist/tools/openInBrowser.d.ts +1 -0
- package/dist/tools/organizeImports.d.ts +1 -0
- package/dist/tools/performanceReport.d.ts +1 -0
- package/dist/tools/planPersistence.d.ts +5 -0
- package/dist/tools/previewEdit.d.ts +1 -0
- package/dist/tools/refactorAnalyze.d.ts +1 -0
- package/dist/tools/refactorPreview.d.ts +2 -0
- package/dist/tools/refactorPreview.js +1 -0
- package/dist/tools/refactorPreview.js.map +1 -1
- package/dist/tools/replaceBlock.d.ts +1 -0
- package/dist/tools/resumeClaudeTask.d.ts +2 -0
- package/dist/tools/resumeClaudeTask.js +1 -0
- package/dist/tools/resumeClaudeTask.js.map +1 -1
- package/dist/tools/runClaudeTask.d.ts +2 -0
- package/dist/tools/runClaudeTask.js +1 -0
- package/dist/tools/runClaudeTask.js.map +1 -1
- package/dist/tools/runCommand.d.ts +1 -0
- package/dist/tools/runCommand.js +5 -0
- package/dist/tools/runCommand.js.map +1 -1
- package/dist/tools/runTests.d.ts +1 -0
- package/dist/tools/saveDocument.d.ts +1 -0
- package/dist/tools/screenshotAndAnnotate.d.ts +1 -0
- package/dist/tools/searchAndReplace.d.ts +1 -0
- package/dist/tools/searchTools.d.ts +1 -0
- package/dist/tools/searchTools.js +1 -1
- package/dist/tools/searchTools.js.map +1 -1
- package/dist/tools/searchWorkspace.d.ts +1 -0
- package/dist/tools/selectionRanges.d.ts +1 -0
- package/dist/tools/semanticTokens.d.ts +1 -0
- package/dist/tools/setActiveWorkspaceFolder.d.ts +1 -0
- package/dist/tools/signatureHelp.d.ts +1 -0
- package/dist/tools/slackListChannels.d.ts +1 -0
- package/dist/tools/slackListChannels.js.map +1 -1
- package/dist/tools/slackPostMessage.d.ts +1 -0
- package/dist/tools/slackPostMessage.js +11 -6
- package/dist/tools/slackPostMessage.js.map +1 -1
- package/dist/tools/terminal.d.ts +6 -0
- package/dist/tools/terminal.js +4 -0
- package/dist/tools/terminal.js.map +1 -1
- package/dist/tools/testTraceToSource.d.ts +1 -0
- package/dist/tools/testTraceToSource.js +2 -2
- package/dist/tools/testTraceToSource.js.map +1 -1
- package/dist/tools/transaction.d.ts +23 -0
- package/dist/tools/transaction.js +29 -0
- package/dist/tools/transaction.js.map +1 -1
- package/dist/tools/typeHierarchy.d.ts +1 -0
- package/dist/tools/updateLinearIssue.d.ts +1 -0
- package/dist/tools/updateLinearIssue.js +20 -6
- package/dist/tools/updateLinearIssue.js.map +1 -1
- package/dist/tools/utils.d.ts +6 -0
- package/dist/tools/utils.js +59 -0
- package/dist/tools/utils.js.map +1 -1
- package/dist/tools/vscodeCommands.d.ts +2 -0
- package/dist/tools/vscodeTasks.d.ts +2 -0
- package/dist/tools/workspaceSettings.d.ts +1 -0
- package/dist/traceEncryption.d.ts +46 -0
- package/dist/traceEncryption.js +124 -0
- package/dist/traceEncryption.js.map +1 -0
- package/dist/transport.d.ts +46 -1
- package/dist/transport.js +173 -19
- package/dist/transport.js.map +1 -1
- package/package.json +30 -8
- package/scripts/mcp-stdio-shim.cjs +19 -3
- package/scripts/start-all.sh +34 -3
- package/templates/automation-policies/recipe-authoring.json +25 -0
- package/templates/automation-policy.example.json +6 -0
- package/templates/co.patchwork-os.bridge.plist +34 -0
- package/templates/policies/README.md +72 -0
- package/templates/policies/conservative.json +14 -0
- package/templates/policies/developer.json +14 -0
- package/templates/policies/headless-ci.json +24 -0
- package/templates/policies/personal-assistant.json +15 -0
- package/templates/policies/regulated-industry.json +18 -0
- package/templates/recipes/approval-queue-ui-test.yaml +205 -0
- package/templates/recipes/lint-on-save.yaml +1 -2
- package/templates/recipes/morning-brief-slack.yaml +57 -0
- package/templates/recipes/morning-brief.yaml +2 -2
- package/templates/recipes/project-health-check.yaml +50 -0
- package/templates/recipes/webhook/README.md +70 -0
- package/templates/recipes/webhook/capture-thought.yaml +26 -0
- package/templates/recipes/webhook/customer-escalation.yaml +49 -0
- package/templates/recipes/webhook/incident-intake.yaml +46 -0
- package/templates/recipes/webhook/meeting-prep.yaml +48 -0
- package/templates/recipes/webhook/morning-brief.yaml +57 -0
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* patchwork recipe install — download and install a recipe package.
|
|
3
|
+
* patchwork recipe list — list installed recipe packages.
|
|
4
|
+
*
|
|
5
|
+
* Supports:
|
|
6
|
+
* github:owner/repo
|
|
7
|
+
* github:owner/repo/subdir
|
|
8
|
+
* https://github.com/owner/repo
|
|
9
|
+
* ./local/path
|
|
10
|
+
*/
|
|
11
|
+
import { cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
12
|
+
import https from "node:https";
|
|
13
|
+
import os from "node:os";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import { disabledMarkerPath, isInstallDirDisabled, } from "../recipes/disabledMarkers.js";
|
|
16
|
+
import { getManifestRecipeFiles, loadManifestFromDir, parseManifest, } from "../recipes/manifest.js";
|
|
17
|
+
export const INSTALL_RECIPES_DIR = path.join(os.homedir(), ".patchwork", "recipes");
|
|
18
|
+
/**
|
|
19
|
+
* Reject path components that aren't a single safe basename — used at every
|
|
20
|
+
* boundary where externally-sourced filenames are joined onto a trusted
|
|
21
|
+
* directory (manifest fields, GitHub API responses, CLI args).
|
|
22
|
+
*
|
|
23
|
+
* Rejects empty/".."/".", any path separator, and control chars (NUL/newline/tab).
|
|
24
|
+
* Exported for testing and reuse.
|
|
25
|
+
*/
|
|
26
|
+
export function isSafeBasename(name) {
|
|
27
|
+
if (typeof name !== "string" || name.length === 0)
|
|
28
|
+
return false;
|
|
29
|
+
if (name === "." || name === "..")
|
|
30
|
+
return false;
|
|
31
|
+
if (name.includes("/") || name.includes("\\"))
|
|
32
|
+
return false;
|
|
33
|
+
if (/[\x00-\x1F\x7F]/.test(name))
|
|
34
|
+
return false;
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Parse a user-supplied source string into a typed InstallSource.
|
|
39
|
+
*
|
|
40
|
+
* Supported forms:
|
|
41
|
+
* github:owner/repo
|
|
42
|
+
* github:owner/repo@<ref> — pin to branch, tag, or commit SHA
|
|
43
|
+
* github:owner/repo/subdir
|
|
44
|
+
* github:owner/repo/subdir@<ref>
|
|
45
|
+
* gh:owner/repo[@<ref>] — short alias for github:
|
|
46
|
+
* https://github.com/owner/repo
|
|
47
|
+
* https://github.com/owner/repo/tree/<ref>/subdir — ref captured from URL
|
|
48
|
+
* ./relative/path
|
|
49
|
+
* /absolute/path
|
|
50
|
+
*
|
|
51
|
+
* `@<ref>` accepts any value that's valid as a git ref (branch, tag, SHA).
|
|
52
|
+
* Empty ref (`...@`) is rejected.
|
|
53
|
+
*/
|
|
54
|
+
export function parseInstallSource(source) {
|
|
55
|
+
// Local path: starts with . or /
|
|
56
|
+
if (source.startsWith("./") ||
|
|
57
|
+
source.startsWith("/") ||
|
|
58
|
+
source.startsWith("../")) {
|
|
59
|
+
return { type: "local", path: source };
|
|
60
|
+
}
|
|
61
|
+
// github:/gh: prefix
|
|
62
|
+
if (source.startsWith("github:")) {
|
|
63
|
+
return parseGithubShorthand(source.slice("github:".length));
|
|
64
|
+
}
|
|
65
|
+
if (source.startsWith("gh:")) {
|
|
66
|
+
return parseGithubShorthand(source.slice("gh:".length));
|
|
67
|
+
}
|
|
68
|
+
// Full GitHub URL — captures owner, repo, optional ref (tree/<ref>), optional subdir
|
|
69
|
+
const githubUrlMatch = source.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/tree\/([^/]+)(?:\/(.+))?)?\/?$/);
|
|
70
|
+
if (githubUrlMatch) {
|
|
71
|
+
const [, owner, repo, ref, subdir] = githubUrlMatch;
|
|
72
|
+
if (!owner || !repo) {
|
|
73
|
+
throw new Error(`Invalid GitHub URL: ${source}`);
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
type: "github",
|
|
77
|
+
owner,
|
|
78
|
+
repo,
|
|
79
|
+
...(subdir ? { subdir } : {}),
|
|
80
|
+
...(ref ? { ref } : {}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
throw new Error(`Unrecognized install source: "${source}"\n` +
|
|
84
|
+
`Supported: github:owner/repo[@ref], github:owner/repo/subdir[@ref], gh:owner/repo[@ref], https://github.com/owner/repo, ./local/path`);
|
|
85
|
+
}
|
|
86
|
+
// ---------------------------------------------------------------------
|
|
87
|
+
// BEGIN A-PR2 EDIT BLOCK — `parseGithubShorthand` strict validation
|
|
88
|
+
// (dogfood R2 M-2). Owner/repo segments are validated against GitHub's own
|
|
89
|
+
// rules (alphanumeric or hyphen/dot/underscore, max 39 chars, must start
|
|
90
|
+
// alphanumeric) so injection attempts via shorthand (`gh:foo@bar:baz/repo`,
|
|
91
|
+
// `gh:owner/<repo>?evil=1`) are rejected before reaching the URL builder.
|
|
92
|
+
// Refs reject userinfo (`@`) and port markers (`:`) — these would otherwise
|
|
93
|
+
// land inside the constructed `https://github.com/.../tree/<ref>/...` URL.
|
|
94
|
+
// ---------------------------------------------------------------------
|
|
95
|
+
const GITHUB_OWNER_REPO_RE = /^[a-zA-Z0-9](?:[a-zA-Z0-9-._]{0,38})$/;
|
|
96
|
+
function parseGithubShorthand(shorthand) {
|
|
97
|
+
// Extract trailing @<ref> if present. The ref is opaque to us — git accepts
|
|
98
|
+
// branches, tags, and commit SHAs in the same slot, and the GitHub API
|
|
99
|
+
// (which is what we ultimately call with this value) does too.
|
|
100
|
+
let ref;
|
|
101
|
+
const atIdx = shorthand.lastIndexOf("@");
|
|
102
|
+
if (atIdx !== -1) {
|
|
103
|
+
ref = shorthand.slice(atIdx + 1);
|
|
104
|
+
shorthand = shorthand.slice(0, atIdx);
|
|
105
|
+
if (!ref) {
|
|
106
|
+
throw new Error(`Invalid github shorthand: empty ref after "@" in "${shorthand}@"`);
|
|
107
|
+
}
|
|
108
|
+
// Reject embedded URL syntax that would corrupt the constructed
|
|
109
|
+
// https://github.com/<owner>/<repo>/tree/<ref> URL (R2 M-2).
|
|
110
|
+
if (/[@:?#\s]/.test(ref) || ref.includes("..")) {
|
|
111
|
+
throw new Error(`Invalid github shorthand: ref "${ref}" contains disallowed characters`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// owner/repo or owner/repo/subdir (may have multiple path segments)
|
|
115
|
+
const parts = shorthand.split("/");
|
|
116
|
+
if (parts.length < 2) {
|
|
117
|
+
throw new Error(`Invalid github shorthand "${shorthand}": expected "owner/repo" or "owner/repo/subdir"`);
|
|
118
|
+
}
|
|
119
|
+
const [owner, repo, ...subdirParts] = parts;
|
|
120
|
+
if (!owner || !repo) {
|
|
121
|
+
throw new Error(`Invalid github shorthand: "${shorthand}"`);
|
|
122
|
+
}
|
|
123
|
+
if (!GITHUB_OWNER_REPO_RE.test(owner)) {
|
|
124
|
+
throw new Error(`Invalid github shorthand: owner "${owner}" is not a valid GitHub username`);
|
|
125
|
+
}
|
|
126
|
+
if (!GITHUB_OWNER_REPO_RE.test(repo)) {
|
|
127
|
+
throw new Error(`Invalid github shorthand: repo "${repo}" is not a valid GitHub repository name`);
|
|
128
|
+
}
|
|
129
|
+
// Subdir segments: each must be a safe path component (no traversal, no
|
|
130
|
+
// control chars). Reuses `isSafeBasename` for consistency with the post-fetch
|
|
131
|
+
// file boundary check.
|
|
132
|
+
for (const seg of subdirParts) {
|
|
133
|
+
if (!isSafeBasename(seg)) {
|
|
134
|
+
throw new Error(`Invalid github shorthand: subdir segment "${seg}" is unsafe`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
type: "github",
|
|
139
|
+
owner,
|
|
140
|
+
repo,
|
|
141
|
+
...(subdirParts.length > 0 ? { subdir: subdirParts.join("/") } : {}),
|
|
142
|
+
...(ref ? { ref } : {}),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
// END A-PR2 EDIT BLOCK
|
|
146
|
+
// ============================================================================
|
|
147
|
+
// Install name determination
|
|
148
|
+
// ============================================================================
|
|
149
|
+
/**
|
|
150
|
+
* Determine the install directory name from the manifest or source.
|
|
151
|
+
* - Manifest present: strip leading @ and replace / with -- for filesystem safety.
|
|
152
|
+
* - GitHub source (no manifest): "owner/repo" or "owner/repo/subdir".
|
|
153
|
+
* - Local source (no manifest): basename of the directory.
|
|
154
|
+
*/
|
|
155
|
+
export function determineInstallName(manifest, source) {
|
|
156
|
+
if (manifest) {
|
|
157
|
+
// Strip leading @ and replace "/" with "--" so it's a valid directory name
|
|
158
|
+
return manifest.name.replace(/^@/, "").replace(/\//g, "--");
|
|
159
|
+
}
|
|
160
|
+
if (source.type === "github") {
|
|
161
|
+
const base = `${source.owner}/${source.repo}`;
|
|
162
|
+
return source.subdir ? `${base}/${source.subdir}` : base;
|
|
163
|
+
}
|
|
164
|
+
return path.basename(path.resolve(source.path));
|
|
165
|
+
}
|
|
166
|
+
// ============================================================================
|
|
167
|
+
// GitHub file fetching via API
|
|
168
|
+
// ============================================================================
|
|
169
|
+
// ---------------------------------------------------------------------
|
|
170
|
+
// BEGIN A-PR2 EDIT BLOCK — `httpsGet` redirect chain hardening
|
|
171
|
+
// (dogfood R2 I-2). Redirect targets must (1) be one of GitHub's known hosts
|
|
172
|
+
// and (2) clear the SSRF guard. Hop count capped at 5 to bound the chain.
|
|
173
|
+
// Origin is also validated up-front: this helper is reached only after
|
|
174
|
+
// `parseGithubShorthand` / GitHub URL parsing, so all callers should already
|
|
175
|
+
// be pointed at github.com / api.github.com / raw.githubusercontent.com.
|
|
176
|
+
// ---------------------------------------------------------------------
|
|
177
|
+
const GITHUB_REDIRECT_HOSTS = new Set([
|
|
178
|
+
"github.com",
|
|
179
|
+
"www.github.com",
|
|
180
|
+
"api.github.com",
|
|
181
|
+
"raw.githubusercontent.com",
|
|
182
|
+
"codeload.github.com",
|
|
183
|
+
"objects.githubusercontent.com",
|
|
184
|
+
"media.githubusercontent.com",
|
|
185
|
+
]);
|
|
186
|
+
const HTTPS_GET_MAX_REDIRECTS = 5;
|
|
187
|
+
function isAllowedGithubHost(hostname) {
|
|
188
|
+
return GITHUB_REDIRECT_HOSTS.has(hostname.toLowerCase());
|
|
189
|
+
}
|
|
190
|
+
async function httpsGet(url, hops = 0) {
|
|
191
|
+
// Lazy-load the SSRF guard so test harnesses that mock https.get don't have
|
|
192
|
+
// to also stub DNS — the guard fast-paths public hostnames anyway.
|
|
193
|
+
const { isPrivateHost } = await import("../ssrfGuard.js");
|
|
194
|
+
let parsed;
|
|
195
|
+
try {
|
|
196
|
+
parsed = new URL(url);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
throw new Error(`Invalid URL: ${url}`);
|
|
200
|
+
}
|
|
201
|
+
if (parsed.protocol !== "https:") {
|
|
202
|
+
throw new Error(`Refusing non-https URL: ${url}`);
|
|
203
|
+
}
|
|
204
|
+
if (!isAllowedGithubHost(parsed.hostname)) {
|
|
205
|
+
throw new Error(`Refusing redirect to non-GitHub host "${parsed.hostname}"`);
|
|
206
|
+
}
|
|
207
|
+
if (isPrivateHost(parsed.hostname)) {
|
|
208
|
+
throw new Error(`Refusing redirect to private host "${parsed.hostname}"`);
|
|
209
|
+
}
|
|
210
|
+
return new Promise((resolve, reject) => {
|
|
211
|
+
const req = https.get(url, {
|
|
212
|
+
headers: {
|
|
213
|
+
"User-Agent": "patchwork-recipe-installer/1.0",
|
|
214
|
+
Accept: "application/vnd.github.v3+json",
|
|
215
|
+
},
|
|
216
|
+
}, (res) => {
|
|
217
|
+
// Follow redirects — bounded chain, allowlisted host, SSRF-guarded.
|
|
218
|
+
if (res.statusCode &&
|
|
219
|
+
res.statusCode >= 300 &&
|
|
220
|
+
res.statusCode < 400 &&
|
|
221
|
+
res.headers.location) {
|
|
222
|
+
if (hops >= HTTPS_GET_MAX_REDIRECTS) {
|
|
223
|
+
reject(new Error(`Too many redirects (>${HTTPS_GET_MAX_REDIRECTS})`));
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
// Resolve relative redirects against the current URL so a relative
|
|
227
|
+
// `Location: /foo` doesn't get treated as an empty hostname.
|
|
228
|
+
let nextUrl;
|
|
229
|
+
try {
|
|
230
|
+
nextUrl = new URL(res.headers.location, url);
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
reject(new Error(`Invalid redirect location: "${res.headers.location}"`));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (nextUrl.protocol !== "https:") {
|
|
237
|
+
reject(new Error(`Refusing redirect to non-https protocol: "${nextUrl.protocol}"`));
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
httpsGet(nextUrl.toString(), hops + 1)
|
|
241
|
+
.then(resolve)
|
|
242
|
+
.catch(reject);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
246
|
+
reject(new Error(`HTTP ${res.statusCode} fetching ${url}`));
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const chunks = [];
|
|
250
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
251
|
+
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
252
|
+
res.on("error", reject);
|
|
253
|
+
});
|
|
254
|
+
req.on("error", reject);
|
|
255
|
+
req.end();
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
async function listGitHubContents(owner, repo, dirPath, ref) {
|
|
259
|
+
const url = `https://api.github.com/repos/${owner}/${repo}/contents/${dirPath}?ref=${ref}`;
|
|
260
|
+
const body = await httpsGet(url);
|
|
261
|
+
const parsed = JSON.parse(body.toString("utf-8"));
|
|
262
|
+
if (!Array.isArray(parsed)) {
|
|
263
|
+
throw new Error(`Expected array from GitHub contents API, got: ${typeof parsed}`);
|
|
264
|
+
}
|
|
265
|
+
return parsed;
|
|
266
|
+
}
|
|
267
|
+
async function fetchGitHubFile(downloadUrl) {
|
|
268
|
+
return httpsGet(downloadUrl);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Download all .yaml/.yml files (and recipe.json if present) from a GitHub
|
|
272
|
+
* directory into `destDir`. Returns list of filenames written.
|
|
273
|
+
*/
|
|
274
|
+
async function downloadGitHubDir(owner, repo, dirPath, ref, destDir) {
|
|
275
|
+
const items = await listGitHubContents(owner, repo, dirPath, ref);
|
|
276
|
+
const written = [];
|
|
277
|
+
for (const item of items) {
|
|
278
|
+
if (item.type !== "file")
|
|
279
|
+
continue;
|
|
280
|
+
if (item.name !== "recipe.json" && !/\.ya?ml$/i.test(item.name)) {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (!item.download_url)
|
|
284
|
+
continue;
|
|
285
|
+
// GitHub Contents API responses are not implicitly trusted: a hostile
|
|
286
|
+
// repo (or a redirect-to-attacker) could supply names like `../etc/x`.
|
|
287
|
+
// The existing extension filter above already blocks the most obvious
|
|
288
|
+
// payloads, but we explicitly reject anything that isn't a single
|
|
289
|
+
// basename so a future change to the filter doesn't reopen the gap.
|
|
290
|
+
if (!isSafeBasename(item.name)) {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
const content = await fetchGitHubFile(item.download_url);
|
|
294
|
+
const destPath = path.join(destDir, item.name);
|
|
295
|
+
// Belt-and-suspenders: confirm the resolved write path lives inside destDir.
|
|
296
|
+
if (!path.resolve(destPath).startsWith(`${path.resolve(destDir)}${path.sep}`)) {
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
writeFileSync(destPath, content);
|
|
300
|
+
written.push(item.name);
|
|
301
|
+
}
|
|
302
|
+
return written;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Install a recipe package from a source into INSTALL_RECIPES_DIR.
|
|
306
|
+
* Returns metadata about what was installed.
|
|
307
|
+
*/
|
|
308
|
+
export async function runRecipeInstall(rawSource, options = {}) {
|
|
309
|
+
const source = parseInstallSource(rawSource);
|
|
310
|
+
const recipesDir = options.recipesDir ?? INSTALL_RECIPES_DIR;
|
|
311
|
+
// Stage into temp dir first
|
|
312
|
+
const tmpDir = mkdtempSync(path.join(os.tmpdir(), "patchwork-recipe-"));
|
|
313
|
+
try {
|
|
314
|
+
if (source.type === "local") {
|
|
315
|
+
await stageLocalSource(source, tmpDir);
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
await stageGitHubSource(source, tmpDir);
|
|
319
|
+
}
|
|
320
|
+
// Read manifest if present
|
|
321
|
+
let manifest = null;
|
|
322
|
+
const manifestPath = path.join(tmpDir, "recipe.json");
|
|
323
|
+
if (existsSync(manifestPath)) {
|
|
324
|
+
manifest = parseManifest(readFileSync(manifestPath, "utf-8"));
|
|
325
|
+
}
|
|
326
|
+
// Determine which files to copy
|
|
327
|
+
let filesToCopy;
|
|
328
|
+
if (manifest) {
|
|
329
|
+
const declared = getManifestRecipeFiles(manifest);
|
|
330
|
+
// Include recipe.json + declared recipe files (that exist in tmpDir)
|
|
331
|
+
filesToCopy = ["recipe.json", ...declared].filter((f) => existsSync(path.join(tmpDir, f)));
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
// No manifest: take all .yaml/.yml files
|
|
335
|
+
filesToCopy = readdirSync(tmpDir).filter((f) => /\.ya?ml$/i.test(f));
|
|
336
|
+
}
|
|
337
|
+
if (filesToCopy.length === 0) {
|
|
338
|
+
throw new Error(`No recipe files found in source "${rawSource}". ` +
|
|
339
|
+
`Expected .yaml/.yml files or a recipe.json manifest.`);
|
|
340
|
+
}
|
|
341
|
+
const installName = determineInstallName(manifest, source);
|
|
342
|
+
const installDir = path.join(recipesDir, installName);
|
|
343
|
+
// Reinstall correctness: detect whether this is an upgrade in place,
|
|
344
|
+
// and snapshot the existing enabled state so the upgrade doesn't
|
|
345
|
+
// silently re-disable a recipe the user explicitly opted into.
|
|
346
|
+
const isReinstall = existsSync(installDir);
|
|
347
|
+
const wasEnabled = isReinstall ? !isInstallDirDisabled(installDir) : false;
|
|
348
|
+
if (isReinstall) {
|
|
349
|
+
// Clear stale files from the previous version so files dropped from
|
|
350
|
+
// the new manifest don't linger. We rebuild the install dir wholesale
|
|
351
|
+
// rather than overlay the new files on top of the old.
|
|
352
|
+
try {
|
|
353
|
+
rmSync(installDir, { recursive: true, force: true });
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
// best-effort; mkdirSync below will throw with a clearer error
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
mkdirSync(installDir, { recursive: true });
|
|
360
|
+
// Copy files
|
|
361
|
+
for (const file of filesToCopy) {
|
|
362
|
+
const src = path.join(tmpDir, file);
|
|
363
|
+
const dest = path.join(installDir, file);
|
|
364
|
+
// Ensure subdirs exist (recipe.json could declare children in subdirs)
|
|
365
|
+
const destParent = path.dirname(dest);
|
|
366
|
+
if (!existsSync(destParent)) {
|
|
367
|
+
mkdirSync(destParent, { recursive: true });
|
|
368
|
+
}
|
|
369
|
+
cpSync(src, dest);
|
|
370
|
+
}
|
|
371
|
+
// Write the disabled-marker policy:
|
|
372
|
+
// - Fresh install: start disabled (per the wave2 plan's safety story).
|
|
373
|
+
// - Reinstall (upgrade in place): preserve whatever the user had set.
|
|
374
|
+
// If the recipe was enabled before, leave it enabled; if disabled,
|
|
375
|
+
// leave it disabled. Don't silently revoke an explicit user opt-in.
|
|
376
|
+
if (!isReinstall || !wasEnabled) {
|
|
377
|
+
writeFileSync(disabledMarkerPath(installDir), "");
|
|
378
|
+
}
|
|
379
|
+
return {
|
|
380
|
+
name: installName,
|
|
381
|
+
version: manifest?.version,
|
|
382
|
+
installDir,
|
|
383
|
+
filesInstalled: filesToCopy,
|
|
384
|
+
manifest,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
finally {
|
|
388
|
+
try {
|
|
389
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
// best-effort cleanup
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
async function stageLocalSource(source, tmpDir) {
|
|
397
|
+
const resolvedPath = path.resolve(source.path);
|
|
398
|
+
if (!existsSync(resolvedPath)) {
|
|
399
|
+
throw new Error(`Local path does not exist: ${resolvedPath}`);
|
|
400
|
+
}
|
|
401
|
+
if (!statSync(resolvedPath).isDirectory()) {
|
|
402
|
+
throw new Error(`Local path is not a directory: ${resolvedPath}`);
|
|
403
|
+
}
|
|
404
|
+
cpSync(resolvedPath, tmpDir, { recursive: true });
|
|
405
|
+
}
|
|
406
|
+
async function stageGitHubSource(source, tmpDir) {
|
|
407
|
+
const ref = source.ref ?? "main";
|
|
408
|
+
const dirPath = source.subdir ?? "";
|
|
409
|
+
try {
|
|
410
|
+
await downloadGitHubDir(source.owner, source.repo, dirPath, ref, tmpDir);
|
|
411
|
+
}
|
|
412
|
+
catch (err) {
|
|
413
|
+
// If main branch fails, try master
|
|
414
|
+
if (ref === "main") {
|
|
415
|
+
try {
|
|
416
|
+
await downloadGitHubDir(source.owner, source.repo, dirPath, "master", tmpDir);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
// fall through to original error
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
throw err;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Returns true if the install dir does not contain the disabled marker.
|
|
428
|
+
* Recipes installed before this marker existed have no marker and are
|
|
429
|
+
* therefore considered enabled — preserves backwards compatibility.
|
|
430
|
+
*/
|
|
431
|
+
export function isRecipeEnabled(installDir) {
|
|
432
|
+
return !isInstallDirDisabled(installDir);
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Locate an installed recipe directory by name. Returns null if not found.
|
|
436
|
+
*
|
|
437
|
+
* Validates `name` is a safe basename to defend against `recipe enable
|
|
438
|
+
* ../../../etc/foo` and similar — even though the on-disk effect would be
|
|
439
|
+
* limited to the `.disabled` filename, an arbitrary-path file write under
|
|
440
|
+
* the user's privilege is still a real attack surface.
|
|
441
|
+
*/
|
|
442
|
+
function findInstalledRecipeDir(name, recipesDir) {
|
|
443
|
+
if (!isSafeBasename(name)) {
|
|
444
|
+
throw new Error(`Invalid recipe name "${name}" — must be a single directory name without path separators or control characters.`);
|
|
445
|
+
}
|
|
446
|
+
const direct = path.join(recipesDir, name);
|
|
447
|
+
// Defense-in-depth: even with the basename check above, confirm the resolved
|
|
448
|
+
// path lives under recipesDir. Symlinks inside recipesDir could in principle
|
|
449
|
+
// escape, so this catches that too.
|
|
450
|
+
const resolvedRoot = path.resolve(recipesDir);
|
|
451
|
+
const resolvedDir = path.resolve(direct);
|
|
452
|
+
if (!resolvedDir.startsWith(`${resolvedRoot}${path.sep}`)) {
|
|
453
|
+
throw new Error(`Resolved recipe path escapes recipes directory: "${name}"`);
|
|
454
|
+
}
|
|
455
|
+
if (existsSync(direct) && statSync(direct).isDirectory()) {
|
|
456
|
+
return direct;
|
|
457
|
+
}
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Resolve an install-dir-name (the directory `runRecipeInstall` created) to
|
|
462
|
+
* the YAML entrypoint inside it. Used by `recipe run <name>` so the user can
|
|
463
|
+
* pass the name they see in `recipe list` rather than having to dig into the
|
|
464
|
+
* install directory layout.
|
|
465
|
+
*
|
|
466
|
+
* Resolution order:
|
|
467
|
+
* 1. `recipe.json` manifest's `recipes.main`, if the manifest exists and
|
|
468
|
+
* the file it points at exists on disk.
|
|
469
|
+
* 2. First `*.yaml` / `*.yml` in the install dir.
|
|
470
|
+
*
|
|
471
|
+
* Returns null if `name` doesn't correspond to an install dir, or the dir
|
|
472
|
+
* exists but contains no resolvable entrypoint. Path-traversal `name` values
|
|
473
|
+
* (e.g. `../../etc`) throw via the underlying `findInstalledRecipeDir` —
|
|
474
|
+
* same defence as enable/disable/uninstall.
|
|
475
|
+
*/
|
|
476
|
+
export function findInstalledRecipeEntrypoint(name, options = {}) {
|
|
477
|
+
const recipesDir = options.recipesDir ?? INSTALL_RECIPES_DIR;
|
|
478
|
+
const installDir = findInstalledRecipeDir(name, recipesDir);
|
|
479
|
+
if (!installDir)
|
|
480
|
+
return null;
|
|
481
|
+
const manifestPath = path.join(installDir, "recipe.json");
|
|
482
|
+
if (existsSync(manifestPath)) {
|
|
483
|
+
try {
|
|
484
|
+
const m = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
485
|
+
if (m.recipes?.main && isSafeBasename(m.recipes.main)) {
|
|
486
|
+
const candidate = path.join(installDir, m.recipes.main);
|
|
487
|
+
if (existsSync(candidate))
|
|
488
|
+
return candidate;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
catch {
|
|
492
|
+
// Malformed manifest → fall through to first-yaml lookup. The
|
|
493
|
+
// scheduler does the same; surfacing the parse error here would
|
|
494
|
+
// shadow the top-level "recipe not found" error from the CLI.
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
try {
|
|
498
|
+
for (const entry of readdirSync(installDir)) {
|
|
499
|
+
if (/\.ya?ml$/i.test(entry)) {
|
|
500
|
+
return path.join(installDir, entry);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
catch {
|
|
505
|
+
// unreadable
|
|
506
|
+
}
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Enable a recipe — removes the .disabled marker so triggers can fire.
|
|
511
|
+
* Idempotent: enabling an already-enabled recipe is a no-op.
|
|
512
|
+
*/
|
|
513
|
+
export function runRecipeEnable(name, options = {}) {
|
|
514
|
+
const recipesDir = options.recipesDir ?? INSTALL_RECIPES_DIR;
|
|
515
|
+
const installDir = findInstalledRecipeDir(name, recipesDir);
|
|
516
|
+
if (!installDir) {
|
|
517
|
+
throw new Error(`No installed recipe named "${name}". Run \`patchwork recipe list\` to see installed recipes.`);
|
|
518
|
+
}
|
|
519
|
+
const markerPath = disabledMarkerPath(installDir);
|
|
520
|
+
if (!existsSync(markerPath)) {
|
|
521
|
+
return { name, installDir, alreadyEnabled: true };
|
|
522
|
+
}
|
|
523
|
+
unlinkSync(markerPath);
|
|
524
|
+
return { name, installDir, alreadyEnabled: false };
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Disable a recipe — writes the .disabled marker so triggers stop firing.
|
|
528
|
+
* Idempotent: disabling an already-disabled recipe is a no-op.
|
|
529
|
+
*/
|
|
530
|
+
export function runRecipeDisable(name, options = {}) {
|
|
531
|
+
const recipesDir = options.recipesDir ?? INSTALL_RECIPES_DIR;
|
|
532
|
+
const installDir = findInstalledRecipeDir(name, recipesDir);
|
|
533
|
+
if (!installDir) {
|
|
534
|
+
throw new Error(`No installed recipe named "${name}". Run \`patchwork recipe list\` to see installed recipes.`);
|
|
535
|
+
}
|
|
536
|
+
const markerPath = disabledMarkerPath(installDir);
|
|
537
|
+
if (existsSync(markerPath)) {
|
|
538
|
+
return { name, installDir, alreadyDisabled: true };
|
|
539
|
+
}
|
|
540
|
+
writeFileSync(markerPath, "");
|
|
541
|
+
return { name, installDir, alreadyDisabled: false };
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Uninstall a recipe — removes its install directory entirely.
|
|
545
|
+
*
|
|
546
|
+
* Returns `{ ok: false, error }` when the recipe isn't found rather than
|
|
547
|
+
* throwing, so the CLI can surface a clean error message instead of a
|
|
548
|
+
* stack trace. Path-traversal attempts in `name` still throw via
|
|
549
|
+
* `findInstalledRecipeDir`'s validator (HIGH-2 hardening from #46).
|
|
550
|
+
*/
|
|
551
|
+
export function runRecipeUninstall(name, options = {}) {
|
|
552
|
+
const recipesDir = options.recipesDir ?? INSTALL_RECIPES_DIR;
|
|
553
|
+
const installDir = findInstalledRecipeDir(name, recipesDir);
|
|
554
|
+
if (!installDir) {
|
|
555
|
+
return {
|
|
556
|
+
ok: false,
|
|
557
|
+
error: `No installed recipe named "${name}". Run \`patchwork recipe list\` to see installed recipes.`,
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
rmSync(installDir, { recursive: true, force: true });
|
|
561
|
+
return { ok: true, installDir };
|
|
562
|
+
}
|
|
563
|
+
export function listInstalledRecipes(options = {}) {
|
|
564
|
+
const recipesDir = options.recipesDir ?? INSTALL_RECIPES_DIR;
|
|
565
|
+
if (!existsSync(recipesDir)) {
|
|
566
|
+
return [];
|
|
567
|
+
}
|
|
568
|
+
const entries = [];
|
|
569
|
+
function scanDir(dir, namePrefix) {
|
|
570
|
+
const items = readdirSync(dir);
|
|
571
|
+
for (const item of items) {
|
|
572
|
+
const itemPath = path.join(dir, item);
|
|
573
|
+
if (!statSync(itemPath).isDirectory())
|
|
574
|
+
continue;
|
|
575
|
+
const entryName = namePrefix ? `${namePrefix}/${item}` : item;
|
|
576
|
+
const manifest = loadManifestFromDir(itemPath);
|
|
577
|
+
if (manifest) {
|
|
578
|
+
entries.push({
|
|
579
|
+
name: entryName,
|
|
580
|
+
version: manifest.version,
|
|
581
|
+
description: manifest.description,
|
|
582
|
+
connectors: manifest.connectors,
|
|
583
|
+
mainRecipe: manifest.recipes.main,
|
|
584
|
+
hasManifest: true,
|
|
585
|
+
enabled: isRecipeEnabled(itemPath),
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
else {
|
|
589
|
+
const yamlFiles = readdirSync(itemPath).filter((f) => /\.ya?ml$/i.test(f));
|
|
590
|
+
if (yamlFiles.length > 0) {
|
|
591
|
+
entries.push({
|
|
592
|
+
name: entryName,
|
|
593
|
+
yamlFiles,
|
|
594
|
+
hasManifest: false,
|
|
595
|
+
enabled: isRecipeEnabled(itemPath),
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
else {
|
|
599
|
+
// Recurse one level for namespaced dirs like "owner/repo"
|
|
600
|
+
if (!namePrefix) {
|
|
601
|
+
scanDir(itemPath, item);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
scanDir(recipesDir, "");
|
|
608
|
+
return entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
609
|
+
}
|
|
610
|
+
// ============================================================================
|
|
611
|
+
// CLI output helpers
|
|
612
|
+
// ============================================================================
|
|
613
|
+
export function printInstallResult(result) {
|
|
614
|
+
const versionStr = result.version ? `@${result.version}` : "";
|
|
615
|
+
console.log(`✓ Installed ${result.name}${versionStr} to ${result.installDir}`);
|
|
616
|
+
if (result.manifest?.connectors && result.manifest.connectors.length > 0) {
|
|
617
|
+
console.log(` Requires connectors: ${result.manifest.connectors.join(", ")}`);
|
|
618
|
+
}
|
|
619
|
+
console.log(` Status: disabled (run \`patchwork recipe enable ${result.name}\` to activate scheduled triggers)`);
|
|
620
|
+
const mainRecipe = result.manifest?.recipes.main ?? result.filesInstalled[0];
|
|
621
|
+
if (mainRecipe) {
|
|
622
|
+
console.log(` Run with: patchwork recipe run ${path.join(result.installDir, mainRecipe)}`);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
export function printInstalledList(entries) {
|
|
626
|
+
if (entries.length === 0) {
|
|
627
|
+
console.log("No recipes installed. Use `patchwork recipe install <source>` to install.");
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
const maxName = Math.max(...entries.map((e) => e.name.length), 4);
|
|
631
|
+
const maxVersion = Math.max(...entries.map((e) => (e.version ?? "—").length), 7);
|
|
632
|
+
const header = `${"Name".padEnd(maxName)} ${"Version".padEnd(maxVersion)} Status Description / Files`;
|
|
633
|
+
console.log(header);
|
|
634
|
+
console.log("-".repeat(Math.min(header.length, 100)));
|
|
635
|
+
for (const entry of entries) {
|
|
636
|
+
const version = (entry.version ?? "—").padEnd(maxVersion);
|
|
637
|
+
const status = (entry.enabled ? "enabled" : "disabled").padEnd(8);
|
|
638
|
+
const detail = entry.hasManifest
|
|
639
|
+
? (entry.description ?? "")
|
|
640
|
+
: `[${(entry.yamlFiles ?? []).join(", ")}]`;
|
|
641
|
+
console.log(`${entry.name.padEnd(maxName)} ${version} ${status} ${detail}`);
|
|
642
|
+
if (entry.connectors && entry.connectors.length > 0) {
|
|
643
|
+
console.log(`${"".padEnd(maxName)} ${"".padEnd(maxVersion)} ${"".padEnd(8)} connectors: ${entry.connectors.join(", ")}`);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
//# sourceMappingURL=recipeInstall.js.map
|