docs(coding-agent): improve extensions.md, add missing examples

- Add cross-links to session.md, keybindings.md, themes.md
- Document truncateLine, highlightCode, getLanguageFromPath utilities
- Add examples for previously undocumented APIs:
  - message-renderer.ts: registerMessageRenderer with Box styling
  - session-name.ts: setSessionName/getSessionName
  - event-bus.ts: pi.events for inter-extension communication
  - bookmark.ts: setLabel for entry bookmarking
- Update examples table in extensions.md and README.md
This commit is contained in:
Mario Zechner 2026-01-26 11:42:53 +01:00
parent c565fa9af8
commit 930207130b
6 changed files with 285 additions and 68 deletions

View file

@ -88,6 +88,20 @@ cp permission-gate.ts ~/.pi/agent/extensions/
|-----------|-------------|
| `mac-system-theme.ts` | Syncs pi theme with macOS dark/light mode |
### Messages & Communication
| Extension | Description |
|-----------|-------------|
| `message-renderer.ts` | Custom message rendering with colors and expandable details via `registerMessageRenderer` |
| `event-bus.ts` | Inter-extension communication via `pi.events` |
### Session Metadata
| Extension | Description |
|-----------|-------------|
| `session-name.ts` | Name sessions for the session selector via `setSessionName` |
| `bookmark.ts` | Bookmark entries with labels for `/tree` navigation via `setLabel` |
### Custom Providers
| Extension | Description |

View file

@ -0,0 +1,50 @@
/**
* Entry bookmarking example.
*
* Shows setLabel to mark entries with labels for easy navigation in /tree.
* Labels appear in the tree view and help you find important points.
*
* Usage: /bookmark [label] - bookmark the last assistant message
*/
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
export default function (pi: ExtensionAPI) {
pi.registerCommand("bookmark", {
description: "Bookmark last message (usage: /bookmark [label])",
handler: async (args, ctx) => {
const label = args.trim() || `bookmark-${Date.now()}`;
// Find the last assistant message entry
const entries = ctx.sessionManager.getEntries();
for (let i = entries.length - 1; i >= 0; i--) {
const entry = entries[i];
if (entry.type === "message" && entry.message.role === "assistant") {
pi.setLabel(entry.id, label);
ctx.ui.notify(`Bookmarked as: ${label}`, "info");
return;
}
}
ctx.ui.notify("No assistant message to bookmark", "warning");
},
});
// Remove bookmark
pi.registerCommand("unbookmark", {
description: "Remove bookmark from last labeled entry",
handler: async (_args, ctx) => {
const entries = ctx.sessionManager.getEntries();
for (let i = entries.length - 1; i >= 0; i--) {
const entry = entries[i];
const label = ctx.sessionManager.getLabel(entry.id);
if (label) {
pi.setLabel(entry.id, undefined);
ctx.ui.notify(`Removed bookmark: ${label}`, "info");
return;
}
}
ctx.ui.notify("No bookmarked entry found", "warning");
},
});
}

View file

@ -0,0 +1,43 @@
/**
* Inter-extension event bus example.
*
* Shows pi.events for communication between extensions. One extension
* can emit events that other extensions listen to.
*
* Usage: /emit [event-name] [data] - emit an event on the bus
*/
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
export default function (pi: ExtensionAPI) {
// Store ctx for use in event handler
let currentCtx: ExtensionContext | undefined;
pi.on("session_start", async (_event, ctx) => {
currentCtx = ctx;
});
// Listen for events from other extensions
pi.events.on("my:notification", (data) => {
const { message, from } = data as { message: string; from: string };
currentCtx?.ui.notify(`Event from ${from}: ${message}`, "info");
});
// Command to emit events (emits "my:notification" which the listener above receives)
pi.registerCommand("emit", {
description: "Emit my:notification event (usage: /emit message)",
handler: async (args, _ctx) => {
const message = args.trim() || "hello";
pi.events.emit("my:notification", { message, from: "/emit command" });
// Listener above will show the notification
},
});
// Example: emit on session start
pi.on("session_start", async () => {
pi.events.emit("my:notification", {
message: "Session started",
from: "event-bus-example",
});
});
}

View file

@ -0,0 +1,59 @@
/**
* Custom message rendering example.
*
* Shows how to use registerMessageRenderer to control how custom messages
* appear in the TUI, with colors, formatting, and expandable details.
*
* Usage: /status [message] - sends a status message with custom rendering
*/
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Box, Text } from "@mariozechner/pi-tui";
export default function (pi: ExtensionAPI) {
// Register custom renderer for "status-update" messages
pi.registerMessageRenderer("status-update", (message, { expanded }, theme) => {
const details = message.details as { level: string; timestamp: number } | undefined;
const level = details?.level ?? "info";
// Color based on level
const color = level === "error" ? "error" : level === "warn" ? "warning" : "success";
const prefix = theme.fg(color, `[${level.toUpperCase()}]`);
let text = `${prefix} ${message.content}`;
// Show timestamp when expanded
if (expanded && details?.timestamp) {
const time = new Date(details.timestamp).toLocaleTimeString();
text += `\n${theme.fg("dim", ` at ${time}`)}`;
}
// Use Box with customMessageBg for consistent styling
const box = new Box(1, 1, (t) => theme.bg("customMessageBg", t));
box.addChild(new Text(text, 0, 0));
return box;
});
// Command to send status messages
pi.registerCommand("status", {
description: "Send a status message (usage: /status [warn|error] message)",
handler: async (args, _ctx) => {
const parts = args.trim().split(/\s+/);
let level = "info";
let content = args.trim();
// Check for level prefix
if (parts[0] === "warn" || parts[0] === "error") {
level = parts[0];
content = parts.slice(1).join(" ") || "Status update";
}
pi.sendMessage({
customType: "status-update",
content,
display: true,
details: { level, timestamp: Date.now() },
});
},
});
}

View file

@ -0,0 +1,27 @@
/**
* Session naming example.
*
* Shows setSessionName/getSessionName to give sessions friendly names
* that appear in the session selector instead of the first message.
*
* Usage: /session-name [name] - set or show session name
*/
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
export default function (pi: ExtensionAPI) {
pi.registerCommand("session-name", {
description: "Set or show session name (usage: /session-name [new name])",
handler: async (args, ctx) => {
const name = args.trim();
if (name) {
pi.setSessionName(name);
ctx.ui.notify(`Session named: ${name}`, "info");
} else {
const current = pi.getSessionName();
ctx.ui.notify(current ? `Session: ${current}` : "No session name set", "info");
}
},
});
}