patchwork-os 0.2.0-alpha.33 → 0.2.0-alpha.35
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 +248 -48
- package/deploy/bootstrap-new-vps.sh +12 -12
- package/deploy/bootstrap-vps.sh +6 -3
- package/deploy/deploy-landing.sh +59 -2
- package/dist/bridge.js +35 -1
- package/dist/bridge.js.map +1 -1
- package/dist/commands/recipe.d.ts +11 -0
- package/dist/commands/recipe.js +32 -3
- package/dist/commands/recipe.js.map +1 -1
- package/dist/commands/recipeInstall.d.ts +79 -1
- package/dist/commands/recipeInstall.js +241 -13
- package/dist/commands/recipeInstall.js.map +1 -1
- package/dist/connectors/asana.d.ts +198 -0
- package/dist/connectors/asana.js +680 -0
- package/dist/connectors/asana.js.map +1 -0
- package/dist/connectors/baseConnector.d.ts +16 -0
- package/dist/connectors/baseConnector.js +107 -25
- package/dist/connectors/baseConnector.js.map +1 -1
- package/dist/connectors/discord.d.ts +150 -0
- package/dist/connectors/discord.js +544 -0
- package/dist/connectors/discord.js.map +1 -0
- package/dist/connectors/github.js +15 -7
- package/dist/connectors/github.js.map +1 -1
- package/dist/connectors/gitlab.d.ts +180 -0
- package/dist/connectors/gitlab.js +582 -0
- package/dist/connectors/gitlab.js.map +1 -0
- package/dist/connectors/gmail.js +45 -0
- package/dist/connectors/gmail.js.map +1 -1
- package/dist/connectors/googleDrive.d.ts +34 -0
- package/dist/connectors/googleDrive.js +305 -0
- package/dist/connectors/googleDrive.js.map +1 -0
- package/dist/connectors/htmlEscape.d.ts +5 -0
- package/dist/connectors/htmlEscape.js +13 -0
- package/dist/connectors/htmlEscape.js.map +1 -0
- package/dist/connectors/linear.js +26 -6
- package/dist/connectors/linear.js.map +1 -1
- package/dist/connectors/mcpOAuth.d.ts +2 -0
- package/dist/connectors/mcpOAuth.js +8 -4
- package/dist/connectors/mcpOAuth.js.map +1 -1
- package/dist/connectors/pagerduty.d.ts +160 -0
- package/dist/connectors/pagerduty.js +464 -0
- package/dist/connectors/pagerduty.js.map +1 -0
- package/dist/connectors/sentry.js +3 -2
- package/dist/connectors/sentry.js.map +1 -1
- package/dist/connectors/slack.d.ts +1 -1
- package/dist/connectors/slack.js +7 -4
- package/dist/connectors/slack.js.map +1 -1
- package/dist/featureFlags.d.ts +17 -11
- package/dist/featureFlags.js +52 -47
- package/dist/featureFlags.js.map +1 -1
- package/dist/index.js +262 -129
- package/dist/index.js.map +1 -1
- package/dist/oauth.js +3 -2
- package/dist/oauth.js.map +1 -1
- package/dist/recipeOrchestration.d.ts +7 -0
- package/dist/recipeOrchestration.js +154 -28
- package/dist/recipeOrchestration.js.map +1 -1
- package/dist/recipes/agentExecutor.d.ts +1 -0
- package/dist/recipes/agentExecutor.js +7 -0
- package/dist/recipes/agentExecutor.js.map +1 -1
- package/dist/recipes/captureForRunlog.d.ts +27 -0
- package/dist/recipes/captureForRunlog.js +128 -0
- package/dist/recipes/captureForRunlog.js.map +1 -0
- package/dist/recipes/chainedRunner.d.ts +39 -3
- package/dist/recipes/chainedRunner.js +183 -28
- package/dist/recipes/chainedRunner.js.map +1 -1
- package/dist/recipes/detectSilentFail.d.ts +34 -0
- package/dist/recipes/detectSilentFail.js +105 -0
- package/dist/recipes/detectSilentFail.js.map +1 -0
- package/dist/recipes/legacyRecipeCompat.d.ts +8 -0
- package/dist/recipes/legacyRecipeCompat.js +20 -1
- package/dist/recipes/legacyRecipeCompat.js.map +1 -1
- package/dist/recipes/manifest.js +21 -6
- package/dist/recipes/manifest.js.map +1 -1
- package/dist/recipes/migrations/index.d.ts +24 -0
- package/dist/recipes/migrations/index.js +55 -0
- package/dist/recipes/migrations/index.js.map +1 -0
- package/dist/recipes/migrations/types.d.ts +28 -0
- package/dist/recipes/migrations/types.js +2 -0
- package/dist/recipes/migrations/types.js.map +1 -0
- package/dist/recipes/migrations/v1.d.ts +11 -0
- package/dist/recipes/migrations/v1.js +18 -0
- package/dist/recipes/migrations/v1.js.map +1 -0
- package/dist/recipes/replayRun.d.ts +62 -0
- package/dist/recipes/replayRun.js +97 -0
- package/dist/recipes/replayRun.js.map +1 -0
- package/dist/recipes/scheduler.js +102 -11
- package/dist/recipes/scheduler.js.map +1 -1
- package/dist/recipes/schemaGenerator.js +3 -3
- package/dist/recipes/schemaGenerator.js.map +1 -1
- package/dist/recipes/templateEngine.js +8 -1
- package/dist/recipes/templateEngine.js.map +1 -1
- package/dist/recipes/toolRegistry.d.ts +5 -0
- package/dist/recipes/toolRegistry.js +9 -0
- package/dist/recipes/toolRegistry.js.map +1 -1
- package/dist/recipes/tools/asana.d.ts +16 -0
- package/dist/recipes/tools/asana.js +524 -0
- package/dist/recipes/tools/asana.js.map +1 -0
- package/dist/recipes/tools/discord.d.ts +18 -0
- package/dist/recipes/tools/discord.js +254 -0
- package/dist/recipes/tools/discord.js.map +1 -0
- package/dist/recipes/tools/github.js +29 -4
- package/dist/recipes/tools/github.js.map +1 -1
- package/dist/recipes/tools/gitlab.d.ts +11 -0
- package/dist/recipes/tools/gitlab.js +285 -0
- package/dist/recipes/tools/gitlab.js.map +1 -0
- package/dist/recipes/tools/gmail.d.ts +1 -1
- package/dist/recipes/tools/gmail.js +230 -6
- package/dist/recipes/tools/gmail.js.map +1 -1
- package/dist/recipes/tools/googleDrive.d.ts +1 -0
- package/dist/recipes/tools/googleDrive.js +55 -0
- package/dist/recipes/tools/googleDrive.js.map +1 -0
- package/dist/recipes/tools/index.d.ts +6 -0
- package/dist/recipes/tools/index.js +6 -0
- package/dist/recipes/tools/index.js.map +1 -1
- package/dist/recipes/tools/linear.d.ts +2 -1
- package/dist/recipes/tools/linear.js +222 -1
- package/dist/recipes/tools/linear.js.map +1 -1
- package/dist/recipes/tools/meetingNotes.d.ts +21 -0
- package/dist/recipes/tools/meetingNotes.js +701 -0
- package/dist/recipes/tools/meetingNotes.js.map +1 -0
- package/dist/recipes/tools/pagerduty.d.ts +15 -0
- package/dist/recipes/tools/pagerduty.js +451 -0
- package/dist/recipes/tools/pagerduty.js.map +1 -0
- package/dist/recipes/tools/slack.js +8 -2
- package/dist/recipes/tools/slack.js.map +1 -1
- package/dist/recipes/validation.js +54 -15
- package/dist/recipes/validation.js.map +1 -1
- package/dist/recipes/yamlRunner.d.ts +23 -2
- package/dist/recipes/yamlRunner.js +265 -60
- package/dist/recipes/yamlRunner.js.map +1 -1
- package/dist/recipesHttp.d.ts +60 -0
- package/dist/recipesHttp.js +418 -3
- package/dist/recipesHttp.js.map +1 -1
- package/dist/runLog.d.ts +64 -2
- package/dist/runLog.js +116 -2
- package/dist/runLog.js.map +1 -1
- package/dist/server.d.ts +21 -0
- package/dist/server.js +387 -8
- package/dist/server.js.map +1 -1
- package/dist/streamableHttp.d.ts +31 -1
- package/dist/streamableHttp.js +20 -2
- package/dist/streamableHttp.js.map +1 -1
- package/dist/tools/activityLog.d.ts +2 -0
- package/dist/tools/addLinearComment.d.ts +1 -0
- package/dist/tools/batchLsp.d.ts +3 -0
- package/dist/tools/bridgeDoctor.d.ts +1 -0
- package/dist/tools/bridgeStatus.d.ts +1 -0
- package/dist/tools/cancelClaudeTask.d.ts +1 -0
- package/dist/tools/checkDocumentDirty.d.ts +1 -0
- package/dist/tools/clipboard.d.ts +2 -0
- package/dist/tools/closeTabs.d.ts +2 -0
- package/dist/tools/codeLens.d.ts +1 -0
- package/dist/tools/contextBundle.d.ts +1 -0
- package/dist/tools/createIssueFromAIComment.d.ts +1 -0
- package/dist/tools/createLinearIssue.d.ts +1 -0
- package/dist/tools/ctxGetTaskContext.d.ts +1 -0
- package/dist/tools/ctxQueryTraces.d.ts +1 -0
- package/dist/tools/ctxSaveTrace.d.ts +1 -0
- package/dist/tools/debug.d.ts +4 -0
- package/dist/tools/decorations.d.ts +2 -0
- package/dist/tools/documentLinks.d.ts +1 -0
- package/dist/tools/editText.d.ts +1 -0
- package/dist/tools/enrichCommit.d.ts +1 -0
- package/dist/tools/enrichStackTrace.d.ts +1 -0
- package/dist/tools/explainDiagnostic.d.ts +1 -0
- package/dist/tools/explainSymbol.d.ts +1 -0
- package/dist/tools/fetchCalendarEvents.d.ts +1 -0
- package/dist/tools/fetchGithubIssue.d.ts +1 -0
- package/dist/tools/fetchGithubPR.d.ts +1 -0
- package/dist/tools/fetchLinearIssue.d.ts +1 -0
- package/dist/tools/fetchSentryIssue.d.ts +1 -0
- package/dist/tools/fetchSlackProfile.d.ts +1 -0
- package/dist/tools/fileOperations.d.ts +3 -0
- package/dist/tools/fileWatcher.d.ts +2 -0
- package/dist/tools/findFiles.d.ts +1 -0
- package/dist/tools/findRelatedTests.d.ts +1 -0
- package/dist/tools/fixAllLintErrors.d.ts +1 -0
- package/dist/tools/foldingRanges.d.ts +1 -0
- package/dist/tools/formatDocument.d.ts +1 -0
- package/dist/tools/generateTests.d.ts +1 -0
- package/dist/tools/getAIComments.d.ts +1 -0
- package/dist/tools/getAnalyticsReport.d.ts +1 -0
- package/dist/tools/getArchitectureContext.d.ts +1 -0
- package/dist/tools/getBufferContent.d.ts +1 -0
- package/dist/tools/getChangeImpact.d.ts +1 -0
- package/dist/tools/getClaudeTaskStatus.d.ts +1 -0
- package/dist/tools/getCodeCoverage.d.ts +1 -0
- package/dist/tools/getCommitsForIssue.d.ts +1 -0
- package/dist/tools/getConnectorStatus.d.ts +1 -0
- package/dist/tools/getCurrentSelection.d.ts +2 -0
- package/dist/tools/getDebugState.d.ts +1 -0
- package/dist/tools/getDependencyTree.d.ts +1 -0
- package/dist/tools/getDiagnostics.d.ts +1 -0
- package/dist/tools/getDiffFromHandoff.d.ts +1 -0
- package/dist/tools/getDocumentSymbols.d.ts +1 -0
- package/dist/tools/getFileTree.d.ts +1 -0
- package/dist/tools/getGitDiff.d.ts +1 -0
- package/dist/tools/getGitHotspots.d.ts +1 -0
- package/dist/tools/getGitLog.d.ts +1 -0
- package/dist/tools/getGitStatus.d.ts +1 -0
- package/dist/tools/getImportTree.d.ts +1 -0
- package/dist/tools/getImportedSignatures.d.ts +1 -0
- package/dist/tools/getOpenEditors.d.ts +1 -0
- package/dist/tools/getPRTemplate.d.ts +1 -0
- package/dist/tools/getProjectContext.d.ts +1 -0
- package/dist/tools/getProjectInfo.d.ts +1 -0
- package/dist/tools/getSecurityAdvisories.d.ts +1 -0
- package/dist/tools/getSessionUsage.d.ts +1 -0
- package/dist/tools/getSymbolHistory.d.ts +1 -0
- package/dist/tools/getToolCapabilities.d.ts +1 -0
- package/dist/tools/getTypeSignature.d.ts +1 -0
- package/dist/tools/getWorkspaceFolders.d.ts +1 -0
- package/dist/tools/getWorkspaceSettings.d.ts +1 -0
- package/dist/tools/gitHistory.d.ts +2 -0
- package/dist/tools/gitWrite.d.ts +11 -0
- package/dist/tools/github/actions.d.ts +2 -0
- package/dist/tools/github/composite.d.ts +3 -0
- package/dist/tools/github/issues.d.ts +4 -0
- package/dist/tools/github/pr.d.ts +7 -0
- package/dist/tools/handoffNote.d.ts +2 -0
- package/dist/tools/hoverAtCursor.d.ts +1 -0
- package/dist/tools/httpClient.d.ts +2 -0
- package/dist/tools/inlayHints.d.ts +1 -0
- package/dist/tools/launchQuickTask.d.ts +1 -0
- package/dist/tools/listClaudeTasks.d.ts +1 -0
- package/dist/tools/listTerminals.d.ts +1 -0
- package/dist/tools/lsp.d.ts +14 -0
- package/dist/tools/navigateToSymbolByName.d.ts +1 -0
- package/dist/tools/openDiff.d.ts +1 -0
- package/dist/tools/openFile.d.ts +1 -0
- package/dist/tools/openInBrowser.d.ts +1 -0
- package/dist/tools/organizeImports.d.ts +1 -0
- package/dist/tools/performanceReport.d.ts +1 -0
- package/dist/tools/planPersistence.d.ts +5 -0
- package/dist/tools/previewEdit.d.ts +1 -0
- package/dist/tools/refactorAnalyze.d.ts +1 -0
- package/dist/tools/refactorPreview.d.ts +1 -0
- package/dist/tools/replaceBlock.d.ts +1 -0
- package/dist/tools/resumeClaudeTask.d.ts +1 -0
- package/dist/tools/runClaudeTask.d.ts +1 -0
- package/dist/tools/runCommand.d.ts +1 -0
- package/dist/tools/runTests.d.ts +1 -0
- package/dist/tools/saveDocument.d.ts +1 -0
- package/dist/tools/screenshotAndAnnotate.d.ts +1 -0
- package/dist/tools/searchAndReplace.d.ts +1 -0
- package/dist/tools/searchTools.d.ts +1 -0
- package/dist/tools/searchWorkspace.d.ts +1 -0
- package/dist/tools/selectionRanges.d.ts +1 -0
- package/dist/tools/semanticTokens.d.ts +1 -0
- package/dist/tools/setActiveWorkspaceFolder.d.ts +1 -0
- package/dist/tools/signatureHelp.d.ts +1 -0
- package/dist/tools/slackListChannels.d.ts +1 -0
- package/dist/tools/slackPostMessage.d.ts +1 -0
- package/dist/tools/slackPostMessage.js +1 -1
- package/dist/tools/slackPostMessage.js.map +1 -1
- package/dist/tools/terminal.d.ts +6 -0
- package/dist/tools/testTraceToSource.d.ts +1 -0
- package/dist/tools/transaction.d.ts +4 -0
- package/dist/tools/typeHierarchy.d.ts +1 -0
- package/dist/tools/updateLinearIssue.d.ts +1 -0
- package/dist/tools/utils.d.ts +2 -0
- package/dist/tools/utils.js.map +1 -1
- package/dist/tools/vscodeCommands.d.ts +2 -0
- package/dist/tools/vscodeTasks.d.ts +2 -0
- package/dist/tools/workspaceSettings.d.ts +1 -0
- package/package.json +20 -4
- package/templates/recipes/project-health-check.yaml +1 -1
- package/dist/schemas/dry-run-plan.v1.json +0 -139
- package/dist/schemas/recipe.v1.json +0 -684
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord connector — read guilds, channels, messages.
|
|
3
|
+
*
|
|
4
|
+
* OAuth 2.0 Authorization Code Grant. Discord is a confidential client
|
|
5
|
+
* (bridge holds the client secret), so PKCE is not required. Refresh
|
|
6
|
+
* tokens are issued; access tokens expire (typically 7 days).
|
|
7
|
+
*
|
|
8
|
+
* Auth: standard OAuth 2.0 with `client_id` + `client_secret` + `redirect_uri`.
|
|
9
|
+
* - Env vars: DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET (mirrors slack)
|
|
10
|
+
* - Stored: getSecretJsonSync("discord") → DiscordTokens
|
|
11
|
+
* - Header: Authorization: Bearer <access_token>
|
|
12
|
+
*
|
|
13
|
+
* Tools: getCurrentUser, listGuilds, listChannels, listMessages (read);
|
|
14
|
+
* sendMessage (write).
|
|
15
|
+
*
|
|
16
|
+
* NOTE on writes: Discord's REST API does NOT permit user-context OAuth tokens
|
|
17
|
+
* to send messages on behalf of the user — only bot-scope tokens can hit
|
|
18
|
+
* `POST /channels/{id}/messages`. The current connector authenticates as a
|
|
19
|
+
* regular user (scopes: identify, guilds, messages.read), so `sendMessage`
|
|
20
|
+
* will return a `permission_denied` error until the user re-authenticates
|
|
21
|
+
* with the `bot` scope. The method is wired correctly; the gap is the auth
|
|
22
|
+
* scope, which is left to operators to upgrade when they want write access.
|
|
23
|
+
*
|
|
24
|
+
* HTTP routes (wired in src/server.ts):
|
|
25
|
+
* GET /connections/discord/auth — redirect to Discord consent
|
|
26
|
+
* GET /connections/discord/callback — exchange code for tokens
|
|
27
|
+
* POST /connections/discord/test — ping Discord API
|
|
28
|
+
* DELETE /connections/discord — clear stored tokens
|
|
29
|
+
*
|
|
30
|
+
* Extends BaseConnector for unified auth, retry, rate-limit, error handling.
|
|
31
|
+
* Token refresh is delegated to BaseConnector.refreshToken() via apiCall.
|
|
32
|
+
*/
|
|
33
|
+
import crypto from "node:crypto";
|
|
34
|
+
import { BaseConnector, } from "./baseConnector.js";
|
|
35
|
+
import { escHtml } from "./htmlEscape.js";
|
|
36
|
+
import { deleteSecretJsonSync, getSecretJsonSync, storeSecretJsonSync, } from "./tokenStorage.js";
|
|
37
|
+
const DISCORD_API_BASE = "https://discord.com/api/v10";
|
|
38
|
+
const DISCORD_AUTH_URL = "https://discord.com/api/oauth2/authorize";
|
|
39
|
+
const DISCORD_TOKEN_URL = "https://discord.com/api/oauth2/token";
|
|
40
|
+
const DISCORD_REVOKE_URL = "https://discord.com/api/oauth2/token/revoke";
|
|
41
|
+
const SCOPES = ["identify", "guilds", "messages.read"];
|
|
42
|
+
// ── Config ───────────────────────────────────────────────────────────────────
|
|
43
|
+
function clientId() {
|
|
44
|
+
return process.env.DISCORD_CLIENT_ID ?? "";
|
|
45
|
+
}
|
|
46
|
+
function clientSecret() {
|
|
47
|
+
return process.env.DISCORD_CLIENT_SECRET ?? "";
|
|
48
|
+
}
|
|
49
|
+
function redirectUri() {
|
|
50
|
+
const base = (process.env.PATCHWORK_BRIDGE_URL ??
|
|
51
|
+
`http://localhost:${process.env.PATCHWORK_BRIDGE_PORT ?? "3101"}`).replace(/\/$/, "");
|
|
52
|
+
return `${base}/connections/discord/callback`;
|
|
53
|
+
}
|
|
54
|
+
function isConfigured() {
|
|
55
|
+
return Boolean(clientId() && clientSecret());
|
|
56
|
+
}
|
|
57
|
+
// ── Token persistence ────────────────────────────────────────────────────────
|
|
58
|
+
export function loadTokens() {
|
|
59
|
+
return getSecretJsonSync("discord");
|
|
60
|
+
}
|
|
61
|
+
export function saveTokens(tokens) {
|
|
62
|
+
storeSecretJsonSync("discord", tokens);
|
|
63
|
+
}
|
|
64
|
+
export function clearTokens() {
|
|
65
|
+
try {
|
|
66
|
+
deleteSecretJsonSync("discord");
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// ignore
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export function isConnected() {
|
|
73
|
+
return loadTokens() !== null;
|
|
74
|
+
}
|
|
75
|
+
// ── State (CSRF) ─────────────────────────────────────────────────────────────
|
|
76
|
+
// In-memory map keyed by hex random — short-lived (5 min). Mirrors gmail's
|
|
77
|
+
// approach (Set + setTimeout) rather than slack's on-disk file because we
|
|
78
|
+
// don't need cross-process resumption for an OAuth round-trip.
|
|
79
|
+
const pendingStates = new Set();
|
|
80
|
+
const STATE_TTL_MS = 5 * 60 * 1000;
|
|
81
|
+
function generateState() {
|
|
82
|
+
const state = crypto.randomBytes(32).toString("hex");
|
|
83
|
+
pendingStates.add(state);
|
|
84
|
+
setTimeout(() => pendingStates.delete(state), STATE_TTL_MS);
|
|
85
|
+
return state;
|
|
86
|
+
}
|
|
87
|
+
function consumeState(state) {
|
|
88
|
+
if (!pendingStates.has(state))
|
|
89
|
+
return false;
|
|
90
|
+
pendingStates.delete(state);
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
// ── Connector class ──────────────────────────────────────────────────────────
|
|
94
|
+
export class DiscordConnector extends BaseConnector {
|
|
95
|
+
providerName = "discord";
|
|
96
|
+
getOAuthConfig() {
|
|
97
|
+
// Resolve credentials from env first, then fall back to credentials we
|
|
98
|
+
// stored at auth time (so refresh keeps working even if env is unset).
|
|
99
|
+
const tokens = loadTokens();
|
|
100
|
+
const id = clientId() || tokens?._client_id || "";
|
|
101
|
+
const secret = clientSecret() || tokens?._client_secret || "";
|
|
102
|
+
if (!id || !secret)
|
|
103
|
+
return null;
|
|
104
|
+
return {
|
|
105
|
+
clientId: id,
|
|
106
|
+
clientSecret: secret,
|
|
107
|
+
tokenEndpoint: DISCORD_TOKEN_URL,
|
|
108
|
+
scopes: SCOPES,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
async authenticate() {
|
|
112
|
+
const tokens = loadTokens();
|
|
113
|
+
if (!tokens) {
|
|
114
|
+
throw new Error("Discord not connected. Visit /connections/discord/auth to authorize.");
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
token: tokens.access_token,
|
|
118
|
+
refreshToken: tokens.refresh_token,
|
|
119
|
+
expiresAt: tokens.expires_at ? new Date(tokens.expires_at) : undefined,
|
|
120
|
+
scopes: tokens.scope ? tokens.scope.split(" ") : SCOPES,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Persist refreshed tokens after BaseConnector.refreshToken() updates
|
|
125
|
+
* `this.auth`. BaseConnector calls `saveTokens()` (its own method on the
|
|
126
|
+
* tokenStorage StoredToken shape); we additionally mirror to our
|
|
127
|
+
* Discord-specific JSON so loadTokens() keeps working for HTTP probes.
|
|
128
|
+
*/
|
|
129
|
+
async saveTokens() {
|
|
130
|
+
await super.saveTokens();
|
|
131
|
+
if (!this.auth)
|
|
132
|
+
return;
|
|
133
|
+
const existing = loadTokens();
|
|
134
|
+
saveTokens({
|
|
135
|
+
access_token: this.auth.token,
|
|
136
|
+
refresh_token: this.auth.refreshToken,
|
|
137
|
+
expires_at: this.auth.expiresAt
|
|
138
|
+
? this.auth.expiresAt.getTime()
|
|
139
|
+
: undefined,
|
|
140
|
+
scope: this.auth.scopes?.join(" "),
|
|
141
|
+
token_type: existing?.token_type ?? "Bearer",
|
|
142
|
+
_client_id: existing?._client_id,
|
|
143
|
+
_client_secret: existing?._client_secret,
|
|
144
|
+
username: existing?.username,
|
|
145
|
+
user_id: existing?.user_id,
|
|
146
|
+
connected_at: existing?.connected_at ?? new Date().toISOString(),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
async healthCheck() {
|
|
150
|
+
try {
|
|
151
|
+
const result = await this.apiCall(async (token) => {
|
|
152
|
+
const res = await fetch(`${DISCORD_API_BASE}/users/@me`, {
|
|
153
|
+
headers: this.buildHeaders(token),
|
|
154
|
+
});
|
|
155
|
+
if (!res.ok)
|
|
156
|
+
throw res;
|
|
157
|
+
return res.json();
|
|
158
|
+
});
|
|
159
|
+
if ("error" in result)
|
|
160
|
+
return { ok: false, error: result.error };
|
|
161
|
+
return { ok: true };
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
return { ok: false, error: this.normalizeError(err) };
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
normalizeError(error) {
|
|
168
|
+
if (error instanceof Response) {
|
|
169
|
+
const s = error.status;
|
|
170
|
+
if (s === 401)
|
|
171
|
+
return {
|
|
172
|
+
code: "auth_expired",
|
|
173
|
+
message: "Discord authentication failed — token expired or revoked",
|
|
174
|
+
retryable: true,
|
|
175
|
+
suggestedAction: "Reconnect via /connections/discord/auth",
|
|
176
|
+
};
|
|
177
|
+
if (s === 403)
|
|
178
|
+
return {
|
|
179
|
+
code: "permission_denied",
|
|
180
|
+
message: "Discord write requires bot scope — re-authenticate with the bot scope to enable sendMessage. (Other 403s indicate insufficient permissions for this resource.)",
|
|
181
|
+
retryable: false,
|
|
182
|
+
};
|
|
183
|
+
if (s === 404)
|
|
184
|
+
return {
|
|
185
|
+
code: "not_found",
|
|
186
|
+
message: "Discord resource not found",
|
|
187
|
+
retryable: false,
|
|
188
|
+
};
|
|
189
|
+
if (s === 429)
|
|
190
|
+
return {
|
|
191
|
+
code: "rate_limited",
|
|
192
|
+
message: "Discord API rate limit exceeded",
|
|
193
|
+
retryable: true,
|
|
194
|
+
suggestedAction: "Wait and retry",
|
|
195
|
+
};
|
|
196
|
+
return {
|
|
197
|
+
code: "provider_error",
|
|
198
|
+
message: `Discord API error: HTTP ${s}`,
|
|
199
|
+
retryable: s >= 500,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
if (error instanceof Error) {
|
|
203
|
+
if (error.message.includes("ENOTFOUND") ||
|
|
204
|
+
error.message.includes("ECONNREFUSED")) {
|
|
205
|
+
return {
|
|
206
|
+
code: "network_error",
|
|
207
|
+
message: `Cannot connect to Discord: ${error.message}`,
|
|
208
|
+
retryable: true,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
code: "provider_error",
|
|
214
|
+
message: error instanceof Error ? error.message : String(error),
|
|
215
|
+
retryable: false,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
getStatus() {
|
|
219
|
+
const tokens = loadTokens();
|
|
220
|
+
return {
|
|
221
|
+
id: "discord",
|
|
222
|
+
status: tokens ? "connected" : "disconnected",
|
|
223
|
+
lastSync: tokens?.connected_at,
|
|
224
|
+
workspace: tokens?.username,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
// ── API Methods ────────────────────────────────────────────────────────────
|
|
228
|
+
async getCurrentUser() {
|
|
229
|
+
const result = await this.apiCall(async (token) => {
|
|
230
|
+
const res = await fetch(`${DISCORD_API_BASE}/users/@me`, {
|
|
231
|
+
headers: this.buildHeaders(token),
|
|
232
|
+
});
|
|
233
|
+
this.captureRateLimit(res);
|
|
234
|
+
if (!res.ok)
|
|
235
|
+
throw res;
|
|
236
|
+
return res.json();
|
|
237
|
+
});
|
|
238
|
+
if ("error" in result)
|
|
239
|
+
throw new Error(result.error.message);
|
|
240
|
+
return result.data;
|
|
241
|
+
}
|
|
242
|
+
async listGuilds(params = {}) {
|
|
243
|
+
const result = await this.apiCall(async (token) => {
|
|
244
|
+
const qs = new URLSearchParams({
|
|
245
|
+
limit: String(Math.min(params.limit ?? 100, 200)),
|
|
246
|
+
});
|
|
247
|
+
const res = await fetch(`${DISCORD_API_BASE}/users/@me/guilds?${qs}`, {
|
|
248
|
+
headers: this.buildHeaders(token),
|
|
249
|
+
});
|
|
250
|
+
this.captureRateLimit(res);
|
|
251
|
+
if (!res.ok)
|
|
252
|
+
throw res;
|
|
253
|
+
return res.json();
|
|
254
|
+
});
|
|
255
|
+
if ("error" in result)
|
|
256
|
+
throw new Error(result.error.message);
|
|
257
|
+
return result.data;
|
|
258
|
+
}
|
|
259
|
+
async listChannels(guildId) {
|
|
260
|
+
const result = await this.apiCall(async (token) => {
|
|
261
|
+
const res = await fetch(`${DISCORD_API_BASE}/guilds/${encodeURIComponent(guildId)}/channels`, { headers: this.buildHeaders(token) });
|
|
262
|
+
this.captureRateLimit(res);
|
|
263
|
+
if (!res.ok)
|
|
264
|
+
throw res;
|
|
265
|
+
return res.json();
|
|
266
|
+
});
|
|
267
|
+
if ("error" in result)
|
|
268
|
+
throw new Error(result.error.message);
|
|
269
|
+
// Filter to text channels only (type 0). Voice/category/thread channels
|
|
270
|
+
// are excluded — recipe consumers only care about messageable text rooms.
|
|
271
|
+
return result.data.filter((c) => c.type === 0);
|
|
272
|
+
}
|
|
273
|
+
async listMessages(channelId, params = {}) {
|
|
274
|
+
const result = await this.apiCall(async (token) => {
|
|
275
|
+
const qs = new URLSearchParams({
|
|
276
|
+
limit: String(Math.min(params.limit ?? 50, 100)),
|
|
277
|
+
});
|
|
278
|
+
const res = await fetch(`${DISCORD_API_BASE}/channels/${encodeURIComponent(channelId)}/messages?${qs}`, { headers: this.buildHeaders(token) });
|
|
279
|
+
this.captureRateLimit(res);
|
|
280
|
+
if (!res.ok)
|
|
281
|
+
throw res;
|
|
282
|
+
return res.json();
|
|
283
|
+
});
|
|
284
|
+
if ("error" in result)
|
|
285
|
+
throw new Error(result.error.message);
|
|
286
|
+
return result.data;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Send a message to a Discord channel.
|
|
290
|
+
*
|
|
291
|
+
* Caveat: requires bot-scope OAuth — regular user-context tokens cannot
|
|
292
|
+
* send via this endpoint. On 403 the connector surfaces a
|
|
293
|
+
* `permission_denied` error pointing at the bot-scope requirement.
|
|
294
|
+
*
|
|
295
|
+
* @param channelId Discord channel id (non-empty)
|
|
296
|
+
* @param body { content (≤2000 chars), tts? defaults false }
|
|
297
|
+
*/
|
|
298
|
+
async sendMessage(channelId, body) {
|
|
299
|
+
if (!channelId || typeof channelId !== "string") {
|
|
300
|
+
throw new Error("sendMessage requires a non-empty channelId");
|
|
301
|
+
}
|
|
302
|
+
if (!body?.content || typeof body.content !== "string") {
|
|
303
|
+
throw new Error("sendMessage requires a non-empty content string");
|
|
304
|
+
}
|
|
305
|
+
if (body.content.length > 2000) {
|
|
306
|
+
throw new Error(`sendMessage content exceeds Discord's 2000-character limit (${body.content.length})`);
|
|
307
|
+
}
|
|
308
|
+
const payload = {
|
|
309
|
+
content: body.content,
|
|
310
|
+
tts: body.tts ?? false,
|
|
311
|
+
};
|
|
312
|
+
const result = await this.apiCall(async (token) => {
|
|
313
|
+
const res = await fetch(`${DISCORD_API_BASE}/channels/${encodeURIComponent(channelId)}/messages`, {
|
|
314
|
+
method: "POST",
|
|
315
|
+
headers: this.buildHeaders(token),
|
|
316
|
+
body: JSON.stringify(payload),
|
|
317
|
+
});
|
|
318
|
+
this.captureRateLimit(res);
|
|
319
|
+
if (!res.ok)
|
|
320
|
+
throw res;
|
|
321
|
+
return res.json();
|
|
322
|
+
});
|
|
323
|
+
if ("error" in result)
|
|
324
|
+
throw new Error(result.error.message);
|
|
325
|
+
return result.data;
|
|
326
|
+
}
|
|
327
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
328
|
+
buildHeaders(token) {
|
|
329
|
+
return {
|
|
330
|
+
Authorization: `Bearer ${token}`,
|
|
331
|
+
Accept: "application/json",
|
|
332
|
+
"Content-Type": "application/json",
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
captureRateLimit(res) {
|
|
336
|
+
this.updateRateLimitFromHeaders({
|
|
337
|
+
"x-ratelimit-remaining": res.headers.get("x-ratelimit-remaining") ?? undefined,
|
|
338
|
+
"x-ratelimit-reset": res.headers.get("x-ratelimit-reset") ?? undefined,
|
|
339
|
+
"retry-after": res.headers.get("retry-after") ?? undefined,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// ── Singleton ────────────────────────────────────────────────────────────────
|
|
344
|
+
let _instance = null;
|
|
345
|
+
function resetDiscordConnector() {
|
|
346
|
+
_instance = null;
|
|
347
|
+
}
|
|
348
|
+
export function getDiscordConnector() {
|
|
349
|
+
if (!_instance) {
|
|
350
|
+
_instance = new DiscordConnector();
|
|
351
|
+
}
|
|
352
|
+
return _instance;
|
|
353
|
+
}
|
|
354
|
+
export { getDiscordConnector as discord };
|
|
355
|
+
/**
|
|
356
|
+
* GET /connections/discord/auth — redirect to Discord consent screen.
|
|
357
|
+
*/
|
|
358
|
+
export function handleDiscordAuthorize() {
|
|
359
|
+
if (!isConfigured()) {
|
|
360
|
+
return {
|
|
361
|
+
status: 503,
|
|
362
|
+
contentType: "application/json",
|
|
363
|
+
body: JSON.stringify({
|
|
364
|
+
ok: false,
|
|
365
|
+
error: "Discord connector not configured. Set DISCORD_CLIENT_ID and DISCORD_CLIENT_SECRET.",
|
|
366
|
+
}),
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
const state = generateState();
|
|
370
|
+
const params = new URLSearchParams({
|
|
371
|
+
client_id: clientId(),
|
|
372
|
+
redirect_uri: redirectUri(),
|
|
373
|
+
response_type: "code",
|
|
374
|
+
scope: SCOPES.join(" "),
|
|
375
|
+
state,
|
|
376
|
+
});
|
|
377
|
+
return {
|
|
378
|
+
status: 302,
|
|
379
|
+
body: "",
|
|
380
|
+
redirect: `${DISCORD_AUTH_URL}?${params.toString()}`,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* GET /connections/discord/callback — exchange code for tokens.
|
|
385
|
+
*/
|
|
386
|
+
export async function handleDiscordCallback(code, state, error) {
|
|
387
|
+
if (error) {
|
|
388
|
+
return {
|
|
389
|
+
status: 400,
|
|
390
|
+
contentType: "text/html",
|
|
391
|
+
body: `<html><body><h2>Discord connect failed</h2><pre>${escHtml(error)}</pre></body></html>`,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
if (!code || !state) {
|
|
395
|
+
return {
|
|
396
|
+
status: 400,
|
|
397
|
+
contentType: "text/html",
|
|
398
|
+
body: `<html><body><h2>Discord connect failed</h2><pre>missing code or state</pre></body></html>`,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
if (!consumeState(state)) {
|
|
402
|
+
return {
|
|
403
|
+
status: 400,
|
|
404
|
+
contentType: "text/html",
|
|
405
|
+
body: `<html><body><h2>Discord connect failed</h2><pre>invalid or expired state</pre></body></html>`,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
try {
|
|
409
|
+
const params = new URLSearchParams({
|
|
410
|
+
grant_type: "authorization_code",
|
|
411
|
+
code,
|
|
412
|
+
redirect_uri: redirectUri(),
|
|
413
|
+
client_id: clientId(),
|
|
414
|
+
client_secret: clientSecret(),
|
|
415
|
+
});
|
|
416
|
+
const res = await fetch(DISCORD_TOKEN_URL, {
|
|
417
|
+
method: "POST",
|
|
418
|
+
headers: {
|
|
419
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
420
|
+
Accept: "application/json",
|
|
421
|
+
},
|
|
422
|
+
body: params.toString(),
|
|
423
|
+
});
|
|
424
|
+
if (!res.ok) {
|
|
425
|
+
const body = await res.text();
|
|
426
|
+
throw new Error(`Token exchange HTTP ${res.status}: ${body}`);
|
|
427
|
+
}
|
|
428
|
+
const json = (await res.json());
|
|
429
|
+
if (!json.access_token) {
|
|
430
|
+
throw new Error("Token exchange returned no access_token");
|
|
431
|
+
}
|
|
432
|
+
// Fetch user info so we can show "connected as <username>" on the dash.
|
|
433
|
+
let username;
|
|
434
|
+
let userId;
|
|
435
|
+
try {
|
|
436
|
+
const userRes = await fetch(`${DISCORD_API_BASE}/users/@me`, {
|
|
437
|
+
headers: { Authorization: `Bearer ${json.access_token}` },
|
|
438
|
+
});
|
|
439
|
+
if (userRes.ok) {
|
|
440
|
+
const u = (await userRes.json());
|
|
441
|
+
username = u.global_name ?? u.username;
|
|
442
|
+
userId = u.id;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
catch {
|
|
446
|
+
// best-effort — don't fail connect just because /users/@me hiccuped
|
|
447
|
+
}
|
|
448
|
+
const expiresAt = typeof json.expires_in === "number" && json.expires_in > 0
|
|
449
|
+
? Date.now() + json.expires_in * 1000
|
|
450
|
+
: undefined;
|
|
451
|
+
saveTokens({
|
|
452
|
+
access_token: json.access_token,
|
|
453
|
+
refresh_token: json.refresh_token,
|
|
454
|
+
expires_at: expiresAt,
|
|
455
|
+
scope: json.scope,
|
|
456
|
+
token_type: json.token_type ?? "Bearer",
|
|
457
|
+
_client_id: clientId() || undefined,
|
|
458
|
+
_client_secret: clientSecret() || undefined,
|
|
459
|
+
username,
|
|
460
|
+
user_id: userId,
|
|
461
|
+
connected_at: new Date().toISOString(),
|
|
462
|
+
});
|
|
463
|
+
resetDiscordConnector();
|
|
464
|
+
return {
|
|
465
|
+
status: 200,
|
|
466
|
+
contentType: "text/html",
|
|
467
|
+
body: `<html><body><h2>Discord connected${username ? ` as ${escHtml(username)}` : ""}</h2><script>try { window.opener.postMessage('patchwork:discord:connected', '*'); } catch(_) {} window.close();</script></body></html>`,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
catch (err) {
|
|
471
|
+
return {
|
|
472
|
+
status: 400,
|
|
473
|
+
contentType: "text/html",
|
|
474
|
+
body: `<html><body><h2>Discord connect failed</h2><pre>${escHtml(err instanceof Error ? err.message : String(err))}</pre></body></html>`,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* POST /connections/discord/test — verify stored token works.
|
|
480
|
+
*/
|
|
481
|
+
export async function handleDiscordTest() {
|
|
482
|
+
const tokens = loadTokens();
|
|
483
|
+
if (!tokens) {
|
|
484
|
+
return {
|
|
485
|
+
status: 400,
|
|
486
|
+
contentType: "application/json",
|
|
487
|
+
body: JSON.stringify({ ok: false, error: "Discord not connected" }),
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
try {
|
|
491
|
+
const connector = getDiscordConnector();
|
|
492
|
+
const check = await connector.healthCheck();
|
|
493
|
+
return {
|
|
494
|
+
status: check.ok ? 200 : 401,
|
|
495
|
+
contentType: "application/json",
|
|
496
|
+
body: JSON.stringify(check.ok
|
|
497
|
+
? { ok: true, username: tokens.username }
|
|
498
|
+
: { ok: false, error: check.error?.message }),
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
catch (err) {
|
|
502
|
+
return {
|
|
503
|
+
status: 500,
|
|
504
|
+
contentType: "application/json",
|
|
505
|
+
body: JSON.stringify({
|
|
506
|
+
ok: false,
|
|
507
|
+
error: err instanceof Error ? err.message : String(err),
|
|
508
|
+
}),
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* DELETE /connections/discord — clear stored tokens (and revoke at Discord).
|
|
514
|
+
*/
|
|
515
|
+
export async function handleDiscordDisconnect() {
|
|
516
|
+
const tokens = loadTokens();
|
|
517
|
+
// Best-effort revoke at Discord. Slack doesn't do this but Discord exposes
|
|
518
|
+
// a standard RFC 7009 revocation endpoint, and the spec allowlist permits
|
|
519
|
+
// it. Failure to revoke must NOT block the local disconnect.
|
|
520
|
+
if (tokens?.access_token && isConfigured()) {
|
|
521
|
+
try {
|
|
522
|
+
await fetch(DISCORD_REVOKE_URL, {
|
|
523
|
+
method: "POST",
|
|
524
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
525
|
+
body: new URLSearchParams({
|
|
526
|
+
token: tokens.access_token,
|
|
527
|
+
client_id: clientId(),
|
|
528
|
+
client_secret: clientSecret(),
|
|
529
|
+
}).toString(),
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
catch {
|
|
533
|
+
// ignore — still drop local tokens
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
clearTokens();
|
|
537
|
+
resetDiscordConnector();
|
|
538
|
+
return {
|
|
539
|
+
status: 200,
|
|
540
|
+
contentType: "application/json",
|
|
541
|
+
body: JSON.stringify({ ok: true }),
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
//# sourceMappingURL=discord.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discord.js","sourceRoot":"","sources":["../../src/connectors/discord.ts"],"names":[],"mappings":"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"}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Exports preserved for yamlRunner; listIssues/listPRs are now async.
|
|
15
15
|
*/
|
|
16
|
+
import { escHtml } from "./htmlEscape.js";
|
|
16
17
|
import { McpClient } from "./mcpClient.js";
|
|
17
18
|
import { completeAuthorize, getAccessToken, isConnected, loadTokenFile, revoke, startAuthorize, updateTokenProfile, vendorConfig, } from "./mcpOAuth.js";
|
|
18
19
|
const GITHUB_MCP_ENDPOINT = "https://api.githubcopilot.com/mcp/";
|
|
@@ -78,8 +79,14 @@ export async function listIssues(opts = {}) {
|
|
|
78
79
|
const fallbackRepo = opts.repo ?? "";
|
|
79
80
|
return arr.map((i) => coerceIssue(i, fallbackRepo));
|
|
80
81
|
}
|
|
81
|
-
catch {
|
|
82
|
-
|
|
82
|
+
catch (err) {
|
|
83
|
+
// Pre-fix this swallowed everything to `[]`. A token expiry, rate
|
|
84
|
+
// limit, or MCP outage looked identical to "no issues this week"
|
|
85
|
+
// — the recipe agent then summarized "no work" with confidence.
|
|
86
|
+
// Throw real failures so the recipe-tool wrapper can return a
|
|
87
|
+
// `{count:0, issues:[], error}` shape that the runner's silent-
|
|
88
|
+
// fail detector flags as a step error (PR #72).
|
|
89
|
+
throw new Error(`github list_issues failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
83
90
|
}
|
|
84
91
|
}
|
|
85
92
|
export async function listPRs(opts = {}) {
|
|
@@ -112,8 +119,9 @@ export async function listPRs(opts = {}) {
|
|
|
112
119
|
reviewDecision: p.review_decision ?? p.reviewDecision ?? "",
|
|
113
120
|
}));
|
|
114
121
|
}
|
|
115
|
-
catch {
|
|
116
|
-
|
|
122
|
+
catch (err) {
|
|
123
|
+
// Same antipattern as listIssues — see comment there.
|
|
124
|
+
throw new Error(`github list_pull_requests failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
117
125
|
}
|
|
118
126
|
}
|
|
119
127
|
function parseIssueRef(ref) {
|
|
@@ -243,7 +251,7 @@ export async function handleGithubCallback(code, state, error) {
|
|
|
243
251
|
return {
|
|
244
252
|
status: 400,
|
|
245
253
|
contentType: "text/html",
|
|
246
|
-
body: `<html><body><h2>GitHub connect failed</h2><pre>${error}</pre></body></html>`,
|
|
254
|
+
body: `<html><body><h2>GitHub connect failed</h2><pre>${escHtml(error)}</pre></body></html>`,
|
|
247
255
|
};
|
|
248
256
|
}
|
|
249
257
|
if (!code || !state) {
|
|
@@ -280,14 +288,14 @@ export async function handleGithubCallback(code, state, error) {
|
|
|
280
288
|
return {
|
|
281
289
|
status: 200,
|
|
282
290
|
contentType: "text/html",
|
|
283
|
-
body: `<html><body><h2>GitHub connected${login ? ` as ${login}` : ""}</h2><script>window.close();</script></body></html>`,
|
|
291
|
+
body: `<html><body><h2>GitHub connected${login ? ` as ${escHtml(login)}` : ""}</h2><script>window.close();</script></body></html>`,
|
|
284
292
|
};
|
|
285
293
|
}
|
|
286
294
|
catch (err) {
|
|
287
295
|
return {
|
|
288
296
|
status: 400,
|
|
289
297
|
contentType: "text/html",
|
|
290
|
-
body: `<html><body><h2>GitHub connect failed</h2><pre>${err instanceof Error ? err.message : String(err)}</pre></body></html>`,
|
|
298
|
+
body: `<html><body><h2>GitHub connect failed</h2><pre>${escHtml(err instanceof Error ? err.message : String(err))}</pre></body></html>`,
|
|
291
299
|
};
|
|
292
300
|
}
|
|
293
301
|
}
|