zmarketplace 0.4.2

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.
@@ -0,0 +1,166 @@
1
+ /**
2
+ * npm registry adapter — searches the npm registry by keyword.
3
+ * Covers pi-package, claude-code, opencode, gemini-cli, codex keywords.
4
+ */
5
+
6
+ import type { Ecosystem, PackageResult, PackageType, RegistrySource } from "../core/types.ts";
7
+ import { ECOSYSTEM_KEYWORDS } from "../core/types.ts";
8
+
9
+ const NPM_SEARCH_URL = "https://registry.npmjs.org/-/v1/search";
10
+ const NPM_PACKAGE_URL = "https://registry.npmjs.org";
11
+
12
+ interface NpmSearchResponse {
13
+ total: number;
14
+ objects: Array<{
15
+ package: {
16
+ name: string;
17
+ version: string;
18
+ description: string;
19
+ keywords?: string[];
20
+ publisher?: { username?: string };
21
+ date?: string;
22
+ links: { npm?: string; repository?: string; homepage?: string };
23
+ scope?: string;
24
+ };
25
+ downloads?: number;
26
+ readme?: string;
27
+ }>;
28
+ }
29
+
30
+ interface NpmPackageMeta {
31
+ "dist-tags": { latest: string };
32
+ description?: string;
33
+ readme?: string;
34
+ versions: Record<string, {
35
+ dependencies?: Record<string, string>;
36
+ dist?: { unpackedSize?: number; fileCount?: number; tarball?: string };
37
+ license?: string;
38
+ repository?: { url?: string } | string;
39
+ homepage?: string;
40
+ keywords?: string[];
41
+ description?: string;
42
+ }>;
43
+ time?: Record<string, string>;
44
+ }
45
+
46
+ /** Detect which ecosystems a package targets based on its keywords. */
47
+ export function detectEcosystems(keywords?: string[], name?: string): Ecosystem[] {
48
+ const kws = new Set((keywords ?? []).map(k => k.toLowerCase()));
49
+ const result: Ecosystem[] = [];
50
+ const n = (name ?? "").toLowerCase();
51
+
52
+ if (kws.has("pi-package") || n.includes("pi-")) result.push("pi", "omp");
53
+ if (kws.has("claude-code") || kws.has("claude-code-plugin") || kws.has("cc-plugin") || n.includes("claude")) result.push("claude");
54
+ if (kws.has("opencode") || kws.has("opencode-plugin") || n.includes("opencode")) result.push("opencode");
55
+ if (kws.has("gemini-cli") || kws.has("gemini-extension") || kws.has("gemini-cli-extension") || n.includes("gemini")) result.push("gemini");
56
+ if (kws.has("codex") || kws.has("codex-plugin") || kws.has("codex-cli") || n.includes("codex")) result.push("codex");
57
+
58
+ if (result.length === 0) result.push("unknown");
59
+ return result;
60
+ }
61
+
62
+ /** Detect the package resource type from keywords and name. */
63
+ export function detectType(keywords?: string[], name?: string): PackageType {
64
+ const kws = new Set((keywords ?? []).map(k => k.toLowerCase()));
65
+ const n = (name ?? "").toLowerCase();
66
+
67
+ if (kws.has("theme")) return "theme";
68
+ if (kws.has("prompt") || kws.has("prompts") || kws.has("prompt-template")) return "prompt";
69
+ if (kws.has("skill") || kws.has("agent-skill")) return "skill";
70
+ if (kws.has("mcp") || kws.has("mcp-server") || n.includes("mcp")) return "mcp";
71
+ if (kws.has("extension") || kws.has("plugin")) return "extension";
72
+ // Heuristic: pi packages are usually extensions, claude/cc packages are plugins
73
+ if (kws.has("pi-package") || kws.has("opencode")) return "extension";
74
+ if (kws.has("claude-code")) return "plugin";
75
+ return "unknown";
76
+ }
77
+
78
+ /** Generate the appropriate install command for an ecosystem. */
79
+ export function installCommandFor(name: string, ecosystem: Ecosystem): string {
80
+ switch (ecosystem) {
81
+ case "pi":
82
+ case "omp": return `pi install npm:${name}`;
83
+ case "claude": return `claude plugin install npm:${name}`;
84
+ case "opencode": return `opencode plugin ${name}`;
85
+ case "gemini": return `gemini extension install npm:${name}`;
86
+ case "codex": return `codex plugin add npm:${name}`;
87
+ default: return `npm install ${name}`;
88
+ }
89
+ }
90
+
91
+ /** Search npm by ecosystem keyword(s). */
92
+ export async function searchNpm(
93
+ query: string,
94
+ options: { ecosystems?: Ecosystem[]; type?: PackageType | "all"; limit?: number },
95
+ ): Promise<PackageResult[]> {
96
+ const limit = options.limit ?? 25;
97
+ const allEcosystems: Ecosystem[] = options.ecosystems && options.ecosystems.length > 0
98
+ ? options.ecosystems
99
+ : ["pi", "claude", "opencode", "gemini", "codex"];
100
+
101
+ // Collect unique keywords for the selected ecosystems
102
+ const keywords = new Set<string>();
103
+ for (const eco of allEcosystems) {
104
+ const kws = ECOSYSTEM_KEYWORDS[eco as keyof typeof ECOSYSTEM_KEYWORDS];
105
+ if (kws) for (const k of kws) keywords.add(k);
106
+ }
107
+
108
+ // npm search doesn't handle complex OR queries well — query per keyword in parallel.
109
+ const perKeyword = Math.max(5, Math.ceil(limit / keywords.size));
110
+ const q = query.trim();
111
+
112
+ const fetches = [...keywords].map(async kw => {
113
+ const searchText = q ? `keywords:${kw} ${q}` : `keywords:${kw}`;
114
+ const url = `${NPM_SEARCH_URL}?text=${encodeURIComponent(searchText)}&size=${perKeyword}`;
115
+ try {
116
+ const resp = await fetch(url, { signal: AbortSignal.timeout(10000) });
117
+ if (!resp.ok) return [];
118
+ const data = await resp.json() as NpmSearchResponse;
119
+ return data.objects;
120
+ } catch {
121
+ return [];
122
+ }
123
+ });
124
+
125
+ const settled = await Promise.all(fetches);
126
+ const objects = settled.flat();
127
+
128
+ // Deduplicate by package name
129
+ const seen = new Set<string>();
130
+ const unique = objects.filter(obj => {
131
+ if (seen.has(obj.package.name)) return false;
132
+ seen.add(obj.package.name);
133
+ return true;
134
+ });
135
+
136
+ return unique.slice(0, limit).map(obj => {
137
+ const pkg = obj.package;
138
+ const ecosystems = detectEcosystems(pkg.keywords, pkg.name);
139
+ const type = detectType(pkg.keywords, pkg.name);
140
+ return {
141
+ name: pkg.name,
142
+ description: pkg.description ?? "",
143
+ version: pkg.version,
144
+ author: pkg.publisher?.username,
145
+ ecosystems,
146
+ type,
147
+ source: "npm" as RegistrySource,
148
+ homepage: pkg.links.homepage ?? pkg.links.repository,
149
+ npmUrl: pkg.links.npm ?? `https://www.npmjs.com/package/${pkg.name}`,
150
+ repository: pkg.links.repository,
151
+ publishedAt: pkg.date,
152
+ installCommand: installCommandFor(pkg.name, ecosystems[0] ?? "unknown"),
153
+ };
154
+ });
155
+ }
156
+
157
+ /** Fetch full metadata for a specific package. */
158
+ export async function getNpmPackageMeta(name: string): Promise<NpmPackageMeta | null> {
159
+ try {
160
+ const resp = await fetch(`${NPM_PACKAGE_URL}/${encodeURIComponent(name)}`, { signal: AbortSignal.timeout(10000) });
161
+ if (!resp.ok) return null;
162
+ return await resp.json() as NpmPackageMeta;
163
+ } catch {
164
+ return null;
165
+ }
166
+ }