#!/usr/bin/env node import { existsSync, readFileSync, writeFileSync } from "fs"; import { createInterface } from "readline"; import { loginAnthropic } from "./utils/oauth/anthropic.js"; import { loginGitHubCopilot } from "./utils/oauth/github-copilot.js"; import { loginAntigravity } from "./utils/oauth/google-antigravity.js"; import { loginGeminiCli } from "./utils/oauth/google-gemini-cli.js"; import { getOAuthProviders } from "./utils/oauth/index.js"; import { loginOpenAICodex } from "./utils/oauth/openai-codex.js"; import type { OAuthCredentials, OAuthProvider } from "./utils/oauth/types.js"; const AUTH_FILE = "auth.json"; const PROVIDERS = getOAuthProviders(); function prompt(rl: ReturnType, question: string): Promise { return new Promise((resolve) => rl.question(question, resolve)); } function loadAuth(): Record { if (!existsSync(AUTH_FILE)) return {}; try { return JSON.parse(readFileSync(AUTH_FILE, "utf-8")); } catch { return {}; } } function saveAuth(auth: Record): void { writeFileSync(AUTH_FILE, JSON.stringify(auth, null, 2), "utf-8"); } async function login(provider: OAuthProvider): Promise { const rl = createInterface({ input: process.stdin, output: process.stdout }); const promptFn = (msg: string) => prompt(rl, `${msg} `); try { let credentials: OAuthCredentials; switch (provider) { case "anthropic": credentials = await loginAnthropic( (url) => { console.log(`\nOpen this URL in your browser:\n${url}\n`); }, async () => { return await promptFn("Paste the authorization code:"); }, ); break; case "github-copilot": credentials = await loginGitHubCopilot({ onAuth: (url, instructions) => { console.log(`\nOpen this URL in your browser:\n${url}`); if (instructions) console.log(instructions); console.log(); }, onPrompt: async (p) => { return await promptFn(`${p.message}${p.placeholder ? ` (${p.placeholder})` : ""}:`); }, onProgress: (msg) => console.log(msg), }); break; case "google-gemini-cli": credentials = await loginGeminiCli( (info) => { console.log(`\nOpen this URL in your browser:\n${info.url}`); if (info.instructions) console.log(info.instructions); console.log(); }, (msg) => console.log(msg), ); break; case "google-antigravity": credentials = await loginAntigravity( (info) => { console.log(`\nOpen this URL in your browser:\n${info.url}`); if (info.instructions) console.log(info.instructions); console.log(); }, (msg) => console.log(msg), ); break; case "openai-codex": credentials = await loginOpenAICodex({ onAuth: (info) => { console.log(`\nOpen this URL in your browser:\n${info.url}`); if (info.instructions) console.log(info.instructions); console.log(); }, onPrompt: async (p) => { return await promptFn(`${p.message}${p.placeholder ? ` (${p.placeholder})` : ""}:`); }, onProgress: (msg) => console.log(msg), }); break; } const auth = loadAuth(); auth[provider] = { type: "oauth", ...credentials }; saveAuth(auth); console.log(`\nCredentials saved to ${AUTH_FILE}`); } finally { rl.close(); } } async function main(): Promise { const args = process.argv.slice(2); const command = args[0]; if (!command || command === "help" || command === "--help" || command === "-h") { console.log(`Usage: npx @mariozechner/pi-ai [provider] Commands: login [provider] Login to an OAuth provider list List available providers Providers: anthropic Anthropic (Claude Pro/Max) github-copilot GitHub Copilot google-gemini-cli Google Gemini CLI google-antigravity Antigravity (Gemini 3, Claude, GPT-OSS) openai-codex OpenAI Codex (ChatGPT Plus/Pro) Examples: npx @mariozechner/pi-ai login # interactive provider selection npx @mariozechner/pi-ai login anthropic # login to specific provider npx @mariozechner/pi-ai list # list providers `); return; } if (command === "list") { console.log("Available OAuth providers:\n"); for (const p of PROVIDERS) { console.log(` ${p.id.padEnd(20)} ${p.name}`); } return; } if (command === "login") { let provider = args[1] as OAuthProvider | undefined; if (!provider) { const rl = createInterface({ input: process.stdin, output: process.stdout }); console.log("Select a provider:\n"); for (let i = 0; i < PROVIDERS.length; i++) { console.log(` ${i + 1}. ${PROVIDERS[i].name}`); } console.log(); const choice = await prompt(rl, `Enter number (1-${PROVIDERS.length}): `); rl.close(); const index = parseInt(choice, 10) - 1; if (index < 0 || index >= PROVIDERS.length) { console.error("Invalid selection"); process.exit(1); } provider = PROVIDERS[index].id; } if (!PROVIDERS.some((p) => p.id === provider)) { console.error(`Unknown provider: ${provider}`); console.error(`Use 'npx @mariozechner/pi-ai list' to see available providers`); process.exit(1); } console.log(`Logging in to ${provider}...`); await login(provider); return; } console.error(`Unknown command: ${command}`); console.error(`Use 'npx @mariozechner/pi-ai --help' for usage`); process.exit(1); } main().catch((err) => { console.error("Error:", err.message); process.exit(1); });