@arp-protocol/cli 0.0.0-stage → 0.0.1
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 +68 -2
- package/dist/chunks/backfill-j4hxt1w1.js +262 -0
- package/dist/chunks/ccip-qmytgn8e.js +198 -0
- package/dist/chunks/cli-0hqxver6.js +2042 -0
- package/dist/chunks/cli-0z6f3603.js +7527 -0
- package/dist/chunks/cli-3kbbwfbp.js +359 -0
- package/dist/chunks/cli-74hv4etk.js +14371 -0
- package/dist/chunks/cli-c8568g4c.js +4043 -0
- package/dist/chunks/cli-d3dy7cxe.js +2110 -0
- package/dist/chunks/cli-g5zvr10v.js +19 -0
- package/dist/chunks/cli-tyey74p3.js +303 -0
- package/dist/chunks/cli-v446mw5v.js +1033 -0
- package/dist/chunks/cli-zpft011j.js +43 -0
- package/dist/chunks/commands-dnk10vze.js +805 -0
- package/dist/chunks/env-s4585w6j.js +56 -0
- package/dist/chunks/index-3a3ssef1.js +53 -0
- package/dist/chunks/line-zy1tc2np.js +45 -0
- package/dist/chunks/node-0ydee9hy.js +593 -0
- package/dist/chunks/node-init-4v2m2vj0.js +255 -0
- package/dist/chunks/secp256k1-j2w0pmjr.js +17 -0
- package/dist/chunks/server-jmyjttvv.js +3170 -0
- package/dist/chunks/share-g8v5qe6r.js +101 -0
- package/dist/cli.js +229 -0
- package/dist/hook.js +1027 -0
- package/package.json +47 -4
package/dist/hook.js
ADDED
|
@@ -0,0 +1,1027 @@
|
|
|
1
|
+
// ../hook/src/append.ts
|
|
2
|
+
import { closeSync, constants, lstatSync, openSync, writeSync } from "node:fs";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
function appendOwn(path, text) {
|
|
5
|
+
if (lstatSync(dirname(path)).isSymbolicLink())
|
|
6
|
+
throw new Error(`${dirname(path)} is a symbolic link`);
|
|
7
|
+
const flags = constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | (constants.O_NOFOLLOW ?? 0);
|
|
8
|
+
const fd = openSync(path, flags, 384);
|
|
9
|
+
try {
|
|
10
|
+
writeSync(fd, text);
|
|
11
|
+
} finally {
|
|
12
|
+
closeSync(fd);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// ../hook/src/hook.ts
|
|
17
|
+
import { existsSync as existsSync2, lstatSync as lstatSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
18
|
+
import { homedir as homedir3 } from "node:os";
|
|
19
|
+
import { dirname as dirname3, join as join4 } 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
|
+
// ../../packages/protocol/dist/session.js
|
|
53
|
+
function refForInvocation(name, source, fetched) {
|
|
54
|
+
const bare = name.includes(":") ? name.split(":").pop() ?? name : name;
|
|
55
|
+
let matches = fetched.filter((row) => row.skill === bare);
|
|
56
|
+
if (source !== null && matches.length > 1) {
|
|
57
|
+
const narrowed = matches.filter((row) => source.includes(repoOf(row.url)));
|
|
58
|
+
if (narrowed.length > 0)
|
|
59
|
+
matches = narrowed;
|
|
60
|
+
}
|
|
61
|
+
const atoms = new Set(matches.map((row) => row.atom.toLowerCase()));
|
|
62
|
+
if (atoms.size !== 1)
|
|
63
|
+
return null;
|
|
64
|
+
return matches[0]?.atom ?? null;
|
|
65
|
+
}
|
|
66
|
+
function repoOf(url) {
|
|
67
|
+
const parts = /github\.com\/([^/?#]+)\/([^/?#]+)/.exec(url);
|
|
68
|
+
return parts === null ? url : `${parts[1]}/${parts[2]}`;
|
|
69
|
+
}
|
|
70
|
+
// ../../packages/protocol/dist/skill-requires.js
|
|
71
|
+
var LOGIN = /\b(auth login|log ?in|sign ?up|api[ _-]?key|access token)\b/i;
|
|
72
|
+
function frontmatter(markdown) {
|
|
73
|
+
const lines = markdown.split(`
|
|
74
|
+
`);
|
|
75
|
+
if (lines[0]?.trim() !== "---")
|
|
76
|
+
return { lines: [], bodyStart: 0 };
|
|
77
|
+
const end = lines.findIndex((line, index) => index > 0 && line.trim() === "---");
|
|
78
|
+
return end === -1 ? { lines: [], bodyStart: 0 } : { lines: lines.slice(1, end), bodyStart: end + 1 };
|
|
79
|
+
}
|
|
80
|
+
function unquote(value) {
|
|
81
|
+
return value.trim().replace(/^["']|["']$/g, "");
|
|
82
|
+
}
|
|
83
|
+
function declaredBins(lines) {
|
|
84
|
+
const at = lines.findIndex((line) => /^\s*bins\s*:/.test(line));
|
|
85
|
+
if (at === -1)
|
|
86
|
+
return [];
|
|
87
|
+
const inline = lines[at]?.split(":").slice(1).join(":").trim() ?? "";
|
|
88
|
+
if (inline.startsWith("[")) {
|
|
89
|
+
return inline.replace(/^\[|\]$/g, "").split(",").map(unquote).filter((name) => name.length > 0);
|
|
90
|
+
}
|
|
91
|
+
const out = [];
|
|
92
|
+
for (const line of lines.slice(at + 1)) {
|
|
93
|
+
const item = /^\s*-\s*(.+)$/.exec(line);
|
|
94
|
+
if (item === null)
|
|
95
|
+
break;
|
|
96
|
+
out.push(unquote(item[1] ?? ""));
|
|
97
|
+
}
|
|
98
|
+
return out.filter((name) => name.length > 0);
|
|
99
|
+
}
|
|
100
|
+
function bashTools(lines) {
|
|
101
|
+
const line = lines.find((candidate) => /^\s*allowed-tools\s*:/.test(candidate));
|
|
102
|
+
if (line === undefined)
|
|
103
|
+
return [];
|
|
104
|
+
return [...line.matchAll(/Bash\(\s*([A-Za-z0-9._-]+)/g)].map((match) => match[1] ?? "").filter((name) => name.length > 0);
|
|
105
|
+
}
|
|
106
|
+
function declaredDescription(lines) {
|
|
107
|
+
const at = lines.findIndex((line) => /^description\s*:/.test(line));
|
|
108
|
+
if (at === -1)
|
|
109
|
+
return null;
|
|
110
|
+
const inline = lines[at]?.split(":").slice(1).join(":").trim() ?? "";
|
|
111
|
+
if (inline !== "" && !/^[>|][-+]?$/.test(inline))
|
|
112
|
+
return unquote(inline) || null;
|
|
113
|
+
const block = [];
|
|
114
|
+
for (const line of lines.slice(at + 1)) {
|
|
115
|
+
if (!/^\s+\S/.test(line))
|
|
116
|
+
break;
|
|
117
|
+
block.push(line.trim());
|
|
118
|
+
}
|
|
119
|
+
return block.length === 0 ? null : block.join(" ");
|
|
120
|
+
}
|
|
121
|
+
var COMMON = new Set(("awk bash break bun case cat cd chmod command continue cp curl cut date diff do done echo else end env " + "esac exit export false fi find for function git grep head if kill ln local ls mkdir mv node npm npx open pip pip3 pnpm printf pwd " + "python python3 read return rm sed set sh shift sleep sort source sudo tail tar tee test then time " + "touch tr true uniq unset unzip wait wc wget which while xargs yarn zip").split(" "));
|
|
122
|
+
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;
|
|
123
|
+
var PROBE = /\b(?:command\s+-v|which)\s+([a-z][\w-]*)/i;
|
|
124
|
+
var COMMAND = /^[a-z][a-z0-9_-]*$/;
|
|
125
|
+
function commandsIn(shell, explicitOnly) {
|
|
126
|
+
const found = [];
|
|
127
|
+
for (const segment of shell.split(/&&|\|\||[|;]/)) {
|
|
128
|
+
const line = segment.trim().replace(/^\$\s+/, "");
|
|
129
|
+
if (line === "" || line.startsWith("#"))
|
|
130
|
+
continue;
|
|
131
|
+
const named = INSTALL.exec(line)?.[1] ?? PROBE.exec(line)?.[1];
|
|
132
|
+
if (named !== undefined) {
|
|
133
|
+
found.push(named);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (explicitOnly)
|
|
137
|
+
continue;
|
|
138
|
+
const words = line.split(/\s+/).filter((word) => !/^[A-Za-z_][A-Za-z0-9_]*=/.test(word));
|
|
139
|
+
const first = words[0] === "sudo" ? words[1] : words[0];
|
|
140
|
+
if (first === undefined || !COMMAND.test(first))
|
|
141
|
+
continue;
|
|
142
|
+
found.push(first);
|
|
143
|
+
}
|
|
144
|
+
return found;
|
|
145
|
+
}
|
|
146
|
+
var SHELL_FENCE = /^(sh|bash|shell|zsh|console|terminal)$/i;
|
|
147
|
+
function shellLinesRun(lines) {
|
|
148
|
+
const run = [];
|
|
149
|
+
let heredocEnd = null;
|
|
150
|
+
let quote = null;
|
|
151
|
+
let continued = false;
|
|
152
|
+
for (const line of lines) {
|
|
153
|
+
if (heredocEnd !== null) {
|
|
154
|
+
if (line.trim() === heredocEnd)
|
|
155
|
+
heredocEnd = null;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (quote === null && !continued)
|
|
159
|
+
run.push(line);
|
|
160
|
+
continued = line.trimEnd().endsWith("\\");
|
|
161
|
+
heredocEnd = /<<-?\s*['"]?([A-Za-z_]+)/.exec(line)?.[1] ?? null;
|
|
162
|
+
for (const char of line) {
|
|
163
|
+
if (quote === null && (char === '"' || char === "'"))
|
|
164
|
+
quote = char;
|
|
165
|
+
else if (char === quote)
|
|
166
|
+
quote = null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return run;
|
|
170
|
+
}
|
|
171
|
+
function commandsRun(body, declared) {
|
|
172
|
+
const found = [];
|
|
173
|
+
let fence = null;
|
|
174
|
+
let fenced = [];
|
|
175
|
+
for (const line of body) {
|
|
176
|
+
const marker = /^\s*(?:```|~~~)\s*([\w-]*)/.exec(line);
|
|
177
|
+
if (marker !== null) {
|
|
178
|
+
if (fence === "shell") {
|
|
179
|
+
for (const run of shellLinesRun(fenced))
|
|
180
|
+
found.push(...commandsIn(run, false));
|
|
181
|
+
}
|
|
182
|
+
fenced = [];
|
|
183
|
+
fence = fence !== null ? null : SHELL_FENCE.test(marker[1] ?? "") ? "shell" : "other";
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (fence !== null) {
|
|
187
|
+
fenced.push(line);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
for (const inline of line.matchAll(/`([^`]+)`/g))
|
|
191
|
+
found.push(...commandsIn(inline[1] ?? "", true));
|
|
192
|
+
}
|
|
193
|
+
return [...new Set(found)].filter((name) => !COMMON.has(name) && !declared.includes(name));
|
|
194
|
+
}
|
|
195
|
+
function requiresLine(lines) {
|
|
196
|
+
const name = lines.map((line) => /^\s*requires\s*:\s*(\S+)\s*$/.exec(line)?.[1]).find(Boolean);
|
|
197
|
+
return name === undefined ? [] : [unquote(name)];
|
|
198
|
+
}
|
|
199
|
+
function readRequires(markdown) {
|
|
200
|
+
const { lines, bodyStart } = frontmatter(markdown);
|
|
201
|
+
const cli = [...new Set([...declaredBins(lines), ...bashTools(lines), ...requiresLine(lines)])];
|
|
202
|
+
const description = declaredDescription(lines);
|
|
203
|
+
const body = markdown.split(`
|
|
204
|
+
`);
|
|
205
|
+
const runs = commandsRun(body.slice(bodyStart), cli);
|
|
206
|
+
for (let index = bodyStart;index < body.length; index += 1) {
|
|
207
|
+
const text = body[index] ?? "";
|
|
208
|
+
if (LOGIN.test(text)) {
|
|
209
|
+
return { cli, runs, login: { line: index + 1, text: text.trim().slice(0, 160) }, description };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return { cli, runs, login: null, description };
|
|
213
|
+
}
|
|
214
|
+
// ../../packages/protocol/dist/skill-domains.js
|
|
215
|
+
var SKILL_DOMAINS = [
|
|
216
|
+
{
|
|
217
|
+
domain: "payment",
|
|
218
|
+
parent: "tool",
|
|
219
|
+
words: ["payment", "stripe", "paypal", "invoice", "checkout", "billing", "paywall"]
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
domain: "video",
|
|
223
|
+
parent: "tool",
|
|
224
|
+
words: ["video", "remotion", "hyperframes", "keyframe", "footage", "youtube"],
|
|
225
|
+
nameWords: ["render", "caption", "motion", "title", "carousel", "cursor", "frame", "overlay", "shard", "animation", "avatar", "clip"]
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
domain: "image",
|
|
229
|
+
parent: "tool",
|
|
230
|
+
words: ["image generation", "inpainting", "outpainting", "flux", "midjourney", "stable diffusion", "nano banana"],
|
|
231
|
+
nameWords: ["image", "photo", "relight", "upscale", "thumbnail", "imagegen"]
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
domain: "audio",
|
|
235
|
+
parent: "tool",
|
|
236
|
+
words: ["elevenlabs", "music generation", "text to speech", "voiceover", "sound effect", "instrumental"],
|
|
237
|
+
nameWords: ["audio", "music", "voice", "speech", "sound"]
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
domain: "document",
|
|
241
|
+
parent: "tool",
|
|
242
|
+
words: ["pptx", "docx", "xlsx", "powerpoint", "spreadsheet", ".potx", "pdf file"],
|
|
243
|
+
nameWords: ["pdf", "docx", "pptx", "xlsx", "document", "slide", "markdown"]
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
domain: "scraping",
|
|
247
|
+
parent: "tool",
|
|
248
|
+
words: ["scrap", "crawl", "firecrawl", "apify"],
|
|
249
|
+
nameWords: ["extract", "scrape"]
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
domain: "browser",
|
|
253
|
+
parent: "tool",
|
|
254
|
+
words: ["playwright", "puppeteer", "headless browser", "selenium"],
|
|
255
|
+
nameWords: ["browser", "chrome"]
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
domain: "database",
|
|
259
|
+
parent: "tool",
|
|
260
|
+
words: ["database", "postgres", "prisma", "supabase", "mongo", " sql"],
|
|
261
|
+
nameWords: ["schema", "query", "migration", "sql"]
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
domain: "design",
|
|
265
|
+
parent: "tool",
|
|
266
|
+
words: ["figma", "typograph", "design system", "design token"],
|
|
267
|
+
nameWords: ["design", "ux", "colour", "color", "layout", "logo", "icon", "theme"]
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
domain: "collaboration",
|
|
271
|
+
parent: "tool",
|
|
272
|
+
words: ["slack", "notion", "jira", "linear", "atlassian", "monday", "飞书", "obsidian"],
|
|
273
|
+
nameWords: ["lark", "calendar", "whiteboard", "meeting"]
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
domain: "marketing",
|
|
277
|
+
parent: "tool",
|
|
278
|
+
words: ["marketing", "seo", "campaign", "copywriting", "newsletter", "onboarding", "referral", "product hunt"],
|
|
279
|
+
nameWords: ["cro", "launch", "photography", "signup", "popup", "ads", "audience", "growth", "brand"]
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
domain: "blockchain",
|
|
283
|
+
parent: "tool",
|
|
284
|
+
words: ["blockchain", "onchain", "on-chain", "ethereum", "solidity", "web3", "smart contract", "erc-20", "erc-721"],
|
|
285
|
+
nameWords: ["wallet", "crypto", "nft", "defi"]
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
domain: "infrastructure",
|
|
289
|
+
parent: "tool",
|
|
290
|
+
words: ["deploy", "docker", "kubernetes", "terraform", "cloudflare", "aws", "azure", "serverless", "ci/cd", "firebase", "hosting", "vercel"],
|
|
291
|
+
nameWords: ["infra", "cli"]
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
domain: "observability",
|
|
295
|
+
parent: "tool",
|
|
296
|
+
words: ["observab", "logging", "grafana", "sentry", "telemetry"],
|
|
297
|
+
nameWords: ["monitor", "metric", "trace", "alert", "log"]
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
domain: "quality",
|
|
301
|
+
parent: "tool",
|
|
302
|
+
words: ["linting", "unit test", "code coverage", "static analysis"],
|
|
303
|
+
nameWords: ["test", "lint", "review", "debug", "audit", "coverage", "quality", "refactor", "eval"]
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
domain: "writing",
|
|
307
|
+
parent: "tool",
|
|
308
|
+
words: ["cold email", "pr body", "commit message", "conventional commits"],
|
|
309
|
+
nameWords: ["writing", "write", "article", "prose", "caveman", "cave", "edit"]
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
domain: "method",
|
|
313
|
+
parent: "tool",
|
|
314
|
+
words: ["handoff", "hand the current", "implementation plan", "subagent", "superpowers", "overbuilding", "chief of staff", "retrospective", "acceptance condition", "router over", "re-pitch"],
|
|
315
|
+
nameWords: ["handoff", "triage", "retro", "brainstorm", "spec", "prd", "questionnaire", "plan", "implement", "dispatch", "execute", "executing", "investigate", "wayfinder", "decision", "teach", "verify", "verification", "authoring", "template", "zoom", "wait", "grill", "ask", "manage", "optimize", "worktree", "lean", "sensei", "help", "ticket", "issue"]
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
domain: "website",
|
|
319
|
+
parent: "tool",
|
|
320
|
+
words: ["landing page", "static site", "next.js", "nextjs", "tailwind"],
|
|
321
|
+
nameWords: ["website", "html", "css", "landing", "frontend", "react"]
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
domain: "api",
|
|
325
|
+
parent: "tool",
|
|
326
|
+
words: ["rest api", "graphql", "webhook", "openapi", "postman"],
|
|
327
|
+
nameWords: ["api", "endpoint", "rest"]
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
domain: "automation",
|
|
331
|
+
parent: "tool",
|
|
332
|
+
words: ["zapier", "cron", "orchestrat"],
|
|
333
|
+
nameWords: ["automation", "workflow", "schedule", "pipeline"]
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
domain: "development",
|
|
337
|
+
parent: "tool",
|
|
338
|
+
words: ["typescript", "python", "rust ", "sdk"],
|
|
339
|
+
nameWords: ["code", "compile", "library", "framework", "git"]
|
|
340
|
+
}
|
|
341
|
+
];
|
|
342
|
+
function classifySkill(skill, description) {
|
|
343
|
+
const name = ` ${skill.replace(/-/g, " ").toLowerCase()} `;
|
|
344
|
+
const both = `${name}${description.toLowerCase()}`;
|
|
345
|
+
const hit = SKILL_DOMAINS.find((rule) => rule.words.some((word) => both.includes(word)) || (rule.nameWords ?? []).some((word) => name.includes(` ${word}`)));
|
|
346
|
+
return hit?.domain ?? null;
|
|
347
|
+
}
|
|
348
|
+
// ../../packages/protocol/dist/content-path.js
|
|
349
|
+
function skillRefFromUrl(url) {
|
|
350
|
+
let parsed;
|
|
351
|
+
try {
|
|
352
|
+
parsed = new URL(url.includes("://") ? url : `https://${url}`);
|
|
353
|
+
} catch {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
if (parsed.hostname.replace(/^www\./, "") !== "github.com")
|
|
357
|
+
return null;
|
|
358
|
+
const skill = parsed.searchParams.get("skill");
|
|
359
|
+
if (skill === null || skill.length === 0)
|
|
360
|
+
return null;
|
|
361
|
+
const [owner, repo] = parsed.pathname.replace(/^\/+/, "").replace(/\/+$/, "").replace(/\.git$/, "").split("/");
|
|
362
|
+
if (owner === undefined || repo === undefined)
|
|
363
|
+
return null;
|
|
364
|
+
if (owner.length === 0 || repo.length === 0)
|
|
365
|
+
return null;
|
|
366
|
+
return { repo: `${owner}/${repo}`, skill };
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// ../../packages/protocol/dist/share-failures.js
|
|
370
|
+
function installedFromSkillLock(lock) {
|
|
371
|
+
if (lock === null)
|
|
372
|
+
return [];
|
|
373
|
+
try {
|
|
374
|
+
const parsed = JSON.parse(lock);
|
|
375
|
+
return Object.entries(parsed.skills ?? {}).flatMap(([skill, entry]) => entry?.sourceType === "github" && typeof entry.source === "string" ? [{ skill, source: entry.source }] : []);
|
|
376
|
+
} catch {
|
|
377
|
+
return [];
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
function installedFromReceipts(text) {
|
|
381
|
+
const found = [];
|
|
382
|
+
for (const line of (text ?? "").split(`
|
|
383
|
+
`)) {
|
|
384
|
+
if (line.trim() === "")
|
|
385
|
+
continue;
|
|
386
|
+
try {
|
|
387
|
+
const row = JSON.parse(line);
|
|
388
|
+
const url = typeof row.url === "string" ? row.url : row.receipt?.url;
|
|
389
|
+
const ref = typeof url === "string" ? skillRefFromUrl(url) : null;
|
|
390
|
+
if (ref !== null)
|
|
391
|
+
found.push({ skill: ref.skill, source: ref.repo });
|
|
392
|
+
} catch {
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return found;
|
|
397
|
+
}
|
|
398
|
+
// ../../packages/protocol/dist/pulse-score.js
|
|
399
|
+
function inferDomain(query, labels) {
|
|
400
|
+
const words = ` ${query.toLowerCase().replace(/[^a-z0-9-]+/g, " ").trim()} `;
|
|
401
|
+
const matches = labels.filter((label) => words.includes(` ${label.toLowerCase()} `));
|
|
402
|
+
return [...matches].sort((a, b) => b.length - a.length)[0] ?? null;
|
|
403
|
+
}
|
|
404
|
+
// ../hook/src/classify.ts
|
|
405
|
+
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;
|
|
406
|
+
var AUTH = /\b(unauthori[sz]ed|forbidden|invalid (api )?(key|token)|authentication|not authenticated|expired token)\b/i;
|
|
407
|
+
var TIMEOUT = /\b(timed? ?out|timeout|deadline exceeded|ETIMEDOUT)\b/i;
|
|
408
|
+
var UNREACHABLE = /\b(ENOTFOUND|EAI_AGAIN|ECONNREFUSED|ECONNRESET|EHOSTUNREACH|ENETUNREACH|getaddrinfo|certificate|TLS|SSL|socket hang up|connection refused|fetch failed)\b/i;
|
|
409
|
+
var PAYMENT = /\b(payment required|insufficient (funds|balance)|payment (failed|refused|rejected)|x402)\b/i;
|
|
410
|
+
var STATUS = /\b(40[123]|429|5\d\d)\b/;
|
|
411
|
+
function isRecord(value) {
|
|
412
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
413
|
+
}
|
|
414
|
+
function failureText(call) {
|
|
415
|
+
const parts = [];
|
|
416
|
+
const push = (value) => {
|
|
417
|
+
if (typeof value === "string")
|
|
418
|
+
parts.push(value);
|
|
419
|
+
else if (isRecord(value)) {
|
|
420
|
+
for (const key of ["error", "message", "reason"]) {
|
|
421
|
+
const text = value[key];
|
|
422
|
+
if (typeof text === "string")
|
|
423
|
+
parts.push(text);
|
|
424
|
+
}
|
|
425
|
+
if (Array.isArray(value.content)) {
|
|
426
|
+
for (const item of value.content) {
|
|
427
|
+
if (isRecord(item) && typeof item.text === "string")
|
|
428
|
+
parts.push(item.text);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
push(call.error);
|
|
434
|
+
push(call.response);
|
|
435
|
+
return parts.join(`
|
|
436
|
+
`).slice(0, 4000);
|
|
437
|
+
}
|
|
438
|
+
function statusOf(call) {
|
|
439
|
+
if (!isRecord(call.response))
|
|
440
|
+
return null;
|
|
441
|
+
for (const key of ["status", "statusCode", "status_code"]) {
|
|
442
|
+
const value = call.response[key];
|
|
443
|
+
if (typeof value === "number" && Number.isInteger(value))
|
|
444
|
+
return value;
|
|
445
|
+
}
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
function flaggedError(response) {
|
|
449
|
+
return isRecord(response) && (response.isError === true || response.is_error === true);
|
|
450
|
+
}
|
|
451
|
+
function classifyCall(call) {
|
|
452
|
+
if (call.interrupted)
|
|
453
|
+
return null;
|
|
454
|
+
const failed = call.event === "PostToolUseFailure" || call.error !== null || flaggedError(call.response);
|
|
455
|
+
const status = statusOf(call);
|
|
456
|
+
if (!failed && (status === null || status < 400))
|
|
457
|
+
return { outcome: "ok", code: null };
|
|
458
|
+
const text = failureText(call);
|
|
459
|
+
if (CANCELLED.test(text))
|
|
460
|
+
return null;
|
|
461
|
+
const found = STATUS.exec(text);
|
|
462
|
+
const match = status ?? (found === null ? null : Number(found[0]));
|
|
463
|
+
const code = match === null ? null : String(match);
|
|
464
|
+
if (match === 402 || PAYMENT.test(text))
|
|
465
|
+
return { outcome: "payment", code };
|
|
466
|
+
if (match === 401 || match === 403 || AUTH.test(text))
|
|
467
|
+
return { outcome: "auth", code };
|
|
468
|
+
if (TIMEOUT.test(text))
|
|
469
|
+
return { outcome: "timeout", code };
|
|
470
|
+
if (UNREACHABLE.test(text))
|
|
471
|
+
return { outcome: "unreachable", code };
|
|
472
|
+
return { outcome: "tool_error", code };
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// ../hook/src/mcp-config.ts
|
|
476
|
+
import { readFileSync } from "node:fs";
|
|
477
|
+
import { homedir } from "node:os";
|
|
478
|
+
import { join } from "node:path";
|
|
479
|
+
function isRecord2(value) {
|
|
480
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
481
|
+
}
|
|
482
|
+
function readJson(path) {
|
|
483
|
+
try {
|
|
484
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
485
|
+
} catch {
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
function entryOf(raw) {
|
|
490
|
+
if (!isRecord2(raw))
|
|
491
|
+
return null;
|
|
492
|
+
if (typeof raw.url === "string" && raw.url.length > 0)
|
|
493
|
+
return { transport: "remote", url: raw.url };
|
|
494
|
+
if (typeof raw.command === "string" && raw.command.length > 0) {
|
|
495
|
+
const args = Array.isArray(raw.args) ? raw.args.filter((arg) => typeof arg === "string") : [];
|
|
496
|
+
return { transport: "local", command: raw.command, args };
|
|
497
|
+
}
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
function collect(into, servers) {
|
|
501
|
+
if (!isRecord2(servers))
|
|
502
|
+
return;
|
|
503
|
+
for (const [name, raw] of Object.entries(servers)) {
|
|
504
|
+
const entry = entryOf(raw);
|
|
505
|
+
if (entry !== null)
|
|
506
|
+
into.set(name, entry);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
function readMcpServers({
|
|
510
|
+
cwd,
|
|
511
|
+
home = homedir()
|
|
512
|
+
}) {
|
|
513
|
+
const servers = new Map;
|
|
514
|
+
const user = readJson(join(home, ".claude.json"));
|
|
515
|
+
if (isRecord2(user)) {
|
|
516
|
+
collect(servers, user.mcpServers);
|
|
517
|
+
if (isRecord2(user.projects)) {
|
|
518
|
+
const project = user.projects[cwd];
|
|
519
|
+
if (isRecord2(project))
|
|
520
|
+
collect(servers, project.mcpServers);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
const shared = readJson(join(cwd, ".mcp.json"));
|
|
524
|
+
if (isRecord2(shared))
|
|
525
|
+
collect(servers, shared.mcpServers);
|
|
526
|
+
return servers;
|
|
527
|
+
}
|
|
528
|
+
function packageArg(args) {
|
|
529
|
+
const first = args.find((arg) => !arg.startsWith("-"));
|
|
530
|
+
if (first === undefined)
|
|
531
|
+
return null;
|
|
532
|
+
const at = first.lastIndexOf("@");
|
|
533
|
+
return at > 0 ? first.slice(0, at) : first;
|
|
534
|
+
}
|
|
535
|
+
var NPM_LAUNCHERS = new Set(["npx", "bunx", "pnpx"]);
|
|
536
|
+
var PYPI_LAUNCHERS = new Set(["uvx", "pipx"]);
|
|
537
|
+
var LOCALHOST = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
|
|
538
|
+
var SECRET_SEGMENT = /^(?=[A-Za-z0-9_-]*[A-Za-z])(?=[A-Za-z0-9_-]*\d)[A-Za-z0-9_-]{24,}$/;
|
|
539
|
+
function credentialFree(url) {
|
|
540
|
+
const segments = url.pathname.split("/").filter((segment) => segment.length > 0);
|
|
541
|
+
const path = segments.some((segment) => SECRET_SEGMENT.test(segment)) ? "" : url.pathname;
|
|
542
|
+
const port = url.port === "" ? "" : `:${url.port}`;
|
|
543
|
+
return `${url.protocol}//${url.hostname}${port}${path}`;
|
|
544
|
+
}
|
|
545
|
+
function serverUrlOf(entry) {
|
|
546
|
+
if (entry === undefined)
|
|
547
|
+
return null;
|
|
548
|
+
if (entry.transport === "remote") {
|
|
549
|
+
try {
|
|
550
|
+
const url = new URL(entry.url);
|
|
551
|
+
if (url.protocol !== "https:" && url.protocol !== "http:")
|
|
552
|
+
return null;
|
|
553
|
+
return LOCALHOST.has(url.hostname) ? null : credentialFree(url);
|
|
554
|
+
} catch {
|
|
555
|
+
return null;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
const launcher = entry.command.split(/[\\/]/).pop() ?? entry.command;
|
|
559
|
+
const args = launcher === "pipx" && entry.args[0] === "run" ? entry.args.slice(1) : entry.args;
|
|
560
|
+
const pkg = packageArg(args);
|
|
561
|
+
if (pkg === null || pkg.startsWith(".") || pkg.startsWith("/"))
|
|
562
|
+
return null;
|
|
563
|
+
if (NPM_LAUNCHERS.has(launcher))
|
|
564
|
+
return `https://www.npmjs.com/package/${pkg}`;
|
|
565
|
+
if (PYPI_LAUNCHERS.has(launcher))
|
|
566
|
+
return `https://pypi.org/project/${pkg}`;
|
|
567
|
+
return null;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// ../hook/src/capture.ts
|
|
571
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
572
|
+
import { join as join2 } from "node:path";
|
|
573
|
+
var TOP_LEVEL_NAMES = new Set([
|
|
574
|
+
"hook_event_name",
|
|
575
|
+
"hookEventName",
|
|
576
|
+
"tool_name",
|
|
577
|
+
"toolName",
|
|
578
|
+
"tool_use_id",
|
|
579
|
+
"toolUseId",
|
|
580
|
+
"session_id",
|
|
581
|
+
"sessionId",
|
|
582
|
+
"conversation_id",
|
|
583
|
+
"generation_id",
|
|
584
|
+
"turn_id",
|
|
585
|
+
"model",
|
|
586
|
+
"model_id",
|
|
587
|
+
"permission_mode",
|
|
588
|
+
"permissionMode",
|
|
589
|
+
"timestamp",
|
|
590
|
+
"mcp_server_name",
|
|
591
|
+
"original_request_name"
|
|
592
|
+
]);
|
|
593
|
+
var URL_KEYS = new Set(["mcp_server_url", "server_url", "serverUrl", "url"]);
|
|
594
|
+
var IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]{0,39}$/;
|
|
595
|
+
var SECRET_SHAPED = /^(?=.*\d)(?=.*[A-Za-z]).{20,}$/;
|
|
596
|
+
function keyOf(key) {
|
|
597
|
+
return IDENTIFIER.test(key) && !SECRET_SHAPED.test(key) ? key : `<key:${key.length}>`;
|
|
598
|
+
}
|
|
599
|
+
function hostOnly(value) {
|
|
600
|
+
try {
|
|
601
|
+
const url = new URL(value);
|
|
602
|
+
return `${url.protocol}//${url.hostname}<rest:${value.length}>`;
|
|
603
|
+
} catch {
|
|
604
|
+
return `<string:${value.length}>`;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
function deep(value) {
|
|
608
|
+
if (typeof value === "string")
|
|
609
|
+
return `<string:${value.length}>`;
|
|
610
|
+
if (typeof value === "number")
|
|
611
|
+
return "<number>";
|
|
612
|
+
if (Array.isArray(value))
|
|
613
|
+
return value.map(deep);
|
|
614
|
+
if (typeof value === "object" && value !== null) {
|
|
615
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [keyOf(key), deep(item)]));
|
|
616
|
+
}
|
|
617
|
+
return value;
|
|
618
|
+
}
|
|
619
|
+
function redact(payload) {
|
|
620
|
+
if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
|
|
621
|
+
return deep(payload);
|
|
622
|
+
}
|
|
623
|
+
return Object.fromEntries(Object.entries(payload).map(([key, value]) => {
|
|
624
|
+
if (typeof value === "string") {
|
|
625
|
+
if (URL_KEYS.has(key))
|
|
626
|
+
return [keyOf(key), hostOnly(value)];
|
|
627
|
+
return [keyOf(key), TOP_LEVEL_NAMES.has(key) ? value : `<string:${value.length}>`];
|
|
628
|
+
}
|
|
629
|
+
if (typeof value === "number" || typeof value === "boolean" || value === null) {
|
|
630
|
+
return [keyOf(key), value];
|
|
631
|
+
}
|
|
632
|
+
return [keyOf(key), deep(value)];
|
|
633
|
+
}));
|
|
634
|
+
}
|
|
635
|
+
function capture(payload, dir, now = new Date) {
|
|
636
|
+
try {
|
|
637
|
+
const record = redact(payload);
|
|
638
|
+
const event = typeof record === "object" && record !== null && !Array.isArray(record) ? Object.entries(record).find(([key]) => key === "hook_event_name" || key === "hookEventName")?.[1] : undefined;
|
|
639
|
+
const name = `${now.toISOString().replace(/[:.]/g, "-")}-${String(event ?? "event").replace(/[^A-Za-z]/g, "")}.json`;
|
|
640
|
+
mkdirSync(dir, { recursive: true });
|
|
641
|
+
writeFileSync(join2(dir, name), `${JSON.stringify(record, null, 2)}
|
|
642
|
+
`);
|
|
643
|
+
} catch {}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// ../hook/src/payload.ts
|
|
647
|
+
function isRecord3(value) {
|
|
648
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
649
|
+
}
|
|
650
|
+
function str(value) {
|
|
651
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
652
|
+
}
|
|
653
|
+
function ms(value) {
|
|
654
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.round(value) : null;
|
|
655
|
+
}
|
|
656
|
+
function first(raw, keys) {
|
|
657
|
+
for (const key of keys)
|
|
658
|
+
if (key in raw)
|
|
659
|
+
return raw[key];
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
function toolCallOf(raw) {
|
|
663
|
+
if (!isRecord3(raw))
|
|
664
|
+
return null;
|
|
665
|
+
const format = typeof raw.toolName === "string" && typeof raw.tool_name !== "string" ? "grok" : "claude";
|
|
666
|
+
const camel = format === "grok";
|
|
667
|
+
const tool = str(camel ? raw.toolName : raw.tool_name);
|
|
668
|
+
if (tool === null)
|
|
669
|
+
return null;
|
|
670
|
+
const input = camel ? raw.toolInput : raw.tool_input;
|
|
671
|
+
const responseKeys = camel ? ["toolResult", "tool_response"] : ["tool_response"];
|
|
672
|
+
const hasResponse = responseKeys.some((key) => (key in raw) && raw[key] !== undefined);
|
|
673
|
+
return {
|
|
674
|
+
format,
|
|
675
|
+
event: str(camel ? first(raw, ["hookEventName", "hook_event_name"]) : raw.hook_event_name),
|
|
676
|
+
session: str(camel ? raw.sessionId : raw.session_id),
|
|
677
|
+
id: str(camel ? raw.toolUseId : raw.tool_use_id),
|
|
678
|
+
tool,
|
|
679
|
+
input: isRecord3(input) ? input : undefined,
|
|
680
|
+
hasResponse,
|
|
681
|
+
response: hasResponse ? first(raw, responseKeys) : undefined,
|
|
682
|
+
error: str(raw.error),
|
|
683
|
+
interrupted: (camel ? first(raw, ["isInterrupt", "is_interrupt"]) : raw.is_interrupt) === true,
|
|
684
|
+
ms: ms(camel ? first(raw, ["durationMs", "duration_ms"]) : raw.duration_ms),
|
|
685
|
+
cwd: str(raw.cwd),
|
|
686
|
+
transcript: str(raw.transcript_path)
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// ../hook/src/skill-failure.ts
|
|
691
|
+
import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
692
|
+
import { homedir as homedir2 } from "node:os";
|
|
693
|
+
import { dirname as dirname2, join as join3 } from "node:path";
|
|
694
|
+
var MISSING_KEY = /\b(api[ _-]?key|access[ _-]?token|credentials?)\b.{0,40}\b(missing|not set|not found|required|invalid|expired)\b|(?<!\bnot )\b(missing|no|invalid|expired)\b.{0,20}\b(api[ _-]?key|access[ _-]?token|credentials?)\b|\b(HTTP|status)(\/[\d.]+| code)?[ :=]*(401|403)\b|\b(401|403) (unauthori[sz]ed|forbidden)\b|\bunauthori[sz]ed\b|not authenticated|not logged in/i;
|
|
695
|
+
var MISSING_KEY_VARIABLE = /\b[A-Z0-9_]*(API_KEY|TOKEN|SECRET)\b.{0,30}\b(not set|missing|required|unset)\b/;
|
|
696
|
+
var MISSING_PROGRAM = /command not found|executable file not found|\bnot installed\b|No module named|spawn \S+ ENOENT|Cannot find module/i;
|
|
697
|
+
var MISSING_FILE = /No such file or directory|\bENOENT\b|file not found|\b(file|path|directory|folder)\b(?!\.).{0,80}\bdoes not exist\b/i;
|
|
698
|
+
function failureReason(text) {
|
|
699
|
+
if (MISSING_PROGRAM.test(text))
|
|
700
|
+
return "missing-program";
|
|
701
|
+
if (MISSING_KEY.test(text) || MISSING_KEY_VARIABLE.test(text))
|
|
702
|
+
return "missing-key";
|
|
703
|
+
if (MISSING_FILE.test(text))
|
|
704
|
+
return "missing-file";
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
var RUNNERS = new Set(["bash", "bun", "bunx", "node", "npx", "python", "python3", "sh", "uv", "uvx", "zsh"]);
|
|
708
|
+
function programsOf(command) {
|
|
709
|
+
const found = [];
|
|
710
|
+
for (const part of command.split(/&&|\|\||[;|\n]/)) {
|
|
711
|
+
const words = part.trim().split(/\s+/).filter((word) => !/^[A-Za-z_][A-Za-z0-9_]*=/.test(word));
|
|
712
|
+
const first2 = words[0];
|
|
713
|
+
if (first2 === undefined || first2 === "")
|
|
714
|
+
continue;
|
|
715
|
+
const program = first2.split("/").pop() ?? first2;
|
|
716
|
+
if (!RUNNERS.has(program)) {
|
|
717
|
+
found.push(program);
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
const target = words.slice(1).find((word) => !word.startsWith("-") && word !== "run");
|
|
721
|
+
if (target !== undefined)
|
|
722
|
+
found.push(target.split("/").pop() ?? target);
|
|
723
|
+
}
|
|
724
|
+
return found;
|
|
725
|
+
}
|
|
726
|
+
var escaped = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
727
|
+
function askedBySkill(command, markdown, directory) {
|
|
728
|
+
if (new RegExp(`${escaped(directory)}(?=[/\\s'"]|$)`).test(command))
|
|
729
|
+
return true;
|
|
730
|
+
const { cli, runs } = readRequires(markdown);
|
|
731
|
+
const named = new Set([...cli, ...runs]);
|
|
732
|
+
return programsOf(command).some((program) => named.has(program) || program.includes(".") && new RegExp(`(?:^|[\\s\`'"/(])${escaped(program)}(?=[\\s\`'")]|$)`, "m").test(markdown));
|
|
733
|
+
}
|
|
734
|
+
var DOMAIN = /^[a-z0-9][a-z0-9-]*$/;
|
|
735
|
+
var DOMAINS = [...new Set(SKILL_DOMAINS.map((rule) => rule.domain))];
|
|
736
|
+
function lastUserWords(transcript) {
|
|
737
|
+
let words = "";
|
|
738
|
+
for (const line of transcript.split(`
|
|
739
|
+
`)) {
|
|
740
|
+
if (!line.includes('"user"'))
|
|
741
|
+
continue;
|
|
742
|
+
try {
|
|
743
|
+
const entry = JSON.parse(line);
|
|
744
|
+
if (entry.type !== "user" || entry.isMeta === true)
|
|
745
|
+
continue;
|
|
746
|
+
const content = entry.message?.content;
|
|
747
|
+
const text = typeof content === "string" ? content : Array.isArray(content) ? content.map((block) => block?.type === "text" && typeof block.text === "string" ? block.text : "").join(" ") : "";
|
|
748
|
+
if (text.trim() !== "" && !text.trimStart().startsWith("<"))
|
|
749
|
+
words = text;
|
|
750
|
+
} catch {
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return words;
|
|
755
|
+
}
|
|
756
|
+
function contextOf(words, skill, description) {
|
|
757
|
+
return inferDomain(words, DOMAINS) ?? classifySkill(skill.split(":").pop() ?? skill, description ?? "");
|
|
758
|
+
}
|
|
759
|
+
function answerOf(response) {
|
|
760
|
+
const blocks = Array.isArray(response) ? response : typeof response === "object" && response !== null && Array.isArray(response.content) ? response.content : [response];
|
|
761
|
+
for (const block of blocks) {
|
|
762
|
+
const text = typeof block === "string" ? block : block?.text;
|
|
763
|
+
if (typeof text !== "string")
|
|
764
|
+
continue;
|
|
765
|
+
try {
|
|
766
|
+
const parsed = JSON.parse(text);
|
|
767
|
+
if (typeof parsed === "object" && parsed !== null)
|
|
768
|
+
return parsed;
|
|
769
|
+
} catch {
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return null;
|
|
774
|
+
}
|
|
775
|
+
var searchesPath = (cwd) => join3(dirname2(process.env["ARP_SESSION_LOG"] ?? join3(cwd, ".arp", "session.jsonl")), "searches.jsonl");
|
|
776
|
+
function recordSearch(call, cwd) {
|
|
777
|
+
if (call.session === null)
|
|
778
|
+
return;
|
|
779
|
+
const answer = answerOf(call.response);
|
|
780
|
+
if (answer === null || !Array.isArray(answer["skills"]))
|
|
781
|
+
return;
|
|
782
|
+
const domain = typeof answer["domain"] === "string" && DOMAIN.test(answer["domain"]) ? answer["domain"] : null;
|
|
783
|
+
const skills = answer["skills"].flatMap((row) => typeof row?.name === "string" && typeof row.source === "string" ? [{ name: row.name, source: row.source }] : []);
|
|
784
|
+
if (skills.length === 0)
|
|
785
|
+
return;
|
|
786
|
+
try {
|
|
787
|
+
const path = searchesPath(cwd);
|
|
788
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
789
|
+
appendOwn(path, `${JSON.stringify({ session: call.session, domain, skills })}
|
|
790
|
+
`);
|
|
791
|
+
} catch {}
|
|
792
|
+
}
|
|
793
|
+
function foundBySearch(cwd, session, skill) {
|
|
794
|
+
const path = searchesPath(cwd);
|
|
795
|
+
if (!existsSync(path))
|
|
796
|
+
return null;
|
|
797
|
+
const name = skill.split(":").pop() ?? skill;
|
|
798
|
+
let found = null;
|
|
799
|
+
for (const line of readFileSync2(path, "utf8").split(`
|
|
800
|
+
`)) {
|
|
801
|
+
try {
|
|
802
|
+
const row = JSON.parse(line);
|
|
803
|
+
if (row.session !== session)
|
|
804
|
+
continue;
|
|
805
|
+
const hit = row.skills?.find((entry) => entry.name === name);
|
|
806
|
+
if (hit !== undefined)
|
|
807
|
+
found = { domain: typeof row.domain === "string" ? row.domain : null, source: hit.source };
|
|
808
|
+
} catch {
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return found;
|
|
813
|
+
}
|
|
814
|
+
var readIfThere = (path) => existsSync(path) ? readFileSync2(path, "utf8") : null;
|
|
815
|
+
function installedSource(name, cwd) {
|
|
816
|
+
const known = [
|
|
817
|
+
...installedFromSkillLock(readIfThere(join3(homedir2(), ".agents", ".skill-lock.json"))),
|
|
818
|
+
...installedFromReceipts(readIfThere(join3(arpHome(), "receipts.jsonl"))),
|
|
819
|
+
...installedFromReceipts(readIfThere(join3(cwd, ".arp", "receipts.jsonl")))
|
|
820
|
+
];
|
|
821
|
+
const sources = new Set(known.filter((entry) => entry.skill === name).map((entry) => entry.source));
|
|
822
|
+
return sources.size === 1 ? [...sources][0] ?? null : null;
|
|
823
|
+
}
|
|
824
|
+
function skillsOfSession(log, session) {
|
|
825
|
+
if (!existsSync(log))
|
|
826
|
+
return [];
|
|
827
|
+
const skills = [];
|
|
828
|
+
for (const line of readFileSync2(log, "utf8").split(`
|
|
829
|
+
`)) {
|
|
830
|
+
try {
|
|
831
|
+
const row = JSON.parse(line);
|
|
832
|
+
if (row["kind"] === "skill" && row["session"] === session && typeof row["name"] === "string") {
|
|
833
|
+
skills.unshift({
|
|
834
|
+
name: row["name"],
|
|
835
|
+
source: typeof row["source"] === "string" ? row["source"] : null
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
} catch {}
|
|
839
|
+
}
|
|
840
|
+
return skills;
|
|
841
|
+
}
|
|
842
|
+
function skillDirectory(name, cwd) {
|
|
843
|
+
const roots = [join3(cwd, ".claude", "skills"), join3(homedir2(), ".claude", "skills")];
|
|
844
|
+
const given = process.env["ARP_SKILLS_DIR"];
|
|
845
|
+
if (given !== undefined && given !== "")
|
|
846
|
+
roots.unshift(given);
|
|
847
|
+
return roots.map((root) => join3(root, name)).find((dir) => existsSync(join3(dir, "SKILL.md"))) ?? null;
|
|
848
|
+
}
|
|
849
|
+
function arpHome() {
|
|
850
|
+
const given = process.env["ARP_HOME"]?.trim();
|
|
851
|
+
return given !== undefined && given !== "" ? given : join3(homedir2(), ".arp");
|
|
852
|
+
}
|
|
853
|
+
function appendFailure(line) {
|
|
854
|
+
const path = join3(arpHome(), "failures.jsonl");
|
|
855
|
+
const seen = existsSync(path) ? readFileSync2(path, "utf8") : "";
|
|
856
|
+
const key = `"session":${JSON.stringify(line.session)},"skill":${JSON.stringify(line.skill)},`;
|
|
857
|
+
if (seen.split(`
|
|
858
|
+
`).some((row) => row.includes(key) && row.includes(`"reason":"${line.reason}"`)))
|
|
859
|
+
return false;
|
|
860
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
861
|
+
appendOwn(path, `${JSON.stringify(line)}
|
|
862
|
+
`);
|
|
863
|
+
return true;
|
|
864
|
+
}
|
|
865
|
+
function recordSkillFailure(call, cwd) {
|
|
866
|
+
const classified = classifyCall(call);
|
|
867
|
+
if (classified === null || classified.outcome === "ok" || call.session === null)
|
|
868
|
+
return;
|
|
869
|
+
const command = call.input?.["command"];
|
|
870
|
+
if (typeof command !== "string")
|
|
871
|
+
return;
|
|
872
|
+
const reason = failureReason(failureText(call));
|
|
873
|
+
if (reason === null)
|
|
874
|
+
return;
|
|
875
|
+
try {
|
|
876
|
+
const log = process.env["ARP_SESSION_LOG"] ?? join3(cwd, ".arp", "session.jsonl");
|
|
877
|
+
for (const skill of skillsOfSession(log, call.session)) {
|
|
878
|
+
const directory = skillDirectory(skill.name, cwd);
|
|
879
|
+
if (directory === null)
|
|
880
|
+
continue;
|
|
881
|
+
const markdown = readFileSync2(join3(directory, "SKILL.md"), "utf8");
|
|
882
|
+
if (!askedBySkill(command, markdown, directory))
|
|
883
|
+
continue;
|
|
884
|
+
const search = foundBySearch(cwd, call.session, skill.name);
|
|
885
|
+
const words = call.transcript !== null && existsSync(call.transcript) ? lastUserWords(readFileSync2(call.transcript, "utf8")) : "";
|
|
886
|
+
const domain = search?.domain ?? contextOf(words, skill.name, readRequires(markdown).description);
|
|
887
|
+
appendFailure({
|
|
888
|
+
at: new Date().toISOString(),
|
|
889
|
+
session: call.session,
|
|
890
|
+
skill: skill.name,
|
|
891
|
+
source: skill.source ?? search?.source ?? installedSource(skill.name.split(":").pop() ?? skill.name, cwd),
|
|
892
|
+
...domain === null ? {} : { domain },
|
|
893
|
+
reason
|
|
894
|
+
});
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
} catch {}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// ../hook/src/hook.ts
|
|
901
|
+
function fetchedRefs(cwd) {
|
|
902
|
+
const path = process.env["ARP_RECEIPTS"] ?? join4(cwd, ".arp", "receipts.jsonl");
|
|
903
|
+
if (!existsSync2(path))
|
|
904
|
+
return [];
|
|
905
|
+
let text;
|
|
906
|
+
try {
|
|
907
|
+
text = readFileSync3(path, "utf8");
|
|
908
|
+
} catch {
|
|
909
|
+
return [];
|
|
910
|
+
}
|
|
911
|
+
const rows = [];
|
|
912
|
+
for (const line of text.split(`
|
|
913
|
+
`)) {
|
|
914
|
+
if (line.trim().length === 0)
|
|
915
|
+
continue;
|
|
916
|
+
try {
|
|
917
|
+
const row = JSON.parse(line);
|
|
918
|
+
if (typeof row.skill !== "string" || typeof row.atom !== "string")
|
|
919
|
+
continue;
|
|
920
|
+
rows.push({
|
|
921
|
+
skill: row.skill,
|
|
922
|
+
atom: row.atom,
|
|
923
|
+
url: typeof row.url === "string" ? row.url : "",
|
|
924
|
+
at: typeof row.at === "string" ? row.at : ""
|
|
925
|
+
});
|
|
926
|
+
} catch {
|
|
927
|
+
continue;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
return rows;
|
|
931
|
+
}
|
|
932
|
+
function responseOf(call, outcome) {
|
|
933
|
+
if (!call.hasResponse)
|
|
934
|
+
return null;
|
|
935
|
+
const raw = call.response;
|
|
936
|
+
let bytes = 0;
|
|
937
|
+
try {
|
|
938
|
+
bytes = typeof raw === "string" ? raw.length : JSON.stringify(raw)?.length ?? 0;
|
|
939
|
+
} catch {
|
|
940
|
+
bytes = 0;
|
|
941
|
+
}
|
|
942
|
+
return { answered: raw !== null, bytes, failed: outcome !== "ok" };
|
|
943
|
+
}
|
|
944
|
+
function readString(from, key) {
|
|
945
|
+
const value = from?.[key];
|
|
946
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
947
|
+
}
|
|
948
|
+
function serverRef(server, cwd) {
|
|
949
|
+
const url = serverUrlOf(readMcpServers({ cwd }).get(server));
|
|
950
|
+
if (url === null)
|
|
951
|
+
return null;
|
|
952
|
+
try {
|
|
953
|
+
return normaliseToolUrl(url);
|
|
954
|
+
} catch {
|
|
955
|
+
return null;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
function whatOf(call, cwd, fetched) {
|
|
959
|
+
if (call.tool === "Skill") {
|
|
960
|
+
const name = readString(call.input, "skill");
|
|
961
|
+
if (name === null)
|
|
962
|
+
return null;
|
|
963
|
+
const source = readString(call.input, "source");
|
|
964
|
+
return {
|
|
965
|
+
kind: "skill",
|
|
966
|
+
name,
|
|
967
|
+
source,
|
|
968
|
+
ref: readString(call.input, "atom_id") ?? readString(call.input, "tool_atom_id") ?? readString(call.input, "url") ?? refForInvocation(name, source, fetched())
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
const mcp = /^mcp__([^_]+(?:_[^_]+)*?)__/.exec(call.tool);
|
|
972
|
+
if (mcp?.[1] !== undefined) {
|
|
973
|
+
return { kind: "mcp", name: mcp[1], source: null, ref: serverRef(mcp[1], cwd) };
|
|
974
|
+
}
|
|
975
|
+
return null;
|
|
976
|
+
}
|
|
977
|
+
var raw = "";
|
|
978
|
+
for await (const chunk of process.stdin)
|
|
979
|
+
raw += chunk;
|
|
980
|
+
var parsed;
|
|
981
|
+
try {
|
|
982
|
+
parsed = JSON.parse(raw);
|
|
983
|
+
} catch {
|
|
984
|
+
process.exit(0);
|
|
985
|
+
}
|
|
986
|
+
if (process.argv.includes("--capture"))
|
|
987
|
+
capture(parsed, join4(homedir3(), ".arp", "captures"));
|
|
988
|
+
var call = toolCallOf(parsed);
|
|
989
|
+
if (call === null)
|
|
990
|
+
process.exit(0);
|
|
991
|
+
var cwd = call.cwd ?? process.cwd();
|
|
992
|
+
if (call.tool === "Bash") {
|
|
993
|
+
recordSkillFailure(call, cwd);
|
|
994
|
+
process.exit(0);
|
|
995
|
+
}
|
|
996
|
+
if (call.tool.endsWith("__arp_pulse"))
|
|
997
|
+
recordSearch(call, cwd);
|
|
998
|
+
var what = whatOf(call, cwd, () => fetchedRefs(cwd));
|
|
999
|
+
if (what === null)
|
|
1000
|
+
process.exit(0);
|
|
1001
|
+
var classified = classifyCall(call);
|
|
1002
|
+
if (classified === null)
|
|
1003
|
+
process.exit(0);
|
|
1004
|
+
var own = join4(cwd, ".arp");
|
|
1005
|
+
var path = process.env["ARP_SESSION_LOG"] ?? join4(own, "session.jsonl");
|
|
1006
|
+
var line = {
|
|
1007
|
+
at: new Date().toISOString(),
|
|
1008
|
+
session: call.session,
|
|
1009
|
+
call: call.id,
|
|
1010
|
+
format: call.format,
|
|
1011
|
+
...what,
|
|
1012
|
+
outcome: classified.outcome,
|
|
1013
|
+
code: classified.code,
|
|
1014
|
+
ms: call.ms,
|
|
1015
|
+
response: responseOf(call, classified.outcome)
|
|
1016
|
+
};
|
|
1017
|
+
try {
|
|
1018
|
+
mkdirSync3(dirname3(path), { recursive: true });
|
|
1019
|
+
if (dirname3(path) === own && lstatSync2(own).isSymbolicLink())
|
|
1020
|
+
throw new Error(`${own} is a symbolic link`);
|
|
1021
|
+
const ignore = join4(own, ".gitignore");
|
|
1022
|
+
if (dirname3(path) === own && !existsSync2(ignore))
|
|
1023
|
+
writeFileSync2(ignore, `*
|
|
1024
|
+
`, { flag: "wx" });
|
|
1025
|
+
appendOwn(path, `${JSON.stringify(line)}
|
|
1026
|
+
`);
|
|
1027
|
+
} catch {}
|