@arp-protocol/cli 0.0.3 → 0.0.5
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/CHANGELOG.md +36 -1
- package/README.md +5 -2
- package/dist/chunks/{backfill-awengrsd.js → backfill-kzgeaffa.js} +3 -1
- package/dist/chunks/check-k4qnc9ee.js +2011 -0
- package/dist/chunks/cli-7c393v47.js +173 -0
- package/dist/chunks/{cli-wev0jgf7.js → cli-g3q6fxm0.js} +929 -983
- package/dist/chunks/{cli-10wacn76.js → cli-jew4syvf.js} +15 -173
- package/dist/chunks/cli-kda5hz4w.js +23 -0
- package/dist/chunks/cli-zehr6kdx.js +719 -0
- package/dist/chunks/{commands-157bms70.js → commands-vvrdahbr.js} +43 -13
- package/dist/chunks/{line-691gbaha.js → line-wra91qkr.js} +54 -5
- package/dist/chunks/{node-m0mx4y5k.js → node-2rw44jrf.js} +2 -1
- package/dist/chunks/{node-init-rcepfyeh.js → node-init-rmrmmg59.js} +2 -1
- package/dist/chunks/{server-59zry2jp.js → server-bcsxfd7q.js} +197 -112
- package/dist/chunks/share-k1dnzsna.js +219 -0
- package/dist/cli.js +69 -19
- package/dist/hook.js +949 -167
- package/package.json +2 -2
- package/dist/chunks/share-vtbkzs5f.js +0 -104
package/dist/hook.js
CHANGED
|
@@ -14,9 +14,42 @@ function appendOwn(path, text) {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
// ../hook/src/hook.ts
|
|
17
|
-
import { existsSync as
|
|
17
|
+
import { existsSync as existsSync4, lstatSync as lstatSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
18
18
|
import { homedir as homedir3 } from "node:os";
|
|
19
|
-
import { dirname as dirname4, join as
|
|
19
|
+
import { dirname as dirname4, join as join6 } from "node:path";
|
|
20
|
+
// ../../packages/protocol/dist/declare/tool-thing.js
|
|
21
|
+
class InvalidToolUrlError extends Error {
|
|
22
|
+
input;
|
|
23
|
+
constructor(input, why) {
|
|
24
|
+
super(`${JSON.stringify(input)} is not a usable tool URL: ${why}`);
|
|
25
|
+
this.name = "InvalidToolUrlError";
|
|
26
|
+
this.input = input;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function normaliseToolUrl(input) {
|
|
30
|
+
const trimmed = input.trim();
|
|
31
|
+
if (trimmed.length === 0)
|
|
32
|
+
throw new InvalidToolUrlError(input, "empty");
|
|
33
|
+
const withScheme = /^[a-z][a-z0-9+.-]*:/i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
34
|
+
let url;
|
|
35
|
+
try {
|
|
36
|
+
url = new URL(withScheme);
|
|
37
|
+
} catch {
|
|
38
|
+
throw new InvalidToolUrlError(input, "not parseable as a URL");
|
|
39
|
+
}
|
|
40
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
41
|
+
throw new InvalidToolUrlError(input, `scheme ${url.protocol} is not http or https`);
|
|
42
|
+
}
|
|
43
|
+
if (url.hostname.length === 0)
|
|
44
|
+
throw new InvalidToolUrlError(input, "no host");
|
|
45
|
+
const host = url.hostname.replace(/\.+$/, "").replace(/^(?:www\.)+/, "");
|
|
46
|
+
const path = url.pathname.replace(/(?:\.git|\/)+$/, "");
|
|
47
|
+
const params = [...new URLSearchParams(url.search).entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
48
|
+
const query = params.length === 0 ? "" : `?${new URLSearchParams(params).toString()}`;
|
|
49
|
+
const port = url.port === "" || url.port === "80" || url.port === "443" ? "" : `:${url.port}`;
|
|
50
|
+
return `https://${host}${port}${path}${query}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
20
53
|
// ../../packages/graphql/dist/http.js
|
|
21
54
|
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
22
55
|
var DEFAULT_MAX_BODY_BYTES = 8 * 1024 * 1024;
|
|
@@ -504,38 +537,6 @@ var X402_REASONS_QUERY = `query X402Reasons($ids: [String!]!, $because: String!,
|
|
|
504
537
|
${LEVEL}
|
|
505
538
|
}
|
|
506
539
|
}`;
|
|
507
|
-
// ../../packages/protocol/dist/declare/tool-thing.js
|
|
508
|
-
class InvalidToolUrlError extends Error {
|
|
509
|
-
input;
|
|
510
|
-
constructor(input, why) {
|
|
511
|
-
super(`${JSON.stringify(input)} is not a usable tool URL: ${why}`);
|
|
512
|
-
this.name = "InvalidToolUrlError";
|
|
513
|
-
this.input = input;
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
function normaliseToolUrl(input) {
|
|
517
|
-
const trimmed = input.trim();
|
|
518
|
-
if (trimmed.length === 0)
|
|
519
|
-
throw new InvalidToolUrlError(input, "empty");
|
|
520
|
-
const withScheme = /^[a-z][a-z0-9+.-]*:/i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
521
|
-
let url;
|
|
522
|
-
try {
|
|
523
|
-
url = new URL(withScheme);
|
|
524
|
-
} catch {
|
|
525
|
-
throw new InvalidToolUrlError(input, "not parseable as a URL");
|
|
526
|
-
}
|
|
527
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
528
|
-
throw new InvalidToolUrlError(input, `scheme ${url.protocol} is not http or https`);
|
|
529
|
-
}
|
|
530
|
-
if (url.hostname.length === 0)
|
|
531
|
-
throw new InvalidToolUrlError(input, "no host");
|
|
532
|
-
const host = url.hostname.replace(/\.+$/, "").replace(/^(?:www\.)+/, "");
|
|
533
|
-
const path = url.pathname.replace(/(?:\.git|\/)+$/, "");
|
|
534
|
-
const params = [...new URLSearchParams(url.search).entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
535
|
-
const query = params.length === 0 ? "" : `?${new URLSearchParams(params).toString()}`;
|
|
536
|
-
const port = url.port === "" || url.port === "80" || url.port === "443" ? "" : `:${url.port}`;
|
|
537
|
-
return `https://${host}${port}${path}${query}`;
|
|
538
|
-
}
|
|
539
540
|
// ../../packages/protocol/dist/session.js
|
|
540
541
|
function refForInvocation(name, source, fetched) {
|
|
541
542
|
const bare = name.includes(":") ? name.split(":").pop() ?? name : name;
|
|
@@ -609,9 +610,45 @@ var COMMON = new Set(("awk bash break bun case cat cd chmod command continue cp
|
|
|
609
610
|
var INSTALL = /\b(?:npm\s+(?:i|install)\s+(?:-g|--global)|pnpm\s+add\s+-g|yarn\s+global\s+add|brew\s+install|pipx\s+install|pip3?\s+install|cargo\s+install)\s+([@a-z0-9][@\w./-]*)/i;
|
|
610
611
|
var PROBE = /\b(?:command\s+-v|which)\s+([a-z][\w-]*)/i;
|
|
611
612
|
var COMMAND = /^[a-z][a-z0-9_-]*$/;
|
|
613
|
+
function shellSegments(line) {
|
|
614
|
+
const segments = [];
|
|
615
|
+
let current = "";
|
|
616
|
+
let quote = null;
|
|
617
|
+
for (let index = 0;index < line.length; index += 1) {
|
|
618
|
+
const char = line[index] ?? "";
|
|
619
|
+
if (char === "\\" && quote !== "'") {
|
|
620
|
+
current += char + (line[index + 1] ?? "");
|
|
621
|
+
index += 1;
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
if (quote !== null) {
|
|
625
|
+
if (char === quote)
|
|
626
|
+
quote = null;
|
|
627
|
+
current += char;
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
if (char === '"' || char === "'") {
|
|
631
|
+
quote = char;
|
|
632
|
+
current += char;
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
if (char === "#" && (index === 0 || /\s/.test(line[index - 1] ?? "")))
|
|
636
|
+
break;
|
|
637
|
+
if (char === "|" || char === ";" || char === "&" && line[index + 1] === "&") {
|
|
638
|
+
segments.push(current);
|
|
639
|
+
current = "";
|
|
640
|
+
if (line[index + 1] === char)
|
|
641
|
+
index += 1;
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
current += char;
|
|
645
|
+
}
|
|
646
|
+
segments.push(current);
|
|
647
|
+
return segments;
|
|
648
|
+
}
|
|
612
649
|
function commandsIn(shell, explicitOnly) {
|
|
613
650
|
const found = [];
|
|
614
|
-
for (const segment of shell
|
|
651
|
+
for (const segment of shellSegments(shell)) {
|
|
615
652
|
const line = segment.trim().replace(/^\$\s+/, "");
|
|
616
653
|
if (line === "" || line.startsWith("#"))
|
|
617
654
|
continue;
|
|
@@ -646,7 +683,7 @@ function shellLinesRun(lines) {
|
|
|
646
683
|
run.push(line);
|
|
647
684
|
continued = line.trimEnd().endsWith("\\");
|
|
648
685
|
heredocEnd = /<<-?\s*['"]?([A-Za-z_]+)/.exec(line)?.[1] ?? null;
|
|
649
|
-
for (const char of line) {
|
|
686
|
+
for (const char of quote === null ? shellSegments(line).join(";") : line) {
|
|
650
687
|
if (quote === null && (char === '"' || char === "'"))
|
|
651
688
|
quote = char;
|
|
652
689
|
else if (char === quote)
|
|
@@ -749,12 +786,21 @@ function installedFromReceipts(text) {
|
|
|
749
786
|
return found;
|
|
750
787
|
}
|
|
751
788
|
// ../hook/src/classify.ts
|
|
789
|
+
function isFailure(outcome) {
|
|
790
|
+
return outcome !== "ok" && outcome !== "payment_required";
|
|
791
|
+
}
|
|
752
792
|
var CANCELLED = /\b(interrupted|cancel(l)?ed|aborted) by (the )?user\b|\buser (rejected|denied|declined)\b|\bpermission denied by user\b|\buser doesn't want to (proceed|take this action)\b/i;
|
|
753
793
|
var AUTH = /\b(unauthori[sz]ed|forbidden|invalid (api )?(key|token)|authentication|not authenticated|expired token)\b/i;
|
|
754
794
|
var TIMEOUT = /\b(timed? ?out|timeout|deadline exceeded|ETIMEDOUT)\b/i;
|
|
755
795
|
var UNREACHABLE = /\b(ENOTFOUND|EAI_AGAIN|ECONNREFUSED|ECONNRESET|EHOSTUNREACH|ENETUNREACH|getaddrinfo|certificate|TLS|SSL|socket hang up|connection refused|fetch failed)\b/i;
|
|
756
|
-
var
|
|
757
|
-
var
|
|
796
|
+
var CHALLENGE = /\bpayment required\b|"x402Version"[\s\S]{0,2000}"accepts"|"accepts"[\s\S]{0,2000}"x402Version"/i;
|
|
797
|
+
var REFUSED = /\binsufficient[ _](funds|balance)\b|\bpayment (failed|refused|rejected|declined|invalid)\b|\bsettlement failed\b/i;
|
|
798
|
+
var REFUSED_IN_X402 = /"success"\s*:\s*false|"errorReason"\s*:\s*"[^"]+"|\binvalid_[a-z_]*(signature|payload|payment)[a-z_]*\b/i;
|
|
799
|
+
var ABOUT_PAYMENT = /\bx402|\bpayment\b/i;
|
|
800
|
+
var TRANSACTION = /^(0x[0-9a-fA-F]{64}|[1-9A-HJ-NP-Za-km-z]{64,90})$/;
|
|
801
|
+
var FLAT_OBJECT = /\{[^{}]{0,2000}\}/g;
|
|
802
|
+
var RESPONSE_HEADER = /\b(?:x-)?payment-response\s*[:=]\s*"?([A-Za-z0-9+/=_-]{16,4000})/gi;
|
|
803
|
+
var STATUS = /\b(40[123]|429|5\d\d)\b/g;
|
|
758
804
|
function isRecord2(value) {
|
|
759
805
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
760
806
|
}
|
|
@@ -795,21 +841,96 @@ function statusOf(call) {
|
|
|
795
841
|
function flaggedError(response) {
|
|
796
842
|
return isRecord2(response) && (response.isError === true || response.is_error === true);
|
|
797
843
|
}
|
|
844
|
+
function isSettled(value) {
|
|
845
|
+
return isRecord2(value) && value.success === true && typeof value.network === "string" && typeof value.transaction === "string" && TRANSACTION.test(value.transaction);
|
|
846
|
+
}
|
|
847
|
+
function stringsOf(value, depth = 0, out = []) {
|
|
848
|
+
if (typeof value === "string")
|
|
849
|
+
out.push(value);
|
|
850
|
+
else if (depth < 4 && Array.isArray(value))
|
|
851
|
+
for (const item of value)
|
|
852
|
+
stringsOf(item, depth + 1, out);
|
|
853
|
+
else if (depth < 4 && isRecord2(value))
|
|
854
|
+
for (const item of Object.values(value))
|
|
855
|
+
stringsOf(item, depth + 1, out);
|
|
856
|
+
return out;
|
|
857
|
+
}
|
|
858
|
+
function isUnsettled(value) {
|
|
859
|
+
return isRecord2(value) && value.success === false && typeof value.network === "string";
|
|
860
|
+
}
|
|
861
|
+
function anyIn(value, test, depth = 0) {
|
|
862
|
+
if (test(value))
|
|
863
|
+
return true;
|
|
864
|
+
if (depth >= 4)
|
|
865
|
+
return false;
|
|
866
|
+
if (Array.isArray(value))
|
|
867
|
+
return value.some((item) => anyIn(item, test, depth + 1));
|
|
868
|
+
if (isRecord2(value))
|
|
869
|
+
return Object.values(value).some((item) => anyIn(item, test, depth + 1));
|
|
870
|
+
return false;
|
|
871
|
+
}
|
|
872
|
+
function parsed(text) {
|
|
873
|
+
try {
|
|
874
|
+
return JSON.parse(text);
|
|
875
|
+
} catch {
|
|
876
|
+
return null;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
function decoded(base64) {
|
|
880
|
+
try {
|
|
881
|
+
return Buffer.from(base64.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf8");
|
|
882
|
+
} catch {
|
|
883
|
+
return "";
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
function settledInText(text) {
|
|
887
|
+
for (const [object] of text.matchAll(FLAT_OBJECT))
|
|
888
|
+
if (isSettled(parsed(object)))
|
|
889
|
+
return true;
|
|
890
|
+
for (const match of text.matchAll(RESPONSE_HEADER))
|
|
891
|
+
if (isSettled(parsed(decoded(match[1] ?? ""))))
|
|
892
|
+
return true;
|
|
893
|
+
return false;
|
|
894
|
+
}
|
|
895
|
+
function settlementIn(call) {
|
|
896
|
+
if (anyIn(call.response, isSettled))
|
|
897
|
+
return true;
|
|
898
|
+
const texts = [...call.error === null ? [] : [call.error], ...stringsOf(call.response)];
|
|
899
|
+
return texts.some(settledInText);
|
|
900
|
+
}
|
|
901
|
+
function emptyContent(response) {
|
|
902
|
+
const blocks = Array.isArray(response) ? response : isRecord2(response) && Array.isArray(response.content) ? response.content : null;
|
|
903
|
+
if (typeof response === "string")
|
|
904
|
+
return response.trim().length === 0;
|
|
905
|
+
if (blocks === null)
|
|
906
|
+
return false;
|
|
907
|
+
return blocks.every((block) => !isRecord2(block) || typeof block.text !== "string" || block.text.trim().length === 0 || isSettled(parsed(block.text)));
|
|
908
|
+
}
|
|
798
909
|
function classifyCall(call) {
|
|
799
910
|
if (call.interrupted)
|
|
800
911
|
return null;
|
|
801
912
|
const failed = call.event === "PostToolUseFailure" || call.error !== null || flaggedError(call.response);
|
|
802
913
|
const status = statusOf(call);
|
|
803
|
-
if (!failed && (status === null || status < 400))
|
|
804
|
-
return { outcome: "ok", code: null };
|
|
914
|
+
if (!failed && (status === null || status < 400)) {
|
|
915
|
+
return settlementIn(call) && emptyContent(call.response) ? { outcome: "paid_no_delivery", code: null } : { outcome: "ok", code: null };
|
|
916
|
+
}
|
|
805
917
|
const text = failureText(call);
|
|
806
918
|
if (CANCELLED.test(text))
|
|
807
919
|
return null;
|
|
808
|
-
const found = STATUS.
|
|
809
|
-
const
|
|
920
|
+
const found = [...text.matchAll(STATUS)].map((one) => Number(one[0]));
|
|
921
|
+
const after = status !== null && status !== 402 ? status : found.find((one) => one !== 402) ?? null;
|
|
922
|
+
const challenge = status === 402 || status === null && (found.includes(402) || CHALLENGE.test(text));
|
|
923
|
+
const match = after ?? (challenge ? 402 : null);
|
|
810
924
|
const code = match === null ? null : String(match);
|
|
811
|
-
if (
|
|
812
|
-
return { outcome: "
|
|
925
|
+
if (settlementIn(call))
|
|
926
|
+
return { outcome: "paid_no_delivery", code: after === null ? null : String(after) };
|
|
927
|
+
const refused = anyIn(call.response, isUnsettled) || REFUSED.test(text) || (challenge || ABOUT_PAYMENT.test(text)) && REFUSED_IN_X402.test(text);
|
|
928
|
+
if (refused) {
|
|
929
|
+
return { outcome: "payment_refused", code };
|
|
930
|
+
}
|
|
931
|
+
if (challenge && after === null && !TIMEOUT.test(text) && !UNREACHABLE.test(text)) {
|
|
932
|
+
return { outcome: "payment_required", code };
|
|
933
|
+
}
|
|
813
934
|
if (match === 401 || match === 403 || AUTH.test(text))
|
|
814
935
|
return { outcome: "auth", code };
|
|
815
936
|
if (TIMEOUT.test(text))
|
|
@@ -1041,18 +1162,18 @@ function toolCallOf(raw) {
|
|
|
1041
1162
|
|
|
1042
1163
|
// ../../packages/protocol/src/content-path.ts
|
|
1043
1164
|
function skillRefFromUrl3(url) {
|
|
1044
|
-
let
|
|
1165
|
+
let parsed2;
|
|
1045
1166
|
try {
|
|
1046
|
-
|
|
1167
|
+
parsed2 = new URL(url.includes("://") ? url : `https://${url}`);
|
|
1047
1168
|
} catch {
|
|
1048
1169
|
return null;
|
|
1049
1170
|
}
|
|
1050
|
-
if (
|
|
1171
|
+
if (parsed2.hostname.replace(/^www\./, "") !== "github.com")
|
|
1051
1172
|
return null;
|
|
1052
|
-
const skill =
|
|
1173
|
+
const skill = parsed2.searchParams.get("skill");
|
|
1053
1174
|
if (skill === null || skill.length === 0)
|
|
1054
1175
|
return null;
|
|
1055
|
-
const [owner, repo] =
|
|
1176
|
+
const [owner, repo] = parsed2.pathname.replace(/^\/+/, "").replace(/\/+$/, "").replace(/\.git$/, "").split("/");
|
|
1056
1177
|
if (owner === undefined || repo === undefined)
|
|
1057
1178
|
return null;
|
|
1058
1179
|
if (owner.length === 0 || repo.length === 0)
|
|
@@ -1122,9 +1243,45 @@ var COMMON2 = new Set(("awk bash break bun case cat cd chmod command continue cp
|
|
|
1122
1243
|
var INSTALL2 = /\b(?:npm\s+(?:i|install)\s+(?:-g|--global)|pnpm\s+add\s+-g|yarn\s+global\s+add|brew\s+install|pipx\s+install|pip3?\s+install|cargo\s+install)\s+([@a-z0-9][@\w./-]*)/i;
|
|
1123
1244
|
var PROBE2 = /\b(?:command\s+-v|which)\s+([a-z][\w-]*)/i;
|
|
1124
1245
|
var COMMAND2 = /^[a-z][a-z0-9_-]*$/;
|
|
1246
|
+
function shellSegments2(line) {
|
|
1247
|
+
const segments = [];
|
|
1248
|
+
let current = "";
|
|
1249
|
+
let quote = null;
|
|
1250
|
+
for (let index = 0;index < line.length; index += 1) {
|
|
1251
|
+
const char = line[index] ?? "";
|
|
1252
|
+
if (char === "\\" && quote !== "'") {
|
|
1253
|
+
current += char + (line[index + 1] ?? "");
|
|
1254
|
+
index += 1;
|
|
1255
|
+
continue;
|
|
1256
|
+
}
|
|
1257
|
+
if (quote !== null) {
|
|
1258
|
+
if (char === quote)
|
|
1259
|
+
quote = null;
|
|
1260
|
+
current += char;
|
|
1261
|
+
continue;
|
|
1262
|
+
}
|
|
1263
|
+
if (char === '"' || char === "'") {
|
|
1264
|
+
quote = char;
|
|
1265
|
+
current += char;
|
|
1266
|
+
continue;
|
|
1267
|
+
}
|
|
1268
|
+
if (char === "#" && (index === 0 || /\s/.test(line[index - 1] ?? "")))
|
|
1269
|
+
break;
|
|
1270
|
+
if (char === "|" || char === ";" || char === "&" && line[index + 1] === "&") {
|
|
1271
|
+
segments.push(current);
|
|
1272
|
+
current = "";
|
|
1273
|
+
if (line[index + 1] === char)
|
|
1274
|
+
index += 1;
|
|
1275
|
+
continue;
|
|
1276
|
+
}
|
|
1277
|
+
current += char;
|
|
1278
|
+
}
|
|
1279
|
+
segments.push(current);
|
|
1280
|
+
return segments;
|
|
1281
|
+
}
|
|
1125
1282
|
function commandsIn2(shell, explicitOnly) {
|
|
1126
1283
|
const found = [];
|
|
1127
|
-
for (const segment of shell
|
|
1284
|
+
for (const segment of shellSegments2(shell)) {
|
|
1128
1285
|
const line = segment.trim().replace(/^\$\s+/, "");
|
|
1129
1286
|
if (line === "" || line.startsWith("#"))
|
|
1130
1287
|
continue;
|
|
@@ -1159,7 +1316,7 @@ function shellLinesRun2(lines) {
|
|
|
1159
1316
|
run.push(line);
|
|
1160
1317
|
continued = line.trimEnd().endsWith("\\");
|
|
1161
1318
|
heredocEnd = /<<-?\s*['"]?([A-Za-z_]+)/.exec(line)?.[1] ?? null;
|
|
1162
|
-
for (const char of line) {
|
|
1319
|
+
for (const char of quote === null ? shellSegments2(line).join(";") : line) {
|
|
1163
1320
|
if (quote === null && (char === '"' || char === "'"))
|
|
1164
1321
|
quote = char;
|
|
1165
1322
|
else if (char === quote)
|
|
@@ -1212,6 +1369,223 @@ function readRequires2(markdown) {
|
|
|
1212
1369
|
return { cli, runs, login: null, description };
|
|
1213
1370
|
}
|
|
1214
1371
|
|
|
1372
|
+
// ../../packages/protocol/src/skill-needs.ts
|
|
1373
|
+
var MARKDOWN = /\.(md|mdx|markdown)$/i;
|
|
1374
|
+
var SCRIPT = /\.(sh|bash|zsh|py|js|mjs|cjs|ts|rb|pl|ps1|php|lua)$/i;
|
|
1375
|
+
var COMMENT_LINE = /^\s*(?:#|\/\/|\*|--|;|rem\s)/i;
|
|
1376
|
+
function isTestFile2(path) {
|
|
1377
|
+
return /(^|\/)(tests?|__tests__|spec|fixtures?)\/|\.(test|spec)\.[a-z]+$|(^|\/)test_[^/]+\.py$|_test\.(py|go|rb)$/i.test(path);
|
|
1378
|
+
}
|
|
1379
|
+
function isSecretName2(name) {
|
|
1380
|
+
return /(^|_)(API_?KEY|KEY|TOKEN|SECRET|PASSWORD|PASSWD|PASS|CREDENTIALS|CREDS)$/.test(name);
|
|
1381
|
+
}
|
|
1382
|
+
var SET_IN_SKILL_MD = /(?:^|\s|`)(?:export\s+|set\s+|\$env:)?([A-Z][A-Z0-9_]{2,})=/g;
|
|
1383
|
+
function lineAt(text, index) {
|
|
1384
|
+
let line = 1;
|
|
1385
|
+
for (let at = text.indexOf(`
|
|
1386
|
+
`);at !== -1 && at < index; at = text.indexOf(`
|
|
1387
|
+
`, at + 1))
|
|
1388
|
+
line += 1;
|
|
1389
|
+
return line;
|
|
1390
|
+
}
|
|
1391
|
+
function keysIn2(files, settings) {
|
|
1392
|
+
const skillMd = files.find((file) => file.path === "SKILL.md");
|
|
1393
|
+
const toSet = new Set;
|
|
1394
|
+
if (skillMd !== undefined)
|
|
1395
|
+
for (const match of skillMd.text.matchAll(SET_IN_SKILL_MD))
|
|
1396
|
+
toSet.add(match[1] ?? "");
|
|
1397
|
+
const wanted = new Set(settings.filter((name) => isSecretName2(name) || toSet.has(name)));
|
|
1398
|
+
const ordered = [
|
|
1399
|
+
...skillMd === undefined ? [] : [skillMd],
|
|
1400
|
+
...files.filter((file) => file.path !== "SKILL.md" && !isTestFile2(file.path))
|
|
1401
|
+
];
|
|
1402
|
+
const found = new Map;
|
|
1403
|
+
for (const file of ordered) {
|
|
1404
|
+
for (const [index, line] of file.text.split(`
|
|
1405
|
+
`).entries()) {
|
|
1406
|
+
for (const match of line.matchAll(/\b[A-Z][A-Z0-9_]{2,}\b/g)) {
|
|
1407
|
+
const name = match[0];
|
|
1408
|
+
if (!wanted.has(name) || found.has(name))
|
|
1409
|
+
continue;
|
|
1410
|
+
found.set(name, { name, why: isSecretName2(name) ? "secret" : "set in SKILL.md", file: file.path, line: index + 1 });
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
return [...found.values()].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
1415
|
+
}
|
|
1416
|
+
var EVERYWHERE = new Set("sh bash zsh dash env cat ls echo printf mkdir rm cp mv ln grep sed awk tr cut head tail sort uniq wc find xargs test true false base64 date sleep chmod touch tee kill ps uname which open xdg-open cmd powershell pwsh taskkill taskkill.exe where where.exe".split(" "));
|
|
1417
|
+
var ALIASES = {
|
|
1418
|
+
soffice: "LibreOffice",
|
|
1419
|
+
libreoffice: "LibreOffice",
|
|
1420
|
+
"google-chrome": "Chrome",
|
|
1421
|
+
chromium: "Chrome",
|
|
1422
|
+
"chromium-browser": "Chrome",
|
|
1423
|
+
magick: "ImageMagick",
|
|
1424
|
+
convert: "ImageMagick",
|
|
1425
|
+
pdftoppm: "poppler",
|
|
1426
|
+
pdftotext: "poppler",
|
|
1427
|
+
python: "python3",
|
|
1428
|
+
docker: "Docker"
|
|
1429
|
+
};
|
|
1430
|
+
var SPAWNS = [
|
|
1431
|
+
/\b(?:execFileSync|execFile|spawnSync|spawn|execaSync|execa)\(\s*["'`]([a-z][\w.+-]*)["'`]/g,
|
|
1432
|
+
/\b(?:execSync|exec)\(\s*["'`]([a-z][\w.+-]*)[\s"'`]/g,
|
|
1433
|
+
/\bBun\.spawn(?:Sync)?\(\s*\[\s*["'`]([a-z][\w.+-]*)["'`]/g,
|
|
1434
|
+
/\bnew\s+Deno\.Command\(\s*["'`]([a-z][\w.+-]*)["'`]/g,
|
|
1435
|
+
/\bsubprocess\.(?:run|call|check_call|check_output|Popen)\(\s*\[\s*["']([a-z][\w.+-]*)["']/g,
|
|
1436
|
+
/\bsubprocess\.(?:run|call|check_call|check_output|Popen)\(\s*["']([a-z][\w.+-]*)[\s"']/g,
|
|
1437
|
+
/\bos\.(?:system|popen)\(\s*["']([a-z][\w.+-]*)[\s"']/g
|
|
1438
|
+
];
|
|
1439
|
+
var DRIVES = {
|
|
1440
|
+
puppeteer: "Chrome",
|
|
1441
|
+
"puppeteer-core": "Chrome",
|
|
1442
|
+
"chrome-launcher": "Chrome",
|
|
1443
|
+
"chrome-remote-interface": "Chrome",
|
|
1444
|
+
pyppeteer: "Chrome",
|
|
1445
|
+
playwright: "a Playwright browser",
|
|
1446
|
+
"playwright-core": "a Playwright browser",
|
|
1447
|
+
"@playwright/test": "a Playwright browser",
|
|
1448
|
+
"selenium-webdriver": "a browser and its driver",
|
|
1449
|
+
selenium: "a browser and its driver",
|
|
1450
|
+
"fluent-ffmpeg": "ffmpeg",
|
|
1451
|
+
"ffmpeg-python": "ffmpeg",
|
|
1452
|
+
ffmpeg: "ffmpeg",
|
|
1453
|
+
pydub: "ffmpeg",
|
|
1454
|
+
moviepy: "ffmpeg",
|
|
1455
|
+
"openai-whisper": "ffmpeg",
|
|
1456
|
+
whisper: "ffmpeg",
|
|
1457
|
+
pytesseract: "tesseract",
|
|
1458
|
+
"node-tesseract-ocr": "tesseract",
|
|
1459
|
+
pdf2image: "poppler",
|
|
1460
|
+
"pdf-poppler": "poppler",
|
|
1461
|
+
wand: "ImageMagick",
|
|
1462
|
+
gm: "ImageMagick",
|
|
1463
|
+
pypandoc: "pandoc",
|
|
1464
|
+
"node-pandoc": "pandoc",
|
|
1465
|
+
docker: "Docker",
|
|
1466
|
+
dockerode: "Docker"
|
|
1467
|
+
};
|
|
1468
|
+
var IMPORTS = [
|
|
1469
|
+
/\bfrom\s+["']([@\w./-]+)["']/g,
|
|
1470
|
+
/\bimport\s*\(\s*["']([@\w./-]+)["']\s*\)/g,
|
|
1471
|
+
/\bimport\s+["']([@\w./-]+)["']/g,
|
|
1472
|
+
/\brequire\(\s*["']([@\w./-]+)["']\s*\)/g,
|
|
1473
|
+
/^\s*(?:from\s+([\w.]+)\s+import|import\s+([\w.]+))/gm
|
|
1474
|
+
];
|
|
1475
|
+
function packageRoot(specifier) {
|
|
1476
|
+
if (specifier.startsWith("@"))
|
|
1477
|
+
return specifier.split("/").slice(0, 2).join("/");
|
|
1478
|
+
return specifier.split("/")[0]?.split(".")[0] ?? specifier;
|
|
1479
|
+
}
|
|
1480
|
+
var NAMED_PROGRAMS = [
|
|
1481
|
+
{ name: "ffmpeg", pattern: /\bffmpeg\b/i },
|
|
1482
|
+
{ name: "ffprobe", pattern: /\bffprobe\b/i },
|
|
1483
|
+
{ name: "Chrome", pattern: /\b(?:Google Chrome|Chrome|Chromium|chrome-headless-shell|google-chrome|chromium-browser)\b/ },
|
|
1484
|
+
{ name: "Docker", pattern: /\b[Dd]ocker\b/ },
|
|
1485
|
+
{ name: "git", pattern: /\bgit\b(?!\s*(?:hub|lab))/ },
|
|
1486
|
+
{ name: "jq", pattern: /\bjq\b/ },
|
|
1487
|
+
{ name: "pandoc", pattern: /\bpandoc\b/i },
|
|
1488
|
+
{ name: "ImageMagick", pattern: /\b(?:ImageMagick|imagemagick|magick)\b/ },
|
|
1489
|
+
{ name: "yt-dlp", pattern: /\byt-dlp\b/ },
|
|
1490
|
+
{ name: "tesseract", pattern: /\btesseract\b/i },
|
|
1491
|
+
{ name: "poppler", pattern: /\b(?:poppler|pdftoppm|pdftotext)\b/i },
|
|
1492
|
+
{ name: "LibreOffice", pattern: /\b(?:LibreOffice|libreoffice|soffice)\b/ },
|
|
1493
|
+
{ name: "sox", pattern: /\bsox\b/ },
|
|
1494
|
+
{ name: "exiftool", pattern: /\bexiftool\b/i },
|
|
1495
|
+
{ name: "ollama", pattern: /\b[Oo]llama\b/ },
|
|
1496
|
+
{ name: "gh", pattern: /\bgh\s+(?:CLI|cli)\b|\bGitHub CLI\b/ },
|
|
1497
|
+
{ name: "python3", pattern: /\b[Pp]ython\s?3?\b(?![-.\w])/ },
|
|
1498
|
+
{ name: "node", pattern: /\b(?:Node\.js|node)\b(?![-.\w])/ },
|
|
1499
|
+
{ name: "Java", pattern: /\b(?:Java|JDK|JRE)\b/ }
|
|
1500
|
+
];
|
|
1501
|
+
var NEEDED = /\b(?:requires?|required|requirements?|needs?|needed|prerequisites?|depends on|must (?:be )?install(?:ed)?|must have|install(?:ed)? first|has to be installed|be installed)\b/i;
|
|
1502
|
+
var NEEDED_GLOBAL = new RegExp(NEEDED.source, "gi");
|
|
1503
|
+
var NOT_NEEDED = /\b(?:no|not|never|without|doesn't|don't|isn't|aren't|zero)\b[^.;]{0,30}\b(?:requires?|required|needs?|needed|install)/i;
|
|
1504
|
+
var NOT_AFTER = /\b(?:requires?|needs?)\s+(?:no|nothing|neither)\b/i;
|
|
1505
|
+
function saysWhatItNeeds(path, isScript) {
|
|
1506
|
+
if (isScript)
|
|
1507
|
+
return true;
|
|
1508
|
+
if (!MARKDOWN.test(path))
|
|
1509
|
+
return false;
|
|
1510
|
+
const base = path.slice(path.lastIndexOf("/") + 1).toLowerCase();
|
|
1511
|
+
return base === "skill.md" || /^(readme|requirements?|prerequisites?|setup|install(ation)?|dependencies|getting-started)\b/.test(base);
|
|
1512
|
+
}
|
|
1513
|
+
var COMMENT_OR_STRING = /^\s*(?:#|\/\/|\*|\/\*|"""|''')|["'`][^"'`]*\b(?:requires?|needs?|install)/i;
|
|
1514
|
+
function namedAfterVerb(line, program) {
|
|
1515
|
+
for (const verb of line.matchAll(NEEDED_GLOBAL)) {
|
|
1516
|
+
const at = verb.index;
|
|
1517
|
+
const after = line.slice(at + verb[0].length).slice(0, 40).split(/[.;:!?](?:\s|$)/)[0] ?? "";
|
|
1518
|
+
const before = line.slice(Math.max(0, at - 30), at).split(/[.;:!?](?:\s|$)/).at(-1) ?? "";
|
|
1519
|
+
if (program.test(after) || program.test(before))
|
|
1520
|
+
return true;
|
|
1521
|
+
}
|
|
1522
|
+
return false;
|
|
1523
|
+
}
|
|
1524
|
+
function programsNeededIn2(files) {
|
|
1525
|
+
const out = new Map;
|
|
1526
|
+
const add = (program) => {
|
|
1527
|
+
if (!out.has(program.name))
|
|
1528
|
+
out.set(program.name, program);
|
|
1529
|
+
};
|
|
1530
|
+
for (const file of files) {
|
|
1531
|
+
if (isTestFile2(file.path))
|
|
1532
|
+
continue;
|
|
1533
|
+
const lines = file.text.split(`
|
|
1534
|
+
`);
|
|
1535
|
+
const isScript = SCRIPT.test(file.path) || lines[0]?.startsWith("#!") === true;
|
|
1536
|
+
if (isScript && !/\.(sh|bash|zsh)$/i.test(file.path)) {
|
|
1537
|
+
for (const pattern of SPAWNS) {
|
|
1538
|
+
for (const match of file.text.matchAll(pattern)) {
|
|
1539
|
+
const name = match[1] ?? "";
|
|
1540
|
+
const line = lineAt(file.text, match.index);
|
|
1541
|
+
if (name === "" || EVERYWHERE.has(name) || COMMENT_LINE.test(lines[line - 1] ?? ""))
|
|
1542
|
+
continue;
|
|
1543
|
+
add({ name: ALIASES[name] ?? name, from: "spawned", file: file.path, line });
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
for (const pattern of IMPORTS) {
|
|
1547
|
+
for (const match of file.text.matchAll(pattern)) {
|
|
1548
|
+
const root = packageRoot(match[1] ?? match[2] ?? "");
|
|
1549
|
+
const driven = DRIVES[root];
|
|
1550
|
+
const line = lineAt(file.text, match.index);
|
|
1551
|
+
if (driven === undefined || COMMENT_LINE.test(lines[line - 1] ?? ""))
|
|
1552
|
+
continue;
|
|
1553
|
+
add({ name: driven, from: "package", file: file.path, line, via: root });
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
if (/(^|\/)package\.json$/.test(file.path)) {
|
|
1558
|
+
for (const [index, line] of lines.entries()) {
|
|
1559
|
+
const name = /^\s*"(@?[\w./-]+)"\s*:\s*"/.exec(line)?.[1];
|
|
1560
|
+
const driven = name === undefined ? undefined : DRIVES[name];
|
|
1561
|
+
if (driven !== undefined)
|
|
1562
|
+
add({ name: driven, from: "package", file: file.path, line: index + 1, via: name ?? "" });
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
if (/(^|\/)requirements[\w.-]*\.txt$/i.test(file.path)) {
|
|
1566
|
+
for (const [index, line] of lines.entries()) {
|
|
1567
|
+
const name = /^\s*([A-Za-z0-9][\w.-]*)/.exec(line.replace(/#.*/, ""))?.[1]?.toLowerCase();
|
|
1568
|
+
const driven = name === undefined ? undefined : DRIVES[name];
|
|
1569
|
+
if (driven !== undefined)
|
|
1570
|
+
add({ name: driven, from: "package", file: file.path, line: index + 1, via: name ?? "" });
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
if (saysWhatItNeeds(file.path, isScript)) {
|
|
1574
|
+
for (const [index, line] of lines.entries()) {
|
|
1575
|
+
if (NOT_NEEDED.test(line) || NOT_AFTER.test(line))
|
|
1576
|
+
continue;
|
|
1577
|
+
if (isScript && !COMMENT_OR_STRING.test(line))
|
|
1578
|
+
continue;
|
|
1579
|
+
for (const { name, pattern } of NAMED_PROGRAMS) {
|
|
1580
|
+
if (namedAfterVerb(line, pattern))
|
|
1581
|
+
add({ name, from: "named as needed", file: file.path, line: index + 1 });
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
return [...out.values()];
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1215
1589
|
// ../../packages/protocol/src/skill-contents.ts
|
|
1216
1590
|
var CONTENTS_LIMITS2 = {
|
|
1217
1591
|
files: 200,
|
|
@@ -1219,8 +1593,8 @@ var CONTENTS_LIMITS2 = {
|
|
|
1219
1593
|
items: 40,
|
|
1220
1594
|
lineChars: 160
|
|
1221
1595
|
};
|
|
1222
|
-
var
|
|
1223
|
-
var
|
|
1596
|
+
var SCRIPT2 = /\.(sh|bash|zsh|py|js|mjs|cjs|ts|rb|pl|ps1|php|lua)$/i;
|
|
1597
|
+
var MARKDOWN2 = /\.(md|mdx|markdown)$/i;
|
|
1224
1598
|
var FENCE = /^\s*(?:```|~~~)/;
|
|
1225
1599
|
function cap(text) {
|
|
1226
1600
|
const flat = text.replace(/\s+/g, " ").trim();
|
|
@@ -1240,10 +1614,10 @@ function interpreterOf(firstLine) {
|
|
|
1240
1614
|
}
|
|
1241
1615
|
var SCRIPT_PROBE = /(?:^|[;&|(!{]|\bif|\bthen|\$\()\s*(?:!\s*)?(?:command\s+-v|which|type\s+-p|hash)\s+([a-z][\w.+-]*)/g;
|
|
1242
1616
|
var SYSTEM_INSTALL = /\b(?:brew|apt-get|apt|dnf|yum|apk|pacman|choco|winget|scoop|port)\s+(?:install|add|-S)\s+(?:-\S+\s+)*([a-z][\w.+-]*)/g;
|
|
1243
|
-
var
|
|
1617
|
+
var COMMENT_LINE2 = /^\s*(?:#|\/\/|\*|--|;|rem\s)/i;
|
|
1244
1618
|
var PYTHON_PROBE = /\bshutil\.which\(\s*["']([a-z][\w.+-]*)["']/g;
|
|
1245
1619
|
function programsInScriptLine(line, shell) {
|
|
1246
|
-
if (
|
|
1620
|
+
if (COMMENT_LINE2.test(line))
|
|
1247
1621
|
return [];
|
|
1248
1622
|
const matches = shell ? [...line.matchAll(SCRIPT_PROBE), ...line.matchAll(SYSTEM_INSTALL)] : [...line.matchAll(PYTHON_PROBE)];
|
|
1249
1623
|
return matches.map((match) => match[1] ?? "").filter((name) => name !== "");
|
|
@@ -1307,7 +1681,7 @@ function settingsIn(path, text, code) {
|
|
|
1307
1681
|
for (const match of text.matchAll(pattern))
|
|
1308
1682
|
found.add((match[1] ?? "").toUpperCase());
|
|
1309
1683
|
}
|
|
1310
|
-
if (
|
|
1684
|
+
if (MARKDOWN2.test(path)) {
|
|
1311
1685
|
for (const match of text.matchAll(EXPORT_IN_PROSE))
|
|
1312
1686
|
found.add(match[1] ?? "");
|
|
1313
1687
|
for (const match of text.matchAll(KEY_NAMED))
|
|
@@ -1323,7 +1697,7 @@ function codeOf(path, text, isScript) {
|
|
|
1323
1697
|
`);
|
|
1324
1698
|
if (isScript)
|
|
1325
1699
|
return lines;
|
|
1326
|
-
if (!
|
|
1700
|
+
if (!MARKDOWN2.test(path))
|
|
1327
1701
|
return [];
|
|
1328
1702
|
const out = [];
|
|
1329
1703
|
let fenced = false;
|
|
@@ -1496,7 +1870,7 @@ function readSkillContents2(input) {
|
|
|
1496
1870
|
if (files.length >= CONTENTS_LIMITS2.files)
|
|
1497
1871
|
skipped.push({ path: file.path, why: "over the file limit" });
|
|
1498
1872
|
else if (file.text === null)
|
|
1499
|
-
skipped.push({ path: file.path, why: file.notFetched === true ? "not fetched" : "binary" });
|
|
1873
|
+
skipped.push({ path: file.path, why: file.tooLarge === true ? "too large" : file.notFetched === true ? "not fetched" : "binary" });
|
|
1500
1874
|
else if (file.text.length > CONTENTS_LIMITS2.fileChars)
|
|
1501
1875
|
skipped.push({ path: file.path, why: "too large" });
|
|
1502
1876
|
else
|
|
@@ -1507,7 +1881,7 @@ function readSkillContents2(input) {
|
|
|
1507
1881
|
const programs = new Map;
|
|
1508
1882
|
const add = (name, from) => {
|
|
1509
1883
|
if (!programs.has(name))
|
|
1510
|
-
programs.set(name, from);
|
|
1884
|
+
programs.set(name, { name, from });
|
|
1511
1885
|
};
|
|
1512
1886
|
const requires = readRequires2(skillMd);
|
|
1513
1887
|
for (const name of requires.cli)
|
|
@@ -1519,11 +1893,11 @@ function readSkillContents2(input) {
|
|
|
1519
1893
|
const domains = new Set;
|
|
1520
1894
|
const fetchAndRun = [];
|
|
1521
1895
|
const hidden = [];
|
|
1522
|
-
const scripts = skipped.filter((file) => file.why === "not fetched" &&
|
|
1896
|
+
const scripts = skipped.filter((file) => file.why === "not fetched" && SCRIPT2.test(file.path)).map((file) => ({ path: file.path, namedInSkillMd: namesFile(skillMd, file.path) }));
|
|
1523
1897
|
for (const file of files) {
|
|
1524
1898
|
const lines = file.text.split(`
|
|
1525
1899
|
`);
|
|
1526
|
-
const isScript =
|
|
1900
|
+
const isScript = SCRIPT2.test(file.path) || lines[0]?.startsWith("#!") === true;
|
|
1527
1901
|
const code = codeOf(file.path, file.text, isScript);
|
|
1528
1902
|
const shell = isShell(file.path, file.text);
|
|
1529
1903
|
for (const line of code) {
|
|
@@ -1556,18 +1930,22 @@ function readSkillContents2(input) {
|
|
|
1556
1930
|
if (run !== undefined)
|
|
1557
1931
|
fetchAndRun.push({ kind: run.kind, file: file.path, line: index + 1, text: cap(line) });
|
|
1558
1932
|
}
|
|
1559
|
-
if (
|
|
1933
|
+
if (MARKDOWN2.test(file.path))
|
|
1560
1934
|
hidden.push(...hiddenInMarkdown(file.path, file.text));
|
|
1561
1935
|
}
|
|
1562
1936
|
const packageNames = new Set([...packages.values()].map((pkg) => pkg.name));
|
|
1563
|
-
for (const [name,
|
|
1564
|
-
if (from !== "declared" && packageNames.has(name))
|
|
1937
|
+
for (const [name, program] of programs)
|
|
1938
|
+
if (program.from !== "declared" && packageNames.has(name))
|
|
1565
1939
|
programs.delete(name);
|
|
1940
|
+
for (const needed of programsNeededIn2(files))
|
|
1941
|
+
if (!programs.has(needed.name))
|
|
1942
|
+
programs.set(needed.name, needed);
|
|
1566
1943
|
const cut = [];
|
|
1567
1944
|
return {
|
|
1568
|
-
programs: bounded("programs", [...programs.
|
|
1945
|
+
programs: bounded("programs", [...programs.values()].sort((a, b) => compare(a.name, b.name)), cut),
|
|
1569
1946
|
packages: bounded("packages", [...packages.values()].sort((a, b) => compare(`${a.manager} ${a.name}`, `${b.manager} ${b.name}`)), cut),
|
|
1570
1947
|
settings: bounded("settings", [...settings].sort(compare), cut),
|
|
1948
|
+
keys: bounded("keys", keysIn2(files, [...settings]), cut),
|
|
1571
1949
|
scripts: bounded("scripts", scripts.sort((a, b) => compare(a.path, b.path)), cut),
|
|
1572
1950
|
fetchAndRun: bounded("fetchAndRun", fetchAndRun, cut),
|
|
1573
1951
|
domains: bounded("domains", [...domains].sort(compare), cut),
|
|
@@ -1580,24 +1958,12 @@ function readSkillContents2(input) {
|
|
|
1580
1958
|
cut
|
|
1581
1959
|
};
|
|
1582
1960
|
}
|
|
1583
|
-
function contentsLines2(contents, perList = 6) {
|
|
1584
|
-
const list = (items, cut) => items.length === 0 ? "none found" : `${items.slice(0, perList).join(", ")}${items.length > perList || cut ? `, and ${Math.max(0, items.length - perList)}${cut ? "+" : ""} more` : ""}`;
|
|
1585
|
-
const at = (rows, name) => list(rows.map((row) => `${row.kind} at ${row.file}:${row.line} "${row.text}"`), contents.cut.includes(name));
|
|
1586
|
-
return [
|
|
1587
|
-
`Programs: ${list(contents.programs.map((p) => `${p.name} (${p.from})`), contents.cut.includes("programs"))}`,
|
|
1588
|
-
...contents.packages.length === 0 ? [] : [`Packages: ${list(contents.packages.map((p) => `${p.manager} ${p.name}`), contents.cut.includes("packages"))}`],
|
|
1589
|
-
`Keys and settings it reads: ${list(contents.settings, contents.cut.includes("settings"))}`,
|
|
1590
|
-
`Scripts: ${list(contents.scripts.map((s) => s.namedInSkillMd ? `${s.path} (named in SKILL.md)` : s.path), contents.cut.includes("scripts"))}`,
|
|
1591
|
-
`Fetches and runs code: ${at(contents.fetchAndRun, "fetchAndRun")}`,
|
|
1592
|
-
`Domains named: ${list(contents.domains, contents.cut.includes("domains"))}`,
|
|
1593
|
-
`Hidden or encoded text: ${at(contents.hidden, "hidden")}`,
|
|
1594
|
-
...contents.read.skipped.length === 0 ? [] : [`Not read: ${list(contents.read.skipped.map((s) => `${s.path} (${s.why})`), false)}`]
|
|
1595
|
-
];
|
|
1596
|
-
}
|
|
1597
1961
|
|
|
1598
1962
|
// ../../packages/protocol/src/pulse.ts
|
|
1599
1963
|
var MAX_SKILL_FILES_READ2 = 200;
|
|
1600
1964
|
var MAX_SCRIPTS_READ = 5;
|
|
1965
|
+
var MAX_DIRECTORY_FILES_READ = 80;
|
|
1966
|
+
var NOT_TEXT = /\.(png|jpe?g|gif|webp|ico|bmp|tiff?|avif|svgz|pdf|zip|gz|tgz|bz2|xz|7z|rar|jar|woff2?|ttf|otf|eot|mp[34]|m4a|wav|ogg|flac|aac|mov|webm|avi|mkv|pptx|docx|xlsx|psd|ai|sketch|fig|db|sqlite|bin|exe|dll|so|dylib|wasm|pyc|class|hdr|exr|glb|ktx2?)$/i;
|
|
1601
1967
|
var GITHUB_READ_TIMEOUT_MS = 1e4;
|
|
1602
1968
|
var INSTALLER_ORDER = [
|
|
1603
1969
|
"",
|
|
@@ -1669,7 +2035,7 @@ var PAID_WORD = /\b(pricing|paid|subscription|credits?|billing|free tier|per min
|
|
|
1669
2035
|
var HOST = /https?:\/\/([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g;
|
|
1670
2036
|
var NOT_A_SERVICE = /(^|\.)(github\.com|githubusercontent\.com|example\.(com|org|net))$/;
|
|
1671
2037
|
var LOOKS_LIKE_A_FILE = /\.(jpe?g|png|gif|webp|mp[34]|wav|mov|webm|pdf|md|json|txt)$/;
|
|
1672
|
-
var
|
|
2038
|
+
var SCRIPT3 = /\.(sh|bash|py|js|mjs|cjs|ts|rb|pl)$/i;
|
|
1673
2039
|
var RUNS_FILE = /\b(?:node|bun|deno|tsx|python3?|bash|sh)\s+((?:~|\.{1,2})?\/?[\w.@-]+(?:\/[\w.@-]+)*\.(?:mjs|cjs|js|ts|py|sh))\b/g;
|
|
1674
2040
|
var PLACEHOLDER = /(^|\/)(path|your|my|foo|example|script|file|input|output)(\/|\.|$)/i;
|
|
1675
2041
|
function filesItRuns(markdown, repositoryFiles) {
|
|
@@ -1699,7 +2065,7 @@ function readSkillFacts2(markdown, filesInDirectory, repositoryFiles = filesInDi
|
|
|
1699
2065
|
return {
|
|
1700
2066
|
files: filesInDirectory.length,
|
|
1701
2067
|
bytes: filesInDirectory.reduce((total, file) => total + file.size, 0),
|
|
1702
|
-
scripts: filesInDirectory.filter((file) =>
|
|
2068
|
+
scripts: filesInDirectory.filter((file) => SCRIPT3.test(file.path)).length,
|
|
1703
2069
|
keys: [...new Set(markdown.match(KEY_NAME3) ?? [])],
|
|
1704
2070
|
paidWords: [...new Set((markdown.match(PAID_WORD) ?? []).map((word) => word.toLowerCase()))],
|
|
1705
2071
|
hosts: [...new Set(hosts)],
|
|
@@ -1713,6 +2079,14 @@ function parentOf(path) {
|
|
|
1713
2079
|
const at = path.lastIndexOf("/");
|
|
1714
2080
|
return at === -1 ? "" : path.slice(0, at);
|
|
1715
2081
|
}
|
|
2082
|
+
var MAX_NAME_READS2 = 12;
|
|
2083
|
+
var NAME_BATCH = 6;
|
|
2084
|
+
var wordsOf = (text) => text.toLowerCase().split(/[^a-z0-9]+/).filter((word) => word.length > 1);
|
|
2085
|
+
function byNearness(files, wanted) {
|
|
2086
|
+
const words = new Set(wordsOf(wanted));
|
|
2087
|
+
const shared = (file) => wordsOf(parentOf(file.path)).filter((word) => words.has(word)).length;
|
|
2088
|
+
return files.map((file) => ({ file, shared: shared(file) })).sort((a, b) => b.shared - a.shared || (a.file.path < b.file.path ? -1 : a.file.path > b.file.path ? 1 : 0));
|
|
2089
|
+
}
|
|
1716
2090
|
async function locate(source, repo, skill, listing) {
|
|
1717
2091
|
const wanted = normaliseName(skill);
|
|
1718
2092
|
const candidates = listing.files.filter((file) => skillFilesIn2([file]).length === 1);
|
|
@@ -1724,12 +2098,18 @@ async function locate(source, repo, skill, listing) {
|
|
|
1724
2098
|
if (others.length > MAX_SKILL_FILES_READ2) {
|
|
1725
2099
|
return { status: "incomplete", reason: "too_many_skill_files" };
|
|
1726
2100
|
}
|
|
1727
|
-
|
|
1728
|
-
|
|
2101
|
+
const nearest = byNearness(others, wanted).slice(0, MAX_NAME_READS2);
|
|
2102
|
+
const sharing = nearest.filter((entry) => entry.shared > 0).length;
|
|
2103
|
+
let readTo = 0;
|
|
2104
|
+
while (readTo < nearest.length && named.length === 0) {
|
|
2105
|
+
const size = readTo < sharing ? sharing - readTo : NAME_BATCH;
|
|
2106
|
+
named = await read(nearest.slice(readTo, readTo + size).map((entry) => entry.file));
|
|
2107
|
+
readTo += size;
|
|
1729
2108
|
}
|
|
1730
2109
|
if (named.length > 0) {
|
|
1731
|
-
|
|
1732
|
-
|
|
2110
|
+
named = [...named, ...await read(nearest.slice(readTo, sharing).map((entry) => entry.file))];
|
|
2111
|
+
} else if (nearest.length < others.length) {
|
|
2112
|
+
return { status: "incomplete", reason: "too_many_skill_files" };
|
|
1733
2113
|
}
|
|
1734
2114
|
}
|
|
1735
2115
|
if (named.length === 0) {
|
|
@@ -1769,7 +2149,24 @@ async function readFileOnly(result, source) {
|
|
|
1769
2149
|
}
|
|
1770
2150
|
return null;
|
|
1771
2151
|
}
|
|
1772
|
-
|
|
2152
|
+
var SCRIPT_READ_CAP2 = { files: MAX_SCRIPTS_READ, bytesEach: 256 * 1024, bytes: 512 * 1024 };
|
|
2153
|
+
var NOT_CODE_DIRECTORY = /(^|\/)(examples?|assets?|fonts?|images?|media|samples?|fixtures?)\//i;
|
|
2154
|
+
function withinReadCap(paths, listed) {
|
|
2155
|
+
const sizeOf = new Map(listed.map((file) => [file.path, file.size]));
|
|
2156
|
+
const kept = [];
|
|
2157
|
+
let bytes = 0;
|
|
2158
|
+
for (const path of paths) {
|
|
2159
|
+
const size = sizeOf.get(path) ?? 0;
|
|
2160
|
+
if (kept.length >= SCRIPT_READ_CAP2.files || NOT_CODE_DIRECTORY.test(path))
|
|
2161
|
+
continue;
|
|
2162
|
+
if (size > SCRIPT_READ_CAP2.bytesEach || bytes + size > SCRIPT_READ_CAP2.bytes)
|
|
2163
|
+
continue;
|
|
2164
|
+
kept.push(path);
|
|
2165
|
+
bytes += size;
|
|
2166
|
+
}
|
|
2167
|
+
return kept;
|
|
2168
|
+
}
|
|
2169
|
+
async function readPulse2(result, source, depth) {
|
|
1773
2170
|
if (!/^(?!\.{1,2}\/)[\w.-]+\/(?!\.{1,2}$)[\w.-]+$/.test(result.source))
|
|
1774
2171
|
return { ...result, status: "not_github" };
|
|
1775
2172
|
const listing = await source.listing(result.source);
|
|
@@ -1787,15 +2184,20 @@ async function readPulse2(result, source) {
|
|
|
1787
2184
|
const directory = parentOf(located.file.path);
|
|
1788
2185
|
const prefix = directory === "" ? "" : `${directory}/`;
|
|
1789
2186
|
const inDirectory = listing.files.filter((file) => file.path.startsWith(prefix));
|
|
1790
|
-
const scripts = filesItRuns(located.markdown, listing.files)
|
|
2187
|
+
const scripts = withinReadCap(filesItRuns(located.markdown, listing.files), listing.files);
|
|
1791
2188
|
const scriptTexts = await Promise.all(scripts.map(async (file) => ({ file, text: await source.file(result.source, file) })));
|
|
1792
2189
|
const keysInScriptsItRuns = scriptTexts.map(({ file, text }) => ({ file, keys: [...new Set(text?.match(KEY_NAME3) ?? [])] })).filter((entry) => entry.keys.length > 0);
|
|
1793
2190
|
const relative = (path) => path.startsWith(prefix) ? path.slice(prefix.length) : path;
|
|
1794
2191
|
const fetched = new Set(scriptTexts.filter((entry) => entry.text !== null).map((entry) => entry.file));
|
|
2192
|
+
const others = depth === undefined ? [] : await readDirectory(source, result.source, inDirectory, new Set([located.file.path, ...fetched]), depth.directory.by);
|
|
2193
|
+
for (const file of others)
|
|
2194
|
+
if (file.text !== null)
|
|
2195
|
+
fetched.add(file.path);
|
|
1795
2196
|
const contains = readSkillContents2([
|
|
1796
2197
|
{ path: "SKILL.md", text: located.markdown },
|
|
1797
2198
|
...scriptTexts.flatMap(({ file, text }) => text === null ? [] : [{ path: relative(file), text }]),
|
|
1798
|
-
...
|
|
2199
|
+
...others.flatMap(({ path, text }) => text === null ? [] : [{ path: relative(path), text }]),
|
|
2200
|
+
...inDirectory.filter((file) => !fetched.has(file.path) && file.path !== located.file.path && (depth !== undefined || SCRIPT3.test(file.path))).map((file) => NOT_TEXT.test(file.path) ? { path: relative(file.path), text: null } : file.size > CONTENTS_LIMITS2.fileChars ? { path: relative(file.path), text: null, notFetched: true, tooLarge: true } : { path: relative(file.path), text: null, notFetched: true })
|
|
1799
2201
|
]);
|
|
1800
2202
|
return {
|
|
1801
2203
|
...result,
|
|
@@ -1812,6 +2214,22 @@ async function readPulse2(result, source) {
|
|
|
1812
2214
|
contains
|
|
1813
2215
|
};
|
|
1814
2216
|
}
|
|
2217
|
+
async function readDirectory(source, repo, inDirectory, done, by) {
|
|
2218
|
+
let late = false;
|
|
2219
|
+
by.then(() => {
|
|
2220
|
+
late = true;
|
|
2221
|
+
});
|
|
2222
|
+
const plan = inDirectory.filter((file) => !done.has(file.path) && !NOT_TEXT.test(file.path) && file.size <= CONTENTS_LIMITS2.fileChars).slice(0, MAX_DIRECTORY_FILES_READ);
|
|
2223
|
+
const out = [];
|
|
2224
|
+
for (let at = 0;at < plan.length && !late; at += 8) {
|
|
2225
|
+
const batch = Promise.all(plan.slice(at, at + 8).map(async (file) => ({ path: file.path, text: await source.file(repo, file.path).catch(() => null) })));
|
|
2226
|
+
const settled = await Promise.race([batch, by.then(() => null)]);
|
|
2227
|
+
if (settled === null)
|
|
2228
|
+
break;
|
|
2229
|
+
out.push(...settled);
|
|
2230
|
+
}
|
|
2231
|
+
return out;
|
|
2232
|
+
}
|
|
1815
2233
|
function githubPulseSource2(options = {}) {
|
|
1816
2234
|
const get = options.fetch ?? fetch;
|
|
1817
2235
|
const timeoutMs = options.timeoutMs ?? GITHUB_READ_TIMEOUT_MS;
|
|
@@ -1826,6 +2244,7 @@ function githubPulseSource2(options = {}) {
|
|
|
1826
2244
|
};
|
|
1827
2245
|
const listings = new Map;
|
|
1828
2246
|
const repositories = new Map;
|
|
2247
|
+
const fileReads = new Map;
|
|
1829
2248
|
const quota = { apiRequests: 0, limited: false, resetsAt: null };
|
|
1830
2249
|
const noteLimit = (response) => {
|
|
1831
2250
|
const hit = response.status === 429 || response.status === 403 && (response.headers.get("x-ratelimit-remaining") === "0" || response.headers.has("retry-after"));
|
|
@@ -1898,15 +2317,191 @@ function githubPulseSource2(options = {}) {
|
|
|
1898
2317
|
return {
|
|
1899
2318
|
listing: (repo) => once(listings, repo, readListing),
|
|
1900
2319
|
repository: (repo) => once(repositories, repo, readRepository),
|
|
1901
|
-
file:
|
|
1902
|
-
const
|
|
1903
|
-
|
|
1904
|
-
|
|
2320
|
+
file: (repo, path) => {
|
|
2321
|
+
const key = `${repo}
|
|
2322
|
+
${path}`;
|
|
2323
|
+
const kept = options.files?.get(key);
|
|
2324
|
+
if (kept !== undefined)
|
|
2325
|
+
return Promise.resolve(kept);
|
|
2326
|
+
return once(fileReads, key, async () => {
|
|
2327
|
+
const encoded = path.split("/").map(encodeURIComponent).join("/");
|
|
2328
|
+
const response = await get(`https://raw.githubusercontent.com/${repo}/HEAD/${encoded}`, { signal: bounded2() });
|
|
2329
|
+
const text = response.ok ? await response.text() : null;
|
|
2330
|
+
if (response.ok || response.status === 404)
|
|
2331
|
+
options.files?.set(key, text);
|
|
2332
|
+
return text;
|
|
2333
|
+
});
|
|
1905
2334
|
},
|
|
1906
2335
|
quota: () => ({ ...quota })
|
|
1907
2336
|
};
|
|
1908
2337
|
}
|
|
1909
2338
|
|
|
2339
|
+
// ../../packages/protocol/src/before-install.ts
|
|
2340
|
+
var PAID_SERVICES2 = [
|
|
2341
|
+
{ name: "Anthropic", hosts: ["api.anthropic.com"], keyPrefixes: ["ANTHROPIC_"] },
|
|
2342
|
+
{ name: "AssemblyAI", hosts: ["api.assemblyai.com"], keyPrefixes: ["ASSEMBLYAI_"] },
|
|
2343
|
+
{ name: "Azure OpenAI", hosts: ["openai.azure.com"], keyPrefixes: ["AZURE_OPENAI_"] },
|
|
2344
|
+
{ name: "Brave Search", hosts: ["api.search.brave.com"], keyPrefixes: ["BRAVE_"] },
|
|
2345
|
+
{ name: "Browserbase", hosts: ["api.browserbase.com"], keyPrefixes: ["BROWSERBASE_"] },
|
|
2346
|
+
{ name: "Cohere", hosts: ["api.cohere.ai", "api.cohere.com"], keyPrefixes: ["COHERE_", "CO_API_"] },
|
|
2347
|
+
{ name: "Deepgram", hosts: ["api.deepgram.com"], keyPrefixes: ["DEEPGRAM_"] },
|
|
2348
|
+
{ name: "DeepSeek", hosts: ["api.deepseek.com"], keyPrefixes: ["DEEPSEEK_"] },
|
|
2349
|
+
{ name: "ElevenLabs", hosts: ["api.elevenlabs.io"], keyPrefixes: ["ELEVENLABS_", "ELEVEN_"] },
|
|
2350
|
+
{ name: "Exa", hosts: ["api.exa.ai"], keyPrefixes: ["EXA_"] },
|
|
2351
|
+
{ name: "fal", hosts: ["fal.run", "queue.fal.run"], keyPrefixes: ["FAL_"] },
|
|
2352
|
+
{ name: "Firecrawl", hosts: ["api.firecrawl.dev"], keyPrefixes: ["FIRECRAWL_"] },
|
|
2353
|
+
{ name: "Fireworks AI", hosts: ["api.fireworks.ai"], keyPrefixes: ["FIREWORKS_"] },
|
|
2354
|
+
{ name: "Google Gemini", hosts: ["generativelanguage.googleapis.com"], keyPrefixes: ["GEMINI_"] },
|
|
2355
|
+
{ name: "Google Vertex AI", hosts: ["aiplatform.googleapis.com"], keyPrefixes: ["VERTEX_"] },
|
|
2356
|
+
{ name: "Groq", hosts: ["api.groq.com"], keyPrefixes: ["GROQ_"] },
|
|
2357
|
+
{ name: "HeyGen", hosts: ["api.heygen.com"], keyPrefixes: ["HEYGEN_"] },
|
|
2358
|
+
{ name: "Mistral", hosts: ["api.mistral.ai"], keyPrefixes: ["MISTRAL_"] },
|
|
2359
|
+
{ name: "OpenAI", hosts: ["api.openai.com"], keyPrefixes: ["OPENAI_"] },
|
|
2360
|
+
{ name: "OpenRouter", hosts: ["openrouter.ai"], keyPrefixes: ["OPENROUTER_"] },
|
|
2361
|
+
{ name: "Perplexity", hosts: ["api.perplexity.ai"], keyPrefixes: ["PERPLEXITY_", "PPLX_"] },
|
|
2362
|
+
{ name: "Replicate", hosts: ["api.replicate.com"], keyPrefixes: ["REPLICATE_"] },
|
|
2363
|
+
{ name: "Resend", hosts: ["api.resend.com"], keyPrefixes: ["RESEND_"] },
|
|
2364
|
+
{ name: "Runway", hosts: ["api.dev.runwayml.com", "api.runwayml.com"], keyPrefixes: ["RUNWAY_", "RUNWAYML_"] },
|
|
2365
|
+
{ name: "SerpApi", hosts: ["serpapi.com"], keyPrefixes: ["SERPAPI_", "SERP_API_"] },
|
|
2366
|
+
{ name: "Stability AI", hosts: ["api.stability.ai"], keyPrefixes: ["STABILITY_"] },
|
|
2367
|
+
{ name: "Tavily", hosts: ["api.tavily.com"], keyPrefixes: ["TAVILY_"] },
|
|
2368
|
+
{ name: "Together AI", hosts: ["api.together.xyz", "api.together.ai"], keyPrefixes: ["TOGETHER_"] },
|
|
2369
|
+
{ name: "Twilio", hosts: ["api.twilio.com"], keyPrefixes: ["TWILIO_"] },
|
|
2370
|
+
{ name: "xAI", hosts: ["api.x.ai"], keyPrefixes: ["XAI_"] }
|
|
2371
|
+
];
|
|
2372
|
+
var STATIC_HOSTS = [
|
|
2373
|
+
"fonts.googleapis.com",
|
|
2374
|
+
"gstatic.com",
|
|
2375
|
+
"cdn.jsdelivr.net",
|
|
2376
|
+
"unpkg.com",
|
|
2377
|
+
"cdnjs.cloudflare.com",
|
|
2378
|
+
"esm.sh",
|
|
2379
|
+
"cdn.skypack.dev",
|
|
2380
|
+
"ga.jspm.io",
|
|
2381
|
+
"githubusercontent.com",
|
|
2382
|
+
"cdn.tailwindcss.com",
|
|
2383
|
+
"code.jquery.com",
|
|
2384
|
+
"use.typekit.net",
|
|
2385
|
+
"fonts.bunny.net",
|
|
2386
|
+
"rsms.me",
|
|
2387
|
+
"cartocdn.com",
|
|
2388
|
+
"tile.openstreetmap.org",
|
|
2389
|
+
"server.arcgisonline.com",
|
|
2390
|
+
"images.unsplash.com"
|
|
2391
|
+
];
|
|
2392
|
+
var STATIC_SHAPE = /^(?:cdn|static|fonts?|tiles?|assets|img|images|media)\d*[.-]|(?:^|[.-])cdn(?:[.-]|$)|\.cdn\./;
|
|
2393
|
+
var API_SHAPE = /^api\d*[.-]|[.-]api\.|^.*\.googleapis\.com$/;
|
|
2394
|
+
function under(host, base) {
|
|
2395
|
+
return host === base || host.endsWith(`.${base}`);
|
|
2396
|
+
}
|
|
2397
|
+
var ASSET = /\.(woff2?|ttf|otf|eot|png|jpe?g|gif|webp|svgz?|ico|bmp|tiff?|avif|heic|hdr|exr|mp[34]|m4a|wav|ogg|flac|aac|mov|webm|avi|mkv|psd|ai|sketch|fig|glb|gltf|fbx|blend|ktx2?|basis|pdf|zip|gz|tgz)$|(^|\/)\.(gitkeep|keep|DS_Store)$/i;
|
|
2398
|
+
function beforeInstall2(input) {
|
|
2399
|
+
const services = new Map;
|
|
2400
|
+
const service = (name) => {
|
|
2401
|
+
const found = services.get(name) ?? { name, hosts: [], keys: [] };
|
|
2402
|
+
services.set(name, found);
|
|
2403
|
+
return found;
|
|
2404
|
+
};
|
|
2405
|
+
const apiHosts = [];
|
|
2406
|
+
const staticHosts = [];
|
|
2407
|
+
const otherHosts = [];
|
|
2408
|
+
for (const host of input.domains) {
|
|
2409
|
+
const known = PAID_SERVICES2.find((entry) => entry.hosts.some((base) => under(host, base)));
|
|
2410
|
+
if (known !== undefined) {
|
|
2411
|
+
service(known.name).hosts.push(host);
|
|
2412
|
+
apiHosts.push(host);
|
|
2413
|
+
} else if (STATIC_HOSTS.some((base) => under(host, base)) || STATIC_SHAPE.test(host))
|
|
2414
|
+
staticHosts.push(host);
|
|
2415
|
+
else if (API_SHAPE.test(host))
|
|
2416
|
+
apiHosts.push(host);
|
|
2417
|
+
else
|
|
2418
|
+
otherHosts.push(host);
|
|
2419
|
+
}
|
|
2420
|
+
for (const key of input.keys) {
|
|
2421
|
+
if (key.why !== "secret")
|
|
2422
|
+
continue;
|
|
2423
|
+
const known = PAID_SERVICES2.find((entry) => entry.keyPrefixes.some((prefix) => key.name.startsWith(prefix)));
|
|
2424
|
+
if (known !== undefined)
|
|
2425
|
+
service(known.name).keys.push(key.name);
|
|
2426
|
+
}
|
|
2427
|
+
const asked = new Set(input.keys.map((key) => key.name));
|
|
2428
|
+
return {
|
|
2429
|
+
fetchAndRun: [...input.fetchAndRun],
|
|
2430
|
+
keys: [...input.keys],
|
|
2431
|
+
otherSettings: input.settings.filter((name) => !asked.has(name)).length,
|
|
2432
|
+
services: [...services.values()].sort((a, b) => a.name < b.name ? -1 : 1),
|
|
2433
|
+
programs: [...input.programs],
|
|
2434
|
+
scripts: [...input.scripts],
|
|
2435
|
+
apiHosts,
|
|
2436
|
+
staticHosts,
|
|
2437
|
+
otherHosts,
|
|
2438
|
+
hidden: [...input.hidden],
|
|
2439
|
+
notRead: input.skipped.filter((file) => file.why !== "binary" && !ASSET.test(file.path)),
|
|
2440
|
+
cut: [...input.cut]
|
|
2441
|
+
};
|
|
2442
|
+
}
|
|
2443
|
+
var plural = (count, one, many = `${one}s`) => `${count} ${count === 1 ? one : many}`;
|
|
2444
|
+
function programNames(programs, shown = 4) {
|
|
2445
|
+
const names = programs.map((program) => program.name);
|
|
2446
|
+
return names.length <= shown ? names.join(", ") : `${names.slice(0, shown).join(", ")} +${names.length - shown}`;
|
|
2447
|
+
}
|
|
2448
|
+
function beforeInstallSummary2(view) {
|
|
2449
|
+
const secrets = view.keys.filter((key) => key.why === "secret").length;
|
|
2450
|
+
const toSet = view.keys.length - secrets;
|
|
2451
|
+
const asks = secrets + toSet === 0 ? "no keys" : [secrets === 0 ? null : plural(secrets, "key"), toSet === 0 ? null : plural(toSet, "setting")].filter((part) => part !== null).join(", ");
|
|
2452
|
+
return [
|
|
2453
|
+
...view.fetchAndRun.length === 0 ? [] : ["fetches and runs code"],
|
|
2454
|
+
asks,
|
|
2455
|
+
view.services.length === 0 ? "no paid services" : plural(view.services.length, "paid service"),
|
|
2456
|
+
view.programs.length === 0 ? "nothing to install named" : `needs ${programNames(view.programs)}`,
|
|
2457
|
+
view.scripts.length === 0 ? "no scripts" : plural(view.scripts.length, "script"),
|
|
2458
|
+
...view.hidden.length === 0 ? [] : [plural(view.hidden.length, "hidden text", "hidden texts")],
|
|
2459
|
+
...view.notRead.length === 0 ? [] : [`${plural(view.notRead.length, "file")} not read`]
|
|
2460
|
+
].join(" · ");
|
|
2461
|
+
}
|
|
2462
|
+
function programSourceText2(program) {
|
|
2463
|
+
const at = program.file === undefined ? "" : `${program.file}${program.line === undefined ? "" : `:${program.line}`}`;
|
|
2464
|
+
switch (program.from) {
|
|
2465
|
+
case "declared":
|
|
2466
|
+
return "declared in SKILL.md";
|
|
2467
|
+
case "runs":
|
|
2468
|
+
return "run in SKILL.md";
|
|
2469
|
+
case "interpreter":
|
|
2470
|
+
return "script interpreter";
|
|
2471
|
+
case "script":
|
|
2472
|
+
return "a script checks for it or installs it";
|
|
2473
|
+
case "spawned":
|
|
2474
|
+
return `run by ${at}`;
|
|
2475
|
+
case "package":
|
|
2476
|
+
return `via ${program.via ?? "a package"}, ${at}`;
|
|
2477
|
+
case "named as needed":
|
|
2478
|
+
return `said to be needed, ${at}`;
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
function contentsLines2(contents, perList = 6) {
|
|
2482
|
+
const view = beforeInstall2({ ...contents, skipped: contents.read.skipped });
|
|
2483
|
+
const list = (items, cut2) => `${items.slice(0, perList).join(", ")}${items.length > perList || cut2 ? `, and ${Math.max(0, items.length - perList)}${cut2 ? "+" : ""} more` : ""}`;
|
|
2484
|
+
const cut = (name) => view.cut.includes(name);
|
|
2485
|
+
const at = (rows, name) => list(rows.map((row) => `${row.kind} at ${row.file}:${row.line} "${row.text}"`), cut(name));
|
|
2486
|
+
const others = view.otherSettings === 0 ? "" : `; ${plural(view.otherSettings, "other setting")} it reads`;
|
|
2487
|
+
const hostParts = [
|
|
2488
|
+
view.apiHosts.length === 0 ? "no API host" : list(view.apiHosts, false),
|
|
2489
|
+
...view.staticHosts.length === 0 ? [] : [`fonts, CDNs and map tiles: ${view.staticHosts.length}`],
|
|
2490
|
+
...view.otherHosts.length === 0 ? [] : [`other sites named: ${view.otherHosts.length}`]
|
|
2491
|
+
];
|
|
2492
|
+
return [
|
|
2493
|
+
`Before you install: ${beforeInstallSummary2(view)}`,
|
|
2494
|
+
...view.fetchAndRun.length === 0 ? [] : [`Fetches and runs code: ${at(view.fetchAndRun, "fetchAndRun")}`],
|
|
2495
|
+
`Asks for: ${view.keys.length === 0 ? `no keys${others}` : `${list(view.keys.map((key) => `${key.name} (${key.why === "secret" ? "" : "set in SKILL.md, "}${key.file}:${key.line})`), cut("keys"))}${others}`}`,
|
|
2496
|
+
`Paid services: ${view.services.length === 0 ? "none named" : list(view.services.map((entry) => `${entry.name} (${[...entry.hosts, ...entry.keys].join(", ")})`), false)}`,
|
|
2497
|
+
`Needs installed: ${view.programs.length === 0 ? "none named" : list(view.programs.map((program) => `${program.name} (${programSourceText2(program)})`), cut("programs"))}`,
|
|
2498
|
+
`Runs on your machine: ${view.scripts.length === 0 ? "no scripts" : `${plural(view.scripts.length, "script")}: ${list(view.scripts.map((script) => script.namedInSkillMd ? `${script.path} (named in SKILL.md)` : script.path), cut("scripts"))}`}${view.fetchAndRun.length === 0 ? "; fetches and runs no code" : ""}`,
|
|
2499
|
+
`Sends data to (hosts its files name, not shown to be contacted): ${hostParts.join("; ")}`,
|
|
2500
|
+
...view.hidden.length === 0 ? [] : [`Hidden or encoded text: ${at(view.hidden, "hidden")}`],
|
|
2501
|
+
...view.notRead.length === 0 ? [] : [`Not read: ${list(view.notRead.map((file) => `${file.path} (${file.why})`), false)}`]
|
|
2502
|
+
];
|
|
2503
|
+
}
|
|
2504
|
+
|
|
1910
2505
|
// ../../packages/protocol/src/skill-install.ts
|
|
1911
2506
|
var MAX_SKILLS_READ2 = 3;
|
|
1912
2507
|
var ADD = new Set(["add", "a", "install", "i"]);
|
|
@@ -2062,7 +2657,7 @@ async function namesUnder(install, source) {
|
|
|
2062
2657
|
}));
|
|
2063
2658
|
return { names, total: files.length };
|
|
2064
2659
|
}
|
|
2065
|
-
async function readSkillInstall2(install, source, deadline) {
|
|
2660
|
+
async function readSkillInstall2(install, source, deadline, directoryBy = deadline) {
|
|
2066
2661
|
if (install.kind === "elsewhere")
|
|
2067
2662
|
return install;
|
|
2068
2663
|
const late = deadline.then(() => "late");
|
|
@@ -2090,7 +2685,7 @@ async function readSkillInstall2(install, source, deadline) {
|
|
|
2090
2685
|
total = install.skills.length;
|
|
2091
2686
|
}
|
|
2092
2687
|
const readings = await Promise.all(names.map(async (skill) => {
|
|
2093
|
-
const pulse = readPulse2({ source: install.repo, skill, installs: null, installsText: null, url: null }, source).then((read) => ({ skill, pulse: read })).catch(() => ({ skill, pulse: null, why: "read failed" }));
|
|
2688
|
+
const pulse = readPulse2({ source: install.repo, skill, installs: null, installsText: null, url: null }, source, { directory: { by: directoryBy } }).then((read) => ({ skill, pulse: read })).catch(() => ({ skill, pulse: null, why: "read failed" }));
|
|
2094
2689
|
const settled = await Promise.race([pulse, late]);
|
|
2095
2690
|
return settled === "late" ? { skill, pulse: null, why: "no answer in time" } : settled;
|
|
2096
2691
|
}));
|
|
@@ -2102,7 +2697,7 @@ async function readSkillInstall2(install, source, deadline) {
|
|
|
2102
2697
|
listing: null
|
|
2103
2698
|
};
|
|
2104
2699
|
}
|
|
2105
|
-
var
|
|
2700
|
+
var plural2 = (count, word) => `${count} ${word}${count === 1 ? "" : "s"}`;
|
|
2106
2701
|
function readingLines(repo, reading) {
|
|
2107
2702
|
const head = `${repo} · ${reading.skill}`;
|
|
2108
2703
|
const { pulse } = reading;
|
|
@@ -2114,9 +2709,9 @@ function readingLines(repo, reading) {
|
|
|
2114
2709
|
case "read": {
|
|
2115
2710
|
const facts = [
|
|
2116
2711
|
`${pulse.path}`,
|
|
2117
|
-
|
|
2712
|
+
plural2(pulse.files, "file"),
|
|
2118
2713
|
...pulse.repository === null ? [] : [
|
|
2119
|
-
|
|
2714
|
+
plural2(pulse.repository.stars, "star"),
|
|
2120
2715
|
...pulse.repository.pushedAt === null ? [] : [`last push ${pulse.repository.pushedAt.slice(0, 10)}`],
|
|
2121
2716
|
...pulse.repository.archived ? ["archived"] : []
|
|
2122
2717
|
]
|
|
@@ -2134,7 +2729,7 @@ function readingLines(repo, reading) {
|
|
|
2134
2729
|
];
|
|
2135
2730
|
case "absent":
|
|
2136
2731
|
return [
|
|
2137
|
-
`${head}: no SKILL.md in the repository names it (${
|
|
2732
|
+
`${head}: no SKILL.md in the repository names it (${plural2(pulse.skillFiles, "skill file")} read)`
|
|
2138
2733
|
];
|
|
2139
2734
|
case "ambiguous":
|
|
2140
2735
|
return [
|
|
@@ -2169,7 +2764,7 @@ ${read.source}: not read (ARP reads GitHub sources only)`;
|
|
|
2169
2764
|
for (const reading of read.readings)
|
|
2170
2765
|
lines.push(...readingLines(install.repo, reading));
|
|
2171
2766
|
if (read.notRead > 0)
|
|
2172
|
-
lines.push(`${
|
|
2767
|
+
lines.push(`${plural2(read.notRead, "more skill")} in this install not read (ARP reads ${MAX_SKILLS_READ2})`);
|
|
2173
2768
|
if (install.ref !== null)
|
|
2174
2769
|
lines.push(`Read at the default branch's HEAD; the command names ${install.ref}.`);
|
|
2175
2770
|
return lines.join(`
|
|
@@ -2178,6 +2773,7 @@ ${read.source}: not read (ARP reads GitHub sources only)`;
|
|
|
2178
2773
|
|
|
2179
2774
|
// ../hook/src/pre-install.ts
|
|
2180
2775
|
var GITHUB_BUDGET_MS = 6000;
|
|
2776
|
+
var DIRECTORY_MARGIN_MS = 1500;
|
|
2181
2777
|
var OUTPUT_CAP = 9500;
|
|
2182
2778
|
var FOR_THE_AGENT = "This is what the skill's own files contain, read by ARP's hook before the install ran. " + "They are facts, not a verdict, and nothing was blocked. The person was shown the same lines.";
|
|
2183
2779
|
function capped(text) {
|
|
@@ -2198,13 +2794,14 @@ async function preToolUse(raw, options = {}) {
|
|
|
2198
2794
|
const token = (env["GITHUB_TOKEN"] ?? env["GH_TOKEN"])?.trim() || undefined;
|
|
2199
2795
|
const budgetMs = options.budgetMs ?? GITHUB_BUDGET_MS;
|
|
2200
2796
|
const deadline = new Promise((resolve) => setTimeout(resolve, budgetMs));
|
|
2797
|
+
const directoryBy = new Promise((resolve) => setTimeout(resolve, Math.max(0, budgetMs - DIRECTORY_MARGIN_MS)));
|
|
2201
2798
|
const source = githubPulseSource2({
|
|
2202
2799
|
...options.fetch === undefined ? {} : { fetch: options.fetch },
|
|
2203
2800
|
...token === undefined ? {} : { token },
|
|
2204
2801
|
timeoutMs: budgetMs + 500,
|
|
2205
2802
|
signal: AbortSignal.timeout(budgetMs + 500)
|
|
2206
2803
|
});
|
|
2207
|
-
const report = capped(skillInstallReport2(await readSkillInstall2(install, source, deadline)));
|
|
2804
|
+
const report = capped(skillInstallReport2(await readSkillInstall2(install, source, deadline, directoryBy)));
|
|
2208
2805
|
return JSON.stringify({
|
|
2209
2806
|
systemMessage: report,
|
|
2210
2807
|
hookSpecificOutput: {
|
|
@@ -2633,7 +3230,7 @@ async function readSkillLines(atomUri, config) {
|
|
|
2633
3230
|
function counted(counts) {
|
|
2634
3231
|
return [...counts.entries()].sort(([a, x], [b, y]) => y.size - x.size || a.localeCompare(b)).map(([name, wallets]) => `${name} (${wallets.size})`).join(", ");
|
|
2635
3232
|
}
|
|
2636
|
-
var
|
|
3233
|
+
var plural3 = (n) => `${n} wallet${n === 1 ? "" : "s"}`;
|
|
2637
3234
|
function linesSentence(usage, label, where) {
|
|
2638
3235
|
if (usage === null)
|
|
2639
3236
|
return null;
|
|
@@ -2661,7 +3258,7 @@ function linesSentence(usage, label, where) {
|
|
|
2661
3258
|
...because.size === 0 ? [] : [`because ${counted(because)}`],
|
|
2662
3259
|
...named.size === 0 ? [] : [`${outcome === "failed" ? "needs" : "with"} ${counted(named)}`]
|
|
2663
3260
|
];
|
|
2664
|
-
parts.push([`${usage.truncated ? "at least " : ""}${
|
|
3261
|
+
parts.push([`${usage.truncated ? "at least " : ""}${plural3(wallets.size)} wrote ${outcome}`, ...detail].join(" · "));
|
|
2665
3262
|
}
|
|
2666
3263
|
if (parts.length === 0)
|
|
2667
3264
|
return null;
|
|
@@ -3843,9 +4440,9 @@ function answerOf(response) {
|
|
|
3843
4440
|
if (typeof text !== "string")
|
|
3844
4441
|
continue;
|
|
3845
4442
|
try {
|
|
3846
|
-
const
|
|
3847
|
-
if (typeof
|
|
3848
|
-
return
|
|
4443
|
+
const parsed2 = JSON.parse(text);
|
|
4444
|
+
if (typeof parsed2 === "object" && parsed2 !== null)
|
|
4445
|
+
return parsed2;
|
|
3849
4446
|
} catch {
|
|
3850
4447
|
continue;
|
|
3851
4448
|
}
|
|
@@ -3945,7 +4542,7 @@ function appendFailure(line) {
|
|
|
3945
4542
|
}
|
|
3946
4543
|
function recordSkillFailure(call, cwd) {
|
|
3947
4544
|
const classified = classifyCall(call);
|
|
3948
|
-
if (classified === null || classified.outcome
|
|
4545
|
+
if (classified === null || !isFailure(classified.outcome) || call.session === null)
|
|
3949
4546
|
return null;
|
|
3950
4547
|
const command = call.input?.["command"];
|
|
3951
4548
|
if (typeof command !== "string")
|
|
@@ -4025,19 +4622,200 @@ async function postToolUseFailure(payload) {
|
|
|
4025
4622
|
return context === null ? null : failureOutput(context);
|
|
4026
4623
|
}
|
|
4027
4624
|
|
|
4028
|
-
// ../hook/src/
|
|
4029
|
-
import {
|
|
4625
|
+
// ../hook/src/share-ask.ts
|
|
4626
|
+
import { spawn } from "node:child_process";
|
|
4627
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
4030
4628
|
import { dirname as dirname3, join as join4 } from "node:path";
|
|
4629
|
+
import { fileURLToPath } from "node:url";
|
|
4630
|
+
|
|
4631
|
+
// ../../packages/protocol/src/sharing-state.ts
|
|
4632
|
+
var SHARING_HOWS2 = ["init", "command", "desktop", "asked-once", "asked-always", "asked-no"];
|
|
4633
|
+
function sharingChoiceOf2(text) {
|
|
4634
|
+
if (text === null)
|
|
4635
|
+
return null;
|
|
4636
|
+
try {
|
|
4637
|
+
const parsed2 = JSON.parse(text);
|
|
4638
|
+
if (typeof parsed2 !== "object" || parsed2 === null)
|
|
4639
|
+
return { on: false, at: null, how: null };
|
|
4640
|
+
const { failures, at, how } = parsed2;
|
|
4641
|
+
return {
|
|
4642
|
+
on: failures === true,
|
|
4643
|
+
at: typeof at === "string" ? at : null,
|
|
4644
|
+
how: typeof how === "string" && SHARING_HOWS2.includes(how) ? how : null
|
|
4645
|
+
};
|
|
4646
|
+
} catch {
|
|
4647
|
+
return { on: false, at: null, how: null };
|
|
4648
|
+
}
|
|
4649
|
+
}
|
|
4650
|
+
function sharingFileText2(on, how, at) {
|
|
4651
|
+
return `${JSON.stringify({ failures: on, at: at.toISOString(), how })}
|
|
4652
|
+
`;
|
|
4653
|
+
}
|
|
4654
|
+
function choiceForAnswer2(answer) {
|
|
4655
|
+
return { on: answer === "always", how: `asked-${answer}` };
|
|
4656
|
+
}
|
|
4657
|
+
var ANSWER = /^[\s>*`-]*arp-share:\s*(once|always|no)[\s.`*]*$/gim;
|
|
4658
|
+
function shareAnswerIn2(text) {
|
|
4659
|
+
const found = new Set;
|
|
4660
|
+
for (const match of text.matchAll(ANSWER)) {
|
|
4661
|
+
const word = match[1]?.toLowerCase();
|
|
4662
|
+
if (word === "once" || word === "always" || word === "no")
|
|
4663
|
+
found.add(word);
|
|
4664
|
+
}
|
|
4665
|
+
return found.size === 1 ? [...found][0] ?? null : null;
|
|
4666
|
+
}
|
|
4667
|
+
|
|
4668
|
+
// ../hook/src/share-ask.ts
|
|
4669
|
+
var isOff = () => process.env["ARP_SHARE_AUTO"]?.trim().toLowerCase() === "off";
|
|
4670
|
+
var GITHUB_REPO = /^[\w.-]+\/[\w.-]+$/;
|
|
4671
|
+
var readIfThere2 = (path) => existsSync2(path) ? readFileSync3(path, "utf8") : null;
|
|
4672
|
+
function shareQuestionFor() {
|
|
4673
|
+
return " Sharing is not set on this machine yet. If that line says worked or failed, ask the person once, right after it, " + "in their language: share this result as a public line on Intuition testnet? Three choices: yes, this once; " + "always (this result and every one from now on, sent at the end of each session; earlier ones stay here); no (not asked again). Say what would leave: " + "the skill's public address, its category, worked or failed with a closed reason, signed by this machine's own " + "ARP address. Never their prompts, files, paths, code or keys. Do not choose for them. At the end of your reply to " + "their answer, add one line: `arp-share: once`, `arp-share: always` or `arp-share: no`, the one they chose; if " + "they did not answer it, add none.";
|
|
4674
|
+
}
|
|
4675
|
+
function stateOf(home) {
|
|
4676
|
+
const text = readIfThere2(join4(home, "share-question.json"));
|
|
4677
|
+
if (text === null)
|
|
4678
|
+
return null;
|
|
4679
|
+
try {
|
|
4680
|
+
const row = JSON.parse(text);
|
|
4681
|
+
if (typeof row.session !== "string" || typeof row.skill !== "string")
|
|
4682
|
+
return null;
|
|
4683
|
+
if (row.state !== "offered" && row.state !== "put" && row.state !== "answered")
|
|
4684
|
+
return null;
|
|
4685
|
+
return { at: typeof row.at === "string" ? row.at : "", session: row.session, skill: row.skill, state: row.state };
|
|
4686
|
+
} catch {
|
|
4687
|
+
return { at: "", session: "", skill: "", state: "put" };
|
|
4688
|
+
}
|
|
4689
|
+
}
|
|
4690
|
+
var writeState = (home, state) => writeFileSync2(join4(home, "share-question.json"), `${JSON.stringify(state)}
|
|
4691
|
+
`);
|
|
4692
|
+
function offerShare(session, skill, source, cwd, now = new Date) {
|
|
4693
|
+
if (isOff())
|
|
4694
|
+
return false;
|
|
4695
|
+
try {
|
|
4696
|
+
const home = arpHome();
|
|
4697
|
+
if (existsSync2(join4(home, "sharing.json")))
|
|
4698
|
+
return false;
|
|
4699
|
+
const state = stateOf(home);
|
|
4700
|
+
if (state !== null && (state.state !== "offered" || state.session === session))
|
|
4701
|
+
return false;
|
|
4702
|
+
const name = skill.split(":").pop() ?? skill;
|
|
4703
|
+
const known = source ?? installedSource(name, cwd);
|
|
4704
|
+
if (known === null || !GITHUB_REPO.test(known))
|
|
4705
|
+
return false;
|
|
4706
|
+
mkdirSync3(home, { recursive: true });
|
|
4707
|
+
writeState(home, { at: now.toISOString(), session, skill, state: "offered" });
|
|
4708
|
+
return true;
|
|
4709
|
+
} catch {
|
|
4710
|
+
return false;
|
|
4711
|
+
}
|
|
4712
|
+
}
|
|
4713
|
+
function onStopShare(session, last, recorded, now = new Date) {
|
|
4714
|
+
if (isOff())
|
|
4715
|
+
return;
|
|
4716
|
+
try {
|
|
4717
|
+
const home = arpHome();
|
|
4718
|
+
const state = stateOf(home);
|
|
4719
|
+
if (state === null || state.session !== session)
|
|
4720
|
+
return;
|
|
4721
|
+
if (state.state === "put") {
|
|
4722
|
+
if (existsSync2(join4(home, "sharing.json")))
|
|
4723
|
+
return;
|
|
4724
|
+
const answer = shareAnswerIn2(last);
|
|
4725
|
+
if (answer === null)
|
|
4726
|
+
return;
|
|
4727
|
+
const choice = choiceForAnswer2(answer);
|
|
4728
|
+
writeFileSync2(join4(home, "sharing.json"), sharingFileText2(choice.on, choice.how, now));
|
|
4729
|
+
if (answer !== "no") {
|
|
4730
|
+
appendOwn(join4(home, "share-once.jsonl"), `${JSON.stringify({ at: now.toISOString(), session, skill: state.skill })}
|
|
4731
|
+
`);
|
|
4732
|
+
}
|
|
4733
|
+
writeState(home, { ...state, state: "answered", answer, at: now.toISOString() });
|
|
4734
|
+
return;
|
|
4735
|
+
}
|
|
4736
|
+
if (state.state !== "offered")
|
|
4737
|
+
return;
|
|
4738
|
+
const outcome = recorded.get(state.skill);
|
|
4739
|
+
if (outcome === "worked" || outcome === "failed")
|
|
4740
|
+
writeState(home, { ...state, state: "put", at: now.toISOString() });
|
|
4741
|
+
} catch {}
|
|
4742
|
+
}
|
|
4743
|
+
function senderOf(here = dirname3(fileURLToPath(import.meta.url))) {
|
|
4744
|
+
const send = ["share", "send", "--auto"];
|
|
4745
|
+
const given = process.env["ARP_CLI"]?.trim();
|
|
4746
|
+
if (given !== undefined && given !== "")
|
|
4747
|
+
return { command: process.execPath, args: [given, ...send] };
|
|
4748
|
+
for (const name of ["arp.js", "cli.js"]) {
|
|
4749
|
+
if (existsSync2(join4(here, name)))
|
|
4750
|
+
return { command: process.execPath, args: [join4(here, name), ...send] };
|
|
4751
|
+
}
|
|
4752
|
+
const written = readIfThere2(join4(here, "send.json"));
|
|
4753
|
+
if (written === null)
|
|
4754
|
+
return null;
|
|
4755
|
+
try {
|
|
4756
|
+
const parsed2 = JSON.parse(written);
|
|
4757
|
+
if (typeof parsed2.command !== "string" || !Array.isArray(parsed2.args))
|
|
4758
|
+
return null;
|
|
4759
|
+
const args = parsed2.args.filter((arg) => typeof arg === "string");
|
|
4760
|
+
return { command: parsed2.command, args };
|
|
4761
|
+
} catch {
|
|
4762
|
+
return null;
|
|
4763
|
+
}
|
|
4764
|
+
}
|
|
4765
|
+
function onceFor(home, session) {
|
|
4766
|
+
for (const line of (readIfThere2(join4(home, "share-once.jsonl")) ?? "").split(`
|
|
4767
|
+
`)) {
|
|
4768
|
+
try {
|
|
4769
|
+
if (JSON.parse(line).session === session)
|
|
4770
|
+
return true;
|
|
4771
|
+
} catch {
|
|
4772
|
+
continue;
|
|
4773
|
+
}
|
|
4774
|
+
}
|
|
4775
|
+
return false;
|
|
4776
|
+
}
|
|
4777
|
+
async function sessionEnd(payload) {
|
|
4778
|
+
if (isOff() || typeof payload !== "object" || payload === null)
|
|
4779
|
+
return null;
|
|
4780
|
+
const raw = payload;
|
|
4781
|
+
const session = raw["session_id"] ?? raw["sessionId"];
|
|
4782
|
+
if (typeof session !== "string")
|
|
4783
|
+
return null;
|
|
4784
|
+
const cwd = typeof raw["cwd"] === "string" ? raw["cwd"] : process.cwd();
|
|
4785
|
+
try {
|
|
4786
|
+
const home = arpHome();
|
|
4787
|
+
const choice = sharingChoiceOf2(readIfThere2(join4(home, "sharing.json")));
|
|
4788
|
+
if (choice?.on !== true && !onceFor(home, session))
|
|
4789
|
+
return null;
|
|
4790
|
+
const sender = senderOf();
|
|
4791
|
+
if (sender === null) {
|
|
4792
|
+
mkdirSync3(home, { recursive: true });
|
|
4793
|
+
appendOwn(join4(home, "share-log.jsonl"), `${JSON.stringify({ at: new Date().toISOString(), sent: 0, failed: 0, error: "no arp program beside the hook; run arp share send" })}
|
|
4794
|
+
`);
|
|
4795
|
+
return null;
|
|
4796
|
+
}
|
|
4797
|
+
const child = spawn(sender.command, sender.args, { cwd, detached: true, stdio: "ignore", env: process.env });
|
|
4798
|
+
child.on("error", () => {
|
|
4799
|
+
return;
|
|
4800
|
+
});
|
|
4801
|
+
child.unref();
|
|
4802
|
+
} catch {}
|
|
4803
|
+
return null;
|
|
4804
|
+
}
|
|
4805
|
+
|
|
4806
|
+
// ../hook/src/worked.ts
|
|
4807
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
4808
|
+
import { join as join5 } from "node:path";
|
|
4031
4809
|
|
|
4032
4810
|
// ../../packages/protocol/src/local-outcomes.ts
|
|
4033
4811
|
var OUTCOME_ANSWERS2 = ["worked", "failed", "unsure"];
|
|
4034
4812
|
function isOutcomeAnswer2(word) {
|
|
4035
4813
|
return OUTCOME_ANSWERS2.includes(word);
|
|
4036
4814
|
}
|
|
4037
|
-
var
|
|
4815
|
+
var ANSWER2 = /^[\s>*`-]*arp:\s*([A-Za-z0-9._:@/-]+)\s+(worked|failed|unsure)[\s.`*]*$/gim;
|
|
4038
4816
|
function answersIn2(text, asked) {
|
|
4039
4817
|
const found = new Map;
|
|
4040
|
-
for (const match of text.matchAll(
|
|
4818
|
+
for (const match of text.matchAll(ANSWER2)) {
|
|
4041
4819
|
const [, skill, word] = match;
|
|
4042
4820
|
if (skill === undefined || word === undefined)
|
|
4043
4821
|
continue;
|
|
@@ -4050,7 +4828,6 @@ function answersIn2(text, asked) {
|
|
|
4050
4828
|
}
|
|
4051
4829
|
|
|
4052
4830
|
// ../hook/src/worked.ts
|
|
4053
|
-
var MAX_ASKED = 5;
|
|
4054
4831
|
function isRecord5(value) {
|
|
4055
4832
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4056
4833
|
}
|
|
@@ -4065,20 +4842,22 @@ function stopInputOf(raw) {
|
|
|
4065
4842
|
return {
|
|
4066
4843
|
session,
|
|
4067
4844
|
cwd: text(raw["cwd"]) ?? process.cwd(),
|
|
4068
|
-
active: field(raw, "stop_hook_active", "stopHookActive") === true,
|
|
4069
4845
|
last: text(field(raw, "last_assistant_message", "lastAssistantMessage")) ?? "",
|
|
4070
4846
|
transcript: text(field(raw, "transcript_path", "transcriptPath"))
|
|
4071
4847
|
};
|
|
4072
4848
|
}
|
|
4073
|
-
function
|
|
4074
|
-
return
|
|
4849
|
+
function requestFor(skill, share = false) {
|
|
4850
|
+
return `ARP: at the end of your final reply, add one line saying whether the skill ${skill} did its job: ` + `\`arp: ${skill} worked\`, \`arp: ${skill} failed\` or \`arp: ${skill} unsure\`. ` + "Kept on this machine unless `arp share` is on." + (share ? shareQuestionFor() : "");
|
|
4851
|
+
}
|
|
4852
|
+
function requestOutput(context) {
|
|
4853
|
+
return JSON.stringify({ hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext: context } });
|
|
4075
4854
|
}
|
|
4076
4855
|
var isArpsOwn = (name) => name === "arp" || name.endsWith(":arp");
|
|
4077
4856
|
function rows(path) {
|
|
4078
|
-
if (!
|
|
4857
|
+
if (!existsSync3(path))
|
|
4079
4858
|
return [];
|
|
4080
4859
|
const found = [];
|
|
4081
|
-
for (const line of
|
|
4860
|
+
for (const line of readFileSync4(path, "utf8").split(`
|
|
4082
4861
|
`)) {
|
|
4083
4862
|
if (line.trim() === "")
|
|
4084
4863
|
continue;
|
|
@@ -4094,7 +4873,7 @@ function withOf(skill, source, cwd, before) {
|
|
|
4094
4873
|
const name = skill.split(":").pop() ?? skill;
|
|
4095
4874
|
let need = null;
|
|
4096
4875
|
let latest = "";
|
|
4097
|
-
for (const row of rows(
|
|
4876
|
+
for (const row of rows(join5(arpHome(), "failures.jsonl"))) {
|
|
4098
4877
|
const { skill: rowSkill, source: rowSource, need: rowNeed, at } = row;
|
|
4099
4878
|
if (typeof rowSkill !== "string" || typeof rowNeed !== "string" || typeof at !== "string")
|
|
4100
4879
|
continue;
|
|
@@ -4112,11 +4891,11 @@ function withOf(skill, source, cwd, before) {
|
|
|
4112
4891
|
const directory = skillDirectory(name, cwd);
|
|
4113
4892
|
if (directory === null)
|
|
4114
4893
|
return null;
|
|
4115
|
-
return publishableNeed3(need,
|
|
4894
|
+
return publishableNeed3(need, readFileSync4(join5(directory, "SKILL.md"), "utf8"));
|
|
4116
4895
|
}
|
|
4117
4896
|
function record(input, asked, outcome, at) {
|
|
4118
4897
|
const search = foundBySearch(input.cwd, input.session, asked.skill);
|
|
4119
|
-
const words = input.transcript !== null &&
|
|
4898
|
+
const words = input.transcript !== null && existsSync3(input.transcript) ? lastUserWords(readFileSync4(input.transcript, "utf8")) : "";
|
|
4120
4899
|
const domain = search?.domain ?? contextOf(words, asked.skill);
|
|
4121
4900
|
const source = asked.source ?? search?.source ?? installedSource(asked.skill.split(":").pop() ?? asked.skill, input.cwd);
|
|
4122
4901
|
const named = outcome === "worked" ? withOf(asked.skill, source, input.cwd, at) : null;
|
|
@@ -4129,63 +4908,60 @@ function record(input, asked, outcome, at) {
|
|
|
4129
4908
|
outcome,
|
|
4130
4909
|
...named === null ? {} : { with: named }
|
|
4131
4910
|
};
|
|
4132
|
-
appendOwn(
|
|
4911
|
+
appendOwn(join5(arpHome(), "outcomes.jsonl"), `${JSON.stringify(line)}
|
|
4912
|
+
`);
|
|
4913
|
+
}
|
|
4914
|
+
function askOnSkill(session, skill, source, cwd = process.cwd(), now = new Date) {
|
|
4915
|
+
if (isArpsOwn(skill))
|
|
4916
|
+
return null;
|
|
4917
|
+
try {
|
|
4918
|
+
const home = arpHome();
|
|
4919
|
+
mkdirSync4(home, { recursive: true });
|
|
4920
|
+
const askedPath = join5(home, "asked.jsonl");
|
|
4921
|
+
if (rows(askedPath).some((row2) => row2["session"] === session && row2["skill"] === skill))
|
|
4922
|
+
return null;
|
|
4923
|
+
const row = { at: now.toISOString(), session, skill, source };
|
|
4924
|
+
appendOwn(askedPath, `${JSON.stringify(row)}
|
|
4133
4925
|
`);
|
|
4926
|
+
return requestFor(skill, offerShare(session, skill, source, cwd, now));
|
|
4927
|
+
} catch {
|
|
4928
|
+
return null;
|
|
4929
|
+
}
|
|
4134
4930
|
}
|
|
4135
4931
|
async function stop(payload) {
|
|
4136
4932
|
const input = stopInputOf(payload);
|
|
4137
|
-
if (input
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
return ask === null ? null : JSON.stringify(ask);
|
|
4933
|
+
if (input !== null)
|
|
4934
|
+
onStop(input);
|
|
4935
|
+
return null;
|
|
4141
4936
|
}
|
|
4142
4937
|
function onStop(input, now = new Date) {
|
|
4143
4938
|
try {
|
|
4144
4939
|
const home = arpHome();
|
|
4145
|
-
|
|
4146
|
-
const askedPath = join4(home, "asked.jsonl");
|
|
4147
|
-
const asked = rows(askedPath).flatMap((row) => row["session"] === input.session && typeof row["skill"] === "string" ? [{
|
|
4940
|
+
const asked = rows(join5(home, "asked.jsonl")).flatMap((row) => row["session"] === input.session && typeof row["skill"] === "string" ? [{
|
|
4148
4941
|
at: typeof row["at"] === "string" ? row["at"] : "",
|
|
4149
4942
|
session: input.session,
|
|
4150
4943
|
skill: row["skill"],
|
|
4151
4944
|
source: typeof row["source"] === "string" ? row["source"] : null
|
|
4152
4945
|
}] : []);
|
|
4153
|
-
const answered = new Set(rows(
|
|
4946
|
+
const answered = new Set(rows(join5(home, "outcomes.jsonl")).flatMap((row) => row["session"] === input.session && typeof row["skill"] === "string" ? [row["skill"]] : []));
|
|
4154
4947
|
const pending = asked.filter((row) => !answered.has(row.skill));
|
|
4155
4948
|
const at = now.toISOString();
|
|
4156
|
-
|
|
4949
|
+
const answers = answersIn2(input.last, pending.map((row) => row.skill));
|
|
4950
|
+
for (const [skill, answer] of answers) {
|
|
4157
4951
|
const row = pending.find((entry) => entry.skill === skill);
|
|
4158
4952
|
if (row !== undefined)
|
|
4159
4953
|
record(input, row, answer, at);
|
|
4160
4954
|
}
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
const known = new Set(asked.map((row) => row.skill));
|
|
4164
|
-
const log = process.env["ARP_SESSION_LOG"] ?? join4(input.cwd, ".arp", "session.jsonl");
|
|
4165
|
-
const fresh = [];
|
|
4166
|
-
for (const skill of skillsOfSession(log, input.session)) {
|
|
4167
|
-
if (known.has(skill.name) || isArpsOwn(skill.name))
|
|
4168
|
-
continue;
|
|
4169
|
-
known.add(skill.name);
|
|
4170
|
-
fresh.push({ at, session: input.session, skill: skill.name, source: skill.source });
|
|
4171
|
-
}
|
|
4172
|
-
const ask = fresh.slice(0, MAX_ASKED);
|
|
4173
|
-
if (ask.length === 0)
|
|
4174
|
-
return null;
|
|
4175
|
-
mkdirSync3(dirname3(askedPath), { recursive: true });
|
|
4176
|
-
appendOwn(askedPath, ask.map((row) => `${JSON.stringify(row)}
|
|
4177
|
-
`).join(""));
|
|
4178
|
-
return { decision: "block", reason: questionFor(ask.map((row) => row.skill)) };
|
|
4179
|
-
} catch {
|
|
4180
|
-
return null;
|
|
4181
|
-
}
|
|
4955
|
+
onStopShare(input.session, input.last, answers, now);
|
|
4956
|
+
} catch {}
|
|
4182
4957
|
}
|
|
4183
4958
|
|
|
4184
4959
|
// ../hook/src/hook.ts
|
|
4185
4960
|
var ROUTES = {
|
|
4186
4961
|
PreToolUse: (payload) => preToolUse(payload),
|
|
4187
4962
|
PostToolUseFailure: (payload) => postToolUseFailure(payload),
|
|
4188
|
-
Stop: (payload) => stop(payload)
|
|
4963
|
+
Stop: (payload) => stop(payload),
|
|
4964
|
+
SessionEnd: (payload) => sessionEnd(payload)
|
|
4189
4965
|
};
|
|
4190
4966
|
async function route(handler, payload) {
|
|
4191
4967
|
let out = null;
|
|
@@ -4205,12 +4981,12 @@ async function route(handler, payload) {
|
|
|
4205
4981
|
});
|
|
4206
4982
|
}
|
|
4207
4983
|
function fetchedRefs(cwd) {
|
|
4208
|
-
const path = process.env["ARP_RECEIPTS"] ??
|
|
4209
|
-
if (!
|
|
4984
|
+
const path = process.env["ARP_RECEIPTS"] ?? join6(cwd, ".arp", "receipts.jsonl");
|
|
4985
|
+
if (!existsSync4(path))
|
|
4210
4986
|
return [];
|
|
4211
4987
|
let text2;
|
|
4212
4988
|
try {
|
|
4213
|
-
text2 =
|
|
4989
|
+
text2 = readFileSync5(path, "utf8");
|
|
4214
4990
|
} catch {
|
|
4215
4991
|
return [];
|
|
4216
4992
|
}
|
|
@@ -4245,7 +5021,7 @@ function responseOf(call, outcome) {
|
|
|
4245
5021
|
} catch {
|
|
4246
5022
|
bytes = 0;
|
|
4247
5023
|
}
|
|
4248
|
-
return { answered: raw !== null, bytes, failed: outcome
|
|
5024
|
+
return { answered: raw !== null, bytes, failed: isFailure(outcome) };
|
|
4249
5025
|
}
|
|
4250
5026
|
function readString2(from, key) {
|
|
4251
5027
|
const value = from?.[key];
|
|
@@ -4283,19 +5059,19 @@ function whatOf(call, cwd, fetched) {
|
|
|
4283
5059
|
var raw = "";
|
|
4284
5060
|
for await (const chunk of process.stdin)
|
|
4285
5061
|
raw += chunk;
|
|
4286
|
-
var
|
|
5062
|
+
var parsed2;
|
|
4287
5063
|
try {
|
|
4288
|
-
|
|
5064
|
+
parsed2 = JSON.parse(raw);
|
|
4289
5065
|
} catch {
|
|
4290
5066
|
process.exit(0);
|
|
4291
5067
|
}
|
|
4292
5068
|
if (process.argv.includes("--capture"))
|
|
4293
|
-
capture(
|
|
4294
|
-
var event = eventOf(
|
|
5069
|
+
capture(parsed2, join6(homedir3(), ".arp", "captures"));
|
|
5070
|
+
var event = eventOf(parsed2);
|
|
4295
5071
|
var handler = event === null ? undefined : ROUTES[event];
|
|
4296
5072
|
if (handler !== undefined)
|
|
4297
|
-
await route(handler,
|
|
4298
|
-
var call = toolCallOf(
|
|
5073
|
+
await route(handler, parsed2);
|
|
5074
|
+
var call = toolCallOf(parsed2);
|
|
4299
5075
|
if (call === null)
|
|
4300
5076
|
process.exit(0);
|
|
4301
5077
|
var cwd = call.cwd ?? process.cwd();
|
|
@@ -4311,8 +5087,8 @@ if (what === null)
|
|
|
4311
5087
|
var classified = classifyCall(call);
|
|
4312
5088
|
if (classified === null)
|
|
4313
5089
|
process.exit(0);
|
|
4314
|
-
var own =
|
|
4315
|
-
var path = process.env["ARP_SESSION_LOG"] ??
|
|
5090
|
+
var own = join6(cwd, ".arp");
|
|
5091
|
+
var path = process.env["ARP_SESSION_LOG"] ?? join6(own, "session.jsonl");
|
|
4316
5092
|
var line = {
|
|
4317
5093
|
at: new Date().toISOString(),
|
|
4318
5094
|
session: call.session,
|
|
@@ -4325,13 +5101,19 @@ var line = {
|
|
|
4325
5101
|
response: responseOf(call, classified.outcome)
|
|
4326
5102
|
};
|
|
4327
5103
|
try {
|
|
4328
|
-
|
|
5104
|
+
mkdirSync5(dirname4(path), { recursive: true });
|
|
4329
5105
|
if (dirname4(path) === own && lstatSync2(own).isSymbolicLink())
|
|
4330
5106
|
throw new Error(`${own} is a symbolic link`);
|
|
4331
|
-
const ignore =
|
|
4332
|
-
if (dirname4(path) === own && !
|
|
4333
|
-
|
|
5107
|
+
const ignore = join6(own, ".gitignore");
|
|
5108
|
+
if (dirname4(path) === own && !existsSync4(ignore))
|
|
5109
|
+
writeFileSync3(ignore, `*
|
|
4334
5110
|
`, { flag: "wx" });
|
|
4335
5111
|
appendOwn(path, `${JSON.stringify(line)}
|
|
4336
5112
|
`);
|
|
4337
5113
|
} catch {}
|
|
5114
|
+
if (what.kind === "skill" && call.event === "PostToolUse" && classified.outcome === "ok" && call.session !== null) {
|
|
5115
|
+
const request = askOnSkill(call.session, what.name, what.source, cwd);
|
|
5116
|
+
if (request !== null)
|
|
5117
|
+
process.stdout.write(`${requestOutput(request)}
|
|
5118
|
+
`);
|
|
5119
|
+
}
|