zidane 4.0.2 → 4.1.3

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.
Files changed (77) hide show
  1. package/README.md +196 -614
  2. package/dist/agent-BoV5Twdl.d.ts +2347 -0
  3. package/dist/agent-BoV5Twdl.d.ts.map +1 -0
  4. package/dist/contexts-3Arvn7yR.js +321 -0
  5. package/dist/contexts-3Arvn7yR.js.map +1 -0
  6. package/dist/contexts.d.ts +2 -25
  7. package/dist/contexts.js +2 -10
  8. package/dist/errors-D1lhd6mX.js +118 -0
  9. package/dist/errors-D1lhd6mX.js.map +1 -0
  10. package/dist/index-28otmfLX.d.ts +400 -0
  11. package/dist/index-28otmfLX.d.ts.map +1 -0
  12. package/dist/index-BfSdALzk.d.ts +113 -0
  13. package/dist/index-BfSdALzk.d.ts.map +1 -0
  14. package/dist/index-DPsd0qwm.d.ts +254 -0
  15. package/dist/index-DPsd0qwm.d.ts.map +1 -0
  16. package/dist/index.d.ts +5 -95
  17. package/dist/index.js +141 -271
  18. package/dist/index.js.map +1 -0
  19. package/dist/interpolate-CukJwP2G.js +887 -0
  20. package/dist/interpolate-CukJwP2G.js.map +1 -0
  21. package/dist/mcp-8wClKY-3.js +771 -0
  22. package/dist/mcp-8wClKY-3.js.map +1 -0
  23. package/dist/mcp.d.ts +2 -4
  24. package/dist/mcp.js +2 -13
  25. package/dist/messages-z5Pq20p7.js +1020 -0
  26. package/dist/messages-z5Pq20p7.js.map +1 -0
  27. package/dist/presets-Cs7_CsMk.js +39 -0
  28. package/dist/presets-Cs7_CsMk.js.map +1 -0
  29. package/dist/presets.d.ts +2 -43
  30. package/dist/presets.js +2 -17
  31. package/dist/providers-CX-R-Oy-.js +969 -0
  32. package/dist/providers-CX-R-Oy-.js.map +1 -0
  33. package/dist/providers.d.ts +2 -4
  34. package/dist/providers.js +3 -23
  35. package/dist/session/sqlite.d.ts +7 -12
  36. package/dist/session/sqlite.d.ts.map +1 -0
  37. package/dist/session/sqlite.js +67 -79
  38. package/dist/session/sqlite.js.map +1 -0
  39. package/dist/session-Cn68UASv.js +440 -0
  40. package/dist/session-Cn68UASv.js.map +1 -0
  41. package/dist/session.d.ts +2 -4
  42. package/dist/session.js +3 -27
  43. package/dist/skills.d.ts +3 -322
  44. package/dist/skills.js +24 -47
  45. package/dist/skills.js.map +1 -0
  46. package/dist/stats-DoKUtF5T.js +58 -0
  47. package/dist/stats-DoKUtF5T.js.map +1 -0
  48. package/dist/tools-DpeWKzP1.js +3941 -0
  49. package/dist/tools-DpeWKzP1.js.map +1 -0
  50. package/dist/tools.d.ts +3 -95
  51. package/dist/tools.js +2 -40
  52. package/dist/tui.d.ts +533 -0
  53. package/dist/tui.d.ts.map +1 -0
  54. package/dist/tui.js +2004 -0
  55. package/dist/tui.js.map +1 -0
  56. package/dist/types-Bx_F8jet.js +39 -0
  57. package/dist/types-Bx_F8jet.js.map +1 -0
  58. package/dist/types.d.ts +4 -55
  59. package/dist/types.js +4 -28
  60. package/package.json +38 -4
  61. package/dist/agent-BAHrGtqu.d.ts +0 -2425
  62. package/dist/chunk-4ILGBQ23.js +0 -803
  63. package/dist/chunk-4LPBN547.js +0 -3540
  64. package/dist/chunk-64LLNY7F.js +0 -28
  65. package/dist/chunk-6STZTA4N.js +0 -830
  66. package/dist/chunk-7GQ7P6DM.js +0 -566
  67. package/dist/chunk-IC7FT4OD.js +0 -37
  68. package/dist/chunk-JCOB6IYO.js +0 -22
  69. package/dist/chunk-JH6IAAFA.js +0 -28
  70. package/dist/chunk-LNN5UTS2.js +0 -97
  71. package/dist/chunk-PMCQOMV4.js +0 -490
  72. package/dist/chunk-UD25QF3H.js +0 -304
  73. package/dist/chunk-W57VY6DJ.js +0 -834
  74. package/dist/sandbox-D7v6Wy62.d.ts +0 -28
  75. package/dist/skills-use-DwZrNmcw.d.ts +0 -80
  76. package/dist/types-Bai5rKpa.d.ts +0 -89
  77. package/dist/validation-Pm--dQEU.d.ts +0 -185
@@ -1,830 +0,0 @@
1
- import {
2
- AgentToolNotAllowedError
3
- } from "./chunk-LNN5UTS2.js";
4
-
5
- // src/skills/activation.ts
6
- function createSkillActivationState(options = {}) {
7
- const byName = /* @__PURE__ */ new Map();
8
- const maxActive = typeof options.maxActive === "number" && options.maxActive > 0 ? options.maxActive : void 0;
9
- return {
10
- active() {
11
- return [...byName.values()];
12
- },
13
- isActive(name) {
14
- return byName.has(name);
15
- },
16
- get(name) {
17
- return byName.get(name);
18
- },
19
- activate(skill, via) {
20
- if (byName.has(skill.name))
21
- return "already-active";
22
- if (maxActive !== void 0 && byName.size >= maxActive)
23
- return "cap-reached";
24
- byName.set(skill.name, {
25
- skill,
26
- activatedAt: Date.now(),
27
- activatedVia: via
28
- });
29
- return "ok";
30
- },
31
- deactivate(name) {
32
- const existing = byName.get(name);
33
- if (!existing)
34
- return void 0;
35
- byName.delete(name);
36
- return existing;
37
- },
38
- clear() {
39
- const snapshot = [...byName.values()];
40
- byName.clear();
41
- return snapshot;
42
- }
43
- };
44
- }
45
-
46
- // src/skills/validate.ts
47
- import { basename } from "path";
48
- var NAME_MAX = 64;
49
- var DESCRIPTION_MAX = 1024;
50
- var COMPATIBILITY_MAX = 500;
51
- var SKILL_NAME_RE = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
52
- var CONSECUTIVE_HYPHENS_RE = /--/;
53
- var ALLOWED_TOOL_PATTERN_RE = /^([\w-]+)(?:\(([^)]*)\))?$/;
54
- var ABS_WINDOWS_PATH_RE = /^[a-z]:[\\/]/i;
55
- var PATH_SEPARATOR_RE = /[\\/]/;
56
- var TRAILING_SLASHES_RE = /\/+$/;
57
- function validateSkillName(name) {
58
- if (typeof name !== "string")
59
- return false;
60
- if (name.length < 1 || name.length > NAME_MAX)
61
- return false;
62
- if (CONSECUTIVE_HYPHENS_RE.test(name))
63
- return false;
64
- return SKILL_NAME_RE.test(name);
65
- }
66
- function validateSkillForWrite(skill) {
67
- const errors = [];
68
- if (!validateSkillName(skill.name)) {
69
- errors.push({
70
- code: "invalid-name",
71
- message: `Skill name "${skill.name}" must be 1-64 chars, lowercase alphanumeric + hyphens, no leading/trailing/consecutive hyphens.`,
72
- field: "name"
73
- });
74
- }
75
- if (skill.location) {
76
- const dirName = basename(skill.baseDir ?? "");
77
- if (dirName && dirName !== skill.name) {
78
- errors.push({
79
- code: "name-mismatch-directory",
80
- message: `Skill name "${skill.name}" must match parent directory name "${dirName}".`,
81
- field: "name"
82
- });
83
- }
84
- }
85
- if (typeof skill.description !== "string" || skill.description.length < 1) {
86
- errors.push({
87
- code: "missing-description",
88
- message: "Skill description is required (non-empty).",
89
- field: "description"
90
- });
91
- } else if (skill.description.length > DESCRIPTION_MAX) {
92
- errors.push({
93
- code: "description-too-long",
94
- message: `Skill description must be at most ${DESCRIPTION_MAX} characters (got ${skill.description.length}).`,
95
- field: "description"
96
- });
97
- }
98
- if (skill.compatibility !== void 0) {
99
- if (typeof skill.compatibility !== "string" || skill.compatibility.length === 0) {
100
- errors.push({
101
- code: "invalid-compatibility",
102
- message: "Compatibility must be a non-empty string when provided.",
103
- field: "compatibility"
104
- });
105
- } else if (skill.compatibility.length > COMPATIBILITY_MAX) {
106
- errors.push({
107
- code: "compatibility-too-long",
108
- message: `Compatibility must be at most ${COMPATIBILITY_MAX} characters (got ${skill.compatibility.length}).`,
109
- field: "compatibility"
110
- });
111
- }
112
- }
113
- if (skill.metadata) {
114
- for (const [key, value] of Object.entries(skill.metadata)) {
115
- if (typeof value !== "string") {
116
- errors.push({
117
- code: "invalid-metadata-value",
118
- message: `Metadata value for "${key}" must be a string (spec: "A map from string keys to string values").`,
119
- field: "metadata"
120
- });
121
- }
122
- }
123
- }
124
- if (skill.allowedTools) {
125
- for (const pattern of skill.allowedTools) {
126
- if (!ALLOWED_TOOL_PATTERN_RE.test(pattern)) {
127
- errors.push({
128
- code: "invalid-allowed-tool-pattern",
129
- message: `Allowed-tools entry "${pattern}" is not a recognized pattern (expected "ToolName" or "ToolName(arg:*)").`,
130
- field: "allowed-tools"
131
- });
132
- }
133
- }
134
- }
135
- return { valid: errors.length === 0, errors };
136
- }
137
- function validateResourcePath(relPath, baseDir) {
138
- if (typeof relPath !== "string" || relPath.length === 0)
139
- return { valid: false, error: "Resource path must be a non-empty string." };
140
- if (relPath.includes("\0"))
141
- return { valid: false, error: "Resource path contains a null byte." };
142
- if (relPath.startsWith("/") || ABS_WINDOWS_PATH_RE.test(relPath))
143
- return { valid: false, error: `Absolute paths are not allowed ("${relPath}").` };
144
- const segments = [];
145
- for (const segment of relPath.split(PATH_SEPARATOR_RE)) {
146
- if (segment === "" || segment === ".")
147
- continue;
148
- if (segment === "..") {
149
- if (segments.length === 0) {
150
- return {
151
- valid: false,
152
- error: `Resource path "${relPath}" escapes the skill directory.`
153
- };
154
- }
155
- segments.pop();
156
- continue;
157
- }
158
- segments.push(segment);
159
- }
160
- if (segments.length === 0)
161
- return { valid: false, error: "Resource path resolves to the skill root itself." };
162
- const absolutePath = `${baseDir.replace(TRAILING_SLASHES_RE, "")}/${segments.join("/")}`;
163
- return { valid: true, absolutePath };
164
- }
165
- function parseAllowedToolPattern(entry) {
166
- const m = entry.trim().match(ALLOWED_TOOL_PATTERN_RE);
167
- if (!m)
168
- return null;
169
- const tool = m[1];
170
- const arg = m[2];
171
- if (!arg)
172
- return { tool };
173
- if (arg.endsWith(":*"))
174
- return { tool, argPrefix: arg.slice(0, -2) };
175
- return { tool, argPrefix: arg };
176
- }
177
- function matchesAllowedTool(displayName, input, pattern) {
178
- const parsed = parseAllowedToolPattern(pattern);
179
- if (!parsed)
180
- return false;
181
- if (parsed.tool !== displayName)
182
- return false;
183
- if (parsed.argPrefix === void 0)
184
- return true;
185
- for (const value of Object.values(input)) {
186
- if (typeof value === "string" && value.startsWith(parsed.argPrefix))
187
- return true;
188
- }
189
- return false;
190
- }
191
- function isToolAllowedByUnion(displayName, input, union) {
192
- if (union.length === 0)
193
- return true;
194
- return union.some((pattern) => matchesAllowedTool(displayName, input, pattern));
195
- }
196
-
197
- // src/skills/allowed-tools.ts
198
- var IMPLICITLY_ALLOWED_SKILL_TOOLS = [
199
- "skills_use",
200
- "skills_read",
201
- "skills_run_script"
202
- ];
203
- function installAllowedToolsGate(hooks, state) {
204
- function effectiveUnion() {
205
- const active = state.active();
206
- const declared = [];
207
- for (const record of active) {
208
- if (record.skill.allowedTools?.length)
209
- declared.push(...record.skill.allowedTools);
210
- }
211
- return {
212
- union: declared.length > 0 ? [...declared, ...IMPLICITLY_ALLOWED_SKILL_TOOLS] : [],
213
- active: active.map((a) => a.skill.name)
214
- };
215
- }
216
- function gateHandler(ctx) {
217
- const { union, active } = effectiveUnion();
218
- if (union.length === 0)
219
- return;
220
- if (isToolAllowedByUnion(ctx.displayName, ctx.input, union))
221
- return;
222
- const err = new AgentToolNotAllowedError({
223
- toolName: ctx.name,
224
- displayName: ctx.displayName,
225
- allowedUnion: union,
226
- activeSkills: active
227
- });
228
- ctx.block = true;
229
- ctx.reason = err.message;
230
- }
231
- function mcpGateHandler(ctx) {
232
- const { union, active } = effectiveUnion();
233
- if (union.length === 0)
234
- return;
235
- if (isToolAllowedByUnion(ctx.displayName, ctx.input, union))
236
- return;
237
- const err = new AgentToolNotAllowedError({
238
- toolName: `mcp_${ctx.server}_${ctx.tool}`,
239
- displayName: ctx.displayName,
240
- allowedUnion: union,
241
- activeSkills: active
242
- });
243
- ctx.block = true;
244
- ctx.reason = err.message;
245
- }
246
- const unregisterTool = hooks.hook("tool:gate", gateHandler);
247
- const unregisterMcp = hooks.hook("mcp:tool:gate", mcpGateHandler);
248
- return function uninstall() {
249
- unregisterTool();
250
- unregisterMcp();
251
- };
252
- }
253
-
254
- // src/xml.ts
255
- var RE_AMP = /&/g;
256
- var RE_LT = /</g;
257
- var RE_GT = />/g;
258
- var RE_QUOT = /"/g;
259
- function escapeXml(str) {
260
- return str.replace(RE_AMP, "&amp;").replace(RE_LT, "&lt;").replace(RE_GT, "&gt;").replace(RE_QUOT, "&quot;");
261
- }
262
-
263
- // src/skills/catalog.ts
264
- function buildCatalog(skills, options = {}) {
265
- if (skills.length === 0)
266
- return "";
267
- const skillsToolRegistered = options.skillsToolRegistered ?? true;
268
- const readToolName = options.readToolName ?? "read_file";
269
- const entries = skills.map((skill) => {
270
- const locationLine = skill.location ? `
271
- <location>${escapeXml(skill.location)}</location>` : "";
272
- return ` <skill>
273
- <name>${escapeXml(skill.name)}</name>
274
- <description>${escapeXml(skill.description)}</description>${locationLine}
275
- </skill>`;
276
- }).join("\n");
277
- const hasFsSkills = skills.some((s) => s.location);
278
- const hasInlineSkills = skills.some((s) => !s.location);
279
- const behavioralParts = [];
280
- behavioralParts.push(
281
- "The following skills provide specialized instructions for specific tasks.",
282
- "When a task matches a skill's description, activate the skill to load its full instructions before proceeding."
283
- );
284
- if (skillsToolRegistered) {
285
- behavioralParts.push(
286
- "To activate a skill, call the `skills_use` tool with the skill's name. The response contains the full instructions and any bundled resources you can then load via `skills_read` (reference files) or execute via `skills_run_script` (scripts/ directory).",
287
- "Relative paths referenced in a skill's instructions resolve against the skill directory noted in the `skills_use` response."
288
- );
289
- } else if (hasFsSkills) {
290
- behavioralParts.push(
291
- `For skills with a <location>, use the ${readToolName} tool to read the SKILL.md file at that path.`,
292
- "When a skill references relative paths, resolve them against the skill's directory (the parent of SKILL.md) and use absolute paths in tool calls."
293
- );
294
- }
295
- if (hasInlineSkills && !skillsToolRegistered) {
296
- behavioralParts.push(
297
- "Skills without a <location> have their instructions included directly in <instructions> tags below."
298
- );
299
- }
300
- const parts = [
301
- behavioralParts.join("\n"),
302
- "",
303
- "<available_skills>",
304
- entries,
305
- "</available_skills>"
306
- ];
307
- if (hasInlineSkills && !skillsToolRegistered) {
308
- parts.push("");
309
- for (const skill of skills) {
310
- if (!skill.location && skill.instructions) {
311
- parts.push(`<skill_instructions name="${escapeXml(skill.name)}">`);
312
- parts.push(skill.instructions);
313
- if (skill.resources && skill.resources.length > 0) {
314
- parts.push("");
315
- parts.push("<skill_resources>");
316
- for (const res of skill.resources) {
317
- parts.push(` <file type="${res.type}">${escapeXml(res.path)}</file>`);
318
- }
319
- parts.push("</skill_resources>");
320
- }
321
- parts.push("</skill_instructions>");
322
- }
323
- }
324
- }
325
- return parts.join("\n");
326
- }
327
-
328
- // src/skills/discovery.ts
329
- import { existsSync, readdirSync, readFileSync, statSync } from "fs";
330
- import { homedir } from "os";
331
- import { basename as basename2, dirname, join, resolve } from "path";
332
- var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
333
- var INDENT_RE = /^[ \t]{2,}/;
334
- var KV_RE = /^([^:]+):(.*)$/;
335
- var DOUBLE_QUOTED_RE = /^"((?:\\.|[^"\\])*)"$/;
336
- var SINGLE_QUOTED_RE = /^'((?:''|[^'])*)'$/;
337
- var DQ_ESCAPE_RE = /\\(["\\/bfnrt])/g;
338
- var WHITESPACE_SPLIT_RE = /\s+/;
339
- var PARAGRAPH_SPLIT_RE = /\n\n/;
340
- var COMMA_OR_SPACE_RE = /[,\s]+/;
341
- function parseFrontmatter(content) {
342
- const diagnostics = [];
343
- const match = content.match(FRONTMATTER_RE);
344
- if (!match) {
345
- return { frontmatter: {}, body: content.trim(), diagnostics };
346
- }
347
- const yamlBlock = match[1];
348
- const body = match[2].trim();
349
- const frontmatter = {};
350
- let currentKey = null;
351
- let currentMap = null;
352
- for (const line of yamlBlock.split("\n")) {
353
- if (!line.trim() || line.trim().startsWith("#"))
354
- continue;
355
- if (currentKey && currentMap && INDENT_RE.test(line)) {
356
- const nestedMatch = line.trim().match(KV_RE);
357
- if (nestedMatch) {
358
- const val = nestedMatch[2].trim();
359
- currentMap[nestedMatch[1].trim()] = unquoteYaml(val);
360
- }
361
- continue;
362
- }
363
- if (currentKey && currentMap) {
364
- frontmatter[currentKey] = currentMap;
365
- currentKey = null;
366
- currentMap = null;
367
- }
368
- const kvMatch = matchFirstColon(line);
369
- if (!kvMatch)
370
- continue;
371
- const key = kvMatch.key.trim();
372
- const rawValue = kvMatch.value.trim();
373
- if (!rawValue) {
374
- currentKey = key;
375
- currentMap = {};
376
- } else {
377
- frontmatter[key] = unquoteYaml(rawValue);
378
- }
379
- }
380
- if (currentKey && currentMap) {
381
- frontmatter[currentKey] = currentMap;
382
- }
383
- return { frontmatter, body, diagnostics };
384
- }
385
- function matchFirstColon(line) {
386
- const idx = line.indexOf(":");
387
- if (idx < 0)
388
- return null;
389
- const key = line.slice(0, idx);
390
- const value = line.slice(idx + 1);
391
- if (!KV_RE.test(`${key}:`))
392
- return null;
393
- return { key, value };
394
- }
395
- function unquoteYaml(val) {
396
- const dq = val.match(DOUBLE_QUOTED_RE);
397
- if (dq) {
398
- return dq[1].replace(DQ_ESCAPE_RE, (_, ch) => {
399
- switch (ch) {
400
- case '"':
401
- return '"';
402
- case "\\":
403
- return "\\";
404
- case "/":
405
- return "/";
406
- case "b":
407
- return "\b";
408
- case "f":
409
- return "\f";
410
- case "n":
411
- return "\n";
412
- case "r":
413
- return "\r";
414
- case "t":
415
- return " ";
416
- default:
417
- return ch;
418
- }
419
- });
420
- }
421
- const sq = val.match(SINGLE_QUOTED_RE);
422
- if (sq) {
423
- return sq[1].replace(/''/g, "'");
424
- }
425
- const hashIdx = val.indexOf(" #");
426
- if (hashIdx >= 0)
427
- return val.slice(0, hashIdx).trimEnd();
428
- return val;
429
- }
430
- function takeString(frontmatter, key, diagnostics) {
431
- const raw = frontmatter[key];
432
- if (raw === void 0 || raw === null)
433
- return void 0;
434
- if (typeof raw === "string")
435
- return raw;
436
- diagnostics.push({
437
- severity: "warning",
438
- code: "invalid-field-type",
439
- message: `Frontmatter field "${key}" expected string, got ${typeof raw}. Coerced.`,
440
- field: key
441
- });
442
- return String(raw);
443
- }
444
- var RESOURCE_DIRS = {
445
- scripts: "script",
446
- references: "reference",
447
- assets: "asset"
448
- };
449
- function enumerateResources(baseDir) {
450
- const resources = [];
451
- for (const [dir, type] of Object.entries(RESOURCE_DIRS)) {
452
- const dirPath = join(baseDir, dir);
453
- if (!existsSync(dirPath) || !statSync(dirPath).isDirectory())
454
- continue;
455
- try {
456
- const files = readdirSync(dirPath, { recursive: true });
457
- for (const file of files) {
458
- const rel = typeof file === "string" ? file : file.toString("utf-8");
459
- const fullPath = join(dirPath, rel);
460
- if (statSync(fullPath).isFile()) {
461
- resources.push({ path: join(dir, rel), type });
462
- }
463
- }
464
- } catch {
465
- }
466
- }
467
- try {
468
- for (const entry of readdirSync(baseDir)) {
469
- if (entry === "SKILL.md")
470
- continue;
471
- const entryPath = join(baseDir, entry);
472
- if (statSync(entryPath).isFile()) {
473
- resources.push({ path: entry, type: "other" });
474
- }
475
- }
476
- } catch {
477
- }
478
- return resources;
479
- }
480
- async function parseSkillFile(filePath, options = {}) {
481
- const absPath = resolve(filePath);
482
- if (!existsSync(absPath))
483
- return null;
484
- const content = readFileSync(absPath, "utf-8");
485
- const { frontmatter, body, diagnostics } = parseFrontmatter(content);
486
- let description = takeString(frontmatter, "description", diagnostics);
487
- if (!description && body) {
488
- const firstParagraph = body.split(PARAGRAPH_SPLIT_RE)[0]?.trim();
489
- if (firstParagraph)
490
- description = firstParagraph;
491
- }
492
- if (!description)
493
- return null;
494
- if (description.length > 1024) {
495
- diagnostics.push({
496
- severity: "warning",
497
- code: "description-too-long",
498
- message: `Description exceeds spec limit of 1024 characters (got ${description.length}). Loading anyway.`,
499
- field: "description"
500
- });
501
- }
502
- const baseDir = dirname(absPath);
503
- const dirName = basename2(baseDir);
504
- const frontmatterName = takeString(frontmatter, "name", diagnostics);
505
- const name = frontmatterName || dirName;
506
- if (frontmatterName && frontmatterName !== dirName) {
507
- diagnostics.push({
508
- severity: "warning",
509
- code: "name-mismatch-directory",
510
- message: `Skill name "${frontmatterName}" does not match parent directory "${dirName}". Loading anyway.`,
511
- field: "name"
512
- });
513
- }
514
- if (name.length > 64) {
515
- diagnostics.push({
516
- severity: "warning",
517
- code: "name-too-long",
518
- message: `Skill name "${name}" exceeds spec limit of 64 characters. Loading anyway.`,
519
- field: "name"
520
- });
521
- }
522
- if (!validateSkillName(name)) {
523
- diagnostics.push({
524
- severity: "warning",
525
- code: "invalid-name-format",
526
- message: `Skill name "${name}" does not match spec format (lowercase alphanumeric + hyphens, no leading/trailing/consecutive hyphens). Loading anyway.`,
527
- field: "name"
528
- });
529
- }
530
- const config = {
531
- name,
532
- description,
533
- instructions: body,
534
- source: options.source ?? "project",
535
- location: absPath,
536
- baseDir,
537
- resources: enumerateResources(baseDir)
538
- };
539
- const license = takeString(frontmatter, "license", diagnostics);
540
- if (license)
541
- config.license = license;
542
- const compatibility = takeString(frontmatter, "compatibility", diagnostics);
543
- if (compatibility) {
544
- if (compatibility.length > 500) {
545
- diagnostics.push({
546
- severity: "warning",
547
- code: "compatibility-too-long",
548
- message: `Compatibility exceeds spec limit of 500 characters (got ${compatibility.length}). Loading anyway.`,
549
- field: "compatibility"
550
- });
551
- }
552
- config.compatibility = compatibility;
553
- }
554
- const metadata = {};
555
- const rawMetadata = frontmatter.metadata;
556
- if (rawMetadata && typeof rawMetadata === "object" && !Array.isArray(rawMetadata)) {
557
- for (const [k, v] of Object.entries(rawMetadata)) {
558
- if (typeof v !== "string") {
559
- diagnostics.push({
560
- severity: "warning",
561
- code: "invalid-metadata-value",
562
- message: `Metadata value for "${k}" is not a string; coerced. (Spec requires string values.)`,
563
- field: "metadata"
564
- });
565
- metadata[k] = String(v);
566
- continue;
567
- }
568
- metadata[k] = v;
569
- }
570
- } else if (rawMetadata !== void 0) {
571
- diagnostics.push({
572
- severity: "warning",
573
- code: "invalid-metadata-shape",
574
- message: `Frontmatter "metadata" expected a map, got ${Array.isArray(rawMetadata) ? "array" : typeof rawMetadata}. Ignored.`,
575
- field: "metadata"
576
- });
577
- }
578
- const pathsField = takeString(frontmatter, "paths", diagnostics);
579
- if (pathsField) {
580
- metadata["zidane.paths"] = pathsField.split(COMMA_OR_SPACE_RE).filter(Boolean).join(",");
581
- diagnostics.push({
582
- severity: "warning",
583
- code: "deprecated-top-level-field",
584
- message: '`paths` is not a spec field and is deprecated \u2014 moved to `metadata["zidane.paths"]`.',
585
- field: "paths"
586
- });
587
- }
588
- const modelField = takeString(frontmatter, "model", diagnostics);
589
- if (modelField) {
590
- metadata["zidane.model"] = modelField;
591
- diagnostics.push({
592
- severity: "warning",
593
- code: "deprecated-top-level-field",
594
- message: '`model` is not a spec field and is deprecated \u2014 moved to `metadata["zidane.model"]`.',
595
- field: "model"
596
- });
597
- }
598
- const thinkingField = takeString(frontmatter, "thinking", diagnostics);
599
- const effortField = thinkingField ? void 0 : takeString(frontmatter, "effort", diagnostics);
600
- const legacyThinking = thinkingField ?? effortField;
601
- if (legacyThinking) {
602
- metadata["zidane.thinking"] = legacyThinking;
603
- diagnostics.push({
604
- severity: "warning",
605
- code: "deprecated-top-level-field",
606
- message: `\`${thinkingField ? "thinking" : "effort"}\` is not a spec field and is deprecated \u2014 moved to \`metadata["zidane.thinking"]\`.`,
607
- field: thinkingField ? "thinking" : "effort"
608
- });
609
- }
610
- if (Object.keys(metadata).length > 0)
611
- config.metadata = metadata;
612
- const allowedTools = takeString(frontmatter, "allowed-tools", diagnostics);
613
- if (allowedTools) {
614
- config.allowedTools = allowedTools.split(WHITESPACE_SPLIT_RE).filter(Boolean);
615
- }
616
- if (diagnostics.length > 0)
617
- config.diagnostics = diagnostics;
618
- return config;
619
- }
620
- var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".DS_Store", "dist", "build"]);
621
- function findSkillDirs(root, maxDepth = 4, _depth = 0) {
622
- if (_depth > maxDepth)
623
- return [];
624
- if (!existsSync(root) || !statSync(root).isDirectory())
625
- return [];
626
- const results = [];
627
- try {
628
- for (const entry of readdirSync(root)) {
629
- if (SKIP_DIRS.has(entry))
630
- continue;
631
- const entryPath = join(root, entry);
632
- if (!statSync(entryPath).isDirectory())
633
- continue;
634
- const skillFile = join(entryPath, "SKILL.md");
635
- if (existsSync(skillFile) && statSync(skillFile).isFile()) {
636
- results.push(skillFile);
637
- } else {
638
- results.push(...findSkillDirs(entryPath, maxDepth, _depth + 1));
639
- }
640
- }
641
- } catch {
642
- }
643
- return results;
644
- }
645
- function getDefaultScanPaths() {
646
- const home = homedir();
647
- const cwd = process.cwd();
648
- return [
649
- // Project-level (higher priority)
650
- { path: join(cwd, ".agents", "skills"), source: "project" },
651
- { path: join(cwd, ".zidane", "skills"), source: "project" },
652
- // User-level (lower priority)
653
- { path: join(home, ".agents", "skills"), source: "user" },
654
- { path: join(home, ".zidane", "skills"), source: "user" }
655
- ];
656
- }
657
- function inferSource(path) {
658
- return path.startsWith(homedir()) ? "user" : "project";
659
- }
660
- async function discoverSkills(paths) {
661
- const skillsByName = /* @__PURE__ */ new Map();
662
- for (const { path: scanPath, source } of paths) {
663
- const skillFiles = findSkillDirs(resolve(scanPath));
664
- for (const file of skillFiles) {
665
- const skill = await parseSkillFile(file, { source });
666
- if (!skill)
667
- continue;
668
- if (skillsByName.has(skill.name)) {
669
- const existing = skillsByName.get(skill.name);
670
- const diag = {
671
- severity: "warning",
672
- code: "name-collision-shadowed",
673
- message: `A skill with name "${skill.name}" was also found at ${file} (source: ${source}); shadowed by ${existing.location} (source: ${existing.source}).`
674
- };
675
- existing.diagnostics = [...existing.diagnostics ?? [], diag];
676
- continue;
677
- }
678
- skillsByName.set(skill.name, skill);
679
- }
680
- }
681
- return [...skillsByName.values()];
682
- }
683
-
684
- // src/skills/writer.ts
685
- import { mkdirSync, writeFileSync } from "fs";
686
- import { join as join2 } from "path";
687
- var YAML_RESERVED_RE = /[:#&*!|>%@`]/;
688
- var YAML_EDGE_OR_QUOTE_RE = /^\s|\s$|["']/;
689
- var DQUOTE_RE = /"/g;
690
- var LEADING_NEWLINES_RE = /^\n+/;
691
- function yamlEscape(value) {
692
- if (YAML_RESERVED_RE.test(value) || YAML_EDGE_OR_QUOTE_RE.test(value) || value === "")
693
- return `"${value.replace(DQUOTE_RE, '\\"')}"`;
694
- return value;
695
- }
696
- function serializeFrontmatter(skill) {
697
- const lines = ["---"];
698
- lines.push(`name: ${yamlEscape(skill.name)}`);
699
- lines.push(`description: ${yamlEscape(skill.description)}`);
700
- if (skill.license)
701
- lines.push(`license: ${yamlEscape(skill.license)}`);
702
- if (skill.compatibility)
703
- lines.push(`compatibility: ${yamlEscape(skill.compatibility)}`);
704
- if (skill.allowedTools?.length)
705
- lines.push(`allowed-tools: ${skill.allowedTools.join(" ")}`);
706
- if (skill.metadata && Object.keys(skill.metadata).length > 0) {
707
- lines.push("metadata:");
708
- for (const [key, value] of Object.entries(skill.metadata)) {
709
- lines.push(` ${key}: "${value.replace(DQUOTE_RE, '\\"')}"`);
710
- }
711
- }
712
- lines.push("---");
713
- return lines.join("\n");
714
- }
715
- function writeSkillToDisk(skill, targetDir) {
716
- const result = validateSkillForWrite(skill);
717
- if (!result.valid) {
718
- const summary = result.errors.map((e) => ` - [${e.code}] ${e.message}`).join("\n");
719
- throw new Error(`Cannot write invalid skill "${skill.name}":
720
- ${summary}`);
721
- }
722
- const skillDir = join2(targetDir, skill.name);
723
- mkdirSync(skillDir, { recursive: true });
724
- const frontmatter = serializeFrontmatter(skill);
725
- const bodyTrimmed = skill.instructions ? skill.instructions.replace(LEADING_NEWLINES_RE, "") : "";
726
- const content = bodyTrimmed ? `${frontmatter}
727
-
728
- ${bodyTrimmed}
729
- ` : `${frontmatter}
730
- `;
731
- const skillPath = join2(skillDir, "SKILL.md");
732
- writeFileSync(skillPath, content);
733
- return skillPath;
734
- }
735
- function writeSkillsToDisk(skills, targetDir) {
736
- mkdirSync(targetDir, { recursive: true });
737
- for (const skill of skills) {
738
- writeSkillToDisk(skill, targetDir);
739
- }
740
- return targetDir;
741
- }
742
-
743
- // src/skills/resolve.ts
744
- import { mkdtempSync, rmSync } from "fs";
745
- import { tmpdir } from "os";
746
- import { join as join3 } from "path";
747
- async function resolveSkills(config) {
748
- const sourcedPaths = [];
749
- let writeDir;
750
- if (!config.skipDefaultPaths) {
751
- sourcedPaths.push(...getDefaultScanPaths());
752
- }
753
- for (const p of config.scan ?? []) {
754
- sourcedPaths.push({ path: p, source: inferSource(p) });
755
- }
756
- if (config.write?.length) {
757
- writeDir = mkdtempSync(join3(tmpdir(), "zidane-skills-"));
758
- writeSkillsToDisk(config.write, writeDir);
759
- sourcedPaths.push({ path: writeDir, source: "inline" });
760
- }
761
- let skills = await discoverSkills(sourcedPaths);
762
- if (config.trustProjectSkills === false) {
763
- skills = skills.filter((s) => s.source !== void 0 && s.source !== "project");
764
- }
765
- const exclude = new Set(config.exclude ?? []);
766
- let filtered = skills.filter((s) => !exclude.has(s.name));
767
- if (Array.isArray(config.enabled)) {
768
- const allowlist = new Set(config.enabled);
769
- filtered = filtered.filter((s) => allowlist.has(s.name));
770
- }
771
- const cleanup = writeDir ? () => {
772
- try {
773
- rmSync(writeDir, { recursive: true, force: true });
774
- } catch {
775
- }
776
- } : () => {
777
- };
778
- return { skills: filtered, cleanup };
779
- }
780
-
781
- // src/skills/interpolate.ts
782
- var SHELL_INTERPOLATION_RE = /!`([^`]+)`/g;
783
- async function interpolateShellCommands(instructions, execution, handle) {
784
- const matches = [...instructions.matchAll(SHELL_INTERPOLATION_RE)];
785
- if (matches.length === 0)
786
- return instructions;
787
- const replacements = [];
788
- for (const match of matches) {
789
- const command = match[1];
790
- const index = match.index;
791
- const length = match[0].length;
792
- try {
793
- const result2 = await execution.exec(handle, command, { timeout: 30 });
794
- const output = result2.exitCode === 0 ? result2.stdout.trim() : `[command failed (exit ${result2.exitCode}): ${result2.stderr.trim() || result2.stdout.trim()}]`;
795
- replacements.push({ index, length, output });
796
- } catch (err) {
797
- const message = err instanceof Error ? err.message : String(err);
798
- replacements.push({ index, length, output: `[command error: ${message}]` });
799
- }
800
- }
801
- let result = instructions;
802
- for (let i = replacements.length - 1; i >= 0; i--) {
803
- const { index, length, output } = replacements[i];
804
- result = result.slice(0, index) + output + result.slice(index + length);
805
- }
806
- return result;
807
- }
808
-
809
- export {
810
- createSkillActivationState,
811
- validateSkillName,
812
- validateSkillForWrite,
813
- validateResourcePath,
814
- parseAllowedToolPattern,
815
- matchesAllowedTool,
816
- isToolAllowedByUnion,
817
- IMPLICITLY_ALLOWED_SKILL_TOOLS,
818
- installAllowedToolsGate,
819
- escapeXml,
820
- buildCatalog,
821
- parseFrontmatter,
822
- parseSkillFile,
823
- getDefaultScanPaths,
824
- inferSource,
825
- discoverSkills,
826
- writeSkillToDisk,
827
- writeSkillsToDisk,
828
- resolveSkills,
829
- interpolateShellCommands
830
- };