@neta-art/neta-search-cli 0.2.31 → 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,9 +17,9 @@ 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
- Every result has a trust level: `platform` (published by the Neta team), `verified` (passed Neta's review) 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.
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
 
24
24
  Each `find --json` item carries a ready-to-run `use` command (`skills use <id> --via find`); prefix it with `npx -y @neta-art/neta-search-cli@latest` or `neta-search`. `--via` only labels where the use came from in usage stats.
25
25
 
package/neta-search.mjs CHANGED
@@ -28179,6 +28179,7 @@ var FIND_COMMUNITY_NOTE = "Community results were shared by Cohub users and have
28179
28179
  var TRUST_LABEL = {
28180
28180
  platform: "platform",
28181
28181
  verified: "verified",
28182
+ official: "official",
28182
28183
  community: "community, not reviewed"
28183
28184
  };
28184
28185
  function label(c) {
@@ -28299,18 +28300,22 @@ var ARCHIVE_PATH = "/v1/skills/archive";
28299
28300
  var TIMEOUT_MS = 6e4;
28300
28301
  var MAX_TRIES = 4;
28301
28302
  function parseTrust(v) {
28302
- return v === "platform" || v === "verified" || v === "community" ? v : void 0;
28303
+ return v === "platform" || v === "official" || v === "verified" || v === "community" ? v : void 0;
28303
28304
  }
28304
28305
  var ArchiveNotFound = class extends Error {
28305
28306
  };
28306
28307
  var DIGEST_RE = /^sha256:[0-9a-f]{64}$/;
28307
- async function fetchArchive(env, id, pin) {
28308
- let url = `${env.apiBase}${ARCHIVE_PATH}?id=${encodeURIComponent(id)}`;
28309
- if (pin) url += `&digest=${encodeURIComponent(pin)}`;
28308
+ async function fetchArchive(env, id, pin, verified = false) {
28309
+ const url = archiveURL(env, id, pin, verified);
28310
28310
  const res = await fetch(url, { redirect: "manual", signal: AbortSignal.timeout(TIMEOUT_MS) });
28311
- 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
+ }
28312
28316
  const location = res.headers.get("location");
28313
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);
28314
28319
  const digest = res.headers.get("x-skill-digest") ?? "";
28315
28320
  const name = res.headers.get("x-skill-name") ?? id.split("/").pop();
28316
28321
  if (!digest) throw new Error(`failed to download ${id}: the server did not return a version (x-skill-digest)`);
@@ -28322,12 +28327,13 @@ async function fetchArchive(env, id, pin) {
28322
28327
  const handle = res.headers.get("x-skill-handle") || void 0;
28323
28328
  return { id, handle, name, digest, trust: parseTrust(res.headers.get("x-skill-trust")), files };
28324
28329
  }
28325
- async function archiveHead(env, id) {
28326
- 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);
28327
28332
  const res = await fetch(url, { method: "HEAD", redirect: "manual", signal: AbortSignal.timeout(1e4) });
28328
28333
  if (res.status === 404) return null;
28329
28334
  const digest = res.headers.get("x-skill-digest") ?? "";
28330
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);
28331
28337
  return {
28332
28338
  id: res.headers.get("x-skill-id") ?? id,
28333
28339
  handle: res.headers.get("x-skill-handle") || void 0,
@@ -28336,6 +28342,17 @@ async function archiveHead(env, id) {
28336
28342
  trust: parseTrust(res.headers.get("x-skill-trust"))
28337
28343
  };
28338
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
+ }
28339
28356
  async function download(url, id) {
28340
28357
  for (let attempt = 1; ; attempt++) {
28341
28358
  const res = await fetch(url, { signal: AbortSignal.timeout(TIMEOUT_MS) });
@@ -28518,23 +28535,43 @@ function writeSkill(target, files, source) {
28518
28535
  }
28519
28536
  if (hadOld) rmSync(trash, { recursive: true, force: true });
28520
28537
  }
28521
- 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
+ }
28522
28548
  function frontmatterOf(raw) {
28523
28549
  if (!raw.startsWith("---\n")) return "";
28524
28550
  const end = raw.indexOf("\n---", 4);
28525
28551
  return end < 0 ? "" : raw.slice(4, end);
28526
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
+ }
28527
28562
  function pointerTargetOf(raw) {
28528
- const m = POINTER_RE.exec(frontmatterOf(raw));
28529
- return m ? m[1] : null;
28563
+ return pointerOf(raw)?.target ?? null;
28530
28564
  }
28531
- function pointerTarget(dir) {
28565
+ function pointerAt(dir) {
28532
28566
  try {
28533
- return pointerTargetOf(readFileSync3(join2(dir, "SKILL.md"), "utf8"));
28567
+ return pointerOf(readFileSync3(join2(dir, "SKILL.md"), "utf8"));
28534
28568
  } catch {
28535
28569
  return null;
28536
28570
  }
28537
28571
  }
28572
+ function pointerTarget(dir) {
28573
+ return pointerAt(dir)?.target ?? null;
28574
+ }
28538
28575
  function shareNotice(id, draft) {
28539
28576
  const name = id.split("/").pop();
28540
28577
  if (draft) {
@@ -28571,6 +28608,21 @@ async function cmdUse(idOrName, opts) {
28571
28608
  let notice = null;
28572
28609
  let found = null;
28573
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
+ }
28574
28626
  const pin = opts.digest?.trim().toLowerCase();
28575
28627
  if (opts.digest !== void 0) {
28576
28628
  if (!pin || !DIGEST_RE.test(pin)) {
@@ -28608,19 +28660,37 @@ async function cmdUse(idOrName, opts) {
28608
28660
  if (r === "ambiguous") return 1;
28609
28661
  if (r) found = r;
28610
28662
  }
28663
+ let wantVerified = false;
28611
28664
  if (found) {
28612
- const target = pointerTarget(found.path);
28613
- 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
+ }
28614
28683
  if (!parseId(target)) {
28615
- 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}`);
28616
28685
  return 1;
28617
28686
  }
28618
28687
  viaPointer = true;
28688
+ wantVerified = ptr.version === "verified";
28619
28689
  fetchId = target;
28620
28690
  found = null;
28621
28691
  notice = null;
28622
28692
  const p = parseId(target);
28623
- const r = findInMounts(p.spaceId, p.name);
28693
+ const r = wantVerified ? null : findInMounts(p.spaceId, p.name);
28624
28694
  if (r && r !== "ambiguous" && pointerTarget(r.path) === null) {
28625
28695
  found = r;
28626
28696
  fetchId = null;
@@ -28628,7 +28698,7 @@ async function cmdUse(idOrName, opts) {
28628
28698
  }
28629
28699
  }
28630
28700
  if (!found && fetchId) {
28631
- const r = await fromCacheOrDownload(env, fetchId);
28701
+ const r = await fromCacheOrDownload(env, fetchId, wantVerified);
28632
28702
  if (!r) return 1;
28633
28703
  found = r.found;
28634
28704
  handle = r.handle ?? handle;
@@ -28734,7 +28804,8 @@ async function pinnedFromCacheOrDownload(env, fetchId, pin) {
28734
28804
  }
28735
28805
  return download2(env, fetchId, pin, dir);
28736
28806
  }
28737
- async function fromCacheOrDownload(env, fetchId) {
28807
+ async function fromCacheOrDownload(env, fetchId, verified = false) {
28808
+ if (verified) return verifiedFromCacheOrDownload(env, fetchId);
28738
28809
  const p = parseId(fetchId);
28739
28810
  const cacheDir = join3(FETCH_ROOT, p.spaceId, p.name);
28740
28811
  const have = skillDirExists(cacheDir) ? readSource(cacheDir) : null;
@@ -28761,20 +28832,42 @@ async function fromCacheOrDownload(env, fetchId) {
28761
28832
  }
28762
28833
  return download2(env, fetchId);
28763
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
+ }
28764
28856
  function sameVersion(dir, want) {
28765
28857
  if (!skillDirExists(dir)) return false;
28766
28858
  const have = readSource(dir);
28767
28859
  return !!have && have.id === want.id && have.digest === want.digest;
28768
28860
  }
28769
- async function download2(env, fetchId, pin, target) {
28861
+ async function download2(env, fetchId, pin, target, verified = false) {
28770
28862
  try {
28771
- const a = await fetchArchive(env, fetchId, pin);
28863
+ const a = await fetchArchive(env, fetchId, pin, verified);
28772
28864
  const skillMd = a.files.find((f) => f.path === "SKILL.md");
28773
28865
  if (skillMd && pointerTargetOf(skillMd.data.toString("utf8")) !== null) {
28774
28866
  console.error(`${fetchId} downloaded as another pointer; stopping here (pointers are followed only one level, to avoid loops).`);
28775
28867
  return null;
28776
28868
  }
28777
28869
  const q = parseId(a.id);
28870
+ if (verified && !target) target = pinnedDir(q.spaceId, q.name, a.digest);
28778
28871
  const path = target ?? join3(FETCH_ROOT, q.spaceId, q.name);
28779
28872
  const record = { id: a.id, digest: a.digest, handle: a.handle, trust: a.trust };
28780
28873
  if (target) {
@@ -29072,7 +29165,7 @@ async function cmdSubmit(name, opts) {
29072
29165
  }
29073
29166
  }
29074
29167
  function finish(opts, id, digest, name, s) {
29075
- const base = { id, digest, state: s.state, handle: s.handle || void 0, gated: s.gated, released: s.released, reason: s.reason || void 0, duplicate_of: s.duplicate_of || void 0 };
29168
+ const base = { id, digest, state: s.state, handle: s.handle || void 0, released: s.released, reason: s.reason || void 0, duplicate_of: s.duplicate_of || void 0 };
29076
29169
  switch (s.state) {
29077
29170
  case "rejected":
29078
29171
  return done(opts, base, `Not published: ${rejectLabel(s.reason ?? "the server rejected the directory")}.`, 1);
@@ -29085,19 +29178,19 @@ function finish(opts, id, digest, name, s) {
29085
29178
  return done(
29086
29179
  opts,
29087
29180
  { ...base, taken_down: true },
29088
- `Not listed: ${s.handle || id} was taken down by the Neta operators, so new versions are kept but not shown anywhere. If you think this is a mistake, contact the Neta team.`,
29181
+ `Not listed: this version of ${s.handle || id} was taken down by the Neta operators, so it is kept but not shown anywhere. Submitting changed content creates a new version, which is published as usual. If you think this is a mistake, contact the Neta team.`,
29089
29182
  1
29090
29183
  );
29091
29184
  }
29092
29185
  const lines = [`${s.published === true ? "Published" : "Collected"}: ${s.handle || id} ${digest}`];
29093
29186
  if (s.duplicate_of) {
29094
- lines.push(`Its content is identical to ${s.duplicate_of}, so the hub treats it as a copy of that skill; it will not be listed on its own.`);
29187
+ lines.push(`Its content is identical to ${s.duplicate_of}, so the public list shows them as one entry. It still works under its own name.`);
29095
29188
  } else if (s.released) {
29096
29189
  lines.push("This version is already live.");
29097
- } else if (s.gated) {
29098
- lines.push("It is waiting for review: it goes live once the gate writes back pass for this version (publishing runs every 30 minutes). Until then the current live version, if any, stays.");
29190
+ } else if (s.trusted === true) {
29191
+ lines.push("It appears on the public skills site and in find at the next publish (within a few minutes). Studio only uses reviewed versions: until this one is reviewed (picked or nominated skills are), Studio keeps the last reviewed version, if any.");
29099
29192
  } else {
29100
- lines.push("It appears on the public skills site and in find at the next publish (every 30 minutes), marked as a community skill. Getting into Studio needs a review.");
29193
+ lines.push("It appears on the public skills site and in find at the next publish (within a few minutes), marked as a community skill. Getting into Studio needs a review.");
29101
29194
  }
29102
29195
  lines.push(`To take it down later: neta-search skills submit --withdraw ${name} (deleting the directory does not withdraw it).`);
29103
29196
  return done(opts, base, lines.join("\n"), 0);
@@ -29137,7 +29230,7 @@ async function withdraw(env, name, opts) {
29137
29230
  return done(
29138
29231
  opts,
29139
29232
  { id, state: s.state },
29140
- `Withdrawn: ${id} is taken off the public skills site, find and downloads (people who already installed it keep their copy). Your copy here is unchanged; run neta-search skills submit ${name} to publish it again.`,
29233
+ `Withdrawn: ${id} is no longer shared \u2014 it is taken off the public skills site, find and Studio. Anyone who already uses it by name can still install it; to have its files deleted for good (for example a leaked secret), contact the Neta team. Your copy here is unchanged; run neta-search skills submit ${name} to share it again.`,
29141
29234
  0
29142
29235
  );
29143
29236
  } else if (s.state === "rejected") {
@@ -29173,7 +29266,7 @@ function scanLocal(dir) {
29173
29266
  continue;
29174
29267
  }
29175
29268
  const ptr = pointerTarget(path);
29176
- 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" });
29177
29270
  }
29178
29271
  return out.sort((a, b) => a.name.localeCompare(b.name));
29179
29272
  }
@@ -29285,7 +29378,7 @@ async function cmdUpdate(names, opts) {
29285
29378
  latest: (r) => `${r.name} is up to date`,
29286
29379
  unavailable: (r) => `${r.name} is no longer available from its source; keeping the local copy`,
29287
29380
  own_skipped: (r) => `${r.name} is your own skill; nothing to update`,
29288
- 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`,
29289
29382
  not_found: (r) => `${opts.dir}/${r.name} does not exist`,
29290
29383
  error: (r) => `Failed to update ${r.name}: ${r.from ?? ""}`
29291
29384
  };
@@ -29332,7 +29425,7 @@ skills.command("find").description("search the ecosystem for skills").argument("
29332
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) => {
29333
29426
  await run(cmdAdd(idOrName, opts));
29334
29427
  });
29335
- 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) => {
29336
29429
  await run(cmdUse(idOrName, opts));
29337
29430
  });
29338
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.31",
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": {