@actiondock/cli 2.0.11-beta.0 → 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 (54) hide show
  1. package/README.md +15 -2
  2. package/bin/ad.js +13 -2
  3. package/dist/commands/action/create.d.ts +2 -0
  4. package/dist/commands/action/index.d.ts +16 -0
  5. package/dist/commands/action/list.d.ts +5 -0
  6. package/dist/commands/action/run.d.ts +4 -0
  7. package/dist/commands/action/show.d.ts +5 -0
  8. package/dist/commands/action/sync.d.ts +2 -0
  9. package/dist/commands/action/validate.d.ts +5 -0
  10. package/dist/commands/build.d.ts +2 -0
  11. package/dist/commands/config.d.ts +2 -0
  12. package/dist/commands/doctor.d.ts +2 -0
  13. package/dist/commands/export.d.ts +2 -0
  14. package/dist/commands/index.d.ts +2 -0
  15. package/dist/commands/info.d.ts +2 -0
  16. package/dist/commands/init.d.ts +2 -0
  17. package/dist/commands/link.d.ts +2 -0
  18. package/dist/commands/mcp.d.ts +2 -0
  19. package/dist/commands/playbook.d.ts +6 -0
  20. package/dist/commands/profile.d.ts +2 -0
  21. package/dist/commands/runs.d.ts +2 -0
  22. package/dist/commands/serve.d.ts +2 -0
  23. package/dist/commands/state.d.ts +2 -0
  24. package/dist/commands/test.d.ts +2 -0
  25. package/dist/index.d.ts +8 -0
  26. package/dist/index.js +745 -65708
  27. package/{src/utils/bytes.ts → dist/utils/bytes.d.ts} +0 -1
  28. package/{src/utils/filter.ts → dist/utils/filter.d.ts} +0 -1
  29. package/package.json +20 -15
  30. package/src/commands/action/create.ts +0 -129
  31. package/src/commands/action/index.ts +0 -27
  32. package/src/commands/action/list.ts +0 -247
  33. package/src/commands/action/run.ts +0 -197
  34. package/src/commands/action/show.ts +0 -128
  35. package/src/commands/action/sync.ts +0 -76
  36. package/src/commands/action/validate.ts +0 -102
  37. package/src/commands/build.ts +0 -56
  38. package/src/commands/config.ts +0 -583
  39. package/src/commands/doctor.ts +0 -84
  40. package/src/commands/export.ts +0 -196
  41. package/src/commands/index.ts +0 -75
  42. package/src/commands/info.ts +0 -537
  43. package/src/commands/init.ts +0 -24
  44. package/src/commands/link.ts +0 -73
  45. package/src/commands/mcp.ts +0 -195
  46. package/src/commands/playbook.ts +0 -566
  47. package/src/commands/profile.ts +0 -296
  48. package/src/commands/runs.ts +0 -452
  49. package/src/commands/serve.ts +0 -132
  50. package/src/commands/state.ts +0 -590
  51. package/src/commands/test.ts +0 -48
  52. package/src/index.ts +0 -58
  53. /package/{src/commands/action.ts → dist/commands/action.d.ts} +0 -0
  54. /package/{src/utils/duration.ts → dist/utils/duration.d.ts} +0 -0
@@ -1,566 +0,0 @@
1
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
- import { dirname, resolve } from "node:path";
3
- import {
4
- fetchRemotePlaybooks,
5
- fetchRemotePlaybookShow,
6
- filterWithFallbackInfo,
7
- findProjectRoot,
8
- listLinkedPackages,
9
- loadActions,
10
- loadManifest,
11
- loadPlaybooks,
12
- loadProjectConfig,
13
- resolveActionProject,
14
- resolvePackageRoot,
15
- resolvePlaybookProject,
16
- resolveTarget,
17
- } from "@actiondock/core";
18
- import type { PlaybookDefinition } from "@actiondock/core";
19
- import { Command } from "commander";
20
- import {
21
- ArgumentError,
22
- CliError,
23
- ExecutionError,
24
- getEffectiveOptions,
25
- renderResult,
26
- } from "@actiondock/runtime-cli";
27
- import { resolveIntent } from "../utils/filter";
28
-
29
- export function registerPlaybookCommands(program: Command): void {
30
- const pbCmd = program
31
- .command("playbook")
32
- .description("Manage task Playbooks (Task SOPs for AI Agents)");
33
-
34
- // playbook list
35
- pbCmd
36
- .command("list [patterns...]")
37
- .description("List playbooks in current project or linked packages")
38
- .option("-i, --intent <pattern>", "Regex or fuzzy intent filter; falls back to full list when no match")
39
- .option("-P, --package <id>", "Target package ID or path")
40
- .option("-p, --profile <name>", "Query against a specific profile")
41
- .option("-s, --server <url>", "Remote server URL")
42
- .option("-t, --token <token>", "Auth token for remote server")
43
- .option("--fallback", "Enable fallback to full list when no items match intent")
44
- .option("--no-fallback", "Disable fallback to full list when no items match intent")
45
- .option("--json", "Output as JSON")
46
- .option("--envelope", "Wrap JSON output in standard envelope")
47
- .action(async (patterns, rawOptions, cmd) => {
48
- try {
49
- const options = getEffectiveOptions(rawOptions, cmd);
50
- const effectiveIntent = resolveIntent(options.intent, patterns);
51
- const isMachine = Boolean(options.json || options.envelope);
52
- const fallbackExplicit = options.fallback === true || (Array.isArray(process.argv) && process.argv.includes("--fallback"));
53
- const shouldFallback = isMachine ? fallbackExplicit : options.fallback !== false;
54
-
55
- const target = resolveTarget({
56
- profile: options.profile,
57
- server: options.server,
58
- token: options.token,
59
- });
60
-
61
- if (target.type === "remote") {
62
- let remotePbs = await fetchRemotePlaybooks(target.serverUrl!, target.token, {
63
- intent: effectiveIntent,
64
- package: options.package,
65
- });
66
-
67
- let isFallback = false;
68
- if (remotePbs.length === 0 && effectiveIntent && shouldFallback) {
69
- remotePbs = await fetchRemotePlaybooks(target.serverUrl!, target.token, {
70
- package: options.package,
71
- });
72
- isFallback = true;
73
- }
74
-
75
- if (isFallback && isMachine) {
76
- renderResult(
77
- { items: remotePbs, isFallback: true, matchedCount: 0 },
78
- { json: options.json, envelope: options.envelope }
79
- );
80
- return;
81
- }
82
-
83
- renderResult(remotePbs, {
84
- json: options.json,
85
- envelope: options.envelope,
86
- humanFormatter: () => {
87
- const lines = [
88
- `Playbooks on remote server ${target.serverUrl}${target.profileName ? ` (Profile: ${target.profileName})` : ""}:\n`,
89
- ];
90
- if (isFallback && effectiveIntent) {
91
- lines.push(`(No remote playbooks matched intent '${effectiveIntent}', showing all playbooks)\n`);
92
- }
93
- if (remotePbs.length === 0) {
94
- lines.push(" (No playbooks found)");
95
- } else {
96
- for (const p of remotePbs) {
97
- lines.push(` ${p.id.padEnd(24)} ${p.description} (Package: ${p.packageId})`);
98
- }
99
- }
100
- return lines.join("\n");
101
- },
102
- });
103
- return;
104
- }
105
-
106
- let targetRoot: string | null = null;
107
- if (options.package) {
108
- targetRoot = resolvePackageRoot(options.package);
109
- if (!targetRoot) {
110
- throw new ArgumentError(
111
- `Package '${options.package}' not found in linked packages or path`
112
- );
113
- }
114
- } else {
115
- targetRoot = findProjectRoot();
116
- }
117
-
118
- if (targetRoot) {
119
- const config = loadProjectConfig(targetRoot);
120
- const playbooks = loadPlaybooks(targetRoot, config.playbooksDir);
121
- const rawList = Array.from(playbooks.values()).map((p) => ({
122
- id: p.id,
123
- description: p.description || "",
124
- actions: p.actions || [],
125
- file: p.filePath,
126
- packageId: config.id,
127
- }));
128
-
129
- const filterRes = filterWithFallbackInfo(
130
- rawList,
131
- effectiveIntent,
132
- [(p) => p.id, (p) => p.description, (p) => p.actions, (p) => p.file],
133
- shouldFallback
134
- );
135
-
136
- if (filterRes.isFallback && isMachine) {
137
- renderResult(
138
- { items: filterRes.items, isFallback: true, matchedCount: 0 },
139
- { json: options.json, envelope: options.envelope }
140
- );
141
- return;
142
- }
143
-
144
- renderResult(filterRes.items, {
145
- json: options.json,
146
- envelope: options.envelope,
147
- humanFormatter: () => {
148
- const lines = [`Playbooks in ${config.id} (${targetRoot}):\n`];
149
- if (filterRes.isFallback && effectiveIntent) {
150
- lines.push(`(No playbooks matched intent '${effectiveIntent}', showing all playbooks)\n`);
151
- }
152
- for (const p of filterRes.items) {
153
- lines.push(` ${p.id.padEnd(24)} ${p.description}`);
154
- }
155
- return lines.join("\n");
156
- },
157
- });
158
- return;
159
- }
160
- // List playbooks across all linked packages
161
- const linkedList = listLinkedPackages();
162
- if (linkedList.length === 0) {
163
- renderResult([], {
164
- json: options.json,
165
- envelope: options.envelope,
166
- humanFormatter: () =>
167
- "No ActionDock project in current directory, and no packages linked.\nRun 'ad link' inside an Action package to register it.",
168
- });
169
- return;
170
- }
171
-
172
- const aggregated: Array<{
173
- packageId: string;
174
- packageName: string;
175
- path: string;
176
- playbooks: Array<{
177
- id: string;
178
- description: string;
179
- actions: string[];
180
- file: string;
181
- }>;
182
- }> = [];
183
-
184
- for (const pkg of linkedList) {
185
- if (!existsSync(pkg.path)) continue;
186
- try {
187
- const config = loadProjectConfig(pkg.path);
188
- const playbooks = loadPlaybooks(pkg.path, config.playbooksDir);
189
- const pkgPlaybooks = Array.from(playbooks.values()).map((p) => ({
190
- id: p.id,
191
- description: p.description || "",
192
- actions: p.actions || [],
193
- file: p.filePath,
194
- }));
195
-
196
- aggregated.push({
197
- packageId: pkg.id,
198
- packageName: pkg.name,
199
- path: pkg.path,
200
- playbooks: pkgPlaybooks,
201
- });
202
- } catch {
203
- // Ignore broken linked package
204
- }
205
- }
206
-
207
- let filteredPackages: typeof aggregated = [];
208
- let isFallback = false;
209
- if (!effectiveIntent) {
210
- filteredPackages = aggregated;
211
- } else {
212
- for (const pkg of aggregated) {
213
- const pkgMatches = filterWithFallbackInfo(
214
- [pkg],
215
- effectiveIntent,
216
- [(p) => p.packageId, (p) => p.packageName, (p) => p.path],
217
- false
218
- ).matchedCount > 0;
219
-
220
- if (pkgMatches) {
221
- filteredPackages.push(pkg);
222
- } else {
223
- const matchedPlaybooks = filterWithFallbackInfo(
224
- pkg.playbooks,
225
- effectiveIntent,
226
- [(p) => p.id, (p) => p.description, (p) => p.actions, (p) => p.file],
227
- false
228
- ).items;
229
-
230
- if (matchedPlaybooks.length > 0) {
231
- filteredPackages.push({
232
- ...pkg,
233
- playbooks: matchedPlaybooks,
234
- });
235
- }
236
- }
237
- }
238
-
239
- if (filteredPackages.length === 0 && shouldFallback) {
240
- filteredPackages = aggregated;
241
- isFallback = true;
242
- }
243
- }
244
-
245
- if (isFallback && isMachine) {
246
- renderResult(
247
- { packages: filteredPackages, isFallback: true, matchedCount: 0 },
248
- { json: options.json, envelope: options.envelope }
249
- );
250
- return;
251
- }
252
-
253
- renderResult(filteredPackages, {
254
- json: options.json,
255
- envelope: options.envelope,
256
- humanFormatter: () => {
257
- const lines = ["Playbooks in Linked Packages:\n"];
258
- for (const pkg of filteredPackages) {
259
- lines.push(`* Package: ${pkg.packageId} (${pkg.path})`);
260
- if (pkg.playbooks.length === 0) {
261
- lines.push(" (No playbooks)");
262
- } else {
263
- for (const p of pkg.playbooks) {
264
- lines.push(` - ${p.id.padEnd(26)} ${p.description}`);
265
- }
266
- }
267
- }
268
- return lines.join("\n");
269
- },
270
- });
271
- } catch (err: any) {
272
- if (err instanceof ArgumentError || err instanceof CliError) throw err;
273
- throw new ExecutionError(err.message);
274
- }
275
- });
276
-
277
- // playbook create / new
278
- pbCmd
279
- .command("create <id>")
280
- .alias("new")
281
- .description("Scaffold a new Playbook markdown file")
282
- .option("-d, --desc <description>", "Playbook description")
283
- .option("-a, --actions <actions...>", "Referenced action IDs")
284
- .option("-f, --file <filePath>", "Target file path relative to playbooks dir")
285
- .action((id, options) => {
286
- const root = findProjectRoot();
287
- if (!root) {
288
- throw new ExecutionError("Not in an ActionDock project (actiondock.json not found)");
289
- }
290
- try {
291
- const config = loadProjectConfig(root);
292
- const pbDir = resolve(root, config.playbooksDir || "playbooks");
293
- if (!existsSync(pbDir)) {
294
- mkdirSync(pbDir, { recursive: true });
295
- }
296
-
297
- const cleanName = id.replace(/[^a-zA-Z0-9-_]/g, "-");
298
- const targetRelFile = options.file || `${cleanName}.md`;
299
- const targetFullFile = resolve(pbDir, targetRelFile);
300
-
301
- if (existsSync(targetFullFile)) {
302
- throw new ExecutionError(`File '${targetFullFile}' already exists`);
303
- }
304
-
305
- mkdirSync(dirname(targetFullFile), { recursive: true });
306
-
307
- const desc = options.desc || `SOP guide for ${id}`;
308
- const actionsList = Array.isArray(options.actions) ? options.actions : [];
309
- const actionsYaml =
310
- actionsList.length > 0
311
- ? `actions:\n${actionsList.map((a: string) => ` - ${a}`).join("\n")}\n`
312
- : "actions: []\n";
313
-
314
- const template = `---
315
- id: ${id}
316
- description: ${desc}
317
- ${actionsYaml}---
318
-
319
- # ${id.replace(/[-_]/g, " ").replace(/\b\w/g, (c: string) => c.toUpperCase())} SOP
320
-
321
- This playbook provides task execution guidance for AI Agents.
322
-
323
- ## Instructions
324
-
325
- 1. Inspect available actions with \`<binary> list --json\`.
326
- 2. Follow the required steps to complete the task.
327
- `;
328
-
329
- writeFileSync(targetFullFile, template, "utf-8");
330
- console.log(`[OK] Created Playbook '${id}' at ${targetFullFile}`);
331
- } catch (err: any) {
332
- if (err instanceof CliError) {
333
- throw err;
334
- }
335
- throw new ExecutionError(err.message);
336
- }
337
- });
338
-
339
- // playbook show
340
- pbCmd
341
- .command("show <id>")
342
- .description("Show playbook content and metadata (from current project or linked packages)")
343
- .option("-P, --package <id>", "Target package ID or path")
344
- .option("-p, --profile <name>", "Query against a specific profile")
345
- .option("-s, --server <url>", "Remote server URL")
346
- .option("-t, --token <token>", "Auth token for remote server")
347
- .option("--json", "Output as JSON")
348
- .option("--envelope", "Wrap JSON output in standard envelope")
349
- .action(async (id, rawOptions, cmd) => {
350
- try {
351
- const options = getEffectiveOptions(rawOptions, cmd);
352
- if (!id) {
353
- throw new ArgumentError("Playbook ID is required for show");
354
- }
355
-
356
- const target = resolveTarget({
357
- profile: options.profile,
358
- server: options.server,
359
- token: options.token,
360
- });
361
-
362
- if (target.type === "remote") {
363
- const pb = await fetchRemotePlaybookShow(target.serverUrl!, id, target.token);
364
- renderResult(pb, {
365
- json: options.json,
366
- envelope: options.envelope,
367
- humanFormatter: () => {
368
- const lines = [`Playbook: ${pb.id} (Package: ${pb.packageId})`];
369
- if (pb.description) lines.push(`Description: ${pb.description}`);
370
- if (pb.actions && pb.actions.length > 0) {
371
- lines.push(`Actions: ${pb.actions.join(", ")}`);
372
- }
373
- if (pb.filePath) lines.push(`File: ${pb.filePath}\n`);
374
- lines.push("--- Content ---", pb.content);
375
- return lines.join("\n");
376
- },
377
- });
378
- return;
379
- }
380
-
381
- let showTarget = id;
382
- if (options.package && !id.includes("/") && !id.includes(":")) {
383
- const pkgRoot = resolvePackageRoot(options.package);
384
- if (!pkgRoot) {
385
- throw new ArgumentError(
386
- `Package '${options.package}' not found in linked packages or path`
387
- );
388
- }
389
- showTarget = `${options.package}/${id}`;
390
- }
391
-
392
- let resolved;
393
- try {
394
- resolved = resolvePlaybookProject(showTarget);
395
- } catch (err: any) {
396
- if (err.message?.includes("not found") || err.message?.includes("no longer exists")) {
397
- throw new ArgumentError(err.message);
398
- }
399
- throw new ExecutionError(err.message);
400
- }
401
-
402
- const pb = resolved.playbook;
403
- const payload = { ...pb, packageId: resolved.packageId };
404
-
405
- renderResult(payload, {
406
- json: options.json,
407
- envelope: options.envelope,
408
- humanFormatter: () => {
409
- const lines = [`Playbook: ${pb.id} (Package: ${resolved.packageId})`];
410
- if (pb.description) lines.push(`Description: ${pb.description}`);
411
- if (pb.actions && pb.actions.length > 0) {
412
- lines.push(`Actions: ${pb.actions.join(", ")}`);
413
- }
414
- if (pb.filePath) lines.push(`File: ${pb.filePath}\n`);
415
- lines.push("--- Content ---", pb.content);
416
- return lines.join("\n");
417
- },
418
- });
419
- } catch (err: any) {
420
- if (err instanceof ArgumentError || err instanceof CliError) throw err;
421
- throw new ExecutionError(err.message);
422
- }
423
- });
424
-
425
- // playbook validate
426
- pbCmd
427
- .command("validate [id]")
428
- .description("Validate playbook format and action references (in current project or linked packages)")
429
- .option("-P, --package <id>", "Target package ID or path")
430
- .option("--json", "Output as JSON")
431
- .option("--envelope", "Wrap JSON output in standard envelope")
432
- .action(async (id, rawOptions, cmd) => {
433
- try {
434
- const options = getEffectiveOptions(rawOptions, cmd);
435
- let root: string | null = null;
436
- if (options.package) {
437
- root = resolvePackageRoot(options.package);
438
- if (!root) {
439
- throw new ArgumentError(
440
- `Package '${options.package}' not found in linked packages or path`
441
- );
442
- }
443
- } else {
444
- root = findProjectRoot();
445
- }
446
-
447
- const targets: Array<{ root: string; packageId: string; playbooks: PlaybookDefinition[] }> = [];
448
-
449
- if (root) {
450
- const config = loadProjectConfig(root);
451
- const playbooks = loadPlaybooks(root, config.playbooksDir);
452
- if (id) {
453
- const pb = playbooks.get(id);
454
- if (!pb) {
455
- throw new ArgumentError(`Playbook '${id}' not found in package '${config.id}'`);
456
- }
457
- targets.push({ root, packageId: config.id, playbooks: [pb] });
458
- } else {
459
- targets.push({ root, packageId: config.id, playbooks: Array.from(playbooks.values()) });
460
- }
461
- } else if (id) {
462
- const resolved = resolvePlaybookProject(id);
463
- targets.push({
464
- root: resolved.projectRoot,
465
- packageId: resolved.packageId,
466
- playbooks: [resolved.playbook],
467
- });
468
- } else {
469
- // Outside project: validate all linked packages
470
- const linkedList = listLinkedPackages();
471
- if (linkedList.length === 0) {
472
- throw new ArgumentError("Not in an ActionDock project, and no packages linked.");
473
- }
474
- for (const pkg of linkedList) {
475
- if (!existsSync(pkg.path)) continue;
476
- try {
477
- const config = loadProjectConfig(pkg.path);
478
- const playbooks = loadPlaybooks(pkg.path, config.playbooksDir);
479
- targets.push({
480
- root: pkg.path,
481
- packageId: pkg.id,
482
- playbooks: Array.from(playbooks.values()),
483
- });
484
- } catch {}
485
- }
486
- }
487
-
488
- const results: Array<{ id: string; packageId: string; valid: boolean; warnings: string[]; errors: string[] }> = [];
489
-
490
- for (const target of targets) {
491
- const config = loadProjectConfig(target.root);
492
- let actionIds = new Set<string>();
493
- const manifest = loadManifest(target.root);
494
- if (manifest?.actions) {
495
- actionIds = new Set(Object.keys(manifest.actions));
496
- } else {
497
- try {
498
- const actions = await loadActions(target.root, config.actionsDir, { autoInstall: false });
499
- actionIds = new Set(actions.keys());
500
- } catch {
501
- // Ignore action loading failure during validation
502
- }
503
- }
504
-
505
- for (const pb of target.playbooks) {
506
- if (!pb) continue;
507
- const errors: string[] = [];
508
- const warnings: string[] = [];
509
-
510
- if (!pb.id) errors.push("Missing playbook id");
511
- if (!pb.content) warnings.push("Playbook content is empty");
512
-
513
- if (pb.actions) {
514
- for (const actId of pb.actions) {
515
- if (actionIds.has(actId)) {
516
- continue;
517
- }
518
- try {
519
- await resolveActionProject(actId, target.root);
520
- } catch (err: any) {
521
- warnings.push(err.message);
522
- }
523
- }
524
- }
525
-
526
- results.push({
527
- id: pb.id,
528
- packageId: target.packageId,
529
- valid: errors.length === 0,
530
- warnings,
531
- errors,
532
- });
533
- }
534
- }
535
-
536
- const allValid = results.every((r) => r.valid);
537
- renderResult(
538
- { valid: allValid, results },
539
- {
540
- json: options.json,
541
- envelope: options.envelope,
542
- humanFormatter: () => {
543
- const lines: string[] = [];
544
- for (const r of results) {
545
- const prefix = targets.length > 1 || !root ? `[${r.packageId}] ` : "";
546
- if (r.valid) {
547
- const warn = r.warnings.length > 0 ? ` (Warnings: ${r.warnings.join("; ")})` : "";
548
- lines.push(`[OK] ${prefix}${r.id}: Valid${warn}`);
549
- } else {
550
- lines.push(`[FAIL] ${prefix}${r.id}: ${r.errors.join("; ")}`);
551
- }
552
- }
553
- return lines.join("\n");
554
- },
555
- }
556
- );
557
- if (!allValid) process.exitCode = 1;
558
- } catch (err: any) {
559
- if (err instanceof ArgumentError || err instanceof CliError) {
560
- throw err;
561
- }
562
- throw new ExecutionError(err.message);
563
- }
564
- });
565
- }
566
-