gsd-pi 2.69.0 → 2.70.0
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/dist/resources/extensions/gsd/bootstrap/system-context.js +6 -2
- package/dist/resources/extensions/gsd/commands-cmux.js +30 -1
- package/dist/resources/extensions/gsd/workflow-mcp.js +53 -6
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
- package/dist/web/standalone/.next/server/chunks/6897.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/daemon/src/orchestrator.ts +9 -84
- package/packages/mcp-server/README.md +25 -3
- package/packages/mcp-server/dist/cli.d.ts +0 -1
- package/packages/mcp-server/dist/cli.d.ts.map +1 -1
- package/packages/mcp-server/dist/cli.js +4 -2
- package/packages/mcp-server/dist/cli.js.map +1 -1
- package/packages/mcp-server/dist/server.d.ts +32 -1
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +118 -1
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/tool-credentials.d.ts +6 -0
- package/packages/mcp-server/dist/tool-credentials.d.ts.map +1 -0
- package/packages/mcp-server/dist/tool-credentials.js +90 -0
- package/packages/mcp-server/dist/tool-credentials.js.map +1 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts +1 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +274 -2
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/cli.ts +5 -3
- package/packages/mcp-server/src/mcp-server.test.ts +85 -1
- package/packages/mcp-server/src/server.ts +188 -1
- package/packages/mcp-server/src/tool-credentials.test.ts +95 -0
- package/packages/mcp-server/src/tool-credentials.ts +97 -0
- package/packages/mcp-server/src/workflow-tools.test.ts +32 -25
- package/packages/mcp-server/src/workflow-tools.ts +365 -2
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +1 -23
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/index.d.ts +3 -2
- package/packages/pi-ai/dist/utils/oauth/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/index.js +3 -5
- package/packages/pi-ai/dist/utils/oauth/index.js.map +1 -1
- package/packages/pi-ai/src/providers/anthropic.ts +1 -31
- package/packages/pi-ai/src/utils/oauth/index.ts +3 -5
- package/packages/pi-coding-agent/package.json +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +9 -5
- package/src/resources/extensions/gsd/commands-cmux.ts +32 -1
- package/src/resources/extensions/gsd/tests/cmux.test.ts +67 -1
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +6 -2
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +23 -7
- package/src/resources/extensions/gsd/workflow-mcp.ts +59 -5
- package/packages/pi-ai/dist/utils/oauth/anthropic.d.ts +0 -17
- package/packages/pi-ai/dist/utils/oauth/anthropic.d.ts.map +0 -1
- package/packages/pi-ai/dist/utils/oauth/anthropic.js +0 -106
- package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +0 -1
- package/packages/pi-ai/src/utils/oauth/anthropic.ts +0 -140
- /package/dist/web/standalone/.next/static/{DrWdzskk28E5Qz-Wjw1mj → Nl6lg7zP5dNgNBV1107v1}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{DrWdzskk28E5Qz-Wjw1mj → Nl6lg7zP5dNgNBV1107v1}/_ssgManifest.js +0 -0
|
@@ -34,9 +34,6 @@ async function getAnthropicClass(): Promise<typeof Anthropic> {
|
|
|
34
34
|
return _AnthropicClass;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
// Stealth mode: Mimic Claude Code's tool naming exactly
|
|
38
|
-
const claudeCodeVersion = "2.1.62";
|
|
39
|
-
|
|
40
37
|
function mergeHeaders(...headerSources: (Record<string, string> | undefined)[]): Record<string, string> {
|
|
41
38
|
const merged: Record<string, string> = {};
|
|
42
39
|
for (const headers of headerSources) {
|
|
@@ -47,10 +44,6 @@ function mergeHeaders(...headerSources: (Record<string, string> | undefined)[]):
|
|
|
47
44
|
return merged;
|
|
48
45
|
}
|
|
49
46
|
|
|
50
|
-
function isOAuthToken(apiKey: string): boolean {
|
|
51
|
-
return apiKey.includes("sk-ant-oat");
|
|
52
|
-
}
|
|
53
|
-
|
|
54
47
|
async function createClient(
|
|
55
48
|
model: Model<"anthropic-messages">,
|
|
56
49
|
apiKey: string,
|
|
@@ -97,30 +90,7 @@ async function createClient(
|
|
|
97
90
|
betaFeatures.push("interleaved-thinking-2025-05-14");
|
|
98
91
|
}
|
|
99
92
|
|
|
100
|
-
//
|
|
101
|
-
if (isOAuthToken(apiKey)) {
|
|
102
|
-
const client = new AnthropicClass({
|
|
103
|
-
apiKey: null,
|
|
104
|
-
authToken: apiKey,
|
|
105
|
-
baseURL: model.baseUrl,
|
|
106
|
-
dangerouslyAllowBrowser: true,
|
|
107
|
-
defaultHeaders: mergeHeaders(
|
|
108
|
-
{
|
|
109
|
-
accept: "application/json",
|
|
110
|
-
"anthropic-dangerous-direct-browser-access": "true",
|
|
111
|
-
...(betaFeatures.length > 0 ? { "anthropic-beta": `claude-code-20250219,oauth-2025-04-20,${betaFeatures.join(",")}` } : {}),
|
|
112
|
-
"user-agent": `claude-cli/${claudeCodeVersion}`,
|
|
113
|
-
"x-app": "cli",
|
|
114
|
-
},
|
|
115
|
-
model.headers,
|
|
116
|
-
optionsHeaders,
|
|
117
|
-
),
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
return { client, isOAuthToken: true };
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// API key auth
|
|
93
|
+
// API key auth (Anthropic OAuth removed per TOS compliance — use API keys or Claude CLI)
|
|
124
94
|
// Alibaba Coding Plan uses Bearer token auth instead of x-api-key
|
|
125
95
|
const isAlibabaProvider = model.provider === "alibaba-coding-plan";
|
|
126
96
|
const client = new AnthropicClass({
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This module handles login, token refresh, and credential storage
|
|
5
5
|
* for OAuth-based providers:
|
|
6
|
-
* - Anthropic (Claude Pro/Max)
|
|
7
6
|
* - GitHub Copilot
|
|
8
7
|
* - Google Cloud Code Assist (Gemini CLI)
|
|
9
8
|
* - Antigravity (Gemini 3, Claude, GPT-OSS via Google Cloud)
|
|
9
|
+
*
|
|
10
|
+
* Note: Anthropic OAuth was removed per TOS compliance (see docs/user-docs/claude-code-auth-compliance.md).
|
|
11
|
+
* Use API keys or the local Claude Code CLI for Anthropic access.
|
|
10
12
|
*/
|
|
11
13
|
|
|
12
|
-
// Anthropic
|
|
13
|
-
export { anthropicOAuthProvider, loginAnthropic, refreshAnthropicToken } from "./anthropic.js";
|
|
14
14
|
// GitHub Copilot
|
|
15
15
|
export {
|
|
16
16
|
getGitHubCopilotBaseUrl,
|
|
@@ -32,7 +32,6 @@ export * from "./types.js";
|
|
|
32
32
|
// Provider Registry
|
|
33
33
|
// ============================================================================
|
|
34
34
|
|
|
35
|
-
import { anthropicOAuthProvider } from "./anthropic.js";
|
|
36
35
|
import { githubCopilotOAuthProvider } from "./github-copilot.js";
|
|
37
36
|
import { antigravityOAuthProvider } from "./google-antigravity.js";
|
|
38
37
|
import { geminiCliOAuthProvider } from "./google-gemini-cli.js";
|
|
@@ -40,7 +39,6 @@ import { openaiCodexOAuthProvider } from "./openai-codex.js";
|
|
|
40
39
|
import type { OAuthCredentials, OAuthProviderId, OAuthProviderInterface } from "./types.js";
|
|
41
40
|
|
|
42
41
|
const BUILT_IN_OAUTH_PROVIDERS: OAuthProviderInterface[] = [
|
|
43
|
-
anthropicOAuthProvider,
|
|
44
42
|
githubCopilotOAuthProvider,
|
|
45
43
|
geminiCliOAuthProvider,
|
|
46
44
|
antigravityOAuthProvider,
|
package/pkg/package.json
CHANGED
|
@@ -19,6 +19,7 @@ import { deriveState } from "../state.js";
|
|
|
19
19
|
import { formatOverridesSection, formatShortcut, loadActiveOverrides, loadFile, parseContinue, parseSummary } from "../files.js";
|
|
20
20
|
import { toPosixPath } from "../../shared/mod.js";
|
|
21
21
|
import { markCmuxPromptShown, shouldPromptToEnableCmux } from "../../cmux/index.js";
|
|
22
|
+
import { autoEnableCmuxPreferences } from "../commands-cmux.js";
|
|
22
23
|
|
|
23
24
|
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
24
25
|
|
|
@@ -76,13 +77,16 @@ export async function buildBeforeAgentStartResult(
|
|
|
76
77
|
shortcutDashboard: formatShortcut("Ctrl+Alt+G"),
|
|
77
78
|
shortcutShell: formatShortcut("Ctrl+Alt+B"),
|
|
78
79
|
});
|
|
79
|
-
|
|
80
|
+
let loadedPreferences = loadEffectiveGSDPreferences();
|
|
80
81
|
if (shouldPromptToEnableCmux(loadedPreferences?.preferences)) {
|
|
81
82
|
markCmuxPromptShown();
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
if (autoEnableCmuxPreferences()) {
|
|
84
|
+
loadedPreferences = loadEffectiveGSDPreferences();
|
|
85
|
+
ctx.ui.notify(
|
|
86
|
+
"cmux detected — auto-enabled. Run /gsd cmux off to disable.",
|
|
87
|
+
"info",
|
|
88
|
+
);
|
|
89
|
+
}
|
|
86
90
|
}
|
|
87
91
|
|
|
88
92
|
let preferenceBlock = "";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
2
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { clearCmuxSidebar, CmuxClient, detectCmuxEnvironment, resolveCmuxConfig } from "../cmux/index.js";
|
|
4
4
|
import { saveFile } from "./files.js";
|
|
5
5
|
import {
|
|
@@ -9,6 +9,37 @@ import {
|
|
|
9
9
|
} from "./preferences.js";
|
|
10
10
|
import { ensurePreferencesFile, serializePreferencesToFrontmatter } from "./commands-prefs-wizard.js";
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Auto-enable cmux in project preferences when detected but never configured.
|
|
14
|
+
* Called at boot (before agent start) — no ExtensionCommandContext needed.
|
|
15
|
+
* Returns true if preferences were written, false if skipped.
|
|
16
|
+
*/
|
|
17
|
+
export function autoEnableCmuxPreferences(): boolean {
|
|
18
|
+
const path = getProjectGSDPreferencesPath();
|
|
19
|
+
if (!existsSync(path)) return false;
|
|
20
|
+
|
|
21
|
+
const existing = loadProjectGSDPreferences();
|
|
22
|
+
const prefs: Record<string, unknown> = existing?.preferences ? { ...existing.preferences } : { version: 1 };
|
|
23
|
+
prefs.cmux = {
|
|
24
|
+
enabled: true,
|
|
25
|
+
notifications: true,
|
|
26
|
+
sidebar: true,
|
|
27
|
+
splits: false,
|
|
28
|
+
browser: false,
|
|
29
|
+
...((prefs.cmux as Record<string, unknown> | undefined) ?? {}),
|
|
30
|
+
};
|
|
31
|
+
(prefs.cmux as Record<string, unknown>).enabled = true;
|
|
32
|
+
prefs.version = prefs.version || 1;
|
|
33
|
+
|
|
34
|
+
const frontmatter = serializePreferencesToFrontmatter(prefs);
|
|
35
|
+
let body = "\n# GSD Skill Preferences\n\nSee `~/.gsd/agent/extensions/gsd/docs/preferences-reference.md` for full field documentation and examples.\n";
|
|
36
|
+
const preserved = extractBodyAfterFrontmatter(readFileSync(path, "utf-8"));
|
|
37
|
+
if (preserved) body = preserved;
|
|
38
|
+
|
|
39
|
+
writeFileSync(path, `---\n${frontmatter}---${body}`, "utf-8");
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
12
43
|
function extractBodyAfterFrontmatter(content: string): string | null {
|
|
13
44
|
const start = content.startsWith("---\n") ? 4 : content.startsWith("---\r\n") ? 5 : -1;
|
|
14
45
|
if (start === -1) return null;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import test, { describe } from "node:test";
|
|
1
|
+
import test, { describe, beforeEach, afterEach } from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
6
7
|
import {
|
|
7
8
|
buildCmuxProgress,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
resolveCmuxConfig,
|
|
13
14
|
shouldPromptToEnableCmux,
|
|
14
15
|
} from "../../cmux/index.ts";
|
|
16
|
+
import { autoEnableCmuxPreferences } from "../commands-cmux.ts";
|
|
15
17
|
import type { GSDState } from "../types.ts";
|
|
16
18
|
|
|
17
19
|
test("detectCmuxEnvironment requires workspace, surface, and socket", () => {
|
|
@@ -79,6 +81,70 @@ test("shouldPromptToEnableCmux only prompts once per session", () => {
|
|
|
79
81
|
resetCmuxPromptState();
|
|
80
82
|
});
|
|
81
83
|
|
|
84
|
+
describe("autoEnableCmuxPreferences", () => {
|
|
85
|
+
let tmp: string;
|
|
86
|
+
let originalCwd: string;
|
|
87
|
+
|
|
88
|
+
beforeEach(() => {
|
|
89
|
+
originalCwd = process.cwd();
|
|
90
|
+
tmp = fs.mkdtempSync(path.join(tmpdir(), "cmux-auto-test-"));
|
|
91
|
+
fs.mkdirSync(path.join(tmp, ".gsd"), { recursive: true });
|
|
92
|
+
process.chdir(tmp);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
afterEach(() => {
|
|
96
|
+
process.chdir(originalCwd);
|
|
97
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("writes cmux.enabled true when preferences file exists with no cmux config", () => {
|
|
101
|
+
const prefsPath = path.join(tmp, ".gsd", "preferences.md");
|
|
102
|
+
fs.writeFileSync(prefsPath, [
|
|
103
|
+
"---",
|
|
104
|
+
"version: 1",
|
|
105
|
+
"---",
|
|
106
|
+
"",
|
|
107
|
+
"# GSD Skill Preferences",
|
|
108
|
+
].join("\n"));
|
|
109
|
+
|
|
110
|
+
const result = autoEnableCmuxPreferences();
|
|
111
|
+
assert.equal(result, true);
|
|
112
|
+
|
|
113
|
+
const content = fs.readFileSync(prefsPath, "utf-8");
|
|
114
|
+
assert.ok(content.includes("enabled: true"), "should write enabled: true");
|
|
115
|
+
assert.ok(content.includes("notifications: true"), "should default notifications on");
|
|
116
|
+
assert.ok(content.includes("sidebar: true"), "should default sidebar on");
|
|
117
|
+
assert.ok(content.includes("splits: false"), "should default splits off");
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("returns false when preferences file does not exist", () => {
|
|
121
|
+
const result = autoEnableCmuxPreferences();
|
|
122
|
+
assert.equal(result, false);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("preserves existing cmux sub-preferences when auto-enabling", () => {
|
|
126
|
+
const prefsPath = path.join(tmp, ".gsd", "preferences.md");
|
|
127
|
+
fs.writeFileSync(prefsPath, [
|
|
128
|
+
"---",
|
|
129
|
+
"version: 1",
|
|
130
|
+
"cmux:",
|
|
131
|
+
" splits: true",
|
|
132
|
+
" browser: true",
|
|
133
|
+
"---",
|
|
134
|
+
"",
|
|
135
|
+
"# GSD Skill Preferences",
|
|
136
|
+
].join("\n"));
|
|
137
|
+
|
|
138
|
+
const result = autoEnableCmuxPreferences();
|
|
139
|
+
assert.equal(result, true);
|
|
140
|
+
|
|
141
|
+
const content = fs.readFileSync(prefsPath, "utf-8");
|
|
142
|
+
assert.ok(content.includes("enabled: true"), "should set enabled: true");
|
|
143
|
+
assert.ok(content.includes("splits: true"), "should preserve existing splits: true");
|
|
144
|
+
assert.ok(content.includes("browser: true"), "should preserve existing browser: true");
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
82
148
|
test("buildCmuxStatusLabel and progress prefer deepest active unit", () => {
|
|
83
149
|
const state: GSDState = {
|
|
84
150
|
activeMilestone: { id: "M001", title: "Milestone" },
|
|
@@ -26,8 +26,12 @@ test("ensureProjectWorkflowMcpConfig creates .mcp.json with the workflow server"
|
|
|
26
26
|
assert.equal(typeof server?.command, "string");
|
|
27
27
|
assert.equal(Array.isArray(server?.args), true);
|
|
28
28
|
assert.equal(server?.env?.GSD_WORKFLOW_PROJECT_ROOT, projectRoot);
|
|
29
|
-
assert.match(server?.env?.GSD_WORKFLOW_EXECUTORS_MODULE ?? "", /workflow-tool-executors\.js$/);
|
|
30
|
-
assert.match(server?.env?.GSD_WORKFLOW_WRITE_GATE_MODULE ?? "", /write-gate\.js$/);
|
|
29
|
+
assert.match(server?.env?.GSD_WORKFLOW_EXECUTORS_MODULE ?? "", /workflow-tool-executors\.(js|ts)$/);
|
|
30
|
+
assert.match(server?.env?.GSD_WORKFLOW_WRITE_GATE_MODULE ?? "", /write-gate\.(js|ts)$/);
|
|
31
|
+
if ((server?.env?.GSD_WORKFLOW_EXECUTORS_MODULE ?? "").endsWith(".ts")) {
|
|
32
|
+
assert.match(server?.env?.NODE_OPTIONS ?? "", /--experimental-strip-types/);
|
|
33
|
+
assert.match(server?.env?.NODE_OPTIONS ?? "", /resolve-ts\.mjs/);
|
|
34
|
+
}
|
|
31
35
|
} finally {
|
|
32
36
|
rmSync(projectRoot, { recursive: true, force: true });
|
|
33
37
|
}
|
|
@@ -141,7 +141,11 @@ test("detectWorkflowMcpLaunchConfig resolves the bundled server relative to the
|
|
|
141
141
|
assert.match(launch?.env?.GSD_WORKFLOW_EXECUTORS_MODULE ?? "", /workflow-tool-executors\.(js|ts)$/);
|
|
142
142
|
assert.match(launch?.env?.GSD_WORKFLOW_WRITE_GATE_MODULE ?? "", /write-gate\.(js|ts)$/);
|
|
143
143
|
assert.equal(typeof launch?.args?.[0], "string");
|
|
144
|
-
assert.match(launch?.args?.[0] ?? "", /packages[\/\\]mcp-server[\/\\]dist[\/\\]cli\.js$/);
|
|
144
|
+
assert.match(launch?.args?.[0] ?? "", /packages[\/\\]mcp-server[\/\\](dist[\/\\]cli\.js|src[\/\\]cli\.ts)$/);
|
|
145
|
+
if ((launch?.args?.[0] ?? "").endsWith(".ts")) {
|
|
146
|
+
assert.match(launch?.env?.NODE_OPTIONS ?? "", /--experimental-strip-types/);
|
|
147
|
+
assert.match(launch?.env?.NODE_OPTIONS ?? "", /resolve-ts\.mjs/);
|
|
148
|
+
}
|
|
145
149
|
});
|
|
146
150
|
|
|
147
151
|
test("detectWorkflowMcpLaunchConfig resolves the bundled server relative to the package without env hints", () => {
|
|
@@ -154,7 +158,11 @@ test("detectWorkflowMcpLaunchConfig resolves the bundled server relative to the
|
|
|
154
158
|
assert.match(launch?.env?.GSD_WORKFLOW_EXECUTORS_MODULE ?? "", /workflow-tool-executors\.(js|ts)$/);
|
|
155
159
|
assert.match(launch?.env?.GSD_WORKFLOW_WRITE_GATE_MODULE ?? "", /write-gate\.(js|ts)$/);
|
|
156
160
|
assert.equal(typeof launch?.args?.[0], "string");
|
|
157
|
-
assert.match(launch?.args?.[0] ?? "", /packages[\/\\]mcp-server[\/\\]dist[\/\\]cli\.js$/);
|
|
161
|
+
assert.match(launch?.args?.[0] ?? "", /packages[\/\\]mcp-server[\/\\](dist[\/\\]cli\.js|src[\/\\]cli\.ts)$/);
|
|
162
|
+
if ((launch?.args?.[0] ?? "").endsWith(".ts")) {
|
|
163
|
+
assert.match(launch?.env?.NODE_OPTIONS ?? "", /--experimental-strip-types/);
|
|
164
|
+
assert.match(launch?.env?.NODE_OPTIONS ?? "", /resolve-ts\.mjs/);
|
|
165
|
+
}
|
|
158
166
|
});
|
|
159
167
|
|
|
160
168
|
test("workflow MCP launch config reaches mutation tools over stdio", async () => {
|
|
@@ -165,12 +173,16 @@ test("workflow MCP launch config reaches mutation tools over stdio", async () =>
|
|
|
165
173
|
assert.ok(launch, "expected a workflow MCP launch config");
|
|
166
174
|
assert.match(
|
|
167
175
|
launch.env?.GSD_WORKFLOW_EXECUTORS_MODULE ?? "",
|
|
168
|
-
/dist[\/\\]resources[\/\\]extensions[\/\\]gsd[\/\\]tools[\/\\]workflow-tool-executors\.js$/,
|
|
176
|
+
/(dist[\/\\]resources[\/\\]extensions[\/\\]gsd[\/\\]tools[\/\\]workflow-tool-executors\.js|src[\/\\]resources[\/\\]extensions[\/\\]gsd[\/\\]tools[\/\\]workflow-tool-executors\.(js|ts))$/,
|
|
169
177
|
);
|
|
170
178
|
assert.match(
|
|
171
179
|
launch.env?.GSD_WORKFLOW_WRITE_GATE_MODULE ?? "",
|
|
172
|
-
/dist[\/\\]resources[\/\\]extensions[\/\\]gsd[\/\\]bootstrap[\/\\]write-gate\.js$/,
|
|
180
|
+
/(dist[\/\\]resources[\/\\]extensions[\/\\]gsd[\/\\]bootstrap[\/\\]write-gate\.js|src[\/\\]resources[\/\\]extensions[\/\\]gsd[\/\\]bootstrap[\/\\]write-gate\.(js|ts))$/,
|
|
173
181
|
);
|
|
182
|
+
if ((launch.env?.GSD_WORKFLOW_EXECUTORS_MODULE ?? "").endsWith(".ts")) {
|
|
183
|
+
assert.match(launch.env?.NODE_OPTIONS ?? "", /--experimental-strip-types/);
|
|
184
|
+
assert.match(launch.env?.NODE_OPTIONS ?? "", /resolve-ts\.mjs/);
|
|
185
|
+
}
|
|
174
186
|
|
|
175
187
|
const client = new Client({ name: "workflow-mcp-transport-test", version: "1.0.0" });
|
|
176
188
|
const transport = new StdioClientTransport({
|
|
@@ -189,6 +201,10 @@ test("workflow MCP launch config reaches mutation tools over stdio", async () =>
|
|
|
189
201
|
(tools.tools ?? []).some((tool) => tool.name === "gsd_plan_slice"),
|
|
190
202
|
"expected workflow MCP surface to expose gsd_plan_slice",
|
|
191
203
|
);
|
|
204
|
+
assert.ok(
|
|
205
|
+
(tools.tools ?? []).some((tool) => tool.name === "ask_user_questions"),
|
|
206
|
+
"expected workflow MCP surface to expose ask_user_questions",
|
|
207
|
+
);
|
|
192
208
|
|
|
193
209
|
const milestoneResult = await client.callTool(
|
|
194
210
|
{
|
|
@@ -465,18 +481,18 @@ test("transport compatibility now allows replan-slice over workflow MCP surface"
|
|
|
465
481
|
test("transport compatibility still blocks units whose MCP tools are not exposed", () => {
|
|
466
482
|
const error = getWorkflowTransportSupportError(
|
|
467
483
|
"claude-code",
|
|
468
|
-
["
|
|
484
|
+
["secure_env_collect"],
|
|
469
485
|
{
|
|
470
486
|
projectRoot: "/tmp/project",
|
|
471
487
|
env: { GSD_WORKFLOW_MCP_COMMAND: "node" },
|
|
472
488
|
surface: "auto-mode",
|
|
473
|
-
unitType: "
|
|
489
|
+
unitType: "guided-discussion",
|
|
474
490
|
authMode: "externalCli",
|
|
475
491
|
baseUrl: "local://claude-code",
|
|
476
492
|
},
|
|
477
493
|
);
|
|
478
494
|
|
|
479
|
-
assert.match(error ?? "", /requires
|
|
495
|
+
assert.match(error ?? "", /requires secure_env_collect/);
|
|
480
496
|
assert.match(error ?? "", /currently exposes only/);
|
|
481
497
|
});
|
|
482
498
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
|
|
6
6
|
export interface WorkflowMcpLaunchConfig {
|
|
7
7
|
name: string;
|
|
@@ -21,22 +21,35 @@ export interface WorkflowCapabilityOptions {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
const MCP_WORKFLOW_TOOL_SURFACE = new Set([
|
|
24
|
+
"ask_user_questions",
|
|
25
|
+
"gsd_decision_save",
|
|
24
26
|
"gsd_complete_milestone",
|
|
25
27
|
"gsd_complete_task",
|
|
26
28
|
"gsd_complete_slice",
|
|
29
|
+
"gsd_generate_milestone_id",
|
|
30
|
+
"gsd_journal_query",
|
|
27
31
|
"gsd_milestone_complete",
|
|
32
|
+
"gsd_milestone_generate_id",
|
|
28
33
|
"gsd_milestone_status",
|
|
29
34
|
"gsd_milestone_validate",
|
|
35
|
+
"gsd_plan_task",
|
|
30
36
|
"gsd_plan_milestone",
|
|
31
37
|
"gsd_plan_slice",
|
|
32
38
|
"gsd_replan_slice",
|
|
33
39
|
"gsd_reassess_roadmap",
|
|
40
|
+
"gsd_requirement_save",
|
|
41
|
+
"gsd_requirement_update",
|
|
34
42
|
"gsd_roadmap_reassess",
|
|
43
|
+
"gsd_save_decision",
|
|
35
44
|
"gsd_save_gate_result",
|
|
45
|
+
"gsd_save_requirement",
|
|
46
|
+
"gsd_skip_slice",
|
|
36
47
|
"gsd_slice_replan",
|
|
37
48
|
"gsd_slice_complete",
|
|
38
49
|
"gsd_summary_save",
|
|
50
|
+
"gsd_task_plan",
|
|
39
51
|
"gsd_task_complete",
|
|
52
|
+
"gsd_update_requirement",
|
|
40
53
|
"gsd_validate_milestone",
|
|
41
54
|
]);
|
|
42
55
|
|
|
@@ -95,6 +108,8 @@ function getBundledWorkflowMcpCliPath(env: NodeJS.ProcessEnv): string | null {
|
|
|
95
108
|
}
|
|
96
109
|
|
|
97
110
|
const candidates = [
|
|
111
|
+
resolve(fileURLToPath(new URL("../../../../packages/mcp-server/src/cli.ts", import.meta.url))),
|
|
112
|
+
resolve(fileURLToPath(new URL("../../../../../packages/mcp-server/src/cli.ts", import.meta.url))),
|
|
98
113
|
resolve(fileURLToPath(new URL("../../../../packages/mcp-server/dist/cli.js", import.meta.url))),
|
|
99
114
|
resolve(fileURLToPath(new URL("../../../../../packages/mcp-server/dist/cli.js", import.meta.url))),
|
|
100
115
|
];
|
|
@@ -108,9 +123,9 @@ function getBundledWorkflowMcpCliPath(env: NodeJS.ProcessEnv): string | null {
|
|
|
108
123
|
|
|
109
124
|
function getBundledWorkflowExecutorModulePath(): string | null {
|
|
110
125
|
const candidates = [
|
|
111
|
-
resolve(fileURLToPath(new URL("../../../../dist/resources/extensions/gsd/tools/workflow-tool-executors.js", import.meta.url))),
|
|
112
126
|
resolve(fileURLToPath(new URL("./tools/workflow-tool-executors.js", import.meta.url))),
|
|
113
127
|
resolve(fileURLToPath(new URL("./tools/workflow-tool-executors.ts", import.meta.url))),
|
|
128
|
+
resolve(fileURLToPath(new URL("../../../../dist/resources/extensions/gsd/tools/workflow-tool-executors.js", import.meta.url))),
|
|
114
129
|
];
|
|
115
130
|
|
|
116
131
|
for (const candidate of candidates) {
|
|
@@ -122,9 +137,9 @@ function getBundledWorkflowExecutorModulePath(): string | null {
|
|
|
122
137
|
|
|
123
138
|
function getBundledWorkflowWriteGateModulePath(): string | null {
|
|
124
139
|
const candidates = [
|
|
125
|
-
resolve(fileURLToPath(new URL("../../../../dist/resources/extensions/gsd/bootstrap/write-gate.js", import.meta.url))),
|
|
126
140
|
resolve(fileURLToPath(new URL("./bootstrap/write-gate.js", import.meta.url))),
|
|
127
141
|
resolve(fileURLToPath(new URL("./bootstrap/write-gate.ts", import.meta.url))),
|
|
142
|
+
resolve(fileURLToPath(new URL("../../../../dist/resources/extensions/gsd/bootstrap/write-gate.js", import.meta.url))),
|
|
128
143
|
];
|
|
129
144
|
|
|
130
145
|
for (const candidate of candidates) {
|
|
@@ -134,19 +149,58 @@ function getBundledWorkflowWriteGateModulePath(): string | null {
|
|
|
134
149
|
return null;
|
|
135
150
|
}
|
|
136
151
|
|
|
152
|
+
function getResolveTsHookPath(): string | null {
|
|
153
|
+
const candidates = [
|
|
154
|
+
resolve(fileURLToPath(new URL("./tests/resolve-ts.mjs", import.meta.url))),
|
|
155
|
+
resolve(fileURLToPath(new URL("../../../../src/resources/extensions/gsd/tests/resolve-ts.mjs", import.meta.url))),
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
for (const candidate of candidates) {
|
|
159
|
+
if (existsSync(candidate)) return candidate;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function mergeNodeOptions(existing: string | undefined, additions: string[]): string | undefined {
|
|
166
|
+
const tokens = (existing ?? "").split(/\s+/).map((value) => value.trim()).filter(Boolean);
|
|
167
|
+
for (const addition of additions) {
|
|
168
|
+
if (!tokens.includes(addition)) {
|
|
169
|
+
tokens.push(addition);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return tokens.length > 0 ? tokens.join(" ") : undefined;
|
|
173
|
+
}
|
|
174
|
+
|
|
137
175
|
function buildWorkflowLaunchEnv(
|
|
138
176
|
projectRoot: string,
|
|
139
177
|
gsdCliPath: string | undefined,
|
|
140
178
|
explicitEnv?: Record<string, string>,
|
|
179
|
+
workflowCliPath?: string,
|
|
141
180
|
): Record<string, string> {
|
|
142
181
|
const executorModulePath = getBundledWorkflowExecutorModulePath();
|
|
143
182
|
const writeGateModulePath = getBundledWorkflowWriteGateModulePath();
|
|
183
|
+
const resolveTsHookPath = getResolveTsHookPath();
|
|
184
|
+
const wantsSourceTs =
|
|
185
|
+
Boolean(resolveTsHookPath) &&
|
|
186
|
+
(
|
|
187
|
+
(workflowCliPath?.endsWith(".ts") ?? false) ||
|
|
188
|
+
(executorModulePath?.endsWith(".ts") ?? false) ||
|
|
189
|
+
(writeGateModulePath?.endsWith(".ts") ?? false)
|
|
190
|
+
);
|
|
191
|
+
const nodeOptions = wantsSourceTs
|
|
192
|
+
? mergeNodeOptions(explicitEnv?.NODE_OPTIONS, [
|
|
193
|
+
"--experimental-strip-types",
|
|
194
|
+
`--import=${pathToFileURL(resolveTsHookPath!).href}`,
|
|
195
|
+
])
|
|
196
|
+
: explicitEnv?.NODE_OPTIONS;
|
|
144
197
|
|
|
145
198
|
return {
|
|
146
199
|
...(explicitEnv ?? {}),
|
|
147
200
|
...(gsdCliPath ? { GSD_CLI_PATH: gsdCliPath } : {}),
|
|
148
201
|
...(executorModulePath ? { GSD_WORKFLOW_EXECUTORS_MODULE: executorModulePath } : {}),
|
|
149
202
|
...(writeGateModulePath ? { GSD_WORKFLOW_WRITE_GATE_MODULE: writeGateModulePath } : {}),
|
|
203
|
+
...(nodeOptions ? { NODE_OPTIONS: nodeOptions } : {}),
|
|
150
204
|
GSD_PERSIST_WRITE_GATE_STATE: "1",
|
|
151
205
|
GSD_WORKFLOW_PROJECT_ROOT: projectRoot,
|
|
152
206
|
};
|
|
@@ -188,7 +242,7 @@ export function detectWorkflowMcpLaunchConfig(
|
|
|
188
242
|
command: process.execPath,
|
|
189
243
|
args: [distCli],
|
|
190
244
|
cwd: resolvedWorkflowProjectRoot,
|
|
191
|
-
env: buildWorkflowLaunchEnv(resolvedWorkflowProjectRoot, gsdCliPath),
|
|
245
|
+
env: buildWorkflowLaunchEnv(resolvedWorkflowProjectRoot, gsdCliPath, undefined, distCli),
|
|
192
246
|
};
|
|
193
247
|
}
|
|
194
248
|
|
|
@@ -199,7 +253,7 @@ export function detectWorkflowMcpLaunchConfig(
|
|
|
199
253
|
command: process.execPath,
|
|
200
254
|
args: [bundledCli],
|
|
201
255
|
cwd: resolvedWorkflowProjectRoot,
|
|
202
|
-
env: buildWorkflowLaunchEnv(resolvedWorkflowProjectRoot, gsdCliPath),
|
|
256
|
+
env: buildWorkflowLaunchEnv(resolvedWorkflowProjectRoot, gsdCliPath, undefined, bundledCli),
|
|
203
257
|
};
|
|
204
258
|
}
|
|
205
259
|
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Anthropic OAuth flow (Claude Pro/Max)
|
|
3
|
-
*/
|
|
4
|
-
import type { OAuthCredentials, OAuthProviderInterface } from "./types.js";
|
|
5
|
-
/**
|
|
6
|
-
* Login with Anthropic OAuth (device code flow)
|
|
7
|
-
*
|
|
8
|
-
* @param onAuthUrl - Callback to handle the authorization URL (e.g., open browser)
|
|
9
|
-
* @param onPromptCode - Callback to prompt user for the authorization code
|
|
10
|
-
*/
|
|
11
|
-
export declare function loginAnthropic(onAuthUrl: (url: string) => void, onPromptCode: () => Promise<string>): Promise<OAuthCredentials>;
|
|
12
|
-
/**
|
|
13
|
-
* Refresh Anthropic OAuth token
|
|
14
|
-
*/
|
|
15
|
-
export declare function refreshAnthropicToken(refreshToken: string): Promise<OAuthCredentials>;
|
|
16
|
-
export declare const anthropicOAuthProvider: OAuthProviderInterface;
|
|
17
|
-
//# sourceMappingURL=anthropic.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/anthropic.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAuB,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAShG;;;;;GAKG;AACH,wBAAsB,cAAc,CACnC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,EAChC,YAAY,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GACjC,OAAO,CAAC,gBAAgB,CAAC,CA+D3B;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA4B3F;AAED,eAAO,MAAM,sBAAsB,EAAE,sBAkBpC,CAAC"}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Anthropic OAuth flow (Claude Pro/Max)
|
|
3
|
-
*/
|
|
4
|
-
import { generatePKCE } from "./pkce.js";
|
|
5
|
-
const decode = (s) => atob(s);
|
|
6
|
-
const CLIENT_ID = decode("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl");
|
|
7
|
-
const AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
|
|
8
|
-
const TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
|
|
9
|
-
const REDIRECT_URI = "https://platform.claude.com/oauth/code/callback";
|
|
10
|
-
const SCOPES = "org:create_api_key user:profile user:inference";
|
|
11
|
-
/**
|
|
12
|
-
* Login with Anthropic OAuth (device code flow)
|
|
13
|
-
*
|
|
14
|
-
* @param onAuthUrl - Callback to handle the authorization URL (e.g., open browser)
|
|
15
|
-
* @param onPromptCode - Callback to prompt user for the authorization code
|
|
16
|
-
*/
|
|
17
|
-
export async function loginAnthropic(onAuthUrl, onPromptCode) {
|
|
18
|
-
const { verifier, challenge } = await generatePKCE();
|
|
19
|
-
// Build authorization URL
|
|
20
|
-
const authParams = new URLSearchParams({
|
|
21
|
-
code: "true",
|
|
22
|
-
client_id: CLIENT_ID,
|
|
23
|
-
response_type: "code",
|
|
24
|
-
redirect_uri: REDIRECT_URI,
|
|
25
|
-
scope: SCOPES,
|
|
26
|
-
code_challenge: challenge,
|
|
27
|
-
code_challenge_method: "S256",
|
|
28
|
-
state: verifier,
|
|
29
|
-
});
|
|
30
|
-
const authUrl = `${AUTHORIZE_URL}?${authParams.toString()}`;
|
|
31
|
-
// Notify caller with URL to open
|
|
32
|
-
onAuthUrl(authUrl);
|
|
33
|
-
// Wait for user to paste authorization code (format: code#state)
|
|
34
|
-
const authCode = await onPromptCode();
|
|
35
|
-
const splits = authCode.split("#");
|
|
36
|
-
const code = splits[0];
|
|
37
|
-
const state = splits[1];
|
|
38
|
-
// Exchange code for tokens
|
|
39
|
-
const tokenResponse = await fetch(TOKEN_URL, {
|
|
40
|
-
method: "POST",
|
|
41
|
-
headers: {
|
|
42
|
-
"Content-Type": "application/json",
|
|
43
|
-
},
|
|
44
|
-
body: JSON.stringify({
|
|
45
|
-
grant_type: "authorization_code",
|
|
46
|
-
client_id: CLIENT_ID,
|
|
47
|
-
code: code,
|
|
48
|
-
state: state,
|
|
49
|
-
redirect_uri: REDIRECT_URI,
|
|
50
|
-
code_verifier: verifier,
|
|
51
|
-
}),
|
|
52
|
-
signal: AbortSignal.timeout(30_000),
|
|
53
|
-
});
|
|
54
|
-
if (!tokenResponse.ok) {
|
|
55
|
-
const error = await tokenResponse.text();
|
|
56
|
-
throw new Error(`Token exchange failed: ${error}`);
|
|
57
|
-
}
|
|
58
|
-
const tokenData = (await tokenResponse.json());
|
|
59
|
-
// Calculate expiry time (current time + expires_in seconds - 5 min buffer)
|
|
60
|
-
const expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;
|
|
61
|
-
// Save credentials
|
|
62
|
-
return {
|
|
63
|
-
refresh: tokenData.refresh_token,
|
|
64
|
-
access: tokenData.access_token,
|
|
65
|
-
expires: expiresAt,
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Refresh Anthropic OAuth token
|
|
70
|
-
*/
|
|
71
|
-
export async function refreshAnthropicToken(refreshToken) {
|
|
72
|
-
const response = await fetch(TOKEN_URL, {
|
|
73
|
-
method: "POST",
|
|
74
|
-
headers: { "Content-Type": "application/json" },
|
|
75
|
-
body: JSON.stringify({
|
|
76
|
-
grant_type: "refresh_token",
|
|
77
|
-
client_id: CLIENT_ID,
|
|
78
|
-
refresh_token: refreshToken,
|
|
79
|
-
}),
|
|
80
|
-
signal: AbortSignal.timeout(30_000),
|
|
81
|
-
});
|
|
82
|
-
if (!response.ok) {
|
|
83
|
-
const error = await response.text();
|
|
84
|
-
throw new Error(`Anthropic token refresh failed: ${error}`);
|
|
85
|
-
}
|
|
86
|
-
const data = (await response.json());
|
|
87
|
-
return {
|
|
88
|
-
refresh: data.refresh_token,
|
|
89
|
-
access: data.access_token,
|
|
90
|
-
expires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
export const anthropicOAuthProvider = {
|
|
94
|
-
id: "anthropic",
|
|
95
|
-
name: "Anthropic (Claude Pro/Max)",
|
|
96
|
-
async login(callbacks) {
|
|
97
|
-
return loginAnthropic((url) => callbacks.onAuth({ url }), () => callbacks.onPrompt({ message: "Paste the authorization code:" }));
|
|
98
|
-
},
|
|
99
|
-
async refreshToken(credentials) {
|
|
100
|
-
return refreshAnthropicToken(credentials.refresh);
|
|
101
|
-
},
|
|
102
|
-
getApiKey(credentials) {
|
|
103
|
-
return credentials.access;
|
|
104
|
-
},
|
|
105
|
-
};
|
|
106
|
-
//# sourceMappingURL=anthropic.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../../src/utils/oauth/anthropic.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Anthropic OAuth flow (Claude Pro/Max)\n */\n\nimport { generatePKCE } from \"./pkce.js\";\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from \"./types.js\";\n\nconst decode = (s: string) => atob(s);\nconst CLIENT_ID = decode(\"OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl\");\nconst AUTHORIZE_URL = \"https://claude.ai/oauth/authorize\";\nconst TOKEN_URL = \"https://platform.claude.com/v1/oauth/token\";\nconst REDIRECT_URI = \"https://platform.claude.com/oauth/code/callback\";\nconst SCOPES = \"org:create_api_key user:profile user:inference\";\n\n/**\n * Login with Anthropic OAuth (device code flow)\n *\n * @param onAuthUrl - Callback to handle the authorization URL (e.g., open browser)\n * @param onPromptCode - Callback to prompt user for the authorization code\n */\nexport async function loginAnthropic(\n\tonAuthUrl: (url: string) => void,\n\tonPromptCode: () => Promise<string>,\n): Promise<OAuthCredentials> {\n\tconst { verifier, challenge } = await generatePKCE();\n\n\t// Build authorization URL\n\tconst authParams = new URLSearchParams({\n\t\tcode: \"true\",\n\t\tclient_id: CLIENT_ID,\n\t\tresponse_type: \"code\",\n\t\tredirect_uri: REDIRECT_URI,\n\t\tscope: SCOPES,\n\t\tcode_challenge: challenge,\n\t\tcode_challenge_method: \"S256\",\n\t\tstate: verifier,\n\t});\n\n\tconst authUrl = `${AUTHORIZE_URL}?${authParams.toString()}`;\n\n\t// Notify caller with URL to open\n\tonAuthUrl(authUrl);\n\n\t// Wait for user to paste authorization code (format: code#state)\n\tconst authCode = await onPromptCode();\n\tconst splits = authCode.split(\"#\");\n\tconst code = splits[0];\n\tconst state = splits[1];\n\n\t// Exchange code for tokens\n\tconst tokenResponse = await fetch(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t},\n\t\tbody: JSON.stringify({\n\t\t\tgrant_type: \"authorization_code\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tcode: code,\n\t\t\tstate: state,\n\t\t\tredirect_uri: REDIRECT_URI,\n\t\t\tcode_verifier: verifier,\n\t\t}),\n\t\tsignal: AbortSignal.timeout(30_000),\n\t});\n\n\tif (!tokenResponse.ok) {\n\t\tconst error = await tokenResponse.text();\n\t\tthrow new Error(`Token exchange failed: ${error}`);\n\t}\n\n\tconst tokenData = (await tokenResponse.json()) as {\n\t\taccess_token: string;\n\t\trefresh_token: string;\n\t\texpires_in: number;\n\t};\n\n\t// Calculate expiry time (current time + expires_in seconds - 5 min buffer)\n\tconst expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000;\n\n\t// Save credentials\n\treturn {\n\t\trefresh: tokenData.refresh_token,\n\t\taccess: tokenData.access_token,\n\t\texpires: expiresAt,\n\t};\n}\n\n/**\n * Refresh Anthropic OAuth token\n */\nexport async function refreshAnthropicToken(refreshToken: string): Promise<OAuthCredentials> {\n\tconst response = await fetch(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\tbody: JSON.stringify({\n\t\t\tgrant_type: \"refresh_token\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\trefresh_token: refreshToken,\n\t\t}),\n\t\tsignal: AbortSignal.timeout(30_000),\n\t});\n\n\tif (!response.ok) {\n\t\tconst error = await response.text();\n\t\tthrow new Error(`Anthropic token refresh failed: ${error}`);\n\t}\n\n\tconst data = (await response.json()) as {\n\t\taccess_token: string;\n\t\trefresh_token: string;\n\t\texpires_in: number;\n\t};\n\n\treturn {\n\t\trefresh: data.refresh_token,\n\t\taccess: data.access_token,\n\t\texpires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,\n\t};\n}\n\nexport const anthropicOAuthProvider: OAuthProviderInterface = {\n\tid: \"anthropic\",\n\tname: \"Anthropic (Claude Pro/Max)\",\n\n\tasync login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\t\treturn loginAnthropic(\n\t\t\t(url) => callbacks.onAuth({ url }),\n\t\t\t() => callbacks.onPrompt({ message: \"Paste the authorization code:\" }),\n\t\t);\n\t},\n\n\tasync refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n\t\treturn refreshAnthropicToken(credentials.refresh);\n\t},\n\n\tgetApiKey(credentials: OAuthCredentials): string {\n\t\treturn credentials.access;\n\t},\n};\n"]}
|