mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-04-28 06:19:43 +00:00
feat(coding-agent): Add built-in update command (#3680)
Some checks are pending
CI / build-check-test (push) Waiting to run
Some checks are pending
CI / build-check-test (push) Waiting to run
This commit is contained in:
parent
0b271a2c4f
commit
dcf2651631
7 changed files with 372 additions and 35 deletions
|
|
@ -381,7 +381,10 @@ pi install ssh://git@github.com/user/repo@v1 # tag or commit
|
|||
pi remove npm:@foo/pi-tools
|
||||
pi uninstall npm:@foo/pi-tools # alias for remove
|
||||
pi list
|
||||
pi update # skips pinned packages
|
||||
pi update # update pi and packages (skips pinned packages)
|
||||
pi update --extensions # update packages only
|
||||
pi update --self # update pi only
|
||||
pi update npm:@foo/pi-tools # update one package
|
||||
pi config # enable/disable extensions, skills, prompts, themes
|
||||
```
|
||||
|
||||
|
|
@ -476,7 +479,10 @@ pi [options] [@files...] [messages...]
|
|||
pi install <source> [-l] # Install package, -l for project-local
|
||||
pi remove <source> [-l] # Remove package
|
||||
pi uninstall <source> [-l] # Alias for remove
|
||||
pi update [source] # Update packages (skips pinned)
|
||||
pi update [source|self|pi] # Update pi and packages (skips pinned packages)
|
||||
pi update --extensions # Update packages only
|
||||
pi update --self # Update pi only
|
||||
pi update --extension <src> # Update one package
|
||||
pi list # List installed packages
|
||||
pi config # Enable/disable package resources
|
||||
```
|
||||
|
|
|
|||
|
|
@ -27,8 +27,12 @@ pi install /absolute/path/to/package
|
|||
pi install ./relative/path/to/package
|
||||
|
||||
pi remove npm:@foo/bar
|
||||
pi list # show installed packages from settings
|
||||
pi update # update all non-pinned packages
|
||||
pi list # show installed packages from settings
|
||||
pi update # update pi and all non-pinned packages
|
||||
pi update --extensions # update all non-pinned packages only
|
||||
pi update --self # update pi only
|
||||
pi update npm:@foo/bar # update one package
|
||||
pi update --extension npm:@foo/bar
|
||||
```
|
||||
|
||||
By default, `install` and `remove` write to global settings (`~/.pi/agent/settings.json`). Use `-l` to write to project settings (`.pi/settings.json`) instead. Project settings can be shared with your team, and pi installs any missing packages automatically on startup.
|
||||
|
|
@ -51,7 +55,7 @@ npm:@scope/pkg@1.2.3
|
|||
npm:pkg
|
||||
```
|
||||
|
||||
- Versioned specs are pinned and skipped by `pi update`.
|
||||
- Versioned specs are pinned and skipped by package updates (`pi update`, `pi update --extensions`).
|
||||
- Global installs use `npm install -g`.
|
||||
- Project installs go under `.pi/npm/`.
|
||||
- Set `npmCommand` in `settings.json` to pin npm package lookup and install operations to a specific wrapper command such as `mise` or `asdf`.
|
||||
|
|
@ -78,7 +82,7 @@ ssh://git@github.com/user/repo@v1
|
|||
- HTTPS and SSH URLs are both supported.
|
||||
- SSH URLs use your configured SSH keys automatically (respects `~/.ssh/config`).
|
||||
- For non-interactive runs (for example CI), you can set `GIT_TERMINAL_PROMPT=0` to disable credential prompts and set `GIT_SSH_COMMAND` (for example `ssh -o BatchMode=yes -o ConnectTimeout=5`) to fail fast.
|
||||
- Refs pin the package and skip `pi update`.
|
||||
- Refs pin the package and skip package updates (`pi update`, `pi update --extensions`).
|
||||
- Cloned to `~/.pi/agent/git/<host>/<path>` (global) or `.pi/git/<host>/<path>` (project).
|
||||
- Runs `npm install` after clone or pull if `package.json` exists.
|
||||
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ ${chalk.bold("Commands:")}
|
|||
${APP_NAME} install <source> [-l] Install extension source and add to settings
|
||||
${APP_NAME} remove <source> [-l] Remove extension source from settings
|
||||
${APP_NAME} uninstall <source> [-l] Alias for remove
|
||||
${APP_NAME} update [source] Update installed extensions (skips pinned sources)
|
||||
${APP_NAME} update [source|self|pi] Update pi and installed extensions
|
||||
${APP_NAME} list List installed extensions from settings
|
||||
${APP_NAME} config Open TUI to enable/disable package resources
|
||||
${APP_NAME} <command> --help Show help for install/remove/uninstall/update/list
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { spawnSync } from "child_process";
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import { homedir } from "os";
|
||||
import { dirname, join, resolve } from "path";
|
||||
import { dirname, join, resolve, sep } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
// =============================================================================
|
||||
|
|
@ -26,6 +27,12 @@ export const isBunRuntime = !!process.versions.bun;
|
|||
|
||||
export type InstallMethod = "bun-binary" | "npm" | "pnpm" | "yarn" | "bun" | "unknown";
|
||||
|
||||
export interface SelfUpdateCommand {
|
||||
command: string;
|
||||
args: string[];
|
||||
display: string;
|
||||
}
|
||||
|
||||
export function detectInstallMethod(): InstallMethod {
|
||||
if (isBunBinary) {
|
||||
return "bun-binary";
|
||||
|
|
@ -49,24 +56,125 @@ export function detectInstallMethod(): InstallMethod {
|
|||
return "unknown";
|
||||
}
|
||||
|
||||
export function getUpdateInstruction(packageName: string): string {
|
||||
const method = detectInstallMethod();
|
||||
function getSelfUpdateCommandForMethod(method: InstallMethod, packageName: string): SelfUpdateCommand | undefined {
|
||||
switch (method) {
|
||||
case "bun-binary":
|
||||
return `Download from: https://github.com/badlogic/pi-mono/releases/latest`;
|
||||
return undefined;
|
||||
case "pnpm":
|
||||
return `Run: pnpm install -g ${packageName}`;
|
||||
return {
|
||||
command: "pnpm",
|
||||
args: ["install", "-g", packageName],
|
||||
display: `pnpm install -g ${packageName}`,
|
||||
};
|
||||
case "yarn":
|
||||
return `Run: yarn global add ${packageName}`;
|
||||
return {
|
||||
command: "yarn",
|
||||
args: ["global", "add", packageName],
|
||||
display: `yarn global add ${packageName}`,
|
||||
};
|
||||
case "bun":
|
||||
return `Run: bun install -g ${packageName}`;
|
||||
return {
|
||||
command: "bun",
|
||||
args: ["install", "-g", packageName],
|
||||
display: `bun install -g ${packageName}`,
|
||||
};
|
||||
case "npm":
|
||||
return `Run: npm install -g ${packageName}`;
|
||||
default:
|
||||
return `Run: npm install -g ${packageName}`;
|
||||
return {
|
||||
command: "npm",
|
||||
args: ["install", "-g", packageName],
|
||||
display: `npm install -g ${packageName}`,
|
||||
};
|
||||
case "unknown":
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function readCommandOutput(command: string, args: string[]): string | undefined {
|
||||
const result = spawnSync(command, args, {
|
||||
encoding: "utf-8",
|
||||
stdio: ["ignore", "pipe", "ignore"],
|
||||
timeout: 2000,
|
||||
});
|
||||
if (result.status !== 0) return undefined;
|
||||
const stdout = result.stdout.trim();
|
||||
return stdout || undefined;
|
||||
}
|
||||
|
||||
function getGlobalPackageRoots(method: InstallMethod): string[] {
|
||||
switch (method) {
|
||||
case "npm": {
|
||||
const root = readCommandOutput("npm", ["root", "-g"]);
|
||||
return root ? [root] : [];
|
||||
}
|
||||
case "pnpm": {
|
||||
const root = readCommandOutput("pnpm", ["root", "-g"]);
|
||||
return root ? [root, dirname(root)] : [];
|
||||
}
|
||||
case "yarn": {
|
||||
const dir = readCommandOutput("yarn", ["global", "dir"]);
|
||||
return dir ? [dir, join(dir, "node_modules")] : [];
|
||||
}
|
||||
case "bun": {
|
||||
const bunBin = readCommandOutput("bun", ["pm", "bin", "-g"]);
|
||||
const roots = [join(homedir(), ".bun", "install", "global", "node_modules")];
|
||||
if (bunBin) {
|
||||
roots.push(join(dirname(dirname(bunBin)), "install", "global", "node_modules"));
|
||||
}
|
||||
return roots;
|
||||
}
|
||||
case "bun-binary":
|
||||
case "unknown":
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function isManagedByGlobalPackageManager(method: InstallMethod): boolean {
|
||||
let packageDir = resolve(getPackageDir());
|
||||
if (process.platform === "win32") {
|
||||
packageDir = packageDir.toLowerCase();
|
||||
}
|
||||
return getGlobalPackageRoots(method).some((root) => {
|
||||
let normalizedRoot = resolve(root);
|
||||
if (process.platform === "win32") {
|
||||
normalizedRoot = normalizedRoot.toLowerCase();
|
||||
}
|
||||
return (
|
||||
existsSync(normalizedRoot) &&
|
||||
(packageDir === normalizedRoot ||
|
||||
packageDir.startsWith(normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function getSelfUpdateCommand(packageName: string): SelfUpdateCommand | undefined {
|
||||
const method = detectInstallMethod();
|
||||
const command = getSelfUpdateCommandForMethod(method, packageName);
|
||||
if (!command || !isManagedByGlobalPackageManager(method)) {
|
||||
return undefined;
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
export function getSelfUpdateUnavailableInstruction(packageName: string): string {
|
||||
const method = detectInstallMethod();
|
||||
if (method === "bun-binary") {
|
||||
return `Download from: https://github.com/badlogic/pi-mono/releases/latest`;
|
||||
}
|
||||
if (getSelfUpdateCommandForMethod(method, packageName)) {
|
||||
return `This installation is not managed by a global ${method} install. Update it with the package manager, wrapper, or source checkout that provides it.`;
|
||||
}
|
||||
return `Update ${packageName} using the package manager, wrapper, or source checkout that provides this installation.`;
|
||||
}
|
||||
|
||||
export function getUpdateInstruction(packageName: string): string {
|
||||
const method = detectInstallMethod();
|
||||
const command = getSelfUpdateCommandForMethod(method, packageName);
|
||||
if (command) {
|
||||
return `Run: ${command.display}`;
|
||||
}
|
||||
return getSelfUpdateUnavailableInstruction(packageName);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Package Asset Paths (shipped with executable)
|
||||
// =============================================================================
|
||||
|
|
@ -182,13 +290,23 @@ export function getBundledInteractiveAssetPath(name: string): string {
|
|||
// App Config (from package.json piConfig)
|
||||
// =============================================================================
|
||||
|
||||
const pkg = JSON.parse(readFileSync(getPackageJsonPath(), "utf-8"));
|
||||
interface PackageJson {
|
||||
name?: string;
|
||||
version?: string;
|
||||
piConfig?: {
|
||||
name?: string;
|
||||
configDir?: string;
|
||||
};
|
||||
}
|
||||
|
||||
const pkg = JSON.parse(readFileSync(getPackageJsonPath(), "utf-8")) as PackageJson;
|
||||
|
||||
const piConfigName: string | undefined = pkg.piConfig?.name;
|
||||
export const PACKAGE_NAME: string = pkg.name || "@mariozechner/pi-coding-agent";
|
||||
export const APP_NAME: string = piConfigName || "pi";
|
||||
export const APP_TITLE: string = piConfigName ? APP_NAME : "π";
|
||||
export const CONFIG_DIR_NAME: string = pkg.piConfig?.configDir || ".pi";
|
||||
export const VERSION: string = pkg.version;
|
||||
export const VERSION: string = pkg.version || "0.0.0";
|
||||
|
||||
// e.g., PI_CODING_AGENT_DIR or TAU_CODING_AGENT_DIR
|
||||
export const ENV_AGENT_DIR = `${APP_NAME.toUpperCase()}_CODING_AGENT_DIR`;
|
||||
|
|
|
|||
|
|
@ -54,7 +54,6 @@ import {
|
|||
getDebugLogPath,
|
||||
getDocsPath,
|
||||
getShareViewerUrl,
|
||||
getUpdateInstruction,
|
||||
VERSION,
|
||||
} from "../../config.js";
|
||||
import { type AgentSession, type AgentSessionEvent, parseSkillBlock } from "../../core/agent-session.js";
|
||||
|
|
@ -3515,8 +3514,8 @@ export class InteractiveMode {
|
|||
}
|
||||
|
||||
showNewVersionNotification(newVersion: string): void {
|
||||
const action = theme.fg("accent", getUpdateInstruction("@mariozechner/pi-coding-agent"));
|
||||
const updateInstruction = theme.fg("muted", `New version ${newVersion} is available. `) + action;
|
||||
const action = theme.fg("accent", `${APP_NAME} update`);
|
||||
const updateInstruction = theme.fg("muted", `New version ${newVersion} is available. Run `) + action;
|
||||
const changelogUrl = theme.fg(
|
||||
"accent",
|
||||
"https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/CHANGELOG.md",
|
||||
|
|
|
|||
|
|
@ -1,17 +1,30 @@
|
|||
import chalk from "chalk";
|
||||
import { spawn } from "child_process";
|
||||
import { selectConfig } from "./cli/config-selector.js";
|
||||
import { APP_NAME, getAgentDir } from "./config.js";
|
||||
import {
|
||||
APP_NAME,
|
||||
getAgentDir,
|
||||
getSelfUpdateCommand,
|
||||
getSelfUpdateUnavailableInstruction,
|
||||
PACKAGE_NAME,
|
||||
} from "./config.js";
|
||||
import { DefaultPackageManager } from "./core/package-manager.js";
|
||||
import { SettingsManager } from "./core/settings-manager.js";
|
||||
|
||||
export type PackageCommand = "install" | "remove" | "update" | "list";
|
||||
|
||||
type UpdateTarget = { type: "all" } | { type: "self" } | { type: "extensions"; source?: string };
|
||||
|
||||
interface PackageCommandOptions {
|
||||
command: PackageCommand;
|
||||
source?: string;
|
||||
updateTarget?: UpdateTarget;
|
||||
local: boolean;
|
||||
help: boolean;
|
||||
invalidOption?: string;
|
||||
invalidArgument?: string;
|
||||
missingOptionValue?: string;
|
||||
conflictingOptions?: string;
|
||||
}
|
||||
|
||||
function reportSettingsErrors(settingsManager: SettingsManager, context: string): void {
|
||||
|
|
@ -31,7 +44,7 @@ function getPackageCommandUsage(command: PackageCommand): string {
|
|||
case "remove":
|
||||
return `${APP_NAME} remove <source> [-l]`;
|
||||
case "update":
|
||||
return `${APP_NAME} update [source]`;
|
||||
return `${APP_NAME} update [source|self|pi] [--self] [--extensions] [--extension <source>]`;
|
||||
case "list":
|
||||
return `${APP_NAME} list`;
|
||||
}
|
||||
|
|
@ -78,8 +91,17 @@ Examples:
|
|||
console.log(`${chalk.bold("Usage:")}
|
||||
${getPackageCommandUsage("update")}
|
||||
|
||||
Update installed packages.
|
||||
If <source> is provided, only that package is updated.
|
||||
Update pi and installed packages.
|
||||
|
||||
Options:
|
||||
--self Update pi only
|
||||
--extensions Update installed packages only
|
||||
--extension <source> Update one package only
|
||||
|
||||
Short forms:
|
||||
${APP_NAME} update Update pi and all extensions
|
||||
${APP_NAME} update <source> Update one package
|
||||
${APP_NAME} update pi Update pi only (self works as alias to pi)
|
||||
`);
|
||||
return;
|
||||
|
||||
|
|
@ -108,9 +130,16 @@ function parsePackageCommand(args: string[]): PackageCommandOptions | undefined
|
|||
let local = false;
|
||||
let help = false;
|
||||
let invalidOption: string | undefined;
|
||||
let invalidArgument: string | undefined;
|
||||
let missingOptionValue: string | undefined;
|
||||
let conflictingOptions: string | undefined;
|
||||
let source: string | undefined;
|
||||
let selfFlag = false;
|
||||
let extensionsFlag = false;
|
||||
let extensionFlagSource: string | undefined;
|
||||
|
||||
for (const arg of rest) {
|
||||
for (let index = 0; index < rest.length; index++) {
|
||||
const arg = rest[index];
|
||||
if (arg === "-h" || arg === "--help") {
|
||||
help = true;
|
||||
continue;
|
||||
|
|
@ -125,6 +154,43 @@ function parsePackageCommand(args: string[]): PackageCommandOptions | undefined
|
|||
continue;
|
||||
}
|
||||
|
||||
if (arg === "--self") {
|
||||
if (command === "update") {
|
||||
selfFlag = true;
|
||||
} else {
|
||||
invalidOption = invalidOption ?? arg;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === "--extensions") {
|
||||
if (command === "update") {
|
||||
extensionsFlag = true;
|
||||
} else {
|
||||
invalidOption = invalidOption ?? arg;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === "--extension") {
|
||||
if (command !== "update") {
|
||||
invalidOption = invalidOption ?? arg;
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = rest[index + 1];
|
||||
if (!value || value.startsWith("-")) {
|
||||
missingOptionValue = missingOptionValue ?? arg;
|
||||
} else if (extensionFlagSource) {
|
||||
conflictingOptions = conflictingOptions ?? "--extension can only be provided once";
|
||||
index++;
|
||||
} else {
|
||||
extensionFlagSource = value;
|
||||
index++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.startsWith("-")) {
|
||||
invalidOption = invalidOption ?? arg;
|
||||
continue;
|
||||
|
|
@ -132,10 +198,103 @@ function parsePackageCommand(args: string[]): PackageCommandOptions | undefined
|
|||
|
||||
if (!source) {
|
||||
source = arg;
|
||||
} else {
|
||||
invalidArgument = invalidArgument ?? arg;
|
||||
}
|
||||
}
|
||||
|
||||
return { command, source, local, help, invalidOption };
|
||||
let updateTarget: UpdateTarget | undefined;
|
||||
if (command === "update") {
|
||||
if (extensionFlagSource) {
|
||||
if (selfFlag || extensionsFlag) {
|
||||
conflictingOptions = conflictingOptions ?? "--extension cannot be combined with --self or --extensions";
|
||||
}
|
||||
if (source) {
|
||||
conflictingOptions = conflictingOptions ?? "--extension cannot be combined with a positional source";
|
||||
}
|
||||
updateTarget = { type: "extensions", source: extensionFlagSource };
|
||||
} else if (source) {
|
||||
const sourceIsSelf = source === "self" || source === "pi";
|
||||
if (sourceIsSelf) {
|
||||
updateTarget = extensionsFlag ? { type: "all" } : { type: "self" };
|
||||
} else {
|
||||
if (extensionsFlag || selfFlag) {
|
||||
conflictingOptions =
|
||||
conflictingOptions ?? "positional update targets cannot be combined with --self or --extensions";
|
||||
}
|
||||
updateTarget = { type: "extensions", source };
|
||||
}
|
||||
} else if (selfFlag && extensionsFlag) {
|
||||
updateTarget = { type: "all" };
|
||||
} else if (selfFlag) {
|
||||
updateTarget = { type: "self" };
|
||||
} else if (extensionsFlag) {
|
||||
updateTarget = { type: "extensions" };
|
||||
} else {
|
||||
updateTarget = { type: "all" };
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
command,
|
||||
source,
|
||||
updateTarget,
|
||||
local,
|
||||
help,
|
||||
invalidOption,
|
||||
invalidArgument,
|
||||
missingOptionValue,
|
||||
conflictingOptions,
|
||||
};
|
||||
}
|
||||
|
||||
function updateTargetIncludesSelf(target: UpdateTarget): boolean {
|
||||
return target.type === "all" || target.type === "self";
|
||||
}
|
||||
|
||||
function updateTargetIncludesExtensions(target: UpdateTarget): boolean {
|
||||
return target.type === "all" || target.type === "extensions";
|
||||
}
|
||||
|
||||
function canSelfUpdate(): boolean {
|
||||
return getSelfUpdateCommand(PACKAGE_NAME) !== undefined;
|
||||
}
|
||||
|
||||
function printSelfUpdateUnavailable(): void {
|
||||
console.error(`error: ${APP_NAME} cannot self-update this installation.`);
|
||||
console.error(getSelfUpdateUnavailableInstruction(PACKAGE_NAME));
|
||||
|
||||
const entrypoint = process.argv[1];
|
||||
if (entrypoint) {
|
||||
console.error("");
|
||||
console.error(`Location of pi executable: ${entrypoint}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function runSelfUpdate(): Promise<void> {
|
||||
const command = getSelfUpdateCommand(PACKAGE_NAME);
|
||||
if (!command) {
|
||||
throw new Error(
|
||||
`${APP_NAME} cannot self-update this installation. ${getSelfUpdateUnavailableInstruction(PACKAGE_NAME)}`,
|
||||
);
|
||||
}
|
||||
|
||||
console.log(chalk.dim(`Updating ${APP_NAME} with ${command.display}...`));
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const child = spawn(command.command, command.args, { stdio: "inherit" });
|
||||
child.on("error", (error) => {
|
||||
reject(error);
|
||||
});
|
||||
child.on("close", (code, signal) => {
|
||||
if (code === 0) {
|
||||
resolve();
|
||||
} else if (signal) {
|
||||
reject(new Error(`${command.display} terminated by signal ${signal}`));
|
||||
} else {
|
||||
reject(new Error(`${command.display} exited with code ${code ?? "unknown"}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function handleConfigCommand(args: string[]): Promise<boolean> {
|
||||
|
|
@ -178,6 +337,27 @@ export async function handlePackageCommand(args: string[]): Promise<boolean> {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (options.missingOptionValue) {
|
||||
console.error(chalk.red(`Missing value for ${options.missingOptionValue}.`));
|
||||
console.error(chalk.dim(`Usage: ${getPackageCommandUsage(options.command)}`));
|
||||
process.exitCode = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (options.invalidArgument) {
|
||||
console.error(chalk.red(`Unexpected argument ${options.invalidArgument}.`));
|
||||
console.error(chalk.dim(`Usage: ${getPackageCommandUsage(options.command)}`));
|
||||
process.exitCode = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (options.conflictingOptions) {
|
||||
console.error(chalk.red(options.conflictingOptions));
|
||||
console.error(chalk.dim(`Usage: ${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.`));
|
||||
|
|
@ -186,6 +366,12 @@ export async function handlePackageCommand(args: string[]): Promise<boolean> {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (options.command === "update" && options.updateTarget?.type === "self" && !canSelfUpdate()) {
|
||||
printSelfUpdateUnavailable();
|
||||
process.exitCode = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
const cwd = process.cwd();
|
||||
const agentDir = getAgentDir();
|
||||
const settingsManager = SettingsManager.create(cwd, agentDir);
|
||||
|
|
@ -252,14 +438,28 @@ export async function handlePackageCommand(args: string[]): Promise<boolean> {
|
|||
return true;
|
||||
}
|
||||
|
||||
case "update":
|
||||
await packageManager.update(source);
|
||||
if (source) {
|
||||
console.log(chalk.green(`Updated ${source}`));
|
||||
} else {
|
||||
console.log(chalk.green("Updated packages"));
|
||||
case "update": {
|
||||
const target = options.updateTarget ?? { type: "all" };
|
||||
if (updateTargetIncludesExtensions(target)) {
|
||||
const updateSource = target.type === "extensions" ? target.source : undefined;
|
||||
await packageManager.update(updateSource);
|
||||
if (updateSource) {
|
||||
console.log(chalk.green(`Updated ${updateSource}`));
|
||||
} else {
|
||||
console.log(chalk.green("Updated packages"));
|
||||
}
|
||||
}
|
||||
if (updateTargetIncludesSelf(target)) {
|
||||
if (canSelfUpdate()) {
|
||||
await runSelfUpdate();
|
||||
console.log(chalk.green(`Updated ${APP_NAME}`));
|
||||
} else {
|
||||
printSelfUpdateUnavailable();
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : "Unknown package command error";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { afterEach, describe, expect, test } from "vitest";
|
||||
import { detectInstallMethod, getUpdateInstruction } from "../src/config.js";
|
||||
import { detectInstallMethod, getSelfUpdateCommand, getUpdateInstruction } from "../src/config.js";
|
||||
|
||||
const execPathDescriptor = Object.getOwnPropertyDescriptor(process, "execPath");
|
||||
|
||||
|
|
@ -27,4 +27,14 @@ describe("detectInstallMethod", () => {
|
|||
"Run: pnpm install -g @mariozechner/pi-coding-agent",
|
||||
);
|
||||
});
|
||||
|
||||
test("does not self-update unknown wrapper installs", () => {
|
||||
setExecPath("/usr/local/bin/node");
|
||||
|
||||
expect(detectInstallMethod()).toBe("unknown");
|
||||
expect(getSelfUpdateCommand("@mariozechner/pi-coding-agent")).toBeUndefined();
|
||||
expect(getUpdateInstruction("@mariozechner/pi-coding-agent")).toBe(
|
||||
"Update @mariozechner/pi-coding-agent using the package manager, wrapper, or source checkout that provides this installation.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue