@neta-art/neta-search-cli 0.2.8 → 0.2.14

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
@@ -5,13 +5,15 @@ Command-line client for **neta-search**, the skill registry of the Cohub / Neta
5
5
  ```bash
6
6
  npx -y @neta-art/neta-search-cli@0.2 skills find "generate video" # search (Chinese or English)
7
7
  npx -y @neta-art/neta-search-cli@0.2 skills find "browser automation" --json
8
- npx -y @neta-art/neta-search-cli@0.2 skills use <id> # pull into a temp dir, no files written
8
+ npx -y @neta-art/neta-search-cli@0.2 skills use <id> # print the SKILL.md (pulled into a temp dir; no project files written)
9
9
  npx -y @neta-art/neta-search-cli@0.2 skills add <id> # install into .agents/skills/
10
10
  npx -y @neta-art/neta-search-cli@0.2 skills init <name> # scaffold your own skill
11
11
  ```
12
12
 
13
13
  `<id>` comes from `find` results, e.g. `cohub:<spaceId>/<name>`.
14
14
 
15
+ `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.
16
+
15
17
  Inside a Cohub sandbox the CLI picks up the sandbox identity automatically; elsewhere it runs anonymously.
16
18
 
17
19
  Single file, no runtime dependencies. Node.js 20+.
package/neta-search.mjs CHANGED
@@ -3247,13 +3247,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
3247
3247
  contextOptions = void 0;
3248
3248
  }
3249
3249
  const outputContext = this._getOutputContext(contextOptions);
3250
- const eventContext = {
3250
+ const eventContext2 = {
3251
3251
  error: outputContext.error,
3252
3252
  write: outputContext.write,
3253
3253
  command: this
3254
3254
  };
3255
- this._getCommandAndAncestors().reverse().forEach((command) => command.emit("beforeAllHelp", eventContext));
3256
- this.emit("beforeHelp", eventContext);
3255
+ this._getCommandAndAncestors().reverse().forEach((command) => command.emit("beforeAllHelp", eventContext2));
3256
+ this.emit("beforeHelp", eventContext2);
3257
3257
  let helpInformation = this.helpInformation({ error: outputContext.error });
3258
3258
  if (deprecatedCallback) {
3259
3259
  helpInformation = deprecatedCallback(helpInformation);
@@ -3265,9 +3265,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3265
3265
  if (this._getHelpOption()?.long) {
3266
3266
  this.emit(this._getHelpOption().long);
3267
3267
  }
3268
- this.emit("afterHelp", eventContext);
3268
+ this.emit("afterHelp", eventContext2);
3269
3269
  this._getCommandAndAncestors().forEach(
3270
- (command) => command.emit("afterAllHelp", eventContext)
3270
+ (command) => command.emit("afterAllHelp", eventContext2)
3271
3271
  );
3272
3272
  }
3273
3273
  /**
@@ -26912,10 +26912,18 @@ function detectEnv() {
26912
26912
  const spaceId = process.env["COHUB_SPACE_ID"] ?? "";
26913
26913
  const apiBase = process.env["NETASEARCH_API"] ?? // sandbox 内(平台注入 COHUB_*):默认 prod Service——真实用户的事件该写
26914
26914
  // prod 账本;否则 prod sandbox 的事件会全部写进 dev 账本(review #76 抓过的
26915
- // 隐患)。sandbox 外(本地/验收):默认 dev 公网地址,NETASEARCH_API 可覆盖。
26915
+ // 隐患)。sandbox 外:默认 prod 公网地址(2026-10-09 起;此前是 dev,公网 npm 版发布前改),
26916
+ // 本地验收用 NETASEARCH_API 指 dev。
26916
26917
  // 不依赖平台注入 NETASEARCH_API——COHUB_SPACE_ID 就是平台已给的信号。
26917
- (spaceId !== "" ? "http://neta-search-api.recsys.svc.cluster.local" : "https://dev-search.neta.art");
26918
- return { apiBase, actorId, spaceId, anonymous: actorId === "" || spaceId === "" };
26918
+ (spaceId !== "" ? "http://neta-search-api.recsys.svc.cluster.local" : "https://search.neta.art");
26919
+ return {
26920
+ apiBase,
26921
+ actorId,
26922
+ spaceId,
26923
+ sessionId: process.env["COHUB_SESSION_ID"] ?? "",
26924
+ turnId: process.env["COHUB_TURN_ID"] ?? "",
26925
+ anonymous: actorId === "" || spaceId === ""
26926
+ };
26919
26927
  }
26920
26928
  function searchClient(env) {
26921
26929
  const transport = createConnectTransport({ baseUrl: env.apiBase, httpVersion: "1.1" });
@@ -26925,12 +26933,27 @@ function feedbackClient(env) {
26925
26933
  const transport = createConnectTransport({ baseUrl: env.apiBase, httpVersion: "1.1" });
26926
26934
  return createClient(SkillFeedbackService, transport);
26927
26935
  }
26928
- async function reportSkillEvent(env, event, skillId, digest, skillName) {
26936
+ function eventContext(env, extra = {}) {
26937
+ const ctx = { source: "cli", ...extra };
26938
+ if (env.sessionId) ctx["session_id"] = env.sessionId;
26939
+ if (env.turnId) ctx["turn_id"] = env.turnId;
26940
+ return ctx;
26941
+ }
26942
+ async function reportSkillEvent(env, event, skillId, digest, skillName, extra = {}) {
26929
26943
  if (env.anonymous) return false;
26930
26944
  try {
26931
26945
  await feedbackClient(env).reportSkillEvents({
26932
26946
  events: [
26933
- { event, skillId, skillName, contentHash: digest, actorId: env.actorId, spaceId: env.spaceId, anonymous: false }
26947
+ {
26948
+ event,
26949
+ skillId,
26950
+ skillName,
26951
+ contentHash: digest,
26952
+ actorId: env.actorId,
26953
+ spaceId: env.spaceId,
26954
+ anonymous: false,
26955
+ context: eventContext(env, extra)
26956
+ }
26934
26957
  ]
26935
26958
  });
26936
26959
  return true;
@@ -27041,21 +27064,29 @@ var ArchiveNotFound = class extends Error {
27041
27064
  };
27042
27065
  async function fetchArchive(env, id) {
27043
27066
  const url = `${env.apiBase}${ARCHIVE_PATH}?id=${encodeURIComponent(id)}`;
27067
+ const res = await fetch(url, { redirect: "manual", signal: AbortSignal.timeout(TIMEOUT_MS) });
27068
+ if (res.status === 404) throw new ArchiveNotFound(`${id} \u4E0D\u5B58\u5728\uFF0C\u6216\u8FD8\u6CA1\u6709\u4E0A\u67B6\u7684\u7248\u672C`);
27069
+ const location = res.headers.get("location");
27070
+ if (res.status < 300 || res.status >= 400 || !location) throw new Error(`\u4E0B\u8F7D ${id} \u5931\u8D25\uFF1AHTTP ${res.status}`);
27071
+ const digest = res.headers.get("x-skill-digest") ?? "";
27072
+ const name = res.headers.get("x-skill-name") ?? id.split("/").pop();
27073
+ if (!digest) throw new Error(`\u4E0B\u8F7D ${id} \u5931\u8D25\uFF1A\u670D\u52A1\u7AEF\u6CA1\u6709\u7ED9\u51FA\u7248\u672C\uFF08x-skill-digest\uFF09`);
27074
+ const body = await download(new URL(location, url).toString(), id);
27075
+ const files = untarGz(body);
27076
+ const got = digestOf(new Map(files.map((f) => [f.path, f.data])));
27077
+ if (got !== digest) throw new Error(`\u4E0B\u8F7D\u7684 ${id} \u5185\u5BB9\u6821\u9A8C\u5931\u8D25\uFF08\u671F\u671B ${digest}\uFF0C\u5B9E\u9645 ${got}\uFF09`);
27078
+ return { id, name, digest, files };
27079
+ }
27080
+ async function download(url, id) {
27044
27081
  for (let attempt = 1; ; attempt++) {
27045
27082
  const res = await fetch(url, { signal: AbortSignal.timeout(TIMEOUT_MS) });
27046
- if (res.status === 404) throw new ArchiveNotFound(`${id} \u4E0D\u5B58\u5728\uFF0C\u6216\u4F5C\u8005\u8FD8\u6CA1\u6709\u53EF\u5206\u4EAB\u7684\u7248\u672C`);
27047
- if (res.status === 503 && attempt < MAX_TRIES) {
27048
- const wait = Number(res.headers.get("retry-after") ?? "2") || 2;
27049
- await new Promise((r) => setTimeout(r, wait * 1e3));
27083
+ if (res.status === 202 && attempt < MAX_TRIES) {
27084
+ const body = await res.json().catch(() => ({}));
27085
+ await new Promise((r) => setTimeout(r, Math.min(Math.max(body.retryAfterMs ?? 2e3, 250), 5e3)));
27050
27086
  continue;
27051
27087
  }
27052
- if (!res.ok) throw new Error(`\u4E0B\u8F7D ${id} \u5931\u8D25\uFF1AHTTP ${res.status}`);
27053
- const digest = res.headers.get("x-skill-digest") ?? "";
27054
- const name = res.headers.get("x-skill-name") ?? id.split("/").pop();
27055
- const files = untarGz(Buffer.from(await res.arrayBuffer()));
27056
- const got = digestOf(new Map(files.map((f) => [f.path, f.data])));
27057
- if (!digest || got !== digest) throw new Error(`\u4E0B\u8F7D\u7684 ${id} \u5185\u5BB9\u6821\u9A8C\u5931\u8D25\uFF08\u671F\u671B ${digest}\uFF0C\u5B9E\u9645 ${got}\uFF09`);
27058
- return { id, name, digest, files };
27088
+ if (!res.ok) throw new Error(`\u4E0B\u8F7D ${id} \u5931\u8D25\uFF1Aregistry \u8FD4\u56DE HTTP ${res.status}`);
27089
+ return Buffer.from(await res.arrayBuffer());
27059
27090
  }
27060
27091
  }
27061
27092
  function untarGz(gz) {
@@ -27156,6 +27187,23 @@ function writeSkill(target, files, source) {
27156
27187
  renameSync(staging, target);
27157
27188
  if (hadOld) rmSync(trash, { recursive: true, force: true });
27158
27189
  }
27190
+ var POINTER_RE = /^\s*neta-skill-id:\s*["']?(cohub:[^"'\s]+)["']?\s*$/m;
27191
+ function frontmatterOf(raw) {
27192
+ if (!raw.startsWith("---\n")) return "";
27193
+ const end = raw.indexOf("\n---", 4);
27194
+ return end < 0 ? "" : raw.slice(4, end);
27195
+ }
27196
+ function pointerTargetOf(raw) {
27197
+ const m = POINTER_RE.exec(frontmatterOf(raw));
27198
+ return m ? m[1] : null;
27199
+ }
27200
+ function pointerTarget(dir) {
27201
+ try {
27202
+ return pointerTargetOf(readFileSync2(join2(dir, "SKILL.md"), "utf8"));
27203
+ } catch {
27204
+ return null;
27205
+ }
27206
+ }
27159
27207
  function sharedOptOut(dir) {
27160
27208
  try {
27161
27209
  const raw = readFileSync2(join2(dir, "SKILL.md"), "utf8");
@@ -27227,10 +27275,23 @@ async function graduate(env, name, target, opts) {
27227
27275
  }
27228
27276
 
27229
27277
  // cli/src/skills/use.ts
27230
- import { existsSync as existsSync3, readdirSync as readdirSync2 } from "node:fs";
27278
+ import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "node:fs";
27279
+ import { randomUUID } from "node:crypto";
27231
27280
  import { join as join4 } from "node:path";
27281
+ var FETCH_ROOT = process.env["NETASEARCH_FETCH_ROOT"] ?? join4("/tmp", "neta-skills");
27282
+ var HANDLE_RE = /^(@[a-z0-9_.-]{1,64}|[a-z0-9][a-z0-9-]{0,31})\/[a-z0-9][a-z0-9-]{0,63}$/i;
27283
+ async function resolveHandle(env, input) {
27284
+ const r = await fetch(`${env.apiBase}/v1/skills/resolve?handle=${encodeURIComponent(input)}`, { signal: AbortSignal.timeout(5e3) });
27285
+ if (!r.ok) return null;
27286
+ const out = await r.json();
27287
+ return typeof out.id === "string" && parseId(out.id) ? out.id : null;
27288
+ }
27232
27289
  async function cmdUse(idOrName, opts) {
27233
27290
  const env = detectEnv();
27291
+ if (!parseId(idOrName) && HANDLE_RE.test(idOrName)) {
27292
+ const resolved = await resolveHandle(env, idOrName).catch(() => null);
27293
+ if (resolved) idOrName = resolved;
27294
+ }
27234
27295
  const parsed = parseId(idOrName);
27235
27296
  const name = parsed?.name ?? idOrName;
27236
27297
  const mine = !parsed || parsed.spaceId === env.spaceId;
@@ -27238,6 +27299,8 @@ async function cmdUse(idOrName, opts) {
27238
27299
  let where = "local";
27239
27300
  let reportId = null;
27240
27301
  let notice = null;
27302
+ let fetchId = parsed && !mine ? idOrName : null;
27303
+ let via = opts.via ? opts.via.slice(0, 32) : null;
27241
27304
  const installed = join4(SKILLS_DIR, name);
27242
27305
  const draft = join4(DRAFTS_DIR, name);
27243
27306
  if (skillDirExists(installed)) {
@@ -27263,11 +27326,32 @@ async function cmdUse(idOrName, opts) {
27263
27326
  where = "mod";
27264
27327
  }
27265
27328
  }
27266
- if (!path && parsed && !mine) {
27329
+ if (path) {
27330
+ const target = pointerTarget(path);
27331
+ if (target !== null) {
27332
+ if (!parseId(target)) {
27333
+ console.error(`${join4(path, "SKILL.md")} \u662F\u6307\u9488\uFF0C\u4F46\u5B83\u7684 neta-skill-id \u4E0D\u662F\u5408\u6CD5 id\uFF1A${target}`);
27334
+ return 1;
27335
+ }
27336
+ via = "pointer";
27337
+ fetchId = target;
27338
+ path = null;
27339
+ reportId = null;
27340
+ notice = null;
27341
+ }
27342
+ }
27343
+ if (!path && fetchId) {
27267
27344
  try {
27268
- const a = await fetchArchive(env, idOrName);
27269
- path = join4("/tmp", "neta-skills", `${name}-${a.digest.slice(7, 19)}`);
27270
- if (!existsSync3(join4(path, "SKILL.md"))) writeSkill(path, a.files, { id: a.id, digest: a.digest });
27345
+ const a = await fetchArchive(env, fetchId);
27346
+ const skillMd2 = a.files.find((f) => f.path === "SKILL.md");
27347
+ if (skillMd2 && pointerTargetOf(skillMd2.data.toString("utf8")) !== null) {
27348
+ console.error(`${fetchId} \u4E0B\u8F7D\u5230\u7684\u5185\u5BB9\u8FD8\u662F\u6307\u9488\uFF0C\u505C\u5728\u8FD9\u91CC\uFF08\u4E0D\u8DDF\u7B2C\u4E8C\u5C42\uFF0C\u9632\u6B62\u7ED5\u5708\uFF09\u3002`);
27349
+ return 1;
27350
+ }
27351
+ const p = parseId(a.id);
27352
+ path = join4(FETCH_ROOT, p.spaceId, p.name);
27353
+ const have = readSource(path);
27354
+ if (!skillDirExists(path) || have?.digest !== a.digest) writeSkill(path, a.files, { id: a.id, digest: a.digest });
27271
27355
  reportId = a.id;
27272
27356
  where = "fetched";
27273
27357
  } catch (e) {
@@ -27279,14 +27363,21 @@ async function cmdUse(idOrName, opts) {
27279
27363
  console.error(`skill "${idOrName}" \u672C\u5730\u6CA1\u6709\u2014\u2014\u5148 neta-search skills find "${name}" \u62FF\u5230 id\u3002`);
27280
27364
  return 1;
27281
27365
  }
27282
- const reported = reportId ? await reportSkillEvent(env, "use", reportId, skillDirDigest(path), name) : false;
27283
- if (opts.json) console.log(JSON.stringify({ skill: reportId ?? name, path, where }));
27284
- else {
27366
+ const useId = randomUUID();
27367
+ const extra = { use_id: useId };
27368
+ if (via) extra["via"] = via;
27369
+ const reported = reportId ? await reportSkillEvent(env, "use", reportId, skillDirDigest(path), name, extra) : false;
27370
+ const skillMd = readFileSync3(join4(path, "SKILL.md"), "utf8");
27371
+ if (opts.json) {
27372
+ console.log(JSON.stringify({ skill: reportId ?? name, path, where, use_id: reportId ? useId : void 0, skill_md: skillMd }));
27373
+ } else {
27285
27374
  const label = { local: "\u672C\u5730", mod: "\u5E73\u53F0 mod", fetched: "\u5DF2\u4E0B\u8F7D\u5230\u4E34\u65F6\u76EE\u5F55" }[where];
27286
- console.log(`${name} \u5C31\u7EEA\uFF08${label}\uFF09\uFF1A`);
27287
- console.log(` ${join4(path, "SKILL.md")}`);
27375
+ console.log(`${reportId ?? name} \u5C31\u7EEA\uFF08${label}${via === "pointer" ? "\uFF0C\u7ECF\u6307\u9488" : ""}\uFF09`);
27376
+ console.log(`skill \u76EE\u5F55\uFF1A${path}`);
27377
+ console.log(`SKILL.md \u91CC\u7684\u76F8\u5BF9\u8DEF\u5F84\u90FD\u4ECE\u8FD9\u4E2A\u76EE\u5F55\u7B97\u8D77\u3002\u4E0B\u9762\u5C31\u662F SKILL.md \u7684\u5168\u6587\uFF0C\u7167\u7740\u505A\u5373\u53EF\uFF08\u4E0D\u7528\u518D\u8BFB\u4E00\u6B21\u6587\u4EF6\uFF09\u3002`);
27288
27378
  console.log(`
27289
- \u7528 agent \u8BFB\u4E0A\u9762\u7684\u8DEF\u5F84\u5373\u53EF\uFF08use \u4E0D\u5B89\u88C5\u3001\u4E0D\u6539\u9879\u76EE\u6587\u4EF6\uFF09\u3002`);
27379
+ ----- SKILL.md -----
27380
+ ${skillMd}${skillMd.endsWith("\n") ? "" : "\n"}----- end -----`);
27290
27381
  }
27291
27382
  if (notice && reportId && !env.anonymous) shareNotice(reportId, path, notice === "draft", reported);
27292
27383
  return 0;
@@ -27377,7 +27468,7 @@ skills.command("find").description("\u627E skill\uFF08\u751F\u6001\u8D26\u672C+\
27377
27468
  skills.command("add").description("\u88C5 skill\uFF08find \u7ED3\u679C\u7684 id\uFF1A\u4E0B\u8F7D\u4F5C\u8005\u7528\u8FC7\u7684\u90A3\u4E00\u7248\uFF1B\u6216\u81EA\u5DF1\u8349\u7A3F\u533A\u7684\u540D\u5B57\uFF1A\u79FB\u5230 .agents/skills/\uFF09").argument("<id-or-name>", "find \u8FD4\u56DE\u7684 skill id\uFF0C\u6216\u81EA\u5DF1\u8349\u7A3F\u533A\u7684 skill \u540D").option("--dir <dir>", "\u5B89\u88C5\u76EE\u5F55\uFF08\u9ED8\u8BA4 .agents/skills/\uFF09", ".agents/skills").option("--json", "\u7D27\u51D1 JSON \u8F93\u51FA\uFF08agent \u6D88\u8D39\uFF09").action(async (idOrName, opts) => {
27378
27469
  await run(cmdAdd(idOrName, opts));
27379
27470
  });
27380
- skills.command("use").description("\u7528 skill\uFF08\u4E0D\u88C5\uFF1A\u672C\u5730\u6709\u2192\u7ED9\u8DEF\u5F84\uFF1B\u672C\u5730\u6CA1\u6709\u4E14\u662F id\u2192\u4E0B\u8F7D\u5230\u4E34\u65F6\u76EE\u5F55+\u7ED9\u8DEF\u5F84\uFF09").argument("<id-or-name>", "skill id \u6216 name").option("--json", "\u7D27\u51D1 JSON \u8F93\u51FA\uFF08agent \u6D88\u8D39\uFF09").action(async (idOrName, opts) => {
27471
+ skills.command("use").description("\u7528 skill\uFF08\u4E0D\u88C5\uFF1A\u6253\u5370 SKILL.md \u5168\u6587\u548C skill \u76EE\u5F55\uFF1B\u672C\u5730\u6709\u2192\u7528\u672C\u5730\u7684\uFF1B\u672C\u5730\u662F\u6307\u9488\u6216\u6CA1\u6709\u2192\u6309 id \u4E0B\u8F7D\u5230\u4E34\u65F6\u76EE\u5F55\uFF09").argument("<id-or-name>", "skill id \u6216 name").option("--json", "\u7D27\u51D1 JSON \u8F93\u51FA\uFF08agent \u6D88\u8D39\uFF09").option("--via <channel>", "\u4ECE\u54EA\u4E2A\u6E20\u9053\u6765\u7684\uFF08\u8BB0\u8FDB\u4F7F\u7528\u8BB0\u5F55\uFF1Bhub mod \u7684\u6307\u9488\u5199 pointer\uFF09").action(async (idOrName, opts) => {
27381
27472
  await run(cmdUse(idOrName, opts));
27382
27473
  });
27383
27474
  skills.command("init").description("\u521B\u5EFA skill \u811A\u624B\u67B6\uFF08SKILL.md + frontmatter \u89C4\u8303\uFF09").argument("[name]", "skill \u540D\uFF08kebab-case\uFF09").option("--here", "\u5728\u5F53\u524D\u76EE\u5F55\u751F\u6210\uFF08\u9ED8\u8BA4\u751F\u6210\u5230 ./<name>/ \u5B50\u76EE\u5F55\uFF09").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.8",
3
+ "version": "0.2.14",
4
4
  "description": "neta-search CLI — find, try and install skills from the Cohub / Neta skill registry",
5
5
  "type": "module",
6
6
  "bin": {