@neta-art/neta-search-cli 0.2.36 → 0.2.38

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
@@ -17,7 +17,7 @@ npx -y @neta-art/neta-search-cli@latest skills submit <name> # submi
17
17
 
18
18
  `remove` deletes installed skills only. Skills you wrote yourself (no install record) are left alone unless you pass `--force`.
19
19
 
20
- `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
+ `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`, a handle or an id), `use` fetches the full skill it points to; with `metadata.neta-skill-version: verified` it fetches the version that passed Neta's review. `use <id> --verified` does the same directly: it fails if there is no reviewed version and never falls back to the latest.
21
21
 
22
22
  Every result has a trust level: `platform` (published by the Neta team), `verified` (made or reviewed by the Neta team) or `community` (shared by a Cohub user, not reviewed). `find --json` items carry it as `trust`, plus `basic: true` for built-in Cohub abilities and `author` for community skills. `use` and `add` say so when a skill is a community one; read those before relying on them.
23
23
 
package/neta-search.mjs CHANGED
@@ -28305,13 +28305,17 @@ function parseTrust(v) {
28305
28305
  var ArchiveNotFound = class extends Error {
28306
28306
  };
28307
28307
  var DIGEST_RE = /^sha256:[0-9a-f]{64}$/;
28308
- async function fetchArchive(env, id, pin) {
28309
- let url = `${env.apiBase}${ARCHIVE_PATH}?id=${encodeURIComponent(id)}`;
28310
- if (pin) url += `&digest=${encodeURIComponent(pin)}`;
28308
+ async function fetchArchive(env, id, pin, verified = false) {
28309
+ const url = archiveURL(env, id, pin, verified);
28311
28310
  const res = await fetch(url, { redirect: "manual", signal: AbortSignal.timeout(TIMEOUT_MS) });
28312
- if (res.status === 404) throw new ArchiveNotFound(pin ? `version ${pin} of ${id} is not available` : `${id} does not exist or has no released version`);
28311
+ if (res.status === 404) {
28312
+ throw new ArchiveNotFound(
28313
+ pin ? `version ${pin} of ${id} is not available` : verified ? `${id} has no version that passed Neta's review (or does not exist)` : `${id} does not exist or has no released version`
28314
+ );
28315
+ }
28313
28316
  const location = res.headers.get("location");
28314
28317
  if (res.status < 300 || res.status >= 400 || !location) throw new Error(`failed to download ${id}: HTTP ${res.status}`);
28318
+ if (verified) checkVerified(res, id);
28315
28319
  const digest = res.headers.get("x-skill-digest") ?? "";
28316
28320
  const name = res.headers.get("x-skill-name") ?? id.split("/").pop();
28317
28321
  if (!digest) throw new Error(`failed to download ${id}: the server did not return a version (x-skill-digest)`);
@@ -28323,12 +28327,13 @@ async function fetchArchive(env, id, pin) {
28323
28327
  const handle = res.headers.get("x-skill-handle") || void 0;
28324
28328
  return { id, handle, name, digest, trust: parseTrust(res.headers.get("x-skill-trust")), files };
28325
28329
  }
28326
- async function archiveHead(env, id) {
28327
- const url = `${env.apiBase}${ARCHIVE_PATH}?id=${encodeURIComponent(id)}`;
28330
+ async function archiveHead(env, id, verified = false) {
28331
+ const url = archiveURL(env, id, void 0, verified);
28328
28332
  const res = await fetch(url, { method: "HEAD", redirect: "manual", signal: AbortSignal.timeout(1e4) });
28329
28333
  if (res.status === 404) return null;
28330
28334
  const digest = res.headers.get("x-skill-digest") ?? "";
28331
28335
  if (res.status < 300 || res.status >= 400 || !digest) throw new Error(`failed to look up ${id}: HTTP ${res.status}`);
28336
+ if (verified) checkVerified(res, id);
28332
28337
  return {
28333
28338
  id: res.headers.get("x-skill-id") ?? id,
28334
28339
  handle: res.headers.get("x-skill-handle") || void 0,
@@ -28337,6 +28342,17 @@ async function archiveHead(env, id) {
28337
28342
  trust: parseTrust(res.headers.get("x-skill-trust"))
28338
28343
  };
28339
28344
  }
28345
+ function archiveURL(env, id, pin, verified = false) {
28346
+ let url = `${env.apiBase}${ARCHIVE_PATH}?id=${encodeURIComponent(id)}`;
28347
+ if (pin) url += `&digest=${encodeURIComponent(pin)}`;
28348
+ if (verified) url += "&version=verified";
28349
+ return url;
28350
+ }
28351
+ function checkVerified(res, id) {
28352
+ if (res.headers.get("x-skill-version") !== "verified") {
28353
+ throw new Error(`failed to get the reviewed version of ${id}: the server did not confirm it (x-skill-version); not using an unconfirmed version`);
28354
+ }
28355
+ }
28340
28356
  async function download(url, id) {
28341
28357
  for (let attempt = 1; ; attempt++) {
28342
28358
  const res = await fetch(url, { signal: AbortSignal.timeout(TIMEOUT_MS) });
@@ -28519,23 +28535,43 @@ function writeSkill(target, files, source) {
28519
28535
  }
28520
28536
  if (hadOld) rmSync(trash, { recursive: true, force: true });
28521
28537
  }
28522
- var POINTER_RE = /^\s*neta-skill-id:\s*["']?(cohub:[^"'\s]+)["']?\s*$/m;
28538
+ var POINTER_KEY_RE = /^\s*neta-skill-id:(.*)$/m;
28539
+ var POINTER_VERSION_KEY_RE = /^\s*neta-skill-version:(.*)$/m;
28540
+ function yamlScalar(raw) {
28541
+ let v = raw.trim();
28542
+ if ((v.startsWith('"') || v.startsWith("'")) && v.indexOf(v[0], 1) > 0) return v.slice(1, v.indexOf(v[0], 1));
28543
+ const hash = v.search(/\s#/);
28544
+ if (hash >= 0) v = v.slice(0, hash);
28545
+ if (v.startsWith("#")) v = "";
28546
+ return v.trim();
28547
+ }
28523
28548
  function frontmatterOf(raw) {
28524
28549
  if (!raw.startsWith("---\n")) return "";
28525
28550
  const end = raw.indexOf("\n---", 4);
28526
28551
  return end < 0 ? "" : raw.slice(4, end);
28527
28552
  }
28553
+ function pointerOf(raw) {
28554
+ const fm = frontmatterOf(raw);
28555
+ const m = POINTER_KEY_RE.exec(fm);
28556
+ const target = m ? yamlScalar(m[1]) : "";
28557
+ if (!target) return null;
28558
+ const v = POINTER_VERSION_KEY_RE.exec(fm);
28559
+ const version = v ? yamlScalar(v[1]).toLowerCase() : "";
28560
+ return { target, version: version || "latest" };
28561
+ }
28528
28562
  function pointerTargetOf(raw) {
28529
- const m = POINTER_RE.exec(frontmatterOf(raw));
28530
- return m ? m[1] : null;
28563
+ return pointerOf(raw)?.target ?? null;
28531
28564
  }
28532
- function pointerTarget(dir) {
28565
+ function pointerAt(dir) {
28533
28566
  try {
28534
- return pointerTargetOf(readFileSync3(join2(dir, "SKILL.md"), "utf8"));
28567
+ return pointerOf(readFileSync3(join2(dir, "SKILL.md"), "utf8"));
28535
28568
  } catch {
28536
28569
  return null;
28537
28570
  }
28538
28571
  }
28572
+ function pointerTarget(dir) {
28573
+ return pointerAt(dir)?.target ?? null;
28574
+ }
28539
28575
  function shareNotice(id, draft) {
28540
28576
  const name = id.split("/").pop();
28541
28577
  if (draft) {
@@ -28572,6 +28608,21 @@ async function cmdUse(idOrName, opts) {
28572
28608
  let notice = null;
28573
28609
  let found = null;
28574
28610
  let fetchId = parsed && !mine ? idOrName : null;
28611
+ if (opts.verified && opts.digest !== void 0) {
28612
+ console.error("Give either --digest or --verified, not both.");
28613
+ return 1;
28614
+ }
28615
+ if (opts.verified) {
28616
+ if (!parsed) {
28617
+ console.error(
28618
+ resolved.unreachable ? `Cannot reach the server (${env.apiBase}), so handle "${input}" could not be resolved. Try again later.` : `--verified needs a handle or an id (cohub:<space>/<name>); "${input}" is neither.`
28619
+ );
28620
+ return 1;
28621
+ }
28622
+ const r = await fromCacheOrDownload(env, idOrName, true);
28623
+ if (!r) return 1;
28624
+ return emit(env, r.found, r.handle ?? handle, name, { via, entry, viaPointer: false, notice: null, json: opts.json, pinned: false });
28625
+ }
28575
28626
  const pin = opts.digest?.trim().toLowerCase();
28576
28627
  if (opts.digest !== void 0) {
28577
28628
  if (!pin || !DIGEST_RE.test(pin)) {
@@ -28609,19 +28660,37 @@ async function cmdUse(idOrName, opts) {
28609
28660
  if (r === "ambiguous") return 1;
28610
28661
  if (r) found = r;
28611
28662
  }
28663
+ let wantVerified = false;
28612
28664
  if (found) {
28613
- const target = pointerTarget(found.path);
28614
- if (target !== null) {
28665
+ const ptr = pointerAt(found.path);
28666
+ if (ptr !== null) {
28667
+ const at = join3(found.path, "SKILL.md");
28668
+ if (ptr.version !== "verified" && ptr.version !== "latest") {
28669
+ console.error(`${at} is a pointer, but its neta-skill-version is not verified or latest: ${ptr.version}`);
28670
+ return 1;
28671
+ }
28672
+ let target = ptr.target;
28673
+ if (isHandle(target)) {
28674
+ const t = await resolveRefDetail(env, target);
28675
+ if (!parseId(t.ref)) {
28676
+ console.error(
28677
+ t.unreachable ? `${at} is a pointer to ${target}, but the server (${env.apiBase}) cannot be reached to resolve it. Try again later.` : `${at} is a pointer to ${target}, but no skill has that handle.`
28678
+ );
28679
+ return 1;
28680
+ }
28681
+ target = t.ref;
28682
+ }
28615
28683
  if (!parseId(target)) {
28616
- console.error(`${join3(found.path, "SKILL.md")} is a pointer, but its neta-skill-id is not a valid id: ${target}`);
28684
+ console.error(`${at} is a pointer, but its neta-skill-id is not a valid handle or id: ${ptr.target}`);
28617
28685
  return 1;
28618
28686
  }
28619
28687
  viaPointer = true;
28688
+ wantVerified = ptr.version === "verified";
28620
28689
  fetchId = target;
28621
28690
  found = null;
28622
28691
  notice = null;
28623
28692
  const p = parseId(target);
28624
- const r = findInMounts(p.spaceId, p.name);
28693
+ const r = wantVerified ? null : findInMounts(p.spaceId, p.name);
28625
28694
  if (r && r !== "ambiguous" && pointerTarget(r.path) === null) {
28626
28695
  found = r;
28627
28696
  fetchId = null;
@@ -28629,7 +28698,7 @@ async function cmdUse(idOrName, opts) {
28629
28698
  }
28630
28699
  }
28631
28700
  if (!found && fetchId) {
28632
- const r = await fromCacheOrDownload(env, fetchId);
28701
+ const r = await fromCacheOrDownload(env, fetchId, wantVerified);
28633
28702
  if (!r) return 1;
28634
28703
  found = r.found;
28635
28704
  handle = r.handle ?? handle;
@@ -28735,7 +28804,8 @@ async function pinnedFromCacheOrDownload(env, fetchId, pin) {
28735
28804
  }
28736
28805
  return download2(env, fetchId, pin, dir);
28737
28806
  }
28738
- async function fromCacheOrDownload(env, fetchId) {
28807
+ async function fromCacheOrDownload(env, fetchId, verified = false) {
28808
+ if (verified) return verifiedFromCacheOrDownload(env, fetchId);
28739
28809
  const p = parseId(fetchId);
28740
28810
  const cacheDir = join3(FETCH_ROOT, p.spaceId, p.name);
28741
28811
  const have = skillDirExists(cacheDir) ? readSource(cacheDir) : null;
@@ -28762,20 +28832,42 @@ async function fromCacheOrDownload(env, fetchId) {
28762
28832
  }
28763
28833
  return download2(env, fetchId);
28764
28834
  }
28835
+ var NO_REVIEWED = "Tell the user this skill is temporarily unavailable; do not substitute another skill or another version of it.";
28836
+ async function verifiedFromCacheOrDownload(env, fetchId) {
28837
+ const p = parseId(fetchId);
28838
+ let head;
28839
+ try {
28840
+ head = await archiveHead(env, fetchId, true);
28841
+ } catch (e) {
28842
+ console.error(`${String(e instanceof Error ? e.message : e)} (needs the reviewed version, so the local cache is not used). ${NO_REVIEWED}`);
28843
+ return null;
28844
+ }
28845
+ if (head === null) {
28846
+ console.error(`${fetchId} has no version that passed Neta's review (or does not exist). ${NO_REVIEWED}`);
28847
+ return null;
28848
+ }
28849
+ const dir = pinnedDir(p.spaceId, p.name, head.digest);
28850
+ const have = skillDirExists(dir) ? readSource(dir) : null;
28851
+ if (have && have.id === fetchId && have.digest === head.digest) {
28852
+ return { found: { path: dir, where: "cache", reportId: have.id, digest: have.digest, trust: head.trust ?? have.trust }, handle: head.handle ?? have.handle };
28853
+ }
28854
+ return download2(env, fetchId, void 0, void 0, true);
28855
+ }
28765
28856
  function sameVersion(dir, want) {
28766
28857
  if (!skillDirExists(dir)) return false;
28767
28858
  const have = readSource(dir);
28768
28859
  return !!have && have.id === want.id && have.digest === want.digest;
28769
28860
  }
28770
- async function download2(env, fetchId, pin, target) {
28861
+ async function download2(env, fetchId, pin, target, verified = false) {
28771
28862
  try {
28772
- const a = await fetchArchive(env, fetchId, pin);
28863
+ const a = await fetchArchive(env, fetchId, pin, verified);
28773
28864
  const skillMd = a.files.find((f) => f.path === "SKILL.md");
28774
28865
  if (skillMd && pointerTargetOf(skillMd.data.toString("utf8")) !== null) {
28775
28866
  console.error(`${fetchId} downloaded as another pointer; stopping here (pointers are followed only one level, to avoid loops).`);
28776
28867
  return null;
28777
28868
  }
28778
28869
  const q = parseId(a.id);
28870
+ if (verified && !target) target = pinnedDir(q.spaceId, q.name, a.digest);
28779
28871
  const path = target ?? join3(FETCH_ROOT, q.spaceId, q.name);
28780
28872
  const record = { id: a.id, digest: a.digest, handle: a.handle, trust: a.trust };
28781
28873
  if (target) {
@@ -29174,7 +29266,7 @@ function scanLocal(dir) {
29174
29266
  continue;
29175
29267
  }
29176
29268
  const ptr = pointerTarget(path);
29177
- out.push(ptr ? { name: e.name, path, kind: "pointer", id: ptr } : { name: e.name, path, kind: "own" });
29269
+ out.push(ptr ? { name: e.name, path, kind: "pointer", id: ptr, handle: isHandle(ptr) ? ptr : void 0 } : { name: e.name, path, kind: "own" });
29178
29270
  }
29179
29271
  return out.sort((a, b) => a.name.localeCompare(b.name));
29180
29272
  }
@@ -29286,7 +29378,7 @@ async function cmdUpdate(names, opts) {
29286
29378
  latest: (r) => `${r.name} is up to date`,
29287
29379
  unavailable: (r) => `${r.name} is no longer available from its source; keeping the local copy`,
29288
29380
  own_skipped: (r) => `${r.name} is your own skill; nothing to update`,
29289
- pointer_skipped: (r) => `${r.name} is a pointer; use always fetches the latest, nothing to update`,
29381
+ pointer_skipped: (r) => `${r.name} is a pointer; use fetches the version it asks for, nothing to update`,
29290
29382
  not_found: (r) => `${opts.dir}/${r.name} does not exist`,
29291
29383
  error: (r) => `Failed to update ${r.name}: ${r.from ?? ""}`
29292
29384
  };
@@ -29333,7 +29425,7 @@ skills.command("find").description("search the ecosystem for skills").argument("
29333
29425
  skills.command("add").description("install a skill: an id or handle from find downloads its current release; a name from your drafts moves it into .agents/skills/").argument("<id-or-name>", "skill id or handle from find (e.g. neta-skills/holo-pack-opening), or the name of one of your drafts").option("--dir <dir>", "install directory", ".agents/skills").option("--json", "compact JSON output (for agents)").action(async (idOrName, opts) => {
29334
29426
  await run(cmdAdd(idOrName, opts));
29335
29427
  });
29336
- skills.command("use").description("use a skill without installing it: prints the full SKILL.md and the skill directory (local copy, mounted mod, cache, or download)").argument("<id-or-name>", "skill id, handle (e.g. neta-skills/holo-pack-opening), or local skill name").option("--json", "compact JSON output (for agents)").option("--via <channel>", "where this use came from, recorded in usage stats (site / board-menu / skill-picker / find)").option("--entry <id>", "entry id (picker / menu entry), recorded in usage stats").addOption(new Option("--digest <sha256>", "use exactly this version (sha256:<hex>); needs a handle or id").hideHelp()).action(async (idOrName, opts) => {
29428
+ skills.command("use").description("use a skill without installing it: prints the full SKILL.md and the skill directory (local copy, mounted mod, cache, or download)").argument("<id-or-name>", "skill id, handle (e.g. neta-skills/holo-pack-opening), or local skill name").option("--json", "compact JSON output (for agents)").option("--via <channel>", "where this use came from, recorded in usage stats (site / board-menu / skill-picker / find)").option("--entry <id>", "entry id (picker / menu entry), recorded in usage stats").addOption(new Option("--digest <sha256>", "use exactly this version (sha256:<hex>); needs a handle or id").hideHelp()).option("--verified", "use the version that passed Neta's review; fails if there is none (never falls back to the latest); needs a handle or id").action(async (idOrName, opts) => {
29337
29429
  await run(cmdUse(idOrName, opts));
29338
29430
  });
29339
29431
  skills.command("submit").description("publish the current version of one of your skills (.agents/skills/<name>/ in this space) on the public skills site and wait for the result; only the space owner can publish. --withdraw takes it down").argument("<name>", "skill directory name under .agents/skills/").option("--withdraw", "take a published skill down (deleting the directory does not)").option("--wait <seconds>", "how long to wait for the server's result", "60").option("--json", "compact JSON output (for agents)").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.36",
3
+ "version": "0.2.38",
4
4
  "description": "neta-search CLI — find, try and install skills from the Cohub / Neta skill registry",
5
5
  "type": "module",
6
6
  "bin": {