edge-pi-cli 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/dist/auth/anthropic-oauth.d.ts +10 -0
  2. package/dist/auth/anthropic-oauth.d.ts.map +1 -0
  3. package/dist/auth/anthropic-oauth.js +97 -0
  4. package/dist/auth/anthropic-oauth.js.map +1 -0
  5. package/dist/auth/auth-storage.d.ts +46 -0
  6. package/dist/auth/auth-storage.d.ts.map +1 -0
  7. package/dist/auth/auth-storage.js +213 -0
  8. package/dist/auth/auth-storage.js.map +1 -0
  9. package/dist/auth/github-copilot-oauth.d.ts +8 -0
  10. package/dist/auth/github-copilot-oauth.d.ts.map +1 -0
  11. package/dist/auth/github-copilot-oauth.js +131 -0
  12. package/dist/auth/github-copilot-oauth.js.map +1 -0
  13. package/dist/auth/index.d.ts +6 -0
  14. package/dist/auth/index.d.ts.map +1 -0
  15. package/dist/auth/index.js +5 -0
  16. package/dist/auth/index.js.map +1 -0
  17. package/dist/auth/openai-codex-oauth.d.ts +8 -0
  18. package/dist/auth/openai-codex-oauth.d.ts.map +1 -0
  19. package/dist/auth/openai-codex-oauth.js +131 -0
  20. package/dist/auth/openai-codex-oauth.js.map +1 -0
  21. package/dist/auth/types.d.ts +41 -0
  22. package/dist/auth/types.d.ts.map +1 -0
  23. package/dist/auth/types.js +5 -0
  24. package/dist/auth/types.js.map +1 -0
  25. package/dist/cli/args.d.ts +35 -0
  26. package/dist/cli/args.d.ts.map +1 -0
  27. package/dist/cli/args.js +191 -0
  28. package/dist/cli/args.js.map +1 -0
  29. package/dist/cli.d.ts +3 -0
  30. package/dist/cli.d.ts.map +1 -0
  31. package/dist/cli.js +8 -0
  32. package/dist/cli.js.map +1 -0
  33. package/dist/context.d.ts +16 -0
  34. package/dist/context.d.ts.map +1 -0
  35. package/dist/context.js +38 -0
  36. package/dist/context.js.map +1 -0
  37. package/dist/main.d.ts +8 -0
  38. package/dist/main.d.ts.map +1 -0
  39. package/dist/main.js +313 -0
  40. package/dist/main.js.map +1 -0
  41. package/dist/model-factory.d.ts +45 -0
  42. package/dist/model-factory.d.ts.map +1 -0
  43. package/dist/model-factory.js +175 -0
  44. package/dist/model-factory.js.map +1 -0
  45. package/dist/modes/interactive/bash-helpers.d.ts +31 -0
  46. package/dist/modes/interactive/bash-helpers.d.ts.map +1 -0
  47. package/dist/modes/interactive/bash-helpers.js +68 -0
  48. package/dist/modes/interactive/bash-helpers.js.map +1 -0
  49. package/dist/modes/interactive/components/assistant-message.d.ts +19 -0
  50. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -0
  51. package/dist/modes/interactive/components/assistant-message.js +54 -0
  52. package/dist/modes/interactive/components/assistant-message.js.map +1 -0
  53. package/dist/modes/interactive/components/bash-execution.d.ts +18 -0
  54. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -0
  55. package/dist/modes/interactive/components/bash-execution.js +77 -0
  56. package/dist/modes/interactive/components/bash-execution.js.map +1 -0
  57. package/dist/modes/interactive/components/compaction-summary.d.ts +18 -0
  58. package/dist/modes/interactive/components/compaction-summary.d.ts.map +1 -0
  59. package/dist/modes/interactive/components/compaction-summary.js +45 -0
  60. package/dist/modes/interactive/components/compaction-summary.js.map +1 -0
  61. package/dist/modes/interactive/components/footer.d.ts +20 -0
  62. package/dist/modes/interactive/components/footer.d.ts.map +1 -0
  63. package/dist/modes/interactive/components/footer.js +82 -0
  64. package/dist/modes/interactive/components/footer.js.map +1 -0
  65. package/dist/modes/interactive/components/tool-execution.d.ts +30 -0
  66. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -0
  67. package/dist/modes/interactive/components/tool-execution.js +133 -0
  68. package/dist/modes/interactive/components/tool-execution.js.map +1 -0
  69. package/dist/modes/interactive/components/user-message.d.ts +9 -0
  70. package/dist/modes/interactive/components/user-message.d.ts.map +1 -0
  71. package/dist/modes/interactive/components/user-message.js +17 -0
  72. package/dist/modes/interactive/components/user-message.js.map +1 -0
  73. package/dist/modes/interactive/interactive-mode.d.ts +49 -0
  74. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -0
  75. package/dist/modes/interactive/interactive-mode.js +1397 -0
  76. package/dist/modes/interactive/interactive-mode.js.map +1 -0
  77. package/dist/modes/interactive/theme.d.ts +26 -0
  78. package/dist/modes/interactive/theme.d.ts.map +1 -0
  79. package/dist/modes/interactive/theme.js +64 -0
  80. package/dist/modes/interactive/theme.js.map +1 -0
  81. package/dist/modes/interactive-mode.d.ts +5 -0
  82. package/dist/modes/interactive-mode.d.ts.map +1 -0
  83. package/dist/modes/interactive-mode.js +5 -0
  84. package/dist/modes/interactive-mode.js.map +1 -0
  85. package/dist/modes/print-mode.d.ts +20 -0
  86. package/dist/modes/print-mode.d.ts.map +1 -0
  87. package/dist/modes/print-mode.js +56 -0
  88. package/dist/modes/print-mode.js.map +1 -0
  89. package/dist/prompts.d.ts +53 -0
  90. package/dist/prompts.d.ts.map +1 -0
  91. package/dist/prompts.js +132 -0
  92. package/dist/prompts.js.map +1 -0
  93. package/dist/settings.d.ts +34 -0
  94. package/dist/settings.d.ts.map +1 -0
  95. package/dist/settings.js +73 -0
  96. package/dist/settings.js.map +1 -0
  97. package/dist/skills.d.ts +51 -0
  98. package/dist/skills.d.ts.map +1 -0
  99. package/dist/skills.js +304 -0
  100. package/dist/skills.js.map +1 -0
  101. package/dist/utils/bash-executor.d.ts +32 -0
  102. package/dist/utils/bash-executor.d.ts.map +1 -0
  103. package/dist/utils/bash-executor.js +166 -0
  104. package/dist/utils/bash-executor.js.map +1 -0
  105. package/dist/utils/clipboard-image.d.ts +24 -0
  106. package/dist/utils/clipboard-image.d.ts.map +1 -0
  107. package/dist/utils/clipboard-image.js +211 -0
  108. package/dist/utils/clipboard-image.js.map +1 -0
  109. package/dist/utils/find-fd.d.ts +12 -0
  110. package/dist/utils/find-fd.d.ts.map +1 -0
  111. package/dist/utils/find-fd.js +33 -0
  112. package/dist/utils/find-fd.js.map +1 -0
  113. package/dist/utils/frontmatter.d.ts +7 -0
  114. package/dist/utils/frontmatter.d.ts.map +1 -0
  115. package/dist/utils/frontmatter.js +25 -0
  116. package/dist/utils/frontmatter.js.map +1 -0
  117. package/package.json +39 -0
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Anthropic OAuth provider implementation.
3
+ * Uses OAuth 2.0 with PKCE for authentication.
4
+ */
5
+ import type { OAuthProviderInterface } from "./types.js";
6
+ /** Check if an API key is an Anthropic OAuth token. */
7
+ export declare function isAnthropicOAuthToken(apiKey: string): boolean;
8
+ /** The built-in Anthropic OAuth provider. */
9
+ export declare const anthropicOAuthProvider: OAuthProviderInterface;
10
+ //# sourceMappingURL=anthropic-oauth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic-oauth.d.ts","sourceRoot":"","sources":["../../src/auth/anthropic-oauth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAyC,sBAAsB,EAAE,MAAM,YAAY,CAAC;AA2GhG,uDAAuD;AACvD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAE7D;AAED,6CAA6C;AAC7C,eAAO,MAAM,sBAAsB,EAAE,sBAMpC,CAAC","sourcesContent":["/**\n * Anthropic OAuth provider implementation.\n * Uses OAuth 2.0 with PKCE for authentication.\n */\n\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://console.anthropic.com/v1/oauth/token\";\nconst REDIRECT_URI = \"https://console.anthropic.com/oauth/code/callback\";\nconst SCOPES = \"org:create_api_key user:profile user:inference\";\n\nasync function generatePKCE(): Promise<{ verifier: string; challenge: string }> {\n\tconst array = new Uint8Array(32);\n\tcrypto.getRandomValues(array);\n\tconst verifier = btoa(String.fromCharCode(...array))\n\t\t.replace(/\\+/g, \"-\")\n\t\t.replace(/\\//g, \"_\")\n\t\t.replace(/=+$/, \"\");\n\n\tconst encoder = new TextEncoder();\n\tconst data = encoder.encode(verifier);\n\tconst hash = await crypto.subtle.digest(\"SHA-256\", data);\n\tconst challenge = btoa(String.fromCharCode(...new Uint8Array(hash)))\n\t\t.replace(/\\+/g, \"-\")\n\t\t.replace(/\\//g, \"_\")\n\t\t.replace(/=+$/, \"\");\n\n\treturn { verifier, challenge };\n}\n\nasync function loginAnthropic(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\tconst { verifier, challenge } = await generatePKCE();\n\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\tcallbacks.onAuth({ url: `${AUTHORIZE_URL}?${authParams.toString()}` });\n\n\tconst authCode = await callbacks.onPrompt({ message: \"Paste the authorization code:\" });\n\tconst [code, state] = authCode.split(\"#\");\n\n\tcallbacks.onProgress?.(\"Exchanging code for token...\");\n\n\tconst tokenResponse = 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: \"authorization_code\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tcode,\n\t\t\tstate,\n\t\t\tredirect_uri: REDIRECT_URI,\n\t\t\tcode_verifier: verifier,\n\t\t}),\n\t});\n\n\tif (!tokenResponse.ok) {\n\t\tthrow new Error(`Token exchange failed: ${await tokenResponse.text()}`);\n\t}\n\n\tconst data = (await tokenResponse.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\nasync function refreshAnthropicToken(credentials: OAuthCredentials): 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: credentials.refresh,\n\t\t}),\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Token refresh failed: ${await response.text()}`);\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\n/** Check if an API key is an Anthropic OAuth token. */\nexport function isAnthropicOAuthToken(apiKey: string): boolean {\n\treturn apiKey.includes(\"sk-ant-oat\");\n}\n\n/** The built-in Anthropic OAuth provider. */\nexport const anthropicOAuthProvider: OAuthProviderInterface = {\n\tid: \"anthropic\",\n\tname: \"Anthropic (Claude Pro/Max)\",\n\tlogin: loginAnthropic,\n\trefreshToken: refreshAnthropicToken,\n\tgetApiKey: (cred) => cred.access,\n};\n"]}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Anthropic OAuth provider implementation.
3
+ * Uses OAuth 2.0 with PKCE for authentication.
4
+ */
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://console.anthropic.com/v1/oauth/token";
9
+ const REDIRECT_URI = "https://console.anthropic.com/oauth/code/callback";
10
+ const SCOPES = "org:create_api_key user:profile user:inference";
11
+ async function generatePKCE() {
12
+ const array = new Uint8Array(32);
13
+ crypto.getRandomValues(array);
14
+ const verifier = btoa(String.fromCharCode(...array))
15
+ .replace(/\+/g, "-")
16
+ .replace(/\//g, "_")
17
+ .replace(/=+$/, "");
18
+ const encoder = new TextEncoder();
19
+ const data = encoder.encode(verifier);
20
+ const hash = await crypto.subtle.digest("SHA-256", data);
21
+ const challenge = btoa(String.fromCharCode(...new Uint8Array(hash)))
22
+ .replace(/\+/g, "-")
23
+ .replace(/\//g, "_")
24
+ .replace(/=+$/, "");
25
+ return { verifier, challenge };
26
+ }
27
+ async function loginAnthropic(callbacks) {
28
+ const { verifier, challenge } = await generatePKCE();
29
+ const authParams = new URLSearchParams({
30
+ code: "true",
31
+ client_id: CLIENT_ID,
32
+ response_type: "code",
33
+ redirect_uri: REDIRECT_URI,
34
+ scope: SCOPES,
35
+ code_challenge: challenge,
36
+ code_challenge_method: "S256",
37
+ state: verifier,
38
+ });
39
+ callbacks.onAuth({ url: `${AUTHORIZE_URL}?${authParams.toString()}` });
40
+ const authCode = await callbacks.onPrompt({ message: "Paste the authorization code:" });
41
+ const [code, state] = authCode.split("#");
42
+ callbacks.onProgress?.("Exchanging code for token...");
43
+ const tokenResponse = await fetch(TOKEN_URL, {
44
+ method: "POST",
45
+ headers: { "Content-Type": "application/json" },
46
+ body: JSON.stringify({
47
+ grant_type: "authorization_code",
48
+ client_id: CLIENT_ID,
49
+ code,
50
+ state,
51
+ redirect_uri: REDIRECT_URI,
52
+ code_verifier: verifier,
53
+ }),
54
+ });
55
+ if (!tokenResponse.ok) {
56
+ throw new Error(`Token exchange failed: ${await tokenResponse.text()}`);
57
+ }
58
+ const data = (await tokenResponse.json());
59
+ return {
60
+ refresh: data.refresh_token,
61
+ access: data.access_token,
62
+ expires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,
63
+ };
64
+ }
65
+ async function refreshAnthropicToken(credentials) {
66
+ const response = await fetch(TOKEN_URL, {
67
+ method: "POST",
68
+ headers: { "Content-Type": "application/json" },
69
+ body: JSON.stringify({
70
+ grant_type: "refresh_token",
71
+ client_id: CLIENT_ID,
72
+ refresh_token: credentials.refresh,
73
+ }),
74
+ });
75
+ if (!response.ok) {
76
+ throw new Error(`Token refresh failed: ${await response.text()}`);
77
+ }
78
+ const data = (await response.json());
79
+ return {
80
+ refresh: data.refresh_token,
81
+ access: data.access_token,
82
+ expires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,
83
+ };
84
+ }
85
+ /** Check if an API key is an Anthropic OAuth token. */
86
+ export function isAnthropicOAuthToken(apiKey) {
87
+ return apiKey.includes("sk-ant-oat");
88
+ }
89
+ /** The built-in Anthropic OAuth provider. */
90
+ export const anthropicOAuthProvider = {
91
+ id: "anthropic",
92
+ name: "Anthropic (Claude Pro/Max)",
93
+ login: loginAnthropic,
94
+ refreshToken: refreshAnthropicToken,
95
+ getApiKey: (cred) => cred.access,
96
+ };
97
+ //# sourceMappingURL=anthropic-oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic-oauth.js","sourceRoot":"","sources":["../../src/auth/anthropic-oauth.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Anthropic OAuth provider implementation.\n * Uses OAuth 2.0 with PKCE for authentication.\n */\n\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://console.anthropic.com/v1/oauth/token\";\nconst REDIRECT_URI = \"https://console.anthropic.com/oauth/code/callback\";\nconst SCOPES = \"org:create_api_key user:profile user:inference\";\n\nasync function generatePKCE(): Promise<{ verifier: string; challenge: string }> {\n\tconst array = new Uint8Array(32);\n\tcrypto.getRandomValues(array);\n\tconst verifier = btoa(String.fromCharCode(...array))\n\t\t.replace(/\\+/g, \"-\")\n\t\t.replace(/\\//g, \"_\")\n\t\t.replace(/=+$/, \"\");\n\n\tconst encoder = new TextEncoder();\n\tconst data = encoder.encode(verifier);\n\tconst hash = await crypto.subtle.digest(\"SHA-256\", data);\n\tconst challenge = btoa(String.fromCharCode(...new Uint8Array(hash)))\n\t\t.replace(/\\+/g, \"-\")\n\t\t.replace(/\\//g, \"_\")\n\t\t.replace(/=+$/, \"\");\n\n\treturn { verifier, challenge };\n}\n\nasync function loginAnthropic(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\tconst { verifier, challenge } = await generatePKCE();\n\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\tcallbacks.onAuth({ url: `${AUTHORIZE_URL}?${authParams.toString()}` });\n\n\tconst authCode = await callbacks.onPrompt({ message: \"Paste the authorization code:\" });\n\tconst [code, state] = authCode.split(\"#\");\n\n\tcallbacks.onProgress?.(\"Exchanging code for token...\");\n\n\tconst tokenResponse = 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: \"authorization_code\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tcode,\n\t\t\tstate,\n\t\t\tredirect_uri: REDIRECT_URI,\n\t\t\tcode_verifier: verifier,\n\t\t}),\n\t});\n\n\tif (!tokenResponse.ok) {\n\t\tthrow new Error(`Token exchange failed: ${await tokenResponse.text()}`);\n\t}\n\n\tconst data = (await tokenResponse.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\nasync function refreshAnthropicToken(credentials: OAuthCredentials): 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: credentials.refresh,\n\t\t}),\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Token refresh failed: ${await response.text()}`);\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\n/** Check if an API key is an Anthropic OAuth token. */\nexport function isAnthropicOAuthToken(apiKey: string): boolean {\n\treturn apiKey.includes(\"sk-ant-oat\");\n}\n\n/** The built-in Anthropic OAuth provider. */\nexport const anthropicOAuthProvider: OAuthProviderInterface = {\n\tid: \"anthropic\",\n\tname: \"Anthropic (Claude Pro/Max)\",\n\tlogin: loginAnthropic,\n\trefreshToken: refreshAnthropicToken,\n\tgetApiKey: (cred) => cred.access,\n};\n"]}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Credential storage backed by auth.json.
3
+ * Handles API keys and OAuth tokens with file-locked token refresh.
4
+ */
5
+ import type { AuthCredential, OAuthLoginCallbacks, OAuthProviderInterface } from "./types.js";
6
+ export declare class AuthStorage {
7
+ private authPath;
8
+ private data;
9
+ private runtimeOverrides;
10
+ private providers;
11
+ constructor(authPath: string);
12
+ /** Register an OAuth provider. */
13
+ registerProvider(provider: OAuthProviderInterface): void;
14
+ /** Get a registered OAuth provider. */
15
+ getProvider(id: string): OAuthProviderInterface | undefined;
16
+ /** Get all registered OAuth providers. */
17
+ getProviders(): OAuthProviderInterface[];
18
+ /** Set a runtime API key override (not persisted). Used for --api-key flag. */
19
+ setRuntimeApiKey(provider: string, apiKey: string): void;
20
+ /** Reload credentials from disk. */
21
+ reload(): void;
22
+ private save;
23
+ /** Get credential for a provider. */
24
+ get(provider: string): AuthCredential | undefined;
25
+ /** Set credential for a provider. */
26
+ set(provider: string, credential: AuthCredential): void;
27
+ /** Remove credential for a provider. */
28
+ remove(provider: string): void;
29
+ /** List all providers with stored credentials. */
30
+ list(): string[];
31
+ /** Check if a provider has stored credentials. */
32
+ has(provider: string): boolean;
33
+ /** Check if any auth is available for a provider. */
34
+ hasAuth(provider: string): boolean;
35
+ /** Login to an OAuth provider. */
36
+ login(providerId: string, callbacks: OAuthLoginCallbacks): Promise<void>;
37
+ /** Logout from a provider. */
38
+ logout(provider: string): void;
39
+ private refreshOAuthTokenWithLock;
40
+ /**
41
+ * Get API key for a provider.
42
+ * Priority: runtime override → auth.json API key → OAuth (auto-refresh) → env var
43
+ */
44
+ getApiKey(providerId: string): Promise<string | undefined>;
45
+ }
46
+ //# sourceMappingURL=auth-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-storage.d.ts","sourceRoot":"","sources":["../../src/auth/auth-storage.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EACX,cAAc,EAGd,mBAAmB,EACnB,sBAAsB,EACtB,MAAM,YAAY,CAAC;AAqBpB,qBAAa,WAAW;IAKX,OAAO,CAAC,QAAQ;IAJ5B,OAAO,CAAC,IAAI,CAAuB;IACnC,OAAO,CAAC,gBAAgB,CAAkC;IAC1D,OAAO,CAAC,SAAS,CAAkD;IAEnE,YAAoB,QAAQ,EAAE,MAAM,EAEnC;IAED,kCAAkC;IAClC,gBAAgB,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI,CAEvD;IAED,uCAAuC;IACvC,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,sBAAsB,GAAG,SAAS,CAE1D;IAED,0CAA0C;IAC1C,YAAY,IAAI,sBAAsB,EAAE,CAEvC;IAED,+EAA+E;IAC/E,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAEvD;IAED,oCAAoC;IACpC,MAAM,IAAI,IAAI,CAUb;IAED,OAAO,CAAC,IAAI;IASZ,qCAAqC;IACrC,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAEhD;IAED,qCAAqC;IACrC,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,GAAG,IAAI,CAGtD;IAED,wCAAwC;IACxC,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAG7B;IAED,kDAAkD;IAClD,IAAI,IAAI,MAAM,EAAE,CAEf;IAED,kDAAkD;IAClD,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE7B;IAED,qDAAqD;IACrD,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAKjC;IAED,kCAAkC;IAC5B,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAO7E;IAED,8BAA8B;IAC9B,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAE7B;YAKa,yBAAyB;IA2DvC;;;OAGG;IACG,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAmC/D;CACD","sourcesContent":["/**\n * Credential storage backed by auth.json.\n * Handles API keys and OAuth tokens with file-locked token refresh.\n */\n\nimport { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport lockfile from \"proper-lockfile\";\nimport type {\n\tAuthCredential,\n\tAuthStorageData,\n\tOAuthCredentials,\n\tOAuthLoginCallbacks,\n\tOAuthProviderInterface,\n} from \"./types.js\";\n\nconst ENV_KEY_MAP: Record<string, string> = {\n\tanthropic: \"ANTHROPIC_API_KEY\",\n\topenai: \"OPENAI_API_KEY\",\n\tgoogle: \"GEMINI_API_KEY\",\n};\n\nfunction getEnvApiKey(provider: string): string | undefined {\n\t// Anthropic: ANTHROPIC_OAUTH_TOKEN takes precedence\n\tif (provider === \"anthropic\") {\n\t\treturn process.env.ANTHROPIC_OAUTH_TOKEN || process.env.ANTHROPIC_API_KEY;\n\t}\n\t// GitHub Copilot: check multiple env vars\n\tif (provider === \"github-copilot\") {\n\t\treturn process.env.COPILOT_GITHUB_TOKEN || process.env.GH_TOKEN || process.env.GITHUB_TOKEN;\n\t}\n\tconst envVar = ENV_KEY_MAP[provider];\n\treturn envVar ? process.env[envVar] : undefined;\n}\n\nexport class AuthStorage {\n\tprivate data: AuthStorageData = {};\n\tprivate runtimeOverrides: Map<string, string> = new Map();\n\tprivate providers: Map<string, OAuthProviderInterface> = new Map();\n\n\tconstructor(private authPath: string) {\n\t\tthis.reload();\n\t}\n\n\t/** Register an OAuth provider. */\n\tregisterProvider(provider: OAuthProviderInterface): void {\n\t\tthis.providers.set(provider.id, provider);\n\t}\n\n\t/** Get a registered OAuth provider. */\n\tgetProvider(id: string): OAuthProviderInterface | undefined {\n\t\treturn this.providers.get(id);\n\t}\n\n\t/** Get all registered OAuth providers. */\n\tgetProviders(): OAuthProviderInterface[] {\n\t\treturn Array.from(this.providers.values());\n\t}\n\n\t/** Set a runtime API key override (not persisted). Used for --api-key flag. */\n\tsetRuntimeApiKey(provider: string, apiKey: string): void {\n\t\tthis.runtimeOverrides.set(provider, apiKey);\n\t}\n\n\t/** Reload credentials from disk. */\n\treload(): void {\n\t\tif (!existsSync(this.authPath)) {\n\t\t\tthis.data = {};\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tthis.data = JSON.parse(readFileSync(this.authPath, \"utf-8\"));\n\t\t} catch {\n\t\t\tthis.data = {};\n\t\t}\n\t}\n\n\tprivate save(): void {\n\t\tconst dir = dirname(this.authPath);\n\t\tif (!existsSync(dir)) {\n\t\t\tmkdirSync(dir, { recursive: true, mode: 0o700 });\n\t\t}\n\t\twriteFileSync(this.authPath, JSON.stringify(this.data, null, 2), \"utf-8\");\n\t\tchmodSync(this.authPath, 0o600);\n\t}\n\n\t/** Get credential for a provider. */\n\tget(provider: string): AuthCredential | undefined {\n\t\treturn this.data[provider] ?? undefined;\n\t}\n\n\t/** Set credential for a provider. */\n\tset(provider: string, credential: AuthCredential): void {\n\t\tthis.data[provider] = credential;\n\t\tthis.save();\n\t}\n\n\t/** Remove credential for a provider. */\n\tremove(provider: string): void {\n\t\tdelete this.data[provider];\n\t\tthis.save();\n\t}\n\n\t/** List all providers with stored credentials. */\n\tlist(): string[] {\n\t\treturn Object.keys(this.data);\n\t}\n\n\t/** Check if a provider has stored credentials. */\n\thas(provider: string): boolean {\n\t\treturn provider in this.data;\n\t}\n\n\t/** Check if any auth is available for a provider. */\n\thasAuth(provider: string): boolean {\n\t\tif (this.runtimeOverrides.has(provider)) return true;\n\t\tif (this.data[provider]) return true;\n\t\tif (getEnvApiKey(provider)) return true;\n\t\treturn false;\n\t}\n\n\t/** Login to an OAuth provider. */\n\tasync login(providerId: string, callbacks: OAuthLoginCallbacks): Promise<void> {\n\t\tconst provider = this.providers.get(providerId);\n\t\tif (!provider) {\n\t\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t\t}\n\t\tconst credentials = await provider.login(callbacks);\n\t\tthis.set(providerId, { type: \"oauth\", ...credentials });\n\t}\n\n\t/** Logout from a provider. */\n\tlogout(provider: string): void {\n\t\tthis.remove(provider);\n\t}\n\n\t/**\n\t * Refresh an OAuth token with file locking to prevent race conditions.\n\t */\n\tprivate async refreshOAuthTokenWithLock(\n\t\tproviderId: string,\n\t): Promise<{ apiKey: string; newCredentials: OAuthCredentials } | null> {\n\t\tconst provider = this.providers.get(providerId);\n\t\tif (!provider) return null;\n\n\t\t// Ensure auth file exists for locking\n\t\tif (!existsSync(this.authPath)) {\n\t\t\tconst dir = dirname(this.authPath);\n\t\t\tif (!existsSync(dir)) {\n\t\t\t\tmkdirSync(dir, { recursive: true, mode: 0o700 });\n\t\t\t}\n\t\t\twriteFileSync(this.authPath, \"{}\", \"utf-8\");\n\t\t\tchmodSync(this.authPath, 0o600);\n\t\t}\n\n\t\tlet release: (() => Promise<void>) | undefined;\n\n\t\ttry {\n\t\t\trelease = await lockfile.lock(this.authPath, {\n\t\t\t\tretries: {\n\t\t\t\t\tretries: 10,\n\t\t\t\t\tfactor: 2,\n\t\t\t\t\tminTimeout: 100,\n\t\t\t\t\tmaxTimeout: 10000,\n\t\t\t\t\trandomize: true,\n\t\t\t\t},\n\t\t\t\tstale: 30000,\n\t\t\t});\n\n\t\t\t// Re-read after acquiring lock - another instance may have refreshed\n\t\t\tthis.reload();\n\n\t\t\tconst cred = this.data[providerId];\n\t\t\tif (cred?.type !== \"oauth\") return null;\n\n\t\t\t// Check if still expired (another instance may have refreshed)\n\t\t\tif (Date.now() < cred.expires) {\n\t\t\t\tconst apiKey = provider.getApiKey(cred);\n\t\t\t\treturn { apiKey, newCredentials: cred };\n\t\t\t}\n\n\t\t\t// Refresh the token\n\t\t\tconst newCredentials = await provider.refreshToken(cred);\n\t\t\tthis.data[providerId] = { type: \"oauth\", ...newCredentials };\n\t\t\tthis.save();\n\n\t\t\treturn { apiKey: provider.getApiKey(newCredentials), newCredentials };\n\t\t} finally {\n\t\t\tif (release) {\n\t\t\t\ttry {\n\t\t\t\t\tawait release();\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore unlock errors\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Get API key for a provider.\n\t * Priority: runtime override → auth.json API key → OAuth (auto-refresh) → env var\n\t */\n\tasync getApiKey(providerId: string): Promise<string | undefined> {\n\t\t// Runtime override\n\t\tconst runtimeKey = this.runtimeOverrides.get(providerId);\n\t\tif (runtimeKey) return runtimeKey;\n\n\t\tconst cred = this.data[providerId];\n\n\t\t// Stored API key\n\t\tif (cred?.type === \"api_key\") return cred.key;\n\n\t\t// OAuth token with auto-refresh\n\t\tif (cred?.type === \"oauth\") {\n\t\t\tconst provider = this.providers.get(providerId);\n\t\t\tif (!provider) return undefined;\n\n\t\t\tif (Date.now() >= cred.expires) {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await this.refreshOAuthTokenWithLock(providerId);\n\t\t\t\t\tif (result) return result.apiKey;\n\t\t\t\t} catch {\n\t\t\t\t\t// Re-read to check if another instance succeeded\n\t\t\t\t\tthis.reload();\n\t\t\t\t\tconst updated = this.data[providerId];\n\t\t\t\t\tif (updated?.type === \"oauth\" && Date.now() < updated.expires) {\n\t\t\t\t\t\treturn provider.getApiKey(updated);\n\t\t\t\t\t}\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn provider.getApiKey(cred);\n\t\t\t}\n\t\t}\n\n\t\t// Environment variable\n\t\treturn getEnvApiKey(providerId);\n\t}\n}\n"]}
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Credential storage backed by auth.json.
3
+ * Handles API keys and OAuth tokens with file-locked token refresh.
4
+ */
5
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
6
+ import { dirname } from "node:path";
7
+ import lockfile from "proper-lockfile";
8
+ const ENV_KEY_MAP = {
9
+ anthropic: "ANTHROPIC_API_KEY",
10
+ openai: "OPENAI_API_KEY",
11
+ google: "GEMINI_API_KEY",
12
+ };
13
+ function getEnvApiKey(provider) {
14
+ // Anthropic: ANTHROPIC_OAUTH_TOKEN takes precedence
15
+ if (provider === "anthropic") {
16
+ return process.env.ANTHROPIC_OAUTH_TOKEN || process.env.ANTHROPIC_API_KEY;
17
+ }
18
+ // GitHub Copilot: check multiple env vars
19
+ if (provider === "github-copilot") {
20
+ return process.env.COPILOT_GITHUB_TOKEN || process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
21
+ }
22
+ const envVar = ENV_KEY_MAP[provider];
23
+ return envVar ? process.env[envVar] : undefined;
24
+ }
25
+ export class AuthStorage {
26
+ authPath;
27
+ data = {};
28
+ runtimeOverrides = new Map();
29
+ providers = new Map();
30
+ constructor(authPath) {
31
+ this.authPath = authPath;
32
+ this.reload();
33
+ }
34
+ /** Register an OAuth provider. */
35
+ registerProvider(provider) {
36
+ this.providers.set(provider.id, provider);
37
+ }
38
+ /** Get a registered OAuth provider. */
39
+ getProvider(id) {
40
+ return this.providers.get(id);
41
+ }
42
+ /** Get all registered OAuth providers. */
43
+ getProviders() {
44
+ return Array.from(this.providers.values());
45
+ }
46
+ /** Set a runtime API key override (not persisted). Used for --api-key flag. */
47
+ setRuntimeApiKey(provider, apiKey) {
48
+ this.runtimeOverrides.set(provider, apiKey);
49
+ }
50
+ /** Reload credentials from disk. */
51
+ reload() {
52
+ if (!existsSync(this.authPath)) {
53
+ this.data = {};
54
+ return;
55
+ }
56
+ try {
57
+ this.data = JSON.parse(readFileSync(this.authPath, "utf-8"));
58
+ }
59
+ catch {
60
+ this.data = {};
61
+ }
62
+ }
63
+ save() {
64
+ const dir = dirname(this.authPath);
65
+ if (!existsSync(dir)) {
66
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
67
+ }
68
+ writeFileSync(this.authPath, JSON.stringify(this.data, null, 2), "utf-8");
69
+ chmodSync(this.authPath, 0o600);
70
+ }
71
+ /** Get credential for a provider. */
72
+ get(provider) {
73
+ return this.data[provider] ?? undefined;
74
+ }
75
+ /** Set credential for a provider. */
76
+ set(provider, credential) {
77
+ this.data[provider] = credential;
78
+ this.save();
79
+ }
80
+ /** Remove credential for a provider. */
81
+ remove(provider) {
82
+ delete this.data[provider];
83
+ this.save();
84
+ }
85
+ /** List all providers with stored credentials. */
86
+ list() {
87
+ return Object.keys(this.data);
88
+ }
89
+ /** Check if a provider has stored credentials. */
90
+ has(provider) {
91
+ return provider in this.data;
92
+ }
93
+ /** Check if any auth is available for a provider. */
94
+ hasAuth(provider) {
95
+ if (this.runtimeOverrides.has(provider))
96
+ return true;
97
+ if (this.data[provider])
98
+ return true;
99
+ if (getEnvApiKey(provider))
100
+ return true;
101
+ return false;
102
+ }
103
+ /** Login to an OAuth provider. */
104
+ async login(providerId, callbacks) {
105
+ const provider = this.providers.get(providerId);
106
+ if (!provider) {
107
+ throw new Error(`Unknown OAuth provider: ${providerId}`);
108
+ }
109
+ const credentials = await provider.login(callbacks);
110
+ this.set(providerId, { type: "oauth", ...credentials });
111
+ }
112
+ /** Logout from a provider. */
113
+ logout(provider) {
114
+ this.remove(provider);
115
+ }
116
+ /**
117
+ * Refresh an OAuth token with file locking to prevent race conditions.
118
+ */
119
+ async refreshOAuthTokenWithLock(providerId) {
120
+ const provider = this.providers.get(providerId);
121
+ if (!provider)
122
+ return null;
123
+ // Ensure auth file exists for locking
124
+ if (!existsSync(this.authPath)) {
125
+ const dir = dirname(this.authPath);
126
+ if (!existsSync(dir)) {
127
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
128
+ }
129
+ writeFileSync(this.authPath, "{}", "utf-8");
130
+ chmodSync(this.authPath, 0o600);
131
+ }
132
+ let release;
133
+ try {
134
+ release = await lockfile.lock(this.authPath, {
135
+ retries: {
136
+ retries: 10,
137
+ factor: 2,
138
+ minTimeout: 100,
139
+ maxTimeout: 10000,
140
+ randomize: true,
141
+ },
142
+ stale: 30000,
143
+ });
144
+ // Re-read after acquiring lock - another instance may have refreshed
145
+ this.reload();
146
+ const cred = this.data[providerId];
147
+ if (cred?.type !== "oauth")
148
+ return null;
149
+ // Check if still expired (another instance may have refreshed)
150
+ if (Date.now() < cred.expires) {
151
+ const apiKey = provider.getApiKey(cred);
152
+ return { apiKey, newCredentials: cred };
153
+ }
154
+ // Refresh the token
155
+ const newCredentials = await provider.refreshToken(cred);
156
+ this.data[providerId] = { type: "oauth", ...newCredentials };
157
+ this.save();
158
+ return { apiKey: provider.getApiKey(newCredentials), newCredentials };
159
+ }
160
+ finally {
161
+ if (release) {
162
+ try {
163
+ await release();
164
+ }
165
+ catch {
166
+ // Ignore unlock errors
167
+ }
168
+ }
169
+ }
170
+ }
171
+ /**
172
+ * Get API key for a provider.
173
+ * Priority: runtime override → auth.json API key → OAuth (auto-refresh) → env var
174
+ */
175
+ async getApiKey(providerId) {
176
+ // Runtime override
177
+ const runtimeKey = this.runtimeOverrides.get(providerId);
178
+ if (runtimeKey)
179
+ return runtimeKey;
180
+ const cred = this.data[providerId];
181
+ // Stored API key
182
+ if (cred?.type === "api_key")
183
+ return cred.key;
184
+ // OAuth token with auto-refresh
185
+ if (cred?.type === "oauth") {
186
+ const provider = this.providers.get(providerId);
187
+ if (!provider)
188
+ return undefined;
189
+ if (Date.now() >= cred.expires) {
190
+ try {
191
+ const result = await this.refreshOAuthTokenWithLock(providerId);
192
+ if (result)
193
+ return result.apiKey;
194
+ }
195
+ catch {
196
+ // Re-read to check if another instance succeeded
197
+ this.reload();
198
+ const updated = this.data[providerId];
199
+ if (updated?.type === "oauth" && Date.now() < updated.expires) {
200
+ return provider.getApiKey(updated);
201
+ }
202
+ return undefined;
203
+ }
204
+ }
205
+ else {
206
+ return provider.getApiKey(cred);
207
+ }
208
+ }
209
+ // Environment variable
210
+ return getEnvApiKey(providerId);
211
+ }
212
+ }
213
+ //# sourceMappingURL=auth-storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-storage.js","sourceRoot":"","sources":["../../src/auth/auth-storage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Credential storage backed by auth.json.\n * Handles API keys and OAuth tokens with file-locked token refresh.\n */\n\nimport { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport lockfile from \"proper-lockfile\";\nimport type {\n\tAuthCredential,\n\tAuthStorageData,\n\tOAuthCredentials,\n\tOAuthLoginCallbacks,\n\tOAuthProviderInterface,\n} from \"./types.js\";\n\nconst ENV_KEY_MAP: Record<string, string> = {\n\tanthropic: \"ANTHROPIC_API_KEY\",\n\topenai: \"OPENAI_API_KEY\",\n\tgoogle: \"GEMINI_API_KEY\",\n};\n\nfunction getEnvApiKey(provider: string): string | undefined {\n\t// Anthropic: ANTHROPIC_OAUTH_TOKEN takes precedence\n\tif (provider === \"anthropic\") {\n\t\treturn process.env.ANTHROPIC_OAUTH_TOKEN || process.env.ANTHROPIC_API_KEY;\n\t}\n\t// GitHub Copilot: check multiple env vars\n\tif (provider === \"github-copilot\") {\n\t\treturn process.env.COPILOT_GITHUB_TOKEN || process.env.GH_TOKEN || process.env.GITHUB_TOKEN;\n\t}\n\tconst envVar = ENV_KEY_MAP[provider];\n\treturn envVar ? process.env[envVar] : undefined;\n}\n\nexport class AuthStorage {\n\tprivate data: AuthStorageData = {};\n\tprivate runtimeOverrides: Map<string, string> = new Map();\n\tprivate providers: Map<string, OAuthProviderInterface> = new Map();\n\n\tconstructor(private authPath: string) {\n\t\tthis.reload();\n\t}\n\n\t/** Register an OAuth provider. */\n\tregisterProvider(provider: OAuthProviderInterface): void {\n\t\tthis.providers.set(provider.id, provider);\n\t}\n\n\t/** Get a registered OAuth provider. */\n\tgetProvider(id: string): OAuthProviderInterface | undefined {\n\t\treturn this.providers.get(id);\n\t}\n\n\t/** Get all registered OAuth providers. */\n\tgetProviders(): OAuthProviderInterface[] {\n\t\treturn Array.from(this.providers.values());\n\t}\n\n\t/** Set a runtime API key override (not persisted). Used for --api-key flag. */\n\tsetRuntimeApiKey(provider: string, apiKey: string): void {\n\t\tthis.runtimeOverrides.set(provider, apiKey);\n\t}\n\n\t/** Reload credentials from disk. */\n\treload(): void {\n\t\tif (!existsSync(this.authPath)) {\n\t\t\tthis.data = {};\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tthis.data = JSON.parse(readFileSync(this.authPath, \"utf-8\"));\n\t\t} catch {\n\t\t\tthis.data = {};\n\t\t}\n\t}\n\n\tprivate save(): void {\n\t\tconst dir = dirname(this.authPath);\n\t\tif (!existsSync(dir)) {\n\t\t\tmkdirSync(dir, { recursive: true, mode: 0o700 });\n\t\t}\n\t\twriteFileSync(this.authPath, JSON.stringify(this.data, null, 2), \"utf-8\");\n\t\tchmodSync(this.authPath, 0o600);\n\t}\n\n\t/** Get credential for a provider. */\n\tget(provider: string): AuthCredential | undefined {\n\t\treturn this.data[provider] ?? undefined;\n\t}\n\n\t/** Set credential for a provider. */\n\tset(provider: string, credential: AuthCredential): void {\n\t\tthis.data[provider] = credential;\n\t\tthis.save();\n\t}\n\n\t/** Remove credential for a provider. */\n\tremove(provider: string): void {\n\t\tdelete this.data[provider];\n\t\tthis.save();\n\t}\n\n\t/** List all providers with stored credentials. */\n\tlist(): string[] {\n\t\treturn Object.keys(this.data);\n\t}\n\n\t/** Check if a provider has stored credentials. */\n\thas(provider: string): boolean {\n\t\treturn provider in this.data;\n\t}\n\n\t/** Check if any auth is available for a provider. */\n\thasAuth(provider: string): boolean {\n\t\tif (this.runtimeOverrides.has(provider)) return true;\n\t\tif (this.data[provider]) return true;\n\t\tif (getEnvApiKey(provider)) return true;\n\t\treturn false;\n\t}\n\n\t/** Login to an OAuth provider. */\n\tasync login(providerId: string, callbacks: OAuthLoginCallbacks): Promise<void> {\n\t\tconst provider = this.providers.get(providerId);\n\t\tif (!provider) {\n\t\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t\t}\n\t\tconst credentials = await provider.login(callbacks);\n\t\tthis.set(providerId, { type: \"oauth\", ...credentials });\n\t}\n\n\t/** Logout from a provider. */\n\tlogout(provider: string): void {\n\t\tthis.remove(provider);\n\t}\n\n\t/**\n\t * Refresh an OAuth token with file locking to prevent race conditions.\n\t */\n\tprivate async refreshOAuthTokenWithLock(\n\t\tproviderId: string,\n\t): Promise<{ apiKey: string; newCredentials: OAuthCredentials } | null> {\n\t\tconst provider = this.providers.get(providerId);\n\t\tif (!provider) return null;\n\n\t\t// Ensure auth file exists for locking\n\t\tif (!existsSync(this.authPath)) {\n\t\t\tconst dir = dirname(this.authPath);\n\t\t\tif (!existsSync(dir)) {\n\t\t\t\tmkdirSync(dir, { recursive: true, mode: 0o700 });\n\t\t\t}\n\t\t\twriteFileSync(this.authPath, \"{}\", \"utf-8\");\n\t\t\tchmodSync(this.authPath, 0o600);\n\t\t}\n\n\t\tlet release: (() => Promise<void>) | undefined;\n\n\t\ttry {\n\t\t\trelease = await lockfile.lock(this.authPath, {\n\t\t\t\tretries: {\n\t\t\t\t\tretries: 10,\n\t\t\t\t\tfactor: 2,\n\t\t\t\t\tminTimeout: 100,\n\t\t\t\t\tmaxTimeout: 10000,\n\t\t\t\t\trandomize: true,\n\t\t\t\t},\n\t\t\t\tstale: 30000,\n\t\t\t});\n\n\t\t\t// Re-read after acquiring lock - another instance may have refreshed\n\t\t\tthis.reload();\n\n\t\t\tconst cred = this.data[providerId];\n\t\t\tif (cred?.type !== \"oauth\") return null;\n\n\t\t\t// Check if still expired (another instance may have refreshed)\n\t\t\tif (Date.now() < cred.expires) {\n\t\t\t\tconst apiKey = provider.getApiKey(cred);\n\t\t\t\treturn { apiKey, newCredentials: cred };\n\t\t\t}\n\n\t\t\t// Refresh the token\n\t\t\tconst newCredentials = await provider.refreshToken(cred);\n\t\t\tthis.data[providerId] = { type: \"oauth\", ...newCredentials };\n\t\t\tthis.save();\n\n\t\t\treturn { apiKey: provider.getApiKey(newCredentials), newCredentials };\n\t\t} finally {\n\t\t\tif (release) {\n\t\t\t\ttry {\n\t\t\t\t\tawait release();\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore unlock errors\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Get API key for a provider.\n\t * Priority: runtime override → auth.json API key → OAuth (auto-refresh) → env var\n\t */\n\tasync getApiKey(providerId: string): Promise<string | undefined> {\n\t\t// Runtime override\n\t\tconst runtimeKey = this.runtimeOverrides.get(providerId);\n\t\tif (runtimeKey) return runtimeKey;\n\n\t\tconst cred = this.data[providerId];\n\n\t\t// Stored API key\n\t\tif (cred?.type === \"api_key\") return cred.key;\n\n\t\t// OAuth token with auto-refresh\n\t\tif (cred?.type === \"oauth\") {\n\t\t\tconst provider = this.providers.get(providerId);\n\t\t\tif (!provider) return undefined;\n\n\t\t\tif (Date.now() >= cred.expires) {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await this.refreshOAuthTokenWithLock(providerId);\n\t\t\t\t\tif (result) return result.apiKey;\n\t\t\t\t} catch {\n\t\t\t\t\t// Re-read to check if another instance succeeded\n\t\t\t\t\tthis.reload();\n\t\t\t\t\tconst updated = this.data[providerId];\n\t\t\t\t\tif (updated?.type === \"oauth\" && Date.now() < updated.expires) {\n\t\t\t\t\t\treturn provider.getApiKey(updated);\n\t\t\t\t\t}\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn provider.getApiKey(cred);\n\t\t\t}\n\t\t}\n\n\t\t// Environment variable\n\t\treturn getEnvApiKey(providerId);\n\t}\n}\n"]}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * GitHub Copilot OAuth provider implementation.
3
+ * Uses the GitHub Device Code flow followed by Copilot token exchange.
4
+ */
5
+ import type { OAuthProviderInterface } from "./types.js";
6
+ /** The built-in GitHub Copilot OAuth provider. */
7
+ export declare const githubCopilotOAuthProvider: OAuthProviderInterface;
8
+ //# sourceMappingURL=github-copilot-oauth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-copilot-oauth.d.ts","sourceRoot":"","sources":["../../src/auth/github-copilot-oauth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAyC,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAsKhG,kDAAkD;AAClD,eAAO,MAAM,0BAA0B,EAAE,sBAMxC,CAAC","sourcesContent":["/**\n * GitHub Copilot OAuth provider implementation.\n * Uses the GitHub Device Code flow followed by Copilot token exchange.\n */\n\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from \"./types.js\";\n\nconst CLIENT_ID = \"Iv1.b507a08c87ecfe98\";\nconst DEVICE_CODE_URL = \"https://github.com/login/device/code\";\nconst ACCESS_TOKEN_URL = \"https://github.com/login/oauth/access_token\";\nconst COPILOT_TOKEN_URL = \"https://api.github.com/copilot_internal/v2/token\";\n\ninterface DeviceCodeResponse {\n\tdevice_code: string;\n\tuser_code: string;\n\tverification_uri: string;\n\tinterval: number;\n\texpires_in: number;\n}\n\ninterface CopilotTokenResponse {\n\ttoken: string;\n\texpires_at: number;\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (signal?.aborted) {\n\t\t\treject(new Error(\"Login cancelled\"));\n\t\t\treturn;\n\t\t}\n\t\tconst timer = setTimeout(resolve, ms);\n\t\tsignal?.addEventListener(\n\t\t\t\"abort\",\n\t\t\t() => {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\treject(new Error(\"Login cancelled\"));\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t});\n}\n\nasync function exchangeForCopilotToken(githubAccessToken: string): Promise<{ token: string; expires: number }> {\n\tconst response = await fetch(COPILOT_TOKEN_URL, {\n\t\theaders: {\n\t\t\tAuthorization: `token ${githubAccessToken}`,\n\t\t\tAccept: \"application/json\",\n\t\t},\n\t});\n\n\tif (!response.ok) {\n\t\tconst text = await response.text();\n\t\tthrow new Error(`Copilot token exchange failed (${response.status}): ${text}`);\n\t}\n\n\tconst data = (await response.json()) as CopilotTokenResponse;\n\treturn {\n\t\ttoken: data.token,\n\t\t// expires_at is a Unix timestamp in seconds\n\t\texpires: data.expires_at * 1000 - 60 * 1000,\n\t};\n}\n\nasync function loginGitHubCopilot(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\t// Step 1: Request device code\n\tconst deviceResponse = await fetch(DEVICE_CODE_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\tAccept: \"application/json\",\n\t\t},\n\t\tbody: JSON.stringify({\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tscope: \"user:email\",\n\t\t}),\n\t});\n\n\tif (!deviceResponse.ok) {\n\t\tthrow new Error(`Device code request failed: ${await deviceResponse.text()}`);\n\t}\n\n\tconst deviceData = (await deviceResponse.json()) as DeviceCodeResponse;\n\n\t// Step 2: Show verification URL and user code\n\tcallbacks.onAuth({\n\t\turl: deviceData.verification_uri,\n\t\tinstructions: `Enter code: ${deviceData.user_code}`,\n\t});\n\n\tcallbacks.onProgress?.(\"Waiting for browser authentication...\");\n\n\t// Step 3: Poll for access token\n\tconst interval = (deviceData.interval || 5) * 1000;\n\tconst expiresAt = Date.now() + deviceData.expires_in * 1000;\n\tlet githubAccessToken: string | undefined;\n\n\twhile (Date.now() < expiresAt) {\n\t\tawait sleep(interval, callbacks.signal);\n\n\t\tconst tokenResponse = await fetch(ACCESS_TOKEN_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\tAccept: \"application/json\",\n\t\t\t},\n\t\t\tbody: JSON.stringify({\n\t\t\t\tclient_id: CLIENT_ID,\n\t\t\t\tdevice_code: deviceData.device_code,\n\t\t\t\tgrant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n\t\t\t}),\n\t\t});\n\n\t\tconst tokenData = (await tokenResponse.json()) as {\n\t\t\taccess_token?: string;\n\t\t\terror?: string;\n\t\t};\n\n\t\tif (tokenData.access_token) {\n\t\t\tgithubAccessToken = tokenData.access_token;\n\t\t\tbreak;\n\t\t}\n\n\t\tif (tokenData.error === \"authorization_pending\") {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (tokenData.error === \"slow_down\") {\n\t\t\tawait sleep(5000, callbacks.signal);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (tokenData.error === \"expired_token\") {\n\t\t\tthrow new Error(\"Device code expired. Please try again.\");\n\t\t}\n\n\t\tif (tokenData.error === \"access_denied\") {\n\t\t\tthrow new Error(\"Authorization was denied.\");\n\t\t}\n\n\t\tif (tokenData.error) {\n\t\t\tthrow new Error(`OAuth error: ${tokenData.error}`);\n\t\t}\n\t}\n\n\tif (!githubAccessToken) {\n\t\tthrow new Error(\"Device code expired. Please try again.\");\n\t}\n\n\tcallbacks.onProgress?.(\"Exchanging token for Copilot access...\");\n\n\t// Step 4: Exchange GitHub token for Copilot session token\n\tconst copilot = await exchangeForCopilotToken(githubAccessToken);\n\n\treturn {\n\t\trefresh: githubAccessToken,\n\t\taccess: copilot.token,\n\t\texpires: copilot.expires,\n\t};\n}\n\nasync function refreshCopilotToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n\tconst copilot = await exchangeForCopilotToken(credentials.refresh);\n\n\treturn {\n\t\trefresh: credentials.refresh,\n\t\taccess: copilot.token,\n\t\texpires: copilot.expires,\n\t};\n}\n\n/** The built-in GitHub Copilot OAuth provider. */\nexport const githubCopilotOAuthProvider: OAuthProviderInterface = {\n\tid: \"github-copilot\",\n\tname: \"GitHub Copilot\",\n\tlogin: loginGitHubCopilot,\n\trefreshToken: refreshCopilotToken,\n\tgetApiKey: (cred) => cred.access,\n};\n"]}
@@ -0,0 +1,131 @@
1
+ /**
2
+ * GitHub Copilot OAuth provider implementation.
3
+ * Uses the GitHub Device Code flow followed by Copilot token exchange.
4
+ */
5
+ const CLIENT_ID = "Iv1.b507a08c87ecfe98";
6
+ const DEVICE_CODE_URL = "https://github.com/login/device/code";
7
+ const ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
8
+ const COPILOT_TOKEN_URL = "https://api.github.com/copilot_internal/v2/token";
9
+ function sleep(ms, signal) {
10
+ return new Promise((resolve, reject) => {
11
+ if (signal?.aborted) {
12
+ reject(new Error("Login cancelled"));
13
+ return;
14
+ }
15
+ const timer = setTimeout(resolve, ms);
16
+ signal?.addEventListener("abort", () => {
17
+ clearTimeout(timer);
18
+ reject(new Error("Login cancelled"));
19
+ }, { once: true });
20
+ });
21
+ }
22
+ async function exchangeForCopilotToken(githubAccessToken) {
23
+ const response = await fetch(COPILOT_TOKEN_URL, {
24
+ headers: {
25
+ Authorization: `token ${githubAccessToken}`,
26
+ Accept: "application/json",
27
+ },
28
+ });
29
+ if (!response.ok) {
30
+ const text = await response.text();
31
+ throw new Error(`Copilot token exchange failed (${response.status}): ${text}`);
32
+ }
33
+ const data = (await response.json());
34
+ return {
35
+ token: data.token,
36
+ // expires_at is a Unix timestamp in seconds
37
+ expires: data.expires_at * 1000 - 60 * 1000,
38
+ };
39
+ }
40
+ async function loginGitHubCopilot(callbacks) {
41
+ // Step 1: Request device code
42
+ const deviceResponse = await fetch(DEVICE_CODE_URL, {
43
+ method: "POST",
44
+ headers: {
45
+ "Content-Type": "application/json",
46
+ Accept: "application/json",
47
+ },
48
+ body: JSON.stringify({
49
+ client_id: CLIENT_ID,
50
+ scope: "user:email",
51
+ }),
52
+ });
53
+ if (!deviceResponse.ok) {
54
+ throw new Error(`Device code request failed: ${await deviceResponse.text()}`);
55
+ }
56
+ const deviceData = (await deviceResponse.json());
57
+ // Step 2: Show verification URL and user code
58
+ callbacks.onAuth({
59
+ url: deviceData.verification_uri,
60
+ instructions: `Enter code: ${deviceData.user_code}`,
61
+ });
62
+ callbacks.onProgress?.("Waiting for browser authentication...");
63
+ // Step 3: Poll for access token
64
+ const interval = (deviceData.interval || 5) * 1000;
65
+ const expiresAt = Date.now() + deviceData.expires_in * 1000;
66
+ let githubAccessToken;
67
+ while (Date.now() < expiresAt) {
68
+ await sleep(interval, callbacks.signal);
69
+ const tokenResponse = await fetch(ACCESS_TOKEN_URL, {
70
+ method: "POST",
71
+ headers: {
72
+ "Content-Type": "application/json",
73
+ Accept: "application/json",
74
+ },
75
+ body: JSON.stringify({
76
+ client_id: CLIENT_ID,
77
+ device_code: deviceData.device_code,
78
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
79
+ }),
80
+ });
81
+ const tokenData = (await tokenResponse.json());
82
+ if (tokenData.access_token) {
83
+ githubAccessToken = tokenData.access_token;
84
+ break;
85
+ }
86
+ if (tokenData.error === "authorization_pending") {
87
+ continue;
88
+ }
89
+ if (tokenData.error === "slow_down") {
90
+ await sleep(5000, callbacks.signal);
91
+ continue;
92
+ }
93
+ if (tokenData.error === "expired_token") {
94
+ throw new Error("Device code expired. Please try again.");
95
+ }
96
+ if (tokenData.error === "access_denied") {
97
+ throw new Error("Authorization was denied.");
98
+ }
99
+ if (tokenData.error) {
100
+ throw new Error(`OAuth error: ${tokenData.error}`);
101
+ }
102
+ }
103
+ if (!githubAccessToken) {
104
+ throw new Error("Device code expired. Please try again.");
105
+ }
106
+ callbacks.onProgress?.("Exchanging token for Copilot access...");
107
+ // Step 4: Exchange GitHub token for Copilot session token
108
+ const copilot = await exchangeForCopilotToken(githubAccessToken);
109
+ return {
110
+ refresh: githubAccessToken,
111
+ access: copilot.token,
112
+ expires: copilot.expires,
113
+ };
114
+ }
115
+ async function refreshCopilotToken(credentials) {
116
+ const copilot = await exchangeForCopilotToken(credentials.refresh);
117
+ return {
118
+ refresh: credentials.refresh,
119
+ access: copilot.token,
120
+ expires: copilot.expires,
121
+ };
122
+ }
123
+ /** The built-in GitHub Copilot OAuth provider. */
124
+ export const githubCopilotOAuthProvider = {
125
+ id: "github-copilot",
126
+ name: "GitHub Copilot",
127
+ login: loginGitHubCopilot,
128
+ refreshToken: refreshCopilotToken,
129
+ getApiKey: (cred) => cred.access,
130
+ };
131
+ //# sourceMappingURL=github-copilot-oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-copilot-oauth.js","sourceRoot":"","sources":["../../src/auth/github-copilot-oauth.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * GitHub Copilot OAuth provider implementation.\n * Uses the GitHub Device Code flow followed by Copilot token exchange.\n */\n\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from \"./types.js\";\n\nconst CLIENT_ID = \"Iv1.b507a08c87ecfe98\";\nconst DEVICE_CODE_URL = \"https://github.com/login/device/code\";\nconst ACCESS_TOKEN_URL = \"https://github.com/login/oauth/access_token\";\nconst COPILOT_TOKEN_URL = \"https://api.github.com/copilot_internal/v2/token\";\n\ninterface DeviceCodeResponse {\n\tdevice_code: string;\n\tuser_code: string;\n\tverification_uri: string;\n\tinterval: number;\n\texpires_in: number;\n}\n\ninterface CopilotTokenResponse {\n\ttoken: string;\n\texpires_at: number;\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (signal?.aborted) {\n\t\t\treject(new Error(\"Login cancelled\"));\n\t\t\treturn;\n\t\t}\n\t\tconst timer = setTimeout(resolve, ms);\n\t\tsignal?.addEventListener(\n\t\t\t\"abort\",\n\t\t\t() => {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\treject(new Error(\"Login cancelled\"));\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t});\n}\n\nasync function exchangeForCopilotToken(githubAccessToken: string): Promise<{ token: string; expires: number }> {\n\tconst response = await fetch(COPILOT_TOKEN_URL, {\n\t\theaders: {\n\t\t\tAuthorization: `token ${githubAccessToken}`,\n\t\t\tAccept: \"application/json\",\n\t\t},\n\t});\n\n\tif (!response.ok) {\n\t\tconst text = await response.text();\n\t\tthrow new Error(`Copilot token exchange failed (${response.status}): ${text}`);\n\t}\n\n\tconst data = (await response.json()) as CopilotTokenResponse;\n\treturn {\n\t\ttoken: data.token,\n\t\t// expires_at is a Unix timestamp in seconds\n\t\texpires: data.expires_at * 1000 - 60 * 1000,\n\t};\n}\n\nasync function loginGitHubCopilot(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\t// Step 1: Request device code\n\tconst deviceResponse = await fetch(DEVICE_CODE_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\tAccept: \"application/json\",\n\t\t},\n\t\tbody: JSON.stringify({\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tscope: \"user:email\",\n\t\t}),\n\t});\n\n\tif (!deviceResponse.ok) {\n\t\tthrow new Error(`Device code request failed: ${await deviceResponse.text()}`);\n\t}\n\n\tconst deviceData = (await deviceResponse.json()) as DeviceCodeResponse;\n\n\t// Step 2: Show verification URL and user code\n\tcallbacks.onAuth({\n\t\turl: deviceData.verification_uri,\n\t\tinstructions: `Enter code: ${deviceData.user_code}`,\n\t});\n\n\tcallbacks.onProgress?.(\"Waiting for browser authentication...\");\n\n\t// Step 3: Poll for access token\n\tconst interval = (deviceData.interval || 5) * 1000;\n\tconst expiresAt = Date.now() + deviceData.expires_in * 1000;\n\tlet githubAccessToken: string | undefined;\n\n\twhile (Date.now() < expiresAt) {\n\t\tawait sleep(interval, callbacks.signal);\n\n\t\tconst tokenResponse = await fetch(ACCESS_TOKEN_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\tAccept: \"application/json\",\n\t\t\t},\n\t\t\tbody: JSON.stringify({\n\t\t\t\tclient_id: CLIENT_ID,\n\t\t\t\tdevice_code: deviceData.device_code,\n\t\t\t\tgrant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n\t\t\t}),\n\t\t});\n\n\t\tconst tokenData = (await tokenResponse.json()) as {\n\t\t\taccess_token?: string;\n\t\t\terror?: string;\n\t\t};\n\n\t\tif (tokenData.access_token) {\n\t\t\tgithubAccessToken = tokenData.access_token;\n\t\t\tbreak;\n\t\t}\n\n\t\tif (tokenData.error === \"authorization_pending\") {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (tokenData.error === \"slow_down\") {\n\t\t\tawait sleep(5000, callbacks.signal);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (tokenData.error === \"expired_token\") {\n\t\t\tthrow new Error(\"Device code expired. Please try again.\");\n\t\t}\n\n\t\tif (tokenData.error === \"access_denied\") {\n\t\t\tthrow new Error(\"Authorization was denied.\");\n\t\t}\n\n\t\tif (tokenData.error) {\n\t\t\tthrow new Error(`OAuth error: ${tokenData.error}`);\n\t\t}\n\t}\n\n\tif (!githubAccessToken) {\n\t\tthrow new Error(\"Device code expired. Please try again.\");\n\t}\n\n\tcallbacks.onProgress?.(\"Exchanging token for Copilot access...\");\n\n\t// Step 4: Exchange GitHub token for Copilot session token\n\tconst copilot = await exchangeForCopilotToken(githubAccessToken);\n\n\treturn {\n\t\trefresh: githubAccessToken,\n\t\taccess: copilot.token,\n\t\texpires: copilot.expires,\n\t};\n}\n\nasync function refreshCopilotToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n\tconst copilot = await exchangeForCopilotToken(credentials.refresh);\n\n\treturn {\n\t\trefresh: credentials.refresh,\n\t\taccess: copilot.token,\n\t\texpires: copilot.expires,\n\t};\n}\n\n/** The built-in GitHub Copilot OAuth provider. */\nexport const githubCopilotOAuthProvider: OAuthProviderInterface = {\n\tid: \"github-copilot\",\n\tname: \"GitHub Copilot\",\n\tlogin: loginGitHubCopilot,\n\trefreshToken: refreshCopilotToken,\n\tgetApiKey: (cred) => cred.access,\n};\n"]}
@@ -0,0 +1,6 @@
1
+ export { anthropicOAuthProvider, isAnthropicOAuthToken } from "./anthropic-oauth.js";
2
+ export { AuthStorage } from "./auth-storage.js";
3
+ export { githubCopilotOAuthProvider } from "./github-copilot-oauth.js";
4
+ export { openaiCodexOAuthProvider } from "./openai-codex-oauth.js";
5
+ export type { ApiKeyCredential, AuthCredential, AuthStorageData, OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface, } from "./types.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACrF,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACnE,YAAY,EACX,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,sBAAsB,GACtB,MAAM,YAAY,CAAC","sourcesContent":["export { anthropicOAuthProvider, isAnthropicOAuthToken } from \"./anthropic-oauth.js\";\nexport { AuthStorage } from \"./auth-storage.js\";\nexport { githubCopilotOAuthProvider } from \"./github-copilot-oauth.js\";\nexport { openaiCodexOAuthProvider } from \"./openai-codex-oauth.js\";\nexport type {\n\tApiKeyCredential,\n\tAuthCredential,\n\tAuthStorageData,\n\tOAuthCredentials,\n\tOAuthLoginCallbacks,\n\tOAuthProviderInterface,\n} from \"./types.js\";\n"]}