patchwork-os 0.2.0-alpha.33 → 0.2.0-alpha.35

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 (270) hide show
  1. package/README.md +248 -48
  2. package/deploy/bootstrap-new-vps.sh +12 -12
  3. package/deploy/bootstrap-vps.sh +6 -3
  4. package/deploy/deploy-landing.sh +59 -2
  5. package/dist/bridge.js +35 -1
  6. package/dist/bridge.js.map +1 -1
  7. package/dist/commands/recipe.d.ts +11 -0
  8. package/dist/commands/recipe.js +32 -3
  9. package/dist/commands/recipe.js.map +1 -1
  10. package/dist/commands/recipeInstall.d.ts +79 -1
  11. package/dist/commands/recipeInstall.js +241 -13
  12. package/dist/commands/recipeInstall.js.map +1 -1
  13. package/dist/connectors/asana.d.ts +198 -0
  14. package/dist/connectors/asana.js +680 -0
  15. package/dist/connectors/asana.js.map +1 -0
  16. package/dist/connectors/baseConnector.d.ts +16 -0
  17. package/dist/connectors/baseConnector.js +107 -25
  18. package/dist/connectors/baseConnector.js.map +1 -1
  19. package/dist/connectors/discord.d.ts +150 -0
  20. package/dist/connectors/discord.js +544 -0
  21. package/dist/connectors/discord.js.map +1 -0
  22. package/dist/connectors/github.js +15 -7
  23. package/dist/connectors/github.js.map +1 -1
  24. package/dist/connectors/gitlab.d.ts +180 -0
  25. package/dist/connectors/gitlab.js +582 -0
  26. package/dist/connectors/gitlab.js.map +1 -0
  27. package/dist/connectors/gmail.js +45 -0
  28. package/dist/connectors/gmail.js.map +1 -1
  29. package/dist/connectors/googleDrive.d.ts +34 -0
  30. package/dist/connectors/googleDrive.js +305 -0
  31. package/dist/connectors/googleDrive.js.map +1 -0
  32. package/dist/connectors/htmlEscape.d.ts +5 -0
  33. package/dist/connectors/htmlEscape.js +13 -0
  34. package/dist/connectors/htmlEscape.js.map +1 -0
  35. package/dist/connectors/linear.js +26 -6
  36. package/dist/connectors/linear.js.map +1 -1
  37. package/dist/connectors/mcpOAuth.d.ts +2 -0
  38. package/dist/connectors/mcpOAuth.js +8 -4
  39. package/dist/connectors/mcpOAuth.js.map +1 -1
  40. package/dist/connectors/pagerduty.d.ts +160 -0
  41. package/dist/connectors/pagerduty.js +464 -0
  42. package/dist/connectors/pagerduty.js.map +1 -0
  43. package/dist/connectors/sentry.js +3 -2
  44. package/dist/connectors/sentry.js.map +1 -1
  45. package/dist/connectors/slack.d.ts +1 -1
  46. package/dist/connectors/slack.js +7 -4
  47. package/dist/connectors/slack.js.map +1 -1
  48. package/dist/featureFlags.d.ts +17 -11
  49. package/dist/featureFlags.js +52 -47
  50. package/dist/featureFlags.js.map +1 -1
  51. package/dist/index.js +262 -129
  52. package/dist/index.js.map +1 -1
  53. package/dist/oauth.js +3 -2
  54. package/dist/oauth.js.map +1 -1
  55. package/dist/recipeOrchestration.d.ts +7 -0
  56. package/dist/recipeOrchestration.js +154 -28
  57. package/dist/recipeOrchestration.js.map +1 -1
  58. package/dist/recipes/agentExecutor.d.ts +1 -0
  59. package/dist/recipes/agentExecutor.js +7 -0
  60. package/dist/recipes/agentExecutor.js.map +1 -1
  61. package/dist/recipes/captureForRunlog.d.ts +27 -0
  62. package/dist/recipes/captureForRunlog.js +128 -0
  63. package/dist/recipes/captureForRunlog.js.map +1 -0
  64. package/dist/recipes/chainedRunner.d.ts +39 -3
  65. package/dist/recipes/chainedRunner.js +183 -28
  66. package/dist/recipes/chainedRunner.js.map +1 -1
  67. package/dist/recipes/detectSilentFail.d.ts +34 -0
  68. package/dist/recipes/detectSilentFail.js +105 -0
  69. package/dist/recipes/detectSilentFail.js.map +1 -0
  70. package/dist/recipes/legacyRecipeCompat.d.ts +8 -0
  71. package/dist/recipes/legacyRecipeCompat.js +20 -1
  72. package/dist/recipes/legacyRecipeCompat.js.map +1 -1
  73. package/dist/recipes/manifest.js +21 -6
  74. package/dist/recipes/manifest.js.map +1 -1
  75. package/dist/recipes/migrations/index.d.ts +24 -0
  76. package/dist/recipes/migrations/index.js +55 -0
  77. package/dist/recipes/migrations/index.js.map +1 -0
  78. package/dist/recipes/migrations/types.d.ts +28 -0
  79. package/dist/recipes/migrations/types.js +2 -0
  80. package/dist/recipes/migrations/types.js.map +1 -0
  81. package/dist/recipes/migrations/v1.d.ts +11 -0
  82. package/dist/recipes/migrations/v1.js +18 -0
  83. package/dist/recipes/migrations/v1.js.map +1 -0
  84. package/dist/recipes/replayRun.d.ts +62 -0
  85. package/dist/recipes/replayRun.js +97 -0
  86. package/dist/recipes/replayRun.js.map +1 -0
  87. package/dist/recipes/scheduler.js +102 -11
  88. package/dist/recipes/scheduler.js.map +1 -1
  89. package/dist/recipes/schemaGenerator.js +3 -3
  90. package/dist/recipes/schemaGenerator.js.map +1 -1
  91. package/dist/recipes/templateEngine.js +8 -1
  92. package/dist/recipes/templateEngine.js.map +1 -1
  93. package/dist/recipes/toolRegistry.d.ts +5 -0
  94. package/dist/recipes/toolRegistry.js +9 -0
  95. package/dist/recipes/toolRegistry.js.map +1 -1
  96. package/dist/recipes/tools/asana.d.ts +16 -0
  97. package/dist/recipes/tools/asana.js +524 -0
  98. package/dist/recipes/tools/asana.js.map +1 -0
  99. package/dist/recipes/tools/discord.d.ts +18 -0
  100. package/dist/recipes/tools/discord.js +254 -0
  101. package/dist/recipes/tools/discord.js.map +1 -0
  102. package/dist/recipes/tools/github.js +29 -4
  103. package/dist/recipes/tools/github.js.map +1 -1
  104. package/dist/recipes/tools/gitlab.d.ts +11 -0
  105. package/dist/recipes/tools/gitlab.js +285 -0
  106. package/dist/recipes/tools/gitlab.js.map +1 -0
  107. package/dist/recipes/tools/gmail.d.ts +1 -1
  108. package/dist/recipes/tools/gmail.js +230 -6
  109. package/dist/recipes/tools/gmail.js.map +1 -1
  110. package/dist/recipes/tools/googleDrive.d.ts +1 -0
  111. package/dist/recipes/tools/googleDrive.js +55 -0
  112. package/dist/recipes/tools/googleDrive.js.map +1 -0
  113. package/dist/recipes/tools/index.d.ts +6 -0
  114. package/dist/recipes/tools/index.js +6 -0
  115. package/dist/recipes/tools/index.js.map +1 -1
  116. package/dist/recipes/tools/linear.d.ts +2 -1
  117. package/dist/recipes/tools/linear.js +222 -1
  118. package/dist/recipes/tools/linear.js.map +1 -1
  119. package/dist/recipes/tools/meetingNotes.d.ts +21 -0
  120. package/dist/recipes/tools/meetingNotes.js +701 -0
  121. package/dist/recipes/tools/meetingNotes.js.map +1 -0
  122. package/dist/recipes/tools/pagerduty.d.ts +15 -0
  123. package/dist/recipes/tools/pagerduty.js +451 -0
  124. package/dist/recipes/tools/pagerduty.js.map +1 -0
  125. package/dist/recipes/tools/slack.js +8 -2
  126. package/dist/recipes/tools/slack.js.map +1 -1
  127. package/dist/recipes/validation.js +54 -15
  128. package/dist/recipes/validation.js.map +1 -1
  129. package/dist/recipes/yamlRunner.d.ts +23 -2
  130. package/dist/recipes/yamlRunner.js +265 -60
  131. package/dist/recipes/yamlRunner.js.map +1 -1
  132. package/dist/recipesHttp.d.ts +60 -0
  133. package/dist/recipesHttp.js +418 -3
  134. package/dist/recipesHttp.js.map +1 -1
  135. package/dist/runLog.d.ts +64 -2
  136. package/dist/runLog.js +116 -2
  137. package/dist/runLog.js.map +1 -1
  138. package/dist/server.d.ts +21 -0
  139. package/dist/server.js +387 -8
  140. package/dist/server.js.map +1 -1
  141. package/dist/streamableHttp.d.ts +31 -1
  142. package/dist/streamableHttp.js +20 -2
  143. package/dist/streamableHttp.js.map +1 -1
  144. package/dist/tools/activityLog.d.ts +2 -0
  145. package/dist/tools/addLinearComment.d.ts +1 -0
  146. package/dist/tools/batchLsp.d.ts +3 -0
  147. package/dist/tools/bridgeDoctor.d.ts +1 -0
  148. package/dist/tools/bridgeStatus.d.ts +1 -0
  149. package/dist/tools/cancelClaudeTask.d.ts +1 -0
  150. package/dist/tools/checkDocumentDirty.d.ts +1 -0
  151. package/dist/tools/clipboard.d.ts +2 -0
  152. package/dist/tools/closeTabs.d.ts +2 -0
  153. package/dist/tools/codeLens.d.ts +1 -0
  154. package/dist/tools/contextBundle.d.ts +1 -0
  155. package/dist/tools/createIssueFromAIComment.d.ts +1 -0
  156. package/dist/tools/createLinearIssue.d.ts +1 -0
  157. package/dist/tools/ctxGetTaskContext.d.ts +1 -0
  158. package/dist/tools/ctxQueryTraces.d.ts +1 -0
  159. package/dist/tools/ctxSaveTrace.d.ts +1 -0
  160. package/dist/tools/debug.d.ts +4 -0
  161. package/dist/tools/decorations.d.ts +2 -0
  162. package/dist/tools/documentLinks.d.ts +1 -0
  163. package/dist/tools/editText.d.ts +1 -0
  164. package/dist/tools/enrichCommit.d.ts +1 -0
  165. package/dist/tools/enrichStackTrace.d.ts +1 -0
  166. package/dist/tools/explainDiagnostic.d.ts +1 -0
  167. package/dist/tools/explainSymbol.d.ts +1 -0
  168. package/dist/tools/fetchCalendarEvents.d.ts +1 -0
  169. package/dist/tools/fetchGithubIssue.d.ts +1 -0
  170. package/dist/tools/fetchGithubPR.d.ts +1 -0
  171. package/dist/tools/fetchLinearIssue.d.ts +1 -0
  172. package/dist/tools/fetchSentryIssue.d.ts +1 -0
  173. package/dist/tools/fetchSlackProfile.d.ts +1 -0
  174. package/dist/tools/fileOperations.d.ts +3 -0
  175. package/dist/tools/fileWatcher.d.ts +2 -0
  176. package/dist/tools/findFiles.d.ts +1 -0
  177. package/dist/tools/findRelatedTests.d.ts +1 -0
  178. package/dist/tools/fixAllLintErrors.d.ts +1 -0
  179. package/dist/tools/foldingRanges.d.ts +1 -0
  180. package/dist/tools/formatDocument.d.ts +1 -0
  181. package/dist/tools/generateTests.d.ts +1 -0
  182. package/dist/tools/getAIComments.d.ts +1 -0
  183. package/dist/tools/getAnalyticsReport.d.ts +1 -0
  184. package/dist/tools/getArchitectureContext.d.ts +1 -0
  185. package/dist/tools/getBufferContent.d.ts +1 -0
  186. package/dist/tools/getChangeImpact.d.ts +1 -0
  187. package/dist/tools/getClaudeTaskStatus.d.ts +1 -0
  188. package/dist/tools/getCodeCoverage.d.ts +1 -0
  189. package/dist/tools/getCommitsForIssue.d.ts +1 -0
  190. package/dist/tools/getConnectorStatus.d.ts +1 -0
  191. package/dist/tools/getCurrentSelection.d.ts +2 -0
  192. package/dist/tools/getDebugState.d.ts +1 -0
  193. package/dist/tools/getDependencyTree.d.ts +1 -0
  194. package/dist/tools/getDiagnostics.d.ts +1 -0
  195. package/dist/tools/getDiffFromHandoff.d.ts +1 -0
  196. package/dist/tools/getDocumentSymbols.d.ts +1 -0
  197. package/dist/tools/getFileTree.d.ts +1 -0
  198. package/dist/tools/getGitDiff.d.ts +1 -0
  199. package/dist/tools/getGitHotspots.d.ts +1 -0
  200. package/dist/tools/getGitLog.d.ts +1 -0
  201. package/dist/tools/getGitStatus.d.ts +1 -0
  202. package/dist/tools/getImportTree.d.ts +1 -0
  203. package/dist/tools/getImportedSignatures.d.ts +1 -0
  204. package/dist/tools/getOpenEditors.d.ts +1 -0
  205. package/dist/tools/getPRTemplate.d.ts +1 -0
  206. package/dist/tools/getProjectContext.d.ts +1 -0
  207. package/dist/tools/getProjectInfo.d.ts +1 -0
  208. package/dist/tools/getSecurityAdvisories.d.ts +1 -0
  209. package/dist/tools/getSessionUsage.d.ts +1 -0
  210. package/dist/tools/getSymbolHistory.d.ts +1 -0
  211. package/dist/tools/getToolCapabilities.d.ts +1 -0
  212. package/dist/tools/getTypeSignature.d.ts +1 -0
  213. package/dist/tools/getWorkspaceFolders.d.ts +1 -0
  214. package/dist/tools/getWorkspaceSettings.d.ts +1 -0
  215. package/dist/tools/gitHistory.d.ts +2 -0
  216. package/dist/tools/gitWrite.d.ts +11 -0
  217. package/dist/tools/github/actions.d.ts +2 -0
  218. package/dist/tools/github/composite.d.ts +3 -0
  219. package/dist/tools/github/issues.d.ts +4 -0
  220. package/dist/tools/github/pr.d.ts +7 -0
  221. package/dist/tools/handoffNote.d.ts +2 -0
  222. package/dist/tools/hoverAtCursor.d.ts +1 -0
  223. package/dist/tools/httpClient.d.ts +2 -0
  224. package/dist/tools/inlayHints.d.ts +1 -0
  225. package/dist/tools/launchQuickTask.d.ts +1 -0
  226. package/dist/tools/listClaudeTasks.d.ts +1 -0
  227. package/dist/tools/listTerminals.d.ts +1 -0
  228. package/dist/tools/lsp.d.ts +14 -0
  229. package/dist/tools/navigateToSymbolByName.d.ts +1 -0
  230. package/dist/tools/openDiff.d.ts +1 -0
  231. package/dist/tools/openFile.d.ts +1 -0
  232. package/dist/tools/openInBrowser.d.ts +1 -0
  233. package/dist/tools/organizeImports.d.ts +1 -0
  234. package/dist/tools/performanceReport.d.ts +1 -0
  235. package/dist/tools/planPersistence.d.ts +5 -0
  236. package/dist/tools/previewEdit.d.ts +1 -0
  237. package/dist/tools/refactorAnalyze.d.ts +1 -0
  238. package/dist/tools/refactorPreview.d.ts +1 -0
  239. package/dist/tools/replaceBlock.d.ts +1 -0
  240. package/dist/tools/resumeClaudeTask.d.ts +1 -0
  241. package/dist/tools/runClaudeTask.d.ts +1 -0
  242. package/dist/tools/runCommand.d.ts +1 -0
  243. package/dist/tools/runTests.d.ts +1 -0
  244. package/dist/tools/saveDocument.d.ts +1 -0
  245. package/dist/tools/screenshotAndAnnotate.d.ts +1 -0
  246. package/dist/tools/searchAndReplace.d.ts +1 -0
  247. package/dist/tools/searchTools.d.ts +1 -0
  248. package/dist/tools/searchWorkspace.d.ts +1 -0
  249. package/dist/tools/selectionRanges.d.ts +1 -0
  250. package/dist/tools/semanticTokens.d.ts +1 -0
  251. package/dist/tools/setActiveWorkspaceFolder.d.ts +1 -0
  252. package/dist/tools/signatureHelp.d.ts +1 -0
  253. package/dist/tools/slackListChannels.d.ts +1 -0
  254. package/dist/tools/slackPostMessage.d.ts +1 -0
  255. package/dist/tools/slackPostMessage.js +1 -1
  256. package/dist/tools/slackPostMessage.js.map +1 -1
  257. package/dist/tools/terminal.d.ts +6 -0
  258. package/dist/tools/testTraceToSource.d.ts +1 -0
  259. package/dist/tools/transaction.d.ts +4 -0
  260. package/dist/tools/typeHierarchy.d.ts +1 -0
  261. package/dist/tools/updateLinearIssue.d.ts +1 -0
  262. package/dist/tools/utils.d.ts +2 -0
  263. package/dist/tools/utils.js.map +1 -1
  264. package/dist/tools/vscodeCommands.d.ts +2 -0
  265. package/dist/tools/vscodeTasks.d.ts +2 -0
  266. package/dist/tools/workspaceSettings.d.ts +1 -0
  267. package/package.json +20 -4
  268. package/templates/recipes/project-health-check.yaml +1 -1
  269. package/dist/schemas/dry-run-plan.v1.json +0 -139
  270. package/dist/schemas/recipe.v1.json +0 -684
package/dist/index.js CHANGED
@@ -3,10 +3,15 @@
3
3
  // Uses Node 20.6+ native dotenv loader; falls back to manual parse for older Node.
4
4
  {
5
5
  const { fileURLToPath: _fileURLToPath } = await import("node:url");
6
- const envPath = _fileURLToPath(new URL("../.env", import.meta.url));
7
6
  try {
8
7
  const { readFileSync, existsSync } = await import("node:fs");
9
- if (existsSync(envPath)) {
8
+ // Try both "../.env" (compiled dist/) and ".env" (tsx src/ dev run)
9
+ const candidates = [
10
+ _fileURLToPath(new URL("../.env", import.meta.url)),
11
+ _fileURLToPath(new URL(".env", import.meta.url)),
12
+ ];
13
+ const envPath = candidates.find(existsSync);
14
+ if (envPath) {
10
15
  for (const line of readFileSync(envPath, "utf-8").split("\n")) {
11
16
  const m = /^([A-Z_][A-Z0-9_]*)=(.*)$/.exec(line.trim());
12
17
  if (m?.[1] && !process.env[m[1]]) {
@@ -87,6 +92,59 @@ async function downloadVsixFromOpenVsx() {
87
92
  writeFileSync(tmpPath, Buffer.from(buf));
88
93
  return tmpPath;
89
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
+ "dashboard",
127
+ "launchd",
128
+ ];
129
+ const __invokedSubcommand = (() => {
130
+ const sub = process.argv[2];
131
+ if (!sub || sub.startsWith("-"))
132
+ return null;
133
+ // Treat KNOWN_SUBCOMMANDS as the dispatch source. The bare-binary
134
+ // dashboard launcher (no argv) is handled separately below.
135
+ return KNOWN_SUBCOMMANDS.includes(sub)
136
+ ? sub
137
+ : null;
138
+ })();
139
+ const __invokedBareBinaryDashboard = (() => {
140
+ if (process.argv[2])
141
+ return false;
142
+ const binName = path.basename(process.argv[1] ?? "");
143
+ return (binName === "patchwork-os" ||
144
+ binName === "patchwork" ||
145
+ binName === "patchwork.js");
146
+ })();
147
+ const __subcommandWillRun = __invokedSubcommand !== null || __invokedBareBinaryDashboard;
90
148
  // Handle --version flag — print package version and exit.
91
149
  if (process.argv[2] === "--version" || process.argv[2] === "-v") {
92
150
  console.log(`claude-ide-bridge ${PACKAGE_VERSION}`);
@@ -592,6 +650,68 @@ if (process.argv[2] === "recipe" && process.argv[3] === "list") {
592
650
  process.exit(0);
593
651
  })();
594
652
  }
653
+ // Patchwork: `patchwork recipe enable <name>` / `recipe disable <name>` —
654
+ // flip the disabled marker so scheduled triggers (cron/file-watch) take
655
+ // effect (or stop). Manual `recipe run` is unaffected.
656
+ if (process.argv[2] === "recipe" &&
657
+ (process.argv[3] === "enable" || process.argv[3] === "disable")) {
658
+ const subcommand = process.argv[3];
659
+ const name = process.argv[4];
660
+ if (!name) {
661
+ process.stderr.write(`Usage: patchwork recipe ${subcommand} <name>\n` +
662
+ ` See \`patchwork recipe list\` for installed recipe names.\n`);
663
+ process.exit(1);
664
+ }
665
+ (async () => {
666
+ try {
667
+ const { runRecipeEnable, runRecipeDisable } = await import("./commands/recipeInstall.js");
668
+ if (subcommand === "enable") {
669
+ const r = runRecipeEnable(name);
670
+ process.stdout.write(r.alreadyEnabled
671
+ ? ` ℹ ${r.name} is already enabled\n`
672
+ : ` ✓ enabled ${r.name}\n`);
673
+ }
674
+ else {
675
+ const r = runRecipeDisable(name);
676
+ process.stdout.write(r.alreadyDisabled
677
+ ? ` ℹ ${r.name} is already disabled\n`
678
+ : ` ✓ disabled ${r.name}\n`);
679
+ }
680
+ process.exit(0);
681
+ }
682
+ catch (err) {
683
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
684
+ process.exit(1);
685
+ }
686
+ })();
687
+ }
688
+ // Patchwork: `patchwork recipe uninstall <name>` — remove an installed recipe
689
+ // directory and all its files. Sister to `recipe install`. Idempotent on
690
+ // success (subsequent uninstalls error with "no installed recipe").
691
+ if (process.argv[2] === "recipe" && process.argv[3] === "uninstall") {
692
+ const name = process.argv[4];
693
+ if (!name) {
694
+ process.stderr.write("Usage: patchwork recipe uninstall <name>\n" +
695
+ " See `patchwork recipe list` for installed recipe names.\n");
696
+ process.exit(1);
697
+ }
698
+ (async () => {
699
+ try {
700
+ const { runRecipeUninstall } = await import("./commands/recipeInstall.js");
701
+ const r = runRecipeUninstall(name);
702
+ if (!r.ok) {
703
+ process.stderr.write(`Error: ${r.error}\n`);
704
+ process.exit(1);
705
+ }
706
+ process.stdout.write(` ✓ Uninstalled ${name} (${r.installDir})\n`);
707
+ process.exit(0);
708
+ }
709
+ catch (err) {
710
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
711
+ process.exit(1);
712
+ }
713
+ })();
714
+ }
595
715
  // Patchwork: `patchwork recipe run <name>` — runs a recipe locally or via
596
716
  // a running bridge's /recipes/run endpoint if one is available.
597
717
  if (process.argv[2] === "recipe" && process.argv[3] === "run") {
@@ -831,7 +951,10 @@ if (process.argv[2] === "recipe" && process.argv[3] === "new") {
831
951
  const args = process.argv.slice(4);
832
952
  const recipeName = args[0];
833
953
  if (!recipeName) {
834
- process.stderr.write("Usage: patchwork recipe new <name> [--template <name>] [--desc <description>]\n");
954
+ process.stderr.write("Usage: patchwork recipe new <name> [--template <name>] [--desc <description>] [--out <dir>]\n" +
955
+ " --out <dir> Write the recipe to <dir>/<name>.yaml.\n" +
956
+ " Defaults to ~/.patchwork/recipes/ — pass `--out .` to\n" +
957
+ " write into the current directory instead.\n");
835
958
  process.stderr.write("\nTemplates:\n");
836
959
  (async () => {
837
960
  const { listTemplates } = await import("./commands/recipe.js");
@@ -850,10 +973,16 @@ if (process.argv[2] === "recipe" && process.argv[3] === "new") {
850
973
  const descIdx = args.indexOf("--desc");
851
974
  const description = (descIdx >= 0 ? args[descIdx + 1] : undefined) ??
852
975
  `Recipe: ${recipeName}`;
976
+ const outIdx = args.indexOf("--out");
977
+ const outRaw = outIdx >= 0 ? args[outIdx + 1] : undefined;
978
+ // `--out .` is the common case for "scaffold in cwd" — resolve so
979
+ // the success message shows the absolute path the user can open.
980
+ const outputDir = outRaw ? path.resolve(outRaw) : undefined;
853
981
  const result = runNew({
854
982
  name: recipeName,
855
983
  description,
856
984
  ...(template ? { template } : {}),
985
+ ...(outputDir ? { outputDir } : {}),
857
986
  });
858
987
  process.stdout.write(` ✓ Created ${result.path}\n`);
859
988
  process.exit(0);
@@ -1957,27 +2086,12 @@ if (process.argv[2] === "launchd") {
1957
2086
  }
1958
2087
  }
1959
2088
  {
1960
- const KNOWN_COMMANDS = [
1961
- "init",
1962
- "patchwork-init",
1963
- "start-all",
1964
- "install-extension",
1965
- "gen-claude-md",
1966
- "print-token",
1967
- "gen-plugin-stub",
1968
- "notify",
1969
- "install",
1970
- "marketplace",
1971
- "status",
1972
- "shim",
1973
- "recipe",
1974
- "dashboard",
1975
- "launchd",
1976
- ];
2089
+ // Reuses the KNOWN_SUBCOMMANDS list from the top of this file as a single
2090
+ // source of truth for "what subcommand argv tokens are recognized".
1977
2091
  const unknownSub = process.argv[2];
1978
2092
  if (unknownSub &&
1979
2093
  !unknownSub.startsWith("-") &&
1980
- !KNOWN_COMMANDS.includes(unknownSub)) {
2094
+ !KNOWN_SUBCOMMANDS.includes(unknownSub)) {
1981
2095
  const lev = (a, b) => {
1982
2096
  const dp = Array.from({ length: a.length + 1 }, (_, i) => Array.from({ length: b.length + 1 }, (_, j) => i === 0 ? j : j === 0 ? i : 0));
1983
2097
  for (let i = 1; i <= a.length; i++)
@@ -1993,127 +2107,146 @@ if (process.argv[2] === "launchd") {
1993
2107
  // biome-ignore lint/style/noNonNullAssertion: dp is fully pre-allocated
1994
2108
  return dp[a.length][b.length];
1995
2109
  };
1996
- const closest = [...KNOWN_COMMANDS].sort((a, b) => lev(unknownSub, a) - lev(unknownSub, b))[0];
2110
+ const closest = [...KNOWN_SUBCOMMANDS].sort((a, b) => lev(unknownSub, a) - lev(unknownSub, b))[0];
1997
2111
  console.error(`Unknown command: '${unknownSub}'. Did you mean: ${closest}?`);
1998
2112
  process.exit(1);
1999
2113
  }
2000
2114
  }
2001
- const config = parseConfig(process.argv);
2002
- // Patchwork: resolve --model flag (optional, non-invasive) stashes the
2003
- // configured adapter on globalThis for consumers that opt into the adapter
2004
- // layer. Bridge subprocess driver still works when --model is absent.
2005
- try {
2006
- const { resolveModel } = await import("./patchworkCli.js");
2007
- const resolved = resolveModel(process.argv);
2008
- if (resolved) {
2009
- globalThis.__patchworkAdapter =
2010
- resolved.adapter;
2011
- process.stderr.write(`[patchwork] model adapter initialized: ${resolved.adapter.name}\n`);
2012
- }
2013
- }
2014
- catch (err) {
2015
- process.stderr.write(`[patchwork] adapter init failed: ${err instanceof Error ? err.message : String(err)}\n`);
2115
+ // Skip the bridge-mode tail entirely when a subcommand IIFE will own the
2116
+ // process. `parseConfig` validates argv against the bridge's known-flag list
2117
+ // and raises "Unknown option" for subcommand-specific flags (e.g. `recipe
2118
+ // new --out .`); without this guard that throw kills the process before
2119
+ // the IIFE's microtask runs. The subcommand handles its own arg parsing.
2120
+ if (__subcommandWillRun) {
2121
+ // Subcommand IIFE is in flight or about to fire; sit tight until it
2122
+ // process.exits. Empty body — control naturally falls past end-of-file
2123
+ // and Node keeps the process alive on the IIFE's pending microtask.
2016
2124
  }
2017
- // If --analytics flag was passed, persist the preference immediately
2018
- if (config.analyticsEnabled !== null) {
2019
- setAnalyticsPref(config.analyticsEnabled);
2020
- }
2021
- // Auto-tmux: if requested and not already inside tmux or screen, re-exec inside a tmux session
2022
- if (config.autoTmux &&
2023
- !process.env.TMUX &&
2024
- !process.env.STY &&
2025
- !process.env.ZELLIJ &&
2026
- !process.env.ZELLIJ_SESSION_NAME) {
2027
- const ws = config.workspace.replace(/[^a-zA-Z0-9]/g, "").slice(-8);
2028
- const hash = crypto
2029
- .createHash("sha256")
2030
- .update(config.workspace)
2031
- .digest("hex")
2032
- .slice(0, 6);
2033
- const sessionName = `claude-bridge-${ws}${hash}`;
2034
- // Check if tmux is available
2035
- const tmuxCheck = spawnSync("which", ["tmux"], { stdio: "ignore" });
2036
- if (tmuxCheck.status !== 0) {
2037
- process.stderr.write("WARNING: --auto-tmux requested but tmux is not installed. Running without tmux.\n");
2038
- }
2039
- else {
2040
- // Strip --auto-tmux from argv to avoid infinite re-exec loop
2041
- const newArgv = process.argv.filter((a) => a !== "--auto-tmux");
2042
- // Pass each argv token as a separate tmux argument so paths with spaces work correctly
2043
- const result = spawnSync("tmux", ["new-session", "-d", "-s", sessionName, ...newArgv], { stdio: "inherit", timeout: 5000 });
2044
- if (result.status === 0) {
2045
- process.stderr.write(`Bridge launched in tmux session '${sessionName}'.\n`);
2046
- process.stderr.write(` Attach with: tmux attach -t ${sessionName}\n`);
2047
- process.exit(0);
2048
- }
2049
- else {
2050
- // tmux session likely already exists — attach to it or fall through
2051
- process.stderr.write(`WARNING: Could not create tmux session '${sessionName}' (already exists?). Running without auto-tmux.\n`);
2125
+ else {
2126
+ const config = parseConfig(process.argv);
2127
+ // Patchwork: resolve --model flag (optional, non-invasive) — stashes the
2128
+ // configured adapter on globalThis for consumers that opt into the adapter
2129
+ // layer. Bridge subprocess driver still works when --model is absent.
2130
+ try {
2131
+ const { resolveModel } = await import("./patchworkCli.js");
2132
+ const resolved = resolveModel(process.argv);
2133
+ if (resolved) {
2134
+ globalThis.__patchworkAdapter =
2135
+ resolved.adapter;
2136
+ process.stderr.write(`[patchwork] model adapter initialized: ${resolved.adapter.name}\n`);
2052
2137
  }
2053
2138
  }
2054
- }
2055
- // --watch: supervisor mode spawn this binary as a child (without --watch) and restart on crash
2056
- if (config.watch) {
2057
- const childArgv = process.argv.filter((a) => a !== "--watch");
2058
- const STABLE_THRESHOLD_MS = 60_000;
2059
- const BASE_DELAY_MS = 2_000;
2060
- const MAX_DELAY_MS = 30_000;
2061
- let delay = BASE_DELAY_MS;
2062
- let stopping = false;
2063
- function runChild() {
2064
- if (stopping)
2065
- return;
2066
- const startAt = Date.now();
2067
- process.stderr.write("[supervisor] starting bridge\n");
2068
- const [cmd, ...args] = childArgv;
2069
- if (!cmd)
2070
- return;
2071
- const child = spawn(cmd, args, {
2072
- stdio: "inherit",
2073
- });
2074
- for (const sig of ["SIGTERM", "SIGINT"]) {
2075
- process.once(sig, () => {
2076
- stopping = true;
2077
- child.kill(sig);
2078
- });
2139
+ catch (err) {
2140
+ process.stderr.write(`[patchwork] adapter init failed: ${err instanceof Error ? err.message : String(err)}\n`);
2141
+ }
2142
+ // If --analytics flag was passed, persist the preference immediately
2143
+ if (config.analyticsEnabled !== null) {
2144
+ setAnalyticsPref(config.analyticsEnabled);
2145
+ }
2146
+ // Auto-tmux: if requested and not already inside tmux or screen, re-exec inside a tmux session
2147
+ if (config.autoTmux &&
2148
+ !process.env.TMUX &&
2149
+ !process.env.STY &&
2150
+ !process.env.ZELLIJ &&
2151
+ !process.env.ZELLIJ_SESSION_NAME) {
2152
+ const ws = config.workspace.replace(/[^a-zA-Z0-9]/g, "").slice(-8);
2153
+ const hash = crypto
2154
+ .createHash("sha256")
2155
+ .update(config.workspace)
2156
+ .digest("hex")
2157
+ .slice(0, 6);
2158
+ const sessionName = `claude-bridge-${ws}${hash}`;
2159
+ // Check if tmux is available
2160
+ const tmuxCheck = spawnSync("which", ["tmux"], { stdio: "ignore" });
2161
+ if (tmuxCheck.status !== 0) {
2162
+ process.stderr.write("WARNING: --auto-tmux requested but tmux is not installed. Running without tmux.\n");
2079
2163
  }
2080
- child.on("exit", (code, signal) => {
2081
- if (stopping) {
2082
- process.stderr.write("[supervisor] bridge stopped\n");
2164
+ else {
2165
+ // Strip --auto-tmux from argv to avoid infinite re-exec loop
2166
+ const newArgv = process.argv.filter((a) => a !== "--auto-tmux");
2167
+ // Pass each argv token as a separate tmux argument so paths with spaces work correctly
2168
+ const result = spawnSync("tmux", ["new-session", "-d", "-s", sessionName, ...newArgv], { stdio: "inherit", timeout: 5000 });
2169
+ if (result.status === 0) {
2170
+ process.stderr.write(`Bridge launched in tmux session '${sessionName}'.\n`);
2171
+ process.stderr.write(` Attach with: tmux attach -t ${sessionName}\n`);
2083
2172
  process.exit(0);
2084
2173
  }
2085
- const uptime = Date.now() - startAt;
2086
- if (uptime >= STABLE_THRESHOLD_MS) {
2087
- delay = BASE_DELAY_MS; // reset backoff after a stable run
2174
+ else {
2175
+ // tmux session likely already exists — attach to it or fall through
2176
+ process.stderr.write(`WARNING: Could not create tmux session '${sessionName}' (already exists?). Running without auto-tmux.\n`);
2088
2177
  }
2089
- process.stderr.write(`[supervisor] bridge exited (code=${code ?? signal}), restarting in ${delay / 1000}s\n`);
2090
- setTimeout(() => {
2091
- delay = Math.min(delay * 2, MAX_DELAY_MS);
2092
- runChild();
2093
- }, delay);
2094
- });
2178
+ }
2095
2179
  }
2096
- runChild();
2097
- }
2098
- else {
2099
- const bridge = new Bridge(config);
2100
- bridge.start().catch((err) => {
2101
- const message = err instanceof Error ? err.message : String(err);
2102
- process.stderr.write(`Error: ${message}\n`);
2103
- process.exit(1);
2104
- });
2105
- // F5: Silent self-update nudge (fire-and-forget)
2106
- import("node:child_process")
2107
- .then(({ exec }) => {
2108
- exec("npm view claude-ide-bridge version", { timeout: 5000 }, (err, stdout) => {
2109
- if (err || !stdout)
2180
+ // Skip bridge boot when a subcommand IIFE is doing the work — avoids the
2181
+ // race where bridge.start() began initialising in parallel with the
2182
+ // subcommand's async path. See the KNOWN_SUBCOMMANDS / __subcommandWillRun
2183
+ // gate at the top of this file.
2184
+ if (__subcommandWillRun) {
2185
+ // intentionally empty subcommand IIFE owns the process from here.
2186
+ }
2187
+ // --watch: supervisor mode — spawn this binary as a child (without --watch) and restart on crash
2188
+ else if (config.watch) {
2189
+ const childArgv = process.argv.filter((a) => a !== "--watch");
2190
+ const STABLE_THRESHOLD_MS = 60_000;
2191
+ const BASE_DELAY_MS = 2_000;
2192
+ const MAX_DELAY_MS = 30_000;
2193
+ let delay = BASE_DELAY_MS;
2194
+ let stopping = false;
2195
+ function runChild() {
2196
+ if (stopping)
2197
+ return;
2198
+ const startAt = Date.now();
2199
+ process.stderr.write("[supervisor] starting bridge\n");
2200
+ const [cmd, ...args] = childArgv;
2201
+ if (!cmd)
2110
2202
  return;
2111
- const latest = stdout.trim();
2112
- if (latest && semverGt(latest, PACKAGE_VERSION)) {
2113
- console.log(`\n Bridge v${latest} available — run: npm update -g claude-ide-bridge\n`);
2203
+ const child = spawn(cmd, args, {
2204
+ stdio: "inherit",
2205
+ });
2206
+ for (const sig of ["SIGTERM", "SIGINT"]) {
2207
+ process.once(sig, () => {
2208
+ stopping = true;
2209
+ child.kill(sig);
2210
+ });
2114
2211
  }
2212
+ child.on("exit", (code, signal) => {
2213
+ if (stopping) {
2214
+ process.stderr.write("[supervisor] bridge stopped\n");
2215
+ process.exit(0);
2216
+ }
2217
+ const uptime = Date.now() - startAt;
2218
+ if (uptime >= STABLE_THRESHOLD_MS) {
2219
+ delay = BASE_DELAY_MS; // reset backoff after a stable run
2220
+ }
2221
+ process.stderr.write(`[supervisor] bridge exited (code=${code ?? signal}), restarting in ${delay / 1000}s\n`);
2222
+ setTimeout(() => {
2223
+ delay = Math.min(delay * 2, MAX_DELAY_MS);
2224
+ runChild();
2225
+ }, delay);
2226
+ });
2227
+ }
2228
+ runChild();
2229
+ }
2230
+ else {
2231
+ const bridge = new Bridge(config);
2232
+ bridge.start().catch((err) => {
2233
+ const message = err instanceof Error ? err.message : String(err);
2234
+ process.stderr.write(`Error: ${message}\n`);
2235
+ process.exit(1);
2115
2236
  });
2116
- })
2117
- .catch(() => { });
2118
- }
2237
+ // F5: Silent self-update nudge (fire-and-forget)
2238
+ import("node:child_process")
2239
+ .then(({ exec }) => {
2240
+ exec("npm view claude-ide-bridge version", { timeout: 5000 }, (err, stdout) => {
2241
+ if (err || !stdout)
2242
+ return;
2243
+ const latest = stdout.trim();
2244
+ if (latest && semverGt(latest, PACKAGE_VERSION)) {
2245
+ console.log(`\n Bridge v${latest} available — run: npm update -g claude-ide-bridge\n`);
2246
+ }
2247
+ });
2248
+ })
2249
+ .catch(() => { });
2250
+ }
2251
+ } // end of `else` for `if (__subcommandWillRun)` (bridge-mode block)
2119
2252
  //# sourceMappingURL=index.js.map