@nodeskai/genehub 2026.3.2-9 → 2026.3.3-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.
package/dist/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command9 } from "commander";
4
+ import { Command as Command12 } from "commander";
5
5
 
6
- // src/commands/config.ts
6
+ // src/commands/auth.ts
7
+ import { createServer } from "http";
7
8
  import { Command } from "commander";
8
9
 
9
10
  // src/config.ts
@@ -16,12 +17,31 @@ var DEFAULT_CONFIG = {
16
17
  registryUrl: "https://genehub.nodeskai.com"
17
18
  };
18
19
  async function loadConfig() {
20
+ let fileConfig = {};
19
21
  try {
20
22
  const raw = await readFile(CONFIG_PATH, "utf-8");
21
- return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
23
+ fileConfig = JSON.parse(raw);
22
24
  } catch {
23
- return DEFAULT_CONFIG;
24
25
  }
26
+ const merged = { ...DEFAULT_CONFIG, ...fileConfig };
27
+ const envUrl = process.env.GENEHUB_REGISTRY_URL ?? process.env.GENEHUB_REGISTRY;
28
+ if (envUrl) {
29
+ merged.registryUrl = envUrl;
30
+ }
31
+ const envToken = process.env.GENEHUB_TOKEN;
32
+ if (envToken) {
33
+ merged.token = envToken;
34
+ }
35
+ return merged;
36
+ }
37
+ function getConfigSource(key) {
38
+ if (key === "registry") {
39
+ if (process.env.GENEHUB_REGISTRY_URL || process.env.GENEHUB_REGISTRY) return "env";
40
+ }
41
+ if (key === "token") {
42
+ if (process.env.GENEHUB_TOKEN) return "env";
43
+ }
44
+ return "file";
25
45
  }
26
46
  async function saveConfig(config) {
27
47
  const current = await loadConfig();
@@ -56,8 +76,102 @@ function table(headers, rows) {
56
76
  console.log(t.toString());
57
77
  }
58
78
 
79
+ // src/commands/auth.ts
80
+ var authCommand = new Command("auth").description("\u8BA4\u8BC1\u7BA1\u7406");
81
+ authCommand.command("login").description("\u901A\u8FC7 GitHub OAuth \u767B\u5F55\u5E76\u81EA\u52A8\u521B\u5EFA API Key").action(async () => {
82
+ const config = await loadConfig();
83
+ const registryUrl = config.registryUrl.replace(/\/$/, "");
84
+ const port = await findAvailablePort(9876);
85
+ const callbackUrl = `http://localhost:${port}/callback`;
86
+ const tokenPromise = waitForCallback(port);
87
+ const authUrl = `${registryUrl}/auth/github?cli_callback=${encodeURIComponent(callbackUrl)}`;
88
+ info(`Opening browser for GitHub login...`);
89
+ info(` ${authUrl}`);
90
+ const open = await import("open").catch(() => null);
91
+ if (open) {
92
+ await open.default(authUrl);
93
+ } else {
94
+ warn("Cannot open browser automatically. Please open the URL above manually.");
95
+ }
96
+ try {
97
+ const { token, login } = await tokenPromise;
98
+ await saveConfig({ token });
99
+ ok(`Logged in as @${login}`);
100
+ ok(`Token saved to config (${token.slice(0, 12)}****)`);
101
+ } catch (err) {
102
+ fail(err instanceof Error ? err.message : "Login failed");
103
+ process.exit(1);
104
+ }
105
+ });
106
+ authCommand.command("status").description("\u67E5\u770B\u5F53\u524D\u767B\u5F55\u72B6\u6001").action(async () => {
107
+ const config = await loadConfig();
108
+ if (!config.token) {
109
+ info("Not logged in");
110
+ info(" Run: genehub auth login");
111
+ return;
112
+ }
113
+ ok(`Token: ${config.token.slice(0, 12)}****`);
114
+ info(`Registry: ${config.registryUrl}`);
115
+ });
116
+ authCommand.command("logout").description("\u9000\u51FA\u767B\u5F55\uFF08\u6E05\u9664\u672C\u5730 token\uFF09").action(async () => {
117
+ await saveConfig({ token: void 0 });
118
+ ok("Logged out. Token removed.");
119
+ });
120
+ function findAvailablePort(preferred) {
121
+ return new Promise((resolve5, reject) => {
122
+ const server = createServer();
123
+ server.listen(preferred, () => {
124
+ server.close(() => resolve5(preferred));
125
+ });
126
+ server.on("error", () => {
127
+ const fallback = createServer();
128
+ fallback.listen(0, () => {
129
+ const addr = fallback.address();
130
+ const port = typeof addr === "object" && addr ? addr.port : 0;
131
+ fallback.close(() => resolve5(port));
132
+ });
133
+ fallback.on("error", reject);
134
+ });
135
+ });
136
+ }
137
+ function waitForCallback(port) {
138
+ return new Promise((resolve5, reject) => {
139
+ const timeout = setTimeout(() => {
140
+ server.close();
141
+ reject(new Error("Login timeout (60s)"));
142
+ }, 6e4);
143
+ const server = createServer((req, res) => {
144
+ if (!req.url?.startsWith("/callback")) {
145
+ res.writeHead(404);
146
+ res.end();
147
+ return;
148
+ }
149
+ const url = new URL(req.url, `http://localhost:${port}`);
150
+ const token = url.searchParams.get("token");
151
+ const login = url.searchParams.get("login");
152
+ if (!token) {
153
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
154
+ res.end("<h2>Login failed</h2><p>No token received.</p>");
155
+ return;
156
+ }
157
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
158
+ res.end(`<h2>Login successful</h2><p>@${login ?? "unknown"} - you can close this tab.</p>`);
159
+ clearTimeout(timeout);
160
+ server.close();
161
+ resolve5({ token, login: login ?? "" });
162
+ });
163
+ server.listen(port);
164
+ });
165
+ }
166
+
59
167
  // src/commands/config.ts
60
- var configCommand = new Command("config").description("\u7BA1\u7406 GeneHub CLI \u914D\u7F6E");
168
+ import { Command as Command2 } from "commander";
169
+ var configCommand = new Command2("config").description("\u7BA1\u7406 GeneHub CLI \u914D\u7F6E");
170
+ function sourceLabel(source) {
171
+ if (source === "env") return " [env]";
172
+ if (source === "default") return " [default]";
173
+ return "";
174
+ }
61
175
  configCommand.command("set <key> <value>").description("\u8BBE\u7F6E\u914D\u7F6E\u9879\uFF08registry / token\uFF09").action(async (key, value) => {
62
176
  const validKeys = ["registry", "token"];
63
177
  if (!validKeys.includes(key)) {
@@ -71,24 +185,257 @@ configCommand.command("set <key> <value>").description("\u8BBE\u7F6E\u914D\u7F6E
71
185
  }
72
186
  ok(`${key} = ${key === "token" ? `${value.slice(0, 8)}***` : value}`);
73
187
  });
74
- configCommand.command("get [key]").description("\u67E5\u770B\u914D\u7F6E").action(async (key) => {
188
+ configCommand.command("get [key]").description("\u67E5\u770B\u914D\u7F6E\uFF08\u652F\u6301 GENEHUB_REGISTRY_URL / GENEHUB_TOKEN \u73AF\u5883\u53D8\u91CF\u8986\u76D6\uFF09").action(async (key) => {
75
189
  const config = await loadConfig();
76
190
  if (key) {
77
191
  const map = {
78
192
  registry: config.registryUrl,
79
193
  token: config.token ? `${config.token.slice(0, 8)}***` : void 0
80
194
  };
81
- info(`${key} = ${map[key] ?? "(\u672A\u8BBE\u7F6E)"}`);
195
+ const src = sourceLabel(getConfigSource(key));
196
+ info(`${key} = ${map[key] ?? "(\u672A\u8BBE\u7F6E)"}${src}`);
82
197
  } else {
83
- info(`registry = ${config.registryUrl}`);
84
- info(`token = ${config.token ? `${config.token.slice(0, 8)}***` : "(\u672A\u8BBE\u7F6E)"}`);
198
+ const regSrc = sourceLabel(getConfigSource("registry"));
199
+ const tokSrc = sourceLabel(getConfigSource("token"));
200
+ info(`registry = ${config.registryUrl}${regSrc}`);
201
+ info(
202
+ `token = ${config.token ? `${config.token.slice(0, 8)}***` : "(\u672A\u8BBE\u7F6E)"}${tokSrc}`
203
+ );
85
204
  }
86
205
  });
87
206
 
207
+ // src/commands/genome.ts
208
+ import { readdir, readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
209
+ import { tmpdir } from "os";
210
+ import { join as join2, relative, resolve } from "path";
211
+ import { detectAdapter, GeneHubClient, getAdapter } from "@nodeskai/genehub-sdk";
212
+ import { Command as Command3 } from "commander";
213
+ import ora from "ora";
214
+ import { parse } from "yaml";
215
+ var IGNORED_PATTERNS = [".git", "node_modules", ".DS_Store", "__pycache__", ".venv"];
216
+ async function scanDirectory(dirPath) {
217
+ const files = {};
218
+ async function walk(currentPath) {
219
+ const entries = await readdir(currentPath, { withFileTypes: true });
220
+ for (const entry of entries) {
221
+ if (IGNORED_PATTERNS.includes(entry.name)) continue;
222
+ const fullPath = join2(currentPath, entry.name);
223
+ if (entry.isDirectory()) {
224
+ await walk(fullPath);
225
+ } else if (entry.isFile()) {
226
+ const relPath = relative(dirPath, fullPath);
227
+ const content = await readFile2(fullPath, "utf-8");
228
+ files[relPath] = content;
229
+ }
230
+ }
231
+ }
232
+ await walk(dirPath);
233
+ return files;
234
+ }
235
+ async function extractTarGz(buffer, destDir) {
236
+ const { mkdir: mkdir4 } = await import("fs/promises");
237
+ await mkdir4(destDir, { recursive: true });
238
+ const { extract } = await import("tar");
239
+ const tarPath = join2(tmpdir(), `genehub-genome-${Date.now()}.tar.gz`);
240
+ await writeFile2(tarPath, Buffer.from(buffer));
241
+ try {
242
+ await extract({ file: tarPath, cwd: destDir, strip: 1 });
243
+ } finally {
244
+ await rm(tarPath, { force: true });
245
+ }
246
+ }
247
+ var publishGenomeCommand = new Command3("publish").description("\u53D1\u5E03\u57FA\u56E0\u7EC4\u5230 GeneHub Registry").argument("<path>", "\u57FA\u56E0\u7EC4\u76EE\u5F55\u8DEF\u5F84\uFF08\u5305\u542B genome.yaml\uFF09").action(async (dirPath) => {
248
+ const config = await loadConfig();
249
+ if (!config.token) {
250
+ fail("\u672A\u914D\u7F6E\u8BA4\u8BC1 token");
251
+ process.exit(1);
252
+ }
253
+ const client = new GeneHubClient({ registryUrl: config.registryUrl, token: config.token });
254
+ const absPath = resolve(dirPath);
255
+ try {
256
+ const yamlPath = join2(absPath, "genome.yaml");
257
+ const raw = await readFile2(yamlPath, "utf-8");
258
+ const parsed = parse(raw);
259
+ if (!parsed.name || !parsed.slug || !parsed.version || !parsed.genes?.length) {
260
+ fail("genome.yaml \u7F3A\u5C11\u5FC5\u586B\u5B57\u6BB5: name, slug, version, genes");
261
+ process.exit(1);
262
+ }
263
+ const scanSpinner = ora("\u626B\u63CF\u57FA\u56E0\u7EC4\u76EE\u5F55...").start();
264
+ const files = await scanDirectory(absPath);
265
+ const fileCount = Object.keys(files).length;
266
+ scanSpinner.succeed(`\u626B\u63CF\u5B8C\u6210: ${fileCount} \u4E2A\u6587\u4EF6`);
267
+ const spinner = ora(`\u53D1\u5E03\u57FA\u56E0\u7EC4 ${parsed.slug}@${parsed.version}...`).start();
268
+ try {
269
+ const genome = await client.publishGenome(parsed, files);
270
+ spinner.succeed("\u53D1\u5E03\u6210\u529F");
271
+ ok(
272
+ `${genome.slug}@${genome.version} \u5DF2\u53D1\u5E03\u5230 GeneHub Registry (${fileCount} \u4E2A\u6587\u4EF6)`
273
+ );
274
+ } catch (err) {
275
+ const isSlugExists = err instanceof Error && err.message.includes("genome_slug_exists");
276
+ if (!isSlugExists) throw err;
277
+ spinner.text = `\u57FA\u56E0\u7EC4 ${parsed.slug} \u5DF2\u5B58\u5728\uFF0C\u53D1\u5E03\u65B0\u7248\u672C ${parsed.version}...`;
278
+ const genome = await client.publishGenomeVersion(
279
+ parsed.slug,
280
+ { version: parsed.version, genes: parsed.genes, files },
281
+ files
282
+ );
283
+ spinner.succeed("\u53D1\u5E03\u6210\u529F");
284
+ ok(`${genome.slug}@${genome.version} \u65B0\u7248\u672C\u5DF2\u53D1\u5E03 (${fileCount} \u4E2A\u6587\u4EF6)`);
285
+ }
286
+ } catch (err) {
287
+ fail(err instanceof Error ? err.message : String(err));
288
+ process.exit(1);
289
+ }
290
+ });
291
+ var installGenomeCommand = new Command3("install").description("\u5B89\u88C5\u57FA\u56E0\u7EC4\uFF08\u9012\u5F52\u5B89\u88C5\u6240\u6709\u5F15\u7528\u7684\u57FA\u56E0\uFF09").argument("<slug>", "\u57FA\u56E0\u7EC4\u6807\u8BC6\u7B26\uFF08\u652F\u6301 slug@version \u683C\u5F0F\uFF09").option("-p, --product <product>", "\u6307\u5B9A\u76EE\u6807\u4EA7\u54C1").option("-f, --force", "\u5F3A\u5236\u8986\u76D6\u5DF2\u5B89\u88C5\u57FA\u56E0", false).option("--target <path>", "\u6307\u5B9A\u5B89\u88C5\u76EE\u6807\u8DEF\u5F84").action(async (rawSlug, opts) => {
292
+ const config = await loadConfig();
293
+ const client = new GeneHubClient({ registryUrl: config.registryUrl, token: config.token });
294
+ const atIdx = rawSlug.lastIndexOf("@");
295
+ const slug = atIdx > 0 ? rawSlug.slice(0, atIdx) : rawSlug;
296
+ const version = atIdx > 0 ? rawSlug.slice(atIdx + 1) : void 0;
297
+ const spinner = ora(`\u83B7\u53D6\u57FA\u56E0\u7EC4 ${slug}...`).start();
298
+ try {
299
+ const genome = await client.getGenome(slug);
300
+ spinner.succeed(`\u57FA\u56E0\u7EC4: ${genome.name} v${genome.version} (${genome.genes.length} \u4E2A\u57FA\u56E0)`);
301
+ const adapter = opts.product ? getAdapter(opts.product) : await detectAdapter();
302
+ info(`\u76EE\u6807\u4EA7\u54C1: ${adapter.product}`);
303
+ const resolveSpinner = ora("\u89E3\u6790\u57FA\u56E0\u4F9D\u8D56...").start();
304
+ const resolved = await client.resolveGenome(slug, version);
305
+ resolveSpinner.succeed(`\u89E3\u6790\u5B8C\u6210: ${resolved.genes.length} \u4E2A\u57FA\u56E0\uFF08\u542B\u4F9D\u8D56\uFF09`);
306
+ if (resolved.warnings.length > 0) {
307
+ for (const w of resolved.warnings) warn(w);
308
+ }
309
+ if (resolved.conflicts.length > 0) {
310
+ for (const c of resolved.conflicts) warn(`\u51B2\u7A81: ${c}`);
311
+ }
312
+ let installed = 0;
313
+ let skipped = 0;
314
+ for (const gene of resolved.genes) {
315
+ if (!opts.force && await adapter.isInstalled(gene.slug)) {
316
+ skipped++;
317
+ continue;
318
+ }
319
+ const geneSpinner = ora(` \u5B89\u88C5 ${gene.slug}@${gene.version}...`).start();
320
+ try {
321
+ const manifest = await client.getManifest(gene.slug, gene.version);
322
+ let result;
323
+ try {
324
+ const archive = await client.downloadArchive(gene.slug, gene.version);
325
+ const tempDir = join2(tmpdir(), `genehub-install-${gene.slug}-${Date.now()}`);
326
+ await extractTarGz(archive, tempDir);
327
+ if (adapter.installFromDirectory) {
328
+ result = await adapter.installFromDirectory(tempDir, manifest, {
329
+ force: opts.force,
330
+ targetPath: opts.target
331
+ });
332
+ } else {
333
+ result = await adapter.install(manifest, {
334
+ force: opts.force,
335
+ targetPath: opts.target
336
+ });
337
+ }
338
+ await rm(tempDir, { recursive: true, force: true });
339
+ } catch {
340
+ result = await adapter.install(manifest, {
341
+ force: opts.force,
342
+ targetPath: opts.target
343
+ });
344
+ }
345
+ geneSpinner.succeed(` ${result.slug}@${result.version}`);
346
+ installed++;
347
+ try {
348
+ await client.reportInstall(gene.slug);
349
+ } catch {
350
+ }
351
+ } catch (err) {
352
+ geneSpinner.fail(
353
+ ` ${gene.slug} \u5B89\u88C5\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`
354
+ );
355
+ }
356
+ }
357
+ ok(`\u57FA\u56E0\u7EC4\u5B89\u88C5\u5B8C\u6210: ${installed} \u5B89\u88C5, ${skipped} \u8DF3\u8FC7`);
358
+ try {
359
+ await client.reportGenomeInstall(slug);
360
+ } catch {
361
+ }
362
+ } catch (err) {
363
+ spinner.fail("\u5B89\u88C5\u5931\u8D25");
364
+ fail(err instanceof Error ? err.message : String(err));
365
+ process.exit(1);
366
+ }
367
+ });
368
+ var infoGenomeCommand = new Command3("info").description("\u67E5\u770B\u57FA\u56E0\u7EC4\u8BE6\u60C5").argument("<slug>", "\u57FA\u56E0\u7EC4\u6807\u8BC6\u7B26").option("--json", "JSON \u683C\u5F0F\u8F93\u51FA", false).action(async (slug, opts) => {
369
+ const config = await loadConfig();
370
+ const client = new GeneHubClient({ registryUrl: config.registryUrl, token: config.token });
371
+ try {
372
+ const genome = await client.getGenome(slug);
373
+ if (opts.json) {
374
+ console.log(JSON.stringify(genome, null, 2));
375
+ return;
376
+ }
377
+ info(`\u540D\u79F0: ${genome.name}`);
378
+ info(`Slug: ${genome.slug}`);
379
+ info(`\u7248\u672C: ${genome.version}`);
380
+ info(`\u5206\u7C7B: ${genome.category}`);
381
+ info(`\u63CF\u8FF0: ${genome.description || genome.short_description || "(\u65E0)"}`);
382
+ info(`\u5B89\u88C5\u6570: ${genome.install_count}`);
383
+ if (genome.genes.length > 0) {
384
+ info(`
385
+ \u57FA\u56E0\u5217\u8868 (${genome.genes.length}\u4E2A):`);
386
+ table(
387
+ ["slug", "\u7248\u672C"],
388
+ genome.genes.map((g) => [g.slug, g.version])
389
+ );
390
+ }
391
+ } catch (err) {
392
+ fail(err instanceof Error ? err.message : String(err));
393
+ process.exit(1);
394
+ }
395
+ });
396
+ var listGenomeCommand = new Command3("list").description("\u641C\u7D22\u57FA\u56E0\u7EC4").option("-q, --query <keyword>", "\u641C\u7D22\u5173\u952E\u8BCD").option("-c, --category <category>", "\u6309\u5206\u7C7B\u8FC7\u6EE4").option("--json", "JSON \u683C\u5F0F\u8F93\u51FA", false).action(async (opts) => {
397
+ const config = await loadConfig();
398
+ const client = new GeneHubClient({ registryUrl: config.registryUrl, token: config.token });
399
+ try {
400
+ const result = await client.searchGenomes({
401
+ q: opts.query,
402
+ category: opts.category
403
+ });
404
+ if (opts.json) {
405
+ console.log(JSON.stringify(result, null, 2));
406
+ return;
407
+ }
408
+ if (result.items.length === 0) {
409
+ info("\u672A\u627E\u5230\u57FA\u56E0\u7EC4");
410
+ return;
411
+ }
412
+ table(
413
+ ["slug", "\u540D\u79F0", "\u7248\u672C", "\u5206\u7C7B", "\u57FA\u56E0\u6570", "\u5B89\u88C5\u6570"],
414
+ result.items.map((g) => [
415
+ g.slug,
416
+ g.name,
417
+ g.version,
418
+ g.category,
419
+ String(g.genes.length),
420
+ String(g.install_count)
421
+ ])
422
+ );
423
+ info(`\u5171 ${result.total} \u6761\u7ED3\u679C`);
424
+ } catch (err) {
425
+ fail(err instanceof Error ? err.message : String(err));
426
+ process.exit(1);
427
+ }
428
+ });
429
+ var genomeCommand = new Command3("genome").description("\u57FA\u56E0\u7EC4\u7BA1\u7406");
430
+ genomeCommand.addCommand(publishGenomeCommand);
431
+ genomeCommand.addCommand(installGenomeCommand);
432
+ genomeCommand.addCommand(infoGenomeCommand);
433
+ genomeCommand.addCommand(listGenomeCommand);
434
+
88
435
  // src/commands/init.ts
89
- import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
90
- import { join as join2, resolve } from "path";
91
- import { Command as Command2 } from "commander";
436
+ import { mkdir as mkdir2, writeFile as writeFile3 } from "fs/promises";
437
+ import { join as join3, resolve as resolve2 } from "path";
438
+ import { Command as Command4 } from "commander";
92
439
  import { stringify } from "yaml";
93
440
  var TEMPLATE = {
94
441
  slug: "my-gene",
@@ -131,14 +478,14 @@ metadata:
131
478
 
132
479
  \u5728\u6B64\u7F16\u5199\u57FA\u56E0\u7684\u6280\u80FD\u63CF\u8FF0...
133
480
  `;
134
- var initCommand = new Command2("init").description("\u521D\u59CB\u5316\u57FA\u56E0\u6A21\u677F").argument("[path]", "\u76EE\u6807\u76EE\u5F55", ".").action(async (dirPath) => {
135
- const absPath = resolve(dirPath);
481
+ var initCommand = new Command4("init").description("\u521D\u59CB\u5316\u57FA\u56E0\u6A21\u677F").argument("[path]", "\u76EE\u6807\u76EE\u5F55", ".").action(async (dirPath) => {
482
+ const absPath = resolve2(dirPath);
136
483
  try {
137
484
  await mkdir2(absPath, { recursive: true });
138
- const yamlPath = join2(absPath, "gene.yaml");
139
- await writeFile2(yamlPath, stringify(TEMPLATE), "utf-8");
140
- const skillPath = join2(absPath, "SKILL.md");
141
- await writeFile2(skillPath, SKILL_TEMPLATE, "utf-8");
485
+ const yamlPath = join3(absPath, "gene.yaml");
486
+ await writeFile3(yamlPath, stringify(TEMPLATE), "utf-8");
487
+ const skillPath = join3(absPath, "SKILL.md");
488
+ await writeFile3(skillPath, SKILL_TEMPLATE, "utf-8");
142
489
  ok(`\u57FA\u56E0\u6A21\u677F\u5DF2\u521B\u5EFA:`);
143
490
  info(` ${yamlPath}`);
144
491
  info(` ${skillPath}`);
@@ -150,11 +497,12 @@ var initCommand = new Command2("init").description("\u521D\u59CB\u5316\u57FA\u56
150
497
  });
151
498
 
152
499
  // src/commands/install.ts
153
- import { homedir as homedir2 } from "os";
154
- import { join as join3 } from "path";
155
- import { detectAdapter, GeneHubClient, getAdapter, LearningEngine } from "@nodeskai/genehub-sdk";
156
- import { Command as Command3 } from "commander";
157
- import ora from "ora";
500
+ import { mkdir as mkdir3, rm as rm2, writeFile as writeFile4 } from "fs/promises";
501
+ import { homedir as homedir2, tmpdir as tmpdir2 } from "os";
502
+ import { join as join4 } from "path";
503
+ import { detectAdapter as detectAdapter2, GeneHubClient as GeneHubClient2, getAdapter as getAdapter2, LearningEngine } from "@nodeskai/genehub-sdk";
504
+ import { Command as Command5 } from "commander";
505
+ import ora2 from "ora";
158
506
  function parseSlugVersion(input) {
159
507
  const atIdx = input.lastIndexOf("@");
160
508
  if (atIdx > 0) {
@@ -162,27 +510,59 @@ function parseSlugVersion(input) {
162
510
  }
163
511
  return { slug: input };
164
512
  }
165
- var installCommand = new Command3("install").description("\u5B89\u88C5\u57FA\u56E0\u5230\u5F53\u524D Agent \u73AF\u5883").argument("<slug>", "\u57FA\u56E0\u6807\u8BC6\u7B26\uFF08\u652F\u6301 slug@version \u683C\u5F0F\uFF09").option("-p, --product <product>", "\u6307\u5B9A\u76EE\u6807\u4EA7\u54C1\uFF08openclaw / nanobot / generic\uFF09").option("-f, --force", "\u5F3A\u5236\u8986\u76D6\u5DF2\u5B89\u88C5\u7248\u672C", false).option("--target <path>", "\u6307\u5B9A\u5B89\u88C5\u76EE\u6807\u8DEF\u5F84").option("--learn", "\u5B89\u88C5\u540E\u81EA\u52A8\u89E6\u53D1\u6DF1\u5EA6\u5B66\u4E60", false).action(async (rawSlug, opts) => {
513
+ async function extractTarGz2(buffer, destDir) {
514
+ await mkdir3(destDir, { recursive: true });
515
+ const { extract } = await import("tar");
516
+ const tarPath = join4(tmpdir2(), `genehub-${Date.now()}.tar.gz`);
517
+ await writeFile4(tarPath, Buffer.from(buffer));
518
+ try {
519
+ await extract({ file: tarPath, cwd: destDir, strip: 1 });
520
+ } finally {
521
+ await rm2(tarPath, { force: true });
522
+ }
523
+ }
524
+ var installCommand = new Command5("install").description("\u5B89\u88C5\u57FA\u56E0\u5230\u5F53\u524D Agent \u73AF\u5883").argument("<slug>", "\u57FA\u56E0\u6807\u8BC6\u7B26\uFF08\u652F\u6301 slug@version \u683C\u5F0F\uFF09").option("-p, --product <product>", "\u6307\u5B9A\u76EE\u6807\u4EA7\u54C1\uFF08openclaw / nanobot / generic\uFF09").option("-f, --force", "\u5F3A\u5236\u8986\u76D6\u5DF2\u5B89\u88C5\u7248\u672C", false).option("--target <path>", "\u6307\u5B9A\u5B89\u88C5\u76EE\u6807\u8DEF\u5F84").option("--learn", "\u5B89\u88C5\u540E\u81EA\u52A8\u89E6\u53D1\u6DF1\u5EA6\u5B66\u4E60", false).action(async (rawSlug, opts) => {
166
525
  const config = await loadConfig();
167
- const client = new GeneHubClient({ registryUrl: config.registryUrl, token: config.token });
526
+ const client = new GeneHubClient2({ registryUrl: config.registryUrl, token: config.token });
168
527
  const { slug, version } = parseSlugVersion(rawSlug);
169
- const spinner = ora(`\u83B7\u53D6\u57FA\u56E0 ${slug}${version ? `@${version}` : ""} \u7684 manifest...`).start();
528
+ const spinner = ora2(`\u83B7\u53D6\u57FA\u56E0 ${slug}${version ? `@${version}` : ""} \u7684 manifest...`).start();
170
529
  try {
171
530
  const manifest = await client.getManifest(slug, version);
172
531
  spinner.succeed(`\u83B7\u53D6 ${manifest.name} v${manifest.version}`);
173
- const adapter = opts.product ? getAdapter(opts.product) : await detectAdapter();
532
+ const adapter = opts.product ? getAdapter2(opts.product) : await detectAdapter2();
174
533
  info(`\u76EE\u6807\u4EA7\u54C1: ${adapter.product}`);
175
534
  if (!opts.force && await adapter.isInstalled(slug)) {
176
535
  const installedVer = await adapter.getInstalledVersion(slug);
177
536
  warn(`${slug}${installedVer ? ` v${installedVer}` : ""} \u5DF2\u5B89\u88C5\uFF0C\u4F7F\u7528 --force \u8986\u76D6`);
178
537
  return;
179
538
  }
180
- const installSpinner = ora("\u5B89\u88C5\u4E2D...").start();
181
- const result = await adapter.install(manifest, {
182
- force: opts.force,
183
- targetPath: opts.target
184
- });
185
- installSpinner.succeed("\u5B89\u88C5\u5B8C\u6210");
539
+ const installSpinner = ora2("\u5B89\u88C5\u4E2D...").start();
540
+ let result;
541
+ let isMultiFile = false;
542
+ try {
543
+ const archive = await client.downloadArchive(slug, version);
544
+ const tempDir = join4(tmpdir2(), `genehub-install-${slug}-${Date.now()}`);
545
+ await extractTarGz2(archive, tempDir);
546
+ isMultiFile = true;
547
+ if (adapter.installFromDirectory) {
548
+ result = await adapter.installFromDirectory(tempDir, manifest, {
549
+ force: opts.force,
550
+ targetPath: opts.target
551
+ });
552
+ } else {
553
+ result = await adapter.install(manifest, {
554
+ force: opts.force,
555
+ targetPath: opts.target
556
+ });
557
+ }
558
+ await rm2(tempDir, { recursive: true, force: true });
559
+ } catch {
560
+ result = await adapter.install(manifest, {
561
+ force: opts.force,
562
+ targetPath: opts.target
563
+ });
564
+ }
565
+ installSpinner.succeed(isMultiFile ? "\u5B89\u88C5\u5B8C\u6210\uFF08\u591A\u6587\u4EF6\u57FA\u56E0\uFF09" : "\u5B89\u88C5\u5B8C\u6210");
186
566
  ok(`${result.slug}@${result.version} \u5B89\u88C5\u6210\u529F`);
187
567
  info(`\u6587\u4EF6: ${result.files.join(", ")}`);
188
568
  if (result.needsRestart) {
@@ -196,11 +576,22 @@ var installCommand = new Command3("install").description("\u5B89\u88C5\u57FA\u56
196
576
  } catch {
197
577
  }
198
578
  if (opts.learn) {
199
- const workspaceDir = adapter.product === "openclaw" ? join3(homedir2(), ".openclaw", "workspace") : adapter.product === "nanobot" ? join3(homedir2(), ".nanobot", "workspace") : join3(process.cwd(), ".genehub");
579
+ const workspaceDir = adapter.product === "openclaw" ? join4(homedir2(), ".openclaw", "workspace") : adapter.product === "nanobot" ? join4(homedir2(), ".nanobot", "workspace") : join4(process.cwd(), ".genehub");
200
580
  const engine = new LearningEngine({ workspaceDir, adapter });
201
- const learnSpinner = ora("\u751F\u6210\u5B66\u4E60\u4EFB\u52A1...").start();
581
+ const learnSpinner = ora2("\u751F\u6210\u5B66\u4E60\u4EFB\u52A1...").start();
202
582
  await engine.createLearningTask(manifest);
203
- learnSpinner.succeed("\u5B66\u4E60\u4EFB\u52A1\u5DF2\u521B\u5EFA\uFF0CAgent \u5C06\u5728\u4E0B\u6B21\u5BF9\u8BDD\u4E2D\u5904\u7406");
583
+ learnSpinner.succeed("\u5B66\u4E60\u4EFB\u52A1\u5DF2\u521B\u5EFA");
584
+ if (adapter.triggerLearning) {
585
+ const triggerSpinner = ora2("\u89E6\u53D1 bot \u5B66\u4E60...").start();
586
+ try {
587
+ await adapter.triggerLearning("\u68C0\u67E5 learning-tasks/ \u76EE\u5F55\u5E76\u5904\u7406\u5B66\u4E60\u4EFB\u52A1");
588
+ triggerSpinner.succeed("\u5DF2\u89E6\u53D1 bot \u5B66\u4E60\uFF08\u540E\u53F0\u5904\u7406\u4E2D\uFF09");
589
+ } catch {
590
+ triggerSpinner.warn("\u81EA\u52A8\u89E6\u53D1\u5931\u8D25\uFF0CAgent \u5C06\u5728\u4E0B\u6B21\u5BF9\u8BDD\u4E2D\u5904\u7406");
591
+ }
592
+ } else {
593
+ info("Agent \u5C06\u5728\u4E0B\u6B21\u5BF9\u8BDD\u4E2D\u5904\u7406\u5B66\u4E60\u4EFB\u52A1");
594
+ }
204
595
  }
205
596
  } catch (err) {
206
597
  spinner.fail("\u5B89\u88C5\u5931\u8D25");
@@ -211,28 +602,28 @@ var installCommand = new Command3("install").description("\u5B89\u88C5\u57FA\u56
211
602
 
212
603
  // src/commands/learn.ts
213
604
  import { homedir as homedir3 } from "os";
214
- import { join as join4 } from "path";
215
- import { detectAdapter as detectAdapter2, GeneHubClient as GeneHubClient2, getAdapter as getAdapter2, LearningEngine as LearningEngine2 } from "@nodeskai/genehub-sdk";
216
- import { Command as Command4 } from "commander";
217
- import ora2 from "ora";
605
+ import { join as join5 } from "path";
606
+ import { detectAdapter as detectAdapter3, GeneHubClient as GeneHubClient3, getAdapter as getAdapter3, LearningEngine as LearningEngine2 } from "@nodeskai/genehub-sdk";
607
+ import { Command as Command6 } from "commander";
608
+ import ora3 from "ora";
218
609
  function getWorkspaceDir(product) {
219
610
  switch (product) {
220
611
  case "openclaw":
221
- return join4(homedir3(), ".openclaw", "workspace");
612
+ return join5(homedir3(), ".openclaw", "workspace");
222
613
  case "nanobot":
223
- return join4(homedir3(), ".nanobot", "workspace");
614
+ return join5(homedir3(), ".nanobot", "workspace");
224
615
  default:
225
- return join4(process.cwd(), ".genehub");
616
+ return join5(process.cwd(), ".genehub");
226
617
  }
227
618
  }
228
- var learnCommand = new Command4("learn").description("\u89E6\u53D1\u57FA\u56E0\u6DF1\u5EA6\u5B66\u4E60\uFF08L2\uFF09").argument("<slug>", "\u57FA\u56E0\u6807\u8BC6\u7B26").option("-p, --product <product>", "\u6307\u5B9A\u76EE\u6807\u4EA7\u54C1").option("--check", "\u68C0\u67E5\u5B66\u4E60\u7ED3\u679C\u5E76\u5E94\u7528").action(async (slug, opts) => {
619
+ var learnCommand = new Command6("learn").description("\u89E6\u53D1\u57FA\u56E0\u6DF1\u5EA6\u5B66\u4E60\uFF08L2\uFF09").argument("<slug>", "\u57FA\u56E0\u6807\u8BC6\u7B26").option("-p, --product <product>", "\u6307\u5B9A\u76EE\u6807\u4EA7\u54C1").option("--check", "\u68C0\u67E5\u5B66\u4E60\u7ED3\u679C\u5E76\u5E94\u7528").action(async (slug, opts) => {
229
620
  const config = await loadConfig();
230
- const client = new GeneHubClient2({ registryUrl: config.registryUrl, token: config.token });
231
- const adapter = opts.product ? getAdapter2(opts.product) : await detectAdapter2();
621
+ const client = new GeneHubClient3({ registryUrl: config.registryUrl, token: config.token });
622
+ const adapter = opts.product ? getAdapter3(opts.product) : await detectAdapter3();
232
623
  const workspaceDir = getWorkspaceDir(adapter.product);
233
624
  const engine = new LearningEngine2({ workspaceDir, adapter });
234
625
  if (opts.check) {
235
- const spinner2 = ora2("\u68C0\u67E5\u5B66\u4E60\u7ED3\u679C...").start();
626
+ const spinner2 = ora3("\u68C0\u67E5\u5B66\u4E60\u7ED3\u679C...").start();
236
627
  const result = await engine.checkResult(slug);
237
628
  if (!result) {
238
629
  spinner2.fail("\u672A\u627E\u5230\u5B66\u4E60\u7ED3\u679C");
@@ -251,7 +642,7 @@ var learnCommand = new Command4("learn").description("\u89E6\u53D1\u57FA\u56E0\u
251
642
  info(`\u7406\u7531: ${result.reason}`);
252
643
  }
253
644
  if (result.decision === "learned" && result.content) {
254
- const skillsDir = join4(workspaceDir, "skills");
645
+ const skillsDir = join5(workspaceDir, "skills");
255
646
  const applied = await engine.applyResult(slug, skillsDir);
256
647
  if (applied) {
257
648
  ok("\u5DF2\u5C06\u4E2A\u6027\u5316\u7248\u672C\u5E94\u7528\u5230\u6280\u80FD\u76EE\u5F55");
@@ -259,17 +650,17 @@ var learnCommand = new Command4("learn").description("\u89E6\u53D1\u57FA\u56E0\u
259
650
  }
260
651
  return;
261
652
  }
262
- const spinner = ora2(`\u83B7\u53D6 ${slug} \u7684 manifest...`).start();
653
+ const spinner = ora3(`\u83B7\u53D6 ${slug} \u7684 manifest...`).start();
263
654
  try {
264
655
  const manifest = await client.getManifest(slug);
265
656
  spinner.succeed(`\u83B7\u53D6 ${manifest.name} v${manifest.version}`);
266
657
  if (!manifest.learning?.objectives?.length && !manifest.learning?.scenarios?.length) {
267
658
  warn("\u8BE5\u57FA\u56E0\u6CA1\u6709\u5B9A\u4E49\u5B66\u4E60\u76EE\u6807\u6216\u7EC3\u4E60\u573A\u666F\uFF0C\u5C06\u521B\u5EFA\u57FA\u7840\u5B66\u4E60\u4EFB\u52A1");
268
659
  }
269
- const learnSpinner = ora2("\u751F\u6210\u5B66\u4E60\u4EFB\u52A1...").start();
660
+ const learnSpinner = ora3("\u751F\u6210\u5B66\u4E60\u4EFB\u52A1...").start();
270
661
  const task = await engine.createLearningTask(manifest);
271
662
  learnSpinner.succeed("\u5B66\u4E60\u4EFB\u52A1\u5DF2\u521B\u5EFA");
272
- ok(`\u4EFB\u52A1\u6587\u4EF6: ${join4(workspaceDir, "learning-tasks", `${slug}.md`)}`);
663
+ ok(`\u4EFB\u52A1\u6587\u4EF6: ${join5(workspaceDir, "learning-tasks", `${slug}.md`)}`);
273
664
  info(`\u7ED3\u679C\u8DEF\u5F84: ${task.callback_path}`);
274
665
  info("");
275
666
  info("\u4E0B\u4E00\u6B65\uFF1A");
@@ -283,11 +674,11 @@ var learnCommand = new Command4("learn").description("\u89E6\u53D1\u57FA\u56E0\u
283
674
  });
284
675
 
285
676
  // src/commands/list.ts
286
- import { detectAdapter as detectAdapter3, getAdapter as getAdapter3 } from "@nodeskai/genehub-sdk";
287
- import { Command as Command5 } from "commander";
288
- var listCommand = new Command5("list").description("\u5217\u51FA\u5F53\u524D\u73AF\u5883\u5DF2\u5B89\u88C5\u7684\u57FA\u56E0").option("-p, --product <product>", "\u6307\u5B9A\u76EE\u6807\u4EA7\u54C1").option("--json", "JSON \u683C\u5F0F\u8F93\u51FA", false).action(async (opts) => {
677
+ import { detectAdapter as detectAdapter4, getAdapter as getAdapter4 } from "@nodeskai/genehub-sdk";
678
+ import { Command as Command7 } from "commander";
679
+ var listCommand = new Command7("list").description("\u5217\u51FA\u5F53\u524D\u73AF\u5883\u5DF2\u5B89\u88C5\u7684\u57FA\u56E0").option("-p, --product <product>", "\u6307\u5B9A\u76EE\u6807\u4EA7\u54C1").option("--json", "JSON \u683C\u5F0F\u8F93\u51FA", false).action(async (opts) => {
289
680
  try {
290
- const adapter = opts.product ? getAdapter3(opts.product) : await detectAdapter3();
681
+ const adapter = opts.product ? getAdapter4(opts.product) : await detectAdapter4();
291
682
  const genes = await adapter.list();
292
683
  if (opts.json) {
293
684
  console.log(JSON.stringify(genes, null, 2));
@@ -310,31 +701,55 @@ var listCommand = new Command5("list").description("\u5217\u51FA\u5F53\u524D\u73
310
701
  });
311
702
 
312
703
  // src/commands/publish.ts
313
- import { readFile as readFile2 } from "fs/promises";
314
- import { join as join5, resolve as resolve2 } from "path";
315
- import { GeneHubClient as GeneHubClient3 } from "@nodeskai/genehub-sdk";
704
+ import { readdir as readdir2, readFile as readFile3 } from "fs/promises";
705
+ import { join as join6, relative as relative2, resolve as resolve3 } from "path";
706
+ import { GeneHubClient as GeneHubClient4 } from "@nodeskai/genehub-sdk";
316
707
  import { GeneManifestSchema } from "@nodeskai/genehub-types";
317
- import { Command as Command6 } from "commander";
318
- import ora3 from "ora";
319
- import { parse } from "yaml";
320
- var publishCommand = new Command6("publish").description("\u53D1\u5E03\u57FA\u56E0\u5230 GeneHub Registry").argument("<path>", "\u57FA\u56E0\u76EE\u5F55\u8DEF\u5F84\uFF08\u5305\u542B gene.yaml\uFF09").action(async (dirPath) => {
708
+ import { Command as Command8 } from "commander";
709
+ import ora4 from "ora";
710
+ import { parse as parse2 } from "yaml";
711
+ var IGNORED_PATTERNS2 = [".git", "node_modules", ".DS_Store", "__pycache__", ".venv"];
712
+ async function scanDirectory2(dirPath) {
713
+ const files = {};
714
+ async function walk(currentPath) {
715
+ const entries = await readdir2(currentPath, { withFileTypes: true });
716
+ for (const entry of entries) {
717
+ if (IGNORED_PATTERNS2.includes(entry.name)) continue;
718
+ const fullPath = join6(currentPath, entry.name);
719
+ if (entry.isDirectory()) {
720
+ await walk(fullPath);
721
+ } else if (entry.isFile()) {
722
+ const relPath = relative2(dirPath, fullPath);
723
+ const content = await readFile3(fullPath, "utf-8");
724
+ files[relPath] = content;
725
+ }
726
+ }
727
+ }
728
+ await walk(dirPath);
729
+ return files;
730
+ }
731
+ var publishCommand = new Command8("publish").description("\u53D1\u5E03\u57FA\u56E0\u5230 GeneHub Registry").argument("<path>", "\u57FA\u56E0\u76EE\u5F55\u8DEF\u5F84\uFF08\u5305\u542B gene.yaml\uFF09").action(async (dirPath) => {
321
732
  const config = await loadConfig();
322
733
  if (!config.token) {
323
- fail("\u672A\u914D\u7F6E\u8BA4\u8BC1 token\uFF0C\u8BF7\u5148\u8FD0\u884C genehub config --token <token>");
734
+ fail("\u672A\u914D\u7F6E\u8BA4\u8BC1 token");
735
+ info(" \u65B9\u5F0F 1: genehub config set token <token>");
736
+ info(" \u65B9\u5F0F 2: export GENEHUB_TOKEN=<token>");
324
737
  process.exit(1);
325
738
  }
326
- const client = new GeneHubClient3({ registryUrl: config.registryUrl, token: config.token });
327
- const absPath = resolve2(dirPath);
739
+ const client = new GeneHubClient4({ registryUrl: config.registryUrl, token: config.token });
740
+ const absPath = resolve3(dirPath);
328
741
  try {
329
- const yamlPath = join5(absPath, "gene.yaml");
330
- const raw = await readFile2(yamlPath, "utf-8");
331
- const parsed = parse(raw);
332
- const skillMdPath = join5(absPath, "SKILL.md");
742
+ const yamlPath = join6(absPath, "gene.yaml");
743
+ const raw = await readFile3(yamlPath, "utf-8");
744
+ const parsed = parse2(raw);
333
745
  if (parsed.skill?.file && !parsed.skill.content) {
334
746
  try {
335
- parsed.skill.content = await readFile2(join5(absPath, parsed.skill.file), "utf-8");
747
+ parsed.skill.content = await readFile3(join6(absPath, parsed.skill.file), "utf-8");
336
748
  } catch {
337
- parsed.skill.content = await readFile2(skillMdPath, "utf-8");
749
+ try {
750
+ parsed.skill.content = await readFile3(join6(absPath, "SKILL.md"), "utf-8");
751
+ } catch {
752
+ }
338
753
  }
339
754
  }
340
755
  const validation = GeneManifestSchema.safeParse(parsed);
@@ -345,10 +760,23 @@ var publishCommand = new Command6("publish").description("\u53D1\u5E03\u57FA\u56
345
760
  }
346
761
  process.exit(1);
347
762
  }
348
- const spinner = ora3(`\u53D1\u5E03 ${validation.data.slug}@${validation.data.version}...`).start();
349
- const gene = await client.publishGene(validation.data);
763
+ const { slug, version } = validation.data;
764
+ const scanSpinner = ora4("\u626B\u63CF\u57FA\u56E0\u76EE\u5F55...").start();
765
+ const files = await scanDirectory2(absPath);
766
+ const fileCount = Object.keys(files).length;
767
+ scanSpinner.succeed(`\u626B\u63CF\u5B8C\u6210: ${fileCount} \u4E2A\u6587\u4EF6`);
768
+ const spinner = ora4(`\u53D1\u5E03 ${slug}@${version} (${fileCount} \u4E2A\u6587\u4EF6)...`).start();
769
+ let gene;
770
+ try {
771
+ gene = await client.publishGene(validation.data, files);
772
+ } catch (err) {
773
+ const isSlugExists = err instanceof Error && err.message.includes("gene_slug_exists");
774
+ if (!isSlugExists) throw err;
775
+ spinner.text = `\u57FA\u56E0 ${slug} \u5DF2\u5B58\u5728\uFF0C\u53D1\u5E03\u65B0\u7248\u672C ${version}...`;
776
+ gene = await client.publishVersion(slug, validation.data, void 0, files);
777
+ }
350
778
  spinner.succeed("\u53D1\u5E03\u6210\u529F");
351
- ok(`${gene.slug}@${gene.version} \u5DF2\u53D1\u5E03\u5230 GeneHub Registry`);
779
+ ok(`${gene.slug}@${gene.version} \u5DF2\u53D1\u5E03\u5230 GeneHub Registry (${fileCount} \u4E2A\u6587\u4EF6)`);
352
780
  } catch (err) {
353
781
  fail(err instanceof Error ? err.message : String(err));
354
782
  process.exit(1);
@@ -356,11 +784,11 @@ var publishCommand = new Command6("publish").description("\u53D1\u5E03\u57FA\u56
356
784
  });
357
785
 
358
786
  // src/commands/search.ts
359
- import { GeneHubClient as GeneHubClient4 } from "@nodeskai/genehub-sdk";
360
- import { Command as Command7 } from "commander";
361
- var searchCommand = new Command7("search").description("\u641C\u7D22\u57FA\u56E0\u5E93").argument("[keyword]", "\u641C\u7D22\u5173\u952E\u8BCD").option("-c, --category <category>", "\u6309\u5206\u7C7B\u8FC7\u6EE4").option("-t, --tags <tags>", "\u6309\u6807\u7B7E\u8FC7\u6EE4\uFF08\u9017\u53F7\u5206\u9694\uFF09").option("--compat <product>", "\u6309\u517C\u5BB9\u4EA7\u54C1\u8FC7\u6EE4").option("-s, --sort <sort>", "\u6392\u5E8F\u65B9\u5F0F\uFF08newest / popular / rating\uFF09", "newest").option("--page <page>", "\u9875\u7801", "1").option("--json", "JSON \u683C\u5F0F\u8F93\u51FA", false).action(async (keyword, opts) => {
787
+ import { GeneHubClient as GeneHubClient5 } from "@nodeskai/genehub-sdk";
788
+ import { Command as Command9 } from "commander";
789
+ var searchCommand = new Command9("search").description("\u641C\u7D22\u57FA\u56E0\u5E93").argument("[keyword]", "\u641C\u7D22\u5173\u952E\u8BCD").option("-c, --category <category>", "\u6309\u5206\u7C7B\u8FC7\u6EE4").option("-t, --tags <tags>", "\u6309\u6807\u7B7E\u8FC7\u6EE4\uFF08\u9017\u53F7\u5206\u9694\uFF09").option("--compat <product>", "\u6309\u517C\u5BB9\u4EA7\u54C1\u8FC7\u6EE4").option("-s, --sort <sort>", "\u6392\u5E8F\u65B9\u5F0F\uFF08newest / popular / rating\uFF09", "newest").option("--page <page>", "\u9875\u7801", "1").option("--json", "JSON \u683C\u5F0F\u8F93\u51FA", false).action(async (keyword, opts) => {
362
790
  const config = await loadConfig();
363
- const client = new GeneHubClient4({ registryUrl: config.registryUrl, token: config.token });
791
+ const client = new GeneHubClient5({ registryUrl: config.registryUrl, token: config.token });
364
792
  try {
365
793
  const result = await client.searchGenes({
366
794
  q: keyword,
@@ -396,18 +824,323 @@ var searchCommand = new Command7("search").description("\u641C\u7D22\u57FA\u56E0
396
824
  }
397
825
  });
398
826
 
827
+ // src/commands/template.ts
828
+ import { readdir as readdir3, readFile as readFile4, rm as rm3, writeFile as writeFile5 } from "fs/promises";
829
+ import { tmpdir as tmpdir3 } from "os";
830
+ import { join as join7, relative as relative3, resolve as resolve4 } from "path";
831
+ import { detectAdapter as detectAdapter5, GeneHubClient as GeneHubClient6, getAdapter as getAdapter5 } from "@nodeskai/genehub-sdk";
832
+ import { Command as Command10 } from "commander";
833
+ import ora5 from "ora";
834
+ import { parse as parse3 } from "yaml";
835
+ var IGNORED_PATTERNS3 = [".git", "node_modules", ".DS_Store", "__pycache__", ".venv"];
836
+ async function scanDirectory3(dirPath) {
837
+ const files = {};
838
+ async function walk(currentPath) {
839
+ const entries = await readdir3(currentPath, { withFileTypes: true });
840
+ for (const entry of entries) {
841
+ if (IGNORED_PATTERNS3.includes(entry.name)) continue;
842
+ const fullPath = join7(currentPath, entry.name);
843
+ if (entry.isDirectory()) {
844
+ await walk(fullPath);
845
+ } else if (entry.isFile()) {
846
+ const relPath = relative3(dirPath, fullPath);
847
+ const content = await readFile4(fullPath, "utf-8");
848
+ files[relPath] = content;
849
+ }
850
+ }
851
+ }
852
+ await walk(dirPath);
853
+ return files;
854
+ }
855
+ async function extractTarGz3(buffer, destDir) {
856
+ const { mkdir: mkdir4 } = await import("fs/promises");
857
+ await mkdir4(destDir, { recursive: true });
858
+ const { extract } = await import("tar");
859
+ const tarPath = join7(tmpdir3(), `genehub-template-${Date.now()}.tar.gz`);
860
+ await writeFile5(tarPath, Buffer.from(buffer));
861
+ try {
862
+ await extract({ file: tarPath, cwd: destDir, strip: 1 });
863
+ } finally {
864
+ await rm3(tarPath, { force: true });
865
+ }
866
+ }
867
+ var publishTemplateCommand = new Command10("publish").description("\u53D1\u5E03 AI \u5458\u5DE5\u6A21\u677F\u5230 GeneHub Registry").argument("<path>", "\u6A21\u677F\u76EE\u5F55\u8DEF\u5F84\uFF08\u5305\u542B template.yaml\uFF09").action(async (dirPath) => {
868
+ const config = await loadConfig();
869
+ if (!config.token) {
870
+ fail("\u672A\u914D\u7F6E\u8BA4\u8BC1 token");
871
+ process.exit(1);
872
+ }
873
+ const client = new GeneHubClient6({ registryUrl: config.registryUrl, token: config.token });
874
+ const absPath = resolve4(dirPath);
875
+ try {
876
+ const yamlPath = join7(absPath, "template.yaml");
877
+ const raw = await readFile4(yamlPath, "utf-8");
878
+ const parsed = parse3(raw);
879
+ if (!parsed.name || !parsed.slug || !parsed.version) {
880
+ fail("template.yaml \u7F3A\u5C11\u5FC5\u586B\u5B57\u6BB5: name, slug, version");
881
+ process.exit(1);
882
+ }
883
+ if (!parsed.genomes?.length && !parsed.genes?.length) {
884
+ fail("template.yaml \u81F3\u5C11\u9700\u8981\u5F15\u7528\u4E00\u4E2A genome \u6216 gene");
885
+ process.exit(1);
886
+ }
887
+ const scanSpinner = ora5("\u626B\u63CF\u6A21\u677F\u76EE\u5F55...").start();
888
+ const files = await scanDirectory3(absPath);
889
+ const fileCount = Object.keys(files).length;
890
+ scanSpinner.succeed(`\u626B\u63CF\u5B8C\u6210: ${fileCount} \u4E2A\u6587\u4EF6`);
891
+ const spinner = ora5(`\u53D1\u5E03\u6A21\u677F ${parsed.slug}@${parsed.version}...`).start();
892
+ try {
893
+ const template = await client.publishTemplate(parsed, files);
894
+ spinner.succeed("\u53D1\u5E03\u6210\u529F");
895
+ ok(
896
+ `${template.slug}@${template.version} \u5DF2\u53D1\u5E03\u5230 GeneHub Registry (${fileCount} \u4E2A\u6587\u4EF6)`
897
+ );
898
+ } catch (err) {
899
+ const isSlugExists = err instanceof Error && err.message.includes("template_slug_exists");
900
+ if (!isSlugExists) throw err;
901
+ spinner.text = `\u6A21\u677F ${parsed.slug} \u5DF2\u5B58\u5728\uFF0C\u53D1\u5E03\u65B0\u7248\u672C ${parsed.version}...`;
902
+ const template = await client.publishTemplateVersion(
903
+ parsed.slug,
904
+ {
905
+ version: parsed.version,
906
+ genomes: parsed.genomes ?? [],
907
+ genes: parsed.genes,
908
+ files
909
+ },
910
+ files
911
+ );
912
+ spinner.succeed("\u53D1\u5E03\u6210\u529F");
913
+ ok(`${template.slug}@${template.version} \u65B0\u7248\u672C\u5DF2\u53D1\u5E03 (${fileCount} \u4E2A\u6587\u4EF6)`);
914
+ }
915
+ } catch (err) {
916
+ fail(err instanceof Error ? err.message : String(err));
917
+ process.exit(1);
918
+ }
919
+ });
920
+ var installTemplateCommand = new Command10("install").description("\u5B89\u88C5 AI \u5458\u5DE5\u6A21\u677F\uFF08\u9012\u5F52\u5B89\u88C5\u6240\u6709\u57FA\u56E0\u7EC4\u548C\u57FA\u56E0\uFF09").argument("<slug>", "\u6A21\u677F\u6807\u8BC6\u7B26\uFF08\u652F\u6301 slug@version \u683C\u5F0F\uFF09").option("-p, --product <product>", "\u6307\u5B9A\u76EE\u6807\u4EA7\u54C1").option("-f, --force", "\u5F3A\u5236\u8986\u76D6\u5DF2\u5B89\u88C5\u57FA\u56E0", false).option("--target <path>", "\u6307\u5B9A\u5B89\u88C5\u76EE\u6807\u8DEF\u5F84").action(async (rawSlug, opts) => {
921
+ const config = await loadConfig();
922
+ const client = new GeneHubClient6({ registryUrl: config.registryUrl, token: config.token });
923
+ const atIdx = rawSlug.lastIndexOf("@");
924
+ const slug = atIdx > 0 ? rawSlug.slice(0, atIdx) : rawSlug;
925
+ const spinner = ora5(`\u83B7\u53D6\u6A21\u677F ${slug}...`).start();
926
+ try {
927
+ const template = await client.getTemplate(slug);
928
+ spinner.succeed(
929
+ `\u6A21\u677F: ${template.name} v${template.version} (${template.genomes.length} \u57FA\u56E0\u7EC4, ${template.genes.length} \u989D\u5916\u57FA\u56E0)`
930
+ );
931
+ const adapter = opts.product ? getAdapter5(opts.product) : await detectAdapter5();
932
+ info(`\u76EE\u6807\u4EA7\u54C1: ${adapter.product}`);
933
+ const allGeneSlugs = /* @__PURE__ */ new Set();
934
+ let installed = 0;
935
+ let skipped = 0;
936
+ for (const genomeRef of template.genomes) {
937
+ info(`
938
+ \u89E3\u6790\u57FA\u56E0\u7EC4: ${genomeRef.slug}@${genomeRef.version}`);
939
+ try {
940
+ const resolved = await client.resolveGenome(genomeRef.slug, genomeRef.version);
941
+ if (resolved.warnings.length > 0) {
942
+ for (const w of resolved.warnings) warn(w);
943
+ }
944
+ for (const gene of resolved.genes) {
945
+ if (allGeneSlugs.has(gene.slug)) continue;
946
+ allGeneSlugs.add(gene.slug);
947
+ if (!opts.force && await adapter.isInstalled(gene.slug)) {
948
+ skipped++;
949
+ continue;
950
+ }
951
+ const geneSpinner = ora5(` \u5B89\u88C5 ${gene.slug}@${gene.version}...`).start();
952
+ try {
953
+ const manifest = await client.getManifest(gene.slug, gene.version);
954
+ let result;
955
+ try {
956
+ const archive = await client.downloadArchive(gene.slug, gene.version);
957
+ const tempDir = join7(tmpdir3(), `genehub-install-${gene.slug}-${Date.now()}`);
958
+ await extractTarGz3(archive, tempDir);
959
+ if (adapter.installFromDirectory) {
960
+ result = await adapter.installFromDirectory(tempDir, manifest, {
961
+ force: opts.force,
962
+ targetPath: opts.target
963
+ });
964
+ } else {
965
+ result = await adapter.install(manifest, {
966
+ force: opts.force,
967
+ targetPath: opts.target
968
+ });
969
+ }
970
+ await rm3(tempDir, { recursive: true, force: true });
971
+ } catch {
972
+ result = await adapter.install(manifest, {
973
+ force: opts.force,
974
+ targetPath: opts.target
975
+ });
976
+ }
977
+ geneSpinner.succeed(` ${result.slug}@${result.version}`);
978
+ installed++;
979
+ try {
980
+ await client.reportInstall(gene.slug);
981
+ } catch {
982
+ }
983
+ } catch (err) {
984
+ geneSpinner.fail(
985
+ ` ${gene.slug} \u5B89\u88C5\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`
986
+ );
987
+ }
988
+ }
989
+ } catch (err) {
990
+ warn(
991
+ `\u57FA\u56E0\u7EC4 ${genomeRef.slug} \u89E3\u6790\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`
992
+ );
993
+ }
994
+ }
995
+ if (template.genes.length > 0) {
996
+ info(`
997
+ \u5B89\u88C5\u989D\u5916\u57FA\u56E0 (${template.genes.length}\u4E2A):`);
998
+ for (const geneRef of template.genes) {
999
+ if (allGeneSlugs.has(geneRef.slug)) continue;
1000
+ allGeneSlugs.add(geneRef.slug);
1001
+ if (!opts.force && await adapter.isInstalled(geneRef.slug)) {
1002
+ skipped++;
1003
+ continue;
1004
+ }
1005
+ const geneSpinner = ora5(` \u5B89\u88C5 ${geneRef.slug}@${geneRef.version}...`).start();
1006
+ try {
1007
+ const manifest = await client.getManifest(geneRef.slug, geneRef.version);
1008
+ let result;
1009
+ try {
1010
+ const archive = await client.downloadArchive(geneRef.slug, geneRef.version);
1011
+ const tempDir = join7(tmpdir3(), `genehub-install-${geneRef.slug}-${Date.now()}`);
1012
+ await extractTarGz3(archive, tempDir);
1013
+ if (adapter.installFromDirectory) {
1014
+ result = await adapter.installFromDirectory(tempDir, manifest, {
1015
+ force: opts.force,
1016
+ targetPath: opts.target
1017
+ });
1018
+ } else {
1019
+ result = await adapter.install(manifest, {
1020
+ force: opts.force,
1021
+ targetPath: opts.target
1022
+ });
1023
+ }
1024
+ await rm3(tempDir, { recursive: true, force: true });
1025
+ } catch {
1026
+ result = await adapter.install(manifest, {
1027
+ force: opts.force,
1028
+ targetPath: opts.target
1029
+ });
1030
+ }
1031
+ geneSpinner.succeed(` ${result.slug}@${result.version}`);
1032
+ installed++;
1033
+ try {
1034
+ await client.reportInstall(geneRef.slug);
1035
+ } catch {
1036
+ }
1037
+ } catch (err) {
1038
+ geneSpinner.fail(
1039
+ ` ${geneRef.slug} \u5B89\u88C5\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`
1040
+ );
1041
+ }
1042
+ }
1043
+ }
1044
+ ok(`\u6A21\u677F\u5B89\u88C5\u5B8C\u6210: ${installed} \u5B89\u88C5, ${skipped} \u8DF3\u8FC7`);
1045
+ try {
1046
+ await client.reportTemplateInstall(slug);
1047
+ } catch {
1048
+ }
1049
+ } catch (err) {
1050
+ spinner.fail("\u5B89\u88C5\u5931\u8D25");
1051
+ fail(err instanceof Error ? err.message : String(err));
1052
+ process.exit(1);
1053
+ }
1054
+ });
1055
+ var infoTemplateCommand = new Command10("info").description("\u67E5\u770B AI \u5458\u5DE5\u6A21\u677F\u8BE6\u60C5").argument("<slug>", "\u6A21\u677F\u6807\u8BC6\u7B26").option("--json", "JSON \u683C\u5F0F\u8F93\u51FA", false).action(async (slug, opts) => {
1056
+ const config = await loadConfig();
1057
+ const client = new GeneHubClient6({ registryUrl: config.registryUrl, token: config.token });
1058
+ try {
1059
+ const template = await client.getTemplate(slug);
1060
+ if (opts.json) {
1061
+ console.log(JSON.stringify(template, null, 2));
1062
+ return;
1063
+ }
1064
+ info(`\u540D\u79F0: ${template.name}`);
1065
+ info(`Slug: ${template.slug}`);
1066
+ info(`\u7248\u672C: ${template.version}`);
1067
+ info(`\u89D2\u8272: ${template.role ?? "(\u65E0)"}`);
1068
+ info(`\u5206\u7C7B: ${template.category}`);
1069
+ info(`\u63CF\u8FF0: ${template.description || template.short_description || "(\u65E0)"}`);
1070
+ info(`\u5B89\u88C5\u6570: ${template.install_count}`);
1071
+ if (template.genomes.length > 0) {
1072
+ info(`
1073
+ \u57FA\u56E0\u7EC4 (${template.genomes.length}\u4E2A):`);
1074
+ table(
1075
+ ["slug", "\u7248\u672C"],
1076
+ template.genomes.map((g) => [g.slug, g.version])
1077
+ );
1078
+ }
1079
+ if (template.genes.length > 0) {
1080
+ info(`
1081
+ \u989D\u5916\u57FA\u56E0 (${template.genes.length}\u4E2A):`);
1082
+ table(
1083
+ ["slug", "\u7248\u672C"],
1084
+ template.genes.map((g) => [g.slug, g.version])
1085
+ );
1086
+ }
1087
+ } catch (err) {
1088
+ fail(err instanceof Error ? err.message : String(err));
1089
+ process.exit(1);
1090
+ }
1091
+ });
1092
+ var listTemplateCommand = new Command10("list").description("\u641C\u7D22 AI \u5458\u5DE5\u6A21\u677F").option("-q, --query <keyword>", "\u641C\u7D22\u5173\u952E\u8BCD").option("-c, --category <category>", "\u6309\u5206\u7C7B\u8FC7\u6EE4").option("-r, --role <role>", "\u6309\u89D2\u8272\u8FC7\u6EE4").option("--json", "JSON \u683C\u5F0F\u8F93\u51FA", false).action(async (opts) => {
1093
+ const config = await loadConfig();
1094
+ const client = new GeneHubClient6({ registryUrl: config.registryUrl, token: config.token });
1095
+ try {
1096
+ const result = await client.searchTemplates({
1097
+ q: opts.query,
1098
+ category: opts.category,
1099
+ role: opts.role
1100
+ });
1101
+ if (opts.json) {
1102
+ console.log(JSON.stringify(result, null, 2));
1103
+ return;
1104
+ }
1105
+ if (result.items.length === 0) {
1106
+ info("\u672A\u627E\u5230\u6A21\u677F");
1107
+ return;
1108
+ }
1109
+ table(
1110
+ ["slug", "\u540D\u79F0", "\u7248\u672C", "\u89D2\u8272", "\u57FA\u56E0\u7EC4", "\u5B89\u88C5\u6570"],
1111
+ result.items.map((t) => [
1112
+ t.slug,
1113
+ t.name,
1114
+ t.version,
1115
+ t.role ?? "-",
1116
+ String(t.genomes.length),
1117
+ String(t.install_count)
1118
+ ])
1119
+ );
1120
+ info(`\u5171 ${result.total} \u6761\u7ED3\u679C`);
1121
+ } catch (err) {
1122
+ fail(err instanceof Error ? err.message : String(err));
1123
+ process.exit(1);
1124
+ }
1125
+ });
1126
+ var templateCommand = new Command10("template").description("AI \u5458\u5DE5\u6A21\u677F\u7BA1\u7406");
1127
+ templateCommand.addCommand(publishTemplateCommand);
1128
+ templateCommand.addCommand(installTemplateCommand);
1129
+ templateCommand.addCommand(infoTemplateCommand);
1130
+ templateCommand.addCommand(listTemplateCommand);
1131
+
399
1132
  // src/commands/uninstall.ts
400
- import { detectAdapter as detectAdapter4, getAdapter as getAdapter4 } from "@nodeskai/genehub-sdk";
401
- import { Command as Command8 } from "commander";
402
- import ora4 from "ora";
403
- var uninstallCommand = new Command8("uninstall").description("\u4ECE\u5F53\u524D Agent \u73AF\u5883\u5378\u8F7D\u57FA\u56E0").argument("<slug>", "\u57FA\u56E0\u6807\u8BC6\u7B26").option("-p, --product <product>", "\u6307\u5B9A\u76EE\u6807\u4EA7\u54C1\uFF08openclaw / nanobot / generic\uFF09").action(async (slug, opts) => {
404
- const adapter = opts.product ? getAdapter4(opts.product) : await detectAdapter4();
1133
+ import { detectAdapter as detectAdapter6, getAdapter as getAdapter6 } from "@nodeskai/genehub-sdk";
1134
+ import { Command as Command11 } from "commander";
1135
+ import ora6 from "ora";
1136
+ var uninstallCommand = new Command11("uninstall").description("\u4ECE\u5F53\u524D Agent \u73AF\u5883\u5378\u8F7D\u57FA\u56E0").argument("<slug>", "\u57FA\u56E0\u6807\u8BC6\u7B26").option("-p, --product <product>", "\u6307\u5B9A\u76EE\u6807\u4EA7\u54C1\uFF08openclaw / nanobot / generic\uFF09").action(async (slug, opts) => {
1137
+ const adapter = opts.product ? getAdapter6(opts.product) : await detectAdapter6();
405
1138
  info(`\u76EE\u6807\u4EA7\u54C1: ${adapter.product}`);
406
1139
  if (!await adapter.isInstalled(slug)) {
407
1140
  warn(`${slug} \u672A\u5B89\u88C5`);
408
1141
  return;
409
1142
  }
410
- const spinner = ora4("\u5378\u8F7D\u4E2D...").start();
1143
+ const spinner = ora6("\u5378\u8F7D\u4E2D...").start();
411
1144
  try {
412
1145
  const result = await adapter.uninstall(slug);
413
1146
  spinner.succeed("\u5378\u8F7D\u5B8C\u6210");
@@ -424,8 +1157,9 @@ var uninstallCommand = new Command8("uninstall").description("\u4ECE\u5F53\u524D
424
1157
  });
425
1158
 
426
1159
  // src/index.ts
427
- var program = new Command9();
1160
+ var program = new Command12();
428
1161
  program.name("genehub").description("GeneHub CLI - AI \u5458\u5DE5\u57FA\u56E0\u7BA1\u7406\u5DE5\u5177").version("0.1.0");
1162
+ program.addCommand(authCommand);
429
1163
  program.addCommand(installCommand);
430
1164
  program.addCommand(uninstallCommand);
431
1165
  program.addCommand(searchCommand);
@@ -434,5 +1168,7 @@ program.addCommand(publishCommand);
434
1168
  program.addCommand(initCommand);
435
1169
  program.addCommand(configCommand);
436
1170
  program.addCommand(learnCommand);
1171
+ program.addCommand(genomeCommand);
1172
+ program.addCommand(templateCommand);
437
1173
  program.parse();
438
1174
  //# sourceMappingURL=index.js.map