zmarketplace 0.4.6 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,84 +1,116 @@
1
1
  # zmarketplace
2
2
 
3
- Cross-agent marketplace search plugin. Type `/zmarketplace` in pi/omp to search, inspect, audit, and install packages across npm, Claude marketplace, and Gemini extensions.
3
+ Cross-agent package marketplace search. One `/zmarketplace` command searches npm, Claude marketplace, Gemini extensions, and the official MCP registry then browse, audit, and install.
4
4
 
5
5
  ## Install
6
6
 
7
7
  ```bash
8
- # pi / omp (slash command)
9
8
  pi install npm:zmarketplace
10
- # or link locally:
11
- omp plugin link ./search_marketplace
12
-
13
- # then in omp TUI:
14
- /reload
15
- /zmarketplace search mcp
16
9
  ```
17
10
 
18
- ## Usage
19
-
20
- ```
21
- /zmarketplace search <query> [--type=<type>] [--eco=<ecosystem>] [--limit=<n>]
22
- /zmarketplace detail <id|name>
23
- /zmarketplace audit <id|name>
24
- /zmarketplace install <id|name>
11
+ Or omp:
12
+ ```bash
13
+ omp plugin install npm:zmarketplace
25
14
  ```
26
15
 
27
- ### Search
16
+ Then `/reload` and type `/zmarketplace`.
17
+
18
+ ## Quick Start
28
19
 
29
20
  ```
30
- /zmarketplace search mcp
31
- /zmarketplace search subagent --eco=pi
32
- /zmarketplace search theme --type=theme --limit=10
21
+ /zmarketplace
22
+ → Choose limit: 25 / 50 / 150 / All (paged)
23
+ Type query: "mcp"
24
+ → Browse 50 results per page (← Previous / → Next)
25
+ → Enter on package → detail + README
26
+ → Enter on ⬇ Install → choose ecosystem → command at bottom
33
27
  ```
34
28
 
35
- Opens an interactive select list. Scroll up/down, type to filter, enter to pick.
29
+ ## Commands
36
30
 
37
- ### Detail + README
31
+ | Command | What |
32
+ |---|---|
33
+ | `/zmarketplace` | Interactive search prompt |
34
+ | `/zmarketplace search <query>` | Search directly |
35
+ | `/zmarketplace search mcp --eco=pi` | Filter by ecosystem |
36
+ | `/zmarketplace search theme --type=theme` | Filter by type |
37
+ | `/zmarketplace detail <name>` | Show package detail |
38
+ | `/zmarketplace audit <name>` | Security scan |
39
+ | `/zmarketplace install <name>` | Install flow |
38
40
 
39
- Pick a package → select "📋 Details + README" → full README in a scrollable list.
41
+ ## Detail View
40
42
 
41
- - **Enter on a URL line** (🖼 IMAGE, 🔗 link) → opens in your browser
42
- - **Enter on text** → stays in detail view
43
- - **Type to search** → filters README lines
44
- - **ESC or "Back to menu"** → returns to action menu
43
+ ```
44
+ learnship Details
45
+
46
+ Install (audit first)
47
+ 🔒 Audit only
48
+ ↩ Back to results
49
+ 📦 learnship v2.4.0 — MIT · 0 deps · 1296KB
50
+ Learn as you build. Build with intent.
51
+ 🔗 https://www.npmjs.com/package/learnship
52
+ 🔗 https://github.com/FavioVazquez/learnship
53
+ ━━━ README ━━━
54
+ # learnship
55
+ ...40 lines of README...
56
+ ...(see npm for full README)
57
+ ✅ Run: pi install npm:learnship
58
+ ```
59
+
60
+ - **Actions at top** — Install / Audit / Back
61
+ - **README below** — 40 lines, scrollable
62
+ - **Enter on 🔗 or 🖼** — opens URL in browser
63
+ - **Enter on text** — stays in detail
64
+ - **Selected command at bottom** — after picking install option
45
65
 
46
- ### Install
66
+ ## Install Options
47
67
 
48
- Pick "⬇ Install" choose ecosystem:
68
+ After audit, choose how to install:
49
69
 
50
70
  | Option | Command |
51
71
  |---|---|
52
- | 🥧 pi install | `pi install npm:<name>` |
53
- | ⌥ omp install | `omp plugin install npm:<name>` |
54
- | 🤖 claude install | `claude plugin install npm:<name>` |
55
- | 🔓 opencode install | `opencode plugin <name>` |
56
- | 💎 gemini install | `gemini extension install <url>` |
57
- | 🔲 codex install | `codex plugin add npm:<name>` |
58
- | 📦 npm install | `npm install <name>` |
72
+ | 🥧 pi | `pi install npm:<name>` |
73
+ | ⌥ omp | `omp plugin install npm:<name>` |
74
+ | 🤖 claude | `claude plugin install npm:<name>` |
75
+ | 🔓 opencode | `opencode plugin <name>` |
76
+ | 💎 gemini | `gemini extension install <url>` |
77
+ | 🔲 codex | `codex plugin add npm:<name>` |
78
+ | 📦 npm | `npm install <name>` |
79
+ | 🟤 bun | `bun add <name>` |
80
+ | 📦 pnpm | `pnpm add <name>` |
59
81
  | ⚡ bunx | `bunx <name>` |
60
82
 
61
- Quick security check runs before showing the command. High-risk packages require confirmation.
83
+ High-risk packages require confirmation before showing the command.
62
84
 
63
- ### Audit
85
+ ## Security Audit
64
86
 
65
- ```
66
- /zmarketplace audit pi-mcp-adapter
67
- ```
87
+ | Layer | What it checks |
88
+ |---|---|
89
+ | Metadata | Dependency count, file count, size, license |
90
+ | Source scan | Downloads tarball, scans `.ts`/`.js` for `eval()`, `execSync()`, `rm -rf`, `child_process`, `process.env`, HTTP calls |
68
91
 
69
- Two-layer security scan:
70
- - **Layer 1 (metadata):** dependency count, file count, size, license
71
- - **Layer 2 (source):** downloads tarball, scans `.ts`/`.js` for dangerous patterns (`rm -rf`, `eval()`, `execSync()`, `child_process`, etc.)
92
+ Severity levels: 🔴 critical · 🟠 high · 🟡 medium · 🟢 low
72
93
 
73
94
  ## Registries
74
95
 
75
- | Source | Coverage |
96
+ | Registry | Coverage |
76
97
  |---|---|
77
- | npm | `pi-package`, `claude-code`, `opencode`, `gemini-cli`, `codex` keywords |
78
- | Claude marketplace | `anthropics/claude-plugins-official` + community (~800+ plugins) |
79
- | Gemini extensions | `geminicli.com/extensions.json` (~993 extensions) |
98
+ | npm | `pi-package`, `claude-code`, `opencode`, `gemini-cli`, `codex`, `npm`, `bun`, `pnpm` keywords |
99
+ | Claude marketplace | Official + community (~800+ plugins) |
100
+ | Gemini extensions | geminicli.com (~993 extensions) |
101
+ | MCP registry | registry.modelcontextprotocol.io (official) |
102
+
103
+ All queried in parallel, deduplicated, ranked by relevance.
104
+
105
+ ## Filters
106
+
107
+ ```
108
+ --type=extension extension, skill, theme, prompt, plugin, mcp
109
+ --eco=pi pi, claude, opencode, gemini, codex, npm
110
+ --limit=50 25, 50, 150, All (paged at 50/page)
111
+ ```
80
112
 
81
- ## CLI (standalone, no agent needed)
113
+ ## CLI (standalone)
82
114
 
83
115
  ```bash
84
116
  bunx zmarketplace search "mcp" --limit=5
@@ -87,40 +119,44 @@ bunx zmarketplace audit pi-marketplace
87
119
  bunx zmarketplace install pi-marketplace
88
120
  ```
89
121
 
122
+ Works without any agent. Same core, different interface.
123
+
90
124
  ## Architecture
91
125
 
92
126
  ```
93
127
  src/
94
- ├── index.tspi/omp extension: registerCommand("/zmarketplace")
95
- ├── cli.tsstandalone CLI: bunx zmarketplace
96
- ├── opencode.tsopencode plugin entry
128
+ ├── index.ts pi/omp extension (registerCommand)
129
+ ├── cli.ts standalone CLI (bunx zmarketplace)
130
+ ├── opencode.ts opencode plugin entry
97
131
  ├── core/
98
- │ ├── types.tsunified package model
99
- │ ├── search.tscross-registry search + dedup + ranking
100
- │ ├── detail.tsnpm metadata + README fetch
101
- │ ├── audit.ts2-layer security scanner
102
- │ ├── install.tsagent detection + install command dispatch
103
- │ ├── cache.tsresults cache for ID-based reference
104
- │ └── tui.tsicons, formatting, arg parser
132
+ │ ├── types.ts unified package model + ecosystem keywords
133
+ │ ├── search.ts cross-registry search + dedup + ranking
134
+ │ ├── detail.ts npm metadata + README fetch
135
+ │ ├── audit.ts 2-layer security scanner
136
+ │ ├── install.ts agent detection + command dispatch
137
+ │ ├── cache.ts results cache for ID reference
138
+ │ └── tui.ts icons, formatting, arg parser, help text
105
139
  └── registries/
106
- ├── npm.tsnpm registry (parallel per-keyword queries)
107
- ├── claude.tsClaude marketplace JSON
108
- └── gemini.tsGemini CLI extensions registry
140
+ ├── npm.ts npm registry (parallel per-keyword queries)
141
+ ├── claude.ts Claude marketplace JSON
142
+ ├── gemini.ts Gemini CLI extensions registry
143
+ └── mcp.ts Official MCP registry
109
144
  ```
110
145
 
111
- ## Cross-agent manifests
146
+ ## Compatibility
112
147
 
113
- | File | Ecosystem |
114
- |---|---|
115
- | `package.json` (`pi.extensions`, `omp.extensions`) | Pi, OMP |
116
- | `package.json` (`bin`, `exports`) | CLI, OpenCode |
117
- | `.claude-plugin/plugin.json` | Claude Code |
118
- | `gemini-extension.json` | Gemini CLI |
119
- | `.codex-plugin/plugin.json` | Codex |
148
+ | Agent | Status | How |
149
+ |---|---|---|
150
+ | pi | ✅ Works | `pi install npm:zmarketplace` |
151
+ | omp | Works | `omp plugin install npm:zmarketplace` |
152
+ | OpenCode | Plugin entry | `opencode plugin zmarketplace` |
153
+ | Claude Code | CLI | `bunx zmarketplace search "mcp"` |
154
+ | Gemini CLI | CLI | `bunx zmarketplace search "mcp"` |
155
+ | Codex | ✅ CLI | `bunx zmarketplace search "mcp"` |
120
156
 
121
- ## Support
157
+ Zero runtime dependencies. TypeScript with Bun. No Bun-specific APIs (works on pi's jiti/Node loader).
122
158
 
123
- If this plugin helped you, consider supporting:
159
+ ## Support
124
160
 
125
161
  ☕ [ko-fi.com/zicodev](https://ko-fi.com/zicodev)
126
162
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zmarketplace",
3
- "version": "0.4.6",
3
+ "version": "0.5.3",
4
4
  "description": "Cross-agent marketplace search: find, audit, and install plugins/skills/themes/prompts across pi, omp, claude code, opencode, gemini cli, and codex",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -20,6 +20,7 @@
20
20
  "opencode",
21
21
  "gemini-cli",
22
22
  "codex",
23
+ "npm",
23
24
  "marketplace",
24
25
  "plugin",
25
26
  "search",
@@ -31,8 +31,9 @@ function buildCommand(packageName: string, target: InstallTarget): string {
31
31
  case "claude": return `claude plugin install npm:${packageName}`;
32
32
  case "opencode": return `opencode plugin ${packageName}`;
33
33
  case "gemini": return `gemini extension install npm:${packageName}`;
34
- case "codex": return `codex plugin add npm:${packageName}`;
35
- case "auto": return `npm install ${packageName}`;
34
+ case "npm":
35
+ case "auto":
36
+ default: return `npm install ${packageName}`;
36
37
  }
37
38
  }
38
39
 
@@ -49,6 +50,7 @@ async function resolveAutoTarget(packageName: string): Promise<InstallTarget> {
49
50
  if (kws.has("opencode")) return "opencode";
50
51
  if (kws.has("gemini-cli")) return "gemini";
51
52
  if (kws.has("codex")) return "codex";
53
+ if (kws.has("npm") || kws.has("bun") || kws.has("nodejs")) return "npm";
52
54
  return "auto";
53
55
  }
54
56
 
@@ -20,7 +20,6 @@ function registriesForOptions(registry?: RegistrySource | "all"): RegistrySource
20
20
  /** Map a user-facing ecosystem filter to npm ecosystem list for the npm adapter. */
21
21
  function ecosystemsForFilter(ecosystem?: Ecosystem | "all"): Ecosystem[] | undefined {
22
22
  if (!ecosystem || ecosystem === "all") return undefined;
23
- // pi and omp share the same npm keyword
24
23
  if (ecosystem === "omp") return ["pi"];
25
24
  return [ecosystem];
26
25
  }
package/src/core/tui.ts CHANGED
@@ -24,6 +24,7 @@ const ECO_LABEL: Record<Ecosystem, string> = {
24
24
  opencode: "opencode",
25
25
  gemini: "gemini",
26
26
  codex: "codex",
27
+ npm: "npm",
27
28
  universal: "universal",
28
29
  unknown: "?",
29
30
  };
@@ -131,10 +132,10 @@ export function formatHelp(): string {
131
132
  "",
132
133
  "Options for 'search':",
133
134
  " --type=<type> Filter: extension, skill, theme, prompt, plugin, mcp",
134
- " --eco=<ecosystem> Filter: pi, claude, opencode, gemini, codex",
135
+ " --eco=<ecosystem> Filter: pi, claude, opencode, gemini, codex, npm",
135
136
  " --limit=<n> Max results (default 20)",
136
137
  "",
137
- "Registries: npm + Claude marketplace + Gemini extensions",
138
+ "Registries: npm + Claude marketplace + Gemini extensions + MCP registry",
138
139
  "",
139
140
  "Examples:",
140
141
  " /zmarketplace search mcp",
package/src/core/types.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  /** Which agent ecosystem a package targets. */
6
- export type Ecosystem = "pi" | "omp" | "claude" | "opencode" | "gemini" | "codex" | "universal" | "unknown";
6
+ export type Ecosystem = "pi" | "omp" | "claude" | "opencode" | "gemini" | "codex" | "npm" | "universal" | "unknown";
7
7
 
8
8
  /** Package resource type. */
9
9
  export type PackageType = "extension" | "skill" | "theme" | "prompt" | "plugin" | "mcp" | "unknown";
@@ -113,7 +113,7 @@ export interface AuditReport {
113
113
  }
114
114
 
115
115
  /** Install target — which agent's install command to use. */
116
- export type InstallTarget = "pi" | "omp" | "claude" | "opencode" | "gemini" | "codex" | "auto";
116
+ export type InstallTarget = "pi" | "omp" | "claude" | "opencode" | "gemini" | "codex" | "npm" | "auto";
117
117
 
118
118
  /** Result of an install operation. */
119
119
  export interface InstallResult {
@@ -132,4 +132,5 @@ export const ECOSYSTEM_KEYWORDS: Record<Exclude<Ecosystem, "unknown" | "universa
132
132
  opencode: ["opencode", "opencode-plugin"],
133
133
  gemini: ["gemini-cli", "gemini-extension", "gemini-cli-extension"],
134
134
  codex: ["codex", "codex-plugin", "codex-cli"],
135
+ npm: ["npm", "bun", "pnpm", "yarn", "nodejs", "deno"],
135
136
  };
package/src/index.ts CHANGED
@@ -10,6 +10,7 @@ import { auditPackage } from "./core/audit.ts";
10
10
  import { cacheResults, resolveRef, cacheAudit } from "./core/cache.ts";
11
11
  import { formatResultOption, formatAuditReport, formatHelp, parseArgs } from "./core/tui.ts";
12
12
  import type { PackageResult } from "./core/types.ts";
13
+ import { spawn } from "node:child_process";
13
14
 
14
15
  // ── Types ──────────────────────────────────────────────────────────────────
15
16
 
@@ -26,9 +27,9 @@ interface Ctx { cwd: string; hasUI: boolean; ui: UI }
26
27
  // ── Helpers ────────────────────────────────────────────────────────────────
27
28
 
28
29
  function openUrl(url: string): void {
29
- if (process.platform === "win32") Bun.spawn(["cmd", "/c", "start", "", url], { stdout: "ignore", stderr: "ignore" });
30
- else if (process.platform === "darwin") Bun.spawn(["open", url], { stdout: "ignore", stderr: "ignore" });
31
- else Bun.spawn(["xdg-open", url], { stdout: "ignore", stderr: "ignore" });
30
+ const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
31
+ const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
32
+ spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
32
33
  }
33
34
 
34
35
  function extractUrl(line: string, repoBase?: string): string | null {
@@ -96,36 +97,34 @@ async function packageDetail(pkg: PackageResult, ctx: Ctx): Promise<void> {
96
97
  "⬇ Install (audit first)",
97
98
  "🔒 Audit only",
98
99
  "↩ Back to results",
99
- "━━━ Package ━━━",
100
- `📦 ${detail.name} v${detail.version ?? "?"}`,
100
+ `📦 ${detail.name} v${detail.version ?? "?"} ${detail.license ?? "?"} · ${detail.dependencyCount ?? "?"} deps · ${detail.size ? (detail.size / 1024).toFixed(0) + "KB" : "?"}`,
101
101
  detail.description || "",
102
- `License: ${detail.license ?? "?"} Deps: ${detail.dependencyCount ?? "?"} Size: ${detail.size ? (detail.size / 1024).toFixed(1) + " KB" : "?"}`,
103
- `Published: ${detail.publishedAt?.slice(0, 10) ?? "?"}`,
104
102
  ];
105
- if (detail.keywords?.length) lines.push(`Keywords: ${detail.keywords.join(", ")}`);
106
103
  if (detail.npmUrl) lines.push(`🔗 ${detail.npmUrl}`);
107
104
  if (repoBase) lines.push(`🔗 ${repoBase}`);
108
105
 
109
106
  if (detail.readme) {
110
- lines.push("━━━ README (enter on 🔗/🖼 to open) ━━━");
107
+ lines.push("━━━ README (40 lines — enter on 🔗 to open) ━━━");
111
108
  const rl = detail.readme
112
- .replace(/!\[.*?\]\((https?:\/\/[^)]+)\)/g, "\n🖼 IMAGE: $1\n")
113
- .replace(/!\[.*?\]\(([^)]+)\)/g, (_m, p) => `\n🖼 IMAGE: ${repoBase ? repoBase + "/raw/main/" + p.replace(/^\.\//, "") : p}\n`)
109
+ .replace(/!\[.*?\]\((https?:\/\/[^)]+)\)/g, "\n🖼 $1\n")
110
+ .replace(/!\[.*?\]\(([^)]+)\)/g, (_m, p) => `\n🖼 ${repoBase ? repoBase + "/raw/main/" + p.replace(/^\.\//, "") : p}\n`)
114
111
  .replace(/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g, "$1 → $2")
115
- .replace(/<img[^>]*src=["']([^"']+)["'][^>]*>/gi, (_m, p) => `\n🖼 IMAGE: ${p}\n`)
116
- .replace(/<a[^>]*href=["']([^"']+)["'][^>]*>(.*?)<\/a>/gi, "$2 → $1")
117
112
  .replace(/<[^>]+>/g, "")
118
113
  .split("\n").map(l => l.trimEnd()).filter(l => l.length > 0)
119
- .slice(0, 150);
114
+ .slice(0, 40);
120
115
  lines.push(...rl);
121
- if (detail.readme.length > 8000) lines.push("...(truncated — see npm for full README)");
116
+ lines.push("...(see npm for full README)");
122
117
  }
123
118
 
124
119
  while (true) {
125
120
  const selected = await ctx.ui.select(`${detail.name} — Details`, lines);
126
- if (!selected) continue; // ESC → stay in detail, don't quit to results
121
+ if (!selected) continue;
127
122
  if (selected.includes("Back to results")) return;
128
- if (selected.includes("Install")) { await doInstall(pkg, ctx); continue; } // stay in detail after install
123
+ if (selected.includes("Install")) {
124
+ const cmd = await doInstall(pkg, ctx);
125
+ if (cmd) lines.push(`✅ Run: ${cmd}`); // show command at BOTTOM
126
+ continue;
127
+ }
129
128
  if (selected.includes("Audit only")) { await doAudit(pkg.name, ctx); continue; }
130
129
  const url = extractUrl(selected, repoBase);
131
130
  if (url) { openUrl(url); ctx.ui.notify("🌐 Opened in browser", "info"); }
@@ -151,7 +150,7 @@ async function doAudit(name: string, ctx: Ctx): Promise<void> {
151
150
 
152
151
  // ── Install (audit FIRST, then options) ────────────────────────────────────
153
152
 
154
- async function doInstall(pkg: PackageResult, ctx: Ctx): Promise<void> {
153
+ async function doInstall(pkg: PackageResult, ctx: Ctx): Promise<string | null> {
155
154
  // Audit first
156
155
  ctx.ui.setStatus?.(`Auditing ${pkg.name} before install...`);
157
156
  const report = await auditPackage(pkg.name, { deepScan: true });
@@ -182,6 +181,12 @@ async function doInstall(pkg: PackageResult, ctx: Ctx): Promise<void> {
182
181
  if (eco === "opencode" && !seen.has("opencode")) { seen.add("opencode"); addCmd("🔓 opencode", `opencode plugin ${pkg.name}`); }
183
182
  if (eco === "gemini" && !seen.has("gemini")) { seen.add("gemini"); addCmd("💎 gemini", `gemini extension install ${pkg.repository ?? pkg.name}`); }
184
183
  if (eco === "codex" && !seen.has("codex")) { seen.add("codex"); addCmd("🔲 codex", `codex plugin add npm:${pkg.name}`); }
184
+ if (eco === "npm" && !seen.has("npm")) {
185
+ seen.add("npm");
186
+ addCmd("📦 npm", `npm install ${pkg.name}`);
187
+ addCmd("🟤 bun add", `bun add ${pkg.name}`);
188
+ addCmd("📦 pnpm add", `pnpm add ${pkg.name}`);
189
+ }
185
190
  }
186
191
  if (!seen.has("npm")) addCmd("📦 npm", `npm install ${pkg.name}`);
187
192
  addCmd("⚡ bunx", `bunx ${pkg.name}`);
@@ -190,13 +195,14 @@ async function doInstall(pkg: PackageResult, ctx: Ctx): Promise<void> {
190
195
  // High risk confirmation
191
196
  if (report.risk === "critical" || report.risk === "high") {
192
197
  const proceed = await ctx.ui.confirm(`${report.risk} risk`, `⚠️ ${pkg.name}: ${report.findings.length} security findings. Install anyway?`);
193
- if (!proceed) { ctx.ui.notify("Cancelled.", "info"); return; }
198
+ if (!proceed) { ctx.ui.notify("Cancelled.", "info"); return null; }
194
199
  }
195
200
 
196
201
  const choice = await ctx.ui.select(`Install ${pkg.name} — Risk: ${report.risk}`, cmds);
197
- if (!choice || choice === "↩ Cancel") return;
202
+ if (!choice || choice === "↩ Cancel") return null;
198
203
  const command = cmdMap.get(choice);
199
204
  if (command) ctx.ui.notify(`✅ Run:\n ${command}`, "info");
205
+ return command ?? null;
200
206
  }
201
207
 
202
208
  // ── Factory ────────────────────────────────────────────────────────────────
@@ -54,6 +54,7 @@ export function detectEcosystems(keywords?: string[], name?: string): Ecosystem[
54
54
  if (kws.has("opencode") || kws.has("opencode-plugin") || n.includes("opencode")) result.push("opencode");
55
55
  if (kws.has("gemini-cli") || kws.has("gemini-extension") || kws.has("gemini-cli-extension") || n.includes("gemini")) result.push("gemini");
56
56
  if (kws.has("codex") || kws.has("codex-plugin") || kws.has("codex-cli") || n.includes("codex")) result.push("codex");
57
+ if (kws.has("npm") || kws.has("bun") || kws.has("pnpm") || kws.has("yarn") || kws.has("nodejs") || kws.has("deno")) result.push("npm");
57
58
 
58
59
  if (result.length === 0) result.push("unknown");
59
60
  return result;
@@ -83,7 +84,7 @@ export function installCommandFor(name: string, ecosystem: Ecosystem): string {
83
84
  case "claude": return `claude plugin install npm:${name}`;
84
85
  case "opencode": return `opencode plugin ${name}`;
85
86
  case "gemini": return `gemini extension install npm:${name}`;
86
- case "codex": return `codex plugin add npm:${name}`;
87
+ case "npm": return `npm install ${name}`;
87
88
  default: return `npm install ${name}`;
88
89
  }
89
90
  }
@@ -96,7 +97,7 @@ export async function searchNpm(
96
97
  const limit = options.limit ?? 25;
97
98
  const allEcosystems: Ecosystem[] = options.ecosystems && options.ecosystems.length > 0
98
99
  ? options.ecosystems
99
- : ["pi", "claude", "opencode", "gemini", "codex"];
100
+ : ["pi", "claude", "opencode", "gemini", "codex", "npm"];
100
101
 
101
102
  // Collect unique keywords for the selected ecosystems
102
103
  const keywords = new Set<string>();