rpg-combat-pi-01/.pi/extensions/clear-export.ts
Willem van den Ende 934551197c fix: use pi.getAllTools() instead of ctx.getAllTools()
getAllTools() is on the ExtensionAPI (pi), not on ExtensionContext (ctx).
2026-06-12 22:52:36 +01:00

119 lines
4.0 KiB
TypeScript

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");
}
},
});
}