patchwork-os 0.2.0-alpha.2 → 0.2.0-alpha.22
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.bridge.md +6 -0
- package/README.md +40 -15
- package/deploy/bootstrap-vps.sh +184 -0
- package/dist/approvalHttp.d.ts +11 -2
- package/dist/approvalHttp.js +98 -10
- package/dist/approvalHttp.js.map +1 -1
- package/dist/approvalQueue.d.ts +12 -1
- package/dist/approvalQueue.js +25 -3
- package/dist/approvalQueue.js.map +1 -1
- package/dist/automation.d.ts +20 -0
- package/dist/automation.js +35 -0
- package/dist/automation.js.map +1 -1
- package/dist/bridge.js +145 -23
- package/dist/bridge.js.map +1 -1
- package/dist/bridgeToken.js +57 -19
- package/dist/bridgeToken.js.map +1 -1
- package/dist/claudeDriver.d.ts +3 -1
- package/dist/claudeDriver.js +48 -0
- package/dist/claudeDriver.js.map +1 -1
- package/dist/claudeOrchestrator.d.ts +1 -1
- package/dist/claudeOrchestrator.js +14 -8
- package/dist/claudeOrchestrator.js.map +1 -1
- package/dist/commands/launchd.d.ts +2 -0
- package/dist/commands/launchd.js +94 -0
- package/dist/commands/launchd.js.map +1 -0
- package/dist/commands/recipe.d.ts +256 -0
- package/dist/commands/recipe.js +1313 -0
- package/dist/commands/recipe.js.map +1 -0
- package/dist/config.d.ts +15 -2
- package/dist/config.js +94 -8
- package/dist/config.js.map +1 -1
- package/dist/connectors/baseConnector.d.ts +117 -0
- package/dist/connectors/baseConnector.js +213 -0
- package/dist/connectors/baseConnector.js.map +1 -0
- package/dist/connectors/confluence.d.ts +111 -0
- package/dist/connectors/confluence.js +406 -0
- package/dist/connectors/confluence.js.map +1 -0
- package/dist/connectors/fixtureLibrary.d.ts +21 -0
- package/dist/connectors/fixtureLibrary.js +70 -0
- package/dist/connectors/fixtureLibrary.js.map +1 -0
- package/dist/connectors/fixtureRecorder.d.ts +1 -0
- package/dist/connectors/fixtureRecorder.js +35 -0
- package/dist/connectors/fixtureRecorder.js.map +1 -0
- package/dist/connectors/github.d.ts +58 -8
- package/dist/connectors/github.js +312 -84
- package/dist/connectors/github.js.map +1 -1
- package/dist/connectors/gmail.d.ts +4 -1
- package/dist/connectors/gmail.js +93 -16
- package/dist/connectors/gmail.js.map +1 -1
- package/dist/connectors/googleCalendar.d.ts +60 -0
- package/dist/connectors/googleCalendar.js +345 -0
- package/dist/connectors/googleCalendar.js.map +1 -0
- package/dist/connectors/jira.d.ts +98 -0
- package/dist/connectors/jira.js +379 -0
- package/dist/connectors/jira.js.map +1 -0
- package/dist/connectors/linear.d.ts +117 -0
- package/dist/connectors/linear.js +239 -0
- package/dist/connectors/linear.js.map +1 -0
- package/dist/connectors/mcpClient.d.ts +56 -0
- package/dist/connectors/mcpClient.js +189 -0
- package/dist/connectors/mcpClient.js.map +1 -0
- package/dist/connectors/mcpOAuth.d.ts +84 -0
- package/dist/connectors/mcpOAuth.js +389 -0
- package/dist/connectors/mcpOAuth.js.map +1 -0
- package/dist/connectors/mockConnector.d.ts +28 -0
- package/dist/connectors/mockConnector.js +81 -0
- package/dist/connectors/mockConnector.js.map +1 -0
- package/dist/connectors/notion.d.ts +143 -0
- package/dist/connectors/notion.js +424 -0
- package/dist/connectors/notion.js.map +1 -0
- package/dist/connectors/sentry.d.ts +43 -0
- package/dist/connectors/sentry.js +188 -0
- package/dist/connectors/sentry.js.map +1 -0
- package/dist/connectors/slack.d.ts +50 -0
- package/dist/connectors/slack.js +324 -0
- package/dist/connectors/slack.js.map +1 -0
- package/dist/connectors/tokenStorage.d.ts +35 -0
- package/dist/connectors/tokenStorage.js +394 -0
- package/dist/connectors/tokenStorage.js.map +1 -0
- package/dist/connectors/zendesk.d.ts +104 -0
- package/dist/connectors/zendesk.js +424 -0
- package/dist/connectors/zendesk.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 +18 -0
- package/dist/drivers/gemini/index.js +210 -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 +23 -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/featureFlags.d.ts +73 -0
- package/dist/featureFlags.js +203 -0
- package/dist/featureFlags.js.map +1 -0
- package/dist/fp/automationInterpreter.js +1 -0
- package/dist/fp/automationInterpreter.js.map +1 -1
- package/dist/fp/automationProgram.d.ts +1 -1
- package/dist/fp/automationProgram.js.map +1 -1
- package/dist/fp/policyParser.js +17 -0
- package/dist/fp/policyParser.js.map +1 -1
- package/dist/index.js +543 -37
- package/dist/index.js.map +1 -1
- package/dist/installGuard.d.ts +25 -0
- package/dist/installGuard.js +48 -0
- package/dist/installGuard.js.map +1 -0
- package/dist/oauth.d.ts +4 -1
- package/dist/oauth.js +50 -14
- package/dist/oauth.js.map +1 -1
- package/dist/patchworkConfig.d.ts +9 -0
- package/dist/patchworkConfig.js.map +1 -1
- package/dist/recipes/chainedRunner.d.ts +104 -0
- package/dist/recipes/chainedRunner.js +359 -0
- package/dist/recipes/chainedRunner.js.map +1 -0
- package/dist/recipes/dependencyGraph.d.ts +39 -0
- package/dist/recipes/dependencyGraph.js +199 -0
- package/dist/recipes/dependencyGraph.js.map +1 -0
- package/dist/recipes/legacyRecipeCompat.d.ts +1 -0
- package/dist/recipes/legacyRecipeCompat.js +97 -0
- package/dist/recipes/legacyRecipeCompat.js.map +1 -0
- package/dist/recipes/nestedRecipeStep.d.ts +58 -0
- package/dist/recipes/nestedRecipeStep.js +95 -0
- package/dist/recipes/nestedRecipeStep.js.map +1 -0
- package/dist/recipes/outputRegistry.d.ts +28 -0
- package/dist/recipes/outputRegistry.js +52 -0
- package/dist/recipes/outputRegistry.js.map +1 -0
- package/dist/recipes/scheduler.d.ts +23 -7
- package/dist/recipes/scheduler.js +135 -41
- package/dist/recipes/scheduler.js.map +1 -1
- package/dist/recipes/schemaGenerator.d.ts +28 -0
- package/dist/recipes/schemaGenerator.js +484 -0
- package/dist/recipes/schemaGenerator.js.map +1 -0
- package/dist/recipes/templateEngine.d.ts +62 -0
- package/dist/recipes/templateEngine.js +182 -0
- package/dist/recipes/templateEngine.js.map +1 -0
- package/dist/recipes/toolRegistry.d.ts +181 -0
- package/dist/recipes/toolRegistry.js +300 -0
- package/dist/recipes/toolRegistry.js.map +1 -0
- package/dist/recipes/tools/calendar.d.ts +6 -0
- package/dist/recipes/tools/calendar.js +61 -0
- package/dist/recipes/tools/calendar.js.map +1 -0
- package/dist/recipes/tools/confluence.d.ts +6 -0
- package/dist/recipes/tools/confluence.js +254 -0
- package/dist/recipes/tools/confluence.js.map +1 -0
- package/dist/recipes/tools/diagnostics.d.ts +6 -0
- package/dist/recipes/tools/diagnostics.js +36 -0
- package/dist/recipes/tools/diagnostics.js.map +1 -0
- package/dist/recipes/tools/file.d.ts +6 -0
- package/dist/recipes/tools/file.js +170 -0
- package/dist/recipes/tools/file.js.map +1 -0
- package/dist/recipes/tools/git.d.ts +6 -0
- package/dist/recipes/tools/git.js +63 -0
- package/dist/recipes/tools/git.js.map +1 -0
- package/dist/recipes/tools/github.d.ts +6 -0
- package/dist/recipes/tools/github.js +91 -0
- package/dist/recipes/tools/github.js.map +1 -0
- package/dist/recipes/tools/gmail.d.ts +6 -0
- package/dist/recipes/tools/gmail.js +210 -0
- package/dist/recipes/tools/gmail.js.map +1 -0
- package/dist/recipes/tools/index.d.ts +18 -0
- package/dist/recipes/tools/index.js +21 -0
- package/dist/recipes/tools/index.js.map +1 -0
- package/dist/recipes/tools/linear.d.ts +6 -0
- package/dist/recipes/tools/linear.js +83 -0
- package/dist/recipes/tools/linear.js.map +1 -0
- package/dist/recipes/tools/notion.d.ts +6 -0
- package/dist/recipes/tools/notion.js +278 -0
- package/dist/recipes/tools/notion.js.map +1 -0
- package/dist/recipes/tools/slack.d.ts +6 -0
- package/dist/recipes/tools/slack.js +72 -0
- package/dist/recipes/tools/slack.js.map +1 -0
- package/dist/recipes/tools/zendesk.d.ts +6 -0
- package/dist/recipes/tools/zendesk.js +245 -0
- package/dist/recipes/tools/zendesk.js.map +1 -0
- package/dist/recipes/yamlRunner.d.ts +79 -0
- package/dist/recipes/yamlRunner.js +612 -346
- package/dist/recipes/yamlRunner.js.map +1 -1
- package/dist/recipesHttp.d.ts +14 -1
- package/dist/recipesHttp.js +21 -4
- package/dist/recipesHttp.js.map +1 -1
- package/dist/riskTier.js +1 -0
- package/dist/riskTier.js.map +1 -1
- package/dist/runLog.d.ts +23 -0
- package/dist/runLog.js +56 -1
- package/dist/runLog.js.map +1 -1
- package/dist/server.d.ts +19 -1
- package/dist/server.js +682 -31
- package/dist/server.js.map +1 -1
- package/dist/streamableHttp.js +2 -0
- package/dist/streamableHttp.js.map +1 -1
- package/dist/tools/addLinearComment.d.ts +55 -0
- package/dist/tools/addLinearComment.js +72 -0
- package/dist/tools/addLinearComment.js.map +1 -0
- package/dist/tools/bridgeDoctor.js +2 -2
- package/dist/tools/bridgeDoctor.js.map +1 -1
- package/dist/tools/createLinearIssue.d.ts +84 -0
- package/dist/tools/createLinearIssue.js +146 -0
- package/dist/tools/createLinearIssue.js.map +1 -0
- 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/fetchCalendarEvents.d.ts +94 -0
- package/dist/tools/fetchCalendarEvents.js +97 -0
- package/dist/tools/fetchCalendarEvents.js.map +1 -0
- package/dist/tools/fetchGithubIssue.d.ts +80 -0
- package/dist/tools/fetchGithubIssue.js +84 -0
- package/dist/tools/fetchGithubIssue.js.map +1 -0
- package/dist/tools/fetchGithubPR.d.ts +89 -0
- package/dist/tools/fetchGithubPR.js +96 -0
- package/dist/tools/fetchGithubPR.js.map +1 -0
- 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/fetchSlackProfile.d.ts +43 -0
- package/dist/tools/fetchSlackProfile.js +46 -0
- package/dist/tools/fetchSlackProfile.js.map +1 -0
- package/dist/tools/getConnectorStatus.d.ts +58 -0
- package/dist/tools/getConnectorStatus.js +56 -0
- package/dist/tools/getConnectorStatus.js.map +1 -0
- package/dist/tools/github/actions.js +4 -2
- package/dist/tools/github/actions.js.map +1 -1
- package/dist/tools/github/composite.d.ts +339 -0
- package/dist/tools/github/composite.js +343 -0
- package/dist/tools/github/composite.js.map +1 -0
- package/dist/tools/github/index.d.ts +2 -1
- package/dist/tools/github/index.js +2 -1
- package/dist/tools/github/index.js.map +1 -1
- package/dist/tools/github/issues.js +8 -4
- package/dist/tools/github/issues.js.map +1 -1
- package/dist/tools/github/pr.d.ts +122 -0
- package/dist/tools/github/pr.js +195 -5
- package/dist/tools/github/pr.js.map +1 -1
- package/dist/tools/index.js +36 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/searchTools.js +1 -1
- package/dist/tools/searchTools.js.map +1 -1
- package/dist/tools/slackListChannels.d.ts +65 -0
- package/dist/tools/slackListChannels.js +70 -0
- package/dist/tools/slackListChannels.js.map +1 -0
- package/dist/tools/slackPostMessage.d.ts +57 -0
- package/dist/tools/slackPostMessage.js +77 -0
- package/dist/tools/slackPostMessage.js.map +1 -0
- package/dist/tools/updateLinearIssue.d.ts +89 -0
- package/dist/tools/updateLinearIssue.js +117 -0
- package/dist/tools/updateLinearIssue.js.map +1 -0
- package/dist/transport.d.ts +7 -1
- package/dist/transport.js +85 -11
- package/dist/transport.js.map +1 -1
- package/package.json +4 -2
- package/scripts/start-all.sh +56 -19
- package/templates/automation-policies/recipe-authoring.json +25 -0
- package/templates/automation-policy.example.json +6 -0
- package/templates/co.patchwork-os.bridge.plist +34 -0
- package/templates/recipes/ctx-loop-test.yaml +75 -0
- package/templates/recipes/lint-on-save.yaml +1 -2
- package/templates/recipes/morning-brief-slack.yaml +57 -0
- package/templates/recipes/morning-brief.yaml +21 -5
- package/templates/recipes/sentry-to-linear.yaml +77 -0
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notion connector — read/write Notion databases and pages via the Notion API.
|
|
3
|
+
*
|
|
4
|
+
* Auth: API token (internal integration) or OAuth 2.0 (public integration).
|
|
5
|
+
* - Env var: NOTION_TOKEN overrides stored token for CI/headless use.
|
|
6
|
+
* - Stored: getSecretJsonSync("notion") → NotionTokens
|
|
7
|
+
*
|
|
8
|
+
* Tools: queryDatabase, getPage, search, createPage, appendBlock
|
|
9
|
+
*
|
|
10
|
+
* Extends BaseConnector for unified auth, retry, rate-limit, error handling.
|
|
11
|
+
*/
|
|
12
|
+
import { unlinkSync } from "node:fs";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import { BaseConnector, } from "./baseConnector.js";
|
|
16
|
+
import { getSecretJsonSync, storeSecretJsonSync } from "./tokenStorage.js";
|
|
17
|
+
const NOTION_API = "https://api.notion.com/v1";
|
|
18
|
+
const NOTION_VERSION = "2022-06-28";
|
|
19
|
+
// ------------------------------------------------------------------ token helpers
|
|
20
|
+
export function loadTokens() {
|
|
21
|
+
const envToken = process.env.NOTION_TOKEN;
|
|
22
|
+
if (envToken) {
|
|
23
|
+
return {
|
|
24
|
+
accessToken: envToken,
|
|
25
|
+
connected_at: new Date().toISOString(),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return getSecretJsonSync("notion");
|
|
29
|
+
}
|
|
30
|
+
export function saveTokens(tokens) {
|
|
31
|
+
storeSecretJsonSync("notion", tokens);
|
|
32
|
+
}
|
|
33
|
+
export function clearTokens() {
|
|
34
|
+
try {
|
|
35
|
+
const p = path.join(homedir(), ".patchwork", "tokens", "notion.json");
|
|
36
|
+
unlinkSync(p);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
/* already gone */
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// ------------------------------------------------------------------ connector
|
|
43
|
+
export class NotionConnector extends BaseConnector {
|
|
44
|
+
providerName = "notion";
|
|
45
|
+
// Cached after authenticate(); re-read in getStatus() to stay fresh
|
|
46
|
+
cachedTokens = null;
|
|
47
|
+
getOAuthConfig() {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
async authenticate() {
|
|
51
|
+
const tokens = loadTokens();
|
|
52
|
+
if (!tokens) {
|
|
53
|
+
throw new Error("Notion not connected. Run: patchwork connect notion or set NOTION_TOKEN");
|
|
54
|
+
}
|
|
55
|
+
this.cachedTokens = tokens;
|
|
56
|
+
return { token: tokens.accessToken };
|
|
57
|
+
}
|
|
58
|
+
async healthCheck() {
|
|
59
|
+
try {
|
|
60
|
+
const result = await this.apiCall(async (token) => {
|
|
61
|
+
const res = await fetch(`${NOTION_API}/users/me`, {
|
|
62
|
+
headers: this.buildHeaders(token),
|
|
63
|
+
});
|
|
64
|
+
if (!res.ok)
|
|
65
|
+
throw new Error(`HTTP ${res.status}`);
|
|
66
|
+
return res.json();
|
|
67
|
+
});
|
|
68
|
+
if ("error" in result)
|
|
69
|
+
return { ok: false, error: result.error };
|
|
70
|
+
return { ok: true };
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
return { ok: false, error: this.normalizeError(err) };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
normalizeError(error) {
|
|
77
|
+
if (error instanceof Response ||
|
|
78
|
+
(error && typeof error === "object" && "status" in error)) {
|
|
79
|
+
const status = error.status;
|
|
80
|
+
if (status === 401)
|
|
81
|
+
return {
|
|
82
|
+
code: "auth_expired",
|
|
83
|
+
message: "Notion token expired or invalid",
|
|
84
|
+
retryable: false,
|
|
85
|
+
suggestedAction: "Reconnect: patchwork connect notion",
|
|
86
|
+
};
|
|
87
|
+
if (status === 403)
|
|
88
|
+
return {
|
|
89
|
+
code: "permission_denied",
|
|
90
|
+
message: "Notion integration lacks permission for this resource",
|
|
91
|
+
retryable: false,
|
|
92
|
+
suggestedAction: "Share the page/database with your integration",
|
|
93
|
+
};
|
|
94
|
+
if (status === 404)
|
|
95
|
+
return {
|
|
96
|
+
code: "not_found",
|
|
97
|
+
message: "Notion page or database not found",
|
|
98
|
+
retryable: false,
|
|
99
|
+
};
|
|
100
|
+
if (status === 429)
|
|
101
|
+
return {
|
|
102
|
+
code: "rate_limited",
|
|
103
|
+
message: "Notion API rate limit exceeded",
|
|
104
|
+
retryable: true,
|
|
105
|
+
suggestedAction: "Wait and retry",
|
|
106
|
+
};
|
|
107
|
+
return {
|
|
108
|
+
code: "provider_error",
|
|
109
|
+
message: `Notion API error: HTTP ${status}`,
|
|
110
|
+
retryable: status >= 500,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if (error instanceof Error) {
|
|
114
|
+
if (error.message.includes("ENOTFOUND") ||
|
|
115
|
+
error.message.includes("ECONNREFUSED")) {
|
|
116
|
+
return {
|
|
117
|
+
code: "network_error",
|
|
118
|
+
message: `Cannot reach Notion API: ${error.message}`,
|
|
119
|
+
retryable: true,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
code: "provider_error",
|
|
125
|
+
message: error instanceof Error ? error.message : String(error),
|
|
126
|
+
retryable: false,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
getStatus() {
|
|
130
|
+
const tokens = loadTokens();
|
|
131
|
+
return {
|
|
132
|
+
id: "notion",
|
|
133
|
+
status: tokens ? "connected" : "disconnected",
|
|
134
|
+
lastSync: tokens?.connected_at,
|
|
135
|
+
workspace: tokens?.workspaceName,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
buildHeaders(token) {
|
|
139
|
+
return {
|
|
140
|
+
Authorization: `Bearer ${token}`,
|
|
141
|
+
"Content-Type": "application/json",
|
|
142
|
+
"Notion-Version": NOTION_VERSION,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
// ---------------------------------------------------------------- read ops
|
|
146
|
+
async queryDatabase(databaseId, filter, sorts, pageSize = 20) {
|
|
147
|
+
const result = await this.apiCall(async (token) => {
|
|
148
|
+
const body = {
|
|
149
|
+
page_size: Math.min(pageSize, 100),
|
|
150
|
+
};
|
|
151
|
+
if (filter)
|
|
152
|
+
body.filter = filter;
|
|
153
|
+
if (sorts)
|
|
154
|
+
body.sorts = sorts;
|
|
155
|
+
const res = await fetch(`${NOTION_API}/databases/${normalizeId(databaseId)}/query`, {
|
|
156
|
+
method: "POST",
|
|
157
|
+
headers: this.buildHeaders(token),
|
|
158
|
+
body: JSON.stringify(body),
|
|
159
|
+
});
|
|
160
|
+
if (!res.ok) {
|
|
161
|
+
const err = (await res.json().catch(() => ({})));
|
|
162
|
+
throw Object.assign(new Error(err.message ?? `HTTP ${res.status}`), {
|
|
163
|
+
status: res.status,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return res.json();
|
|
167
|
+
});
|
|
168
|
+
if ("error" in result)
|
|
169
|
+
throw new Error(result.error.message);
|
|
170
|
+
return result.data;
|
|
171
|
+
}
|
|
172
|
+
async getPage(pageId) {
|
|
173
|
+
const result = await this.apiCall(async (token) => {
|
|
174
|
+
const res = await fetch(`${NOTION_API}/pages/${normalizeId(pageId)}`, {
|
|
175
|
+
headers: this.buildHeaders(token),
|
|
176
|
+
});
|
|
177
|
+
if (!res.ok) {
|
|
178
|
+
const err = (await res.json().catch(() => ({})));
|
|
179
|
+
throw Object.assign(new Error(err.message ?? `HTTP ${res.status}`), {
|
|
180
|
+
status: res.status,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return res.json();
|
|
184
|
+
});
|
|
185
|
+
if ("error" in result)
|
|
186
|
+
throw new Error(result.error.message);
|
|
187
|
+
return result.data;
|
|
188
|
+
}
|
|
189
|
+
async search(query, filterType, pageSize = 10) {
|
|
190
|
+
const result = await this.apiCall(async (token) => {
|
|
191
|
+
const body = {
|
|
192
|
+
query,
|
|
193
|
+
page_size: Math.min(pageSize, 100),
|
|
194
|
+
};
|
|
195
|
+
if (filterType)
|
|
196
|
+
body.filter = { value: filterType, property: "object" };
|
|
197
|
+
const res = await fetch(`${NOTION_API}/search`, {
|
|
198
|
+
method: "POST",
|
|
199
|
+
headers: this.buildHeaders(token),
|
|
200
|
+
body: JSON.stringify(body),
|
|
201
|
+
});
|
|
202
|
+
if (!res.ok) {
|
|
203
|
+
const err = (await res.json().catch(() => ({})));
|
|
204
|
+
throw Object.assign(new Error(err.message ?? `HTTP ${res.status}`), {
|
|
205
|
+
status: res.status,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return res.json();
|
|
209
|
+
});
|
|
210
|
+
if ("error" in result)
|
|
211
|
+
throw new Error(result.error.message);
|
|
212
|
+
return result.data;
|
|
213
|
+
}
|
|
214
|
+
// ---------------------------------------------------------------- write ops
|
|
215
|
+
async createPage(params) {
|
|
216
|
+
const result = await this.apiCall(async (token) => {
|
|
217
|
+
const parent = params.parentType === "database"
|
|
218
|
+
? { database_id: normalizeId(params.parentId) }
|
|
219
|
+
: { page_id: normalizeId(params.parentId) };
|
|
220
|
+
const properties = params.properties ?? {};
|
|
221
|
+
// Set title property — key is "title" for pages, "Name" for database rows
|
|
222
|
+
const titleKey = params.parentType === "database" ? "Name" : "title";
|
|
223
|
+
properties[titleKey] = {
|
|
224
|
+
title: [{ text: { content: params.title } }],
|
|
225
|
+
};
|
|
226
|
+
const body = { parent, properties };
|
|
227
|
+
if (params.content) {
|
|
228
|
+
body.children = [
|
|
229
|
+
{
|
|
230
|
+
object: "block",
|
|
231
|
+
type: "paragraph",
|
|
232
|
+
paragraph: {
|
|
233
|
+
rich_text: [{ type: "text", text: { content: params.content } }],
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
];
|
|
237
|
+
}
|
|
238
|
+
const res = await fetch(`${NOTION_API}/pages`, {
|
|
239
|
+
method: "POST",
|
|
240
|
+
headers: this.buildHeaders(token),
|
|
241
|
+
body: JSON.stringify(body),
|
|
242
|
+
});
|
|
243
|
+
if (!res.ok) {
|
|
244
|
+
const err = (await res.json().catch(() => ({})));
|
|
245
|
+
throw Object.assign(new Error(err.message ?? `HTTP ${res.status}`), {
|
|
246
|
+
status: res.status,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
return res.json();
|
|
250
|
+
});
|
|
251
|
+
if ("error" in result)
|
|
252
|
+
throw new Error(result.error.message);
|
|
253
|
+
return result.data;
|
|
254
|
+
}
|
|
255
|
+
async appendBlock(params) {
|
|
256
|
+
const result = await this.apiCall(async (token) => {
|
|
257
|
+
const type = params.blockType ?? "paragraph";
|
|
258
|
+
const richText = [{ type: "text", text: { content: params.content } }];
|
|
259
|
+
const block = {
|
|
260
|
+
object: "block",
|
|
261
|
+
type,
|
|
262
|
+
[type]: { rich_text: richText },
|
|
263
|
+
};
|
|
264
|
+
const res = await fetch(`${NOTION_API}/blocks/${normalizeId(params.pageId)}/children`, {
|
|
265
|
+
method: "PATCH",
|
|
266
|
+
headers: this.buildHeaders(token),
|
|
267
|
+
body: JSON.stringify({ children: [block] }),
|
|
268
|
+
});
|
|
269
|
+
if (!res.ok) {
|
|
270
|
+
const err = (await res.json().catch(() => ({})));
|
|
271
|
+
throw Object.assign(new Error(err.message ?? `HTTP ${res.status}`), {
|
|
272
|
+
status: res.status,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
return res.json();
|
|
276
|
+
});
|
|
277
|
+
if ("error" in result)
|
|
278
|
+
throw new Error(result.error.message);
|
|
279
|
+
return result.data;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// ------------------------------------------------------------------ helpers
|
|
283
|
+
/** Normalize Notion IDs — strip hyphens if present, add back in UUID format. */
|
|
284
|
+
function normalizeId(id) {
|
|
285
|
+
const stripped = id.replace(/-/g, "");
|
|
286
|
+
if (stripped.length === 32) {
|
|
287
|
+
return `${stripped.slice(0, 8)}-${stripped.slice(8, 12)}-${stripped.slice(12, 16)}-${stripped.slice(16, 20)}-${stripped.slice(20)}`;
|
|
288
|
+
}
|
|
289
|
+
return id;
|
|
290
|
+
}
|
|
291
|
+
// ------------------------------------------------------------------ singleton
|
|
292
|
+
let _instance = null;
|
|
293
|
+
export function getNotionConnector() {
|
|
294
|
+
if (!_instance)
|
|
295
|
+
_instance = new NotionConnector();
|
|
296
|
+
return _instance;
|
|
297
|
+
}
|
|
298
|
+
export function resetNotionConnector() {
|
|
299
|
+
_instance = null;
|
|
300
|
+
}
|
|
301
|
+
// ------------------------------------------------------------------ convenience re-exports
|
|
302
|
+
export { loadTokens as isConnected };
|
|
303
|
+
/**
|
|
304
|
+
* POST /connections/notion/connect { token: "secret_..." }
|
|
305
|
+
* Stores the integration token and verifies it by calling /users/me.
|
|
306
|
+
*/
|
|
307
|
+
export async function handleNotionConnect(body) {
|
|
308
|
+
let token;
|
|
309
|
+
try {
|
|
310
|
+
const parsed = JSON.parse(body);
|
|
311
|
+
if (typeof parsed.token !== "string" ||
|
|
312
|
+
!parsed.token.startsWith("secret_")) {
|
|
313
|
+
return {
|
|
314
|
+
status: 400,
|
|
315
|
+
contentType: "application/json",
|
|
316
|
+
body: JSON.stringify({
|
|
317
|
+
ok: false,
|
|
318
|
+
error: 'Notion integration token must start with "secret_". Find it at https://www.notion.so/my-integrations',
|
|
319
|
+
}),
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
token = parsed.token;
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
return {
|
|
326
|
+
status: 400,
|
|
327
|
+
contentType: "application/json",
|
|
328
|
+
body: JSON.stringify({ ok: false, error: "Invalid JSON body" }),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
try {
|
|
332
|
+
const res = await fetch(`${NOTION_API}/users/me`, {
|
|
333
|
+
headers: {
|
|
334
|
+
Authorization: `Bearer ${token}`,
|
|
335
|
+
"Notion-Version": NOTION_VERSION,
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
if (!res.ok) {
|
|
339
|
+
return {
|
|
340
|
+
status: 401,
|
|
341
|
+
contentType: "application/json",
|
|
342
|
+
body: JSON.stringify({
|
|
343
|
+
ok: false,
|
|
344
|
+
error: "Token rejected by Notion API — check the token is valid and the integration is active",
|
|
345
|
+
}),
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
const user = (await res.json());
|
|
349
|
+
const tokens = {
|
|
350
|
+
accessToken: token,
|
|
351
|
+
workspaceName: user.bot?.workspace_name,
|
|
352
|
+
workspaceId: user.bot?.owner?.workspace_id,
|
|
353
|
+
connected_at: new Date().toISOString(),
|
|
354
|
+
};
|
|
355
|
+
saveTokens(tokens);
|
|
356
|
+
resetNotionConnector();
|
|
357
|
+
return {
|
|
358
|
+
status: 200,
|
|
359
|
+
contentType: "application/json",
|
|
360
|
+
body: JSON.stringify({
|
|
361
|
+
ok: true,
|
|
362
|
+
workspace: tokens.workspaceName ?? "unknown",
|
|
363
|
+
connectedAt: tokens.connected_at,
|
|
364
|
+
}),
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
catch (err) {
|
|
368
|
+
return {
|
|
369
|
+
status: 500,
|
|
370
|
+
contentType: "application/json",
|
|
371
|
+
body: JSON.stringify({
|
|
372
|
+
ok: false,
|
|
373
|
+
error: err instanceof Error ? err.message : String(err),
|
|
374
|
+
}),
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* POST /connections/notion/test
|
|
380
|
+
* Verifies stored token is still valid.
|
|
381
|
+
*/
|
|
382
|
+
export async function handleNotionTest() {
|
|
383
|
+
const tokens = loadTokens();
|
|
384
|
+
if (!tokens) {
|
|
385
|
+
return {
|
|
386
|
+
status: 400,
|
|
387
|
+
contentType: "application/json",
|
|
388
|
+
body: JSON.stringify({ ok: false, error: "Notion not connected" }),
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
const connector = getNotionConnector();
|
|
393
|
+
const check = await connector.healthCheck();
|
|
394
|
+
return {
|
|
395
|
+
status: check.ok ? 200 : 401,
|
|
396
|
+
contentType: "application/json",
|
|
397
|
+
body: JSON.stringify(check.ok ? { ok: true } : { ok: false, error: check.error?.message }),
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
catch (err) {
|
|
401
|
+
return {
|
|
402
|
+
status: 500,
|
|
403
|
+
contentType: "application/json",
|
|
404
|
+
body: JSON.stringify({
|
|
405
|
+
ok: false,
|
|
406
|
+
error: err instanceof Error ? err.message : String(err),
|
|
407
|
+
}),
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* DELETE /connections/notion
|
|
413
|
+
* Removes stored token.
|
|
414
|
+
*/
|
|
415
|
+
export function handleNotionDisconnect() {
|
|
416
|
+
clearTokens();
|
|
417
|
+
resetNotionConnector();
|
|
418
|
+
return {
|
|
419
|
+
status: 200,
|
|
420
|
+
contentType: "application/json",
|
|
421
|
+
body: JSON.stringify({ ok: true }),
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
//# sourceMappingURL=notion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notion.js","sourceRoot":"","sources":["../../src/connectors/notion.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentry connector — routes through Sentry's official MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Endpoint: https://mcp.sentry.dev/mcp
|
|
5
|
+
* Auth: OAuth 2.1 w/ PKCE; dynamic client registration (RFC 7591).
|
|
6
|
+
*
|
|
7
|
+
* HTTP routes (wired in src/server.ts):
|
|
8
|
+
* GET /connections/sentry/authorize — returns { url } for popup
|
|
9
|
+
* GET /connections/sentry/callback — token exchange
|
|
10
|
+
* POST /connections/sentry/test — ping MCP server
|
|
11
|
+
* DELETE /connections/sentry — revoke + delete 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 interface ConnectorHandlerResult {
|
|
28
|
+
status: number;
|
|
29
|
+
body: string;
|
|
30
|
+
contentType?: string;
|
|
31
|
+
redirect?: string;
|
|
32
|
+
}
|
|
33
|
+
export declare function loadTokens(): SentryTokens | null;
|
|
34
|
+
export declare function getStatus(): ConnectorStatus;
|
|
35
|
+
export declare function fetchIssueStackTrace(issueIdOrUrl: string, signal?: AbortSignal): Promise<{
|
|
36
|
+
stackTrace: string;
|
|
37
|
+
title: string;
|
|
38
|
+
issueId: string;
|
|
39
|
+
}>;
|
|
40
|
+
export declare function handleSentryAuthorize(): Promise<ConnectorHandlerResult>;
|
|
41
|
+
export declare function handleSentryCallback(code: string | null, state: string | null, error: string | null): Promise<ConnectorHandlerResult>;
|
|
42
|
+
export declare function handleSentryTest(): Promise<ConnectorHandlerResult>;
|
|
43
|
+
export declare function handleSentryDisconnect(): Promise<ConnectorHandlerResult>;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentry connector — routes through Sentry's official MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Endpoint: https://mcp.sentry.dev/mcp
|
|
5
|
+
* Auth: OAuth 2.1 w/ PKCE; dynamic client registration (RFC 7591).
|
|
6
|
+
*
|
|
7
|
+
* HTTP routes (wired in src/server.ts):
|
|
8
|
+
* GET /connections/sentry/authorize — returns { url } for popup
|
|
9
|
+
* GET /connections/sentry/callback — token exchange
|
|
10
|
+
* POST /connections/sentry/test — ping MCP server
|
|
11
|
+
* DELETE /connections/sentry — revoke + delete 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 { McpClient } from "./mcpClient.js";
|
|
17
|
+
import { completeAuthorize, getAccessToken, loadTokenFile, revoke, startAuthorize, updateTokenProfile, vendorConfig, } from "./mcpOAuth.js";
|
|
18
|
+
const SENTRY_MCP_ENDPOINT = "https://mcp.sentry.dev/mcp";
|
|
19
|
+
// ── MCP client ───────────────────────────────────────────────────────────────
|
|
20
|
+
let _client = null;
|
|
21
|
+
function client() {
|
|
22
|
+
if (!_client) {
|
|
23
|
+
_client = new McpClient(SENTRY_MCP_ENDPOINT, () => getAccessToken("sentry"));
|
|
24
|
+
}
|
|
25
|
+
return _client;
|
|
26
|
+
}
|
|
27
|
+
// ── Back-compat ──────────────────────────────────────────────────────────────
|
|
28
|
+
export function loadTokens() {
|
|
29
|
+
const file = loadTokenFile("sentry");
|
|
30
|
+
if (!file)
|
|
31
|
+
return null;
|
|
32
|
+
return {
|
|
33
|
+
auth_token: file.access_token,
|
|
34
|
+
org: file.profile?.org,
|
|
35
|
+
connected_at: file.connected_at,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export function getStatus() {
|
|
39
|
+
const file = loadTokenFile("sentry");
|
|
40
|
+
return {
|
|
41
|
+
id: "sentry",
|
|
42
|
+
status: file ? "connected" : "disconnected",
|
|
43
|
+
lastSync: file?.connected_at,
|
|
44
|
+
org: file?.profile?.org,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
// ── Issue fetch ──────────────────────────────────────────────────────────────
|
|
48
|
+
function extractIssueId(issueIdOrUrl) {
|
|
49
|
+
const urlMatch = issueIdOrUrl.match(/\/issues\/(\d+)/);
|
|
50
|
+
if (urlMatch)
|
|
51
|
+
return urlMatch[1];
|
|
52
|
+
const trimmed = issueIdOrUrl.trim();
|
|
53
|
+
if (/^\d+$/.test(trimmed))
|
|
54
|
+
return trimmed;
|
|
55
|
+
throw new Error(`Cannot parse Sentry issue ID from: ${issueIdOrUrl}`);
|
|
56
|
+
}
|
|
57
|
+
function extractOrgSlug(issueIdOrUrl) {
|
|
58
|
+
const m = issueIdOrUrl.match(/https?:\/\/([^.]+)\.sentry\.io/);
|
|
59
|
+
return m ? m[1] : null;
|
|
60
|
+
}
|
|
61
|
+
function buildSentryIssueUrl(issueId, orgSlug) {
|
|
62
|
+
return `https://${orgSlug}.sentry.io/issues/${issueId}/`;
|
|
63
|
+
}
|
|
64
|
+
export async function fetchIssueStackTrace(issueIdOrUrl, signal) {
|
|
65
|
+
if (!loadTokens())
|
|
66
|
+
throw new Error("Sentry not connected. GET /connections/sentry/authorize first.");
|
|
67
|
+
const issueId = extractIssueId(issueIdOrUrl);
|
|
68
|
+
const orgSlug = extractOrgSlug(issueIdOrUrl) ?? loadTokenFile("sentry")?.profile?.org;
|
|
69
|
+
if (!orgSlug)
|
|
70
|
+
throw new Error("Cannot determine Sentry org slug. Pass full sentry.io issue URL.");
|
|
71
|
+
const issueUrl = buildSentryIssueUrl(issueId, orgSlug);
|
|
72
|
+
const res = await client().callTool("get_sentry_resource", { url: issueUrl }, { signal });
|
|
73
|
+
// get_sentry_resource returns markdown text — extract title and stacktrace
|
|
74
|
+
const text = res.content?.[0]?.text ?? "";
|
|
75
|
+
const titleMatch = text.match(/\*\*Description\*\*:\s*(.+)/);
|
|
76
|
+
const title = titleMatch
|
|
77
|
+
? titleMatch[1].trim()
|
|
78
|
+
: `Sentry issue ${issueId}`;
|
|
79
|
+
// Extract stacktrace block
|
|
80
|
+
const stMatch = text.match(/```(?:\w+)?\n([\s\S]*?)\n```/);
|
|
81
|
+
if (!stMatch) {
|
|
82
|
+
throw new Error(`Sentry returned no stack trace for issue ${issueId}`);
|
|
83
|
+
}
|
|
84
|
+
const stackTrace = stMatch[1].trim();
|
|
85
|
+
return { stackTrace, title, issueId };
|
|
86
|
+
}
|
|
87
|
+
// ── HTTP handlers ────────────────────────────────────────────────────────────
|
|
88
|
+
export async function handleSentryAuthorize() {
|
|
89
|
+
try {
|
|
90
|
+
const { url } = await startAuthorize(vendorConfig("sentry"));
|
|
91
|
+
return { status: 302, body: "", redirect: url };
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
return {
|
|
95
|
+
status: 400,
|
|
96
|
+
contentType: "application/json",
|
|
97
|
+
body: JSON.stringify({
|
|
98
|
+
ok: false,
|
|
99
|
+
error: err instanceof Error ? err.message : String(err),
|
|
100
|
+
}),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export async function handleSentryCallback(code, state, error) {
|
|
105
|
+
if (error) {
|
|
106
|
+
return {
|
|
107
|
+
status: 400,
|
|
108
|
+
contentType: "text/html",
|
|
109
|
+
body: `<html><body><h2>Sentry connect failed</h2><pre>${error}</pre></body></html>`,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (!code || !state) {
|
|
113
|
+
return {
|
|
114
|
+
status: 400,
|
|
115
|
+
contentType: "text/html",
|
|
116
|
+
body: `<html><body><h2>Sentry connect failed</h2><pre>missing code/state</pre></body></html>`,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
await completeAuthorize(vendorConfig("sentry"), code, state);
|
|
121
|
+
// Best-effort org capture
|
|
122
|
+
try {
|
|
123
|
+
const res = await client().callTool("find_organizations", {}, {
|
|
124
|
+
timeoutMs: 10_000,
|
|
125
|
+
});
|
|
126
|
+
const orgs = McpClient.extractJson(res);
|
|
127
|
+
const first = Array.isArray(orgs)
|
|
128
|
+
? orgs[0]
|
|
129
|
+
: (orgs.organizations ?? [])[0];
|
|
130
|
+
const org = first?.slug;
|
|
131
|
+
if (org) {
|
|
132
|
+
updateTokenProfile("sentry", { org });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// Profile fetch is best-effort
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
status: 200,
|
|
140
|
+
contentType: "text/html",
|
|
141
|
+
body: `<html><body><h2>Sentry connected</h2><script>window.close();</script></body></html>`,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
return {
|
|
146
|
+
status: 400,
|
|
147
|
+
contentType: "text/html",
|
|
148
|
+
body: `<html><body><h2>Sentry connect failed</h2><pre>${err instanceof Error ? err.message : String(err)}</pre></body></html>`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
export async function handleSentryTest() {
|
|
153
|
+
if (!loadTokens()) {
|
|
154
|
+
return {
|
|
155
|
+
status: 400,
|
|
156
|
+
contentType: "application/json",
|
|
157
|
+
body: JSON.stringify({ ok: false, error: "Sentry not connected" }),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
const ok = await client().ping({ timeoutMs: 10_000 });
|
|
162
|
+
return {
|
|
163
|
+
status: ok ? 200 : 400,
|
|
164
|
+
contentType: "application/json",
|
|
165
|
+
body: JSON.stringify({ ok, message: ok ? "connected" : "ping failed" }),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
return {
|
|
170
|
+
status: 400,
|
|
171
|
+
contentType: "application/json",
|
|
172
|
+
body: JSON.stringify({
|
|
173
|
+
ok: false,
|
|
174
|
+
error: err instanceof Error ? err.message : String(err),
|
|
175
|
+
}),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
export async function handleSentryDisconnect() {
|
|
180
|
+
await revoke("sentry");
|
|
181
|
+
_client = null;
|
|
182
|
+
return {
|
|
183
|
+
status: 200,
|
|
184
|
+
contentType: "application/json",
|
|
185
|
+
body: JSON.stringify({ ok: true }),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=sentry.js.map
|