/** * pi-switch extension entry. * * Pure logic lives under ../src; runtime IO and command wiring live in * ./runtime.ts, ./commands.ts, ./switch-lifecycle.ts. * Runtime notes (from old cc-switch extension): * - bun:sqlite is NOT available → shell out to sqlite3 CLI * - node:* builtins must be dynamic-imported inside the async factory */ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; import { findOwningPackageVersion } from "../src/host-package-version.ts"; import { resolveSqlitePath } from "../src/sqlite-path.ts"; import { Runtime } from "./runtime.ts"; import { registerCommands } from "./commands.ts"; import { createSwitchLifecycle } from "./switch-lifecycle.ts"; import { installClaudeCodeCompat } from "./claude-code-compat.ts"; import { installGeminiToolCompat } from "./gemini-tool-compat.ts"; import { installDeveloperRoleCompat } from "./developer-role-compat.ts"; export default async function (pi: ExtensionAPI) { const [cp, cryptoMod, fs, osMod, pathMod, urlMod, httpMod] = await Promise.all([ import("node:child_process"), import("node:crypto"), import("node:fs"), import("node:os"), import("node:path"), import("node:url"), import("node:http"), ]); const probeHttp = (url: string, timeoutMs: number): Promise => new Promise((resolve) => { const req = httpMod.get(url, (res) => { res.resume(); resolve(true); }); req.setTimeout(timeoutMs, () => { req.destroy(); resolve(false); }); req.on("error", () => resolve(false)); }); const extensionDir = pathMod.dirname(urlMod.fileURLToPath(import.meta.url)); const snapshotPath = pathMod.join(extensionDir, "..", "defaults", "fingerprint-snapshot.json"); const packageVersionDeps = { existsSync: fs.existsSync, readFileSync: fs.readFileSync, dirname: pathMod.dirname, join: pathMod.join, resolve: pathMod.resolve, }; const resolvePackageVersion = (name: string): string | undefined => { const hostVersion = findOwningPackageVersion( process.argv[1], name, packageVersionDeps, ); if (hostVersion) return hostVersion; try { const entry = urlMod.fileURLToPath(import.meta.resolve(name)); return findOwningPackageVersion(entry, name, packageVersionDeps); } catch { return undefined; } }; const rt = new Runtime({ execFileSync: cp.execFileSync, existsSync: fs.existsSync, readFileSync: fs.readFileSync, writeFileSync: fs.writeFileSync, renameSync: fs.renameSync, unlinkSync: fs.unlinkSync, randomUUID: cryptoMod.randomUUID, resolvePackageVersion, snapshotPath, probeHttp, fetchJson: async (url: string) => { const res = await fetch(url); if (!res.ok) throw new Error(`fetch ${url}: HTTP ${res.status}`); return res.json(); }, release: osMod.release(), home: osMod.homedir(), }); rt.reloadConfig(); rt.reloadHeaderRules(); const resolved = resolveSqlitePath({ configPath: rt.config.sqlitePath, exists: rt.io.existsSync, }); rt.sqlite3Tried = resolved.tried ?? []; if (!resolved.path) { console.error( "[pi-switch] sqlite3 not found. Set SQLITE3_PATH or pi-switch.json.sqlitePath. Tried:", resolved.tried, ); rt.sqlite3Path = ""; } else { rt.sqlite3Path = resolved.path; } const lifecycle = createSwitchLifecycle(pi, rt); lifecycle.install(); installClaudeCodeCompat(pi, rt); installGeminiToolCompat(pi, rt); installDeveloperRoleCompat(pi, rt); registerCommands(pi, rt, lifecycle); }