patchwork-os 0.2.0-alpha.34 → 0.2.0-alpha.36
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.md +202 -93
- package/deploy/bootstrap-new-vps.sh +12 -12
- package/deploy/bootstrap-vps.sh +6 -3
- package/deploy/deploy-landing.sh +59 -2
- package/dist/activityLog.d.ts +49 -0
- package/dist/activityLog.js +78 -0
- package/dist/activityLog.js.map +1 -1
- package/dist/approvalHttp.d.ts +25 -0
- package/dist/approvalHttp.js +74 -18
- 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 +11 -0
- package/dist/approvalQueue.js +80 -1
- 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 +37 -0
- package/dist/automation.js +105 -61
- 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.js +78 -1
- package/dist/bridge.js.map +1 -1
- package/dist/ccPermissions.d.ts +15 -0
- package/dist/ccPermissions.js +15 -0
- package/dist/ccPermissions.js.map +1 -1
- package/dist/claudeDriver.js +74 -16
- package/dist/claudeDriver.js.map +1 -1
- package/dist/commands/patchworkInit.d.ts +8 -0
- package/dist/commands/patchworkInit.js +41 -5
- package/dist/commands/patchworkInit.js.map +1 -1
- package/dist/commands/recipe.d.ts +20 -0
- package/dist/commands/recipe.js +212 -6
- package/dist/commands/recipe.js.map +1 -1
- package/dist/commands/recipeInstall.d.ts +79 -1
- package/dist/commands/recipeInstall.js +333 -16
- package/dist/commands/recipeInstall.js.map +1 -1
- 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/config.d.ts +8 -0
- package/dist/config.js +9 -1
- package/dist/config.js.map +1 -1
- package/dist/connectorRoutes.d.ts +43 -0
- package/dist/connectorRoutes.js +1023 -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 +36 -0
- package/dist/connectors/baseConnector.js +151 -28
- package/dist/connectors/baseConnector.js.map +1 -1
- 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/github.js +11 -4
- 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.js +50 -10
- package/dist/connectors/gmail.js.map +1 -1
- package/dist/connectors/googleCalendar.js +36 -10
- 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/linear.js +23 -4
- package/dist/connectors/linear.js.map +1 -1
- package/dist/connectors/mcpOAuth.js +26 -2
- package/dist/connectors/mcpOAuth.js.map +1 -1
- 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/slack.d.ts +16 -1
- package/dist/connectors/slack.js +57 -5
- package/dist/connectors/slack.js.map +1 -1
- package/dist/connectors/tokenStorage.js +27 -2
- package/dist/connectors/tokenStorage.js.map +1 -1
- package/dist/connectors/zendesk.js +19 -1
- package/dist/connectors/zendesk.js.map +1 -1
- 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/featureFlags.d.ts +17 -11
- package/dist/featureFlags.js +52 -47
- package/dist/featureFlags.js.map +1 -1
- package/dist/fp/automationInterpreter.js +25 -21
- package/dist/fp/automationInterpreter.js.map +1 -1
- package/dist/fp/automationState.js +4 -1
- package/dist/fp/automationState.js.map +1 -1
- package/dist/fp/policyParser.js +4 -1
- package/dist/fp/policyParser.js.map +1 -1
- package/dist/inboxRoutes.d.ts +22 -0
- package/dist/inboxRoutes.js +114 -0
- package/dist/inboxRoutes.js.map +1 -0
- package/dist/index.js +734 -144
- package/dist/index.js.map +1 -1
- 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 +3 -0
- package/dist/oauth.js +151 -26
- package/dist/oauth.js.map +1 -1
- package/dist/oauthRoutes.d.ts +32 -0
- package/dist/oauthRoutes.js +124 -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 +7 -0
- package/dist/patchworkConfig.js.map +1 -1
- package/dist/pluginLoader.d.ts +12 -0
- package/dist/pluginLoader.js +43 -4
- 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 +23 -0
- package/dist/preToolUseHook.js.map +1 -1
- package/dist/recipeOrchestration.d.ts +8 -0
- package/dist/recipeOrchestration.js +320 -39
- package/dist/recipeOrchestration.js.map +1 -1
- package/dist/recipeRoutes.d.ts +154 -0
- package/dist/recipeRoutes.js +1098 -0
- package/dist/recipeRoutes.js.map +1 -0
- package/dist/recipes/captureForRunlog.d.ts +27 -0
- package/dist/recipes/captureForRunlog.js +128 -0
- package/dist/recipes/captureForRunlog.js.map +1 -0
- package/dist/recipes/chainedRunner.d.ts +54 -3
- package/dist/recipes/chainedRunner.js +256 -36
- package/dist/recipes/chainedRunner.js.map +1 -1
- package/dist/recipes/compiler.js +3 -3
- package/dist/recipes/compiler.js.map +1 -1
- package/dist/recipes/detectSilentFail.d.ts +34 -0
- package/dist/recipes/detectSilentFail.js +105 -0
- package/dist/recipes/detectSilentFail.js.map +1 -0
- package/dist/recipes/installer.js +3 -3
- package/dist/recipes/installer.js.map +1 -1
- package/dist/recipes/manifest.js +21 -6
- package/dist/recipes/manifest.js.map +1 -1
- 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/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.js +102 -11
- package/dist/recipes/scheduler.js.map +1 -1
- package/dist/recipes/schemaGenerator.js +3 -3
- package/dist/recipes/schemaGenerator.js.map +1 -1
- package/dist/recipes/toolRegistry.d.ts +5 -0
- package/dist/recipes/toolRegistry.js +9 -0
- package/dist/recipes/toolRegistry.js.map +1 -1
- 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/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 +6 -0
- package/dist/recipes/tools/file.js +12 -8
- package/dist/recipes/tools/file.js.map +1 -1
- package/dist/recipes/tools/github.js +29 -4
- package/dist/recipes/tools/github.js.map +1 -1
- 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 +1 -1
- package/dist/recipes/tools/gmail.js +230 -6
- package/dist/recipes/tools/gmail.js.map +1 -1
- 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/index.d.ts +8 -0
- package/dist/recipes/tools/index.js +8 -0
- package/dist/recipes/tools/index.js.map +1 -1
- 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 +2 -1
- package/dist/recipes/tools/linear.js +227 -3
- package/dist/recipes/tools/linear.js.map +1 -1
- 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/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.js +15 -5
- package/dist/recipes/tools/slack.js.map +1 -1
- package/dist/recipes/validation.js +83 -14
- package/dist/recipes/validation.js.map +1 -1
- package/dist/recipes/yamlRunner.d.ts +30 -2
- package/dist/recipes/yamlRunner.js +369 -70
- package/dist/recipes/yamlRunner.js.map +1 -1
- package/dist/recipesHttp.d.ts +76 -1
- package/dist/recipesHttp.js +474 -12
- package/dist/recipesHttp.js.map +1 -1
- package/dist/runLog.d.ts +78 -2
- package/dist/runLog.js +204 -6
- 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 +79 -10
- package/dist/server.js +366 -1384
- 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 +126 -17
- package/dist/streamableHttp.js.map +1 -1
- package/dist/tools/getDocumentSymbols.d.ts +24 -0
- package/dist/tools/getDocumentSymbols.js +74 -8
- package/dist/tools/getDocumentSymbols.js.map +1 -1
- package/dist/tools/getSecurityAdvisories.js +10 -1
- package/dist/tools/getSecurityAdvisories.js.map +1 -1
- package/dist/tools/getSessionUsage.d.ts +3 -0
- package/dist/tools/getSessionUsage.js +3 -0
- package/dist/tools/getSessionUsage.js.map +1 -1
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.js +32 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/slackPostMessage.js +1 -1
- package/dist/tools/slackPostMessage.js.map +1 -1
- package/dist/tools/transaction.d.ts +19 -0
- package/dist/tools/transaction.js +29 -0
- package/dist/tools/transaction.js.map +1 -1
- 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 +39 -0
- package/dist/transport.js +88 -8
- package/dist/transport.js.map +1 -1
- package/package.json +22 -5
- 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/project-health-check.yaml +1 -1
- 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
package/dist/index.js
CHANGED
|
@@ -92,6 +92,61 @@ async function downloadVsixFromOpenVsx() {
|
|
|
92
92
|
writeFileSync(tmpPath, Buffer.from(buf));
|
|
93
93
|
return tmpPath;
|
|
94
94
|
}
|
|
95
|
+
// Closes the race where bridge.start() began initialising in parallel with
|
|
96
|
+
// a subcommand's async work — observed in the 2026-04-29 dogfood pass
|
|
97
|
+
// where `recipe install` errors interleaved with bridge "Tools: full"
|
|
98
|
+
// startup logs.
|
|
99
|
+
//
|
|
100
|
+
// Every subcommand `if`-block below dispatches via an `(async () => {...})()`
|
|
101
|
+
// IIFE that ends with `process.exit`. The IIFE invocation returns
|
|
102
|
+
// synchronously, so without this gate, control immediately falls through
|
|
103
|
+
// to the bridge.start() block at end-of-file and starts initialising
|
|
104
|
+
// alongside the subcommand's async work. process.exit fires *eventually*
|
|
105
|
+
// after the await chain, but the bridge has already begun in parallel.
|
|
106
|
+
// Two IIFEs (patchwork no-args dashboard, recipe watch) lack process.exit
|
|
107
|
+
// entirely — without this gate they would run alongside the bridge
|
|
108
|
+
// indefinitely.
|
|
109
|
+
//
|
|
110
|
+
// Single source of truth for "is this argv invoking a subcommand?" — the
|
|
111
|
+
// same list is also used by the unknown-command suggester at L2570.
|
|
112
|
+
const KNOWN_SUBCOMMANDS = [
|
|
113
|
+
"init",
|
|
114
|
+
"patchwork-init",
|
|
115
|
+
"start-all",
|
|
116
|
+
"install-extension",
|
|
117
|
+
"gen-claude-md",
|
|
118
|
+
"print-token",
|
|
119
|
+
"gen-plugin-stub",
|
|
120
|
+
"notify",
|
|
121
|
+
"install",
|
|
122
|
+
"marketplace",
|
|
123
|
+
"status",
|
|
124
|
+
"shim",
|
|
125
|
+
"recipe",
|
|
126
|
+
"traces",
|
|
127
|
+
"suggest",
|
|
128
|
+
"dashboard",
|
|
129
|
+
"launchd",
|
|
130
|
+
];
|
|
131
|
+
const __invokedSubcommand = (() => {
|
|
132
|
+
const sub = process.argv[2];
|
|
133
|
+
if (!sub || sub.startsWith("-"))
|
|
134
|
+
return null;
|
|
135
|
+
// Treat KNOWN_SUBCOMMANDS as the dispatch source. The bare-binary
|
|
136
|
+
// dashboard launcher (no argv) is handled separately below.
|
|
137
|
+
return KNOWN_SUBCOMMANDS.includes(sub)
|
|
138
|
+
? sub
|
|
139
|
+
: null;
|
|
140
|
+
})();
|
|
141
|
+
const __invokedBareBinaryDashboard = (() => {
|
|
142
|
+
if (process.argv[2])
|
|
143
|
+
return false;
|
|
144
|
+
const binName = path.basename(process.argv[1] ?? "");
|
|
145
|
+
return (binName === "patchwork-os" ||
|
|
146
|
+
binName === "patchwork" ||
|
|
147
|
+
binName === "patchwork.js");
|
|
148
|
+
})();
|
|
149
|
+
const __subcommandWillRun = __invokedSubcommand !== null || __invokedBareBinaryDashboard;
|
|
95
150
|
// Handle --version flag — print package version and exit.
|
|
96
151
|
if (process.argv[2] === "--version" || process.argv[2] === "-v") {
|
|
97
152
|
console.log(`claude-ide-bridge ${PACKAGE_VERSION}`);
|
|
@@ -489,14 +544,20 @@ Options:
|
|
|
489
544
|
// Handle gen-plugin-stub subcommand — scaffolds a new plugin directory
|
|
490
545
|
if (process.argv[2] === "gen-plugin-stub") {
|
|
491
546
|
const argv = process.argv.slice(3);
|
|
492
|
-
// Parse args: gen-plugin-stub <dir> [--name <name>] [--prefix <prefix>]
|
|
547
|
+
// Parse args: gen-plugin-stub <dir> [--name <name>] [--prefix <prefix>] [--ts]
|
|
493
548
|
const dirArg = argv.find((a) => !a.startsWith("--"));
|
|
494
549
|
if (!dirArg) {
|
|
495
|
-
process.stderr.write("Usage: claude-ide-bridge gen-plugin-stub <output-dir> [--name <org/plugin-name>] [--prefix <toolPrefix>]\n");
|
|
550
|
+
process.stderr.write("Usage: claude-ide-bridge gen-plugin-stub <output-dir> [--name <org/plugin-name>] [--prefix <toolPrefix>] [--ts]\n");
|
|
496
551
|
process.exit(1);
|
|
497
552
|
}
|
|
498
553
|
const nameIdx = argv.indexOf("--name");
|
|
499
554
|
const prefixIdx = argv.indexOf("--prefix");
|
|
555
|
+
// --ts emits a TypeScript variant (src/index.ts + tsconfig.json + build
|
|
556
|
+
// scripts) alongside a compiled-output manifest pointing at index.mjs.
|
|
557
|
+
// Plugin authors get type-checked tools without changing the hot-reload
|
|
558
|
+
// contract — `npm run dev` watches src/, emits index.mjs, bridge picks
|
|
559
|
+
// up the rebuilt artifact via --plugin-watch.
|
|
560
|
+
const useTypeScript = argv.includes("--ts");
|
|
500
561
|
const pluginName = nameIdx !== -1 && argv[nameIdx + 1]
|
|
501
562
|
? argv[nameIdx + 1]
|
|
502
563
|
: "my-org/my-plugin";
|
|
@@ -530,8 +591,8 @@ if (process.argv[2] === "gen-plugin-stub") {
|
|
|
530
591
|
minBridgeVersion: "2.1.24",
|
|
531
592
|
};
|
|
532
593
|
writeFileSync(path.join(outDir, "claude-ide-bridge-plugin.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf-8");
|
|
533
|
-
//
|
|
534
|
-
const
|
|
594
|
+
// ── shared tool body — same logic, different surface syntax ──
|
|
595
|
+
const jsEntrypoint = `/**
|
|
535
596
|
* ${pluginName} — Claude IDE Bridge plugin
|
|
536
597
|
*
|
|
537
598
|
* Each tool must have a name starting with "${toolPrefix}".
|
|
@@ -539,7 +600,7 @@ if (process.argv[2] === "gen-plugin-stub") {
|
|
|
539
600
|
* ctx.config (commandTimeout, maxResultSize), and ctx.logger.
|
|
540
601
|
*/
|
|
541
602
|
|
|
542
|
-
/** @param {import('
|
|
603
|
+
/** @param {import('patchwork-os/plugin').PluginContext} ctx */
|
|
543
604
|
export function register(ctx) {
|
|
544
605
|
ctx.logger.info(${JSON.stringify(`${pluginName} loaded`)}, { workspace: ctx.workspace });
|
|
545
606
|
|
|
@@ -567,25 +628,216 @@ export function register(ctx) {
|
|
|
567
628
|
};
|
|
568
629
|
}
|
|
569
630
|
`;
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
631
|
+
const tsEntrypoint = `/**
|
|
632
|
+
* ${pluginName} — Claude IDE Bridge plugin
|
|
633
|
+
*
|
|
634
|
+
* Each tool must have a name starting with "${toolPrefix}".
|
|
635
|
+
* The \`ctx\` object provides: ctx.workspace, ctx.workspaceFolders,
|
|
636
|
+
* ctx.config (commandTimeout, maxResultSize), and ctx.logger.
|
|
637
|
+
*/
|
|
638
|
+
import type { PluginContext } from "patchwork-os/plugin";
|
|
639
|
+
|
|
640
|
+
interface HelloArgs {
|
|
641
|
+
name: string;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
export function register(ctx: PluginContext) {
|
|
645
|
+
ctx.logger.info(${JSON.stringify(`${pluginName} loaded`)}, { workspace: ctx.workspace });
|
|
646
|
+
|
|
647
|
+
return {
|
|
648
|
+
tools: [
|
|
649
|
+
{
|
|
650
|
+
schema: {
|
|
651
|
+
name: ${JSON.stringify(`${toolPrefix}Hello`)},
|
|
652
|
+
description: "Example tool — returns a greeting",
|
|
653
|
+
inputSchema: {
|
|
654
|
+
type: "object" as const,
|
|
655
|
+
required: ["name"] as const,
|
|
656
|
+
additionalProperties: false as const,
|
|
657
|
+
properties: {
|
|
658
|
+
name: { type: "string" as const, description: "Name to greet" },
|
|
659
|
+
},
|
|
660
|
+
},
|
|
661
|
+
annotations: { readOnlyHint: true },
|
|
662
|
+
},
|
|
663
|
+
handler: async (args: HelloArgs) => ({
|
|
664
|
+
content: [
|
|
665
|
+
{
|
|
666
|
+
type: "text" as const,
|
|
667
|
+
text: \`Hello from ${pluginName}, \${args.name}!\`,
|
|
668
|
+
},
|
|
669
|
+
],
|
|
670
|
+
}),
|
|
671
|
+
},
|
|
672
|
+
],
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
`;
|
|
676
|
+
// Write entrypoint — TS goes under src/, JS at root.
|
|
677
|
+
if (useTypeScript) {
|
|
678
|
+
mkdirSync(path.join(outDir, "src"), { recursive: true });
|
|
679
|
+
writeFileSync(path.join(outDir, "src", "index.ts"), tsEntrypoint, "utf-8");
|
|
680
|
+
}
|
|
681
|
+
else {
|
|
682
|
+
writeFileSync(path.join(outDir, "index.mjs"), jsEntrypoint, "utf-8");
|
|
683
|
+
}
|
|
684
|
+
// tsconfig.json — TS variant only. Emits a single ESM file at index.mjs
|
|
685
|
+
// so the plugin manifest's entrypoint stays the same shape as the JS
|
|
686
|
+
// scaffold and --plugin-watch reload semantics don't change.
|
|
687
|
+
if (useTypeScript) {
|
|
688
|
+
const tsconfig = {
|
|
689
|
+
compilerOptions: {
|
|
690
|
+
target: "ES2022",
|
|
691
|
+
module: "ES2022",
|
|
692
|
+
moduleResolution: "Bundler",
|
|
693
|
+
outDir: ".",
|
|
694
|
+
rootDir: "src",
|
|
695
|
+
declaration: false,
|
|
696
|
+
strict: true,
|
|
697
|
+
esModuleInterop: true,
|
|
698
|
+
skipLibCheck: true,
|
|
699
|
+
resolveJsonModule: true,
|
|
700
|
+
// Emit .mjs so the plugin loader (which expects ESM) picks it up
|
|
701
|
+
// without relying on package.json "type": "module" alone.
|
|
702
|
+
// tsc doesn't emit .mjs natively, so package.json's "build" script
|
|
703
|
+
// does a rename pass — see below.
|
|
704
|
+
},
|
|
705
|
+
include: ["src/**/*"],
|
|
706
|
+
exclude: ["node_modules"],
|
|
707
|
+
};
|
|
708
|
+
writeFileSync(path.join(outDir, "tsconfig.json"), `${JSON.stringify(tsconfig, null, 2)}\n`, "utf-8");
|
|
709
|
+
}
|
|
710
|
+
// package.json — TS variant adds build + dev (watch) scripts.
|
|
711
|
+
const pkgBase = {
|
|
573
712
|
name: pluginName.replace(/^@[^/]+\//, "").replace(/\//g, "-"),
|
|
574
713
|
version: "0.1.0",
|
|
575
714
|
description: "A Claude IDE Bridge plugin",
|
|
576
715
|
type: "module",
|
|
577
716
|
main: "index.mjs",
|
|
578
|
-
keywords: ["
|
|
579
|
-
peerDependencies: { "
|
|
717
|
+
keywords: ["patchwork-os", "claude-ide-bridge-plugin"],
|
|
718
|
+
peerDependencies: { "patchwork-os": ">=0.2.0-alpha.0" },
|
|
580
719
|
};
|
|
720
|
+
const pkg = useTypeScript
|
|
721
|
+
? {
|
|
722
|
+
...pkgBase,
|
|
723
|
+
scripts: {
|
|
724
|
+
// tsc emits index.js — rename to index.mjs so the loader treats
|
|
725
|
+
// it as ESM regardless of the consumer's package.json.
|
|
726
|
+
build: "tsc && mv index.js index.mjs",
|
|
727
|
+
dev: "tsc --watch",
|
|
728
|
+
clean: "rm -f index.mjs",
|
|
729
|
+
},
|
|
730
|
+
devDependencies: {
|
|
731
|
+
typescript: "^5.4.0",
|
|
732
|
+
"patchwork-os": ">=0.2.0-alpha.0",
|
|
733
|
+
},
|
|
734
|
+
}
|
|
735
|
+
: pkgBase;
|
|
581
736
|
writeFileSync(path.join(outDir, "package.json"), `${JSON.stringify(pkg, null, 2)}\n`, "utf-8");
|
|
737
|
+
// README.md — included in both variants. Spells out the hot-reload
|
|
738
|
+
// contract so plugin authors don't have to read the platform docs to
|
|
739
|
+
// get started.
|
|
740
|
+
const readmeBody = useTypeScript
|
|
741
|
+
? `# ${pluginName}
|
|
742
|
+
|
|
743
|
+
A [Claude IDE Bridge](https://github.com/Oolab-labs/patchwork-os) plugin (TypeScript).
|
|
744
|
+
|
|
745
|
+
## Quick start
|
|
746
|
+
|
|
747
|
+
\`\`\`sh
|
|
748
|
+
npm install
|
|
749
|
+
npm run dev # in one terminal — watches src/, emits index.mjs
|
|
750
|
+
|
|
751
|
+
# In another terminal:
|
|
752
|
+
claude-ide-bridge --plugin . --plugin-watch
|
|
753
|
+
\`\`\`
|
|
754
|
+
|
|
755
|
+
Edit \`src/index.ts\`. \`tsc --watch\` rebuilds, the bridge hot-reloads, your tool is callable from the live Claude session on the next turn.
|
|
756
|
+
|
|
757
|
+
## Build for distribution
|
|
758
|
+
|
|
759
|
+
\`\`\`sh
|
|
760
|
+
npm run build # emits index.mjs
|
|
761
|
+
npm publish # publish to npm (optional)
|
|
762
|
+
\`\`\`
|
|
763
|
+
|
|
764
|
+
When published with the \`claude-ide-bridge-plugin\` keyword, users can install with:
|
|
765
|
+
|
|
766
|
+
\`\`\`sh
|
|
767
|
+
claude-ide-bridge --plugin ${pluginName.replace(/^@[^/]+\//, "")}
|
|
768
|
+
\`\`\`
|
|
769
|
+
|
|
770
|
+
## Tool naming
|
|
771
|
+
|
|
772
|
+
Every tool exposed by this plugin **must** have a \`name\` starting with \`${toolPrefix}\`. The bridge enforces this at load time (\`/^[a-zA-Z][a-zA-Z0-9_]{1,19}$/\`).
|
|
773
|
+
|
|
774
|
+
## Plugin context
|
|
775
|
+
|
|
776
|
+
The \`ctx\` argument to \`register()\` provides:
|
|
777
|
+
|
|
778
|
+
- \`ctx.workspace\` — workspace root path
|
|
779
|
+
- \`ctx.workspaceFolders\` — array of workspace folders
|
|
780
|
+
- \`ctx.config\` — \`{ commandTimeout, maxResultSize }\`
|
|
781
|
+
- \`ctx.logger\` — \`info\` / \`warn\` / \`error\` logging that respects bridge log level
|
|
782
|
+
|
|
783
|
+
## Live toolsmithing
|
|
784
|
+
|
|
785
|
+
The whole point of plugins is that you can author tools *while Claude is using the bridge*. Edit \`src/index.ts\`, save, the watcher rebuilds, the bridge reloads — Claude's next turn sees the new tool.
|
|
786
|
+
|
|
787
|
+
See [documents/live-toolsmithing.md](https://github.com/Oolab-labs/patchwork-os/blob/main/documents/live-toolsmithing.md) for the full narrative.
|
|
788
|
+
`
|
|
789
|
+
: `# ${pluginName}
|
|
790
|
+
|
|
791
|
+
A [Claude IDE Bridge](https://github.com/Oolab-labs/patchwork-os) plugin.
|
|
792
|
+
|
|
793
|
+
## Quick start
|
|
794
|
+
|
|
795
|
+
\`\`\`sh
|
|
796
|
+
claude-ide-bridge --plugin . --plugin-watch
|
|
797
|
+
\`\`\`
|
|
798
|
+
|
|
799
|
+
Edit \`index.mjs\`. The bridge hot-reloads on save — your tool is callable from the live Claude session on the next turn. No build step needed for the JS variant.
|
|
800
|
+
|
|
801
|
+
## Tool naming
|
|
802
|
+
|
|
803
|
+
Every tool exposed by this plugin **must** have a \`name\` starting with \`${toolPrefix}\`. The bridge enforces this at load time (\`/^[a-zA-Z][a-zA-Z0-9_]{1,19}$/\`).
|
|
804
|
+
|
|
805
|
+
## Plugin context
|
|
806
|
+
|
|
807
|
+
The \`ctx\` argument to \`register()\` provides:
|
|
808
|
+
|
|
809
|
+
- \`ctx.workspace\` — workspace root path
|
|
810
|
+
- \`ctx.workspaceFolders\` — array of workspace folders
|
|
811
|
+
- \`ctx.config\` — \`{ commandTimeout, maxResultSize }\`
|
|
812
|
+
- \`ctx.logger\` — \`info\` / \`warn\` / \`error\` logging that respects bridge log level
|
|
813
|
+
|
|
814
|
+
## Want types?
|
|
815
|
+
|
|
816
|
+
Re-scaffold with \`claude-ide-bridge gen-plugin-stub <dir> --ts\` for a TypeScript variant with \`tsc --watch\` build pipeline.
|
|
817
|
+
|
|
818
|
+
## Live toolsmithing
|
|
819
|
+
|
|
820
|
+
Edit, save, hot-reload — Claude's next turn sees the new tool. See [documents/live-toolsmithing.md](https://github.com/Oolab-labs/patchwork-os/blob/main/documents/live-toolsmithing.md) for the full narrative.
|
|
821
|
+
`;
|
|
822
|
+
writeFileSync(path.join(outDir, "README.md"), readmeBody, "utf-8");
|
|
582
823
|
// .gitignore
|
|
583
|
-
|
|
584
|
-
|
|
824
|
+
const gitignore = useTypeScript
|
|
825
|
+
? "node_modules\nindex.mjs\nindex.js\n*.tsbuildinfo\n"
|
|
826
|
+
: "node_modules\n";
|
|
827
|
+
writeFileSync(path.join(outDir, ".gitignore"), gitignore, "utf-8");
|
|
828
|
+
process.stderr.write(`✓ Plugin stub created at ${outDir} (${useTypeScript ? "TypeScript" : "JavaScript"})\n`);
|
|
585
829
|
process.stderr.write("\nNext steps:\n");
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
830
|
+
if (useTypeScript) {
|
|
831
|
+
process.stderr.write(` 1. cd ${outDir} && npm install\n`);
|
|
832
|
+
process.stderr.write(` 2. Edit ${path.join(outDir, "src", "index.ts")} to implement your tools\n`);
|
|
833
|
+
process.stderr.write(` 3. npm run dev (in one terminal)\n`);
|
|
834
|
+
process.stderr.write(` 4. claude-ide-bridge --plugin ${outDir} --plugin-watch (in another)\n`);
|
|
835
|
+
}
|
|
836
|
+
else {
|
|
837
|
+
process.stderr.write(` 1. Edit ${path.join(outDir, "index.mjs")} to implement your tools\n`);
|
|
838
|
+
process.stderr.write(` 2. Run the bridge with: claude-ide-bridge --plugin ${outDir} --plugin-watch\n`);
|
|
839
|
+
process.stderr.write(` 3. Or add to your config: { "plugins": ["${outDir}"] }\n`);
|
|
840
|
+
}
|
|
589
841
|
process.exit(0);
|
|
590
842
|
}
|
|
591
843
|
// Patchwork: `patchwork recipe list` — enumerate installed recipes.
|
|
@@ -597,6 +849,68 @@ if (process.argv[2] === "recipe" && process.argv[3] === "list") {
|
|
|
597
849
|
process.exit(0);
|
|
598
850
|
})();
|
|
599
851
|
}
|
|
852
|
+
// Patchwork: `patchwork recipe enable <name>` / `recipe disable <name>` —
|
|
853
|
+
// flip the disabled marker so scheduled triggers (cron/file-watch) take
|
|
854
|
+
// effect (or stop). Manual `recipe run` is unaffected.
|
|
855
|
+
if (process.argv[2] === "recipe" &&
|
|
856
|
+
(process.argv[3] === "enable" || process.argv[3] === "disable")) {
|
|
857
|
+
const subcommand = process.argv[3];
|
|
858
|
+
const name = process.argv[4];
|
|
859
|
+
if (!name) {
|
|
860
|
+
process.stderr.write(`Usage: patchwork recipe ${subcommand} <name>\n` +
|
|
861
|
+
` See \`patchwork recipe list\` for installed recipe names.\n`);
|
|
862
|
+
process.exit(1);
|
|
863
|
+
}
|
|
864
|
+
(async () => {
|
|
865
|
+
try {
|
|
866
|
+
const { runRecipeEnable, runRecipeDisable } = await import("./commands/recipeInstall.js");
|
|
867
|
+
if (subcommand === "enable") {
|
|
868
|
+
const r = runRecipeEnable(name);
|
|
869
|
+
process.stdout.write(r.alreadyEnabled
|
|
870
|
+
? ` ℹ ${r.name} is already enabled\n`
|
|
871
|
+
: ` ✓ enabled ${r.name}\n`);
|
|
872
|
+
}
|
|
873
|
+
else {
|
|
874
|
+
const r = runRecipeDisable(name);
|
|
875
|
+
process.stdout.write(r.alreadyDisabled
|
|
876
|
+
? ` ℹ ${r.name} is already disabled\n`
|
|
877
|
+
: ` ✓ disabled ${r.name}\n`);
|
|
878
|
+
}
|
|
879
|
+
process.exit(0);
|
|
880
|
+
}
|
|
881
|
+
catch (err) {
|
|
882
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
883
|
+
process.exit(1);
|
|
884
|
+
}
|
|
885
|
+
})();
|
|
886
|
+
}
|
|
887
|
+
// Patchwork: `patchwork recipe uninstall <name>` — remove an installed recipe
|
|
888
|
+
// directory and all its files. Sister to `recipe install`. Idempotent on
|
|
889
|
+
// success (subsequent uninstalls error with "no installed recipe").
|
|
890
|
+
if (process.argv[2] === "recipe" && process.argv[3] === "uninstall") {
|
|
891
|
+
const name = process.argv[4];
|
|
892
|
+
if (!name) {
|
|
893
|
+
process.stderr.write("Usage: patchwork recipe uninstall <name>\n" +
|
|
894
|
+
" See `patchwork recipe list` for installed recipe names.\n");
|
|
895
|
+
process.exit(1);
|
|
896
|
+
}
|
|
897
|
+
(async () => {
|
|
898
|
+
try {
|
|
899
|
+
const { runRecipeUninstall } = await import("./commands/recipeInstall.js");
|
|
900
|
+
const r = runRecipeUninstall(name);
|
|
901
|
+
if (!r.ok) {
|
|
902
|
+
process.stderr.write(`Error: ${r.error}\n`);
|
|
903
|
+
process.exit(1);
|
|
904
|
+
}
|
|
905
|
+
process.stdout.write(` ✓ Uninstalled ${name} (${r.installDir})\n`);
|
|
906
|
+
process.exit(0);
|
|
907
|
+
}
|
|
908
|
+
catch (err) {
|
|
909
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
910
|
+
process.exit(1);
|
|
911
|
+
}
|
|
912
|
+
})();
|
|
913
|
+
}
|
|
600
914
|
// Patchwork: `patchwork recipe run <name>` — runs a recipe locally or via
|
|
601
915
|
// a running bridge's /recipes/run endpoint if one is available.
|
|
602
916
|
if (process.argv[2] === "recipe" && process.argv[3] === "run") {
|
|
@@ -794,9 +1108,15 @@ if (process.argv[2] === "recipe" && process.argv[3] === "install") {
|
|
|
794
1108
|
const result = installRecipeFromFile(path.resolve(source), {
|
|
795
1109
|
recipesDir,
|
|
796
1110
|
});
|
|
1111
|
+
// alpha.36+ — sidecar `<name>.permissions.json` is no longer written
|
|
1112
|
+
// (was decorative, never read by toolRegistry). Print the suggested
|
|
1113
|
+
// permissions snippet inline so users can hand-merge into settings.
|
|
797
1114
|
process.stdout.write(` ✓ ${result.action} ${result.installedPath}\n` +
|
|
798
|
-
` ℹ permissions
|
|
799
|
-
`
|
|
1115
|
+
` ℹ Patchwork does not enforce per-recipe permissions; configure tool gating in ~/.claude/settings.json.\n` +
|
|
1116
|
+
` Suggested permissions snippet:\n${result.permissionsJson
|
|
1117
|
+
.split("\n")
|
|
1118
|
+
.map((l) => ` ${l}`)
|
|
1119
|
+
.join("\n")}\n`);
|
|
800
1120
|
}
|
|
801
1121
|
else {
|
|
802
1122
|
// Marketplace install: github:, https://, ./local/
|
|
@@ -812,7 +1132,264 @@ if (process.argv[2] === "recipe" && process.argv[3] === "install") {
|
|
|
812
1132
|
}
|
|
813
1133
|
})();
|
|
814
1134
|
}
|
|
1135
|
+
// Patchwork: `patchwork suggest [--since <date>]` — pattern-mine the
|
|
1136
|
+
// activity log + run history for "you've been doing X by hand; want to
|
|
1137
|
+
// make a recipe?" hints. See documents/strategic/2026-05-02/memory-
|
|
1138
|
+
// ecosystem-report.md §6 for the catalog this implements.
|
|
1139
|
+
//
|
|
1140
|
+
// Three suggestion kinds: co-occurring tool pairs (worth a recipe), tools
|
|
1141
|
+
// installed but unused (worth reviewing or pruning), and recipes that
|
|
1142
|
+
// always succeed (worth trust-graduating). Read-only — does not change
|
|
1143
|
+
// any policy or registry state.
|
|
1144
|
+
if (process.argv[2] === "suggest") {
|
|
1145
|
+
(async () => {
|
|
1146
|
+
try {
|
|
1147
|
+
const args = process.argv.slice(3);
|
|
1148
|
+
let sinceDays;
|
|
1149
|
+
for (let i = 0; i < args.length; i++) {
|
|
1150
|
+
const a = args[i];
|
|
1151
|
+
if (a === "--since-days") {
|
|
1152
|
+
const next = args[i + 1];
|
|
1153
|
+
if (next)
|
|
1154
|
+
sinceDays = Number.parseInt(next, 10);
|
|
1155
|
+
i++;
|
|
1156
|
+
}
|
|
1157
|
+
else if (a === "--help" || a === "-h") {
|
|
1158
|
+
process.stdout.write("patchwork suggest [--since-days <N>]\n\n" +
|
|
1159
|
+
"Pattern-mine the activity log + recipe runs for automation hints:\n" +
|
|
1160
|
+
" - Co-occurring tool pairs that don't yet appear in any recipe\n" +
|
|
1161
|
+
" - Installed tools that haven't been called recently\n" +
|
|
1162
|
+
" - Recipes that have succeeded ≥ 10 times in a row (trust-graduation candidates)\n\n" +
|
|
1163
|
+
"Default lookback window is 7 days. --since-days overrides.\n\n" +
|
|
1164
|
+
"Read-only — does not modify policy, registry, or run history.\n");
|
|
1165
|
+
process.exit(0);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
const { ActivityLog } = await import("./activityLog.js");
|
|
1169
|
+
const { RecipeRunLog } = await import("./runLog.js");
|
|
1170
|
+
const { computeAutomationSuggestions } = await import("./automationSuggestions.js");
|
|
1171
|
+
// Side-effect import — populates the tool registry that
|
|
1172
|
+
// computeAutomationSuggestions consults for installed-tool inventory.
|
|
1173
|
+
await import("./recipes/tools/index.js");
|
|
1174
|
+
// Wire up the bridge's standard log paths. The CLI reads from
|
|
1175
|
+
// disk; it doesn't need a running bridge.
|
|
1176
|
+
const patchworkDir = path.join(os.homedir(), ".patchwork");
|
|
1177
|
+
const activityLog = new ActivityLog();
|
|
1178
|
+
// Find the most recent activity log file (any port). For the
|
|
1179
|
+
// suggest CLI we union all of them.
|
|
1180
|
+
const claudeIdeDir = path.join(os.homedir(), ".claude", "ide");
|
|
1181
|
+
try {
|
|
1182
|
+
const entries = await import("node:fs").then((m) => m.readdirSync(claudeIdeDir));
|
|
1183
|
+
for (const name of entries) {
|
|
1184
|
+
if (/^activity(-\d+)?\.jsonl$/i.test(name)) {
|
|
1185
|
+
activityLog.setPersistPath(path.join(claudeIdeDir, name));
|
|
1186
|
+
break; // setPersistPath loads on call; first existing wins
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
catch {
|
|
1191
|
+
// No activity dir / files — proceed with an empty log; the
|
|
1192
|
+
// suggestions just return fewer / no items.
|
|
1193
|
+
}
|
|
1194
|
+
const recipeRunLog = new RecipeRunLog({ dir: patchworkDir });
|
|
1195
|
+
const opts = {
|
|
1196
|
+
activityLog,
|
|
1197
|
+
recipeRunLog,
|
|
1198
|
+
};
|
|
1199
|
+
if (sinceDays !== undefined && Number.isFinite(sinceDays)) {
|
|
1200
|
+
opts.activitySinceMs = sinceDays * 24 * 60 * 60 * 1000;
|
|
1201
|
+
}
|
|
1202
|
+
const suggestions = computeAutomationSuggestions(opts);
|
|
1203
|
+
if (suggestions.length === 0) {
|
|
1204
|
+
process.stdout.write("No automation suggestions yet. Patchwork mines patterns from the activity log\n" +
|
|
1205
|
+
"and recipe run history; come back after a few days of use.\n");
|
|
1206
|
+
process.exit(0);
|
|
1207
|
+
}
|
|
1208
|
+
process.stdout.write(`${suggestions.length} suggestion${suggestions.length === 1 ? "" : "s"}:\n\n`);
|
|
1209
|
+
for (const s of suggestions) {
|
|
1210
|
+
const icon = s.kind === "co_occurring_pair"
|
|
1211
|
+
? "→"
|
|
1212
|
+
: s.kind === "installed_but_unused"
|
|
1213
|
+
? "·"
|
|
1214
|
+
: "★";
|
|
1215
|
+
process.stdout.write(` ${icon} ${s.label}\n`);
|
|
1216
|
+
}
|
|
1217
|
+
process.stdout.write("\nRead-only output. Nothing changed.\n");
|
|
1218
|
+
process.exit(0);
|
|
1219
|
+
}
|
|
1220
|
+
catch (err) {
|
|
1221
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
1222
|
+
process.exit(1);
|
|
1223
|
+
}
|
|
1224
|
+
})();
|
|
1225
|
+
}
|
|
815
1226
|
// Patchwork: `patchwork recipe schema [outputDir]` — write generated recipe schemas to disk.
|
|
1227
|
+
// Patchwork: `patchwork traces export [--output <path>]` — bundle the four
|
|
1228
|
+
// local trace logs into a single .jsonl.gz so a user can move machines,
|
|
1229
|
+
// take a compliance snapshot, or share traces with another tool. See
|
|
1230
|
+
// docs/strategic/2026-05-02/memory-ecosystem-report.md items 1, 3, 12 for
|
|
1231
|
+
// the durability rationale this PR addresses.
|
|
1232
|
+
if (process.argv[2] === "traces" && process.argv[3] === "export") {
|
|
1233
|
+
(async () => {
|
|
1234
|
+
try {
|
|
1235
|
+
const args = process.argv.slice(4);
|
|
1236
|
+
let output;
|
|
1237
|
+
let patchworkDir;
|
|
1238
|
+
let activityDir;
|
|
1239
|
+
for (let i = 0; i < args.length; i++) {
|
|
1240
|
+
const a = args[i];
|
|
1241
|
+
if (a === "--output" || a === "-o") {
|
|
1242
|
+
output = args[i + 1];
|
|
1243
|
+
i++;
|
|
1244
|
+
}
|
|
1245
|
+
else if (a === "--patchwork-dir") {
|
|
1246
|
+
patchworkDir = args[i + 1];
|
|
1247
|
+
i++;
|
|
1248
|
+
}
|
|
1249
|
+
else if (a === "--activity-dir") {
|
|
1250
|
+
activityDir = args[i + 1];
|
|
1251
|
+
i++;
|
|
1252
|
+
}
|
|
1253
|
+
else if (a === "--help" || a === "-h") {
|
|
1254
|
+
process.stdout.write("patchwork traces export [--output <path>] [--patchwork-dir <dir>] [--activity-dir <dir>]\n\n" +
|
|
1255
|
+
"Bundles ~/.patchwork/{runs,decision_traces,commit_issue_links}.jsonl\n" +
|
|
1256
|
+
"and ~/.claude/ide/activity-*.jsonl into a single gzipped JSONL file.\n\n" +
|
|
1257
|
+
"Output is a manifest line followed by one envelope per row:\n" +
|
|
1258
|
+
' {"type":"manifest", ...}\n' +
|
|
1259
|
+
' {"source":"runs", "entry":{...}}\n' +
|
|
1260
|
+
" ...\n\n" +
|
|
1261
|
+
"Filter one source with:\n" +
|
|
1262
|
+
" gunzip -c traces-export-*.jsonl.gz | jq 'select(.source==\"decision_traces\") | .entry'\n");
|
|
1263
|
+
process.exit(0);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
const { runTracesExport } = await import("./commands/tracesExport.js");
|
|
1267
|
+
const result = await runTracesExport({
|
|
1268
|
+
...(output !== undefined && { output }),
|
|
1269
|
+
...(patchworkDir !== undefined && { patchworkDir }),
|
|
1270
|
+
...(activityDir !== undefined && { activityDir }),
|
|
1271
|
+
});
|
|
1272
|
+
process.stdout.write(` ✓ Wrote ${result.outputPath}\n`);
|
|
1273
|
+
process.stdout.write(` ${result.totalCount} rows from ${result.files.length} file${result.files.length === 1 ? "" : "s"} (${result.totalBytes} bytes read)\n`);
|
|
1274
|
+
for (const f of result.files) {
|
|
1275
|
+
process.stdout.write(` - ${f.source}: ${f.count} rows (${f.path})\n`);
|
|
1276
|
+
}
|
|
1277
|
+
process.exit(0);
|
|
1278
|
+
}
|
|
1279
|
+
catch (err) {
|
|
1280
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
1281
|
+
process.exit(1);
|
|
1282
|
+
}
|
|
1283
|
+
})();
|
|
1284
|
+
}
|
|
1285
|
+
// Patchwork: `patchwork traces import <bundle>` — restore an export bundle
|
|
1286
|
+
// into the local patchwork dirs. Closes the half-shipped backup loop.
|
|
1287
|
+
if (process.argv[2] === "traces" && process.argv[3] === "import") {
|
|
1288
|
+
(async () => {
|
|
1289
|
+
try {
|
|
1290
|
+
const args = process.argv.slice(4);
|
|
1291
|
+
let input;
|
|
1292
|
+
let patchworkDir;
|
|
1293
|
+
let activityDir;
|
|
1294
|
+
let mode = "append";
|
|
1295
|
+
let dryRun = false;
|
|
1296
|
+
let passphrase;
|
|
1297
|
+
for (let i = 0; i < args.length; i++) {
|
|
1298
|
+
const a = args[i];
|
|
1299
|
+
if (a === "--patchwork-dir") {
|
|
1300
|
+
patchworkDir = args[i + 1];
|
|
1301
|
+
i++;
|
|
1302
|
+
}
|
|
1303
|
+
else if (a === "--activity-dir") {
|
|
1304
|
+
activityDir = args[i + 1];
|
|
1305
|
+
i++;
|
|
1306
|
+
}
|
|
1307
|
+
else if (a === "--mode") {
|
|
1308
|
+
const m = args[i + 1];
|
|
1309
|
+
if (m !== "append" && m !== "overwrite") {
|
|
1310
|
+
process.stderr.write(`Error: --mode must be "append" or "overwrite" (got: ${m})\n`);
|
|
1311
|
+
process.exit(1);
|
|
1312
|
+
}
|
|
1313
|
+
mode = m;
|
|
1314
|
+
i++;
|
|
1315
|
+
}
|
|
1316
|
+
else if (a === "--passphrase") {
|
|
1317
|
+
passphrase = args[i + 1];
|
|
1318
|
+
i++;
|
|
1319
|
+
}
|
|
1320
|
+
else if (a === "--dry-run") {
|
|
1321
|
+
dryRun = true;
|
|
1322
|
+
}
|
|
1323
|
+
else if (a === "--help" || a === "-h") {
|
|
1324
|
+
process.stdout.write("patchwork traces import <bundle> [--mode append|overwrite] [--dry-run]\n" +
|
|
1325
|
+
" [--passphrase <phrase>]\n" +
|
|
1326
|
+
" [--patchwork-dir <dir>] [--activity-dir <dir>]\n\n" +
|
|
1327
|
+
"Restore a bundle written by `patchwork traces export` into the local\n" +
|
|
1328
|
+
"patchwork dirs (~/.patchwork/ and ~/.claude/ide/ by default).\n\n" +
|
|
1329
|
+
"Formats:\n" +
|
|
1330
|
+
" .jsonl.gz Plain gzip bundle — no passphrase required.\n" +
|
|
1331
|
+
" .enc AES-256-GCM encrypted bundle — pass --passphrase.\n\n" +
|
|
1332
|
+
"Modes:\n" +
|
|
1333
|
+
" append (default) Append rows to existing files.\n" +
|
|
1334
|
+
" overwrite Truncate target files before writing. Use for fresh-machine\n" +
|
|
1335
|
+
" restore; never use when there's local data you want to keep.\n");
|
|
1336
|
+
process.exit(0);
|
|
1337
|
+
}
|
|
1338
|
+
else if (a !== undefined &&
|
|
1339
|
+
!a.startsWith("--") &&
|
|
1340
|
+
input === undefined) {
|
|
1341
|
+
input = a;
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
if (!input) {
|
|
1345
|
+
process.stderr.write("Usage: patchwork traces import <bundle> [--passphrase <phrase>] [--mode append|overwrite] [--dry-run]\n");
|
|
1346
|
+
process.exit(1);
|
|
1347
|
+
}
|
|
1348
|
+
// Auto-detect encrypted bundle and decrypt before import.
|
|
1349
|
+
if (passphrase !== undefined || input.endsWith(".enc")) {
|
|
1350
|
+
const { readFileSync } = await import("node:fs");
|
|
1351
|
+
const { isEncryptedTraceBundle, decryptTraceBundle } = await import("./traceEncryption.js");
|
|
1352
|
+
const raw = readFileSync(input);
|
|
1353
|
+
if (isEncryptedTraceBundle(raw)) {
|
|
1354
|
+
if (!passphrase) {
|
|
1355
|
+
process.stderr.write("Error: bundle is encrypted — provide --passphrase <phrase>\n");
|
|
1356
|
+
process.exit(1);
|
|
1357
|
+
}
|
|
1358
|
+
const plain = decryptTraceBundle(raw, passphrase);
|
|
1359
|
+
const { tmpdir } = await import("node:os");
|
|
1360
|
+
const { writeFileSync } = await import("node:fs");
|
|
1361
|
+
const tmp = `${tmpdir()}/patchwork-import-${Date.now()}.jsonl.gz`;
|
|
1362
|
+
writeFileSync(tmp, plain, { mode: 0o600 });
|
|
1363
|
+
input = tmp;
|
|
1364
|
+
process.stderr.write("Decryption succeeded.\n");
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
const { runTracesImport } = await import("./commands/tracesImport.js");
|
|
1368
|
+
const result = await runTracesImport({
|
|
1369
|
+
input,
|
|
1370
|
+
...(patchworkDir !== undefined && { patchworkDir }),
|
|
1371
|
+
...(activityDir !== undefined && { activityDir }),
|
|
1372
|
+
mode,
|
|
1373
|
+
dryRun,
|
|
1374
|
+
});
|
|
1375
|
+
const verb = result.dryRun
|
|
1376
|
+
? "Would restore"
|
|
1377
|
+
: result.mode === "overwrite"
|
|
1378
|
+
? "Restored (overwrite)"
|
|
1379
|
+
: "Restored (append)";
|
|
1380
|
+
process.stdout.write(` ${result.dryRun ? "•" : "✓"} ${verb} ${result.totalCount} rows from ${result.inputPath}\n`);
|
|
1381
|
+
process.stdout.write(` Bundle exportedAt: ${result.exportedAt}\n`);
|
|
1382
|
+
for (const f of result.files) {
|
|
1383
|
+
process.stdout.write(` - ${f.source}: ${f.count} rows → ${f.targetPath}\n`);
|
|
1384
|
+
}
|
|
1385
|
+
process.exit(0);
|
|
1386
|
+
}
|
|
1387
|
+
catch (err) {
|
|
1388
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
1389
|
+
process.exit(1);
|
|
1390
|
+
}
|
|
1391
|
+
})();
|
|
1392
|
+
}
|
|
816
1393
|
if (process.argv[2] === "recipe" && process.argv[3] === "schema") {
|
|
817
1394
|
const outputDir = process.argv[4] ?? path.join(process.cwd(), "schemas");
|
|
818
1395
|
(async () => {
|
|
@@ -836,7 +1413,10 @@ if (process.argv[2] === "recipe" && process.argv[3] === "new") {
|
|
|
836
1413
|
const args = process.argv.slice(4);
|
|
837
1414
|
const recipeName = args[0];
|
|
838
1415
|
if (!recipeName) {
|
|
839
|
-
process.stderr.write("Usage: patchwork recipe new <name> [--template <name>] [--desc <description>]\n"
|
|
1416
|
+
process.stderr.write("Usage: patchwork recipe new <name> [--template <name>] [--desc <description>] [--out <dir>]\n" +
|
|
1417
|
+
" --out <dir> Write the recipe to <dir>/<name>.yaml.\n" +
|
|
1418
|
+
" Defaults to ~/.patchwork/recipes/ — pass `--out .` to\n" +
|
|
1419
|
+
" write into the current directory instead.\n");
|
|
840
1420
|
process.stderr.write("\nTemplates:\n");
|
|
841
1421
|
(async () => {
|
|
842
1422
|
const { listTemplates } = await import("./commands/recipe.js");
|
|
@@ -855,10 +1435,16 @@ if (process.argv[2] === "recipe" && process.argv[3] === "new") {
|
|
|
855
1435
|
const descIdx = args.indexOf("--desc");
|
|
856
1436
|
const description = (descIdx >= 0 ? args[descIdx + 1] : undefined) ??
|
|
857
1437
|
`Recipe: ${recipeName}`;
|
|
1438
|
+
const outIdx = args.indexOf("--out");
|
|
1439
|
+
const outRaw = outIdx >= 0 ? args[outIdx + 1] : undefined;
|
|
1440
|
+
// `--out .` is the common case for "scaffold in cwd" — resolve so
|
|
1441
|
+
// the success message shows the absolute path the user can open.
|
|
1442
|
+
const outputDir = outRaw ? path.resolve(outRaw) : undefined;
|
|
858
1443
|
const result = runNew({
|
|
859
1444
|
name: recipeName,
|
|
860
1445
|
description,
|
|
861
1446
|
...(template ? { template } : {}),
|
|
1447
|
+
...(outputDir ? { outputDir } : {}),
|
|
862
1448
|
});
|
|
863
1449
|
process.stdout.write(` ✓ Created ${result.path}\n`);
|
|
864
1450
|
process.exit(0);
|
|
@@ -1962,27 +2548,12 @@ if (process.argv[2] === "launchd") {
|
|
|
1962
2548
|
}
|
|
1963
2549
|
}
|
|
1964
2550
|
{
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
"patchwork-init",
|
|
1968
|
-
"start-all",
|
|
1969
|
-
"install-extension",
|
|
1970
|
-
"gen-claude-md",
|
|
1971
|
-
"print-token",
|
|
1972
|
-
"gen-plugin-stub",
|
|
1973
|
-
"notify",
|
|
1974
|
-
"install",
|
|
1975
|
-
"marketplace",
|
|
1976
|
-
"status",
|
|
1977
|
-
"shim",
|
|
1978
|
-
"recipe",
|
|
1979
|
-
"dashboard",
|
|
1980
|
-
"launchd",
|
|
1981
|
-
];
|
|
2551
|
+
// Reuses the KNOWN_SUBCOMMANDS list from the top of this file as a single
|
|
2552
|
+
// source of truth for "what subcommand argv tokens are recognized".
|
|
1982
2553
|
const unknownSub = process.argv[2];
|
|
1983
2554
|
if (unknownSub &&
|
|
1984
2555
|
!unknownSub.startsWith("-") &&
|
|
1985
|
-
!
|
|
2556
|
+
!KNOWN_SUBCOMMANDS.includes(unknownSub)) {
|
|
1986
2557
|
const lev = (a, b) => {
|
|
1987
2558
|
const dp = Array.from({ length: a.length + 1 }, (_, i) => Array.from({ length: b.length + 1 }, (_, j) => i === 0 ? j : j === 0 ? i : 0));
|
|
1988
2559
|
for (let i = 1; i <= a.length; i++)
|
|
@@ -1998,127 +2569,146 @@ if (process.argv[2] === "launchd") {
|
|
|
1998
2569
|
// biome-ignore lint/style/noNonNullAssertion: dp is fully pre-allocated
|
|
1999
2570
|
return dp[a.length][b.length];
|
|
2000
2571
|
};
|
|
2001
|
-
const closest = [...
|
|
2572
|
+
const closest = [...KNOWN_SUBCOMMANDS].sort((a, b) => lev(unknownSub, a) - lev(unknownSub, b))[0];
|
|
2002
2573
|
console.error(`Unknown command: '${unknownSub}'. Did you mean: ${closest}?`);
|
|
2003
2574
|
process.exit(1);
|
|
2004
2575
|
}
|
|
2005
2576
|
}
|
|
2006
|
-
|
|
2007
|
-
//
|
|
2008
|
-
//
|
|
2009
|
-
//
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
resolved.adapter;
|
|
2016
|
-
process.stderr.write(`[patchwork] model adapter initialized: ${resolved.adapter.name}\n`);
|
|
2017
|
-
}
|
|
2018
|
-
}
|
|
2019
|
-
catch (err) {
|
|
2020
|
-
process.stderr.write(`[patchwork] adapter init failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
2577
|
+
// Skip the bridge-mode tail entirely when a subcommand IIFE will own the
|
|
2578
|
+
// process. `parseConfig` validates argv against the bridge's known-flag list
|
|
2579
|
+
// and raises "Unknown option" for subcommand-specific flags (e.g. `recipe
|
|
2580
|
+
// new --out .`); without this guard that throw kills the process before
|
|
2581
|
+
// the IIFE's microtask runs. The subcommand handles its own arg parsing.
|
|
2582
|
+
if (__subcommandWillRun) {
|
|
2583
|
+
// Subcommand IIFE is in flight or about to fire; sit tight until it
|
|
2584
|
+
// process.exits. Empty body — control naturally falls past end-of-file
|
|
2585
|
+
// and Node keeps the process alive on the IIFE's pending microtask.
|
|
2021
2586
|
}
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
//
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
.createHash("sha256")
|
|
2035
|
-
.update(config.workspace)
|
|
2036
|
-
.digest("hex")
|
|
2037
|
-
.slice(0, 6);
|
|
2038
|
-
const sessionName = `claude-bridge-${ws}${hash}`;
|
|
2039
|
-
// Check if tmux is available
|
|
2040
|
-
const tmuxCheck = spawnSync("which", ["tmux"], { stdio: "ignore" });
|
|
2041
|
-
if (tmuxCheck.status !== 0) {
|
|
2042
|
-
process.stderr.write("WARNING: --auto-tmux requested but tmux is not installed. Running without tmux.\n");
|
|
2043
|
-
}
|
|
2044
|
-
else {
|
|
2045
|
-
// Strip --auto-tmux from argv to avoid infinite re-exec loop
|
|
2046
|
-
const newArgv = process.argv.filter((a) => a !== "--auto-tmux");
|
|
2047
|
-
// Pass each argv token as a separate tmux argument so paths with spaces work correctly
|
|
2048
|
-
const result = spawnSync("tmux", ["new-session", "-d", "-s", sessionName, ...newArgv], { stdio: "inherit", timeout: 5000 });
|
|
2049
|
-
if (result.status === 0) {
|
|
2050
|
-
process.stderr.write(`Bridge launched in tmux session '${sessionName}'.\n`);
|
|
2051
|
-
process.stderr.write(` Attach with: tmux attach -t ${sessionName}\n`);
|
|
2052
|
-
process.exit(0);
|
|
2053
|
-
}
|
|
2054
|
-
else {
|
|
2055
|
-
// tmux session likely already exists — attach to it or fall through
|
|
2056
|
-
process.stderr.write(`WARNING: Could not create tmux session '${sessionName}' (already exists?). Running without auto-tmux.\n`);
|
|
2587
|
+
else {
|
|
2588
|
+
const config = parseConfig(process.argv);
|
|
2589
|
+
// Patchwork: resolve --model flag (optional, non-invasive) — stashes the
|
|
2590
|
+
// configured adapter on globalThis for consumers that opt into the adapter
|
|
2591
|
+
// layer. Bridge subprocess driver still works when --model is absent.
|
|
2592
|
+
try {
|
|
2593
|
+
const { resolveModel } = await import("./patchworkCli.js");
|
|
2594
|
+
const resolved = resolveModel(process.argv);
|
|
2595
|
+
if (resolved) {
|
|
2596
|
+
globalThis.__patchworkAdapter =
|
|
2597
|
+
resolved.adapter;
|
|
2598
|
+
process.stderr.write(`[patchwork] model adapter initialized: ${resolved.adapter.name}\n`);
|
|
2057
2599
|
}
|
|
2058
2600
|
}
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
const
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
}
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
});
|
|
2601
|
+
catch (err) {
|
|
2602
|
+
process.stderr.write(`[patchwork] adapter init failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
2603
|
+
}
|
|
2604
|
+
// If --analytics flag was passed, persist the preference immediately
|
|
2605
|
+
if (config.analyticsEnabled !== null) {
|
|
2606
|
+
setAnalyticsPref(config.analyticsEnabled);
|
|
2607
|
+
}
|
|
2608
|
+
// Auto-tmux: if requested and not already inside tmux or screen, re-exec inside a tmux session
|
|
2609
|
+
if (config.autoTmux &&
|
|
2610
|
+
!process.env.TMUX &&
|
|
2611
|
+
!process.env.STY &&
|
|
2612
|
+
!process.env.ZELLIJ &&
|
|
2613
|
+
!process.env.ZELLIJ_SESSION_NAME) {
|
|
2614
|
+
const ws = config.workspace.replace(/[^a-zA-Z0-9]/g, "").slice(-8);
|
|
2615
|
+
const hash = crypto
|
|
2616
|
+
.createHash("sha256")
|
|
2617
|
+
.update(config.workspace)
|
|
2618
|
+
.digest("hex")
|
|
2619
|
+
.slice(0, 6);
|
|
2620
|
+
const sessionName = `claude-bridge-${ws}${hash}`;
|
|
2621
|
+
// Check if tmux is available
|
|
2622
|
+
const tmuxCheck = spawnSync("which", ["tmux"], { stdio: "ignore" });
|
|
2623
|
+
if (tmuxCheck.status !== 0) {
|
|
2624
|
+
process.stderr.write("WARNING: --auto-tmux requested but tmux is not installed. Running without tmux.\n");
|
|
2084
2625
|
}
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2626
|
+
else {
|
|
2627
|
+
// Strip --auto-tmux from argv to avoid infinite re-exec loop
|
|
2628
|
+
const newArgv = process.argv.filter((a) => a !== "--auto-tmux");
|
|
2629
|
+
// Pass each argv token as a separate tmux argument so paths with spaces work correctly
|
|
2630
|
+
const result = spawnSync("tmux", ["new-session", "-d", "-s", sessionName, ...newArgv], { stdio: "inherit", timeout: 5000 });
|
|
2631
|
+
if (result.status === 0) {
|
|
2632
|
+
process.stderr.write(`Bridge launched in tmux session '${sessionName}'.\n`);
|
|
2633
|
+
process.stderr.write(` Attach with: tmux attach -t ${sessionName}\n`);
|
|
2088
2634
|
process.exit(0);
|
|
2089
2635
|
}
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2636
|
+
else {
|
|
2637
|
+
// tmux session likely already exists — attach to it or fall through
|
|
2638
|
+
process.stderr.write(`WARNING: Could not create tmux session '${sessionName}' (already exists?). Running without auto-tmux.\n`);
|
|
2093
2639
|
}
|
|
2094
|
-
|
|
2095
|
-
setTimeout(() => {
|
|
2096
|
-
delay = Math.min(delay * 2, MAX_DELAY_MS);
|
|
2097
|
-
runChild();
|
|
2098
|
-
}, delay);
|
|
2099
|
-
});
|
|
2640
|
+
}
|
|
2100
2641
|
}
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2642
|
+
// Skip bridge boot when a subcommand IIFE is doing the work — avoids the
|
|
2643
|
+
// race where bridge.start() began initialising in parallel with the
|
|
2644
|
+
// subcommand's async path. See the KNOWN_SUBCOMMANDS / __subcommandWillRun
|
|
2645
|
+
// gate at the top of this file.
|
|
2646
|
+
if (__subcommandWillRun) {
|
|
2647
|
+
// intentionally empty — subcommand IIFE owns the process from here.
|
|
2648
|
+
}
|
|
2649
|
+
// --watch: supervisor mode — spawn this binary as a child (without --watch) and restart on crash
|
|
2650
|
+
else if (config.watch) {
|
|
2651
|
+
const childArgv = process.argv.filter((a) => a !== "--watch");
|
|
2652
|
+
const STABLE_THRESHOLD_MS = 60_000;
|
|
2653
|
+
const BASE_DELAY_MS = 2_000;
|
|
2654
|
+
const MAX_DELAY_MS = 30_000;
|
|
2655
|
+
let delay = BASE_DELAY_MS;
|
|
2656
|
+
let stopping = false;
|
|
2657
|
+
function runChild() {
|
|
2658
|
+
if (stopping)
|
|
2115
2659
|
return;
|
|
2116
|
-
const
|
|
2117
|
-
|
|
2118
|
-
|
|
2660
|
+
const startAt = Date.now();
|
|
2661
|
+
process.stderr.write("[supervisor] starting bridge\n");
|
|
2662
|
+
const [cmd, ...args] = childArgv;
|
|
2663
|
+
if (!cmd)
|
|
2664
|
+
return;
|
|
2665
|
+
const child = spawn(cmd, args, {
|
|
2666
|
+
stdio: "inherit",
|
|
2667
|
+
});
|
|
2668
|
+
for (const sig of ["SIGTERM", "SIGINT"]) {
|
|
2669
|
+
process.once(sig, () => {
|
|
2670
|
+
stopping = true;
|
|
2671
|
+
child.kill(sig);
|
|
2672
|
+
});
|
|
2119
2673
|
}
|
|
2674
|
+
child.on("exit", (code, signal) => {
|
|
2675
|
+
if (stopping) {
|
|
2676
|
+
process.stderr.write("[supervisor] bridge stopped\n");
|
|
2677
|
+
process.exit(0);
|
|
2678
|
+
}
|
|
2679
|
+
const uptime = Date.now() - startAt;
|
|
2680
|
+
if (uptime >= STABLE_THRESHOLD_MS) {
|
|
2681
|
+
delay = BASE_DELAY_MS; // reset backoff after a stable run
|
|
2682
|
+
}
|
|
2683
|
+
process.stderr.write(`[supervisor] bridge exited (code=${code ?? signal}), restarting in ${delay / 1000}s\n`);
|
|
2684
|
+
setTimeout(() => {
|
|
2685
|
+
delay = Math.min(delay * 2, MAX_DELAY_MS);
|
|
2686
|
+
runChild();
|
|
2687
|
+
}, delay);
|
|
2688
|
+
});
|
|
2689
|
+
}
|
|
2690
|
+
runChild();
|
|
2691
|
+
}
|
|
2692
|
+
else {
|
|
2693
|
+
const bridge = new Bridge(config);
|
|
2694
|
+
bridge.start().catch((err) => {
|
|
2695
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2696
|
+
process.stderr.write(`Error: ${message}\n`);
|
|
2697
|
+
process.exit(1);
|
|
2120
2698
|
});
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
}
|
|
2699
|
+
// F5: Silent self-update nudge (fire-and-forget)
|
|
2700
|
+
import("node:child_process")
|
|
2701
|
+
.then(({ exec }) => {
|
|
2702
|
+
exec("npm view claude-ide-bridge version", { timeout: 5000 }, (err, stdout) => {
|
|
2703
|
+
if (err || !stdout)
|
|
2704
|
+
return;
|
|
2705
|
+
const latest = stdout.trim();
|
|
2706
|
+
if (latest && semverGt(latest, PACKAGE_VERSION)) {
|
|
2707
|
+
console.log(`\n Bridge v${latest} available — run: npm update -g claude-ide-bridge\n`);
|
|
2708
|
+
}
|
|
2709
|
+
});
|
|
2710
|
+
})
|
|
2711
|
+
.catch(() => { });
|
|
2712
|
+
}
|
|
2713
|
+
} // end of `else` for `if (__subcommandWillRun)` (bridge-mode block)
|
|
2124
2714
|
//# sourceMappingURL=index.js.map
|