import type { ExtensionAPI, ToolInfo } from '@earendil-works/pi-coding-agent'; import { fileURLToPath } from 'node:url'; import { homedir } from 'node:os'; import * as fs from 'node:fs'; import * as path from 'node:path'; // Resolve the pi-coding-agent package directory // Search in: project node_modules, then global mise node_modules function findPkgDir(): string { // Try project node_modules first const projectPkg = path.join(process.cwd(), 'node_modules', '@earendil-works', 'pi-coding-agent'); if (fs.existsSync(projectPkg)) return projectPkg; // Try global mise node_modules const home = homedir(); const globalPkg = path.join( home, '.local', 'share', 'mise', 'installs', 'node', '*', 'lib', 'node_modules', '@earendil-works', 'pi-coding-agent', ); const matches = fs .readdirSync(path.join(home, '.local', 'share', 'mise', 'installs', 'node')) .filter((d) => fs.existsSync(path.join(home, '.local', 'share', 'mise', 'installs', 'node', d)), ); for (const version of matches) { const candidate = path.join( home, '.local', 'share', 'mise', 'installs', 'node', version, 'lib', 'node_modules', '@earendil-works', 'pi-coding-agent', ); if (fs.existsSync(candidate)) return candidate; } // Fallback: try the directory of the loaded module const extPath = fileURLToPath(import.meta.url); let dir = extPath; for (let i = 0; i < 20; i++) { const candidate = path.join(dir, 'node_modules', '@earendil-works', 'pi-coding-agent'); if (fs.existsSync(candidate)) return candidate; const parent = path.dirname(dir); if (parent === dir) break; dir = parent; } throw new Error('Could not find @earendil-works/pi-coding-agent package'); } const pkgDir = findPkgDir(); const exportHtmlModule = await import(path.join(pkgDir, 'dist', 'core', 'export-html', 'index.js')); const { exportSessionToHtml } = exportHtmlModule; export default function (pi: ExtensionAPI) { pi.registerCommand('clear', { description: 'Export session to transcripts/ and start a new session', handler: async (_args, ctx) => { if (ctx.mode !== 'tui') { ctx.ui.notify('clear requires interactive mode', 'error'); return; } // Prompt for transcript name const name = await ctx.ui.input('Clear & Export', 'Transcript name (spaces become dashes)', { timeout: 60000, }); if (!name || !name.trim()) { ctx.ui.notify('Clear & Export cancelled', 'info'); return; } // Sanitize: spaces → dashes, ensure .html extension const sanitizedName = name.trim().replace(/\s+/g, '-'); const fileName = sanitizedName.endsWith('.html') ? sanitizedName : `${sanitizedName}.html`; // Build output path in transcripts/ const transcriptsDir = path.join(ctx.cwd, 'transcripts'); const outputPath = path.join(transcriptsDir, fileName); // Ensure transcripts directory exists if (!fs.existsSync(transcriptsDir)) { fs.mkdirSync(transcriptsDir, { recursive: true }); } // Get session file path const sessionFile = ctx.sessionManager.getSessionFile(); if (!sessionFile) { ctx.ui.notify('No session to export', 'error'); return; } // Build state from in-memory data (not available in a separate pi process) const state = { systemPrompt: ctx.getSystemPrompt(), tools: pi.getAllTools(), }; // Export session to HTML with full in-memory state await exportSessionToHtml(ctx.sessionManager, state, { outputPath }); ctx.ui.notify(`Exported to: transcripts/${fileName}`, 'info'); // Start a new session const currentSessionFile = ctx.sessionManager.getSessionFile(); const newResult = await ctx.newSession({ parentSession: currentSessionFile, withSession: (replacementCtx) => { replacementCtx.ui.notify('Session cleared and exported', 'info'); }, }); if (newResult.cancelled) { ctx.ui.notify('New session cancelled', 'info'); } }, }); }