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
@@ -1,113 +1,350 @@
1
1
  /**
2
- * GitHub connector — uses the `gh` CLI for all operations.
3
- * No OAuth app required; piggybacks on `gh auth login`.
2
+ * GitHub connector — routes through the official GitHub MCP server.
4
3
  *
5
- * Exported step helpers used by yamlRunner:
6
- * listIssues(opts) — open issues assigned to / mentioning viewer
7
- * listPRs(opts) — open PRs authored by / requested for review by viewer
8
- * getStatus() — { connected: boolean, user?: string }
4
+ * Endpoint: https://api.githubcopilot.com/mcp/
5
+ * Auth: OAuth 2.0 via github.com/login/oauth (requires pre-registered
6
+ * Patchwork OS OAuth App; client_id via PATCHWORK_GITHUB_CLIENT_ID env).
7
+ *
8
+ * HTTP routes (wired in src/server.ts):
9
+ * GET /connections/github/authorize — returns { url } for popup
10
+ * GET /connections/github/callback — token exchange
11
+ * POST /connections/github/test — ping MCP server
12
+ * DELETE /connections/github — delete token
13
+ *
14
+ * Exports preserved for yamlRunner; listIssues/listPRs are now async.
9
15
  */
10
- import { spawnSync } from "node:child_process";
11
- function gh(args) {
12
- const result = spawnSync("gh", args, {
13
- encoding: "utf-8",
14
- timeout: 15_000,
15
- env: { ...process.env, GITHUB_TOKEN: undefined },
16
- });
17
- return {
18
- ok: !result.error && result.status === 0,
19
- stdout: result.stdout ?? "",
20
- stderr: result.stderr ?? "",
21
- };
16
+ import { McpClient } from "./mcpClient.js";
17
+ import { completeAuthorize, getAccessToken, isConnected, loadTokenFile, revoke, startAuthorize, vendorConfig, } from "./mcpOAuth.js";
18
+ const GITHUB_MCP_ENDPOINT = "https://api.githubcopilot.com/mcp/";
19
+ // ── MCP client (memoized) ────────────────────────────────────────────────────
20
+ let _client = null;
21
+ function client() {
22
+ if (!_client) {
23
+ _client = new McpClient(GITHUB_MCP_ENDPOINT, () => getAccessToken("github"));
24
+ }
25
+ return _client;
22
26
  }
27
+ // ── Status ───────────────────────────────────────────────────────────────────
23
28
  export function getStatus() {
24
- const r = gh(["auth", "status", "--json", "loggedInAccounts"]);
25
- if (!r.ok)
29
+ if (!isConnected("github"))
26
30
  return { connected: false };
27
- try {
28
- const data = JSON.parse(r.stdout);
29
- const user = data.loggedInAccounts?.[0]?.user;
30
- return { connected: true, user };
31
- }
32
- catch {
33
- // gh auth status without --json on older versions
34
- const match = /Logged in to .+ account (\S+)/.exec(r.stdout + r.stderr);
35
- return { connected: true, user: match?.[1] };
36
- }
31
+ const file = loadTokenFile("github");
32
+ return { connected: true, user: file?.profile?.login };
37
33
  }
38
- export function listIssues(opts = {}) {
39
- const args = ["issue", "list", "--json", "number,title,url,labels,updatedAt"];
40
- if (opts.repo) {
41
- args.push("--repo", opts.repo);
42
- }
34
+ function parseRepo(opts) {
35
+ if (!opts.repo)
36
+ return {};
37
+ const [owner, repo] = opts.repo.split("/");
38
+ return { owner, repo };
39
+ }
40
+ function coerceIssue(raw, fallbackRepo) {
41
+ return {
42
+ number: raw.number,
43
+ title: raw.title,
44
+ repo: raw.repository?.full_name ??
45
+ raw.repository?.nameWithOwner ??
46
+ fallbackRepo,
47
+ url: raw.html_url ?? raw.url ?? "",
48
+ labels: (raw.labels ?? [])
49
+ .map((l) => (typeof l === "string" ? l : (l.name ?? "")))
50
+ .filter(Boolean),
51
+ updatedAt: raw.updated_at ?? raw.updatedAt ?? "",
52
+ };
53
+ }
54
+ // ── Listing ──────────────────────────────────────────────────────────────────
55
+ export async function listIssues(opts = {}) {
56
+ if (!isConnected("github"))
57
+ return [];
58
+ const { owner, repo } = parseRepo(opts);
59
+ const args = {
60
+ state: "open",
61
+ perPage: Math.min(opts.limit ?? 20, 50),
62
+ };
63
+ if (owner)
64
+ args.owner = owner;
65
+ if (repo)
66
+ args.repo = repo;
43
67
  if (opts.assignee)
44
- args.push("--assignee", opts.assignee);
68
+ args.assignee = opts.assignee === "@me" ? "@me" : opts.assignee;
45
69
  if (opts.mention)
46
- args.push("--mention", opts.mention);
47
- args.push("--limit", String(Math.min(opts.limit ?? 20, 50)));
48
- const r = gh(args);
49
- if (!r.ok)
50
- return [];
70
+ args.mentioned = opts.mention;
51
71
  try {
52
- const raw = JSON.parse(r.stdout);
53
- const repo = opts.repo ?? inferRepo();
54
- return raw.map((i) => ({
55
- number: i.number,
56
- title: i.title,
57
- repo,
58
- url: i.url,
59
- labels: i.labels.map((l) => l.name),
60
- updatedAt: i.updatedAt,
61
- }));
72
+ const res = await client().callTool("list_issues", args, {
73
+ cacheKey: `gh:issues:${JSON.stringify(args)}`,
74
+ cacheTtlMs: 60_000,
75
+ });
76
+ const parsed = McpClient.extractJson(res);
77
+ const arr = Array.isArray(parsed) ? parsed : (parsed.items ?? []);
78
+ const fallbackRepo = opts.repo ?? "";
79
+ return arr.map((i) => coerceIssue(i, fallbackRepo));
62
80
  }
63
81
  catch {
64
82
  return [];
65
83
  }
66
84
  }
67
- export function listPRs(opts = {}) {
68
- const args = [
69
- "pr",
70
- "list",
71
- "--json",
72
- "number,title,url,isDraft,reviewDecision,updatedAt",
73
- ];
74
- if (opts.repo) {
75
- args.push("--repo", opts.repo);
76
- }
85
+ export async function listPRs(opts = {}) {
86
+ if (!isConnected("github"))
87
+ return [];
88
+ const { owner, repo } = parseRepo(opts);
89
+ const args = {
90
+ state: "open",
91
+ perPage: Math.min(opts.limit ?? 20, 50),
92
+ };
93
+ if (owner)
94
+ args.owner = owner;
95
+ if (repo)
96
+ args.repo = repo;
77
97
  if (opts.author)
78
- args.push("--author", opts.author);
98
+ args.author = opts.author === "@me" ? "@me" : opts.author;
79
99
  if (opts.reviewRequested)
80
- args.push("--review-requested", opts.reviewRequested);
81
- args.push("--limit", String(Math.min(opts.limit ?? 20, 50)));
82
- const r = gh(args);
83
- if (!r.ok)
84
- return [];
100
+ args.reviewRequested = opts.reviewRequested;
85
101
  try {
86
- const raw = JSON.parse(r.stdout);
87
- const repo = opts.repo ?? inferRepo();
88
- return raw.map((p) => ({
89
- number: p.number,
90
- title: p.title,
91
- repo,
92
- url: p.url,
93
- isDraft: p.isDraft,
94
- reviewDecision: p.reviewDecision ?? "",
95
- updatedAt: p.updatedAt,
102
+ const res = await client().callTool("list_pull_requests", args, {
103
+ cacheKey: `gh:prs:${JSON.stringify(args)}`,
104
+ cacheTtlMs: 60_000,
105
+ });
106
+ const parsed = McpClient.extractJson(res);
107
+ const arr = Array.isArray(parsed) ? parsed : (parsed.items ?? []);
108
+ const fallbackRepo = opts.repo ?? "";
109
+ return arr.map((p) => ({
110
+ ...coerceIssue(p, fallbackRepo),
111
+ isDraft: Boolean(p.draft ?? p.isDraft ?? false),
112
+ reviewDecision: p.review_decision ?? p.reviewDecision ?? "",
96
113
  }));
97
114
  }
98
115
  catch {
99
116
  return [];
100
117
  }
101
118
  }
102
- function inferRepo() {
103
- const r = gh(["repo", "view", "--json", "nameWithOwner"]);
104
- if (!r.ok)
105
- return "";
119
+ function parseIssueRef(ref) {
120
+ // https://github.com/owner/repo/issues/42
121
+ const urlMatch = ref.match(/github\.com\/([^/]+)\/([^/]+)\/issues\/(\d+)/);
122
+ if (urlMatch) {
123
+ return {
124
+ owner: urlMatch[1],
125
+ repo: urlMatch[2],
126
+ number: Number(urlMatch[3]),
127
+ };
128
+ }
129
+ // owner/repo#42 or owner/repo/42
130
+ const shortMatch = ref.match(/^([^/]+)\/([^/#]+)[/#](\d+)$/);
131
+ if (shortMatch) {
132
+ return {
133
+ owner: shortMatch[1],
134
+ repo: shortMatch[2],
135
+ number: Number(shortMatch[3]),
136
+ };
137
+ }
138
+ throw new Error(`Cannot parse GitHub issue ref: ${ref}`);
139
+ }
140
+ function parsePRRef(ref) {
141
+ const urlMatch = ref.match(/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
142
+ if (urlMatch) {
143
+ return {
144
+ owner: urlMatch[1],
145
+ repo: urlMatch[2],
146
+ number: Number(urlMatch[3]),
147
+ };
148
+ }
149
+ const shortMatch = ref.match(/^([^/]+)\/([^/#]+)[/#](\d+)$/);
150
+ if (shortMatch) {
151
+ return {
152
+ owner: shortMatch[1],
153
+ repo: shortMatch[2],
154
+ number: Number(shortMatch[3]),
155
+ };
156
+ }
157
+ throw new Error(`Cannot parse GitHub PR ref: ${ref}`);
158
+ }
159
+ export async function fetchGitHubIssue(ref, signal) {
160
+ if (!isConnected("github"))
161
+ throw new Error("GitHub not connected. GET /connections/github/auth first.");
162
+ const { owner, repo, number } = parseIssueRef(ref);
163
+ const res = await client().callTool("issue_read", { owner, repo, issue_number: number, method: "get" }, { signal });
164
+ const raw = McpClient.extractJson(res);
165
+ return {
166
+ number: raw.number ?? number,
167
+ title: raw.title ?? "",
168
+ body: raw.body ?? "",
169
+ state: raw.state ?? "",
170
+ url: raw.html_url ?? raw.url ?? "",
171
+ repo: `${owner}/${repo}`,
172
+ author: raw.user?.login ??
173
+ raw.author ??
174
+ "",
175
+ labels: (raw.labels ?? []).map((l) => l.name ?? String(l)),
176
+ assignees: (raw.assignees ?? []).map((a) => a.login ?? String(a)),
177
+ createdAt: raw.created_at ?? "",
178
+ updatedAt: raw.updated_at ?? "",
179
+ comments: raw.comments ?? 0,
180
+ };
181
+ }
182
+ export async function fetchGitHubPR(ref, signal) {
183
+ if (!isConnected("github"))
184
+ throw new Error("GitHub not connected. GET /connections/github/auth first.");
185
+ const { owner, repo, number } = parsePRRef(ref);
186
+ const res = await client().callTool("pull_request_read", { owner, repo, pullNumber: number, method: "get" }, { signal });
187
+ const raw = McpClient.extractJson(res);
188
+ return {
189
+ number: raw.number ?? number,
190
+ title: raw.title ?? "",
191
+ body: raw.body ?? "",
192
+ state: raw.state ?? "",
193
+ url: raw.html_url ?? raw.url ?? "",
194
+ repo: `${owner}/${repo}`,
195
+ author: raw.user?.login ??
196
+ raw.author ??
197
+ "",
198
+ isDraft: Boolean(raw.draft ?? raw.isDraft ?? false),
199
+ reviewDecision: raw.review_decision ?? raw.reviewDecision ?? "",
200
+ labels: (raw.labels ?? []).map((l) => l.name ?? String(l)),
201
+ headBranch: raw.head?.ref ??
202
+ raw.headRefName ??
203
+ "",
204
+ baseBranch: raw.base?.ref ??
205
+ raw.baseRefName ??
206
+ "",
207
+ createdAt: raw.created_at ?? "",
208
+ updatedAt: raw.updated_at ?? "",
209
+ additions: raw.additions ?? 0,
210
+ deletions: raw.deletions ?? 0,
211
+ };
212
+ }
213
+ // ── HTTP handlers ────────────────────────────────────────────────────────────
214
+ export async function handleGithubAuthorize() {
215
+ const config = vendorConfig("github");
216
+ if (!config.preregisteredClientId) {
217
+ return {
218
+ status: 400,
219
+ contentType: "application/json",
220
+ body: JSON.stringify({
221
+ ok: false,
222
+ error: "PATCHWORK_GITHUB_CLIENT_ID not set — register a GitHub OAuth App first",
223
+ }),
224
+ };
225
+ }
106
226
  try {
107
- return JSON.parse(r.stdout).nameWithOwner;
227
+ const { url } = await startAuthorize(config);
228
+ return { status: 302, body: "", redirect: url };
108
229
  }
109
- catch {
110
- return "";
230
+ catch (err) {
231
+ return {
232
+ status: 400,
233
+ contentType: "application/json",
234
+ body: JSON.stringify({
235
+ ok: false,
236
+ error: err instanceof Error ? err.message : String(err),
237
+ }),
238
+ };
239
+ }
240
+ }
241
+ export async function handleGithubCallback(code, state, error) {
242
+ if (error) {
243
+ return {
244
+ status: 400,
245
+ contentType: "text/html",
246
+ body: `<html><body><h2>GitHub connect failed</h2><pre>${error}</pre></body></html>`,
247
+ };
111
248
  }
249
+ if (!code || !state) {
250
+ return {
251
+ status: 400,
252
+ contentType: "text/html",
253
+ body: `<html><body><h2>GitHub connect failed</h2><pre>missing code or state</pre></body></html>`,
254
+ };
255
+ }
256
+ const config = vendorConfig("github");
257
+ try {
258
+ await completeAuthorize(config, code, state);
259
+ // Capture user login for status display
260
+ let login = "";
261
+ try {
262
+ const token = await getAccessToken("github");
263
+ const res = await fetch("https://api.github.com/user", {
264
+ headers: {
265
+ Authorization: `Bearer ${token}`,
266
+ Accept: "application/json",
267
+ },
268
+ });
269
+ if (res.ok) {
270
+ const u = (await res.json());
271
+ login = u.login ?? "";
272
+ if (login) {
273
+ const file = loadTokenFile("github");
274
+ if (file) {
275
+ const { writeFileSync, mkdirSync } = await import("node:fs");
276
+ const { homedir } = await import("node:os");
277
+ const path = await import("node:path");
278
+ const p = path.join(homedir(), ".patchwork", "tokens", "github-mcp.json");
279
+ mkdirSync(path.dirname(p), { recursive: true, mode: 0o700 });
280
+ file.profile = { ...(file.profile ?? {}), login };
281
+ writeFileSync(p, JSON.stringify(file, null, 2), { mode: 0o600 });
282
+ }
283
+ }
284
+ }
285
+ }
286
+ catch {
287
+ // Profile fetch is best-effort
288
+ }
289
+ return {
290
+ status: 200,
291
+ contentType: "text/html",
292
+ body: `<html><body><h2>GitHub connected${login ? ` as ${login}` : ""}</h2><script>window.close();</script></body></html>`,
293
+ };
294
+ }
295
+ catch (err) {
296
+ return {
297
+ status: 400,
298
+ contentType: "text/html",
299
+ body: `<html><body><h2>GitHub connect failed</h2><pre>${err instanceof Error ? err.message : String(err)}</pre></body></html>`,
300
+ };
301
+ }
302
+ }
303
+ export async function handleGithubTest() {
304
+ const s = getStatus();
305
+ if (!s.connected) {
306
+ return {
307
+ status: 200,
308
+ contentType: "application/json",
309
+ body: JSON.stringify({ ok: false, message: "Not connected" }),
310
+ };
311
+ }
312
+ try {
313
+ const ok = await client().ping({ timeoutMs: 10_000 });
314
+ if (ok) {
315
+ return {
316
+ status: 200,
317
+ contentType: "application/json",
318
+ body: JSON.stringify({
319
+ ok: true,
320
+ message: `Connected as ${s.user ?? "unknown"}`,
321
+ }),
322
+ };
323
+ }
324
+ return {
325
+ status: 200,
326
+ contentType: "application/json",
327
+ body: JSON.stringify({ ok: false, message: "MCP ping failed" }),
328
+ };
329
+ }
330
+ catch (err) {
331
+ return {
332
+ status: 200,
333
+ contentType: "application/json",
334
+ body: JSON.stringify({
335
+ ok: false,
336
+ message: err instanceof Error ? err.message : String(err),
337
+ }),
338
+ };
339
+ }
340
+ }
341
+ export async function handleGithubDisconnect() {
342
+ await revoke("github");
343
+ _client = null;
344
+ return {
345
+ status: 200,
346
+ contentType: "application/json",
347
+ body: JSON.stringify({ ok: true }),
348
+ };
112
349
  }
113
350
  //# sourceMappingURL=github.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/connectors/github.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/connectors/github.ts"],"names":[],"mappings":"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"}
@@ -17,10 +17,13 @@ export interface GmailTokens {
17
17
  expiry_date?: number;
18
18
  token_type?: string;
19
19
  scope?: string;
20
+ /** Stored at auth time so refresh works even if env vars are absent. */
21
+ _client_id?: string;
22
+ _client_secret?: string;
20
23
  }
21
24
  export interface ConnectorStatus {
22
25
  id: string;
23
- status: "connected" | "disconnected";
26
+ status: "connected" | "disconnected" | "needs_reauth";
24
27
  lastSync?: string;
25
28
  email?: string;
26
29
  }
@@ -12,12 +12,16 @@
12
12
  * Client credentials read from env: GMAIL_CLIENT_ID, GMAIL_CLIENT_SECRET.
13
13
  */
14
14
  import crypto from "node:crypto";
15
- import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
15
+ import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
16
16
  import { homedir } from "node:os";
17
17
  import path from "node:path";
18
18
  const SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"];
19
- const REDIRECT_URI = "http://localhost:3100/connections/gmail/callback";
20
- const TOKEN_PATH = path.join(homedir(), ".patchwork", "tokens", "gmail.json");
19
+ const REDIRECT_URI = `http://localhost:${process.env.PATCHWORK_BRIDGE_PORT ?? "3101"}/connections/gmail/callback`;
20
+ function getTokenPath() {
21
+ const dir = process.env.PATCHWORK_TOKEN_DIR ??
22
+ path.join(homedir(), ".patchwork", "tokens");
23
+ return path.join(dir, "gmail.json");
24
+ }
21
25
  function clientId() {
22
26
  return process.env.GMAIL_CLIENT_ID ?? "";
23
27
  }
@@ -29,22 +33,26 @@ function isConfigured() {
29
33
  }
30
34
  // ── Token storage ─────────────────────────────────────────────────────────────
31
35
  export function loadTokens() {
32
- if (!existsSync(TOKEN_PATH))
36
+ const tokenPath = getTokenPath();
37
+ if (!existsSync(tokenPath))
33
38
  return null;
34
39
  try {
35
- return JSON.parse(readFileSync(TOKEN_PATH, "utf-8"));
40
+ return JSON.parse(readFileSync(tokenPath, "utf-8"));
36
41
  }
37
42
  catch {
38
43
  return null;
39
44
  }
40
45
  }
41
46
  function saveTokens(tokens) {
42
- mkdirSync(path.dirname(TOKEN_PATH), { recursive: true, mode: 0o700 });
43
- writeFileSync(TOKEN_PATH, JSON.stringify(tokens, null, 2), { mode: 0o600 });
47
+ const tokenPath = getTokenPath();
48
+ mkdirSync(path.dirname(tokenPath), { recursive: true, mode: 0o700 });
49
+ writeFileSync(tokenPath, JSON.stringify(tokens, null, 2), { mode: 0o600 });
50
+ chmodSync(tokenPath, 0o600);
44
51
  }
45
52
  function deleteTokens() {
46
- if (existsSync(TOKEN_PATH))
47
- unlinkSync(TOKEN_PATH);
53
+ const tokenPath = getTokenPath();
54
+ if (existsSync(tokenPath))
55
+ unlinkSync(tokenPath);
48
56
  }
49
57
  // ── OAuth helpers ─────────────────────────────────────────────────────────────
50
58
  function buildAuthUrl(state) {
@@ -84,18 +92,24 @@ async function exchangeCode(code) {
84
92
  : undefined,
85
93
  token_type: json.token_type,
86
94
  scope: json.scope,
95
+ _client_id: clientId() || undefined,
96
+ _client_secret: clientSecret() || undefined,
87
97
  };
88
98
  }
89
99
  async function refreshAccessToken(tokens) {
90
100
  if (!tokens.refresh_token)
91
101
  throw new Error("No refresh token available");
102
+ const id = clientId() || tokens._client_id || "";
103
+ const secret = clientSecret() || tokens._client_secret || "";
104
+ if (!id || !secret)
105
+ throw new Error("Gmail client credentials not available — reconnect the Gmail connector");
92
106
  const res = await fetch("https://oauth2.googleapis.com/token", {
93
107
  method: "POST",
94
108
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
95
109
  body: new URLSearchParams({
96
110
  refresh_token: tokens.refresh_token,
97
- client_id: clientId(),
98
- client_secret: clientSecret(),
111
+ client_id: id,
112
+ client_secret: secret,
99
113
  grant_type: "refresh_token",
100
114
  }).toString(),
101
115
  });
@@ -120,7 +134,7 @@ export async function getValidAccessToken() {
120
134
  if (!tokens)
121
135
  throw new Error("Gmail not connected");
122
136
  const bufferMs = 60_000;
123
- if (tokens.expiry_date && Date.now() > tokens.expiry_date - bufferMs) {
137
+ if (!tokens.expiry_date || Date.now() > tokens.expiry_date - bufferMs) {
124
138
  tokens = await refreshAccessToken(tokens);
125
139
  }
126
140
  return tokens.access_token;
@@ -143,14 +157,32 @@ function generateState() {
143
157
  setTimeout(() => pendingStates.delete(state), 10 * 60 * 1000); // 10 min TTL
144
158
  return state;
145
159
  }
160
+ function gmailStatus(tokens) {
161
+ if (!tokens)
162
+ return "disconnected";
163
+ const expired = !tokens.expiry_date || Date.now() > tokens.expiry_date;
164
+ const hasCredentials = Boolean((process.env.GMAIL_CLIENT_ID || tokens._client_id) &&
165
+ (process.env.GMAIL_CLIENT_SECRET || tokens._client_secret));
166
+ const canRefresh = Boolean(tokens.refresh_token) && hasCredentials;
167
+ return expired && !canRefresh ? "needs_reauth" : "connected";
168
+ }
146
169
  export async function handleConnectionsList() {
147
170
  const tokens = loadTokens();
148
171
  const { getStatus: getGitHubStatus } = await import("./github.js");
172
+ const { getStatus: getSentryStatus } = await import("./sentry.js");
173
+ const { getStatus: getLinearStatus } = await import("./linear.js");
174
+ const { getStatus: getCalendarStatus } = await import("./googleCalendar.js");
175
+ const { isConnected: isSlackConnected, getProfile: getSlackProfile } = await import("./slack.js");
149
176
  const gh = getGitHubStatus();
177
+ const sentry = getSentryStatus();
178
+ const linear = getLinearStatus();
179
+ const calendar = getCalendarStatus();
180
+ const slackConnected = isSlackConnected();
181
+ const slackProfile = getSlackProfile();
150
182
  const connectors = [
151
183
  {
152
184
  id: "gmail",
153
- status: tokens ? "connected" : "disconnected",
185
+ status: gmailStatus(tokens),
154
186
  lastSync: tokens ? new Date().toISOString() : undefined,
155
187
  },
156
188
  {
@@ -158,6 +190,26 @@ export async function handleConnectionsList() {
158
190
  status: gh.connected ? "connected" : "disconnected",
159
191
  lastSync: gh.connected ? new Date().toISOString() : undefined,
160
192
  },
193
+ {
194
+ id: "sentry",
195
+ status: sentry.status,
196
+ lastSync: sentry.lastSync,
197
+ },
198
+ {
199
+ id: "linear",
200
+ status: linear.status,
201
+ lastSync: linear.lastSync,
202
+ },
203
+ {
204
+ id: "google-calendar",
205
+ status: calendar.status,
206
+ lastSync: calendar.lastSync,
207
+ },
208
+ {
209
+ id: "slack",
210
+ status: slackConnected ? "connected" : "disconnected",
211
+ lastSync: slackConnected && slackProfile ? new Date().toISOString() : undefined,
212
+ },
161
213
  ];
162
214
  return {
163
215
  status: 200,
@@ -247,10 +299,19 @@ export async function handleGmailDisconnect() {
247
299
  };
248
300
  }
249
301
  // ── Callback HTML ─────────────────────────────────────────────────────────────
302
+ function escHtml(s) {
303
+ return s
304
+ .replace(/&/g, "&amp;")
305
+ .replace(/</g, "&lt;")
306
+ .replace(/>/g, "&gt;")
307
+ .replace(/"/g, "&quot;");
308
+ }
250
309
  function callbackHtml(title, message, success) {
251
310
  const color = success ? "#b8ff57" : "#ff5555";
311
+ const t = escHtml(title);
312
+ const m = escHtml(message);
252
313
  return `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">
253
- <title>${title} — Patchwork OS</title>
314
+ <title>${t} — Patchwork OS</title>
254
315
  <style>
255
316
  body { background: #040406; color: #e0e0e0; font-family: system-ui, sans-serif;
256
317
  display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; }
@@ -262,8 +323,8 @@ function callbackHtml(title, message, success) {
262
323
  </style>
263
324
  </head>
264
325
  <body><div class="card">
265
- <h1>${title}</h1>
266
- <p>${message}</p>
326
+ <h1>${t}</h1>
327
+ <p>${m}</p>
267
328
  <br><a href="javascript:window.close()">Close this tab</a>
268
329
  </div>
269
330
  <script>