@heybox/hb-sdk 0.8.0-alpha → 0.8.0-alpha.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 (64) hide show
  1. package/CHANGELOG.md +44 -1
  2. package/README.md +87 -14
  3. package/THIRD_PARTY_NOTICES.md +1755 -0
  4. package/dist/cli-chunks/{build-DJWFSM1B.cjs → build-qWzAbpS7.cjs} +2 -2
  5. package/dist/cli-chunks/{context-DV2UK1Nz.cjs → context-CtS2Thp0.cjs} +1 -26
  6. package/dist/cli-chunks/{create-2HfoB48V.cjs → create-PV5ua977.cjs} +1 -1
  7. package/dist/cli-chunks/{dev-Dgt2zS9k.cjs → dev-CwKbAm_H.cjs} +337 -522
  8. package/dist/cli-chunks/doctor-tJUGOYrm.cjs +65 -0
  9. package/dist/cli-chunks/{index-BjoSXl8C.cjs → index-DVuD75Hr.cjs} +1 -1
  10. package/dist/cli-chunks/{index-D62ANeBv.cjs → index-De687C6-.cjs} +33 -24
  11. package/dist/cli-chunks/{index.esm-CigcxJ2B.cjs → index.esm-B-4yrLNm.cjs} +6 -5
  12. package/dist/cli-chunks/{login-Cumknwdx.cjs → login-DolpqD8K.cjs} +2 -2
  13. package/dist/cli-chunks/{project-vite-CcE-HMmd.cjs → project-vite-1rvkK-M8.cjs} +1 -1
  14. package/dist/cli-chunks/{remote-DDdP3xcE.cjs → remote-rIAQE_G2.cjs} +4 -4
  15. package/dist/cli-chunks/{session-BDi_AZSv.cjs → session-CzaM2Cq3.cjs} +1 -1
  16. package/dist/cli-chunks/skill-CM40_9WH.cjs +83 -0
  17. package/dist/cli-chunks/version-Bz-AfXQU.cjs +8 -0
  18. package/dist/cli.cjs +1 -1
  19. package/dist/devtools/browser-dev-host/assets/browser-dev-host-uq-Wac6k.js +97 -0
  20. package/dist/devtools/browser-dev-host/assets/heybox-logo-CogNENsk.svg +6 -0
  21. package/dist/devtools/browser-dev-host/assets/index-CeP6SLB3.js +567 -0
  22. package/dist/devtools/browser-dev-host/assets/index-KD2f3Jdz.css +1 -0
  23. package/dist/devtools/browser-dev-host/assets/workbench-state-D-1U0JRq.js +5 -0
  24. package/dist/devtools/browser-dev-host/index.html +6 -435
  25. package/dist/index.cjs.js +924 -28
  26. package/dist/index.esm.js +924 -29
  27. package/dist/protocol.cjs.js +208 -6
  28. package/dist/protocol.esm.js +182 -7
  29. package/dist/vite.cjs.js +1 -1
  30. package/dist/vite.esm.js +1 -1
  31. package/package.json +29 -10
  32. package/skill/SKILL.md +16 -13
  33. package/skill/references/api-protocol.md +100 -4
  34. package/skill/references/api-root.md +142 -15
  35. package/skill/references/cli.md +27 -22
  36. package/skill/references/examples.md +30 -1
  37. package/skill/references/llms-index.md +1 -1
  38. package/skill/references/recipes.md +106 -2
  39. package/skill/references/safety-boundaries.md +12 -1
  40. package/skill/skill.json +10 -5
  41. package/types/core/client.d.ts +17 -1
  42. package/types/core/sdk.d.ts +3 -0
  43. package/types/core/singleton.d.ts +3 -0
  44. package/types/index.d.ts +4 -2
  45. package/types/modules/files/index.d.ts +5 -0
  46. package/types/modules/files/registry.d.ts +35 -0
  47. package/types/modules/files/types.d.ts +159 -0
  48. package/types/modules/network/index.d.ts +50 -3
  49. package/types/protocol/capabilities.d.ts +2 -2
  50. package/types/protocol/constants.d.ts +1 -1
  51. package/types/protocol/guards.d.ts +1 -1
  52. package/types/protocol/types.d.ts +1 -1
  53. package/types/protocol.d.ts +4 -3
  54. package/types/skill-metadata.d.ts +0 -4
  55. package/dist/cli-chunks/doctor-C95gIao_.cjs +0 -204
  56. package/dist/devtools/browser-dev-host/main.js +0 -12263
  57. package/skill/scripts/check-references.mjs +0 -14
  58. package/skill/scripts/markdown-sections.mjs +0 -36
  59. package/skill/scripts/package-skill.mjs +0 -60
  60. package/skill/scripts/package-skill.sh +0 -6
  61. package/skill/scripts/skill-metadata.mjs +0 -77
  62. package/skill/scripts/sync-agent-skills-payload.mjs +0 -359
  63. package/skill/scripts/sync-references.mjs +0 -794
  64. package/skill/scripts/validate-skill.mjs +0 -263
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env node
2
- import { spawnSync } from 'node:child_process';
3
- import path from 'node:path';
4
- import { fileURLToPath } from 'node:url';
5
- import process from 'node:process';
6
-
7
- const scriptPath = path.join(path.dirname(fileURLToPath(import.meta.url)), 'sync-references.mjs');
8
- const result = spawnSync(process.execPath, [scriptPath, '--check'], {
9
- cwd: process.cwd(),
10
- encoding: 'utf8',
11
- stdio: 'inherit',
12
- });
13
-
14
- process.exit(result.status ?? 1);
@@ -1,36 +0,0 @@
1
- export function extractSection(markdown, heading) {
2
- const lines = markdown.split('\n');
3
- const start = lines.findIndex((line) => line.trim() === heading);
4
- if (start === -1) return '';
5
- const level = heading.match(/^#+/)?.[0].length ?? 2;
6
- const out = [lines[start]];
7
- let fenceChar = '';
8
- for (let index = start + 1; index < lines.length; index += 1) {
9
- const line = lines[index];
10
- const fenceMatch = line.match(/^\s*(`{3,}|~{3,})/);
11
-
12
- if (fenceChar) {
13
- out.push(line);
14
- if (fenceMatch?.[1]?.startsWith(fenceChar)) fenceChar = '';
15
- continue;
16
- }
17
-
18
- if (fenceMatch) {
19
- fenceChar = fenceMatch[1][0];
20
- out.push(line);
21
- continue;
22
- }
23
-
24
- const headingMatch = line.match(/^(#+)\s/);
25
- if (headingMatch && headingMatch[1].length <= level) break;
26
- out.push(line);
27
- }
28
- return out.join('\n').trimEnd();
29
- }
30
-
31
- export function replaceRequiredSection(markdown, heading, replacement) {
32
- const section = extractSection(markdown, heading);
33
- if (!section) throw new Error(`sync-references: 未找到待覆盖章节 ${heading}`);
34
- const start = markdown.indexOf(section);
35
- return `${markdown.slice(0, start)}${replacement.trimEnd()}${markdown.slice(start + section.length)}`;
36
- }
@@ -1,60 +0,0 @@
1
- #!/usr/bin/env node
2
- import { cpSync, existsSync, mkdtempSync, rmSync } from 'node:fs';
3
- import { spawnSync } from 'node:child_process';
4
- import { tmpdir } from 'node:os';
5
- import path from 'node:path';
6
- import { fileURLToPath } from 'node:url';
7
- import process from 'node:process';
8
- import { HB_SDK_SKILL_NAME } from './skill-metadata.mjs';
9
-
10
- const scriptDir = path.dirname(fileURLToPath(import.meta.url));
11
- const skillDir = path.resolve(scriptDir, '..');
12
- const packageRoot = path.resolve(skillDir, '..');
13
- const skillName = HB_SDK_SKILL_NAME;
14
- const repoRoot = findRepoRoot(process.cwd()) ?? packageRoot;
15
- const zipPath = path.join(packageRoot, `${skillName}.zip`);
16
-
17
- function findRepoRoot(startDir) {
18
- let dir = path.resolve(startDir);
19
- while (true) {
20
- if (existsSync(path.join(dir, 'package.json')) && existsSync(path.join(dir, 'packages/hb-sdk/package.json'))) {
21
- return dir;
22
- }
23
- const parent = path.dirname(dir);
24
- if (parent === dir) return null;
25
- dir = parent;
26
- }
27
- }
28
-
29
- if (!existsSync(path.join(skillDir, 'SKILL.md'))) {
30
- console.error(`Missing ${path.relative(repoRoot, path.join(skillDir, 'SKILL.md'))}.`);
31
- process.exit(1);
32
- }
33
-
34
- if (existsSync(zipPath)) rmSync(zipPath);
35
-
36
- const tempRoot = mkdtempSync(path.join(tmpdir(), 'hb-sdk-skill-'));
37
- const packagedSkillRoot = path.join(tempRoot, skillName);
38
-
39
- try {
40
- cpSync(skillDir, packagedSkillRoot, { recursive: true });
41
-
42
- const result = spawnSync(
43
- 'zip',
44
- ['-r', zipPath, skillName, '-x', `${skillName}/README.md`, '-x', `${skillName}/**/.DS_Store`],
45
- {
46
- cwd: tempRoot,
47
- encoding: 'utf8',
48
- },
49
- );
50
-
51
- if (result.status !== 0) {
52
- process.stderr.write(result.stderr || result.stdout);
53
- process.exit(result.status ?? 1);
54
- }
55
-
56
- console.error(result.stdout.trim());
57
- console.log(JSON.stringify({ ok: true, zip: path.relative(repoRoot, zipPath).split(path.sep).join('/') }));
58
- } finally {
59
- rmSync(tempRoot, { recursive: true, force: true });
60
- }
@@ -1,6 +0,0 @@
1
- #!/bin/bash
2
- set -euo pipefail
3
-
4
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5
- echo "Packaging hb-sdk skill" >&2
6
- node "$SCRIPT_DIR/package-skill.mjs"
@@ -1,77 +0,0 @@
1
- import { createHash } from 'node:crypto';
2
-
3
- export const HB_SDK_SKILL_NAME = 'hb-sdk';
4
- export const HB_SDK_PACKAGE_NAME = '@heybox/hb-sdk';
5
- export const HB_SDK_AGENT_SKILLS_BASE_URL = 'https://open.xiaoheihe.cn/agent-skills';
6
- export const HB_SDK_SKILL_SOURCE = `${HB_SDK_AGENT_SKILLS_BASE_URL}/${HB_SDK_SKILL_NAME}`;
7
- export const HB_SDK_SKILL_INDEX_URL = `${HB_SDK_AGENT_SKILLS_BASE_URL}/.well-known/agent-skills/index.json`;
8
- export const HB_SDK_SKILL_INSTALL_COMMAND = `npx skills add ${HB_SDK_SKILL_SOURCE}`;
9
-
10
- export function createSkillContentHash(fileEntries) {
11
- const hash = createHash('sha256');
12
-
13
- for (const entry of [...fileEntries].sort((left, right) => left.path.localeCompare(right.path))) {
14
- hash.update(entry.path);
15
- hash.update('\0');
16
- hash.update(entry.content);
17
- hash.update('\0');
18
- }
19
-
20
- return {
21
- hex: hash.digest('hex'),
22
- };
23
- }
24
-
25
- export function createSkillManifest({ contentHash, sdkVersion }) {
26
- const shortHash = contentHash.hex.slice(0, 12);
27
-
28
- return {
29
- name: HB_SDK_SKILL_NAME,
30
- skillVersion: `${sdkVersion}+skill.${shortHash}`,
31
- sdk: {
32
- package: HB_SDK_PACKAGE_NAME,
33
- version: sdkVersion,
34
- compatibility: sdkVersion,
35
- },
36
- source: HB_SDK_SKILL_SOURCE,
37
- integrity: `sha256-${contentHash.hex}`,
38
- };
39
- }
40
-
41
- export function validateSkillManifest({ manifest, sdkVersion }) {
42
- const errors = [];
43
-
44
- if (manifest?.name !== HB_SDK_SKILL_NAME) {
45
- errors.push(`skill.json name must be \`${HB_SDK_SKILL_NAME}\`.`);
46
- }
47
-
48
- if (manifest?.sdk?.package !== HB_SDK_PACKAGE_NAME) {
49
- errors.push(`skill.json sdk.package must be \`${HB_SDK_PACKAGE_NAME}\`.`);
50
- }
51
-
52
- if (manifest?.sdk?.version !== sdkVersion) {
53
- errors.push('skill.json sdk.version must match packages/hb-sdk/package.json version.');
54
- }
55
-
56
- if (manifest?.sdk?.compatibility !== sdkVersion) {
57
- errors.push('skill.json sdk.compatibility must match packages/hb-sdk/package.json version.');
58
- }
59
-
60
- if (typeof manifest?.skillVersion !== 'string' || !manifest.skillVersion.startsWith(`${sdkVersion}+skill.`)) {
61
- errors.push('skill.json skillVersion must be derived from the current SDK version.');
62
- }
63
-
64
- if (manifest?.source !== HB_SDK_SKILL_SOURCE) {
65
- errors.push('skill.json source must be the public hb-sdk skill URL.');
66
- }
67
-
68
- if (typeof manifest?.integrity !== 'string' || !/^sha256-[0-9a-f]{64}$/.test(manifest.integrity)) {
69
- errors.push('skill.json integrity must be a sha256 hex digest.');
70
- }
71
-
72
- return errors;
73
- }
74
-
75
- export function normalizeSkillManifest(manifest) {
76
- return `${JSON.stringify(manifest, null, 2)}\n`;
77
- }
@@ -1,359 +0,0 @@
1
- #!/usr/bin/env node
2
- import { Buffer } from 'node:buffer';
3
- import { promises as fs } from 'node:fs';
4
- import path from 'node:path';
5
- import { fileURLToPath, pathToFileURL } from 'node:url';
6
- import { HB_SDK_PACKAGE_NAME } from './skill-metadata.mjs';
7
-
8
- const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
9
- const HB_SDK_ROOT = path.resolve(SCRIPT_DIR, '../..');
10
- const REPO_ROOT = path.resolve(HB_SDK_ROOT, '../..');
11
- const WELL_KNOWN_ROOT = path.join('.well-known', 'agent-skills');
12
- const AGENT_SKILL_PACKAGES = [
13
- {
14
- packageName: HB_SDK_PACKAGE_NAME,
15
- skillDir: 'skill',
16
- },
17
- ];
18
-
19
- const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n?/;
20
- const TEXT_FILE_RE = /\.(?:md|mjs|js|cjs|ts|tsx|json|txt|sh|yaml|yml)$/i;
21
- const PREFERRED_ORDER = new Map([
22
- ['SKILL.md', 0],
23
- ['skill.json', 1],
24
- ['references/api-root.md', 100],
25
- ['references/api-protocol.md', 101],
26
- ['references/cli.md', 102],
27
- ['references/recipes.md', 103],
28
- ['references/safety-boundaries.md', 104],
29
- ['references/llms-index.md', 105],
30
- ['references/examples.md', 106],
31
- ['references/smoke-evaluation.md', 107],
32
- ['scripts/sync-references.mjs', 200],
33
- ['scripts/check-references.mjs', 201],
34
- ['scripts/validate-skill.mjs', 202],
35
- ['scripts/package-skill.mjs', 203],
36
- ['scripts/package-skill.sh', 204],
37
- ]);
38
-
39
- function parseCliArgs(argv) {
40
- const args = argv.slice(2);
41
- let outDir = path.resolve(process.cwd(), '.agent-skills-payload');
42
- let check = false;
43
-
44
- for (let index = 0; index < args.length; index += 1) {
45
- const arg = args[index];
46
- if (arg === '--check') {
47
- check = true;
48
- continue;
49
- }
50
- if (arg === '--out-dir') {
51
- const value = args[index + 1];
52
- if (!value) {
53
- throw new Error('--out-dir requires a path argument');
54
- }
55
- outDir = path.resolve(process.cwd(), value);
56
- index += 1;
57
- }
58
- }
59
-
60
- return { outDir, check };
61
- }
62
-
63
- function toPosixPath(value) {
64
- return value.split(path.sep).join('/');
65
- }
66
-
67
- function assertSafeRelativePath(relativePath, context) {
68
- const normalized = path.posix.normalize(relativePath);
69
-
70
- if (path.isAbsolute(relativePath) || normalized.startsWith('../') || normalized === '..') {
71
- throw new Error(`${context} has unsafe path: ${relativePath}`);
72
- }
73
-
74
- if (normalized !== relativePath) {
75
- throw new Error(`${context} must be normalized: ${relativePath}`);
76
- }
77
-
78
- if (relativePath.includes('/../') || relativePath.endsWith('/..')) {
79
- throw new Error(`${context} cannot contain parent traversal: ${relativePath}`);
80
- }
81
-
82
- if (/\.zip$/i.test(relativePath)) {
83
- throw new Error(`${context} must not include zip archives: ${relativePath}`);
84
- }
85
- }
86
-
87
- function parseFrontmatter(skillText, skillPath) {
88
- const match = skillText.match(FRONTMATTER_RE);
89
- if (!match) {
90
- throw new Error(`${skillPath} is missing YAML frontmatter`);
91
- }
92
-
93
- const metadata = {};
94
- for (const rawLine of match[1].split('\n')) {
95
- const line = rawLine.trim();
96
- if (!line || line.startsWith('#') || line.startsWith('metadata:')) {
97
- continue;
98
- }
99
-
100
- const keyValue = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
101
- if (!keyValue) {
102
- continue;
103
- }
104
-
105
- const [, key, rawValue] = keyValue;
106
- metadata[key] = rawValue.replace(/^['"]|['"]$/g, '').trim();
107
- }
108
-
109
- return metadata;
110
- }
111
-
112
- async function exists(filePath) {
113
- try {
114
- await fs.access(filePath);
115
- return true;
116
- } catch {
117
- return false;
118
- }
119
- }
120
-
121
- async function readJsonFile(filePath) {
122
- return JSON.parse(await fs.readFile(filePath, 'utf8'));
123
- }
124
-
125
- async function resolvePackageRoot(packageSource) {
126
- if (packageSource.packageName !== HB_SDK_PACKAGE_NAME) {
127
- throw new Error(`Unsupported agent skill package: ${packageSource.packageName}`);
128
- }
129
-
130
- if (!(await exists(path.join(HB_SDK_ROOT, 'package.json')))) {
131
- throw new Error(`Cannot resolve ${packageSource.packageName}: missing package root at ${toPosixPath(HB_SDK_ROOT)}`);
132
- }
133
-
134
- return HB_SDK_ROOT;
135
- }
136
-
137
- async function listFilesRecursive(rootDir, relativeBase = '', options = {}) {
138
- if (!(await exists(rootDir))) {
139
- return [];
140
- }
141
-
142
- const entries = await fs.readdir(rootDir, { withFileTypes: true });
143
- const files = [];
144
-
145
- for (const entry of entries) {
146
- if (!options.includeHidden && entry.name.startsWith('.')) {
147
- continue;
148
- }
149
-
150
- const absolutePath = path.join(rootDir, entry.name);
151
- const relativePath = path.posix.join(relativeBase, entry.name);
152
-
153
- if (entry.isDirectory()) {
154
- files.push(...(await listFilesRecursive(absolutePath, relativePath, options)));
155
- continue;
156
- }
157
-
158
- if (entry.isFile()) {
159
- files.push(relativePath);
160
- }
161
- }
162
-
163
- return files;
164
- }
165
-
166
- function sortSkillFiles(files) {
167
- return [...files].sort((left, right) => {
168
- const leftOrder = PREFERRED_ORDER.get(left) ?? 10_000;
169
- const rightOrder = PREFERRED_ORDER.get(right) ?? 10_000;
170
- return leftOrder - rightOrder || left.localeCompare(right);
171
- });
172
- }
173
-
174
- async function discoverSkill(packageSource) {
175
- const packageRoot = await resolvePackageRoot(packageSource);
176
- const skillDir = path.join(packageRoot, packageSource.skillDir);
177
- const skillMarkdownPath = path.join(skillDir, 'SKILL.md');
178
-
179
- if (!(await exists(skillMarkdownPath))) {
180
- throw new Error(`${packageSource.packageName} must provide ${packageSource.skillDir}/SKILL.md.`);
181
- }
182
-
183
- const skillMarkdown = await fs.readFile(skillMarkdownPath, 'utf8');
184
- const frontmatter = parseFrontmatter(skillMarkdown, skillMarkdownPath);
185
- const skillName = frontmatter.name;
186
-
187
- if (!skillName) {
188
- throw new Error(`${skillMarkdownPath} frontmatter must include name`);
189
- }
190
-
191
- if (!frontmatter.description) {
192
- throw new Error(`${skillMarkdownPath} frontmatter must include description`);
193
- }
194
-
195
- const files = ['SKILL.md'];
196
- const skillJsonPath = path.join(skillDir, 'skill.json');
197
- let metadata = null;
198
-
199
- if (await exists(skillJsonPath)) {
200
- const relativeFile = 'skill.json';
201
- assertSafeRelativePath(relativeFile, `${skillName} source file`);
202
- files.push(relativeFile);
203
- metadata = await readJsonFile(skillJsonPath);
204
- if (metadata.name !== skillName) {
205
- throw new Error(`${skillJsonPath} name (${metadata.name}) must match SKILL.md frontmatter (${skillName})`);
206
- }
207
- }
208
-
209
- for (const topLevelDir of ['references', 'scripts', 'assets']) {
210
- const nestedFiles = await listFilesRecursive(path.join(skillDir, topLevelDir), topLevelDir);
211
- for (const relativeFile of nestedFiles) {
212
- if (!TEXT_FILE_RE.test(relativeFile)) {
213
- continue;
214
- }
215
- assertSafeRelativePath(relativeFile, `${skillName} source file`);
216
- files.push(relativeFile);
217
- }
218
- }
219
-
220
- return {
221
- sourceDir: skillDir,
222
- name: skillName,
223
- description: frontmatter.description,
224
- files: sortSkillFiles(files),
225
- metadata,
226
- };
227
- }
228
-
229
- async function discoverSkills() {
230
- const skills = [];
231
-
232
- for (const packageSource of AGENT_SKILL_PACKAGES) {
233
- skills.push(await discoverSkill(packageSource));
234
- }
235
-
236
- if (skills.length === 0) {
237
- throw new Error('No configured agent skills found.');
238
- }
239
-
240
- return skills;
241
- }
242
-
243
- export function createSkillIndex(skills) {
244
- return {
245
- skills: skills.map((skill) => ({
246
- name: skill.name,
247
- description: skill.description,
248
- ...(skill.metadata
249
- ? {
250
- version: skill.metadata.skillVersion,
251
- sdk: skill.metadata.sdk,
252
- source: skill.metadata.source,
253
- integrity: skill.metadata.integrity,
254
- }
255
- : {}),
256
- files: skill.files,
257
- })),
258
- };
259
- }
260
-
261
- async function addIndexFile(outputFiles, baseDir, skills) {
262
- const indexPath = path.join(baseDir, WELL_KNOWN_ROOT, 'index.json');
263
- const index = createSkillIndex(skills);
264
-
265
- outputFiles.set(indexPath, Buffer.from(`${JSON.stringify(index, null, 2)}\n`, 'utf8'));
266
- }
267
-
268
- async function addSkillFiles(outputFiles, baseDir, skills) {
269
- for (const skill of skills) {
270
- for (const relativeFile of skill.files) {
271
- assertSafeRelativePath(relativeFile, `${skill.name} generated file`);
272
- const sourcePath = path.join(skill.sourceDir, ...relativeFile.split('/'));
273
- const targetPath = path.join(baseDir, WELL_KNOWN_ROOT, skill.name, ...relativeFile.split('/'));
274
- outputFiles.set(targetPath, await fs.readFile(sourcePath));
275
- }
276
- }
277
- }
278
-
279
- async function buildExpectedOutput(skills, targetRoot) {
280
- const outputFiles = new Map();
281
-
282
- await addIndexFile(outputFiles, targetRoot, skills);
283
- await addSkillFiles(outputFiles, targetRoot, skills);
284
-
285
- return outputFiles;
286
- }
287
-
288
- async function listExistingGeneratedFiles(targetRoot) {
289
- if (!(await exists(targetRoot))) {
290
- return [];
291
- }
292
-
293
- const relativeFiles = await listFilesRecursive(targetRoot, '', { includeHidden: true });
294
- return relativeFiles.map((relativeFile) => path.join(targetRoot, ...relativeFile.split('/')));
295
- }
296
-
297
- async function syncFiles(expectedOutput, targetRoot) {
298
- await fs.rm(targetRoot, { recursive: true, force: true });
299
-
300
- for (const [targetPath, content] of [...expectedOutput.entries()].sort(([left], [right]) => left.localeCompare(right))) {
301
- await fs.mkdir(path.dirname(targetPath), { recursive: true });
302
- await fs.writeFile(targetPath, content);
303
- }
304
- }
305
-
306
- async function checkFiles(expectedOutput, targetRoot) {
307
- const expectedPaths = new Set(expectedOutput.keys());
308
- const existingPaths = new Set(await listExistingGeneratedFiles(targetRoot));
309
- const problems = [];
310
-
311
- for (const expectedPath of [...expectedPaths].sort()) {
312
- if (!existingPaths.has(expectedPath)) {
313
- problems.push(`missing ${path.relative(REPO_ROOT, expectedPath)}`);
314
- continue;
315
- }
316
-
317
- const actual = await fs.readFile(expectedPath);
318
- const expected = expectedOutput.get(expectedPath);
319
- if (!actual.equals(expected)) {
320
- problems.push(`stale ${path.relative(REPO_ROOT, expectedPath)}`);
321
- }
322
- }
323
-
324
- for (const existingPath of [...existingPaths].sort()) {
325
- if (!expectedPaths.has(existingPath)) {
326
- problems.push(`extra ${path.relative(REPO_ROOT, existingPath)}`);
327
- }
328
- }
329
-
330
- if (problems.length > 0) {
331
- throw new Error(`Agent skills payload is out of sync:\n${problems.map((problem) => `- ${problem}`).join('\n')}`);
332
- }
333
- }
334
-
335
- async function main() {
336
- const { outDir, check } = parseCliArgs(process.argv);
337
- const skills = await discoverSkills();
338
- const expectedOutput = await buildExpectedOutput(skills, outDir);
339
-
340
- if (check) {
341
- await checkFiles(expectedOutput, outDir);
342
- console.log(
343
- `Agent skills payload is in sync at ${toPosixPath(path.relative(REPO_ROOT, outDir))} (${expectedOutput.size} files, ${skills.length} skills).`,
344
- );
345
- return;
346
- }
347
-
348
- await syncFiles(expectedOutput, outDir);
349
- console.log(
350
- `Synced ${skills.length} skills to ${toPosixPath(path.relative(REPO_ROOT, outDir))} (${expectedOutput.size} files).`,
351
- );
352
- }
353
-
354
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
355
- main().catch((error) => {
356
- console.error(error.message);
357
- process.exitCode = 1;
358
- });
359
- }