9993c96907
Move provider auth and OAuth flows onto pi-ai Models, compose models.json and extension overlays through ModelRuntime, and retain ModelRegistry as an extension compatibility facade.
119 lines
4.2 KiB
JavaScript
119 lines
4.2 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
import { createInterface } from "node:readline";
|
|
import type { AuthPrompt, OAuthCredential, Provider } from "./index.ts";
|
|
import { builtinProviders } from "./providers/all.ts";
|
|
|
|
const AUTH_FILE = "auth.json";
|
|
const PROVIDERS = builtinProviders().filter(
|
|
(provider): provider is Provider & { auth: { oauth: NonNullable<Provider["auth"]["oauth"]> } } =>
|
|
provider.auth.oauth !== undefined,
|
|
);
|
|
|
|
function prompt(rl: ReturnType<typeof createInterface>, question: string): Promise<string> {
|
|
return new Promise((resolve) => rl.question(question, resolve));
|
|
}
|
|
|
|
function loadAuth(): Record<string, OAuthCredential> {
|
|
if (!existsSync(AUTH_FILE)) return {};
|
|
try {
|
|
return JSON.parse(readFileSync(AUTH_FILE, "utf-8")) as Record<string, OAuthCredential>;
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
function saveAuth(auth: Record<string, OAuthCredential>): void {
|
|
writeFileSync(AUTH_FILE, JSON.stringify(auth, null, 2), "utf-8");
|
|
}
|
|
|
|
async function answerPrompt(rl: ReturnType<typeof createInterface>, authPrompt: AuthPrompt): Promise<string> {
|
|
if (authPrompt.type === "select") {
|
|
console.log(`\n${authPrompt.message}`);
|
|
for (let index = 0; index < authPrompt.options.length; index++) {
|
|
console.log(` ${index + 1}. ${authPrompt.options[index].label}`);
|
|
}
|
|
const choice = Number.parseInt(await prompt(rl, `Enter number (1-${authPrompt.options.length}): `), 10) - 1;
|
|
const selected = authPrompt.options[choice];
|
|
if (!selected) throw new Error("Invalid selection");
|
|
return selected.id;
|
|
}
|
|
return prompt(rl, `${authPrompt.message}${authPrompt.placeholder ? ` (${authPrompt.placeholder})` : ""}: `);
|
|
}
|
|
|
|
async function login(providerId: string): Promise<void> {
|
|
const provider = PROVIDERS.find((entry) => entry.id === providerId);
|
|
if (!provider) throw new Error(`Unknown provider: ${providerId}`);
|
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
try {
|
|
const credential = await provider.auth.oauth.login({
|
|
prompt: (authPrompt) => answerPrompt(rl, authPrompt),
|
|
notify: (event) => {
|
|
switch (event.type) {
|
|
case "auth_url":
|
|
console.log(`\nOpen this URL in your browser:\n${event.url}`);
|
|
if (event.instructions) console.log(event.instructions);
|
|
break;
|
|
case "device_code":
|
|
console.log(`\nOpen this URL in your browser:\n${event.verificationUri}`);
|
|
console.log(`Enter code: ${event.userCode}`);
|
|
break;
|
|
case "info":
|
|
case "progress":
|
|
console.log(event.message);
|
|
break;
|
|
}
|
|
},
|
|
});
|
|
const auth = loadAuth();
|
|
auth[providerId] = credential;
|
|
saveAuth(auth);
|
|
console.log(`\nCredentials saved to ${AUTH_FILE}`);
|
|
} finally {
|
|
rl.close();
|
|
}
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
const args = process.argv.slice(2);
|
|
const command = args[0];
|
|
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
const providerList = PROVIDERS.map((provider) => ` ${provider.id.padEnd(20)} ${provider.name}`).join("\n");
|
|
console.log(
|
|
`Usage: npx @earendil-works/pi-ai <command> [provider]\n\nCommands:\n login [provider] Login to an OAuth provider\n list List available providers\n\nProviders:\n${providerList}`,
|
|
);
|
|
return;
|
|
}
|
|
if (command === "list") {
|
|
for (const provider of PROVIDERS) console.log(`${provider.id.padEnd(20)} ${provider.name}`);
|
|
return;
|
|
}
|
|
if (command === "login") {
|
|
let providerId = args[1];
|
|
if (!providerId) {
|
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
try {
|
|
for (let index = 0; index < PROVIDERS.length; index++) {
|
|
console.log(` ${index + 1}. ${PROVIDERS[index].name}`);
|
|
}
|
|
const index = Number.parseInt(await prompt(rl, `Enter number (1-${PROVIDERS.length}): `), 10) - 1;
|
|
providerId = PROVIDERS[index]?.id;
|
|
} finally {
|
|
rl.close();
|
|
}
|
|
}
|
|
if (!providerId || !PROVIDERS.some((provider) => provider.id === providerId)) {
|
|
throw new Error(`Unknown provider: ${providerId ?? ""}`);
|
|
}
|
|
await login(providerId);
|
|
return;
|
|
}
|
|
throw new Error(`Unknown command: ${command}`);
|
|
}
|
|
|
|
main().catch((error: unknown) => {
|
|
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
process.exit(1);
|
|
});
|