@open-vibe-lab/open-sub-auth 0.1.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/LICENSE +21 -0
- package/README.md +218 -0
- package/dist/cli/claude-3WKImhqM.mjs +56 -0
- package/dist/cli/claude-3WKImhqM.mjs.map +1 -0
- package/dist/cli/index.d.mts +2 -0
- package/dist/cli/index.mjs +90 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/cli/login-_HdTW5J_.mjs +33 -0
- package/dist/cli/login-_HdTW5J_.mjs.map +1 -0
- package/dist/cli/logout-CZm-tSVH.mjs +23 -0
- package/dist/cli/logout-CZm-tSVH.mjs.map +1 -0
- package/dist/cli/manager-CKGbp7Yz.mjs +331 -0
- package/dist/cli/manager-CKGbp7Yz.mjs.map +1 -0
- package/dist/cli/oauth-pkce-Bi02-h23.mjs +282 -0
- package/dist/cli/oauth-pkce-Bi02-h23.mjs.map +1 -0
- package/dist/cli/openai-codex-DJi_Q6Zm.mjs +102 -0
- package/dist/cli/openai-codex-DJi_Q6Zm.mjs.map +1 -0
- package/dist/cli/registry-Cp-_Ipc6.mjs +96 -0
- package/dist/cli/registry-Cp-_Ipc6.mjs.map +1 -0
- package/dist/cli/status-C-vkcjVM.mjs +47 -0
- package/dist/cli/status-C-vkcjVM.mjs.map +1 -0
- package/dist/cli/token-DF_-h4Rb.mjs +23 -0
- package/dist/cli/token-DF_-h4Rb.mjs.map +1 -0
- package/dist/cli/ui-CdGEuLwh.mjs +34 -0
- package/dist/cli/ui-CdGEuLwh.mjs.map +1 -0
- package/dist/index.cjs +859 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +328 -0
- package/dist/index.d.mts +328 -0
- package/dist/index.mjs +827 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +65 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-pkce-Bi02-h23.mjs","names":[],"sources":["../../src/core/browser.ts","../../src/core/callback-server.ts","../../src/core/crypto.ts","../../src/core/manual-code-input.ts","../../src/core/oauth-pkce.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\nimport { platform } from \"node:os\";\n\n/** Open a URL in the user's default browser. Does not throw on failure. */\nexport function openBrowser(url: string): void {\n const os = platform();\n try {\n if (os === \"darwin\") {\n execFile(\"open\", [url]);\n } else if (os === \"win32\") {\n execFile(\"cmd\", [\"/c\", \"start\", \"\", url]);\n } else {\n execFile(\"xdg-open\", [url]);\n }\n } catch {\n // Silently ignore — caller should provide fallback instructions\n }\n}\n","import { createServer, type Server } from \"node:http\";\nimport { OAuthCallbackError, OAuthTimeoutError } from \"@/errors.ts\";\nimport type { AuthorizationResult } from \"@/types.ts\";\n\nconst SUCCESS_HTML = `<!DOCTYPE html>\n<html><head><meta charset=\"utf-8\"><title>Authorization Successful</title>\n<style>body{font-family:system-ui,sans-serif;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#f8f9fa}\n.card{text-align:center;padding:2rem;border-radius:12px;background:white;box-shadow:0 2px 8px rgba(0,0,0,0.1)}\nh1{color:#22c55e;font-size:1.5rem}p{color:#666}</style></head>\n<body><div class=\"card\"><h1>Authorization Successful</h1><p>You can close this window and return to the terminal.</p></div></body></html>`;\n\nconst ERROR_HTML = `<!DOCTYPE html>\n<html><head><meta charset=\"utf-8\"><title>Authorization Failed</title>\n<style>body{font-family:system-ui,sans-serif;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#f8f9fa}\n.card{text-align:center;padding:2rem;border-radius:12px;background:white;box-shadow:0 2px 8px rgba(0,0,0,0.1)}\nh1{color:#ef4444;font-size:1.5rem}p{color:#666}</style></head>\n<body><div class=\"card\"><h1>Authorization Failed</h1><p>Something went wrong. Please try again.</p></div></body></html>`;\n\nexport interface CallbackServerOptions {\n /** Port to listen on (0 = random available port) */\n port?: number;\n /** Expected state parameter for CSRF validation */\n expectedState: string;\n /** Timeout in milliseconds (default: 120000) */\n timeout?: number;\n /** Path to listen on (default: \"/callback\") */\n path?: string;\n}\n\n/** Start a local HTTP server to receive the OAuth callback redirect */\nexport function startCallbackServer(\n options: CallbackServerOptions,\n): Promise<{ result: Promise<AuthorizationResult>; port: number; close: () => void }> {\n const { expectedState, timeout = 120_000, path = \"/callback\" } = options;\n const port = options.port ?? 0;\n\n return new Promise((resolveSetup, _rejectSetup) => {\n let server: Server;\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n const resultPromise = new Promise<AuthorizationResult>((resolveResult, rejectResult) => {\n server = createServer((req, res) => {\n const url = new URL(req.url ?? \"/\", `http://localhost`);\n\n if (url.pathname !== path) {\n res.writeHead(404);\n res.end(\"Not found\");\n return;\n }\n\n const code = url.searchParams.get(\"code\");\n const state = url.searchParams.get(\"state\");\n const error = url.searchParams.get(\"error\");\n\n if (error) {\n const errorDesc = url.searchParams.get(\"error_description\") ?? error;\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(ERROR_HTML);\n cleanup();\n rejectResult(new OAuthCallbackError(`OAuth error: ${errorDesc}`));\n return;\n }\n\n if (!code) {\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(ERROR_HTML);\n cleanup();\n rejectResult(new OAuthCallbackError(\"No authorization code in callback\"));\n return;\n }\n\n if (state && state !== expectedState) {\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(ERROR_HTML);\n cleanup();\n rejectResult(\n new OAuthCallbackError(\"State mismatch — possible CSRF attack. Please try again.\"),\n );\n return;\n }\n\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(SUCCESS_HTML);\n cleanup();\n resolveResult({ code, state: state ?? expectedState });\n });\n\n const cleanup = () => {\n if (timeoutId) clearTimeout(timeoutId);\n server.close();\n };\n\n timeoutId = setTimeout(() => {\n cleanup();\n rejectResult(new OAuthTimeoutError(timeout));\n }, timeout);\n\n server.on(\"error\", (err) => {\n cleanup();\n rejectResult(new OAuthCallbackError(`Callback server error: ${err.message}`));\n });\n\n server.listen(port, \"127.0.0.1\", () => {\n const addr = server.address();\n const actualPort = typeof addr === \"object\" && addr ? addr.port : port;\n resolveSetup({\n result: resultPromise,\n port: actualPort,\n close: cleanup,\n });\n });\n });\n });\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport type { PKCEParams } from \"@/types.ts\";\n\n/** Generate a cryptographically random PKCE code verifier (43-128 chars, base64url) */\nexport function generateCodeVerifier(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\n/** Generate a PKCE code challenge from a verifier using S256 method */\nexport function generateCodeChallenge(verifier: string): string {\n return createHash(\"sha256\").update(verifier).digest(\"base64url\");\n}\n\n/** Generate both PKCE code verifier and challenge */\nexport function generatePKCE(): PKCEParams {\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = generateCodeChallenge(codeVerifier);\n return { codeVerifier, codeChallenge };\n}\n\n/** Generate a random state parameter for CSRF protection */\nexport function generateState(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n","import { createInterface } from \"node:readline\";\nimport { OAuthCallbackError, StateMismatchError } from \"@/errors.ts\";\nimport type { AuthorizationResult } from \"@/types.ts\";\n\n/**\n * Parse a \"code#state\" string (Anthropic's manual paste format).\n * Also supports a plain authorization code (state validated separately).\n */\nexport function parseCodeAndState(input: string, expectedState: string): AuthorizationResult {\n const trimmed = input.trim();\n if (!trimmed) {\n throw new OAuthCallbackError(\"Empty authorization code input\");\n }\n\n const hashIndex = trimmed.indexOf(\"#\");\n if (hashIndex !== -1) {\n const code = trimmed.slice(0, hashIndex);\n const state = trimmed.slice(hashIndex + 1);\n if (!code) {\n throw new OAuthCallbackError(\"Empty authorization code in code#state input\");\n }\n if (state !== expectedState) {\n throw new StateMismatchError();\n }\n return { code, state };\n }\n\n // No \"#\" found — assume just the code was provided\n return { code: trimmed, state: expectedState };\n}\n\n/** Prompt the user to paste the authorization code from their browser */\nexport function promptForCode(expectedState: string): Promise<AuthorizationResult> {\n return new Promise((resolve, reject) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stderr, // Use stderr so stdout stays clean for piping\n });\n\n rl.question(\"Paste the authorization code (or code#state) from your browser: \", (answer) => {\n rl.close();\n try {\n resolve(parseCodeAndState(answer, expectedState));\n } catch (err) {\n reject(err);\n }\n });\n });\n}\n","import { OAuthCallbackError } from \"@/errors.ts\";\nimport type { AuthorizationResult, LoginOptions, ProviderConfig, TokenSet } from \"@/types.ts\";\nimport { openBrowser } from \"@/core/browser.ts\";\nimport { startCallbackServer } from \"@/core/callback-server.ts\";\nimport { generatePKCE, generateState } from \"@/core/crypto.ts\";\nimport { promptForCode } from \"@/core/manual-code-input.ts\";\n\n/** Build the full OAuth authorization URL with PKCE and state params */\nexport function buildAuthorizationUrl(\n config: ProviderConfig,\n codeChallenge: string,\n state: string,\n redirectUri: string,\n): string {\n if (!config.authorizationEndpoint) {\n throw new OAuthCallbackError(`Provider \"${config.name}\" has no authorization endpoint`);\n }\n\n const url = new URL(config.authorizationEndpoint);\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"client_id\", config.clientId);\n url.searchParams.set(\"redirect_uri\", redirectUri);\n url.searchParams.set(\"code_challenge\", codeChallenge);\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n url.searchParams.set(\"state\", state);\n\n if (config.scopes?.length) {\n url.searchParams.set(\"scope\", config.scopes.join(\" \"));\n }\n\n return url.toString();\n}\n\n/** Exchange an authorization code for tokens */\nexport async function exchangeCode(\n config: ProviderConfig,\n code: string,\n codeVerifier: string,\n redirectUri: string,\n state?: string,\n): Promise<TokenSet> {\n const useJson = config.tokenBodyFormat === \"json\";\n\n const params: Record<string, string> = {\n grant_type: \"authorization_code\",\n code,\n code_verifier: codeVerifier,\n client_id: config.clientId,\n redirect_uri: redirectUri,\n };\n if (state !== undefined) {\n params.state = state;\n }\n\n const response = await fetch(config.tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": useJson ? \"application/json\" : \"application/x-www-form-urlencoded\" },\n body: useJson ? JSON.stringify(params) : new URLSearchParams(params).toString(),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new OAuthCallbackError(`Token exchange failed (${response.status}): ${errorText}`);\n }\n\n const data = (await response.json()) as Record<string, unknown>;\n return parseTokenResponse(data);\n}\n\n/** Refresh an access token using a refresh token */\nexport async function refreshAccessToken(\n config: ProviderConfig,\n refreshToken: string,\n): Promise<TokenSet> {\n const useJson = config.tokenBodyFormat === \"json\";\n\n const params: Record<string, string> = {\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n client_id: config.clientId,\n };\n\n const response = await fetch(config.tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": useJson ? \"application/json\" : \"application/x-www-form-urlencoded\" },\n body: useJson ? JSON.stringify(params) : new URLSearchParams(params).toString(),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new OAuthCallbackError(`Token refresh failed (${response.status}): ${errorText}`);\n }\n\n const data = (await response.json()) as Record<string, unknown>;\n return parseTokenResponse(data);\n}\n\nexport interface PKCEFlowOptions {\n config: ProviderConfig;\n loginOptions?: LoginOptions;\n /**\n * Manual mode redirect URI (used when manual=true).\n * If not provided, manual mode is not available.\n */\n manualRedirectUri?: string;\n}\n\n/** Execute the full PKCE flow: generate params, open browser, wait for callback, exchange code */\nexport async function executePKCEFlow(options: PKCEFlowOptions): Promise<TokenSet> {\n const { config, loginOptions } = options;\n const { codeVerifier, codeChallenge } = generatePKCE();\n // Some providers (e.g. Claude) require state === verifier for their token exchange\n const state = config.stateIsVerifier ? codeVerifier : generateState();\n const manual = loginOptions?.manual ?? false;\n\n let authResult: AuthorizationResult;\n let redirectUri: string;\n\n if (manual && options.manualRedirectUri) {\n // Manual mode: redirect to provider's callback page, user copies code\n redirectUri = options.manualRedirectUri;\n const authUrl = buildAuthorizationUrl(config, codeChallenge, state, redirectUri);\n\n if (loginOptions?.onOpenBrowser) {\n loginOptions.onOpenBrowser(authUrl);\n } else {\n openBrowser(authUrl);\n }\n\n process.stderr.write(\n `\\nOpen this URL in your browser if it didn't open automatically:\\n${authUrl}\\n\\n`,\n );\n authResult = await promptForCode(state);\n } else {\n // Automatic mode: local callback server\n const port = loginOptions?.port ?? 0;\n const timeout = loginOptions?.timeout ?? 120_000;\n\n const server = await startCallbackServer({\n port,\n expectedState: state,\n timeout,\n });\n\n redirectUri = `http://127.0.0.1:${server.port}/callback`;\n const authUrl = buildAuthorizationUrl(config, codeChallenge, state, redirectUri);\n\n if (loginOptions?.onOpenBrowser) {\n loginOptions.onOpenBrowser(authUrl);\n } else {\n openBrowser(authUrl);\n }\n\n process.stderr.write(\n `\\nOpen this URL in your browser if it didn't open automatically:\\n${authUrl}\\n\\nWaiting for authorization...\\n`,\n );\n\n try {\n authResult = await server.result;\n } catch (err) {\n server.close();\n throw err;\n }\n }\n\n // Pass state to exchange when provider uses JSON body format (e.g. Claude)\n const exchangeState = config.tokenBodyFormat === \"json\" ? authResult.state : undefined;\n return exchangeCode(config, authResult.code, codeVerifier, redirectUri, exchangeState);\n}\n\nfunction parseTokenResponse(data: Record<string, unknown>): TokenSet {\n const accessToken = data.access_token as string | undefined;\n if (!accessToken) {\n throw new OAuthCallbackError(\"No access_token in token response\");\n }\n\n const expiresIn = (data.expires_in as number | undefined) ?? 3600;\n const expiresAt = Date.now() + expiresIn * 1000;\n\n return {\n accessToken,\n refreshToken: (data.refresh_token as string | undefined) ?? null,\n expiresAt,\n idToken: data.id_token as string | undefined,\n tokenType: ((data.token_type as string | undefined) ?? \"bearer\").toLowerCase() as\n | \"bearer\"\n | \"api-key\",\n scopes: data.scope ? (data.scope as string).split(\" \") : undefined,\n raw: data,\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { r as registerProvider } from "./registry-Cp-_Ipc6.mjs";
|
|
3
|
+
import { n as refreshAccessToken, t as executePKCEFlow } from "./oauth-pkce-Bi02-h23.mjs";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
//#region src/token/jwt.ts
|
|
9
|
+
/**
|
|
10
|
+
* Decode a JWT token's payload without verifying the signature.
|
|
11
|
+
* Used to extract user info from id_tokens (e.g., OpenAI).
|
|
12
|
+
*/
|
|
13
|
+
function decodeJWT(token) {
|
|
14
|
+
const parts = token.split(".");
|
|
15
|
+
if (parts.length !== 3) throw new Error("Invalid JWT format: expected 3 parts");
|
|
16
|
+
const payload = parts[1];
|
|
17
|
+
if (!payload) throw new Error("Invalid JWT: empty payload");
|
|
18
|
+
const padded = payload.replace(/-/g, "+").replace(/_/g, "/");
|
|
19
|
+
const decoded = Buffer.from(padded, "base64").toString("utf8");
|
|
20
|
+
return JSON.parse(decoded);
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/providers/openai-codex.ts
|
|
24
|
+
const OPENAI_CODEX_CONFIG = {
|
|
25
|
+
name: "openai-codex",
|
|
26
|
+
displayName: "OpenAI ChatGPT Plus/Pro",
|
|
27
|
+
authorizationEndpoint: "https://auth.openai.com/oauth/authorize",
|
|
28
|
+
tokenEndpoint: "https://auth.openai.com/oauth/token",
|
|
29
|
+
clientId: "app_EMoamEEZ73f0CkXaXp7hrann",
|
|
30
|
+
redirectUri: "http://localhost:1455/auth/callback",
|
|
31
|
+
scopes: [
|
|
32
|
+
"openid",
|
|
33
|
+
"profile",
|
|
34
|
+
"email",
|
|
35
|
+
"offline_access"
|
|
36
|
+
],
|
|
37
|
+
grantType: "authorization_code"
|
|
38
|
+
};
|
|
39
|
+
var OpenAICodexProvider = class {
|
|
40
|
+
config = OPENAI_CODEX_CONFIG;
|
|
41
|
+
async login(options) {
|
|
42
|
+
return executePKCEFlow({
|
|
43
|
+
config: this.config,
|
|
44
|
+
loginOptions: {
|
|
45
|
+
...options,
|
|
46
|
+
port: options?.port ?? 1455
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
async refresh(refreshToken) {
|
|
51
|
+
return refreshAccessToken(this.config, refreshToken);
|
|
52
|
+
}
|
|
53
|
+
getAuthHeaders(accessToken) {
|
|
54
|
+
return {
|
|
55
|
+
authorization: `Bearer ${accessToken}`,
|
|
56
|
+
"content-type": "application/json"
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
getAccountId(tokenSet) {
|
|
60
|
+
if (tokenSet.idToken) try {
|
|
61
|
+
const claims = decodeJWT(tokenSet.idToken);
|
|
62
|
+
if (claims.sub) return claims.sub;
|
|
63
|
+
} catch {}
|
|
64
|
+
return createHash("sha256").update(tokenSet.accessToken).digest("hex").slice(0, 16);
|
|
65
|
+
}
|
|
66
|
+
getAccountLabel(tokenSet) {
|
|
67
|
+
if (tokenSet.idToken) try {
|
|
68
|
+
const claims = decodeJWT(tokenSet.idToken);
|
|
69
|
+
if (claims.email) return claims.email;
|
|
70
|
+
} catch {}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Import tokens from an existing Codex CLI installation.
|
|
75
|
+
* Reads from ~/.codex/auth.json or $CODEX_HOME/auth.json.
|
|
76
|
+
*/
|
|
77
|
+
function importFromCodexCli() {
|
|
78
|
+
const paths = [process.env.CODEX_HOME ? join(process.env.CODEX_HOME, "auth.json") : null, join(homedir(), ".codex", "auth.json")].filter(Boolean);
|
|
79
|
+
for (const filePath of paths) {
|
|
80
|
+
if (!existsSync(filePath)) continue;
|
|
81
|
+
try {
|
|
82
|
+
const content = readFileSync(filePath, "utf8");
|
|
83
|
+
const data = JSON.parse(content);
|
|
84
|
+
if (!data.tokens?.access_token) continue;
|
|
85
|
+
return {
|
|
86
|
+
accessToken: data.tokens.access_token,
|
|
87
|
+
refreshToken: data.tokens.refresh_token ?? null,
|
|
88
|
+
expiresAt: Date.now() + 36e5,
|
|
89
|
+
idToken: data.tokens.id_token,
|
|
90
|
+
tokenType: "bearer"
|
|
91
|
+
};
|
|
92
|
+
} catch {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
registerProvider("openai-codex", () => new OpenAICodexProvider());
|
|
99
|
+
//#endregion
|
|
100
|
+
export { OpenAICodexProvider, importFromCodexCli };
|
|
101
|
+
|
|
102
|
+
//# sourceMappingURL=openai-codex-DJi_Q6Zm.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai-codex-DJi_Q6Zm.mjs","names":[],"sources":["../../src/token/jwt.ts","../../src/providers/openai-codex.ts"],"sourcesContent":["/** Decoded JWT claims (no signature verification) */\nexport interface JWTClaims {\n sub?: string;\n email?: string;\n name?: string;\n iss?: string;\n aud?: string | string[];\n exp?: number;\n iat?: number;\n [key: string]: unknown;\n}\n\n/**\n * Decode a JWT token's payload without verifying the signature.\n * Used to extract user info from id_tokens (e.g., OpenAI).\n */\nexport function decodeJWT(token: string): JWTClaims {\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n throw new Error(\"Invalid JWT format: expected 3 parts\");\n }\n\n const payload = parts[1];\n if (!payload) {\n throw new Error(\"Invalid JWT: empty payload\");\n }\n\n // Add padding if needed for base64url decoding\n const padded = payload.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const decoded = Buffer.from(padded, \"base64\").toString(\"utf8\");\n\n return JSON.parse(decoded) as JWTClaims;\n}\n","import { createHash } from \"node:crypto\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { executePKCEFlow, refreshAccessToken } from \"@/core/oauth-pkce.ts\";\nimport { decodeJWT } from \"@/token/jwt.ts\";\nimport type { AuthHeaders, LoginOptions, Provider, ProviderConfig, TokenSet } from \"@/types.ts\";\nimport { registerProvider } from \"@/providers/registry.ts\";\n\nconst OPENAI_CODEX_CONFIG: ProviderConfig = {\n name: \"openai-codex\",\n displayName: \"OpenAI ChatGPT Plus/Pro\",\n authorizationEndpoint: \"https://auth.openai.com/oauth/authorize\",\n tokenEndpoint: \"https://auth.openai.com/oauth/token\",\n clientId: \"app_EMoamEEZ73f0CkXaXp7hrann\",\n redirectUri: \"http://localhost:1455/auth/callback\",\n scopes: [\"openid\", \"profile\", \"email\", \"offline_access\"],\n grantType: \"authorization_code\",\n};\n\nexport class OpenAICodexProvider implements Provider {\n readonly config = OPENAI_CODEX_CONFIG;\n\n async login(options?: LoginOptions): Promise<TokenSet> {\n return executePKCEFlow({\n config: this.config,\n loginOptions: {\n ...options,\n port: options?.port ?? 1455,\n },\n });\n }\n\n async refresh(refreshToken: string): Promise<TokenSet> {\n return refreshAccessToken(this.config, refreshToken);\n }\n\n getAuthHeaders(accessToken: string): AuthHeaders {\n return {\n authorization: `Bearer ${accessToken}`,\n \"content-type\": \"application/json\",\n };\n }\n\n getAccountId(tokenSet: TokenSet): string {\n if (tokenSet.idToken) {\n try {\n const claims = decodeJWT(tokenSet.idToken);\n if (claims.sub) return claims.sub;\n } catch {\n // Fall through\n }\n }\n return createHash(\"sha256\").update(tokenSet.accessToken).digest(\"hex\").slice(0, 16);\n }\n\n getAccountLabel(tokenSet: TokenSet): string | undefined {\n if (tokenSet.idToken) {\n try {\n const claims = decodeJWT(tokenSet.idToken);\n if (claims.email) return claims.email as string;\n } catch {\n // Fall through\n }\n }\n return undefined;\n }\n}\n\n/**\n * Import tokens from an existing Codex CLI installation.\n * Reads from ~/.codex/auth.json or $CODEX_HOME/auth.json.\n */\nexport function importFromCodexCli(): TokenSet | null {\n const paths = [\n process.env.CODEX_HOME ? join(process.env.CODEX_HOME, \"auth.json\") : null,\n join(homedir(), \".codex\", \"auth.json\"),\n ].filter(Boolean) as string[];\n\n for (const filePath of paths) {\n if (!existsSync(filePath)) continue;\n\n try {\n const content = readFileSync(filePath, \"utf8\");\n const data = JSON.parse(content) as {\n tokens?: {\n access_token?: string;\n refresh_token?: string;\n id_token?: string;\n };\n };\n\n if (!data.tokens?.access_token) continue;\n\n return {\n accessToken: data.tokens.access_token,\n refreshToken: data.tokens.refresh_token ?? null,\n expiresAt: Date.now() + 3600_000, // Assume 1 hour, will refresh as needed\n idToken: data.tokens.id_token,\n tokenType: \"bearer\",\n };\n } catch {\n continue;\n }\n }\n\n return null;\n}\n\n// Auto-register\nregisterProvider(\"openai-codex\", () => new OpenAICodexProvider());\n"],"mappings":";;;;;;;;;;;;AAgBA,SAAgB,UAAU,OAA0B;CAClD,MAAM,QAAQ,MAAM,MAAM,IAAI;AAC9B,KAAI,MAAM,WAAW,EACnB,OAAM,IAAI,MAAM,uCAAuC;CAGzD,MAAM,UAAU,MAAM;AACtB,KAAI,CAAC,QACH,OAAM,IAAI,MAAM,6BAA6B;CAI/C,MAAM,SAAS,QAAQ,QAAQ,MAAM,IAAI,CAAC,QAAQ,MAAM,IAAI;CAC5D,MAAM,UAAU,OAAO,KAAK,QAAQ,SAAS,CAAC,SAAS,OAAO;AAE9D,QAAO,KAAK,MAAM,QAAQ;;;;ACtB5B,MAAM,sBAAsC;CAC1C,MAAM;CACN,aAAa;CACb,uBAAuB;CACvB,eAAe;CACf,UAAU;CACV,aAAa;CACb,QAAQ;EAAC;EAAU;EAAW;EAAS;EAAiB;CACxD,WAAW;CACZ;AAED,IAAa,sBAAb,MAAqD;CACnD,SAAkB;CAElB,MAAM,MAAM,SAA2C;AACrD,SAAO,gBAAgB;GACrB,QAAQ,KAAK;GACb,cAAc;IACZ,GAAG;IACH,MAAM,SAAS,QAAQ;IACxB;GACF,CAAC;;CAGJ,MAAM,QAAQ,cAAyC;AACrD,SAAO,mBAAmB,KAAK,QAAQ,aAAa;;CAGtD,eAAe,aAAkC;AAC/C,SAAO;GACL,eAAe,UAAU;GACzB,gBAAgB;GACjB;;CAGH,aAAa,UAA4B;AACvC,MAAI,SAAS,QACX,KAAI;GACF,MAAM,SAAS,UAAU,SAAS,QAAQ;AAC1C,OAAI,OAAO,IAAK,QAAO,OAAO;UACxB;AAIV,SAAO,WAAW,SAAS,CAAC,OAAO,SAAS,YAAY,CAAC,OAAO,MAAM,CAAC,MAAM,GAAG,GAAG;;CAGrF,gBAAgB,UAAwC;AACtD,MAAI,SAAS,QACX,KAAI;GACF,MAAM,SAAS,UAAU,SAAS,QAAQ;AAC1C,OAAI,OAAO,MAAO,QAAO,OAAO;UAC1B;;;;;;;AAYd,SAAgB,qBAAsC;CACpD,MAAM,QAAQ,CACZ,QAAQ,IAAI,aAAa,KAAK,QAAQ,IAAI,YAAY,YAAY,GAAG,MACrE,KAAK,SAAS,EAAE,UAAU,YAAY,CACvC,CAAC,OAAO,QAAQ;AAEjB,MAAK,MAAM,YAAY,OAAO;AAC5B,MAAI,CAAC,WAAW,SAAS,CAAE;AAE3B,MAAI;GACF,MAAM,UAAU,aAAa,UAAU,OAAO;GAC9C,MAAM,OAAO,KAAK,MAAM,QAAQ;AAQhC,OAAI,CAAC,KAAK,QAAQ,aAAc;AAEhC,UAAO;IACL,aAAa,KAAK,OAAO;IACzB,cAAc,KAAK,OAAO,iBAAiB;IAC3C,WAAW,KAAK,KAAK,GAAG;IACxB,SAAS,KAAK,OAAO;IACrB,WAAW;IACZ;UACK;AACN;;;AAIJ,QAAO;;AAIT,iBAAiB,sBAAsB,IAAI,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __exportAll = (all, no_symbols) => {
|
|
5
|
+
let target = {};
|
|
6
|
+
for (var name in all) __defProp(target, name, {
|
|
7
|
+
get: all[name],
|
|
8
|
+
enumerable: true
|
|
9
|
+
});
|
|
10
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
11
|
+
return target;
|
|
12
|
+
};
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/errors.ts
|
|
15
|
+
var OpenSubAuthError = class extends Error {
|
|
16
|
+
constructor(message) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "OpenSubAuthError";
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
var TokenExpiredError = class extends OpenSubAuthError {
|
|
22
|
+
constructor(provider) {
|
|
23
|
+
super(`Access token for "${provider}" has expired and no refresh token is available. Please login again.`);
|
|
24
|
+
this.provider = provider;
|
|
25
|
+
this.name = "TokenExpiredError";
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var TokenRefreshError = class extends OpenSubAuthError {
|
|
29
|
+
constructor(provider, cause_) {
|
|
30
|
+
super(`Failed to refresh token for "${provider}": ${cause_ instanceof Error ? cause_.message : String(cause_)}`);
|
|
31
|
+
this.provider = provider;
|
|
32
|
+
this.cause_ = cause_;
|
|
33
|
+
this.name = "TokenRefreshError";
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var ProviderNotFoundError = class extends OpenSubAuthError {
|
|
37
|
+
constructor(providerName) {
|
|
38
|
+
super(`Provider "${providerName}" is not registered. Available providers can be listed with listProviders().`);
|
|
39
|
+
this.providerName = providerName;
|
|
40
|
+
this.name = "ProviderNotFoundError";
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var NoCredentialError = class extends OpenSubAuthError {
|
|
44
|
+
constructor(provider, accountId) {
|
|
45
|
+
const msg = accountId ? `No stored credential for "${provider}" account "${accountId}". Please login first.` : `No stored credential for "${provider}". Please login first.`;
|
|
46
|
+
super(msg);
|
|
47
|
+
this.provider = provider;
|
|
48
|
+
this.accountId = accountId;
|
|
49
|
+
this.name = "NoCredentialError";
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var OAuthCallbackError = class extends OpenSubAuthError {
|
|
53
|
+
constructor(message) {
|
|
54
|
+
super(message);
|
|
55
|
+
this.name = "OAuthCallbackError";
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
var OAuthTimeoutError = class extends OpenSubAuthError {
|
|
59
|
+
constructor(timeoutMs) {
|
|
60
|
+
super(`OAuth login timed out after ${Math.round(timeoutMs / 1e3)} seconds. Please try again.`);
|
|
61
|
+
this.timeoutMs = timeoutMs;
|
|
62
|
+
this.name = "OAuthTimeoutError";
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var StateMismatchError = class extends OpenSubAuthError {
|
|
66
|
+
constructor() {
|
|
67
|
+
super("OAuth state parameter mismatch — possible CSRF attack. Please try again.");
|
|
68
|
+
this.name = "StateMismatchError";
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/providers/registry.ts
|
|
73
|
+
var registry_exports = /* @__PURE__ */ __exportAll({
|
|
74
|
+
getProvider: () => getProvider,
|
|
75
|
+
listProviders: () => listProviders,
|
|
76
|
+
registerProvider: () => registerProvider
|
|
77
|
+
});
|
|
78
|
+
const providers = /* @__PURE__ */ new Map();
|
|
79
|
+
/** Register a provider factory */
|
|
80
|
+
function registerProvider(name, factory) {
|
|
81
|
+
providers.set(name, factory);
|
|
82
|
+
}
|
|
83
|
+
/** Get a provider instance by name */
|
|
84
|
+
function getProvider(name) {
|
|
85
|
+
const factory = providers.get(name);
|
|
86
|
+
if (!factory) throw new ProviderNotFoundError(name);
|
|
87
|
+
return factory();
|
|
88
|
+
}
|
|
89
|
+
/** List all registered provider names */
|
|
90
|
+
function listProviders() {
|
|
91
|
+
return Array.from(providers.keys());
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
export { NoCredentialError as a, StateMismatchError as c, registry_exports as i, TokenExpiredError as l, listProviders as n, OAuthCallbackError as o, registerProvider as r, OAuthTimeoutError as s, getProvider as t, TokenRefreshError as u };
|
|
95
|
+
|
|
96
|
+
//# sourceMappingURL=registry-Cp-_Ipc6.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry-Cp-_Ipc6.mjs","names":[],"sources":["../../src/errors.ts","../../src/providers/registry.ts"],"sourcesContent":["export class OpenSubAuthError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"OpenSubAuthError\";\n }\n}\n\nexport class AuthenticationError extends OpenSubAuthError {\n constructor(\n message: string,\n public readonly provider?: string,\n ) {\n super(message);\n this.name = \"AuthenticationError\";\n }\n}\n\nexport class TokenExpiredError extends OpenSubAuthError {\n constructor(public readonly provider: string) {\n super(\n `Access token for \"${provider}\" has expired and no refresh token is available. Please login again.`,\n );\n this.name = \"TokenExpiredError\";\n }\n}\n\nexport class TokenRefreshError extends OpenSubAuthError {\n constructor(\n public readonly provider: string,\n public readonly cause_: unknown,\n ) {\n super(\n `Failed to refresh token for \"${provider}\": ${cause_ instanceof Error ? cause_.message : String(cause_)}`,\n );\n this.name = \"TokenRefreshError\";\n }\n}\n\nexport class ProviderNotFoundError extends OpenSubAuthError {\n constructor(public readonly providerName: string) {\n super(\n `Provider \"${providerName}\" is not registered. Available providers can be listed with listProviders().`,\n );\n this.name = \"ProviderNotFoundError\";\n }\n}\n\nexport class NoCredentialError extends OpenSubAuthError {\n constructor(\n public readonly provider: string,\n public readonly accountId?: string,\n ) {\n const msg = accountId\n ? `No stored credential for \"${provider}\" account \"${accountId}\". Please login first.`\n : `No stored credential for \"${provider}\". Please login first.`;\n super(msg);\n this.name = \"NoCredentialError\";\n }\n}\n\nexport class OAuthCallbackError extends OpenSubAuthError {\n constructor(message: string) {\n super(message);\n this.name = \"OAuthCallbackError\";\n }\n}\n\nexport class OAuthTimeoutError extends OpenSubAuthError {\n constructor(public readonly timeoutMs: number) {\n super(`OAuth login timed out after ${Math.round(timeoutMs / 1000)} seconds. Please try again.`);\n this.name = \"OAuthTimeoutError\";\n }\n}\n\nexport class StateMismatchError extends OpenSubAuthError {\n constructor() {\n super(\"OAuth state parameter mismatch — possible CSRF attack. Please try again.\");\n this.name = \"StateMismatchError\";\n }\n}\n","import { ProviderNotFoundError } from \"@/errors.ts\";\nimport type { Provider } from \"@/types.ts\";\n\nconst providers = new Map<string, () => Provider>();\n\n/** Register a provider factory */\nexport function registerProvider(name: string, factory: () => Provider): void {\n providers.set(name, factory);\n}\n\n/** Get a provider instance by name */\nexport function getProvider(name: string): Provider {\n const factory = providers.get(name);\n if (!factory) {\n throw new ProviderNotFoundError(name);\n }\n return factory();\n}\n\n/** List all registered provider names */\nexport function listProviders(): string[] {\n return Array.from(providers.keys());\n}\n"],"mappings":";;;;;;;;;;;;;;AAAA,IAAa,mBAAb,cAAsC,MAAM;CAC1C,YAAY,SAAiB;AAC3B,QAAM,QAAQ;AACd,OAAK,OAAO;;;AAchB,IAAa,oBAAb,cAAuC,iBAAiB;CACtD,YAAY,UAAkC;AAC5C,QACE,qBAAqB,SAAS,sEAC/B;AAHyB,OAAA,WAAA;AAI1B,OAAK,OAAO;;;AAIhB,IAAa,oBAAb,cAAuC,iBAAiB;CACtD,YACE,UACA,QACA;AACA,QACE,gCAAgC,SAAS,KAAK,kBAAkB,QAAQ,OAAO,UAAU,OAAO,OAAO,GACxG;AALe,OAAA,WAAA;AACA,OAAA,SAAA;AAKhB,OAAK,OAAO;;;AAIhB,IAAa,wBAAb,cAA2C,iBAAiB;CAC1D,YAAY,cAAsC;AAChD,QACE,aAAa,aAAa,8EAC3B;AAHyB,OAAA,eAAA;AAI1B,OAAK,OAAO;;;AAIhB,IAAa,oBAAb,cAAuC,iBAAiB;CACtD,YACE,UACA,WACA;EACA,MAAM,MAAM,YACR,6BAA6B,SAAS,aAAa,UAAU,0BAC7D,6BAA6B,SAAS;AAC1C,QAAM,IAAI;AANM,OAAA,WAAA;AACA,OAAA,YAAA;AAMhB,OAAK,OAAO;;;AAIhB,IAAa,qBAAb,cAAwC,iBAAiB;CACvD,YAAY,SAAiB;AAC3B,QAAM,QAAQ;AACd,OAAK,OAAO;;;AAIhB,IAAa,oBAAb,cAAuC,iBAAiB;CACtD,YAAY,WAAmC;AAC7C,QAAM,+BAA+B,KAAK,MAAM,YAAY,IAAK,CAAC,6BAA6B;AADrE,OAAA,YAAA;AAE1B,OAAK,OAAO;;;AAIhB,IAAa,qBAAb,cAAwC,iBAAiB;CACvD,cAAc;AACZ,QAAM,2EAA2E;AACjF,OAAK,OAAO;;;;;;;;;;AC1EhB,MAAM,4BAAY,IAAI,KAA6B;;AAGnD,SAAgB,iBAAiB,MAAc,SAA+B;AAC5E,WAAU,IAAI,MAAM,QAAQ;;;AAI9B,SAAgB,YAAY,MAAwB;CAClD,MAAM,UAAU,UAAU,IAAI,KAAK;AACnC,KAAI,CAAC,QACH,OAAM,IAAI,sBAAsB,KAAK;AAEvC,QAAO,SAAS;;;AAIlB,SAAgB,gBAA0B;AACxC,QAAO,MAAM,KAAK,UAAU,MAAM,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { t as printError } from "./ui-CdGEuLwh.mjs";
|
|
3
|
+
import { n as createTokenStore, t as TokenManager } from "./manager-CKGbp7Yz.mjs";
|
|
4
|
+
//#region src/cli/commands/status.ts
|
|
5
|
+
async function statusCommand() {
|
|
6
|
+
await Promise.all([import("./claude-3WKImhqM.mjs"), import("./openai-codex-DJi_Q6Zm.mjs")]);
|
|
7
|
+
const manager = new TokenManager(await createTokenStore());
|
|
8
|
+
try {
|
|
9
|
+
const statuses = await manager.status();
|
|
10
|
+
if (statuses.length === 0) {
|
|
11
|
+
console.log("No stored credentials. Run `open-sub-auth login` to get started.");
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const header = [
|
|
15
|
+
"Provider".padEnd(22),
|
|
16
|
+
"Account".padEnd(20),
|
|
17
|
+
"Status".padEnd(10),
|
|
18
|
+
"Expires"
|
|
19
|
+
].join(" ");
|
|
20
|
+
console.log(header);
|
|
21
|
+
console.log("-".repeat(header.length));
|
|
22
|
+
for (const s of statuses) {
|
|
23
|
+
const account = s.accountLabel ?? s.accountId.slice(0, 12) + "...";
|
|
24
|
+
const expires = s.isExpired ? "-" : formatDuration(s.expiresAt - Date.now());
|
|
25
|
+
console.log([
|
|
26
|
+
s.displayName.padEnd(22),
|
|
27
|
+
account.padEnd(20),
|
|
28
|
+
(s.isExpired ? "Expired" : "Valid").padEnd(10),
|
|
29
|
+
expires
|
|
30
|
+
].join(" "));
|
|
31
|
+
}
|
|
32
|
+
} catch (err) {
|
|
33
|
+
printError(`Failed to get status: ${err instanceof Error ? err.message : String(err)}`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function formatDuration(ms) {
|
|
38
|
+
if (ms <= 0) return "-";
|
|
39
|
+
const hours = Math.floor(ms / 36e5);
|
|
40
|
+
const minutes = Math.floor(ms % 36e5 / 6e4);
|
|
41
|
+
if (hours > 0) return `${hours}h ${minutes}m`;
|
|
42
|
+
return `${minutes}m`;
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
export { statusCommand };
|
|
46
|
+
|
|
47
|
+
//# sourceMappingURL=status-C-vkcjVM.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status-C-vkcjVM.mjs","names":[],"sources":["../../src/cli/commands/status.ts"],"sourcesContent":["import { createTokenStore } from \"@/storage/store.ts\";\nimport { TokenManager } from \"@/token/manager.ts\";\nimport { printError } from \"@/cli/ui.ts\";\n\nexport async function statusCommand(): Promise<void> {\n await Promise.all([import(\"@/providers/claude.ts\"), import(\"@/providers/openai-codex.ts\")]);\n\n const store = await createTokenStore();\n const manager = new TokenManager(store);\n\n try {\n const statuses = await manager.status();\n\n if (statuses.length === 0) {\n console.log(\"No stored credentials. Run `open-sub-auth login` to get started.\");\n return;\n }\n\n // Table header\n const header = [\n \"Provider\".padEnd(22),\n \"Account\".padEnd(20),\n \"Status\".padEnd(10),\n \"Expires\",\n ].join(\" \");\n\n console.log(header);\n console.log(\"-\".repeat(header.length));\n\n for (const s of statuses) {\n const account = s.accountLabel ?? s.accountId.slice(0, 12) + \"...\";\n const expires = s.isExpired ? \"-\" : formatDuration(s.expiresAt - Date.now());\n\n console.log(\n [\n s.displayName.padEnd(22),\n account.padEnd(20),\n (s.isExpired ? \"Expired\" : \"Valid\").padEnd(10),\n expires,\n ].join(\" \"),\n );\n }\n } catch (err) {\n printError(`Failed to get status: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n}\n\nfunction formatDuration(ms: number): string {\n if (ms <= 0) return \"-\";\n const hours = Math.floor(ms / 3600_000);\n const minutes = Math.floor((ms % 3600_000) / 60_000);\n if (hours > 0) return `${hours}h ${minutes}m`;\n return `${minutes}m`;\n}\n"],"mappings":";;;;AAIA,eAAsB,gBAA+B;AACnD,OAAM,QAAQ,IAAI,CAAC,OAAO,0BAA0B,OAAO,+BAA+B,CAAC;CAG3F,MAAM,UAAU,IAAI,aADN,MAAM,kBAAkB,CACC;AAEvC,KAAI;EACF,MAAM,WAAW,MAAM,QAAQ,QAAQ;AAEvC,MAAI,SAAS,WAAW,GAAG;AACzB,WAAQ,IAAI,mEAAmE;AAC/E;;EAIF,MAAM,SAAS;GACb,WAAW,OAAO,GAAG;GACrB,UAAU,OAAO,GAAG;GACpB,SAAS,OAAO,GAAG;GACnB;GACD,CAAC,KAAK,KAAK;AAEZ,UAAQ,IAAI,OAAO;AACnB,UAAQ,IAAI,IAAI,OAAO,OAAO,OAAO,CAAC;AAEtC,OAAK,MAAM,KAAK,UAAU;GACxB,MAAM,UAAU,EAAE,gBAAgB,EAAE,UAAU,MAAM,GAAG,GAAG,GAAG;GAC7D,MAAM,UAAU,EAAE,YAAY,MAAM,eAAe,EAAE,YAAY,KAAK,KAAK,CAAC;AAE5E,WAAQ,IACN;IACE,EAAE,YAAY,OAAO,GAAG;IACxB,QAAQ,OAAO,GAAG;KACjB,EAAE,YAAY,YAAY,SAAS,OAAO,GAAG;IAC9C;IACD,CAAC,KAAK,KAAK,CACb;;UAEI,KAAK;AACZ,aAAW,yBAAyB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;AACvF,UAAQ,KAAK,EAAE;;;AAInB,SAAS,eAAe,IAAoB;AAC1C,KAAI,MAAM,EAAG,QAAO;CACpB,MAAM,QAAQ,KAAK,MAAM,KAAK,KAAS;CACvC,MAAM,UAAU,KAAK,MAAO,KAAK,OAAY,IAAO;AACpD,KAAI,QAAQ,EAAG,QAAO,GAAG,MAAM,IAAI,QAAQ;AAC3C,QAAO,GAAG,QAAQ"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { n as listProviders } from "./registry-Cp-_Ipc6.mjs";
|
|
3
|
+
import { r as promptSelect, t as printError } from "./ui-CdGEuLwh.mjs";
|
|
4
|
+
import { n as createTokenStore, t as TokenManager } from "./manager-CKGbp7Yz.mjs";
|
|
5
|
+
//#region src/cli/commands/token.ts
|
|
6
|
+
async function tokenCommand(providerArg) {
|
|
7
|
+
await Promise.all([import("./claude-3WKImhqM.mjs"), import("./openai-codex-DJi_Q6Zm.mjs")]);
|
|
8
|
+
const providers = listProviders();
|
|
9
|
+
let providerName = providerArg;
|
|
10
|
+
if (!providerName) providerName = await promptSelect("Select a provider:", providers);
|
|
11
|
+
const manager = new TokenManager(await createTokenStore());
|
|
12
|
+
try {
|
|
13
|
+
const token = await manager.getToken(providerName);
|
|
14
|
+
process.stdout.write(token);
|
|
15
|
+
} catch (err) {
|
|
16
|
+
printError(`Failed to get token: ${err instanceof Error ? err.message : String(err)}`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { tokenCommand };
|
|
22
|
+
|
|
23
|
+
//# sourceMappingURL=token-DF_-h4Rb.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-DF_-h4Rb.mjs","names":[],"sources":["../../src/cli/commands/token.ts"],"sourcesContent":["import { listProviders } from \"@/providers/registry.ts\";\nimport { createTokenStore } from \"@/storage/store.ts\";\nimport { TokenManager } from \"@/token/manager.ts\";\nimport { printError, promptSelect } from \"@/cli/ui.ts\";\n\nexport async function tokenCommand(providerArg?: string): Promise<void> {\n await Promise.all([import(\"@/providers/claude.ts\"), import(\"@/providers/openai-codex.ts\")]);\n\n const providers = listProviders();\n let providerName = providerArg;\n if (!providerName) {\n providerName = await promptSelect(\"Select a provider:\", providers);\n }\n\n const store = await createTokenStore();\n const manager = new TokenManager(store);\n\n try {\n const token = await manager.getToken(providerName);\n process.stdout.write(token);\n } catch (err) {\n printError(`Failed to get token: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n}\n"],"mappings":";;;;;AAKA,eAAsB,aAAa,aAAqC;AACtE,OAAM,QAAQ,IAAI,CAAC,OAAO,0BAA0B,OAAO,+BAA+B,CAAC;CAE3F,MAAM,YAAY,eAAe;CACjC,IAAI,eAAe;AACnB,KAAI,CAAC,aACH,gBAAe,MAAM,aAAa,sBAAsB,UAAU;CAIpE,MAAM,UAAU,IAAI,aADN,MAAM,kBAAkB,CACC;AAEvC,KAAI;EACF,MAAM,QAAQ,MAAM,QAAQ,SAAS,aAAa;AAClD,UAAQ,OAAO,MAAM,MAAM;UACpB,KAAK;AACZ,aAAW,wBAAwB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;AACtF,UAAQ,KAAK,EAAE"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createInterface } from "node:readline";
|
|
3
|
+
//#region src/cli/ui.ts
|
|
4
|
+
function printError(message) {
|
|
5
|
+
process.stderr.write(`\x1b[31merror:\x1b[0m ${message}\n`);
|
|
6
|
+
}
|
|
7
|
+
function printSuccess(message) {
|
|
8
|
+
process.stderr.write(`\x1b[32msuccess:\x1b[0m ${message}\n`);
|
|
9
|
+
}
|
|
10
|
+
function promptSelect(question, options) {
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
|
+
const rl = createInterface({
|
|
13
|
+
input: process.stdin,
|
|
14
|
+
output: process.stderr
|
|
15
|
+
});
|
|
16
|
+
process.stderr.write(`${question}\n`);
|
|
17
|
+
options.forEach((opt, i) => {
|
|
18
|
+
process.stderr.write(` ${i + 1}. ${opt}\n`);
|
|
19
|
+
});
|
|
20
|
+
rl.question("Enter number: ", (answer) => {
|
|
21
|
+
rl.close();
|
|
22
|
+
const selected = options[Number.parseInt(answer, 10) - 1];
|
|
23
|
+
if (selected === void 0) {
|
|
24
|
+
printError("Invalid selection.");
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
resolve(selected);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { printSuccess as n, promptSelect as r, printError as t };
|
|
33
|
+
|
|
34
|
+
//# sourceMappingURL=ui-CdGEuLwh.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-CdGEuLwh.mjs","names":[],"sources":["../../src/cli/ui.ts"],"sourcesContent":["import { createInterface } from \"node:readline\";\n\nexport function printError(message: string): void {\n process.stderr.write(`\\x1b[31merror:\\x1b[0m ${message}\\n`);\n}\n\nexport function printSuccess(message: string): void {\n process.stderr.write(`\\x1b[32msuccess:\\x1b[0m ${message}\\n`);\n}\n\nexport function promptSelect(question: string, options: string[]): Promise<string> {\n return new Promise((resolve) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stderr,\n });\n\n process.stderr.write(`${question}\\n`);\n options.forEach((opt, i) => {\n process.stderr.write(` ${i + 1}. ${opt}\\n`);\n });\n\n rl.question(\"Enter number: \", (answer) => {\n rl.close();\n const index = Number.parseInt(answer, 10) - 1;\n const selected = options[index];\n if (selected === undefined) {\n printError(\"Invalid selection.\");\n process.exit(1);\n }\n resolve(selected);\n });\n });\n}\n"],"mappings":";;;AAEA,SAAgB,WAAW,SAAuB;AAChD,SAAQ,OAAO,MAAM,yBAAyB,QAAQ,IAAI;;AAG5D,SAAgB,aAAa,SAAuB;AAClD,SAAQ,OAAO,MAAM,2BAA2B,QAAQ,IAAI;;AAG9D,SAAgB,aAAa,UAAkB,SAAoC;AACjF,QAAO,IAAI,SAAS,YAAY;EAC9B,MAAM,KAAK,gBAAgB;GACzB,OAAO,QAAQ;GACf,QAAQ,QAAQ;GACjB,CAAC;AAEF,UAAQ,OAAO,MAAM,GAAG,SAAS,IAAI;AACrC,UAAQ,SAAS,KAAK,MAAM;AAC1B,WAAQ,OAAO,MAAM,KAAK,IAAI,EAAE,IAAI,IAAI,IAAI;IAC5C;AAEF,KAAG,SAAS,mBAAmB,WAAW;AACxC,MAAG,OAAO;GAEV,MAAM,WAAW,QADH,OAAO,SAAS,QAAQ,GAAG,GAAG;AAE5C,OAAI,aAAa,KAAA,GAAW;AAC1B,eAAW,qBAAqB;AAChC,YAAQ,KAAK,EAAE;;AAEjB,WAAQ,SAAS;IACjB;GACF"}
|