c3beb370a5
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
119 lines
3.2 KiB
TypeScript
119 lines
3.2 KiB
TypeScript
import { spawnWithTimeout } from "../spawn-with-timeout"
|
|
|
|
export interface GhCliInfo {
|
|
installed: boolean
|
|
version: string | null
|
|
path: string | null
|
|
authenticated: boolean
|
|
username: string | null
|
|
scopes: string[]
|
|
error: string | null
|
|
}
|
|
|
|
async function checkBinaryExists(binary: string): Promise<{ exists: boolean; path: string | null }> {
|
|
try {
|
|
const binaryPath = Bun.which(binary)
|
|
return { exists: Boolean(binaryPath), path: binaryPath ?? null }
|
|
} catch {
|
|
return { exists: false, path: null }
|
|
}
|
|
}
|
|
|
|
async function getGhVersion(): Promise<string | null> {
|
|
try {
|
|
const result = await spawnWithTimeout(["gh", "--version"], { stdout: "pipe", stderr: "pipe" })
|
|
if (result.timedOut || result.exitCode !== 0) return null
|
|
|
|
const matchedVersion = result.stdout.match(/gh version (\S+)/)
|
|
return matchedVersion?.[1] ?? result.stdout.trim().split("\n")[0] ?? null
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
async function getGhAuthStatus(): Promise<{
|
|
authenticated: boolean
|
|
username: string | null
|
|
scopes: string[]
|
|
error: string | null
|
|
}> {
|
|
try {
|
|
const result = await spawnWithTimeout(
|
|
["gh", "auth", "status"],
|
|
{ stdout: "pipe", stderr: "pipe", env: { ...process.env, GH_NO_UPDATE_NOTIFIER: "1" } }
|
|
)
|
|
|
|
if (result.timedOut) {
|
|
return { authenticated: false, username: null, scopes: [], error: "gh auth status timed out" }
|
|
}
|
|
|
|
const output = result.stderr || result.stdout
|
|
if (result.exitCode === 0) {
|
|
const usernameMatch = output.match(/Logged in to github\.com account (\S+)/)
|
|
const scopesMatch = output.match(/Token scopes?:\s*(.+)/i)
|
|
|
|
return {
|
|
authenticated: true,
|
|
username: usernameMatch?.[1]?.replace(/[()]/g, "") ?? null,
|
|
scopes: scopesMatch?.[1]?.split(/,\s*/).map((scope) => scope.trim()).filter(Boolean) ?? [],
|
|
error: null,
|
|
}
|
|
}
|
|
|
|
const errorMatch = output.match(/error[:\s]+(.+)/i)
|
|
return {
|
|
authenticated: false,
|
|
username: null,
|
|
scopes: [],
|
|
error: errorMatch?.[1]?.trim() ?? "Not authenticated",
|
|
}
|
|
} catch (error) {
|
|
return {
|
|
authenticated: false,
|
|
username: null,
|
|
scopes: [],
|
|
error: error instanceof Error ? error.message : "Failed to check auth status",
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function getGhCliInfo(): Promise<GhCliInfo> {
|
|
const binaryStatus = await checkBinaryExists("gh")
|
|
if (!binaryStatus.exists) {
|
|
const version = await getGhVersion()
|
|
if (version) {
|
|
const authStatus = await getGhAuthStatus()
|
|
return {
|
|
installed: true,
|
|
version,
|
|
path: null,
|
|
authenticated: authStatus.authenticated,
|
|
username: authStatus.username,
|
|
scopes: authStatus.scopes,
|
|
error: authStatus.error,
|
|
}
|
|
}
|
|
|
|
return {
|
|
installed: false,
|
|
version: null,
|
|
path: null,
|
|
authenticated: false,
|
|
username: null,
|
|
scopes: [],
|
|
error: null,
|
|
}
|
|
}
|
|
|
|
const [version, authStatus] = await Promise.all([getGhVersion(), getGhAuthStatus()])
|
|
return {
|
|
installed: true,
|
|
version,
|
|
path: binaryStatus.path,
|
|
authenticated: authStatus.authenticated,
|
|
username: authStatus.username,
|
|
scopes: authStatus.scopes,
|
|
error: authStatus.error,
|
|
}
|
|
}
|