@actiondock/cli 2.0.11 → 2.2.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 (123) hide show
  1. package/README.md +41 -21
  2. package/bin/ad.js +29 -31
  3. package/dist/commands/action/create.d.ts +10 -0
  4. package/dist/commands/action/create.js +120 -0
  5. package/dist/commands/add.d.ts +7 -0
  6. package/dist/commands/add.js +144 -0
  7. package/dist/commands/build.d.ts +3 -0
  8. package/dist/commands/build.js +83 -0
  9. package/dist/commands/config/delete.d.ts +9 -0
  10. package/dist/commands/config/delete.js +52 -0
  11. package/dist/commands/config/env.d.ts +9 -0
  12. package/dist/commands/config/env.js +85 -0
  13. package/dist/commands/config/get.d.ts +9 -0
  14. package/dist/commands/config/get.js +120 -0
  15. package/dist/commands/config/index.d.ts +9 -0
  16. package/dist/commands/config/index.js +23 -0
  17. package/dist/commands/config/list.d.ts +9 -0
  18. package/dist/commands/config/list.js +137 -0
  19. package/dist/commands/config/schema.d.ts +9 -0
  20. package/dist/commands/config/schema.js +121 -0
  21. package/dist/commands/config/set.d.ts +9 -0
  22. package/dist/commands/config/set.js +85 -0
  23. package/dist/commands/describe.d.ts +9 -0
  24. package/dist/commands/describe.js +68 -0
  25. package/dist/commands/doctor.d.ts +3 -0
  26. package/dist/commands/doctor.js +82 -0
  27. package/dist/commands/export.d.ts +3 -0
  28. package/dist/commands/export.js +212 -0
  29. package/dist/commands/generate.d.ts +9 -0
  30. package/dist/commands/generate.js +44 -0
  31. package/dist/commands/index.d.ts +32 -0
  32. package/dist/commands/index.js +111 -0
  33. package/dist/commands/info.d.ts +9 -0
  34. package/dist/commands/info.js +210 -0
  35. package/dist/commands/init.d.ts +2 -0
  36. package/dist/commands/init.js +24 -0
  37. package/dist/commands/link.d.ts +2 -0
  38. package/dist/commands/link.js +71 -0
  39. package/dist/commands/list.d.ts +9 -0
  40. package/dist/commands/list.js +77 -0
  41. package/dist/commands/mcp.d.ts +9 -0
  42. package/dist/commands/mcp.js +172 -0
  43. package/dist/commands/new.d.ts +9 -0
  44. package/dist/commands/new.js +33 -0
  45. package/dist/commands/pack.d.ts +3 -0
  46. package/dist/commands/pack.js +63 -0
  47. package/dist/commands/playbook.d.ts +10 -0
  48. package/dist/commands/playbook.js +433 -0
  49. package/dist/commands/profile.d.ts +2 -0
  50. package/dist/commands/profile.js +253 -0
  51. package/dist/commands/remove.d.ts +7 -0
  52. package/dist/commands/remove.js +118 -0
  53. package/dist/commands/run.d.ts +13 -0
  54. package/dist/commands/run.js +146 -0
  55. package/dist/commands/runs.d.ts +9 -0
  56. package/dist/commands/runs.js +245 -0
  57. package/dist/commands/serve.d.ts +9 -0
  58. package/dist/commands/serve.js +129 -0
  59. package/dist/commands/state-list.d.ts +24 -0
  60. package/dist/commands/state-list.js +82 -0
  61. package/dist/commands/state.d.ts +9 -0
  62. package/dist/commands/state.js +310 -0
  63. package/dist/commands/test.d.ts +2 -0
  64. package/dist/commands/test.js +44 -0
  65. package/dist/commands/validate.d.ts +9 -0
  66. package/dist/commands/validate.js +106 -0
  67. package/dist/errors.d.ts +43 -0
  68. package/dist/errors.js +103 -0
  69. package/dist/index.d.ts +15 -0
  70. package/dist/index.js +47 -65969
  71. package/dist/prompt.d.ts +53 -0
  72. package/dist/prompt.js +192 -0
  73. package/dist/renderer.d.ts +197 -0
  74. package/dist/renderer.js +502 -0
  75. package/dist/services/index.d.ts +1 -0
  76. package/dist/services/index.js +1 -0
  77. package/dist/services/inspect.d.ts +23 -0
  78. package/dist/services/inspect.js +96 -0
  79. package/dist/standalone/index.d.ts +16 -0
  80. package/dist/standalone/index.js +22 -0
  81. package/dist/types.d.ts +132 -0
  82. package/dist/types.js +9 -0
  83. package/dist/utils/bytes.d.ts +1 -0
  84. package/dist/utils/bytes.js +1 -0
  85. package/dist/utils/duration.js +1 -0
  86. package/dist/utils/filter.d.ts +1 -0
  87. package/dist/utils/filter.js +1 -0
  88. package/dist/utils/index.d.ts +6 -0
  89. package/dist/utils/index.js +6 -0
  90. package/dist/utils/spawn.d.ts +27 -0
  91. package/dist/utils/spawn.js +35 -0
  92. package/dist/utils/target.d.ts +45 -0
  93. package/dist/utils/target.js +78 -0
  94. package/dist/utils/utils.d.ts +38 -0
  95. package/dist/utils/utils.js +111 -0
  96. package/package.json +20 -17
  97. package/src/commands/action/create.ts +0 -129
  98. package/src/commands/action/index.ts +0 -27
  99. package/src/commands/action/list.ts +0 -247
  100. package/src/commands/action/run.ts +0 -197
  101. package/src/commands/action/show.ts +0 -128
  102. package/src/commands/action/sync.ts +0 -76
  103. package/src/commands/action/validate.ts +0 -102
  104. package/src/commands/action.ts +0 -1
  105. package/src/commands/build.ts +0 -56
  106. package/src/commands/config.ts +0 -583
  107. package/src/commands/doctor.ts +0 -84
  108. package/src/commands/export.ts +0 -196
  109. package/src/commands/index.ts +0 -75
  110. package/src/commands/info.ts +0 -537
  111. package/src/commands/init.ts +0 -24
  112. package/src/commands/link.ts +0 -73
  113. package/src/commands/mcp.ts +0 -195
  114. package/src/commands/playbook.ts +0 -566
  115. package/src/commands/profile.ts +0 -296
  116. package/src/commands/runs.ts +0 -452
  117. package/src/commands/serve.ts +0 -132
  118. package/src/commands/state.ts +0 -590
  119. package/src/commands/test.ts +0 -48
  120. package/src/index.ts +0 -58
  121. package/src/utils/bytes.ts +0 -2
  122. package/src/utils/filter.ts +0 -2
  123. /package/{src/utils/duration.ts → dist/utils/duration.d.ts} +0 -0
@@ -0,0 +1,63 @@
1
+ import { packProject } from "@actiondock/builder";
2
+ import { findProjectRoot, resolvePackageRoot } from "@actiondock/core";
3
+ import { ExecutionError } from "../errors.js";
4
+ import { renderResult, writeStdout } from "../renderer.js";
5
+ import { getEffectiveOptions } from "../utils/index.js";
6
+ export function registerPackCommand(program, context) {
7
+ program
8
+ .command("pack")
9
+ .description("Pack Action package into a standard npm tarball (.tgz) for distribution")
10
+ .option("-P, --package <id>", "Target package ID or path")
11
+ .option("-o, --out <path>", "Output directory for the .tgz package")
12
+ .option("--dry-run", "Validate and show package summary without creating .tgz")
13
+ .option("--json", "Output as JSON")
14
+ .action(async (rawOptions, cmd) => {
15
+ const options = getEffectiveOptions(rawOptions, cmd);
16
+ const root = options.package
17
+ ? resolvePackageRoot(options.package)
18
+ : findProjectRoot();
19
+ if (!root) {
20
+ if (options.package) {
21
+ throw new ExecutionError(`Package '${options.package}' not found in linked packages or path`);
22
+ }
23
+ throw new ExecutionError("Not in an ActionDock project (actiondock.json not found).\nPlease specify -P, --package <id> or cd into a project directory.");
24
+ }
25
+ const isMachine = Boolean(options.json || options.envelope);
26
+ if (!isMachine && !options.dryRun) {
27
+ writeStdout("Packing Action package...");
28
+ }
29
+ let result;
30
+ try {
31
+ result = await packProject({
32
+ projectRoot: root,
33
+ outDir: options.out,
34
+ dryRun: options.dryRun,
35
+ });
36
+ }
37
+ catch (err) {
38
+ throw new ExecutionError(`Pack failed: ${err.message}`);
39
+ }
40
+ renderResult(result, {
41
+ json: isMachine,
42
+ envelope: options.envelope,
43
+ humanFormatter: () => {
44
+ const lines = [];
45
+ if (options.dryRun) {
46
+ lines.push(`[OK] Pack dry-run passed for ${result.packageId} (v${result.version})`);
47
+ lines.push(` Tarball Name: ${result.tarballName}`);
48
+ lines.push(` Actions: ${result.manifestSummary.actions.join(", ")}`);
49
+ lines.push(` Files Count: ${result.manifestSummary.filesCount}`);
50
+ lines.push(" Notice: Dry-run mode enabled, no tarball was generated.");
51
+ return lines.join("\n");
52
+ }
53
+ lines.push(`[OK] Successfully packed ${result.packageId} (v${result.version})`);
54
+ lines.push(` Tarball: ${result.tarballPath}`);
55
+ lines.push(` Size: ${result.sizeBytes} bytes`);
56
+ lines.push(` SHA-256: ${result.sha256}`);
57
+ lines.push(` Actions: ${result.manifestSummary.actions.join(", ")}`);
58
+ return lines.join("\n");
59
+ },
60
+ context,
61
+ });
62
+ });
63
+ }
@@ -0,0 +1,10 @@
1
+ import { Command } from "commander";
2
+ import type { CliContext } from "../types.js";
3
+ /**
4
+ * 注册 Playbook 命令集合(list, show, validate, create)。
5
+ *
6
+ * @param program Commander 根程序对象
7
+ * @param context 命令行上下文
8
+ */
9
+ export declare function registerPlaybookCommands(program: Command, context?: CliContext): void;
10
+ export declare function handlePlaybookCreate(id: string, options: any, context?: CliContext): void;
@@ -0,0 +1,433 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { dirname, isAbsolute, relative, resolve } from "node:path";
3
+ import { assertPathWithinRoot, fetchRemotePlaybooks, fetchRemotePlaybookShow, filterWithFallbackInfo, findProjectRoot, listLinkedPackages, loadActions, loadManifest, loadPlaybooks, loadProjectConfig, resolveActionProject, resolvePackageRoot, resolvePlaybookProject, resolveTarget, saveManifest, } from "@actiondock/core";
4
+ import { ArgumentError, CliError, ExecutionError } from "../errors.js";
5
+ import { renderPlaybookDetail, renderPlaybookList, renderResult, writeStdout, } from "../renderer.js";
6
+ import { getEffectiveOptions, resolveIntent } from "../utils/index.js";
7
+ /**
8
+ * 注册 Playbook 命令集合(list, show, validate, create)。
9
+ *
10
+ * @param program Commander 根程序对象
11
+ * @param context 命令行上下文
12
+ */
13
+ export function registerPlaybookCommands(program, context) {
14
+ const pbCmd = program
15
+ .command("playbook")
16
+ .description("Manage task Playbooks (Task SOPs for AI Agents)");
17
+ // playbook list
18
+ pbCmd
19
+ .command("list [patterns...]")
20
+ .description("List playbooks in current project or linked packages")
21
+ .option("-i, --intent <pattern>", "Regex or fuzzy intent filter; falls back to full list when no match")
22
+ .option("-P, --package <id>", "Target package ID or path")
23
+ .option("-p, --profile <name>", "Query against a specific profile")
24
+ .option("-s, --server <url>", "Remote server URL")
25
+ .option("-t, --token <token>", "Auth token for remote server")
26
+ .option("--no-fallback", "Disable fallback to full list when no items match intent")
27
+ .option("--json", "Output as JSON")
28
+ .option("--envelope", "Wrap JSON output in standard envelope")
29
+ .action(async (patterns = [], rawOptions, cmd) => {
30
+ const options = getEffectiveOptions(rawOptions, cmd);
31
+ const effectiveIntent = resolveIntent(options.intent, patterns);
32
+ const shouldFallback = options.fallback !== false;
33
+ const isMachine = Boolean(options.json || options.envelope);
34
+ // 1. 远端服务分支
35
+ const target = resolveTarget({
36
+ profile: options.profile,
37
+ server: options.server,
38
+ token: options.token,
39
+ }, context?.customHome);
40
+ if (target.type === "remote") {
41
+ const remotePbs = await fetchRemotePlaybooks(target.serverUrl, target.token, {
42
+ intent: effectiveIntent,
43
+ package: options.package,
44
+ });
45
+ renderResult(remotePbs, {
46
+ json: options.json,
47
+ envelope: options.envelope,
48
+ humanFormatter: () => renderPlaybookList(remotePbs, `Playbooks on remote server ${target.serverUrl}${target.profileName ? ` (Profile: ${target.profileName})` : ""}`, false, effectiveIntent),
49
+ context,
50
+ });
51
+ return;
52
+ }
53
+ // 2. 本地工程模式
54
+ let targetRoot = null;
55
+ if (options.package) {
56
+ targetRoot = resolvePackageRoot(options.package);
57
+ if (!targetRoot) {
58
+ throw new ArgumentError(`Package '${options.package}' not found in linked packages or path`);
59
+ }
60
+ }
61
+ else {
62
+ targetRoot = findProjectRoot();
63
+ }
64
+ if (targetRoot) {
65
+ const config = loadProjectConfig(targetRoot);
66
+ const playbooks = loadPlaybooks(targetRoot, config.playbooksDir);
67
+ const rawList = Array.from(playbooks.values()).map((pb) => ({
68
+ id: pb.id,
69
+ description: pb.description,
70
+ packageId: config.id,
71
+ }));
72
+ const filterRes = filterWithFallbackInfo(rawList, effectiveIntent, [(p) => p.id, (p) => p.description], shouldFallback);
73
+ if (filterRes.isFallback && isMachine) {
74
+ renderResult({ items: filterRes.items, isFallback: true, matchedCount: 0 }, { json: options.json, envelope: options.envelope, context });
75
+ return;
76
+ }
77
+ renderResult(filterRes.items, {
78
+ json: options.json,
79
+ envelope: options.envelope,
80
+ humanFormatter: () => renderPlaybookList(filterRes.items, `Playbooks in ${config.id} (${targetRoot})`, filterRes.isFallback, effectiveIntent),
81
+ context,
82
+ });
83
+ return;
84
+ }
85
+ // 3. 扫描所有已链接的外部包
86
+ const linkedList = listLinkedPackages(context?.customHome);
87
+ if (linkedList.length === 0) {
88
+ renderResult([], {
89
+ json: options.json,
90
+ envelope: options.envelope,
91
+ humanFormatter: () => "No ActionDock project in current directory, and no packages linked.",
92
+ context,
93
+ });
94
+ return;
95
+ }
96
+ const aggregated = [];
97
+ for (const pkg of linkedList) {
98
+ if (!existsSync(pkg.path))
99
+ continue;
100
+ try {
101
+ const config = loadProjectConfig(pkg.path);
102
+ const pbs = loadPlaybooks(pkg.path, config.playbooksDir);
103
+ const list = Array.from(pbs.values()).map((p) => ({
104
+ id: p.id,
105
+ description: p.description,
106
+ }));
107
+ aggregated.push({
108
+ packageId: pkg.id,
109
+ packageName: pkg.name,
110
+ path: pkg.path,
111
+ playbooks: list,
112
+ });
113
+ }
114
+ catch {
115
+ // 单个链接包清单损坏或 Playbook 加载失败时跳过,继续聚合其余包
116
+ }
117
+ }
118
+ let filteredPackages = [];
119
+ let isFallback = false;
120
+ if (!effectiveIntent) {
121
+ filteredPackages = aggregated;
122
+ }
123
+ else {
124
+ for (const pkg of aggregated) {
125
+ const pkgMatches = filterWithFallbackInfo([pkg], effectiveIntent, [(p) => p.packageId, (p) => p.packageName, (p) => p.path], false).matchedCount > 0;
126
+ if (pkgMatches) {
127
+ filteredPackages.push(pkg);
128
+ }
129
+ else {
130
+ const matchedPbs = filterWithFallbackInfo(pkg.playbooks, effectiveIntent, [(p) => p.id, (p) => p.description], false).items;
131
+ if (matchedPbs.length > 0) {
132
+ filteredPackages.push({
133
+ ...pkg,
134
+ playbooks: matchedPbs,
135
+ });
136
+ }
137
+ }
138
+ }
139
+ if (filteredPackages.length === 0 && shouldFallback) {
140
+ filteredPackages = aggregated;
141
+ isFallback = true;
142
+ }
143
+ }
144
+ if (isFallback && isMachine) {
145
+ renderResult({ packages: filteredPackages, isFallback: true, matchedCount: 0 }, { json: options.json, envelope: options.envelope, context });
146
+ return;
147
+ }
148
+ renderResult(filteredPackages, {
149
+ json: options.json,
150
+ envelope: options.envelope,
151
+ humanFormatter: () => {
152
+ const lines = ["Linked Playbooks:\n"];
153
+ for (const pkg of filteredPackages) {
154
+ lines.push(`- Package: ${pkg.packageId} (${pkg.path})`);
155
+ for (const pb of pkg.playbooks) {
156
+ lines.push(` - ${pb.id.padEnd(26)} ${pb.description || ""}`);
157
+ }
158
+ }
159
+ return lines.join("\n");
160
+ },
161
+ context,
162
+ });
163
+ });
164
+ // playbook show <id>
165
+ pbCmd
166
+ .command("show <id>")
167
+ .description("Show playbook content and metadata")
168
+ .option("-P, --package <id>", "Target package ID or path")
169
+ .option("-p, --profile <name>", "Query against a specific profile")
170
+ .option("-s, --server <url>", "Remote server URL")
171
+ .option("-t, --token <token>", "Auth token for remote server")
172
+ .option("--json", "Output as JSON")
173
+ .option("--envelope", "Wrap JSON output in standard envelope")
174
+ .action(async (id, rawOptions, cmd) => {
175
+ const options = getEffectiveOptions(rawOptions, cmd);
176
+ if (!id) {
177
+ throw new ArgumentError("Playbook ID is required for show");
178
+ }
179
+ // 1. 远端服务分支
180
+ const target = resolveTarget({
181
+ profile: options.profile,
182
+ server: options.server,
183
+ token: options.token,
184
+ }, context?.customHome);
185
+ if (target.type === "remote") {
186
+ const detail = await fetchRemotePlaybookShow(target.serverUrl, id, target.token);
187
+ renderResult(detail, {
188
+ json: options.json,
189
+ envelope: options.envelope,
190
+ humanFormatter: () => renderPlaybookDetail(detail),
191
+ context,
192
+ });
193
+ return;
194
+ }
195
+ // 2. 本地项目模式
196
+ let showTarget = id;
197
+ if (options.package && !id.includes("/")) {
198
+ const pkgRoot = resolvePackageRoot(options.package);
199
+ if (!pkgRoot) {
200
+ throw new ArgumentError(`Package '${options.package}' not found in linked packages or path`);
201
+ }
202
+ showTarget = `${options.package}/${id}`;
203
+ }
204
+ let resolved;
205
+ try {
206
+ resolved = resolvePlaybookProject(showTarget);
207
+ }
208
+ catch (err) {
209
+ throw new ArgumentError(err.message);
210
+ }
211
+ const pb = resolved.playbook;
212
+ const detail = {
213
+ id: pb.id,
214
+ packageId: resolved.packageId,
215
+ description: pb.description,
216
+ actions: pb.actions,
217
+ filePath: pb.filePath,
218
+ content: pb.content,
219
+ };
220
+ renderResult(detail, {
221
+ json: options.json,
222
+ envelope: options.envelope,
223
+ humanFormatter: () => renderPlaybookDetail(detail),
224
+ context,
225
+ });
226
+ });
227
+ // playbook validate
228
+ pbCmd
229
+ .command("validate [id]")
230
+ .description("Validate playbook format and action references (in current project or linked packages)")
231
+ .option("-P, --package <id>", "Target package ID or path")
232
+ .option("--json", "Output as JSON")
233
+ .option("--envelope", "Wrap JSON output in standard envelope")
234
+ .action(async (id, rawOptions, cmd) => {
235
+ const options = getEffectiveOptions(rawOptions, cmd);
236
+ let root = null;
237
+ if (options.package) {
238
+ root = resolvePackageRoot(options.package);
239
+ if (!root) {
240
+ throw new ArgumentError(`Package '${options.package}' not found in linked packages or path`);
241
+ }
242
+ }
243
+ else {
244
+ root = findProjectRoot();
245
+ }
246
+ const targets = [];
247
+ if (root) {
248
+ const config = loadProjectConfig(root);
249
+ const playbooks = loadPlaybooks(root, config.playbooksDir);
250
+ if (id) {
251
+ const pb = playbooks.get(id);
252
+ if (!pb) {
253
+ throw new ArgumentError(`Playbook '${id}' not found in package '${config.id}'`);
254
+ }
255
+ targets.push({ root, packageId: config.id, playbooks: [pb] });
256
+ }
257
+ else {
258
+ targets.push({ root, packageId: config.id, playbooks: Array.from(playbooks.values()) });
259
+ }
260
+ }
261
+ else if (id) {
262
+ let resolved;
263
+ try {
264
+ resolved = resolvePlaybookProject(id);
265
+ }
266
+ catch (err) {
267
+ throw new ArgumentError(err.message);
268
+ }
269
+ targets.push({
270
+ root: resolved.projectRoot,
271
+ packageId: resolved.packageId,
272
+ playbooks: [resolved.playbook],
273
+ });
274
+ }
275
+ else {
276
+ const linkedList = listLinkedPackages(context?.customHome);
277
+ if (linkedList.length === 0) {
278
+ throw new ArgumentError("Not in an ActionDock project, and no packages linked.");
279
+ }
280
+ for (const pkg of linkedList) {
281
+ if (!existsSync(pkg.path))
282
+ continue;
283
+ try {
284
+ const config = loadProjectConfig(pkg.path);
285
+ const playbooks = loadPlaybooks(pkg.path, config.playbooksDir);
286
+ targets.push({
287
+ root: pkg.path,
288
+ packageId: pkg.id,
289
+ playbooks: Array.from(playbooks.values()),
290
+ });
291
+ }
292
+ catch {
293
+ // 单个链接包清单损坏或 Playbook 加载失败时跳过,不阻断整体校验
294
+ }
295
+ }
296
+ }
297
+ const results = [];
298
+ for (const target of targets) {
299
+ const config = loadProjectConfig(target.root);
300
+ const manifest = loadManifest(target.root);
301
+ const actions = await loadActions(target.root, config.actionsDir);
302
+ for (const pb of target.playbooks) {
303
+ const warnings = [];
304
+ const errors = [];
305
+ if (!pb.id) {
306
+ errors.push("Missing id in playbook");
307
+ }
308
+ if (!pb.description) {
309
+ warnings.push("Missing description in actiondock.json");
310
+ }
311
+ if (pb.actions && Array.isArray(pb.actions)) {
312
+ for (const actRef of pb.actions) {
313
+ if (actRef.includes("/")) {
314
+ try {
315
+ await resolveActionProject(actRef);
316
+ }
317
+ catch (e) {
318
+ errors.push(`Referenced cross-package action '${actRef}' not resolvable: ${e.message}`);
319
+ }
320
+ }
321
+ else {
322
+ const foundInActions = actions.has(actRef);
323
+ const foundInManifest = Boolean(manifest?.actions?.[actRef]);
324
+ if (!foundInActions && !foundInManifest) {
325
+ errors.push(`Referenced action '${actRef}' not found in project actions`);
326
+ }
327
+ }
328
+ }
329
+ }
330
+ results.push({
331
+ id: pb.id,
332
+ packageId: target.packageId,
333
+ valid: errors.length === 0,
334
+ warnings,
335
+ errors,
336
+ });
337
+ }
338
+ }
339
+ const allValid = results.every((r) => r.valid);
340
+ renderResult({ valid: allValid, results }, {
341
+ json: options.json,
342
+ envelope: options.envelope,
343
+ humanFormatter: () => {
344
+ const lines = [];
345
+ for (const r of results) {
346
+ const tag = r.valid ? "[OK]" : "[FAIL]";
347
+ lines.push(`${tag} ${r.packageId}/${r.id}: ${r.valid ? "Valid" : "Invalid"}`);
348
+ for (const e of r.errors) {
349
+ lines.push(` Error: ${e}`);
350
+ }
351
+ for (const w of r.warnings) {
352
+ lines.push(` Warning: ${w}`);
353
+ }
354
+ }
355
+ return lines.join("\n");
356
+ },
357
+ context,
358
+ });
359
+ if (!allValid) {
360
+ process.exitCode = 1;
361
+ return;
362
+ }
363
+ });
364
+ // playbook create / new
365
+ pbCmd
366
+ .command("create <id>")
367
+ .alias("new")
368
+ .description("Scaffold a new Playbook markdown file")
369
+ .option("-d, --desc <description>", "Playbook description")
370
+ .option("-a, --actions <actions...>", "Referenced action IDs")
371
+ .option("-f, --file <filePath>", "Target file path relative to playbooks dir")
372
+ .action((id, options) => {
373
+ handlePlaybookCreate(id, options, context);
374
+ });
375
+ }
376
+ export function handlePlaybookCreate(id, options, context) {
377
+ const root = findProjectRoot();
378
+ if (!root) {
379
+ throw new ExecutionError("Not in an ActionDock project (actiondock.json not found)");
380
+ }
381
+ try {
382
+ const config = loadProjectConfig(root);
383
+ const pbDir = resolve(root, config.playbooksDir || "playbooks");
384
+ if (!existsSync(pbDir)) {
385
+ mkdirSync(pbDir, { recursive: true });
386
+ }
387
+ if (options.file && isAbsolute(options.file)) {
388
+ throw new ExecutionError(`--file option must be a relative path, received: ${options.file}`);
389
+ }
390
+ const cleanName = id.replace(/[^a-zA-Z0-9-_]/g, "-");
391
+ const targetRelFile = options.file || `${cleanName}.md`;
392
+ const targetFullFile = resolve(pbDir, targetRelFile);
393
+ assertPathWithinRoot(pbDir, targetFullFile, "playbook file");
394
+ if (existsSync(targetFullFile)) {
395
+ throw new ExecutionError(`File '${targetFullFile}' already exists`);
396
+ }
397
+ mkdirSync(dirname(targetFullFile), { recursive: true });
398
+ const desc = options.desc || `SOP guide for ${id}`;
399
+ const actionsList = Array.isArray(options.actions) ? options.actions : [];
400
+ const template = `# ${id.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())} SOP
401
+
402
+ This playbook provides task execution guidance for AI Agents.
403
+
404
+ ## Instructions
405
+
406
+ - Inspect available actions with \`<binary> list --json\`.
407
+ - Follow the required steps to complete the task.
408
+ `;
409
+ writeFileSync(targetFullFile, template, "utf-8");
410
+ const manifest = loadManifest(root) || {
411
+ $schema: "https://actiondock.dev/schema/v2/actiondock.json",
412
+ id: config.id,
413
+ name: config.name,
414
+ version: config.version,
415
+ playbooks: {},
416
+ };
417
+ manifest.playbooks = manifest.playbooks || {};
418
+ const relEntry = relative(root, targetFullFile).replace(/\\/g, "/");
419
+ manifest.playbooks[id] = {
420
+ entry: relEntry,
421
+ description: desc,
422
+ actions: actionsList,
423
+ };
424
+ saveManifest(root, manifest);
425
+ writeStdout(`[OK] Created Playbook '${id}' at ${targetFullFile}`, context);
426
+ }
427
+ catch (err) {
428
+ if (err instanceof CliError) {
429
+ throw err;
430
+ }
431
+ throw new ExecutionError(err.message);
432
+ }
433
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerProfileCommands(program: Command): void;