@actiondock/cli 2.7.0 → 2.9.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 (66) hide show
  1. package/README.md +6 -7
  2. package/bin/ad.js +52 -23
  3. package/dist/commands/action/create.js +4 -5
  4. package/dist/commands/add.js +9 -17
  5. package/dist/commands/build.js +3 -18
  6. package/dist/commands/config/delete.js +8 -7
  7. package/dist/commands/config/env.js +7 -11
  8. package/dist/commands/config/get.js +8 -5
  9. package/dist/commands/config/list.js +16 -22
  10. package/dist/commands/config/schema.d.ts +7 -1
  11. package/dist/commands/config/schema.js +44 -67
  12. package/dist/commands/config/set.js +9 -7
  13. package/dist/commands/describe.js +10 -9
  14. package/dist/commands/doctor.js +4 -3
  15. package/dist/commands/export.js +5 -13
  16. package/dist/commands/generate.js +4 -3
  17. package/dist/commands/info.js +3 -1
  18. package/dist/commands/init.js +3 -3
  19. package/dist/commands/link.js +4 -4
  20. package/dist/commands/list.js +19 -10
  21. package/dist/commands/mcp.js +27 -66
  22. package/dist/commands/pack.js +3 -12
  23. package/dist/commands/playbook.js +30 -10
  24. package/dist/commands/profile.js +10 -19
  25. package/dist/commands/remove.js +5 -14
  26. package/dist/commands/run.d.ts +5 -2
  27. package/dist/commands/run.js +19 -20
  28. package/dist/commands/runs.js +26 -35
  29. package/dist/commands/serve.js +177 -57
  30. package/dist/commands/state-list.d.ts +2 -2
  31. package/dist/commands/state-list.js +10 -8
  32. package/dist/commands/state.d.ts +2 -2
  33. package/dist/commands/state.js +110 -103
  34. package/dist/commands/test.js +1 -1
  35. package/dist/commands/validate.js +4 -17
  36. package/dist/errors.d.ts +14 -0
  37. package/dist/errors.js +46 -2
  38. package/dist/index.d.ts +2 -2
  39. package/dist/index.js +3 -3
  40. package/dist/renderer.d.ts +1 -1
  41. package/dist/renderer.js +1 -1
  42. package/dist/services/config-merge.d.ts +6 -5
  43. package/dist/services/config-merge.js +10 -8
  44. package/dist/services/inspect.js +3 -1
  45. package/dist/standalone/index.d.ts +4 -14
  46. package/dist/standalone/index.js +5 -44
  47. package/dist/types.d.ts +2 -1
  48. package/dist/types.js +1 -1
  49. package/dist/utils/cert.js +1 -1
  50. package/dist/utils/duration.d.ts +1 -1
  51. package/dist/utils/duration.js +1 -1
  52. package/dist/utils/index.d.ts +1 -2
  53. package/dist/utils/index.js +1 -2
  54. package/dist/utils/input.d.ts +9 -1
  55. package/dist/utils/input.js +12 -1
  56. package/dist/utils/server-launch.d.ts +114 -0
  57. package/dist/utils/server-launch.js +275 -0
  58. package/dist/utils/target.d.ts +14 -13
  59. package/dist/utils/target.js +20 -39
  60. package/dist/utils/utils.d.ts +101 -1
  61. package/dist/utils/utils.js +97 -7
  62. package/package.json +5 -6
  63. package/dist/utils/bytes.d.ts +0 -1
  64. package/dist/utils/bytes.js +0 -1
  65. package/dist/utils/filter.d.ts +0 -1
  66. package/dist/utils/filter.js +0 -1
@@ -1,7 +1,9 @@
1
- import { isSecretConfigKey, loadProjectConfig, resolvePackageRoot, } from "@actiondock/core";
1
+ import { isSecretConfigKey, } from "@actiondock/core/project";
2
+ import { loadProjectConfig, } from "@actiondock/core";
3
+ import { resolvePackageRoot, } from "@actiondock/core/registry";
2
4
  import { ArgumentError, packageNotFoundError } from "../../errors.js";
3
5
  import { writeStdout } from "../../renderer.js";
4
- import { applyTargetOptions, getEffectiveOptions, withTarget } from "../../utils/index.js";
6
+ import { applyTargetOptions, getEffectiveOptions, withService } from "../../utils/index.js";
5
7
  import { resolveConfigValueInput } from "../../prompt.js";
6
8
  /**
7
9
  * 注册 config set 子命令:写入配置值(支持标准输入与安全提示)。
@@ -56,14 +58,14 @@ export function registerConfigSetCommand(configCmd, context) {
56
58
  parsedVal = effectiveValueStr;
57
59
  }
58
60
  // 目标解析与写入(远端分支直接写远端作用域,本地分支按全局/项目作用域写入)
59
- await withTarget(options, context, async (target, resolved) => {
61
+ await withService(options, context, async (service, resolved) => {
60
62
  if (resolved.type === "remote") {
61
- await target.setConfig(options.package || "", key, parsedVal);
63
+ await service.management?.config.set(options.package || "", key, parsedVal);
62
64
  writeStdout(`[OK] Configuration '${key}' updated on remote server`, context);
63
65
  return;
64
66
  }
65
67
  if (options.global) {
66
- await target.setConfig("global", key, parsedVal);
68
+ await service.management?.config.set("global", key, parsedVal);
67
69
  writeStdout(`[OK] Global configuration '${key}' updated`, context);
68
70
  return;
69
71
  }
@@ -71,11 +73,11 @@ export function registerConfigSetCommand(configCmd, context) {
71
73
  if (options.package) {
72
74
  throw packageNotFoundError(options.package);
73
75
  }
74
- await target.setConfig("global", key, parsedVal);
76
+ await service.management?.config.set("global", key, parsedVal);
75
77
  writeStdout(`[OK] Global configuration '${key}' updated (no project in current directory)`, context);
76
78
  return;
77
79
  }
78
- await target.setConfig(projConfig.id, key, parsedVal);
80
+ await service.management?.config.set(projConfig.id, key, parsedVal);
79
81
  writeStdout(`[OK] Configuration '${key}' updated for package '${projConfig.id}'`, context);
80
82
  });
81
83
  });
@@ -1,7 +1,8 @@
1
- import { buildActionDescribePayload, formatActionDetail } from "@actiondock/core";
1
+ import { ACTION_NOT_FOUND, PACKAGE_NOT_FOUND, NOT_FOUND, } from "@actiondock/core";
2
+ import { buildActionDescribePayload, formatActionDetail, } from "@actiondock/core/project";
2
3
  import { ArgumentError, ExecutionError, packageNotFoundError } from "../errors.js";
3
4
  import { renderResult } from "../renderer.js";
4
- import { applyTargetOptions, getEffectiveOptions, resolveLocalPackageRoot, withTarget, } from "../utils/index.js";
5
+ import { applyTargetOptions, getEffectiveOptions, resolveLocalPackageRoot, withService, } from "../utils/index.js";
5
6
  /**
6
7
  * 挂载 describe 子命令至指定 Commander 节点。
7
8
  *
@@ -28,18 +29,18 @@ export function attachDescribeCommand(parent, context) {
28
29
  throw packageNotFoundError(options.package);
29
30
  }
30
31
  const targetRef = id;
31
- // 通过 Target 门面统一查询 Action 规范
32
- await withTarget(options, context, async (target) => {
32
+ // 通过 Service 门面统一查询 Action 规范
33
+ await withService(options, context, async (service) => {
33
34
  let spec;
34
35
  try {
35
- spec = await target.describeAction(targetRef);
36
+ spec = await service.discovery.describeAction(targetRef);
36
37
  }
37
38
  catch (err) {
38
- const msg = err?.message || String(err);
39
- if (msg.includes("not found") || msg.includes("ACTION_NOT_FOUND")) {
40
- throw new ArgumentError(msg);
39
+ const code = err?.code;
40
+ if (code === ACTION_NOT_FOUND || code === NOT_FOUND || code === PACKAGE_NOT_FOUND) {
41
+ throw new ArgumentError(err?.message || String(err), err?.details, code);
41
42
  }
42
- throw new ExecutionError(msg);
43
+ throw new ExecutionError(err?.message || String(err), err?.details, code);
43
44
  }
44
45
  const payload = buildActionDescribePayload(spec);
45
46
  renderResult(payload, {
@@ -1,5 +1,6 @@
1
- import { fetchRemoteDoctor, runDoctorChecks } from "@actiondock/core";
2
- import { ExecutionError } from "../errors.js";
1
+ import { fetchRemoteDoctor, } from "@actiondock/core/profile";
2
+ import { runDoctorChecks, } from "@actiondock/core/project";
3
+ import { wrapAsExecutionError } from "../errors.js";
3
4
  import { renderResult } from "../renderer.js";
4
5
  import { applyTargetOptions, getEffectiveOptions, resolveTargetFromOptions } from "../utils/index.js";
5
6
  export function registerDoctorCommand(program, context) {
@@ -71,7 +72,7 @@ export function registerDoctorCommand(program, context) {
71
72
  }
72
73
  }
73
74
  catch (err) {
74
- throw new ExecutionError(`Doctor failed to run diagnostics: ${err.message}`);
75
+ throw wrapAsExecutionError(err, "Doctor failed to run diagnostics: ");
75
76
  }
76
77
  });
77
78
  }
@@ -1,7 +1,8 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { basename } from "node:path";
3
- import { discoverProjects, findProjectRoot, listLinkedPackages, resolvePackageRoot } from "@actiondock/core";
4
- import { ExecutionError, notInProjectError, packageNotFoundError } from "../errors.js";
3
+ import { discoverProjects, listLinkedPackages, resolvePackageRoot, } from "@actiondock/core/registry";
4
+ import { findProjectRoot, } from "@actiondock/core";
5
+ import { ExecutionError, notInProjectError, packageNotFoundError, wrapAsExecutionError } from "../errors.js";
5
6
  import { renderResult, writeStdout } from "../renderer.js";
6
7
  import { getEffectiveOptions, parseListOption } from "../utils/index.js";
7
8
  export function registerExportCommand(program, context) {
@@ -16,9 +17,6 @@ export function registerExportCommand(program, context) {
16
17
  .option("--all", "Export all linked packages from global registry")
17
18
  .option("--bundle [name]", "Export multiple packages as a unified composite Skill bundle (defaults to workspace directory name if omitted)")
18
19
  .option("-m, --mode <mode>", "Skill export mode ('source' or 'node')", "source")
19
- .option("-s, --standalone", "Export standalone binary skill (removed)")
20
- .option("-t, --target <target>", "Target compilation platform (removed)")
21
- .option("--bytecode", "Bytecode compilation (removed)")
22
20
  .option("-o, --out <path>", "Output skill directory")
23
21
  .option("-p, --playbook <playbooks...>", "Only export specific playbook(s) and their dependent actions", parseListOption)
24
22
  .option("-a, --actions <actions...>", "Only export specific action(s)", parseListOption)
@@ -31,10 +29,6 @@ export function registerExportCommand(program, context) {
31
29
  .option("--require-reproducible", "Require reproducible build and fail if install scripts must run")
32
30
  .action(async (rawOptions, cmd) => {
33
31
  const options = getEffectiveOptions(rawOptions, cmd);
34
- // 彻底删除 --standalone 单文件编译选项
35
- if (options.standalone || options.target !== undefined || options.bytecode !== undefined) {
36
- throw new ExecutionError("The '--standalone' option and binary target flags have been removed in ActionDock 2.0. Please use '--mode node' for self-contained Node.js directory skills, or '--mode source' for source-based skills.", undefined, "UNSUPPORTED_BUILD_MODE");
37
- }
38
32
  const mode = options.mode === "node" ? "node" : "source";
39
33
  if (options.bundle && mode === "node") {
40
34
  throw new ExecutionError("Composite Skill export (--bundle) currently only supports source mode. Please use '--mode source' to export as a composite workspace Skill.");
@@ -200,10 +194,8 @@ export function registerExportCommand(program, context) {
200
194
  });
201
195
  }
202
196
  catch (err) {
203
- if (err?.code === "UNSUPPORTED_BUILD_MODE") {
204
- throw new ExecutionError(err.message, undefined, "UNSUPPORTED_BUILD_MODE");
205
- }
206
- throw new ExecutionError(`Export failed: ${err.message}`);
197
+ // wrapAsExecutionError 保留原始错误码(含 UNSUPPORTED_BUILD_MODE)
198
+ throw wrapAsExecutionError(err, "Export failed: ");
207
199
  }
208
200
  });
209
201
  }
@@ -1,5 +1,6 @@
1
- import { findProjectRoot, writeActionTypes } from "@actiondock/core";
2
- import { ExecutionError, notInProjectError } from "../errors.js";
1
+ import { findProjectRoot, } from "@actiondock/core";
2
+ import { writeActionTypes, } from "@actiondock/core/project";
3
+ import { notInProjectError, wrapAsExecutionError } from "../errors.js";
3
4
  import { renderResult } from "../renderer.js";
4
5
  import { getEffectiveOptions } from "../utils/index.js";
5
6
  /**
@@ -36,7 +37,7 @@ export function registerGenerateCommands(program, context) {
36
37
  });
37
38
  }
38
39
  catch (err) {
39
- throw new ExecutionError(`Failed to generate types: ${err.message}`);
40
+ throw wrapAsExecutionError(err, "Failed to generate types: ");
40
41
  }
41
42
  });
42
43
  }
@@ -1,4 +1,6 @@
1
- import { fetchRemoteInfo, filterWithFallbackInfo, getRegistryStatus, resolvePackageRoot, } from "@actiondock/core";
1
+ import { fetchRemoteInfo, } from "@actiondock/core/profile";
2
+ import { filterWithFallbackInfo, } from "@actiondock/core/project";
3
+ import { getRegistryStatus, resolvePackageRoot, } from "@actiondock/core/registry";
2
4
  import { ExecutionError, NO_PROJECT_NO_LINKED_MESSAGE, packageNotFoundError } from "../errors.js";
3
5
  import { projectDetailToJson, renderAggregatedPackages, renderProjectDetail, renderRegistryTree, renderResult, } from "../renderer.js";
4
6
  import { getProjectDetailInfo, scanLocalAggregatedPackages } from "../services/index.js";
@@ -1,6 +1,6 @@
1
- import { initProject } from "@actiondock/core";
1
+ import { initProject, } from "@actiondock/core";
2
2
  import { writeStdout } from "../renderer.js";
3
- import { ExecutionError } from "../errors.js";
3
+ import { wrapAsExecutionError } from "../errors.js";
4
4
  export function registerInitCommand(program) {
5
5
  program
6
6
  .command("init [directory]")
@@ -18,7 +18,7 @@ export function registerInitCommand(program) {
18
18
  writeStdout(`Successfully initialized ActionDock project in ${dir}`);
19
19
  }
20
20
  catch (err) {
21
- throw new ExecutionError(err.message);
21
+ throw wrapAsExecutionError(err);
22
22
  }
23
23
  });
24
24
  }
@@ -1,5 +1,5 @@
1
- import { linkPackage, pruneRegistry, unlinkPackage, } from "@actiondock/core";
2
- import { ExecutionError } from "../errors.js";
1
+ import { linkPackage, pruneRegistry, unlinkPackage, } from "@actiondock/core/registry";
2
+ import { wrapAsExecutionError } from "../errors.js";
3
3
  import { writeStdout } from "../renderer.js";
4
4
  export function registerLinkCommands(program) {
5
5
  // ad link [path]
@@ -24,7 +24,7 @@ export function registerLinkCommands(program) {
24
24
  }
25
25
  }
26
26
  catch (err) {
27
- throw new ExecutionError(err.message);
27
+ throw wrapAsExecutionError(err);
28
28
  }
29
29
  });
30
30
  // ad unlink [id|path]
@@ -65,7 +65,7 @@ export function registerLinkCommands(program) {
65
65
  }
66
66
  }
67
67
  catch (err) {
68
- throw new ExecutionError(err.message);
68
+ throw wrapAsExecutionError(err);
69
69
  }
70
70
  });
71
71
  }
@@ -1,7 +1,7 @@
1
- import { filterWithFallbackInfo } from "@actiondock/core";
1
+ import { filterWithFallbackInfo, } from "@actiondock/core/project";
2
2
  import { packageNotFoundError } from "../errors.js";
3
3
  import { renderActionList, renderResult } from "../renderer.js";
4
- import { applyTargetOptions, getEffectiveOptions, resolveFallbackStrategy, resolveIntent, resolveLocalPackageRoot, withTarget, } from "../utils/index.js";
4
+ import { applyTargetOptions, getEffectiveOptions, remoteTargetSuffix, resolveFallbackStrategy, resolveIntent, resolveLocalPackageRoot, withService, } from "../utils/index.js";
5
5
  /**
6
6
  * 挂载 list 子命令至指定 Commander 节点。
7
7
  *
@@ -28,14 +28,23 @@ export function attachListCommand(parent, context) {
28
28
  if (options.package && !targetPackageRoot) {
29
29
  throw packageNotFoundError(options.package);
30
30
  }
31
- // 通过 Target 门面统一获取 Action 列表
32
- await withTarget(options, context, async (target, resolved) => {
33
- let rawSummaries = await target.listActions();
34
- // 若指定了目标包但本地未过滤,则精确匹配包标识
31
+ // 通过 Service 门面统一获取 Action 列表
32
+ await withService(options, context, async (service, resolved) => {
33
+ let rawSummaries = await service.discovery.listActions();
34
+ // 若指定了目标包但本地未过滤,则按包归属精确过滤:
35
+ // 仅保留全限定名命中目标包前缀、或 packageId 归属目标包的条目;
36
+ // 无法归属到目标包的无前缀短 id 条目不放行,避免他包短 id 混入
35
37
  if (options.package && !options.profile && !options.server) {
36
- rawSummaries = rawSummaries.filter((s) => s.id.startsWith(`${options.package}/`) ||
37
- s.packageId === options.package ||
38
- !s.id.includes("/"));
38
+ const targetPkg = options.package;
39
+ rawSummaries = rawSummaries.filter((s) => {
40
+ if (s.packageId) {
41
+ return (s.packageId === targetPkg ||
42
+ s.id === targetPkg ||
43
+ s.id.startsWith(`${targetPkg}/`));
44
+ }
45
+ // 无 packageId 归属信息的条目:仅全限定名前缀命中时放行
46
+ return s.id === targetPkg || s.id.startsWith(`${targetPkg}/`);
47
+ });
39
48
  }
40
49
  const rawList = rawSummaries.map((s) => {
41
50
  let id = s.id;
@@ -59,7 +68,7 @@ export function attachListCommand(parent, context) {
59
68
  return;
60
69
  }
61
70
  const title = resolved.type === "remote"
62
- ? `Actions on remote server ${resolved.serverUrl}${resolved.profileName ? ` (Profile: ${resolved.profileName})` : ""}`
71
+ ? `Actions ${remoteTargetSuffix(resolved)}`
63
72
  : targetPackageRoot
64
73
  ? `Actions in ${options.package || "current project"}`
65
74
  : "Available Actions";
@@ -1,9 +1,22 @@
1
1
  import { resolve } from "node:path";
2
- import { findProjectRoot, formatHostForUrl, loadProjectConfig, parseDuration } from "@actiondock/core";
3
- import { createNodePlatform } from "@actiondock/runtime-node";
4
- import { ArgumentError, ExecutionError } from "../errors.js";
5
- import { writeStderr, writeStdout } from "../renderer.js";
6
- import { getEffectiveOptions, parseByteSize, parseListOption } from "../utils/index.js";
2
+ import { createNodePlatform, findProjectRoot, loadProjectConfig, } from "@actiondock/core";
3
+ import { formatHostForUrl, } from "@actiondock/core/server";
4
+ import { ExecutionError } from "../errors.js";
5
+ import { writeStdout } from "../renderer.js";
6
+ import { getEffectiveOptions, normalizeCorsOrigins, parseListOption, printServerBanner, registerStopSignalHandler, resolveMaxBodyBytes, resolveServerHost, resolveServerPort, resolveServerToken, resolveTimeoutMs, } from "../utils/index.js";
7
+ /**
8
+ * 解析 mcp 命令双分支共享的目标选项(目录、包、超时)。
9
+ */
10
+ function resolveMcpServeOptions(options) {
11
+ return {
12
+ projectRoots: options.dir && options.dir.length > 0
13
+ ? options.dir.map((d) => resolve(d))
14
+ : undefined,
15
+ packageIds: options.package && options.package.length > 0 ? options.package : undefined,
16
+ all: Boolean(options.all),
17
+ timeoutMs: resolveTimeoutMs(options.timeout),
18
+ };
19
+ }
7
20
  /**
8
21
  * 注册 mcp 命令(STDIO 与 HTTP 传输服务模式)。
9
22
  *
@@ -21,20 +34,7 @@ export function registerMcpCommands(program, context) {
21
34
  .option("--data-dir <path>", "Custom database storage directory")
22
35
  .action(async (rawOptions, cmd) => {
23
36
  const options = getEffectiveOptions(rawOptions, cmd);
24
- let timeoutMs;
25
- if (options.timeout) {
26
- try {
27
- timeoutMs = parseDuration(options.timeout);
28
- }
29
- catch (err) {
30
- throw new ArgumentError(`Invalid timeout format: ${err.message}`);
31
- }
32
- }
33
- const projectRoots = options.dir && options.dir.length > 0
34
- ? options.dir.map((d) => resolve(d))
35
- : undefined;
36
- const packageIds = options.package && options.package.length > 0 ? options.package : undefined;
37
- const all = Boolean(options.all);
37
+ const { projectRoots, packageIds, all, timeoutMs } = resolveMcpServeOptions(options);
38
38
  try {
39
39
  const { startMcpStdio } = await import("@actiondock/mcp");
40
40
  await startMcpStdio({
@@ -69,38 +69,15 @@ export function registerMcpCommands(program, context) {
69
69
  .option("--data-dir <path>", "Custom database storage directory")
70
70
  .action(async (rawOptions, cmd) => {
71
71
  const options = getEffectiveOptions(rawOptions, cmd);
72
- const port = parseInt(options.port, 10) || 5178;
73
- const host = options.host || "127.0.0.1";
74
- const token = options.token ||
75
- (options.tokenEnv && typeof process !== "undefined" ? process.env?.[options.tokenEnv] : undefined) ||
76
- (typeof process !== "undefined" ? process.env?.ACTIONDOCK_TOKEN : undefined);
72
+ const port = resolveServerPort(options.port, 5178, true);
73
+ const host = resolveServerHost(options.host);
74
+ const token = resolveServerToken(options.token, options.tokenEnv);
77
75
  const allowInsecureNoAuth = Boolean(options.allowInsecureNoAuth);
78
76
  const allowInsecureHttp = Boolean(options.allowInsecureHttp);
79
77
  const allowQueryToken = Boolean(options.allowQueryToken);
80
- const corsOrigins = options.corsOrigin && options.corsOrigin.length > 0 ? options.corsOrigin : undefined;
81
- let maxBodyBytes;
82
- if (options.maxBody) {
83
- try {
84
- maxBodyBytes = parseByteSize(options.maxBody);
85
- }
86
- catch (err) {
87
- throw new ArgumentError(`Invalid max-body format: ${err.message}`);
88
- }
89
- }
90
- let timeoutMs;
91
- if (options.timeout) {
92
- try {
93
- timeoutMs = parseDuration(options.timeout);
94
- }
95
- catch (err) {
96
- throw new ArgumentError(`Invalid timeout format: ${err.message}`);
97
- }
98
- }
99
- const projectRoots = options.dir && options.dir.length > 0
100
- ? options.dir.map((d) => resolve(d))
101
- : undefined;
102
- const packageIds = options.package && options.package.length > 0 ? options.package : undefined;
103
- const all = Boolean(options.all);
78
+ const corsOrigins = normalizeCorsOrigins(options.corsOrigin);
79
+ const maxBodyBytes = resolveMaxBodyBytes(options.maxBody);
80
+ const { projectRoots, packageIds, all, timeoutMs } = resolveMcpServeOptions(options);
104
81
  let targetDescription = "ActionDock MCP Server";
105
82
  if (all) {
106
83
  targetDescription = "All Linked Packages (Global Registry Mode)";
@@ -161,30 +138,14 @@ export function registerMcpCommands(program, context) {
161
138
  });
162
139
  const displayHost = formatHostForUrl(host);
163
140
  const actualMcpHost = formatHostForUrl(host === "0.0.0.0" ? "127.0.0.1" : host);
164
- writeStdout(`\n======================================================`, context);
165
- writeStdout(` ActionDock 2.0 MCP HTTP Server`, context);
166
- writeStdout(`======================================================`, context);
167
- writeStdout(` * Listening on: http://${displayHost}:${server.port}`, context);
141
+ printServerBanner(`ActionDock 2.0 MCP HTTP Server`, "http", displayHost, server.port, context);
168
142
  writeStdout(` * MCP Endpoint: http://${actualMcpHost}:${server.port}/mcp`, context);
169
143
  writeStdout(` * Target: ${targetDescription}`, context);
170
144
  writeStdout(` * Authentication: ${token ? "Bearer Token Enabled" : "Disabled (Local)"}`, context);
171
145
  writeStdout(` * CORS Origins: ${corsOrigins ? corsOrigins.join(", ") : "Disabled (Default)"}`, context);
172
146
  writeStdout(`======================================================\n`, context);
173
147
  writeStdout(`Press Ctrl+C to terminate.\n`, context);
174
- const stopSignalHandler = () => {
175
- writeStdout("\nStopping MCP HTTP server...", context);
176
- Promise.resolve(server.stop())
177
- .catch((err) => {
178
- // 停止失败必须可见:写入 stderr 一行并标记失败退出码
179
- writeStderr(`[ERROR] Failed to stop MCP HTTP server gracefully: ${err instanceof Error ? err.message : String(err)}`, context);
180
- process.exitCode = 1;
181
- })
182
- .finally(() => {
183
- process.exit(typeof process.exitCode === "number" ? process.exitCode : 0);
184
- });
185
- };
186
- process.once("SIGINT", stopSignalHandler);
187
- process.once("SIGTERM", stopSignalHandler);
148
+ registerStopSignalHandler(server, `MCP HTTP server`, context);
188
149
  }
189
150
  catch (err) {
190
151
  throw new ExecutionError(`Failed to start ActionDock MCP HTTP server: ${err.message}`, err);
@@ -1,7 +1,6 @@
1
- import { findProjectRoot, resolvePackageRoot } from "@actiondock/core";
2
- import { ExecutionError, notInProjectError, packageNotFoundError } from "../errors.js";
1
+ import { ExecutionError } from "../errors.js";
3
2
  import { renderResult, writeStdout } from "../renderer.js";
4
- import { getEffectiveOptions } from "../utils/index.js";
3
+ import { getEffectiveOptions, requirePackageRoot } from "../utils/index.js";
5
4
  export function registerPackCommand(program, context) {
6
5
  program
7
6
  .command("pack")
@@ -12,15 +11,7 @@ export function registerPackCommand(program, context) {
12
11
  .option("--json", "Output as JSON")
13
12
  .action(async (rawOptions, cmd) => {
14
13
  const options = getEffectiveOptions(rawOptions, cmd);
15
- const root = options.package
16
- ? resolvePackageRoot(options.package)
17
- : findProjectRoot();
18
- if (!root) {
19
- if (options.package) {
20
- throw packageNotFoundError(options.package);
21
- }
22
- throw notInProjectError("Please specify -P, --package <id> or cd into a project directory.");
23
- }
14
+ const { root } = requirePackageRoot(options.package);
24
15
  const isMachine = Boolean(options.json);
25
16
  if (!isMachine && !options.dryRun) {
26
17
  writeStdout("Packing Action package...");
@@ -1,9 +1,13 @@
1
1
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
2
  import { dirname, isAbsolute, relative, resolve } from "node:path";
3
- import { assertPathWithinRoot, fetchRemotePlaybooks, fetchRemotePlaybookShow, filterWithFallbackInfo, findProjectRoot, listLinkedPackages, loadActions, loadManifest, loadPlaybooks, loadProjectConfig, resolveActionProject, resolvePackageRoot, resolvePlaybookProject, saveManifest, } from "@actiondock/core";
4
- import { ArgumentError, CliError, ExecutionError, NO_PROJECT_NO_LINKED_MESSAGE, notInProjectError, packageNotFoundError, } from "../errors.js";
3
+ import { assertPathWithinRoot, loadActions, loadManifest, loadPlaybooks, saveManifest, filterWithFallbackInfo, } from "@actiondock/core/project";
4
+ import { DefaultActionCatalog, PackageGraphBuilder, resolveAction, resolvePlaybook, } from "@actiondock/core/graph";
5
+ import { fetchRemotePlaybooks, fetchRemotePlaybookShow, } from "@actiondock/core/profile";
6
+ import { findProjectRoot, loadProjectConfig, } from "@actiondock/core";
7
+ import { listLinkedPackages, resolvePackageRoot, } from "@actiondock/core/registry";
8
+ import { ArgumentError, CliError, ExecutionError, NO_PROJECT_NO_LINKED_MESSAGE, notInProjectError, packageNotFoundError, wrapAsExecutionError, } from "../errors.js";
5
9
  import { renderPlaybookDetail, renderPlaybookList, renderResult, writeStdout, } from "../renderer.js";
6
- import { applyTargetOptions, getEffectiveOptions, resolveIntent, resolveTargetFromOptions, } from "../utils/index.js";
10
+ import { applyTargetOptions, getEffectiveOptions, remoteTargetSuffix, resolveFallbackStrategy, resolveIntent, resolveTargetFromOptions, } from "../utils/index.js";
7
11
  /**
8
12
  * 注册 Playbook 命令集合(list, show, validate, create)。
9
13
  *
@@ -20,13 +24,13 @@ export function registerPlaybookCommands(program, context) {
20
24
  .description("List playbooks in current project or linked packages")
21
25
  .option("-i, --intent <pattern>", "Regex or fuzzy intent filter; falls back to full list when no match")
22
26
  .option("-P, --package <id>", "Target package ID or path"))
27
+ .option("--fallback", "Enable fallback to full list when no items match intent")
23
28
  .option("--no-fallback", "Disable fallback to full list when no items match intent")
24
29
  .option("--json", "Output as JSON")
25
30
  .action(async (patterns = [], rawOptions, cmd) => {
26
31
  const options = getEffectiveOptions(rawOptions, cmd);
27
32
  const effectiveIntent = resolveIntent(options.intent, patterns);
28
- const shouldFallback = options.fallback !== false;
29
- const isMachine = Boolean(options.json);
33
+ const { shouldFallback, isMachine } = resolveFallbackStrategy(options);
30
34
  // 1. 远端服务分支
31
35
  const target = resolveTargetFromOptions(options, context);
32
36
  if (target.type === "remote") {
@@ -38,7 +42,7 @@ export function registerPlaybookCommands(program, context) {
38
42
  });
39
43
  renderResult(remotePbs, {
40
44
  json: options.json,
41
- humanFormatter: () => renderPlaybookList(remotePbs, `Playbooks on remote server ${target.serverUrl}${target.profileName ? ` (Profile: ${target.profileName})` : ""}`, false, effectiveIntent),
45
+ humanFormatter: () => renderPlaybookList(remotePbs, `Playbooks ${remoteTargetSuffix(target)}`, false, effectiveIntent),
42
46
  context,
43
47
  });
44
48
  return;
@@ -187,7 +191,12 @@ export function registerPlaybookCommands(program, context) {
187
191
  }
188
192
  let resolved;
189
193
  try {
190
- resolved = resolvePlaybookProject(showTarget);
194
+ const graph = new PackageGraphBuilder({
195
+ root: findProjectRoot() || undefined,
196
+ customHome: context?.customHome,
197
+ allowDevLinks: true,
198
+ }).buildSync();
199
+ resolved = resolvePlaybook(showTarget, { graph });
191
200
  }
192
201
  catch (err) {
193
202
  throw new ArgumentError(err.message);
@@ -243,7 +252,12 @@ export function registerPlaybookCommands(program, context) {
243
252
  else if (id) {
244
253
  let resolved;
245
254
  try {
246
- resolved = resolvePlaybookProject(id);
255
+ const graph = new PackageGraphBuilder({
256
+ root: findProjectRoot() || undefined,
257
+ customHome: context?.customHome,
258
+ allowDevLinks: true,
259
+ }).buildSync();
260
+ resolved = resolvePlaybook(id, { graph });
247
261
  }
248
262
  catch (err) {
249
263
  throw new ArgumentError(err.message);
@@ -277,6 +291,12 @@ export function registerPlaybookCommands(program, context) {
277
291
  }
278
292
  }
279
293
  const results = [];
294
+ const graph = new PackageGraphBuilder({
295
+ root: findProjectRoot() || undefined,
296
+ customHome: context?.customHome,
297
+ allowDevLinks: true,
298
+ }).buildSync();
299
+ const catalog = new DefaultActionCatalog(graph);
280
300
  for (const target of targets) {
281
301
  const config = loadProjectConfig(target.root);
282
302
  const manifest = loadManifest(target.root);
@@ -294,7 +314,7 @@ export function registerPlaybookCommands(program, context) {
294
314
  for (const actRef of pb.actions) {
295
315
  if (actRef.includes("/")) {
296
316
  try {
297
- await resolveActionProject(actRef);
317
+ resolveAction(actRef, { graph, catalog, caller: target.packageId });
298
318
  }
299
319
  catch (e) {
300
320
  errors.push(`Referenced cross-package action '${actRef}' not resolvable: ${e.message}`);
@@ -408,6 +428,6 @@ This playbook provides task execution guidance for AI Agents.
408
428
  if (err instanceof CliError) {
409
429
  throw err;
410
430
  }
411
- throw new ExecutionError(err.message);
431
+ throw wrapAsExecutionError(err);
412
432
  }
413
433
  }
@@ -1,5 +1,6 @@
1
- import { addProfile, checkRemoteHealth, filterWithFallbackInfo, getProfile, listProfiles, loadProfiles, maskSecretValue, removeProfile, resolveProfileToken, resolveTarget, toSnakeUpperCase, updateProfile, useProfile, } from "@actiondock/core";
2
- import { ArgumentError, CliError, ExecutionError, } from "../errors.js";
1
+ import { addProfile, checkRemoteHealth, getProfile, listProfiles, loadProfiles, removeProfile, resolveProfileToken, resolveTarget, toSnakeUpperCase, updateProfile, useProfile, } from "@actiondock/core/profile";
2
+ import { filterWithFallbackInfo, maskSecretValue, } from "@actiondock/core/project";
3
+ import { ArgumentError, ExecutionError, wrapAsExecutionError, } from "../errors.js";
3
4
  import { renderResult, writeStderr, writeStdout } from "../renderer.js";
4
5
  import { getEffectiveOptions, resolveFallbackStrategy, resolveIntent } from "../utils/index.js";
5
6
  export function registerProfileCommands(program) {
@@ -76,9 +77,7 @@ export function registerProfileCommands(program) {
76
77
  });
77
78
  }
78
79
  catch (err) {
79
- if (err instanceof ArgumentError || err instanceof CliError)
80
- throw err;
81
- throw new ExecutionError(err.message);
80
+ throw wrapAsExecutionError(err);
82
81
  }
83
82
  });
84
83
  // ad profile add <name>
@@ -105,7 +104,7 @@ export function registerProfileCommands(program) {
105
104
  writeStdout(`[OK] Profile '${name}' configured for server: ${options.server}${options.insecure ? " (insecure: true)" : ""}`);
106
105
  }
107
106
  catch (err) {
108
- throw new ExecutionError(err.message);
107
+ throw wrapAsExecutionError(err);
109
108
  }
110
109
  });
111
110
  // ad profile update <name>
@@ -140,7 +139,7 @@ export function registerProfileCommands(program) {
140
139
  writeStdout(`[OK] Profile '${name}' updated`);
141
140
  }
142
141
  catch (err) {
143
- throw new ExecutionError(err.message);
142
+ throw wrapAsExecutionError(err);
144
143
  }
145
144
  });
146
145
  // ad profile use <name>
@@ -153,7 +152,7 @@ export function registerProfileCommands(program) {
153
152
  writeStdout(`[OK] Active profile switched to '${name}'`);
154
153
  }
155
154
  catch (err) {
156
- throw new ExecutionError(err.message);
155
+ throw wrapAsExecutionError(err);
157
156
  }
158
157
  });
159
158
  // ad profile show [name]
@@ -221,10 +220,7 @@ export function registerProfileCommands(program) {
221
220
  });
222
221
  }
223
222
  catch (err) {
224
- if (err instanceof ArgumentError || err instanceof CliError) {
225
- throw err;
226
- }
227
- throw new ExecutionError(err.message);
223
+ throw wrapAsExecutionError(err);
228
224
  }
229
225
  });
230
226
  // ad profile rm / remove <name>
@@ -244,10 +240,7 @@ export function registerProfileCommands(program) {
244
240
  writeStdout(`[OK] Profile '${name}' removed`);
245
241
  }
246
242
  catch (err) {
247
- if (err instanceof CliError) {
248
- throw err;
249
- }
250
- throw new ExecutionError(err.message);
243
+ throw wrapAsExecutionError(err);
251
244
  }
252
245
  });
253
246
  // ad profile test [name]
@@ -290,9 +283,7 @@ export function registerProfileCommands(program) {
290
283
  }
291
284
  }
292
285
  catch (err) {
293
- if (err instanceof ArgumentError || err instanceof CliError)
294
- throw err;
295
- throw new ExecutionError(err.message);
286
+ throw wrapAsExecutionError(err);
296
287
  }
297
288
  });
298
289
  }
@@ -1,8 +1,7 @@
1
- import { resolve } from "node:path";
2
- import { beginTransaction, findProjectRoot, getInstallCommand, loadLockfile, loadManifest, saveLockfile, saveManifest, } from "@actiondock/core";
3
- import { ArgumentError, ExecutionError, notInProjectError } from "../errors.js";
1
+ import { beginTransaction, getInstallCommand, loadLockfile, saveLockfile, saveManifest, } from "@actiondock/core/project";
2
+ import { ArgumentError, ExecutionError } from "../errors.js";
4
3
  import { renderResult, writeStderr } from "../renderer.js";
5
- import { assertSafePackageSpec, getEffectiveOptions, spawnAsync } from "../utils/index.js";
4
+ import { assertSafePackageSpec, getEffectiveOptions, requireProjectManifestRoot, rollbackAndRethrow, spawnAsync, } from "../utils/index.js";
6
5
  /**
7
6
  * 注册 ad remove 依赖移除命令。
8
7
  * 遵循规范:移除依赖并更新锁文件,保留该包的历史存储数据,检查反向引用防止误删。
@@ -15,14 +14,7 @@ export function registerRemoveCommand(program, context) {
15
14
  .option("--json", "Output as JSON")
16
15
  .action(async (packageIdentifier, rawOptions, cmd) => {
17
16
  const options = getEffectiveOptions(rawOptions, cmd);
18
- const root = options.package ? resolve(options.package) : findProjectRoot();
19
- if (!root) {
20
- throw notInProjectError("Please specify -P, --package <path> or cd into a project directory.");
21
- }
22
- const manifest = loadManifest(root);
23
- if (!manifest) {
24
- throw new ArgumentError(`actiondock.json not found in ${root}`);
25
- }
17
+ const { root, manifest } = requireProjectManifestRoot(options.package);
26
18
  // 寻址逻辑包标识
27
19
  let targetPackageId = null;
28
20
  let npmPackageName = packageIdentifier;
@@ -115,8 +107,7 @@ export function registerRemoveCommand(program, context) {
115
107
  });
116
108
  }
117
109
  catch (err) {
118
- await tx.rollback({ frozenInstall: false });
119
- throw new ExecutionError(err.message, err, err.code || "REMOVE_DEPENDENCY_FAILED");
110
+ await rollbackAndRethrow(tx, err, "REMOVE_DEPENDENCY_FAILED");
120
111
  }
121
112
  });
122
113
  }