@neta-art/neta-search-cli 0.2.23 → 0.2.26
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 +3 -0
- package/neta-search.mjs +230 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ npx -y @neta-art/neta-search-cli@latest skills add <id> # insta
|
|
|
10
10
|
npx -y @neta-art/neta-search-cli@latest skills list # what's in .agents/skills/, and which installed ones have updates
|
|
11
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
12
|
npx -y @neta-art/neta-search-cli@latest skills init <name> # scaffold your own skill
|
|
13
|
+
npx -y @neta-art/neta-search-cli@latest skills submit <name> # submit .agents/skills/<name>/ to the hub and wait for the result
|
|
13
14
|
```
|
|
14
15
|
|
|
15
16
|
`<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/`.
|
|
@@ -22,6 +23,8 @@ Each `find --json` item carries a ready-to-run `use` command (`skills use <id> -
|
|
|
22
23
|
|
|
23
24
|
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.
|
|
24
25
|
|
|
26
|
+
`submit` works inside a Cohub sandbox only: it checks the directory locally (frontmatter, size, symlinks), sends this version to the hub under your name, and waits for the result: collected (with its digest) or why not. Submitting is not publishing: a version is offered to others only after it passes review. To keep a skill to yourself, add `share: false` to its frontmatter.
|
|
27
|
+
|
|
25
28
|
Inside a Cohub sandbox the CLI picks up the sandbox identity automatically; elsewhere it runs anonymously.
|
|
26
29
|
|
|
27
30
|
Single file, no runtime dependencies. Node.js 20+.
|
package/neta-search.mjs
CHANGED
|
@@ -4210,8 +4210,8 @@ var require_util = __commonJS({
|
|
|
4210
4210
|
iterator = iterable[Symbol.asyncIterator]();
|
|
4211
4211
|
},
|
|
4212
4212
|
async pull(controller) {
|
|
4213
|
-
const { done, value } = await iterator.next();
|
|
4214
|
-
if (
|
|
4213
|
+
const { done: done2, value } = await iterator.next();
|
|
4214
|
+
if (done2) {
|
|
4215
4215
|
queueMicrotask(() => {
|
|
4216
4216
|
controller.close();
|
|
4217
4217
|
});
|
|
@@ -7158,8 +7158,8 @@ var require_util2 = __commonJS({
|
|
|
7158
7158
|
const bytes = [];
|
|
7159
7159
|
let byteLength = 0;
|
|
7160
7160
|
while (true) {
|
|
7161
|
-
const { done, value: chunk } = await reader.read();
|
|
7162
|
-
if (
|
|
7161
|
+
const { done: done2, value: chunk } = await reader.read();
|
|
7162
|
+
if (done2) {
|
|
7163
7163
|
return Buffer.concat(bytes, byteLength);
|
|
7164
7164
|
}
|
|
7165
7165
|
if (!isUint8Array(chunk)) {
|
|
@@ -7402,8 +7402,8 @@ var require_webidl = __commonJS({
|
|
|
7402
7402
|
});
|
|
7403
7403
|
}
|
|
7404
7404
|
while (true) {
|
|
7405
|
-
const { done, value } = method.next();
|
|
7406
|
-
if (
|
|
7405
|
+
const { done: done2, value } = method.next();
|
|
7406
|
+
if (done2) {
|
|
7407
7407
|
break;
|
|
7408
7408
|
}
|
|
7409
7409
|
seq.push(converter(value));
|
|
@@ -8395,8 +8395,8 @@ Content-Type: ${value.type || "application/octet-stream"}\r
|
|
|
8395
8395
|
iterator = action(object)[Symbol.asyncIterator]();
|
|
8396
8396
|
},
|
|
8397
8397
|
async pull(controller) {
|
|
8398
|
-
const { value, done } = await iterator.next();
|
|
8399
|
-
if (
|
|
8398
|
+
const { value, done: done2 } = await iterator.next();
|
|
8399
|
+
if (done2) {
|
|
8400
8400
|
queueMicrotask(() => {
|
|
8401
8401
|
controller.close();
|
|
8402
8402
|
});
|
|
@@ -16793,11 +16793,11 @@ var require_fetch = __commonJS({
|
|
|
16793
16793
|
let bytes;
|
|
16794
16794
|
let isFailure;
|
|
16795
16795
|
try {
|
|
16796
|
-
const { done, value } = await fetchParams.controller.next();
|
|
16796
|
+
const { done: done2, value } = await fetchParams.controller.next();
|
|
16797
16797
|
if (isAborted(fetchParams)) {
|
|
16798
16798
|
break;
|
|
16799
16799
|
}
|
|
16800
|
-
bytes =
|
|
16800
|
+
bytes = done2 ? void 0 : value;
|
|
16801
16801
|
} catch (err) {
|
|
16802
16802
|
if (fetchParams.controller.ended && !timingInfo.encodedBodySize) {
|
|
16803
16803
|
bytes = void 0;
|
|
@@ -17394,14 +17394,14 @@ var require_util4 = __commonJS({
|
|
|
17394
17394
|
(async () => {
|
|
17395
17395
|
while (!fr[kAborted]) {
|
|
17396
17396
|
try {
|
|
17397
|
-
const { done, value } = await chunkPromise;
|
|
17397
|
+
const { done: done2, value } = await chunkPromise;
|
|
17398
17398
|
if (isFirstChunk && !fr[kAborted]) {
|
|
17399
17399
|
queueMicrotask(() => {
|
|
17400
17400
|
fireAProgressEvent("loadstart", fr);
|
|
17401
17401
|
});
|
|
17402
17402
|
}
|
|
17403
17403
|
isFirstChunk = false;
|
|
17404
|
-
if (!
|
|
17404
|
+
if (!done2 && types.isUint8Array(value)) {
|
|
17405
17405
|
bytes.push(value);
|
|
17406
17406
|
if ((fr[kLastProgressEventFired] === void 0 || Date.now() - fr[kLastProgressEventFired] >= 50) && !fr[kAborted]) {
|
|
17407
17407
|
fr[kLastProgressEventFired] = Date.now();
|
|
@@ -17410,7 +17410,7 @@ var require_util4 = __commonJS({
|
|
|
17410
17410
|
});
|
|
17411
17411
|
}
|
|
17412
17412
|
chunkPromise = reader.read();
|
|
17413
|
-
} else if (
|
|
17413
|
+
} else if (done2) {
|
|
17414
17414
|
queueMicrotask(() => {
|
|
17415
17415
|
fr[kState] = "done";
|
|
17416
17416
|
try {
|
|
@@ -25148,16 +25148,16 @@ function transformConnectPostToGetRequest(request3, message, useBase64) {
|
|
|
25148
25148
|
// cli/node_modules/@connectrpc/connect/dist/esm/protocol/run-call.js
|
|
25149
25149
|
function runUnaryCall(opt) {
|
|
25150
25150
|
const next = applyInterceptors(opt.next, opt.interceptors);
|
|
25151
|
-
const [signal, abort,
|
|
25151
|
+
const [signal, abort, done2] = setupSignal(opt);
|
|
25152
25152
|
const req = Object.assign(Object.assign({}, opt.req), { message: normalize(opt.req.method.I, opt.req.message), signal });
|
|
25153
25153
|
return next(req).then((res) => {
|
|
25154
|
-
|
|
25154
|
+
done2();
|
|
25155
25155
|
return res;
|
|
25156
25156
|
}, abort);
|
|
25157
25157
|
}
|
|
25158
25158
|
function runStreamingCall(opt) {
|
|
25159
25159
|
const next = applyInterceptors(opt.next, opt.interceptors);
|
|
25160
|
-
const [signal, abort,
|
|
25160
|
+
const [signal, abort, done2] = setupSignal(opt);
|
|
25161
25161
|
const req = Object.assign(Object.assign({}, opt.req), { message: normalizeIterable(opt.req.method.I, opt.req.message), signal });
|
|
25162
25162
|
let doneCalled = false;
|
|
25163
25163
|
signal.addEventListener("abort", function() {
|
|
@@ -25179,7 +25179,7 @@ function runStreamingCall(opt) {
|
|
|
25179
25179
|
return it.next().then((r) => {
|
|
25180
25180
|
if (r.done == true) {
|
|
25181
25181
|
doneCalled = true;
|
|
25182
|
-
|
|
25182
|
+
done2();
|
|
25183
25183
|
}
|
|
25184
25184
|
return r;
|
|
25185
25185
|
}, abort);
|
|
@@ -25201,7 +25201,7 @@ function setupSignal(opt) {
|
|
|
25201
25201
|
cleanup();
|
|
25202
25202
|
return Promise.reject(e);
|
|
25203
25203
|
},
|
|
25204
|
-
function
|
|
25204
|
+
function done2() {
|
|
25205
25205
|
cleanup();
|
|
25206
25206
|
controller.abort();
|
|
25207
25207
|
}
|
|
@@ -26957,9 +26957,12 @@ function eventContext(env, extra = {}) {
|
|
|
26957
26957
|
return ctx;
|
|
26958
26958
|
}
|
|
26959
26959
|
async function reportSkillEvent(env, event, skillId, digest, skillName, extra = {}) {
|
|
26960
|
-
|
|
26960
|
+
return await reportSkillEventDetail(env, event, skillId, digest, skillName, extra) !== "unreachable";
|
|
26961
|
+
}
|
|
26962
|
+
async function reportSkillEventDetail(env, event, skillId, digest, skillName, extra = {}) {
|
|
26963
|
+
if (env.anonymous) return "unreachable";
|
|
26961
26964
|
try {
|
|
26962
|
-
await feedbackClient(env).reportSkillEvents({
|
|
26965
|
+
const res = await feedbackClient(env).reportSkillEvents({
|
|
26963
26966
|
events: [
|
|
26964
26967
|
{
|
|
26965
26968
|
event,
|
|
@@ -26973,10 +26976,10 @@ async function reportSkillEvent(env, event, skillId, digest, skillName, extra =
|
|
|
26973
26976
|
}
|
|
26974
26977
|
]
|
|
26975
26978
|
});
|
|
26976
|
-
return
|
|
26979
|
+
return res.accepted > 0 ? "accepted" : "dropped";
|
|
26977
26980
|
} catch (e) {
|
|
26978
26981
|
console.error(`Note: failed to report the ${event} event (this does not affect the ${event}): ${e instanceof Error ? e.message : e}`);
|
|
26979
|
-
return
|
|
26982
|
+
return "unreachable";
|
|
26980
26983
|
}
|
|
26981
26984
|
}
|
|
26982
26985
|
|
|
@@ -27292,14 +27295,15 @@ function sharedOptOut(dir) {
|
|
|
27292
27295
|
}
|
|
27293
27296
|
}
|
|
27294
27297
|
function shareNotice(id, dir, draft, reported) {
|
|
27298
|
+
const name = id.split("/").pop();
|
|
27295
27299
|
if (draft) {
|
|
27296
|
-
console.error(`Note:
|
|
27300
|
+
console.error(`Note: drafts are not sent to the hub. When it is ready, run neta-search skills add ${name} to move it to ${SKILLS_DIR}/, then neta-search skills submit ${name}.`);
|
|
27297
27301
|
} else if (sharedOptOut(dir)) {
|
|
27298
|
-
console.error(`Note: ${id} sets share: false, so it is not
|
|
27302
|
+
console.error(`Note: ${id} sets share: false, so it is not sent to the hub.`);
|
|
27299
27303
|
} else if (!reported) {
|
|
27300
|
-
console.error(`Note: could not reach the server this time, so ${id}
|
|
27304
|
+
console.error(`Note: could not reach the server this time, so this version of ${id} was not sent to the hub; it will retry on the next use.`);
|
|
27301
27305
|
} else {
|
|
27302
|
-
console.error(`Note:
|
|
27306
|
+
console.error(`Note: this version of ${id} was sent to the hub under your name. Others can use it only after it passes review; neta-search skills submit ${name} shows whether it was collected. To keep it to yourself, add share: false to the SKILL.md frontmatter.`);
|
|
27303
27307
|
}
|
|
27304
27308
|
}
|
|
27305
27309
|
|
|
@@ -27644,9 +27648,10 @@ async function cmdInit(name, opts) {
|
|
|
27644
27648
|
console.log(`
|
|
27645
27649
|
Next steps:`);
|
|
27646
27650
|
console.log(` 1. Edit SKILL.md. Make the description say when to use this skill; agents decide by it, and search matches on it.`);
|
|
27647
|
-
console.log(` 2. Try it: neta-search skills use ${slug} (drafts work directly and are not
|
|
27648
|
-
console.log(` 3. When it is ready: neta-search skills add ${slug} moves it to .agents/skills
|
|
27649
|
-
console.log(`
|
|
27651
|
+
console.log(` 2. Try it: neta-search skills use ${slug} (drafts work directly and are not sent to the hub)`);
|
|
27652
|
+
console.log(` 3. When it is ready: neta-search skills add ${slug} moves it to .agents/skills/`);
|
|
27653
|
+
console.log(` 4. Submit it: neta-search skills submit ${slug} sends this version to the hub and tells you whether it was collected.`);
|
|
27654
|
+
console.log(` Others can use it only after it passes review. To keep it to yourself, add share: false to the frontmatter.`);
|
|
27650
27655
|
return 0;
|
|
27651
27656
|
}
|
|
27652
27657
|
function template(slug) {
|
|
@@ -27655,7 +27660,7 @@ name: ${slug}
|
|
|
27655
27660
|
description: One sentence on when to use this skill (what a user would search for). Search matches on this field.
|
|
27656
27661
|
metadata:
|
|
27657
27662
|
neta-scope: universal # character|world|image|video|audio|text|universal
|
|
27658
|
-
# neta-tags: [] # optional: your own tags
|
|
27663
|
+
# neta-tags: [] # optional: your own tags
|
|
27659
27664
|
# disable-model-invocation: true # optional: hide from the agent's skill list; only runs when named explicitly or via use
|
|
27660
27665
|
---
|
|
27661
27666
|
|
|
@@ -27671,15 +27676,197 @@ metadata:
|
|
|
27671
27676
|
`;
|
|
27672
27677
|
}
|
|
27673
27678
|
|
|
27674
|
-
// cli/src/skills/
|
|
27675
|
-
import { existsSync as existsSync5, readdirSync as readdirSync3,
|
|
27679
|
+
// cli/src/skills/submit.ts
|
|
27680
|
+
import { existsSync as existsSync5, lstatSync as lstatSync2, readdirSync as readdirSync3, readFileSync as readFileSync5 } from "node:fs";
|
|
27676
27681
|
import { join as join6 } from "node:path";
|
|
27682
|
+
var SUBMISSION_PATH = "/v1/skills/submission";
|
|
27683
|
+
var MAX_FILES = 1e3;
|
|
27684
|
+
var MAX_FILE_BYTES = 20 << 20;
|
|
27685
|
+
var MAX_TOTAL_BYTES = 100 << 20;
|
|
27686
|
+
function precheck(dir) {
|
|
27687
|
+
const problems = [];
|
|
27688
|
+
const md = join6(dir, "SKILL.md");
|
|
27689
|
+
if (!existsSync5(md)) return ["there is no SKILL.md"];
|
|
27690
|
+
const raw = readFileSync5(md, "utf8");
|
|
27691
|
+
const fm = /^---\r?\n([\s\S]*?)\r?\n---/.exec(raw);
|
|
27692
|
+
if (!fm) problems.push("SKILL.md has no frontmatter (it must start with --- and have name / description)");
|
|
27693
|
+
else if (!/^description\s*:\s*\S/m.test(fm[1])) problems.push("the frontmatter has no description (agents decide when to use a skill by it)");
|
|
27694
|
+
if (sharedOptOut(dir)) problems.push("SKILL.md sets share: false; remove it to submit");
|
|
27695
|
+
const links = [];
|
|
27696
|
+
const walk = (rel) => {
|
|
27697
|
+
for (const ent of readdirSync3(join6(dir, rel), { withFileTypes: true })) {
|
|
27698
|
+
const childRel = rel ? `${rel}/${ent.name}` : ent.name;
|
|
27699
|
+
const st = lstatSync2(join6(dir, childRel));
|
|
27700
|
+
if (st.isSymbolicLink()) links.push(childRel);
|
|
27701
|
+
else if (st.isDirectory() && !skipSegment(ent.name, true)) walk(childRel);
|
|
27702
|
+
}
|
|
27703
|
+
};
|
|
27704
|
+
walk("");
|
|
27705
|
+
if (links.length) problems.push(`symbolic links are not accepted: ${links.slice(0, 5).join(", ")}${links.length > 5 ? ` (+${links.length - 5} more)` : ""}`);
|
|
27706
|
+
const files = readSkillFiles(dir);
|
|
27707
|
+
if (files.size > MAX_FILES) problems.push(`too many files: ${files.size} (at most ${MAX_FILES})`);
|
|
27708
|
+
let total = 0;
|
|
27709
|
+
for (const [p, b] of files) {
|
|
27710
|
+
total += b.length;
|
|
27711
|
+
if (b.length > MAX_FILE_BYTES) problems.push(`${p} is ${mb(b.length)} (at most ${mb(MAX_FILE_BYTES)} per file)`);
|
|
27712
|
+
}
|
|
27713
|
+
if (total > MAX_TOTAL_BYTES) problems.push(`the skill is ${mb(total)} in total (at most ${mb(MAX_TOTAL_BYTES)})`);
|
|
27714
|
+
return problems;
|
|
27715
|
+
}
|
|
27716
|
+
var mb = (n) => `${(n / (1 << 20)).toFixed(1)} MB`;
|
|
27717
|
+
function blockLabel(reason) {
|
|
27718
|
+
const head = reason.split(":")[0];
|
|
27719
|
+
switch (head) {
|
|
27720
|
+
case "draft":
|
|
27721
|
+
return "it is in the drafts directory";
|
|
27722
|
+
case "opt_out":
|
|
27723
|
+
return "SKILL.md sets share: false";
|
|
27724
|
+
case "pointer":
|
|
27725
|
+
return "SKILL.md is a hub pointer, not a skill";
|
|
27726
|
+
case "frontmatter":
|
|
27727
|
+
return `the frontmatter is incomplete (${reason.slice(head.length + 1).trim()})`;
|
|
27728
|
+
case "secret":
|
|
27729
|
+
return `a file looks like it contains a secret (${reason.slice(head.length + 1).trim()}); remove it and submit again`;
|
|
27730
|
+
default:
|
|
27731
|
+
return reason;
|
|
27732
|
+
}
|
|
27733
|
+
}
|
|
27734
|
+
async function fetchStatus(env, id, digest) {
|
|
27735
|
+
const url = `${env.apiBase}${SUBMISSION_PATH}?id=${encodeURIComponent(id)}&digest=${encodeURIComponent(digest)}`;
|
|
27736
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(1e4) });
|
|
27737
|
+
if (!res.ok) return { http: res.status };
|
|
27738
|
+
return await res.json();
|
|
27739
|
+
}
|
|
27740
|
+
async function cmdSubmit(name, opts) {
|
|
27741
|
+
const env = detectEnv();
|
|
27742
|
+
if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) {
|
|
27743
|
+
console.error(`Skill names are kebab-case directory names under ${SKILLS_DIR}/ (e.g. brawl-creator), got "${name}".`);
|
|
27744
|
+
return 2;
|
|
27745
|
+
}
|
|
27746
|
+
if (env.anonymous) {
|
|
27747
|
+
console.error("submit only works inside a Cohub sandbox: it submits a skill from the space you are in.");
|
|
27748
|
+
return 2;
|
|
27749
|
+
}
|
|
27750
|
+
const dir = join6(SKILLS_DIR, name);
|
|
27751
|
+
if (!existsSync5(join6(dir, "SKILL.md"))) {
|
|
27752
|
+
if (existsSync5(join6(DRAFTS_DIR, name, "SKILL.md"))) {
|
|
27753
|
+
console.error(`${name} is still a draft (${DRAFTS_DIR}/${name}/). Drafts are not submitted; run neta-search skills add ${name} to move it to ${SKILLS_DIR}/ first.`);
|
|
27754
|
+
} else {
|
|
27755
|
+
console.error(`There is no skill at ${dir}/ (it needs a SKILL.md).`);
|
|
27756
|
+
}
|
|
27757
|
+
return 1;
|
|
27758
|
+
}
|
|
27759
|
+
const src = readSource(dir);
|
|
27760
|
+
if (src) {
|
|
27761
|
+
console.error(`${dir}/ was installed from ${src.handle ?? src.id}; only skills written in this space can be submitted.`);
|
|
27762
|
+
return 1;
|
|
27763
|
+
}
|
|
27764
|
+
if (pointerTarget(dir)) {
|
|
27765
|
+
console.error(`${dir}/SKILL.md is a hub pointer, not a skill; there is nothing to submit.`);
|
|
27766
|
+
return 1;
|
|
27767
|
+
}
|
|
27768
|
+
const problems = precheck(dir);
|
|
27769
|
+
if (problems.length) {
|
|
27770
|
+
console.error(`Not submitted: ${dir}/ has problems the hub would reject:`);
|
|
27771
|
+
for (const p of problems) console.error(` - ${p}`);
|
|
27772
|
+
return 1;
|
|
27773
|
+
}
|
|
27774
|
+
const files = readSkillFiles(dir);
|
|
27775
|
+
const digest = digestOf(files);
|
|
27776
|
+
const id = ownId(env, name);
|
|
27777
|
+
const total = [...files.values()].reduce((a, b) => a + b.length, 0);
|
|
27778
|
+
if (!opts.json) console.error(`Checked: ${files.size} files, ${mb(total)}. Submitting ${digest} ...`);
|
|
27779
|
+
const sent = await reportSkillEventDetail(env, "use", id, digest, name, { via: "submit", where: "local" });
|
|
27780
|
+
if (sent === "unreachable") {
|
|
27781
|
+
console.error("Not submitted: could not reach the server. Try again in a moment.");
|
|
27782
|
+
return 1;
|
|
27783
|
+
}
|
|
27784
|
+
if (sent === "dropped") {
|
|
27785
|
+
console.error("Not submitted: the server received the request but did not record it (it may be over its daily limit or having trouble). Try again later.");
|
|
27786
|
+
return 1;
|
|
27787
|
+
}
|
|
27788
|
+
const deadline = Date.now() + (opts.wait ?? 60) * 1e3;
|
|
27789
|
+
const poll = opts.pollMs ?? 2e3;
|
|
27790
|
+
let s;
|
|
27791
|
+
let unknown = 0;
|
|
27792
|
+
let toldWaiting = false;
|
|
27793
|
+
for (; ; ) {
|
|
27794
|
+
try {
|
|
27795
|
+
s = await fetchStatus(env, id, digest);
|
|
27796
|
+
} catch {
|
|
27797
|
+
s = { http: 0 };
|
|
27798
|
+
}
|
|
27799
|
+
if ("http" in s) {
|
|
27800
|
+
if (s.http === 404 || s.http === 403) {
|
|
27801
|
+
return done(opts, { id, digest, state: "no_result" }, `Submitted ${digest}, but this server cannot report the result (older server, or not reachable from here). Whether it was collected is unknown.`, 4);
|
|
27802
|
+
}
|
|
27803
|
+
} else if (s.state === "unknown") {
|
|
27804
|
+
if (++unknown >= 3) {
|
|
27805
|
+
return done(
|
|
27806
|
+
opts,
|
|
27807
|
+
{ id, digest, state: s.state },
|
|
27808
|
+
`Not collected: when the server read ${dir}/ it was no longer this version (was it edited right after submitting?). Finish editing, then run neta-search skills submit ${name} again.`,
|
|
27809
|
+
1
|
|
27810
|
+
);
|
|
27811
|
+
}
|
|
27812
|
+
} else if (s.state !== "queued") {
|
|
27813
|
+
return finish(opts, id, digest, name, s);
|
|
27814
|
+
} else if (!toldWaiting && !opts.json) {
|
|
27815
|
+
console.error(`Waiting for the server to collect it${s.reason ? ` (last attempt failed: ${s.reason}; it retries)` : ""} ...`);
|
|
27816
|
+
toldWaiting = true;
|
|
27817
|
+
}
|
|
27818
|
+
if (Date.now() + poll > deadline) {
|
|
27819
|
+
return done(
|
|
27820
|
+
opts,
|
|
27821
|
+
{ id, digest, state: "queued" },
|
|
27822
|
+
`Still waiting after ${opts.wait ?? 60}s: the server has not collected ${digest} yet. Run the same command again later; resubmitting the same version is safe.`,
|
|
27823
|
+
3
|
|
27824
|
+
);
|
|
27825
|
+
}
|
|
27826
|
+
await new Promise((r) => setTimeout(r, poll));
|
|
27827
|
+
}
|
|
27828
|
+
}
|
|
27829
|
+
function finish(opts, id, digest, name, s) {
|
|
27830
|
+
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 };
|
|
27831
|
+
switch (s.state) {
|
|
27832
|
+
case "rejected":
|
|
27833
|
+
return done(opts, base, `Not collected: ${s.reason ?? "the server rejected the directory"}. Fix it and submit again.`, 1);
|
|
27834
|
+
case "blocked":
|
|
27835
|
+
return done(opts, base, `Not collected: ${blockLabel(s.reason ?? "")}.`, 1);
|
|
27836
|
+
case "superseded":
|
|
27837
|
+
return done(opts, base, `A newer version of ${name} was submitted meanwhile; that one is being collected instead of ${digest}.`, 1);
|
|
27838
|
+
default: {
|
|
27839
|
+
const lines = [`Collected: ${s.handle || id} ${digest}`];
|
|
27840
|
+
if (s.duplicate_of) {
|
|
27841
|
+
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.`);
|
|
27842
|
+
} else if (s.released) {
|
|
27843
|
+
lines.push("This version is already live.");
|
|
27844
|
+
} else if (s.gated) {
|
|
27845
|
+
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.");
|
|
27846
|
+
} else if (s.trusted) {
|
|
27847
|
+
lines.push("It goes live at the next publish (every 30 minutes).");
|
|
27848
|
+
} else {
|
|
27849
|
+
lines.push("Skills from personal spaces are not offered to other people yet (that needs the review gate, which is not open yet). You can keep using it here.");
|
|
27850
|
+
}
|
|
27851
|
+
return done(opts, base, lines.join("\n"), 0);
|
|
27852
|
+
}
|
|
27853
|
+
}
|
|
27854
|
+
}
|
|
27855
|
+
function done(opts, json, text, code) {
|
|
27856
|
+
if (opts.json) console.log(JSON.stringify(json));
|
|
27857
|
+
else (code === 0 ? console.log : console.error)(text);
|
|
27858
|
+
return code;
|
|
27859
|
+
}
|
|
27860
|
+
|
|
27861
|
+
// cli/src/skills/manage.ts
|
|
27862
|
+
import { existsSync as existsSync6, readdirSync as readdirSync4, rmSync as rmSync2 } from "node:fs";
|
|
27863
|
+
import { join as join7 } from "node:path";
|
|
27677
27864
|
function scanLocal(dir) {
|
|
27678
|
-
if (!
|
|
27865
|
+
if (!existsSync6(dir)) return [];
|
|
27679
27866
|
const out = [];
|
|
27680
|
-
for (const e of
|
|
27867
|
+
for (const e of readdirSync4(dir, { withFileTypes: true })) {
|
|
27681
27868
|
if (!e.isDirectory() || e.name.startsWith(".")) continue;
|
|
27682
|
-
const path =
|
|
27869
|
+
const path = join7(dir, e.name);
|
|
27683
27870
|
if (!skillDirExists(path)) continue;
|
|
27684
27871
|
const src = readSource(path);
|
|
27685
27872
|
if (src) {
|
|
@@ -27812,7 +27999,7 @@ async function cmdRemove(names, opts) {
|
|
|
27812
27999
|
const local = scanLocal(opts.dir);
|
|
27813
28000
|
const results = [];
|
|
27814
28001
|
for (const n of names) {
|
|
27815
|
-
const found = (await findLocal(env, local, n)).filter((s) =>
|
|
28002
|
+
const found = (await findLocal(env, local, n)).filter((s) => existsSync6(s.path));
|
|
27816
28003
|
if (found.length === 0) results.push({ name: n, result: "not_found" });
|
|
27817
28004
|
for (const s of found) {
|
|
27818
28005
|
if (s.kind === "own" && !opts.force) {
|
|
@@ -27827,8 +28014,8 @@ async function cmdRemove(names, opts) {
|
|
|
27827
28014
|
if (opts.json) console.log(JSON.stringify({ results }));
|
|
27828
28015
|
else
|
|
27829
28016
|
for (const r of results) {
|
|
27830
|
-
if (r.result === "removed") console.log(`Removed ${
|
|
27831
|
-
else if (r.result === "not_found") console.error(`${
|
|
28017
|
+
if (r.result === "removed") console.log(`Removed ${join7(opts.dir, r.name)}`);
|
|
28018
|
+
else if (r.result === "not_found") console.error(`${join7(opts.dir, r.name)} does not exist`);
|
|
27832
28019
|
else console.error(`${r.name} is a skill you wrote yourself (no install source), so it was not removed. Add --force if you really want to delete it.`);
|
|
27833
28020
|
}
|
|
27834
28021
|
return failed > 0 ? 1 : 0;
|
|
@@ -27837,7 +28024,7 @@ async function cmdRemove(names, opts) {
|
|
|
27837
28024
|
// cli/src/cli.ts
|
|
27838
28025
|
var program2 = new Command();
|
|
27839
28026
|
program2.name("neta-search").description("neta-search: search the Cohub skill ecosystem").version(cliVersion());
|
|
27840
|
-
var skills = program2.command("skills").description("skills: find / add / use / list / update / remove / init");
|
|
28027
|
+
var skills = program2.command("skills").description("skills: find / add / use / list / update / remove / init / submit");
|
|
27841
28028
|
skills.command("find").description("search the ecosystem for skills").argument("<query>", "what you want to do (natural language or keywords)").option("-n, --limit <n>", "max results", "20").option("--json", "compact JSON output (for agents)").action(async (query, opts) => {
|
|
27842
28029
|
await run(cmdFind(query, { limit: Number(opts.limit) || 20, json: opts.json }));
|
|
27843
28030
|
});
|
|
@@ -27847,6 +28034,10 @@ skills.command("add").description("install a skill: an id or handle from find do
|
|
|
27847
28034
|
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) => {
|
|
27848
28035
|
await run(cmdUse(idOrName, opts));
|
|
27849
28036
|
});
|
|
28037
|
+
skills.command("submit").description("submit the current version of one of your skills (.agents/skills/<name>/ in this space) to the hub and wait for the result; it goes live for others only after review").argument("<name>", "skill directory name under .agents/skills/").option("--wait <seconds>", "how long to wait for the server's result", "60").option("--json", "compact JSON output (for agents)").action(async (name, opts) => {
|
|
28038
|
+
const wait = Number(opts.wait);
|
|
28039
|
+
await run(cmdSubmit(name, { json: !!opts.json, wait: Number.isFinite(wait) && wait >= 0 ? wait : 60 }));
|
|
28040
|
+
});
|
|
27850
28041
|
skills.command("list").alias("ls").description("list local skills (installed / pointer / your own) and check installed ones for updates").option("--dir <dir>", "skills directory", ".agents/skills").option("--offline", "do not check for updates online").option("--json", "compact JSON output (for agents)").action(async (opts) => {
|
|
27851
28042
|
await run(cmdList({ dir: opts.dir, offline: !!opts.offline, json: !!opts.json }));
|
|
27852
28043
|
});
|