From 75f723b021044ed7bd7dffb12cb243eaadae68fa Mon Sep 17 00:00:00 2001 From: Artur Do Lago Date: Sun, 11 Jan 2026 14:02:37 +0100 Subject: [PATCH] fix(plugin): add Instance.provide context and validate cache - Wrap CLI commands in Instance.provide for proper Config access - Validate registry cache file against schema before use - Prevents crash when Config.update() is called without Instance context Co-Authored-By: Claude Opus 4.5 --- .../agent-core/src/cli/cmd/plugin/info.ts | 110 +++++++++--------- .../agent-core/src/cli/cmd/plugin/install.ts | 52 +++++---- .../agent-core/src/cli/cmd/plugin/list.ts | 62 +++++----- .../agent-core/src/cli/cmd/plugin/remove.ts | 46 ++++---- packages/agent-core/src/plugin/registry.ts | 9 +- 5 files changed, 155 insertions(+), 124 deletions(-) diff --git a/packages/agent-core/src/cli/cmd/plugin/info.ts b/packages/agent-core/src/cli/cmd/plugin/info.ts index 7a3f7c06ba2..8febf4b990d 100644 --- a/packages/agent-core/src/cli/cmd/plugin/info.ts +++ b/packages/agent-core/src/cli/cmd/plugin/info.ts @@ -2,6 +2,7 @@ import { cmd } from "../cmd" import { fetchRegistry, getPlugin } from "../../../plugin/registry" import { getInstalled } from "../../../plugin/manager" import { UI } from "../../ui" +import { Instance } from "../../../project/instance" export const InfoCommand = cmd({ command: "info ", @@ -21,62 +22,67 @@ export const InfoCommand = cmd({ async handler(args) { const { name, json } = args - try { - const registry = await fetchRegistry() - const plugin = getPlugin(registry, name) - const installed = await getInstalled(name) + await Instance.provide({ + directory: process.cwd(), + async fn() { + try { + const registry = await fetchRegistry() + const plugin = getPlugin(registry, name) + const installed = await getInstalled(name) - if (!plugin && !installed) { - UI.error(`Plugin "${name}" not found`) - UI.println(UI.Style.TEXT_DIM + "Search for plugins with: agent-core plugin search" + UI.Style.TEXT_NORMAL) - process.exit(1) - } + if (!plugin && !installed) { + UI.error(`Plugin "${name}" not found`) + UI.println(UI.Style.TEXT_DIM + "Search for plugins with: agent-core plugin search" + UI.Style.TEXT_NORMAL) + process.exit(1) + } - const info = { - ...plugin, - installed: !!installed, - installedVersion: installed?.version, - } + const info = { + ...plugin, + installed: !!installed, + installedVersion: installed?.version, + } - if (json) { - console.log(JSON.stringify(info, null, 2)) - return - } + if (json) { + console.log(JSON.stringify(info, null, 2)) + return + } - // Display info - if (plugin) { - UI.println(UI.Style.TEXT_HIGHLIGHT_BOLD + plugin.displayName + UI.Style.TEXT_NORMAL) - UI.println(UI.Style.TEXT_DIM + plugin.name + UI.Style.TEXT_NORMAL) - UI.empty() - UI.println(plugin.description) - UI.empty() - UI.println(UI.Style.TEXT_NORMAL_BOLD + "Package: " + UI.Style.TEXT_NORMAL + `${plugin.npm}@${plugin.version}`) - UI.println(UI.Style.TEXT_NORMAL_BOLD + "Category: " + UI.Style.TEXT_NORMAL + plugin.category) - UI.println(UI.Style.TEXT_NORMAL_BOLD + "Tags: " + UI.Style.TEXT_NORMAL + plugin.tags.join(", ")) - UI.println(UI.Style.TEXT_NORMAL_BOLD + "Capabilities:" + UI.Style.TEXT_NORMAL + " " + plugin.capabilities.join(", ")) - UI.println(UI.Style.TEXT_NORMAL_BOLD + "Author: " + UI.Style.TEXT_NORMAL + plugin.author) - if (plugin.homepage) { - UI.println(UI.Style.TEXT_NORMAL_BOLD + "Homepage: " + UI.Style.TEXT_INFO + plugin.homepage + UI.Style.TEXT_NORMAL) + // Display info + if (plugin) { + UI.println(UI.Style.TEXT_HIGHLIGHT_BOLD + plugin.displayName + UI.Style.TEXT_NORMAL) + UI.println(UI.Style.TEXT_DIM + plugin.name + UI.Style.TEXT_NORMAL) + UI.empty() + UI.println(plugin.description) + UI.empty() + UI.println(UI.Style.TEXT_NORMAL_BOLD + "Package: " + UI.Style.TEXT_NORMAL + `${plugin.npm}@${plugin.version}`) + UI.println(UI.Style.TEXT_NORMAL_BOLD + "Category: " + UI.Style.TEXT_NORMAL + plugin.category) + UI.println(UI.Style.TEXT_NORMAL_BOLD + "Tags: " + UI.Style.TEXT_NORMAL + plugin.tags.join(", ")) + UI.println(UI.Style.TEXT_NORMAL_BOLD + "Capabilities:" + UI.Style.TEXT_NORMAL + " " + plugin.capabilities.join(", ")) + UI.println(UI.Style.TEXT_NORMAL_BOLD + "Author: " + UI.Style.TEXT_NORMAL + plugin.author) + if (plugin.homepage) { + UI.println(UI.Style.TEXT_NORMAL_BOLD + "Homepage: " + UI.Style.TEXT_INFO + plugin.homepage + UI.Style.TEXT_NORMAL) + } + UI.empty() + + if (installed) { + UI.println(UI.Style.TEXT_SUCCESS + "✓ Installed" + UI.Style.TEXT_NORMAL + UI.Style.TEXT_DIM + ` (${installed.spec})` + UI.Style.TEXT_NORMAL) + } else { + UI.println(UI.Style.TEXT_DIM + "Not installed" + UI.Style.TEXT_NORMAL) + UI.println(UI.Style.TEXT_DIM + `Install with: agent-core plugin install ${plugin.name}` + UI.Style.TEXT_NORMAL) + } + } else if (installed) { + // Plugin not in registry but is installed + UI.println(UI.Style.TEXT_HIGHLIGHT_BOLD + installed.name + UI.Style.TEXT_NORMAL) + UI.empty() + UI.println(UI.Style.TEXT_NORMAL_BOLD + "Package:" + UI.Style.TEXT_NORMAL + ` ${installed.spec}`) + UI.empty() + UI.println(UI.Style.TEXT_WARNING + "⚠" + UI.Style.TEXT_NORMAL + UI.Style.TEXT_DIM + " This plugin is not in the official registry" + UI.Style.TEXT_NORMAL) + } + } catch (error) { + UI.error(`Failed to get plugin info: ${error instanceof Error ? error.message : error}`) + process.exit(1) } - UI.empty() - - if (installed) { - UI.println(UI.Style.TEXT_SUCCESS + "✓ Installed" + UI.Style.TEXT_NORMAL + UI.Style.TEXT_DIM + ` (${installed.spec})` + UI.Style.TEXT_NORMAL) - } else { - UI.println(UI.Style.TEXT_DIM + "Not installed" + UI.Style.TEXT_NORMAL) - UI.println(UI.Style.TEXT_DIM + `Install with: agent-core plugin install ${plugin.name}` + UI.Style.TEXT_NORMAL) - } - } else if (installed) { - // Plugin not in registry but is installed - UI.println(UI.Style.TEXT_HIGHLIGHT_BOLD + installed.name + UI.Style.TEXT_NORMAL) - UI.empty() - UI.println(UI.Style.TEXT_NORMAL_BOLD + "Package:" + UI.Style.TEXT_NORMAL + ` ${installed.spec}`) - UI.empty() - UI.println(UI.Style.TEXT_WARNING + "⚠" + UI.Style.TEXT_NORMAL + UI.Style.TEXT_DIM + " This plugin is not in the official registry" + UI.Style.TEXT_NORMAL) - } - } catch (error) { - UI.error(`Failed to get plugin info: ${error instanceof Error ? error.message : error}`) - process.exit(1) - } + }, + }) }, }) diff --git a/packages/agent-core/src/cli/cmd/plugin/install.ts b/packages/agent-core/src/cli/cmd/plugin/install.ts index 1999aba601a..61eca1c02b0 100644 --- a/packages/agent-core/src/cli/cmd/plugin/install.ts +++ b/packages/agent-core/src/cli/cmd/plugin/install.ts @@ -1,6 +1,7 @@ import { cmd } from "../cmd" import { installPlugin, isInstalled } from "../../../plugin/manager" import { UI } from "../../ui" +import { Instance } from "../../../project/instance" export const InstallCommand = cmd({ command: "install ", @@ -22,32 +23,37 @@ export const InstallCommand = cmd({ async handler(args) { const { name, force } = args - try { - // Check if already installed - if (!force && (await isInstalled(name))) { - UI.println(UI.Style.TEXT_WARNING + `Plugin "${name}" is already installed` + UI.Style.TEXT_NORMAL) - UI.println(UI.Style.TEXT_DIM + "Use --force to reinstall" + UI.Style.TEXT_NORMAL) - return - } + await Instance.provide({ + directory: process.cwd(), + async fn() { + try { + // Check if already installed + if (!force && (await isInstalled(name))) { + UI.println(UI.Style.TEXT_WARNING + `Plugin "${name}" is already installed` + UI.Style.TEXT_NORMAL) + UI.println(UI.Style.TEXT_DIM + "Use --force to reinstall" + UI.Style.TEXT_NORMAL) + return + } - UI.println(UI.Style.TEXT_DIM + `Installing ${name}...` + UI.Style.TEXT_NORMAL) + UI.println(UI.Style.TEXT_DIM + `Installing ${name}...` + UI.Style.TEXT_NORMAL) - const result = await installPlugin(name) + const result = await installPlugin(name) - if (result.success) { - UI.println(UI.Style.TEXT_SUCCESS + "✓" + UI.Style.TEXT_NORMAL + " " + result.message) - if (result.plugin) { - UI.empty() - UI.println(UI.Style.TEXT_DIM + `Capabilities: ${result.plugin.capabilities.join(", ")}` + UI.Style.TEXT_NORMAL) - UI.println(UI.Style.TEXT_DIM + "Restart agent-core for changes to take effect" + UI.Style.TEXT_NORMAL) + if (result.success) { + UI.println(UI.Style.TEXT_SUCCESS + "✓" + UI.Style.TEXT_NORMAL + " " + result.message) + if (result.plugin) { + UI.empty() + UI.println(UI.Style.TEXT_DIM + `Capabilities: ${result.plugin.capabilities.join(", ")}` + UI.Style.TEXT_NORMAL) + UI.println(UI.Style.TEXT_DIM + "Restart agent-core for changes to take effect" + UI.Style.TEXT_NORMAL) + } + } else { + UI.println(UI.Style.TEXT_DANGER + "✗" + UI.Style.TEXT_NORMAL + " " + result.message) + process.exit(1) + } + } catch (error) { + UI.error(`Install failed: ${error instanceof Error ? error.message : error}`) + process.exit(1) } - } else { - UI.println(UI.Style.TEXT_DANGER + "✗" + UI.Style.TEXT_NORMAL + " " + result.message) - process.exit(1) - } - } catch (error) { - UI.error(`Install failed: ${error instanceof Error ? error.message : error}`) - process.exit(1) - } + }, + }) }, }) diff --git a/packages/agent-core/src/cli/cmd/plugin/list.ts b/packages/agent-core/src/cli/cmd/plugin/list.ts index caca49a2423..608e255b751 100644 --- a/packages/agent-core/src/cli/cmd/plugin/list.ts +++ b/packages/agent-core/src/cli/cmd/plugin/list.ts @@ -1,6 +1,7 @@ import { cmd } from "../cmd" import { listInstalled } from "../../../plugin/manager" import { UI } from "../../ui" +import { Instance } from "../../../project/instance" export const ListCommand = cmd({ command: "list", @@ -13,40 +14,45 @@ export const ListCommand = cmd({ default: false, }), async handler(args) { - try { - const installed = await listInstalled() + await Instance.provide({ + directory: process.cwd(), + async fn() { + try { + const installed = await listInstalled() - if (args.json) { - console.log(JSON.stringify(installed, null, 2)) - return - } + if (args.json) { + console.log(JSON.stringify(installed, null, 2)) + return + } - if (installed.length === 0) { - UI.println(UI.Style.TEXT_WARNING + "No plugins installed" + UI.Style.TEXT_NORMAL) - UI.println(UI.Style.TEXT_DIM + "Search for plugins with: agent-core plugin search" + UI.Style.TEXT_NORMAL) - return - } + if (installed.length === 0) { + UI.println(UI.Style.TEXT_WARNING + "No plugins installed" + UI.Style.TEXT_NORMAL) + UI.println(UI.Style.TEXT_DIM + "Search for plugins with: agent-core plugin search" + UI.Style.TEXT_NORMAL) + return + } - UI.println(UI.Style.TEXT_NORMAL_BOLD + `Installed plugins (${installed.length}):` + UI.Style.TEXT_NORMAL) - UI.empty() + UI.println(UI.Style.TEXT_NORMAL_BOLD + `Installed plugins (${installed.length}):` + UI.Style.TEXT_NORMAL) + UI.empty() - for (const plugin of installed) { - const name = plugin.registryInfo?.displayName ?? plugin.name - const badge = plugin.fromRegistry - ? UI.Style.TEXT_SUCCESS + "✓" + UI.Style.TEXT_NORMAL - : UI.Style.TEXT_WARNING + "?" + UI.Style.TEXT_NORMAL + for (const plugin of installed) { + const name = plugin.registryInfo?.displayName ?? plugin.name + const badge = plugin.fromRegistry + ? UI.Style.TEXT_SUCCESS + "✓" + UI.Style.TEXT_NORMAL + : UI.Style.TEXT_WARNING + "?" + UI.Style.TEXT_NORMAL - UI.println(`${badge} ${UI.Style.TEXT_HIGHLIGHT_BOLD}${name}${UI.Style.TEXT_NORMAL}`) - UI.println(UI.Style.TEXT_DIM + ` ${plugin.spec}` + UI.Style.TEXT_NORMAL) + UI.println(`${badge} ${UI.Style.TEXT_HIGHLIGHT_BOLD}${name}${UI.Style.TEXT_NORMAL}`) + UI.println(UI.Style.TEXT_DIM + ` ${plugin.spec}` + UI.Style.TEXT_NORMAL) - if (plugin.registryInfo) { - UI.println(UI.Style.TEXT_DIM + ` ${plugin.registryInfo.description}` + UI.Style.TEXT_NORMAL) + if (plugin.registryInfo) { + UI.println(UI.Style.TEXT_DIM + ` ${plugin.registryInfo.description}` + UI.Style.TEXT_NORMAL) + } + UI.empty() + } + } catch (error) { + UI.error(`Failed to list plugins: ${error instanceof Error ? error.message : error}`) + process.exit(1) } - UI.empty() - } - } catch (error) { - UI.error(`Failed to list plugins: ${error instanceof Error ? error.message : error}`) - process.exit(1) - } + }, + }) }, }) diff --git a/packages/agent-core/src/cli/cmd/plugin/remove.ts b/packages/agent-core/src/cli/cmd/plugin/remove.ts index 0f113ef5ee7..58b5cbafd1e 100644 --- a/packages/agent-core/src/cli/cmd/plugin/remove.ts +++ b/packages/agent-core/src/cli/cmd/plugin/remove.ts @@ -1,6 +1,7 @@ import { cmd } from "../cmd" import { removePlugin, isInstalled } from "../../../plugin/manager" import { UI } from "../../ui" +import { Instance } from "../../../project/instance" export const RemoveCommand = cmd({ command: "remove ", @@ -15,28 +16,33 @@ export const RemoveCommand = cmd({ async handler(args) { const { name } = args - try { - // Check if installed - if (!(await isInstalled(name))) { - UI.println(UI.Style.TEXT_WARNING + `Plugin "${name}" is not installed` + UI.Style.TEXT_NORMAL) - UI.println(UI.Style.TEXT_DIM + "Use 'agent-core plugin list' to see installed plugins" + UI.Style.TEXT_NORMAL) - return - } + await Instance.provide({ + directory: process.cwd(), + async fn() { + try { + // Check if installed + if (!(await isInstalled(name))) { + UI.println(UI.Style.TEXT_WARNING + `Plugin "${name}" is not installed` + UI.Style.TEXT_NORMAL) + UI.println(UI.Style.TEXT_DIM + "Use 'agent-core plugin list' to see installed plugins" + UI.Style.TEXT_NORMAL) + return + } - UI.println(UI.Style.TEXT_DIM + `Removing ${name}...` + UI.Style.TEXT_NORMAL) + UI.println(UI.Style.TEXT_DIM + `Removing ${name}...` + UI.Style.TEXT_NORMAL) - const result = await removePlugin(name) + const result = await removePlugin(name) - if (result.success) { - UI.println(UI.Style.TEXT_SUCCESS + "✓" + UI.Style.TEXT_NORMAL + " " + result.message) - UI.println(UI.Style.TEXT_DIM + "Restart agent-core for changes to take effect" + UI.Style.TEXT_NORMAL) - } else { - UI.println(UI.Style.TEXT_DANGER + "✗" + UI.Style.TEXT_NORMAL + " " + result.message) - process.exit(1) - } - } catch (error) { - UI.error(`Remove failed: ${error instanceof Error ? error.message : error}`) - process.exit(1) - } + if (result.success) { + UI.println(UI.Style.TEXT_SUCCESS + "✓" + UI.Style.TEXT_NORMAL + " " + result.message) + UI.println(UI.Style.TEXT_DIM + "Restart agent-core for changes to take effect" + UI.Style.TEXT_NORMAL) + } else { + UI.println(UI.Style.TEXT_DANGER + "✗" + UI.Style.TEXT_NORMAL + " " + result.message) + process.exit(1) + } + } catch (error) { + UI.error(`Remove failed: ${error instanceof Error ? error.message : error}`) + process.exit(1) + } + }, + }) }, }) diff --git a/packages/agent-core/src/plugin/registry.ts b/packages/agent-core/src/plugin/registry.ts index 9492eed7838..62dd68bb08c 100644 --- a/packages/agent-core/src/plugin/registry.ts +++ b/packages/agent-core/src/plugin/registry.ts @@ -47,7 +47,14 @@ async function readCacheFile(): Promise { try { const cachePath = await getCachePath() const content = await fs.readFile(cachePath, "utf-8") - return JSON.parse(content) + const parsed = JSON.parse(content) + // Validate cached data has expected structure + if (!parsed.data || typeof parsed.timestamp !== "number") { + return null + } + // Validate the registry data itself + RegistrySchema.parse(parsed.data) + return parsed as CachedRegistry } catch { return null }