@actiondock/cli 2.0.12 → 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 (100) hide show
  1. package/README.md +41 -34
  2. package/bin/ad.js +28 -41
  3. package/dist/commands/action/create.d.ts +9 -1
  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 +2 -1
  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 +2 -1
  26. package/dist/commands/doctor.js +82 -0
  27. package/dist/commands/export.d.ts +2 -1
  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 +31 -1
  32. package/dist/commands/index.js +111 -0
  33. package/dist/commands/info.d.ts +9 -2
  34. package/dist/commands/info.js +210 -0
  35. package/dist/commands/init.js +24 -0
  36. package/dist/commands/link.js +71 -0
  37. package/dist/commands/list.d.ts +9 -0
  38. package/dist/commands/list.js +77 -0
  39. package/dist/commands/mcp.d.ts +9 -2
  40. package/dist/commands/mcp.js +172 -0
  41. package/dist/commands/new.d.ts +9 -0
  42. package/dist/commands/new.js +33 -0
  43. package/dist/commands/pack.d.ts +3 -0
  44. package/dist/commands/pack.js +63 -0
  45. package/dist/commands/playbook.d.ts +7 -3
  46. package/dist/commands/playbook.js +433 -0
  47. package/dist/commands/profile.js +253 -0
  48. package/dist/commands/remove.d.ts +7 -0
  49. package/dist/commands/remove.js +118 -0
  50. package/dist/commands/run.d.ts +13 -0
  51. package/dist/commands/run.js +146 -0
  52. package/dist/commands/runs.d.ts +9 -2
  53. package/dist/commands/runs.js +245 -0
  54. package/dist/commands/serve.d.ts +9 -2
  55. package/dist/commands/serve.js +129 -0
  56. package/dist/commands/state-list.d.ts +24 -0
  57. package/dist/commands/state-list.js +82 -0
  58. package/dist/commands/state.d.ts +9 -2
  59. package/dist/commands/state.js +310 -0
  60. package/dist/commands/test.js +44 -0
  61. package/dist/commands/validate.d.ts +9 -0
  62. package/dist/commands/validate.js +106 -0
  63. package/dist/errors.d.ts +43 -0
  64. package/dist/errors.js +103 -0
  65. package/dist/index.d.ts +8 -1
  66. package/dist/index.js +41 -1000
  67. package/dist/prompt.d.ts +53 -0
  68. package/dist/prompt.js +192 -0
  69. package/dist/renderer.d.ts +197 -0
  70. package/dist/renderer.js +502 -0
  71. package/dist/services/index.d.ts +1 -0
  72. package/dist/services/index.js +1 -0
  73. package/dist/services/inspect.d.ts +23 -0
  74. package/dist/services/inspect.js +96 -0
  75. package/dist/standalone/index.d.ts +16 -0
  76. package/dist/standalone/index.js +22 -0
  77. package/dist/types.d.ts +132 -0
  78. package/dist/types.js +9 -0
  79. package/dist/utils/bytes.d.ts +1 -1
  80. package/dist/utils/bytes.js +1 -0
  81. package/dist/utils/duration.js +1 -0
  82. package/dist/utils/filter.d.ts +1 -1
  83. package/dist/utils/filter.js +1 -0
  84. package/dist/utils/index.d.ts +6 -0
  85. package/dist/utils/index.js +6 -0
  86. package/dist/utils/spawn.d.ts +27 -0
  87. package/dist/utils/spawn.js +35 -0
  88. package/dist/utils/target.d.ts +45 -0
  89. package/dist/utils/target.js +78 -0
  90. package/dist/utils/utils.d.ts +38 -0
  91. package/dist/utils/utils.js +111 -0
  92. package/package.json +10 -12
  93. package/dist/commands/action/index.d.ts +0 -16
  94. package/dist/commands/action/list.d.ts +0 -5
  95. package/dist/commands/action/run.d.ts +0 -4
  96. package/dist/commands/action/show.d.ts +0 -5
  97. package/dist/commands/action/sync.d.ts +0 -2
  98. package/dist/commands/action/validate.d.ts +0 -5
  99. package/dist/commands/action.d.ts +0 -1
  100. package/dist/commands/config.d.ts +0 -2
@@ -0,0 +1,7 @@
1
+ import { Command } from "commander";
2
+ import type { CliContext } from "../types.js";
3
+ /**
4
+ * 注册 ad remove 依赖移除命令。
5
+ * 遵循规范:移除依赖并更新锁文件,保留该包的历史存储数据,检查反向引用防止误删。
6
+ */
7
+ export declare function registerRemoveCommand(program: Command, context?: CliContext): void;
@@ -0,0 +1,118 @@
1
+ import { resolve } from "node:path";
2
+ import { beginTransaction, findProjectRoot, getInstallCommand, loadLockfile, loadManifest, saveLockfile, saveManifest, } from "@actiondock/core";
3
+ import { ArgumentError, ExecutionError } from "../errors.js";
4
+ import { renderResult } from "../renderer.js";
5
+ import { getEffectiveOptions, spawnAsync } from "../utils/index.js";
6
+ /**
7
+ * 注册 ad remove 依赖移除命令。
8
+ * 遵循规范:移除依赖并更新锁文件,保留该包的历史存储数据,检查反向引用防止误删。
9
+ */
10
+ export function registerRemoveCommand(program, context) {
11
+ program
12
+ .command("remove <package>")
13
+ .description("Remove an Action package dependency and update actiondock.lock.json")
14
+ .option("-P, --package <path>", "Target project directory path")
15
+ .option("--json", "Output as JSON")
16
+ .option("--envelope", "Wrap JSON output in standard envelope")
17
+ .action(async (packageIdentifier, rawOptions, cmd) => {
18
+ const options = getEffectiveOptions(rawOptions, cmd);
19
+ const root = options.package ? resolve(options.package) : findProjectRoot();
20
+ if (!root) {
21
+ throw new ArgumentError("Not in an ActionDock project (actiondock.json not found).\nPlease specify -P, --package <path> or cd into a project directory.");
22
+ }
23
+ const manifest = loadManifest(root);
24
+ if (!manifest) {
25
+ throw new ArgumentError(`actiondock.json not found in ${root}`);
26
+ }
27
+ // 寻址逻辑包标识
28
+ let targetPackageId = null;
29
+ let npmPackageName = packageIdentifier;
30
+ if (manifest.dependencies?.[packageIdentifier]) {
31
+ targetPackageId = packageIdentifier;
32
+ npmPackageName = manifest.dependencies[packageIdentifier];
33
+ }
34
+ else if (manifest.dependencies) {
35
+ for (const [id, spec] of Object.entries(manifest.dependencies)) {
36
+ if (spec === packageIdentifier || id === packageIdentifier) {
37
+ targetPackageId = id;
38
+ npmPackageName = spec;
39
+ break;
40
+ }
41
+ }
42
+ }
43
+ if (!targetPackageId) {
44
+ throw new ArgumentError(`Package '${packageIdentifier}' is not a declared dependency in actiondock.json`);
45
+ }
46
+ // 检查当前工程中 Action 的反向 uses 依赖声明
47
+ if (manifest.actions) {
48
+ for (const [actionId, actionEntry] of Object.entries(manifest.actions)) {
49
+ if (actionEntry.uses && Array.isArray(actionEntry.uses)) {
50
+ const hasRef = actionEntry.uses.some((u) => u === targetPackageId ||
51
+ u === `${targetPackageId}/*` ||
52
+ u.startsWith(`${targetPackageId}/`));
53
+ if (hasRef) {
54
+ throw new ExecutionError(`Cannot remove package '${targetPackageId}': action '${actionId}' declares dependency on it in 'uses'. Please remove the 'uses' declaration first.`, undefined, "DEPENDENCY_CONFLICT");
55
+ }
56
+ }
57
+ }
58
+ }
59
+ // 检查当前工程中 Playbook 的反向动作引用
60
+ if (manifest.playbooks) {
61
+ for (const [pbId, pbEntry] of Object.entries(manifest.playbooks)) {
62
+ if (pbEntry.actions && Array.isArray(pbEntry.actions)) {
63
+ const hasRef = pbEntry.actions.some((a) => a === targetPackageId ||
64
+ a === `${targetPackageId}/*` ||
65
+ a.startsWith(`${targetPackageId}/`));
66
+ if (hasRef) {
67
+ throw new ExecutionError(`Cannot remove package '${targetPackageId}': playbook '${pbId}' references it in 'actions'. Please update the playbook first.`, undefined, "DEPENDENCY_CONFLICT");
68
+ }
69
+ }
70
+ }
71
+ }
72
+ // 获取排他锁与事务快照保护
73
+ const tx = await beginTransaction(root, `ad remove ${packageIdentifier}`);
74
+ try {
75
+ // 从 actiondock.json 中移除依赖声明
76
+ if (manifest.dependencies) {
77
+ delete manifest.dependencies[targetPackageId];
78
+ if (Object.keys(manifest.dependencies).length === 0) {
79
+ delete manifest.dependencies;
80
+ }
81
+ saveManifest(root, manifest);
82
+ }
83
+ // 从 actiondock.lock.json 中移除锁定记录
84
+ const lockfile = loadLockfile(root);
85
+ if (lockfile && lockfile.packages[targetPackageId]) {
86
+ delete lockfile.packages[targetPackageId];
87
+ saveLockfile(root, lockfile);
88
+ }
89
+ // 调用包管理器执行卸载
90
+ const installCmd = getInstallCommand(root);
91
+ const pm = installCmd[0];
92
+ const args = pm === "bun" ? ["remove", npmPackageName] : ["uninstall", npmPackageName, "--ignore-scripts"];
93
+ const uninstallProc = await spawnAsync(pm, args, {
94
+ cwd: root,
95
+ stdio: "pipe",
96
+ });
97
+ // 卸载失败不阻断事务提交(与原同步实现一致:忽略退出码)
98
+ void uninstallProc;
99
+ // 提交事务
100
+ await tx.commit();
101
+ const resultPayload = {
102
+ packageId: targetPackageId,
103
+ npmPackage: npmPackageName,
104
+ retainedNamespace: targetPackageId,
105
+ };
106
+ renderResult(resultPayload, {
107
+ json: options.json,
108
+ envelope: options.envelope,
109
+ humanFormatter: () => `Successfully removed '${resultPayload.packageId}'.\n[INFO] Retained configuration and state namespace for package '${resultPayload.packageId}'.`,
110
+ context,
111
+ });
112
+ }
113
+ catch (err) {
114
+ await tx.rollback({ frozenInstall: false });
115
+ throw new ExecutionError(err.message, err, err.code || "REMOVE_DEPENDENCY_FAILED");
116
+ }
117
+ });
118
+ }
@@ -0,0 +1,13 @@
1
+ import { Command } from "commander";
2
+ import type { CliContext } from "../types.js";
3
+ /**
4
+ * 统一执行 Action 核心逻辑(使用 ActionDockTarget 门面)。
5
+ */
6
+ export declare function executeAction(id: string, options: any, context?: CliContext): Promise<void>;
7
+ /**
8
+ * 注册顶层统一 run 命令:执行指定 Action。
9
+ *
10
+ * @param program Commander 根程序对象
11
+ * @param context 命令行上下文
12
+ */
13
+ export declare function registerRunCommand(program: Command, context?: CliContext): void;
@@ -0,0 +1,146 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { parseDuration, resolveTarget } from "@actiondock/core";
3
+ import { ArgumentError, ExecutionError, SigintError } from "../errors.js";
4
+ import { writeStdout } from "../renderer.js";
5
+ import { getEffectiveOptions, resolveLocalPackageRoot, withTarget } from "../utils/index.js";
6
+ /**
7
+ * 统一执行 Action 核心逻辑(使用 ActionDockTarget 门面)。
8
+ */
9
+ export async function executeAction(id, options, context) {
10
+ if (!id) {
11
+ throw new ArgumentError("Action ID is required for run");
12
+ }
13
+ let input = {};
14
+ if (options.input) {
15
+ try {
16
+ input = JSON.parse(options.input);
17
+ }
18
+ catch (err) {
19
+ throw new ArgumentError(`Error parsing --input JSON: ${err.message}`);
20
+ }
21
+ }
22
+ else if (options.inputFile) {
23
+ try {
24
+ input = JSON.parse(readFileSync(options.inputFile, "utf-8"));
25
+ }
26
+ catch (err) {
27
+ throw new ArgumentError(`Error reading --input-file: ${err.message}`);
28
+ }
29
+ }
30
+ let timeoutMs;
31
+ if (options.timeout) {
32
+ try {
33
+ timeoutMs = parseDuration(options.timeout);
34
+ }
35
+ catch (err) {
36
+ throw new ArgumentError(`Invalid timeout format: ${err.message}`);
37
+ }
38
+ }
39
+ const configOverrides = {};
40
+ if (options.config) {
41
+ const list = Array.isArray(options.config) ? options.config : [options.config];
42
+ for (const item of list) {
43
+ const [k, ...v] = item.split("=");
44
+ if (k)
45
+ configOverrides[k] = v.join("=");
46
+ }
47
+ }
48
+ const controller = new AbortController();
49
+ let receivedSigint = false;
50
+ const sigintHandler = () => {
51
+ receivedSigint = true;
52
+ controller.abort(new Error("Interrupted by SIGINT"));
53
+ };
54
+ process.once("SIGINT", sigintHandler);
55
+ try {
56
+ // 异步执行仅支持远端目标,本地模式直接拒绝
57
+ const resolvedTopology = resolveTarget({
58
+ profile: options.profile,
59
+ server: options.server,
60
+ token: options.token,
61
+ }, context?.customHome);
62
+ if (resolvedTopology.type === "local" && options.async) {
63
+ throw new ExecutionError("Async execution requires a long-running ActionDock server.\nUse --profile, --server, or start 'ad serve'.");
64
+ }
65
+ // 目标拓扑解析(仅 local 分支需要包寻址)
66
+ const targetPackageRoot = resolveLocalPackageRoot(options.package);
67
+ if (options.package && !targetPackageRoot) {
68
+ throw new ArgumentError(`Package '${options.package}' not found in linked packages or path`);
69
+ }
70
+ let targetRef = id;
71
+ if (options.package && !id.includes("/") && !id.includes(":")) {
72
+ targetRef = `${options.package}/${id}`;
73
+ }
74
+ // 通过 Target 统一执行
75
+ await withTarget(options, context, async (target) => {
76
+ if (options.async) {
77
+ const ticket = await target.startAction(targetRef, input, {
78
+ signal: controller.signal,
79
+ timeoutMs,
80
+ config: configOverrides,
81
+ requestId: options.requestId,
82
+ });
83
+ const asyncOutput = {
84
+ ok: ticket.status !== "failed",
85
+ runId: ticket.runId,
86
+ status: ticket.status,
87
+ };
88
+ writeStdout(JSON.stringify(asyncOutput, null, 2), context);
89
+ if (ticket.status === "failed") {
90
+ process.exitCode = 1;
91
+ return;
92
+ }
93
+ }
94
+ else {
95
+ const result = await target.runAction(targetRef, input, {
96
+ signal: controller.signal,
97
+ timeoutMs,
98
+ config: configOverrides,
99
+ requestId: options.requestId,
100
+ });
101
+ writeStdout(JSON.stringify(result, null, 2), context);
102
+ if (!result.ok) {
103
+ process.exitCode = 1;
104
+ return;
105
+ }
106
+ }
107
+ }, { localRoot: targetPackageRoot || undefined, scanLinkedPackages: true });
108
+ }
109
+ catch (err) {
110
+ if (receivedSigint || err?.name === "AbortError" || err?.message?.includes("SIGINT")) {
111
+ throw new SigintError();
112
+ }
113
+ throw err;
114
+ }
115
+ finally {
116
+ process.removeListener("SIGINT", sigintHandler);
117
+ }
118
+ }
119
+ /**
120
+ * 注册顶层统一 run 命令:执行指定 Action。
121
+ *
122
+ * @param program Commander 根程序对象
123
+ * @param context 命令行上下文
124
+ */
125
+ export function registerRunCommand(program, context) {
126
+ program
127
+ .command("run <id>")
128
+ .description("Execute an action (from current project, linked packages, or remote profile)")
129
+ .option("-P, --package <id>", "Target package ID or path")
130
+ .option("-i, --input <json>", "Input as JSON string")
131
+ .option("-f, --input-file <path>", "Input from JSON file")
132
+ .option("-c, --config <key=value...>", "Temporary config override (repeatable)")
133
+ .option("-p, --profile <name>", "Execute against a specific profile")
134
+ .option("-s, --server <url>", "Remote server URL")
135
+ .option("-t, --token <token>", "Auth token for remote server")
136
+ .option("--timeout <duration>", "Execution timeout (e.g. 30s, 5m, 500ms)")
137
+ .option("--request-id <id>", "Idempotency request ID for deduplication")
138
+ .option("--async", "Execute asynchronously in background (requires remote server or profile)")
139
+ .option("--data-dir <path>", "Custom database directory")
140
+ .option("--json", "Output as JSON")
141
+ .option("--envelope", "Wrap JSON output in standard envelope")
142
+ .action(async (id, rawOptions, cmd) => {
143
+ const options = getEffectiveOptions(rawOptions, cmd);
144
+ await executeAction(id, options, context);
145
+ });
146
+ }
@@ -1,2 +1,9 @@
1
- import type { Command } from "commander";
2
- export declare function registerRunsCommands(program: Command): void;
1
+ import { Command } from "commander";
2
+ import type { CliContext } from "../types.js";
3
+ /**
4
+ * 注册 runs 动作执行历史管理命令(list、show、clear、cancel)。
5
+ *
6
+ * @param program Commander 实例
7
+ * @param context 命令行上下文
8
+ */
9
+ export declare function registerRunsCommands(program: Command, context?: CliContext): void;
@@ -0,0 +1,245 @@
1
+ import { filterWithFallbackInfo, findProjectRoot, listLinkedPackages, loadProjectConfig, resolvePackageRoot, resolveTarget, } from "@actiondock/core";
2
+ import { ArgumentError, ExecutionError } from "../errors.js";
3
+ import { renderResult, renderRunDetail, renderRunsList } from "../renderer.js";
4
+ import { getEffectiveOptions, resolveIntent, withRemoteTarget, withTarget } from "../utils/index.js";
5
+ /**
6
+ * 解析本地目标包根目录与工程配置(仅在 local 分支调用)。
7
+ * 显式指定包且寻址失败时抛出参数错误;工程清单损坏时降级为链接包视图。
8
+ */
9
+ function resolveLocalRunScope(packageOption) {
10
+ if (packageOption) {
11
+ const root = resolvePackageRoot(packageOption);
12
+ if (!root) {
13
+ throw new ArgumentError(`Package '${packageOption}' not found in linked packages or path`);
14
+ }
15
+ try {
16
+ return { targetPackageRoot: root, projConfig: loadProjectConfig(root) };
17
+ }
18
+ catch {
19
+ // 工程清单损坏时降级为链接包作用域视图
20
+ return { targetPackageRoot: root, projConfig: null };
21
+ }
22
+ }
23
+ const root = findProjectRoot();
24
+ if (!root) {
25
+ return { targetPackageRoot: undefined, projConfig: null };
26
+ }
27
+ try {
28
+ return { targetPackageRoot: root, projConfig: loadProjectConfig(root) };
29
+ }
30
+ catch {
31
+ // 工程清单损坏时降级为链接包作用域视图
32
+ return { targetPackageRoot: root, projConfig: null };
33
+ }
34
+ }
35
+ /**
36
+ * 注册 runs 动作执行历史管理命令(list、show、clear、cancel)。
37
+ *
38
+ * @param program Commander 实例
39
+ * @param context 命令行上下文
40
+ */
41
+ export function registerRunsCommands(program, context) {
42
+ const runsCmd = program
43
+ .command("runs")
44
+ .description("Inspect action execution history");
45
+ // runs list
46
+ runsCmd
47
+ .command("list [patterns...]")
48
+ .description("List recent execution records")
49
+ .option("-P, --package <id>", "Target package ID or path")
50
+ .option("-i, --intent <pattern>", "Regex or fuzzy intent filter; falls back to full list when no match")
51
+ .option("-a, --action <actionId>", "Filter by action ID")
52
+ .option("-n, --limit <count>", "Maximum number of records to return", "20")
53
+ .option("-p, --profile <name>", "Query against a specific profile")
54
+ .option("-s, --server <url>", "Remote server URL")
55
+ .option("-t, --token <token>", "Auth token for remote server")
56
+ .option("--no-fallback", "Disable fallback to full list when no items match intent")
57
+ .option("--data-dir <path>", "Custom database storage directory")
58
+ .option("--json", "Output as JSON")
59
+ .option("--envelope", "Wrap JSON output in standard envelope")
60
+ .action(async (patterns = [], rawOptions, cmd) => {
61
+ const options = getEffectiveOptions(rawOptions, cmd);
62
+ const effectiveIntent = resolveIntent(options.intent, patterns);
63
+ const shouldFallback = options.fallback !== false;
64
+ const limit = Number.parseInt(options.limit, 10) || 20;
65
+ const scope = resolveLocalRunScope(options.package);
66
+ // 通过 Target 门面统一访问
67
+ await withTarget(options, context, async (target, resolved) => {
68
+ const records = await target.listRuns({
69
+ packageId: scope.projConfig?.id || options.package,
70
+ actionId: options.action,
71
+ intent: effectiveIntent,
72
+ limit,
73
+ });
74
+ // 若处于本地无项目环境且无任何软链接包,则直接输出友好提示
75
+ if (resolved.type === "local" && !scope.targetPackageRoot) {
76
+ const linked = listLinkedPackages(context?.customHome);
77
+ if (linked.length === 0) {
78
+ renderResult([], {
79
+ json: options.json,
80
+ envelope: options.envelope,
81
+ humanFormatter: () => "No ActionDock project in current directory, and no packages linked.",
82
+ context,
83
+ });
84
+ return;
85
+ }
86
+ }
87
+ const filterRes = filterWithFallbackInfo(records, effectiveIntent, [(r) => r.id, (r) => r.actionId, (r) => r.packageId, (r) => r.status, (r) => r.error?.message], shouldFallback);
88
+ const capped = filterRes.items.slice(0, limit);
89
+ renderResult(capped, {
90
+ json: options.json,
91
+ envelope: options.envelope,
92
+ humanFormatter: () => {
93
+ let title = "Execution Runs";
94
+ if (resolved.type === "remote") {
95
+ title = `Execution Runs on remote server ${resolved.serverUrl}${resolved.profileName ? ` (Profile: ${resolved.profileName})` : ""}`;
96
+ }
97
+ else if (scope.projConfig) {
98
+ title = `Execution Runs in ${scope.projConfig.name} (${scope.projConfig.id})`;
99
+ }
100
+ else {
101
+ title = "Execution Runs (Linked Packages)";
102
+ }
103
+ return renderRunsList(capped, title, filterRes.isFallback, effectiveIntent);
104
+ },
105
+ context,
106
+ });
107
+ }, { localRoot: scope.targetPackageRoot, scanLinkedPackages: true });
108
+ });
109
+ // runs show <id>
110
+ runsCmd
111
+ .command("show <id>")
112
+ .description("Show details of a specific execution run")
113
+ .option("-p, --profile <name>", "Query run against a specific profile")
114
+ .option("-s, --server <url>", "Remote server URL")
115
+ .option("-t, --token <token>", "Auth token for remote server")
116
+ .option("-P, --package <id>", "Target package ID or path")
117
+ .option("--data-dir <path>", "Custom database storage directory")
118
+ .option("--json", "Output as JSON")
119
+ .option("--envelope", "Wrap JSON output in standard envelope")
120
+ .action(async (id, rawOptions, cmd) => {
121
+ const options = getEffectiveOptions(rawOptions, cmd);
122
+ if (!id) {
123
+ throw new ArgumentError("Run ID is required");
124
+ }
125
+ const scope = resolveLocalRunScope(options.package);
126
+ // 通过 Target 门面统一查询
127
+ await withTarget(options, context, async (target, resolved) => {
128
+ const run = await target.getRun(id);
129
+ if (!run) {
130
+ if (resolved.type === "remote") {
131
+ throw new ExecutionError(`Run record '${id}' not found on remote server`);
132
+ }
133
+ else if (options.package) {
134
+ throw new ExecutionError(`Run record '${id}' not found in package '${scope.projConfig?.id || options.package}'`);
135
+ }
136
+ else {
137
+ throw new ExecutionError(`Run record '${id}' not found in current project or any linked packages`);
138
+ }
139
+ }
140
+ renderResult(run, {
141
+ json: options.json,
142
+ envelope: options.envelope,
143
+ humanFormatter: () => renderRunDetail(run),
144
+ context,
145
+ });
146
+ }, { localRoot: scope.targetPackageRoot, scanLinkedPackages: true });
147
+ });
148
+ // runs cancel
149
+ runsCmd
150
+ .command("cancel <id>")
151
+ .description("Cancel a running action execution on a remote server")
152
+ .option("-p, --profile <name>", "Execute cancel against a specific profile")
153
+ .option("-s, --server <url>", "Remote server URL")
154
+ .option("-t, --token <token>", "Auth token for remote server")
155
+ .option("-r, --reason <reason>", "Reason for cancellation")
156
+ .option("--json", "Output as JSON")
157
+ .option("--envelope", "Wrap JSON output in standard envelope")
158
+ .action(async (id, rawOptions, cmd) => {
159
+ const options = getEffectiveOptions(rawOptions, cmd);
160
+ if (!id) {
161
+ throw new ArgumentError("Run ID is required for cancel");
162
+ }
163
+ const resolved = resolveTarget({
164
+ profile: options.profile,
165
+ server: options.server,
166
+ token: options.token,
167
+ }, context?.customHome);
168
+ if (resolved.type === "local") {
169
+ throw new ArgumentError("'ad runs cancel' is only supported for remote execution targets. Use --profile <name> or --server <url>.");
170
+ }
171
+ await withRemoteTarget(options, context, async (target) => {
172
+ const result = await target.cancelRun(id, options.reason);
173
+ const isErrorOutcome = result.outcome === "not_found" || result.outcome === "not_owner";
174
+ renderResult(result, {
175
+ json: options.json,
176
+ envelope: options.envelope,
177
+ humanFormatter: () => {
178
+ if (result.outcome === "not_found") {
179
+ return `Error: Run record '${id}' not found on remote server.`;
180
+ }
181
+ if (result.outcome === "not_owner") {
182
+ return `Error: Cannot cancel run '${id}': not the owner.`;
183
+ }
184
+ return `Run '${id}' cancellation requested (Status: ${result.status || result.outcome}).`;
185
+ },
186
+ context,
187
+ });
188
+ if (isErrorOutcome) {
189
+ process.exitCode = 1;
190
+ }
191
+ });
192
+ });
193
+ // runs clear
194
+ runsCmd
195
+ .command("clear")
196
+ .description("Clear execution run records")
197
+ .option("-P, --package <id>", "Target package ID or path")
198
+ .option("-a, --action <actionId>", "Filter by action ID")
199
+ .option("-p, --profile <name>", "Target profile")
200
+ .option("-s, --server <url>", "Remote server URL")
201
+ .option("-t, --token <token>", "Auth token for remote server")
202
+ .option("--data-dir <path>", "Custom database storage directory")
203
+ .option("--json", "Output as JSON")
204
+ .option("--envelope", "Wrap JSON output in standard envelope")
205
+ .action(async (rawOptions, cmd) => {
206
+ const options = getEffectiveOptions(rawOptions, cmd);
207
+ let targetPackageRoot;
208
+ let packageId = options.package;
209
+ if (!options.profile && !options.server) {
210
+ if (options.package) {
211
+ const root = resolvePackageRoot(options.package);
212
+ if (!root) {
213
+ throw new ArgumentError(`Package '${options.package}' not found in linked packages or path`);
214
+ }
215
+ targetPackageRoot = root;
216
+ }
217
+ else {
218
+ targetPackageRoot = findProjectRoot() || undefined;
219
+ if (!targetPackageRoot) {
220
+ throw new ArgumentError("Not in an ActionDock project. Please specify -P, --package <id> or cd into a project directory.");
221
+ }
222
+ }
223
+ try {
224
+ packageId = loadProjectConfig(targetPackageRoot).id;
225
+ }
226
+ catch {
227
+ // 清单损坏时保留原始 -P 参数作为包标识
228
+ }
229
+ }
230
+ await withTarget(options, context, async (target, resolved) => {
231
+ const count = target.clearRuns
232
+ ? await target.clearRuns({ packageId, actionId: options.action })
233
+ : 0;
234
+ const payload = { ok: true, clearedCount: count };
235
+ renderResult(payload, {
236
+ json: options.json,
237
+ envelope: options.envelope,
238
+ humanFormatter: () => resolved.type === "remote"
239
+ ? `Cleared ${count} execution run(s) on remote server.`
240
+ : `Cleared ${count} execution run(s) in package '${packageId}'.`,
241
+ context,
242
+ });
243
+ }, { localRoot: targetPackageRoot });
244
+ });
245
+ }
@@ -1,2 +1,9 @@
1
- import type { Command } from "commander";
2
- export declare function registerServeCommand(program: Command): void;
1
+ import { Command } from "commander";
2
+ import type { CliContext } from "../types.js";
3
+ /**
4
+ * 注册 serve HTTP 服务启动命令。
5
+ *
6
+ * @param program Commander 实例
7
+ * @param context 命令行上下文
8
+ */
9
+ export declare function registerServeCommand(program: Command, context?: CliContext): void;