patchwork-os 0.2.0-alpha.0 → 0.2.0-alpha.3
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 +41 -46
- package/dist/claudeDriver.d.ts +3 -1
- package/dist/claudeDriver.js +48 -0
- package/dist/claudeDriver.js.map +1 -1
- package/dist/commands/dashboard.d.ts +47 -0
- package/dist/commands/dashboard.js +319 -0
- package/dist/commands/dashboard.js.map +1 -0
- package/dist/config.d.ts +2 -2
- package/dist/config.js +5 -2
- package/dist/config.js.map +1 -1
- package/dist/connectors/github.d.ts +44 -0
- package/dist/connectors/github.js +113 -0
- package/dist/connectors/github.js.map +1 -0
- package/dist/connectors/gmail.d.ts +40 -0
- package/dist/connectors/gmail.js +289 -0
- package/dist/connectors/gmail.js.map +1 -0
- package/dist/connectors/linear.d.ts +67 -0
- package/dist/connectors/linear.js +198 -0
- package/dist/connectors/linear.js.map +1 -0
- package/dist/connectors/sentry.d.ts +47 -0
- package/dist/connectors/sentry.js +204 -0
- package/dist/connectors/sentry.js.map +1 -0
- package/dist/drivers/claude/api.d.ts +11 -0
- package/dist/drivers/claude/api.js +54 -0
- package/dist/drivers/claude/api.js.map +1 -0
- package/dist/drivers/claude/envSanitizer.d.ts +7 -0
- package/dist/drivers/claude/envSanitizer.js +18 -0
- package/dist/drivers/claude/envSanitizer.js.map +1 -0
- package/dist/drivers/claude/streamParser.d.ts +38 -0
- package/dist/drivers/claude/streamParser.js +34 -0
- package/dist/drivers/claude/streamParser.js.map +1 -0
- package/dist/drivers/claude/subprocess.d.ts +19 -0
- package/dist/drivers/claude/subprocess.js +216 -0
- package/dist/drivers/claude/subprocess.js.map +1 -0
- package/dist/drivers/claude/subprocessSettings.d.ts +9 -0
- package/dist/drivers/claude/subprocessSettings.js +55 -0
- package/dist/drivers/claude/subprocessSettings.js.map +1 -0
- package/dist/drivers/gemini/index.d.ts +14 -0
- package/dist/drivers/gemini/index.js +176 -0
- package/dist/drivers/gemini/index.js.map +1 -0
- package/dist/drivers/grok/index.d.ts +11 -0
- package/dist/drivers/grok/index.js +22 -0
- package/dist/drivers/grok/index.js.map +1 -0
- package/dist/drivers/index.d.ts +18 -0
- package/dist/drivers/index.js +31 -0
- package/dist/drivers/index.js.map +1 -0
- package/dist/drivers/openai/index.d.ts +24 -0
- package/dist/drivers/openai/index.js +110 -0
- package/dist/drivers/openai/index.js.map +1 -0
- package/dist/drivers/types.d.ts +72 -0
- package/dist/drivers/types.js +30 -0
- package/dist/drivers/types.js.map +1 -0
- package/dist/index.js +116 -22
- package/dist/index.js.map +1 -1
- package/dist/recipes/yamlRunner.d.ts +95 -0
- package/dist/recipes/yamlRunner.js +598 -0
- package/dist/recipes/yamlRunner.js.map +1 -0
- package/dist/server.js +272 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/ctxGetTaskContext.d.ts +4 -1
- package/dist/tools/ctxGetTaskContext.js +45 -2
- package/dist/tools/ctxGetTaskContext.js.map +1 -1
- package/dist/tools/fetchLinearIssue.d.ts +112 -0
- package/dist/tools/fetchLinearIssue.js +129 -0
- package/dist/tools/fetchLinearIssue.js.map +1 -0
- package/dist/tools/fetchSentryIssue.d.ts +143 -0
- package/dist/tools/fetchSentryIssue.js +150 -0
- package/dist/tools/fetchSentryIssue.js.map +1 -0
- package/dist/tools/index.js +4 -0
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/recipes/gmail-health-check.yaml +19 -0
- package/templates/recipes/inbox-triage.yaml +15 -0
- package/templates/recipes/morning-brief.yaml +64 -0
- package/templates/scheduled-tasks/morning-brief/SKILL.md +37 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linear connector.
|
|
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
|
|
7
|
+
*
|
|
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
|
|
12
|
+
*/
|
|
13
|
+
export interface LinearTokens {
|
|
14
|
+
api_key: string;
|
|
15
|
+
workspace?: string;
|
|
16
|
+
connected_at: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ConnectorStatus {
|
|
19
|
+
id: string;
|
|
20
|
+
status: "connected" | "disconnected";
|
|
21
|
+
lastSync?: string;
|
|
22
|
+
workspace?: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function loadTokens(): LinearTokens | null;
|
|
25
|
+
export declare function getStatus(): ConnectorStatus;
|
|
26
|
+
export declare function linearQuery<T>(query: string, variables: Record<string, unknown>, apiKey: string, signal?: AbortSignal): Promise<T>;
|
|
27
|
+
export interface LinearIssue {
|
|
28
|
+
id: string;
|
|
29
|
+
identifier: string;
|
|
30
|
+
title: string;
|
|
31
|
+
description?: string;
|
|
32
|
+
state: {
|
|
33
|
+
name: string;
|
|
34
|
+
type: string;
|
|
35
|
+
};
|
|
36
|
+
assignee?: {
|
|
37
|
+
name: string;
|
|
38
|
+
email: string;
|
|
39
|
+
};
|
|
40
|
+
priority: number;
|
|
41
|
+
priorityLabel: string;
|
|
42
|
+
url: string;
|
|
43
|
+
createdAt: string;
|
|
44
|
+
updatedAt: string;
|
|
45
|
+
team: {
|
|
46
|
+
name: string;
|
|
47
|
+
key: string;
|
|
48
|
+
};
|
|
49
|
+
labels: {
|
|
50
|
+
nodes: Array<{
|
|
51
|
+
name: string;
|
|
52
|
+
}>;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Fetch a Linear issue by ID or URL.
|
|
57
|
+
* Accepts: "LIN-123", "abc123def456...", "https://linear.app/.../issue/LIN-123/..."
|
|
58
|
+
*/
|
|
59
|
+
export declare function fetchIssue(issueIdOrUrl: string, signal?: AbortSignal): Promise<LinearIssue>;
|
|
60
|
+
export interface ConnectorHandlerResult {
|
|
61
|
+
status: number;
|
|
62
|
+
body: string;
|
|
63
|
+
contentType?: string;
|
|
64
|
+
}
|
|
65
|
+
export declare function handleLinearConnect(body: unknown): Promise<ConnectorHandlerResult>;
|
|
66
|
+
export declare function handleLinearTest(): Promise<ConnectorHandlerResult>;
|
|
67
|
+
export declare function handleLinearDisconnect(): ConnectorHandlerResult;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linear connector.
|
|
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
|
|
7
|
+
*
|
|
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
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
14
|
+
import { homedir } from "node:os";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
const LINEAR_API = "https://api.linear.app/graphql";
|
|
17
|
+
const TOKEN_PATH = path.join(homedir(), ".patchwork", "tokens", "linear.json");
|
|
18
|
+
// ── Token storage ─────────────────────────────────────────────────────────────
|
|
19
|
+
export function loadTokens() {
|
|
20
|
+
const envKey = process.env.LINEAR_API_KEY;
|
|
21
|
+
if (envKey) {
|
|
22
|
+
return { api_key: envKey, connected_at: new Date().toISOString() };
|
|
23
|
+
}
|
|
24
|
+
if (!existsSync(TOKEN_PATH))
|
|
25
|
+
return null;
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(readFileSync(TOKEN_PATH, "utf-8"));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function saveTokens(tokens) {
|
|
34
|
+
mkdirSync(path.dirname(TOKEN_PATH), { recursive: true, mode: 0o700 });
|
|
35
|
+
writeFileSync(TOKEN_PATH, JSON.stringify(tokens, null, 2), { mode: 0o600 });
|
|
36
|
+
}
|
|
37
|
+
function deleteTokens() {
|
|
38
|
+
if (existsSync(TOKEN_PATH))
|
|
39
|
+
unlinkSync(TOKEN_PATH);
|
|
40
|
+
}
|
|
41
|
+
export function getStatus() {
|
|
42
|
+
const tokens = loadTokens();
|
|
43
|
+
return {
|
|
44
|
+
id: "linear",
|
|
45
|
+
status: tokens ? "connected" : "disconnected",
|
|
46
|
+
lastSync: tokens?.connected_at,
|
|
47
|
+
workspace: tokens?.workspace,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// ── GraphQL helpers ───────────────────────────────────────────────────────────
|
|
51
|
+
export async function linearQuery(query, variables, apiKey, signal) {
|
|
52
|
+
const res = await fetch(LINEAR_API, {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: {
|
|
55
|
+
"Content-Type": "application/json",
|
|
56
|
+
Authorization: apiKey,
|
|
57
|
+
},
|
|
58
|
+
body: JSON.stringify({ query, variables }),
|
|
59
|
+
signal,
|
|
60
|
+
});
|
|
61
|
+
if (!res.ok) {
|
|
62
|
+
const body = await res.text();
|
|
63
|
+
throw new Error(`Linear API error ${res.status}: ${body.slice(0, 200)}`);
|
|
64
|
+
}
|
|
65
|
+
const json = (await res.json());
|
|
66
|
+
if (json.errors?.length) {
|
|
67
|
+
throw new Error(`Linear GraphQL error: ${json.errors.map((e) => e.message).join(", ")}`);
|
|
68
|
+
}
|
|
69
|
+
return json.data;
|
|
70
|
+
}
|
|
71
|
+
const VIEWER_QUERY = `query { viewer { id name email organization { name urlKey } } }`;
|
|
72
|
+
async function verifyToken(apiKey, signal) {
|
|
73
|
+
const data = await linearQuery(VIEWER_QUERY, {}, apiKey, signal);
|
|
74
|
+
return {
|
|
75
|
+
name: data.viewer.name,
|
|
76
|
+
email: data.viewer.email,
|
|
77
|
+
workspace: data.viewer.organization.urlKey,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const ISSUE_QUERY = `
|
|
81
|
+
query GetIssue($id: String!) {
|
|
82
|
+
issue(id: $id) {
|
|
83
|
+
id
|
|
84
|
+
identifier
|
|
85
|
+
title
|
|
86
|
+
description
|
|
87
|
+
state { name type }
|
|
88
|
+
assignee { name email }
|
|
89
|
+
priority
|
|
90
|
+
priorityLabel
|
|
91
|
+
url
|
|
92
|
+
createdAt
|
|
93
|
+
updatedAt
|
|
94
|
+
team { name key }
|
|
95
|
+
labels { nodes { name } }
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
`;
|
|
99
|
+
/**
|
|
100
|
+
* Fetch a Linear issue by ID or URL.
|
|
101
|
+
* Accepts: "LIN-123", "abc123def456...", "https://linear.app/.../issue/LIN-123/..."
|
|
102
|
+
*/
|
|
103
|
+
export async function fetchIssue(issueIdOrUrl, signal) {
|
|
104
|
+
const tokens = loadTokens();
|
|
105
|
+
if (!tokens) {
|
|
106
|
+
throw new Error("Linear not connected. POST /connections/linear/connect first.");
|
|
107
|
+
}
|
|
108
|
+
const id = extractIssueId(issueIdOrUrl);
|
|
109
|
+
const data = await linearQuery(ISSUE_QUERY, { id }, tokens.api_key, signal);
|
|
110
|
+
if (!data.issue) {
|
|
111
|
+
throw new Error(`Linear issue not found: ${id}`);
|
|
112
|
+
}
|
|
113
|
+
return data.issue;
|
|
114
|
+
}
|
|
115
|
+
function extractIssueId(issueIdOrUrl) {
|
|
116
|
+
// URL form: https://linear.app/org/issue/LIN-123/title
|
|
117
|
+
const urlMatch = issueIdOrUrl.match(/\/issue\/([A-Z]+-\d+|[a-f0-9-]{36})/i);
|
|
118
|
+
if (urlMatch)
|
|
119
|
+
return urlMatch[1];
|
|
120
|
+
// Identifier form: LIN-123, TEAM-456
|
|
121
|
+
if (/^[A-Z]+-\d+$/i.test(issueIdOrUrl.trim()))
|
|
122
|
+
return issueIdOrUrl.trim();
|
|
123
|
+
// UUID form
|
|
124
|
+
if (/^[a-f0-9-]{36}$/i.test(issueIdOrUrl.trim()))
|
|
125
|
+
return issueIdOrUrl.trim();
|
|
126
|
+
throw new Error(`Cannot parse Linear issue ID from: ${issueIdOrUrl}`);
|
|
127
|
+
}
|
|
128
|
+
export async function handleLinearConnect(body) {
|
|
129
|
+
const { api_key } = (body ?? {});
|
|
130
|
+
if (!api_key || typeof api_key !== "string") {
|
|
131
|
+
return {
|
|
132
|
+
status: 400,
|
|
133
|
+
contentType: "application/json",
|
|
134
|
+
body: JSON.stringify({ ok: false, error: "api_key required" }),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const { name, email, workspace } = await verifyToken(api_key);
|
|
139
|
+
const tokens = {
|
|
140
|
+
api_key,
|
|
141
|
+
workspace,
|
|
142
|
+
connected_at: new Date().toISOString(),
|
|
143
|
+
};
|
|
144
|
+
saveTokens(tokens);
|
|
145
|
+
return {
|
|
146
|
+
status: 200,
|
|
147
|
+
contentType: "application/json",
|
|
148
|
+
body: JSON.stringify({ ok: true, name, email, workspace }),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
return {
|
|
153
|
+
status: 400,
|
|
154
|
+
contentType: "application/json",
|
|
155
|
+
body: JSON.stringify({
|
|
156
|
+
ok: false,
|
|
157
|
+
error: err instanceof Error ? err.message : String(err),
|
|
158
|
+
}),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
export async function handleLinearTest() {
|
|
163
|
+
const tokens = loadTokens();
|
|
164
|
+
if (!tokens) {
|
|
165
|
+
return {
|
|
166
|
+
status: 400,
|
|
167
|
+
contentType: "application/json",
|
|
168
|
+
body: JSON.stringify({ ok: false, error: "Linear not connected" }),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
const { name, email } = await verifyToken(tokens.api_key);
|
|
173
|
+
return {
|
|
174
|
+
status: 200,
|
|
175
|
+
contentType: "application/json",
|
|
176
|
+
body: JSON.stringify({ ok: true, name, email }),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
return {
|
|
181
|
+
status: 400,
|
|
182
|
+
contentType: "application/json",
|
|
183
|
+
body: JSON.stringify({
|
|
184
|
+
ok: false,
|
|
185
|
+
error: err instanceof Error ? err.message : String(err),
|
|
186
|
+
}),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
export function handleLinearDisconnect() {
|
|
191
|
+
deleteTokens();
|
|
192
|
+
return {
|
|
193
|
+
status: 200,
|
|
194
|
+
contentType: "application/json",
|
|
195
|
+
body: JSON.stringify({ ok: true }),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=linear.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linear.js","sourceRoot":"","sources":["../../src/connectors/linear.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentry connector.
|
|
3
|
+
*
|
|
4
|
+
* Uses Sentry's REST API with an auth token (no OAuth app required).
|
|
5
|
+
* Token stored at ~/.patchwork/tokens/sentry.json (mode 0600).
|
|
6
|
+
* Env vars: SENTRY_AUTH_TOKEN, SENTRY_ORG (optional default org slug).
|
|
7
|
+
*
|
|
8
|
+
* HTTP routes registered in bridge.ts:
|
|
9
|
+
* POST /connections/sentry/connect — store token + verify
|
|
10
|
+
* POST /connections/sentry/test — verify stored token works
|
|
11
|
+
* DELETE /connections/sentry — delete stored token
|
|
12
|
+
*
|
|
13
|
+
* MCP tool: fetchSentryIssue — fetches a Sentry issue/event and returns
|
|
14
|
+
* the stack trace string, ready to pass into enrichStackTrace.
|
|
15
|
+
*/
|
|
16
|
+
export interface SentryTokens {
|
|
17
|
+
auth_token: string;
|
|
18
|
+
org?: string;
|
|
19
|
+
connected_at: string;
|
|
20
|
+
}
|
|
21
|
+
export interface ConnectorStatus {
|
|
22
|
+
id: string;
|
|
23
|
+
status: "connected" | "disconnected";
|
|
24
|
+
lastSync?: string;
|
|
25
|
+
org?: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function loadTokens(): SentryTokens | null;
|
|
28
|
+
export declare function getStatus(): ConnectorStatus;
|
|
29
|
+
/**
|
|
30
|
+
* Fetch the latest event for a Sentry issue and extract the stack trace text.
|
|
31
|
+
* issueIdOrUrl accepts:
|
|
32
|
+
* - A numeric issue ID: "12345"
|
|
33
|
+
* - A Sentry issue URL: "https://sentry.io/organizations/my-org/issues/12345/"
|
|
34
|
+
*/
|
|
35
|
+
export declare function fetchIssueStackTrace(issueIdOrUrl: string, signal?: AbortSignal): Promise<{
|
|
36
|
+
stackTrace: string;
|
|
37
|
+
title: string;
|
|
38
|
+
issueId: string;
|
|
39
|
+
}>;
|
|
40
|
+
export interface ConnectorHandlerResult {
|
|
41
|
+
status: number;
|
|
42
|
+
body: string;
|
|
43
|
+
contentType?: string;
|
|
44
|
+
}
|
|
45
|
+
export declare function handleSentryConnect(body: unknown): Promise<ConnectorHandlerResult>;
|
|
46
|
+
export declare function handleSentryTest(): Promise<ConnectorHandlerResult>;
|
|
47
|
+
export declare function handleSentryDisconnect(): ConnectorHandlerResult;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentry connector.
|
|
3
|
+
*
|
|
4
|
+
* Uses Sentry's REST API with an auth token (no OAuth app required).
|
|
5
|
+
* Token stored at ~/.patchwork/tokens/sentry.json (mode 0600).
|
|
6
|
+
* Env vars: SENTRY_AUTH_TOKEN, SENTRY_ORG (optional default org slug).
|
|
7
|
+
*
|
|
8
|
+
* HTTP routes registered in bridge.ts:
|
|
9
|
+
* POST /connections/sentry/connect — store token + verify
|
|
10
|
+
* POST /connections/sentry/test — verify stored token works
|
|
11
|
+
* DELETE /connections/sentry — delete stored token
|
|
12
|
+
*
|
|
13
|
+
* MCP tool: fetchSentryIssue — fetches a Sentry issue/event and returns
|
|
14
|
+
* the stack trace string, ready to pass into enrichStackTrace.
|
|
15
|
+
*/
|
|
16
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
17
|
+
import { homedir } from "node:os";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
const SENTRY_API = "https://sentry.io/api/0";
|
|
20
|
+
const TOKEN_PATH = path.join(homedir(), ".patchwork", "tokens", "sentry.json");
|
|
21
|
+
// ── Token storage ─────────────────────────────────────────────────────────────
|
|
22
|
+
export function loadTokens() {
|
|
23
|
+
if (!existsSync(TOKEN_PATH))
|
|
24
|
+
return null;
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(readFileSync(TOKEN_PATH, "utf-8"));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function saveTokens(tokens) {
|
|
33
|
+
mkdirSync(path.dirname(TOKEN_PATH), { recursive: true, mode: 0o700 });
|
|
34
|
+
writeFileSync(TOKEN_PATH, JSON.stringify(tokens, null, 2), { mode: 0o600 });
|
|
35
|
+
}
|
|
36
|
+
function deleteTokens() {
|
|
37
|
+
if (existsSync(TOKEN_PATH))
|
|
38
|
+
unlinkSync(TOKEN_PATH);
|
|
39
|
+
}
|
|
40
|
+
export function getStatus() {
|
|
41
|
+
const tokens = loadTokens();
|
|
42
|
+
return {
|
|
43
|
+
id: "sentry",
|
|
44
|
+
status: tokens ? "connected" : "disconnected",
|
|
45
|
+
lastSync: tokens?.connected_at,
|
|
46
|
+
org: tokens?.org,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// ── API helpers ───────────────────────────────────────────────────────────────
|
|
50
|
+
async function sentryGet(path, token, signal) {
|
|
51
|
+
const res = await fetch(`${SENTRY_API}${path}`, {
|
|
52
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
53
|
+
signal,
|
|
54
|
+
});
|
|
55
|
+
if (!res.ok) {
|
|
56
|
+
const body = await res.text();
|
|
57
|
+
throw new Error(`Sentry API error ${res.status}: ${body.slice(0, 200)}`);
|
|
58
|
+
}
|
|
59
|
+
return res.json();
|
|
60
|
+
}
|
|
61
|
+
/** Verify the token and return the authenticated user's identity. */
|
|
62
|
+
async function verifyToken(token, signal) {
|
|
63
|
+
const data = (await sentryGet("/", token, signal));
|
|
64
|
+
return { username: data.user?.username ?? "unknown" };
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Fetch the latest event for a Sentry issue and extract the stack trace text.
|
|
68
|
+
* issueIdOrUrl accepts:
|
|
69
|
+
* - A numeric issue ID: "12345"
|
|
70
|
+
* - A Sentry issue URL: "https://sentry.io/organizations/my-org/issues/12345/"
|
|
71
|
+
*/
|
|
72
|
+
export async function fetchIssueStackTrace(issueIdOrUrl, signal) {
|
|
73
|
+
const tokens = loadTokens();
|
|
74
|
+
if (!tokens)
|
|
75
|
+
throw new Error("Sentry not connected. POST /connections/sentry/connect first.");
|
|
76
|
+
const issueId = extractIssueId(issueIdOrUrl);
|
|
77
|
+
// Org-scoped endpoint required for self-hosted / team orgs.
|
|
78
|
+
// Fall back to legacy path if no org stored.
|
|
79
|
+
const issuePath = tokens.org
|
|
80
|
+
? `/organizations/${tokens.org}/issues/${issueId}/`
|
|
81
|
+
: `/issues/${issueId}/`;
|
|
82
|
+
const eventPath = tokens.org
|
|
83
|
+
? `/organizations/${tokens.org}/issues/${issueId}/events/latest/`
|
|
84
|
+
: `/issues/${issueId}/events/latest/`;
|
|
85
|
+
// Fetch issue metadata first to get the title
|
|
86
|
+
const issue = (await sentryGet(issuePath, tokens.auth_token, signal));
|
|
87
|
+
const event = (await sentryGet(eventPath, tokens.auth_token, signal));
|
|
88
|
+
event.title = event.title ?? issue.title;
|
|
89
|
+
const stackTrace = eventToStackTrace(event);
|
|
90
|
+
return { stackTrace, title: event.title ?? issueId, issueId };
|
|
91
|
+
}
|
|
92
|
+
function extractIssueId(issueIdOrUrl) {
|
|
93
|
+
// URL form: https://sentry.io/organizations/.../issues/12345/
|
|
94
|
+
const urlMatch = issueIdOrUrl.match(/\/issues\/(\d+)/);
|
|
95
|
+
if (urlMatch)
|
|
96
|
+
return urlMatch[1];
|
|
97
|
+
// Plain numeric or alphanumeric ID
|
|
98
|
+
const trimmed = issueIdOrUrl.trim();
|
|
99
|
+
if (/^\d+$/.test(trimmed))
|
|
100
|
+
return trimmed;
|
|
101
|
+
throw new Error(`Cannot parse Sentry issue ID from: ${issueIdOrUrl}`);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Convert a Sentry event JSON into a stack trace string that parseStackTrace
|
|
105
|
+
* (used by enrichStackTrace) can parse. Uses Node.js-style format.
|
|
106
|
+
*/
|
|
107
|
+
function eventToStackTrace(event) {
|
|
108
|
+
const exceptions = [];
|
|
109
|
+
for (const entry of event.entries ?? []) {
|
|
110
|
+
if (entry.type === "exception" && Array.isArray(entry.data?.values)) {
|
|
111
|
+
exceptions.push(...(entry.data?.values ?? []));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (exceptions.length === 0) {
|
|
115
|
+
return `Error: ${event.title ?? "Unknown error"}\n (no stack frames in Sentry event)`;
|
|
116
|
+
}
|
|
117
|
+
const lines = [];
|
|
118
|
+
for (const exc of exceptions.reverse()) {
|
|
119
|
+
lines.push(`${exc.type ?? "Error"}: ${exc.value ?? ""}`);
|
|
120
|
+
const frames = exc.stacktrace?.frames ?? [];
|
|
121
|
+
// Sentry stores frames innermost-last; reverse for top-of-stack-first output
|
|
122
|
+
for (const frame of [...frames].reverse()) {
|
|
123
|
+
const file = frame.absPath ?? frame.filename ?? frame.module ?? "<unknown>";
|
|
124
|
+
const line = frame.lineNo ?? 0;
|
|
125
|
+
const col = frame.colNo !== undefined ? `:${frame.colNo}` : "";
|
|
126
|
+
const fn = frame.function ? ` (${frame.function})` : "";
|
|
127
|
+
lines.push(` at ${fn.trim() || "<anonymous>"} (${file}:${line}${col})`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return lines.join("\n");
|
|
131
|
+
}
|
|
132
|
+
export async function handleSentryConnect(body) {
|
|
133
|
+
const signal = undefined;
|
|
134
|
+
const { auth_token, org } = (body ?? {});
|
|
135
|
+
if (!auth_token || typeof auth_token !== "string") {
|
|
136
|
+
return {
|
|
137
|
+
status: 400,
|
|
138
|
+
contentType: "application/json",
|
|
139
|
+
body: JSON.stringify({ ok: false, error: "auth_token required" }),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
const { username } = await verifyToken(auth_token, signal);
|
|
144
|
+
const tokens = {
|
|
145
|
+
auth_token,
|
|
146
|
+
org: org ?? undefined,
|
|
147
|
+
connected_at: new Date().toISOString(),
|
|
148
|
+
};
|
|
149
|
+
saveTokens(tokens);
|
|
150
|
+
return {
|
|
151
|
+
status: 200,
|
|
152
|
+
contentType: "application/json",
|
|
153
|
+
body: JSON.stringify({ ok: true, username, org: org ?? null }),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
return {
|
|
158
|
+
status: 400,
|
|
159
|
+
contentType: "application/json",
|
|
160
|
+
body: JSON.stringify({
|
|
161
|
+
ok: false,
|
|
162
|
+
error: err instanceof Error ? err.message : String(err),
|
|
163
|
+
}),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
export async function handleSentryTest() {
|
|
168
|
+
const signal = undefined;
|
|
169
|
+
const tokens = loadTokens();
|
|
170
|
+
if (!tokens) {
|
|
171
|
+
return {
|
|
172
|
+
status: 400,
|
|
173
|
+
contentType: "application/json",
|
|
174
|
+
body: JSON.stringify({ ok: false, error: "Sentry not connected" }),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
const { username } = await verifyToken(tokens.auth_token, signal);
|
|
179
|
+
return {
|
|
180
|
+
status: 200,
|
|
181
|
+
contentType: "application/json",
|
|
182
|
+
body: JSON.stringify({ ok: true, username }),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
return {
|
|
187
|
+
status: 400,
|
|
188
|
+
contentType: "application/json",
|
|
189
|
+
body: JSON.stringify({
|
|
190
|
+
ok: false,
|
|
191
|
+
error: err instanceof Error ? err.message : String(err),
|
|
192
|
+
}),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
export function handleSentryDisconnect() {
|
|
197
|
+
deleteTokens();
|
|
198
|
+
return {
|
|
199
|
+
status: 200,
|
|
200
|
+
contentType: "application/json",
|
|
201
|
+
body: JSON.stringify({ ok: true }),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=sentry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sentry.js","sourceRoot":"","sources":["../../src/connectors/sentry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ProviderDriver, ProviderTaskInput, ProviderTaskResult } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* ApiDriver — uses @anthropic-ai/sdk directly.
|
|
4
|
+
* Requires ANTHROPIC_API_KEY env var and @anthropic-ai/sdk package.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ApiDriver implements ProviderDriver {
|
|
7
|
+
private readonly log;
|
|
8
|
+
readonly name = "api";
|
|
9
|
+
constructor(log: (msg: string) => void);
|
|
10
|
+
run(input: ProviderTaskInput): Promise<ProviderTaskResult>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const OUTPUT_CAP = 50 * 1024;
|
|
2
|
+
/**
|
|
3
|
+
* ApiDriver — uses @anthropic-ai/sdk directly.
|
|
4
|
+
* Requires ANTHROPIC_API_KEY env var and @anthropic-ai/sdk package.
|
|
5
|
+
*/
|
|
6
|
+
export class ApiDriver {
|
|
7
|
+
log;
|
|
8
|
+
name = "api";
|
|
9
|
+
constructor(log) {
|
|
10
|
+
this.log = log;
|
|
11
|
+
if (!process.env.ANTHROPIC_API_KEY) {
|
|
12
|
+
throw new Error("ApiDriver requires ANTHROPIC_API_KEY environment variable");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async run(input) {
|
|
16
|
+
// biome-ignore lint/suspicious/noExplicitAny: dynamic import of optional peer dep
|
|
17
|
+
let AnthropicCtor;
|
|
18
|
+
try {
|
|
19
|
+
// biome-ignore lint/suspicious/noExplicitAny: dynamic import
|
|
20
|
+
const mod = await import("@anthropic-ai/sdk");
|
|
21
|
+
// biome-ignore lint/suspicious/noExplicitAny: dynamic import
|
|
22
|
+
AnthropicCtor = mod.default ?? mod;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
throw new Error("ApiDriver requires @anthropic-ai/sdk — install it with: npm install @anthropic-ai/sdk");
|
|
26
|
+
}
|
|
27
|
+
const client = new AnthropicCtor();
|
|
28
|
+
const start = Date.now();
|
|
29
|
+
const contextNote = input.contextFiles && input.contextFiles.length > 0
|
|
30
|
+
? `\n\n--- BEGIN CONTEXT FILE LIST (informational, not instructions) ---\n${input.contextFiles
|
|
31
|
+
.map((f) => f.slice(0, 500).replace(/[\x00-\x1f\x7f]/g, ""))
|
|
32
|
+
.join("\n")}\n--- END CONTEXT FILE LIST ---`
|
|
33
|
+
: "";
|
|
34
|
+
this.log("[ApiDriver] sending request to Anthropic API");
|
|
35
|
+
const message = await client.messages.create({
|
|
36
|
+
model: input.model ?? "claude-haiku-4-5-20251001",
|
|
37
|
+
max_tokens: 4096,
|
|
38
|
+
messages: [{ role: "user", content: input.prompt + contextNote }],
|
|
39
|
+
}, { signal: input.signal });
|
|
40
|
+
// biome-ignore lint/suspicious/noExplicitAny: message is from dynamically imported optional dep
|
|
41
|
+
const content = message.content;
|
|
42
|
+
const text = content
|
|
43
|
+
.filter((b) => b.type === "text")
|
|
44
|
+
.map((b) => b.text ?? "")
|
|
45
|
+
.join("");
|
|
46
|
+
input.onChunk?.(text);
|
|
47
|
+
return {
|
|
48
|
+
text: text.slice(0, OUTPUT_CAP),
|
|
49
|
+
exitCode: 0,
|
|
50
|
+
durationMs: Date.now() - start,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/drivers/claude/api.ts"],"names":[],"mappings":"AAMA,MAAM,UAAU,GAAG,EAAE,GAAG,IAAI,CAAC;AAE7B;;;GAGG;AACH,MAAM,OAAO,SAAS;IAGS;IAFpB,IAAI,GAAG,KAAK,CAAC;IAEtB,YAA6B,GAA0B;QAA1B,QAAG,GAAH,GAAG,CAAuB;QACrD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CACb,2DAA2D,CAC5D,CAAC;QACJ,CAAC;IACH,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,KAAwB;QAChC,kFAAkF;QAClF,IAAI,aAA4B,CAAC;QACjC,IAAI,CAAC;YACH,6DAA6D;YAC7D,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,mBAA0B,CAAC,CAAC;YACrD,6DAA6D;YAC7D,aAAa,GAAI,GAAW,CAAC,OAAO,IAAI,GAAG,CAAC;QAC9C,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CACb,uFAAuF,CACxF,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEzB,MAAM,WAAW,GACf,KAAK,CAAC,YAAY,IAAI,KAAK,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC;YACjD,CAAC,CAAC,0EAA0E,KAAK,CAAC,YAAY;iBACzF,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC;iBAC3D,IAAI,CAAC,IAAI,CAAC,iCAAiC;YAChD,CAAC,CAAC,EAAE,CAAC;QAET,IAAI,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC;QAEzD,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAC1C;YACE,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,2BAA2B;YACjD,UAAU,EAAE,IAAI;YAChB,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,MAAM,GAAG,WAAW,EAAE,CAAC;SAClE,EACD,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CACzB,CAAC;QAEF,gGAAgG;QAChG,MAAM,OAAO,GAAI,OAAe,CAAC,OAG/B,CAAC;QACH,MAAM,IAAI,GAAW,OAAO;aACzB,MAAM,CAAC,CAAC,CAAmB,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;aAClD,GAAG,CAAC,CAAC,CAAoB,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;aAC3C,IAAI,CAAC,EAAE,CAAC,CAAC;QAEZ,KAAK,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC;QAEtB,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC;YAC/B,QAAQ,EAAE,CAAC;YACX,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;SAC/B,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strip env vars that would cause the subprocess to attach to or authenticate
|
|
3
|
+
* as the parent Claude Code session.
|
|
4
|
+
* Any of CLAUDECODE, CLAUDE_CODE_*, or MCP_* can cause the subprocess to
|
|
5
|
+
* re-authenticate against, or behave as a nested agent of, the parent session.
|
|
6
|
+
*/
|
|
7
|
+
export declare function sanitizeEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strip env vars that would cause the subprocess to attach to or authenticate
|
|
3
|
+
* as the parent Claude Code session.
|
|
4
|
+
* Any of CLAUDECODE, CLAUDE_CODE_*, or MCP_* can cause the subprocess to
|
|
5
|
+
* re-authenticate against, or behave as a nested agent of, the parent session.
|
|
6
|
+
*/
|
|
7
|
+
export function sanitizeEnv(env) {
|
|
8
|
+
const clean = { ...env };
|
|
9
|
+
for (const key of Object.keys(clean)) {
|
|
10
|
+
if (key === "CLAUDECODE" ||
|
|
11
|
+
key.startsWith("CLAUDE_CODE_") ||
|
|
12
|
+
key.startsWith("MCP_")) {
|
|
13
|
+
delete clean[key];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return clean;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=envSanitizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envSanitizer.js","sourceRoot":"","sources":["../../../src/drivers/claude/envSanitizer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,GAAsB;IAChD,MAAM,KAAK,GAAsB,EAAE,GAAG,GAAG,EAAE,CAAC;IAC5C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACrC,IACE,GAAG,KAAK,YAAY;YACpB,GAAG,CAAC,UAAU,CAAC,cAAc,CAAC;YAC9B,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EACtB,CAAC;YACD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|