mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 06:04:40 +00:00
Add migration for commands->prompts, warn about deprecated hooks/tools dirs
- Auto-migrate commands/ to prompts/ on startup - Warn if hooks/ or tools/ directories contain custom extensions - Show deprecation warnings in interactive mode with keypress to continue - Update CHANGELOG and docs with full migration guide
This commit is contained in:
parent
cf1c4c31f4
commit
91cca23d23
8 changed files with 540 additions and 57 deletions
|
|
@ -19,7 +19,7 @@ read README.md, then ask which module(s) to work on. Based on the answer, read t
|
|||
- Always ask before removing functionality or code that appears to be intentional
|
||||
|
||||
## Commands
|
||||
- After code changes: `npm run check` (get full output, no tail)
|
||||
- After code changes (not documentation changes): `npm run check` (get full output, no tail)
|
||||
- NEVER run: `npm run dev`, `npm run build`, `npm test`
|
||||
- Only run specific tests if user instructs: `npm test -- test/specific.test.ts`
|
||||
- NEVER commit unless user asks
|
||||
|
|
|
|||
|
|
@ -63,6 +63,11 @@ cd packages/coding-agent && npx tsx src/cli.ts
|
|||
cd packages/pods && npx tsx src/cli.ts
|
||||
```
|
||||
|
||||
To run tests that don't require an LLM endpoint:
|
||||
```bash
|
||||
./test.sh
|
||||
```
|
||||
|
||||
### Versioning (Lockstep)
|
||||
|
||||
**All packages MUST always have the same version number.** Use these commands to bump versions:
|
||||
|
|
|
|||
|
|
@ -2,6 +2,197 @@
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
This release unifies hooks and custom tools into a single "extensions" system and renames "slash commands" to "prompt templates". ([#454](https://github.com/badlogic/pi-mono/issues/454))
|
||||
|
||||
**Before migrating, read:**
|
||||
- [docs/extensions.md](docs/extensions.md) - Full API reference
|
||||
- [README.md](README.md) - Extensions section with examples
|
||||
- [examples/extensions/](examples/extensions/) - Working examples
|
||||
|
||||
### Extensions Migration
|
||||
|
||||
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
|
||||
|
||||
**Directory changes:**
|
||||
```
|
||||
# Before
|
||||
~/.pi/agent/hooks/*.ts → ~/.pi/agent/extensions/*.ts
|
||||
~/.pi/agent/tools/*.ts → ~/.pi/agent/extensions/*.ts
|
||||
.pi/hooks/*.ts → .pi/extensions/*.ts
|
||||
.pi/tools/*.ts → .pi/extensions/*.ts
|
||||
```
|
||||
|
||||
**Extension discovery rules** (in `extensions/` directories):
|
||||
1. **Direct files:** `extensions/*.ts` or `*.js` → loaded directly
|
||||
2. **Subdirectory with index:** `extensions/myext/index.ts` → loaded as single extension
|
||||
3. **Subdirectory with package.json:** `extensions/myext/package.json` with `"pi"` field → loads declared paths
|
||||
|
||||
```json
|
||||
// extensions/my-package/package.json
|
||||
{
|
||||
"name": "my-extension-package",
|
||||
"dependencies": { "lodash": "^4.0.0" },
|
||||
"pi": {
|
||||
"extensions": ["./src/main.ts", "./src/tools.ts"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
No recursion beyond one level. Complex packages must use the `package.json` manifest. Dependencies in `package.json` are resolved via jiti.
|
||||
|
||||
**Type renames:**
|
||||
- `HookAPI` → `ExtensionAPI`
|
||||
- `HookContext` → `ExtensionContext`
|
||||
- `HookCommandContext` → `ExtensionCommandContext`
|
||||
- `HookUIContext` → `ExtensionUIContext`
|
||||
- `CustomToolAPI` → `ExtensionAPI` (merged)
|
||||
- `CustomToolContext` → `ExtensionContext` (merged)
|
||||
- `CustomToolUIContext` → `ExtensionUIContext`
|
||||
- `CustomTool` → `ToolDefinition`
|
||||
- `CustomToolFactory` → `ExtensionFactory`
|
||||
- `HookMessage` → `CustomMessage`
|
||||
|
||||
**Import changes:**
|
||||
```typescript
|
||||
// Before (hook)
|
||||
import type { HookAPI, HookContext } from "@mariozechner/pi-coding-agent";
|
||||
export default function (pi: HookAPI) { ... }
|
||||
|
||||
// Before (custom tool)
|
||||
import type { CustomToolFactory } from "@mariozechner/pi-coding-agent";
|
||||
const factory: CustomToolFactory = (pi) => ({ name: "my_tool", ... });
|
||||
export default factory;
|
||||
|
||||
// After (both are now extensions)
|
||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||
export default function (pi: ExtensionAPI) {
|
||||
pi.on("tool_call", async (event, ctx) => { ... });
|
||||
pi.registerTool({ name: "my_tool", ... });
|
||||
}
|
||||
```
|
||||
|
||||
**Custom tools now have full context access.** Tools registered via `pi.registerTool()` now receive the same `ctx` object that event handlers receive. Previously, custom tools had limited context. Now all extension code shares the same capabilities:
|
||||
|
||||
- `pi.registerTool()` - Register tools the LLM can call
|
||||
- `pi.registerCommand()` - Register commands like `/mycommand`
|
||||
- `pi.registerShortcut()` - Register keyboard shortcuts (shown in `/hotkeys`)
|
||||
- `pi.registerFlag()` - Register CLI flags (shown in `--help`)
|
||||
- `pi.registerMessageRenderer()` - Custom TUI rendering for message types
|
||||
- `pi.on()` - Subscribe to lifecycle events (tool_call, session_start, etc.)
|
||||
- `pi.sendMessage()` - Inject messages into the conversation
|
||||
- `pi.appendEntry()` - Persist custom data in session (survives restart/branch)
|
||||
- `pi.exec()` - Run shell commands
|
||||
- `pi.getActiveTools()` / `pi.setActiveTools()` - Dynamic tool enable/disable
|
||||
- `pi.getAllTools()` - List all available tools
|
||||
- `pi.events` - Event bus for cross-extension communication
|
||||
- `ctx.ui.confirm()` / `select()` / `input()` - User prompts
|
||||
- `ctx.ui.notify()` - Toast notifications
|
||||
- `ctx.ui.setStatus()` - Persistent status in footer (multiple extensions can set their own)
|
||||
- `ctx.ui.setWidget()` - Widget display above editor
|
||||
- `ctx.ui.setTitle()` - Set terminal window title
|
||||
- `ctx.ui.custom()` - Full TUI component with keyboard handling
|
||||
- `ctx.ui.editor()` - Multi-line text editor with external editor support
|
||||
- `ctx.sessionManager` - Read session entries, get branch history
|
||||
|
||||
**Settings changes:**
|
||||
```json
|
||||
// Before
|
||||
{
|
||||
"hooks": ["./my-hook.ts"],
|
||||
"customTools": ["./my-tool.ts"]
|
||||
}
|
||||
|
||||
// After
|
||||
{
|
||||
"extensions": ["./my-extension.ts"]
|
||||
}
|
||||
```
|
||||
|
||||
**CLI changes:**
|
||||
```bash
|
||||
# Before
|
||||
pi --hook ./safety.ts --tool ./todo.ts
|
||||
|
||||
# After
|
||||
pi --extension ./safety.ts -e ./todo.ts
|
||||
```
|
||||
|
||||
### Prompt Templates Migration
|
||||
|
||||
"Slash commands" (markdown files defining reusable prompts invoked via `/name`) are renamed to "prompt templates" to avoid confusion with extension-registered commands.
|
||||
|
||||
**Automatic migration:** The `commands/` directory is automatically renamed to `prompts/` on startup (if `prompts/` doesn't exist). Works for both regular directories and symlinks.
|
||||
|
||||
**Directory changes:**
|
||||
```
|
||||
~/.pi/agent/commands/*.md → ~/.pi/agent/prompts/*.md
|
||||
.pi/commands/*.md → .pi/prompts/*.md
|
||||
```
|
||||
|
||||
**SDK type renames:**
|
||||
- `FileSlashCommand` → `PromptTemplate`
|
||||
- `LoadSlashCommandsOptions` → `LoadPromptTemplatesOptions`
|
||||
|
||||
**SDK function renames:**
|
||||
- `discoverSlashCommands()` → `discoverPromptTemplates()`
|
||||
- `loadSlashCommands()` → `loadPromptTemplates()`
|
||||
- `expandSlashCommand()` → `expandPromptTemplate()`
|
||||
- `getCommandsDir()` → `getPromptsDir()`
|
||||
|
||||
**SDK option renames:**
|
||||
- `CreateAgentSessionOptions.slashCommands` → `.promptTemplates`
|
||||
- `AgentSession.fileCommands` → `.promptTemplates`
|
||||
- `PromptOptions.expandSlashCommands` → `.expandPromptTemplates`
|
||||
|
||||
### SDK Migration
|
||||
|
||||
**Discovery functions:**
|
||||
- `discoverAndLoadHooks()` → `discoverAndLoadExtensions()`
|
||||
- `discoverAndLoadCustomTools()` → merged into `discoverAndLoadExtensions()`
|
||||
- `loadHooks()` → `loadExtensions()`
|
||||
- `loadCustomTools()` → merged into `loadExtensions()`
|
||||
|
||||
**Runner and wrapper:**
|
||||
- `HookRunner` → `ExtensionRunner`
|
||||
- `wrapToolsWithHooks()` → `wrapToolsWithExtensions()`
|
||||
- `wrapToolWithHook()` → `wrapToolWithExtensions()`
|
||||
|
||||
**CreateAgentSessionOptions:**
|
||||
- `.hooks` → `.extensions`
|
||||
- `.customTools` → merged into `.extensions`
|
||||
- `.slashCommands` → `.promptTemplates`
|
||||
|
||||
**AgentSession:**
|
||||
- `.hookRunner` → `.extensionRunner`
|
||||
- `.fileCommands` → `.promptTemplates`
|
||||
- `.sendHookMessage()` → `.sendCustomMessage()`
|
||||
|
||||
### Session Migration
|
||||
|
||||
**Automatic.** Session version bumped from 2 to 3. Existing sessions are migrated on first load:
|
||||
- Message role `"hookMessage"` → `"custom"`
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- **Settings:** `hooks` and `customTools` arrays replaced with single `extensions` array
|
||||
- **CLI:** `--hook` and `--tool` flags replaced with `--extension` / `-e`
|
||||
- **Directories:** `hooks/`, `tools/` → `extensions/`; `commands/` → `prompts/`
|
||||
- **Types:** See type renames above
|
||||
- **SDK:** See SDK migration above
|
||||
|
||||
### Changed
|
||||
|
||||
- Extensions can have their own `package.json` with dependencies (resolved via jiti)
|
||||
- Documentation: `docs/hooks.md` and `docs/custom-tools.md` merged into `docs/extensions.md`
|
||||
- Examples: `examples/hooks/` and `examples/custom-tools/` merged into `examples/extensions/`
|
||||
- README: Extensions section expanded with custom tools, commands, events, state persistence, shortcuts, flags, and UI examples
|
||||
|
||||
## [0.34.2] - 2026-01-04
|
||||
|
||||
## [0.34.1] - 2026-01-04
|
||||
|
|
|
|||
|
|
@ -373,10 +373,9 @@ The output becomes part of your next prompt, formatted as:
|
|||
|
||||
```
|
||||
Ran `ls -la`
|
||||
```
|
||||
|
||||
<output here>
|
||||
```
|
||||
```
|
||||
|
||||
Run multiple commands before prompting; all outputs are included together.
|
||||
|
||||
|
|
@ -806,72 +805,248 @@ cd /path/to/brave-search && npm install
|
|||
|
||||
### Extensions
|
||||
|
||||
Extensions are TypeScript modules that extend pi's behavior. They can subscribe to lifecycle events, register custom tools, add commands, and more.
|
||||
Extensions are TypeScript modules that extend pi's behavior.
|
||||
|
||||
**Use cases:**
|
||||
- **Register custom tools** (callable by the LLM, with custom UI and rendering)
|
||||
- **Intercept events** (block commands, modify context/results, customize compaction)
|
||||
- **Persist state** (store custom data in session, reconstruct on reload/branch)
|
||||
- **External integrations** (file watchers, webhooks, git checkpointing)
|
||||
- **Custom tools** - Register tools callable by the LLM with custom UI and rendering
|
||||
- **Custom commands** - Add `/commands` for users (e.g., `/deploy`, `/stats`)
|
||||
- **Event interception** - Block tool calls, modify results, customize compaction
|
||||
- **State persistence** - Store data in session, reconstruct on reload/branch
|
||||
- **External integrations** - File watchers, webhooks, git checkpointing
|
||||
- **Custom UI** - Full TUI control from tools, commands, or event handlers
|
||||
|
||||
**Extension locations:**
|
||||
**Locations:**
|
||||
- Global: `~/.pi/agent/extensions/*.ts` or `~/.pi/agent/extensions/*/index.ts`
|
||||
- Project: `.pi/extensions/*.ts` or `.pi/extensions/*/index.ts`
|
||||
- CLI: `--extension <path>` or `-e <path>`
|
||||
|
||||
**Quick example:**
|
||||
**Dependencies:** Extensions can have their own dependencies. Place a `package.json` next to the extension (or in a parent directory), run `npm install`, and imports are resolved via [jiti](https://github.com/unjs/jiti). See [examples/extensions/with-deps/](examples/extensions/with-deps/).
|
||||
|
||||
#### Custom Tools
|
||||
|
||||
Tools are functions the LLM can call. They appear in the system prompt and can have custom rendering.
|
||||
|
||||
```typescript
|
||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { Text } from "@mariozechner/pi-tui";
|
||||
|
||||
export default function (pi: ExtensionAPI) {
|
||||
// Subscribe to events
|
||||
pi.on("tool_call", async (event, ctx) => {
|
||||
if (event.toolName === "bash" && /sudo/.test(event.input.command as string)) {
|
||||
const ok = await ctx.ui.confirm("Allow sudo?", event.input.command as string);
|
||||
if (!ok) return { block: true, reason: "Blocked by user" };
|
||||
}
|
||||
});
|
||||
|
||||
// Register a custom tool
|
||||
pi.registerTool({
|
||||
name: "greet",
|
||||
label: "Greeting",
|
||||
description: "Generate a greeting",
|
||||
name: "deploy",
|
||||
label: "Deploy",
|
||||
description: "Deploy the application to production",
|
||||
parameters: Type.Object({
|
||||
name: Type.String({ description: "Name to greet" }),
|
||||
environment: Type.String({ description: "Target environment" }),
|
||||
}),
|
||||
|
||||
async execute(toolCallId, params, onUpdate, ctx, signal) {
|
||||
// Show progress via onUpdate
|
||||
onUpdate({ status: "Deploying..." });
|
||||
|
||||
// Ask user for confirmation
|
||||
const ok = await ctx.ui.confirm("Deploy?", `Deploy to ${params.environment}?`);
|
||||
if (!ok) {
|
||||
return { content: [{ type: "text", text: "Cancelled" }], details: { cancelled: true } };
|
||||
}
|
||||
|
||||
// Run shell commands
|
||||
const result = await ctx.exec("./deploy.sh", [params.environment], { signal });
|
||||
|
||||
return {
|
||||
content: [{ type: "text", text: `Hello, ${params.name}!` }],
|
||||
details: {},
|
||||
content: [{ type: "text", text: result.stdout }],
|
||||
details: { environment: params.environment, exitCode: result.exitCode },
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// Register a command
|
||||
pi.registerCommand("hello", {
|
||||
description: "Say hello",
|
||||
handler: async (args, ctx) => {
|
||||
ctx.ui.notify(`Hello ${args || "world"}!`, "info");
|
||||
// Custom TUI rendering (optional)
|
||||
renderCall(args, theme) {
|
||||
return new Text(theme.bold("deploy ") + theme.fg("accent", args.environment), 0, 0);
|
||||
},
|
||||
renderResult(result, options, theme) {
|
||||
const ok = result.details?.exitCode === 0;
|
||||
return new Text(ok ? theme.fg("success", "✓ Deployed") : theme.fg("error", "✗ Failed"), 0, 0);
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Event handlers: `pi.on("tool_call", ...)`, `pi.on("session_start", ...)`, etc.
|
||||
- Custom tools: `pi.registerTool({ name, execute, renderResult, ... })`
|
||||
- Commands: `pi.registerCommand("name", { handler })`
|
||||
- Keyboard shortcuts: `pi.registerShortcut("ctrl+x", { handler })`
|
||||
- CLI flags: `pi.registerFlag("--my-flag", { ... })`
|
||||
- UI access: `ctx.ui.confirm()`, `ctx.ui.select()`, `ctx.ui.input()`
|
||||
- Shell execution: `pi.exec("git", ["status"])`
|
||||
- Message injection: `pi.sendMessage({ content, ... }, { triggerTurn: true })`
|
||||
#### Custom Commands
|
||||
|
||||
> See [Extensions Documentation](docs/extensions.md) for full API reference. pi can help you create extensions.
|
||||
Commands are user-invoked via `/name`. They can show custom UI, modify state, or trigger agent turns.
|
||||
|
||||
```typescript
|
||||
export default function (pi: ExtensionAPI) {
|
||||
pi.registerCommand("stats", {
|
||||
description: "Show session statistics",
|
||||
handler: async (args, ctx) => {
|
||||
// Simple notification
|
||||
ctx.ui.notify(`${ctx.sessionManager.getEntries().length} entries`, "info");
|
||||
},
|
||||
});
|
||||
|
||||
pi.registerCommand("todos", {
|
||||
description: "Interactive todo viewer",
|
||||
handler: async (args, ctx) => {
|
||||
// Full custom UI with keyboard handling
|
||||
await ctx.ui.custom((tui, theme, done) => {
|
||||
return {
|
||||
render(width) {
|
||||
return [
|
||||
theme.bold("Todos"),
|
||||
"- [ ] Item 1",
|
||||
"- [x] Item 2",
|
||||
"",
|
||||
theme.fg("dim", "Press Escape to close"),
|
||||
];
|
||||
},
|
||||
handleInput(data) {
|
||||
if (matchesKey(data, "escape")) done();
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
#### Event Interception
|
||||
|
||||
Subscribe to lifecycle events to block, modify, or observe agent behavior.
|
||||
|
||||
```typescript
|
||||
export default function (pi: ExtensionAPI) {
|
||||
// Block dangerous commands
|
||||
pi.on("tool_call", async (event, ctx) => {
|
||||
if (event.toolName === "bash" && /rm -rf/.test(event.input.command as string)) {
|
||||
const ok = await ctx.ui.confirm("Dangerous!", "Allow rm -rf?");
|
||||
if (!ok) return { block: true, reason: "Blocked by user" };
|
||||
}
|
||||
});
|
||||
|
||||
// Modify tool results
|
||||
pi.on("tool_result", async (event, ctx) => {
|
||||
if (event.toolName === "read") {
|
||||
// Redact secrets from file contents
|
||||
return { modifiedResult: event.result.replace(/API_KEY=\w+/g, "API_KEY=***") };
|
||||
}
|
||||
});
|
||||
|
||||
// Custom compaction
|
||||
pi.on("session_before_compact", async (event, ctx) => {
|
||||
return { customSummary: "My custom summary of the conversation so far..." };
|
||||
});
|
||||
|
||||
// Git checkpoint on each turn
|
||||
pi.on("turn_end", async (event, ctx) => {
|
||||
await ctx.exec("git", ["stash", "push", "-m", `pi-checkpoint-${Date.now()}`]);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
#### State Persistence
|
||||
|
||||
Store state in session entries that survive reload and work correctly with branching.
|
||||
|
||||
```typescript
|
||||
export default function (pi: ExtensionAPI) {
|
||||
let counter = 0;
|
||||
|
||||
// Reconstruct state from session history
|
||||
const reconstruct = (ctx) => {
|
||||
counter = 0;
|
||||
for (const entry of ctx.sessionManager.getBranch()) {
|
||||
if (entry.type === "custom" && entry.customType === "my_counter") {
|
||||
counter = entry.data.value;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pi.on("session_start", async (e, ctx) => reconstruct(ctx));
|
||||
pi.on("session_branch", async (e, ctx) => reconstruct(ctx));
|
||||
pi.on("session_tree", async (e, ctx) => reconstruct(ctx));
|
||||
|
||||
pi.registerCommand("increment", {
|
||||
handler: async (args, ctx) => {
|
||||
counter++;
|
||||
ctx.appendEntry("my_counter", { value: counter }); // Persisted in session
|
||||
ctx.ui.notify(`Counter: ${counter}`, "info");
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
#### Keyboard Shortcuts
|
||||
|
||||
Register custom keyboard shortcuts (shown in `/hotkeys`):
|
||||
|
||||
```typescript
|
||||
export default function (pi: ExtensionAPI) {
|
||||
pi.registerShortcut("ctrl+shift+d", {
|
||||
description: "Deploy to production",
|
||||
handler: async (ctx) => {
|
||||
ctx.ui.notify("Deploying...", "info");
|
||||
await ctx.exec("./deploy.sh", []);
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
#### CLI Flags
|
||||
|
||||
Register custom CLI flags (parsed automatically, shown in `--help`):
|
||||
|
||||
```typescript
|
||||
export default function (pi: ExtensionAPI) {
|
||||
pi.registerFlag("--dry-run", {
|
||||
description: "Run without making changes",
|
||||
type: "boolean",
|
||||
});
|
||||
|
||||
pi.on("tool_call", async (event, ctx) => {
|
||||
if (pi.getFlag("dry-run") && event.toolName === "write") {
|
||||
return { block: true, reason: "Dry run mode" };
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
#### Custom UI
|
||||
|
||||
Extensions have full TUI access via `ctx.ui`:
|
||||
|
||||
```typescript
|
||||
// Simple prompts
|
||||
const confirmed = await ctx.ui.confirm("Title", "Are you sure?");
|
||||
const choice = await ctx.ui.select("Pick one", ["Option A", "Option B"]);
|
||||
const text = await ctx.ui.input("Enter value");
|
||||
|
||||
// Notifications
|
||||
ctx.ui.notify("Done!", "success"); // success, info, warning, error
|
||||
|
||||
// Status line (persistent in footer, multiple extensions can set their own)
|
||||
ctx.ui.setStatus("my-ext", "Processing...");
|
||||
ctx.ui.setStatus("my-ext", null); // Clear
|
||||
|
||||
// Widgets (above editor)
|
||||
ctx.ui.setWidget("my-ext", ["Line 1", "Line 2"]);
|
||||
|
||||
// Full custom component with keyboard handling
|
||||
await ctx.ui.custom((tui, theme, done) => ({
|
||||
render(width) {
|
||||
return [
|
||||
theme.bold("My Component"),
|
||||
theme.fg("dim", "Press Escape to close"),
|
||||
];
|
||||
},
|
||||
handleInput(data) {
|
||||
if (matchesKey(data, "escape")) done();
|
||||
},
|
||||
}));
|
||||
```
|
||||
|
||||
> See [docs/extensions.md](docs/extensions.md) for full API reference.
|
||||
> See [docs/tui.md](docs/tui.md) for TUI components and custom rendering.
|
||||
> See [examples/extensions/](examples/extensions/) for working examples.
|
||||
|
||||
---
|
||||
|
|
@ -1053,7 +1228,7 @@ pi --export session.jsonl # Auto-generated filename
|
|||
pi --export session.jsonl output.html # Custom filename
|
||||
```
|
||||
|
||||
Works with both session files and streaming event logs from `--mode json`.
|
||||
Works with session files.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -1120,3 +1295,4 @@ MIT
|
|||
|
||||
- [@mariozechner/pi-ai](https://www.npmjs.com/package/@mariozechner/pi-ai): Core LLM toolkit
|
||||
- [@mariozechner/pi-agent](https://www.npmjs.com/package/@mariozechner/pi-agent): Agent framework
|
||||
- [@mariozechner/pi-tui](https://www.npmjs.com/package/@mariozechner/pi-tui): Terminal UI components
|
||||
|
|
|
|||
|
|
@ -15,13 +15,15 @@ Extensions are TypeScript modules that extend pi's behavior. They can subscribe
|
|||
|
||||
**Example use cases:**
|
||||
- Permission gates (confirm before `rm -rf`, `sudo`, etc.)
|
||||
- Git checkpointing (stash at each turn, restore on `/branch`)
|
||||
- Git checkpointing (stash at each turn, restore on branch)
|
||||
- Path protection (block writes to `.env`, `node_modules/`)
|
||||
- Custom compaction (summarize conversation your way)
|
||||
- Interactive tools (questions, wizards, custom dialogs)
|
||||
- Stateful tools (todo lists, connection pools)
|
||||
- External integrations (file watchers, webhooks, CI triggers)
|
||||
- Games while you wait (see `snake.ts` example)
|
||||
|
||||
See [examples/extensions/](../examples/extensions/) and [examples/hooks/](../examples/hooks/) for working implementations.
|
||||
See [examples/extensions/](../examples/extensions/) for working implementations.
|
||||
|
||||
## Quick Start
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import { SettingsManager } from "./core/settings-manager.js";
|
|||
import { resolvePromptInput } from "./core/system-prompt.js";
|
||||
import { printTimings, time } from "./core/timings.js";
|
||||
import { allTools } from "./core/tools/index.js";
|
||||
import { runMigrations } from "./migrations.js";
|
||||
import { runMigrations, showDeprecationWarnings } from "./migrations.js";
|
||||
import { InteractiveMode, runPrintMode, runRpcMode } from "./modes/index.js";
|
||||
import { initTheme, stopThemeWatcher } from "./modes/interactive/theme/theme.js";
|
||||
import { getChangelogPath, getNewEntries, parseChangelog } from "./utils/changelog.js";
|
||||
|
|
@ -282,8 +282,8 @@ function buildSessionOptions(
|
|||
export async function main(args: string[]) {
|
||||
time("start");
|
||||
|
||||
// Run migrations
|
||||
const { migratedAuthProviders: migratedProviders } = runMigrations();
|
||||
// Run migrations (pass cwd for project-local migrations)
|
||||
const { migratedAuthProviders: migratedProviders, deprecationWarnings } = runMigrations(process.cwd());
|
||||
|
||||
// Create AuthStorage and ModelRegistry upfront
|
||||
const authStorage = discoverAuthStorage();
|
||||
|
|
@ -366,6 +366,11 @@ export async function main(args: string[]) {
|
|||
initTheme(settingsManager.getTheme(), isInteractive);
|
||||
time("initTheme");
|
||||
|
||||
// Show deprecation warnings in interactive mode
|
||||
if (isInteractive && deprecationWarnings.length > 0) {
|
||||
await showDeprecationWarnings(deprecationWarnings);
|
||||
}
|
||||
|
||||
let scopedModels: ScopedModel[] = [];
|
||||
const modelPatterns = parsed.models ?? settingsManager.getEnabledModels();
|
||||
if (modelPatterns && modelPatterns.length > 0) {
|
||||
|
|
|
|||
|
|
@ -2,9 +2,14 @@
|
|||
* One-time migrations that run on startup.
|
||||
*/
|
||||
|
||||
import chalk from "chalk";
|
||||
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
||||
import { dirname, join } from "path";
|
||||
import { getAgentDir } from "./config.js";
|
||||
import { CONFIG_DIR_NAME, getAgentDir } from "./config.js";
|
||||
|
||||
const MIGRATION_GUIDE_URL =
|
||||
"https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/CHANGELOG.md#extensions-migration";
|
||||
const EXTENSIONS_DOC_URL = "https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/extensions.md";
|
||||
|
||||
/**
|
||||
* Migrate legacy oauth.json and settings.json apiKeys to auth.json.
|
||||
|
|
@ -123,13 +128,118 @@ export function migrateSessionsFromAgentRoot(): void {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate commands/ to prompts/ if needed.
|
||||
* Works for both regular directories and symlinks.
|
||||
*/
|
||||
function migrateCommandsToPrompts(baseDir: string, label: string): boolean {
|
||||
const commandsDir = join(baseDir, "commands");
|
||||
const promptsDir = join(baseDir, "prompts");
|
||||
|
||||
if (existsSync(commandsDir) && !existsSync(promptsDir)) {
|
||||
try {
|
||||
renameSync(commandsDir, promptsDir);
|
||||
console.log(chalk.green(`Migrated ${label} commands/ → prompts/`));
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
`Warning: Could not migrate ${label} commands/ to prompts/: ${err instanceof Error ? err.message : err}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for deprecated hooks/ and tools/ directories.
|
||||
* Note: tools/ may contain fd/rg binaries extracted by pi, so only warn if it has other files.
|
||||
*/
|
||||
function checkDeprecatedExtensionDirs(baseDir: string, label: string): string[] {
|
||||
const hooksDir = join(baseDir, "hooks");
|
||||
const toolsDir = join(baseDir, "tools");
|
||||
const warnings: string[] = [];
|
||||
|
||||
if (existsSync(hooksDir)) {
|
||||
warnings.push(`${label} hooks/ directory found. Hooks have been renamed to extensions.`);
|
||||
}
|
||||
|
||||
if (existsSync(toolsDir)) {
|
||||
// Check if tools/ contains anything other than fd/rg (which are auto-extracted binaries)
|
||||
try {
|
||||
const entries = readdirSync(toolsDir);
|
||||
const customTools = entries.filter((e) => e !== "fd" && e !== "rg");
|
||||
if (customTools.length > 0) {
|
||||
warnings.push(
|
||||
`${label} tools/ directory contains custom tools. Custom tools have been merged into extensions.`,
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
// Ignore read errors
|
||||
}
|
||||
}
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run extension system migrations (commands→prompts) and collect warnings about deprecated directories.
|
||||
*/
|
||||
function migrateExtensionSystem(cwd: string): string[] {
|
||||
const agentDir = getAgentDir();
|
||||
const projectDir = join(cwd, CONFIG_DIR_NAME);
|
||||
|
||||
// Migrate commands/ to prompts/
|
||||
migrateCommandsToPrompts(agentDir, "Global");
|
||||
migrateCommandsToPrompts(projectDir, "Project");
|
||||
|
||||
// Check for deprecated directories
|
||||
const warnings = [
|
||||
...checkDeprecatedExtensionDirs(agentDir, "Global"),
|
||||
...checkDeprecatedExtensionDirs(projectDir, "Project"),
|
||||
];
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print deprecation warnings and wait for keypress.
|
||||
*/
|
||||
export async function showDeprecationWarnings(warnings: string[]): Promise<void> {
|
||||
if (warnings.length === 0) return;
|
||||
|
||||
for (const warning of warnings) {
|
||||
console.log(chalk.yellow(`Warning: ${warning}`));
|
||||
}
|
||||
console.log(chalk.yellow(`\nMove your extensions to the extensions/ directory.`));
|
||||
console.log(chalk.yellow(`Migration guide: ${MIGRATION_GUIDE_URL}`));
|
||||
console.log(chalk.yellow(`Documentation: ${EXTENSIONS_DOC_URL}`));
|
||||
console.log(chalk.dim(`\nPress any key to continue...`));
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
process.stdin.setRawMode?.(true);
|
||||
process.stdin.resume();
|
||||
process.stdin.once("data", () => {
|
||||
process.stdin.setRawMode?.(false);
|
||||
process.stdin.pause();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
console.log();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all migrations. Called once on startup.
|
||||
*
|
||||
* @returns Object with migration results
|
||||
* @returns Object with migration results and deprecation warnings
|
||||
*/
|
||||
export function runMigrations(): { migratedAuthProviders: string[] } {
|
||||
export function runMigrations(cwd: string = process.cwd()): {
|
||||
migratedAuthProviders: string[];
|
||||
deprecationWarnings: string[];
|
||||
} {
|
||||
const migratedAuthProviders = migrateAuthToAuthJson();
|
||||
migrateSessionsFromAgentRoot();
|
||||
return { migratedAuthProviders };
|
||||
const deprecationWarnings = migrateExtensionSystem(cwd);
|
||||
return { migratedAuthProviders, deprecationWarnings };
|
||||
}
|
||||
|
|
|
|||
6
todo.md
6
todo.md
|
|
@ -1,6 +0,0 @@
|
|||
# Export HTML TODOs
|
||||
|
||||
- [x] "Ctrl+T toggle thinking · Ctrl+O toggle tools" font size is not the same as all the other font sizes
|
||||
- [ ] System prompt doesn't show included AGENTS.md, skills. See `packages/coding-agent/src/core/system-prompt.ts`. Can only be done if we export live from a session, not via `--export` CLI flag
|
||||
- [x] "Available Tools" has no newline after it
|
||||
- [x] `read` tool has too much vertical spacing between tool call header and tool result, and also with tool bottom border (was white-space: pre-wrap on .tool-output preserving template literal whitespace)
|
||||
Loading…
Add table
Add a link
Reference in a new issue