patchwork-os 0.2.0-alpha.34 → 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 (117) hide show
  1. package/README.md +142 -88
  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 +32 -1
  6. package/dist/bridge.js.map +1 -1
  7. package/dist/commands/recipe.js +18 -1
  8. package/dist/commands/recipe.js.map +1 -1
  9. package/dist/commands/recipeInstall.d.ts +79 -1
  10. package/dist/commands/recipeInstall.js +241 -13
  11. package/dist/commands/recipeInstall.js.map +1 -1
  12. package/dist/connectors/asana.d.ts +198 -0
  13. package/dist/connectors/asana.js +680 -0
  14. package/dist/connectors/asana.js.map +1 -0
  15. package/dist/connectors/baseConnector.d.ts +16 -0
  16. package/dist/connectors/baseConnector.js +106 -24
  17. package/dist/connectors/baseConnector.js.map +1 -1
  18. package/dist/connectors/discord.d.ts +150 -0
  19. package/dist/connectors/discord.js +544 -0
  20. package/dist/connectors/discord.js.map +1 -0
  21. package/dist/connectors/github.js +11 -4
  22. package/dist/connectors/github.js.map +1 -1
  23. package/dist/connectors/gitlab.d.ts +180 -0
  24. package/dist/connectors/gitlab.js +582 -0
  25. package/dist/connectors/gitlab.js.map +1 -0
  26. package/dist/connectors/gmail.js +11 -0
  27. package/dist/connectors/gmail.js.map +1 -1
  28. package/dist/connectors/googleDrive.d.ts +34 -0
  29. package/dist/connectors/googleDrive.js +305 -0
  30. package/dist/connectors/googleDrive.js.map +1 -0
  31. package/dist/connectors/linear.js +23 -4
  32. package/dist/connectors/linear.js.map +1 -1
  33. package/dist/connectors/pagerduty.d.ts +160 -0
  34. package/dist/connectors/pagerduty.js +464 -0
  35. package/dist/connectors/pagerduty.js.map +1 -0
  36. package/dist/connectors/slack.d.ts +1 -1
  37. package/dist/connectors/slack.js +3 -1
  38. package/dist/connectors/slack.js.map +1 -1
  39. package/dist/featureFlags.d.ts +17 -11
  40. package/dist/featureFlags.js +52 -47
  41. package/dist/featureFlags.js.map +1 -1
  42. package/dist/index.js +255 -127
  43. package/dist/index.js.map +1 -1
  44. package/dist/recipeOrchestration.d.ts +7 -0
  45. package/dist/recipeOrchestration.js +149 -28
  46. package/dist/recipeOrchestration.js.map +1 -1
  47. package/dist/recipes/captureForRunlog.d.ts +27 -0
  48. package/dist/recipes/captureForRunlog.js +128 -0
  49. package/dist/recipes/captureForRunlog.js.map +1 -0
  50. package/dist/recipes/chainedRunner.d.ts +39 -3
  51. package/dist/recipes/chainedRunner.js +183 -28
  52. package/dist/recipes/chainedRunner.js.map +1 -1
  53. package/dist/recipes/detectSilentFail.d.ts +34 -0
  54. package/dist/recipes/detectSilentFail.js +105 -0
  55. package/dist/recipes/detectSilentFail.js.map +1 -0
  56. package/dist/recipes/manifest.js +21 -6
  57. package/dist/recipes/manifest.js.map +1 -1
  58. package/dist/recipes/replayRun.d.ts +62 -0
  59. package/dist/recipes/replayRun.js +97 -0
  60. package/dist/recipes/replayRun.js.map +1 -0
  61. package/dist/recipes/scheduler.js +102 -11
  62. package/dist/recipes/scheduler.js.map +1 -1
  63. package/dist/recipes/schemaGenerator.js +3 -3
  64. package/dist/recipes/schemaGenerator.js.map +1 -1
  65. package/dist/recipes/toolRegistry.d.ts +5 -0
  66. package/dist/recipes/toolRegistry.js +9 -0
  67. package/dist/recipes/toolRegistry.js.map +1 -1
  68. package/dist/recipes/tools/asana.d.ts +16 -0
  69. package/dist/recipes/tools/asana.js +524 -0
  70. package/dist/recipes/tools/asana.js.map +1 -0
  71. package/dist/recipes/tools/discord.d.ts +18 -0
  72. package/dist/recipes/tools/discord.js +254 -0
  73. package/dist/recipes/tools/discord.js.map +1 -0
  74. package/dist/recipes/tools/github.js +29 -4
  75. package/dist/recipes/tools/github.js.map +1 -1
  76. package/dist/recipes/tools/gitlab.d.ts +11 -0
  77. package/dist/recipes/tools/gitlab.js +285 -0
  78. package/dist/recipes/tools/gitlab.js.map +1 -0
  79. package/dist/recipes/tools/gmail.d.ts +1 -1
  80. package/dist/recipes/tools/gmail.js +230 -6
  81. package/dist/recipes/tools/gmail.js.map +1 -1
  82. package/dist/recipes/tools/googleDrive.d.ts +1 -0
  83. package/dist/recipes/tools/googleDrive.js +55 -0
  84. package/dist/recipes/tools/googleDrive.js.map +1 -0
  85. package/dist/recipes/tools/index.d.ts +6 -0
  86. package/dist/recipes/tools/index.js +6 -0
  87. package/dist/recipes/tools/index.js.map +1 -1
  88. package/dist/recipes/tools/linear.d.ts +2 -1
  89. package/dist/recipes/tools/linear.js +222 -1
  90. package/dist/recipes/tools/linear.js.map +1 -1
  91. package/dist/recipes/tools/meetingNotes.d.ts +21 -0
  92. package/dist/recipes/tools/meetingNotes.js +701 -0
  93. package/dist/recipes/tools/meetingNotes.js.map +1 -0
  94. package/dist/recipes/tools/pagerduty.d.ts +15 -0
  95. package/dist/recipes/tools/pagerduty.js +451 -0
  96. package/dist/recipes/tools/pagerduty.js.map +1 -0
  97. package/dist/recipes/tools/slack.js +8 -2
  98. package/dist/recipes/tools/slack.js.map +1 -1
  99. package/dist/recipes/yamlRunner.d.ts +23 -2
  100. package/dist/recipes/yamlRunner.js +263 -58
  101. package/dist/recipes/yamlRunner.js.map +1 -1
  102. package/dist/recipesHttp.d.ts +32 -0
  103. package/dist/recipesHttp.js +310 -1
  104. package/dist/recipesHttp.js.map +1 -1
  105. package/dist/runLog.d.ts +64 -2
  106. package/dist/runLog.js +116 -2
  107. package/dist/runLog.js.map +1 -1
  108. package/dist/server.d.ts +8 -0
  109. package/dist/server.js +331 -9
  110. package/dist/server.js.map +1 -1
  111. package/dist/streamableHttp.d.ts +31 -1
  112. package/dist/streamableHttp.js +20 -2
  113. package/dist/streamableHttp.js.map +1 -1
  114. package/dist/tools/slackPostMessage.js +1 -1
  115. package/dist/tools/slackPostMessage.js.map +1 -1
  116. package/package.json +19 -4
  117. package/templates/recipes/project-health-check.yaml +1 -1
@@ -30,17 +30,67 @@ export function registerFlag(flag) {
30
30
  // Initialize with default value
31
31
  FLAG_VALUES.set(flag.id, flag.defaultValue);
32
32
  }
33
+ /**
34
+ * Snapshot of env-derived kill-switch values, populated by `lockKillSwitchEnv()`
35
+ * at bridge startup. Once locked, kill-switch flags ignore live `process.env`
36
+ * mutations — defends against a plugin / recipe step trying to disable an
37
+ * active emergency stop by writing `process.env.PATCHWORK_FLAG_*`.
38
+ */
39
+ const FROZEN_KILL_SWITCH_ENV = new Map();
40
+ let envLocked = false;
41
+ /**
42
+ * Snapshot the current `process.env` values for every registered kill-switch
43
+ * flag. Called once by `Bridge.start()` after `loadFlags()` so subsequent
44
+ * env mutations are ignored for kill-switch reads. Idempotent — second call
45
+ * is a no-op (would otherwise let an attacker re-snapshot a tampered env).
46
+ *
47
+ * Non-kill-switch flags remain dynamic so test infrastructure that mutates
48
+ * env per case continues to work.
49
+ */
50
+ export function lockKillSwitchEnv() {
51
+ if (envLocked)
52
+ return;
53
+ for (const [id, flag] of FLAG_REGISTRY.entries()) {
54
+ if (!flag.isKillSwitch)
55
+ continue;
56
+ const envKey = `PATCHWORK_FLAG_${id.replace(/[.-]/g, "_").toUpperCase()}`;
57
+ const envVal = process.env[envKey];
58
+ FROZEN_KILL_SWITCH_ENV.set(id, envVal === undefined
59
+ ? undefined
60
+ : envVal === "1" || envVal.toLowerCase() === "true");
61
+ }
62
+ envLocked = true;
63
+ }
64
+ /**
65
+ * TEST ONLY — resets the env lock so tests can exercise both locked and
66
+ * unlocked paths without process restart. Do not call from production code.
67
+ */
68
+ export function _resetEnvLockForTesting() {
69
+ envLocked = false;
70
+ FROZEN_KILL_SWITCH_ENV.clear();
71
+ }
33
72
  /**
34
73
  * Check if a feature flag is enabled.
35
74
  * Resolution order (highest to lowest priority):
36
- * 1. Environment variable (PATCHWORK_FLAG_<ID>)
75
+ * 1. Environment variable (PATCHWORK_FLAG_<ID>) — frozen at `lockKillSwitchEnv()`
76
+ * time for kill-switch flags so post-lock mutations are ignored
37
77
  * 2. User config file (~/.patchwork/config/flags.json)
38
78
  * 3. Default value from registration
39
79
  */
40
80
  export function isEnabled(flagId) {
41
81
  // Check cache first
42
82
  if (FLAG_VALUES.has(flagId)) {
43
- // Check environment override
83
+ const flag = FLAG_REGISTRY.get(flagId);
84
+ // Kill-switch flags read from the frozen snapshot once locked. This
85
+ // closes the gap where a plugin / recipe step could `process.env[...] = "0"`
86
+ // to disable an active emergency stop.
87
+ if (envLocked && flag?.isKillSwitch) {
88
+ const frozen = FROZEN_KILL_SWITCH_ENV.get(flagId);
89
+ if (frozen !== undefined)
90
+ return frozen;
91
+ return FLAG_VALUES.get(flagId);
92
+ }
93
+ // Dynamic env read for non-kill-switch flags (test-friendly).
44
94
  const envKey = `PATCHWORK_FLAG_${flagId.replace(/[.-]/g, "_").toUpperCase()}`;
45
95
  const envVal = process.env[envKey];
46
96
  if (envVal !== undefined) {
@@ -117,18 +167,8 @@ function persistFlags() {
117
167
  // ============================================================================
118
168
  /** Kill switch for ALL write-tier operations */
119
169
  export const KILL_SWITCH_WRITES = "kill-switch.writes";
120
- /** Enable visual recipe debugger (A3) */
121
- export const FLAG_DEBUGGER = "ui.recipe-debugger";
122
- /** Enable CLI test/watch commands (A2) */
123
- export const FLAG_CLI_UX = "ui.cli-ux-commands";
124
- /** Enable mock connector harness (A4) */
125
- export const FLAG_MOCK_HARNESS = "experimental.mock-harness";
126
- /** Enable Wave 2 connectors (A5) */
127
- export const FLAG_WAVE2_CONNECTORS = "connector.wave2";
128
170
  /** Enable recipe lint with schema validation (A1) */
129
171
  export const FLAG_SCHEMA_LINT = "ui.schema-lint";
130
- /** Enable community recipe gallery (M5) */
131
- export const FLAG_COMMUNITY_GALLERY = "experimental.community-gallery";
132
172
  // Register built-in flags
133
173
  registerFlag({
134
174
  id: KILL_SWITCH_WRITES,
@@ -138,34 +178,6 @@ registerFlag({
138
178
  requiresOptIn: false,
139
179
  isKillSwitch: true,
140
180
  });
141
- registerFlag({
142
- id: FLAG_DEBUGGER,
143
- description: "Visual recipe debugger at /runs/[seq] with step timeline",
144
- defaultValue: false,
145
- category: "ui",
146
- requiresOptIn: true,
147
- });
148
- registerFlag({
149
- id: FLAG_CLI_UX,
150
- description: "Enhanced CLI commands: new, lint, test, watch, record, fmt",
151
- defaultValue: false,
152
- category: "ui",
153
- requiresOptIn: true,
154
- });
155
- registerFlag({
156
- id: FLAG_MOCK_HARNESS,
157
- description: "Mock connector harness with fixture recording and VCR replay",
158
- defaultValue: false,
159
- category: "experimental",
160
- requiresOptIn: true,
161
- });
162
- registerFlag({
163
- id: FLAG_WAVE2_CONNECTORS,
164
- description: "Wave 2 connectors: Confluence, Zendesk, Intercom, HubSpot, Datadog, Stripe",
165
- defaultValue: false,
166
- category: "connector",
167
- requiresOptIn: true,
168
- });
169
181
  registerFlag({
170
182
  id: FLAG_SCHEMA_LINT,
171
183
  description: "Recipe linting with JSON Schema validation",
@@ -173,13 +185,6 @@ registerFlag({
173
185
  category: "ui",
174
186
  requiresOptIn: true,
175
187
  });
176
- registerFlag({
177
- id: FLAG_COMMUNITY_GALLERY,
178
- description: "Community recipe gallery and GitHub-backed install",
179
- defaultValue: false,
180
- category: "experimental",
181
- requiresOptIn: true,
182
- });
183
188
  // Load persisted flags on module init
184
189
  loadFlags();
185
190
  // ============================================================================
@@ -1 +1 @@
1
- {"version":3,"file":"featureFlags.js","sourceRoot":"","sources":["../src/featureFlags.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"featureFlags.js","sourceRoot":"","sources":["../src/featureFlags.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAkBjC,sCAAsC;AACtC,MAAM,aAAa,GAA6B,IAAI,GAAG,EAAE,CAAC;AAE1D,sDAAsD;AACtD,MAAM,WAAW,GAAyB,IAAI,GAAG,EAAE,CAAC;AAEpD,wBAAwB;AACxB,SAAS,YAAY;IACnB,OAAO,IAAI,CACT,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,YAAY,CAAC,EAC9D,QAAQ,EACR,YAAY,CACb,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAiB;IAC5C,IAAI,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,CAAC,EAAE,yBAAyB,CAAC,CAAC;IACrE,CAAC;IACD,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACjC,gCAAgC;IAChC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;AAC9C,CAAC;AAED;;;;;GAKG;AACH,MAAM,sBAAsB,GAAqC,IAAI,GAAG,EAAE,CAAC;AAC3E,IAAI,SAAS,GAAG,KAAK,CAAC;AAEtB;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB;IAC/B,IAAI,SAAS;QAAE,OAAO;IACtB,KAAK,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,aAAa,CAAC,OAAO,EAAE,EAAE,CAAC;QACjD,IAAI,CAAC,IAAI,CAAC,YAAY;YAAE,SAAS;QACjC,MAAM,MAAM,GAAG,kBAAkB,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;QAC1E,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACnC,sBAAsB,CAAC,GAAG,CACxB,EAAE,EACF,MAAM,KAAK,SAAS;YAClB,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,WAAW,EAAE,KAAK,MAAM,CACtD,CAAC;IACJ,CAAC;IACD,SAAS,GAAG,IAAI,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB;IACrC,SAAS,GAAG,KAAK,CAAC;IAClB,sBAAsB,CAAC,KAAK,EAAE,CAAC;AACjC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,MAAc;IACtC,oBAAoB;IACpB,IAAI,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACvC,oEAAoE;QACpE,6EAA6E;QAC7E,uCAAuC;QACvC,IAAI,SAAS,IAAI,IAAI,EAAE,YAAY,EAAE,CAAC;YACpC,MAAM,MAAM,GAAG,sBAAsB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAClD,IAAI,MAAM,KAAK,SAAS;gBAAE,OAAO,MAAM,CAAC;YACxC,OAAO,WAAW,CAAC,GAAG,CAAC,MAAM,CAAE,CAAC;QAClC,CAAC;QACD,8DAA8D;QAC9D,MAAM,MAAM,GAAG,kBAAkB,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;QAC9E,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACnC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC;QAC3D,CAAC;QACD,OAAO,WAAW,CAAC,GAAG,CAAC,MAAM,CAAE,CAAC;IAClC,CAAC;IAED,yCAAyC;IACzC,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,OAAO,CAAC,MAAc,EAAE,KAAc,EAAE,OAAO,GAAG,KAAK;IACrE,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,0BAA0B,MAAM,GAAG,CAAC,CAAC;IACvD,CAAC;IAED,WAAW,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAE/B,IAAI,OAAO,EAAE,CAAC;QACZ,YAAY,EAAE,CAAC;IACjB,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS;IACvB,OAAO,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACvD,GAAG,IAAI;QACP,YAAY,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;KACjC,CAAC,CAAC,CAAC;AACN,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS;IACvB,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;IAC5B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO;IACT,CAAC;IAED,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAC;QAE1D,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAClD,IAAI,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC3B,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,sCAAsC;IACxC,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,YAAY;IACnB,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;IAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAE7B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,CAAC;IAED,MAAM,MAAM,GAA4B,EAAE,CAAC;IAC3C,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,CAAC;QAChD,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnC,sCAAsC;QACtC,IAAI,IAAI,IAAI,KAAK,KAAK,IAAI,CAAC,YAAY,EAAE,CAAC;YACxC,MAAM,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC;QACrB,CAAC;IACH,CAAC;IAED,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,+EAA+E;AAC/E,iBAAiB;AACjB,+EAA+E;AAE/E,gDAAgD;AAChD,MAAM,CAAC,MAAM,kBAAkB,GAAG,oBAAoB,CAAC;AAEvD,qDAAqD;AACrD,MAAM,CAAC,MAAM,gBAAgB,GAAG,gBAAgB,CAAC;AAEjD,0BAA0B;AAC1B,YAAY,CAAC;IACX,EAAE,EAAE,kBAAkB;IACtB,WAAW,EACT,uGAAuG;IACzG,YAAY,EAAE,KAAK;IACnB,QAAQ,EAAE,QAAQ;IAClB,aAAa,EAAE,KAAK;IACpB,YAAY,EAAE,IAAI;CACnB,CAAC,CAAC;AAEH,YAAY,CAAC;IACX,EAAE,EAAE,gBAAgB;IACpB,WAAW,EAAE,4CAA4C;IACzD,YAAY,EAAE,KAAK;IACnB,QAAQ,EAAE,IAAI;IACd,aAAa,EAAE,IAAI;CACpB,CAAC,CAAC;AAEH,sCAAsC;AACtC,SAAS,EAAE,CAAC;AAEZ,+EAA+E;AAC/E,sBAAsB;AACtB,+EAA+E;AAE/E;;GAEG;AACH,MAAM,UAAU,uBAAuB;IACrC,OAAO,SAAS,CAAC,kBAAkB,CAAC,CAAC;AACvC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,SAAiB;IAClD,IAAI,uBAAuB,EAAE,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CACb,2CAA2C,SAAS,IAAI;YACtD,qFAAqF,CACxF,CAAC;IACJ,CAAC;AACH,CAAC"}
package/dist/index.js CHANGED
@@ -92,6 +92,59 @@ async function downloadVsixFromOpenVsx() {
92
92
  writeFileSync(tmpPath, Buffer.from(buf));
93
93
  return tmpPath;
94
94
  }
95
+ // Closes the race where bridge.start() began initialising in parallel with
96
+ // a subcommand's async work — observed in the 2026-04-29 dogfood pass
97
+ // where `recipe install` errors interleaved with bridge "Tools: full"
98
+ // startup logs.
99
+ //
100
+ // Every subcommand `if`-block below dispatches via an `(async () => {...})()`
101
+ // IIFE that ends with `process.exit`. The IIFE invocation returns
102
+ // synchronously, so without this gate, control immediately falls through
103
+ // to the bridge.start() block at end-of-file and starts initialising
104
+ // alongside the subcommand's async work. process.exit fires *eventually*
105
+ // after the await chain, but the bridge has already begun in parallel.
106
+ // Two IIFEs (patchwork no-args dashboard, recipe watch) lack process.exit
107
+ // entirely — without this gate they would run alongside the bridge
108
+ // indefinitely.
109
+ //
110
+ // Single source of truth for "is this argv invoking a subcommand?" — the
111
+ // same list is also used by the unknown-command suggester at L2570.
112
+ const KNOWN_SUBCOMMANDS = [
113
+ "init",
114
+ "patchwork-init",
115
+ "start-all",
116
+ "install-extension",
117
+ "gen-claude-md",
118
+ "print-token",
119
+ "gen-plugin-stub",
120
+ "notify",
121
+ "install",
122
+ "marketplace",
123
+ "status",
124
+ "shim",
125
+ "recipe",
126
+ "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;
95
148
  // Handle --version flag — print package version and exit.
96
149
  if (process.argv[2] === "--version" || process.argv[2] === "-v") {
97
150
  console.log(`claude-ide-bridge ${PACKAGE_VERSION}`);
@@ -597,6 +650,68 @@ if (process.argv[2] === "recipe" && process.argv[3] === "list") {
597
650
  process.exit(0);
598
651
  })();
599
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
+ }
600
715
  // Patchwork: `patchwork recipe run <name>` — runs a recipe locally or via
601
716
  // a running bridge's /recipes/run endpoint if one is available.
602
717
  if (process.argv[2] === "recipe" && process.argv[3] === "run") {
@@ -836,7 +951,10 @@ if (process.argv[2] === "recipe" && process.argv[3] === "new") {
836
951
  const args = process.argv.slice(4);
837
952
  const recipeName = args[0];
838
953
  if (!recipeName) {
839
- 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");
840
958
  process.stderr.write("\nTemplates:\n");
841
959
  (async () => {
842
960
  const { listTemplates } = await import("./commands/recipe.js");
@@ -855,10 +973,16 @@ if (process.argv[2] === "recipe" && process.argv[3] === "new") {
855
973
  const descIdx = args.indexOf("--desc");
856
974
  const description = (descIdx >= 0 ? args[descIdx + 1] : undefined) ??
857
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;
858
981
  const result = runNew({
859
982
  name: recipeName,
860
983
  description,
861
984
  ...(template ? { template } : {}),
985
+ ...(outputDir ? { outputDir } : {}),
862
986
  });
863
987
  process.stdout.write(` ✓ Created ${result.path}\n`);
864
988
  process.exit(0);
@@ -1962,27 +2086,12 @@ if (process.argv[2] === "launchd") {
1962
2086
  }
1963
2087
  }
1964
2088
  {
1965
- const KNOWN_COMMANDS = [
1966
- "init",
1967
- "patchwork-init",
1968
- "start-all",
1969
- "install-extension",
1970
- "gen-claude-md",
1971
- "print-token",
1972
- "gen-plugin-stub",
1973
- "notify",
1974
- "install",
1975
- "marketplace",
1976
- "status",
1977
- "shim",
1978
- "recipe",
1979
- "dashboard",
1980
- "launchd",
1981
- ];
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".
1982
2091
  const unknownSub = process.argv[2];
1983
2092
  if (unknownSub &&
1984
2093
  !unknownSub.startsWith("-") &&
1985
- !KNOWN_COMMANDS.includes(unknownSub)) {
2094
+ !KNOWN_SUBCOMMANDS.includes(unknownSub)) {
1986
2095
  const lev = (a, b) => {
1987
2096
  const dp = Array.from({ length: a.length + 1 }, (_, i) => Array.from({ length: b.length + 1 }, (_, j) => i === 0 ? j : j === 0 ? i : 0));
1988
2097
  for (let i = 1; i <= a.length; i++)
@@ -1998,127 +2107,146 @@ if (process.argv[2] === "launchd") {
1998
2107
  // biome-ignore lint/style/noNonNullAssertion: dp is fully pre-allocated
1999
2108
  return dp[a.length][b.length];
2000
2109
  };
2001
- 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];
2002
2111
  console.error(`Unknown command: '${unknownSub}'. Did you mean: ${closest}?`);
2003
2112
  process.exit(1);
2004
2113
  }
2005
2114
  }
2006
- const config = parseConfig(process.argv);
2007
- // Patchwork: resolve --model flag (optional, non-invasive) stashes the
2008
- // configured adapter on globalThis for consumers that opt into the adapter
2009
- // layer. Bridge subprocess driver still works when --model is absent.
2010
- try {
2011
- const { resolveModel } = await import("./patchworkCli.js");
2012
- const resolved = resolveModel(process.argv);
2013
- if (resolved) {
2014
- globalThis.__patchworkAdapter =
2015
- resolved.adapter;
2016
- process.stderr.write(`[patchwork] model adapter initialized: ${resolved.adapter.name}\n`);
2017
- }
2018
- }
2019
- catch (err) {
2020
- process.stderr.write(`[patchwork] adapter init failed: ${err instanceof Error ? err.message : String(err)}\n`);
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.
2021
2124
  }
2022
- // If --analytics flag was passed, persist the preference immediately
2023
- if (config.analyticsEnabled !== null) {
2024
- setAnalyticsPref(config.analyticsEnabled);
2025
- }
2026
- // Auto-tmux: if requested and not already inside tmux or screen, re-exec inside a tmux session
2027
- if (config.autoTmux &&
2028
- !process.env.TMUX &&
2029
- !process.env.STY &&
2030
- !process.env.ZELLIJ &&
2031
- !process.env.ZELLIJ_SESSION_NAME) {
2032
- const ws = config.workspace.replace(/[^a-zA-Z0-9]/g, "").slice(-8);
2033
- const hash = crypto
2034
- .createHash("sha256")
2035
- .update(config.workspace)
2036
- .digest("hex")
2037
- .slice(0, 6);
2038
- const sessionName = `claude-bridge-${ws}${hash}`;
2039
- // Check if tmux is available
2040
- const tmuxCheck = spawnSync("which", ["tmux"], { stdio: "ignore" });
2041
- if (tmuxCheck.status !== 0) {
2042
- process.stderr.write("WARNING: --auto-tmux requested but tmux is not installed. Running without tmux.\n");
2043
- }
2044
- else {
2045
- // Strip --auto-tmux from argv to avoid infinite re-exec loop
2046
- const newArgv = process.argv.filter((a) => a !== "--auto-tmux");
2047
- // Pass each argv token as a separate tmux argument so paths with spaces work correctly
2048
- const result = spawnSync("tmux", ["new-session", "-d", "-s", sessionName, ...newArgv], { stdio: "inherit", timeout: 5000 });
2049
- if (result.status === 0) {
2050
- process.stderr.write(`Bridge launched in tmux session '${sessionName}'.\n`);
2051
- process.stderr.write(` Attach with: tmux attach -t ${sessionName}\n`);
2052
- process.exit(0);
2053
- }
2054
- else {
2055
- // tmux session likely already exists — attach to it or fall through
2056
- process.stderr.write(`WARNING: Could not create tmux session '${sessionName}' (already exists?). Running without auto-tmux.\n`);
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`);
2057
2137
  }
2058
2138
  }
2059
- }
2060
- // --watch: supervisor mode spawn this binary as a child (without --watch) and restart on crash
2061
- if (config.watch) {
2062
- const childArgv = process.argv.filter((a) => a !== "--watch");
2063
- const STABLE_THRESHOLD_MS = 60_000;
2064
- const BASE_DELAY_MS = 2_000;
2065
- const MAX_DELAY_MS = 30_000;
2066
- let delay = BASE_DELAY_MS;
2067
- let stopping = false;
2068
- function runChild() {
2069
- if (stopping)
2070
- return;
2071
- const startAt = Date.now();
2072
- process.stderr.write("[supervisor] starting bridge\n");
2073
- const [cmd, ...args] = childArgv;
2074
- if (!cmd)
2075
- return;
2076
- const child = spawn(cmd, args, {
2077
- stdio: "inherit",
2078
- });
2079
- for (const sig of ["SIGTERM", "SIGINT"]) {
2080
- process.once(sig, () => {
2081
- stopping = true;
2082
- child.kill(sig);
2083
- });
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");
2084
2163
  }
2085
- child.on("exit", (code, signal) => {
2086
- if (stopping) {
2087
- 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`);
2088
2172
  process.exit(0);
2089
2173
  }
2090
- const uptime = Date.now() - startAt;
2091
- if (uptime >= STABLE_THRESHOLD_MS) {
2092
- 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`);
2093
2177
  }
2094
- process.stderr.write(`[supervisor] bridge exited (code=${code ?? signal}), restarting in ${delay / 1000}s\n`);
2095
- setTimeout(() => {
2096
- delay = Math.min(delay * 2, MAX_DELAY_MS);
2097
- runChild();
2098
- }, delay);
2099
- });
2178
+ }
2100
2179
  }
2101
- runChild();
2102
- }
2103
- else {
2104
- const bridge = new Bridge(config);
2105
- bridge.start().catch((err) => {
2106
- const message = err instanceof Error ? err.message : String(err);
2107
- process.stderr.write(`Error: ${message}\n`);
2108
- process.exit(1);
2109
- });
2110
- // F5: Silent self-update nudge (fire-and-forget)
2111
- import("node:child_process")
2112
- .then(({ exec }) => {
2113
- exec("npm view claude-ide-bridge version", { timeout: 5000 }, (err, stdout) => {
2114
- 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)
2115
2202
  return;
2116
- const latest = stdout.trim();
2117
- if (latest && semverGt(latest, PACKAGE_VERSION)) {
2118
- 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
+ });
2119
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);
2120
2236
  });
2121
- })
2122
- .catch(() => { });
2123
- }
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)
2124
2252
  //# sourceMappingURL=index.js.map