add cli error formatter and plugin store

This commit is contained in:
Dhruv Sharma 2026-03-31 04:02:25 +05:30
parent 30cf2cb84a
commit 39396d4534
2 changed files with 192 additions and 0 deletions

View File

@ -0,0 +1,34 @@
export interface CommandErrorOptions {
cmd: string;
args?: string[];
action?: string;
installHints?: string[];
}
function formatHints(hints: string[] | undefined): string {
if (!hints || hints.length === 0) return "";
return `\nInstall hint${hints.length === 1 ? "" : "s"}:\n ${hints.join("\n ")}`;
}
export function formatCommandError(err: unknown, options: CommandErrorOptions): Error {
const command = [options.cmd, ...(options.args ?? [])].join(" ").trim();
const action = options.action ?? "run the command";
const code =
err && typeof err === "object" && "code" in err ? String((err as { code?: unknown }).code) : undefined;
const message = err instanceof Error ? err.message : String(err);
if (code === "ENOENT") {
return new Error(
`${options.cmd} is not installed or not on PATH, so AO could not ${action}.${formatHints(options.installHints)}`,
);
}
if (code === "EACCES") {
return new Error(
`${options.cmd} exists but AO could not execute it due to a permission error while trying to ${action}. ` +
`Check that the binary is executable and accessible to this user.${formatHints(options.installHints)}`,
);
}
return new Error(`Failed to ${action}: ${command}${message ? `\n${message}` : ""}`);
}

View File

@ -0,0 +1,158 @@
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { createRequire } from "node:module";
import { homedir } from "node:os";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
import { exec } from "./shell.js";
import { formatCommandError } from "./cli-errors.js";
interface PluginStoreManifest {
name: string;
private: boolean;
type: "module";
}
const STORE_MANIFEST: PluginStoreManifest = {
name: "ao-plugin-store",
private: true,
type: "module",
};
function getPluginStorePackageJsonPath(): string {
return join(getPluginStoreRoot(), "package.json");
}
function getInstalledPackageJsonPath(packageName: string): string {
return join(getPluginStoreRoot(), "node_modules", ...packageName.split("/"), "package.json");
}
function getStoreRequire(): NodeRequire {
const packageJsonPath = getPluginStorePackageJsonPath();
ensurePluginStore();
return createRequire(packageJsonPath);
}
function isPackageSpecifier(specifier: string): boolean {
return !(
specifier.startsWith("file:") ||
specifier.startsWith("node:") ||
specifier.startsWith("/") ||
specifier.startsWith("./") ||
specifier.startsWith("../")
);
}
async function runNpmInStore(args: string[]): Promise<void> {
const storeRoot = ensurePluginStore();
try {
await exec("npm", args, { cwd: storeRoot });
} catch (err) {
throw formatCommandError(err, {
cmd: "npm",
args,
action: "manage AO marketplace plugins",
installHints: ["Install Node.js/npm from https://nodejs.org/ and re-run the command."],
});
}
}
export function getPluginStoreRoot(): string {
return join(homedir(), ".agent-orchestrator", "plugins");
}
export function ensurePluginStore(): string {
const rootDir = getPluginStoreRoot();
mkdirSync(rootDir, { recursive: true });
const packageJsonPath = getPluginStorePackageJsonPath();
if (!existsSync(packageJsonPath)) {
writeFileSync(packageJsonPath, `${JSON.stringify(STORE_MANIFEST, null, 2)}\n`, "utf-8");
}
return rootDir;
}
export function readInstalledPackageVersion(packageName: string): string | null {
const packageJsonPath = getInstalledPackageJsonPath(packageName);
if (!existsSync(packageJsonPath)) return null;
try {
const parsed = JSON.parse(readFileSync(packageJsonPath, "utf-8")) as { version?: unknown };
return typeof parsed.version === "string" && parsed.version.length > 0 ? parsed.version : null;
} catch {
return null;
}
}
export async function installPackageIntoStore(
packageName: string,
version?: string,
): Promise<string> {
const requested = version ? `${packageName}@${version}` : packageName;
await runNpmInStore(["install", "--save-exact", requested]);
const installedVersion = readInstalledPackageVersion(packageName);
if (!installedVersion) {
throw new Error(`Package ${packageName} was installed into the AO plugin store but no version was resolved afterwards.`);
}
return installedVersion;
}
export async function uninstallPackageFromStore(packageName: string): Promise<boolean> {
if (!readInstalledPackageVersion(packageName)) {
return false;
}
await runNpmInStore(["uninstall", packageName]);
return readInstalledPackageVersion(packageName) === null;
}
export function tryResolveInstalledPluginSpecifier(packageName: string): string | null {
try {
const resolvedPath = getStoreRequire().resolve(packageName);
return pathToFileURL(resolvedPath).href;
} catch {
return null;
}
}
export function resolveInstalledPluginSpecifier(packageName: string): string {
const specifier = tryResolveInstalledPluginSpecifier(packageName);
if (!specifier) {
throw new Error(
`Package ${packageName} is not installed in the AO plugin store (${getPluginStoreRoot()}).`,
);
}
return specifier;
}
export async function importPluginModuleFromSource(specifier: string): Promise<unknown> {
if (isPackageSpecifier(specifier)) {
const storeSpecifier = tryResolveInstalledPluginSpecifier(specifier);
if (storeSpecifier) {
return import(storeSpecifier);
}
}
return import(specifier);
}
export async function getLatestPublishedPackageVersion(packageName: string): Promise<string> {
try {
const { stdout } = await exec("npm", ["view", packageName, "version", "--json"]);
const parsed = JSON.parse(stdout) as unknown;
if (typeof parsed === "string" && parsed.length > 0) {
return parsed;
}
} catch (err) {
throw formatCommandError(err, {
cmd: "npm",
args: ["view", packageName, "version", "--json"],
action: `resolve the latest published version for ${packageName}`,
installHints: ["Install Node.js/npm from https://nodejs.org/ and re-run the command."],
});
}
throw new Error(`npm did not return a usable version for ${packageName}.`);
}