|
|
|
@@ -1,3 +1,4 @@
|
|
|
|
|
import { join } from "node:path";
|
|
|
|
|
import { Markdown, type MarkdownTheme } from "@earendil-works/pi-tui";
|
|
|
|
|
import chalk from "chalk";
|
|
|
|
|
import { selectConfig } from "./cli/config-selector.ts";
|
|
|
|
@@ -16,6 +17,7 @@ import {
|
|
|
|
|
VERSION,
|
|
|
|
|
} from "./config.ts";
|
|
|
|
|
import type { InlineExtension } from "./core/extensions/types.ts";
|
|
|
|
|
import { ModelRuntime } from "./core/model-runtime.ts";
|
|
|
|
|
import { DefaultPackageManager } from "./core/package-manager.ts";
|
|
|
|
|
import { type AppMode, resolveProjectTrusted } from "./core/project-trust.ts";
|
|
|
|
|
import { DefaultResourceLoader } from "./core/resource-loader.ts";
|
|
|
|
@@ -30,7 +32,7 @@ import {
|
|
|
|
|
|
|
|
|
|
export type PackageCommand = "install" | "remove" | "update" | "list";
|
|
|
|
|
|
|
|
|
|
type UpdateTarget = { type: "all" } | { type: "self" } | { type: "extensions"; source?: string };
|
|
|
|
|
type UpdateTarget = { type: "all" } | { type: "self" } | { type: "extensions"; source?: string } | { type: "models" };
|
|
|
|
|
|
|
|
|
|
const SELF_UPDATE_NOTE_MARKDOWN_THEME: MarkdownTheme = {
|
|
|
|
|
heading: (text) => chalk.bold(chalk.yellow(text)),
|
|
|
|
@@ -81,7 +83,7 @@ function getPackageCommandUsage(command: PackageCommand): string {
|
|
|
|
|
case "remove":
|
|
|
|
|
return `${APP_NAME} remove <source> [-l] [--approve|--no-approve]`;
|
|
|
|
|
case "update":
|
|
|
|
|
return `${APP_NAME} update [source|self|pi] [--self|--extensions|--all] [--extension <source>] [--approve|--no-approve] [--force]`;
|
|
|
|
|
return `${APP_NAME} update [source|self|pi] [--self|--extensions|--models|--all] [--extension <source>] [--approve|--no-approve] [--force]`;
|
|
|
|
|
case "list":
|
|
|
|
|
return `${APP_NAME} list [--approve|--no-approve]`;
|
|
|
|
|
}
|
|
|
|
@@ -149,11 +151,12 @@ Examples:
|
|
|
|
|
console.log(`${chalk.bold("Usage:")}
|
|
|
|
|
${getPackageCommandUsage("update")}
|
|
|
|
|
|
|
|
|
|
Update pi and installed packages.
|
|
|
|
|
Update pi, installed packages, or model catalogs.
|
|
|
|
|
|
|
|
|
|
Options:
|
|
|
|
|
--self Update pi only (default when no target is given)
|
|
|
|
|
--extensions Update installed packages only
|
|
|
|
|
--models Refresh model catalogs only
|
|
|
|
|
--all Update pi and installed packages
|
|
|
|
|
--extension <source> Update one package only
|
|
|
|
|
-a, --approve Trust project-local files for this command
|
|
|
|
@@ -163,6 +166,7 @@ Options:
|
|
|
|
|
Short forms:
|
|
|
|
|
${APP_NAME} update Update pi only
|
|
|
|
|
${APP_NAME} update --all Update pi and all extensions
|
|
|
|
|
${APP_NAME} update --models Refresh model catalogs only
|
|
|
|
|
${APP_NAME} update <source> Update one package
|
|
|
|
|
${APP_NAME} update pi Update pi only (self works as alias to pi)
|
|
|
|
|
`);
|
|
|
|
@@ -205,6 +209,7 @@ function parsePackageCommand(args: string[]): PackageCommandOptions | undefined
|
|
|
|
|
let source: string | undefined;
|
|
|
|
|
let selfFlag = false;
|
|
|
|
|
let extensionsFlag = false;
|
|
|
|
|
let modelsFlag = false;
|
|
|
|
|
let allFlag = false;
|
|
|
|
|
let extensionFlagSource: string | undefined;
|
|
|
|
|
|
|
|
|
@@ -242,6 +247,15 @@ function parsePackageCommand(args: string[]): PackageCommandOptions | undefined
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (arg === "--models") {
|
|
|
|
|
if (command === "update") {
|
|
|
|
|
modelsFlag = true;
|
|
|
|
|
} else {
|
|
|
|
|
invalidOption = invalidOption ?? arg;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (arg === "--all") {
|
|
|
|
|
if (command === "update") {
|
|
|
|
|
allFlag = true;
|
|
|
|
@@ -304,15 +318,24 @@ function parsePackageCommand(args: string[]): PackageCommandOptions | undefined
|
|
|
|
|
let updateTarget: UpdateTarget | undefined;
|
|
|
|
|
let showExtensionsSkippedNote = false;
|
|
|
|
|
if (command === "update") {
|
|
|
|
|
if (allFlag && (selfFlag || extensionsFlag || extensionFlagSource)) {
|
|
|
|
|
if (allFlag && (selfFlag || extensionsFlag || modelsFlag || extensionFlagSource)) {
|
|
|
|
|
conflictingOptions =
|
|
|
|
|
conflictingOptions ?? "--all cannot be combined with --self, --extensions, or --extension";
|
|
|
|
|
conflictingOptions ?? "--all cannot be combined with --self, --extensions, --models, or --extension";
|
|
|
|
|
}
|
|
|
|
|
if (allFlag && source) {
|
|
|
|
|
conflictingOptions = conflictingOptions ?? "--all cannot be combined with a positional source";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (extensionFlagSource) {
|
|
|
|
|
if (modelsFlag) {
|
|
|
|
|
if (selfFlag || extensionsFlag || allFlag || extensionFlagSource) {
|
|
|
|
|
conflictingOptions =
|
|
|
|
|
conflictingOptions ?? "--models cannot be combined with --self, --extensions, --all, or --extension";
|
|
|
|
|
}
|
|
|
|
|
if (source) {
|
|
|
|
|
conflictingOptions = conflictingOptions ?? "--models cannot be combined with a positional source";
|
|
|
|
|
}
|
|
|
|
|
updateTarget = { type: "models" };
|
|
|
|
|
} else if (extensionFlagSource) {
|
|
|
|
|
if (selfFlag || extensionsFlag || allFlag) {
|
|
|
|
|
conflictingOptions =
|
|
|
|
|
conflictingOptions ?? "--extension cannot be combined with --self, --extensions, or --all";
|
|
|
|
@@ -371,6 +394,33 @@ function updateTargetIncludesExtensions(target: UpdateTarget): boolean {
|
|
|
|
|
return target.type === "all" || target.type === "extensions";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function refreshModelCatalogs(agentDir: string): Promise<void> {
|
|
|
|
|
const modelRuntime = await ModelRuntime.create({
|
|
|
|
|
authPath: join(agentDir, "auth.json"),
|
|
|
|
|
modelsPath: join(agentDir, "models.json"),
|
|
|
|
|
allowModelNetwork: false,
|
|
|
|
|
});
|
|
|
|
|
const controller = new AbortController();
|
|
|
|
|
const timeout = setTimeout(() => controller.abort(), 15_000);
|
|
|
|
|
try {
|
|
|
|
|
const result = await modelRuntime.refresh({
|
|
|
|
|
allowNetwork: true,
|
|
|
|
|
force: true,
|
|
|
|
|
signal: controller.signal,
|
|
|
|
|
});
|
|
|
|
|
if (result.aborted) {
|
|
|
|
|
throw new Error("Model catalog refresh timed out.");
|
|
|
|
|
}
|
|
|
|
|
if (result.errors.size > 0) {
|
|
|
|
|
const details = Array.from(result.errors, ([provider, error]) => `${provider}: ${error.message}`).join("; ");
|
|
|
|
|
throw new Error(`Could not refresh model catalogs: ${details}`);
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
clearTimeout(timeout);
|
|
|
|
|
}
|
|
|
|
|
console.log(chalk.green("Model catalogs refreshed"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function printSelfUpdateUnavailable(
|
|
|
|
|
npmCommand?: string[],
|
|
|
|
|
updatePackageTarget: SelfUpdatePackageTarget = PACKAGE_NAME,
|
|
|
|
@@ -673,6 +723,17 @@ export async function handlePackageCommand(
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (options.command === "update" && options.updateTarget?.type === "models") {
|
|
|
|
|
try {
|
|
|
|
|
await refreshModelCatalogs(getAgentDir());
|
|
|
|
|
} catch (error: unknown) {
|
|
|
|
|
const message = error instanceof Error ? error.message : "Unknown model catalog refresh error";
|
|
|
|
|
console.error(chalk.red(`Error: ${message}`));
|
|
|
|
|
process.exitCode = 1;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cwd = process.cwd();
|
|
|
|
|
const agentDir = getAgentDir();
|
|
|
|
|
const writesProjectPackageConfig = (options.command === "install" || options.command === "remove") && options.local;
|
|
|
|
|