@actiondock/builder 2.0.11 → 2.0.12
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/archive.d.ts +26 -0
- package/dist/build.d.ts +41 -0
- package/dist/compiler.d.ts +22 -0
- package/dist/errors.d.ts +41 -0
- package/dist/exporter.d.ts +59 -0
- package/{src/index.ts → dist/index.d.ts} +1 -0
- package/dist/index.js +1875 -0
- package/dist/planner.d.ts +26 -0
- package/dist/types.d.ts +311 -0
- package/package.json +13 -14
- package/src/archive.ts +0 -248
- package/src/build.ts +0 -106
- package/src/compiler.ts +0 -303
- package/src/errors.ts +0 -67
- package/src/exporter.ts +0 -680
- package/src/planner.ts +0 -731
- package/src/types.ts +0 -337
package/src/exporter.ts
DELETED
|
@@ -1,680 +0,0 @@
|
|
|
1
|
-
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
generateCompositeSkillMd,
|
|
5
|
-
generateSourceSkillMd,
|
|
6
|
-
generateStandaloneEntrypoint,
|
|
7
|
-
generateStandaloneSkillMd,
|
|
8
|
-
getPackageSlug,
|
|
9
|
-
loadManifest,
|
|
10
|
-
loadPlaybooks,
|
|
11
|
-
loadProjectConfig,
|
|
12
|
-
type CompositeSkillPackageInfo,
|
|
13
|
-
type ProjectConfig,
|
|
14
|
-
} from "@actiondock/core";
|
|
15
|
-
import { createTarGzArchive, createZipArchive } from "./archive";
|
|
16
|
-
import { BunCompiler } from "./compiler";
|
|
17
|
-
import { BuilderError } from "./errors";
|
|
18
|
-
import { BuildPlanner } from "./planner";
|
|
19
|
-
import type {
|
|
20
|
-
ArchiveFormat,
|
|
21
|
-
BatchSkillExportOptions,
|
|
22
|
-
BatchSkillExportResult,
|
|
23
|
-
CompositeSkillExportOptions,
|
|
24
|
-
CompositeSkillExportResult,
|
|
25
|
-
SkillExporterOptions,
|
|
26
|
-
SkillExportResult,
|
|
27
|
-
} from "./types";
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* 递归扫描生成目录中的所有相对文件路径。
|
|
31
|
-
*/
|
|
32
|
-
function scanRelativeFiles(dir: string, baseDir = dir): string[] {
|
|
33
|
-
if (!existsSync(dir)) return [];
|
|
34
|
-
const results: string[] = [];
|
|
35
|
-
const entries = readdirSync(dir);
|
|
36
|
-
for (const entry of entries) {
|
|
37
|
-
const fullPath = join(dir, entry);
|
|
38
|
-
const stat = statSync(fullPath);
|
|
39
|
-
if (stat.isDirectory()) {
|
|
40
|
-
results.push(...scanRelativeFiles(fullPath, baseDir));
|
|
41
|
-
} else if (stat.isFile()) {
|
|
42
|
-
results.push(relative(baseDir, fullPath));
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return results;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* 执行归档压缩操作(支持 .zip 与 .tar.gz)。
|
|
50
|
-
*
|
|
51
|
-
* 归档在进程内完成(见 archive.ts),不依赖宿主机的 zip / tar 命令行工具,
|
|
52
|
-
* 保证 Windows 与最小化 Linux 环境下行为一致。
|
|
53
|
-
*/
|
|
54
|
-
function createArchive(
|
|
55
|
-
skillDir: string,
|
|
56
|
-
format: ArchiveFormat
|
|
57
|
-
): string {
|
|
58
|
-
const parentDir = dirname(skillDir);
|
|
59
|
-
const folderName = basename(skillDir);
|
|
60
|
-
const archiveName = `${folderName}.${format === "tar.gz" ? "tar.gz" : "zip"}`;
|
|
61
|
-
const archivePath = join(parentDir, archiveName);
|
|
62
|
-
|
|
63
|
-
if (existsSync(archivePath)) {
|
|
64
|
-
rmSync(archivePath, { force: true });
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
if (format === "tar.gz") {
|
|
69
|
-
createTarGzArchive(skillDir, archivePath);
|
|
70
|
-
} else {
|
|
71
|
-
createZipArchive(skillDir, archivePath);
|
|
72
|
-
}
|
|
73
|
-
} catch (err: any) {
|
|
74
|
-
throw new BuilderError(`Failed to create ${format} archive: ${err?.message || String(err)}`);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return archivePath;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* 检索单个 Action 项目目录中已存在的 SKILL.md 文件。
|
|
82
|
-
*/
|
|
83
|
-
export function findExistingSingleSkillMd(
|
|
84
|
-
projectRoot: string,
|
|
85
|
-
explicitPath?: string,
|
|
86
|
-
pkgSlug?: string
|
|
87
|
-
): string | undefined {
|
|
88
|
-
if (explicitPath && existsSync(explicitPath)) {
|
|
89
|
-
try {
|
|
90
|
-
if (statSync(explicitPath).isFile()) return resolve(explicitPath);
|
|
91
|
-
} catch {
|
|
92
|
-
// 忽略文件属性读取异常
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const candidates = [
|
|
97
|
-
join(projectRoot, "SKILL.md"),
|
|
98
|
-
join(projectRoot, "skill.md"),
|
|
99
|
-
join(projectRoot, "skills", "SKILL.md"),
|
|
100
|
-
join(projectRoot, "skills", "skill.md"),
|
|
101
|
-
];
|
|
102
|
-
|
|
103
|
-
if (pkgSlug) {
|
|
104
|
-
candidates.push(
|
|
105
|
-
join(projectRoot, "skills", pkgSlug, "SKILL.md"),
|
|
106
|
-
join(projectRoot, "skills", pkgSlug, "skill.md")
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
for (const candidate of candidates) {
|
|
111
|
-
if (existsSync(candidate)) {
|
|
112
|
-
try {
|
|
113
|
-
if (statSync(candidate).isFile()) return resolve(candidate);
|
|
114
|
-
} catch {
|
|
115
|
-
// 忽略
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return undefined;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* 检索复合套件工作区中已存在的 SKILL.md 文件。
|
|
125
|
-
*/
|
|
126
|
-
export function findExistingCompositeSkillMd(
|
|
127
|
-
options: CompositeSkillExportOptions
|
|
128
|
-
): string | undefined {
|
|
129
|
-
if (options.skillMdPath && existsSync(options.skillMdPath)) {
|
|
130
|
-
try {
|
|
131
|
-
if (statSync(options.skillMdPath).isFile()) return resolve(options.skillMdPath);
|
|
132
|
-
} catch {
|
|
133
|
-
// 忽略
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const bundleSlug = getPackageSlug(options.bundleName);
|
|
138
|
-
const searchDirs = new Set<string>();
|
|
139
|
-
|
|
140
|
-
if (options.workspaceRoot && existsSync(options.workspaceRoot)) {
|
|
141
|
-
searchDirs.add(resolve(options.workspaceRoot));
|
|
142
|
-
}
|
|
143
|
-
searchDirs.add(process.cwd());
|
|
144
|
-
|
|
145
|
-
for (const root of options.projectRoots) {
|
|
146
|
-
const absRoot = resolve(root);
|
|
147
|
-
const parentDir = dirname(absRoot);
|
|
148
|
-
searchDirs.add(parentDir);
|
|
149
|
-
searchDirs.add(dirname(parentDir));
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const candidateRelativePaths = [
|
|
153
|
-
join("skills", options.bundleName, "SKILL.md"),
|
|
154
|
-
join("skills", bundleSlug, "SKILL.md"),
|
|
155
|
-
join("skills", "SKILL.md"),
|
|
156
|
-
"SKILL.md",
|
|
157
|
-
join("skills", options.bundleName, "skill.md"),
|
|
158
|
-
join("skills", bundleSlug, "skill.md"),
|
|
159
|
-
join("skills", "skill.md"),
|
|
160
|
-
"skill.md",
|
|
161
|
-
];
|
|
162
|
-
|
|
163
|
-
for (const dir of searchDirs) {
|
|
164
|
-
for (const rel of candidateRelativePaths) {
|
|
165
|
-
const fullPath = join(dir, rel);
|
|
166
|
-
if (existsSync(fullPath)) {
|
|
167
|
-
try {
|
|
168
|
-
if (statSync(fullPath).isFile()) return fullPath;
|
|
169
|
-
} catch {
|
|
170
|
-
// 忽略
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return undefined;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Agent Skill 导出器。
|
|
181
|
-
* 负责源码型 Skill 与独立二进制型 Skill 的构建、打包与归档分发。
|
|
182
|
-
*/
|
|
183
|
-
export class SkillExporter {
|
|
184
|
-
/**
|
|
185
|
-
* 导出 Skill 产物包。
|
|
186
|
-
*
|
|
187
|
-
* @param options 导出配置
|
|
188
|
-
* @returns 导出产物详细描述
|
|
189
|
-
*/
|
|
190
|
-
public async export(options: SkillExporterOptions): Promise<SkillExportResult> {
|
|
191
|
-
const root = resolve(options.projectRoot);
|
|
192
|
-
const mode: "source" | "standalone" =
|
|
193
|
-
options.standalone || options.mode === "standalone" ? "standalone" : "source";
|
|
194
|
-
const target = options.target ? String(options.target) : "host";
|
|
195
|
-
|
|
196
|
-
// 1. 调用 BuildPlanner 执行纯声明式构建规划与依赖闭包裁剪
|
|
197
|
-
const planner = new BuildPlanner({ projectRoot: root });
|
|
198
|
-
const plan = planner.plan({
|
|
199
|
-
projectRoot: root,
|
|
200
|
-
config: options.config,
|
|
201
|
-
manifest: options.manifest,
|
|
202
|
-
actions: options.actions,
|
|
203
|
-
playbooks: options.playbooks,
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
const pkgSlug = getPackageSlug(plan.packageId);
|
|
207
|
-
const targetSuffix = mode === "standalone" && target !== "host" && target !== "bun" ? `-${target}` : "";
|
|
208
|
-
const defaultFolderName = `${pkgSlug}-skill${targetSuffix}`;
|
|
209
|
-
const defaultSkillDir = join(root, "dist", defaultFolderName);
|
|
210
|
-
const skillDir = resolve(options.outDir || defaultSkillDir);
|
|
211
|
-
|
|
212
|
-
mkdirSync(skillDir, { recursive: true });
|
|
213
|
-
|
|
214
|
-
const playbooksDestDir = join(skillDir, "playbooks");
|
|
215
|
-
if (plan.playbooks.length > 0) {
|
|
216
|
-
mkdirSync(playbooksDestDir, { recursive: true });
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const configForTemplates: ProjectConfig = {
|
|
220
|
-
id: plan.packageId,
|
|
221
|
-
name: plan.packageName,
|
|
222
|
-
version: plan.version,
|
|
223
|
-
description: plan.description,
|
|
224
|
-
actionsDir: "actions",
|
|
225
|
-
playbooksDir: "playbooks",
|
|
226
|
-
config: plan.configDefs as any,
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
let usedExistingSkillMd: string | undefined;
|
|
230
|
-
|
|
231
|
-
if (mode === "source") {
|
|
232
|
-
// ----------------------------------------------------
|
|
233
|
-
// 源码 Skill 导出 (Source Skill Export)
|
|
234
|
-
// 优先复用当前动作目录下已有的 SKILL.md;若不存在且未显式跳过,则动态生成
|
|
235
|
-
const existingSkill = !options.skipSkillMd
|
|
236
|
-
? findExistingSingleSkillMd(root, options.skillMdPath, pkgSlug)
|
|
237
|
-
: undefined;
|
|
238
|
-
|
|
239
|
-
if (existingSkill) {
|
|
240
|
-
usedExistingSkillMd = existingSkill;
|
|
241
|
-
const destSkillMd = join(skillDir, "SKILL.md");
|
|
242
|
-
if (resolve(existingSkill) !== resolve(destSkillMd)) {
|
|
243
|
-
copyFileSync(existingSkill, destSkillMd);
|
|
244
|
-
}
|
|
245
|
-
} else if (!options.skipSkillMd) {
|
|
246
|
-
const skillMd = generateSourceSkillMd(
|
|
247
|
-
configForTemplates,
|
|
248
|
-
plan.actions as any,
|
|
249
|
-
plan.playbooks as any
|
|
250
|
-
);
|
|
251
|
-
writeFileSync(join(skillDir, "SKILL.md"), skillMd, "utf-8");
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// - 导出精简后的 actiondock.manifest.json 清单
|
|
255
|
-
const manifestActions: Record<string, unknown> = {};
|
|
256
|
-
for (const a of plan.actions) {
|
|
257
|
-
manifestActions[a.id] = {
|
|
258
|
-
entry: a.entry,
|
|
259
|
-
description: a.description,
|
|
260
|
-
inputSchema: a.inputSchema,
|
|
261
|
-
outputSchema: a.outputSchema,
|
|
262
|
-
uses: a.uses,
|
|
263
|
-
tags: a.tags,
|
|
264
|
-
annotations: a.annotations,
|
|
265
|
-
};
|
|
266
|
-
if (a.id.includes("/")) {
|
|
267
|
-
const shortId = a.id.slice(a.id.lastIndexOf("/") + 1);
|
|
268
|
-
if (!manifestActions[shortId]) {
|
|
269
|
-
manifestActions[shortId] = manifestActions[a.id];
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
const exportedManifest = {
|
|
274
|
-
schemaVersion: 1,
|
|
275
|
-
actions: manifestActions,
|
|
276
|
-
assets: plan.assets,
|
|
277
|
-
};
|
|
278
|
-
writeFileSync(
|
|
279
|
-
join(skillDir, "actiondock.manifest.json"),
|
|
280
|
-
JSON.stringify(exportedManifest, null, 2) + "\n",
|
|
281
|
-
"utf-8"
|
|
282
|
-
);
|
|
283
|
-
|
|
284
|
-
// - 导出精简后的 actiondock.json 项目配置
|
|
285
|
-
const exportedConfig = {
|
|
286
|
-
id: plan.packageId,
|
|
287
|
-
name: plan.packageName,
|
|
288
|
-
version: plan.version,
|
|
289
|
-
description: plan.description,
|
|
290
|
-
actionsDir: "actions",
|
|
291
|
-
playbooksDir: "playbooks",
|
|
292
|
-
config: plan.configDefs,
|
|
293
|
-
};
|
|
294
|
-
writeFileSync(
|
|
295
|
-
join(skillDir, "actiondock.json"),
|
|
296
|
-
JSON.stringify(exportedConfig, null, 2) + "\n",
|
|
297
|
-
"utf-8"
|
|
298
|
-
);
|
|
299
|
-
|
|
300
|
-
// - 导出 package.json
|
|
301
|
-
const projectPkgPath = join(root, "package.json");
|
|
302
|
-
let exportedPkg: any;
|
|
303
|
-
if (existsSync(projectPkgPath)) {
|
|
304
|
-
try {
|
|
305
|
-
const raw = readFileSync(projectPkgPath, "utf-8");
|
|
306
|
-
const parsed = JSON.parse(raw);
|
|
307
|
-
exportedPkg = {
|
|
308
|
-
name: parsed.name || pkgSlug,
|
|
309
|
-
version: plan.version || parsed.version || "0.1.0",
|
|
310
|
-
description: plan.description || parsed.description,
|
|
311
|
-
type: "module",
|
|
312
|
-
dependencies: parsed.dependencies || {
|
|
313
|
-
"@actiondock/sdk": "^2.0.0",
|
|
314
|
-
},
|
|
315
|
-
devDependencies: parsed.devDependencies,
|
|
316
|
-
};
|
|
317
|
-
} catch {
|
|
318
|
-
exportedPkg = {
|
|
319
|
-
name: pkgSlug,
|
|
320
|
-
version: plan.version,
|
|
321
|
-
description: plan.description,
|
|
322
|
-
type: "module",
|
|
323
|
-
dependencies: { "@actiondock/sdk": "^2.0.0" },
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
} else {
|
|
327
|
-
exportedPkg = {
|
|
328
|
-
name: pkgSlug,
|
|
329
|
-
version: plan.version,
|
|
330
|
-
description: plan.description,
|
|
331
|
-
type: "module",
|
|
332
|
-
dependencies: { "@actiondock/sdk": "^2.0.0" },
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
writeFileSync(
|
|
336
|
-
join(skillDir, "package.json"),
|
|
337
|
-
JSON.stringify(exportedPkg, null, 2) + "\n",
|
|
338
|
-
"utf-8"
|
|
339
|
-
);
|
|
340
|
-
|
|
341
|
-
// - 拷贝 tsconfig.json(若存在)
|
|
342
|
-
const tsconfigPath = join(root, "tsconfig.json");
|
|
343
|
-
if (existsSync(tsconfigPath)) {
|
|
344
|
-
copyFileSync(tsconfigPath, join(skillDir, "tsconfig.json"));
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// - 拷贝 Action 源码文件,完整保留相对路径
|
|
348
|
-
for (const act of plan.actions) {
|
|
349
|
-
if (existsSync(act.resolvedPath)) {
|
|
350
|
-
const destFile = join(skillDir, act.entry);
|
|
351
|
-
mkdirSync(dirname(destFile), { recursive: true });
|
|
352
|
-
copyFileSync(act.resolvedPath, destFile);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// - 拷贝静态资产与代码模块文件,完整保留相对路径
|
|
357
|
-
for (const dep of plan.dependencies.modulesAndAssets) {
|
|
358
|
-
if ((dep.type === "asset" || dep.type === "module") && existsSync(dep.resolvedPath)) {
|
|
359
|
-
const destAsset = join(skillDir, dep.path);
|
|
360
|
-
mkdirSync(dirname(destAsset), { recursive: true });
|
|
361
|
-
copyFileSync(dep.resolvedPath, destAsset);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// - 拷贝 Playbook 规程文件
|
|
366
|
-
for (const pb of plan.playbooks) {
|
|
367
|
-
if (existsSync(pb.filePath)) {
|
|
368
|
-
const destPb = join(playbooksDestDir, basename(pb.filePath));
|
|
369
|
-
copyFileSync(pb.filePath, destPb);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
} else {
|
|
373
|
-
// ----------------------------------------------------
|
|
374
|
-
// 独立二进制 Skill 导出 (Standalone Binary Skill Export)
|
|
375
|
-
// 包含: SKILL.md, bin/<binary>, playbooks/*, assets/*
|
|
376
|
-
// ----------------------------------------------------
|
|
377
|
-
const binDir = join(skillDir, "bin");
|
|
378
|
-
mkdirSync(binDir, { recursive: true });
|
|
379
|
-
|
|
380
|
-
const binaryName = pkgSlug;
|
|
381
|
-
const binaryPath = join(binDir, binaryName);
|
|
382
|
-
|
|
383
|
-
// 动态构造 Standalone 编译入口文件
|
|
384
|
-
const buildDir = join(root, ".actiondock", ".build");
|
|
385
|
-
mkdirSync(buildDir, { recursive: true });
|
|
386
|
-
|
|
387
|
-
const entryCode = generateStandaloneEntrypoint(
|
|
388
|
-
plan.packageId,
|
|
389
|
-
plan.version,
|
|
390
|
-
plan.description,
|
|
391
|
-
plan.actions.map((a) => ({ id: a.id, filePath: a.resolvedPath })),
|
|
392
|
-
plan.configDefs
|
|
393
|
-
);
|
|
394
|
-
|
|
395
|
-
const entryFileName = `entry-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.ts`;
|
|
396
|
-
const entryPath = join(buildDir, entryFileName);
|
|
397
|
-
writeFileSync(entryPath, entryCode, "utf-8");
|
|
398
|
-
|
|
399
|
-
let compileRes;
|
|
400
|
-
try {
|
|
401
|
-
compileRes = await BunCompiler.compile({
|
|
402
|
-
entrypoint: entryPath,
|
|
403
|
-
outfile: binaryPath,
|
|
404
|
-
target: options.target,
|
|
405
|
-
minify: options.minify,
|
|
406
|
-
bytecode: options.bytecode,
|
|
407
|
-
cwd: root,
|
|
408
|
-
packageId: plan.packageId,
|
|
409
|
-
version: plan.version,
|
|
410
|
-
actions: plan.actions.map((a) => a.id),
|
|
411
|
-
});
|
|
412
|
-
} finally {
|
|
413
|
-
if (existsSync(entryPath)) {
|
|
414
|
-
rmSync(entryPath, { force: true });
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
const actualBinaryName = basename(compileRes.executablePath);
|
|
419
|
-
|
|
420
|
-
// - 生成独立模式 SKILL.md(若未显式跳过)
|
|
421
|
-
const existingSkill = !options.skipSkillMd
|
|
422
|
-
? findExistingSingleSkillMd(root, options.skillMdPath, pkgSlug)
|
|
423
|
-
: undefined;
|
|
424
|
-
|
|
425
|
-
if (existingSkill) {
|
|
426
|
-
usedExistingSkillMd = existingSkill;
|
|
427
|
-
const destSkillMd = join(skillDir, "SKILL.md");
|
|
428
|
-
if (resolve(existingSkill) !== resolve(destSkillMd)) {
|
|
429
|
-
copyFileSync(existingSkill, destSkillMd);
|
|
430
|
-
}
|
|
431
|
-
} else if (!options.skipSkillMd) {
|
|
432
|
-
const skillMd = generateStandaloneSkillMd(
|
|
433
|
-
configForTemplates,
|
|
434
|
-
plan.actions as any,
|
|
435
|
-
plan.playbooks as any,
|
|
436
|
-
`./bin/${actualBinaryName}`
|
|
437
|
-
);
|
|
438
|
-
writeFileSync(join(skillDir, "SKILL.md"), skillMd, "utf-8");
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// - 拷贝 Playbook 规程文件
|
|
442
|
-
for (const pb of plan.playbooks) {
|
|
443
|
-
if (existsSync(pb.filePath)) {
|
|
444
|
-
const destPb = join(playbooksDestDir, basename(pb.filePath));
|
|
445
|
-
copyFileSync(pb.filePath, destPb);
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// - 拷贝静态资产
|
|
450
|
-
for (const dep of plan.dependencies.modulesAndAssets) {
|
|
451
|
-
if (dep.type === "asset" && existsSync(dep.resolvedPath)) {
|
|
452
|
-
const destAsset = join(skillDir, dep.path);
|
|
453
|
-
mkdirSync(dirname(destAsset), { recursive: true });
|
|
454
|
-
copyFileSync(dep.resolvedPath, destAsset);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// 归档压缩处理
|
|
460
|
-
let archivePath: string | undefined;
|
|
461
|
-
if (options.archive) {
|
|
462
|
-
let format: ArchiveFormat = "zip";
|
|
463
|
-
if (options.archiveFormat === "tar.gz" || options.archive === "tar.gz") {
|
|
464
|
-
format = "tar.gz";
|
|
465
|
-
} else if (options.archiveFormat === "zip" || options.archive === "zip") {
|
|
466
|
-
format = "zip";
|
|
467
|
-
}
|
|
468
|
-
archivePath = createArchive(skillDir, format);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
const files = scanRelativeFiles(skillDir);
|
|
472
|
-
|
|
473
|
-
return {
|
|
474
|
-
packageId: plan.packageId,
|
|
475
|
-
version: plan.version,
|
|
476
|
-
mode,
|
|
477
|
-
target,
|
|
478
|
-
skillDir,
|
|
479
|
-
archivePath,
|
|
480
|
-
actionsCount: plan.actions.length,
|
|
481
|
-
playbooksCount: plan.playbooks.length,
|
|
482
|
-
actions: plan.actions.map((a) => a.id),
|
|
483
|
-
playbooks: plan.playbooks.map((p) => p.id),
|
|
484
|
-
files,
|
|
485
|
-
usedExistingSkillMd,
|
|
486
|
-
};
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
/**
|
|
490
|
-
* 批量导出多个 Skill。
|
|
491
|
-
*/
|
|
492
|
-
public async exportBatch(options: BatchSkillExportOptions): Promise<BatchSkillExportResult> {
|
|
493
|
-
if (!options.projectRoots || options.projectRoots.length === 0) {
|
|
494
|
-
throw new BuilderError("No project roots provided for batch skill export.");
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
const results: SkillExportResult[] = [];
|
|
498
|
-
const baseOutDir = resolve(options.outDir || join(process.cwd(), "dist", "skills"));
|
|
499
|
-
mkdirSync(baseOutDir, { recursive: true });
|
|
500
|
-
|
|
501
|
-
const usedDirNames = new Set<string>();
|
|
502
|
-
for (const projectRoot of options.projectRoots) {
|
|
503
|
-
const config = loadProjectConfig(projectRoot);
|
|
504
|
-
let pkgSlug = getPackageSlug(config.id);
|
|
505
|
-
if (usedDirNames.has(pkgSlug)) {
|
|
506
|
-
pkgSlug = config.id.replace(/[^a-zA-Z0-9-_]/g, "-").replace(/^-+|-+$/g, "");
|
|
507
|
-
}
|
|
508
|
-
usedDirNames.add(pkgSlug);
|
|
509
|
-
|
|
510
|
-
const pkgOutDir = join(baseOutDir, `${pkgSlug}-skill`);
|
|
511
|
-
|
|
512
|
-
const res = await this.export({
|
|
513
|
-
...options,
|
|
514
|
-
projectRoot,
|
|
515
|
-
outDir: pkgOutDir,
|
|
516
|
-
});
|
|
517
|
-
results.push(res);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
const totalActions = results.reduce((sum, r) => sum + r.actionsCount, 0);
|
|
521
|
-
const totalPlaybooks = results.reduce((sum, r) => sum + r.playbooksCount, 0);
|
|
522
|
-
|
|
523
|
-
return {
|
|
524
|
-
results,
|
|
525
|
-
outDir: baseOutDir,
|
|
526
|
-
totalActions,
|
|
527
|
-
totalPlaybooks,
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
/**
|
|
532
|
-
* 静态辅助调用批量导出方法。
|
|
533
|
-
*/
|
|
534
|
-
public static async exportBatch(options: BatchSkillExportOptions): Promise<BatchSkillExportResult> {
|
|
535
|
-
const exporter = new SkillExporter();
|
|
536
|
-
return exporter.exportBatch(options);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
/**
|
|
540
|
-
* 复合模式导出:将多个包聚合为一个统一的复合技能目录。
|
|
541
|
-
*/
|
|
542
|
-
public async exportComposite(options: CompositeSkillExportOptions): Promise<CompositeSkillExportResult> {
|
|
543
|
-
if (!options.projectRoots || options.projectRoots.length === 0) {
|
|
544
|
-
throw new BuilderError("No project roots provided for composite skill export.");
|
|
545
|
-
}
|
|
546
|
-
if (!options.bundleName || !options.bundleName.trim()) {
|
|
547
|
-
throw new BuilderError("bundleName is required for composite skill export.");
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
const bundleSlug = getPackageSlug(options.bundleName);
|
|
551
|
-
const defaultSkillDir = join(process.cwd(), "dist", `${bundleSlug}-skill`);
|
|
552
|
-
const skillDir = resolve(options.outDir || defaultSkillDir);
|
|
553
|
-
|
|
554
|
-
mkdirSync(skillDir, { recursive: true });
|
|
555
|
-
const packagesDestDir = join(skillDir, "packages");
|
|
556
|
-
mkdirSync(packagesDestDir, { recursive: true });
|
|
557
|
-
|
|
558
|
-
const packageInfos: CompositeSkillPackageInfo[] = [];
|
|
559
|
-
const packageSummaries: Array<{ packageId: string; actions: string[]; playbooks: string[] }> = [];
|
|
560
|
-
|
|
561
|
-
const usedDirNames = new Set<string>();
|
|
562
|
-
for (const projectRoot of options.projectRoots) {
|
|
563
|
-
const config = loadProjectConfig(projectRoot);
|
|
564
|
-
let pkgSlug = getPackageSlug(config.id);
|
|
565
|
-
if (usedDirNames.has(pkgSlug)) {
|
|
566
|
-
pkgSlug = config.id.replace(/[^a-zA-Z0-9-_]/g, "-").replace(/^-+|-+$/g, "");
|
|
567
|
-
}
|
|
568
|
-
usedDirNames.add(pkgSlug);
|
|
569
|
-
|
|
570
|
-
const destPkgDir = join(packagesDestDir, pkgSlug);
|
|
571
|
-
|
|
572
|
-
const singleExport = await this.export({
|
|
573
|
-
projectRoot,
|
|
574
|
-
outDir: destPkgDir,
|
|
575
|
-
archive: false,
|
|
576
|
-
skipSkillMd: true,
|
|
577
|
-
});
|
|
578
|
-
|
|
579
|
-
const manifest = loadManifest(destPkgDir) || loadManifest(projectRoot);
|
|
580
|
-
const playbooks = loadPlaybooks(projectRoot, config.playbooksDir);
|
|
581
|
-
|
|
582
|
-
const actionEntries = singleExport.actions.map((actId) => ({
|
|
583
|
-
id: actId,
|
|
584
|
-
description: manifest?.actions?.[actId]?.description,
|
|
585
|
-
}));
|
|
586
|
-
|
|
587
|
-
packageInfos.push({
|
|
588
|
-
config,
|
|
589
|
-
actions: actionEntries,
|
|
590
|
-
playbooks: Array.from(playbooks.values()),
|
|
591
|
-
packageDir: pkgSlug,
|
|
592
|
-
});
|
|
593
|
-
|
|
594
|
-
packageSummaries.push({
|
|
595
|
-
packageId: config.id,
|
|
596
|
-
actions: singleExport.actions,
|
|
597
|
-
playbooks: singleExport.playbooks,
|
|
598
|
-
});
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
const existingSkillPath = findExistingCompositeSkillMd(options);
|
|
602
|
-
if (existingSkillPath) {
|
|
603
|
-
const destSkillMdPath = join(skillDir, "SKILL.md");
|
|
604
|
-
if (resolve(existingSkillPath) !== resolve(destSkillMdPath)) {
|
|
605
|
-
copyFileSync(existingSkillPath, destSkillMdPath);
|
|
606
|
-
}
|
|
607
|
-
} else {
|
|
608
|
-
const description =
|
|
609
|
-
options.description ||
|
|
610
|
-
`ActionDock 复合技能套件,聚合 ${packageInfos.map((p) => p.config.name).join("、")}`;
|
|
611
|
-
const compositeSkillMd = generateCompositeSkillMd(options.bundleName, description, packageInfos);
|
|
612
|
-
writeFileSync(join(skillDir, "SKILL.md"), compositeSkillMd, "utf-8");
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
let archivePath: string | undefined;
|
|
616
|
-
if (options.archive) {
|
|
617
|
-
let format: ArchiveFormat = "zip";
|
|
618
|
-
if (options.archiveFormat === "tar.gz" || options.archive === "tar.gz") {
|
|
619
|
-
format = "tar.gz";
|
|
620
|
-
} else if (options.archiveFormat === "zip" || options.archive === "zip") {
|
|
621
|
-
format = "zip";
|
|
622
|
-
}
|
|
623
|
-
archivePath = createArchive(skillDir, format);
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
const files = scanRelativeFiles(skillDir);
|
|
627
|
-
const totalActions = packageSummaries.reduce((sum, p) => sum + p.actions.length, 0);
|
|
628
|
-
const totalPlaybooks = packageSummaries.reduce((sum, p) => sum + p.playbooks.length, 0);
|
|
629
|
-
|
|
630
|
-
return {
|
|
631
|
-
bundleName: options.bundleName,
|
|
632
|
-
skillDir,
|
|
633
|
-
archivePath,
|
|
634
|
-
packagesCount: packageInfos.length,
|
|
635
|
-
actionsCount: totalActions,
|
|
636
|
-
playbooksCount: totalPlaybooks,
|
|
637
|
-
packages: packageSummaries,
|
|
638
|
-
files,
|
|
639
|
-
usedExistingSkillMd: existingSkillPath,
|
|
640
|
-
};
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
/**
|
|
644
|
-
* 静态辅助调用复合导出方法。
|
|
645
|
-
*/
|
|
646
|
-
public static async exportComposite(options: CompositeSkillExportOptions): Promise<CompositeSkillExportResult> {
|
|
647
|
-
const exporter = new SkillExporter();
|
|
648
|
-
return exporter.exportComposite(options);
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
/**
|
|
652
|
-
* 静态辅助调用方法。
|
|
653
|
-
*/
|
|
654
|
-
public static async export(options: SkillExporterOptions): Promise<SkillExportResult> {
|
|
655
|
-
const exporter = new SkillExporter();
|
|
656
|
-
return exporter.export(options);
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
/**
|
|
661
|
-
* 快捷导出 Skill 产物函数。
|
|
662
|
-
*/
|
|
663
|
-
export async function exportSkill(options: SkillExporterOptions): Promise<SkillExportResult> {
|
|
664
|
-
return SkillExporter.export(options);
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
/**
|
|
668
|
-
* 快捷批量导出多个 Skill 产物函数。
|
|
669
|
-
*/
|
|
670
|
-
export async function exportSkillBatch(options: BatchSkillExportOptions): Promise<BatchSkillExportResult> {
|
|
671
|
-
return SkillExporter.exportBatch(options);
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
/**
|
|
675
|
-
* 快捷复合模式导出 Skill 产物函数。
|
|
676
|
-
*/
|
|
677
|
-
export async function exportCompositeSkill(options: CompositeSkillExportOptions): Promise<CompositeSkillExportResult> {
|
|
678
|
-
return SkillExporter.exportComposite(options);
|
|
679
|
-
}
|
|
680
|
-
|