Release v0.4.8: plugin marketplace, namespaced MCP, centered popovers#22
Conversation
Pickers, prompts, and managers (model picker, session picker, link manager, MCP picker, approval prompts, follow-up prompts, hook trust, MCP approval, migration picker) previously rendered inline with the transcript, pushing the conversation down and clipping the input box on shorter terminals. Render every popover inside a single absolutely-positioned overlay that is centered vertically and horizontally over the transcript. The outer Box is now position="relative" so the overlay can anchor to the full viewport, and a derived `popoverOpen` flag drives the overlay's visibility. The old `dynamicHeight += 10` heuristic for inline popovers is no longer needed and is removed.
A new tabbed UI lists installed plugins and browses the complete Anthropic `claude-plugins-official` marketplace. Installation downloads the pinned plugin source as one bundle into `.orb/plugins/<name>/`, preserving skills, commands, agents, MCP config, hooks, scripts, rules, and other supporting files. Search filters by name, description, or author. `/plugin` and the former `/skills` command remain as aliases. Non-interactive plugin management is also available via `orbcode plugin install clickhouse@claude-plugins-official`, `orbcode plugin list`, and `orbcode plugin uninstall <name>`.
Marketplace installation no longer depends on GitHub tree scans. The
previous per-repository `SKILL.md` scan quickly exhausted GitHub's
anonymous API limit and incorrectly reported that plugins such as
ClickHouse had no skills. OrbCode now installs `git-subdir`, `url`,
`github`, and official relative-path sources through git at the
marketplace-pinned revision.
Plugin components are loaded from the installed bundle. Skills and
legacy commands are exposed as `<plugin>:<skill>`, bundled reference
files remain available, and `.mcp.json`/manifest MCP servers are
namespaced and discovered as project-scoped servers with
`${CLAUDE_PLUGIN_ROOT}` substitution.
Standalone project skills use `.orb/skills/`. The skill loader discovers
that directory alongside the legacy `.orbcode/skills/` location.
Installed plugins contribute project-scoped MCP servers. Namespace them like Claude Code so plugins cannot collide with each other or user config.
There was a problem hiding this comment.
🧪 PR Review is completed: Plugin marketplace system is well-structured with good path traversal protections. Two issues: a debug script accidentally committed to repo root, and an unhandled throw in mcpServerCount that could crash plugin listing.
Skipped files
CHANGELOG.md: Skipped file patternREADME.md: Skipped file patternpackage-lock.json: Skipped file pattern
⬇️ Low Priority Suggestions (2)
test-install.mjs (1 suggestion)
Location:
test-install.mjs(Lines 1-8)🔵 Code Quality
Issue:
test-install.mjsis a debug/scratch script that fetches from the GitHub API and logs results. It appears to be a leftover from development and should not be committed to the repository root.Fix: Remove this file from the PR.
Impact: Keeps the repository clean and avoids shipping debug scripts to users via npm.
- const res = await fetch("https://api.github.com/repos/anthropics/claude-plugins-official/git/trees/fe07b5e1bdcca448b346738b7e28ec8959dc6173?recursive=1"); - const data = await res.json(); - const mdFiles = data.tree.filter(item => - item.type === "blob" && - item.path.endsWith(".md") && - (item.path.startsWith("agents/") || item.path.startsWith("commands/") || item.path.startsWith("skills/")) - ); - console.log(mdFiles); +
src/plugins/manager.ts (1 suggestion)
Location:
src/plugins/manager.ts(Lines 41-49)🟡 Performance
Issue:
fetchOfficialMarketplaceusesfetch()without a timeout or AbortController. If the marketplace URL is slow or unresponsive, the CLI will hang indefinitely with no feedback to the user.Fix: Add an
AbortControllerwith a reasonable timeout (e.g., 30 seconds) and include a timeout-specific error message.Impact: Prevents indefinite hangs when the marketplace endpoint is unreachable; provides a clear error message to the user.
- export async function fetchOfficialMarketplace(): Promise<MarketplaceJson> { - const response = await fetch(OFFICIAL_MARKETPLACE_URL) - if (!response.ok) throw new Error(`Failed to fetch marketplace: ${response.statusText}`) - const marketplace = (await response.json()) as MarketplaceJson - if (!marketplace || !Array.isArray(marketplace.plugins)) { - throw new Error("The official marketplace returned an invalid catalog.") - } - return marketplace - } + export async function fetchOfficialMarketplace(): Promise<MarketplaceJson> { + const controller = new AbortController() + const timeout = setTimeout(() => controller.abort(), 30_000) + try { + const response = await fetch(OFFICIAL_MARKETPLACE_URL, { signal: controller.signal }) + if (!response.ok) throw new Error(`Failed to fetch marketplace: ${response.statusText}`) + const marketplace = (await response.json()) as MarketplaceJson + if (!marketplace || !Array.isArray(marketplace.plugins)) { + throw new Error("The official marketplace returned an invalid catalog.") + } + return marketplace + } catch (error) { + if (error instanceof Error && error.name === "AbortError") throw new Error("Marketplace request timed out after 30s.") + throw error + } finally { + clearTimeout(timeout) + } + }
| if (typeof candidate === "string") { | ||
| const custom = readJson(resolveInside(pluginDir, candidate)) | ||
| const servers = custom?.mcpServers ?? custom | ||
| if (isPlainObject(servers)) count += Object.keys(servers).length | ||
| } |
There was a problem hiding this comment.
🟡 Error Handling
Issue: resolveInside(pluginDir, candidate) on line 294 throws an Error if the candidate path escapes the plugin directory. Unlike loadPluginSkills in src/skills/loader.ts which wraps similar calls in try-catch, mcpServerCount has no error handling. Since mcpServerCount is called through inspectPlugin → readInstalledPlugin → listInstalledPlugins, a single plugin with a malformed mcpServers string path would crash the entire plugin listing, breaking both the CLI plugin list command and the TUI PluginManager.
Fix: Wrap the resolveInside call in a try-catch to gracefully skip invalid paths, matching the pattern used in loadPluginSkills.
Impact: Prevents one malformed plugin from crashing the entire plugin listing UI/CLI.
| if (typeof candidate === "string") { | |
| const custom = readJson(resolveInside(pluginDir, candidate)) | |
| const servers = custom?.mcpServers ?? custom | |
| if (isPlainObject(servers)) count += Object.keys(servers).length | |
| } | |
| if (typeof candidate === "string") { | |
| try { | |
| const custom = readJson(resolveInside(pluginDir, candidate)) | |
| const servers = custom?.mcpServers ?? custom | |
| if (isPlainObject(servers)) count += Object.keys(servers).length | |
| } catch { | |
| // Invalid optional MCP server path does not prevent plugin listing. | |
| } |
Summary
Cut the 0.4.8 release. Headline changes:
/plugins) — new tabbed UI lists installed plugins and browses the fullclaude-plugins-officialmarketplace. Install downloads the pinned source as one bundle into.orb/plugins/<name>/, preserving skills, commands, agents, MCP config, hooks, scripts, rules, and supporting files./pluginand the former/skillscommand remain as aliases.orbcode plugin install <name>@<marketplace>,orbcode plugin list, andorbcode plugin uninstall <name>.git-subdir,url,github, and official relative-path sources through git at the marketplace-pinned revision. The previous per-repoSKILL.mdscan exhausted GitHub's anonymous API limit and falsely reported plugins like ClickHouse as skill-less.<plugin>:<skill>, bundled reference files remain available, and.mcp.json/ manifest MCP servers are namespaced and discovered as project-scoped servers with${CLAUDE_PLUGIN_ROOT}substitution..orb/skills/(.orbcode/skills/still supported for back-compat).Test plan
test/plugins.test.tscovers install (git-subdir), list, and uninstall of marketplace pluginsclickhouse@claude-plugins-official, confirm skills appear asclickhouse:<name>and MCP server starts namespaced/pluginsshows installed list + browsable marketplace; search filters by name/description/authorChangelog
Full 0.4.8 notes are in
CHANGELOG.md.🤖 Generated with OrbCode