patchwork-os 0.2.0-alpha.3 → 0.2.0-alpha.4

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 (51) hide show
  1. package/dist/bridge.js +23 -10
  2. package/dist/bridge.js.map +1 -1
  3. package/dist/connectors/github.d.ts +58 -8
  4. package/dist/connectors/github.js +321 -84
  5. package/dist/connectors/github.js.map +1 -1
  6. package/dist/connectors/gmail.js +7 -0
  7. package/dist/connectors/gmail.js.map +1 -1
  8. package/dist/connectors/googleCalendar.d.ts +57 -0
  9. package/dist/connectors/googleCalendar.js +308 -0
  10. package/dist/connectors/googleCalendar.js.map +1 -0
  11. package/dist/connectors/linear.d.ts +52 -19
  12. package/dist/connectors/linear.js +167 -129
  13. package/dist/connectors/linear.js.map +1 -1
  14. package/dist/connectors/mcpClient.d.ts +56 -0
  15. package/dist/connectors/mcpClient.js +189 -0
  16. package/dist/connectors/mcpClient.js.map +1 -0
  17. package/dist/connectors/mcpOAuth.d.ts +73 -0
  18. package/dist/connectors/mcpOAuth.js +338 -0
  19. package/dist/connectors/mcpOAuth.js.map +1 -0
  20. package/dist/connectors/sentry.d.ts +17 -21
  21. package/dist/connectors/sentry.js +124 -131
  22. package/dist/connectors/sentry.js.map +1 -1
  23. package/dist/index.js +1 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/recipes/yamlRunner.js +32 -42
  26. package/dist/recipes/yamlRunner.js.map +1 -1
  27. package/dist/recipesHttp.d.ts +13 -1
  28. package/dist/recipesHttp.js +9 -1
  29. package/dist/recipesHttp.js.map +1 -1
  30. package/dist/server.d.ts +3 -1
  31. package/dist/server.js +220 -49
  32. package/dist/server.js.map +1 -1
  33. package/dist/tools/createLinearIssue.d.ts +84 -0
  34. package/dist/tools/createLinearIssue.js +146 -0
  35. package/dist/tools/createLinearIssue.js.map +1 -0
  36. package/dist/tools/fetchCalendarEvents.d.ts +94 -0
  37. package/dist/tools/fetchCalendarEvents.js +97 -0
  38. package/dist/tools/fetchCalendarEvents.js.map +1 -0
  39. package/dist/tools/fetchGithubIssue.d.ts +80 -0
  40. package/dist/tools/fetchGithubIssue.js +84 -0
  41. package/dist/tools/fetchGithubIssue.js.map +1 -0
  42. package/dist/tools/fetchGithubPR.d.ts +89 -0
  43. package/dist/tools/fetchGithubPR.js +96 -0
  44. package/dist/tools/fetchGithubPR.js.map +1 -0
  45. package/dist/tools/index.js +8 -0
  46. package/dist/tools/index.js.map +1 -1
  47. package/package.json +1 -1
  48. package/scripts/start-all.sh +56 -19
  49. package/templates/recipes/ctx-loop-test.yaml +75 -0
  50. package/templates/recipes/morning-brief.yaml +12 -4
  51. package/templates/recipes/sentry-to-linear.yaml +77 -0
@@ -0,0 +1,308 @@
1
+ /**
2
+ * Google Calendar OAuth 2.0 connector.
3
+ *
4
+ * Handles:
5
+ * GET /connections/google-calendar/auth — redirect to Google consent screen
6
+ * GET /connections/google-calendar/callback — exchange code for tokens, store locally
7
+ * POST /connections/google-calendar/test — verify stored token works
8
+ * DELETE /connections/google-calendar — revoke + delete stored token
9
+ *
10
+ * Tokens stored at ~/.patchwork/tokens/google-calendar.json (mode 0600).
11
+ * Client credentials read from env: GOOGLE_CALENDAR_CLIENT_ID, GOOGLE_CALENDAR_CLIENT_SECRET
12
+ */
13
+ import crypto from "node:crypto";
14
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
15
+ import { homedir } from "node:os";
16
+ import path from "node:path";
17
+ const SCOPES = ["https://www.googleapis.com/auth/calendar.readonly"];
18
+ const REDIRECT_URI = process.env.PATCHWORK_DASHBOARD_URL
19
+ ? `${process.env.PATCHWORK_DASHBOARD_URL}/connections/google-calendar/callback`
20
+ : "http://localhost:3200/connections/google-calendar/callback";
21
+ const CALENDAR_API = "https://www.googleapis.com/calendar/v3";
22
+ const TOKEN_PATH = path.join(homedir(), ".patchwork", "tokens", "google-calendar.json");
23
+ function clientId() {
24
+ return process.env.GOOGLE_CALENDAR_CLIENT_ID ?? "";
25
+ }
26
+ function clientSecret() {
27
+ return process.env.GOOGLE_CALENDAR_CLIENT_SECRET ?? "";
28
+ }
29
+ function isConfigured() {
30
+ return Boolean(clientId() && clientSecret());
31
+ }
32
+ // ── Token storage ─────────────────────────────────────────────────────────────
33
+ export function loadTokens() {
34
+ if (!existsSync(TOKEN_PATH))
35
+ return null;
36
+ try {
37
+ return JSON.parse(readFileSync(TOKEN_PATH, "utf-8"));
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ }
43
+ function saveTokens(tokens) {
44
+ mkdirSync(path.dirname(TOKEN_PATH), { recursive: true, mode: 0o700 });
45
+ writeFileSync(TOKEN_PATH, JSON.stringify(tokens, null, 2), { mode: 0o600 });
46
+ }
47
+ function deleteTokens() {
48
+ if (existsSync(TOKEN_PATH))
49
+ unlinkSync(TOKEN_PATH);
50
+ }
51
+ export function getStatus() {
52
+ const tokens = loadTokens();
53
+ return {
54
+ id: "google-calendar",
55
+ status: tokens ? "connected" : "disconnected",
56
+ lastSync: tokens?.connected_at,
57
+ calendarId: tokens?.calendar_id,
58
+ };
59
+ }
60
+ // ── OAuth helpers ─────────────────────────────────────────────────────────────
61
+ function buildAuthUrl(state) {
62
+ const params = new URLSearchParams({
63
+ client_id: clientId(),
64
+ redirect_uri: REDIRECT_URI,
65
+ response_type: "code",
66
+ scope: SCOPES.join(" "),
67
+ access_type: "offline",
68
+ prompt: "consent",
69
+ state,
70
+ });
71
+ return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
72
+ }
73
+ async function exchangeCode(code) {
74
+ const res = await fetch("https://oauth2.googleapis.com/token", {
75
+ method: "POST",
76
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
77
+ body: new URLSearchParams({
78
+ code,
79
+ client_id: clientId(),
80
+ client_secret: clientSecret(),
81
+ redirect_uri: REDIRECT_URI,
82
+ grant_type: "authorization_code",
83
+ }).toString(),
84
+ });
85
+ if (!res.ok) {
86
+ const body = await res.text();
87
+ throw new Error(`Token exchange failed: ${res.status} ${body}`);
88
+ }
89
+ const json = (await res.json());
90
+ return {
91
+ access_token: json.access_token,
92
+ refresh_token: json.refresh_token,
93
+ expiry_date: json.expires_in
94
+ ? Date.now() + json.expires_in * 1000
95
+ : undefined,
96
+ token_type: json.token_type,
97
+ scope: json.scope,
98
+ };
99
+ }
100
+ async function refreshAccessToken(tokens) {
101
+ if (!tokens.refresh_token)
102
+ throw new Error("No refresh token available");
103
+ const res = await fetch("https://oauth2.googleapis.com/token", {
104
+ method: "POST",
105
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
106
+ body: new URLSearchParams({
107
+ refresh_token: tokens.refresh_token,
108
+ client_id: clientId(),
109
+ client_secret: clientSecret(),
110
+ grant_type: "refresh_token",
111
+ }).toString(),
112
+ });
113
+ if (!res.ok) {
114
+ const body = await res.text();
115
+ throw new Error(`Token refresh failed: ${res.status} ${body}`);
116
+ }
117
+ const json = (await res.json());
118
+ const updated = {
119
+ ...tokens,
120
+ access_token: json.access_token,
121
+ expiry_date: json.expires_in
122
+ ? Date.now() + json.expires_in * 1000
123
+ : tokens.expiry_date,
124
+ };
125
+ saveTokens(updated);
126
+ return updated;
127
+ }
128
+ /** Returns a valid access token, refreshing if needed. */
129
+ export async function getValidAccessToken() {
130
+ let tokens = loadTokens();
131
+ if (!tokens)
132
+ throw new Error("Google Calendar not connected");
133
+ const bufferMs = 60_000;
134
+ if (tokens.expiry_date && Date.now() > tokens.expiry_date - bufferMs) {
135
+ tokens = await refreshAccessToken(tokens);
136
+ }
137
+ return tokens.access_token;
138
+ }
139
+ async function revokeToken(token) {
140
+ await fetch(`https://oauth2.googleapis.com/revoke?token=${encodeURIComponent(token)}`, { method: "POST" }).catch(() => { });
141
+ }
142
+ // ── State map (in-memory CSRF protection) ────────────────────────────────────
143
+ const pendingStates = new Set();
144
+ function generateState() {
145
+ const state = crypto.randomBytes(32).toString("hex");
146
+ pendingStates.add(state);
147
+ setTimeout(() => pendingStates.delete(state), 10 * 60 * 1000);
148
+ return state;
149
+ }
150
+ // ── API helpers ───────────────────────────────────────────────────────────────
151
+ async function calendarGet(endpoint, accessToken, params = {}, signal) {
152
+ const qs = new URLSearchParams(params);
153
+ const res = await fetch(`${CALENDAR_API}${endpoint}?${qs}`, {
154
+ headers: { Authorization: `Bearer ${accessToken}` },
155
+ signal,
156
+ });
157
+ if (!res.ok) {
158
+ const body = await res.text();
159
+ throw new Error(`Google Calendar API error ${res.status}: ${body.slice(0, 200)}`);
160
+ }
161
+ return res.json();
162
+ }
163
+ async function fetchCalendarSummary(accessToken, calendarId) {
164
+ const data = (await calendarGet(`/calendars/${encodeURIComponent(calendarId)}`, accessToken));
165
+ return data.summary ?? calendarId;
166
+ }
167
+ // ── Event fetching ────────────────────────────────────────────────────────────
168
+ export async function listEvents(opts = {}, signal) {
169
+ const accessToken = await getValidAccessToken();
170
+ const tokens = loadTokens();
171
+ const calendarId = opts.calendarId ?? tokens.calendar_id ?? "primary";
172
+ const daysAhead = Math.min(opts.daysAhead ?? 7, 30);
173
+ const maxResults = Math.min(opts.maxResults ?? 20, 50);
174
+ const now = new Date();
175
+ const end = new Date(now.getTime() + daysAhead * 24 * 60 * 60 * 1000);
176
+ const data = (await calendarGet(`/calendars/${encodeURIComponent(calendarId)}/events`, accessToken, {
177
+ timeMin: now.toISOString(),
178
+ timeMax: end.toISOString(),
179
+ maxResults: String(maxResults),
180
+ singleEvents: "true",
181
+ orderBy: "startTime",
182
+ }, signal));
183
+ return (data.items ?? []).map((item) => {
184
+ const startRaw = item.start?.dateTime ?? item.start?.date ?? "";
185
+ const endRaw = item.end?.dateTime ?? item.end?.date ?? "";
186
+ const allDay = !item.start?.dateTime;
187
+ return {
188
+ id: item.id,
189
+ summary: item.summary ?? "(no title)",
190
+ description: item.description,
191
+ start: startRaw,
192
+ end: endRaw,
193
+ allDay,
194
+ location: item.location,
195
+ htmlLink: item.htmlLink ?? "",
196
+ attendees: (item.attendees ?? [])
197
+ .map((a) => a.displayName ?? a.email ?? "")
198
+ .filter(Boolean),
199
+ };
200
+ });
201
+ }
202
+ // ── HTTP handlers ─────────────────────────────────────────────────────────────
203
+ export function handleCalendarAuthRedirect() {
204
+ if (!isConfigured()) {
205
+ return {
206
+ status: 400,
207
+ contentType: "application/json",
208
+ body: JSON.stringify({
209
+ ok: false,
210
+ error: "GOOGLE_CALENDAR_CLIENT_ID and GOOGLE_CALENDAR_CLIENT_SECRET env vars not set",
211
+ }),
212
+ };
213
+ }
214
+ const state = generateState();
215
+ return { status: 302, body: "", redirect: buildAuthUrl(state) };
216
+ }
217
+ export async function handleCalendarCallback(code, state, error) {
218
+ if (error) {
219
+ return {
220
+ status: 400,
221
+ contentType: "application/json",
222
+ body: JSON.stringify({ ok: false, error }),
223
+ };
224
+ }
225
+ if (!code || !state || !pendingStates.has(state)) {
226
+ return {
227
+ status: 400,
228
+ contentType: "application/json",
229
+ body: JSON.stringify({ ok: false, error: "Invalid OAuth state" }),
230
+ };
231
+ }
232
+ pendingStates.delete(state);
233
+ try {
234
+ const oauthTokens = await exchangeCode(code);
235
+ // Default to "primary" calendar; user can update later
236
+ const calId = "primary";
237
+ const summary = await fetchCalendarSummary(oauthTokens.access_token, calId);
238
+ const tokens = {
239
+ ...oauthTokens,
240
+ calendar_id: calId,
241
+ connected_at: new Date().toISOString(),
242
+ };
243
+ saveTokens(tokens);
244
+ return {
245
+ status: 200,
246
+ contentType: "application/json",
247
+ body: JSON.stringify({ ok: true, calendarId: calId, summary }),
248
+ };
249
+ }
250
+ catch (err) {
251
+ return {
252
+ status: 400,
253
+ contentType: "application/json",
254
+ body: JSON.stringify({
255
+ ok: false,
256
+ error: err instanceof Error ? err.message : String(err),
257
+ }),
258
+ };
259
+ }
260
+ }
261
+ export async function handleCalendarTest() {
262
+ const tokens = loadTokens();
263
+ if (!tokens) {
264
+ return {
265
+ status: 400,
266
+ contentType: "application/json",
267
+ body: JSON.stringify({
268
+ ok: false,
269
+ error: "Google Calendar not connected",
270
+ }),
271
+ };
272
+ }
273
+ try {
274
+ const accessToken = await getValidAccessToken();
275
+ const summary = await fetchCalendarSummary(accessToken, tokens.calendar_id);
276
+ return {
277
+ status: 200,
278
+ contentType: "application/json",
279
+ body: JSON.stringify({
280
+ ok: true,
281
+ calendarId: tokens.calendar_id,
282
+ summary,
283
+ }),
284
+ };
285
+ }
286
+ catch (err) {
287
+ return {
288
+ status: 400,
289
+ contentType: "application/json",
290
+ body: JSON.stringify({
291
+ ok: false,
292
+ error: err instanceof Error ? err.message : String(err),
293
+ }),
294
+ };
295
+ }
296
+ }
297
+ export async function handleCalendarDisconnect() {
298
+ const tokens = loadTokens();
299
+ if (tokens?.access_token)
300
+ await revokeToken(tokens.access_token);
301
+ deleteTokens();
302
+ return {
303
+ status: 200,
304
+ contentType: "application/json",
305
+ body: JSON.stringify({ ok: true }),
306
+ };
307
+ }
308
+ //# sourceMappingURL=googleCalendar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"googleCalendar.js","sourceRoot":"","sources":["../../src/connectors/googleCalendar.ts"],"names":[],"mappings":"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"}
@@ -1,14 +1,17 @@
1
1
  /**
2
- * Linear connector.
2
+ * Linear connector — routes through Linear's official MCP server.
3
3
  *
4
- * Uses Linear's GraphQL API with a personal API key (no OAuth app required).
5
- * Token stored at ~/.patchwork/tokens/linear.json (mode 0600).
6
- * Env var: LINEAR_API_KEY
4
+ * Endpoint: https://mcp.linear.app/mcp
5
+ * Auth: OAuth 2.1 w/ PKCE; dynamic client registration (RFC 7591).
7
6
  *
8
- * HTTP routes registered in server.ts:
9
- * POST /connections/linear/connect store token + verify
10
- * POST /connections/linear/test verify stored token works
11
- * DELETE /connections/linear delete stored token
7
+ * HTTP routes (wired in src/server.ts):
8
+ * GET /connections/linear/authorize returns { url } for popup
9
+ * GET /connections/linear/callback — token exchange
10
+ * POST /connections/linear/test ping MCP server
11
+ * DELETE /connections/linear — revoke + delete token
12
+ *
13
+ * Back-compat: loadTokens() returns a shape compatible with legacy code
14
+ * that expected { api_key }. Set LINEAR_API_KEY to bypass OAuth for CI/headless.
12
15
  */
13
16
  export interface LinearTokens {
14
17
  api_key: string;
@@ -21,9 +24,14 @@ export interface ConnectorStatus {
21
24
  lastSync?: string;
22
25
  workspace?: string;
23
26
  }
27
+ export interface ConnectorHandlerResult {
28
+ status: number;
29
+ body: string;
30
+ contentType?: string;
31
+ redirect?: string;
32
+ }
24
33
  export declare function loadTokens(): LinearTokens | null;
25
34
  export declare function getStatus(): ConnectorStatus;
26
- export declare function linearQuery<T>(query: string, variables: Record<string, unknown>, apiKey: string, signal?: AbortSignal): Promise<T>;
27
35
  export interface LinearIssue {
28
36
  id: string;
29
37
  identifier: string;
@@ -52,16 +60,41 @@ export interface LinearIssue {
52
60
  }>;
53
61
  };
54
62
  }
55
- /**
56
- * Fetch a Linear issue by ID or URL.
57
- * Accepts: "LIN-123", "abc123def456...", "https://linear.app/.../issue/LIN-123/..."
58
- */
59
63
  export declare function fetchIssue(issueIdOrUrl: string, signal?: AbortSignal): Promise<LinearIssue>;
60
- export interface ConnectorHandlerResult {
61
- status: number;
62
- body: string;
63
- contentType?: string;
64
+ export interface ListLinearIssuesOpts {
65
+ team?: string;
66
+ assigneeMe?: boolean;
67
+ states?: string[];
68
+ limit?: number;
64
69
  }
65
- export declare function handleLinearConnect(body: unknown): Promise<ConnectorHandlerResult>;
70
+ export declare function listIssues(opts?: ListLinearIssuesOpts, signal?: AbortSignal): Promise<Record<string, unknown>[]>;
71
+ export declare function handleLinearAuthorize(): Promise<ConnectorHandlerResult>;
72
+ export declare function handleLinearCallback(code: string | null, state: string | null, error: string | null): Promise<ConnectorHandlerResult>;
66
73
  export declare function handleLinearTest(): Promise<ConnectorHandlerResult>;
67
- export declare function handleLinearDisconnect(): ConnectorHandlerResult;
74
+ export declare function handleLinearDisconnect(): Promise<ConnectorHandlerResult>;
75
+ export interface LinearTeam {
76
+ id: string;
77
+ key: string;
78
+ name: string;
79
+ }
80
+ export declare function listTeams(signal?: AbortSignal): Promise<LinearTeam[]>;
81
+ export declare function listLabels(signal?: AbortSignal): Promise<Array<{
82
+ id: string;
83
+ name: string;
84
+ }>>;
85
+ export interface CreateIssueInput {
86
+ team: string;
87
+ title: string;
88
+ description?: string;
89
+ priority?: number;
90
+ labels?: string[];
91
+ }
92
+ export declare function createIssue(input: CreateIssueInput, signal?: AbortSignal): Promise<{
93
+ id: string;
94
+ identifier: string;
95
+ title: string;
96
+ url: string;
97
+ state: {
98
+ name: string;
99
+ };
100
+ }>;