patchwork-os 0.2.0-alpha.35 → 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 +70 -15
- 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 +46 -0
- 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 +194 -5
- package/dist/commands/recipe.js.map +1 -1
- package/dist/commands/recipeInstall.js +93 -4
- 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.js +6 -7
- package/dist/connectors/asana.js.map +1 -1
- package/dist/connectors/baseConnector.d.ts +20 -0
- package/dist/connectors/baseConnector.js +45 -4
- package/dist/connectors/baseConnector.js.map +1 -1
- package/dist/connectors/discord.js +6 -7
- package/dist/connectors/discord.js.map +1 -1
- package/dist/connectors/gmail.js +39 -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.js +22 -6
- package/dist/connectors/googleDrive.js.map +1 -1
- package/dist/connectors/linear.js +2 -2
- 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/slack.d.ts +15 -0
- package/dist/connectors/slack.js +54 -4
- 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/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 +479 -17
- 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 +1 -0
- package/dist/recipeOrchestration.js +173 -13
- 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/chainedRunner.d.ts +15 -0
- package/dist/recipes/chainedRunner.js +73 -8
- 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/installer.js +3 -3
- package/dist/recipes/installer.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/resolveRecipePath.d.ts +69 -0
- package/dist/recipes/resolveRecipePath.js +202 -0
- package/dist/recipes/resolveRecipePath.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/index.d.ts +2 -0
- package/dist/recipes/tools/index.js +2 -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.js +6 -3
- package/dist/recipes/tools/linear.js.map +1 -1
- 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 +7 -3
- 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 +7 -0
- package/dist/recipes/yamlRunner.js +107 -13
- package/dist/recipes/yamlRunner.js.map +1 -1
- package/dist/recipesHttp.d.ts +44 -1
- package/dist/recipesHttp.js +168 -15
- package/dist/recipesHttp.js.map +1 -1
- package/dist/runLog.d.ts +14 -0
- package/dist/runLog.js +88 -4
- 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 +71 -10
- package/dist/server.js +363 -1703
- 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 +8 -0
- package/dist/streamableHttp.js +112 -21
- 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/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 +4 -2
- 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/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
|
@@ -123,6 +123,8 @@ const KNOWN_SUBCOMMANDS = [
|
|
|
123
123
|
"status",
|
|
124
124
|
"shim",
|
|
125
125
|
"recipe",
|
|
126
|
+
"traces",
|
|
127
|
+
"suggest",
|
|
126
128
|
"dashboard",
|
|
127
129
|
"launchd",
|
|
128
130
|
];
|
|
@@ -542,14 +544,20 @@ Options:
|
|
|
542
544
|
// Handle gen-plugin-stub subcommand — scaffolds a new plugin directory
|
|
543
545
|
if (process.argv[2] === "gen-plugin-stub") {
|
|
544
546
|
const argv = process.argv.slice(3);
|
|
545
|
-
// Parse args: gen-plugin-stub <dir> [--name <name>] [--prefix <prefix>]
|
|
547
|
+
// Parse args: gen-plugin-stub <dir> [--name <name>] [--prefix <prefix>] [--ts]
|
|
546
548
|
const dirArg = argv.find((a) => !a.startsWith("--"));
|
|
547
549
|
if (!dirArg) {
|
|
548
|
-
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");
|
|
549
551
|
process.exit(1);
|
|
550
552
|
}
|
|
551
553
|
const nameIdx = argv.indexOf("--name");
|
|
552
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");
|
|
553
561
|
const pluginName = nameIdx !== -1 && argv[nameIdx + 1]
|
|
554
562
|
? argv[nameIdx + 1]
|
|
555
563
|
: "my-org/my-plugin";
|
|
@@ -583,8 +591,8 @@ if (process.argv[2] === "gen-plugin-stub") {
|
|
|
583
591
|
minBridgeVersion: "2.1.24",
|
|
584
592
|
};
|
|
585
593
|
writeFileSync(path.join(outDir, "claude-ide-bridge-plugin.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf-8");
|
|
586
|
-
//
|
|
587
|
-
const
|
|
594
|
+
// ── shared tool body — same logic, different surface syntax ──
|
|
595
|
+
const jsEntrypoint = `/**
|
|
588
596
|
* ${pluginName} — Claude IDE Bridge plugin
|
|
589
597
|
*
|
|
590
598
|
* Each tool must have a name starting with "${toolPrefix}".
|
|
@@ -592,7 +600,7 @@ if (process.argv[2] === "gen-plugin-stub") {
|
|
|
592
600
|
* ctx.config (commandTimeout, maxResultSize), and ctx.logger.
|
|
593
601
|
*/
|
|
594
602
|
|
|
595
|
-
/** @param {import('
|
|
603
|
+
/** @param {import('patchwork-os/plugin').PluginContext} ctx */
|
|
596
604
|
export function register(ctx) {
|
|
597
605
|
ctx.logger.info(${JSON.stringify(`${pluginName} loaded`)}, { workspace: ctx.workspace });
|
|
598
606
|
|
|
@@ -620,25 +628,216 @@ export function register(ctx) {
|
|
|
620
628
|
};
|
|
621
629
|
}
|
|
622
630
|
`;
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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 = {
|
|
626
712
|
name: pluginName.replace(/^@[^/]+\//, "").replace(/\//g, "-"),
|
|
627
713
|
version: "0.1.0",
|
|
628
714
|
description: "A Claude IDE Bridge plugin",
|
|
629
715
|
type: "module",
|
|
630
716
|
main: "index.mjs",
|
|
631
|
-
keywords: ["
|
|
632
|
-
peerDependencies: { "
|
|
717
|
+
keywords: ["patchwork-os", "claude-ide-bridge-plugin"],
|
|
718
|
+
peerDependencies: { "patchwork-os": ">=0.2.0-alpha.0" },
|
|
633
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;
|
|
634
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");
|
|
635
823
|
// .gitignore
|
|
636
|
-
|
|
637
|
-
|
|
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`);
|
|
638
829
|
process.stderr.write("\nNext steps:\n");
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
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
|
+
}
|
|
642
841
|
process.exit(0);
|
|
643
842
|
}
|
|
644
843
|
// Patchwork: `patchwork recipe list` — enumerate installed recipes.
|
|
@@ -909,9 +1108,15 @@ if (process.argv[2] === "recipe" && process.argv[3] === "install") {
|
|
|
909
1108
|
const result = installRecipeFromFile(path.resolve(source), {
|
|
910
1109
|
recipesDir,
|
|
911
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.
|
|
912
1114
|
process.stdout.write(` ✓ ${result.action} ${result.installedPath}\n` +
|
|
913
|
-
` ℹ permissions
|
|
914
|
-
`
|
|
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`);
|
|
915
1120
|
}
|
|
916
1121
|
else {
|
|
917
1122
|
// Marketplace install: github:, https://, ./local/
|
|
@@ -927,7 +1132,264 @@ if (process.argv[2] === "recipe" && process.argv[3] === "install") {
|
|
|
927
1132
|
}
|
|
928
1133
|
})();
|
|
929
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
|
+
}
|
|
930
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
|
+
}
|
|
931
1393
|
if (process.argv[2] === "recipe" && process.argv[3] === "schema") {
|
|
932
1394
|
const outputDir = process.argv[4] ?? path.join(process.cwd(), "schemas");
|
|
933
1395
|
(async () => {
|