@sentry/junior 0.7.0 → 0.9.0
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/dist/chunk-4G2LA7RO.js +678 -0
- package/dist/{chunk-JRKU55W5.js → chunk-DIMXJUSL.js} +10952 -9015
- package/dist/{chunk-QHKQ2AWX.js → chunk-I3DYWLM6.js} +15 -6
- package/dist/chunk-IJVZEV3K.js +840 -0
- package/dist/{chunk-Z5E25LRN.js → chunk-KCLEEKYX.js} +124 -17
- package/dist/chunk-VM3CPAZF.js +448 -0
- package/dist/chunk-ZBWWHP6Q.js +1436 -0
- package/dist/{chunk-PY4AI2GZ.js → chunk-ZW4OVKF5.js} +376 -79
- package/dist/cli/check.js +4 -2
- package/dist/cli/snapshot-warmup.js +7 -6
- package/dist/handlers/queue-callback.js +7 -7
- package/dist/handlers/router.d.ts +1 -0
- package/dist/handlers/router.js +523 -85
- package/dist/handlers/webhooks.js +2 -2
- package/dist/next-config.js +1 -1
- package/dist/production-XMCJXOOI.js +15 -0
- package/package.json +13 -15
- package/dist/bot-ZKMCCT3D.js +0 -19
- package/dist/chunk-56WI5Q7P.js +0 -330
- package/dist/chunk-KT5HARSN.js +0 -164
- package/dist/chunk-RKOO42TW.js +0 -1797
- package/dist/chunk-VW26MOSO.js +0 -522
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
// src/chat/
|
|
2
|
-
import { statSync } from "fs";
|
|
1
|
+
// src/chat/discovery.ts
|
|
2
|
+
import fs, { readdirSync, statSync } from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
3
5
|
function isDirectory(targetPath) {
|
|
4
6
|
try {
|
|
5
7
|
return statSync(targetPath).isDirectory();
|
|
@@ -14,11 +16,6 @@ function isFile(targetPath) {
|
|
|
14
16
|
return false;
|
|
15
17
|
}
|
|
16
18
|
}
|
|
17
|
-
|
|
18
|
-
// src/chat/discovery-roots.ts
|
|
19
|
-
import { readdirSync } from "fs";
|
|
20
|
-
import path from "path";
|
|
21
|
-
import { fileURLToPath } from "url";
|
|
22
19
|
function normalizePath(targetPath) {
|
|
23
20
|
return path.resolve(targetPath);
|
|
24
21
|
}
|
|
@@ -95,11 +92,16 @@ function discoverNodeModulesDirs(cwd = process.cwd(), options) {
|
|
|
95
92
|
]);
|
|
96
93
|
}
|
|
97
94
|
function discoverProjectRoots(cwd = process.cwd(), options) {
|
|
98
|
-
const roots = discoverNodeModulesDirs(
|
|
95
|
+
const roots = discoverNodeModulesDirs(
|
|
96
|
+
cwd,
|
|
97
|
+
options?.nodeModulesDirs ? { candidateDirs: options.nodeModulesDirs } : void 0
|
|
98
|
+
).map((nodeModulesDir) => path.dirname(nodeModulesDir));
|
|
99
99
|
return uniqueResolvedPathsInOrder([cwd, ...roots]);
|
|
100
100
|
}
|
|
101
101
|
function listTopLevelPackages(nodeModulesDir) {
|
|
102
|
-
const entries = readdirSync(nodeModulesDir, { withFileTypes: true }).filter(
|
|
102
|
+
const entries = readdirSync(nodeModulesDir, { withFileTypes: true }).filter(
|
|
103
|
+
(entry) => !entry.name.startsWith(".") && entry.name !== ".bin" && entry.name !== ".pnpm"
|
|
104
|
+
).sort((left, right) => left.name.localeCompare(right.name));
|
|
103
105
|
const packages = [];
|
|
104
106
|
for (const entry of entries) {
|
|
105
107
|
const entryPath = path.join(nodeModulesDir, entry.name);
|
|
@@ -107,9 +109,9 @@ function listTopLevelPackages(nodeModulesDir) {
|
|
|
107
109
|
if (!isDirectory(entryPath)) {
|
|
108
110
|
continue;
|
|
109
111
|
}
|
|
110
|
-
const scopedEntries = readdirSync(entryPath, {
|
|
111
|
-
|
|
112
|
-
);
|
|
112
|
+
const scopedEntries = readdirSync(entryPath, {
|
|
113
|
+
withFileTypes: true
|
|
114
|
+
}).sort((left, right) => left.name.localeCompare(right.name));
|
|
113
115
|
for (const scopedEntry of scopedEntries) {
|
|
114
116
|
const packageName = `${entry.name}/${scopedEntry.name}`;
|
|
115
117
|
const packagePath = path.join(entryPath, scopedEntry.name);
|
|
@@ -127,6 +129,107 @@ function listTopLevelPackages(nodeModulesDir) {
|
|
|
127
129
|
}
|
|
128
130
|
return packages;
|
|
129
131
|
}
|
|
132
|
+
function unique(values) {
|
|
133
|
+
return [...new Set(values)];
|
|
134
|
+
}
|
|
135
|
+
function pathExists(targetPath) {
|
|
136
|
+
try {
|
|
137
|
+
fs.accessSync(targetPath);
|
|
138
|
+
return true;
|
|
139
|
+
} catch {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function hasAnyDataMarkers(appDir) {
|
|
144
|
+
return pathExists(path.join(appDir, "SOUL.md")) || pathExists(path.join(appDir, "ABOUT.md"));
|
|
145
|
+
}
|
|
146
|
+
function scoreAppCandidate(appDir) {
|
|
147
|
+
let score = 0;
|
|
148
|
+
if (pathExists(path.join(appDir, "SOUL.md"))) {
|
|
149
|
+
score += 4;
|
|
150
|
+
}
|
|
151
|
+
if (pathExists(path.join(appDir, "ABOUT.md"))) {
|
|
152
|
+
score += 2;
|
|
153
|
+
}
|
|
154
|
+
if (pathExists(path.join(appDir, "skills"))) {
|
|
155
|
+
score += 1;
|
|
156
|
+
}
|
|
157
|
+
if (pathExists(path.join(appDir, "plugins"))) {
|
|
158
|
+
score += 1;
|
|
159
|
+
}
|
|
160
|
+
return score;
|
|
161
|
+
}
|
|
162
|
+
function resolveCandidateAppDirs(cwd, projectRoots) {
|
|
163
|
+
const roots = projectRoots ?? discoverProjectRoots(cwd);
|
|
164
|
+
const resolved = [];
|
|
165
|
+
const seen = /* @__PURE__ */ new Set();
|
|
166
|
+
for (const root of roots) {
|
|
167
|
+
const appDir = path.resolve(root, "app");
|
|
168
|
+
if (!pathExists(appDir)) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (seen.has(appDir)) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
seen.add(appDir);
|
|
175
|
+
resolved.push(appDir);
|
|
176
|
+
}
|
|
177
|
+
return resolved;
|
|
178
|
+
}
|
|
179
|
+
function homeDir() {
|
|
180
|
+
return resolveHomeDir();
|
|
181
|
+
}
|
|
182
|
+
function resolveHomeDir(cwd = process.cwd(), options) {
|
|
183
|
+
const resolvedCwd = path.resolve(cwd);
|
|
184
|
+
const directApp = path.resolve(resolvedCwd, "app");
|
|
185
|
+
if (pathExists(directApp) && hasAnyDataMarkers(directApp)) {
|
|
186
|
+
return directApp;
|
|
187
|
+
}
|
|
188
|
+
const candidates = resolveCandidateAppDirs(
|
|
189
|
+
resolvedCwd,
|
|
190
|
+
options?.projectRoots
|
|
191
|
+
);
|
|
192
|
+
if (candidates.length === 0) {
|
|
193
|
+
return directApp;
|
|
194
|
+
}
|
|
195
|
+
candidates.sort((left, right) => {
|
|
196
|
+
const leftScore = scoreAppCandidate(left);
|
|
197
|
+
const rightScore = scoreAppCandidate(right);
|
|
198
|
+
if (leftScore !== rightScore) {
|
|
199
|
+
return rightScore - leftScore;
|
|
200
|
+
}
|
|
201
|
+
const leftDistance = path.relative(resolvedCwd, left).split(path.sep).length;
|
|
202
|
+
const rightDistance = path.relative(resolvedCwd, right).split(path.sep).length;
|
|
203
|
+
if (leftDistance !== rightDistance) {
|
|
204
|
+
return leftDistance - rightDistance;
|
|
205
|
+
}
|
|
206
|
+
return left.localeCompare(right);
|
|
207
|
+
});
|
|
208
|
+
return candidates[0];
|
|
209
|
+
}
|
|
210
|
+
function resolveContentRoots(subdir) {
|
|
211
|
+
if (subdir === "data") {
|
|
212
|
+
return [homeDir()];
|
|
213
|
+
}
|
|
214
|
+
return [path.join(homeDir(), subdir)];
|
|
215
|
+
}
|
|
216
|
+
function dataRoots() {
|
|
217
|
+
return unique(resolveContentRoots("data"));
|
|
218
|
+
}
|
|
219
|
+
function skillRoots() {
|
|
220
|
+
return unique(resolveContentRoots("skills"));
|
|
221
|
+
}
|
|
222
|
+
function pluginRoots() {
|
|
223
|
+
return unique(resolveContentRoots("plugins"));
|
|
224
|
+
}
|
|
225
|
+
function soulPathCandidates() {
|
|
226
|
+
const candidates = dataRoots().map((root) => path.join(root, "SOUL.md"));
|
|
227
|
+
return unique(candidates);
|
|
228
|
+
}
|
|
229
|
+
function aboutPathCandidates() {
|
|
230
|
+
const candidates = dataRoots().map((root) => path.join(root, "ABOUT.md"));
|
|
231
|
+
return unique(candidates);
|
|
232
|
+
}
|
|
130
233
|
|
|
131
234
|
// src/chat/plugins/package-discovery.ts
|
|
132
235
|
import { readFileSync, readdirSync as readdirSync2 } from "fs";
|
|
@@ -380,7 +483,7 @@ function discoverInstalledPluginPackageContent(cwd = process.cwd(), options) {
|
|
|
380
483
|
configuredPackageNames
|
|
381
484
|
);
|
|
382
485
|
const manifestRoots = [];
|
|
383
|
-
const
|
|
486
|
+
const skillRoots2 = [];
|
|
384
487
|
const tracingIncludes = [];
|
|
385
488
|
for (const pkg of discoveredPackages) {
|
|
386
489
|
const tracingBasePath = pkg.nodeModulesDir ? pathForTracingInclude(
|
|
@@ -400,7 +503,7 @@ function discoverInstalledPluginPackageContent(cwd = process.cwd(), options) {
|
|
|
400
503
|
}
|
|
401
504
|
}
|
|
402
505
|
if (pkg.hasSkillsDir) {
|
|
403
|
-
|
|
506
|
+
skillRoots2.push(path2.join(pkg.dir, "skills"));
|
|
404
507
|
if (tracingBasePath) {
|
|
405
508
|
tracingIncludes.push(`${tracingBasePath}/skills/**/*`);
|
|
406
509
|
}
|
|
@@ -421,7 +524,7 @@ function discoverInstalledPluginPackageContent(cwd = process.cwd(), options) {
|
|
|
421
524
|
}
|
|
422
525
|
}
|
|
423
526
|
if (isDirectory(path2.join(pluginDir, "skills"))) {
|
|
424
|
-
|
|
527
|
+
skillRoots2.push(path2.join(pluginDir, "skills"));
|
|
425
528
|
if (tracingBasePath) {
|
|
426
529
|
tracingIncludes.push(`${tracingBasePath}/skills/**/*`);
|
|
427
530
|
}
|
|
@@ -432,7 +535,7 @@ function discoverInstalledPluginPackageContent(cwd = process.cwd(), options) {
|
|
|
432
535
|
discoveredPackages.map((pkg) => pkg.name)
|
|
433
536
|
),
|
|
434
537
|
manifestRoots: uniqueStringsInOrder(manifestRoots),
|
|
435
|
-
skillRoots: uniqueStringsInOrder(
|
|
538
|
+
skillRoots: uniqueStringsInOrder(skillRoots2),
|
|
436
539
|
tracingIncludes: uniqueStringsInOrder(tracingIncludes)
|
|
437
540
|
};
|
|
438
541
|
}
|
|
@@ -440,6 +543,10 @@ function discoverInstalledPluginPackageContent(cwd = process.cwd(), options) {
|
|
|
440
543
|
export {
|
|
441
544
|
isDirectory,
|
|
442
545
|
discoverNodeModulesDirs,
|
|
443
|
-
|
|
546
|
+
homeDir,
|
|
547
|
+
skillRoots,
|
|
548
|
+
pluginRoots,
|
|
549
|
+
soulPathCandidates,
|
|
550
|
+
aboutPathCandidates,
|
|
444
551
|
discoverInstalledPluginPackageContent
|
|
445
552
|
};
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getPluginCapabilityProviders,
|
|
3
|
+
getPluginForSkillPath,
|
|
4
|
+
getPluginSkillRoots
|
|
5
|
+
} from "./chunk-ZBWWHP6Q.js";
|
|
6
|
+
import {
|
|
7
|
+
skillRoots
|
|
8
|
+
} from "./chunk-KCLEEKYX.js";
|
|
9
|
+
import {
|
|
10
|
+
logInfo,
|
|
11
|
+
logWarn
|
|
12
|
+
} from "./chunk-ZW4OVKF5.js";
|
|
13
|
+
|
|
14
|
+
// src/chat/skills.ts
|
|
15
|
+
import fs from "fs/promises";
|
|
16
|
+
import path from "path";
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
import { parse as parseYaml } from "yaml";
|
|
19
|
+
|
|
20
|
+
// src/chat/capabilities/catalog.ts
|
|
21
|
+
function getCapabilityCatalog() {
|
|
22
|
+
const providers = getPluginCapabilityProviders();
|
|
23
|
+
const capabilityToProvider = /* @__PURE__ */ new Map();
|
|
24
|
+
const configKeys = /* @__PURE__ */ new Set();
|
|
25
|
+
for (const provider of providers) {
|
|
26
|
+
for (const capability of provider.capabilities) {
|
|
27
|
+
if (capabilityToProvider.has(capability)) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`Duplicate capability registration for "${capability}"`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
capabilityToProvider.set(capability, provider);
|
|
33
|
+
}
|
|
34
|
+
for (const configKey of provider.configKeys) {
|
|
35
|
+
configKeys.add(configKey);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
providers,
|
|
40
|
+
capabilityToProvider,
|
|
41
|
+
configKeys
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function getCapabilityProvider(capability) {
|
|
45
|
+
return getCapabilityCatalog().capabilityToProvider.get(capability);
|
|
46
|
+
}
|
|
47
|
+
function isKnownCapability(capability) {
|
|
48
|
+
return getCapabilityCatalog().capabilityToProvider.has(capability);
|
|
49
|
+
}
|
|
50
|
+
function isKnownConfigKey(key) {
|
|
51
|
+
return getCapabilityCatalog().configKeys.has(key);
|
|
52
|
+
}
|
|
53
|
+
function listCapabilityProviders() {
|
|
54
|
+
return getCapabilityCatalog().providers.map((provider) => ({
|
|
55
|
+
...provider,
|
|
56
|
+
capabilities: [...provider.capabilities],
|
|
57
|
+
configKeys: [...provider.configKeys]
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
var startupCatalogSignature = null;
|
|
61
|
+
function logCapabilityCatalogLoadedOnce() {
|
|
62
|
+
const providers = listCapabilityProviders();
|
|
63
|
+
const signature = JSON.stringify(
|
|
64
|
+
providers.map((provider) => ({
|
|
65
|
+
provider: provider.provider,
|
|
66
|
+
capabilities: provider.capabilities,
|
|
67
|
+
configKeys: provider.configKeys,
|
|
68
|
+
target: provider.target
|
|
69
|
+
}))
|
|
70
|
+
);
|
|
71
|
+
if (startupCatalogSignature === signature) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
startupCatalogSignature = signature;
|
|
75
|
+
const capabilityNames = providers.flatMap((provider) => provider.capabilities).sort();
|
|
76
|
+
const configKeys = [
|
|
77
|
+
...new Set(providers.flatMap((provider) => provider.configKeys))
|
|
78
|
+
].sort();
|
|
79
|
+
logInfo(
|
|
80
|
+
"capability_catalog_loaded",
|
|
81
|
+
{},
|
|
82
|
+
{
|
|
83
|
+
"app.capability.providers": providers.map(
|
|
84
|
+
(provider) => provider.provider
|
|
85
|
+
),
|
|
86
|
+
"app.capability.count": capabilityNames.length,
|
|
87
|
+
"app.capability.names": capabilityNames,
|
|
88
|
+
"app.config.key_count": configKeys.length,
|
|
89
|
+
"app.config.keys": configKeys
|
|
90
|
+
},
|
|
91
|
+
"Loaded capability provider catalog"
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/chat/skills.ts
|
|
96
|
+
var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
97
|
+
var SKILL_NAME_RE = /^[a-z0-9-]+$/;
|
|
98
|
+
var DOTTED_TOKEN_RE = /^[a-z0-9-]+(?:\.[a-z0-9-]+)+$/;
|
|
99
|
+
var MAX_NAME_LENGTH = 64;
|
|
100
|
+
var MAX_DESCRIPTION_LENGTH = 1024;
|
|
101
|
+
var MAX_COMPATIBILITY_LENGTH = 500;
|
|
102
|
+
function hasAngleBrackets(value) {
|
|
103
|
+
return value.includes("<") || value.includes(">");
|
|
104
|
+
}
|
|
105
|
+
function validateSkillName(name) {
|
|
106
|
+
if (!name) return "name must not be empty";
|
|
107
|
+
if (name.length > MAX_NAME_LENGTH)
|
|
108
|
+
return `name must be <= ${MAX_NAME_LENGTH} characters`;
|
|
109
|
+
if (!SKILL_NAME_RE.test(name))
|
|
110
|
+
return "name must contain only lowercase letters, digits, and hyphens";
|
|
111
|
+
if (name.startsWith("-") || name.endsWith("-"))
|
|
112
|
+
return "name must not start or end with a hyphen";
|
|
113
|
+
if (name.includes("--")) return "name must not contain consecutive hyphens";
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
function createTokenFieldSchema(fieldName, example) {
|
|
117
|
+
return z.string({
|
|
118
|
+
error: `Frontmatter field "${fieldName}" must be a string when present`
|
|
119
|
+
}).superRefine((value, ctx) => {
|
|
120
|
+
const tokens = value.split(/\s+/).map((token) => token.trim()).filter((token) => token.length > 0);
|
|
121
|
+
for (const token of tokens) {
|
|
122
|
+
if (!DOTTED_TOKEN_RE.test(token)) {
|
|
123
|
+
ctx.addIssue({
|
|
124
|
+
code: z.ZodIssueCode.custom,
|
|
125
|
+
message: `${fieldName} token "${token}" is invalid; expected dotted lowercase tokens (for example "${example}")`
|
|
126
|
+
});
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function parseTokenList(value) {
|
|
133
|
+
if (typeof value !== "string") {
|
|
134
|
+
return void 0;
|
|
135
|
+
}
|
|
136
|
+
const tokens = value.split(/\s+/).map((token) => token.trim()).filter((token) => token.length > 0);
|
|
137
|
+
return tokens.length > 0 ? tokens : void 0;
|
|
138
|
+
}
|
|
139
|
+
var skillFrontmatterSchema = z.object({
|
|
140
|
+
name: z.string({ error: 'Frontmatter field "name" must be a string' }).superRefine((value, ctx) => {
|
|
141
|
+
const nameError = validateSkillName(value);
|
|
142
|
+
if (nameError) {
|
|
143
|
+
ctx.addIssue({
|
|
144
|
+
code: z.ZodIssueCode.custom,
|
|
145
|
+
message: nameError
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}),
|
|
149
|
+
description: z.string({ error: 'Frontmatter field "description" must be a string' }).superRefine((value, ctx) => {
|
|
150
|
+
if (!value.trim()) {
|
|
151
|
+
ctx.addIssue({
|
|
152
|
+
code: z.ZodIssueCode.custom,
|
|
153
|
+
message: "description must not be empty"
|
|
154
|
+
});
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (value.length > MAX_DESCRIPTION_LENGTH) {
|
|
158
|
+
ctx.addIssue({
|
|
159
|
+
code: z.ZodIssueCode.custom,
|
|
160
|
+
message: `description must be <= ${MAX_DESCRIPTION_LENGTH} characters`
|
|
161
|
+
});
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (hasAngleBrackets(value)) {
|
|
165
|
+
ctx.addIssue({
|
|
166
|
+
code: z.ZodIssueCode.custom,
|
|
167
|
+
message: 'description must not contain "<" or ">"'
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}),
|
|
171
|
+
metadata: z.record(z.string(), z.unknown(), {
|
|
172
|
+
error: 'Frontmatter field "metadata" must be an object when present'
|
|
173
|
+
}).optional(),
|
|
174
|
+
compatibility: z.string({
|
|
175
|
+
error: 'Frontmatter field "compatibility" must be a string when present'
|
|
176
|
+
}).superRefine((value, ctx) => {
|
|
177
|
+
if (value.length > MAX_COMPATIBILITY_LENGTH) {
|
|
178
|
+
ctx.addIssue({
|
|
179
|
+
code: z.ZodIssueCode.custom,
|
|
180
|
+
message: `compatibility must be <= ${MAX_COMPATIBILITY_LENGTH} characters`
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}).optional(),
|
|
184
|
+
license: z.string({
|
|
185
|
+
error: 'Frontmatter field "license" must be a string when present'
|
|
186
|
+
}).optional(),
|
|
187
|
+
"allowed-tools": z.string({
|
|
188
|
+
error: 'Frontmatter field "allowed-tools" must be a string when present'
|
|
189
|
+
}).optional(),
|
|
190
|
+
"requires-capabilities": createTokenFieldSchema(
|
|
191
|
+
"requires-capabilities",
|
|
192
|
+
"github.issues.write"
|
|
193
|
+
).optional(),
|
|
194
|
+
"uses-config": createTokenFieldSchema(
|
|
195
|
+
"uses-config",
|
|
196
|
+
"github.repo"
|
|
197
|
+
).optional()
|
|
198
|
+
}).passthrough();
|
|
199
|
+
function stripFrontmatter(raw) {
|
|
200
|
+
return raw.replace(FRONTMATTER_RE, "").trim();
|
|
201
|
+
}
|
|
202
|
+
function parseSkillFile(raw, expectedName) {
|
|
203
|
+
const match = FRONTMATTER_RE.exec(raw);
|
|
204
|
+
if (!match) {
|
|
205
|
+
return { ok: false, error: "Missing YAML frontmatter at start of file" };
|
|
206
|
+
}
|
|
207
|
+
let parsed;
|
|
208
|
+
try {
|
|
209
|
+
parsed = parseYaml(match[1]);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
return {
|
|
212
|
+
ok: false,
|
|
213
|
+
error: `Invalid YAML frontmatter: ${error instanceof Error ? error.message : String(error)}`
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
217
|
+
return { ok: false, error: "Frontmatter must be a YAML object" };
|
|
218
|
+
}
|
|
219
|
+
const result = skillFrontmatterSchema.safeParse(parsed);
|
|
220
|
+
if (!result.success) {
|
|
221
|
+
return {
|
|
222
|
+
ok: false,
|
|
223
|
+
error: result.error.issues[0]?.message ?? "Invalid YAML frontmatter"
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
if (expectedName && result.data.name !== expectedName) {
|
|
227
|
+
return {
|
|
228
|
+
ok: false,
|
|
229
|
+
error: `name "${result.data.name}" must match directory "${expectedName}"`
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
const allowedTools = parseTokenList(result.data["allowed-tools"]);
|
|
233
|
+
const requiresCapabilities = parseTokenList(
|
|
234
|
+
result.data["requires-capabilities"]
|
|
235
|
+
);
|
|
236
|
+
const usesConfig = parseTokenList(result.data["uses-config"]);
|
|
237
|
+
return {
|
|
238
|
+
ok: true,
|
|
239
|
+
skill: {
|
|
240
|
+
name: result.data.name,
|
|
241
|
+
description: result.data.description,
|
|
242
|
+
body: stripFrontmatter(raw),
|
|
243
|
+
...result.data.metadata ? { metadata: result.data.metadata } : {},
|
|
244
|
+
...result.data.compatibility !== void 0 ? { compatibility: result.data.compatibility } : {},
|
|
245
|
+
...result.data.license !== void 0 ? { license: result.data.license } : {},
|
|
246
|
+
...allowedTools ? { allowedTools } : {},
|
|
247
|
+
...requiresCapabilities ? { requiresCapabilities } : {},
|
|
248
|
+
...usesConfig ? { usesConfig } : {}
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
var SKILL_CACHE_TTL_MS = 5e3;
|
|
253
|
+
var skillCache = null;
|
|
254
|
+
function resolveSkillRoots(options) {
|
|
255
|
+
const additionalRoots = options?.additionalRoots ?? [];
|
|
256
|
+
const envRoots = process.env.SKILL_DIRS?.split(path.delimiter).filter(Boolean) ?? [];
|
|
257
|
+
const defaults = skillRoots();
|
|
258
|
+
const pluginRoots = getPluginSkillRoots();
|
|
259
|
+
const seen = /* @__PURE__ */ new Set();
|
|
260
|
+
const resolved = [];
|
|
261
|
+
for (const root of [
|
|
262
|
+
...additionalRoots,
|
|
263
|
+
...envRoots,
|
|
264
|
+
...defaults,
|
|
265
|
+
...pluginRoots
|
|
266
|
+
]) {
|
|
267
|
+
const normalized = path.resolve(root);
|
|
268
|
+
if (seen.has(normalized)) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
seen.add(normalized);
|
|
272
|
+
resolved.push(normalized);
|
|
273
|
+
}
|
|
274
|
+
return resolved;
|
|
275
|
+
}
|
|
276
|
+
function validateSkillMetadata(input) {
|
|
277
|
+
const unknownCapabilities = (input.requiresCapabilities ?? []).filter(
|
|
278
|
+
(capability) => !isKnownCapability(capability)
|
|
279
|
+
);
|
|
280
|
+
if (unknownCapabilities.length > 0) {
|
|
281
|
+
return `Unknown requires-capabilities values: ${unknownCapabilities.join(", ")}`;
|
|
282
|
+
}
|
|
283
|
+
const unknownConfigKeys = (input.usesConfig ?? []).filter(
|
|
284
|
+
(configKey) => !isKnownConfigKey(configKey)
|
|
285
|
+
);
|
|
286
|
+
if (unknownConfigKeys.length > 0) {
|
|
287
|
+
return `Unknown uses-config values: ${unknownConfigKeys.join(", ")}`;
|
|
288
|
+
}
|
|
289
|
+
return void 0;
|
|
290
|
+
}
|
|
291
|
+
async function readSkillDirectory(skillDir) {
|
|
292
|
+
const skillFile = path.join(skillDir, "SKILL.md");
|
|
293
|
+
try {
|
|
294
|
+
const raw = await fs.readFile(skillFile, "utf8");
|
|
295
|
+
const parsed = parseSkillFile(raw, path.basename(skillDir));
|
|
296
|
+
if (!parsed.ok) {
|
|
297
|
+
logWarn(
|
|
298
|
+
"skill_frontmatter_invalid",
|
|
299
|
+
{},
|
|
300
|
+
{
|
|
301
|
+
"file.path": skillDir,
|
|
302
|
+
"error.message": parsed.error
|
|
303
|
+
},
|
|
304
|
+
"Invalid skill frontmatter"
|
|
305
|
+
);
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
const {
|
|
309
|
+
name,
|
|
310
|
+
description,
|
|
311
|
+
allowedTools,
|
|
312
|
+
requiresCapabilities,
|
|
313
|
+
usesConfig
|
|
314
|
+
} = parsed.skill;
|
|
315
|
+
const plugin = getPluginForSkillPath(skillDir);
|
|
316
|
+
const metadataError = validateSkillMetadata({
|
|
317
|
+
requiresCapabilities,
|
|
318
|
+
usesConfig
|
|
319
|
+
});
|
|
320
|
+
if (metadataError) {
|
|
321
|
+
logWarn(
|
|
322
|
+
"skill_frontmatter_invalid",
|
|
323
|
+
{},
|
|
324
|
+
{
|
|
325
|
+
"file.path": skillDir,
|
|
326
|
+
"error.message": metadataError
|
|
327
|
+
},
|
|
328
|
+
"Invalid skill frontmatter"
|
|
329
|
+
);
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
return {
|
|
333
|
+
name,
|
|
334
|
+
description,
|
|
335
|
+
skillPath: skillDir,
|
|
336
|
+
...plugin ? { pluginProvider: plugin.manifest.name } : {},
|
|
337
|
+
allowedTools,
|
|
338
|
+
requiresCapabilities,
|
|
339
|
+
usesConfig
|
|
340
|
+
};
|
|
341
|
+
} catch (error) {
|
|
342
|
+
logWarn(
|
|
343
|
+
"skill_directory_read_failed",
|
|
344
|
+
{},
|
|
345
|
+
{
|
|
346
|
+
"file.path": skillDir,
|
|
347
|
+
"error.message": error instanceof Error ? error.message : String(error)
|
|
348
|
+
},
|
|
349
|
+
"Failed to read skill directory"
|
|
350
|
+
);
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
async function discoverSkills(options) {
|
|
355
|
+
const roots = resolveSkillRoots(options);
|
|
356
|
+
const cacheKey = roots.join(path.delimiter);
|
|
357
|
+
if (skillCache && skillCache.expiresAt > Date.now() && skillCache.key === cacheKey) {
|
|
358
|
+
return skillCache.skills;
|
|
359
|
+
}
|
|
360
|
+
const discovered = [];
|
|
361
|
+
const seen = /* @__PURE__ */ new Set();
|
|
362
|
+
for (const root of roots) {
|
|
363
|
+
try {
|
|
364
|
+
const entries = await fs.readdir(root, { withFileTypes: true });
|
|
365
|
+
for (const entry of entries.sort(
|
|
366
|
+
(a, b) => a.name.localeCompare(b.name)
|
|
367
|
+
)) {
|
|
368
|
+
if (!entry.isDirectory()) {
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
const skill = await readSkillDirectory(path.join(root, entry.name));
|
|
372
|
+
if (skill && !seen.has(skill.name)) {
|
|
373
|
+
seen.add(skill.name);
|
|
374
|
+
discovered.push(skill);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
} catch (error) {
|
|
378
|
+
logWarn(
|
|
379
|
+
"skill_root_read_failed",
|
|
380
|
+
{},
|
|
381
|
+
{
|
|
382
|
+
"file.directory": root,
|
|
383
|
+
"error.message": error instanceof Error ? error.message : String(error)
|
|
384
|
+
},
|
|
385
|
+
"Failed to read skill root"
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
const sorted = discovered.sort((a, b) => a.name.localeCompare(b.name));
|
|
390
|
+
skillCache = {
|
|
391
|
+
expiresAt: Date.now() + SKILL_CACHE_TTL_MS,
|
|
392
|
+
key: cacheKey,
|
|
393
|
+
skills: sorted
|
|
394
|
+
};
|
|
395
|
+
return sorted;
|
|
396
|
+
}
|
|
397
|
+
function parseSkillInvocation(messageText, availableSkills) {
|
|
398
|
+
const trimmed = messageText.trim();
|
|
399
|
+
const match = /(?:^|\s)\/([a-z0-9]+(?:-[a-z0-9]+)*)(?:\s+([\s\S]*))?/i.exec(
|
|
400
|
+
trimmed
|
|
401
|
+
);
|
|
402
|
+
if (!match) {
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
const skillName = match[1].toLowerCase();
|
|
406
|
+
if (!availableSkills.some((skill) => skill.name === skillName)) {
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
return {
|
|
410
|
+
skillName,
|
|
411
|
+
args: (match[2] ?? "").trim()
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
function findSkillByName(skillName, available) {
|
|
415
|
+
return available.find((skill) => skill.name === skillName) ?? null;
|
|
416
|
+
}
|
|
417
|
+
async function loadSkillsByName(skillNames, available) {
|
|
418
|
+
const selected = new Set(skillNames);
|
|
419
|
+
const skills = [];
|
|
420
|
+
for (const meta of available) {
|
|
421
|
+
if (!selected.has(meta.name)) {
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
const skillFile = path.join(meta.skillPath, "SKILL.md");
|
|
425
|
+
const raw = await fs.readFile(skillFile, "utf8");
|
|
426
|
+
const parsed = parseSkillFile(raw, meta.name);
|
|
427
|
+
if (!parsed.ok) {
|
|
428
|
+
throw new Error(`Invalid skill file in ${skillFile}: ${parsed.error}`);
|
|
429
|
+
}
|
|
430
|
+
skills.push({
|
|
431
|
+
...meta,
|
|
432
|
+
body: parsed.skill.body
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
return skills;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export {
|
|
439
|
+
getCapabilityProvider,
|
|
440
|
+
listCapabilityProviders,
|
|
441
|
+
logCapabilityCatalogLoadedOnce,
|
|
442
|
+
stripFrontmatter,
|
|
443
|
+
parseSkillFile,
|
|
444
|
+
discoverSkills,
|
|
445
|
+
parseSkillInvocation,
|
|
446
|
+
findSkillByName,
|
|
447
|
+
loadSkillsByName
|
|
448
|
+
};
|