patchwork-os 0.2.0-alpha.3 → 0.2.0-alpha.31
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 +40 -15
- package/deploy/bootstrap-vps.sh +184 -0
- package/deploy/deploy-dashboard.sh +174 -0
- package/deploy/deploy-landing.sh +79 -0
- package/dist/activationMetrics.d.ts +67 -0
- package/dist/activationMetrics.js +255 -0
- package/dist/activationMetrics.js.map +1 -0
- package/dist/approvalHttp.d.ts +24 -2
- package/dist/approvalHttp.js +150 -10
- package/dist/approvalHttp.js.map +1 -1
- package/dist/approvalQueue.d.ts +16 -1
- package/dist/approvalQueue.js +44 -3
- package/dist/approvalQueue.js.map +1 -1
- package/dist/automation.d.ts +20 -0
- package/dist/automation.js +54 -1
- package/dist/automation.js.map +1 -1
- package/dist/bridge.d.ts +2 -0
- package/dist/bridge.js +55 -130
- package/dist/bridge.js.map +1 -1
- package/dist/bridgeToken.js +57 -19
- package/dist/bridgeToken.js.map +1 -1
- package/dist/ccPermissions.js +6 -4
- package/dist/ccPermissions.js.map +1 -1
- package/dist/claudeOrchestrator.d.ts +1 -1
- package/dist/claudeOrchestrator.js +14 -8
- package/dist/claudeOrchestrator.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/recipe.d.ts +258 -0
- package/dist/commands/recipe.js +1130 -0
- package/dist/commands/recipe.js.map +1 -0
- package/dist/commands/recipeInstall.d.ts +72 -0
- package/dist/commands/recipeInstall.js +339 -0
- package/dist/commands/recipeInstall.js.map +1 -0
- package/dist/config.d.ts +14 -1
- package/dist/config.js +99 -8
- package/dist/config.js.map +1 -1
- package/dist/connectors/baseConnector.d.ts +117 -0
- package/dist/connectors/baseConnector.js +213 -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/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.d.ts +58 -8
- package/dist/connectors/github.js +312 -84
- package/dist/connectors/github.js.map +1 -1
- package/dist/connectors/gmail.d.ts +4 -1
- package/dist/connectors/gmail.js +79 -16
- package/dist/connectors/gmail.js.map +1 -1
- package/dist/connectors/googleCalendar.d.ts +60 -0
- package/dist/connectors/googleCalendar.js +345 -0
- package/dist/connectors/googleCalendar.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 +379 -0
- package/dist/connectors/jira.js.map +1 -0
- package/dist/connectors/linear.d.ts +69 -19
- package/dist/connectors/linear.js +170 -129
- package/dist/connectors/linear.js.map +1 -1
- package/dist/connectors/mcpClient.d.ts +56 -0
- package/dist/connectors/mcpClient.js +189 -0
- package/dist/connectors/mcpClient.js.map +1 -0
- package/dist/connectors/mcpOAuth.d.ts +84 -0
- package/dist/connectors/mcpOAuth.js +389 -0
- package/dist/connectors/mcpOAuth.js.map +1 -0
- 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/sentry.d.ts +17 -21
- package/dist/connectors/sentry.js +115 -131
- package/dist/connectors/sentry.js.map +1 -1
- package/dist/connectors/slack.d.ts +50 -0
- package/dist/connectors/slack.js +324 -0
- package/dist/connectors/slack.js.map +1 -0
- 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 +459 -0
- package/dist/connectors/tokenStorage.js.map +1 -0
- package/dist/connectors/zendesk.d.ts +104 -0
- package/dist/connectors/zendesk.js +424 -0
- package/dist/connectors/zendesk.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 +5 -0
- package/dist/drivers/index.js +1 -1
- package/dist/drivers/index.js.map +1 -1
- package/dist/featureFlags.d.ts +73 -0
- package/dist/featureFlags.js +203 -0
- package/dist/featureFlags.js.map +1 -0
- package/dist/fp/automationInterpreter.js +1 -0
- 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/policyParser.js +17 -0
- package/dist/fp/policyParser.js.map +1 -1
- package/dist/index.js +621 -61
- 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/oauth.d.ts +4 -1
- package/dist/oauth.js +50 -14
- package/dist/oauth.js.map +1 -1
- package/dist/patchworkConfig.d.ts +9 -0
- package/dist/patchworkConfig.js.map +1 -1
- package/dist/recipeOrchestration.d.ts +53 -0
- package/dist/recipeOrchestration.js +272 -0
- package/dist/recipeOrchestration.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 +28 -0
- package/dist/recipes/agentExecutor.js +42 -0
- package/dist/recipes/agentExecutor.js.map +1 -0
- package/dist/recipes/chainedRunner.d.ts +140 -0
- package/dist/recipes/chainedRunner.js +539 -0
- package/dist/recipes/chainedRunner.js.map +1 -0
- 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/legacyRecipeCompat.d.ts +2 -0
- package/dist/recipes/legacyRecipeCompat.js +112 -0
- package/dist/recipes/legacyRecipeCompat.js.map +1 -0
- package/dist/recipes/manifest.d.ts +47 -0
- package/dist/recipes/manifest.js +141 -0
- package/dist/recipes/manifest.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/scheduler.d.ts +23 -7
- package/dist/recipes/scheduler.js +131 -41
- 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/templateEngine.d.ts +62 -0
- package/dist/recipes/templateEngine.js +182 -0
- package/dist/recipes/templateEngine.js.map +1 -0
- package/dist/recipes/toolRegistry.d.ts +181 -0
- package/dist/recipes/toolRegistry.js +300 -0
- package/dist/recipes/toolRegistry.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/file.d.ts +6 -0
- package/dist/recipes/tools/file.js +170 -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 +91 -0
- package/dist/recipes/tools/github.js.map +1 -0
- package/dist/recipes/tools/gmail.d.ts +6 -0
- package/dist/recipes/tools/gmail.js +210 -0
- package/dist/recipes/tools/gmail.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 +22 -0
- package/dist/recipes/tools/index.js +25 -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/linear.d.ts +6 -0
- package/dist/recipes/tools/linear.js +83 -0
- package/dist/recipes/tools/linear.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/slack.d.ts +6 -0
- package/dist/recipes/tools/slack.js +72 -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 +433 -0
- package/dist/recipes/validation.js.map +1 -0
- package/dist/recipes/yamlRunner.d.ts +87 -0
- package/dist/recipes/yamlRunner.js +693 -409
- package/dist/recipes/yamlRunner.js.map +1 -1
- package/dist/recipesHttp.d.ts +34 -6
- package/dist/recipesHttp.js +285 -15
- package/dist/recipesHttp.js.map +1 -1
- package/dist/riskTier.js +1 -0
- package/dist/riskTier.js.map +1 -1
- package/dist/runLog.d.ts +23 -0
- package/dist/runLog.js +56 -1
- 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 +32 -1
- package/dist/server.js +980 -97
- package/dist/server.js.map +1 -1
- package/dist/streamableHttp.js +2 -0
- package/dist/streamableHttp.js.map +1 -1
- package/dist/tools/addLinearComment.d.ts +55 -0
- package/dist/tools/addLinearComment.js +72 -0
- package/dist/tools/addLinearComment.js.map +1 -0
- package/dist/tools/bridgeDoctor.js +2 -2
- package/dist/tools/bridgeDoctor.js.map +1 -1
- package/dist/tools/createLinearIssue.d.ts +84 -0
- package/dist/tools/createLinearIssue.js +146 -0
- package/dist/tools/createLinearIssue.js.map +1 -0
- package/dist/tools/fetchCalendarEvents.d.ts +94 -0
- package/dist/tools/fetchCalendarEvents.js +97 -0
- package/dist/tools/fetchCalendarEvents.js.map +1 -0
- package/dist/tools/fetchGithubIssue.d.ts +80 -0
- package/dist/tools/fetchGithubIssue.js +84 -0
- package/dist/tools/fetchGithubIssue.js.map +1 -0
- package/dist/tools/fetchGithubPR.d.ts +89 -0
- package/dist/tools/fetchGithubPR.js +96 -0
- package/dist/tools/fetchGithubPR.js.map +1 -0
- package/dist/tools/fetchSlackProfile.d.ts +43 -0
- package/dist/tools/fetchSlackProfile.js +46 -0
- package/dist/tools/fetchSlackProfile.js.map +1 -0
- package/dist/tools/getConnectorStatus.d.ts +58 -0
- package/dist/tools/getConnectorStatus.js +56 -0
- package/dist/tools/getConnectorStatus.js.map +1 -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 +339 -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 +2 -1
- package/dist/tools/github/index.js +2 -1
- package/dist/tools/github/index.js.map +1 -1
- package/dist/tools/github/issues.js +8 -4
- package/dist/tools/github/issues.js.map +1 -1
- package/dist/tools/github/pr.d.ts +122 -0
- package/dist/tools/github/pr.js +195 -5
- package/dist/tools/github/pr.js.map +1 -1
- package/dist/tools/index.js +32 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/searchTools.js +1 -1
- package/dist/tools/searchTools.js.map +1 -1
- package/dist/tools/slackListChannels.d.ts +65 -0
- package/dist/tools/slackListChannels.js +70 -0
- package/dist/tools/slackListChannels.js.map +1 -0
- package/dist/tools/slackPostMessage.d.ts +57 -0
- package/dist/tools/slackPostMessage.js +77 -0
- package/dist/tools/slackPostMessage.js.map +1 -0
- package/dist/tools/testTraceToSource.js +2 -2
- package/dist/tools/testTraceToSource.js.map +1 -1
- package/dist/tools/updateLinearIssue.d.ts +89 -0
- package/dist/tools/updateLinearIssue.js +117 -0
- package/dist/tools/updateLinearIssue.js.map +1 -0
- package/dist/transport.d.ts +7 -1
- package/dist/transport.js +85 -11
- package/dist/transport.js.map +1 -1
- package/package.json +5 -2
- package/scripts/start-all.sh +56 -19
- 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/recipes/ctx-loop-test.yaml +75 -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 +14 -6
- package/templates/recipes/project-health-check.yaml +50 -0
- package/templates/recipes/sentry-to-linear.yaml +77 -0
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Uses Node 20.6+ native dotenv loader; falls back to manual parse for older Node.
|
|
4
4
|
{
|
|
5
5
|
const { fileURLToPath: _fileURLToPath } = await import("node:url");
|
|
6
|
-
const envPath = _fileURLToPath(new URL("
|
|
6
|
+
const envPath = _fileURLToPath(new URL("../.env", import.meta.url));
|
|
7
7
|
try {
|
|
8
8
|
const { readFileSync, existsSync } = await import("node:fs");
|
|
9
9
|
if (existsSync(envPath)) {
|
|
@@ -35,8 +35,24 @@ import { getAnalyticsPref, setAnalyticsPref } from "./analyticsPrefs.js";
|
|
|
35
35
|
import { Bridge } from "./bridge.js";
|
|
36
36
|
import { isBridgeToolsFileValid, repairBridgeToolsRulesIfStale, } from "./bridgeToolsRules.js";
|
|
37
37
|
import { findEditor, parseConfig } from "./config.js";
|
|
38
|
+
import { detectWorkspaceSymlinkInstall, PATCHWORK_PACKAGE_NAME, SYMLINK_INSTALL_FIX, } from "./installGuard.js";
|
|
38
39
|
import { PACKAGE_VERSION, semverGt } from "./version.js";
|
|
39
40
|
const __dirnameTop = path.dirname(fileURLToPath(import.meta.url));
|
|
41
|
+
// Warn when a symlinked global install is detected (`npm install -g .`).
|
|
42
|
+
// launchctl / sandbox environments can fail through that link with EPERM.
|
|
43
|
+
// Warn only — do not crash interactive or dev flows.
|
|
44
|
+
{
|
|
45
|
+
const _symlinkInfo = detectWorkspaceSymlinkInstall();
|
|
46
|
+
if (_symlinkInfo) {
|
|
47
|
+
process.stderr.write(`\n⚠️ Detected a symlinked global ${PATCHWORK_PACKAGE_NAME} install.\n` +
|
|
48
|
+
` Logical root: ${_symlinkInfo.logicalRoot}\n` +
|
|
49
|
+
` Real path: ${_symlinkInfo.realRoot}\n\n` +
|
|
50
|
+
" LaunchAgent startup can fail with EPERM when the macOS sandbox\n" +
|
|
51
|
+
" cannot access workspace files under ~/Documents through that link.\n\n" +
|
|
52
|
+
SYMLINK_INSTALL_FIX +
|
|
53
|
+
"\n");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
40
56
|
const OPEN_VSX_PUBLISHER = "oolab-labs";
|
|
41
57
|
const OPEN_VSX_NAME = "claude-ide-bridge-extension";
|
|
42
58
|
// CLAUDE.md versioned-block patching moved to ./claudeMdPatch.ts so tests
|
|
@@ -570,20 +586,9 @@ export function register(ctx) {
|
|
|
570
586
|
// Patchwork: `patchwork recipe list` — enumerate installed recipes.
|
|
571
587
|
if (process.argv[2] === "recipe" && process.argv[3] === "list") {
|
|
572
588
|
(async () => {
|
|
573
|
-
const {
|
|
574
|
-
const
|
|
575
|
-
|
|
576
|
-
if (recipes.length === 0) {
|
|
577
|
-
process.stdout.write("No recipes installed. Run `patchwork-os patchwork-init` to install the starter set.\n");
|
|
578
|
-
}
|
|
579
|
-
else {
|
|
580
|
-
process.stdout.write(`Installed recipes (${recipes.length}):\n\n`);
|
|
581
|
-
for (const r of recipes) {
|
|
582
|
-
const desc = r.description ? ` ${r.description}` : "";
|
|
583
|
-
process.stdout.write(` ${r.name.padEnd(28)} [${r.trigger}]${desc}\n`);
|
|
584
|
-
}
|
|
585
|
-
process.stdout.write(`\nRun a recipe: patchwork-os recipe run <name>\n`);
|
|
586
|
-
}
|
|
589
|
+
const { listInstalledRecipes, printInstalledList } = await import("./commands/recipeInstall.js");
|
|
590
|
+
const entries = listInstalledRecipes();
|
|
591
|
+
printInstalledList(entries);
|
|
587
592
|
process.exit(0);
|
|
588
593
|
})();
|
|
589
594
|
}
|
|
@@ -591,25 +596,94 @@ if (process.argv[2] === "recipe" && process.argv[3] === "list") {
|
|
|
591
596
|
// a running bridge's /recipes/run endpoint if one is available.
|
|
592
597
|
if (process.argv[2] === "recipe" && process.argv[3] === "run") {
|
|
593
598
|
const args = process.argv.slice(4);
|
|
594
|
-
const
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
599
|
+
const usage = "Usage: patchwork recipe run <name-or-file> [--local] [--dry-run] [--step <id>] [--var KEY=VALUE]\n";
|
|
600
|
+
let localFlag = false;
|
|
601
|
+
let dryRun = false;
|
|
602
|
+
let recipeRef;
|
|
603
|
+
let step;
|
|
604
|
+
const vars = {};
|
|
605
|
+
for (let i = 0; i < args.length; i++) {
|
|
606
|
+
const arg = args[i];
|
|
607
|
+
if (arg === undefined) {
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
const currentArg = arg;
|
|
611
|
+
if (currentArg === "--local") {
|
|
612
|
+
localFlag = true;
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
if (currentArg === "--dry-run") {
|
|
616
|
+
dryRun = true;
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
if (currentArg === "--step" || currentArg.startsWith("--step=")) {
|
|
620
|
+
const value = currentArg === "--step"
|
|
621
|
+
? args[++i]
|
|
622
|
+
: currentArg.slice("--step=".length);
|
|
623
|
+
if (!value) {
|
|
624
|
+
process.stderr.write(`Error: --step requires a value\n${usage}`);
|
|
625
|
+
process.exit(1);
|
|
626
|
+
}
|
|
627
|
+
step = value;
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
if (currentArg === "--var" || currentArg.startsWith("--var=")) {
|
|
631
|
+
const assignment = currentArg === "--var" ? args[++i] : currentArg.slice("--var=".length);
|
|
632
|
+
if (!assignment) {
|
|
633
|
+
process.stderr.write(`Error: --var requires KEY=VALUE\n${usage}`);
|
|
634
|
+
process.exit(1);
|
|
635
|
+
}
|
|
636
|
+
const eqIndex = assignment.indexOf("=");
|
|
637
|
+
if (eqIndex <= 0) {
|
|
638
|
+
process.stderr.write(`Error: invalid --var assignment "${assignment}" (expected KEY=VALUE)\n${usage}`);
|
|
639
|
+
process.exit(1);
|
|
640
|
+
}
|
|
641
|
+
const key = assignment.slice(0, eqIndex);
|
|
642
|
+
const value = assignment.slice(eqIndex + 1);
|
|
643
|
+
vars[key] = value;
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
if (currentArg.startsWith("--")) {
|
|
647
|
+
process.stderr.write(`Error: unknown option ${currentArg}\n${usage}`);
|
|
648
|
+
process.exit(1);
|
|
649
|
+
}
|
|
650
|
+
if (!recipeRef) {
|
|
651
|
+
recipeRef = currentArg;
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
process.stderr.write(`Error: unexpected argument ${currentArg}\n${usage}`);
|
|
598
655
|
process.exit(1);
|
|
599
656
|
}
|
|
657
|
+
if (!recipeRef) {
|
|
658
|
+
process.stderr.write(usage);
|
|
659
|
+
process.exit(1);
|
|
660
|
+
}
|
|
661
|
+
const recipeArg = recipeRef;
|
|
600
662
|
(async () => {
|
|
601
663
|
try {
|
|
602
|
-
|
|
664
|
+
const seedVars = Object.keys(vars).length > 0 ? vars : undefined;
|
|
665
|
+
const explicitFile = (() => {
|
|
666
|
+
try {
|
|
667
|
+
const resolved = path.resolve(recipeArg);
|
|
668
|
+
return existsSync(resolved) && statSync(resolved).isFile();
|
|
669
|
+
}
|
|
670
|
+
catch {
|
|
671
|
+
return false;
|
|
672
|
+
}
|
|
673
|
+
})();
|
|
603
674
|
const { findBridgeLock } = await import("./bridgeLockDiscovery.js");
|
|
604
675
|
const lock = localFlag ? null : findBridgeLock();
|
|
605
|
-
if (lock) {
|
|
676
|
+
if (lock && !dryRun && !step && !explicitFile) {
|
|
606
677
|
const res = await fetch(`http://127.0.0.1:${lock.port}/recipes/run`, {
|
|
607
678
|
method: "POST",
|
|
608
679
|
headers: {
|
|
609
680
|
Authorization: `Bearer ${lock.authToken}`,
|
|
610
681
|
"Content-Type": "application/json",
|
|
611
682
|
},
|
|
612
|
-
body: JSON.stringify({
|
|
683
|
+
body: JSON.stringify({
|
|
684
|
+
name: recipeArg,
|
|
685
|
+
...(seedVars ? { vars: seedVars } : {}),
|
|
686
|
+
}),
|
|
613
687
|
});
|
|
614
688
|
const body = (await res.json());
|
|
615
689
|
if (!body.ok) {
|
|
@@ -622,43 +696,64 @@ if (process.argv[2] === "recipe" && process.argv[3] === "run") {
|
|
|
622
696
|
// else: fall through to local runner below
|
|
623
697
|
}
|
|
624
698
|
else {
|
|
625
|
-
process.stdout.write(` ✓ enqueued recipe "${
|
|
699
|
+
process.stdout.write(` ✓ enqueued recipe "${recipeArg}" as task ${(body.taskId ?? "").slice(0, 8)}\n` +
|
|
626
700
|
" Watch progress on the dashboard Tasks page or via listClaudeTasks.\n");
|
|
627
701
|
process.exit(0);
|
|
628
702
|
return;
|
|
629
703
|
}
|
|
630
704
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
path.join(bundledDir, `${name}.yaml`),
|
|
640
|
-
path.join(bundledDir, `${name}.yml`),
|
|
641
|
-
];
|
|
642
|
-
let recipePath;
|
|
643
|
-
for (const c of candidates) {
|
|
644
|
-
if (existsSync(c)) {
|
|
645
|
-
recipePath = c;
|
|
646
|
-
break;
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
if (!recipePath) {
|
|
650
|
-
process.stderr.write(`Error: recipe "${name}" not found in ${recipesDir}\n` +
|
|
651
|
-
" Run `patchwork-os recipe list` to see available recipes.\n");
|
|
652
|
-
process.exit(1);
|
|
705
|
+
const { runRecipe, runRecipeDryPlan, summarizeRecipeExecution, formatRunReport, extractRunLogStepResults, } = await import("./commands/recipe.js");
|
|
706
|
+
if (dryRun) {
|
|
707
|
+
const plan = await runRecipeDryPlan(recipeArg, {
|
|
708
|
+
...(step ? { step } : {}),
|
|
709
|
+
...(seedVars ? { vars: seedVars } : {}),
|
|
710
|
+
});
|
|
711
|
+
process.stdout.write(`${JSON.stringify(plan, null, 2)}\n`);
|
|
712
|
+
process.exit(0);
|
|
653
713
|
return;
|
|
654
714
|
}
|
|
655
|
-
process.stdout.write(
|
|
656
|
-
|
|
715
|
+
process.stdout.write(step
|
|
716
|
+
? ` Running step "${step}" from recipe "${recipeArg}" locally…\n`
|
|
717
|
+
: ` Running recipe "${recipeArg}" locally…\n`);
|
|
657
718
|
const workdir = lock?.workspace || process.cwd();
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
719
|
+
const run = await runRecipe(recipeArg, {
|
|
720
|
+
...(step ? { step } : {}),
|
|
721
|
+
...(seedVars ? { vars: seedVars } : {}),
|
|
722
|
+
workdir,
|
|
723
|
+
});
|
|
724
|
+
if (run.stepSelection) {
|
|
725
|
+
process.stdout.write(` Selected step via ${run.stepSelection.matchedBy}: ${run.stepSelection.matchedValue}\n`);
|
|
726
|
+
}
|
|
727
|
+
const summary = summarizeRecipeExecution(run.result);
|
|
728
|
+
process.stdout.write(`${formatRunReport(run.result, run.recipe.name)}\n`);
|
|
729
|
+
if (summary.errorMessage) {
|
|
730
|
+
process.stderr.write(` Error: ${summary.errorMessage}\n`);
|
|
731
|
+
}
|
|
732
|
+
// Append to run log so CLI runs appear in ctxQueryTraces + dashboard /runs
|
|
733
|
+
try {
|
|
734
|
+
const { RecipeRunLog } = await import("./runLog.js");
|
|
735
|
+
const runLog = new RecipeRunLog({
|
|
736
|
+
dir: path.join(os.homedir(), ".patchwork"),
|
|
737
|
+
});
|
|
738
|
+
const startedAt = Date.now();
|
|
739
|
+
const stepResultsForLog = extractRunLogStepResults(run.result);
|
|
740
|
+
runLog.appendDirect({
|
|
741
|
+
taskId: `cli-${Date.now()}`,
|
|
742
|
+
recipeName: run.recipe.name,
|
|
743
|
+
trigger: "recipe",
|
|
744
|
+
status: summary.ok ? "done" : "error",
|
|
745
|
+
createdAt: startedAt,
|
|
746
|
+
startedAt,
|
|
747
|
+
doneAt: Date.now(),
|
|
748
|
+
durationMs: 0,
|
|
749
|
+
...(summary.errorMessage
|
|
750
|
+
? { errorMessage: summary.errorMessage }
|
|
751
|
+
: {}),
|
|
752
|
+
...(stepResultsForLog ? { stepResults: stepResultsForLog } : {}),
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
catch {
|
|
756
|
+
// Non-fatal — run log write failure must not abort the CLI
|
|
662
757
|
}
|
|
663
758
|
process.exit(0);
|
|
664
759
|
}
|
|
@@ -669,24 +764,324 @@ if (process.argv[2] === "recipe" && process.argv[3] === "run") {
|
|
|
669
764
|
})();
|
|
670
765
|
}
|
|
671
766
|
// Handle init subcommand — one-command setup: install extension + write CLAUDE.md + print next steps
|
|
672
|
-
// Patchwork: `patchwork recipe install <
|
|
767
|
+
// Patchwork: `patchwork recipe install <source>` subcommand.
|
|
768
|
+
// Supports: github:owner/repo, github:owner/repo/subdir, https://github.com/owner/repo,
|
|
769
|
+
// ./local/path, or legacy <file.json> (single-recipe install).
|
|
673
770
|
if (process.argv[2] === "recipe" && process.argv[3] === "install") {
|
|
771
|
+
const source = process.argv[4];
|
|
772
|
+
if (!source) {
|
|
773
|
+
process.stderr.write("Usage: patchwork recipe install <source>\n" +
|
|
774
|
+
" <source> can be:\n" +
|
|
775
|
+
" github:owner/repo\n" +
|
|
776
|
+
" github:owner/repo/subdir\n" +
|
|
777
|
+
" https://github.com/owner/repo\n" +
|
|
778
|
+
" ./local/path\n");
|
|
779
|
+
process.exit(1);
|
|
780
|
+
}
|
|
781
|
+
(async () => {
|
|
782
|
+
try {
|
|
783
|
+
// Legacy path: bare .json file argument → single-file installer
|
|
784
|
+
if (source.endsWith(".json") &&
|
|
785
|
+
!source.startsWith("github:") &&
|
|
786
|
+
!source.startsWith("http")) {
|
|
787
|
+
const { installRecipeFromFile } = await import("./recipes/installer.js");
|
|
788
|
+
const recipesDir = path.join(os.homedir(), ".patchwork", "recipes");
|
|
789
|
+
const result = installRecipeFromFile(path.resolve(source), {
|
|
790
|
+
recipesDir,
|
|
791
|
+
});
|
|
792
|
+
process.stdout.write(` ✓ ${result.action} ${result.installedPath}\n` +
|
|
793
|
+
` ℹ permissions snippet written to ${result.installedPath}.permissions.json\n` +
|
|
794
|
+
` Review + merge into ~/.claude/settings.json to pre-approve recipe steps.\n`);
|
|
795
|
+
}
|
|
796
|
+
else {
|
|
797
|
+
// Marketplace install: github:, https://, ./local/
|
|
798
|
+
const { runRecipeInstall, printInstallResult } = await import("./commands/recipeInstall.js");
|
|
799
|
+
const result = await runRecipeInstall(source);
|
|
800
|
+
printInstallResult(result);
|
|
801
|
+
}
|
|
802
|
+
process.exit(0);
|
|
803
|
+
}
|
|
804
|
+
catch (err) {
|
|
805
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
806
|
+
process.exit(1);
|
|
807
|
+
}
|
|
808
|
+
})();
|
|
809
|
+
}
|
|
810
|
+
// Patchwork: `patchwork recipe schema [outputDir]` — write generated recipe schemas to disk.
|
|
811
|
+
if (process.argv[2] === "recipe" && process.argv[3] === "schema") {
|
|
812
|
+
const outputDir = process.argv[4] ?? path.join(process.cwd(), "schemas");
|
|
813
|
+
(async () => {
|
|
814
|
+
try {
|
|
815
|
+
const { runSchema } = await import("./commands/recipe.js");
|
|
816
|
+
const result = await runSchema(path.resolve(outputDir));
|
|
817
|
+
process.stdout.write(` ✓ Wrote schemas to ${result.outputDir}\n`);
|
|
818
|
+
for (const file of result.filesWritten) {
|
|
819
|
+
process.stdout.write(` ${file}\n`);
|
|
820
|
+
}
|
|
821
|
+
process.exit(0);
|
|
822
|
+
}
|
|
823
|
+
catch (err) {
|
|
824
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
825
|
+
process.exit(1);
|
|
826
|
+
}
|
|
827
|
+
})();
|
|
828
|
+
}
|
|
829
|
+
// Patchwork: `patchwork recipe new <name>` — scaffold a new recipe from template.
|
|
830
|
+
if (process.argv[2] === "recipe" && process.argv[3] === "new") {
|
|
831
|
+
const args = process.argv.slice(4);
|
|
832
|
+
const recipeName = args[0];
|
|
833
|
+
if (!recipeName) {
|
|
834
|
+
process.stderr.write("Usage: patchwork recipe new <name> [--template <name>] [--desc <description>]\n");
|
|
835
|
+
process.stderr.write("\nTemplates:\n");
|
|
836
|
+
(async () => {
|
|
837
|
+
const { listTemplates } = await import("./commands/recipe.js");
|
|
838
|
+
for (const t of listTemplates()) {
|
|
839
|
+
process.stderr.write(` ${t}\n`);
|
|
840
|
+
}
|
|
841
|
+
process.exit(1);
|
|
842
|
+
})();
|
|
843
|
+
}
|
|
844
|
+
else {
|
|
845
|
+
(async () => {
|
|
846
|
+
try {
|
|
847
|
+
const { runNew } = await import("./commands/recipe.js");
|
|
848
|
+
const templateIdx = args.indexOf("--template");
|
|
849
|
+
const template = templateIdx >= 0 ? args[templateIdx + 1] : undefined;
|
|
850
|
+
const descIdx = args.indexOf("--desc");
|
|
851
|
+
const description = (descIdx >= 0 ? args[descIdx + 1] : undefined) ??
|
|
852
|
+
`Recipe: ${recipeName}`;
|
|
853
|
+
const result = runNew({
|
|
854
|
+
name: recipeName,
|
|
855
|
+
description,
|
|
856
|
+
...(template ? { template } : {}),
|
|
857
|
+
});
|
|
858
|
+
process.stdout.write(` ✓ Created ${result.path}\n`);
|
|
859
|
+
process.exit(0);
|
|
860
|
+
}
|
|
861
|
+
catch (err) {
|
|
862
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
863
|
+
process.exit(1);
|
|
864
|
+
}
|
|
865
|
+
})();
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
// Patchwork: `patchwork recipe lint <file.yaml>` — validate recipe against schema.
|
|
869
|
+
if (process.argv[2] === "recipe" && process.argv[3] === "lint") {
|
|
674
870
|
const file = process.argv[4];
|
|
675
871
|
if (!file) {
|
|
676
|
-
process.stderr.write("Usage: patchwork recipe
|
|
872
|
+
process.stderr.write("Usage: patchwork recipe lint <file.yaml>\n");
|
|
873
|
+
process.exit(1);
|
|
874
|
+
}
|
|
875
|
+
(async () => {
|
|
876
|
+
try {
|
|
877
|
+
const { runLint } = await import("./commands/recipe.js");
|
|
878
|
+
const result = runLint(path.resolve(file));
|
|
879
|
+
for (const issue of result.issues) {
|
|
880
|
+
const prefix = issue.level === "error" ? "✗" : "⚠";
|
|
881
|
+
process.stderr.write(` ${prefix} ${issue.message}\n`);
|
|
882
|
+
}
|
|
883
|
+
if (result.valid) {
|
|
884
|
+
process.stdout.write(` ✓ Valid recipe (${result.warnings} warnings)\n`);
|
|
885
|
+
process.exit(0);
|
|
886
|
+
}
|
|
887
|
+
else {
|
|
888
|
+
process.stdout.write(`\n ${result.errors} error(s), ${result.warnings} warning(s)\n`);
|
|
889
|
+
process.exit(1);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
catch (err) {
|
|
893
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
894
|
+
process.exit(1);
|
|
895
|
+
}
|
|
896
|
+
})();
|
|
897
|
+
}
|
|
898
|
+
// Patchwork: `patchwork recipe preflight <file.yaml>` — static policy check (lint + plan + writes + fixtures).
|
|
899
|
+
if (process.argv[2] === "recipe" && process.argv[3] === "preflight") {
|
|
900
|
+
const args = process.argv.slice(4);
|
|
901
|
+
const usage = "Usage: patchwork recipe preflight <file.yaml> [--json] [--watch] [--require-fixtures] [--no-require-write-ack] [--allow-write <tool-or-ns>]\n";
|
|
902
|
+
let json = false;
|
|
903
|
+
let watchMode = false;
|
|
904
|
+
let requireFixtures = false;
|
|
905
|
+
let requireWriteAck = true;
|
|
906
|
+
const allowWrites = [];
|
|
907
|
+
let file;
|
|
908
|
+
for (let i = 0; i < args.length; i++) {
|
|
909
|
+
const arg = args[i];
|
|
910
|
+
if (arg === undefined)
|
|
911
|
+
continue;
|
|
912
|
+
if (arg === "--json") {
|
|
913
|
+
json = true;
|
|
914
|
+
continue;
|
|
915
|
+
}
|
|
916
|
+
if (arg === "--watch") {
|
|
917
|
+
watchMode = true;
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
if (arg === "--require-fixtures") {
|
|
921
|
+
requireFixtures = true;
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
if (arg === "--no-require-write-ack") {
|
|
925
|
+
requireWriteAck = false;
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
if (arg === "--allow-write" || arg.startsWith("--allow-write=")) {
|
|
929
|
+
const value = arg === "--allow-write"
|
|
930
|
+
? args[++i]
|
|
931
|
+
: arg.slice("--allow-write=".length);
|
|
932
|
+
if (!value) {
|
|
933
|
+
process.stderr.write(`Error: --allow-write requires a value\n${usage}`);
|
|
934
|
+
process.exit(1);
|
|
935
|
+
}
|
|
936
|
+
allowWrites.push(value);
|
|
937
|
+
continue;
|
|
938
|
+
}
|
|
939
|
+
if (!arg.startsWith("--")) {
|
|
940
|
+
file = arg;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
if (!file) {
|
|
944
|
+
process.stderr.write(usage);
|
|
945
|
+
process.exit(1);
|
|
946
|
+
}
|
|
947
|
+
const renderResult = (result) => {
|
|
948
|
+
if (json) {
|
|
949
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
for (const issue of result.issues) {
|
|
953
|
+
const prefix = issue.level === "error" ? "✗" : "⚠";
|
|
954
|
+
const where = issue.stepId ? ` [${issue.stepId}]` : "";
|
|
955
|
+
process.stderr.write(` ${prefix} ${issue.code}${where}: ${issue.message}\n`);
|
|
956
|
+
}
|
|
957
|
+
if (result.ok) {
|
|
958
|
+
process.stdout.write(` ✓ Preflight passed for ${result.recipe} (${result.plan.steps.length} steps)\n`);
|
|
959
|
+
}
|
|
960
|
+
else {
|
|
961
|
+
const errorCount = result.issues.filter((i) => i.level === "error").length;
|
|
962
|
+
process.stdout.write(`\n ${errorCount} error(s) — preflight failed\n`);
|
|
963
|
+
}
|
|
964
|
+
};
|
|
965
|
+
(async () => {
|
|
966
|
+
try {
|
|
967
|
+
const { runPreflight, runPreflightWatch } = await import("./commands/recipe.js");
|
|
968
|
+
const resolvedPath = path.resolve(file);
|
|
969
|
+
if (watchMode) {
|
|
970
|
+
process.stdout.write(` Watching ${resolvedPath} — preflight on save…\n`);
|
|
971
|
+
const stop = runPreflightWatch({
|
|
972
|
+
recipePath: resolvedPath,
|
|
973
|
+
requireWriteAck,
|
|
974
|
+
requireFixtures,
|
|
975
|
+
allowWrites,
|
|
976
|
+
onResult: (result) => renderResult(result),
|
|
977
|
+
onError: (err) => {
|
|
978
|
+
process.stderr.write(`Error: ${err.message}\n`);
|
|
979
|
+
},
|
|
980
|
+
});
|
|
981
|
+
process.on("SIGINT", () => {
|
|
982
|
+
stop();
|
|
983
|
+
process.exit(0);
|
|
984
|
+
});
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
const result = await runPreflight(resolvedPath, {
|
|
988
|
+
requireWriteAck,
|
|
989
|
+
requireFixtures,
|
|
990
|
+
allowWrites,
|
|
991
|
+
});
|
|
992
|
+
renderResult(result);
|
|
993
|
+
process.exit(result.ok ? 0 : 1);
|
|
994
|
+
}
|
|
995
|
+
catch (err) {
|
|
996
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
997
|
+
process.exit(1);
|
|
998
|
+
}
|
|
999
|
+
})();
|
|
1000
|
+
}
|
|
1001
|
+
// Patchwork: `patchwork recipe fmt <file.yaml>` — format/normalize recipe.
|
|
1002
|
+
if (process.argv[2] === "recipe" && process.argv[3] === "fmt") {
|
|
1003
|
+
const args = process.argv.slice(4);
|
|
1004
|
+
const check = args.includes("--check");
|
|
1005
|
+
const watchMode = args.includes("--watch");
|
|
1006
|
+
const file = args.find((arg) => !arg.startsWith("--"));
|
|
1007
|
+
if (!file) {
|
|
1008
|
+
process.stderr.write("Usage: patchwork recipe fmt <file.yaml> [--check] [--watch]\n");
|
|
1009
|
+
process.exit(1);
|
|
1010
|
+
}
|
|
1011
|
+
const renderResult = (result, filePath) => {
|
|
1012
|
+
if (check) {
|
|
1013
|
+
process.stdout.write(result.changed
|
|
1014
|
+
? " ✗ File would be reformatted\n"
|
|
1015
|
+
: " ✓ File is already formatted\n");
|
|
1016
|
+
}
|
|
1017
|
+
else {
|
|
1018
|
+
process.stdout.write(result.changed
|
|
1019
|
+
? ` ✓ Formatted ${filePath}\n`
|
|
1020
|
+
: ` ✓ Already formatted ${filePath}\n`);
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
(async () => {
|
|
1024
|
+
try {
|
|
1025
|
+
const { runFmt, runFmtWatch } = await import("./commands/recipe.js");
|
|
1026
|
+
const resolvedPath = path.resolve(file);
|
|
1027
|
+
if (watchMode) {
|
|
1028
|
+
process.stdout.write(` Watching ${resolvedPath} — fmt on save…\n`);
|
|
1029
|
+
const stop = runFmtWatch({
|
|
1030
|
+
recipePath: resolvedPath,
|
|
1031
|
+
check,
|
|
1032
|
+
onResult: (result) => {
|
|
1033
|
+
process.stdout.write(`\n[${new Date().toLocaleTimeString()}] ${resolvedPath}\n`);
|
|
1034
|
+
renderResult(result, resolvedPath);
|
|
1035
|
+
},
|
|
1036
|
+
onError: (err) => {
|
|
1037
|
+
process.stderr.write(`Error: ${err.message}\n`);
|
|
1038
|
+
},
|
|
1039
|
+
});
|
|
1040
|
+
process.on("SIGINT", () => {
|
|
1041
|
+
stop();
|
|
1042
|
+
process.exit(0);
|
|
1043
|
+
});
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
const result = runFmt(resolvedPath, { check });
|
|
1047
|
+
renderResult(result, file);
|
|
1048
|
+
process.exit(check && result.changed ? 1 : 0);
|
|
1049
|
+
}
|
|
1050
|
+
catch (err) {
|
|
1051
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
1052
|
+
process.exit(1);
|
|
1053
|
+
}
|
|
1054
|
+
})();
|
|
1055
|
+
}
|
|
1056
|
+
// Patchwork: `patchwork recipe record <file.yaml>` — execute live and record connector fixtures.
|
|
1057
|
+
if (process.argv[2] === "recipe" && process.argv[3] === "record") {
|
|
1058
|
+
const args = process.argv.slice(4);
|
|
1059
|
+
const file = args.find((arg) => !arg.startsWith("--"));
|
|
1060
|
+
const fixturesIdx = args.indexOf("--fixtures");
|
|
1061
|
+
const fixturesDir = fixturesIdx >= 0 ? args[fixturesIdx + 1] : undefined;
|
|
1062
|
+
if (!file) {
|
|
1063
|
+
process.stderr.write("Usage: patchwork recipe record <file.yaml> [--fixtures <dir>]\n");
|
|
677
1064
|
process.exit(1);
|
|
678
1065
|
}
|
|
679
1066
|
(async () => {
|
|
680
1067
|
try {
|
|
681
|
-
const {
|
|
682
|
-
const
|
|
683
|
-
|
|
684
|
-
recipesDir,
|
|
1068
|
+
const { runRecord } = await import("./commands/recipe.js");
|
|
1069
|
+
const result = await runRecord(path.resolve(file), {
|
|
1070
|
+
...(fixturesDir ? { fixturesDir: path.resolve(fixturesDir) } : {}),
|
|
685
1071
|
});
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
`
|
|
689
|
-
|
|
1072
|
+
for (const issue of result.issues) {
|
|
1073
|
+
const prefix = issue.level === "error" ? "✗" : "⚠";
|
|
1074
|
+
process.stderr.write(` ${prefix} ${issue.message}\n`);
|
|
1075
|
+
}
|
|
1076
|
+
if (result.recordedFixtures.length > 0) {
|
|
1077
|
+
process.stdout.write(` ℹ Recorded fixture libraries: ${result.recordedFixtures.join(", ")}\n`);
|
|
1078
|
+
}
|
|
1079
|
+
if (result.valid) {
|
|
1080
|
+
process.stdout.write(" ✓ Recipe fixtures recorded\n");
|
|
1081
|
+
process.exit(0);
|
|
1082
|
+
}
|
|
1083
|
+
process.stdout.write(`\n ${result.errors} error(s), ${result.warnings} warning(s)\n`);
|
|
1084
|
+
process.exit(1);
|
|
690
1085
|
}
|
|
691
1086
|
catch (err) {
|
|
692
1087
|
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
@@ -694,6 +1089,153 @@ if (process.argv[2] === "recipe" && process.argv[3] === "install") {
|
|
|
694
1089
|
}
|
|
695
1090
|
})();
|
|
696
1091
|
}
|
|
1092
|
+
// Patchwork: `patchwork recipe test <file.yaml>` — validate fixture coverage for mocked execution.
|
|
1093
|
+
if (process.argv[2] === "recipe" && process.argv[3] === "test") {
|
|
1094
|
+
const args = process.argv.slice(4);
|
|
1095
|
+
const file = args.find((arg) => !arg.startsWith("--"));
|
|
1096
|
+
const fixturesIdx = args.indexOf("--fixtures");
|
|
1097
|
+
const fixturesDir = fixturesIdx >= 0 ? args[fixturesIdx + 1] : undefined;
|
|
1098
|
+
const watchMode = args.includes("--watch");
|
|
1099
|
+
if (!file) {
|
|
1100
|
+
process.stderr.write("Usage: patchwork recipe test <file.yaml> [--fixtures <dir>] [--watch]\n");
|
|
1101
|
+
process.exit(1);
|
|
1102
|
+
}
|
|
1103
|
+
const renderResult = (result) => {
|
|
1104
|
+
for (const issue of result.issues) {
|
|
1105
|
+
const prefix = issue.level === "error" ? "✗" : "⚠";
|
|
1106
|
+
process.stderr.write(` ${prefix} ${issue.message}\n`);
|
|
1107
|
+
}
|
|
1108
|
+
if (result.requiredFixtures.length > 0) {
|
|
1109
|
+
process.stdout.write(` ℹ Required fixtures: ${result.requiredFixtures.join(", ")}\n`);
|
|
1110
|
+
}
|
|
1111
|
+
if (result.valid) {
|
|
1112
|
+
process.stdout.write(" ✓ Test passed\n");
|
|
1113
|
+
}
|
|
1114
|
+
else {
|
|
1115
|
+
process.stdout.write(`\n ${result.errors} error(s), ${result.warnings} warning(s)\n`);
|
|
1116
|
+
}
|
|
1117
|
+
};
|
|
1118
|
+
(async () => {
|
|
1119
|
+
try {
|
|
1120
|
+
const { runTest, runTestWatch } = await import("./commands/recipe.js");
|
|
1121
|
+
const resolvedPath = path.resolve(file);
|
|
1122
|
+
const resolvedFixtures = fixturesDir
|
|
1123
|
+
? path.resolve(fixturesDir)
|
|
1124
|
+
: undefined;
|
|
1125
|
+
if (watchMode) {
|
|
1126
|
+
process.stdout.write(` Watching ${resolvedPath} — test on save…\n`);
|
|
1127
|
+
const stop = runTestWatch({
|
|
1128
|
+
recipePath: resolvedPath,
|
|
1129
|
+
...(resolvedFixtures ? { fixturesDir: resolvedFixtures } : {}),
|
|
1130
|
+
onResult: (result) => {
|
|
1131
|
+
process.stdout.write(`\n[${new Date().toLocaleTimeString()}] ${resolvedPath}\n`);
|
|
1132
|
+
renderResult(result);
|
|
1133
|
+
},
|
|
1134
|
+
onError: (err) => {
|
|
1135
|
+
process.stderr.write(`Error: ${err.message}\n`);
|
|
1136
|
+
},
|
|
1137
|
+
});
|
|
1138
|
+
process.on("SIGINT", () => {
|
|
1139
|
+
stop();
|
|
1140
|
+
process.exit(0);
|
|
1141
|
+
});
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
const result = await runTest(resolvedPath, {
|
|
1145
|
+
...(resolvedFixtures ? { fixturesDir: resolvedFixtures } : {}),
|
|
1146
|
+
});
|
|
1147
|
+
renderResult(result);
|
|
1148
|
+
process.exit(result.valid ? 0 : 1);
|
|
1149
|
+
}
|
|
1150
|
+
catch (err) {
|
|
1151
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
1152
|
+
process.exit(1);
|
|
1153
|
+
}
|
|
1154
|
+
})();
|
|
1155
|
+
}
|
|
1156
|
+
// Patchwork: `patchwork recipe watch <file.yaml>` — watch for changes and validate.
|
|
1157
|
+
if (process.argv[2] === "recipe" && process.argv[3] === "watch") {
|
|
1158
|
+
const file = process.argv[4];
|
|
1159
|
+
if (!file) {
|
|
1160
|
+
process.stderr.write("Usage: patchwork recipe watch <file.yaml>\n");
|
|
1161
|
+
process.exit(1);
|
|
1162
|
+
}
|
|
1163
|
+
(async () => {
|
|
1164
|
+
const { findBridgeLock } = await import("./bridgeLockDiscovery.js");
|
|
1165
|
+
const { runWatch, runLint, runWatchedRecipe, formatRunReport, summarizeRecipeExecution, extractRunLogStepResults, } = await import("./commands/recipe.js");
|
|
1166
|
+
const filePath = path.resolve(file);
|
|
1167
|
+
const lock = findBridgeLock();
|
|
1168
|
+
const workdir = lock?.workspace || process.cwd();
|
|
1169
|
+
const initial = runLint(filePath);
|
|
1170
|
+
if (!initial.valid) {
|
|
1171
|
+
process.stderr.write(" ✗ Recipe has errors - fix before watching\n");
|
|
1172
|
+
for (const issue of initial.issues) {
|
|
1173
|
+
process.stderr.write(` ${issue.level}: ${issue.message}\n`);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
else {
|
|
1177
|
+
process.stdout.write(` ✓ Watching ${file} for changes...\n`);
|
|
1178
|
+
}
|
|
1179
|
+
const stop = runWatch({
|
|
1180
|
+
recipePath: filePath,
|
|
1181
|
+
onChange: async () => {
|
|
1182
|
+
process.stdout.write(`\n Change detected, running...\n`);
|
|
1183
|
+
const watched = await runWatchedRecipe(filePath, { workdir });
|
|
1184
|
+
if (!watched.lint.valid) {
|
|
1185
|
+
process.stderr.write(` ✗ Invalid (${watched.lint.errors} errors)\n`);
|
|
1186
|
+
for (const issue of watched.lint.issues) {
|
|
1187
|
+
process.stderr.write(` ${issue.level}: ${issue.message}\n`);
|
|
1188
|
+
}
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
if (watched.run?.stepSelection) {
|
|
1192
|
+
process.stdout.write(` Selected step via ${watched.run.stepSelection.matchedBy}: ${watched.run.stepSelection.matchedValue}\n`);
|
|
1193
|
+
}
|
|
1194
|
+
if (watched.run) {
|
|
1195
|
+
process.stdout.write(`${formatRunReport(watched.run.result, watched.run.recipe.name)}\n`);
|
|
1196
|
+
const summary = summarizeRecipeExecution(watched.run.result);
|
|
1197
|
+
if (summary.errorMessage) {
|
|
1198
|
+
process.stderr.write(` Error: ${summary.errorMessage}\n`);
|
|
1199
|
+
}
|
|
1200
|
+
// Append to run log
|
|
1201
|
+
try {
|
|
1202
|
+
const { RecipeRunLog } = await import("./runLog.js");
|
|
1203
|
+
const runLog = new RecipeRunLog({
|
|
1204
|
+
dir: path.join(os.homedir(), ".patchwork"),
|
|
1205
|
+
});
|
|
1206
|
+
const now = Date.now();
|
|
1207
|
+
const stepResultsForLog = extractRunLogStepResults(watched.run.result);
|
|
1208
|
+
runLog.appendDirect({
|
|
1209
|
+
taskId: `watch-${now}`,
|
|
1210
|
+
recipeName: watched.run.recipe.name,
|
|
1211
|
+
trigger: "recipe",
|
|
1212
|
+
status: summary.ok ? "done" : "error",
|
|
1213
|
+
createdAt: now,
|
|
1214
|
+
startedAt: now,
|
|
1215
|
+
doneAt: now,
|
|
1216
|
+
durationMs: 0,
|
|
1217
|
+
...(summary.errorMessage
|
|
1218
|
+
? { errorMessage: summary.errorMessage }
|
|
1219
|
+
: {}),
|
|
1220
|
+
...(stepResultsForLog ? { stepResults: stepResultsForLog } : {}),
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
catch {
|
|
1224
|
+
// non-fatal
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
},
|
|
1228
|
+
onError: (err) => {
|
|
1229
|
+
process.stderr.write(` Error: ${err.message}\n`);
|
|
1230
|
+
},
|
|
1231
|
+
});
|
|
1232
|
+
process.on("SIGINT", () => {
|
|
1233
|
+
process.stdout.write("\n Stopping watch...\n");
|
|
1234
|
+
stop();
|
|
1235
|
+
process.exit(0);
|
|
1236
|
+
});
|
|
1237
|
+
})();
|
|
1238
|
+
}
|
|
697
1239
|
if (process.argv[2] === "init") {
|
|
698
1240
|
const argv = process.argv.slice(3);
|
|
699
1241
|
// Handle init --help
|
|
@@ -1383,6 +1925,23 @@ Options:
|
|
|
1383
1925
|
}
|
|
1384
1926
|
process.exit(0);
|
|
1385
1927
|
}
|
|
1928
|
+
// Handle launchd subcommand — install/uninstall macOS LaunchAgent for auto-start
|
|
1929
|
+
if (process.argv[2] === "launchd") {
|
|
1930
|
+
const sub = process.argv[3];
|
|
1931
|
+
if (sub === "install") {
|
|
1932
|
+
const { runLaunchdInstall } = await import("./commands/launchd.js");
|
|
1933
|
+
await runLaunchdInstall(process.argv.slice(4));
|
|
1934
|
+
}
|
|
1935
|
+
else if (sub === "uninstall") {
|
|
1936
|
+
const { runLaunchdUninstall } = await import("./commands/launchd.js");
|
|
1937
|
+
await runLaunchdUninstall(process.argv.slice(4));
|
|
1938
|
+
}
|
|
1939
|
+
else {
|
|
1940
|
+
process.stderr.write("Usage: patchwork-os launchd install|uninstall\n");
|
|
1941
|
+
process.exit(1);
|
|
1942
|
+
}
|
|
1943
|
+
process.exit(0);
|
|
1944
|
+
}
|
|
1386
1945
|
// F6: "Did you mean?" for unknown CLI subcommands
|
|
1387
1946
|
// Patchwork: no-args → terminal dashboard (when invoked as patchwork-os or patchwork).
|
|
1388
1947
|
{
|
|
@@ -1413,6 +1972,7 @@ Options:
|
|
|
1413
1972
|
"shim",
|
|
1414
1973
|
"recipe",
|
|
1415
1974
|
"dashboard",
|
|
1975
|
+
"launchd",
|
|
1416
1976
|
];
|
|
1417
1977
|
const unknownSub = process.argv[2];
|
|
1418
1978
|
if (unknownSub &&
|