@actiondock/cli 2.0.8 → 2.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,633 +1 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import { dirname, join, resolve } from "node:path";
3
- import {
4
- ActionRunner,
5
- createStorage,
6
- executeRemoteAction,
7
- fetchRemoteActions,
8
- fetchRemoteActionShow,
9
- filterWithFallbackInfo,
10
- findProjectRoot,
11
- getPackageSlug,
12
- listLinkedPackages,
13
- loadActions,
14
- loadManifest,
15
- loadProjectConfig,
16
- resolveActionProject,
17
- resolveTarget,
18
- saveManifest,
19
- validateSchema,
20
- } from "@actiondock/core";
21
- import { Command } from "commander";
22
- import { parseDuration } from "../utils/duration";
23
- import { resolveIntent } from "../utils/filter";
24
-
25
- /**
26
- * 注册 Action 相关的 CLI 命令集合(包括 action list, action describe, action run, action new, action test 以及顶层 run 别名)。
27
- *
28
- * @param program Commander 根程序对象
29
- */
30
- export function registerActionCommands(program: Command): void {
31
- const actionCmd = program
32
- .command("action")
33
- .description("Manage and execute Actions");
34
-
35
- // action list
36
- actionCmd
37
- .command("list [patterns...]")
38
- .description("List actions in current project, linked packages, or remote profile")
39
- .option("-i, --intent <pattern>", "Regex or fuzzy intent filter; falls back to full list when no match")
40
- .option("-p, --profile <name>", "Execute or 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("--no-fallback", "Disable fallback to full list when no items match intent")
44
- .option("--json", "Output as JSON")
45
- .action(async (patterns, options) => {
46
- try {
47
- const effectiveIntent = resolveIntent(options.intent, patterns);
48
- const shouldFallback = options.fallback !== false;
49
-
50
- const target = resolveTarget({
51
- profile: options.profile,
52
- server: options.server,
53
- token: options.token,
54
- });
55
-
56
- if (target.type === "remote") {
57
- let list = await fetchRemoteActions(
58
- target.serverUrl!,
59
- target.token,
60
- effectiveIntent
61
- );
62
-
63
- let isFallback = false;
64
- if (list.length === 0 && effectiveIntent && shouldFallback) {
65
- list = await fetchRemoteActions(target.serverUrl!, target.token);
66
- isFallback = true;
67
- }
68
-
69
- if (options.json) {
70
- console.log(JSON.stringify(list, null, 2));
71
- } else {
72
- console.log(
73
- `Actions on remote server ${target.serverUrl}${target.profileName ? ` (Profile: ${target.profileName})` : ""}:\n`
74
- );
75
- if (isFallback && effectiveIntent) {
76
- console.log(`(No remote actions matched intent '${effectiveIntent}', showing all actions)\n`);
77
- }
78
- for (const a of list) {
79
- console.log(` ${a.id.padEnd(28)} ${a.description}`);
80
- }
81
- }
82
- return;
83
- }
84
-
85
- const root = findProjectRoot();
86
- if (root) {
87
- const config = loadProjectConfig(root);
88
- const actions = await loadActions(root, config.actionsDir);
89
- const rawList = Array.from(actions.values()).map((a) => ({
90
- id: a.id,
91
- description: a.description || "",
92
- packageId: config.id,
93
- }));
94
-
95
- const filterRes = filterWithFallbackInfo(
96
- rawList,
97
- effectiveIntent,
98
- [(a) => a.id, (a) => a.description, (a) => a.packageId],
99
- shouldFallback
100
- );
101
-
102
- if (options.json) {
103
- console.log(JSON.stringify(filterRes.items, null, 2));
104
- } else {
105
- console.log(`Actions in ${config.id} (${root}):\n`);
106
- if (filterRes.isFallback && effectiveIntent) {
107
- console.log(`(No actions matched intent '${effectiveIntent}', showing all actions)\n`);
108
- }
109
- for (const a of filterRes.items) {
110
- console.log(` ${a.id.padEnd(28)} ${a.description}`);
111
- }
112
- }
113
- } else {
114
- // List actions across all linked packages
115
- const linkedList = listLinkedPackages();
116
- if (linkedList.length === 0) {
117
- console.log("No ActionDock project in current directory, and no packages linked.");
118
- console.log("Run 'ad link' inside an Action package to register it.");
119
- return;
120
- }
121
-
122
- const aggregated: Array<{
123
- packageId: string;
124
- packageName: string;
125
- path: string;
126
- actions: Array<{ id: string; description: string }>;
127
- }> = [];
128
-
129
- for (const pkg of linkedList) {
130
- if (!existsSync(pkg.path)) continue;
131
- try {
132
- const config = loadProjectConfig(pkg.path);
133
- const actions = await loadActions(pkg.path, config.actionsDir);
134
- const pkgActions = Array.from(actions.values()).map((a) => ({
135
- id: a.id,
136
- description: a.description || "",
137
- }));
138
-
139
- aggregated.push({
140
- packageId: pkg.id,
141
- packageName: pkg.name,
142
- path: pkg.path,
143
- actions: pkgActions,
144
- });
145
- } catch {
146
- // Ignore broken linked package
147
- }
148
- }
149
-
150
- let filteredPackages: typeof aggregated = [];
151
- if (!effectiveIntent) {
152
- filteredPackages = aggregated;
153
- } else {
154
- for (const pkg of aggregated) {
155
- const pkgMatches = filterWithFallbackInfo(
156
- [pkg],
157
- effectiveIntent,
158
- [(p) => p.packageId, (p) => p.packageName, (p) => p.path],
159
- false
160
- ).matchedCount > 0;
161
-
162
- if (pkgMatches) {
163
- filteredPackages.push(pkg);
164
- } else {
165
- const matchedActions = filterWithFallbackInfo(
166
- pkg.actions,
167
- effectiveIntent,
168
- [(a) => a.id, (a) => a.description],
169
- false
170
- ).items;
171
-
172
- if (matchedActions.length > 0) {
173
- filteredPackages.push({
174
- ...pkg,
175
- actions: matchedActions,
176
- });
177
- }
178
- }
179
- }
180
-
181
- if (filteredPackages.length === 0 && shouldFallback) {
182
- filteredPackages = aggregated;
183
- }
184
- }
185
-
186
- if (options.json) {
187
- console.log(JSON.stringify(filteredPackages, null, 2));
188
- } else {
189
- console.log("Linked Action Packages:\n");
190
- for (const pkg of filteredPackages) {
191
- console.log(`* Package: ${pkg.packageId} (${pkg.path})`);
192
- for (const a of pkg.actions) {
193
- console.log(` - ${a.id.padEnd(26)} ${a.description}`);
194
- }
195
- }
196
- }
197
- }
198
- } catch (err: any) {
199
- console.error(`Error: ${err.message}`);
200
- process.exit(1);
201
- }
202
- });
203
-
204
-
205
- // action create / new
206
- actionCmd
207
- .command("create <id>")
208
- .alias("new")
209
- .description("Scaffold a new Action definition file")
210
- .option("-d, --desc <description>", "Action description")
211
- .option("-f, --file <filePath>", "Target file path relative to actions dir")
212
- .action(async (id, options) => {
213
- const root = findProjectRoot();
214
- if (!root) {
215
- console.error("Error: Not in an ActionDock project (actiondock.json not found)");
216
- process.exit(1);
217
- }
218
- try {
219
- const config = loadProjectConfig(root);
220
- const actionsDir = resolve(root, config.actionsDir || "actions");
221
- if (!existsSync(actionsDir)) {
222
- mkdirSync(actionsDir, { recursive: true });
223
- }
224
-
225
- const cleanName = getPackageSlug(id);
226
- const targetRelFile = options.file || `${cleanName}.ts`;
227
- const targetFullFile = resolve(actionsDir, targetRelFile);
228
-
229
- if (existsSync(targetFullFile)) {
230
- console.error(`Error: Target action file already exists at ${targetFullFile}`);
231
- process.exit(1);
232
- }
233
-
234
- const desc = options.desc || `Action ${id}`;
235
- const template = `import { defineAction } from "@actiondock/sdk";
236
-
237
- export interface Input {
238
- exampleParam?: string;
239
- }
240
-
241
- export interface Output {
242
- success: boolean;
243
- result?: unknown;
244
- }
245
-
246
- export default defineAction<Input, Output>({
247
- id: "${id}",
248
- description: "${desc}",
249
-
250
- inputSchema: {
251
- type: "object",
252
- properties: {
253
- exampleParam: {
254
- type: "string",
255
- description: "Example parameter description",
256
- },
257
- },
258
- required: [],
259
- },
260
-
261
- outputSchema: {
262
- type: "object",
263
- properties: {
264
- success: { type: "boolean" },
265
- result: {},
266
- },
267
- required: ["success"],
268
- },
269
-
270
- async run(input, ctx) {
271
- ctx.log.info("Running ${id}", input);
272
-
273
- // Access config: ctx.config.get("MY_CONFIG")
274
- // Access state: await ctx.state.get("my_key") / await ctx.state.set("my_key", val)
275
- // Call action: await ctx.actions.invoke(otherAction, input)
276
-
277
- return {
278
- success: true,
279
- result: input.exampleParam || "done",
280
- };
281
- },
282
- });
283
- `;
284
-
285
- writeFileSync(targetFullFile, template, "utf-8");
286
-
287
- const manifest = loadManifest(root) || { schemaVersion: 1, actions: {}, assets: [] };
288
- manifest.actions = manifest.actions || {};
289
- manifest.actions[id] = {
290
- entry: join(config.actionsDir || "actions", targetRelFile),
291
- description: desc,
292
- inputSchema: {
293
- type: "object",
294
- properties: {
295
- exampleParam: {
296
- type: "string",
297
- description: "Example parameter description",
298
- },
299
- },
300
- required: [],
301
- },
302
- outputSchema: {
303
- type: "object",
304
- properties: {
305
- success: { type: "boolean" },
306
- result: {},
307
- },
308
- required: ["success"],
309
- },
310
- uses: [],
311
- tags: [],
312
- };
313
- saveManifest(root, manifest);
314
-
315
- console.log(`[OK] Created Action '${id}' at ${targetFullFile}`);
316
- console.log(`\nTo run this action:`);
317
- console.log(` ad action run ${id} --input '{"exampleParam": "hello"}'`);
318
- } catch (err: any) {
319
- console.error(`Error: ${err.message}`);
320
- process.exit(1);
321
- }
322
- });
323
-
324
- // action show / describe
325
- actionCmd
326
- .command("show <id>")
327
- .alias("describe")
328
- .description("Show action definition, schema, and description")
329
- .option("-p, --profile <name>", "Execute or query against a specific profile")
330
- .option("-s, --server <url>", "Remote server URL")
331
- .option("-t, --token <token>", "Auth token for remote server")
332
- .option("--json", "Output as JSON")
333
- .action(async (id, options) => {
334
- try {
335
- const target = resolveTarget({
336
- profile: options.profile,
337
- server: options.server,
338
- token: options.token,
339
- });
340
-
341
- if (target.type === "remote") {
342
- const detail = await fetchRemoteActionShow(
343
- target.serverUrl!,
344
- id,
345
- target.token
346
- );
347
- if (options.json) {
348
- console.log(JSON.stringify(detail, null, 2));
349
- } else {
350
- console.log(`Action: ${detail.id}`);
351
- if (detail.packageId) console.log(`Package: ${detail.packageId}`);
352
- if (detail.description) console.log(`Description: ${detail.description}`);
353
- if (detail.inputSchema) {
354
- console.log("\nInput Schema:");
355
- console.log(JSON.stringify(detail.inputSchema, null, 2));
356
- }
357
- if (detail.outputSchema) {
358
- console.log("\nOutput Schema:");
359
- console.log(JSON.stringify(detail.outputSchema, null, 2));
360
- }
361
- }
362
- return;
363
- }
364
-
365
- const resolved = await resolveActionProject(id);
366
- const config = loadProjectConfig(resolved.projectRoot);
367
- const actions = await loadActions(resolved.projectRoot, config.actionsDir);
368
- const action = actions.get(resolved.actionId);
369
- if (!action) {
370
- console.error(`Error: Action '${resolved.actionId}' not found in package '${resolved.packageId}'`);
371
- process.exit(1);
372
- }
373
-
374
- if (options.json) {
375
- console.log(
376
- JSON.stringify(
377
- {
378
- id: action.id,
379
- packageId: resolved.packageId,
380
- description: action.description,
381
- inputSchema: action.inputSchema,
382
- outputSchema: action.outputSchema,
383
- },
384
- null,
385
- 2
386
- )
387
- );
388
- } else {
389
- console.log(`Action: ${action.id}`);
390
- console.log(`Package: ${resolved.packageId} (${resolved.projectRoot})`);
391
- if (action.description) console.log(`Description: ${action.description}`);
392
- if (action.inputSchema) {
393
- console.log("\nInput Schema:");
394
- console.log(JSON.stringify(action.inputSchema, null, 2));
395
- }
396
- if (action.outputSchema) {
397
- console.log("\nOutput Schema:");
398
- console.log(JSON.stringify(action.outputSchema, null, 2));
399
- }
400
- }
401
- } catch (err: any) {
402
- console.error(`Error: ${err.message}`);
403
- process.exit(1);
404
- }
405
- });
406
-
407
- // action validate
408
- actionCmd
409
- .command("validate [id]")
410
- .description("Validate action schemas and definitions")
411
- .option("--json", "Output as JSON")
412
- .action(async (id, options) => {
413
- const root = findProjectRoot();
414
- if (!root) {
415
- console.error("Error: Not in an ActionDock project");
416
- process.exit(1);
417
- }
418
- try {
419
- const config = loadProjectConfig(root);
420
- const actions = await loadActions(root, config.actionsDir);
421
-
422
- const toValidate = id ? [actions.get(id)].filter(Boolean) : Array.from(actions.values());
423
- if (id && toValidate.length === 0) {
424
- console.error(`Error: Action '${id}' not found in project`);
425
- process.exit(1);
426
- }
427
-
428
- const results: Array<{ id: string; valid: boolean; errors: string[] }> = [];
429
-
430
- for (const act of toValidate as any[]) {
431
- const errors: string[] = [];
432
- if (!act.id) errors.push("Missing id property");
433
- if (!act.run || typeof act.run !== "function") errors.push("Missing run method");
434
- if (act.inputSchema) {
435
- const vRes = validateSchema(act.inputSchema, {});
436
- // Check if schema itself is valid
437
- if (typeof act.inputSchema !== "object") {
438
- errors.push("Invalid inputSchema object");
439
- }
440
- }
441
- if (act.outputSchema) {
442
- if (typeof act.outputSchema !== "object") {
443
- errors.push("Invalid outputSchema object");
444
- }
445
- }
446
- results.push({
447
- id: act.id,
448
- valid: errors.length === 0,
449
- errors,
450
- });
451
- }
452
-
453
- const allValid = results.every((r) => r.valid);
454
- if (options.json) {
455
- console.log(JSON.stringify({ valid: allValid, results }, null, 2));
456
- } else {
457
- for (const r of results) {
458
- if (r.valid) {
459
- console.log(`[OK] ${r.id}: Valid`);
460
- } else {
461
- console.log(`[FAIL] ${r.id}: ${r.errors.join(", ")}`);
462
- }
463
- }
464
- }
465
- if (!allValid) process.exit(1);
466
- } catch (err: any) {
467
- console.error(`Error: ${err.message}`);
468
- process.exit(1);
469
- }
470
- });
471
-
472
- // action run
473
- actionCmd
474
- .command("run <id>")
475
- .description("Execute an action (from current project, linked packages, or remote profile)")
476
- .option("-i, --input <json>", "Input as JSON string")
477
- .option("-f, --input-file <path>", "Input from JSON file")
478
- .option("-c, --config <key=value...>", "Temporary config override (repeatable or comma-separated)")
479
- .option("-p, --profile <name>", "Execute against a specific profile")
480
- .option("-s, --server <url>", "Remote server URL")
481
- .option("-t, --token <token>", "Auth token for remote server")
482
- .option("--timeout <duration>", "Execution timeout (e.g. 30s, 5m, 500ms)")
483
- .option("--async", "Execute asynchronously in background (requires remote server or profile)")
484
- .action(async (id, options) => {
485
- await executeAction(id, options);
486
- });
487
-
488
- // Root level alias: ad run <id>
489
- program
490
- .command("run <id>")
491
- .description("Alias for 'ad action run <id>'")
492
- .option("-i, --input <json>", "Input as JSON string")
493
- .option("-f, --input-file <path>", "Input from JSON file")
494
- .option("-c, --config <key=value...>", "Temporary config override")
495
- .option("-p, --profile <name>", "Execute against a specific profile")
496
- .option("-s, --server <url>", "Remote server URL")
497
- .option("-t, --token <token>", "Auth token for remote server")
498
- .option("--timeout <duration>", "Execution timeout (e.g. 30s, 5m, 500ms)")
499
- .option("--async", "Execute asynchronously in background (requires remote server or profile)")
500
- .action(async (id, options) => {
501
- await executeAction(id, options);
502
- });
503
- }
504
-
505
- async function executeAction(id: string, options: any): Promise<void> {
506
- let input: unknown = {};
507
- if (options.input) {
508
- try {
509
- input = JSON.parse(options.input);
510
- } catch (err: any) {
511
- console.error(`Error parsing --input JSON: ${err.message}`);
512
- process.exit(1);
513
- }
514
- } else if (options.inputFile) {
515
- try {
516
- input = JSON.parse(readFileSync(options.inputFile, "utf-8"));
517
- } catch (err: any) {
518
- console.error(`Error reading --input-file: ${err.message}`);
519
- process.exit(1);
520
- }
521
- }
522
-
523
- let timeoutMs: number | undefined;
524
- if (options.timeout) {
525
- try {
526
- timeoutMs = parseDuration(options.timeout);
527
- } catch (err: any) {
528
- console.error(`Error: ${err.message}`);
529
- process.exit(1);
530
- }
531
- }
532
-
533
- const configOverrides: Record<string, unknown> = {};
534
- if (options.config) {
535
- const list = Array.isArray(options.config) ? options.config : [options.config];
536
- for (const item of list) {
537
- const [k, ...v] = item.split("=");
538
- if (k) configOverrides[k] = v.join("=");
539
- }
540
- }
541
-
542
- // Check target (remote profile vs local)
543
- let target;
544
- try {
545
- target = resolveTarget({
546
- profile: options.profile,
547
- server: options.server,
548
- token: options.token,
549
- });
550
- } catch (err: any) {
551
- console.error(`Error: ${err.message}`);
552
- process.exit(1);
553
- }
554
-
555
- const controller = new AbortController();
556
- const sigintHandler = () => {
557
- controller.abort(new Error("Interrupted by SIGINT"));
558
- };
559
- process.once("SIGINT", sigintHandler);
560
-
561
- try {
562
- if (target.type === "remote") {
563
- try {
564
- const result = await executeRemoteAction(target.serverUrl!, id, input, {
565
- configOverrides,
566
- token: target.token,
567
- timeoutMs,
568
- signal: controller.signal,
569
- async: Boolean(options.async),
570
- });
571
- console.log(JSON.stringify(result, null, 2));
572
- if (!result.ok) {
573
- process.exit(1);
574
- }
575
- return;
576
- } catch (err: any) {
577
- console.error(`Error: ${err.message}`);
578
- process.exit(1);
579
- }
580
- }
581
-
582
- // Local execution
583
- if (options.async) {
584
- console.error(
585
- "Error: Async execution requires a long-running ActionDock server.\nUse --profile, --server, or start `ad serve`."
586
- );
587
- process.exit(1);
588
- }
589
-
590
- let resolvedProjectRoot: string;
591
- let resolvedActionId: string;
592
-
593
- try {
594
- const resolved = await resolveActionProject(id);
595
- resolvedProjectRoot = resolved.projectRoot;
596
- resolvedActionId = resolved.actionId;
597
- } catch (err: any) {
598
- console.error(`Error: ${err.message}`);
599
- process.exit(1);
600
- }
601
-
602
- try {
603
- const config = loadProjectConfig(resolvedProjectRoot);
604
- const actions = await loadActions(resolvedProjectRoot, config.actionsDir);
605
- const storage = createStorage(config.id, { projectRoot: resolvedProjectRoot });
606
-
607
- const runner = new ActionRunner({
608
- packageId: config.id,
609
- storage,
610
- projectConfig: config,
611
- configOverrides,
612
- actions,
613
- });
614
-
615
- const result = await runner.execute(resolvedActionId, input, {
616
- signal: controller.signal,
617
- timeoutMs,
618
- });
619
- console.log(JSON.stringify(result, null, 2));
620
- storage.close();
621
-
622
- if (!result.ok) {
623
- process.exit(1);
624
- }
625
- } catch (err: any) {
626
- console.error(`Error: ${err.message}`);
627
- process.exit(1);
628
- }
629
- } finally {
630
- process.removeListener("SIGINT", sigintHandler);
631
- }
632
- }
633
-
1
+ export * from "./action/index";
@@ -1,4 +1,6 @@
1
- import { buildProject, findProjectRoot, resolvePackageRoot } from "@actiondock/core";
1
+ import { buildProject } from "@actiondock/builder";
2
+ import { findProjectRoot, resolvePackageRoot } from "@actiondock/core";
3
+ import { ExecutionError } from "@actiondock/runtime-cli";
2
4
  import { Command } from "commander";
3
5
 
4
6
  export function registerBuildCommand(program: Command): void {
@@ -20,18 +22,18 @@ export function registerBuildCommand(program: Command): void {
20
22
 
21
23
  if (!root) {
22
24
  if (options.package) {
23
- console.error(`Error: Package '${options.package}' not found in linked packages or path`);
24
- } else {
25
- console.error(
26
- "Error: Not in an ActionDock project (actiondock.json not found).\nPlease specify -P, --package <id> or cd into a project directory."
27
- );
25
+ throw new ExecutionError(`Package '${options.package}' not found in linked packages or path`);
28
26
  }
29
- process.exit(1);
27
+ throw new ExecutionError(
28
+ "Not in an ActionDock project (actiondock.json not found).\nPlease specify -P, --package <id> or cd into a project directory."
29
+ );
30
30
  }
31
31
 
32
+ console.log("Building standalone executable...");
33
+
34
+ let result;
32
35
  try {
33
- console.log("Building standalone executable...");
34
- const result = await buildProject({
36
+ result = await buildProject({
35
37
  projectRoot: root,
36
38
  target: options.target,
37
39
  outfile: options.out,
@@ -39,16 +41,16 @@ export function registerBuildCommand(program: Command): void {
39
41
  bytecode: options.bytecode,
40
42
  actions: options.actions,
41
43
  });
44
+ } catch (err: any) {
45
+ throw new ExecutionError(`Build failed: ${err.message}`);
46
+ }
42
47
 
43
- console.log(`[OK] Successfully compiled ${result.packageId} (v${result.version})`);
44
- console.log(` Target: ${result.target}`);
45
- console.log(` Actions: ${result.actions.join(", ")}`);
46
- console.log(` Executable: ${result.executablePath}`);
48
+ console.log(`[OK] Successfully compiled ${result.packageId} (v${result.version})`);
49
+ console.log(` Target: ${result.target}`);
50
+ console.log(` Actions: ${result.actions.join(", ")}`);
51
+ console.log(` Executable: ${result.executablePath}`);
52
+ if (result.metadataPath) {
47
53
  console.log(` Metadata: ${result.metadataPath}`);
48
- } catch (err: any) {
49
- console.error(`Build failed: ${err.message}`);
50
- process.exit(1);
51
54
  }
52
55
  });
53
56
  }
54
-