@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,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
+ }
@@ -0,0 +1,9 @@
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;
@@ -0,0 +1,129 @@
1
+ import { resolve } from "node:path";
2
+ import { findProjectRoot, formatHostForUrl, loadProjectConfig, startActionDockServer } from "@actiondock/core";
3
+ import { createActionDockMcpServer } from "@actiondock/mcp";
4
+ import { createMcpHandler } from "@modelcontextprotocol/server";
5
+ import { ArgumentError, ExecutionError } from "../errors.js";
6
+ import { writeStderr, writeStdout } from "../renderer.js";
7
+ import { getEffectiveOptions, parseByteSize } from "../utils/index.js";
8
+ /**
9
+ * 注册 serve HTTP 服务启动命令。
10
+ *
11
+ * @param program Commander 实例
12
+ * @param context 命令行上下文
13
+ */
14
+ export function registerServeCommand(program, context) {
15
+ program
16
+ .command("serve")
17
+ .description("Start the ActionDock lightweight HTTP Runner server for remote execution")
18
+ .option("-p, --port <port>", "Port to listen on (default: 5177)", "5177")
19
+ .option("-H, --host <host>", "Host address to bind to (default: 127.0.0.1)", "127.0.0.1")
20
+ .option("-t, --token <token>", "Authentication token for securing the endpoint (or set ACTIONDOCK_TOKEN)")
21
+ .option("--allow-insecure-no-auth", "Allow non-loopback host binding without authentication token (INSECURE)")
22
+ .option("--cors-origin <origin>", "Allowed CORS origin (can be specified multiple times)", (val, prev = []) => [...prev, val], [])
23
+ .option("--max-body <size>", "Maximum allowed JSON request body size (e.g. 1mb, 500kb)", "1mb")
24
+ .option("--expose-debug-info", "Expose project root path in health and info responses")
25
+ .option("--no-mcp", "Disable unified MCP protocol endpoint at /mcp")
26
+ .option("--allow-query-token", "Allow passing authentication token via URL query parameter (?token=xxx)")
27
+ .option("-d, --dir <path>", "Project root directory (default: current working directory)")
28
+ .action(async (rawOptions, cmd) => {
29
+ const options = getEffectiveOptions(rawOptions, cmd);
30
+ const port = parseInt(options.port, 10) || 5177;
31
+ const host = options.host || "127.0.0.1";
32
+ const token = options.token || (typeof process !== "undefined" ? process.env?.ACTIONDOCK_TOKEN : undefined);
33
+ const allowInsecureNoAuth = Boolean(options.allowInsecureNoAuth);
34
+ const allowQueryToken = Boolean(options.allowQueryToken);
35
+ const corsOrigins = options.corsOrigin && options.corsOrigin.length > 0 ? options.corsOrigin : undefined;
36
+ const exposeDebugInfo = Boolean(options.exposeDebugInfo);
37
+ let maxBodyBytes;
38
+ if (options.maxBody) {
39
+ try {
40
+ maxBodyBytes = parseByteSize(options.maxBody);
41
+ }
42
+ catch (err) {
43
+ throw new ArgumentError(`Invalid max-body format: ${err.message}`);
44
+ }
45
+ }
46
+ const projectRoot = options.dir
47
+ ? resolve(options.dir)
48
+ : findProjectRoot(process.cwd());
49
+ let projectName = "Global Registry Mode";
50
+ if (projectRoot) {
51
+ try {
52
+ const config = loadProjectConfig(projectRoot);
53
+ projectName = `${config.name} (${config.id})`;
54
+ }
55
+ catch {
56
+ // 降级处理
57
+ }
58
+ }
59
+ let mcpHandler;
60
+ const enableMcp = options.mcp !== false;
61
+ if (enableMcp) {
62
+ // MCP 处理器创建失败时直接终止启动,避免横幅宣称不存在的端点
63
+ try {
64
+ const handler = createMcpHandler(() => {
65
+ return createActionDockMcpServer({
66
+ projectRoot: projectRoot || undefined,
67
+ });
68
+ }, {
69
+ onerror: (err) => {
70
+ writeStderr(`[MCP HTTP Error] ${err?.message || String(err)}`, context);
71
+ },
72
+ });
73
+ mcpHandler = async (req) => {
74
+ return handler.fetch(req);
75
+ };
76
+ }
77
+ catch (err) {
78
+ throw new ExecutionError(`Failed to initialize MCP endpoint: ${err?.message || String(err)}`, err);
79
+ }
80
+ }
81
+ try {
82
+ const server = await startActionDockServer({
83
+ port,
84
+ host,
85
+ token,
86
+ allowInsecureNoAuth,
87
+ allowQueryToken,
88
+ corsOrigins,
89
+ maxBodyBytes,
90
+ exposeDebugInfo,
91
+ enableMcp,
92
+ mcpHandler,
93
+ projectRoot: projectRoot || undefined,
94
+ });
95
+ const displayHost = formatHostForUrl(host);
96
+ const actualEndpointHost = formatHostForUrl(host === "0.0.0.0" ? "127.0.0.1" : host);
97
+ writeStdout(`\n======================================================`, context);
98
+ writeStdout(` ActionDock 2.0 HTTP Runner Server`, context);
99
+ writeStdout(`======================================================`, context);
100
+ writeStdout(` * Listening on: http://${displayHost}:${server.port}`, context);
101
+ writeStdout(` * Project: ${projectName}`, context);
102
+ if (projectRoot && exposeDebugInfo) {
103
+ writeStdout(` * Root Path: ${projectRoot}`, context);
104
+ }
105
+ const authDesc = token
106
+ ? (allowQueryToken ? "Bearer Token / Query Token Enabled" : "Bearer Token Enabled (Query Token Disabled)")
107
+ : "Disabled (Public/Local)";
108
+ writeStdout(` * Authentication: ${authDesc}`, context);
109
+ writeStdout(` * CORS Origins: ${corsOrigins ? corsOrigins.join(", ") : "Disabled (Default)"}`, context);
110
+ writeStdout(` * Max Body Size: ${options.maxBody || "1mb"}`, context);
111
+ writeStdout(` * Health Endpoint: http://${actualEndpointHost}:${server.port}/api/v1/health`, context);
112
+ if (enableMcp) {
113
+ writeStdout(` * MCP Endpoint: http://${actualEndpointHost}:${server.port}/mcp`, context);
114
+ }
115
+ writeStdout(`======================================================\n`, context);
116
+ writeStdout(`Server is ready to accept remote requests.`, context);
117
+ writeStdout(`Press Ctrl+C to terminate.\n`, context);
118
+ const stopSignalHandler = () => {
119
+ writeStdout("\nStopping ActionDock server...", context);
120
+ server.stop();
121
+ };
122
+ process.once("SIGINT", stopSignalHandler);
123
+ process.once("SIGTERM", stopSignalHandler);
124
+ }
125
+ catch (err) {
126
+ throw new ExecutionError(`Failed to start ActionDock server: ${err.message}`, err);
127
+ }
128
+ });
129
+ }
@@ -0,0 +1,24 @@
1
+ import type { ActionDockTarget } from "@actiondock/core";
2
+ import type { CliContext } from "../types.js";
3
+ /**
4
+ * 状态键列表渲染参数视图。
5
+ */
6
+ export interface StateListRenderArgs {
7
+ target: ActionDockTarget;
8
+ actionId: string;
9
+ prefix: string;
10
+ options: any;
11
+ effectiveIntent?: string;
12
+ shouldFallback: boolean;
13
+ context?: CliContext;
14
+ }
15
+ /**
16
+ * 渲染单个项目作用域的状态键列表(含 --detail JSON 明细分支)。
17
+ */
18
+ export declare function renderProjectScopedStateList(args: StateListRenderArgs & {
19
+ targetRoot: string;
20
+ }): Promise<void>;
21
+ /**
22
+ * 渲染全部链接包聚合的状态键列表(跳过磁盘失效与清单损坏的链接项)。
23
+ */
24
+ export declare function renderLinkedPackagesStateList(args: StateListRenderArgs): Promise<void>;
@@ -0,0 +1,82 @@
1
+ import { filterWithFallbackInfo, listLinkedPackages, loadProjectConfig, } from "@actiondock/core";
2
+ import { existsSync } from "node:fs";
3
+ import { renderResult, renderStateList } from "../renderer.js";
4
+ /**
5
+ * 渲染单个项目作用域的状态键列表(含 --detail JSON 明细分支)。
6
+ */
7
+ export async function renderProjectScopedStateList(args) {
8
+ const { target, targetRoot, actionId, prefix, options, effectiveIntent, shouldFallback, context } = args;
9
+ const projConfig = loadProjectConfig(targetRoot);
10
+ const allKeys = await target.listStateKeys(projConfig.id, actionId, {
11
+ namespace: options.namespace !== undefined ? options.namespace : null,
12
+ prefix,
13
+ });
14
+ if (options.detail && options.json) {
15
+ const entries = [];
16
+ for (const k of allKeys) {
17
+ const entry = await target.getState(projConfig.id, actionId, k, {
18
+ namespace: options.namespace,
19
+ detail: true,
20
+ });
21
+ if (entry) {
22
+ entries.push(entry);
23
+ }
24
+ }
25
+ const filterRes = filterWithFallbackInfo(entries, effectiveIntent, [(e) => e.fullKey, (e) => e.key, (e) => e.namespace], shouldFallback);
26
+ renderResult(filterRes.items, {
27
+ json: true,
28
+ envelope: options.envelope,
29
+ context,
30
+ });
31
+ return;
32
+ }
33
+ const filterRes = filterWithFallbackInfo(allKeys, effectiveIntent, [(k) => k], shouldFallback);
34
+ renderResult(filterRes.items, {
35
+ json: options.json,
36
+ envelope: options.envelope,
37
+ humanFormatter: () => renderStateList(filterRes.items, `${projConfig.name} (${projConfig.id})`, filterRes.isFallback, effectiveIntent),
38
+ context,
39
+ });
40
+ }
41
+ /**
42
+ * 渲染全部链接包聚合的状态键列表(跳过磁盘失效与清单损坏的链接项)。
43
+ */
44
+ export async function renderLinkedPackagesStateList(args) {
45
+ const { target, actionId, prefix, options, effectiveIntent, shouldFallback, context } = args;
46
+ // 扫描所有链接的包状态
47
+ const linked = listLinkedPackages();
48
+ if (linked.length === 0) {
49
+ renderResult([], {
50
+ json: options.json,
51
+ envelope: options.envelope,
52
+ humanFormatter: () => "No ActionDock project in current directory, and no packages linked.",
53
+ context,
54
+ });
55
+ return;
56
+ }
57
+ const aggregatedKeys = [];
58
+ for (const pkg of linked) {
59
+ if (!existsSync(pkg.path))
60
+ continue;
61
+ try {
62
+ const config = loadProjectConfig(pkg.path);
63
+ const keys = await target.listStateKeys(config.id, actionId, {
64
+ namespace: options.namespace !== undefined ? options.namespace : null,
65
+ prefix,
66
+ });
67
+ for (const k of keys) {
68
+ aggregatedKeys.push(`${config.id}/${k}`);
69
+ }
70
+ }
71
+ catch {
72
+ // 单个链接包清单损坏或状态读取失败时跳过,继续聚合其余包
73
+ }
74
+ }
75
+ const filterRes = filterWithFallbackInfo(aggregatedKeys, effectiveIntent, [(k) => k], shouldFallback);
76
+ renderResult(filterRes.items, {
77
+ json: options.json,
78
+ envelope: options.envelope,
79
+ humanFormatter: () => renderStateList(filterRes.items, "Linked Packages State Store", filterRes.isFallback, effectiveIntent),
80
+ context,
81
+ });
82
+ }
@@ -0,0 +1,9 @@
1
+ import { Command } from "commander";
2
+ import type { CliContext } from "../types.js";
3
+ /**
4
+ * 注册 state 状态管理命令(get、set、delete、clear、keys、list)。
5
+ *
6
+ * @param program Commander 实例
7
+ * @param context 命令行上下文
8
+ */
9
+ export declare function registerStateCommands(program: Command, context?: CliContext): void;