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.
Files changed (207) hide show
  1. package/README.md +70 -15
  2. package/dist/activityLog.d.ts +49 -0
  3. package/dist/activityLog.js +78 -0
  4. package/dist/activityLog.js.map +1 -1
  5. package/dist/approvalHttp.d.ts +25 -0
  6. package/dist/approvalHttp.js +74 -18
  7. package/dist/approvalHttp.js.map +1 -1
  8. package/dist/approvalInsights.d.ts +49 -0
  9. package/dist/approvalInsights.js +97 -0
  10. package/dist/approvalInsights.js.map +1 -0
  11. package/dist/approvalQueue.d.ts +11 -0
  12. package/dist/approvalQueue.js +80 -1
  13. package/dist/approvalQueue.js.map +1 -1
  14. package/dist/approvalSignals.d.ts +124 -0
  15. package/dist/approvalSignals.js +512 -0
  16. package/dist/approvalSignals.js.map +1 -0
  17. package/dist/automation.d.ts +37 -0
  18. package/dist/automation.js +105 -61
  19. package/dist/automation.js.map +1 -1
  20. package/dist/automationSuggestions.d.ts +79 -0
  21. package/dist/automationSuggestions.js +150 -0
  22. package/dist/automationSuggestions.js.map +1 -0
  23. package/dist/bridge.js +46 -0
  24. package/dist/bridge.js.map +1 -1
  25. package/dist/ccPermissions.d.ts +15 -0
  26. package/dist/ccPermissions.js +15 -0
  27. package/dist/ccPermissions.js.map +1 -1
  28. package/dist/claudeDriver.js +74 -16
  29. package/dist/claudeDriver.js.map +1 -1
  30. package/dist/commands/patchworkInit.d.ts +8 -0
  31. package/dist/commands/patchworkInit.js +41 -5
  32. package/dist/commands/patchworkInit.js.map +1 -1
  33. package/dist/commands/recipe.d.ts +20 -0
  34. package/dist/commands/recipe.js +194 -5
  35. package/dist/commands/recipe.js.map +1 -1
  36. package/dist/commands/recipeInstall.js +93 -4
  37. package/dist/commands/recipeInstall.js.map +1 -1
  38. package/dist/commands/tracesExport.d.ts +83 -0
  39. package/dist/commands/tracesExport.js +269 -0
  40. package/dist/commands/tracesExport.js.map +1 -0
  41. package/dist/commands/tracesImport.d.ts +56 -0
  42. package/dist/commands/tracesImport.js +161 -0
  43. package/dist/commands/tracesImport.js.map +1 -0
  44. package/dist/config.d.ts +8 -0
  45. package/dist/config.js +9 -1
  46. package/dist/config.js.map +1 -1
  47. package/dist/connectorRoutes.d.ts +43 -0
  48. package/dist/connectorRoutes.js +1023 -0
  49. package/dist/connectorRoutes.js.map +1 -0
  50. package/dist/connectors/asana.js +6 -7
  51. package/dist/connectors/asana.js.map +1 -1
  52. package/dist/connectors/baseConnector.d.ts +20 -0
  53. package/dist/connectors/baseConnector.js +45 -4
  54. package/dist/connectors/baseConnector.js.map +1 -1
  55. package/dist/connectors/discord.js +6 -7
  56. package/dist/connectors/discord.js.map +1 -1
  57. package/dist/connectors/gmail.js +39 -10
  58. package/dist/connectors/gmail.js.map +1 -1
  59. package/dist/connectors/googleCalendar.js +36 -10
  60. package/dist/connectors/googleCalendar.js.map +1 -1
  61. package/dist/connectors/googleDrive.js +22 -6
  62. package/dist/connectors/googleDrive.js.map +1 -1
  63. package/dist/connectors/linear.js +2 -2
  64. package/dist/connectors/linear.js.map +1 -1
  65. package/dist/connectors/mcpOAuth.js +26 -2
  66. package/dist/connectors/mcpOAuth.js.map +1 -1
  67. package/dist/connectors/oauthStateStore.d.ts +31 -0
  68. package/dist/connectors/oauthStateStore.js +52 -0
  69. package/dist/connectors/oauthStateStore.js.map +1 -0
  70. package/dist/connectors/slack.d.ts +15 -0
  71. package/dist/connectors/slack.js +54 -4
  72. package/dist/connectors/slack.js.map +1 -1
  73. package/dist/connectors/tokenStorage.js +27 -2
  74. package/dist/connectors/tokenStorage.js.map +1 -1
  75. package/dist/connectors/zendesk.js +19 -1
  76. package/dist/connectors/zendesk.js.map +1 -1
  77. package/dist/cors.d.ts +10 -0
  78. package/dist/cors.js +29 -0
  79. package/dist/cors.js.map +1 -0
  80. package/dist/decisionReplay.d.ts +72 -0
  81. package/dist/decisionReplay.js +92 -0
  82. package/dist/decisionReplay.js.map +1 -0
  83. package/dist/decisionTraceLog.d.ts +6 -0
  84. package/dist/decisionTraceLog.js +54 -2
  85. package/dist/decisionTraceLog.js.map +1 -1
  86. package/dist/fp/automationInterpreter.js +25 -21
  87. package/dist/fp/automationInterpreter.js.map +1 -1
  88. package/dist/fp/automationState.js +4 -1
  89. package/dist/fp/automationState.js.map +1 -1
  90. package/dist/fp/policyParser.js +4 -1
  91. package/dist/fp/policyParser.js.map +1 -1
  92. package/dist/inboxRoutes.d.ts +22 -0
  93. package/dist/inboxRoutes.js +114 -0
  94. package/dist/inboxRoutes.js.map +1 -0
  95. package/dist/index.js +479 -17
  96. package/dist/index.js.map +1 -1
  97. package/dist/mcpRoutes.d.ts +37 -0
  98. package/dist/mcpRoutes.js +76 -0
  99. package/dist/mcpRoutes.js.map +1 -0
  100. package/dist/oauth.d.ts +3 -0
  101. package/dist/oauth.js +151 -26
  102. package/dist/oauth.js.map +1 -1
  103. package/dist/oauthRoutes.d.ts +32 -0
  104. package/dist/oauthRoutes.js +124 -0
  105. package/dist/oauthRoutes.js.map +1 -0
  106. package/dist/orchestrator/orchestratorBridge.js +2 -2
  107. package/dist/orchestrator/orchestratorBridge.js.map +1 -1
  108. package/dist/patchworkConfig.d.ts +7 -0
  109. package/dist/patchworkConfig.js.map +1 -1
  110. package/dist/pluginLoader.d.ts +12 -0
  111. package/dist/pluginLoader.js +43 -4
  112. package/dist/pluginLoader.js.map +1 -1
  113. package/dist/pluginWatcher.js +8 -3
  114. package/dist/pluginWatcher.js.map +1 -1
  115. package/dist/preToolUseHook.d.ts +12 -0
  116. package/dist/preToolUseHook.js +23 -0
  117. package/dist/preToolUseHook.js.map +1 -1
  118. package/dist/recipeOrchestration.d.ts +1 -0
  119. package/dist/recipeOrchestration.js +173 -13
  120. package/dist/recipeOrchestration.js.map +1 -1
  121. package/dist/recipeRoutes.d.ts +154 -0
  122. package/dist/recipeRoutes.js +1098 -0
  123. package/dist/recipeRoutes.js.map +1 -0
  124. package/dist/recipes/chainedRunner.d.ts +15 -0
  125. package/dist/recipes/chainedRunner.js +73 -8
  126. package/dist/recipes/chainedRunner.js.map +1 -1
  127. package/dist/recipes/compiler.js +3 -3
  128. package/dist/recipes/compiler.js.map +1 -1
  129. package/dist/recipes/installer.js +3 -3
  130. package/dist/recipes/installer.js.map +1 -1
  131. package/dist/recipes/migrationWarnings.d.ts +12 -0
  132. package/dist/recipes/migrationWarnings.js +44 -0
  133. package/dist/recipes/migrationWarnings.js.map +1 -0
  134. package/dist/recipes/resolveRecipePath.d.ts +69 -0
  135. package/dist/recipes/resolveRecipePath.js +202 -0
  136. package/dist/recipes/resolveRecipePath.js.map +1 -0
  137. package/dist/recipes/tools/file.d.ts +6 -0
  138. package/dist/recipes/tools/file.js +12 -8
  139. package/dist/recipes/tools/file.js.map +1 -1
  140. package/dist/recipes/tools/index.d.ts +2 -0
  141. package/dist/recipes/tools/index.js +2 -0
  142. package/dist/recipes/tools/index.js.map +1 -1
  143. package/dist/recipes/tools/jira.d.ts +14 -0
  144. package/dist/recipes/tools/jira.js +369 -0
  145. package/dist/recipes/tools/jira.js.map +1 -0
  146. package/dist/recipes/tools/linear.js +6 -3
  147. package/dist/recipes/tools/linear.js.map +1 -1
  148. package/dist/recipes/tools/sentry.d.ts +12 -0
  149. package/dist/recipes/tools/sentry.js +73 -0
  150. package/dist/recipes/tools/sentry.js.map +1 -0
  151. package/dist/recipes/tools/slack.js +7 -3
  152. package/dist/recipes/tools/slack.js.map +1 -1
  153. package/dist/recipes/validation.js +83 -14
  154. package/dist/recipes/validation.js.map +1 -1
  155. package/dist/recipes/yamlRunner.d.ts +7 -0
  156. package/dist/recipes/yamlRunner.js +107 -13
  157. package/dist/recipes/yamlRunner.js.map +1 -1
  158. package/dist/recipesHttp.d.ts +44 -1
  159. package/dist/recipesHttp.js +168 -15
  160. package/dist/recipesHttp.js.map +1 -1
  161. package/dist/runLog.d.ts +14 -0
  162. package/dist/runLog.js +88 -4
  163. package/dist/runLog.js.map +1 -1
  164. package/dist/schemas/dry-run-plan.v1.json +139 -0
  165. package/dist/schemas/recipe.v1.json +684 -0
  166. package/dist/server.d.ts +71 -10
  167. package/dist/server.js +363 -1703
  168. package/dist/server.js.map +1 -1
  169. package/dist/ssrfGuard.d.ts +54 -0
  170. package/dist/ssrfGuard.js +122 -0
  171. package/dist/ssrfGuard.js.map +1 -0
  172. package/dist/streamableHttp.d.ts +8 -0
  173. package/dist/streamableHttp.js +112 -21
  174. package/dist/streamableHttp.js.map +1 -1
  175. package/dist/tools/getDocumentSymbols.d.ts +24 -0
  176. package/dist/tools/getDocumentSymbols.js +74 -8
  177. package/dist/tools/getDocumentSymbols.js.map +1 -1
  178. package/dist/tools/getSecurityAdvisories.js +10 -1
  179. package/dist/tools/getSecurityAdvisories.js.map +1 -1
  180. package/dist/tools/getSessionUsage.d.ts +3 -0
  181. package/dist/tools/getSessionUsage.js +3 -0
  182. package/dist/tools/getSessionUsage.js.map +1 -1
  183. package/dist/tools/index.d.ts +8 -0
  184. package/dist/tools/index.js +32 -2
  185. package/dist/tools/index.js.map +1 -1
  186. package/dist/tools/transaction.d.ts +19 -0
  187. package/dist/tools/transaction.js +29 -0
  188. package/dist/tools/transaction.js.map +1 -1
  189. package/dist/traceEncryption.d.ts +46 -0
  190. package/dist/traceEncryption.js +124 -0
  191. package/dist/traceEncryption.js.map +1 -0
  192. package/dist/transport.d.ts +39 -0
  193. package/dist/transport.js +88 -8
  194. package/dist/transport.js.map +1 -1
  195. package/package.json +4 -2
  196. package/templates/policies/README.md +72 -0
  197. package/templates/policies/conservative.json +14 -0
  198. package/templates/policies/developer.json +14 -0
  199. package/templates/policies/headless-ci.json +24 -0
  200. package/templates/policies/personal-assistant.json +15 -0
  201. package/templates/policies/regulated-industry.json +18 -0
  202. package/templates/recipes/webhook/README.md +70 -0
  203. package/templates/recipes/webhook/capture-thought.yaml +26 -0
  204. package/templates/recipes/webhook/customer-escalation.yaml +49 -0
  205. package/templates/recipes/webhook/incident-intake.yaml +46 -0
  206. package/templates/recipes/webhook/meeting-prep.yaml +48 -0
  207. 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
- // index.mjs entrypoint
587
- const entrypoint = `/**
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('claude-ide-bridge/plugin').PluginContext} ctx */
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
- writeFileSync(path.join(outDir, "index.mjs"), entrypoint, "utf-8");
624
- // package.json (optional, for npm publishing)
625
- const pkg = {
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: ["claude-ide-bridge", "claude-ide-bridge-plugin"],
632
- peerDependencies: { "claude-ide-bridge": ">=2.1.24" },
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
- writeFileSync(path.join(outDir, ".gitignore"), "node_modules\n", "utf-8");
637
- process.stderr.write(`✓ Plugin stub created at ${outDir}\n`);
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
- process.stderr.write(` 1. Edit ${path.join(outDir, "index.mjs")} to implement your tools\n`);
640
- process.stderr.write(` 2. Run the bridge with: claude-ide-bridge --plugin ${outDir}\n`);
641
- process.stderr.write(` 3. Or add to your config: { "plugins": ["${outDir}"] }\n`);
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 snippet written to ${result.installedPath}.permissions.json\n` +
914
- ` Review + merge into ~/.claude/settings.json to pre-approve recipe steps.\n`);
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 () => {