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 { 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 { 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 { 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 async function importPluginModuleFromSource(specifier: string): Promise { if (isPackageSpecifier(specifier)) { const storeSpecifier = tryResolveInstalledPluginSpecifier(specifier); if (storeSpecifier) { return import(storeSpecifier); } } return import(specifier); } export async function getLatestPublishedPackageVersion(packageName: string): Promise { 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}.`); }