@neta-art/neta-search-cli 0.2.16 → 0.2.18

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
@@ -3,21 +3,23 @@
3
3
  Command-line client for **neta-search**, the skill registry of the Cohub / Neta platform: search the ecosystem for a ready-made skill, try it without installing, or install it into your space.
4
4
 
5
5
  ```bash
6
- npx -y @neta-art/neta-search-cli@0.2 skills find "generate video" # search (Chinese or English)
7
- npx -y @neta-art/neta-search-cli@0.2 skills find "browser automation" --json
8
- npx -y @neta-art/neta-search-cli@0.2 skills use <id> # print the SKILL.md (pulled into a temp dir; no project files written)
9
- npx -y @neta-art/neta-search-cli@0.2 skills add <id> # install into .agents/skills/
10
- npx -y @neta-art/neta-search-cli@0.2 skills list # what's in .agents/skills/, and which installed ones have updates
11
- npx -y @neta-art/neta-search-cli@0.2 skills update # update installed skills to their current release
12
- npx -y @neta-art/neta-search-cli@0.2 skills init <name> # scaffold your own skill
6
+ npx -y @neta-art/neta-search-cli@latest skills find "generate video" # search (Chinese or English)
7
+ npx -y @neta-art/neta-search-cli@latest skills find "browser automation" --json
8
+ npx -y @neta-art/neta-search-cli@latest skills use <id> # print the SKILL.md (pulled into a temp dir; no project files written)
9
+ npx -y @neta-art/neta-search-cli@latest skills add <id> # install into .agents/skills/
10
+ npx -y @neta-art/neta-search-cli@latest skills list # what's in .agents/skills/, and which installed ones have updates
11
+ npx -y @neta-art/neta-search-cli@latest skills update # update installed skills to their current release (or name some: handle, id or directory)
12
+ npx -y @neta-art/neta-search-cli@latest skills init <name> # scaffold your own skill
13
13
  ```
14
14
 
15
- `<id>` comes from `find` results, e.g. `cohub:<spaceId>/<name>`; a handle such as `neta-skills/holo-pack-opening` works too.
15
+ `<id>` is what `find` prints: a handle such as `neta-skills/holo-pack-opening` or `cohub/agent-browser`. Skills without a handle are listed as `cohub:<spaceId>/<name>`, which works everywhere a handle does. `update` and `remove` also accept the directory name under `.agents/skills/`.
16
16
 
17
- `skills remove <name>` deletes an installed skill from `.agents/skills/`. Skills you wrote yourself (no install record) are left alone unless you pass `--force`.
17
+ `remove` deletes installed skills only. Skills you wrote yourself (no install record) are left alone unless you pass `--force`.
18
18
 
19
19
  `use` prints the skill directory and the full SKILL.md, so an agent can follow it right away; relative paths in SKILL.md resolve against that directory. If the local entry is a pointer (frontmatter `metadata.neta-skill-id`), `use` fetches the full skill it points to.
20
20
 
21
+ Use `@latest` (or an exact version): with a range such as `@0.2`, npx keeps running whatever matching version it cached earlier and never checks for a newer one.
22
+
21
23
  Inside a Cohub sandbox the CLI picks up the sandbox identity automatically; elsewhere it runs anonymously.
22
24
 
23
25
  Single file, no runtime dependencies. Node.js 20+.
package/neta-search.mjs CHANGED
@@ -27075,7 +27075,8 @@ async function fetchArchive(env, id) {
27075
27075
  const files = untarGz(body);
27076
27076
  const got = digestOf(new Map(files.map((f) => [f.path, f.data])));
27077
27077
  if (got !== digest) throw new Error(`\u4E0B\u8F7D\u7684 ${id} \u5185\u5BB9\u6821\u9A8C\u5931\u8D25\uFF08\u671F\u671B ${digest}\uFF0C\u5B9E\u9645 ${got}\uFF09`);
27078
- return { id, name, digest, files };
27078
+ const handle = res.headers.get("x-skill-handle") || void 0;
27079
+ return { id, handle, name, digest, files };
27079
27080
  }
27080
27081
  async function archiveHead(env, id) {
27081
27082
  const url = `${env.apiBase}${ARCHIVE_PATH}?id=${encodeURIComponent(id)}`;
@@ -27083,7 +27084,12 @@ async function archiveHead(env, id) {
27083
27084
  if (res.status === 404) return null;
27084
27085
  const digest = res.headers.get("x-skill-digest") ?? "";
27085
27086
  if (res.status < 300 || res.status >= 400 || !digest) throw new Error(`\u67E5\u8BE2 ${id} \u5931\u8D25\uFF1AHTTP ${res.status}`);
27086
- return { id: res.headers.get("x-skill-id") ?? id, name: res.headers.get("x-skill-name") ?? id.split("/").pop(), digest };
27087
+ return {
27088
+ id: res.headers.get("x-skill-id") ?? id,
27089
+ handle: res.headers.get("x-skill-handle") || void 0,
27090
+ name: res.headers.get("x-skill-name") ?? id.split("/").pop(),
27091
+ digest
27092
+ };
27087
27093
  }
27088
27094
  async function download(url, id) {
27089
27095
  for (let attempt = 1; ; attempt++) {
@@ -27188,11 +27194,20 @@ function skillDirExists(p) {
27188
27194
  function readSource(dir) {
27189
27195
  try {
27190
27196
  const r = JSON.parse(readFileSync2(join2(dir, SOURCE_FILE), "utf8"));
27191
- return typeof r?.id === "string" && parseId(r.id) ? { id: r.id, digest: String(r.digest ?? "") } : null;
27197
+ if (typeof r?.id !== "string" || !parseId(r.id)) return null;
27198
+ const rec = { id: r.id, digest: String(r.digest ?? "") };
27199
+ if (typeof r.handle === "string" && HANDLE_RE.test(r.handle)) rec.handle = r.handle;
27200
+ return rec;
27192
27201
  } catch {
27193
27202
  return null;
27194
27203
  }
27195
27204
  }
27205
+ function writeSource(dir, rec) {
27206
+ writeFileSync(join2(dir, SOURCE_FILE), JSON.stringify(rec, null, 2) + "\n");
27207
+ }
27208
+ function displayRef(rec, fallback2 = "") {
27209
+ return rec?.handle || rec?.id || fallback2;
27210
+ }
27196
27211
  function writeSkill(target, files, source) {
27197
27212
  const staging = join2(dirname(target), `.staging-${target.split("/").pop()}-${process.pid}`);
27198
27213
  rmSync(staging, { recursive: true, force: true });
@@ -27275,10 +27290,10 @@ async function cmdAdd(idOrName, opts) {
27275
27290
  return 1;
27276
27291
  }
27277
27292
  mkdirSync2(opts.dir, { recursive: true });
27278
- writeSkill(target, archive.files, { id: archive.id, digest: archive.digest });
27293
+ writeSkill(target, archive.files, { id: archive.id, digest: archive.digest, handle: archive.handle });
27279
27294
  const report = reportSkillEvent(env, "install", archive.id, archive.digest, name);
27280
- if (opts.json) console.log(JSON.stringify({ installed: archive.id, path: target, digest: archive.digest }));
27281
- else console.log(`\u5DF2\u5B89\u88C5 ${archive.id} \u2192 ${target}`);
27295
+ if (opts.json) console.log(JSON.stringify({ installed: archive.id, handle: archive.handle, path: target, digest: archive.digest }));
27296
+ else console.log(`\u5DF2\u5B89\u88C5 ${displayRef(archive)} \u2192 ${target}`);
27282
27297
  await report;
27283
27298
  return 0;
27284
27299
  }
@@ -27305,7 +27320,9 @@ import { join as join4 } from "node:path";
27305
27320
  var FETCH_ROOT = process.env["NETASEARCH_FETCH_ROOT"] ?? join4("/tmp", "neta-skills");
27306
27321
  async function cmdUse(idOrName, opts) {
27307
27322
  const env = detectEnv();
27323
+ const input = idOrName;
27308
27324
  idOrName = await resolveRef(env, idOrName);
27325
+ let handle = isHandle(input) && parseId(idOrName) ? input.toLowerCase() : void 0;
27309
27326
  const parsed = parseId(idOrName);
27310
27327
  const name = parsed?.name ?? idOrName;
27311
27328
  const mine = !parsed || parsed.spaceId === env.spaceId;
@@ -27322,6 +27339,7 @@ async function cmdUse(idOrName, opts) {
27322
27339
  if (src && (!parsed || src.id === idOrName)) {
27323
27340
  path = installed;
27324
27341
  reportId = src.id;
27342
+ handle = handle ?? src.handle;
27325
27343
  } else if (!src && mine) {
27326
27344
  path = installed;
27327
27345
  reportId = ownId(env, name);
@@ -27365,8 +27383,9 @@ async function cmdUse(idOrName, opts) {
27365
27383
  const p = parseId(a.id);
27366
27384
  path = join4(FETCH_ROOT, p.spaceId, p.name);
27367
27385
  const have = readSource(path);
27368
- if (!skillDirExists(path) || have?.digest !== a.digest) writeSkill(path, a.files, { id: a.id, digest: a.digest });
27386
+ if (!skillDirExists(path) || have?.digest !== a.digest) writeSkill(path, a.files, { id: a.id, digest: a.digest, handle: a.handle });
27369
27387
  reportId = a.id;
27388
+ handle = a.handle ?? handle;
27370
27389
  where = "fetched";
27371
27390
  } catch (e) {
27372
27391
  console.error(e instanceof ArchiveNotFound ? `${e.message}\u3002` : String(e instanceof Error ? e.message : e));
@@ -27383,10 +27402,10 @@ async function cmdUse(idOrName, opts) {
27383
27402
  const reported = reportId ? await reportSkillEvent(env, "use", reportId, skillDirDigest(path), name, extra) : false;
27384
27403
  const skillMd = readFileSync3(join4(path, "SKILL.md"), "utf8");
27385
27404
  if (opts.json) {
27386
- console.log(JSON.stringify({ skill: reportId ?? name, path, where, use_id: reportId ? useId : void 0, skill_md: skillMd }));
27405
+ console.log(JSON.stringify({ skill: reportId ?? name, handle, path, where, use_id: reportId ? useId : void 0, skill_md: skillMd }));
27387
27406
  } else {
27388
27407
  const label = { local: "\u672C\u5730", mod: "\u5E73\u53F0 mod", fetched: "\u5DF2\u4E0B\u8F7D\u5230\u4E34\u65F6\u76EE\u5F55" }[where];
27389
- console.log(`${reportId ?? name} \u5C31\u7EEA\uFF08${label}${via === "pointer" ? "\uFF0C\u7ECF\u6307\u9488" : ""}\uFF09`);
27408
+ console.log(`${handle ?? reportId ?? name} \u5C31\u7EEA\uFF08${label}${via === "pointer" ? "\uFF0C\u7ECF\u6307\u9488" : ""}\uFF09`);
27390
27409
  console.log(`skill \u76EE\u5F55\uFF1A${path}`);
27391
27410
  console.log(`SKILL.md \u91CC\u7684\u76F8\u5BF9\u8DEF\u5F84\u90FD\u4ECE\u8FD9\u4E2A\u76EE\u5F55\u7B97\u8D77\u3002\u4E0B\u9762\u5C31\u662F SKILL.md \u7684\u5168\u6587\uFF0C\u7167\u7740\u505A\u5373\u53EF\uFF08\u4E0D\u7528\u518D\u8BFB\u4E00\u6B21\u6587\u4EF6\uFF09\u3002`);
27392
27411
  console.log(`
@@ -27469,7 +27488,7 @@ function scanLocal(dir) {
27469
27488
  if (!skillDirExists(path)) continue;
27470
27489
  const src = readSource(path);
27471
27490
  if (src) {
27472
- out.push({ name: e.name, path, kind: "installed", id: src.id, digest: src.digest });
27491
+ out.push({ name: e.name, path, kind: "installed", id: src.id, handle: src.handle, digest: src.digest });
27473
27492
  continue;
27474
27493
  }
27475
27494
  const ptr = pointerTarget(path);
@@ -27477,12 +27496,21 @@ function scanLocal(dir) {
27477
27496
  }
27478
27497
  return out.sort((a, b) => a.name.localeCompare(b.name));
27479
27498
  }
27499
+ async function findLocal(env, local, input) {
27500
+ if (!input.includes("/") && !input.startsWith(".")) {
27501
+ const s = local.find((x) => x.name === input);
27502
+ if (s) return [s];
27503
+ }
27504
+ const ref = parseId(input) ? input : await resolveRef(env, input);
27505
+ const lower = input.toLowerCase();
27506
+ return local.filter((x) => x.id !== void 0 && x.id === ref || x.handle !== void 0 && x.handle.toLowerCase() === lower);
27507
+ }
27480
27508
  async function statusOf(env, s) {
27481
27509
  if (s.kind !== "installed" || !s.id) return { status: "unknown" };
27482
27510
  try {
27483
27511
  const head = await archiveHead(env, s.id);
27484
27512
  if (!head) return { status: "unavailable" };
27485
- return { status: head.digest === s.digest ? "latest" : "outdated", latest: head.digest };
27513
+ return { status: head.digest === s.digest ? "latest" : "outdated", latest: head.digest, handle: head.handle };
27486
27514
  } catch (e) {
27487
27515
  return { status: "unknown", error: e instanceof Error ? e.message : String(e) };
27488
27516
  }
@@ -27493,7 +27521,10 @@ async function cmdList(opts) {
27493
27521
  const env = detectEnv();
27494
27522
  const local = scanLocal(opts.dir);
27495
27523
  const rows = await Promise.all(
27496
- local.map(async (s) => ({ ...s, ...opts.offline ? { status: "unknown" } : await statusOf(env, s) }))
27524
+ local.map(async (s) => {
27525
+ const st = opts.offline ? { status: "unknown", handle: void 0 } : await statusOf(env, s);
27526
+ return { ...s, ...st, handle: s.handle ?? st.handle };
27527
+ })
27497
27528
  );
27498
27529
  if (opts.json) {
27499
27530
  console.log(JSON.stringify({ dir: opts.dir, count: rows.length, skills: rows }));
@@ -27506,7 +27537,7 @@ async function cmdList(opts) {
27506
27537
  console.log(`${opts.dir}/\uFF08${rows.length} \u4E2A\uFF09\uFF1A`);
27507
27538
  for (const r of rows) {
27508
27539
  const st = STATUS_LABEL[r.status] ? ` [${STATUS_LABEL[r.status]}]` : "";
27509
- const from = r.id ? ` ${r.id}` : "";
27540
+ const from = r.id ? ` ${displayRef(r)}` : "";
27510
27541
  console.log(` ${r.name.padEnd(28)} ${KIND_LABEL[r.kind]}${st}${from}`);
27511
27542
  }
27512
27543
  const outdated = rows.filter((r) => r.status === "outdated").length;
@@ -27517,7 +27548,6 @@ ${outdated} \u4E2A\u6709\u66F4\u65B0\uFF1Aneta-search skills update`);
27517
27548
  async function cmdUpdate(names, opts) {
27518
27549
  const env = detectEnv();
27519
27550
  const local = scanLocal(opts.dir);
27520
- const byName = new Map(local.map((s) => [s.name, s]));
27521
27551
  const targets = [];
27522
27552
  const results = [];
27523
27553
  let failed = 0;
@@ -27525,18 +27555,21 @@ async function cmdUpdate(names, opts) {
27525
27555
  targets.push(...local.filter((s) => s.kind === "installed"));
27526
27556
  } else {
27527
27557
  for (const n of names) {
27528
- const s = byName.get(n);
27529
- if (!s) {
27558
+ const found = await findLocal(env, local, n);
27559
+ if (found.length === 0) {
27530
27560
  results.push({ name: n, result: "not_found" });
27531
27561
  failed++;
27532
- } else if (s.kind !== "installed") {
27533
- results.push({ name: n, result: s.kind === "own" ? "own_skipped" : "pointer_skipped" });
27534
- } else targets.push(s);
27562
+ }
27563
+ for (const s of found) {
27564
+ if (s.kind !== "installed") results.push({ name: s.name, result: s.kind === "own" ? "own_skipped" : "pointer_skipped" });
27565
+ else if (!targets.includes(s)) targets.push(s);
27566
+ }
27535
27567
  }
27536
27568
  }
27537
27569
  for (const s of targets) {
27538
27570
  const st = await statusOf(env, s);
27539
27571
  if (st.status === "latest") {
27572
+ if (st.handle && st.handle !== s.handle) writeSource(s.path, { id: s.id, digest: s.digest, handle: st.handle });
27540
27573
  results.push({ name: s.name, result: "latest" });
27541
27574
  continue;
27542
27575
  }
@@ -27551,7 +27584,7 @@ async function cmdUpdate(names, opts) {
27551
27584
  }
27552
27585
  try {
27553
27586
  const a = await fetchArchive(env, s.id);
27554
- writeSkill(s.path, a.files, { id: s.id, digest: a.digest });
27587
+ writeSkill(s.path, a.files, { id: s.id, digest: a.digest, handle: a.handle ?? s.handle });
27555
27588
  await reportSkillEvent(env, "install", s.id, a.digest, s.name);
27556
27589
  results.push({ name: s.name, result: "updated", from: s.digest, to: a.digest });
27557
27590
  } catch (e) {
@@ -27580,17 +27613,19 @@ function short(d) {
27580
27613
  return (d ?? "").replace(/^sha256:/, "").slice(0, 12) || "?";
27581
27614
  }
27582
27615
  async function cmdRemove(names, opts) {
27583
- const byName = new Map(scanLocal(opts.dir).map((s) => [s.name, s]));
27616
+ const env = detectEnv();
27617
+ const local = scanLocal(opts.dir);
27584
27618
  const results = [];
27585
27619
  for (const n of names) {
27586
- const s = byName.get(n);
27587
- if (!s || n.includes("/") || n.startsWith(".")) {
27588
- results.push({ name: n, result: "not_found" });
27589
- } else if (s.kind === "own" && !opts.force) {
27590
- results.push({ name: n, result: "own_refused" });
27591
- } else {
27592
- rmSync2(s.path, { recursive: true, force: true });
27593
- results.push({ name: n, result: "removed" });
27620
+ const found = (await findLocal(env, local, n)).filter((s) => existsSync5(s.path));
27621
+ if (found.length === 0) results.push({ name: n, result: "not_found" });
27622
+ for (const s of found) {
27623
+ if (s.kind === "own" && !opts.force) {
27624
+ results.push({ name: s.name, result: "own_refused" });
27625
+ } else {
27626
+ rmSync2(s.path, { recursive: true, force: true });
27627
+ results.push({ name: s.name, result: "removed" });
27628
+ }
27594
27629
  }
27595
27630
  }
27596
27631
  const failed = results.filter((r) => r.result !== "removed").length;
@@ -27635,10 +27670,10 @@ skills.command("use").description("\u7528 skill\uFF08\u4E0D\u88C5\uFF1A\u6253\u5
27635
27670
  skills.command("list").alias("ls").description("\u5217\u51FA\u672C\u5730\u7684 skill\uFF08\u88C5\u6765\u7684 / \u6307\u9488 / \u81EA\u5DF1\u5199\u7684\uFF09\uFF0C\u88C5\u6765\u7684\u987A\u5E26\u67E5\u6709\u6CA1\u6709\u66F4\u65B0").option("--dir <dir>", "\u76EE\u5F55\uFF08\u9ED8\u8BA4 .agents/skills/\uFF09", ".agents/skills").option("--offline", "\u4E0D\u8054\u7F51\u67E5\u66F4\u65B0").option("--json", "\u7D27\u51D1 JSON \u8F93\u51FA\uFF08agent \u6D88\u8D39\uFF09").action(async (opts) => {
27636
27671
  await run(cmdList({ dir: opts.dir, offline: !!opts.offline, json: !!opts.json }));
27637
27672
  });
27638
- skills.command("update").description("\u66F4\u65B0\u88C5\u6765\u7684 skill \u5230\u5F53\u524D\u4E0A\u67B6\u7684\u7248\u672C\uFF08\u4E0D\u7ED9\u540D\u5B57 = \u5168\u90E8\uFF1B\u81EA\u5DF1\u5199\u7684\u548C\u6307\u9488\u4E0D\u78B0\uFF09").argument("[names...]", "\u8981\u66F4\u65B0\u7684 skill \u540D\uFF08\u76EE\u5F55\u540D\uFF09").option("--dir <dir>", "\u76EE\u5F55\uFF08\u9ED8\u8BA4 .agents/skills/\uFF09", ".agents/skills").option("--json", "\u7D27\u51D1 JSON \u8F93\u51FA\uFF08agent \u6D88\u8D39\uFF09").action(async (names, opts) => {
27673
+ skills.command("update").description("\u66F4\u65B0\u88C5\u6765\u7684 skill \u5230\u5F53\u524D\u4E0A\u67B6\u7684\u7248\u672C\uFF08\u4E0D\u7ED9\u540D\u5B57 = \u5168\u90E8\uFF1B\u81EA\u5DF1\u5199\u7684\u548C\u6307\u9488\u4E0D\u78B0\uFF09").argument("[names...]", "\u8981\u66F4\u65B0\u7684 skill\uFF1A\u76EE\u5F55\u540D\u3001handle\uFF08\u5982 neta-skills/holo-pack-opening\uFF09\u6216\u5B9A\u4F4D\u7B26").option("--dir <dir>", "\u76EE\u5F55\uFF08\u9ED8\u8BA4 .agents/skills/\uFF09", ".agents/skills").option("--json", "\u7D27\u51D1 JSON \u8F93\u51FA\uFF08agent \u6D88\u8D39\uFF09").action(async (names, opts) => {
27639
27674
  await run(cmdUpdate(names, { dir: opts.dir, json: !!opts.json }));
27640
27675
  });
27641
- skills.command("remove").alias("rm").description("\u5220\u9664\u672C\u5730 skill\uFF08\u88C5\u6765\u7684 / \u6307\u9488\uFF1B\u81EA\u5DF1\u5199\u7684\u8981 --force\uFF09").argument("<names...>", "\u8981\u5220\u7684 skill \u540D\uFF08\u76EE\u5F55\u540D\uFF09").option("--dir <dir>", "\u76EE\u5F55\uFF08\u9ED8\u8BA4 .agents/skills/\uFF09", ".agents/skills").option("--force", "\u8FDE\u81EA\u5DF1\u5199\u7684\u4E5F\u5220").option("--json", "\u7D27\u51D1 JSON \u8F93\u51FA\uFF08agent \u6D88\u8D39\uFF09").action(async (names, opts) => {
27676
+ skills.command("remove").alias("rm").description("\u5220\u9664\u672C\u5730 skill\uFF08\u88C5\u6765\u7684 / \u6307\u9488\uFF1B\u81EA\u5DF1\u5199\u7684\u8981 --force\uFF09").argument("<names...>", "\u8981\u5220\u7684 skill\uFF1A\u76EE\u5F55\u540D\u3001handle \u6216\u5B9A\u4F4D\u7B26").option("--dir <dir>", "\u76EE\u5F55\uFF08\u9ED8\u8BA4 .agents/skills/\uFF09", ".agents/skills").option("--force", "\u8FDE\u81EA\u5DF1\u5199\u7684\u4E5F\u5220").option("--json", "\u7D27\u51D1 JSON \u8F93\u51FA\uFF08agent \u6D88\u8D39\uFF09").action(async (names, opts) => {
27642
27677
  await run(cmdRemove(names, { dir: opts.dir, force: !!opts.force, json: !!opts.json }));
27643
27678
  });
27644
27679
  skills.command("init").description("\u521B\u5EFA skill \u811A\u624B\u67B6\uFF08SKILL.md + frontmatter \u89C4\u8303\uFF09").argument("[name]", "skill \u540D\uFF08kebab-case\uFF09").option("--here", "\u5728\u5F53\u524D\u76EE\u5F55\u751F\u6210\uFF08\u9ED8\u8BA4\u751F\u6210\u5230 ./<name>/ \u5B50\u76EE\u5F55\uFF09").action(async (name, opts) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neta-art/neta-search-cli",
3
- "version": "0.2.16",
3
+ "version": "0.2.18",
4
4
  "description": "neta-search CLI — find, try and install skills from the Cohub / Neta skill registry",
5
5
  "type": "module",
6
6
  "bin": {