From 0919ec54179b8a04bc36a8b3a6d9ebd727a9584c Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Mon, 5 Jan 2026 03:06:39 +0100 Subject: [PATCH] Add Custom UI section, update CHANGELOG migration docs - Add ## Custom UI section consolidating dialogs, widgets, custom components, message rendering, theme colors - Simplify ctx.ui and pi.registerMessageRenderer in other sections to reference Custom UI - Update CHANGELOG to reflect auto-migration of commands/ to prompts/ --- packages/coding-agent/CHANGELOG.md | 12 +- packages/coding-agent/docs/extensions.md | 189 +++++++++++++++-------- 2 files changed, 131 insertions(+), 70 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 4957e222..8f304397 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -13,11 +13,13 @@ This release unifies hooks and custom tools into a single "extensions" system an Hooks and custom tools are now unified as **extensions**. Both were TypeScript modules exporting a factory function that receives an API object. Now there's one concept, one discovery location, one CLI flag, one settings.json entry. -**No automatic file migration.** You must manually: -1. Move files from `hooks/` and `tools/` directories to `extensions/` -2. Move files from `commands/` to `prompts/` -3. Update imports and type names in your extension code -4. Update `settings.json` if you have explicit hook and custom tool paths configured +**Automatic migration:** +- `commands/` directories are automatically renamed to `prompts/` on startup (both `~/.pi/agent/commands/` and `.pi/commands/`) + +**Manual migration required:** +1. Move files from `hooks/` and `tools/` directories to `extensions/` (deprecation warnings shown on startup) +2. Update imports and type names in your extension code +3. Update `settings.json` if you have explicit hook and custom tool paths configured **Directory changes:** ``` diff --git a/packages/coding-agent/docs/extensions.md b/packages/coding-agent/docs/extensions.md index f43a46db..85f7e507 100644 --- a/packages/coding-agent/docs/extensions.md +++ b/packages/coding-agent/docs/extensions.md @@ -42,6 +42,7 @@ See [examples/extensions/](../examples/extensions/) for working implementations. - [ExtensionAPI Methods](#extensionapi-methods) - [State Management](#state-management) - [Custom Tools](#custom-tools) +- [Custom UI](#custom-ui) - [Error Handling](#error-handling) - [Mode Behavior](#mode-behavior) @@ -503,49 +504,7 @@ Every handler receives `ctx: ExtensionContext`: ### ctx.ui -UI methods for user interaction: - -```typescript -// Select from options -const choice = await ctx.ui.select("Pick one:", ["A", "B", "C"]); - -// Confirm dialog -const ok = await ctx.ui.confirm("Delete?", "This cannot be undone"); - -// Text input -const name = await ctx.ui.input("Name:", "placeholder"); - -// Multi-line editor -const text = await ctx.ui.editor("Edit:", "prefilled text"); - -// Notification -ctx.ui.notify("Done!", "info"); // "info" | "warning" | "error" - -// Status in footer -ctx.ui.setStatus("my-ext", "Processing..."); -ctx.ui.setStatus("my-ext", undefined); // Clear - -// Widget above editor -ctx.ui.setWidget("my-widget", ["Line 1", "Line 2"]); -ctx.ui.setWidget("my-widget", undefined); // Clear - -// Terminal title -ctx.ui.setTitle("pi - my-project"); - -// Editor text -ctx.ui.setEditorText("Prefill text"); -const current = ctx.ui.getEditorText(); -``` - -**Custom components:** - -```typescript -const result = await ctx.ui.custom((tui, theme, done) => { - const component = new MyComponent(); - component.onComplete = (value) => done(value); - return component; -}); -``` +UI methods for user interaction. See [Custom UI](#custom-ui) for full details. ### ctx.hasUI @@ -727,13 +686,7 @@ pi.registerCommand("stats", { ### pi.registerMessageRenderer(customType, renderer) -Register a custom TUI renderer for messages with your `customType`: - -```typescript -pi.registerMessageRenderer("my-extension", (message, options, theme) => { - return new Text(theme.fg("accent", `[INFO] `) + message.content, 0, 0); -}); -``` +Register a custom TUI renderer for messages with your `customType`. See [Custom UI](#custom-ui). ### pi.registerShortcut(shortcut, options) @@ -944,21 +897,6 @@ renderResult(result, { expanded, isPartial }, theme) { } ``` -#### Theme Colors - -```typescript -theme.fg("toolTitle", text) // Tool names -theme.fg("accent", text) // Highlights -theme.fg("success", text) // Success -theme.fg("error", text) // Errors -theme.fg("warning", text) // Warnings -theme.fg("muted", text) // Secondary text -theme.fg("dim", text) // Tertiary text - -theme.bold(text) -theme.italic(text) -``` - #### Best Practices - Use `Text` with padding `(0, 0)` - the Box handles padding @@ -973,6 +911,127 @@ If `renderCall`/`renderResult` is not defined or throws: - `renderCall`: Shows tool name - `renderResult`: Shows raw text from `content` +## Custom UI + +Extensions can interact with users via `ctx.ui` methods and customize how messages/tools render. + +### Dialogs + +```typescript +// Select from options +const choice = await ctx.ui.select("Pick one:", ["A", "B", "C"]); + +// Confirm dialog +const ok = await ctx.ui.confirm("Delete?", "This cannot be undone"); + +// Text input +const name = await ctx.ui.input("Name:", "placeholder"); + +// Multi-line editor +const text = await ctx.ui.editor("Edit:", "prefilled text"); + +// Notification (non-blocking) +ctx.ui.notify("Done!", "info"); // "info" | "warning" | "error" +``` + +### Widgets and Status + +```typescript +// Status in footer (persistent until cleared) +ctx.ui.setStatus("my-ext", "Processing..."); +ctx.ui.setStatus("my-ext", undefined); // Clear + +// Widget above editor (multi-line) +ctx.ui.setWidget("my-widget", ["Line 1", "Line 2"]); +ctx.ui.setWidget("my-widget", undefined); // Clear + +// Terminal title +ctx.ui.setTitle("pi - my-project"); + +// Editor text +ctx.ui.setEditorText("Prefill text"); +const current = ctx.ui.getEditorText(); +``` + +### Custom Components + +For complex UI, use `ctx.ui.custom()` with full TUI components: + +```typescript +import { Text, Box, Component } from "@mariozechner/pi-tui"; + +const result = await ctx.ui.custom((tui, theme, done) => { + // Create a component that handles keyboard input + const box = new Box(0, 0); + const text = new Text("Press Enter to confirm, Escape to cancel", 1, 1); + box.add(text); + + box.onKey = (key) => { + if (key === "return") done({ confirmed: true }); + if (key === "escape") done({ confirmed: false }); + return true; + }; + + return box; +}); + +if (result.confirmed) { + // User pressed Enter +} +``` + +See [tui.md](tui.md) for the full component API. + +### Message Rendering + +Register a custom renderer for messages with your `customType`: + +```typescript +import { Text } from "@mariozechner/pi-tui"; + +pi.registerMessageRenderer("my-extension", (message, options, theme) => { + const { expanded } = options; + let text = theme.fg("accent", `[${message.customType}] `); + text += message.content; + + if (expanded && message.details) { + text += "\n" + theme.fg("dim", JSON.stringify(message.details, null, 2)); + } + + return new Text(text, 0, 0); +}); +``` + +Messages are sent via `pi.sendMessage()`: + +```typescript +pi.sendMessage({ + customType: "my-extension", // Matches registerMessageRenderer + content: "Status update", + display: true, // Show in TUI + details: { ... }, // Available in renderer +}); +``` + +### Theme Colors + +All render functions receive a `theme` object: + +```typescript +// Foreground colors +theme.fg("toolTitle", text) // Tool names +theme.fg("accent", text) // Highlights +theme.fg("success", text) // Success (green) +theme.fg("error", text) // Errors (red) +theme.fg("warning", text) // Warnings (yellow) +theme.fg("muted", text) // Secondary text +theme.fg("dim", text) // Tertiary text + +// Text styles +theme.bold(text) +theme.italic(text) +``` + ## Error Handling - Extension errors are logged, agent continues