mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-24 12:15:51 -04:00
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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 <name>",
|
||||
@@ -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)
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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 <name>",
|
||||
@@ -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)
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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 <name>",
|
||||
@@ -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)
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -47,7 +47,14 @@ async function readCacheFile(): Promise<CachedRegistry | null> {
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user