@actiondock/core 2.0.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.
Files changed (43) hide show
  1. package/README.md +50 -0
  2. package/package.json +51 -0
  3. package/src/build/builder.ts +205 -0
  4. package/src/build/index.ts +2 -0
  5. package/src/build/templates.ts +59 -0
  6. package/src/doctor/doctor.ts +332 -0
  7. package/src/doctor/index.ts +2 -0
  8. package/src/doctor/types.ts +25 -0
  9. package/src/export/index.ts +2 -0
  10. package/src/export/skill.ts +349 -0
  11. package/src/export/templates.ts +258 -0
  12. package/src/filter/index.ts +1 -0
  13. package/src/filter/intent.ts +154 -0
  14. package/src/index.ts +13 -0
  15. package/src/profile/client.ts +302 -0
  16. package/src/profile/index.ts +3 -0
  17. package/src/profile/manager.ts +341 -0
  18. package/src/profile/types.ts +71 -0
  19. package/src/project/index.ts +3 -0
  20. package/src/project/init.ts +194 -0
  21. package/src/project/loader.ts +382 -0
  22. package/src/project/types.ts +62 -0
  23. package/src/registry/index.ts +2 -0
  24. package/src/registry/registry.ts +703 -0
  25. package/src/registry/types.ts +127 -0
  26. package/src/runtime/context.ts +232 -0
  27. package/src/runtime/env.ts +172 -0
  28. package/src/runtime/execution-manager.ts +74 -0
  29. package/src/runtime/index.ts +5 -0
  30. package/src/runtime/runner.ts +368 -0
  31. package/src/runtime/standalone.ts +429 -0
  32. package/src/schema/validator.ts +61 -0
  33. package/src/server/body.ts +112 -0
  34. package/src/server/index.ts +6 -0
  35. package/src/server/runtime-registry.ts +80 -0
  36. package/src/server/security.ts +115 -0
  37. package/src/server/server.ts +572 -0
  38. package/src/server/types.ts +42 -0
  39. package/src/storage/index.ts +64 -0
  40. package/src/storage/mask.ts +34 -0
  41. package/src/storage/sqlite.ts +578 -0
  42. package/src/storage/types.ts +111 -0
  43. package/src/utils/index.ts +60 -0
@@ -0,0 +1,382 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
+ import { basename, dirname, join, resolve } from "node:path";
3
+ import YAML from "yaml";
4
+ import type { ActionDefinition } from "@actiondock/sdk";
5
+ import type { PlaybookDefinition, PlaybookFrontmatter, ProjectConfig } from "./types";
6
+
7
+ /**
8
+ * 从指定目录开始向上逐级递归查找包含 `actiondock.json` 的项目根目录。
9
+ *
10
+ * @param cwd 起始搜索目录(默认为 process.cwd())
11
+ * @returns 项目根目录绝对路径,若未找到则返回 null
12
+ */
13
+ export function findProjectRoot(cwd?: string): string | null {
14
+ let current: string;
15
+ try {
16
+ current = resolve(cwd || process.cwd());
17
+ } catch {
18
+ return null;
19
+ }
20
+ while (true) {
21
+ try {
22
+ const configPath = join(current, "actiondock.json");
23
+ if (existsSync(configPath)) {
24
+ return current;
25
+ }
26
+ } catch {
27
+ return null;
28
+ }
29
+ const parent = dirname(current);
30
+ if (parent === current) {
31
+ break;
32
+ }
33
+ current = parent;
34
+ }
35
+ return null;
36
+ }
37
+
38
+ /**
39
+ * 加载并校验指定目录下的 `actiondock.json` 配置文件。
40
+ *
41
+ * @param projectRoot 项目根目录绝对路径
42
+ * @returns 解析后的 ProjectConfig 对象
43
+ * @throws {Error} 若文件不存在或 JSON 格式错误、缺失必要字段
44
+ */
45
+ export function loadProjectConfig(projectRoot: string): ProjectConfig {
46
+ const configPath = join(projectRoot, "actiondock.json");
47
+ if (!existsSync(configPath)) {
48
+ throw new Error(`actiondock.json not found in ${projectRoot}`);
49
+ }
50
+ const content = readFileSync(configPath, "utf-8");
51
+ try {
52
+ const parsed = JSON.parse(content);
53
+ if (!parsed.id || typeof parsed.id !== "string") {
54
+ throw new Error("actiondock.json missing required 'id' field");
55
+ }
56
+ if (!parsed.name || typeof parsed.name !== "string") {
57
+ parsed.name = parsed.id;
58
+ }
59
+ if (!parsed.version || typeof parsed.version !== "string") {
60
+ parsed.version = "0.1.0";
61
+ }
62
+ parsed.actionsDir = parsed.actionsDir || "actions";
63
+ parsed.playbooksDir = parsed.playbooksDir || "playbooks";
64
+ return parsed as ProjectConfig;
65
+ } catch (err: any) {
66
+ throw new Error(`Failed to parse actiondock.json: ${err.message}`);
67
+ }
68
+ }
69
+
70
+ /**
71
+ * 探测宿主系统中可用的包管理工具(优先级:bun > pnpm > yarn > npm)。
72
+ */
73
+ function getInstallCommand(): string[] {
74
+ const candidates: [string, string][] = [
75
+ ["bun", "install"],
76
+ ["pnpm", "install"],
77
+ ["yarn", "install"],
78
+ ["npm", "install"],
79
+ ];
80
+ for (const [pm, action] of candidates) {
81
+ try {
82
+ const check = Bun.spawnSync([pm, "--version"], {
83
+ stdout: "pipe",
84
+ stderr: "pipe",
85
+ });
86
+ if (check.exitCode === 0) {
87
+ return [pm, action];
88
+ }
89
+ } catch {
90
+ // 继续探测下一个候选包管理器
91
+ }
92
+ }
93
+ return ["bun", "install"];
94
+ }
95
+
96
+ /**
97
+ * 确保项目依赖(node_modules)已正确安装。
98
+ * 若尚未安装或加载失败时,自动触发包管理器执行依赖安装。
99
+ *
100
+ * @param projectRoot 项目根目录
101
+ * @param force 是否强制重新安装
102
+ * @returns 是否成功执行了安装
103
+ */
104
+ export function ensureProjectDependencies(projectRoot: string, force = false): boolean {
105
+ if (process.env.ACTIONDOCK_AUTO_INSTALL === "false") {
106
+ return false;
107
+ }
108
+ const pkgJsonPath = join(projectRoot, "package.json");
109
+ if (!existsSync(pkgJsonPath)) {
110
+ return false;
111
+ }
112
+
113
+ const nodeModulesPath = join(projectRoot, "node_modules");
114
+ if (!force && existsSync(nodeModulesPath)) {
115
+ return false;
116
+ }
117
+
118
+ try {
119
+ const raw = readFileSync(pkgJsonPath, "utf-8");
120
+ const pkg = JSON.parse(raw);
121
+ const hasDeps =
122
+ (pkg.dependencies && Object.keys(pkg.dependencies).length > 0) ||
123
+ (pkg.devDependencies && Object.keys(pkg.devDependencies).length > 0);
124
+
125
+ if (!hasDeps && !force) {
126
+ return false;
127
+ }
128
+
129
+ const installCmd = getInstallCommand();
130
+ process.stderr.write(
131
+ `[actiondock] Installing dependencies using ${installCmd[0]} for '${pkg.name || basename(projectRoot)}'...\n`
132
+ );
133
+
134
+ const proc = Bun.spawnSync(installCmd, {
135
+ cwd: projectRoot,
136
+ stdout: "pipe",
137
+ stderr: "pipe",
138
+ });
139
+
140
+ if (proc.exitCode !== 0) {
141
+ const errText = proc.stderr?.toString() || `Unknown error during ${installCmd[0]} install`;
142
+ process.stderr.write(`[actiondock] Warning: Dependency installation failed: ${errText}\n`);
143
+ return false;
144
+ }
145
+
146
+ process.stderr.write(`[actiondock] Dependencies installed successfully.\n`);
147
+ return true;
148
+ } catch (err: any) {
149
+ process.stderr.write(`[actiondock] Warning: Failed to run auto-install: ${err.message}\n`);
150
+ return false;
151
+ }
152
+ }
153
+
154
+ /**
155
+ * 递归扫描指定目录下的特定后缀文件(自动排除测试文件 *.test.ts, *.spec.ts 和 *.d.ts)。
156
+ */
157
+ function scanFiles(dir: string, extension: string): string[] {
158
+ if (!existsSync(dir)) return [];
159
+ const results: string[] = [];
160
+
161
+ function walk(current: string) {
162
+ const entries = readdirSync(current);
163
+ for (const entry of entries) {
164
+ const fullPath = join(current, entry);
165
+ const stat = statSync(fullPath);
166
+ if (stat.isDirectory()) {
167
+ walk(fullPath);
168
+ } else if (stat.isFile() && fullPath.endsWith(extension)) {
169
+ // 排除测试与类型声明文件
170
+ if (
171
+ !fullPath.endsWith(".test.ts") &&
172
+ !fullPath.endsWith(".spec.ts") &&
173
+ !fullPath.endsWith(".d.ts")
174
+ ) {
175
+ results.push(fullPath);
176
+ }
177
+ }
178
+ }
179
+ }
180
+
181
+ walk(dir);
182
+ return results;
183
+ }
184
+
185
+ /**
186
+ * 发现并检索项目 actions 目录下的所有 Action 源码文件(.ts)。
187
+ *
188
+ * @param projectRoot 项目根目录
189
+ * @param actionsDir actions 子目录名称(默认 "actions")
190
+ */
191
+ export function discoverActionFiles(
192
+ projectRoot: string,
193
+ actionsDir = "actions"
194
+ ): string[] {
195
+ const fullDir = join(projectRoot, actionsDir);
196
+ return scanFiles(fullDir, ".ts");
197
+ }
198
+
199
+ /**
200
+ * 动态导入并加载项目下的所有 Action 定义对象。
201
+ *
202
+ * @param projectRoot 项目根目录
203
+ * @param actionsDir actions 子目录(默认 "actions")
204
+ * @param options 控制是否允许自动安装依赖等选项
205
+ * @returns Map<ActionId, ActionDefinition> 映射
206
+ */
207
+ export async function loadActions(
208
+ projectRoot: string,
209
+ actionsDir = "actions",
210
+ options: { autoInstall?: boolean } = { autoInstall: true }
211
+ ): Promise<Map<string, ActionDefinition>> {
212
+ if (options.autoInstall !== false) {
213
+ ensureProjectDependencies(projectRoot);
214
+ }
215
+
216
+ const files = discoverActionFiles(projectRoot, actionsDir);
217
+ const actions = new Map<string, ActionDefinition>();
218
+
219
+ for (const file of files) {
220
+ try {
221
+ // 动态导入,若缺失模块则自动触发依赖重装与二次重试
222
+ let imported: any;
223
+ try {
224
+ imported = await import(file);
225
+ } catch (err: any) {
226
+ const msg = String(err.message || "");
227
+ if (
228
+ options.autoInstall !== false &&
229
+ (msg.includes("Cannot find package") ||
230
+ msg.includes("Cannot find module") ||
231
+ msg.includes("ERR_MODULE_NOT_FOUND") ||
232
+ msg.includes("Could not resolve"))
233
+ ) {
234
+ const installed = ensureProjectDependencies(projectRoot, true);
235
+ if (installed) {
236
+ imported = await import(file);
237
+ } else {
238
+ throw err;
239
+ }
240
+ } else {
241
+ throw err;
242
+ }
243
+ }
244
+
245
+ const action = imported.default || imported.action;
246
+ if (action && typeof action === "object" && typeof action.id === "string") {
247
+ if (actions.has(action.id)) {
248
+ throw new Error(
249
+ `Duplicate action ID '${action.id}' found in ${file} (previously loaded)`
250
+ );
251
+ }
252
+ actions.set(action.id, action);
253
+ } else {
254
+ console.warn(
255
+ `[WARN] File ${file} does not export a valid default ActionDefinition`
256
+ );
257
+ }
258
+ } catch (err: any) {
259
+ throw new Error(`Failed to load action from ${file}: ${err.message}`);
260
+ }
261
+ }
262
+
263
+ return actions;
264
+ }
265
+
266
+ /**
267
+ * Action 文件映射条目,包含 Action ID、源文件绝对路径与 Action 定义对象。
268
+ */
269
+ export interface ActionFileEntry {
270
+ id: string;
271
+ filePath: string;
272
+ action: ActionDefinition;
273
+ }
274
+
275
+ /**
276
+ * 加载并建立 Action ID 与其物理源码文件路径之间的映射关系(供构建打包器使用)。
277
+ */
278
+ export async function loadActionFileMap(
279
+ projectRoot: string,
280
+ actionsDir = "actions"
281
+ ): Promise<Map<string, ActionFileEntry>> {
282
+ const files = discoverActionFiles(projectRoot, actionsDir);
283
+ const map = new Map<string, ActionFileEntry>();
284
+
285
+ for (const file of files) {
286
+ try {
287
+ const imported = await import(file);
288
+ const act = imported.default || imported.action;
289
+ if (act && typeof act === "object" && typeof act.id === "string") {
290
+ map.set(act.id, {
291
+ id: act.id,
292
+ filePath: resolve(file),
293
+ action: act,
294
+ });
295
+ }
296
+ } catch {
297
+ // 忽略非 Action 导出的辅助模块
298
+ }
299
+ }
300
+
301
+ return map;
302
+ }
303
+
304
+ /**
305
+ * 发现项目 playbooks 目录下的所有 Playbook Markdown 文档(.md)。
306
+ */
307
+ export function discoverPlaybookFiles(
308
+ projectRoot: string,
309
+ playbooksDir = "playbooks"
310
+ ): string[] {
311
+ const fullDir = join(projectRoot, playbooksDir);
312
+ return scanFiles(fullDir, ".md");
313
+ }
314
+
315
+ /**
316
+ * 解析单个 Playbook Markdown 文件的内容与 YAML Frontmatter 头部元数据。
317
+ *
318
+ * @param content 文件文本内容
319
+ * @param filePath 物理文件路径
320
+ * @returns PlaybookDefinition 对象
321
+ */
322
+ export function parsePlaybookContent(
323
+ content: string,
324
+ filePath: string
325
+ ): PlaybookDefinition {
326
+ let frontmatter: Partial<PlaybookFrontmatter> = {};
327
+ let body = content;
328
+
329
+ // 正则提取以 --- 包裹的 YAML Frontmatter
330
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
331
+ if (match) {
332
+ try {
333
+ frontmatter = YAML.parse(match[1]) || {};
334
+ body = match[2];
335
+ } catch (err: any) {
336
+ console.warn(`[WARN] Failed to parse frontmatter in ${filePath}: ${err.message}`);
337
+ }
338
+ }
339
+
340
+ const filename = filePath.split("/").pop() || "unknown";
341
+ const defaultId = filename.replace(/\.md$/, "");
342
+
343
+ return {
344
+ id: frontmatter.id || defaultId,
345
+ description: frontmatter.description,
346
+ actions: Array.isArray(frontmatter.actions) ? frontmatter.actions : [],
347
+ content: body.trim(),
348
+ filePath,
349
+ };
350
+ }
351
+
352
+ /**
353
+ * 加载项目 playbooks 目录下的所有 Playbook SOP 文档。
354
+ *
355
+ * @param projectRoot 项目根目录
356
+ * @param playbooksDir playbooks 子目录(默认 "playbooks")
357
+ * @returns Map<PlaybookId, PlaybookDefinition> 映射
358
+ */
359
+ export function loadPlaybooks(
360
+ projectRoot: string,
361
+ playbooksDir = "playbooks"
362
+ ): Map<string, PlaybookDefinition> {
363
+ const files = discoverPlaybookFiles(projectRoot, playbooksDir);
364
+ const playbooks = new Map<string, PlaybookDefinition>();
365
+
366
+ for (const file of files) {
367
+ try {
368
+ const content = readFileSync(file, "utf-8");
369
+ const playbook = parsePlaybookContent(content, file);
370
+ if (playbooks.has(playbook.id)) {
371
+ throw new Error(
372
+ `Duplicate playbook ID '${playbook.id}' found in ${file}`
373
+ );
374
+ }
375
+ playbooks.set(playbook.id, playbook);
376
+ } catch (err: any) {
377
+ throw new Error(`Failed to load playbook from ${file}: ${err.message}`);
378
+ }
379
+ }
380
+
381
+ return playbooks;
382
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * 配置项的值类型枚举。用于自动类型推断与环境变量自动转换。
3
+ */
4
+ export type ConfigValueType = "string" | "number" | "boolean" | "object" | "array";
5
+
6
+ /**
7
+ * 在 actiondock.json 中声明的单项配置定义。
8
+ */
9
+ export interface ConfigItemDefinition {
10
+ /** 配置项的功能描述,展示在 CLI 配置提示与帮助文档中 */
11
+ description?: string;
12
+ /** 默认回退值。若未配置任何自定义值或环境变量,将使用此默认值 */
13
+ default?: unknown;
14
+ /** 是否为敏感信息(如 API Key, Password)。若为 true,在日志与 CLI 输出中默认脱敏 */
15
+ secret?: boolean;
16
+ /** 期望的目标数据类型,从 process.env 读取字符串时将自动尝试强转为此类型 */
17
+ type?: ConfigValueType;
18
+ /** 显式绑定的外部环境变量名(支持单个或优先级数组) */
19
+ env?: string | string[];
20
+ }
21
+
22
+ /**
23
+ * ActionDock 项目根配置文件契约(对应 actiondock.json)。
24
+ */
25
+ export interface ProjectConfig {
26
+ /** 项目全局唯一 ID(例如 "team4u.github-tools") */
27
+ id: string;
28
+ /** 项目展示名称(例如 "GitHub Tools") */
29
+ name: string;
30
+ /** 项目版本号(遵循语义化版本 Semantic Versioning,如 "1.0.0") */
31
+ version: string;
32
+ /** 项目描述信息 */
33
+ description?: string;
34
+ /** Action 脚本文件存放目录(相对于项目根目录,默认为 "actions") */
35
+ actionsDir?: string;
36
+ /** Playbook SOP 文档存放目录(相对于项目根目录,默认为 "playbooks") */
37
+ playbooksDir?: string;
38
+ /** 声明的项目依赖配置项清单 */
39
+ config?: Record<string, ConfigItemDefinition>;
40
+ }
41
+
42
+ /**
43
+ * Playbook Markdown 文档头部 YAML Frontmatter 元数据。
44
+ */
45
+ export interface PlaybookFrontmatter {
46
+ /** Playbook 唯一标识符(例如 "review-pr") */
47
+ id: string;
48
+ /** Playbook 任务描述 */
49
+ description?: string;
50
+ /** 该 Playbook SOP 所依赖/调用的 Action ID 列表(用于最小化构建与 Tree-shaking 导出) */
51
+ actions?: string[];
52
+ }
53
+
54
+ /**
55
+ * 解析后的完整 Playbook 定义对象。
56
+ */
57
+ export interface PlaybookDefinition extends PlaybookFrontmatter {
58
+ /** Markdown 正文内容(去除了头部 YAML Frontmatter 后的 SOP 指南内容) */
59
+ content: string;
60
+ /** Playbook 源文件的绝对物理路径 */
61
+ filePath: string;
62
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./registry";
2
+ export * from "./types";