@actiondock/core 2.0.9 → 2.0.11-beta.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.
@@ -1,350 +0,0 @@
1
- import { spawnSync } from "node:child_process";
2
- import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
- import { basename, dirname, join, resolve } from "node:path";
4
- import { buildProject } from "../build/builder";
5
- import {
6
- discoverPlaybookFiles,
7
- loadActionFileMap,
8
- loadActions,
9
- loadPlaybooks,
10
- loadProjectConfig,
11
- } from "../project/loader";
12
- import type { ProjectConfig } from "../project/types";
13
- import { getPackageSlug } from "../utils";
14
- import { generateSkillJson, generateSkillMd, generateSourceSkillMd, generateStandaloneSkillMd } from "./templates";
15
-
16
- /**
17
- * 导出 Agent Skill 产物时的配置选项。
18
- */
19
- export interface ExportSkillOptions {
20
- /** 源码项目根目录 */
21
- projectRoot: string;
22
- /** 导出模式:source (源码模式) 或 standalone (独立二进制模式) */
23
- mode?: "source" | "standalone";
24
- /** 是否强制独立二进制模式 */
25
- standalone?: boolean;
26
- /** 二进制编译目标架构(默认 host) */
27
- target?: string;
28
- /** 导出目录路径(默认 dist/skills/) */
29
- outDir?: string;
30
- /** 是否自动压缩打包为 .skill.tar.gz 归档包 */
31
- archive?: boolean;
32
- /** 按需挑选的 Playbook 列表(触发 Playbook-driven 最小化依赖 Tree-shaking 导出) */
33
- playbooks?: string[];
34
- /** 显式挑选导出的 Action ID 清单 */
35
- actions?: string[];
36
- /** 独立模式下是否开启代码混淆压缩 */
37
- minify?: boolean;
38
- /** 独立模式下是否编译为字节码 */
39
- bytecode?: boolean;
40
- }
41
-
42
- /**
43
- * Skill 导出完成后的产物描述结果。
44
- */
45
- export interface ExportSkillResult {
46
- /** 所属 Package ID */
47
- packageId: string;
48
- /** 版本号 */
49
- version: string;
50
- /** 导出模式 */
51
- mode: "source" | "standalone";
52
- /** 目标架构 */
53
- target: string;
54
- /** 生成的 Skill 目录绝对路径 */
55
- skillDir: string;
56
- /** 若开启了 archive,生成的 tar.gz 归档文件绝对路径 */
57
- archivePath?: string;
58
- /** 导出的 Action 数量 */
59
- actionsCount: number;
60
- /** 导出的 Playbook 数量 */
61
- playbooksCount: number;
62
- }
63
-
64
- /**
65
- * 将 ActionDock 项目导出为兼容各大 AI Agent 平台(如 Claude Desktop / Antigravity / Open-Interpreter 等)的通用 Agent Skill 规范包。
66
- *
67
- * 支持两种模式:
68
- * 1. source 源码模式:导出 SKILL.md + actiondock.json + 源码文件,配合 CLI 直接执行。
69
- * 2. standalone 独立模式:导出 SKILL.md + 单文件独立二进制可执行文件,目标环境零依赖即跑。
70
- *
71
- * 支持 Playbook 驱动的最小化 Tree-shaking 依赖裁剪。
72
- *
73
- * @param options 导出配置选项
74
- * @returns 导出产物详细结果
75
- */
76
- export async function exportSkill(
77
- options: ExportSkillOptions
78
- ): Promise<ExportSkillResult> {
79
- const root = resolve(options.projectRoot);
80
- const config = loadProjectConfig(root);
81
- const mode: "source" | "standalone" =
82
- options.standalone || options.mode === "standalone" ? "standalone" : "source";
83
- const target = options.target || "host";
84
-
85
- const actionsMap = await loadActions(root, config.actionsDir);
86
- const playbooksMap = loadPlaybooks(root, config.playbooksDir);
87
-
88
- // Map each action ID to its corresponding source file path
89
- const actionFileMap = await loadActionFileMap(root, config.actionsDir);
90
-
91
- let selectedPlaybooks = Array.from(playbooksMap.values());
92
- let selectedActions = Array.from(actionsMap.values());
93
-
94
- // 1. If playbooks are explicitly specified (Playbook-driven minimal export)
95
- if (options.playbooks && options.playbooks.length > 0) {
96
- const specifiedPlaybookIds = new Set(options.playbooks);
97
- const pbList = [];
98
- for (const id of specifiedPlaybookIds) {
99
- const pb = playbooksMap.get(id);
100
- if (!pb) {
101
- throw new Error(
102
- `Playbook '${id}' specified in export options not found in project`
103
- );
104
- }
105
- pbList.push(pb);
106
- }
107
- selectedPlaybooks = pbList;
108
-
109
- // If actions were not explicitly specified, derive required actions from selected playbooks
110
- if (!options.actions || options.actions.length === 0) {
111
- const requiredActions = new Set<string>();
112
- for (const pb of selectedPlaybooks) {
113
- if (pb.actions) {
114
- for (const act of pb.actions) {
115
- requiredActions.add(act);
116
- }
117
- }
118
- }
119
- if (requiredActions.size > 0) {
120
- for (const actId of requiredActions) {
121
- if (!actionsMap.has(actId)) {
122
- console.warn(
123
- `[WARN] Action '${actId}' referenced in playbook is not found in project`
124
- );
125
- }
126
- }
127
- selectedActions = Array.from(actionsMap.values()).filter((a) =>
128
- requiredActions.has(a.id)
129
- );
130
- }
131
- }
132
- }
133
-
134
- // 2. If actions are explicitly specified (Action-driven export)
135
- if (options.actions && options.actions.length > 0) {
136
- const specifiedActionIds = new Set(options.actions);
137
- for (const actId of specifiedActionIds) {
138
- if (!actionsMap.has(actId)) {
139
- throw new Error(
140
- `Action '${actId}' specified in export options not found in project`
141
- );
142
- }
143
- }
144
- selectedActions = Array.from(actionsMap.values()).filter((a) =>
145
- specifiedActionIds.has(a.id)
146
- );
147
-
148
- // If playbooks were not explicitly specified, tree-shake playbooks whose required actions are not included
149
- if (!options.playbooks || options.playbooks.length === 0) {
150
- selectedPlaybooks = selectedPlaybooks.filter((pb) => {
151
- if (!pb.actions || pb.actions.length === 0) return true;
152
- return pb.actions.every((a) => specifiedActionIds.has(a));
153
- });
154
- }
155
- }
156
-
157
- const pkgSlug = getPackageSlug(config.id);
158
-
159
- const targetSuffix = mode === "standalone" && target !== "host" ? `-${target}` : "";
160
- const skillFolderName = `${pkgSlug}-skill${targetSuffix}`;
161
- const defaultSkillDir = join(root, "dist", skillFolderName);
162
- const skillDir = resolve(options.outDir || defaultSkillDir);
163
-
164
- const playbooksDestDir = join(skillDir, "playbooks");
165
-
166
- if (mode === "source") {
167
- // ----------------------------------------------------
168
- // SOURCE SKILL EXPORT (Default)
169
- // Structure: SKILL.md + actiondock.json + package.json + actions/* + playbooks/*
170
- // ----------------------------------------------------
171
- const actionsDestDir = join(skillDir, "actions");
172
- mkdirSync(actionsDestDir, { recursive: true });
173
- if (selectedPlaybooks.length > 0) {
174
- mkdirSync(playbooksDestDir, { recursive: true });
175
- }
176
-
177
- // 1. Generate SKILL.md for Source Package
178
- const skillMd = generateSourceSkillMd(
179
- config,
180
- selectedActions,
181
- selectedPlaybooks
182
- );
183
- writeFileSync(join(skillDir, "SKILL.md"), skillMd, "utf-8");
184
-
185
- // 2. Export tailored actiondock.json
186
- const exportedConfig: Partial<ProjectConfig> = {
187
- id: config.id,
188
- name: config.name,
189
- version: config.version,
190
- description: config.description,
191
- actionsDir: "actions",
192
- playbooksDir: "playbooks",
193
- };
194
- if (config.config) {
195
- exportedConfig.config = config.config;
196
- }
197
- writeFileSync(
198
- join(skillDir, "actiondock.json"),
199
- JSON.stringify(exportedConfig, null, 2) + "\n",
200
- "utf-8"
201
- );
202
-
203
- // 3. Export package.json
204
- const projectPkgJsonPath = join(root, "package.json");
205
- if (existsSync(projectPkgJsonPath)) {
206
- try {
207
- const rawPkg = readFileSync(projectPkgJsonPath, "utf-8");
208
- const parsedPkg = JSON.parse(rawPkg);
209
- const exportedPkg = {
210
- name: parsedPkg.name || pkgSlug,
211
- version: config.version || parsedPkg.version || "0.1.0",
212
- description: config.description || parsedPkg.description,
213
- type: "module",
214
- dependencies: parsedPkg.dependencies || {
215
- "@actiondock/sdk": "^2.0.0",
216
- },
217
- devDependencies: parsedPkg.devDependencies,
218
- };
219
- writeFileSync(
220
- join(skillDir, "package.json"),
221
- JSON.stringify(exportedPkg, null, 2) + "\n",
222
- "utf-8"
223
- );
224
- } catch {
225
- copyFileSync(projectPkgJsonPath, join(skillDir, "package.json"));
226
- }
227
- } else {
228
- const minimalPkg = {
229
- name: pkgSlug,
230
- version: config.version,
231
- description: config.description,
232
- type: "module",
233
- dependencies: {
234
- "@actiondock/sdk": "^2.0.0",
235
- },
236
- };
237
- writeFileSync(
238
- join(skillDir, "package.json"),
239
- JSON.stringify(minimalPkg, null, 2) + "\n",
240
- "utf-8"
241
- );
242
- }
243
-
244
- // 4. Copy tsconfig.json if available
245
- const tsconfigPath = join(root, "tsconfig.json");
246
- if (existsSync(tsconfigPath)) {
247
- copyFileSync(tsconfigPath, join(skillDir, "tsconfig.json"));
248
- }
249
-
250
- // 5. Copy selected action source files
251
- for (const act of selectedActions) {
252
- const entry = actionFileMap.get(act.id);
253
- const srcPath = entry?.filePath;
254
- if (srcPath && existsSync(srcPath)) {
255
- copyFileSync(srcPath, join(actionsDestDir, basename(srcPath)));
256
- }
257
- }
258
-
259
- // 6. Copy selected playbooks
260
- for (const pb of selectedPlaybooks) {
261
- if (pb.filePath && existsSync(pb.filePath)) {
262
- const filename = basename(pb.filePath);
263
- copyFileSync(pb.filePath, join(playbooksDestDir, filename));
264
- }
265
- }
266
- } else {
267
- // ----------------------------------------------------
268
- // STANDALONE SKILL EXPORT (--standalone)
269
- // Structure: SKILL.md + actiondock.skill.json + bin/<binary> + playbooks/*
270
- // ----------------------------------------------------
271
- const binDir = join(skillDir, "bin");
272
- mkdirSync(binDir, { recursive: true });
273
- if (selectedPlaybooks.length > 0) {
274
- mkdirSync(playbooksDestDir, { recursive: true });
275
- }
276
-
277
- const binaryName = pkgSlug;
278
- const binaryPath = join(binDir, binaryName);
279
-
280
- // Build standalone binary with selected actions
281
- const buildRes = await buildProject({
282
- projectRoot: root,
283
- target: options.target,
284
- outfile: binaryPath,
285
- actions: selectedActions.map((a) => a.id),
286
- minify: options.minify,
287
- bytecode: options.bytecode,
288
- });
289
-
290
- const actualBinaryName = basename(buildRes.executablePath);
291
-
292
- // Generate SKILL.md for Standalone Binary
293
- const skillMd = generateStandaloneSkillMd(
294
- config,
295
- selectedActions,
296
- selectedPlaybooks,
297
- `./bin/${actualBinaryName}`
298
- );
299
- writeFileSync(join(skillDir, "SKILL.md"), skillMd, "utf-8");
300
-
301
- // Generate actiondock.skill.json
302
- const skillJson = generateSkillJson(
303
- config,
304
- selectedActions,
305
- actualBinaryName,
306
- target
307
- );
308
- writeFileSync(join(skillDir, "actiondock.skill.json"), skillJson, "utf-8");
309
-
310
- // Copy selected playbooks
311
- for (const pb of selectedPlaybooks) {
312
- if (pb.filePath && existsSync(pb.filePath)) {
313
- const filename = basename(pb.filePath);
314
- copyFileSync(pb.filePath, join(playbooksDestDir, filename));
315
- }
316
- }
317
- }
318
-
319
- let archivePath: string | undefined;
320
- if (options.archive) {
321
- const zipName = `${skillFolderName}.zip`;
322
- archivePath = join(dirname(skillDir), zipName);
323
- const zipProc = spawnSync(
324
- "zip",
325
- ["-r", archivePath, basename(skillDir)],
326
- {
327
- cwd: dirname(skillDir),
328
- stdio: "pipe",
329
- }
330
- );
331
- if (zipProc.status !== 0) {
332
- console.warn(
333
- `[WARN] Failed to create zip archive: ${zipProc.stderr?.toString() || "zip command failed"}`
334
- );
335
- archivePath = undefined;
336
- }
337
- }
338
-
339
- return {
340
- packageId: config.id,
341
- version: config.version,
342
- mode,
343
- target,
344
- skillDir,
345
- archivePath,
346
- actionsCount: selectedActions.length,
347
- playbooksCount: selectedPlaybooks.length,
348
- };
349
- }
350
-