import chalk from "chalk"; import { selectConfig } from "./cli/config-selector.js"; import { APP_NAME, getAgentDir } from "./config.js"; import { DefaultPackageManager } from "./core/package-manager.js"; import { SettingsManager } from "./core/settings-manager.js"; export type PackageCommand = "install" | "remove" | "update" | "list"; interface PackageCommandOptions { command: PackageCommand; source?: string; local: boolean; help: boolean; invalidOption?: string; } function reportSettingsErrors(settingsManager: SettingsManager, context: string): void { const errors = settingsManager.drainErrors(); for (const { scope, error } of errors) { console.error(chalk.yellow(`Warning (${context}, ${scope} settings): ${error.message}`)); if (error.stack) { console.error(chalk.dim(error.stack)); } } } function getPackageCommandUsage(command: PackageCommand): string { switch (command) { case "install": return `${APP_NAME} install [-l]`; case "remove": return `${APP_NAME} remove [-l]`; case "update": return `${APP_NAME} update [source]`; case "list": return `${APP_NAME} list`; } } function printPackageCommandHelp(command: PackageCommand): void { switch (command) { case "install": console.log(`${chalk.bold("Usage:")} ${getPackageCommandUsage("install")} Install a package and add it to settings. Options: -l, --local Install project-locally (.pi/settings.json) Examples: ${APP_NAME} install npm:@foo/bar ${APP_NAME} install git:github.com/user/repo ${APP_NAME} install git:git@github.com:user/repo ${APP_NAME} install https://github.com/user/repo ${APP_NAME} install ssh://git@github.com/user/repo ${APP_NAME} install ./local/path `); return; case "remove": console.log(`${chalk.bold("Usage:")} ${getPackageCommandUsage("remove")} Remove a package and its source from settings. Alias: ${APP_NAME} uninstall [-l] Options: -l, --local Remove from project settings (.pi/settings.json) Examples: ${APP_NAME} remove npm:@foo/bar ${APP_NAME} uninstall npm:@foo/bar `); return; case "update": console.log(`${chalk.bold("Usage:")} ${getPackageCommandUsage("update")} Update installed packages. If is provided, only that package is updated. `); return; case "list": console.log(`${chalk.bold("Usage:")} ${getPackageCommandUsage("list")} List installed packages from user and project settings. `); return; } } function parsePackageCommand(args: string[]): PackageCommandOptions | undefined { const [rawCommand, ...rest] = args; let command: PackageCommand | undefined; if (rawCommand === "uninstall") { command = "remove"; } else if (rawCommand === "install" || rawCommand === "remove" || rawCommand === "update" || rawCommand === "list") { command = rawCommand; } if (!command) { return undefined; } let local = false; let help = false; let invalidOption: string | undefined; let source: string | undefined; for (const arg of rest) { if (arg === "-h" || arg === "--help") { help = true; continue; } if (arg === "-l" || arg === "--local") { if (command === "install" || command === "remove") { local = true; } else { invalidOption = invalidOption ?? arg; } continue; } if (arg.startsWith("-")) { invalidOption = invalidOption ?? arg; continue; } if (!source) { source = arg; } } return { command, source, local, help, invalidOption }; } export async function handleConfigCommand(args: string[]): Promise { if (args[0] !== "config") { return false; } const cwd = process.cwd(); const agentDir = getAgentDir(); const settingsManager = SettingsManager.create(cwd, agentDir); reportSettingsErrors(settingsManager, "config command"); const packageManager = new DefaultPackageManager({ cwd, agentDir, settingsManager }); const resolvedPaths = await packageManager.resolve(); await selectConfig({ resolvedPaths, settingsManager, cwd, agentDir, }); process.exit(0); } export async function handlePackageCommand(args: string[]): Promise { const options = parsePackageCommand(args); if (!options) { return false; } if (options.help) { printPackageCommandHelp(options.command); return true; } if (options.invalidOption) { console.error(chalk.red(`Unknown option ${options.invalidOption} for "${options.command}".`)); console.error(chalk.dim(`Use "${APP_NAME} --help" or "${getPackageCommandUsage(options.command)}".`)); process.exitCode = 1; return true; } const source = options.source; if ((options.command === "install" || options.command === "remove") && !source) { console.error(chalk.red(`Missing ${options.command} source.`)); console.error(chalk.dim(`Usage: ${getPackageCommandUsage(options.command)}`)); process.exitCode = 1; return true; } const cwd = process.cwd(); const agentDir = getAgentDir(); const settingsManager = SettingsManager.create(cwd, agentDir); reportSettingsErrors(settingsManager, "package command"); const packageManager = new DefaultPackageManager({ cwd, agentDir, settingsManager }); packageManager.setProgressCallback((event) => { if (event.type === "start") { process.stdout.write(chalk.dim(`${event.message}\n`)); } }); try { switch (options.command) { case "install": await packageManager.installAndPersist(source!, { local: options.local }); console.log(chalk.green(`Installed ${source}`)); return true; case "remove": { const removed = await packageManager.removeAndPersist(source!, { local: options.local }); if (!removed) { console.error(chalk.red(`No matching package found for ${source}`)); process.exitCode = 1; return true; } console.log(chalk.green(`Removed ${source}`)); return true; } case "list": { const configuredPackages = packageManager.listConfiguredPackages(); const userPackages = configuredPackages.filter((pkg) => pkg.scope === "user"); const projectPackages = configuredPackages.filter((pkg) => pkg.scope === "project"); if (configuredPackages.length === 0) { console.log(chalk.dim("No packages installed.")); return true; } const formatPackage = (pkg: (typeof configuredPackages)[number]) => { const display = pkg.filtered ? `${pkg.source} (filtered)` : pkg.source; console.log(` ${display}`); if (pkg.installedPath) { console.log(chalk.dim(` ${pkg.installedPath}`)); } }; if (userPackages.length > 0) { console.log(chalk.bold("User packages:")); for (const pkg of userPackages) { formatPackage(pkg); } } if (projectPackages.length > 0) { if (userPackages.length > 0) console.log(); console.log(chalk.bold("Project packages:")); for (const pkg of projectPackages) { formatPackage(pkg); } } return true; } case "update": await packageManager.update(source); if (source) { console.log(chalk.green(`Updated ${source}`)); } else { console.log(chalk.green("Updated packages")); } return true; } } catch (error: unknown) { const message = error instanceof Error ? error.message : "Unknown package command error"; console.error(chalk.red(`Error: ${message}`)); process.exitCode = 1; return true; } }