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

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