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.
- package/README.md +95 -25
- package/dist/activityLog.js +2 -1
- package/dist/activityLog.js.map +1 -1
- package/dist/approvalHttp.js +25 -8
- package/dist/approvalHttp.js.map +1 -1
- package/dist/approvalQueue.d.ts +44 -1
- package/dist/approvalQueue.js +117 -0
- package/dist/approvalQueue.js.map +1 -1
- package/dist/automation.d.ts +3 -3
- package/dist/automation.js +12 -5
- package/dist/automation.js.map +1 -1
- package/dist/bridge.js +29 -1
- package/dist/bridge.js.map +1 -1
- package/dist/bridgeLockDiscovery.js +2 -1
- package/dist/bridgeLockDiscovery.js.map +1 -1
- package/dist/claudeOrchestrator.js +27 -10
- package/dist/claudeOrchestrator.js.map +1 -1
- package/dist/commands/dashboard.js +8 -1
- package/dist/commands/dashboard.js.map +1 -1
- package/dist/commands/install.js +3 -0
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/patchworkInit.js +4 -1
- package/dist/commands/patchworkInit.js.map +1 -1
- package/dist/commitIssueLinkLog.d.ts +16 -0
- package/dist/commitIssueLinkLog.js +87 -4
- package/dist/commitIssueLinkLog.js.map +1 -1
- package/dist/config.d.ts +20 -1
- package/dist/config.js +42 -4
- package/dist/config.js.map +1 -1
- package/dist/connectorRoutes.js +1 -1
- package/dist/connectorRoutes.js.map +1 -1
- package/dist/connectors/asana.js +4 -3
- package/dist/connectors/asana.js.map +1 -1
- package/dist/connectors/confluence.js +35 -0
- package/dist/connectors/confluence.js.map +1 -1
- package/dist/connectors/datadog.js +33 -4
- package/dist/connectors/datadog.js.map +1 -1
- package/dist/connectors/discord.js +5 -4
- package/dist/connectors/discord.js.map +1 -1
- package/dist/connectors/gitlab.js +7 -1
- package/dist/connectors/gitlab.js.map +1 -1
- package/dist/connectors/mcpOAuth.js +71 -6
- package/dist/connectors/mcpOAuth.js.map +1 -1
- package/dist/connectors/slack.d.ts +1 -1
- package/dist/connectors/slack.js +56 -4
- package/dist/connectors/slack.js.map +1 -1
- package/dist/connectors/tokenStorage.js +10 -4
- package/dist/connectors/tokenStorage.js.map +1 -1
- package/dist/decisionTraceLog.d.ts +28 -0
- package/dist/decisionTraceLog.js +115 -7
- package/dist/decisionTraceLog.js.map +1 -1
- package/dist/drivers/claude/subprocess.js +22 -3
- package/dist/drivers/claude/subprocess.js.map +1 -1
- package/dist/drivers/gemini/index.js +19 -3
- package/dist/drivers/gemini/index.js.map +1 -1
- package/dist/extensionClient.d.ts +29 -4
- package/dist/extensionClient.js +26 -11
- package/dist/extensionClient.js.map +1 -1
- package/dist/featureFlags.js +18 -32
- package/dist/featureFlags.js.map +1 -1
- package/dist/fileLockSync.d.ts +67 -0
- package/dist/fileLockSync.js +126 -0
- package/dist/fileLockSync.js.map +1 -0
- package/dist/fp/automationInterpreter.d.ts +6 -0
- package/dist/fp/automationInterpreter.js +15 -2
- package/dist/fp/automationInterpreter.js.map +1 -1
- package/dist/fp/automationState.d.ts +1 -1
- package/dist/fp/automationState.js +10 -0
- package/dist/fp/automationState.js.map +1 -1
- package/dist/fp/commandDescription.js +7 -1
- package/dist/fp/commandDescription.js.map +1 -1
- package/dist/fsWatchWithFallback.d.ts +36 -0
- package/dist/fsWatchWithFallback.js +127 -0
- package/dist/fsWatchWithFallback.js.map +1 -0
- package/dist/index.js +108 -48
- package/dist/index.js.map +1 -1
- package/dist/installGuard.js +6 -2
- package/dist/installGuard.js.map +1 -1
- package/dist/lockfile.js +27 -3
- package/dist/lockfile.js.map +1 -1
- package/dist/patchworkConfig.js +8 -3
- package/dist/patchworkConfig.js.map +1 -1
- package/dist/pluginLoader.js +10 -1
- package/dist/pluginLoader.js.map +1 -1
- package/dist/pluginWatcher.js +6 -13
- package/dist/pluginWatcher.js.map +1 -1
- package/dist/preToolUseHook.js +3 -2
- package/dist/preToolUseHook.js.map +1 -1
- package/dist/processTree.d.ts +34 -0
- package/dist/processTree.js +105 -0
- package/dist/processTree.js.map +1 -0
- package/dist/prompts.js +3 -3
- package/dist/prompts.js.map +1 -1
- package/dist/recipeOrchestration.js +58 -8
- package/dist/recipeOrchestration.js.map +1 -1
- package/dist/recipeRoutes.d.ts +1 -0
- package/dist/recipeRoutes.js +100 -15
- package/dist/recipeRoutes.js.map +1 -1
- package/dist/recipes/connectorPreflight.js +64 -0
- package/dist/recipes/connectorPreflight.js.map +1 -1
- package/dist/recipes/idempotencyKey.js +3 -4
- package/dist/recipes/idempotencyKey.js.map +1 -1
- package/dist/recipes/installer.js +48 -2
- package/dist/recipes/installer.js.map +1 -1
- package/dist/recipes/parser.js +82 -4
- package/dist/recipes/parser.js.map +1 -1
- package/dist/recipes/scheduler.d.ts +17 -0
- package/dist/recipes/scheduler.js +33 -1
- package/dist/recipes/scheduler.js.map +1 -1
- package/dist/recipes/yamlRunner.d.ts +4 -1
- package/dist/recipes/yamlRunner.js +18 -6
- package/dist/recipes/yamlRunner.js.map +1 -1
- package/dist/resources.js +21 -13
- package/dist/resources.js.map +1 -1
- package/dist/runLog.js +14 -3
- package/dist/runLog.js.map +1 -1
- package/dist/sanitizeParsedJson.d.ts +39 -0
- package/dist/sanitizeParsedJson.js +55 -0
- package/dist/sanitizeParsedJson.js.map +1 -0
- package/dist/server.d.ts +14 -0
- package/dist/server.js +105 -33
- package/dist/server.js.map +1 -1
- package/dist/sessionCheckpoint.d.ts +8 -0
- package/dist/sessionCheckpoint.js +18 -2
- package/dist/sessionCheckpoint.js.map +1 -1
- package/dist/tools/detectUnusedCode.js +9 -7
- package/dist/tools/detectUnusedCode.js.map +1 -1
- package/dist/tools/editText.js +2 -1
- package/dist/tools/editText.js.map +1 -1
- package/dist/tools/fileOperations.js +2 -1
- package/dist/tools/fileOperations.js.map +1 -1
- package/dist/tools/fileWatcher.js +8 -2
- package/dist/tools/fileWatcher.js.map +1 -1
- package/dist/tools/fixAllLintErrors.js +10 -5
- package/dist/tools/fixAllLintErrors.js.map +1 -1
- package/dist/tools/formatDocument.js +10 -5
- package/dist/tools/formatDocument.js.map +1 -1
- package/dist/tools/handoffNote.js +2 -1
- package/dist/tools/handoffNote.js.map +1 -1
- package/dist/tools/headless/lspClient.js +3 -0
- package/dist/tools/headless/lspClient.js.map +1 -1
- package/dist/tools/index.js +0 -6
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/lsp.js +17 -0
- package/dist/tools/lsp.js.map +1 -1
- package/dist/tools/openDiff.js +4 -1
- package/dist/tools/openDiff.js.map +1 -1
- package/dist/tools/openFile.js +4 -1
- package/dist/tools/openFile.js.map +1 -1
- package/dist/tools/organizeImports.js +5 -3
- package/dist/tools/organizeImports.js.map +1 -1
- package/dist/tools/previewEdit.js +7 -2
- package/dist/tools/previewEdit.js.map +1 -1
- package/dist/tools/refactorExtractFunction.js +4 -1
- package/dist/tools/refactorExtractFunction.js.map +1 -1
- package/dist/tools/refactorPreview.js +10 -2
- package/dist/tools/refactorPreview.js.map +1 -1
- package/dist/tools/replaceBlock.js +2 -1
- package/dist/tools/replaceBlock.js.map +1 -1
- package/dist/tools/searchAndReplace.js +2 -1
- package/dist/tools/searchAndReplace.js.map +1 -1
- package/dist/tools/spawnWorkspace.js +15 -7
- package/dist/tools/spawnWorkspace.js.map +1 -1
- package/dist/tools/transaction.js +4 -1
- package/dist/tools/transaction.js.map +1 -1
- package/dist/tools/utils.js +62 -5
- package/dist/tools/utils.js.map +1 -1
- package/dist/transport.d.ts +1 -1
- package/dist/transport.js +18 -4
- package/dist/transport.js.map +1 -1
- package/dist/winShim.d.ts +34 -0
- package/dist/winShim.js +94 -0
- package/dist/winShim.js.map +1 -0
- package/dist/writeFileAtomic.d.ts +23 -0
- package/dist/writeFileAtomic.js +94 -0
- package/dist/writeFileAtomic.js.map +1 -0
- package/package.json +1 -1
- package/scripts/postinstall.mjs +18 -5
- package/scripts/smoke/run-all.mjs +55 -4
- package/scripts/start-all.mjs +60 -1
- package/scripts/start-all.ps1 +209 -209
- package/scripts/start-orchestrator.ps1 +158 -158
- package/dist/tools/ccRoutines.d.ts +0 -221
- package/dist/tools/ccRoutines.js +0 -264
- package/dist/tools/ccRoutines.js.map +0 -1
package/scripts/start-all.mjs
CHANGED
|
@@ -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))
|
|
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
|
}
|
package/scripts/start-all.ps1
CHANGED
|
@@ -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
|
+
}
|