@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 +1 -1
- package/neta-search.mjs +114 -22
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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)
|
|
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 =
|
|
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
|
|
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
|
-
|
|
28530
|
-
return m ? m[1] : null;
|
|
28563
|
+
return pointerOf(raw)?.target ?? null;
|
|
28531
28564
|
}
|
|
28532
|
-
function
|
|
28565
|
+
function pointerAt(dir) {
|
|
28533
28566
|
try {
|
|
28534
|
-
return
|
|
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
|
|
28614
|
-
if (
|
|
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(`${
|
|
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
|
|
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) => {
|