mirror of https://github.com/openclaw/openclaw.git
164 lines
4.5 KiB
TypeScript
164 lines
4.5 KiB
TypeScript
import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
|
|
import { delimiter, dirname, join } from "node:path";
|
|
import { CLIENT_ID_KEYS, CLIENT_SECRET_KEYS } from "./oauth.shared.js";
|
|
|
|
function resolveEnv(keys: string[]): string | undefined {
|
|
for (const key of keys) {
|
|
const value = process.env[key]?.trim();
|
|
if (value) {
|
|
return value;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
let cachedGeminiCliCredentials: { clientId: string; clientSecret: string } | null = null;
|
|
|
|
export function clearCredentialsCache(): void {
|
|
cachedGeminiCliCredentials = null;
|
|
}
|
|
|
|
export function extractGeminiCliCredentials(): { clientId: string; clientSecret: string } | null {
|
|
if (cachedGeminiCliCredentials) {
|
|
return cachedGeminiCliCredentials;
|
|
}
|
|
|
|
try {
|
|
const geminiPath = findInPath("gemini");
|
|
if (!geminiPath) {
|
|
return null;
|
|
}
|
|
|
|
const resolvedPath = realpathSync(geminiPath);
|
|
const geminiCliDirs = resolveGeminiCliDirs(geminiPath, resolvedPath);
|
|
|
|
let content: string | null = null;
|
|
for (const geminiCliDir of geminiCliDirs) {
|
|
const searchPaths = [
|
|
join(
|
|
geminiCliDir,
|
|
"node_modules",
|
|
"@google",
|
|
"gemini-cli-core",
|
|
"dist",
|
|
"src",
|
|
"code_assist",
|
|
"oauth2.js",
|
|
),
|
|
join(
|
|
geminiCliDir,
|
|
"node_modules",
|
|
"@google",
|
|
"gemini-cli-core",
|
|
"dist",
|
|
"code_assist",
|
|
"oauth2.js",
|
|
),
|
|
];
|
|
|
|
for (const path of searchPaths) {
|
|
if (existsSync(path)) {
|
|
content = readFileSync(path, "utf8");
|
|
break;
|
|
}
|
|
}
|
|
if (content) {
|
|
break;
|
|
}
|
|
const found = findFile(geminiCliDir, "oauth2.js", 10);
|
|
if (found) {
|
|
content = readFileSync(found, "utf8");
|
|
break;
|
|
}
|
|
}
|
|
if (!content) {
|
|
return null;
|
|
}
|
|
|
|
const idMatch = content.match(/(\d+-[a-z0-9]+\.apps\.googleusercontent\.com)/);
|
|
const secretMatch = content.match(/(GOCSPX-[A-Za-z0-9_-]+)/);
|
|
if (idMatch && secretMatch) {
|
|
cachedGeminiCliCredentials = { clientId: idMatch[1], clientSecret: secretMatch[1] };
|
|
return cachedGeminiCliCredentials;
|
|
}
|
|
} catch {
|
|
// Gemini CLI not installed or extraction failed
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function resolveGeminiCliDirs(geminiPath: string, resolvedPath: string): string[] {
|
|
const binDir = dirname(geminiPath);
|
|
const candidates = [
|
|
dirname(dirname(resolvedPath)),
|
|
join(dirname(resolvedPath), "node_modules", "@google", "gemini-cli"),
|
|
join(binDir, "node_modules", "@google", "gemini-cli"),
|
|
join(dirname(binDir), "node_modules", "@google", "gemini-cli"),
|
|
join(dirname(binDir), "lib", "node_modules", "@google", "gemini-cli"),
|
|
];
|
|
|
|
const deduped: string[] = [];
|
|
const seen = new Set<string>();
|
|
for (const candidate of candidates) {
|
|
const key =
|
|
process.platform === "win32" ? candidate.replace(/\\/g, "/").toLowerCase() : candidate;
|
|
if (seen.has(key)) {
|
|
continue;
|
|
}
|
|
seen.add(key);
|
|
deduped.push(candidate);
|
|
}
|
|
return deduped;
|
|
}
|
|
|
|
function findInPath(name: string): string | null {
|
|
const exts = process.platform === "win32" ? [".cmd", ".bat", ".exe", ""] : [""];
|
|
for (const dir of (process.env.PATH ?? "").split(delimiter)) {
|
|
for (const ext of exts) {
|
|
const path = join(dir, name + ext);
|
|
if (existsSync(path)) {
|
|
return path;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function findFile(dir: string, name: string, depth: number): string | null {
|
|
if (depth <= 0) {
|
|
return null;
|
|
}
|
|
try {
|
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
const path = join(dir, entry.name);
|
|
if (entry.isFile() && entry.name === name) {
|
|
return path;
|
|
}
|
|
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
const found = findFile(path, name, depth - 1);
|
|
if (found) {
|
|
return found;
|
|
}
|
|
}
|
|
}
|
|
} catch {}
|
|
return null;
|
|
}
|
|
|
|
export function resolveOAuthClientConfig(): { clientId: string; clientSecret?: string } {
|
|
const envClientId = resolveEnv(CLIENT_ID_KEYS);
|
|
const envClientSecret = resolveEnv(CLIENT_SECRET_KEYS);
|
|
if (envClientId) {
|
|
return { clientId: envClientId, clientSecret: envClientSecret };
|
|
}
|
|
|
|
const extracted = extractGeminiCliCredentials();
|
|
if (extracted) {
|
|
return extracted;
|
|
}
|
|
|
|
throw new Error(
|
|
"Gemini CLI not found. Install it first: brew install gemini-cli (or npm install -g @google/gemini-cli), or set GEMINI_CLI_OAUTH_CLIENT_ID.",
|
|
);
|
|
}
|