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

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
@@ -5615,7 +5615,7 @@ var require_parseParams = __commonJS({
5615
5615
  var require_basename = __commonJS({
5616
5616
  "cli/node_modules/@fastify/busboy/lib/utils/basename.js"(exports, module) {
5617
5617
  "use strict";
5618
- module.exports = function basename(path) {
5618
+ module.exports = function basename3(path) {
5619
5619
  if (typeof path !== "string") {
5620
5620
  return "";
5621
5621
  }
@@ -5642,7 +5642,7 @@ var require_multipart = __commonJS({
5642
5642
  var Dicer = require_Dicer();
5643
5643
  var parseParams = require_parseParams();
5644
5644
  var decodeText = require_decodeText();
5645
- var basename = require_basename();
5645
+ var basename3 = require_basename();
5646
5646
  var getLimit = require_getLimit();
5647
5647
  var RE_BOUNDARY = /^boundary$/i;
5648
5648
  var RE_FIELD = /^form-data$/i;
@@ -5759,7 +5759,7 @@ var require_multipart = __commonJS({
5759
5759
  } else if (RE_FILENAME.test(parsed[i][0])) {
5760
5760
  filename = parsed[i][1];
5761
5761
  if (!preservePath) {
5762
- filename = basename(filename);
5762
+ filename = basename3(filename);
5763
5763
  }
5764
5764
  }
5765
5765
  }
@@ -28256,12 +28256,16 @@ import { gunzipSync } from "node:zlib";
28256
28256
  // cli/src/digest.ts
28257
28257
  import { createHash } from "node:crypto";
28258
28258
  import { lstatSync, readdirSync, readFileSync } from "node:fs";
28259
- import { join } from "node:path";
28259
+ import { basename, join } from "node:path";
28260
28260
  var SECRET_SUFFIXES = [".env", ".pem", ".key", ".p12", ".pfx", ".jks", ".keystore"];
28261
28261
  var SECRET_PREFIXES = ["id_rsa", "id_dsa", "id_ecdsa", "id_ed25519"];
28262
28262
  var NOISE_DIRS = /* @__PURE__ */ new Set(["node_modules", "__pycache__"]);
28263
- function skipSegment(name, isDir) {
28264
- if (name === "" || name.startsWith(".")) return true;
28263
+ var KEPT_DOT_DIRS = /* @__PURE__ */ new Map([[".cohub", "actions"]]);
28264
+ function skipSegment(name, isDir, parent = "") {
28265
+ if (name === "") return true;
28266
+ const only = KEPT_DOT_DIRS.get(parent);
28267
+ if (only !== void 0 && !(isDir && name === only)) return true;
28268
+ if (name.startsWith(".") && !(isDir && KEPT_DOT_DIRS.has(name))) return true;
28265
28269
  if (/[\u0000-\u001f\u007f]/.test(name)) return true;
28266
28270
  if (isDir) return NOISE_DIRS.has(name);
28267
28271
  const low = name.toLowerCase();
@@ -28283,7 +28287,7 @@ function readSkillFiles(dir) {
28283
28287
  for (const ent of readdirSync(join(dir, rel), { withFileTypes: true })) {
28284
28288
  const childRel = rel ? `${rel}/${ent.name}` : ent.name;
28285
28289
  const st = lstatSync(join(dir, childRel));
28286
- if (st.isSymbolicLink() || skipSegment(ent.name, st.isDirectory())) continue;
28290
+ if (st.isSymbolicLink() || skipSegment(ent.name, st.isDirectory(), basename(rel))) continue;
28287
28291
  if (st.isDirectory()) walk(childRel);
28288
28292
  else if (st.isFile()) out.set(childRel, readFileSync(join(dir, childRel)));
28289
28293
  }
@@ -28305,13 +28309,17 @@ function parseTrust(v) {
28305
28309
  var ArchiveNotFound = class extends Error {
28306
28310
  };
28307
28311
  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)}`;
28312
+ async function fetchArchive(env, id, pin, verified = false) {
28313
+ const url = archiveURL(env, id, pin, verified);
28311
28314
  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`);
28315
+ if (res.status === 404) {
28316
+ throw new ArchiveNotFound(
28317
+ 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`
28318
+ );
28319
+ }
28313
28320
  const location = res.headers.get("location");
28314
28321
  if (res.status < 300 || res.status >= 400 || !location) throw new Error(`failed to download ${id}: HTTP ${res.status}`);
28322
+ if (verified) checkVerified(res, id);
28315
28323
  const digest = res.headers.get("x-skill-digest") ?? "";
28316
28324
  const name = res.headers.get("x-skill-name") ?? id.split("/").pop();
28317
28325
  if (!digest) throw new Error(`failed to download ${id}: the server did not return a version (x-skill-digest)`);
@@ -28323,12 +28331,13 @@ async function fetchArchive(env, id, pin) {
28323
28331
  const handle = res.headers.get("x-skill-handle") || void 0;
28324
28332
  return { id, handle, name, digest, trust: parseTrust(res.headers.get("x-skill-trust")), files };
28325
28333
  }
28326
- async function archiveHead(env, id) {
28327
- const url = `${env.apiBase}${ARCHIVE_PATH}?id=${encodeURIComponent(id)}`;
28334
+ async function archiveHead(env, id, verified = false) {
28335
+ const url = archiveURL(env, id, void 0, verified);
28328
28336
  const res = await fetch(url, { method: "HEAD", redirect: "manual", signal: AbortSignal.timeout(1e4) });
28329
28337
  if (res.status === 404) return null;
28330
28338
  const digest = res.headers.get("x-skill-digest") ?? "";
28331
28339
  if (res.status < 300 || res.status >= 400 || !digest) throw new Error(`failed to look up ${id}: HTTP ${res.status}`);
28340
+ if (verified) checkVerified(res, id);
28332
28341
  return {
28333
28342
  id: res.headers.get("x-skill-id") ?? id,
28334
28343
  handle: res.headers.get("x-skill-handle") || void 0,
@@ -28337,6 +28346,17 @@ async function archiveHead(env, id) {
28337
28346
  trust: parseTrust(res.headers.get("x-skill-trust"))
28338
28347
  };
28339
28348
  }
28349
+ function archiveURL(env, id, pin, verified = false) {
28350
+ let url = `${env.apiBase}${ARCHIVE_PATH}?id=${encodeURIComponent(id)}`;
28351
+ if (pin) url += `&digest=${encodeURIComponent(pin)}`;
28352
+ if (verified) url += "&version=verified";
28353
+ return url;
28354
+ }
28355
+ function checkVerified(res, id) {
28356
+ if (res.headers.get("x-skill-version") !== "verified") {
28357
+ 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`);
28358
+ }
28359
+ }
28340
28360
  async function download(url, id) {
28341
28361
  for (let attempt = 1; ; attempt++) {
28342
28362
  const res = await fetch(url, { signal: AbortSignal.timeout(TIMEOUT_MS) });
@@ -28519,23 +28539,43 @@ function writeSkill(target, files, source) {
28519
28539
  }
28520
28540
  if (hadOld) rmSync(trash, { recursive: true, force: true });
28521
28541
  }
28522
- var POINTER_RE = /^\s*neta-skill-id:\s*["']?(cohub:[^"'\s]+)["']?\s*$/m;
28542
+ var POINTER_KEY_RE = /^\s*neta-skill-id:(.*)$/m;
28543
+ var POINTER_VERSION_KEY_RE = /^\s*neta-skill-version:(.*)$/m;
28544
+ function yamlScalar(raw) {
28545
+ let v = raw.trim();
28546
+ if ((v.startsWith('"') || v.startsWith("'")) && v.indexOf(v[0], 1) > 0) return v.slice(1, v.indexOf(v[0], 1));
28547
+ const hash = v.search(/\s#/);
28548
+ if (hash >= 0) v = v.slice(0, hash);
28549
+ if (v.startsWith("#")) v = "";
28550
+ return v.trim();
28551
+ }
28523
28552
  function frontmatterOf(raw) {
28524
28553
  if (!raw.startsWith("---\n")) return "";
28525
28554
  const end = raw.indexOf("\n---", 4);
28526
28555
  return end < 0 ? "" : raw.slice(4, end);
28527
28556
  }
28557
+ function pointerOf(raw) {
28558
+ const fm = frontmatterOf(raw);
28559
+ const m = POINTER_KEY_RE.exec(fm);
28560
+ const target = m ? yamlScalar(m[1]) : "";
28561
+ if (!target) return null;
28562
+ const v = POINTER_VERSION_KEY_RE.exec(fm);
28563
+ const version = v ? yamlScalar(v[1]).toLowerCase() : "";
28564
+ return { target, version: version || "latest" };
28565
+ }
28528
28566
  function pointerTargetOf(raw) {
28529
- const m = POINTER_RE.exec(frontmatterOf(raw));
28530
- return m ? m[1] : null;
28567
+ return pointerOf(raw)?.target ?? null;
28531
28568
  }
28532
- function pointerTarget(dir) {
28569
+ function pointerAt(dir) {
28533
28570
  try {
28534
- return pointerTargetOf(readFileSync3(join2(dir, "SKILL.md"), "utf8"));
28571
+ return pointerOf(readFileSync3(join2(dir, "SKILL.md"), "utf8"));
28535
28572
  } catch {
28536
28573
  return null;
28537
28574
  }
28538
28575
  }
28576
+ function pointerTarget(dir) {
28577
+ return pointerAt(dir)?.target ?? null;
28578
+ }
28539
28579
  function shareNotice(id, draft) {
28540
28580
  const name = id.split("/").pop();
28541
28581
  if (draft) {
@@ -28572,6 +28612,21 @@ async function cmdUse(idOrName, opts) {
28572
28612
  let notice = null;
28573
28613
  let found = null;
28574
28614
  let fetchId = parsed && !mine ? idOrName : null;
28615
+ if (opts.verified && opts.digest !== void 0) {
28616
+ console.error("Give either --digest or --verified, not both.");
28617
+ return 1;
28618
+ }
28619
+ if (opts.verified) {
28620
+ if (!parsed) {
28621
+ console.error(
28622
+ 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.`
28623
+ );
28624
+ return 1;
28625
+ }
28626
+ const r = await fromCacheOrDownload(env, idOrName, true);
28627
+ if (!r) return 1;
28628
+ return emit(env, r.found, r.handle ?? handle, name, { via, entry, viaPointer: false, notice: null, json: opts.json, pinned: false });
28629
+ }
28575
28630
  const pin = opts.digest?.trim().toLowerCase();
28576
28631
  if (opts.digest !== void 0) {
28577
28632
  if (!pin || !DIGEST_RE.test(pin)) {
@@ -28609,19 +28664,37 @@ async function cmdUse(idOrName, opts) {
28609
28664
  if (r === "ambiguous") return 1;
28610
28665
  if (r) found = r;
28611
28666
  }
28667
+ let wantVerified = false;
28612
28668
  if (found) {
28613
- const target = pointerTarget(found.path);
28614
- if (target !== null) {
28669
+ const ptr = pointerAt(found.path);
28670
+ if (ptr !== null) {
28671
+ const at = join3(found.path, "SKILL.md");
28672
+ if (ptr.version !== "verified" && ptr.version !== "latest") {
28673
+ console.error(`${at} is a pointer, but its neta-skill-version is not verified or latest: ${ptr.version}`);
28674
+ return 1;
28675
+ }
28676
+ let target = ptr.target;
28677
+ if (isHandle(target)) {
28678
+ const t = await resolveRefDetail(env, target);
28679
+ if (!parseId(t.ref)) {
28680
+ console.error(
28681
+ 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.`
28682
+ );
28683
+ return 1;
28684
+ }
28685
+ target = t.ref;
28686
+ }
28615
28687
  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}`);
28688
+ console.error(`${at} is a pointer, but its neta-skill-id is not a valid handle or id: ${ptr.target}`);
28617
28689
  return 1;
28618
28690
  }
28619
28691
  viaPointer = true;
28692
+ wantVerified = ptr.version === "verified";
28620
28693
  fetchId = target;
28621
28694
  found = null;
28622
28695
  notice = null;
28623
28696
  const p = parseId(target);
28624
- const r = findInMounts(p.spaceId, p.name);
28697
+ const r = wantVerified ? null : findInMounts(p.spaceId, p.name);
28625
28698
  if (r && r !== "ambiguous" && pointerTarget(r.path) === null) {
28626
28699
  found = r;
28627
28700
  fetchId = null;
@@ -28629,7 +28702,7 @@ async function cmdUse(idOrName, opts) {
28629
28702
  }
28630
28703
  }
28631
28704
  if (!found && fetchId) {
28632
- const r = await fromCacheOrDownload(env, fetchId);
28705
+ const r = await fromCacheOrDownload(env, fetchId, wantVerified);
28633
28706
  if (!r) return 1;
28634
28707
  found = r.found;
28635
28708
  handle = r.handle ?? handle;
@@ -28735,7 +28808,8 @@ async function pinnedFromCacheOrDownload(env, fetchId, pin) {
28735
28808
  }
28736
28809
  return download2(env, fetchId, pin, dir);
28737
28810
  }
28738
- async function fromCacheOrDownload(env, fetchId) {
28811
+ async function fromCacheOrDownload(env, fetchId, verified = false) {
28812
+ if (verified) return verifiedFromCacheOrDownload(env, fetchId);
28739
28813
  const p = parseId(fetchId);
28740
28814
  const cacheDir = join3(FETCH_ROOT, p.spaceId, p.name);
28741
28815
  const have = skillDirExists(cacheDir) ? readSource(cacheDir) : null;
@@ -28762,20 +28836,42 @@ async function fromCacheOrDownload(env, fetchId) {
28762
28836
  }
28763
28837
  return download2(env, fetchId);
28764
28838
  }
28839
+ var NO_REVIEWED = "Tell the user this skill is temporarily unavailable; do not substitute another skill or another version of it.";
28840
+ async function verifiedFromCacheOrDownload(env, fetchId) {
28841
+ const p = parseId(fetchId);
28842
+ let head;
28843
+ try {
28844
+ head = await archiveHead(env, fetchId, true);
28845
+ } catch (e) {
28846
+ console.error(`${String(e instanceof Error ? e.message : e)} (needs the reviewed version, so the local cache is not used). ${NO_REVIEWED}`);
28847
+ return null;
28848
+ }
28849
+ if (head === null) {
28850
+ console.error(`${fetchId} has no version that passed Neta's review (or does not exist). ${NO_REVIEWED}`);
28851
+ return null;
28852
+ }
28853
+ const dir = pinnedDir(p.spaceId, p.name, head.digest);
28854
+ const have = skillDirExists(dir) ? readSource(dir) : null;
28855
+ if (have && have.id === fetchId && have.digest === head.digest) {
28856
+ return { found: { path: dir, where: "cache", reportId: have.id, digest: have.digest, trust: head.trust ?? have.trust }, handle: head.handle ?? have.handle };
28857
+ }
28858
+ return download2(env, fetchId, void 0, void 0, true);
28859
+ }
28765
28860
  function sameVersion(dir, want) {
28766
28861
  if (!skillDirExists(dir)) return false;
28767
28862
  const have = readSource(dir);
28768
28863
  return !!have && have.id === want.id && have.digest === want.digest;
28769
28864
  }
28770
- async function download2(env, fetchId, pin, target) {
28865
+ async function download2(env, fetchId, pin, target, verified = false) {
28771
28866
  try {
28772
- const a = await fetchArchive(env, fetchId, pin);
28867
+ const a = await fetchArchive(env, fetchId, pin, verified);
28773
28868
  const skillMd = a.files.find((f) => f.path === "SKILL.md");
28774
28869
  if (skillMd && pointerTargetOf(skillMd.data.toString("utf8")) !== null) {
28775
28870
  console.error(`${fetchId} downloaded as another pointer; stopping here (pointers are followed only one level, to avoid loops).`);
28776
28871
  return null;
28777
28872
  }
28778
28873
  const q = parseId(a.id);
28874
+ if (verified && !target) target = pinnedDir(q.spaceId, q.name, a.digest);
28779
28875
  const path = target ?? join3(FETCH_ROOT, q.spaceId, q.name);
28780
28876
  const record = { id: a.id, digest: a.digest, handle: a.handle, trust: a.trust };
28781
28877
  if (target) {
@@ -28905,7 +29001,7 @@ metadata:
28905
29001
 
28906
29002
  // cli/src/skills/submit.ts
28907
29003
  import { existsSync as existsSync5, lstatSync as lstatSync2, readdirSync as readdirSync3, readFileSync as readFileSync5 } from "node:fs";
28908
- import { join as join6 } from "node:path";
29004
+ import { basename as basename2, join as join6 } from "node:path";
28909
29005
  var SUBMISSION_PATH = "/v1/skills/submission";
28910
29006
  var MAX_FILES = 1e3;
28911
29007
  var MAX_FILE_BYTES = 20 << 20;
@@ -28924,7 +29020,7 @@ function precheck(dir) {
28924
29020
  const childRel = rel ? `${rel}/${ent.name}` : ent.name;
28925
29021
  const st = lstatSync2(join6(dir, childRel));
28926
29022
  if (st.isSymbolicLink()) links.push(childRel);
28927
- else if (st.isDirectory() && !skipSegment(ent.name, true)) walk(childRel);
29023
+ else if (st.isDirectory() && !skipSegment(ent.name, true, basename2(rel))) walk(childRel);
28928
29024
  }
28929
29025
  };
28930
29026
  walk("");
@@ -29174,7 +29270,7 @@ function scanLocal(dir) {
29174
29270
  continue;
29175
29271
  }
29176
29272
  const ptr = pointerTarget(path);
29177
- out.push(ptr ? { name: e.name, path, kind: "pointer", id: ptr } : { name: e.name, path, kind: "own" });
29273
+ out.push(ptr ? { name: e.name, path, kind: "pointer", id: ptr, handle: isHandle(ptr) ? ptr : void 0 } : { name: e.name, path, kind: "own" });
29178
29274
  }
29179
29275
  return out.sort((a, b) => a.name.localeCompare(b.name));
29180
29276
  }
@@ -29286,7 +29382,7 @@ async function cmdUpdate(names, opts) {
29286
29382
  latest: (r) => `${r.name} is up to date`,
29287
29383
  unavailable: (r) => `${r.name} is no longer available from its source; keeping the local copy`,
29288
29384
  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`,
29385
+ pointer_skipped: (r) => `${r.name} is a pointer; use fetches the version it asks for, nothing to update`,
29290
29386
  not_found: (r) => `${opts.dir}/${r.name} does not exist`,
29291
29387
  error: (r) => `Failed to update ${r.name}: ${r.from ?? ""}`
29292
29388
  };
@@ -29333,7 +29429,7 @@ skills.command("find").description("search the ecosystem for skills").argument("
29333
29429
  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
29430
  await run(cmdAdd(idOrName, opts));
29335
29431
  });
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) => {
29432
+ 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
29433
  await run(cmdUse(idOrName, opts));
29338
29434
  });
29339
29435
  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.41",
4
4
  "description": "neta-search CLI — find, try and install skills from the Cohub / Neta skill registry",
5
5
  "type": "module",
6
6
  "bin": {