patchwork-os 0.2.0-beta.3 → 0.2.0-beta.5

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 (185) hide show
  1. package/README.md +95 -25
  2. package/dist/activityLog.js +2 -1
  3. package/dist/activityLog.js.map +1 -1
  4. package/dist/approvalHttp.js +25 -8
  5. package/dist/approvalHttp.js.map +1 -1
  6. package/dist/approvalQueue.d.ts +44 -1
  7. package/dist/approvalQueue.js +117 -0
  8. package/dist/approvalQueue.js.map +1 -1
  9. package/dist/automation.d.ts +3 -3
  10. package/dist/automation.js +12 -5
  11. package/dist/automation.js.map +1 -1
  12. package/dist/bridge.js +29 -1
  13. package/dist/bridge.js.map +1 -1
  14. package/dist/bridgeLockDiscovery.js +2 -1
  15. package/dist/bridgeLockDiscovery.js.map +1 -1
  16. package/dist/claudeOrchestrator.js +27 -10
  17. package/dist/claudeOrchestrator.js.map +1 -1
  18. package/dist/commands/dashboard.js +8 -1
  19. package/dist/commands/dashboard.js.map +1 -1
  20. package/dist/commands/install.js +3 -0
  21. package/dist/commands/install.js.map +1 -1
  22. package/dist/commands/patchworkInit.js +4 -1
  23. package/dist/commands/patchworkInit.js.map +1 -1
  24. package/dist/commitIssueLinkLog.d.ts +16 -0
  25. package/dist/commitIssueLinkLog.js +87 -4
  26. package/dist/commitIssueLinkLog.js.map +1 -1
  27. package/dist/config.d.ts +20 -1
  28. package/dist/config.js +42 -4
  29. package/dist/config.js.map +1 -1
  30. package/dist/connectorRoutes.js +1 -1
  31. package/dist/connectorRoutes.js.map +1 -1
  32. package/dist/connectors/asana.js +4 -3
  33. package/dist/connectors/asana.js.map +1 -1
  34. package/dist/connectors/confluence.js +35 -0
  35. package/dist/connectors/confluence.js.map +1 -1
  36. package/dist/connectors/datadog.js +33 -4
  37. package/dist/connectors/datadog.js.map +1 -1
  38. package/dist/connectors/discord.js +5 -4
  39. package/dist/connectors/discord.js.map +1 -1
  40. package/dist/connectors/gitlab.js +7 -1
  41. package/dist/connectors/gitlab.js.map +1 -1
  42. package/dist/connectors/mcpOAuth.js +71 -6
  43. package/dist/connectors/mcpOAuth.js.map +1 -1
  44. package/dist/connectors/slack.d.ts +1 -1
  45. package/dist/connectors/slack.js +56 -4
  46. package/dist/connectors/slack.js.map +1 -1
  47. package/dist/connectors/tokenStorage.js +10 -4
  48. package/dist/connectors/tokenStorage.js.map +1 -1
  49. package/dist/decisionTraceLog.d.ts +28 -0
  50. package/dist/decisionTraceLog.js +115 -7
  51. package/dist/decisionTraceLog.js.map +1 -1
  52. package/dist/drivers/claude/subprocess.js +22 -3
  53. package/dist/drivers/claude/subprocess.js.map +1 -1
  54. package/dist/drivers/gemini/index.js +19 -3
  55. package/dist/drivers/gemini/index.js.map +1 -1
  56. package/dist/extensionClient.d.ts +29 -4
  57. package/dist/extensionClient.js +26 -11
  58. package/dist/extensionClient.js.map +1 -1
  59. package/dist/featureFlags.js +18 -32
  60. package/dist/featureFlags.js.map +1 -1
  61. package/dist/fileLockSync.d.ts +67 -0
  62. package/dist/fileLockSync.js +126 -0
  63. package/dist/fileLockSync.js.map +1 -0
  64. package/dist/fp/automationInterpreter.d.ts +6 -0
  65. package/dist/fp/automationInterpreter.js +15 -2
  66. package/dist/fp/automationInterpreter.js.map +1 -1
  67. package/dist/fp/automationState.d.ts +1 -1
  68. package/dist/fp/automationState.js +10 -0
  69. package/dist/fp/automationState.js.map +1 -1
  70. package/dist/fp/commandDescription.js +7 -1
  71. package/dist/fp/commandDescription.js.map +1 -1
  72. package/dist/fsWatchWithFallback.d.ts +36 -0
  73. package/dist/fsWatchWithFallback.js +127 -0
  74. package/dist/fsWatchWithFallback.js.map +1 -0
  75. package/dist/index.js +108 -48
  76. package/dist/index.js.map +1 -1
  77. package/dist/installGuard.js +6 -2
  78. package/dist/installGuard.js.map +1 -1
  79. package/dist/lockfile.js +27 -3
  80. package/dist/lockfile.js.map +1 -1
  81. package/dist/patchworkConfig.js +8 -3
  82. package/dist/patchworkConfig.js.map +1 -1
  83. package/dist/pluginLoader.js +10 -1
  84. package/dist/pluginLoader.js.map +1 -1
  85. package/dist/pluginWatcher.js +6 -13
  86. package/dist/pluginWatcher.js.map +1 -1
  87. package/dist/preToolUseHook.js +3 -2
  88. package/dist/preToolUseHook.js.map +1 -1
  89. package/dist/processTree.d.ts +34 -0
  90. package/dist/processTree.js +105 -0
  91. package/dist/processTree.js.map +1 -0
  92. package/dist/prompts.js +3 -3
  93. package/dist/prompts.js.map +1 -1
  94. package/dist/recipeOrchestration.js +58 -8
  95. package/dist/recipeOrchestration.js.map +1 -1
  96. package/dist/recipeRoutes.d.ts +1 -0
  97. package/dist/recipeRoutes.js +100 -15
  98. package/dist/recipeRoutes.js.map +1 -1
  99. package/dist/recipes/connectorPreflight.js +64 -0
  100. package/dist/recipes/connectorPreflight.js.map +1 -1
  101. package/dist/recipes/idempotencyKey.js +3 -4
  102. package/dist/recipes/idempotencyKey.js.map +1 -1
  103. package/dist/recipes/installer.js +48 -2
  104. package/dist/recipes/installer.js.map +1 -1
  105. package/dist/recipes/parser.js +82 -4
  106. package/dist/recipes/parser.js.map +1 -1
  107. package/dist/recipes/scheduler.d.ts +17 -0
  108. package/dist/recipes/scheduler.js +33 -1
  109. package/dist/recipes/scheduler.js.map +1 -1
  110. package/dist/recipes/yamlRunner.d.ts +4 -1
  111. package/dist/recipes/yamlRunner.js +18 -6
  112. package/dist/recipes/yamlRunner.js.map +1 -1
  113. package/dist/resources.js +21 -13
  114. package/dist/resources.js.map +1 -1
  115. package/dist/runLog.js +14 -3
  116. package/dist/runLog.js.map +1 -1
  117. package/dist/sanitizeParsedJson.d.ts +39 -0
  118. package/dist/sanitizeParsedJson.js +55 -0
  119. package/dist/sanitizeParsedJson.js.map +1 -0
  120. package/dist/server.d.ts +14 -0
  121. package/dist/server.js +105 -33
  122. package/dist/server.js.map +1 -1
  123. package/dist/sessionCheckpoint.d.ts +8 -0
  124. package/dist/sessionCheckpoint.js +18 -2
  125. package/dist/sessionCheckpoint.js.map +1 -1
  126. package/dist/tools/detectUnusedCode.js +9 -7
  127. package/dist/tools/detectUnusedCode.js.map +1 -1
  128. package/dist/tools/editText.js +2 -1
  129. package/dist/tools/editText.js.map +1 -1
  130. package/dist/tools/fileOperations.js +2 -1
  131. package/dist/tools/fileOperations.js.map +1 -1
  132. package/dist/tools/fileWatcher.js +8 -2
  133. package/dist/tools/fileWatcher.js.map +1 -1
  134. package/dist/tools/fixAllLintErrors.js +10 -5
  135. package/dist/tools/fixAllLintErrors.js.map +1 -1
  136. package/dist/tools/formatDocument.js +10 -5
  137. package/dist/tools/formatDocument.js.map +1 -1
  138. package/dist/tools/handoffNote.js +2 -1
  139. package/dist/tools/handoffNote.js.map +1 -1
  140. package/dist/tools/headless/lspClient.js +3 -0
  141. package/dist/tools/headless/lspClient.js.map +1 -1
  142. package/dist/tools/index.js +0 -6
  143. package/dist/tools/index.js.map +1 -1
  144. package/dist/tools/lsp.js +17 -0
  145. package/dist/tools/lsp.js.map +1 -1
  146. package/dist/tools/openDiff.js +4 -1
  147. package/dist/tools/openDiff.js.map +1 -1
  148. package/dist/tools/openFile.js +4 -1
  149. package/dist/tools/openFile.js.map +1 -1
  150. package/dist/tools/organizeImports.js +5 -3
  151. package/dist/tools/organizeImports.js.map +1 -1
  152. package/dist/tools/previewEdit.js +7 -2
  153. package/dist/tools/previewEdit.js.map +1 -1
  154. package/dist/tools/refactorExtractFunction.js +4 -1
  155. package/dist/tools/refactorExtractFunction.js.map +1 -1
  156. package/dist/tools/refactorPreview.js +10 -2
  157. package/dist/tools/refactorPreview.js.map +1 -1
  158. package/dist/tools/replaceBlock.js +2 -1
  159. package/dist/tools/replaceBlock.js.map +1 -1
  160. package/dist/tools/searchAndReplace.js +2 -1
  161. package/dist/tools/searchAndReplace.js.map +1 -1
  162. package/dist/tools/spawnWorkspace.js +15 -7
  163. package/dist/tools/spawnWorkspace.js.map +1 -1
  164. package/dist/tools/transaction.js +4 -1
  165. package/dist/tools/transaction.js.map +1 -1
  166. package/dist/tools/utils.js +62 -5
  167. package/dist/tools/utils.js.map +1 -1
  168. package/dist/transport.d.ts +1 -1
  169. package/dist/transport.js +18 -4
  170. package/dist/transport.js.map +1 -1
  171. package/dist/winShim.d.ts +34 -0
  172. package/dist/winShim.js +94 -0
  173. package/dist/winShim.js.map +1 -0
  174. package/dist/writeFileAtomic.d.ts +23 -0
  175. package/dist/writeFileAtomic.js +94 -0
  176. package/dist/writeFileAtomic.js.map +1 -0
  177. package/package.json +1 -1
  178. package/scripts/postinstall.mjs +18 -5
  179. package/scripts/smoke/run-all.mjs +55 -4
  180. package/scripts/start-all.mjs +60 -1
  181. package/scripts/start-all.ps1 +209 -209
  182. package/scripts/start-orchestrator.ps1 +158 -158
  183. package/dist/tools/ccRoutines.d.ts +0 -221
  184. package/dist/tools/ccRoutines.js +0 -264
  185. package/dist/tools/ccRoutines.js.map +0 -1
@@ -58,6 +58,56 @@ const DASH_DIR = path.join(BRIDGE_DIR, "dashboard");
58
58
  const DIST_INDEX = path.join(BRIDGE_DIR, "dist", "index.js");
59
59
  const IS_WIN = process.platform === "win32";
60
60
 
61
+ // ── Security helpers ──────────────────────────────────────────────────────────
62
+ // On Windows `\` is the path separator (e.g. `C:\Program Files\nodejs\node.exe`),
63
+ // not a shell-injection vector. cmd.exe's actual metacharacters are
64
+ // `^ & < > | ( )` — `\` does not need escaping. Including it in the regex
65
+ // would reject every legitimate Windows path that spawnProc validates
66
+ // (process.execPath, npm.cmd, npx.cmd, etc.) and fail-stop the whole script.
67
+ // Same fix that PR #525 applied to vscode-extension/src/bridgeProcess.ts.
68
+ const SHELL_METACHARACTERS = IS_WIN
69
+ ? /[;&|`$(){}[\]<>"'\n\r]/
70
+ : /[;&|`$(){}[\]<>"'\\\n\r]/;
71
+
72
+ /**
73
+ * Validate that a command path is safe to execute.
74
+ * Prevents command injection by checking for shell metacharacters.
75
+ * @param {string} cmdPath - Path to validate
76
+ * @param {string} label - Label for error messages
77
+ * @throws {Error} If path contains shell metacharacters or is empty
78
+ */
79
+ function validateCommandPath(cmdPath, label) {
80
+ if (!cmdPath || typeof cmdPath !== "string") {
81
+ throw new Error(`${label}: command path is empty or invalid`);
82
+ }
83
+ if (SHELL_METACHARACTERS.test(cmdPath)) {
84
+ throw new Error(
85
+ `${label}: command path contains shell metacharacters: ${cmdPath}`,
86
+ );
87
+ }
88
+ // Additional check: on Windows, .cmd files are allowed but must not have spaces without proper quoting
89
+ if (IS_WIN && cmdPath.endsWith(".cmd") && cmdPath.includes(" ")) {
90
+ // This is handled by using cmd.exe explicitly, not shell:true
91
+ // We validate the path doesn't have injection chars above
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Validate command arguments don't contain injection vectors.
97
+ * @param {string[]} args - Arguments to validate
98
+ * @param {string} label - Label for error messages
99
+ */
100
+ function validateCommandArgs(args, label) {
101
+ if (!Array.isArray(args)) {
102
+ throw new Error(`${label}: arguments must be an array`);
103
+ }
104
+ for (const arg of args) {
105
+ if (typeof arg !== "string") {
106
+ throw new Error(`${label}: all arguments must be strings`);
107
+ }
108
+ }
109
+ }
110
+
61
111
  // ── Colour helpers ────────────────────────────────────────────────────────────
62
112
  const C = {
63
113
  cyan: (s) => `\x1b[36m${s}\x1b[0m`,
@@ -98,6 +148,10 @@ function notify(msg, priority = "default") {
98
148
  const procs = new Map(); // name → ChildProcess
99
149
 
100
150
  function spawnProc(name, cmd, cmdArgs, opts = {}) {
151
+ // Validate command and arguments to prevent injection attacks
152
+ validateCommandPath(cmd, `spawnProc[${name}]`);
153
+ validateCommandArgs(cmdArgs, `spawnProc[${name}]`);
154
+
101
155
  // shell:false everywhere — on Windows we always invoke cmd.exe explicitly
102
156
  // for .cmd shim resolution, so shell:true would only widen the attack
103
157
  // surface by interpolating env-derived paths into a shell string.
@@ -238,7 +292,12 @@ function readLock(lockPath) {
238
292
  function bridgeBin() {
239
293
  if (fs.existsSync(DIST_INDEX)) return [process.execPath, [DIST_INDEX]];
240
294
  const srcIndex = path.join(BRIDGE_DIR, "src", "index.ts");
241
- if (fs.existsSync(srcIndex)) return ["npx", ["tsx", srcIndex]];
295
+ if (fs.existsSync(srcIndex)) {
296
+ // On Windows the spawnProc helper uses shell:false, so we must point at
297
+ // the .cmd shim directly — bare "npx" would ENOENT.
298
+ const npx = IS_WIN ? "npx.cmd" : "npx";
299
+ return [npx, ["tsx", srcIndex]];
300
+ }
242
301
  console.error("Error: dist/index.js not found. Run 'npm run build' first.");
243
302
  process.exit(1);
244
303
  }
@@ -1,209 +1,209 @@
1
- #Requires -Version 5.1
2
- <#
3
- .SYNOPSIS
4
- Windows orchestrator for bridge + Claude + Patchwork dashboard.
5
-
6
- .DESCRIPTION
7
- Cross-platform alternative to start-all.sh for native Windows (PowerShell/cmd).
8
- Starts the bridge, waits for the lock file, launches Claude --ide, and
9
- optionally starts the Patchwork dashboard dev server and opens it in the browser.
10
-
11
- Run via npm:
12
- npm run start:bridge # bridge only (simplest)
13
- npm run start-all:win # full orchestrator (bridge + claude + dashboard)
14
-
15
- Or directly:
16
- pwsh -File scripts\start-all.ps1
17
- pwsh -File scripts\start-all.ps1 --no-dashboard
18
- pwsh -File scripts\start-all.ps1 --workspace C:\my\project --dashboard-port 3200
19
-
20
- .PARAMETER Workspace
21
- Directory to open in Claude (default: current directory).
22
-
23
- .PARAMETER Full
24
- Pass --full to the bridge, registering all ~95 tools including git/terminal/file ops.
25
- Default is slim mode (27 IDE-exclusive tools).
26
-
27
- .PARAMETER NoDashboard
28
- Skip starting the Patchwork dashboard.
29
-
30
- .PARAMETER DashboardPort
31
- Port for the Next.js dashboard dev server (default: 3200).
32
-
33
- .PARAMETER BridgePort
34
- Port for the bridge (default: auto-assigned via lock file).
35
-
36
- .PARAMETER Notify
37
- ntfy.sh topic for push notifications (optional).
38
- #>
39
- [CmdletBinding()]
40
- param(
41
- [string]$Workspace = ".",
42
- [switch]$Full,
43
- [switch]$NoDashboard,
44
- [int] $DashboardPort = 3200,
45
- [int] $BridgePort = 0,
46
- [string]$Notify = ""
47
- )
48
-
49
- Set-StrictMode -Version Latest
50
- $ErrorActionPreference = "Stop"
51
-
52
- # ── Resolve paths ─────────────────────────────────────────────────────────────
53
- $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
54
- $BridgeDir = Split-Path -Parent $ScriptDir
55
- $DashboardDir = Join-Path $BridgeDir "dashboard"
56
-
57
- try {
58
- $Workspace = (Resolve-Path $Workspace).Path
59
- } catch {
60
- Write-Error "Workspace directory not found: $Workspace"
61
- exit 1
62
- }
63
-
64
- # ── Helpers ───────────────────────────────────────────────────────────────────
65
- function Write-Status($msg) { Write-Host "[orchestrator] $msg" -ForegroundColor Cyan }
66
- function Write-Ok($msg) { Write-Host "[ok] $msg" -ForegroundColor Green }
67
- function Write-Warn($msg) { Write-Host "[warn] $msg" -ForegroundColor Yellow }
68
-
69
- function Send-Notify($msg) {
70
- if (-not $Notify) { return }
71
- try {
72
- Invoke-RestMethod -Uri "https://ntfy.sh/$Notify" -Method Post -Body $msg -TimeoutSec 5 | Out-Null
73
- } catch { Write-Warn "ntfy notification failed: $_" }
74
- }
75
-
76
- # ── Track child processes for cleanup ─────────────────────────────────────────
77
- $Jobs = [System.Collections.Generic.List[System.Diagnostics.Process]]::new()
78
-
79
- function Stop-AllJobs {
80
- foreach ($p in $Jobs) {
81
- if (-not $p.HasExited) {
82
- try { $p.Kill($true) } catch { }
83
- }
84
- }
85
- }
86
-
87
- # Clean up on Ctrl+C or exit
88
- $null = Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Action { Stop-AllJobs }
89
- try { [Console]::TreatControlCAsInput = $false } catch { }
90
-
91
- # ── Build bridge args ─────────────────────────────────────────────────────────
92
- $BridgeArgs = @("--workspace", $Workspace)
93
- if ($BridgePort -gt 0) { $BridgeArgs += @("--port", $BridgePort) }
94
- if ($Full) { $BridgeArgs += "--full" }
95
-
96
- # ── Start bridge ──────────────────────────────────────────────────────────────
97
- Write-Status "Starting bridge (workspace: $Workspace)..."
98
-
99
- $BridgeInfo = New-Object System.Diagnostics.ProcessStartInfo
100
- $BridgeInfo.UseShellExecute = $false
101
- # On Windows, npm global bins are .cmd wrappers — must invoke via cmd.exe.
102
- # Quote each argument so workspace paths with spaces (e.g. "C:\Users\Jane Doe\...") survive.
103
- $BridgeInfo.FileName = "cmd.exe"
104
- $quotedArgs = $BridgeArgs | ForEach-Object { if ($_ -match '\s') { "`"$_`"" } else { $_ } }
105
- $BridgeInfo.Arguments = "/c claude-ide-bridge " + ($quotedArgs -join " ")
106
-
107
- $BridgeProc = [System.Diagnostics.Process]::Start($BridgeInfo)
108
- $Jobs.Add($BridgeProc)
109
-
110
- # ── Wait for lock file ────────────────────────────────────────────────────────
111
- Write-Status "Waiting for bridge lock file..."
112
- $ClaudeBase = if ($env:CLAUDE_CONFIG_DIR) { $env:CLAUDE_CONFIG_DIR } else { Join-Path $env:USERPROFILE ".claude" }
113
- $IdeDir = Join-Path $ClaudeBase "ide"
114
- $Deadline = (Get-Date).AddSeconds(30)
115
- $LockFile = $null
116
-
117
- while ((Get-Date) -lt $Deadline) {
118
- $locks = Get-ChildItem -Path $IdeDir -Filter "*.lock" -ErrorAction SilentlyContinue |
119
- Where-Object { $_.LastWriteTime -gt (Get-Date).AddSeconds(-60) }
120
- if ($locks) {
121
- # Find the lock that matches our workspace
122
- foreach ($lf in $locks) {
123
- try {
124
- $content = Get-Content $lf.FullName -Raw | ConvertFrom-Json
125
- if ($content.isBridge -and
126
- ($content.workspace -replace '\\','/' ) -eq ($Workspace -replace '\\','/')) {
127
- $LockFile = $lf
128
- $DetectedPort = [int][System.IO.Path]::GetFileNameWithoutExtension($lf.Name)
129
- break
130
- }
131
- } catch { }
132
- }
133
- if ($LockFile) { break }
134
- }
135
- Start-Sleep -Milliseconds 200
136
- }
137
-
138
- if (-not $LockFile) {
139
- Write-Error "Bridge lock file not written after 30s. Bridge may have failed to start."
140
- Stop-AllJobs
141
- exit 1
142
- }
143
-
144
- Write-Ok "Bridge ready on port $DetectedPort"
145
- Send-Notify "Bridge started on port $DetectedPort"
146
-
147
- # ── Start Claude --ide ────────────────────────────────────────────────────────
148
- Write-Status "Starting claude --ide..."
149
- $ClaudeInfo = New-Object System.Diagnostics.ProcessStartInfo
150
- $ClaudeInfo.FileName = "cmd.exe"
151
- $ClaudeInfo.Arguments = "/c claude --ide"
152
- $ClaudeInfo.UseShellExecute = $false
153
- $ClaudeProc = [System.Diagnostics.Process]::Start($ClaudeInfo)
154
- $Jobs.Add($ClaudeProc)
155
-
156
- # ── Start dashboard ───────────────────────────────────────────────────────────
157
- $DashProc = $null
158
- if (-not $NoDashboard) {
159
- if (-not (Test-Path (Join-Path $DashboardDir "node_modules"))) {
160
- Write-Warn "dashboard/node_modules not found. Run 'npm ci' in the dashboard directory first, or pass -NoDashboard."
161
- } else {
162
- Write-Status "Starting dashboard on http://localhost:$DashboardPort ..."
163
-
164
- $env:PATCHWORK_BRIDGE_PORT = $DetectedPort
165
- $DashInfo = New-Object System.Diagnostics.ProcessStartInfo
166
- $DashInfo.FileName = "cmd.exe"
167
- $DashInfo.Arguments = "/c npx next dev -p $DashboardPort"
168
- $DashInfo.WorkingDirectory = $DashboardDir
169
- $DashInfo.UseShellExecute = $false
170
- $DashProc = [System.Diagnostics.Process]::Start($DashInfo)
171
- $Jobs.Add($DashProc)
172
-
173
- # Poll until Next.js answers, then open the browser
174
- $DashUrl = "http://localhost:$DashboardPort"
175
- $DashDeadline = (Get-Date).AddSeconds(60)
176
- $Opened = $false
177
- while ((Get-Date) -lt $DashDeadline) {
178
- try {
179
- $r = Invoke-WebRequest -Uri $DashUrl -UseBasicParsing -TimeoutSec 1 -ErrorAction Stop
180
- if ($r.StatusCode -lt 500) {
181
- Write-Ok "Dashboard ready — opening $DashUrl"
182
- Start-Process $DashUrl # opens default browser on Windows
183
- $Opened = $true
184
- break
185
- }
186
- } catch { }
187
- Start-Sleep -Milliseconds 1000
188
- }
189
- if (-not $Opened) {
190
- Write-Warn "Dashboard did not respond within 60s — open $DashUrl manually."
191
- }
192
- }
193
- }
194
-
195
- # ── Wait ──────────────────────────────────────────────────────────────────────
196
- Write-Host ""
197
- Write-Ok "All processes started. Press Ctrl+C to stop."
198
- Write-Host " Bridge PID : $($BridgeProc.Id)"
199
- Write-Host " Claude PID : $($ClaudeProc.Id)"
200
- if ($DashProc) { Write-Host " Dashboard : http://localhost:$DashboardPort (PID $($DashProc.Id))" }
201
- Write-Host ""
202
-
203
- try {
204
- # Block until the bridge exits (primary process)
205
- $BridgeProc.WaitForExit()
206
- } finally {
207
- Write-Status "Bridge exited — stopping all processes..."
208
- Stop-AllJobs
209
- }
1
+ #Requires -Version 5.1
2
+ <#
3
+ .SYNOPSIS
4
+ Windows orchestrator for bridge + Claude + Patchwork dashboard.
5
+
6
+ .DESCRIPTION
7
+ Cross-platform alternative to start-all.sh for native Windows (PowerShell/cmd).
8
+ Starts the bridge, waits for the lock file, launches Claude --ide, and
9
+ optionally starts the Patchwork dashboard dev server and opens it in the browser.
10
+
11
+ Run via npm:
12
+ npm run start:bridge # bridge only (simplest)
13
+ npm run start-all:win # full orchestrator (bridge + claude + dashboard)
14
+
15
+ Or directly:
16
+ pwsh -File scripts\start-all.ps1
17
+ pwsh -File scripts\start-all.ps1 --no-dashboard
18
+ pwsh -File scripts\start-all.ps1 --workspace C:\my\project --dashboard-port 3200
19
+
20
+ .PARAMETER Workspace
21
+ Directory to open in Claude (default: current directory).
22
+
23
+ .PARAMETER Full
24
+ Pass --full to the bridge, registering all ~95 tools including git/terminal/file ops.
25
+ Default is slim mode (27 IDE-exclusive tools).
26
+
27
+ .PARAMETER NoDashboard
28
+ Skip starting the Patchwork dashboard.
29
+
30
+ .PARAMETER DashboardPort
31
+ Port for the Next.js dashboard dev server (default: 3200).
32
+
33
+ .PARAMETER BridgePort
34
+ Port for the bridge (default: auto-assigned via lock file).
35
+
36
+ .PARAMETER Notify
37
+ ntfy.sh topic for push notifications (optional).
38
+ #>
39
+ [CmdletBinding()]
40
+ param(
41
+ [string]$Workspace = ".",
42
+ [switch]$Full,
43
+ [switch]$NoDashboard,
44
+ [int] $DashboardPort = 3200,
45
+ [int] $BridgePort = 0,
46
+ [string]$Notify = ""
47
+ )
48
+
49
+ Set-StrictMode -Version Latest
50
+ $ErrorActionPreference = "Stop"
51
+
52
+ # ── Resolve paths ─────────────────────────────────────────────────────────────
53
+ $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
54
+ $BridgeDir = Split-Path -Parent $ScriptDir
55
+ $DashboardDir = Join-Path $BridgeDir "dashboard"
56
+
57
+ try {
58
+ $Workspace = (Resolve-Path $Workspace).Path
59
+ } catch {
60
+ Write-Error "Workspace directory not found: $Workspace"
61
+ exit 1
62
+ }
63
+
64
+ # ── Helpers ───────────────────────────────────────────────────────────────────
65
+ function Write-Status($msg) { Write-Host "[orchestrator] $msg" -ForegroundColor Cyan }
66
+ function Write-Ok($msg) { Write-Host "[ok] $msg" -ForegroundColor Green }
67
+ function Write-Warn($msg) { Write-Host "[warn] $msg" -ForegroundColor Yellow }
68
+
69
+ function Send-Notify($msg) {
70
+ if (-not $Notify) { return }
71
+ try {
72
+ Invoke-RestMethod -Uri "https://ntfy.sh/$Notify" -Method Post -Body $msg -TimeoutSec 5 | Out-Null
73
+ } catch { Write-Warn "ntfy notification failed: $_" }
74
+ }
75
+
76
+ # ── Track child processes for cleanup ─────────────────────────────────────────
77
+ $Jobs = [System.Collections.Generic.List[System.Diagnostics.Process]]::new()
78
+
79
+ function Stop-AllJobs {
80
+ foreach ($p in $Jobs) {
81
+ if (-not $p.HasExited) {
82
+ try { $p.Kill($true) } catch { }
83
+ }
84
+ }
85
+ }
86
+
87
+ # Clean up on Ctrl+C or exit
88
+ $null = Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Action { Stop-AllJobs }
89
+ try { [Console]::TreatControlCAsInput = $false } catch { }
90
+
91
+ # ── Build bridge args ─────────────────────────────────────────────────────────
92
+ $BridgeArgs = @("--workspace", $Workspace)
93
+ if ($BridgePort -gt 0) { $BridgeArgs += @("--port", $BridgePort) }
94
+ if ($Full) { $BridgeArgs += "--full" }
95
+
96
+ # ── Start bridge ──────────────────────────────────────────────────────────────
97
+ Write-Status "Starting bridge (workspace: $Workspace)..."
98
+
99
+ $BridgeInfo = New-Object System.Diagnostics.ProcessStartInfo
100
+ $BridgeInfo.UseShellExecute = $false
101
+ # On Windows, npm global bins are .cmd wrappers — must invoke via cmd.exe.
102
+ # Quote each argument so workspace paths with spaces (e.g. "C:\Users\Jane Doe\...") survive.
103
+ $BridgeInfo.FileName = "cmd.exe"
104
+ $quotedArgs = $BridgeArgs | ForEach-Object { if ($_ -match '\s') { "`"$_`"" } else { $_ } }
105
+ $BridgeInfo.Arguments = "/c claude-ide-bridge " + ($quotedArgs -join " ")
106
+
107
+ $BridgeProc = [System.Diagnostics.Process]::Start($BridgeInfo)
108
+ $Jobs.Add($BridgeProc)
109
+
110
+ # ── Wait for lock file ────────────────────────────────────────────────────────
111
+ Write-Status "Waiting for bridge lock file..."
112
+ $ClaudeBase = if ($env:CLAUDE_CONFIG_DIR) { $env:CLAUDE_CONFIG_DIR } else { Join-Path $env:USERPROFILE ".claude" }
113
+ $IdeDir = Join-Path $ClaudeBase "ide"
114
+ $Deadline = (Get-Date).AddSeconds(30)
115
+ $LockFile = $null
116
+
117
+ while ((Get-Date) -lt $Deadline) {
118
+ $locks = Get-ChildItem -Path $IdeDir -Filter "*.lock" -ErrorAction SilentlyContinue |
119
+ Where-Object { $_.LastWriteTime -gt (Get-Date).AddSeconds(-60) }
120
+ if ($locks) {
121
+ # Find the lock that matches our workspace
122
+ foreach ($lf in $locks) {
123
+ try {
124
+ $content = Get-Content $lf.FullName -Raw | ConvertFrom-Json
125
+ if ($content.isBridge -and
126
+ ($content.workspace -replace '\\','/' ) -eq ($Workspace -replace '\\','/')) {
127
+ $LockFile = $lf
128
+ $DetectedPort = [int][System.IO.Path]::GetFileNameWithoutExtension($lf.Name)
129
+ break
130
+ }
131
+ } catch { }
132
+ }
133
+ if ($LockFile) { break }
134
+ }
135
+ Start-Sleep -Milliseconds 200
136
+ }
137
+
138
+ if (-not $LockFile) {
139
+ Write-Error "Bridge lock file not written after 30s. Bridge may have failed to start."
140
+ Stop-AllJobs
141
+ exit 1
142
+ }
143
+
144
+ Write-Ok "Bridge ready on port $DetectedPort"
145
+ Send-Notify "Bridge started on port $DetectedPort"
146
+
147
+ # ── Start Claude --ide ────────────────────────────────────────────────────────
148
+ Write-Status "Starting claude --ide..."
149
+ $ClaudeInfo = New-Object System.Diagnostics.ProcessStartInfo
150
+ $ClaudeInfo.FileName = "cmd.exe"
151
+ $ClaudeInfo.Arguments = "/c claude --ide"
152
+ $ClaudeInfo.UseShellExecute = $false
153
+ $ClaudeProc = [System.Diagnostics.Process]::Start($ClaudeInfo)
154
+ $Jobs.Add($ClaudeProc)
155
+
156
+ # ── Start dashboard ───────────────────────────────────────────────────────────
157
+ $DashProc = $null
158
+ if (-not $NoDashboard) {
159
+ if (-not (Test-Path (Join-Path $DashboardDir "node_modules"))) {
160
+ Write-Warn "dashboard/node_modules not found. Run 'npm ci' in the dashboard directory first, or pass -NoDashboard."
161
+ } else {
162
+ Write-Status "Starting dashboard on http://localhost:$DashboardPort ..."
163
+
164
+ $env:PATCHWORK_BRIDGE_PORT = $DetectedPort
165
+ $DashInfo = New-Object System.Diagnostics.ProcessStartInfo
166
+ $DashInfo.FileName = "cmd.exe"
167
+ $DashInfo.Arguments = "/c npx next dev -p $DashboardPort"
168
+ $DashInfo.WorkingDirectory = $DashboardDir
169
+ $DashInfo.UseShellExecute = $false
170
+ $DashProc = [System.Diagnostics.Process]::Start($DashInfo)
171
+ $Jobs.Add($DashProc)
172
+
173
+ # Poll until Next.js answers, then open the browser
174
+ $DashUrl = "http://localhost:$DashboardPort"
175
+ $DashDeadline = (Get-Date).AddSeconds(60)
176
+ $Opened = $false
177
+ while ((Get-Date) -lt $DashDeadline) {
178
+ try {
179
+ $r = Invoke-WebRequest -Uri $DashUrl -UseBasicParsing -TimeoutSec 1 -ErrorAction Stop
180
+ if ($r.StatusCode -lt 500) {
181
+ Write-Ok "Dashboard ready — opening $DashUrl"
182
+ Start-Process $DashUrl # opens default browser on Windows
183
+ $Opened = $true
184
+ break
185
+ }
186
+ } catch { }
187
+ Start-Sleep -Milliseconds 1000
188
+ }
189
+ if (-not $Opened) {
190
+ Write-Warn "Dashboard did not respond within 60s — open $DashUrl manually."
191
+ }
192
+ }
193
+ }
194
+
195
+ # ── Wait ──────────────────────────────────────────────────────────────────────
196
+ Write-Host ""
197
+ Write-Ok "All processes started. Press Ctrl+C to stop."
198
+ Write-Host " Bridge PID : $($BridgeProc.Id)"
199
+ Write-Host " Claude PID : $($ClaudeProc.Id)"
200
+ if ($DashProc) { Write-Host " Dashboard : http://localhost:$DashboardPort (PID $($DashProc.Id))" }
201
+ Write-Host ""
202
+
203
+ try {
204
+ # Block until the bridge exits (primary process)
205
+ $BridgeProc.WaitForExit()
206
+ } finally {
207
+ Write-Status "Bridge exited — stopping all processes..."
208
+ Stop-AllJobs
209
+ }