import fs from 'fs/promises'; import fsSync from 'fs'; import path from 'path'; export interface RigstateManifest { project_id: string; api_url?: string; linked_at?: string; api_key?: string; // Genesis Protocol status (cached locally to avoid API calls) genesis_complete?: boolean; genesis_template?: string; genesis_stack_key?: string; genesis_initialized_at?: string; } export async function loadManifest(): Promise { const cwd = process.cwd(); const manifestPaths = [ path.join(cwd, '.rigstate', 'identity.json'), path.join(cwd, '.rigstate') ]; for (const p of manifestPaths) { try { if (fsSync.existsSync(p) && fsSync.statSync(p).isFile()) { const content = await fs.readFile(p, 'utf-8'); const data = JSON.parse(content); // Handle both flat manifest and identity.json schema return { project_id: data.project?.id || data.project_id, api_url: data.api_url, linked_at: data.linked_at || data.project?.created_at, api_key: data.api_key }; } } catch (e) { continue; } } return null; } /** * Saves project context to the local manifest. * Prioritizes .rigstate/identity.json if the directory exists. */ export async function saveManifest(data: Partial): Promise { const cwd = process.cwd(); const rigstatePath = path.join(cwd, '.rigstate'); let targetFile = rigstatePath; try { const stats = await fs.stat(rigstatePath); if (stats.isDirectory()) { targetFile = path.join(rigstatePath, 'identity.json'); } } catch (e) { // Doesn't exist, will be created as file/dir below } // Load existing to merge const existing = await loadManifest() || {} as RigstateManifest; const merged = { ...existing, ...data }; // If we need to create a directory for identity.json if (targetFile.endsWith('identity.json')) { await fs.mkdir(rigstatePath, { recursive: true }); } await fs.writeFile(targetFile, JSON.stringify(merged, null, 2), 'utf-8'); return targetFile; }