patchwork-os 0.2.0-alpha.2 → 0.2.0-alpha.21

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 (154) hide show
  1. package/README.bridge.md +6 -0
  2. package/README.md +13 -2
  3. package/dist/approvalHttp.d.ts +11 -2
  4. package/dist/approvalHttp.js +92 -9
  5. package/dist/approvalHttp.js.map +1 -1
  6. package/dist/approvalQueue.d.ts +12 -1
  7. package/dist/approvalQueue.js +25 -3
  8. package/dist/approvalQueue.js.map +1 -1
  9. package/dist/bridge.js +127 -23
  10. package/dist/bridge.js.map +1 -1
  11. package/dist/claudeDriver.d.ts +3 -1
  12. package/dist/claudeDriver.js +48 -0
  13. package/dist/claudeDriver.js.map +1 -1
  14. package/dist/claudeOrchestrator.d.ts +1 -1
  15. package/dist/claudeOrchestrator.js +14 -8
  16. package/dist/claudeOrchestrator.js.map +1 -1
  17. package/dist/commands/launchd.d.ts +2 -0
  18. package/dist/commands/launchd.js +94 -0
  19. package/dist/commands/launchd.js.map +1 -0
  20. package/dist/config.d.ts +7 -2
  21. package/dist/config.js +85 -8
  22. package/dist/config.js.map +1 -1
  23. package/dist/connectors/github.d.ts +58 -8
  24. package/dist/connectors/github.js +321 -84
  25. package/dist/connectors/github.js.map +1 -1
  26. package/dist/connectors/gmail.d.ts +4 -1
  27. package/dist/connectors/gmail.js +77 -16
  28. package/dist/connectors/gmail.js.map +1 -1
  29. package/dist/connectors/googleCalendar.d.ts +60 -0
  30. package/dist/connectors/googleCalendar.js +329 -0
  31. package/dist/connectors/googleCalendar.js.map +1 -0
  32. package/dist/connectors/linear.d.ts +117 -0
  33. package/dist/connectors/linear.js +248 -0
  34. package/dist/connectors/linear.js.map +1 -0
  35. package/dist/connectors/mcpClient.d.ts +56 -0
  36. package/dist/connectors/mcpClient.js +189 -0
  37. package/dist/connectors/mcpClient.js.map +1 -0
  38. package/dist/connectors/mcpOAuth.d.ts +83 -0
  39. package/dist/connectors/mcpOAuth.js +363 -0
  40. package/dist/connectors/mcpOAuth.js.map +1 -0
  41. package/dist/connectors/sentry.d.ts +43 -0
  42. package/dist/connectors/sentry.js +197 -0
  43. package/dist/connectors/sentry.js.map +1 -0
  44. package/dist/connectors/slack.d.ts +50 -0
  45. package/dist/connectors/slack.js +289 -0
  46. package/dist/connectors/slack.js.map +1 -0
  47. package/dist/drivers/claude/api.d.ts +11 -0
  48. package/dist/drivers/claude/api.js +54 -0
  49. package/dist/drivers/claude/api.js.map +1 -0
  50. package/dist/drivers/claude/envSanitizer.d.ts +7 -0
  51. package/dist/drivers/claude/envSanitizer.js +18 -0
  52. package/dist/drivers/claude/envSanitizer.js.map +1 -0
  53. package/dist/drivers/claude/streamParser.d.ts +38 -0
  54. package/dist/drivers/claude/streamParser.js +34 -0
  55. package/dist/drivers/claude/streamParser.js.map +1 -0
  56. package/dist/drivers/claude/subprocess.d.ts +19 -0
  57. package/dist/drivers/claude/subprocess.js +216 -0
  58. package/dist/drivers/claude/subprocess.js.map +1 -0
  59. package/dist/drivers/claude/subprocessSettings.d.ts +9 -0
  60. package/dist/drivers/claude/subprocessSettings.js +55 -0
  61. package/dist/drivers/claude/subprocessSettings.js.map +1 -0
  62. package/dist/drivers/gemini/index.d.ts +18 -0
  63. package/dist/drivers/gemini/index.js +210 -0
  64. package/dist/drivers/gemini/index.js.map +1 -0
  65. package/dist/drivers/grok/index.d.ts +11 -0
  66. package/dist/drivers/grok/index.js +22 -0
  67. package/dist/drivers/grok/index.js.map +1 -0
  68. package/dist/drivers/index.d.ts +23 -0
  69. package/dist/drivers/index.js +31 -0
  70. package/dist/drivers/index.js.map +1 -0
  71. package/dist/drivers/openai/index.d.ts +24 -0
  72. package/dist/drivers/openai/index.js +110 -0
  73. package/dist/drivers/openai/index.js.map +1 -0
  74. package/dist/drivers/types.d.ts +72 -0
  75. package/dist/drivers/types.js +30 -0
  76. package/dist/drivers/types.js.map +1 -0
  77. package/dist/index.js +35 -1
  78. package/dist/index.js.map +1 -1
  79. package/dist/installGuard.d.ts +25 -0
  80. package/dist/installGuard.js +48 -0
  81. package/dist/installGuard.js.map +1 -0
  82. package/dist/patchworkConfig.d.ts +9 -0
  83. package/dist/patchworkConfig.js.map +1 -1
  84. package/dist/recipes/scheduler.d.ts +23 -7
  85. package/dist/recipes/scheduler.js +135 -41
  86. package/dist/recipes/scheduler.js.map +1 -1
  87. package/dist/recipes/yamlRunner.d.ts +15 -0
  88. package/dist/recipes/yamlRunner.js +325 -26
  89. package/dist/recipes/yamlRunner.js.map +1 -1
  90. package/dist/recipesHttp.d.ts +14 -1
  91. package/dist/recipesHttp.js +21 -4
  92. package/dist/recipesHttp.js.map +1 -1
  93. package/dist/runLog.d.ts +5 -0
  94. package/dist/runLog.js +51 -1
  95. package/dist/runLog.js.map +1 -1
  96. package/dist/server.d.ts +15 -1
  97. package/dist/server.js +458 -31
  98. package/dist/server.js.map +1 -1
  99. package/dist/tools/addLinearComment.d.ts +55 -0
  100. package/dist/tools/addLinearComment.js +72 -0
  101. package/dist/tools/addLinearComment.js.map +1 -0
  102. package/dist/tools/bridgeDoctor.js +2 -2
  103. package/dist/tools/bridgeDoctor.js.map +1 -1
  104. package/dist/tools/createLinearIssue.d.ts +84 -0
  105. package/dist/tools/createLinearIssue.js +146 -0
  106. package/dist/tools/createLinearIssue.js.map +1 -0
  107. package/dist/tools/ctxGetTaskContext.d.ts +4 -1
  108. package/dist/tools/ctxGetTaskContext.js +45 -2
  109. package/dist/tools/ctxGetTaskContext.js.map +1 -1
  110. package/dist/tools/fetchCalendarEvents.d.ts +94 -0
  111. package/dist/tools/fetchCalendarEvents.js +97 -0
  112. package/dist/tools/fetchCalendarEvents.js.map +1 -0
  113. package/dist/tools/fetchGithubIssue.d.ts +80 -0
  114. package/dist/tools/fetchGithubIssue.js +84 -0
  115. package/dist/tools/fetchGithubIssue.js.map +1 -0
  116. package/dist/tools/fetchGithubPR.d.ts +89 -0
  117. package/dist/tools/fetchGithubPR.js +96 -0
  118. package/dist/tools/fetchGithubPR.js.map +1 -0
  119. package/dist/tools/fetchLinearIssue.d.ts +112 -0
  120. package/dist/tools/fetchLinearIssue.js +129 -0
  121. package/dist/tools/fetchLinearIssue.js.map +1 -0
  122. package/dist/tools/fetchSentryIssue.d.ts +143 -0
  123. package/dist/tools/fetchSentryIssue.js +150 -0
  124. package/dist/tools/fetchSentryIssue.js.map +1 -0
  125. package/dist/tools/fetchSlackProfile.d.ts +43 -0
  126. package/dist/tools/fetchSlackProfile.js +46 -0
  127. package/dist/tools/fetchSlackProfile.js.map +1 -0
  128. package/dist/tools/getConnectorStatus.d.ts +58 -0
  129. package/dist/tools/getConnectorStatus.js +56 -0
  130. package/dist/tools/getConnectorStatus.js.map +1 -0
  131. package/dist/tools/github/index.d.ts +1 -1
  132. package/dist/tools/github/index.js +1 -1
  133. package/dist/tools/github/index.js.map +1 -1
  134. package/dist/tools/github/pr.d.ts +122 -0
  135. package/dist/tools/github/pr.js +183 -0
  136. package/dist/tools/github/pr.js.map +1 -1
  137. package/dist/tools/index.js +27 -1
  138. package/dist/tools/index.js.map +1 -1
  139. package/dist/tools/slackListChannels.d.ts +65 -0
  140. package/dist/tools/slackListChannels.js +70 -0
  141. package/dist/tools/slackListChannels.js.map +1 -0
  142. package/dist/tools/slackPostMessage.d.ts +57 -0
  143. package/dist/tools/slackPostMessage.js +77 -0
  144. package/dist/tools/slackPostMessage.js.map +1 -0
  145. package/dist/tools/updateLinearIssue.d.ts +89 -0
  146. package/dist/tools/updateLinearIssue.js +117 -0
  147. package/dist/tools/updateLinearIssue.js.map +1 -0
  148. package/package.json +4 -2
  149. package/scripts/start-all.sh +56 -19
  150. package/templates/co.patchwork-os.bridge.plist +34 -0
  151. package/templates/recipes/ctx-loop-test.yaml +75 -0
  152. package/templates/recipes/morning-brief-slack.yaml +57 -0
  153. package/templates/recipes/morning-brief.yaml +21 -5
  154. package/templates/recipes/sentry-to-linear.yaml +77 -0
@@ -0,0 +1,57 @@
1
+ export declare function createSlackPostMessageTool(): {
2
+ schema: {
3
+ name: string;
4
+ description: string;
5
+ annotations: {
6
+ readOnlyHint: boolean;
7
+ };
8
+ inputSchema: {
9
+ type: "object";
10
+ required: string[];
11
+ properties: {
12
+ channel: {
13
+ type: string;
14
+ description: string;
15
+ maxLength: number;
16
+ };
17
+ text: {
18
+ type: string;
19
+ description: string;
20
+ maxLength: number;
21
+ };
22
+ threadTs: {
23
+ type: string;
24
+ description: string;
25
+ maxLength: number;
26
+ };
27
+ };
28
+ additionalProperties: false;
29
+ };
30
+ outputSchema: {
31
+ type: "object";
32
+ properties: {
33
+ ts: {
34
+ type: string;
35
+ };
36
+ channel: {
37
+ type: string;
38
+ };
39
+ slackConnected: {
40
+ type: string;
41
+ };
42
+ error: {
43
+ type: string;
44
+ };
45
+ };
46
+ required: string[];
47
+ };
48
+ };
49
+ timeoutMs: number;
50
+ handler(args: Record<string, unknown>, signal?: AbortSignal): Promise<{
51
+ content: Array<{
52
+ type: string;
53
+ text: string;
54
+ }>;
55
+ structuredContent: unknown;
56
+ }>;
57
+ };
@@ -0,0 +1,77 @@
1
+ import { loadTokens, postMessage } from "../connectors/slack.js";
2
+ import { optionalString, requireString, successStructured } from "./utils.js";
3
+ export function createSlackPostMessageTool() {
4
+ return {
5
+ schema: {
6
+ name: "slackPostMessage",
7
+ description: "Post a message to a Slack channel. Use channel name (e.g. 'general') or channel ID. " +
8
+ "Optionally reply in a thread by providing threadTs.",
9
+ annotations: { readOnlyHint: false },
10
+ inputSchema: {
11
+ type: "object",
12
+ required: ["channel", "text"],
13
+ properties: {
14
+ channel: {
15
+ type: "string",
16
+ description: "Channel name (e.g. 'general') or channel ID (e.g. 'C12345').",
17
+ maxLength: 200,
18
+ },
19
+ text: {
20
+ type: "string",
21
+ description: "Message text (Markdown supported via mrkdwn).",
22
+ maxLength: 40000,
23
+ },
24
+ threadTs: {
25
+ type: "string",
26
+ description: "Thread timestamp to reply in. Omit to post as a new message.",
27
+ maxLength: 50,
28
+ },
29
+ },
30
+ additionalProperties: false,
31
+ },
32
+ outputSchema: {
33
+ type: "object",
34
+ properties: {
35
+ ts: { type: "string" },
36
+ channel: { type: "string" },
37
+ slackConnected: { type: "boolean" },
38
+ error: { type: "string" },
39
+ },
40
+ required: ["slackConnected"],
41
+ },
42
+ },
43
+ timeoutMs: 15_000,
44
+ async handler(args, signal) {
45
+ const tokens = loadTokens();
46
+ if (!tokens) {
47
+ return successStructured({
48
+ ts: "",
49
+ channel: "",
50
+ slackConnected: false,
51
+ error: "Slack not connected. GET /connections/slack/authorize first.",
52
+ });
53
+ }
54
+ const channel = requireString(args, "channel", 200);
55
+ const text = requireString(args, "text", 40000);
56
+ const threadTs = optionalString(args, "threadTs", 50);
57
+ try {
58
+ const result = await postMessage(channel, text, threadTs ?? undefined, signal);
59
+ return successStructured({
60
+ ts: result.ts,
61
+ channel: result.channel,
62
+ slackConnected: true,
63
+ });
64
+ }
65
+ catch (err) {
66
+ const notConnected = err instanceof Error && err.message.includes("not connected");
67
+ return successStructured({
68
+ ts: "",
69
+ channel: "",
70
+ slackConnected: !notConnected,
71
+ error: err instanceof Error ? err.message : String(err),
72
+ });
73
+ }
74
+ },
75
+ };
76
+ }
77
+ //# sourceMappingURL=slackPostMessage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slackPostMessage.js","sourceRoot":"","sources":["../../src/tools/slackPostMessage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ export declare function createUpdateLinearIssueTool(): {
2
+ schema: {
3
+ name: string;
4
+ description: string;
5
+ annotations: {
6
+ readOnlyHint: boolean;
7
+ };
8
+ inputSchema: {
9
+ type: "object";
10
+ required: string[];
11
+ properties: {
12
+ id: {
13
+ type: string;
14
+ description: string;
15
+ maxLength: number;
16
+ };
17
+ title: {
18
+ type: string;
19
+ maxLength: number;
20
+ };
21
+ description: {
22
+ type: string;
23
+ maxLength: number;
24
+ };
25
+ priority: {
26
+ type: string;
27
+ description: string;
28
+ minimum: number;
29
+ maximum: number;
30
+ };
31
+ state: {
32
+ type: string;
33
+ description: string;
34
+ maxLength: number;
35
+ };
36
+ assignee: {
37
+ type: string;
38
+ description: string;
39
+ maxLength: number;
40
+ };
41
+ labelNames: {
42
+ type: string;
43
+ description: string;
44
+ items: {
45
+ type: string;
46
+ maxLength: number;
47
+ };
48
+ maxItems: number;
49
+ };
50
+ };
51
+ additionalProperties: false;
52
+ };
53
+ outputSchema: {
54
+ type: "object";
55
+ properties: {
56
+ id: {
57
+ type: string;
58
+ };
59
+ identifier: {
60
+ type: string;
61
+ };
62
+ title: {
63
+ type: string;
64
+ };
65
+ url: {
66
+ type: string;
67
+ };
68
+ state: {
69
+ type: string;
70
+ };
71
+ linearConnected: {
72
+ type: string;
73
+ };
74
+ error: {
75
+ type: string;
76
+ };
77
+ };
78
+ required: string[];
79
+ };
80
+ };
81
+ timeoutMs: number;
82
+ handler(args: Record<string, unknown>, signal?: AbortSignal): Promise<{
83
+ content: Array<{
84
+ type: string;
85
+ text: string;
86
+ }>;
87
+ structuredContent: unknown;
88
+ }>;
89
+ };
@@ -0,0 +1,117 @@
1
+ import { loadTokens, updateIssue } from "../connectors/linear.js";
2
+ import { requireString, successStructured } from "./utils.js";
3
+ export function createUpdateLinearIssueTool() {
4
+ return {
5
+ schema: {
6
+ name: "updateLinearIssue",
7
+ description: "Update an existing Linear issue. Pass only the fields you want to change. Requires Linear connector connected.",
8
+ annotations: { readOnlyHint: false },
9
+ inputSchema: {
10
+ type: "object",
11
+ required: ["id"],
12
+ properties: {
13
+ id: {
14
+ type: "string",
15
+ description: "Issue identifier (e.g. 'ENG-42') or URL.",
16
+ maxLength: 200,
17
+ },
18
+ title: { type: "string", maxLength: 500 },
19
+ description: { type: "string", maxLength: 10000 },
20
+ priority: {
21
+ type: "integer",
22
+ description: "0=no priority, 1=urgent, 2=high, 3=medium, 4=low.",
23
+ minimum: 0,
24
+ maximum: 4,
25
+ },
26
+ state: {
27
+ type: "string",
28
+ description: "Workflow state name (e.g. 'In Progress', 'Done').",
29
+ maxLength: 100,
30
+ },
31
+ assignee: {
32
+ type: "string",
33
+ description: "Assignee name or email.",
34
+ maxLength: 200,
35
+ },
36
+ labelNames: {
37
+ type: "array",
38
+ description: "Label names to set (replaces existing labels).",
39
+ items: { type: "string", maxLength: 100 },
40
+ maxItems: 10,
41
+ },
42
+ },
43
+ additionalProperties: false,
44
+ },
45
+ outputSchema: {
46
+ type: "object",
47
+ properties: {
48
+ id: { type: "string" },
49
+ identifier: { type: "string" },
50
+ title: { type: "string" },
51
+ url: { type: "string" },
52
+ state: { type: "string" },
53
+ linearConnected: { type: "boolean" },
54
+ error: { type: "string" },
55
+ },
56
+ required: ["id", "identifier", "title", "url", "linearConnected"],
57
+ },
58
+ },
59
+ timeoutMs: 20_000,
60
+ async handler(args, signal) {
61
+ const tokens = loadTokens();
62
+ if (!tokens) {
63
+ return successStructured({
64
+ id: "",
65
+ identifier: "",
66
+ title: "",
67
+ url: "",
68
+ linearConnected: false,
69
+ error: "Linear not connected. GET /connections/linear/authorize first.",
70
+ });
71
+ }
72
+ const id = requireString(args, "id", 200);
73
+ const labelNames = Array.isArray(args.labelNames)
74
+ ? args.labelNames
75
+ .filter((l) => typeof l === "string")
76
+ .map(String)
77
+ : undefined;
78
+ try {
79
+ const issue = await updateIssue({
80
+ id,
81
+ ...(typeof args.title === "string" && { title: args.title }),
82
+ ...(typeof args.description === "string" && {
83
+ description: args.description,
84
+ }),
85
+ ...(typeof args.priority === "number" && {
86
+ priority: args.priority,
87
+ }),
88
+ ...(typeof args.state === "string" && { state: args.state }),
89
+ ...(typeof args.assignee === "string" && {
90
+ assignee: args.assignee,
91
+ }),
92
+ ...(labelNames && labelNames.length > 0 && { labels: labelNames }),
93
+ }, signal);
94
+ return successStructured({
95
+ id: issue.id,
96
+ identifier: issue.identifier,
97
+ title: issue.title,
98
+ url: issue.url,
99
+ state: issue.state?.name ?? "",
100
+ linearConnected: true,
101
+ });
102
+ }
103
+ catch (err) {
104
+ const notConnected = err instanceof Error && err.message.includes("not connected");
105
+ return successStructured({
106
+ id: "",
107
+ identifier: "",
108
+ title: "",
109
+ url: "",
110
+ linearConnected: !notConnected,
111
+ error: err instanceof Error ? err.message : String(err),
112
+ });
113
+ }
114
+ },
115
+ };
116
+ }
117
+ //# sourceMappingURL=updateLinearIssue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"updateLinearIssue.js","sourceRoot":"","sources":["../../src/tools/updateLinearIssue.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "patchwork-os",
3
- "version": "0.2.0-alpha.2",
4
- "description": "Patchwork OS \u2014 proactive, multi-model AI teammate built on the Claude IDE Bridge. One agent, any model, works while you're away.",
3
+ "version": "0.2.0-alpha.21",
4
+ "description": "Patchwork OS proactive, multi-model AI teammate built on the Claude IDE Bridge. One agent, any model, works while you're away.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -95,12 +95,14 @@
95
95
  "@opentelemetry/semantic-conventions": "^1.40.0",
96
96
  "ajv": "^8.18.0",
97
97
  "minimatch": "^10.0.0",
98
+ "node-cron": "^3.0.3",
98
99
  "ws": "^8.18.0",
99
100
  "yaml": "^2.8.3"
100
101
  },
101
102
  "devDependencies": {
102
103
  "@biomejs/biome": "^2.4.10",
103
104
  "@types/node": "^22.0.0",
105
+ "@types/node-cron": "^3.0.11",
104
106
  "@types/ws": "^8.5.0",
105
107
  "@vitest/coverage-v8": "^3.0.0",
106
108
  "@vscode/ripgrep": "^1.17.1",
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
- # Full orchestrator for bridge + claude + remote-control.
3
- # Manages all three in tmux panes with health monitoring.
2
+ # Full orchestrator for bridge + claude + remote-control + dashboard.
3
+ # Manages all processes in tmux panes with health monitoring.
4
4
  #
5
5
  # Pane layout:
6
6
  # 0 — health monitor (orchestrator)
@@ -8,7 +8,8 @@
8
8
  # 2 — claude --ide (Claude Code CLI, connects to bridge for IDE tools)
9
9
  # 3 — claude remote-control --spawn=session (exposes the workspace to claude.ai;
10
10
  # spawned sessions auto-discover the bridge via ~/.claude/ide/*.lock)
11
- # 4 — SSH reverse tunnel to VPS (optional, enabled with --vps <user@host:port>)
11
+ # 4 — Patchwork dashboard (Next.js, http://localhost:3200)
12
+ # 5 — SSH reverse tunnel to VPS (optional, enabled with --vps <user@host:port>)
12
13
  # Forwards VPS_PORT on the remote host back to the local bridge port.
13
14
  # Allows claude.ai integrations to use a static VPS URL instead of a
14
15
  # rotating remote-control session URL.
@@ -20,6 +21,8 @@
20
21
  # Options:
21
22
  # --workspace <path> Directory to open in Claude (default: current directory)
22
23
  # --full Register all ~95 bridge tools (git, terminal, file ops, HTTP, GitHub).
24
+ # --no-dashboard Skip starting the Patchwork dashboard (pane 4).
25
+ # --dashboard-port <N> Dashboard port (default: 3200).
23
26
  # Default is slim mode (27 IDE-exclusive tools). Add --full if your
24
27
  # workflow requires git/terminal/file tools.
25
28
  # --notify <topic> Push notifications via ntfy.sh when remote-control connects or
@@ -48,6 +51,8 @@ NTFY_TOPIC=""
48
51
  IDE_NAME=""
49
52
  VPS=""
50
53
  FULL_MODE=""
54
+ NO_DASHBOARD=""
55
+ DASHBOARD_PORT="3200"
51
56
  AUTOMATION_POLICY=""
52
57
  CLAUDE_DRIVER="subprocess"
53
58
  BRIDGE_READY_TIMEOUT="${BRIDGE_READY_TIMEOUT:-30}"
@@ -64,6 +69,8 @@ while [[ $# -gt 0 ]]; do
64
69
  --ide) IDE_NAME="$2"; shift 2 ;;
65
70
  --vps) VPS="$2"; shift 2 ;;
66
71
  --full) FULL_MODE="--full"; shift ;;
72
+ --no-dashboard) NO_DASHBOARD=1; shift ;;
73
+ --dashboard-port) DASHBOARD_PORT="$2"; shift 2 ;;
67
74
  --automation-policy) AUTOMATION_POLICY="$2"; shift 2 ;;
68
75
  --claude-driver) CLAUDE_DRIVER="$2"; shift 2 ;;
69
76
  *) echo "Unknown option: $1" >&2; exit 1 ;;
@@ -90,23 +97,31 @@ fi
90
97
 
91
98
  # --- Bridge conflict check ---
92
99
  # Detect an already-running bridge instance and abort early with a clear message.
93
- # Iterates lock files safely (no xargs injection); filters to isBridge:true + live PID.
100
+ # Validates: isBridge:true + live PID + process is actually a bridge (not Windsurf reusing PID)
101
+ # by checking whether the lock file's port has an active listener.
94
102
  while IFS= read -r lock; do
95
103
  [[ -f "$lock" ]] || continue
96
- bridge_pid=$(python3 -c "
104
+ lock_info=$(python3 -c "
97
105
  import json, sys
98
106
  try:
99
107
  d = json.load(open(sys.argv[1]))
100
108
  if d.get('isBridge') is True:
101
- print(d.get('pid', ''))
109
+ print(d.get('pid', ''), d.get('port', ''))
102
110
  except Exception:
103
111
  pass
104
112
  " "$lock" 2>/dev/null || true)
113
+ bridge_pid="${lock_info%% *}"
114
+ bridge_port="${lock_info##* }"
105
115
  if [[ -n "$bridge_pid" ]] && kill -0 "$bridge_pid" 2>/dev/null; then
106
- echo "Error: bridge already running (PID $bridge_pid, lock: $(basename "$lock"))." >&2
107
- echo " Stop it first: kill $bridge_pid" >&2
108
- echo " Or kill the tmux session: tmux kill-session -t claude-all" >&2
109
- exit 1
116
+ # Cross-check: verify a process is actually listening on the lock's port.
117
+ # Windsurf may reuse PIDs from stale locks — if nothing listens on that port,
118
+ # the bridge is not actually running.
119
+ if [[ -n "$bridge_port" ]] && lsof -i ":${bridge_port}" 2>/dev/null | grep -q LISTEN; then
120
+ echo "Error: bridge already running (PID $bridge_pid, port $bridge_port, lock: $(basename "$lock"))." >&2
121
+ echo " Stop it first: kill $bridge_pid" >&2
122
+ echo " Or kill the tmux session: tmux kill-session -t claude-all" >&2
123
+ exit 1
124
+ fi
110
125
  fi
111
126
  done < <(ls ~/.claude/ide/*.lock 2>/dev/null)
112
127
 
@@ -141,6 +156,7 @@ echo "=== Claude IDE Bridge Full Orchestrator ==="
141
156
  echo " Ctrl+C in any pane — stops that process"
142
157
  echo " Ctrl+B then D (press Ctrl+B, release, then press D) — detach (keeps running)"
143
158
  echo " tmux kill-session -t $SESSION — stop everything"
159
+ [[ -z "$NO_DASHBOARD" ]] && echo " Dashboard: http://localhost:${DASHBOARD_PORT}"
144
160
  [[ -n "$NTFY_TOPIC" ]] && echo " Push notifications: ntfy.sh/$NTFY_TOPIC"
145
161
  if [[ -z "$FULL_MODE" ]]; then
146
162
  echo ""
@@ -276,11 +292,12 @@ while true; do
276
292
  sleep \$delay
277
293
  done'"
278
294
 
279
- # --- Create additional panes (pane 0 = orchestrator, 1 = bridge, 2 = claude, 3 = remote-control, 4 = ssh tunnel) ---
295
+ # --- Create additional panes (pane 0 = orchestrator, 1 = bridge, 2 = claude, 3 = remote-control, 4 = dashboard, 5 = ssh tunnel) ---
280
296
  tmux split-window -v -t "$SESSION" # pane 1 for bridge
281
297
  tmux split-window -v -t "$SESSION" # pane 2 for claude --ide
282
298
  tmux split-window -v -t "$SESSION" # pane 3 for claude remote-control
283
- [[ -n "$TUNNEL_CMD" ]] && tmux split-window -v -t "$SESSION" # pane 4 for ssh tunnel (optional)
299
+ [[ -z "$NO_DASHBOARD" ]] && tmux split-window -v -t "$SESSION" # pane 4 for dashboard (optional)
300
+ [[ -n "$TUNNEL_CMD" ]] && tmux split-window -v -t "$SESSION" # pane 5 for ssh tunnel (optional)
284
301
  tmux select-layout -t "$SESSION" even-vertical
285
302
 
286
303
  # --- Helper: wait for a NEW lock file (ignores pre-existing ones) ---
@@ -359,10 +376,20 @@ sleep 3
359
376
  # Pane 3: Remote control with auto-restart loop
360
377
  tmux send-keys -t "${SESSION}:0.3" "$REMOTE_CMD" Enter
361
378
 
362
- # Pane 4: SSH reverse tunnel (only if --vps was set)
379
+ # Pane 4: Dashboard (only if not --no-dashboard and dashboard dir exists)
380
+ DASHBOARD_DIR="$BRIDGE_DIR/dashboard"
381
+ if [[ -z "$NO_DASHBOARD" ]] && [[ -d "$DASHBOARD_DIR" ]]; then
382
+ BRIDGE_PORT=$(basename "$LOCK_FILE" .lock)
383
+ DASHBOARD_CMD="cd $(printf '%q' "$DASHBOARD_DIR") && PATCHWORK_BRIDGE_PORT=${BRIDGE_PORT} npm run dev"
384
+ notify "Starting dashboard on http://localhost:${DASHBOARD_PORT}"
385
+ tmux send-keys -t "${SESSION}:0.4" "$DASHBOARD_CMD" Enter
386
+ fi
387
+
388
+ # Pane 5: SSH reverse tunnel (only if --vps was set)
363
389
  if [[ -n "$TUNNEL_CMD" ]]; then
364
390
  notify "Starting SSH tunnel to ${VPS}..."
365
- tmux send-keys -t "${SESSION}:0.4" "$TUNNEL_CMD" Enter
391
+ TUNNEL_PANE=$([[ -z "$NO_DASHBOARD" ]] && echo 5 || echo 4)
392
+ tmux send-keys -t "${SESSION}:0.${TUNNEL_PANE}" "$TUNNEL_CMD" Enter
366
393
  fi
367
394
 
368
395
  # --- Health monitor (runs in pane 0 — the orchestrator pane) ---
@@ -377,10 +404,12 @@ cleanup() {
377
404
  tmux send-keys -t "${SESSION}:0.1" C-c 2>/dev/null
378
405
  tmux send-keys -t "${SESSION}:0.2" C-c 2>/dev/null
379
406
  tmux send-keys -t "${SESSION}:0.3" C-c 2>/dev/null
380
- [[ -n "$TUNNEL_CMD" ]] && tmux send-keys -t "${SESSION}:0.4" C-c 2>/dev/null
407
+ [[ -z "$NO_DASHBOARD" ]] && tmux send-keys -t "${SESSION}:0.4" C-c 2>/dev/null
408
+ [[ -n "$TUNNEL_CMD" ]] && tmux send-keys -t "${SESSION}:0.${TUNNEL_PANE:-4}" C-c 2>/dev/null
381
409
  # Wait up to 5s for panes to exit gracefully, then force-kill
382
410
  local panes=(1 2 3)
383
- [[ -n "$TUNNEL_CMD" ]] && panes=(1 2 3 4)
411
+ [[ -z "$NO_DASHBOARD" ]] && panes=(1 2 3 4)
412
+ [[ -n "$TUNNEL_CMD" ]] && panes=("${panes[@]}" "${TUNNEL_PANE:-4}")
384
413
  for _ in $(seq 1 5); do
385
414
  sleep 1
386
415
  all_idle=true
@@ -396,7 +425,8 @@ cleanup() {
396
425
  done
397
426
  sleep 1
398
427
  local rpanes=(3 2 1)
399
- [[ -n "$TUNNEL_CMD" ]] && rpanes=(4 3 2 1)
428
+ [[ -z "$NO_DASHBOARD" ]] && rpanes=(4 3 2 1)
429
+ [[ -n "$TUNNEL_CMD" ]] && rpanes=("${TUNNEL_PANE:-4}" "${rpanes[@]}")
400
430
  for pane in "${rpanes[@]}"; do
401
431
  tmux kill-pane -t "${SESSION}:0.${pane}" 2>/dev/null || true
402
432
  done
@@ -413,7 +443,8 @@ while true; do
413
443
  tmux send-keys -t "${SESSION}:0.1" C-c
414
444
  tmux send-keys -t "${SESSION}:0.2" C-c
415
445
  tmux send-keys -t "${SESSION}:0.3" C-c
416
- [[ -n "$TUNNEL_CMD" ]] && tmux send-keys -t "${SESSION}:0.4" C-c
446
+ [[ -z "$NO_DASHBOARD" ]] && tmux send-keys -t "${SESSION}:0.4" C-c
447
+ [[ -n "$TUNNEL_CMD" ]] && tmux send-keys -t "${SESSION}:0.${TUNNEL_PANE:-4}" C-c
417
448
  # Wait for bridge (pane 1) and claude (pane 2) to actually stop
418
449
  for _ in $(seq 1 10); do
419
450
  pane1_cmd=$(tmux display-message -t "${SESSION}:0.1" -p '#{pane_current_command}' 2>/dev/null || echo "")
@@ -476,9 +507,15 @@ while true; do
476
507
  sleep 3
477
508
  # Restart remote-control
478
509
  tmux send-keys -t "${SESSION}:0.3" "$REMOTE_CMD" Enter
510
+ # Restart dashboard with updated bridge port
511
+ if [[ -z "$NO_DASHBOARD" ]] && [[ -d "$DASHBOARD_DIR" ]]; then
512
+ NEW_BRIDGE_PORT=$(basename "$LOCK_FILE" .lock)
513
+ DASHBOARD_CMD="cd $(printf '%q' "$DASHBOARD_DIR") && PATCHWORK_BRIDGE_PORT=${NEW_BRIDGE_PORT} npm run dev"
514
+ tmux send-keys -t "${SESSION}:0.4" "$DASHBOARD_CMD" Enter
515
+ fi
479
516
  # Restart SSH tunnel (port may have changed — tunnel cmd reads lock file dynamically)
480
517
  if [[ -n "$TUNNEL_CMD" ]]; then
481
- tmux send-keys -t "${SESSION}:0.4" "$TUNNEL_CMD" Enter
518
+ tmux send-keys -t "${SESSION}:0.${TUNNEL_PANE:-4}" "$TUNNEL_CMD" Enter
482
519
  fi
483
520
  notify "All processes restarted"
484
521
  else
@@ -0,0 +1,34 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
3
+ "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
4
+ <plist version="1.0">
5
+ <dict>
6
+ <key>Label</key>
7
+ <string>co.patchwork-os.bridge</string>
8
+ <key>ProgramArguments</key>
9
+ <array>
10
+ <string>__BINARY_PATH__</string>
11
+ <string>--claude-driver</string>
12
+ <string>subprocess</string>
13
+ </array>
14
+ <key>RunAtLoad</key>
15
+ <true/>
16
+ <key>KeepAlive</key>
17
+ <true/>
18
+ <key>StandardOutPath</key>
19
+ <string>__HOME__/Library/Logs/patchwork-os/bridge.log</string>
20
+ <key>StandardErrorPath</key>
21
+ <string>__HOME__/Library/Logs/patchwork-os/bridge.err</string>
22
+ <key>EnvironmentVariables</key>
23
+ <dict>
24
+ <key>HOME</key>
25
+ <string>__HOME__</string>
26
+ <key>PATH</key>
27
+ <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
28
+ </dict>
29
+ <key>WorkingDirectory</key>
30
+ <string>__HOME__</string>
31
+ <key>ThrottleInterval</key>
32
+ <integer>30</integer>
33
+ </dict>
34
+ </plist>