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 +107 -71
- package/package.json +2 -1
- package/src/core/install.ts +4 -2
- package/src/core/search.ts +0 -1
- package/src/core/tui.ts +3 -2
- package/src/core/types.ts +3 -2
- package/src/index.ts +26 -20
- package/src/registries/npm.ts +3 -2
package/README.md
CHANGED
|
@@ -1,84 +1,116 @@
|
|
|
1
1
|
# zmarketplace
|
|
2
2
|
|
|
3
|
-
Cross-agent marketplace search
|
|
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
|
-
|
|
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
|
-
|
|
16
|
+
Then `/reload` and type `/zmarketplace`.
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
28
19
|
|
|
29
20
|
```
|
|
30
|
-
/zmarketplace
|
|
31
|
-
/
|
|
32
|
-
|
|
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
|
-
|
|
29
|
+
## Commands
|
|
36
30
|
|
|
37
|
-
|
|
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
|
-
|
|
41
|
+
## Detail View
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
66
|
+
## Install Options
|
|
47
67
|
|
|
48
|
-
|
|
68
|
+
After audit, choose how to install:
|
|
49
69
|
|
|
50
70
|
| Option | Command |
|
|
51
71
|
|---|---|
|
|
52
|
-
| 🥧 pi
|
|
53
|
-
| ⌥ omp
|
|
54
|
-
| 🤖 claude
|
|
55
|
-
| 🔓 opencode
|
|
56
|
-
| 💎 gemini
|
|
57
|
-
| 🔲 codex
|
|
58
|
-
| 📦 npm
|
|
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
|
-
|
|
83
|
+
High-risk packages require confirmation before showing the command.
|
|
62
84
|
|
|
63
|
-
|
|
85
|
+
## Security Audit
|
|
64
86
|
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
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
|
-
|
|
|
96
|
+
| Registry | Coverage |
|
|
76
97
|
|---|---|
|
|
77
|
-
| npm | `pi-package`, `claude-code`, `opencode`, `gemini-cli`, `codex` keywords |
|
|
78
|
-
| Claude marketplace |
|
|
79
|
-
| Gemini 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
|
|
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.ts
|
|
95
|
-
├── cli.ts
|
|
96
|
-
├── opencode.ts
|
|
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.ts
|
|
99
|
-
│ ├── search.ts
|
|
100
|
-
│ ├── detail.ts
|
|
101
|
-
│ ├── audit.ts
|
|
102
|
-
│ ├── install.ts
|
|
103
|
-
│ ├── cache.ts
|
|
104
|
-
│ └── tui.ts
|
|
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.ts
|
|
107
|
-
├── claude.ts
|
|
108
|
-
|
|
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
|
-
##
|
|
146
|
+
## Compatibility
|
|
112
147
|
|
|
113
|
-
|
|
|
114
|
-
|
|
115
|
-
|
|
|
116
|
-
|
|
|
117
|
-
|
|
|
118
|
-
|
|
|
119
|
-
|
|
|
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
|
-
|
|
157
|
+
Zero runtime dependencies. TypeScript with Bun. No Bun-specific APIs (works on pi's jiti/Node loader).
|
|
122
158
|
|
|
123
|
-
|
|
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.
|
|
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",
|
package/src/core/install.ts
CHANGED
|
@@ -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 "
|
|
35
|
-
case "auto":
|
|
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
|
|
package/src/core/search.ts
CHANGED
|
@@ -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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
"
|
|
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
|
|
107
|
+
lines.push("━━━ README (40 lines — enter on 🔗 to open) ━━━");
|
|
111
108
|
const rl = detail.readme
|
|
112
|
-
.replace(/!\[.*?\]\((https?:\/\/[^)]+)\)/g, "\n🖼
|
|
113
|
-
.replace(/!\[.*?\]\(([^)]+)\)/g, (_m, 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,
|
|
114
|
+
.slice(0, 40);
|
|
120
115
|
lines.push(...rl);
|
|
121
|
-
|
|
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;
|
|
121
|
+
if (!selected) continue;
|
|
127
122
|
if (selected.includes("Back to results")) return;
|
|
128
|
-
if (selected.includes("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<
|
|
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 ────────────────────────────────────────────────────────────────
|
package/src/registries/npm.ts
CHANGED
|
@@ -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 "
|
|
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>();
|