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:
Mario Zechner 2026-01-05 02:41:08 +01:00
parent cf1c4c31f4
commit 91cca23d23
8 changed files with 540 additions and 57 deletions

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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) {

View file

@ -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 (commandsprompts) 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 };
}

View file

@ -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)