@neta-art/neta-search-cli 0.2.43 → 0.2.47
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 +7 -1
- package/neta-search.mjs +841 -104
- package/package.json +1 -1
package/neta-search.mjs
CHANGED
|
@@ -2660,7 +2660,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2660
2660
|
const negativeNumberArg = (arg) => {
|
|
2661
2661
|
if (!/^-(\d+|\d*\.\d+)(e[+-]?\d+)?$/.test(arg)) return false;
|
|
2662
2662
|
return !this._getCommandAndAncestors().some(
|
|
2663
|
-
(cmd) => cmd.options.map((opt) => opt.short).some((
|
|
2663
|
+
(cmd) => cmd.options.map((opt) => opt.short).some((short3) => /^-\d$/.test(short3))
|
|
2664
2664
|
);
|
|
2665
2665
|
};
|
|
2666
2666
|
let activeVariadicOption = null;
|
|
@@ -7021,8 +7021,8 @@ var require_util2 = __commonJS({
|
|
|
7021
7021
|
function createDeferredPromise() {
|
|
7022
7022
|
let res;
|
|
7023
7023
|
let rej;
|
|
7024
|
-
const promise = new Promise((
|
|
7025
|
-
res =
|
|
7024
|
+
const promise = new Promise((resolve3, reject) => {
|
|
7025
|
+
res = resolve3;
|
|
7026
7026
|
rej = reject;
|
|
7027
7027
|
});
|
|
7028
7028
|
return { promise, resolve: res, reject: rej };
|
|
@@ -8526,8 +8526,8 @@ Content-Type: ${value.type || "application/octet-stream"}\r
|
|
|
8526
8526
|
});
|
|
8527
8527
|
}
|
|
8528
8528
|
});
|
|
8529
|
-
const busboyResolve = new Promise((
|
|
8530
|
-
busboy.on("finish",
|
|
8529
|
+
const busboyResolve = new Promise((resolve3, reject) => {
|
|
8530
|
+
busboy.on("finish", resolve3);
|
|
8531
8531
|
busboy.on("error", (err) => reject(new TypeError(err)));
|
|
8532
8532
|
});
|
|
8533
8533
|
if (this.body !== null) for await (const chunk of consumeBody(this[kState].body)) busboy.write(chunk);
|
|
@@ -9061,9 +9061,9 @@ var require_dispatcher_base = __commonJS({
|
|
|
9061
9061
|
}
|
|
9062
9062
|
close(callback) {
|
|
9063
9063
|
if (callback === void 0) {
|
|
9064
|
-
return new Promise((
|
|
9064
|
+
return new Promise((resolve3, reject) => {
|
|
9065
9065
|
this.close((err, data) => {
|
|
9066
|
-
return err ? reject(err) :
|
|
9066
|
+
return err ? reject(err) : resolve3(data);
|
|
9067
9067
|
});
|
|
9068
9068
|
});
|
|
9069
9069
|
}
|
|
@@ -9101,12 +9101,12 @@ var require_dispatcher_base = __commonJS({
|
|
|
9101
9101
|
err = null;
|
|
9102
9102
|
}
|
|
9103
9103
|
if (callback === void 0) {
|
|
9104
|
-
return new Promise((
|
|
9104
|
+
return new Promise((resolve3, reject) => {
|
|
9105
9105
|
this.destroy(err, (err2, data) => {
|
|
9106
9106
|
return err2 ? (
|
|
9107
9107
|
/* istanbul ignore next: should never error */
|
|
9108
9108
|
reject(err2)
|
|
9109
|
-
) :
|
|
9109
|
+
) : resolve3(data);
|
|
9110
9110
|
});
|
|
9111
9111
|
});
|
|
9112
9112
|
}
|
|
@@ -10166,16 +10166,16 @@ var require_client = __commonJS({
|
|
|
10166
10166
|
return this[kNeedDrain] < 2;
|
|
10167
10167
|
}
|
|
10168
10168
|
async [kClose]() {
|
|
10169
|
-
return new Promise((
|
|
10169
|
+
return new Promise((resolve3) => {
|
|
10170
10170
|
if (!this[kSize]) {
|
|
10171
|
-
|
|
10171
|
+
resolve3(null);
|
|
10172
10172
|
} else {
|
|
10173
|
-
this[kClosedResolve] =
|
|
10173
|
+
this[kClosedResolve] = resolve3;
|
|
10174
10174
|
}
|
|
10175
10175
|
});
|
|
10176
10176
|
}
|
|
10177
10177
|
async [kDestroy](err) {
|
|
10178
|
-
return new Promise((
|
|
10178
|
+
return new Promise((resolve3) => {
|
|
10179
10179
|
const requests = this[kQueue].splice(this[kPendingIdx]);
|
|
10180
10180
|
for (let i = 0; i < requests.length; i++) {
|
|
10181
10181
|
const request3 = requests[i];
|
|
@@ -10186,7 +10186,7 @@ var require_client = __commonJS({
|
|
|
10186
10186
|
this[kClosedResolve]();
|
|
10187
10187
|
this[kClosedResolve] = null;
|
|
10188
10188
|
}
|
|
10189
|
-
|
|
10189
|
+
resolve3();
|
|
10190
10190
|
};
|
|
10191
10191
|
if (this[kHTTP2Session] != null) {
|
|
10192
10192
|
util.destroy(this[kHTTP2Session], err);
|
|
@@ -10766,7 +10766,7 @@ var require_client = __commonJS({
|
|
|
10766
10766
|
});
|
|
10767
10767
|
}
|
|
10768
10768
|
try {
|
|
10769
|
-
const socket = await new Promise((
|
|
10769
|
+
const socket = await new Promise((resolve3, reject) => {
|
|
10770
10770
|
client[kConnector]({
|
|
10771
10771
|
host,
|
|
10772
10772
|
hostname,
|
|
@@ -10778,7 +10778,7 @@ var require_client = __commonJS({
|
|
|
10778
10778
|
if (err) {
|
|
10779
10779
|
reject(err);
|
|
10780
10780
|
} else {
|
|
10781
|
-
|
|
10781
|
+
resolve3(socket2);
|
|
10782
10782
|
}
|
|
10783
10783
|
});
|
|
10784
10784
|
});
|
|
@@ -11402,12 +11402,12 @@ upgrade: ${upgrade}\r
|
|
|
11402
11402
|
cb();
|
|
11403
11403
|
}
|
|
11404
11404
|
}
|
|
11405
|
-
const waitForDrain = () => new Promise((
|
|
11405
|
+
const waitForDrain = () => new Promise((resolve3, reject) => {
|
|
11406
11406
|
assert2(callback === null);
|
|
11407
11407
|
if (socket[kError]) {
|
|
11408
11408
|
reject(socket[kError]);
|
|
11409
11409
|
} else {
|
|
11410
|
-
callback =
|
|
11410
|
+
callback = resolve3;
|
|
11411
11411
|
}
|
|
11412
11412
|
});
|
|
11413
11413
|
if (client[kHTTPConnVersion] === "h2") {
|
|
@@ -11752,8 +11752,8 @@ var require_pool_base = __commonJS({
|
|
|
11752
11752
|
if (this[kQueue].isEmpty()) {
|
|
11753
11753
|
return Promise.all(this[kClients].map((c) => c.close()));
|
|
11754
11754
|
} else {
|
|
11755
|
-
return new Promise((
|
|
11756
|
-
this[kClosedResolve] =
|
|
11755
|
+
return new Promise((resolve3) => {
|
|
11756
|
+
this[kClosedResolve] = resolve3;
|
|
11757
11757
|
});
|
|
11758
11758
|
}
|
|
11759
11759
|
}
|
|
@@ -12331,7 +12331,7 @@ var require_readable = __commonJS({
|
|
|
12331
12331
|
if (this.closed) {
|
|
12332
12332
|
return Promise.resolve(null);
|
|
12333
12333
|
}
|
|
12334
|
-
return new Promise((
|
|
12334
|
+
return new Promise((resolve3, reject) => {
|
|
12335
12335
|
const signalListenerCleanup = signal ? util.addAbortListener(signal, () => {
|
|
12336
12336
|
this.destroy();
|
|
12337
12337
|
}) : noop;
|
|
@@ -12340,7 +12340,7 @@ var require_readable = __commonJS({
|
|
|
12340
12340
|
if (signal && signal.aborted) {
|
|
12341
12341
|
reject(signal.reason || Object.assign(new Error("The operation was aborted"), { name: "AbortError" }));
|
|
12342
12342
|
} else {
|
|
12343
|
-
|
|
12343
|
+
resolve3(null);
|
|
12344
12344
|
}
|
|
12345
12345
|
}).on("error", noop).on("data", function(chunk) {
|
|
12346
12346
|
limit -= chunk.length;
|
|
@@ -12362,11 +12362,11 @@ var require_readable = __commonJS({
|
|
|
12362
12362
|
throw new TypeError("unusable");
|
|
12363
12363
|
}
|
|
12364
12364
|
assert2(!stream[kConsume]);
|
|
12365
|
-
return new Promise((
|
|
12365
|
+
return new Promise((resolve3, reject) => {
|
|
12366
12366
|
stream[kConsume] = {
|
|
12367
12367
|
type,
|
|
12368
12368
|
stream,
|
|
12369
|
-
resolve:
|
|
12369
|
+
resolve: resolve3,
|
|
12370
12370
|
reject,
|
|
12371
12371
|
length: 0,
|
|
12372
12372
|
body: []
|
|
@@ -12401,12 +12401,12 @@ var require_readable = __commonJS({
|
|
|
12401
12401
|
}
|
|
12402
12402
|
}
|
|
12403
12403
|
function consumeEnd(consume2) {
|
|
12404
|
-
const { type, body, resolve:
|
|
12404
|
+
const { type, body, resolve: resolve3, stream, length } = consume2;
|
|
12405
12405
|
try {
|
|
12406
12406
|
if (type === "text") {
|
|
12407
|
-
|
|
12407
|
+
resolve3(toUSVString(Buffer.concat(body)));
|
|
12408
12408
|
} else if (type === "json") {
|
|
12409
|
-
|
|
12409
|
+
resolve3(JSON.parse(Buffer.concat(body)));
|
|
12410
12410
|
} else if (type === "arrayBuffer") {
|
|
12411
12411
|
const dst = new Uint8Array(length);
|
|
12412
12412
|
let pos = 0;
|
|
@@ -12414,12 +12414,12 @@ var require_readable = __commonJS({
|
|
|
12414
12414
|
dst.set(buf, pos);
|
|
12415
12415
|
pos += buf.byteLength;
|
|
12416
12416
|
}
|
|
12417
|
-
|
|
12417
|
+
resolve3(dst.buffer);
|
|
12418
12418
|
} else if (type === "blob") {
|
|
12419
12419
|
if (!Blob2) {
|
|
12420
12420
|
Blob2 = __require("buffer").Blob;
|
|
12421
12421
|
}
|
|
12422
|
-
|
|
12422
|
+
resolve3(new Blob2(body, { type: stream[kContentType] }));
|
|
12423
12423
|
}
|
|
12424
12424
|
consumeFinish(consume2);
|
|
12425
12425
|
} catch (err) {
|
|
@@ -12674,9 +12674,9 @@ var require_api_request = __commonJS({
|
|
|
12674
12674
|
};
|
|
12675
12675
|
function request3(opts, callback) {
|
|
12676
12676
|
if (callback === void 0) {
|
|
12677
|
-
return new Promise((
|
|
12677
|
+
return new Promise((resolve3, reject) => {
|
|
12678
12678
|
request3.call(this, opts, (err, data) => {
|
|
12679
|
-
return err ? reject(err) :
|
|
12679
|
+
return err ? reject(err) : resolve3(data);
|
|
12680
12680
|
});
|
|
12681
12681
|
});
|
|
12682
12682
|
}
|
|
@@ -12849,9 +12849,9 @@ var require_api_stream = __commonJS({
|
|
|
12849
12849
|
};
|
|
12850
12850
|
function stream(opts, factory, callback) {
|
|
12851
12851
|
if (callback === void 0) {
|
|
12852
|
-
return new Promise((
|
|
12852
|
+
return new Promise((resolve3, reject) => {
|
|
12853
12853
|
stream.call(this, opts, factory, (err, data) => {
|
|
12854
|
-
return err ? reject(err) :
|
|
12854
|
+
return err ? reject(err) : resolve3(data);
|
|
12855
12855
|
});
|
|
12856
12856
|
});
|
|
12857
12857
|
}
|
|
@@ -13132,9 +13132,9 @@ var require_api_upgrade = __commonJS({
|
|
|
13132
13132
|
};
|
|
13133
13133
|
function upgrade(opts, callback) {
|
|
13134
13134
|
if (callback === void 0) {
|
|
13135
|
-
return new Promise((
|
|
13135
|
+
return new Promise((resolve3, reject) => {
|
|
13136
13136
|
upgrade.call(this, opts, (err, data) => {
|
|
13137
|
-
return err ? reject(err) :
|
|
13137
|
+
return err ? reject(err) : resolve3(data);
|
|
13138
13138
|
});
|
|
13139
13139
|
});
|
|
13140
13140
|
}
|
|
@@ -13223,9 +13223,9 @@ var require_api_connect = __commonJS({
|
|
|
13223
13223
|
};
|
|
13224
13224
|
function connect3(opts, callback) {
|
|
13225
13225
|
if (callback === void 0) {
|
|
13226
|
-
return new Promise((
|
|
13226
|
+
return new Promise((resolve3, reject) => {
|
|
13227
13227
|
connect3.call(this, opts, (err, data) => {
|
|
13228
|
-
return err ? reject(err) :
|
|
13228
|
+
return err ? reject(err) : resolve3(data);
|
|
13229
13229
|
});
|
|
13230
13230
|
});
|
|
13231
13231
|
}
|
|
@@ -16847,7 +16847,7 @@ var require_fetch = __commonJS({
|
|
|
16847
16847
|
async function dispatch({ body }) {
|
|
16848
16848
|
const url = requestCurrentURL(request3);
|
|
16849
16849
|
const agent = fetchParams.controller.dispatcher;
|
|
16850
|
-
return new Promise((
|
|
16850
|
+
return new Promise((resolve3, reject) => agent.dispatch(
|
|
16851
16851
|
{
|
|
16852
16852
|
path: url.pathname + url.search,
|
|
16853
16853
|
origin: url.origin,
|
|
@@ -16923,7 +16923,7 @@ var require_fetch = __commonJS({
|
|
|
16923
16923
|
}
|
|
16924
16924
|
}
|
|
16925
16925
|
}
|
|
16926
|
-
|
|
16926
|
+
resolve3({
|
|
16927
16927
|
status,
|
|
16928
16928
|
statusText,
|
|
16929
16929
|
headersList: headers[kHeadersList],
|
|
@@ -16966,7 +16966,7 @@ var require_fetch = __commonJS({
|
|
|
16966
16966
|
const val = headersList[n + 1].toString("latin1");
|
|
16967
16967
|
headers[kHeadersList].append(key, val);
|
|
16968
16968
|
}
|
|
16969
|
-
|
|
16969
|
+
resolve3({
|
|
16970
16970
|
status,
|
|
16971
16971
|
statusText: STATUS_CODES[status],
|
|
16972
16972
|
headersList: headers[kHeadersList],
|
|
@@ -24863,14 +24863,14 @@ var __asyncValues = function(o) {
|
|
|
24863
24863
|
}, i);
|
|
24864
24864
|
function verb(n) {
|
|
24865
24865
|
i[n] = o[n] && function(v) {
|
|
24866
|
-
return new Promise(function(
|
|
24867
|
-
v = o[n](v), settle(
|
|
24866
|
+
return new Promise(function(resolve3, reject) {
|
|
24867
|
+
v = o[n](v), settle(resolve3, reject, v.done, v.value);
|
|
24868
24868
|
});
|
|
24869
24869
|
};
|
|
24870
24870
|
}
|
|
24871
|
-
function settle(
|
|
24871
|
+
function settle(resolve3, reject, d, v) {
|
|
24872
24872
|
Promise.resolve(v).then(function(v2) {
|
|
24873
|
-
|
|
24873
|
+
resolve3({ value: v2, done: d });
|
|
24874
24874
|
}, reject);
|
|
24875
24875
|
}
|
|
24876
24876
|
};
|
|
@@ -25395,14 +25395,14 @@ var __asyncValues2 = function(o) {
|
|
|
25395
25395
|
}, i);
|
|
25396
25396
|
function verb(n) {
|
|
25397
25397
|
i[n] = o[n] && function(v) {
|
|
25398
|
-
return new Promise(function(
|
|
25399
|
-
v = o[n](v), settle(
|
|
25398
|
+
return new Promise(function(resolve3, reject) {
|
|
25399
|
+
v = o[n](v), settle(resolve3, reject, v.done, v.value);
|
|
25400
25400
|
});
|
|
25401
25401
|
};
|
|
25402
25402
|
}
|
|
25403
|
-
function settle(
|
|
25403
|
+
function settle(resolve3, reject, d, v) {
|
|
25404
25404
|
Promise.resolve(v).then(function(v2) {
|
|
25405
|
-
|
|
25405
|
+
resolve3({ value: v2, done: d });
|
|
25406
25406
|
}, reject);
|
|
25407
25407
|
}
|
|
25408
25408
|
};
|
|
@@ -26137,14 +26137,14 @@ var __asyncValues3 = function(o) {
|
|
|
26137
26137
|
}, i);
|
|
26138
26138
|
function verb(n) {
|
|
26139
26139
|
i[n] = o[n] && function(v) {
|
|
26140
|
-
return new Promise(function(
|
|
26141
|
-
v = o[n](v), settle(
|
|
26140
|
+
return new Promise(function(resolve3, reject) {
|
|
26141
|
+
v = o[n](v), settle(resolve3, reject, v.done, v.value);
|
|
26142
26142
|
});
|
|
26143
26143
|
};
|
|
26144
26144
|
}
|
|
26145
|
-
function settle(
|
|
26145
|
+
function settle(resolve3, reject, d, v) {
|
|
26146
26146
|
Promise.resolve(v).then(function(v2) {
|
|
26147
|
-
|
|
26147
|
+
resolve3({ value: v2, done: d });
|
|
26148
26148
|
}, reject);
|
|
26149
26149
|
}
|
|
26150
26150
|
};
|
|
@@ -26782,17 +26782,17 @@ function closedOrError(reason) {
|
|
|
26782
26782
|
return isCancel ? closed() : error(reason);
|
|
26783
26783
|
}
|
|
26784
26784
|
function connect2(authority, http2SessionOptions) {
|
|
26785
|
-
let
|
|
26785
|
+
let resolve3;
|
|
26786
26786
|
let reject;
|
|
26787
26787
|
const conn = new Promise((res, rej) => {
|
|
26788
|
-
|
|
26788
|
+
resolve3 = res;
|
|
26789
26789
|
reject = rej;
|
|
26790
26790
|
});
|
|
26791
26791
|
const newConn = http2.connect(authority, http2SessionOptions);
|
|
26792
26792
|
newConn.on("connect", onConnect);
|
|
26793
26793
|
newConn.on("error", onError);
|
|
26794
26794
|
function onConnect() {
|
|
26795
|
-
|
|
26795
|
+
resolve3 === null || resolve3 === void 0 ? void 0 : resolve3(newConn);
|
|
26796
26796
|
cleanup();
|
|
26797
26797
|
}
|
|
26798
26798
|
function onError(err) {
|
|
@@ -26878,13 +26878,13 @@ function ready(conn, options) {
|
|
|
26878
26878
|
},
|
|
26879
26879
|
verify() {
|
|
26880
26880
|
conn.ref();
|
|
26881
|
-
return new Promise((
|
|
26881
|
+
return new Promise((resolve3) => {
|
|
26882
26882
|
commonPing(() => {
|
|
26883
26883
|
if (streamCount == 0)
|
|
26884
26884
|
conn.unref();
|
|
26885
|
-
|
|
26885
|
+
resolve3(true);
|
|
26886
26886
|
});
|
|
26887
|
-
conn.once("error", () =>
|
|
26887
|
+
conn.once("error", () => resolve3(false));
|
|
26888
26888
|
});
|
|
26889
26889
|
},
|
|
26890
26890
|
abort(reason) {
|
|
@@ -27015,7 +27015,7 @@ function createNodeHttpClient(options) {
|
|
|
27015
27015
|
function createNodeHttp1Client(httpOptions) {
|
|
27016
27016
|
return async function request3(req) {
|
|
27017
27017
|
const sentinel = createSentinel(req.signal);
|
|
27018
|
-
return new Promise((
|
|
27018
|
+
return new Promise((resolve3, reject) => {
|
|
27019
27019
|
sentinel.catch((e) => {
|
|
27020
27020
|
reject(e);
|
|
27021
27021
|
});
|
|
@@ -27026,7 +27026,7 @@ function createNodeHttp1Client(httpOptions) {
|
|
|
27026
27026
|
response.on("error", sentinel.reject);
|
|
27027
27027
|
sentinel.catch((reason) => response.destroy(connectErrorFromNodeReason(reason)));
|
|
27028
27028
|
const trailer = new Headers();
|
|
27029
|
-
|
|
27029
|
+
resolve3({
|
|
27030
27030
|
status: (_a = response.statusCode) !== null && _a !== void 0 ? _a : 0,
|
|
27031
27031
|
header: nodeHeaderToWebHeader(response.headers),
|
|
27032
27032
|
body: h1ResponseIterable(sentinel, response, trailer),
|
|
@@ -27041,7 +27041,7 @@ function createNodeHttp2Client(sessionProvider) {
|
|
|
27041
27041
|
return function request3(req) {
|
|
27042
27042
|
const sentinel = createSentinel(req.signal);
|
|
27043
27043
|
const sessionManager = sessionProvider(req.url);
|
|
27044
|
-
return new Promise((
|
|
27044
|
+
return new Promise((resolve3, reject) => {
|
|
27045
27045
|
sentinel.catch((e) => {
|
|
27046
27046
|
reject(e);
|
|
27047
27047
|
});
|
|
@@ -27055,7 +27055,7 @@ function createNodeHttp2Client(sessionProvider) {
|
|
|
27055
27055
|
body: h2ResponseIterable(sentinel, stream, sessionManager),
|
|
27056
27056
|
trailer: h2ResponseTrailer(stream)
|
|
27057
27057
|
};
|
|
27058
|
-
|
|
27058
|
+
resolve3(response);
|
|
27059
27059
|
});
|
|
27060
27060
|
});
|
|
27061
27061
|
});
|
|
@@ -27120,7 +27120,7 @@ function h2Request(sentinel, sm, url, method, headers, options, onStream) {
|
|
|
27120
27120
|
return;
|
|
27121
27121
|
}
|
|
27122
27122
|
const rstCode = reason instanceof ConnectError && reason.code == Code.Canceled ? H2Code.CANCEL : H2Code.INTERNAL_ERROR;
|
|
27123
|
-
return new Promise((
|
|
27123
|
+
return new Promise((resolve3) => stream.close(rstCode, resolve3));
|
|
27124
27124
|
});
|
|
27125
27125
|
stream.on("error", function h2StreamError(e) {
|
|
27126
27126
|
if (stream.writableEnded && unwrapNodeErrorChain(e).map(getNodeErrorProps).some((p) => p.code == "ERR_STREAM_WRITE_AFTER_END")) {
|
|
@@ -27172,11 +27172,11 @@ function h2ResponseIterable(sentinel, response, sm) {
|
|
|
27172
27172
|
}
|
|
27173
27173
|
async function sinkRequest(request3, nodeRequest, sentinel) {
|
|
27174
27174
|
if (request3.body === void 0) {
|
|
27175
|
-
await new Promise((
|
|
27175
|
+
await new Promise((resolve3) => nodeRequest.end(resolve3));
|
|
27176
27176
|
return;
|
|
27177
27177
|
}
|
|
27178
27178
|
const it = request3.body[Symbol.asyncIterator]();
|
|
27179
|
-
return new Promise((
|
|
27179
|
+
return new Promise((resolve3) => {
|
|
27180
27180
|
writeNext();
|
|
27181
27181
|
function writeNext() {
|
|
27182
27182
|
if (sentinel.isRejected()) {
|
|
@@ -27184,7 +27184,7 @@ async function sinkRequest(request3, nodeRequest, sentinel) {
|
|
|
27184
27184
|
}
|
|
27185
27185
|
it.next().then((r) => {
|
|
27186
27186
|
if (r.done === true) {
|
|
27187
|
-
nodeRequest.end(
|
|
27187
|
+
nodeRequest.end(resolve3);
|
|
27188
27188
|
return;
|
|
27189
27189
|
}
|
|
27190
27190
|
nodeRequest.write(r.value, "binary", function(e) {
|
|
@@ -27212,8 +27212,8 @@ function createSentinel(signal) {
|
|
|
27212
27212
|
let rej;
|
|
27213
27213
|
let resolved = false;
|
|
27214
27214
|
let rejected = false;
|
|
27215
|
-
const p = new Promise((
|
|
27216
|
-
res =
|
|
27215
|
+
const p = new Promise((resolve3, reject) => {
|
|
27216
|
+
res = resolve3;
|
|
27217
27217
|
rej = reject;
|
|
27218
27218
|
});
|
|
27219
27219
|
const c = {
|
|
@@ -29000,42 +29000,557 @@ metadata:
|
|
|
29000
29000
|
}
|
|
29001
29001
|
|
|
29002
29002
|
// cli/src/skills/submit.ts
|
|
29003
|
-
import { existsSync as
|
|
29004
|
-
import {
|
|
29005
|
-
|
|
29003
|
+
import { existsSync as existsSync6, lstatSync as lstatSync3, readFileSync as readFileSync6 } from "node:fs";
|
|
29004
|
+
import { join as join7 } from "node:path";
|
|
29005
|
+
|
|
29006
|
+
// cli/src/skills/limits.ts
|
|
29006
29007
|
var MAX_FILES = 1e3;
|
|
29007
29008
|
var MAX_FILE_BYTES = 20 << 20;
|
|
29008
29009
|
var MAX_TOTAL_BYTES = 100 << 20;
|
|
29009
|
-
|
|
29010
|
-
|
|
29011
|
-
|
|
29012
|
-
|
|
29013
|
-
|
|
29014
|
-
|
|
29015
|
-
|
|
29016
|
-
|
|
29017
|
-
|
|
29010
|
+
|
|
29011
|
+
// cli/src/skills/lint.ts
|
|
29012
|
+
import { existsSync as existsSync5, lstatSync as lstatSync2, readdirSync as readdirSync3, readFileSync as readFileSync5 } from "node:fs";
|
|
29013
|
+
import { basename as basename2, join as join6, posix, resolve as resolve2 } from "node:path";
|
|
29014
|
+
var CJK_RANGE = "\\u3400-\\u9fff\\uf900-\\ufaff\\u3040-\\u30ff\\uac00-\\ud7af";
|
|
29015
|
+
var CJK = new RegExp(`[${CJK_RANGE}]`);
|
|
29016
|
+
var CJK_G = new RegExp(`[${CJK_RANGE}]`, "g");
|
|
29017
|
+
var CJK_RUN4 = new RegExp(`[${CJK_RANGE}]{4,}`);
|
|
29018
|
+
var NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
29019
|
+
function resolveSkillDir(arg) {
|
|
29020
|
+
const cands = arg ? [arg, join6(SKILLS_DIR, arg), join6(DRAFTS_DIR, arg)] : ["."];
|
|
29021
|
+
for (const c of cands) if (existsSync5(join6(c, "SKILL.md"))) return c;
|
|
29022
|
+
return null;
|
|
29023
|
+
}
|
|
29024
|
+
function readFiles(dir) {
|
|
29025
|
+
const files = [];
|
|
29026
|
+
const symlinks = [];
|
|
29018
29027
|
const walk = (rel) => {
|
|
29019
29028
|
for (const ent of readdirSync3(join6(dir, rel), { withFileTypes: true })) {
|
|
29020
29029
|
const childRel = rel ? `${rel}/${ent.name}` : ent.name;
|
|
29021
29030
|
const st = lstatSync2(join6(dir, childRel));
|
|
29022
|
-
if (st.isSymbolicLink())
|
|
29023
|
-
|
|
29031
|
+
if (st.isSymbolicLink()) {
|
|
29032
|
+
if (!skipSegment(ent.name, false, basename2(rel))) symlinks.push(childRel);
|
|
29033
|
+
continue;
|
|
29034
|
+
}
|
|
29035
|
+
if (skipSegment(ent.name, st.isDirectory(), basename2(rel))) continue;
|
|
29036
|
+
if (st.isDirectory()) {
|
|
29037
|
+
walk(childRel);
|
|
29038
|
+
continue;
|
|
29039
|
+
}
|
|
29040
|
+
if (!st.isFile()) continue;
|
|
29041
|
+
let text = null;
|
|
29042
|
+
if (st.size < 2e6) {
|
|
29043
|
+
const b = readFileSync5(join6(dir, childRel));
|
|
29044
|
+
if (!b.includes(0)) text = b.toString("utf8");
|
|
29045
|
+
}
|
|
29046
|
+
files.push({ rel: childRel, size: st.size, text });
|
|
29024
29047
|
}
|
|
29025
29048
|
};
|
|
29026
29049
|
walk("");
|
|
29027
|
-
|
|
29050
|
+
return { files, symlinks };
|
|
29051
|
+
}
|
|
29052
|
+
var lineOf = (text, idx) => text.slice(0, Math.max(0, idx)).split("\n").length;
|
|
29053
|
+
var mb = (n) => `${(n / (1 << 20)).toFixed(1)} MB`;
|
|
29054
|
+
function frontmatter(raw) {
|
|
29055
|
+
const text = raw.replace(/\r\n/g, "\n");
|
|
29056
|
+
if (!text.startsWith("---\n")) return null;
|
|
29057
|
+
const end = text.indexOf("\n---\n", 4);
|
|
29058
|
+
const block = end >= 0 ? text.slice(4, end) : text.endsWith("\n---") ? text.slice(4, -4) : null;
|
|
29059
|
+
if (block == null) return null;
|
|
29060
|
+
const fm = {};
|
|
29061
|
+
let key = "";
|
|
29062
|
+
for (const l of block.split("\n")) {
|
|
29063
|
+
const m = /^([A-Za-z_][\w.-]*):\s*(.*)$/.exec(l);
|
|
29064
|
+
if (m) {
|
|
29065
|
+
key = m[1];
|
|
29066
|
+
fm[key] = m[2].replace(/^["']|["']$/g, "");
|
|
29067
|
+
} else if (key && /^\s+\S/.test(l)) fm[key] = `${fm[key]} ${l.trim()}`.trim();
|
|
29068
|
+
}
|
|
29069
|
+
for (const k of Object.keys(fm)) if (/^[>|][-+]?$/.test(fm[k].split(" ")[0] ?? "")) fm[k] = fm[k].replace(/^[>|][-+]?\s*/, "");
|
|
29070
|
+
const bodyAt = end >= 0 ? end + 5 : text.length;
|
|
29071
|
+
return { fm, body: text.slice(bodyAt), bodyStart: lineOf(text, bodyAt) };
|
|
29072
|
+
}
|
|
29073
|
+
var SECRET_RULES = [
|
|
29074
|
+
{ kind: "AWS access key", re: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/, captures: false },
|
|
29075
|
+
{ kind: "GitHub token", re: /\b(?:gh[pousr]_[A-Za-z0-9]{36,}|github_pat_[A-Za-z0-9_]{60,})\b/, captures: false },
|
|
29076
|
+
{ kind: "GitLab token", re: /\bglpat-[A-Za-z0-9_-]{20,}\b/, captures: false },
|
|
29077
|
+
{ kind: "Slack token", re: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/, captures: false },
|
|
29078
|
+
{ kind: "Anthropic key", re: /\bsk-ant-[A-Za-z0-9_-]{20,}\b/, captures: false },
|
|
29079
|
+
{ kind: "OpenAI key", re: /\bsk-(?:proj-|svcacct-)?[A-Za-z0-9_-]{32,}\b/, captures: false },
|
|
29080
|
+
{ kind: "Google API key", re: /\bAIza[0-9A-Za-z_-]{35}\b/, captures: false },
|
|
29081
|
+
{ kind: "Stripe key", re: /\b(?:sk|rk)_live_[A-Za-z0-9]{20,}\b/, captures: false },
|
|
29082
|
+
{ kind: "connection string with a password", re: /\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqps?):\/\/[^\s:/@]+:([^\s@/]+)@/g, captures: true },
|
|
29083
|
+
{ kind: "credentials embedded in a URL", re: /\bhttps?:\/\/[^\s:/@]+:([^\s@/]+)@/g, captures: true }
|
|
29084
|
+
];
|
|
29085
|
+
var PRIVATE_KEY_LABEL = /-----BEGIN (?:RSA |EC |DSA |OPENSSH |ENCRYPTED |PGP )?PRIVATE KEY(?: BLOCK)?-----/g;
|
|
29086
|
+
var PLACEHOLDER = /^(?:\$\{?[A-Z0-9_]+\}?|<[^>]*>|\{\{[^}]*\}\}|x+|\*+|\.+|pass(?:word)?|passwd|pwd|secret|changeme|example|your[-_]?password|\[[^\]]*\])$/i;
|
|
29087
|
+
function privateKeyHit(text) {
|
|
29088
|
+
for (const m of text.matchAll(PRIVATE_KEY_LABEL)) {
|
|
29089
|
+
const start = m.index + m[0].length;
|
|
29090
|
+
let w = text.slice(start, start + 400).replace(/\r/g, "");
|
|
29091
|
+
w = w.replace(/\\+[rn]/g, "\n");
|
|
29092
|
+
w = w.replace(/["'`]?[ \t]*\+?[ \t]*\n[ \t]*\+?[ \t]*["'`]?/g, "");
|
|
29093
|
+
w = w.replace(/["'`][ \t]*\+[ \t]*["'`]/g, "");
|
|
29094
|
+
w = w.replace(/^["'` ,;]+/, "");
|
|
29095
|
+
if (/[A-Za-z0-9+/]{40}/.test(w)) return m.index;
|
|
29096
|
+
}
|
|
29097
|
+
return -1;
|
|
29098
|
+
}
|
|
29099
|
+
function scanSecret(text) {
|
|
29100
|
+
const pk = privateKeyHit(text);
|
|
29101
|
+
if (pk >= 0) return { kind: "private key", index: pk };
|
|
29102
|
+
for (const r of SECRET_RULES) {
|
|
29103
|
+
if (!r.captures) {
|
|
29104
|
+
const m = r.re.exec(text);
|
|
29105
|
+
if (m) return { kind: r.kind, index: m.index };
|
|
29106
|
+
continue;
|
|
29107
|
+
}
|
|
29108
|
+
for (const m of text.matchAll(r.re)) if (!PLACEHOLDER.test(m[1])) return { kind: r.kind, index: m.index };
|
|
29109
|
+
}
|
|
29110
|
+
return null;
|
|
29111
|
+
}
|
|
29112
|
+
var SKILL_DIRS = "references|scripts|tools|templates|assets|bin|runtime|evals|examples|hub|lib|src";
|
|
29113
|
+
var PREFIX = String.raw`(?:\.\/|\$\{?[A-Za-z_]+\}?\/|<skill[-_ ]?dir>\/)?`;
|
|
29114
|
+
var REF_IN_CODE = new RegExp(String.raw`(?:\x60|\]\()` + PREFIX + String.raw`((?:${SKILL_DIRS})\/[^\s\x60)'"]+)`, "g");
|
|
29115
|
+
var REF_IN_CMD = new RegExp(String.raw`\b(?:node|python3?|bash|sh|deno|bun)\s+` + PREFIX + String.raw`([A-Za-z0-9_.\-/]+\.(?:m?js|cjs|ts|py|sh))\b`, "g");
|
|
29116
|
+
var ABS_WORKSPACE = /(^|[\s`'"(])(\/workspace\/[^\s`'")]*)/g;
|
|
29117
|
+
var PLATFORM_MODS = /* @__PURE__ */ new Set(["neta", "cohub_base"]);
|
|
29118
|
+
var ABS_MODS = /\/mods\/([a-z0-9_-]+)\//g;
|
|
29119
|
+
var NEGATED = /\b(never|not|don'?t|instead of|avoid|rather than)\b/i;
|
|
29120
|
+
var CREATES = /\b(save[sd]? (it )?(as|to)|write[s]? (it )?to|creat\w* |output[s]? to|generat\w* |produc\w* |render\w* to|export\w* to)\W*$/i;
|
|
29121
|
+
var leadIn = (t, idx) => {
|
|
29122
|
+
const w = t.slice(Math.max(0, idx - 160), idx);
|
|
29123
|
+
const cut = Math.max(w.lastIndexOf("\n\n"), w.search(/[.!?]\s[^.!?]*$/));
|
|
29124
|
+
return cut >= 0 ? w.slice(cut) : w;
|
|
29125
|
+
};
|
|
29126
|
+
function checkPaths(dir, files, add) {
|
|
29127
|
+
const all = files.map((f) => f.rel);
|
|
29128
|
+
const tops = new Set(all.map((r) => r.split("/")[0]));
|
|
29129
|
+
const exists = (p) => existsSync5(join6(dir, p)) || all.some((r) => r === p || r.endsWith(`/${p}`) || r.startsWith(`${p}/`) || r.includes(`/${p}/`));
|
|
29130
|
+
const docs = files.filter((f) => f.text && (f.rel === "SKILL.md" || /^references\//.test(f.rel) && /\.md$/i.test(f.rel)));
|
|
29131
|
+
const mods = /* @__PURE__ */ new Map();
|
|
29132
|
+
for (const f of docs) {
|
|
29133
|
+
const t = f.text;
|
|
29134
|
+
const seen = /* @__PURE__ */ new Set();
|
|
29135
|
+
const report = (p, idx) => {
|
|
29136
|
+
const before = t.slice(Math.max(0, idx - 60), idx);
|
|
29137
|
+
if (CREATES.test(before) || /['\u2019]s\s*$/.test(before.replace(/[`\s]+$/, "")) || /['\u2019]s\s+`?$/.test(before)) return;
|
|
29138
|
+
const clean = p.replace(/[.,:;]+$/, "").replace(/\/$/, "");
|
|
29139
|
+
if (seen.has(clean) || /[<>*{}$]|\.\.\.|\u2026|node_modules|(^|\/)\./.test(clean) || !tops.has(clean.split("/")[0])) return;
|
|
29140
|
+
seen.add(clean);
|
|
29141
|
+
if (!exists(clean)) add({ level: "warn", check: "paths", file: f.rel, line: lineOf(t, idx), message: `refers to ${clean}, which is not in the skill.`, fix: "Fix the path, or add the file. Paths are relative to the skill folder." });
|
|
29142
|
+
};
|
|
29143
|
+
for (const m of t.matchAll(REF_IN_CODE)) report(m[1], m.index);
|
|
29144
|
+
for (const m of t.matchAll(REF_IN_CMD)) {
|
|
29145
|
+
const p = m[1];
|
|
29146
|
+
if (p.startsWith("/") || !new RegExp(`^(?:${SKILL_DIRS})/`).test(p)) continue;
|
|
29147
|
+
report(p, m.index);
|
|
29148
|
+
}
|
|
29149
|
+
for (const m of t.matchAll(ABS_WORKSPACE)) {
|
|
29150
|
+
if (NEGATED.test(leadIn(t, m.index))) continue;
|
|
29151
|
+
add({ level: "warn", check: "paths", file: f.rel, line: lineOf(t, m.index), message: `hard-codes the absolute path ${m[2]}; the skill runs from different places (installed, used without installing, from a mod) and in different worlds.`, fix: "Use relative paths: the skill's own files relative to the skill folder (the folder that holds this SKILL.md), working files relative to the current folder." });
|
|
29152
|
+
break;
|
|
29153
|
+
}
|
|
29154
|
+
for (const m of t.matchAll(ABS_MODS)) if (!PLATFORM_MODS.has(m[1]) && !mods.has(m[1]) && !NEGATED.test(leadIn(t, m.index))) mods.set(m[1], { file: f.rel, line: lineOf(t, m.index) });
|
|
29155
|
+
}
|
|
29156
|
+
for (const [mod, at] of mods) add({ level: "warn", check: "paths", file: at.file, line: at.line, message: `depends on files under /mods/${mod}/; it only works in worlds that mount the ${mod} mod.`, fix: "Fine if that is intended (for example /mods/neta in Neta worlds); otherwise ship what you need inside the skill." });
|
|
29157
|
+
}
|
|
29158
|
+
var KINDS = ["web", "media", "material", "text", "roleplay", "per-request"];
|
|
29159
|
+
var CHECK_TYPES = ["answer", "cmd", "no-cmd", "skill-added", "no-skill-added"];
|
|
29160
|
+
function checkMatrix(files, add) {
|
|
29161
|
+
const mf = files.find((f) => f.rel === "evals/matrix.json");
|
|
29162
|
+
const F = "evals/matrix.json";
|
|
29163
|
+
const w = (message, fix) => add({ level: "warn", check: "test-declarations", file: F, line: 0, message, fix });
|
|
29164
|
+
if (!mf) {
|
|
29165
|
+
add({ level: "warn", check: "test-declarations", file: F, line: 0, message: "no test declarations: if Neta reviews this skill, it has to guess what each route should deliver.", fix: "Add evals/matrix.json with your scenarios and what each delivers (skill-creator writes one for you)." });
|
|
29166
|
+
return;
|
|
29167
|
+
}
|
|
29168
|
+
let mx;
|
|
29169
|
+
try {
|
|
29170
|
+
mx = JSON.parse(mf.text ?? "");
|
|
29171
|
+
} catch (e) {
|
|
29172
|
+
w(`not valid JSON (${e.message}).`);
|
|
29173
|
+
return;
|
|
29174
|
+
}
|
|
29175
|
+
const list = mx?.scenarios?.list ?? [];
|
|
29176
|
+
if (!list.length) w("scenarios.list is empty.");
|
|
29177
|
+
const ids = new Set(list.map((s) => s?.id));
|
|
29178
|
+
const top = mx?.primaryKind?.kind;
|
|
29179
|
+
for (const s of list) {
|
|
29180
|
+
if (!s?.id) {
|
|
29181
|
+
w("a scenario has no id.");
|
|
29182
|
+
continue;
|
|
29183
|
+
}
|
|
29184
|
+
const k = s.primaryKind?.kind;
|
|
29185
|
+
if (!k && !top) w(`scenario ${s.id} does not say what it delivers (primaryKind, on the scenario or once for the whole skill).`);
|
|
29186
|
+
else if (k && !KINDS.includes(k)) w(`scenario ${s.id}: primaryKind.kind "${k}" is not one of ${KINDS.join(", ")}.`);
|
|
29187
|
+
if (k === "text" && !(mx.checks?.[s.id]?.require?.length || mx.checks?.[s.id]?.oneOf?.length))
|
|
29188
|
+
w(`scenario ${s.id} delivers text but declares no checks, so any reply would look like a pass.`, `Add checks.${s.id}.require with what the reply must do (cmd / no-cmd / skill-added / answer).`);
|
|
29189
|
+
if (top && k && top !== "per-request" && k !== top && !mx.scenarios?.overrides?.[s.id]?.primaryKind)
|
|
29190
|
+
w(`scenario ${s.id} delivers ${k} but the skill as a whole says ${top}.`, "Make them agree, or set primaryKind to per-request at the top.");
|
|
29191
|
+
}
|
|
29192
|
+
for (const [col, sid] of Object.entries(mx?.scenarios?.assign ?? {})) if (!ids.has(sid)) w(`assign.${col} points at "${String(sid)}", which is not a scenario id.`);
|
|
29193
|
+
if (mx?.questionnaire && !["always", "conditional", "no"].includes(mx.questionnaire.mode)) w(`questionnaire.mode "${mx.questionnaire.mode}" must be always, conditional or no.`);
|
|
29194
|
+
const assertIds = /* @__PURE__ */ new Set();
|
|
29195
|
+
const checkA = (a, where) => {
|
|
29196
|
+
if (!a?.id || !CHECK_TYPES.includes(a.type)) {
|
|
29197
|
+
w(`${where}: each check needs an id and a type (${CHECK_TYPES.join(", ")}).`);
|
|
29198
|
+
return;
|
|
29199
|
+
}
|
|
29200
|
+
assertIds.add(a.id);
|
|
29201
|
+
if ((a.type === "cmd" || a.type === "no-cmd") && !a.match) w(`${where}.${a.id}: ${a.type} needs match.`);
|
|
29202
|
+
if (a.type === "answer" && !a.point) w(`${where}.${a.id}: answer needs point.`);
|
|
29203
|
+
for (const re of [a.match, a.handle]) if (re) {
|
|
29204
|
+
try {
|
|
29205
|
+
new RegExp(re, "i");
|
|
29206
|
+
} catch (e) {
|
|
29207
|
+
w(`${where}.${a.id}: invalid regular expression (${e.message}).`);
|
|
29208
|
+
}
|
|
29209
|
+
}
|
|
29210
|
+
};
|
|
29211
|
+
for (const [sid, c] of Object.entries(mx?.checks ?? {})) {
|
|
29212
|
+
if (sid.startsWith("_")) continue;
|
|
29213
|
+
if (!ids.has(sid)) w(`checks.${sid}: there is no scenario with that id.`);
|
|
29214
|
+
for (const a of c?.require ?? []) checkA(a, `checks.${sid}`);
|
|
29215
|
+
for (const b of c?.oneOf ?? []) for (const a of b?.require ?? []) checkA(a, `checks.${sid}.oneOf.${b?.id}`);
|
|
29216
|
+
}
|
|
29217
|
+
for (const r of Array.isArray(mx?.rules) ? mx.rules : []) {
|
|
29218
|
+
for (const cid of r?.checks ?? []) if (!assertIds.has(cid)) w(`rule ${r.id}: check "${cid}" is not declared under checks.`);
|
|
29219
|
+
if (r?.risk === "high" && !(r.checks ?? []).length && !r.waived) w(`rule ${r.id} is high-risk but nothing tests it.`, 'Add a check id, or waived: "<why it is not tested>".');
|
|
29220
|
+
}
|
|
29221
|
+
}
|
|
29222
|
+
function checkWriting(body, bodyStart, add) {
|
|
29223
|
+
const S = body;
|
|
29224
|
+
const has = (re) => re.test(S);
|
|
29225
|
+
const at = (re) => {
|
|
29226
|
+
const m = re.exec(S);
|
|
29227
|
+
return m ? lineOf(S, m.index) + bodyStart - 1 : 0;
|
|
29228
|
+
};
|
|
29229
|
+
const w = (line, message, fix) => add({ level: "warn", check: "writing", file: "SKILL.md", line, message, fix });
|
|
29230
|
+
if (!has(/user'?s language|language of the user|same language as the user/i) && !has(new RegExp("\\u7528\\u6237\\u7684?\\u8bed\\u8a00|\\u8ddf\\u968f\\u7528\\u6237")))
|
|
29231
|
+
w(0, "nothing says replies and the deliverable's text follow the user's language.", `Add: "Reply in the user's language. Text the user sees in the deliverable follows the user's language."`);
|
|
29232
|
+
const makesWork = has(/neta(\/neta)?\s+(work\s+(init|publish)|generate)|\bwork init\b/i);
|
|
29233
|
+
if (!has(/^#+[^\n]*(deliver|publish|output|hand ?(off|over)|result)/im) && !has(new RegExp("^#+[^\\n]*(\\u4ea4\\u4ed8|\\u53d1\\u5e03)", "m")))
|
|
29234
|
+
w(0, "no section that says what the user gets at the end and how it reaches them.", "Add a Deliver section: the kind of deliverable and the steps that hand it over.");
|
|
29235
|
+
if (makesWork && !has(/interaction-components/i))
|
|
29236
|
+
w(at(/deliver/i), "makes a Work but does not say to hand it over with an interaction-components card pointing at it.", "Say: hand over with an interaction-components card whose target is the Work (its id comes from the publish output).");
|
|
29237
|
+
if (has(/neta(\/neta)?\s+generate\s+(image|video|audio|3d)\b/i) && !has(/--work-title/))
|
|
29238
|
+
w(at(/neta(\/neta)?\s+generate/i), "generates media without --work-title, so the Work is titled with the prompt's first words.", 'Pass --work-title "<title>" (again with --target-work when re-rendering).');
|
|
29239
|
+
const OOS = new RegExp("^#+[^\\n]*(out of scope|not in scope|explicitly not|not doing|do not|don'?t|never|boundar|won'?t do|will not do|rules|guardrails|limits|\\u4e0d\\u505a|\\u7981\\u6b62|\\u786c\\u89c4\\u5219|\\u8fb9\\u754c|\\u4e0d\\u8981)[^\\n]*$", "im");
|
|
29240
|
+
const oosAt = S.search(OOS);
|
|
29241
|
+
if (oosAt < 0) w(0, "no Out of scope section.", "Add one: no changes to world settings, no deleting existing files, nothing beyond the deliverable.");
|
|
29242
|
+
else {
|
|
29243
|
+
const NEG = "(?:don'?t|do not|never|no|must not|without|\\u4e0d|\\u7981\\u6b62|\\u522b)";
|
|
29244
|
+
const worldOk = new RegExp(`${NEG}[^\\n]{0,60}(world[^\\n]{0,20}(setting|config)|WORLD\\.md|visual ?style|\\u4e16\\u754c(\\u8bbe\\u5b9a|\\u8bbe\\u7f6e|\\u89c2|\\u914d\\u7f6e))`, "i").test(S);
|
|
29245
|
+
const delOk = new RegExp(`${NEG}[^\\n]{0,60}(delet|remov|overwrit|\\u5220|\\u8986\\u76d6)`, "i").test(S);
|
|
29246
|
+
if (!worldOk || !delOk)
|
|
29247
|
+
w(lineOf(S, oosAt) + bodyStart - 1, 'Out of scope does not say "do not change world settings" and "do not delete existing files".', "Add both lines.");
|
|
29248
|
+
}
|
|
29249
|
+
if (!has(/counts? as given|already given|given when|if (a |any )?required .{0,40}missing|ask (once|only)|no questions|never asks?|do(es)? not ask|don'?t ask|without asking|questionnaire/i) && !has(new RegExp("\\u4f55\\u65f6\\u505c\\u4e0b\\u6765\\u95ee|\\u7f3a.{0,8}\\u95ee|\\u7b97\\u5df2\\u7ed9|\\u4e0d\\u95ee|\\u95ee\\u5377")))
|
|
29250
|
+
w(0, "no rule for when to ask the user: which inputs are required, and when each counts as already given.", "List the required inputs with 'counts as given when'; missing \u2192 ask once before building; all given \u2192 build to delivery without asking.");
|
|
29251
|
+
const WAIT = /(wait (for|until) (the )?user'?s? (confirm\w*|approv\w*|go-?ahead|reply|response|answer)|wait for (confirmation|approval)|(get|ask for|request|obtain) (the user'?s? )?(confirmation|approval|sign-?off) before|do not (start|proceed|render|generate|build)\w* until (the user|they)|stop and wait)/i;
|
|
29252
|
+
S.split("\n").forEach((l, i) => {
|
|
29253
|
+
if (WAIT.test(l) && !/\b(if|unless|only when|missing)\b/i.test(l))
|
|
29254
|
+
w(i + bodyStart, `stops for confirmation every time: "${l.trim().slice(0, 100)}"`, "Stop only when something required is missing; otherwise state the default and keep going.");
|
|
29255
|
+
});
|
|
29256
|
+
}
|
|
29257
|
+
var SHIPPED_DIR = /^(templates?|assets|runtime|public|static|web|site)\//;
|
|
29258
|
+
var UI_EXT = /\.(html?|m?js|jsx|tsx|vue|svelte|svg)$/i;
|
|
29259
|
+
var isShipped = (r) => !/(^|\/)(vendor|third_party|node_modules|scripts|tools|tests?|__tests__|i18n|locales?|lang)\//.test(r) && !/\.(test|spec)\.|selftest|\.(zh|ja|ko)([-_][a-z]+)?\.[a-z]+$/i.test(r) && // 测试、按语言命名的语言文件
|
|
29260
|
+
UI_EXT.test(r) && (SHIPPED_DIR.test(r) || /\.(html?|vue|svelte|svg)$/i.test(r));
|
|
29261
|
+
var NOT_UI_LINE = /\bconsole\.(log|warn|error|info|debug)\(|lang(uage)?\s*===?\s*['"](zh|ja|ko)|['"](zh|ja|ko)['"]\s*===?\s*\w*lang/i;
|
|
29262
|
+
var LOCALE_KEY = /^\s*['"]?(zh|ja|ko|zh[-_](?:cn|tw|hans|hant)|ja[-_]jp|ko[-_]kr)['"]?\s*:/i;
|
|
29263
|
+
function uiLines(text) {
|
|
29264
|
+
const out = [];
|
|
29265
|
+
let block = "";
|
|
29266
|
+
let dictDepth = 0;
|
|
29267
|
+
text.split("\n").forEach((raw, i) => {
|
|
29268
|
+
let l = raw;
|
|
29269
|
+
if (block === "c") {
|
|
29270
|
+
const e = l.indexOf("*/");
|
|
29271
|
+
if (e < 0) return;
|
|
29272
|
+
l = l.slice(e + 2);
|
|
29273
|
+
block = "";
|
|
29274
|
+
}
|
|
29275
|
+
if (block === "h") {
|
|
29276
|
+
const e = l.indexOf("-->");
|
|
29277
|
+
if (e < 0) return;
|
|
29278
|
+
l = l.slice(e + 3);
|
|
29279
|
+
block = "";
|
|
29280
|
+
}
|
|
29281
|
+
l = l.replace(/\/\*.*?\*\//g, "").replace(/<!--.*?-->/g, "");
|
|
29282
|
+
const c0 = l.indexOf("/*");
|
|
29283
|
+
if (c0 >= 0) {
|
|
29284
|
+
l = l.slice(0, c0);
|
|
29285
|
+
block = "c";
|
|
29286
|
+
}
|
|
29287
|
+
const h0 = l.indexOf("<!--");
|
|
29288
|
+
if (h0 >= 0) {
|
|
29289
|
+
l = l.slice(0, h0);
|
|
29290
|
+
block = "h";
|
|
29291
|
+
}
|
|
29292
|
+
l = l.replace(/(^|[^:'"])\/\/.*$/, "$1");
|
|
29293
|
+
if (dictDepth > 0) {
|
|
29294
|
+
dictDepth += (l.match(/\{/g) ?? []).length - (l.match(/\}/g) ?? []).length;
|
|
29295
|
+
return;
|
|
29296
|
+
}
|
|
29297
|
+
if (LOCALE_KEY.test(l)) {
|
|
29298
|
+
const d = (l.match(/\{/g) ?? []).length - (l.match(/\}/g) ?? []).length;
|
|
29299
|
+
if (d > 0) dictDepth = d;
|
|
29300
|
+
return;
|
|
29301
|
+
}
|
|
29302
|
+
out.push({ line: i + 1, text: l });
|
|
29303
|
+
});
|
|
29304
|
+
return out;
|
|
29305
|
+
}
|
|
29306
|
+
function checkHardcodedUi(files, add) {
|
|
29307
|
+
for (const f of files) {
|
|
29308
|
+
if (!f.text || !isShipped(f.rel)) continue;
|
|
29309
|
+
const hits = uiLines(f.text).filter((x) => CJK_RUN4.test(x.text) && !NOT_UI_LINE.test(x.text));
|
|
29310
|
+
const lang = /<html[^>]*lang=["'](zh|ja|ko)/i.exec(f.text);
|
|
29311
|
+
if (!hits.length && !lang) continue;
|
|
29312
|
+
const first = hits[0] ?? { line: lineOf(f.text, lang.index), text: "hard-coded <html lang>" };
|
|
29313
|
+
const n = hits.length || 1;
|
|
29314
|
+
add({ level: "warn", check: "writing", file: f.rel, line: first.line, message: `${n} line(s) of non-English text the user will see: "${first.text.trim().slice(0, 80)}"`, fix: "Put UI text in a locale table (English plus your users' languages) chosen from the user's language; set <html lang> from it." });
|
|
29315
|
+
}
|
|
29316
|
+
}
|
|
29317
|
+
var QUOTED = new RegExp(`(\u201C[^\u201D]*\u201D|\u300C[^\u300D]*\u300D|"[^"]*")`, "g");
|
|
29318
|
+
var CJK4 = new RegExp(`[${CJK_RANGE}][^"\u201D\u300D]*?(?:[${CJK_RANGE}][^"\u201D\u300D]*?){3}`);
|
|
29319
|
+
var AGENT_SAYS = new RegExp("\\b(ask(s|ed)?\\s+(the user|them|whether|which|if)|tell (the user|them)|reply with|respond with|say(ing)?\\s*[:\\uff1a]|message\\s*[:\\uff1a]|greet|options?\\s*[:\\uff1a]|choices?\\s*[:\\uff1a])|\\u95ee\\u7528\\u6237|\\u8be2\\u95ee|\\u56de\\u590d|\\u544a\\u8bc9\\u7528\\u6237|\\u5bf9\\u7528\\u6237\\u8bf4|\\u63d0\\u793a\\u7528\\u6237|\\u9009\\u9879", "i");
|
|
29320
|
+
var USER_SAYS = new RegExp("(user|they|users)\\s*(says?|said|types?|typed|writes?|wrote|asks?)\\s*$|\\u7528\\u6237(\\u8bf4|\\u5199|\\u70b9\\u540d|\\u95ee)\\s*$", "i");
|
|
29321
|
+
function checkUserPhrases(files, add) {
|
|
29322
|
+
const docs = files.filter((f) => f.text && (f.rel === "SKILL.md" || /^references\//.test(f.rel) && /\.md$/i.test(f.rel)) && !/\.(zh|ja|ko|zh-cn|zh-tw)\.md$/i.test(f.rel));
|
|
29323
|
+
for (const f of docs) {
|
|
29324
|
+
let inFm = false, fence = false, n = 0, first = 0, sample = "";
|
|
29325
|
+
const headings = (f.text.match(/^#+\s+.*$/gm) ?? []).map((h) => h.replace(/^#+\s+/, "").trim());
|
|
29326
|
+
const isHeading = (q) => {
|
|
29327
|
+
const x = q.slice(1, -1).trim();
|
|
29328
|
+
return x.length > 0 && headings.some((h) => h.includes(x));
|
|
29329
|
+
};
|
|
29330
|
+
f.text.split("\n").forEach((l, i) => {
|
|
29331
|
+
if (i === 0 && l.trim() === "---") {
|
|
29332
|
+
inFm = true;
|
|
29333
|
+
return;
|
|
29334
|
+
}
|
|
29335
|
+
if (inFm) {
|
|
29336
|
+
if (l.trim() === "---") inFm = false;
|
|
29337
|
+
return;
|
|
29338
|
+
}
|
|
29339
|
+
if (/^\s*```/.test(l)) {
|
|
29340
|
+
fence = !fence;
|
|
29341
|
+
return;
|
|
29342
|
+
}
|
|
29343
|
+
if (fence || !AGENT_SAYS.test(l)) return;
|
|
29344
|
+
const said = [...l.matchAll(QUOTED)].some((m) => CJK4.test(m[0]) && !isHeading(m[0]) && !USER_SAYS.test(l.slice(0, m.index).replace(/[\s:\uff1a,\uff0c]+$/, "")));
|
|
29345
|
+
if (said) {
|
|
29346
|
+
n++;
|
|
29347
|
+
if (!first) {
|
|
29348
|
+
first = i + 1;
|
|
29349
|
+
sample = l.trim().slice(0, 90);
|
|
29350
|
+
}
|
|
29351
|
+
}
|
|
29352
|
+
});
|
|
29353
|
+
if (n) add({ level: "warn", check: "user-phrases", file: f.rel, line: first, message: `${n} line(s) give the agent ready-made non-English words to say to the user (questions, options or fixed replies), e.g. "${sample}". Agents copy these, so users writing in another language get asked in this one.`, fix: `Describe what to ask instead (e.g. "ask which style the user wants"), or add "say it in the user's language" next to it.` });
|
|
29354
|
+
}
|
|
29355
|
+
}
|
|
29356
|
+
function checkEnglish(files, fm, body, add) {
|
|
29357
|
+
const agentDoc = (r) => r === "SKILL.md" || /^references\//.test(r) && /\.md$/i.test(r) && !/\.(zh|ja|ko|zh-cn|zh-tw)\.md$/i.test(r);
|
|
29358
|
+
const mostlyNonEnglish = (t) => {
|
|
29359
|
+
let fence = false, cjk = 0, latin = 0;
|
|
29360
|
+
for (const l of t.split("\n")) {
|
|
29361
|
+
if (/^\s*```/.test(l)) {
|
|
29362
|
+
fence = !fence;
|
|
29363
|
+
continue;
|
|
29364
|
+
}
|
|
29365
|
+
if (fence) continue;
|
|
29366
|
+
const x = l.replace(/^\s*>.*$/, "").replace(/\u201c[^\u201d]*\u201d|\u300c[^\u300d]*\u300d|"[^"]*"|`[^`]*`/g, "");
|
|
29367
|
+
cjk += (x.match(CJK_G) ?? []).length;
|
|
29368
|
+
latin += (x.match(/[A-Za-z]/g) ?? []).length;
|
|
29369
|
+
}
|
|
29370
|
+
return cjk > 40 && cjk > latin / 4;
|
|
29371
|
+
};
|
|
29372
|
+
const descHead = (fm.description ?? "").split(/\bTriggers?\s*[-:\u2013]/i)[0];
|
|
29373
|
+
const dc = (descHead.match(CJK_G) ?? []).length, dl = (descHead.match(/[A-Za-z]/g) ?? []).length;
|
|
29374
|
+
const bad = files.filter((f) => f.text && agentDoc(f.rel) && mostlyNonEnglish(f.rel === "SKILL.md" ? body : f.text)).map((f) => f.rel);
|
|
29375
|
+
if (dc > 8 && dl < 40 && !bad.includes("SKILL.md")) bad.unshift("SKILL.md (description)");
|
|
29376
|
+
if (!bad.length) return;
|
|
29377
|
+
add({ level: "warn", check: "english", file: bad[0].split(" ")[0], line: 0, message: `not written in English: ${bad.slice(0, 6).join(", ")}${bad.length > 6 ? ` (+${bad.length - 6} more)` : ""}. Writing what the agent reads in English is recommended: users speak many languages, and replies still follow each user's language.`, fix: "Translate to English (skill-creator can do it), keeping commands, paths, trigger words and quoted user text as they are. Trigger words in other languages can stay in the description." });
|
|
29378
|
+
}
|
|
29379
|
+
var LISTING_LOCALES = ["zh", "en"];
|
|
29380
|
+
var MAX_LISTING_NAME = 80;
|
|
29381
|
+
var MAX_LISTING_DESC = 300;
|
|
29382
|
+
var MAX_LISTING_WORKS = 6;
|
|
29383
|
+
var WORK_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
29384
|
+
var WORK_LINK = /^https:\/\/(?:[a-z0-9-]+\.)?neta\.art\/w\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/?$/i;
|
|
29385
|
+
var APP_LINK = /^https:\/\/(?:[a-z0-9-]+\.)?neta\.art\/w\/[^\s/]+\/[^\s/]+\/[^\s/]+\/?$/i;
|
|
29386
|
+
function checkListing(files, add) {
|
|
29387
|
+
const F = "listing.json";
|
|
29388
|
+
const w = (message, fix) => add({ level: "warn", check: "listing", file: F, line: 0, message, fix });
|
|
29389
|
+
const lf = files.find((f) => f.rel === F);
|
|
29390
|
+
if (!lf) {
|
|
29391
|
+
w("no listing.json (optional): if the Neta team features this skill in Studio, the name, description and showcase you write here are used first.", 'Add {"en": {"name", "description"}, "zh": {...}, "media": {"works": ["<id or https://neta.art/w/<id> link of a Work made with this skill and shared publicly>"]}}. skill-creator can write it.');
|
|
29392
|
+
return;
|
|
29393
|
+
}
|
|
29394
|
+
let j;
|
|
29395
|
+
try {
|
|
29396
|
+
j = JSON.parse(lf.text ?? "");
|
|
29397
|
+
} catch (e) {
|
|
29398
|
+
w(`not valid JSON (${e.message}).`);
|
|
29399
|
+
return;
|
|
29400
|
+
}
|
|
29401
|
+
if (!j || typeof j !== "object" || Array.isArray(j)) {
|
|
29402
|
+
w("must be a JSON object.");
|
|
29403
|
+
return;
|
|
29404
|
+
}
|
|
29405
|
+
for (const k of Object.keys(j)) if (![...LISTING_LOCALES, "media"].includes(k)) w(`unknown key "${k}"; only ${LISTING_LOCALES.join(", ")} and media are read.`);
|
|
29406
|
+
if (!LISTING_LOCALES.some((l) => j[l])) w(`no name and description: give at least one of ${LISTING_LOCALES.join(", ")}.`);
|
|
29407
|
+
const text = (v, field, max) => {
|
|
29408
|
+
if (v == null || v === "") {
|
|
29409
|
+
w(`${field} is empty.`);
|
|
29410
|
+
return;
|
|
29411
|
+
}
|
|
29412
|
+
if (typeof v !== "string") {
|
|
29413
|
+
w(`${field} must be text.`);
|
|
29414
|
+
return;
|
|
29415
|
+
}
|
|
29416
|
+
if ([...v].length > max) w(`${field} is ${[...v].length} characters; at most ${max}.`);
|
|
29417
|
+
if (/[\u0000-\u001f\u007f]/.test(v)) w(`${field} contains control characters.`);
|
|
29418
|
+
};
|
|
29419
|
+
for (const l of LISTING_LOCALES) {
|
|
29420
|
+
if (j[l] == null) continue;
|
|
29421
|
+
text(j[l]?.name, `${l}.name`, MAX_LISTING_NAME);
|
|
29422
|
+
text(j[l]?.description, `${l}.description`, MAX_LISTING_DESC);
|
|
29423
|
+
}
|
|
29424
|
+
if (j.media == null) return;
|
|
29425
|
+
const works = j.media?.works;
|
|
29426
|
+
if (!Array.isArray(works)) {
|
|
29427
|
+
w('media.works must be a list of Works: ["<id or https://neta.art/w/<id> link>"].');
|
|
29428
|
+
return;
|
|
29429
|
+
}
|
|
29430
|
+
if (works.length > MAX_LISTING_WORKS) w(`media.works has ${works.length} Works; at most ${MAX_LISTING_WORKS} (the Neta team picks one).`);
|
|
29431
|
+
for (const x of works) {
|
|
29432
|
+
if (typeof x === "string" && (WORK_ID.test(x) || WORK_LINK.test(x))) continue;
|
|
29433
|
+
if (typeof x === "string" && APP_LINK.test(x)) {
|
|
29434
|
+
w(`media.works: "${x.slice(0, 80)}" is the Work's page address; the skills site cannot match it to a Work.`, "Use the Work's id, or its https://neta.art/w/<id> link (from the share menu in Studio).");
|
|
29435
|
+
continue;
|
|
29436
|
+
}
|
|
29437
|
+
w(`media.works: "${String(x).slice(0, 80)}" is not a Studio Work. Only Works made in Studio are shown (not links to other sites): they show the skill works in Studio, and sharing them publicly is how you agree to have them shown.`, "Use the id or the https://neta.art/w/<id> link of a Work made with this skill and shared publicly.");
|
|
29438
|
+
}
|
|
29439
|
+
}
|
|
29440
|
+
function lintSkill(dir) {
|
|
29441
|
+
const findings = [];
|
|
29442
|
+
const add = (f) => findings.push(f);
|
|
29443
|
+
const { files, symlinks } = readFiles(dir);
|
|
29444
|
+
const name = basename2(resolve2(dir));
|
|
29445
|
+
const skill = files.find((f) => f.rel === "SKILL.md");
|
|
29446
|
+
const mdPath = join6(dir, "SKILL.md");
|
|
29447
|
+
const mdIsLink = existsSync5(mdPath) && lstatSync2(mdPath).isSymbolicLink();
|
|
29448
|
+
const rawFile = skill?.text ?? (mdIsLink ? readFileSync5(mdPath, "utf8") : "");
|
|
29449
|
+
const bom = rawFile.startsWith("\uFEFF");
|
|
29450
|
+
const raw = bom ? rawFile.slice(1) : rawFile;
|
|
29451
|
+
const crlf = !bom && raw.replace(/\r\n/g, "\n").startsWith("---\n") && !(raw.startsWith("---\n") && raw.indexOf("\n---\n") >= 0);
|
|
29452
|
+
const parsed = frontmatter(raw);
|
|
29453
|
+
const fm = parsed?.fm ?? {};
|
|
29454
|
+
const body = parsed?.body ?? raw;
|
|
29455
|
+
const bodyStart = parsed?.bodyStart ?? 1;
|
|
29456
|
+
if (lstatSync2(dir).isSymbolicLink()) add({ level: "error", check: "publish-limits", file: "", line: 0, message: "the skill folder itself is a symbolic link; the skills site rejects it.", fix: "Move the real folder here instead of linking to it." });
|
|
29457
|
+
if (mdIsLink) add({ level: "error", check: "publish-limits", file: "SKILL.md", line: 0, message: "SKILL.md is a symbolic link; the skills site rejects it.", fix: "Replace it with the real file." });
|
|
29458
|
+
if (bom) add({ level: "error", check: "structure", file: "SKILL.md", line: 1, message: "SKILL.md starts with a byte-order mark (BOM); the skills site needs it to start with --- and would not publish it.", fix: "Save SKILL.md as UTF-8 without BOM." });
|
|
29459
|
+
else if (crlf) add({ level: "error", check: "structure", file: "SKILL.md", line: 1, message: "SKILL.md has Windows line endings (CRLF) around the frontmatter; the skills site only reads it with Unix line endings (LF) and would not publish it.", fix: "Save SKILL.md with LF line endings." });
|
|
29460
|
+
if (!parsed) add({ level: "error", check: "structure", file: "SKILL.md", line: 1, message: "SKILL.md has no frontmatter (it must start with --- and have name and description).", fix: "Add a --- block at the top with name and description." });
|
|
29461
|
+
else if (!fm.description?.trim()) add({ level: "error", check: "structure", file: "SKILL.md", line: 2, message: "the frontmatter has no description; agents decide when to use a skill by it.", fix: "Add description: what it makes and when to use it." });
|
|
29462
|
+
if (parsed) {
|
|
29463
|
+
if (fm.name && fm.name !== name) add({ level: "warn", check: "structure", file: "SKILL.md", line: 2, message: `name "${fm.name}" differs from the folder name "${name}"; the folder name is what gets published.`, fix: "Make them the same." });
|
|
29464
|
+
if (fm.description && fm.description.length > 1024) add({ level: "warn", check: "structure", file: "SKILL.md", line: 3, message: `description is ${fm.description.length} characters; keep it under 1024.` });
|
|
29465
|
+
if (fm.description && !/\b(use (it |this )?(when|for)|when the user)\b/i.test(fm.description) && !new RegExp("\\u5f53\\u7528\\u6237|\\u7528\\u4e8e|\\u89e6\\u53d1").test(fm.description))
|
|
29466
|
+
add({ level: "warn", check: "structure", file: "SKILL.md", line: 3, message: "description does not say when to use the skill.", fix: 'Add "Use when the user wants ..." in the words users type.' });
|
|
29467
|
+
}
|
|
29468
|
+
if (!NAME_RE.test(name) || name.length > 60) add({ level: "warn", check: "structure", file: "", line: 0, message: `folder name "${name}" is not lowercase kebab-case of at most 60 characters; the picker skips it and submit refuses it.`, fix: "Rename the folder, e.g. my-skill." });
|
|
29469
|
+
const lines = raw.split("\n").length;
|
|
29470
|
+
if (lines > 500) add({ level: "warn", check: "structure", file: "SKILL.md", line: 0, message: `SKILL.md is ${lines} lines; long files are read less carefully.`, fix: "Keep it under about 500 lines; move detail into references/ and say when to read each file." });
|
|
29471
|
+
const links = symlinks.filter((p) => p !== "SKILL.md");
|
|
29472
|
+
if (links.length) add({ level: "warn", check: "publish-limits", file: links[0], line: 0, message: `symbolic links are skipped when publishing, so installed copies will not have: ${links.slice(0, 5).join(", ")}${links.length > 5 ? ` (+${links.length - 5} more)` : ""}.`, fix: "Replace them with real files if the skill needs them." });
|
|
29473
|
+
if (files.length > MAX_FILES) add({ level: "error", check: "publish-limits", file: "", line: 0, message: `too many files: ${files.length} (at most ${MAX_FILES}).` });
|
|
29474
|
+
let total = 0;
|
|
29475
|
+
for (const f of files) {
|
|
29476
|
+
total += f.size;
|
|
29477
|
+
if (f.size > MAX_FILE_BYTES) add({ level: "error", check: "publish-limits", file: f.rel, line: 0, message: `${mb(f.size)}; at most ${mb(MAX_FILE_BYTES)} per file.` });
|
|
29478
|
+
}
|
|
29479
|
+
if (total > MAX_TOTAL_BYTES) add({ level: "error", check: "publish-limits", file: "", line: 0, message: `${mb(total)} in total; at most ${mb(MAX_TOTAL_BYTES)}.` });
|
|
29480
|
+
if (existsSync5(join6(dir, SOURCE_FILE))) {
|
|
29481
|
+
let h = "";
|
|
29482
|
+
try {
|
|
29483
|
+
const s = JSON.parse(readFileSync5(join6(dir, SOURCE_FILE), "utf8"));
|
|
29484
|
+
h = s.handle || s.id || "";
|
|
29485
|
+
} catch {
|
|
29486
|
+
}
|
|
29487
|
+
add({ level: "error", check: "installed-copy", file: SOURCE_FILE, line: 0, message: `this folder was installed from ${h || "another skill"}; only skills written in this space can be submitted.`, fix: "To publish your own version, copy it to a new name and make it yours; do not just delete the marker." });
|
|
29488
|
+
}
|
|
29489
|
+
for (const f of files) {
|
|
29490
|
+
if (!f.text) continue;
|
|
29491
|
+
const hit = scanSecret(f.text);
|
|
29492
|
+
if (hit) add({ level: "error", check: "secrets", file: f.rel, line: lineOf(f.text, hit.index), message: `looks like a secret (${hit.kind}); one such file blocks publishing the whole skill.`, fix: "Remove the value (not just the file) and rotate it if it was real." });
|
|
29493
|
+
}
|
|
29494
|
+
if (skill?.text) {
|
|
29495
|
+
checkPaths(dir, files, add);
|
|
29496
|
+
checkWriting(body, bodyStart, add);
|
|
29497
|
+
checkUserPhrases(files, add);
|
|
29498
|
+
checkEnglish(files, fm, body, add);
|
|
29499
|
+
}
|
|
29500
|
+
checkHardcodedUi(files, add);
|
|
29501
|
+
checkMatrix(files, add);
|
|
29502
|
+
checkListing(files, add);
|
|
29503
|
+
const errors = findings.filter((f) => f.level === "error").length;
|
|
29504
|
+
return { dir, ok: errors === 0, errors, warnings: findings.length - errors, findings };
|
|
29505
|
+
}
|
|
29506
|
+
function formatFindings(r) {
|
|
29507
|
+
const order = (f) => f.level === "error" ? 0 : 1;
|
|
29508
|
+
return [...r.findings].sort((a, b) => order(a) - order(b)).map((f) => {
|
|
29509
|
+
const where = f.file ? `${f.file}${f.line ? `:${f.line}` : ""}` : posix.basename(r.dir);
|
|
29510
|
+
return `${f.level === "error" ? "error" : "warn "} [${f.check}] ${where}: ${f.message}${f.fix ? `
|
|
29511
|
+
fix: ${f.fix}` : ""}`;
|
|
29512
|
+
});
|
|
29513
|
+
}
|
|
29514
|
+
async function cmdLint(arg, opts) {
|
|
29515
|
+
const dir = resolveSkillDir(arg);
|
|
29516
|
+
if (!dir) {
|
|
29517
|
+
console.error(arg ? `No skill found at ${arg}, ${SKILLS_DIR}/${arg} or ${DRAFTS_DIR}/${arg} (a skill folder has a SKILL.md).` : "There is no SKILL.md here. Give a skill name or folder: neta-search skills lint <name-or-dir>.");
|
|
29518
|
+
return 2;
|
|
29519
|
+
}
|
|
29520
|
+
const r = lintSkill(dir);
|
|
29521
|
+
if (opts.json) {
|
|
29522
|
+
console.log(JSON.stringify(r));
|
|
29523
|
+
return r.ok ? 0 : 1;
|
|
29524
|
+
}
|
|
29525
|
+
for (const l of formatFindings(r)) console.log(l);
|
|
29526
|
+
console.log(`${r.findings.length ? "\n" : ""}Static check of ${dir}: ${r.errors} error(s), ${r.warnings} warning(s).${r.errors ? " Errors are what the skills site would reject; fix them before submitting." : ""}${r.warnings ? " Warnings are suggestions; they do not block submitting." : ""}`);
|
|
29527
|
+
return r.ok ? 0 : 1;
|
|
29528
|
+
}
|
|
29529
|
+
|
|
29530
|
+
// cli/src/skills/submit.ts
|
|
29531
|
+
var SUBMISSION_PATH = "/v1/skills/submission";
|
|
29532
|
+
function precheck(dir) {
|
|
29533
|
+
const problems = [];
|
|
29534
|
+
const md = join7(dir, "SKILL.md");
|
|
29535
|
+
if (!existsSync6(md)) return ["there is no SKILL.md"];
|
|
29536
|
+
if (lstatSync3(md).isSymbolicLink()) problems.push("SKILL.md is a symbolic link; replace it with the real file");
|
|
29537
|
+
const raw = readFileSync6(md, "utf8");
|
|
29538
|
+
const fm = /^---\n([\s\S]*?)\n---\n/.exec(raw);
|
|
29539
|
+
if (raw.startsWith("\uFEFF")) problems.push("SKILL.md starts with a byte-order mark (BOM); save it as UTF-8 without BOM");
|
|
29540
|
+
else if (!fm && /^---\r?\n[\s\S]*?\r?\n---\r?\n/.test(raw)) problems.push("SKILL.md has Windows line endings (CRLF) around the frontmatter; save it with LF line endings");
|
|
29541
|
+
else if (!fm) problems.push("SKILL.md has no frontmatter (it must start with --- and have name / description)");
|
|
29542
|
+
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)");
|
|
29028
29543
|
const files = readSkillFiles(dir);
|
|
29029
29544
|
if (files.size > MAX_FILES) problems.push(`too many files: ${files.size} (at most ${MAX_FILES})`);
|
|
29030
29545
|
let total = 0;
|
|
29031
29546
|
for (const [p, b] of files) {
|
|
29032
29547
|
total += b.length;
|
|
29033
|
-
if (b.length > MAX_FILE_BYTES) problems.push(`${p} is ${
|
|
29548
|
+
if (b.length > MAX_FILE_BYTES) problems.push(`${p} is ${mb2(b.length)} (at most ${mb2(MAX_FILE_BYTES)} per file)`);
|
|
29034
29549
|
}
|
|
29035
|
-
if (total > MAX_TOTAL_BYTES) problems.push(`the skill is ${
|
|
29550
|
+
if (total > MAX_TOTAL_BYTES) problems.push(`the skill is ${mb2(total)} in total (at most ${mb2(MAX_TOTAL_BYTES)})`);
|
|
29036
29551
|
return problems;
|
|
29037
29552
|
}
|
|
29038
|
-
var
|
|
29553
|
+
var mb2 = (n) => `${(n / (1 << 20)).toFixed(1)} MB`;
|
|
29039
29554
|
function blockLabel(reason) {
|
|
29040
29555
|
const head = reason.split(":")[0];
|
|
29041
29556
|
switch (head) {
|
|
@@ -29089,9 +29604,9 @@ async function cmdSubmit(name, opts) {
|
|
|
29089
29604
|
return 2;
|
|
29090
29605
|
}
|
|
29091
29606
|
if (opts.withdraw) return withdraw(env, name, opts);
|
|
29092
|
-
const dir =
|
|
29093
|
-
if (!
|
|
29094
|
-
if (
|
|
29607
|
+
const dir = join7(SKILLS_DIR, name);
|
|
29608
|
+
if (!existsSync6(join7(dir, "SKILL.md"))) {
|
|
29609
|
+
if (existsSync6(join7(DRAFTS_DIR, name, "SKILL.md"))) {
|
|
29095
29610
|
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.`);
|
|
29096
29611
|
} else {
|
|
29097
29612
|
console.error(`There is no skill at ${dir}/ (it needs a SKILL.md).`);
|
|
@@ -29113,11 +29628,20 @@ async function cmdSubmit(name, opts) {
|
|
|
29113
29628
|
for (const p of problems) console.error(` - ${p}`);
|
|
29114
29629
|
return 1;
|
|
29115
29630
|
}
|
|
29631
|
+
const lint = lintSkill(dir);
|
|
29632
|
+
if (!lint.ok) {
|
|
29633
|
+
console.error(`Not submitted: the static check found ${lint.errors} problem(s) the skills site would reject:`);
|
|
29634
|
+
for (const l of formatFindings({ ...lint, findings: lint.findings.filter((f) => f.level === "error") })) console.error(` ${l}`);
|
|
29635
|
+
return 1;
|
|
29636
|
+
}
|
|
29637
|
+
if (lint.warnings && !opts.json) {
|
|
29638
|
+
console.error(`Static check: ${lint.warnings} suggestion(s) (not blocking). Run neta-search skills lint ${name} to see them.`);
|
|
29639
|
+
}
|
|
29116
29640
|
const files = readSkillFiles(dir);
|
|
29117
29641
|
const digest = digestOf(files);
|
|
29118
29642
|
const id = ownId(env, name);
|
|
29119
29643
|
const total = [...files.values()].reduce((a, b) => a + b.length, 0);
|
|
29120
|
-
if (!opts.json) console.error(`Checked: ${files.size} files, ${
|
|
29644
|
+
if (!opts.json) console.error(`Checked: ${files.size} files, ${mb2(total)}. Submitting ${digest} ...`);
|
|
29121
29645
|
const sent = await reportSkillEventDetail(env, "use", id, digest, name, { via: "submit", where: "local" });
|
|
29122
29646
|
if (sent === "unreachable") {
|
|
29123
29647
|
console.error("Not submitted: could not reach the server. Try again in a moment.");
|
|
@@ -29196,11 +29720,55 @@ function finish(opts, id, digest, name, s) {
|
|
|
29196
29720
|
} else {
|
|
29197
29721
|
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.");
|
|
29198
29722
|
}
|
|
29723
|
+
lines.push(...listingLines(s.listing));
|
|
29199
29724
|
lines.push(`To take it down later: neta-search skills submit --withdraw ${name} (deleting the directory does not withdraw it).`);
|
|
29200
|
-
return done(opts, base, lines.join("\n"), 0);
|
|
29725
|
+
return done(opts, s.listing ? { ...base, listing: s.listing } : base, lines.join("\n"), 0);
|
|
29201
29726
|
}
|
|
29202
29727
|
}
|
|
29203
29728
|
}
|
|
29729
|
+
function listingLines(l) {
|
|
29730
|
+
if (!l) return [];
|
|
29731
|
+
const out = [];
|
|
29732
|
+
const text = Object.entries(l.text ?? {});
|
|
29733
|
+
const okLangs = text.filter(([, c]) => c.ok).map(([lang]) => lang);
|
|
29734
|
+
const badLangs = text.filter(([, c]) => !c.ok);
|
|
29735
|
+
const works = l.works ?? [];
|
|
29736
|
+
const hidden = works.filter((w) => !w.shown);
|
|
29737
|
+
const noted = works.filter((w) => w.shown && w.note);
|
|
29738
|
+
out.push(`listing.json: ${okLangs.length ? `name and description read for ${okLangs.join(", ")}` : "no usable name and description"}${works.length ? `; ${works.length - hidden.length} of ${works.length} showcase Work(s) can be shown` : ""}. This does not affect publishing.`);
|
|
29739
|
+
for (const [lang, c] of badLangs) out.push(` - ${lang}: ${textProblem(c.reason)}`);
|
|
29740
|
+
for (const w of hidden) out.push(` - ${w.work}: ${workProblem(w.reason)}`);
|
|
29741
|
+
for (const w of noted) out.push(` - ${w.work}: ${workNote(w.note)}`);
|
|
29742
|
+
return out;
|
|
29743
|
+
}
|
|
29744
|
+
function textProblem(r) {
|
|
29745
|
+
switch (r) {
|
|
29746
|
+
case "too_long":
|
|
29747
|
+
return "too long (name at most 80 characters, description at most 300), not used";
|
|
29748
|
+
case "empty":
|
|
29749
|
+
return "name or description is empty, not used";
|
|
29750
|
+
case "invalid":
|
|
29751
|
+
return "not a name and a description (or has control characters), not used";
|
|
29752
|
+
default:
|
|
29753
|
+
return r || "not usable";
|
|
29754
|
+
}
|
|
29755
|
+
}
|
|
29756
|
+
function workProblem(r) {
|
|
29757
|
+
switch (r) {
|
|
29758
|
+
// 作品进作品表有大约一分钟的延迟;skill-creator 的流程正好是先分享、马上提交(PR #570 review)
|
|
29759
|
+
case "not_found":
|
|
29760
|
+
return "not shown: not found yet. A Work you just shared takes a few minutes to show up, so submit again later; refer to it by its id or its https://neta.art/w/<id> link";
|
|
29761
|
+
case "not_public":
|
|
29762
|
+
return "not shown: not shared publicly yet; choose Public in the Work's share menu in Studio";
|
|
29763
|
+
case "not_yours":
|
|
29764
|
+
return "not shown: it is someone else's Work";
|
|
29765
|
+
default:
|
|
29766
|
+
return `not shown${r ? `: ${r}` : ""}`;
|
|
29767
|
+
}
|
|
29768
|
+
}
|
|
29769
|
+
function workNote(n) {
|
|
29770
|
+
return n === "not_made_with_this_skill" ? "can be shown, but it does not look like it was made with this skill; the Neta team will check" : n || "";
|
|
29771
|
+
}
|
|
29204
29772
|
function done(opts, json, text, code) {
|
|
29205
29773
|
if (opts.json) console.log(JSON.stringify(json));
|
|
29206
29774
|
else (code === 0 ? console.log : console.error)(text);
|
|
@@ -29254,15 +29822,168 @@ async function withdraw(env, name, opts) {
|
|
|
29254
29822
|
}
|
|
29255
29823
|
}
|
|
29256
29824
|
|
|
29825
|
+
// cli/src/skills/review-feedback.ts
|
|
29826
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
29827
|
+
import { join as join8 } from "node:path";
|
|
29828
|
+
var REVIEW_FEEDBACK_PATH = "/v1/skills/review-feedback";
|
|
29829
|
+
var FEEDBACK_DIR = ".neta-search/skill-feedback";
|
|
29830
|
+
var FEEDBACK_NOTE = "README.txt";
|
|
29831
|
+
var RENDERED = ["feedback.md", "fix-pack.md"];
|
|
29832
|
+
function refused(g) {
|
|
29833
|
+
if (g === "unreachable" || g.status < 400 || g.status === 404) return false;
|
|
29834
|
+
console.error(`The server refused the request (HTTP ${g.status}): ${g.body.reason || "no reason given"}.`);
|
|
29835
|
+
return true;
|
|
29836
|
+
}
|
|
29837
|
+
var DIGEST_RE2 = /^sha256:[0-9a-f]{64}$/;
|
|
29838
|
+
async function call(env, method, params) {
|
|
29839
|
+
const q = new URLSearchParams({ space: env.spaceId, turn: env.turnId, ...params });
|
|
29840
|
+
try {
|
|
29841
|
+
const r = await fetch(`${env.apiBase}${REVIEW_FEEDBACK_PATH}?${q}`, { method, signal: AbortSignal.timeout(1e4) });
|
|
29842
|
+
if (r.status >= 500 || r.status !== 200 && r.status !== 404) return r.status >= 500 ? "unreachable" : { status: r.status, body: { state: "", reason: (await r.text()).trim() } };
|
|
29843
|
+
return { status: r.status, body: await r.json() };
|
|
29844
|
+
} catch {
|
|
29845
|
+
return "unreachable";
|
|
29846
|
+
}
|
|
29847
|
+
}
|
|
29848
|
+
var short = (d) => d.replace(/^sha256:/, "").slice(0, 7);
|
|
29849
|
+
async function cmdFeedback(name, opts) {
|
|
29850
|
+
const env = detectEnv();
|
|
29851
|
+
const say = (s) => console.log(s);
|
|
29852
|
+
if (env.anonymous || !env.turnId) {
|
|
29853
|
+
console.error("skills feedback only works inside a Cohub sandbox, in a chat in the space where you wrote the skill: it checks that you are its author.");
|
|
29854
|
+
return 2;
|
|
29855
|
+
}
|
|
29856
|
+
if (name !== void 0 && !/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) {
|
|
29857
|
+
console.error(`Skill names are kebab-case directory names (e.g. brawl-creator), got "${name}".`);
|
|
29858
|
+
return 2;
|
|
29859
|
+
}
|
|
29860
|
+
if (opts.digest && !DIGEST_RE2.test(opts.digest)) {
|
|
29861
|
+
console.error("--digest must look like sha256:<64 lowercase hex>.");
|
|
29862
|
+
return 2;
|
|
29863
|
+
}
|
|
29864
|
+
let got = await call(env, "POST", { actor: env.actorId });
|
|
29865
|
+
if (got === "unreachable") {
|
|
29866
|
+
console.error("Could not reach the server. Try again in a moment.");
|
|
29867
|
+
return 4;
|
|
29868
|
+
}
|
|
29869
|
+
if (refused(got)) return 2;
|
|
29870
|
+
const deadline = Date.now() + (opts.wait ?? 30) * 1e3;
|
|
29871
|
+
while (got !== "unreachable" && got.body.state === "queued" && Date.now() < deadline) {
|
|
29872
|
+
await new Promise((r) => setTimeout(r, opts.pollMs ?? 1e3));
|
|
29873
|
+
got = await call(env, "GET", {});
|
|
29874
|
+
}
|
|
29875
|
+
if (got === "unreachable") {
|
|
29876
|
+
console.error("Could not reach the server. Try again in a moment.");
|
|
29877
|
+
return 4;
|
|
29878
|
+
}
|
|
29879
|
+
if (refused(got)) return 2;
|
|
29880
|
+
const st = got.body.state;
|
|
29881
|
+
if (st === "queued" || st === "") {
|
|
29882
|
+
console.error("Still checking that this chat is yours. Run the same command again in a moment.");
|
|
29883
|
+
return 3;
|
|
29884
|
+
}
|
|
29885
|
+
if (st === "not_applicable") {
|
|
29886
|
+
console.error(`Review feedback is not available here: ${got.body.reason || "skills in this space are reviewed and fixed by the Neta team"}.`);
|
|
29887
|
+
return 1;
|
|
29888
|
+
}
|
|
29889
|
+
if (st === "rejected") {
|
|
29890
|
+
console.error(`Could not confirm you are the author: ${got.body.reason || "rejected"}. Only the owner of this space can read its skills' review feedback, from a chat they started here.`);
|
|
29891
|
+
return 1;
|
|
29892
|
+
}
|
|
29893
|
+
if (name === void 0) {
|
|
29894
|
+
const list = await call(env, "GET", {});
|
|
29895
|
+
if (list === "unreachable") {
|
|
29896
|
+
console.error("Could not reach the server. Try again in a moment.");
|
|
29897
|
+
return 4;
|
|
29898
|
+
}
|
|
29899
|
+
if (refused(list)) return 2;
|
|
29900
|
+
const skills2 = list.body.skills ?? [];
|
|
29901
|
+
if (opts.json) {
|
|
29902
|
+
say(JSON.stringify({ skills: skills2.map(({ feedback: _f, ...s }) => s) }));
|
|
29903
|
+
return 0;
|
|
29904
|
+
}
|
|
29905
|
+
if (!skills2.length) {
|
|
29906
|
+
say("None of the skills in this space has review feedback from Neta. Neta reviews skills it nominates; if one of them needs changes, its feedback shows up here.");
|
|
29907
|
+
return 0;
|
|
29908
|
+
}
|
|
29909
|
+
say("Review feedback from Neta:");
|
|
29910
|
+
for (const s of skills2) say(` ${s.name} version ${short(s.digest)}${s.current ? " (your latest published version)" : ""} ${s.updated_at}`);
|
|
29911
|
+
say(`
|
|
29912
|
+
Read one with: neta-search skills feedback <name>`);
|
|
29913
|
+
return 0;
|
|
29914
|
+
}
|
|
29915
|
+
const one = await call(env, "GET", { name, ...opts.digest ? { digest: opts.digest } : {} });
|
|
29916
|
+
if (one === "unreachable") {
|
|
29917
|
+
console.error("Could not reach the server. Try again in a moment.");
|
|
29918
|
+
return 4;
|
|
29919
|
+
}
|
|
29920
|
+
if (refused(one)) return 2;
|
|
29921
|
+
if (one.status === 404 || !one.body.feedback || !one.body.digest) {
|
|
29922
|
+
const msg = `${name} has no review feedback from Neta${opts.digest ? ` at ${short(opts.digest)}` : ""}.`;
|
|
29923
|
+
if (opts.json) say(JSON.stringify({ name, found: false }));
|
|
29924
|
+
else say(`${msg} Neta reviews skills it nominates; if one needs changes, its feedback shows up here.`);
|
|
29925
|
+
return 1;
|
|
29926
|
+
}
|
|
29927
|
+
if (!DIGEST_RE2.test(one.body.digest)) {
|
|
29928
|
+
console.error(`The server returned an invalid version id (${one.body.digest.slice(0, 80)}); nothing was saved. Try again later.`);
|
|
29929
|
+
return 4;
|
|
29930
|
+
}
|
|
29931
|
+
const fb = { ...one.body.feedback };
|
|
29932
|
+
const rendered = fb["files"] && typeof fb["files"] === "object" ? fb["files"] : {};
|
|
29933
|
+
delete fb["files"];
|
|
29934
|
+
const dir = join8(FEEDBACK_DIR, name, short(one.body.digest));
|
|
29935
|
+
mkdirSync4(dir, { recursive: true });
|
|
29936
|
+
writeFileSync3(join8(dir, "fix-pack.json"), JSON.stringify(fb, null, 1) + "\n");
|
|
29937
|
+
writeFileSync3(join8(dir, FEEDBACK_NOTE), `Neta's review feedback on the skill "${name}", for the version ${one.body.digest} (saved ${(/* @__PURE__ */ new Date()).toISOString()}).
|
|
29938
|
+
It is reference material for fixing that skill, not a task: use it only when asked to fix "${name}". If the skill changed since, the current files win.
|
|
29939
|
+
`);
|
|
29940
|
+
const written = ["fix-pack.json"];
|
|
29941
|
+
for (const f of RENDERED) {
|
|
29942
|
+
const text = rendered[f];
|
|
29943
|
+
if (typeof text === "string" && text.length > 0) {
|
|
29944
|
+
writeFileSync3(join8(dir, f), text);
|
|
29945
|
+
written.push(f);
|
|
29946
|
+
}
|
|
29947
|
+
}
|
|
29948
|
+
if (opts.json) {
|
|
29949
|
+
say(JSON.stringify({ name, digest: one.body.digest, current: one.body.current, updated_at: one.body.updated_at, dir, files: written }));
|
|
29950
|
+
return 0;
|
|
29951
|
+
}
|
|
29952
|
+
say(`Review feedback for ${name} (version ${short(one.body.digest)}${one.body.current ? ", your latest published version" : ", not your latest version"}) saved in ${dir}/: ${written.join(", ")}.`);
|
|
29953
|
+
const md = rendered["feedback.md"];
|
|
29954
|
+
if (typeof md === "string" && md) say(`
|
|
29955
|
+
${md.trim()}
|
|
29956
|
+
`);
|
|
29957
|
+
say(`To fix the skill, follow ${dir}/fix-pack.json (what is wrong, where in the files, how to fix). Then try it once and submit the new version; a nominated skill is reviewed again automatically.`);
|
|
29958
|
+
return 0;
|
|
29959
|
+
}
|
|
29960
|
+
async function ownReviewFeedback(env, opts = {}) {
|
|
29961
|
+
if (env.anonymous || !env.turnId) return null;
|
|
29962
|
+
let got = await call(env, "POST", { actor: env.actorId });
|
|
29963
|
+
const deadline = Date.now() + (opts.wait ?? 15) * 1e3;
|
|
29964
|
+
while (got !== "unreachable" && got.status === 200 && got.body.state === "queued" && Date.now() < deadline) {
|
|
29965
|
+
await new Promise((r) => setTimeout(r, opts.pollMs ?? 1e3));
|
|
29966
|
+
got = await call(env, "GET", {});
|
|
29967
|
+
}
|
|
29968
|
+
if (got === "unreachable" || got.status !== 200 || got.body.state !== "granted") return null;
|
|
29969
|
+
const list = got.body.skills ? got : await call(env, "GET", {});
|
|
29970
|
+
if (list === "unreachable" || list.status !== 200 || !list.body.skills) return null;
|
|
29971
|
+
const out = /* @__PURE__ */ new Map();
|
|
29972
|
+
for (const s of list.body.skills) {
|
|
29973
|
+
if (!out.has(s.name) && DIGEST_RE2.test(s.digest)) out.set(s.name, { digest: s.digest, current: s.current, updated_at: s.updated_at });
|
|
29974
|
+
}
|
|
29975
|
+
return out;
|
|
29976
|
+
}
|
|
29977
|
+
|
|
29257
29978
|
// cli/src/skills/manage.ts
|
|
29258
|
-
import { existsSync as
|
|
29259
|
-
import { join as
|
|
29979
|
+
import { existsSync as existsSync7, readdirSync as readdirSync4, rmSync as rmSync2 } from "node:fs";
|
|
29980
|
+
import { join as join9 } from "node:path";
|
|
29260
29981
|
function scanLocal(dir) {
|
|
29261
|
-
if (!
|
|
29982
|
+
if (!existsSync7(dir)) return [];
|
|
29262
29983
|
const out = [];
|
|
29263
29984
|
for (const e of readdirSync4(dir, { withFileTypes: true })) {
|
|
29264
29985
|
if (!e.isDirectory() || e.name.startsWith(".")) continue;
|
|
29265
|
-
const path =
|
|
29986
|
+
const path = join9(dir, e.name);
|
|
29266
29987
|
if (!skillDirExists(path)) continue;
|
|
29267
29988
|
const src = readSource(path);
|
|
29268
29989
|
if (src) {
|
|
@@ -29298,14 +30019,17 @@ var STATUS_LABEL = { latest: "latest", outdated: "update available", unavailable
|
|
|
29298
30019
|
async function cmdList(opts) {
|
|
29299
30020
|
const env = detectEnv();
|
|
29300
30021
|
const local = scanLocal(opts.dir);
|
|
30022
|
+
const feedback = !opts.offline && local.some((s) => s.kind === "own") ? ownReviewFeedback(env, { pollMs: opts.pollMs }) : Promise.resolve(null);
|
|
29301
30023
|
const rows = await Promise.all(
|
|
29302
30024
|
local.map(async (s) => {
|
|
29303
30025
|
const st = opts.offline ? { status: "unknown", handle: void 0 } : await statusOf(env, s);
|
|
29304
30026
|
return { ...s, ...st, handle: s.handle ?? st.handle };
|
|
29305
30027
|
})
|
|
29306
30028
|
);
|
|
30029
|
+
const fb = await feedback;
|
|
30030
|
+
const withFeedback = rows.map((r) => r.kind === "own" && fb?.has(r.name) ? { ...r, review_feedback: fb.get(r.name) } : r);
|
|
29307
30031
|
if (opts.json) {
|
|
29308
|
-
console.log(JSON.stringify({ dir: opts.dir, count: rows.length, skills:
|
|
30032
|
+
console.log(JSON.stringify({ dir: opts.dir, count: rows.length, skills: withFeedback }));
|
|
29309
30033
|
return 0;
|
|
29310
30034
|
}
|
|
29311
30035
|
if (rows.length === 0) {
|
|
@@ -29313,14 +30037,21 @@ async function cmdList(opts) {
|
|
|
29313
30037
|
return 0;
|
|
29314
30038
|
}
|
|
29315
30039
|
console.log(`${opts.dir}/ (${rows.length}):`);
|
|
29316
|
-
for (const r of
|
|
30040
|
+
for (const r of withFeedback) {
|
|
29317
30041
|
const st = STATUS_LABEL[r.status] ? ` [${STATUS_LABEL[r.status]}]` : "";
|
|
29318
30042
|
const from = r.id ? ` ${displayRef(r)}` : "";
|
|
29319
|
-
|
|
30043
|
+
const rf = "review_feedback" in r ? " [Neta review feedback]" : "";
|
|
30044
|
+
console.log(` ${r.name.padEnd(28)} ${KIND_LABEL[r.kind]}${st}${from}${rf}`);
|
|
29320
30045
|
}
|
|
29321
30046
|
const outdated = rows.filter((r) => r.status === "outdated").length;
|
|
29322
30047
|
if (outdated > 0) console.log(`
|
|
29323
30048
|
${outdated} with updates available: neta-search skills update`);
|
|
30049
|
+
const reviewed = withFeedback.filter((r) => "review_feedback" in r).map((r) => r.name);
|
|
30050
|
+
if (reviewed.length > 0) {
|
|
30051
|
+
console.log(`
|
|
30052
|
+
Neta reviewed ${reviewed.length === 1 ? "this skill" : "these skills"} and left feedback on what to fix: ${reviewed.join(", ")}.`);
|
|
30053
|
+
console.log(`Read it with: neta-search skills feedback ${reviewed.length === 1 ? reviewed[0] : "<name>"}`);
|
|
30054
|
+
}
|
|
29324
30055
|
return 0;
|
|
29325
30056
|
}
|
|
29326
30057
|
async function cmdUpdate(names, opts) {
|
|
@@ -29378,7 +30109,7 @@ async function cmdUpdate(names, opts) {
|
|
|
29378
30109
|
}
|
|
29379
30110
|
if (results.length === 0) console.log(`No installed skills in ${opts.dir}/; nothing to update.`);
|
|
29380
30111
|
const say = {
|
|
29381
|
-
updated: (r) => `Updated ${r.name} (${
|
|
30112
|
+
updated: (r) => `Updated ${r.name} (${short2(r.from)} \u2192 ${short2(r.to)})`,
|
|
29382
30113
|
latest: (r) => `${r.name} is up to date`,
|
|
29383
30114
|
unavailable: (r) => `${r.name} is no longer available from its source; keeping the local copy`,
|
|
29384
30115
|
own_skipped: (r) => `${r.name} is your own skill; nothing to update`,
|
|
@@ -29389,7 +30120,7 @@ async function cmdUpdate(names, opts) {
|
|
|
29389
30120
|
for (const r of results) console.log(say[r.result](r));
|
|
29390
30121
|
return failed > 0 ? 1 : 0;
|
|
29391
30122
|
}
|
|
29392
|
-
function
|
|
30123
|
+
function short2(d) {
|
|
29393
30124
|
return (d ?? "").replace(/^sha256:/, "").slice(0, 12) || "?";
|
|
29394
30125
|
}
|
|
29395
30126
|
async function cmdRemove(names, opts) {
|
|
@@ -29397,7 +30128,7 @@ async function cmdRemove(names, opts) {
|
|
|
29397
30128
|
const local = scanLocal(opts.dir);
|
|
29398
30129
|
const results = [];
|
|
29399
30130
|
for (const n of names) {
|
|
29400
|
-
const found = (await findLocal(env, local, n)).filter((s) =>
|
|
30131
|
+
const found = (await findLocal(env, local, n)).filter((s) => existsSync7(s.path));
|
|
29401
30132
|
if (found.length === 0) results.push({ name: n, result: "not_found" });
|
|
29402
30133
|
for (const s of found) {
|
|
29403
30134
|
if (s.kind === "own" && !opts.force) {
|
|
@@ -29412,8 +30143,8 @@ async function cmdRemove(names, opts) {
|
|
|
29412
30143
|
if (opts.json) console.log(JSON.stringify({ results }));
|
|
29413
30144
|
else
|
|
29414
30145
|
for (const r of results) {
|
|
29415
|
-
if (r.result === "removed") console.log(`Removed ${
|
|
29416
|
-
else if (r.result === "not_found") console.error(`${
|
|
30146
|
+
if (r.result === "removed") console.log(`Removed ${join9(opts.dir, r.name)}`);
|
|
30147
|
+
else if (r.result === "not_found") console.error(`${join9(opts.dir, r.name)} does not exist`);
|
|
29417
30148
|
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.`);
|
|
29418
30149
|
}
|
|
29419
30150
|
return failed > 0 ? 1 : 0;
|
|
@@ -29422,7 +30153,7 @@ async function cmdRemove(names, opts) {
|
|
|
29422
30153
|
// cli/src/cli.ts
|
|
29423
30154
|
var program2 = new Command();
|
|
29424
30155
|
program2.name("neta-search").description("neta-search: search the Cohub skill ecosystem").version(cliVersion());
|
|
29425
|
-
var skills = program2.command("skills").description("skills: find / add / use / list / update / remove / init / submit");
|
|
30156
|
+
var skills = program2.command("skills").description("skills: find / add / use / list / update / remove / init / lint / submit / feedback");
|
|
29426
30157
|
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) => {
|
|
29427
30158
|
await run(cmdFind(query, { limit: Number(opts.limit) || 20, json: opts.json }));
|
|
29428
30159
|
});
|
|
@@ -29432,10 +30163,16 @@ skills.command("add").description("install a skill: an id or handle from find do
|
|
|
29432
30163
|
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) => {
|
|
29433
30164
|
await run(cmdUse(idOrName, opts));
|
|
29434
30165
|
});
|
|
30166
|
+
skills.command("lint").description("static check of a skill folder before you submit it: errors are what the skills site would reject; warnings are suggestions (paths, test declarations, common writing problems). Runs locally, sends nothing").argument("[name-or-dir]", "skill name (.agents/skills/<name> or your drafts) or a folder; default: the current folder").option("--json", "compact JSON output (for agents)").action(async (arg, opts) => {
|
|
30167
|
+
await run(cmdLint(arg, { json: !!opts.json }));
|
|
30168
|
+
});
|
|
29435
30169
|
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) => {
|
|
29436
30170
|
const wait = Number(opts.wait);
|
|
29437
30171
|
await run(cmdSubmit(name, { json: !!opts.json, withdraw: !!opts.withdraw, wait: Number.isFinite(wait) && wait >= 0 ? wait : 60 }));
|
|
29438
30172
|
});
|
|
30173
|
+
skills.command("feedback").description("read Neta's review feedback on your own skills (only the space owner, from a chat in this space). No name: list the skills that have feedback; a name: save the latest feedback to .neta-search/skill-feedback/<name>/ and print it").argument("[name]", "skill directory name").option("--digest <sha256>", "a specific version (sha256:<hex>) instead of the latest one with feedback").option("--json", "compact JSON output (for agents)").action(async (name, opts) => {
|
|
30174
|
+
await run(cmdFeedback(name, { json: !!opts.json, digest: opts.digest }));
|
|
30175
|
+
});
|
|
29439
30176
|
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) => {
|
|
29440
30177
|
await run(cmdList({ dir: opts.dir, offline: !!opts.offline, json: !!opts.json }));
|
|
29441
30178
|
});
|