@actiondock/core 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.
Files changed (119) hide show
  1. package/dist/build/templates.d.ts +19 -0
  2. package/dist/catalog/action-index.d.ts +9 -0
  3. package/dist/catalog/action-resolver.d.ts +20 -0
  4. package/dist/catalog/location-registry.d.ts +15 -0
  5. package/dist/catalog/package-catalog.d.ts +7 -0
  6. package/dist/catalog/types.d.ts +68 -0
  7. package/dist/doctor/doctor.d.ts +6 -0
  8. package/dist/doctor/types.d.ts +23 -0
  9. package/dist/execution/index.d.ts +2 -0
  10. package/dist/execution/service.d.ts +74 -0
  11. package/dist/execution/types.d.ts +74 -0
  12. package/dist/export/templates.d.ts +33 -0
  13. package/dist/filter/intent.d.ts +33 -0
  14. package/{src/index.ts → dist/index.d.ts} +1 -1
  15. package/dist/index.js +8458 -0
  16. package/dist/profile/client.d.ts +116 -0
  17. package/dist/profile/manager.d.ts +47 -0
  18. package/dist/profile/types.d.ts +61 -0
  19. package/dist/project/closure.d.ts +25 -0
  20. package/{src/project/index.ts → dist/project/index.d.ts} +1 -0
  21. package/dist/project/init.d.ts +27 -0
  22. package/dist/project/loader.d.ts +105 -0
  23. package/dist/project/manifest.d.ts +44 -0
  24. package/dist/project/types.d.ts +131 -0
  25. package/dist/registry/registry.d.ts +24 -0
  26. package/dist/registry/types.d.ts +117 -0
  27. package/dist/runtime/clock.d.ts +21 -0
  28. package/dist/runtime/context.d.ts +75 -0
  29. package/dist/runtime/env.d.ts +54 -0
  30. package/dist/runtime/events.d.ts +61 -0
  31. package/dist/runtime/execution-manager.d.ts +48 -0
  32. package/{src/runtime/index.ts → dist/runtime/index.d.ts} +0 -1
  33. package/dist/runtime/module-loader.d.ts +42 -0
  34. package/dist/runtime/process.d.ts +11 -0
  35. package/dist/runtime/runner.d.ts +186 -0
  36. package/dist/runtime/standalone.d.ts +40 -0
  37. package/dist/schema/validator.d.ts +28 -0
  38. package/dist/server/body.d.ts +40 -0
  39. package/{src/server/index.ts → dist/server/index.d.ts} +0 -1
  40. package/dist/server/routes/actions.d.ts +5 -0
  41. package/dist/server/routes/common.d.ts +36 -0
  42. package/dist/server/routes/config.d.ts +5 -0
  43. package/dist/server/routes/doctor.d.ts +5 -0
  44. package/dist/server/routes/health.d.ts +5 -0
  45. package/dist/server/routes/info.d.ts +5 -0
  46. package/dist/server/routes/playbooks.d.ts +5 -0
  47. package/dist/server/routes/runs.d.ts +5 -0
  48. package/dist/server/routes/state.d.ts +5 -0
  49. package/dist/server/runtime-registry.d.ts +78 -0
  50. package/dist/server/security.d.ts +37 -0
  51. package/dist/server/server.d.ts +15 -0
  52. package/dist/server/types.d.ts +59 -0
  53. package/dist/storage/driver.d.ts +15 -0
  54. package/dist/storage/index.d.ts +44 -0
  55. package/{src/storage/mask.ts → dist/storage/mask.d.ts} +5 -20
  56. package/dist/storage/sqlite.d.ts +66 -0
  57. package/dist/storage/types.d.ts +121 -0
  58. package/dist/utils/index.d.ts +29 -0
  59. package/dist/version.d.ts +4 -0
  60. package/package.json +11 -10
  61. package/src/build/templates.ts +0 -59
  62. package/src/catalog/action-index.ts +0 -92
  63. package/src/catalog/action-resolver.ts +0 -120
  64. package/src/catalog/location-registry.ts +0 -134
  65. package/src/catalog/package-catalog.ts +0 -87
  66. package/src/catalog/types.ts +0 -75
  67. package/src/doctor/doctor.ts +0 -419
  68. package/src/doctor/types.ts +0 -25
  69. package/src/execution/index.ts +0 -2
  70. package/src/execution/service.ts +0 -322
  71. package/src/execution/types.ts +0 -73
  72. package/src/export/templates.ts +0 -590
  73. package/src/filter/intent.ts +0 -154
  74. package/src/profile/client.ts +0 -552
  75. package/src/profile/manager.ts +0 -341
  76. package/src/profile/types.ts +0 -71
  77. package/src/project/init.ts +0 -242
  78. package/src/project/loader.ts +0 -479
  79. package/src/project/manifest.ts +0 -299
  80. package/src/project/types.ts +0 -141
  81. package/src/registry/registry.ts +0 -940
  82. package/src/registry/types.ts +0 -127
  83. package/src/runtime/clock.ts +0 -41
  84. package/src/runtime/context.ts +0 -259
  85. package/src/runtime/env.ts +0 -172
  86. package/src/runtime/events.ts +0 -142
  87. package/src/runtime/execution-manager.ts +0 -74
  88. package/src/runtime/module-loader.ts +0 -67
  89. package/src/runtime/process.ts +0 -245
  90. package/src/runtime/runner.ts +0 -527
  91. package/src/runtime/standalone.ts +0 -429
  92. package/src/schema/validator.ts +0 -61
  93. package/src/server/body.ts +0 -112
  94. package/src/server/routes/actions.ts +0 -262
  95. package/src/server/routes/common.ts +0 -126
  96. package/src/server/routes/config.ts +0 -147
  97. package/src/server/routes/doctor.ts +0 -29
  98. package/src/server/routes/health.ts +0 -40
  99. package/src/server/routes/info.ts +0 -221
  100. package/src/server/routes/playbooks.ts +0 -109
  101. package/src/server/routes/runs.ts +0 -301
  102. package/src/server/routes/state.ts +0 -133
  103. package/src/server/runtime-registry.ts +0 -115
  104. package/src/server/security.ts +0 -115
  105. package/src/server/server.ts +0 -251
  106. package/src/server/types.ts +0 -57
  107. package/src/storage/driver.ts +0 -126
  108. package/src/storage/index.ts +0 -81
  109. package/src/storage/sqlite.ts +0 -714
  110. package/src/storage/types.ts +0 -139
  111. package/src/utils/index.ts +0 -62
  112. /package/{src/build/index.ts → dist/build/index.d.ts} +0 -0
  113. /package/{src/catalog/index.ts → dist/catalog/index.d.ts} +0 -0
  114. /package/{src/doctor/index.ts → dist/doctor/index.d.ts} +0 -0
  115. /package/{src/export/index.ts → dist/export/index.d.ts} +0 -0
  116. /package/{src/filter/index.ts → dist/filter/index.d.ts} +0 -0
  117. /package/{src/profile/index.ts → dist/profile/index.d.ts} +0 -0
  118. /package/{src/registry/index.ts → dist/registry/index.d.ts} +0 -0
  119. /package/{src/server/routes/index.ts → dist/server/routes/index.d.ts} +0 -0
@@ -1,940 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
2
- import { basename, dirname, join, resolve } from "node:path";
3
- import { findProjectRoot, loadActions, loadPlaybooks, loadProjectConfig } from "../project/loader";
4
- import { loadManifest } from "../project/manifest";
5
- import { getActionDockHome, getPackageSlug } from "../utils";
6
- import type {
7
- GlobalRegistryData,
8
- LinkedPackageEntry,
9
- LinkedWorkspaceEntry,
10
- LinkResult,
11
- PruneResult,
12
- RegistryStatusReport,
13
- RegistryTreeItem,
14
- ResolvedActionProject,
15
- ResolvedPlaybookProject,
16
- UnlinkResult,
17
- } from "./types";
18
-
19
- const IGNORED_SCAN_DIRS = new Set([
20
- "node_modules",
21
- ".git",
22
- "dist",
23
- "build",
24
- ".gemini",
25
- ".actiondock",
26
- ".claude",
27
- ".idea",
28
- ".vscode",
29
- ]);
30
-
31
- /**
32
- * 递归扫描包含 actiondock.json 的子项目根目录
33
- */
34
- export function discoverProjects(dir: string, maxDepth: number = 3): string[] {
35
- const results: string[] = [];
36
- const resolvedDir = resolve(dir);
37
-
38
- function walk(currentDir: string, currentDepth: number) {
39
- if (currentDepth > maxDepth) return;
40
- try {
41
- const entries = readdirSync(currentDir, { withFileTypes: true });
42
- const hasActiondock = entries.some((e) => e.isFile() && e.name === "actiondock.json");
43
-
44
- if (hasActiondock && currentDir !== resolvedDir) {
45
- results.push(currentDir);
46
- return; // 不再向项目内部子目录递归
47
- }
48
-
49
- for (const entry of entries) {
50
- if (entry.isDirectory() && !IGNORED_SCAN_DIRS.has(entry.name)) {
51
- walk(join(currentDir, entry.name), currentDepth + 1);
52
- }
53
- }
54
- } catch {
55
- // 忽略无法读取的目录
56
- }
57
- }
58
-
59
- walk(resolvedDir, 1);
60
- return results;
61
- }
62
-
63
- export function getRegistryFilePath(customHome?: string): string {
64
- const baseDir = getActionDockHome(customHome);
65
- return join(baseDir, ".actiondock", "registry.json");
66
- }
67
-
68
- export function loadRegistry(customHome?: string): GlobalRegistryData {
69
- const filePath = getRegistryFilePath(customHome);
70
- if (!existsSync(filePath)) {
71
- return { version: "2.0.0", packages: {}, workspaces: {} };
72
- }
73
- try {
74
- const raw = readFileSync(filePath, "utf-8");
75
- const parsed = JSON.parse(raw);
76
- if (!parsed || typeof parsed !== "object" || !parsed.packages) {
77
- return { version: "2.0.0", packages: {}, workspaces: {} };
78
- }
79
- return {
80
- version: "2.0.0",
81
- packages: parsed.packages || {},
82
- workspaces: parsed.workspaces || {},
83
- };
84
- } catch {
85
- return { version: "2.0.0", packages: {}, workspaces: {} };
86
- }
87
- }
88
-
89
- export function saveRegistry(data: GlobalRegistryData, customHome?: string): void {
90
- const filePath = getRegistryFilePath(customHome);
91
- mkdirSync(dirname(filePath), { recursive: true });
92
- writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
93
- }
94
-
95
- export function linkPackage(
96
- targetPath: string = process.cwd(),
97
- customHome?: string,
98
- options?: { recursive?: boolean }
99
- ): LinkResult {
100
- const absPath = resolve(targetPath);
101
- const directHasConfig = existsSync(join(absPath, "actiondock.json"));
102
-
103
- // 1. 如果当前目录直接包含 actiondock.json 且未强制递归,按单包链接
104
- if (directHasConfig && !options?.recursive) {
105
- const config = loadProjectConfig(absPath);
106
- const registry = loadRegistry(customHome);
107
-
108
- const entry: LinkedPackageEntry = {
109
- id: config.id,
110
- name: config.name,
111
- version: config.version,
112
- path: absPath,
113
- linkedAt: new Date().toISOString(),
114
- };
115
-
116
- registry.packages[config.id] = entry;
117
- saveRegistry(registry, customHome);
118
-
119
- return {
120
- id: config.id,
121
- name: config.name,
122
- version: config.version,
123
- path: absPath,
124
- linkedAt: entry.linkedAt,
125
- isWorkspace: false,
126
- entries: [entry],
127
- };
128
- }
129
-
130
- // 2. 尝试扫描子目录发现多个 ActionDock 子项目(Workspace 模式)
131
- const discoveredRoots = discoverProjects(absPath);
132
-
133
- if (discoveredRoots.length > 0) {
134
- const registry = loadRegistry(customHome);
135
- const now = new Date().toISOString();
136
- const wsEntry: LinkedWorkspaceEntry = {
137
- path: absPath,
138
- linkedAt: now,
139
- };
140
-
141
- registry.workspaces = registry.workspaces || {};
142
- registry.workspaces[absPath] = wsEntry;
143
-
144
- const linkedEntries: LinkedPackageEntry[] = [];
145
-
146
- for (const root of discoveredRoots) {
147
- try {
148
- const config = loadProjectConfig(root);
149
- const entry: LinkedPackageEntry = {
150
- id: config.id,
151
- name: config.name,
152
- version: config.version,
153
- path: root,
154
- linkedAt: now,
155
- workspaceRoot: absPath,
156
- };
157
- registry.packages[config.id] = entry;
158
- linkedEntries.push(entry);
159
- } catch {
160
- // 忽略异常项目
161
- }
162
- }
163
-
164
- saveRegistry(registry, customHome);
165
-
166
- const wsName = basename(absPath);
167
- return {
168
- id: wsName,
169
- name: wsName,
170
- version: "2.0.0",
171
- path: absPath,
172
- linkedAt: now,
173
- isWorkspace: true,
174
- entries: linkedEntries,
175
- workspace: wsEntry,
176
- };
177
- }
178
-
179
- // 3. 回退检查:如果在子目录执行(例如在 package 的 actions/ 目录下),查找父级项目根目录
180
- const parentRoot = findProjectRoot(absPath);
181
- if (parentRoot) {
182
- const config = loadProjectConfig(parentRoot);
183
- const registry = loadRegistry(customHome);
184
-
185
- const entry: LinkedPackageEntry = {
186
- id: config.id,
187
- name: config.name,
188
- version: config.version,
189
- path: parentRoot,
190
- linkedAt: new Date().toISOString(),
191
- };
192
-
193
- registry.packages[config.id] = entry;
194
- saveRegistry(registry, customHome);
195
-
196
- return {
197
- id: config.id,
198
- name: config.name,
199
- version: config.version,
200
- path: parentRoot,
201
- linkedAt: entry.linkedAt,
202
- isWorkspace: false,
203
- entries: [entry],
204
- };
205
- }
206
-
207
- throw new Error(`Cannot link: actiondock.json not found in '${absPath}' or its subdirectories`);
208
- }
209
-
210
- export function unlinkPackage(
211
- identifier: string = process.cwd(),
212
- customHome?: string
213
- ): UnlinkResult | null {
214
- const registry = loadRegistry(customHome);
215
- const absPath = resolve(identifier);
216
-
217
- // 1. 检查是否匹配 Workspace 绝对路径
218
- if (registry.workspaces && registry.workspaces[absPath]) {
219
- const removedWs = registry.workspaces[absPath];
220
- delete registry.workspaces[absPath];
221
-
222
- let removedCount = 0;
223
- for (const [id, entry] of Object.entries(registry.packages)) {
224
- if (entry.workspaceRoot === absPath || entry.path.startsWith(absPath)) {
225
- delete registry.packages[id];
226
- removedCount++;
227
- }
228
- }
229
- saveRegistry(registry, customHome);
230
- return {
231
- type: "workspace",
232
- id: basename(absPath),
233
- path: absPath,
234
- packagesCount: removedCount,
235
- removedWorkspace: removedWs,
236
- };
237
- }
238
-
239
- // 2. 检查是否匹配 Workspace 目录别名
240
- if (registry.workspaces) {
241
- for (const [wsPath, wsEntry] of Object.entries(registry.workspaces)) {
242
- if (basename(wsPath) === identifier) {
243
- delete registry.workspaces[wsPath];
244
- let removedCount = 0;
245
- for (const [id, entry] of Object.entries(registry.packages)) {
246
- if (entry.workspaceRoot === wsPath || entry.path.startsWith(wsPath)) {
247
- delete registry.packages[id];
248
- removedCount++;
249
- }
250
- }
251
- saveRegistry(registry, customHome);
252
- return {
253
- type: "workspace",
254
- id: basename(wsPath),
255
- path: wsPath,
256
- packagesCount: removedCount,
257
- removedWorkspace: wsEntry,
258
- };
259
- }
260
- }
261
- }
262
-
263
- // 3. 检查是否直接匹配 Package ID
264
- let targetKey: string | undefined;
265
- if (registry.packages[identifier]) {
266
- targetKey = identifier;
267
- } else {
268
- // 匹配路径或短 slug
269
- for (const [id, entry] of Object.entries(registry.packages)) {
270
- if (
271
- entry.path === absPath ||
272
- entry.id === identifier ||
273
- getPackageSlug(entry.id) === identifier
274
- ) {
275
- targetKey = id;
276
- break;
277
- }
278
- }
279
- }
280
-
281
- if (!targetKey) {
282
- return null;
283
- }
284
-
285
- const removed = registry.packages[targetKey];
286
- delete registry.packages[targetKey];
287
- saveRegistry(registry, customHome);
288
- return {
289
- type: "package",
290
- id: removed.id,
291
- path: removed.path,
292
- packagesCount: 1,
293
- removedPackage: removed,
294
- };
295
- }
296
-
297
- export function listLinkedPackages(customHome?: string): LinkedPackageEntry[] {
298
- const registry = loadRegistry(customHome);
299
- const result: Record<string, LinkedPackageEntry> = { ...registry.packages };
300
-
301
- // 动态扫描已挂载的 Workspace 目录,确保新拉取/新建的子包即时感知
302
- if (registry.workspaces) {
303
- for (const ws of Object.values(registry.workspaces)) {
304
- if (!existsSync(ws.path)) continue;
305
- const discovered = discoverProjects(ws.path);
306
- for (const root of discovered) {
307
- try {
308
- const config = loadProjectConfig(root);
309
- if (!result[config.id] || result[config.id].workspaceRoot === ws.path) {
310
- result[config.id] = {
311
- id: config.id,
312
- name: config.name,
313
- version: config.version,
314
- path: root,
315
- linkedAt: ws.linkedAt,
316
- workspaceRoot: ws.path,
317
- };
318
- }
319
- } catch {
320
- // 忽略异常项目
321
- }
322
- }
323
- }
324
- }
325
-
326
- return Object.values(result);
327
- }
328
-
329
- export function listLinkedWorkspaces(customHome?: string): LinkedWorkspaceEntry[] {
330
- const registry = loadRegistry(customHome);
331
- return Object.values(registry.workspaces || {});
332
- }
333
-
334
- async function projectHasAction(
335
- projectRoot: string,
336
- actionsDir: string | undefined,
337
- actionId: string
338
- ): Promise<boolean> {
339
- const manifest = loadManifest(projectRoot);
340
- if (manifest?.actions && actionId in manifest.actions) {
341
- return true;
342
- }
343
- try {
344
- const actions = await loadActions(projectRoot, actionsDir, { autoInstall: false });
345
- return actions.has(actionId);
346
- } catch {
347
- return false;
348
- }
349
- }
350
-
351
- function projectHasActionSync(
352
- projectRoot: string,
353
- actionsDir: string | undefined,
354
- actionId: string
355
- ): boolean {
356
- const manifest = loadManifest(projectRoot);
357
- if (manifest?.actions && actionId in manifest.actions) {
358
- return true;
359
- }
360
- const dir = join(projectRoot, actionsDir || "actions");
361
- if (!existsSync(dir)) {
362
- return false;
363
- }
364
- if (actionId.includes("..") || actionId.startsWith("/") || actionId.startsWith("\\")) {
365
- return false;
366
- }
367
- if (existsSync(join(dir, `${actionId}.ts`)) || existsSync(join(dir, `${actionId}.js`))) {
368
- return true;
369
- }
370
- const relFile = actionId.replace(/\./g, "/");
371
- if (existsSync(join(dir, `${relFile}.ts`)) || existsSync(join(dir, `${relFile}.js`))) {
372
- return true;
373
- }
374
- return false;
375
- }
376
-
377
- export function resolveActionProjectSync(
378
- actionIdentifier: string,
379
- cwd: string = process.cwd(),
380
- customHome?: string
381
- ): ResolvedActionProject {
382
- // 1. Check current directory / parent project
383
- const currentRoot = findProjectRoot(cwd);
384
- if (currentRoot) {
385
- try {
386
- const config = loadProjectConfig(currentRoot);
387
- if (projectHasActionSync(currentRoot, config.actionsDir, actionIdentifier)) {
388
- return {
389
- projectRoot: currentRoot,
390
- packageId: config.id,
391
- actionId: actionIdentifier,
392
- };
393
- }
394
- } catch {
395
- // Ignore and proceed to registry lookup
396
- }
397
- }
398
-
399
- // 2. Check if scoped format: <package-id>/<action-id> or <package-id>:<action-id>
400
- let targetPackage: string | undefined;
401
- let pureActionId = actionIdentifier;
402
-
403
- if (actionIdentifier.includes("/")) {
404
- const slashIdx = actionIdentifier.lastIndexOf("/");
405
- targetPackage = actionIdentifier.slice(0, slashIdx);
406
- pureActionId = actionIdentifier.slice(slashIdx + 1);
407
- } else if (actionIdentifier.includes(":")) {
408
- const colonIdx = actionIdentifier.lastIndexOf(":");
409
- targetPackage = actionIdentifier.slice(0, colonIdx);
410
- pureActionId = actionIdentifier.slice(colonIdx + 1);
411
- }
412
-
413
- const linkedList = listLinkedPackages(customHome);
414
-
415
- if (targetPackage) {
416
- let targetRoot: string | undefined;
417
- let targetPkgId = targetPackage;
418
-
419
- const pkg = linkedList.find(
420
- (p) => p.id === targetPackage || getPackageSlug(p.id) === targetPackage
421
- );
422
- if (pkg && existsSync(pkg.path)) {
423
- targetRoot = pkg.path;
424
- targetPkgId = pkg.id;
425
- } else if (currentRoot) {
426
- try {
427
- const config = loadProjectConfig(currentRoot);
428
- if (config.id === targetPackage || getPackageSlug(config.id) === targetPackage) {
429
- targetRoot = currentRoot;
430
- targetPkgId = config.id;
431
- }
432
- } catch {
433
- // Ignore
434
- }
435
- }
436
-
437
- if (!targetRoot || !existsSync(targetRoot)) {
438
- throw new Error(
439
- `Linked package '${targetPackage}' not found or path no longer exists (${pkg?.path || "unregistered"}). Run 'ad link' in the package directory.`
440
- );
441
- }
442
-
443
- const config = loadProjectConfig(targetRoot);
444
- if (!projectHasActionSync(targetRoot, config.actionsDir, pureActionId)) {
445
- throw new Error(`Action '${pureActionId}' not found in package '${targetPkgId}' (${targetRoot})`);
446
- }
447
-
448
- return {
449
- projectRoot: targetRoot,
450
- packageId: targetPkgId,
451
- actionId: pureActionId,
452
- };
453
- }
454
-
455
- // 3. Search across all linked packages
456
- const matches: Array<{ entry: LinkedPackageEntry; actionId: string }> = [];
457
-
458
- for (const pkg of linkedList) {
459
- if (!existsSync(pkg.path)) continue;
460
- try {
461
- const config = loadProjectConfig(pkg.path);
462
- if (projectHasActionSync(pkg.path, config.actionsDir, actionIdentifier)) {
463
- matches.push({ entry: pkg, actionId: actionIdentifier });
464
- }
465
- } catch {
466
- // Ignore invalid linked package
467
- }
468
- }
469
-
470
- if (matches.length === 1) {
471
- return {
472
- projectRoot: matches[0].entry.path,
473
- packageId: matches[0].entry.id,
474
- actionId: matches[0].actionId,
475
- };
476
- }
477
-
478
- if (matches.length > 1) {
479
- const pkgList = matches.map((m) => `'${m.entry.id}'`).join(", ");
480
- throw new Error(
481
- `Action '${actionIdentifier}' is provided by multiple linked packages: ${pkgList}. Please specify using '<package-id>/${actionIdentifier}'.`
482
- );
483
- }
484
-
485
- if (currentRoot) {
486
- throw new Error(`Action '${actionIdentifier}' not found in current project or any linked packages`);
487
- } else {
488
- throw new Error(
489
- `Action '${actionIdentifier}' not found. You are not in an ActionDock project, and no linked package provides '${actionIdentifier}'. Use 'ad link' to register your package.`
490
- );
491
- }
492
- }
493
-
494
- export async function resolveActionProject(
495
- actionIdentifier: string,
496
- cwd: string = process.cwd(),
497
- customHome?: string
498
- ): Promise<ResolvedActionProject> {
499
- // 1. Check current directory / parent project
500
- const currentRoot = findProjectRoot(cwd);
501
- if (currentRoot) {
502
- try {
503
- const config = loadProjectConfig(currentRoot);
504
- if (await projectHasAction(currentRoot, config.actionsDir, actionIdentifier)) {
505
- return {
506
- projectRoot: currentRoot,
507
- packageId: config.id,
508
- actionId: actionIdentifier,
509
- };
510
- }
511
- } catch {
512
- // Ignore and proceed to registry lookup
513
- }
514
- }
515
-
516
- // 2. Check if scoped format: <package-id>/<action-id> or <package-id>:<action-id>
517
- let targetPackage: string | undefined;
518
- let pureActionId = actionIdentifier;
519
-
520
- if (actionIdentifier.includes("/")) {
521
- const slashIdx = actionIdentifier.lastIndexOf("/");
522
- targetPackage = actionIdentifier.slice(0, slashIdx);
523
- pureActionId = actionIdentifier.slice(slashIdx + 1);
524
- } else if (actionIdentifier.includes(":")) {
525
- const colonIdx = actionIdentifier.lastIndexOf(":");
526
- targetPackage = actionIdentifier.slice(0, colonIdx);
527
- pureActionId = actionIdentifier.slice(colonIdx + 1);
528
- }
529
-
530
- const linkedList = listLinkedPackages(customHome);
531
-
532
- if (targetPackage) {
533
- let targetRoot: string | undefined;
534
- let targetPkgId = targetPackage;
535
-
536
- const pkg = linkedList.find(
537
- (p) => p.id === targetPackage || getPackageSlug(p.id) === targetPackage
538
- );
539
- if (pkg && existsSync(pkg.path)) {
540
- targetRoot = pkg.path;
541
- targetPkgId = pkg.id;
542
- } else if (currentRoot) {
543
- try {
544
- const config = loadProjectConfig(currentRoot);
545
- if (config.id === targetPackage || getPackageSlug(config.id) === targetPackage) {
546
- targetRoot = currentRoot;
547
- targetPkgId = config.id;
548
- }
549
- } catch {
550
- // Ignore
551
- }
552
- }
553
-
554
- if (!targetRoot || !existsSync(targetRoot)) {
555
- throw new Error(
556
- `Linked package '${targetPackage}' not found or path no longer exists (${pkg?.path || "unregistered"}). Run 'ad link' in the package directory.`
557
- );
558
- }
559
-
560
- const config = loadProjectConfig(targetRoot);
561
- if (!(await projectHasAction(targetRoot, config.actionsDir, pureActionId))) {
562
- throw new Error(`Action '${pureActionId}' not found in package '${targetPkgId}' (${targetRoot})`);
563
- }
564
-
565
- return {
566
- projectRoot: targetRoot,
567
- packageId: targetPkgId,
568
- actionId: pureActionId,
569
- };
570
- }
571
-
572
- // 3. Search across all linked packages
573
- const matches: Array<{ entry: LinkedPackageEntry; actionId: string }> = [];
574
-
575
- for (const pkg of linkedList) {
576
- if (!existsSync(pkg.path)) continue;
577
- try {
578
- const config = loadProjectConfig(pkg.path);
579
- if (await projectHasAction(pkg.path, config.actionsDir, actionIdentifier)) {
580
- matches.push({ entry: pkg, actionId: actionIdentifier });
581
- }
582
- } catch {
583
- // Ignore invalid linked package
584
- }
585
- }
586
-
587
- if (matches.length === 1) {
588
- return {
589
- projectRoot: matches[0].entry.path,
590
- packageId: matches[0].entry.id,
591
- actionId: matches[0].actionId,
592
- };
593
- }
594
-
595
- if (matches.length > 1) {
596
- const pkgList = matches.map((m) => `'${m.entry.id}'`).join(", ");
597
- throw new Error(
598
- `Action '${actionIdentifier}' is provided by multiple linked packages: ${pkgList}. Please specify using '<package-id>/${actionIdentifier}'.`
599
- );
600
- }
601
-
602
- if (currentRoot) {
603
- throw new Error(`Action '${actionIdentifier}' not found in current project or any linked packages`);
604
- } else {
605
- throw new Error(
606
- `Action '${actionIdentifier}' not found. You are not in an ActionDock project, and no linked package provides '${actionIdentifier}'. Use 'ad link' to register your package.`
607
- );
608
- }
609
- }
610
-
611
- export function resolvePackageRoot(
612
- packageIdOrPath?: string,
613
- cwd?: string,
614
- customHome?: string
615
- ): string | null {
616
- if (!packageIdOrPath) {
617
- return findProjectRoot(cwd);
618
- }
619
-
620
- const baseDir = cwd || process.cwd();
621
- const resolvedPath = resolve(baseDir, packageIdOrPath);
622
-
623
- // 1. Check if packageIdOrPath is an existing directory or file path on disk
624
- if (existsSync(resolvedPath)) {
625
- try {
626
- const stat = statSync(resolvedPath);
627
- const targetDir = stat.isDirectory() ? resolvedPath : dirname(resolvedPath);
628
- if (existsSync(join(targetDir, "actiondock.json"))) {
629
- return targetDir;
630
- }
631
- const parentRoot = findProjectRoot(targetDir);
632
- if (parentRoot) {
633
- return parentRoot;
634
- }
635
- } catch {
636
- // ignore
637
- }
638
- }
639
-
640
- // If it was explicitly a path (starts with . or / or ~ or contains / or \), and did not resolve above:
641
- const isExplicitPath =
642
- packageIdOrPath.startsWith(".") ||
643
- packageIdOrPath.startsWith("/") ||
644
- packageIdOrPath.startsWith("~") ||
645
- packageIdOrPath.includes("/") ||
646
- packageIdOrPath.includes("\\");
647
-
648
- if (isExplicitPath) {
649
- // An explicit path that does not exist or is not an ActionDock project must fail
650
- return null;
651
- }
652
-
653
- // 2. Check linked packages in registry
654
- const linkedList = listLinkedPackages(customHome);
655
- const found = linkedList.find(
656
- (p) =>
657
- p.id === packageIdOrPath ||
658
- getPackageSlug(p.id) === packageIdOrPath ||
659
- p.path === resolvedPath
660
- );
661
- if (found) {
662
- return found.path;
663
- }
664
-
665
- // 3. Check current project (from cwd)
666
- const currentRoot = findProjectRoot(cwd);
667
- if (currentRoot) {
668
- try {
669
- const config = loadProjectConfig(currentRoot);
670
- if (config.id === packageIdOrPath || getPackageSlug(config.id) === packageIdOrPath) {
671
- return currentRoot;
672
- }
673
- } catch {
674
- // ignore broken config
675
- }
676
- }
677
-
678
- return null;
679
- }
680
-
681
- export function resolvePlaybookProject(
682
- playbookIdentifier: string,
683
- cwd: string = process.cwd(),
684
- customHome?: string
685
- ): ResolvedPlaybookProject {
686
- // 1. Check current directory / parent project
687
- const currentRoot = findProjectRoot(cwd);
688
- if (currentRoot) {
689
- try {
690
- const config = loadProjectConfig(currentRoot);
691
- const playbooks = loadPlaybooks(currentRoot, config.playbooksDir);
692
- if (playbooks.has(playbookIdentifier)) {
693
- return {
694
- projectRoot: currentRoot,
695
- packageId: config.id,
696
- playbookId: playbookIdentifier,
697
- playbook: playbooks.get(playbookIdentifier)!,
698
- };
699
- }
700
- } catch {
701
- // Ignore and proceed to registry lookup
702
- }
703
- }
704
-
705
- // 2. Check if scoped format: <package-id>/<playbook-id> or <package-id>:<playbook-id>
706
- let targetPackage: string | undefined;
707
- let purePlaybookId = playbookIdentifier;
708
-
709
- if (playbookIdentifier.includes("/")) {
710
- const slashIdx = playbookIdentifier.indexOf("/");
711
- targetPackage = playbookIdentifier.slice(0, slashIdx);
712
- purePlaybookId = playbookIdentifier.slice(slashIdx + 1);
713
- } else if (playbookIdentifier.includes(":")) {
714
- const colonIdx = playbookIdentifier.indexOf(":");
715
- targetPackage = playbookIdentifier.slice(0, colonIdx);
716
- purePlaybookId = playbookIdentifier.slice(colonIdx + 1);
717
- }
718
-
719
- const linkedList = listLinkedPackages(customHome);
720
-
721
- if (targetPackage) {
722
- let targetRoot: string | undefined;
723
- let targetPkgId = targetPackage;
724
-
725
- const pkg = linkedList.find(
726
- (p) => p.id === targetPackage || getPackageSlug(p.id) === targetPackage
727
- );
728
- if (pkg && existsSync(pkg.path)) {
729
- targetRoot = pkg.path;
730
- targetPkgId = pkg.id;
731
- } else if (currentRoot) {
732
- try {
733
- const config = loadProjectConfig(currentRoot);
734
- if (config.id === targetPackage || getPackageSlug(config.id) === targetPackage) {
735
- targetRoot = currentRoot;
736
- targetPkgId = config.id;
737
- }
738
- } catch {
739
- // Ignore
740
- }
741
- }
742
-
743
- if (!targetRoot || !existsSync(targetRoot)) {
744
- throw new Error(
745
- `Linked package '${targetPackage}' not found or path no longer exists (${pkg?.path || "unregistered"}). Run 'ad link' in the package directory.`
746
- );
747
- }
748
-
749
- const config = loadProjectConfig(targetRoot);
750
- const playbooks = loadPlaybooks(targetRoot, config.playbooksDir);
751
- const pb = playbooks.get(purePlaybookId);
752
- if (!pb) {
753
- throw new Error(`Playbook '${purePlaybookId}' not found in package '${targetPkgId}' (${targetRoot})`);
754
- }
755
-
756
- return {
757
- projectRoot: targetRoot,
758
- packageId: targetPkgId,
759
- playbookId: purePlaybookId,
760
- playbook: pb,
761
- };
762
- }
763
-
764
- // 3. Search across all linked packages
765
- const matches: Array<{ entry: LinkedPackageEntry; playbookId: string; playbook: import("../project/types").PlaybookDefinition }> = [];
766
-
767
- for (const pkg of linkedList) {
768
- if (!existsSync(pkg.path)) continue;
769
- try {
770
- const config = loadProjectConfig(pkg.path);
771
- const playbooks = loadPlaybooks(pkg.path, config.playbooksDir);
772
- if (playbooks.has(playbookIdentifier)) {
773
- matches.push({
774
- entry: pkg,
775
- playbookId: playbookIdentifier,
776
- playbook: playbooks.get(playbookIdentifier)!,
777
- });
778
- }
779
- } catch {
780
- // Ignore invalid linked package
781
- }
782
- }
783
-
784
- if (matches.length === 1) {
785
- return {
786
- projectRoot: matches[0].entry.path,
787
- packageId: matches[0].entry.id,
788
- playbookId: matches[0].playbookId,
789
- playbook: matches[0].playbook,
790
- };
791
- }
792
-
793
- if (matches.length > 1) {
794
- const pkgList = matches.map((m) => `'${m.entry.id}'`).join(", ");
795
- throw new Error(
796
- `Playbook '${playbookIdentifier}' is provided by multiple linked packages: ${pkgList}. Please specify using '<package-id>/${playbookIdentifier}'.`
797
- );
798
- }
799
-
800
- if (currentRoot) {
801
- throw new Error(`Playbook '${playbookIdentifier}' not found in current project or any linked packages`);
802
- } else {
803
- throw new Error(
804
- `Playbook '${playbookIdentifier}' not found. You are not in an ActionDock project, and no linked package provides '${playbookIdentifier}'. Use 'ad link' to register your package.`
805
- );
806
- }
807
- }
808
-
809
- export function getRegistryStatus(customHome?: string): RegistryStatusReport {
810
- const registry = loadRegistry(customHome);
811
- const workspaces: RegistryTreeItem[] = [];
812
- const packages: RegistryTreeItem[] = [];
813
- let staleCount = 0;
814
- const seenPackageIds = new Set<string>();
815
-
816
- // 1. Process workspaces
817
- if (registry.workspaces) {
818
- for (const [wsPath, wsEntry] of Object.entries(registry.workspaces)) {
819
- const isWsActive = existsSync(wsPath);
820
- if (!isWsActive) {
821
- staleCount++;
822
- workspaces.push({
823
- type: "workspace",
824
- id: basename(wsPath),
825
- path: wsPath,
826
- status: "stale",
827
- packagesCount: 0,
828
- children: [],
829
- });
830
- continue;
831
- }
832
-
833
- const discoveredRoots = discoverProjects(wsPath);
834
- const children: NonNullable<RegistryTreeItem["children"]> = [];
835
-
836
- for (const root of discoveredRoots) {
837
- try {
838
- const config = loadProjectConfig(root);
839
- seenPackageIds.add(config.id);
840
- children.push({
841
- id: config.id,
842
- name: config.name,
843
- version: config.version,
844
- path: root,
845
- status: "active",
846
- });
847
- } catch {
848
- // ignore broken project
849
- }
850
- }
851
-
852
- workspaces.push({
853
- type: "workspace",
854
- id: basename(wsPath),
855
- path: wsPath,
856
- status: "active",
857
- packagesCount: children.length,
858
- children,
859
- });
860
- }
861
- }
862
-
863
- // 2. Process standalone packages (not part of an active workspace)
864
- for (const [pkgId, pkgEntry] of Object.entries(registry.packages)) {
865
- if (pkgEntry.workspaceRoot && registry.workspaces && registry.workspaces[pkgEntry.workspaceRoot]) {
866
- continue;
867
- }
868
- if (seenPackageIds.has(pkgId)) {
869
- continue;
870
- }
871
-
872
- const isPkgActive = existsSync(pkgEntry.path);
873
- if (!isPkgActive) {
874
- staleCount++;
875
- packages.push({
876
- type: "package",
877
- id: pkgEntry.id,
878
- name: pkgEntry.name,
879
- version: pkgEntry.version,
880
- path: pkgEntry.path,
881
- status: "stale",
882
- });
883
- } else {
884
- packages.push({
885
- type: "package",
886
- id: pkgEntry.id,
887
- name: pkgEntry.name,
888
- version: pkgEntry.version,
889
- path: pkgEntry.path,
890
- status: "active",
891
- });
892
- }
893
- }
894
-
895
- const totalPackagesCount =
896
- workspaces.reduce((acc, ws) => acc + (ws.children?.length || 0), 0) +
897
- packages.filter((p) => p.status === "active").length;
898
-
899
- return {
900
- workspaces,
901
- packages,
902
- staleCount,
903
- totalPackagesCount,
904
- };
905
- }
906
-
907
- export function pruneRegistry(customHome?: string): PruneResult {
908
- const registry = loadRegistry(customHome);
909
- const prunedWorkspaces: LinkedWorkspaceEntry[] = [];
910
- const prunedPackages: LinkedPackageEntry[] = [];
911
-
912
- // 1. Prune workspaces
913
- if (registry.workspaces) {
914
- for (const [wsPath, wsEntry] of Object.entries(registry.workspaces)) {
915
- if (!existsSync(wsPath)) {
916
- prunedWorkspaces.push(wsEntry);
917
- delete registry.workspaces[wsPath];
918
- }
919
- }
920
- }
921
-
922
- // 2. Prune packages
923
- for (const [pkgId, pkgEntry] of Object.entries(registry.packages)) {
924
- if (!existsSync(pkgEntry.path)) {
925
- prunedPackages.push(pkgEntry);
926
- delete registry.packages[pkgId];
927
- }
928
- }
929
-
930
- if (prunedWorkspaces.length > 0 || prunedPackages.length > 0) {
931
- saveRegistry(registry, customHome);
932
- }
933
-
934
- return {
935
- prunedPackages,
936
- prunedWorkspaces,
937
- };
938
- }
939
-
940
-