Commit 9cd49ff4e3ca
Changed files (1)
dots
pi
agent
extensions
dots/pi/agent/extensions/session-history.ts
@@ -0,0 +1,253 @@
+/**
+ * Session History Extension for Pi
+ *
+ * Integrates pi with Claude Code's history system.
+ * Auto-detects the tool being used (pi, claude, copilot, etc.)
+ *
+ * Features:
+ * - /save-session: Triggers AI to generate and save session summary
+ * - /session-log: Shows today's session activity
+ * - Auto-logs session starts
+ *
+ * Compatible with Claude Code's hook system and history structure.
+ */
+
+import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
+import { writeFile, mkdir, appendFile, readFile } from "node:fs/promises";
+import { existsSync } from "node:fs";
+import { join } from "node:path";
+import { homedir, hostname } from "node:os";
+
+export default function (pi: ExtensionAPI) {
+ const HISTORY_DIR = join(homedir(), ".config", "claude", "history");
+ const SESSIONS_DIR = join(HISTORY_DIR, "sessions");
+
+ /**
+ * Detect which tool is being used
+ */
+ function detectTool(): string {
+ // Check environment variables
+ if (process.env.CLAUDE_PROJECT_DIR || process.env.CLAUDE_AGENT_TYPE) {
+ return "claude";
+ }
+ if (process.env.PI_VERSION || process.env.PI_PROJECT_DIR) {
+ return "pi";
+ }
+ // Check if running under specific tools
+ const execPath = process.argv0 || "";
+ if (execPath.includes("copilot")) {
+ return "copilot";
+ }
+ if (execPath.includes("cursor")) {
+ return "cursor";
+ }
+ if (execPath.includes("pi")) {
+ return "pi";
+ }
+ if (execPath.includes("claude")) {
+ return "claude";
+ }
+
+ // Default to pi since this is a pi extension
+ return "pi";
+ }
+
+ function getDateInfo() {
+ const now = new Date();
+ const year = now.getFullYear();
+ const month = String(now.getMonth() + 1).padStart(2, "0");
+ const day = String(now.getDate()).padStart(2, "0");
+ const hours = String(now.getHours()).padStart(2, "0");
+ const minutes = String(now.getMinutes()).padStart(2, "0");
+ const seconds = String(now.getSeconds()).padStart(2, "0");
+
+ return {
+ yearMonth: `${year}-${month}`,
+ date: `${year}-${month}-${day}`,
+ timestamp: `${year}-${month}-${day}T${hours}:${minutes}:${seconds}+01:00`,
+ time: `${hours}:${minutes}`,
+ };
+ }
+
+ async function logSessionStart() {
+ const tool = detectTool();
+ const { yearMonth, date, timestamp } = getDateInfo();
+ const sessionDir = join(SESSIONS_DIR, yearMonth);
+ const logFile = join(sessionDir, `${date}_session-log.txt`);
+
+ await mkdir(sessionDir, { recursive: true });
+ await appendFile(logFile, `${timestamp} - Session started (${tool})\n`);
+ }
+
+ async function appendToSessionLog(message: string) {
+ const { yearMonth, date, timestamp } = getDateInfo();
+ const sessionDir = join(SESSIONS_DIR, yearMonth);
+ const logFile = join(sessionDir, `${date}_session-log.txt`);
+
+ await mkdir(sessionDir, { recursive: true });
+ await appendFile(logFile, `${timestamp} - ${message}\n`);
+ }
+
+ // Internal command to actually save the session (called by AI after generating summary)
+ pi.registerTool({
+ name: "save_session_to_history",
+ label: "Save Session to History",
+ description:
+ "Saves a session summary to ~/.config/claude/history/sessions/. Works with any AI coding tool (pi, claude, copilot, cursor). Auto-detects which tool is being used.",
+ parameters: {
+ type: "object",
+ properties: {
+ description: {
+ type: "string",
+ description: "Brief description for the filename (e.g., 'added-hostname-extension')",
+ },
+ content: {
+ type: "string",
+ description: "Full markdown content following the Session Entry template from history-system.md",
+ },
+ },
+ required: ["description", "content"],
+ },
+ async execute(toolCallId, params, signal, onUpdate, ctx) {
+ try {
+ const { description, content } = params;
+ const { yearMonth, date, time } = getDateInfo();
+ const sessionDir = join(SESSIONS_DIR, yearMonth);
+
+ // Sanitize filename
+ const slug = description
+ .toLowerCase()
+ .replace(/[^a-z0-9]+/g, "-")
+ .replace(/^-+|-+$/g, "")
+ .substring(0, 60);
+
+ const filename = `${date}-${slug}.md`;
+ const filepath = join(sessionDir, filename);
+
+ // Create directory
+ await mkdir(sessionDir, { recursive: true });
+
+ // Write file
+ await writeFile(filepath, content, "utf-8");
+
+ // Append to session log
+ await appendToSessionLog(`Session saved: ${filename}`);
+
+ return {
+ content: [
+ {
+ type: "text",
+ text: `โ Session saved to: ${filepath}`,
+ },
+ ],
+ details: {},
+ };
+ } catch (error) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: `Error saving session: ${error}`,
+ },
+ ],
+ details: { error: String(error) },
+ };
+ }
+ },
+ });
+
+ // Log session start
+ pi.on("session_start", async (_event, ctx) => {
+ try {
+ await logSessionStart();
+ // Show hint
+ ctx.ui.setStatus("session", ctx.ui.theme.fg("dim", "๐พ /save-session"));
+ } catch (error) {
+ // Silent failure
+ }
+ });
+
+ // Register /save-session command
+ pi.registerCommand("save-session", {
+ description: "Generate and save a session summary to history (auto-detects tool)",
+ handler: async (_args, ctx) => {
+ const tool = detectTool();
+
+ // This triggers a message that prompts the AI to generate a summary
+ ctx.ui.notify("Generating session summary...", "info");
+
+ // Insert a prompt for the AI
+ const prompt = `Please generate a session summary for this conversation following the Session Entry template from the CORE skill's history-system.md.
+
+The summary should include:
+- A descriptive title
+- What was accomplished
+- Files that were changed
+- Commands that were run
+- The outcome
+- Any next steps
+
+Use the Session Entry template format:
+
+\`\`\`markdown
+# Session: <Description>
+
+**Date:** ${getDateInfo().date}
+**Time:** ${getDateInfo().time}
+**Host:** ${hostname()}
+**Tool:** ${tool}
+
+## Summary
+Brief description of what was accomplished.
+
+## What Was Accomplished
+- Task 1
+- Task 2
+
+## Files Changed
+- \`path/to/file\` - Description of change
+
+## Commands Run
+\`\`\`bash
+# Key commands executed
+\`\`\`
+
+## Outcome
+Result of the session.
+
+## Next Steps
+- [ ] TODO 1
+- [ ] TODO 2
+
+### Tags
+#${tool} #relevant-tags
+\`\`\`
+
+After generating the summary, use the save_session_to_history tool to save it with an appropriate filename description.`;
+
+ // Set the editor text to trigger the AI
+ ctx.ui.setEditorText(prompt);
+ },
+ });
+
+ // Register /session-log command
+ pi.registerCommand("session-log", {
+ description: "View today's session log",
+ handler: async (_args, ctx) => {
+ const { yearMonth, date } = getDateInfo();
+ const logFile = join(SESSIONS_DIR, yearMonth, `${date}_session-log.txt`);
+
+ if (!existsSync(logFile)) {
+ ctx.ui.notify("No session log for today", "info");
+ return;
+ }
+
+ try {
+ const content = await readFile(logFile, "utf-8");
+ console.log("\n๐ Session log:\n" + content);
+ } catch (error) {
+ ctx.ui.notify(`Error: ${error}`, "error");
+ }
+ },
+ });
+}