Skip to content

Instantly share code, notes, and snippets.

@jssee
Last active January 29, 2026 06:19
Show Gist options
  • Select an option

  • Save jssee/465d0c960dc73db7cb0d9a6eada776ad to your computer and use it in GitHub Desktop.

Select an option

Save jssee/465d0c960dc73db7cb0d9a6eada776ad to your computer and use it in GitHub Desktop.
/**
* Lumen Diff Extension
*
* /lumen-diff command lists all files the model has read/written/edited in the active session branch,
* coalesced by path and sorted newest first. Selecting a file opens it in lumen
*/
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { DynamicBorder } from "@mariozechner/pi-coding-agent";
import {
Container,
Key,
matchesKey,
type SelectItem,
SelectList,
Text,
} from "@mariozechner/pi-tui";
interface FileEntry {
path: string;
operations: Set<"read" | "write" | "edit">;
lastTimestamp: number;
}
type FileToolName = "read" | "write" | "edit";
export default function (pi: ExtensionAPI) {
pi.registerCommand("lumen-diff", {
description: "Open files read/written/edited in this session in lumen",
handler: async (_args, ctx) => {
if (!ctx.hasUI) {
ctx.ui.notify("No UI available", "error");
return;
}
// Get the current branch (path from leaf to root)
const branch = ctx.sessionManager.getBranch();
// First pass: collect tool calls (id -> {path, name}) from assistant messages
const toolCalls = new Map<
string,
{ path: string; name: FileToolName; timestamp: number }
>();
for (const entry of branch) {
if (entry.type !== "message") continue;
const msg = entry.message;
if (msg.role === "assistant" && Array.isArray(msg.content)) {
for (const block of msg.content) {
if (block.type === "toolCall") {
const name = block.name;
if (name === "read" || name === "write" || name === "edit") {
const path = block.arguments?.path;
if (path && typeof path === "string") {
toolCalls.set(block.id, {
path,
name,
timestamp: msg.timestamp,
});
}
}
}
}
}
}
// Second pass: match tool results to get the actual execution timestamp
const fileMap = new Map<string, FileEntry>();
for (const entry of branch) {
if (entry.type !== "message") continue;
const msg = entry.message;
if (msg.role === "toolResult") {
const toolCall = toolCalls.get(msg.toolCallId);
if (!toolCall) continue;
const { path, name } = toolCall;
const timestamp = msg.timestamp;
const existing = fileMap.get(path);
if (existing) {
existing.operations.add(name);
if (timestamp > existing.lastTimestamp) {
existing.lastTimestamp = timestamp;
}
} else {
fileMap.set(path, {
path,
operations: new Set([name]),
lastTimestamp: timestamp,
});
}
}
}
if (fileMap.size === 0) {
ctx.ui.notify("No files read/written/edited in this session", "info");
return;
}
// Sort by most recent first
const files = Array.from(fileMap.values()).sort(
(a, b) => b.lastTimestamp - a.lastTimestamp,
);
const selectedFiles = new Set<FileEntry>();
const runLumen = async (filesToRun: FileEntry[]): Promise<void> => {
if (filesToRun.length === 0) return;
const args = ["diff"];
for (const f of filesToRun) {
args.push("--file", f.path);
}
try {
if (process.env.ZELLIJ) {
const zellijArgs = [
"run",
"--floating",
"--width",
"90%",
"--height",
"90%",
"--x",
"5%",
"--y",
"5%",
"--close-on-exit",
"--name",
"lumen-diff",
"--cwd",
ctx.cwd,
"--",
"lumen",
...args,
];
const result = await pi.exec("zellij", zellijArgs, {
cwd: ctx.cwd,
});
if (result.code !== 0) {
const errorOutput = result.stderr.trim();
const detail =
errorOutput.length > 0
? errorOutput
: `Exit code ${result.code}`;
ctx.ui.notify(
`Failed to run lumen in zellij: ${detail}`,
"error",
);
}
} else {
ctx.ui.notify(
"Not running in zellij; lumen may not render in this pane",
"warning",
);
const result = await pi.exec("lumen", args, { cwd: ctx.cwd });
if (result.code !== 0) {
const errorOutput = result.stderr.trim();
const detail =
errorOutput.length > 0
? errorOutput
: `Exit code ${result.code}`;
ctx.ui.notify(`Failed to run lumen: ${detail}`, "error");
}
}
} catch (error) {
const message =
error instanceof Error ? error.message : String(error);
ctx.ui.notify(`Failed to run lumen: ${message}`, "error");
}
};
// Show file picker with SelectList
await ctx.ui.custom<void>((tui, theme, _kb, done) => {
const container = new Container();
// Top border
container.addChild(
new DynamicBorder((s: string) => theme.fg("accent", s)),
);
// Title
container.addChild(
new Text(
theme.fg("accent", theme.bold(" Select file(s) for lumen diff")),
0,
0,
),
);
let currentIndex = 0;
const getLabel = (f: FileEntry) => {
const ops: string[] = [];
if (f.operations.has("read")) ops.push(theme.fg("muted", "R"));
if (f.operations.has("write")) ops.push(theme.fg("success", "W"));
if (f.operations.has("edit")) ops.push(theme.fg("warning", "E"));
const opsLabel = ops.join("");
const hasManualSelection = selectedFiles.size > 0;
const isSelected = hasManualSelection
? selectedFiles.has(f)
: files[currentIndex] === f;
const checkbox = isSelected ? theme.fg("success", "◉") : "○";
return `${checkbox} ${opsLabel} ${f.path}`;
};
// Build select items with colored operations
const items: SelectItem[] = files.map((f) => {
return {
value: f,
label: getLabel(f),
};
});
const visibleRows = Math.min(files.length, 15);
const updateIndex = (nextIndex: number) => {
const clampedIndex = Math.max(
0,
Math.min(nextIndex, items.length - 1),
);
if (clampedIndex === currentIndex) return;
const previousIndex = currentIndex;
currentIndex = clampedIndex;
if (selectedFiles.size === 0) {
const previousItem = items[previousIndex];
if (previousItem) {
previousItem.label = getLabel(previousItem.value as FileEntry);
}
const currentItem = items[currentIndex];
if (currentItem) {
currentItem.label = getLabel(currentItem.value as FileEntry);
}
}
};
const selectList = new SelectList(items, visibleRows, {
selectedPrefix: (t) => theme.fg("accent", t),
selectedText: (t) => t, // Keep existing colors
description: (t) => theme.fg("muted", t),
scrollInfo: (t) => theme.fg("dim", t),
noMatch: (t) => theme.fg("warning", t),
});
selectList.onSelect = (item) => {
const selected = Array.from(selectedFiles);
if (selected.length > 0) {
void runLumen(selected);
} else {
void runLumen([item.value as FileEntry]);
}
};
selectList.onCancel = () => done();
selectList.onSelectionChange = (item) => {
updateIndex(items.indexOf(item));
};
container.addChild(selectList);
// Help text
container.addChild(
new Text(
theme.fg(
"dim",
" ↑↓ navigate • ←→ page • space toggle • enter run • esc close",
),
0,
0,
),
);
// Bottom border
container.addChild(
new DynamicBorder((s: string) => theme.fg("accent", s)),
);
return {
render: (w) => container.render(w),
invalidate: () => container.invalidate(),
handleInput: (data) => {
// Add paging with left/right
if (matchesKey(data, Key.left)) {
// Page up - clamp to 0
updateIndex(Math.max(0, currentIndex - visibleRows));
selectList.setSelectedIndex(currentIndex);
} else if (matchesKey(data, Key.right)) {
// Page down - clamp to last
updateIndex(
Math.min(items.length - 1, currentIndex + visibleRows),
);
selectList.setSelectedIndex(currentIndex);
} else if (matchesKey(data, Key.space)) {
const currentItem = items[currentIndex];
if (currentItem) {
const f = currentItem.value as FileEntry;
if (selectedFiles.has(f)) {
selectedFiles.delete(f);
} else {
selectedFiles.add(f);
}
// Update label
currentItem.label = getLabel(f);
}
} else {
selectList.handleInput(data);
}
tui.requestRender();
},
};
});
},
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment