singularity-forge/src/pi-migration.ts
TÂCHES 2a3c2b5194 Merge pull request #151 from dbachelder/fix/pi-provider-reuse-and-extension-loading
fix: reuse Pi provider config and load Pi extensions correctly
2026-03-12 22:25:15 -06:00

79 lines
2.4 KiB
TypeScript

/**
* One-time migration of provider credentials from Pi (~/.pi/agent/auth.json)
* into GSD's auth storage. Runs when GSD has no LLM providers configured,
* so users with an existing Pi install skip re-authentication.
*/
import { existsSync, readFileSync } from 'node:fs'
import { homedir } from 'node:os'
import { join } from 'node:path'
import type { AuthStorage, AuthCredential } from '@gsd/pi-coding-agent'
const PI_AUTH_PATH = join(homedir(), '.pi', 'agent', 'auth.json')
const PI_SETTINGS_PATH = join(homedir(), '.pi', 'agent', 'settings.json')
const LLM_PROVIDER_IDS = [
'anthropic',
'openai',
'github-copilot',
'openai-codex',
'google-gemini-cli',
'google-antigravity',
'google',
'groq',
'xai',
'openrouter',
'mistral',
]
/**
* Migrate provider credentials from Pi's auth.json into GSD's AuthStorage.
*
* Only runs when GSD has no LLM provider configured and Pi's auth.json exists.
* Copies any credentials GSD doesn't already have. Returns true if an LLM
* provider was migrated (so onboarding can be skipped).
*/
export function migratePiCredentials(authStorage: AuthStorage): boolean {
try {
const existing = authStorage.list()
const hasLlm = existing.some(id => LLM_PROVIDER_IDS.includes(id))
if (hasLlm) return false
if (!existsSync(PI_AUTH_PATH)) return false
const raw = readFileSync(PI_AUTH_PATH, 'utf-8')
const piData = JSON.parse(raw) as Record<string, AuthCredential>
let migratedLlm = false
for (const [providerId, credential] of Object.entries(piData)) {
if (authStorage.has(providerId)) continue
authStorage.set(providerId, credential)
const isLlm = LLM_PROVIDER_IDS.includes(providerId)
if (isLlm) migratedLlm = true
process.stderr.write(`[gsd] Migrated ${isLlm ? 'LLM provider' : 'credential'}: ${providerId} (from Pi)\n`)
}
return migratedLlm
} catch {
return false
}
}
export function getPiDefaultModelAndProvider(): { provider: string; model: string } | null {
try {
if (!existsSync(PI_SETTINGS_PATH)) return null
const raw = readFileSync(PI_SETTINGS_PATH, 'utf-8')
const data = JSON.parse(raw) as { defaultProvider?: unknown; defaultModel?: unknown }
if (typeof data.defaultProvider !== 'string' || typeof data.defaultModel !== 'string') {
return null
}
return {
provider: data.defaultProvider,
model: data.defaultModel,
}
} catch {
return null
}
}