Files
oh-my-pi/packages/ai/src/cli.ts
T

189 lines
5.3 KiB
TypeScript
Executable File

#!/usr/bin/env bun
import { logger } from "@oh-my-pi/pi-coding-agent";
import { loginAnthropic } from "./utils/oauth/anthropic";
import { loginGitHubCopilot } from "./utils/oauth/github-copilot";
import { loginAntigravity } from "./utils/oauth/google-antigravity";
import { loginGeminiCli } from "./utils/oauth/google-gemini-cli";
import { getOAuthProviders } from "./utils/oauth/index";
import { loginOpenAICodex } from "./utils/oauth/openai-codex";
import type { OAuthCredentials, OAuthProvider } from "./utils/oauth/types";
const AUTH_FILE = "auth.json";
const PROVIDERS = getOAuthProviders();
function prompt(question: string): Promise<string> {
return new Promise((resolve) => {
process.stdout.write(question);
process.stdin.resume();
process.stdin.once("data", (data) => {
process.stdin.pause();
resolve(data.toString().trim());
});
});
}
async function loadAuth(): Promise<Record<string, { type: "oauth" } & OAuthCredentials>> {
const file = Bun.file(AUTH_FILE);
if (!(await file.exists())) return {};
try {
return await file.json();
} catch (err) {
logger.debug("Failed to parse config file", { error: String(err) });
return {};
}
}
async function saveAuth(auth: Record<string, { type: "oauth" } & OAuthCredentials>): Promise<void> {
await Bun.write(AUTH_FILE, JSON.stringify(auth, null, 2));
}
async function login(provider: OAuthProvider): Promise<void> {
const promptFn = (msg: string) => prompt(`${msg} `);
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 = await loadAuth();
auth[provider] = { type: "oauth", ...credentials };
await saveAuth(auth);
console.log(`\nCredentials saved to ${AUTH_FILE}`);
}
async function main(): Promise<void> {
const args = process.argv.slice(2);
const command = args[0];
if (!command || command === "help" || command === "--help" || command === "-h") {
console.log(`Usage: npx @oh-my-pi/pi-ai <command> [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 @oh-my-pi/pi-ai login # interactive provider selection
npx @oh-my-pi/pi-ai login anthropic # login to specific provider
npx @oh-my-pi/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) {
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(`Enter number (1-${PROVIDERS.length}): `);
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 @oh-my-pi/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 @oh-my-pi/pi-ai --help' for usage`);
process.exit(1);
}
main().catch((err) => {
console.error("Error:", err.message);
process.exit(1);
});