@actiondock/cli 2.6.0 → 2.8.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 +34 -22
  2. package/bin/ad.js +4 -4
  3. package/dist/commands/action/create.js +9 -25
  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 +15 -26
  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 +6 -2
  27. package/dist/commands/run.js +60 -37
  28. package/dist/commands/runs.js +26 -35
  29. package/dist/commands/serve.js +66 -44
  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 +78 -2
  38. package/dist/index.d.ts +8 -1
  39. package/dist/index.js +60 -8
  40. package/dist/renderer.d.ts +3 -2
  41. package/dist/renderer.js +4 -19
  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 +7 -11
  46. package/dist/standalone/index.js +8 -17
  47. package/dist/types.d.ts +7 -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 +5 -42
  55. package/dist/utils/input.js +7 -84
  56. package/dist/utils/server-launch.d.ts +61 -0
  57. package/dist/utils/server-launch.js +114 -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
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @actiondock/cli
2
2
 
3
- ActionDock 2.0 官方命令行门面工具链。
3
+ ActionDock 2.x 官方命令行门面工具链。
4
4
 
5
5
  [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D24.12.0-green?logo=node.js)](https://nodejs.org/)
6
6
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue?logo=typescript)](https://www.typescriptlang.org/)
@@ -15,7 +15,6 @@ ActionDock 2.0 官方命令行门面工具链。
15
15
  - 原生基于 Node.js >=24.12.0 运行,充分依托原生类型擦除、内置 SQLite 与原生 HTTP 特性。
16
16
  - 支持 npm、pnpm 与 yarn 标准包管理器进行全局安装与项目级管理。
17
17
  - 日常开发、调试、测试、构建、打包、MCP 服务启动与 HTTP 部署完全基于 Node.js 与 npm 标准工作流,无需安装外部编译器。
18
- - 单文件独立二进制构建(`--target`、`--bytecode`、`--standalone`)与 `actiondock.manifest.json` 已在 2.0 中彻底移除,由标准的 Node.js 目录交付产物与 npm 打包取代。
19
18
 
20
19
  ---
21
20
 
@@ -51,10 +50,13 @@ ad add @actiondock/example-tools
51
50
 
52
51
  - 本地执行 Action:
53
52
  ```bash
54
- # 简单参数直接内联传递
55
- ad run sample.greet --input '{"name": "Alice"}'
53
+ # 扁平参数赋值(规范语法,推荐)
54
+ ad run sample.greet -- name=Alice
56
55
 
57
- # 复杂参数或对象从文件读取
56
+ # 传递 JSON 标量与结构(数值校验为有限数)
57
+ ad run sample.greet -- name=Alice count:=1
58
+
59
+ # 复杂参数或对象从文件读取(与扁平参数互斥)
58
60
  ad run sample.greet --input-file input.json
59
61
 
60
62
  # 自动化与脚本通过标准输入传递
@@ -109,20 +111,31 @@ ad pack
109
111
 
110
112
  ### Action 执行入参契约
111
113
 
112
- 执行 Action 支持两种互斥的传参方式:
113
-
114
- - 简单内联参数:使用 `--input <json>` 直接解析 JSON 字符串,适合简易标量入参。
115
- - 复杂对象文件:使用 `--input-file <path>` 读取文件并解析 JSON,避免各类终端的引号转义损坏。
116
- - 标准输入管道:使用 `--input-file -` 从标准输入读取全部数据并解析 JSON,适合跨进程协同与 CI 自动化脚本。
117
- - 默认无输入:未指定 `--input` 与 `--input-file` 时,入参默认提供 `{}`。
118
- - 严格互斥:`--input` 与 `--input-file` 互斥,同时提供时直接报错并退出。
119
- - 统一解析:无论内联参数、文件还是标准输入,解析前均自动剔除 UTF-8 BOM 标记,且不设人为大小上限。
114
+ 执行 Action 推荐使用扁平参数赋值协议,同时支持传统选项,三者之间严格互斥:
115
+
116
+ - 规范调用语法:`ad run <action> [control-options] -- <assignments...>`。
117
+ - 协议边界:`--` 分隔符作为控制平面选项(如 `--json`、`--config`、`--data-dir`、`--profile` 等)与数据平面(Action 入参)的协议边界。
118
+ - 两种赋值操作符语义:
119
+ - `path=value`:严格保留为字符串,不执行 JSON 解析与类型猜测。
120
+ - `path:=json`:严格解析为 JSON 值,递归校验所有数值为有限数(`Number.isFinite`)。
121
+ - 路径语法规则:
122
+ - 命名段(`^[A-Za-z_][A-Za-z0-9_-]*$`)表示对象属性。
123
+ - 纯数字段(`^(0|[1-9][0-9]*)$`)表示数组索引,数组索引必须从 0 开始连续编号,拒绝稀疏数组。
124
+ - 根节点始终物化为对象。
125
+ - 路径冲突(叶节点与容器冲突、对象与数组冲突、重复赋值)严格拒绝(`INPUT_PATH_CONFLICT`)。
126
+ - 拦截原型污染敏感属性(`__proto__`、`constructor`、`prototype`)。
127
+ - 三种输入模式互斥:扁平参数、`--input` 与 `--input-file` 严格互斥,不可混用(`INPUT_CONFLICT`);未指定输入时默认为 `{}`。
128
+ - 传统输入选项:
129
+ - 简单内联参数:使用 `--input <json>` 直接解析 JSON 字符串。
130
+ - 复杂对象文件:使用 `--input-file <path>` 读取文件并解析 JSON,避免各类终端的引号转义损坏。
131
+ - 标准输入管道:使用 `--input-file -` 从标准输入读取全部数据并解析 JSON。
132
+ - 统一解析:解析前均自动剔除 UTF-8 BOM 标记,且不设人为大小上限。
120
133
 
121
134
  ### Windows 与多终端传参建议
122
135
 
123
136
  针对 Windows PowerShell、cmd 以及各终端中复杂 JSON 容易遇到的双引号转义问题,推荐按以下规范传参:
124
137
 
125
- - 简单入参:使用 `--input`,如 `ad run greet --input '{"name":"Alice"}'`。
138
+ - 扁平参数传参(推荐):使用 `--` 分隔并传参,如 `ad run greet -- name=Alice` 或 `ad run greet -- name=Alice count:=1`。
126
139
  - 复杂结构:推荐先保存为 JSON 文件并使用 `--input-file`,如 `ad run complex-action --input-file input.json`。
127
140
  - 动态生成输入:通过管道输出配合 `--input-file -` 传递,在 PowerShell 中可执行:
128
141
 
@@ -138,8 +151,8 @@ $data | ConvertTo-Json -Depth 100 | ad run complex-action --input-file -
138
151
 
139
152
  ### Action 输出模式与机器模式
140
153
 
141
- - **默认原始纯文本输出**:`ad run` 默认采用面向终端人类阅读与 LLM Agent 上下文消费的原始输出模式。直接将执行结果正文内容(如文件 `content`、`text`、`message` 或标量字符串)输出至 stdout(保留真实换行且无 JSON 转义),相关元数据(如文件路径、行号范围、截断标记)独立输出至 stderr,符合 Unix 管道安全原则。执行失败时在 stderr 输出错误详情并以退出码 1 退出。
142
- - **机器 JSON 格式 (`--json`)**:当需要以程序化方式消费、获取完整结构化数据或被外部系统集成时,传入 `--json`。此时输出标准 JSON 结果(包含 `ok`、`runId`、`data` 或 `error`)。
154
+ - **默认原始纯文本输出**:`ad run` 默认采用面向终端人类阅读与 LLM Agent 上下文消费的原始输出模式。直接将执行结果正文内容(如文件 `content`、`text`、`message` 或标量字符串)输出至 stdout(保留真实换行且无 JSON 转义),相关元数据(如文件路径、行号范围、截断标记)独立输出至 stderr,符合 Unix 管道安全原则。业务执行失败时在 stderr 输出错误详情并以退出码 1 退出。
155
+ - **机器 JSON 格式**(`--json`):当需要以程序化方式消费、获取完整结构化数据或被外部系统集成时,传入 `--json`。面向智能体调用推荐使用该模式;此时输出标准 JSON 结果(包含 `ok`、`runId`、`data` 或 `error`);若参数解析出错输出标准错误信封并以退出码 2 退出。
143
156
 
144
157
  ---
145
158
 
@@ -152,8 +165,8 @@ $data | ConvertTo-Json -Depth 100 | ad run complex-action --input-file -
152
165
  | `ad remove <package>` | 卸载并更新锁定依赖,提供原子回滚保护 |
153
166
  | `ad info [patterns...]` | 检索包元数据与能力清单,支持模式匹配与树形展示 |
154
167
  | `ad list [patterns...]` | 列出包内所有已注册的 Action |
155
- | `ad describe <id>` | 查看 Action 的详情、参数与模式规范(别名 `ad show`) |
156
- | `ad run <id>` | 本地或远程执行指定 Action(默认输出原始纯文本,支持 `--json` 输出标准信封) |
168
+ | `ad describe <id>` | 编码顾问:查看 Action 的详情、模式字段明细、Flat 编码指引与建议赋值(别名 `ad show`) |
169
+ | `ad run <id>` | 本地或远程执行指定 Action(规范语法 `ad run <id> [options] -- <assignments...>`,支持 `--json` 输出标准信封) |
157
170
  | `ad validate [id]` | 校验 Action 规范与模式规范 |
158
171
  | `ad doctor` | 执行运行环境与项目结构健康诊断 |
159
172
  | `ad action create <id>` | 创建新 Action 源码(别名 `ad action new`) |
@@ -176,10 +189,9 @@ $data | ConvertTo-Json -Depth 100 | ad run complex-action --input-file -
176
189
 
177
190
  作为顶层门面与独立运行分发器,`@actiondock/cli` 串联以下子包:
178
191
 
179
- - 运行时适配:依赖 [@actiondock/runtime-node](../runtime-node/README.md),在启动时自动注入基于 Node.js 原生能力的驱动实现。
180
- - 领域内核:依赖 [@actiondock/core](../core/README.md),调度统一调用门面 ActionDockTarget、数据目录锁 DataDirLock 与依赖事务管理器。
181
- - 构建与打包:依赖 [@actiondock/builder](../builder/README.md),完成目录交付构建、npm 打包与技能导出。
182
- - 协议服务:通过 [@actiondock/mcp](../mcp/README.md) 启动协议监听。
192
+ - 领域内核与运行时:依赖 [@actiondock/core](../core/README.md),统一对接标准服务端口体系(`DiscoveryPort`、`ExecutionPort`、`RunsPort`、`ConfigPort`、`StatePort`)与统一服务门面(`createActionDock`、`connectActionDock`),承载原生运行时、存储驱动与受管进程治理。
193
+ - 构建与打包:依赖 [@actiondock/builder](../builder/README.md),完成依赖闭包规划、Node 目录交付产物构建、npm 打包与 Agent Skill 资产导出。
194
+ - 协议服务:通过 [@actiondock/mcp](../mcp/README.md) 启动 Model Context Protocol 协议监听与适配。
183
195
 
184
196
  ---
185
197
 
package/bin/ad.js CHANGED
@@ -8,8 +8,8 @@ import { fileURLToPath, pathToFileURL } from "node:url";
8
8
  const distEntry = resolve(import.meta.dirname, "../dist/index.js");
9
9
 
10
10
  if (existsSync(distEntry)) {
11
- const { main } = await import(pathToFileURL(distEntry).href);
12
- await main(process.argv);
11
+ const { runCliProcess } = await import(pathToFileURL(distEntry).href);
12
+ await runCliProcess(process.argv);
13
13
  } else {
14
14
  const isBun = typeof process.versions.bun !== "undefined";
15
15
  const hasTsx =
@@ -37,6 +37,6 @@ if (existsSync(distEntry)) {
37
37
  }
38
38
  }
39
39
 
40
- const { main } = await import("../src/index.ts");
41
- await main(process.argv);
40
+ const { runCliProcess } = await import("../src/index.ts");
41
+ await runCliProcess(process.argv);
42
42
  }
@@ -1,7 +1,8 @@
1
1
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
2
  import { dirname, isAbsolute, join, relative, resolve } from "node:path";
3
- import { assertPathWithinRoot, findProjectRoot, getPackageSlug, loadManifest, loadProjectConfig, saveManifest, writeActionTypes, } from "@actiondock/core";
4
- import { ExecutionError, notInProjectError } from "../../errors.js";
3
+ import { assertPathWithinRoot, getPackageSlug, loadManifest, saveManifest, writeActionTypes, } from "@actiondock/core/project";
4
+ import { findProjectRoot, loadProjectConfig, } from "@actiondock/core";
5
+ import { ExecutionError, notInProjectError, wrapAsExecutionError } from "../../errors.js";
5
6
  import { writeStdout } from "../../renderer.js";
6
7
  import { attachDescribeCommand } from "../describe.js";
7
8
  import { attachListCommand } from "../list.js";
@@ -202,31 +203,14 @@ ${returnBody}
202
203
  saveManifest(root, manifest);
203
204
  // 始终自动生成或更新类型声明文件,确保单一事实源即时生效
204
205
  writeActionTypes(root, manifest);
205
- let sampleInputStr = "{}";
206
- const inputEntries = Object.entries(inputSchema.properties || {});
207
- if (inputEntries.length > 0) {
208
- const sampleObj = {};
209
- for (const [k, prop] of inputEntries) {
210
- if (prop.type === "number" || prop.type === "integer")
211
- sampleObj[k] = 42;
212
- else if (prop.type === "boolean")
213
- sampleObj[k] = true;
214
- else if (prop.type === "array")
215
- sampleObj[k] = ["item"];
216
- else if (prop.type === "object")
217
- sampleObj[k] = {};
218
- else
219
- sampleObj[k] = "hello";
220
- }
221
- sampleInputStr = JSON.stringify(sampleObj);
222
- }
223
206
  writeStdout(`[OK] Created Action '${id}' at ${targetFullFile}`, context);
224
- writeStdout(`\nTo run this action:`, context);
225
- writeStdout(` ad run ${id} --input '${sampleInputStr}'`, context);
207
+ writeStdout(`\n查阅参数契约:`, context);
208
+ writeStdout(` ad describe ${id}`, context);
209
+ writeStdout(`\n调用方式:`, context);
210
+ writeStdout(` 根据 describe 输出的赋值模板传递参数:ad run ${id} --json -- ASSIGNMENT...`, context);
211
+ writeStdout(` 复杂输入或大段文本:ad run ${id} --json --input-file input.json`, context);
226
212
  }
227
213
  catch (err) {
228
- if (err instanceof ExecutionError)
229
- throw err;
230
- throw new ExecutionError(err.message);
214
+ throw wrapAsExecutionError(err);
231
215
  }
232
216
  }
@@ -1,9 +1,9 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
- import { join, resolve } from "node:path";
3
- import { ActionPackageResolver, beginTransaction, computeManifestDigest, findProjectRoot, getInstallCommand, loadLockfile, loadManifest, MANIFEST_FILE_NAME, parseJsonWithoutDuplicates, saveLockfile, saveManifest, } from "@actiondock/core";
4
- import { ArgumentError, ExecutionError, notInProjectError } from "../errors.js";
2
+ import { join } from "node:path";
3
+ import { PackageGraphBuilder, } from "@actiondock/core/graph";
4
+ import { beginTransaction, computeManifestDigest, getInstallCommand, loadLockfile, MANIFEST_FILE_NAME, parseJsonWithoutDuplicates, saveLockfile, saveManifest, } from "@actiondock/core/project";
5
5
  import { renderResult } from "../renderer.js";
6
- import { assertSafePackageSpec, getEffectiveOptions, spawnAsync } from "../utils/index.js";
6
+ import { assertSafePackageSpec, getEffectiveOptions, requireProjectManifestRoot, rollbackAndRethrow, spawnAsync, } from "../utils/index.js";
7
7
  /**
8
8
  * 从安装说明符中提取基础 npm 包名(去除版本号及前缀范围)。
9
9
  */
@@ -36,14 +36,7 @@ export function registerAddCommand(program, context) {
36
36
  .option("--json", "Output as JSON")
37
37
  .action(async (packageSpec, rawOptions, cmd) => {
38
38
  const options = getEffectiveOptions(rawOptions, cmd);
39
- const root = options.package ? resolve(options.package) : findProjectRoot();
40
- if (!root) {
41
- throw notInProjectError("Please specify -P, --package <path> or cd into a project directory.");
42
- }
43
- const manifest = loadManifest(root);
44
- if (!manifest) {
45
- throw new ArgumentError(`actiondock.json not found in ${root}`);
46
- }
39
+ const { root, manifest } = requireProjectManifestRoot(options.package);
47
40
  // 包说明符白名单前置校验:拒绝 shell 元字符,消除 win32 shell 拼接注入面
48
41
  assertSafePackageSpec(packageSpec);
49
42
  const npmPackageName = extractNpmPackageName(packageSpec);
@@ -97,9 +90,9 @@ export function registerAddCommand(program, context) {
97
90
  manifest.dependencies = manifest.dependencies || {};
98
91
  manifest.dependencies[targetPackageId] =
99
92
  targetPackageId === npmPackageName ? `^${targetVersion}` : npmPackageName;
100
- // 触发依赖图解析及版本冲突检测
101
- const resolver = new ActionPackageResolver({ projectRoot: root, manifest });
102
- const graph = resolver.resolveSync();
93
+ // 触发依赖图构建及版本冲突检测
94
+ const builder = new PackageGraphBuilder({ projectRoot: root, manifest });
95
+ const graph = builder.buildSync();
103
96
  // 更新 actiondock.lock.json
104
97
  const lockfile = loadLockfile(root) || {
105
98
  lockfileVersion: 1,
@@ -137,8 +130,7 @@ export function registerAddCommand(program, context) {
137
130
  });
138
131
  }
139
132
  catch (err) {
140
- await tx.rollback({ frozenInstall: false });
141
- throw new ExecutionError(err.message, err, err.code || "ADD_DEPENDENCY_FAILED");
133
+ await rollbackAndRethrow(tx, err, "ADD_DEPENDENCY_FAILED");
142
134
  }
143
135
  });
144
136
  }
@@ -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, parseListOption } from "../utils/index.js";
3
+ import { getEffectiveOptions, parseListOption, requirePackageRoot } from "../utils/index.js";
5
4
  export function registerBuildCommand(program, context) {
6
5
  program
7
6
  .command("build")
@@ -14,23 +13,9 @@ export function registerBuildCommand(program, context) {
14
13
  .option("--vendor-deps", "Materialize locked production dependencies into the output")
15
14
  .option("--allow-install-scripts", "Allow lifecycle install scripts to run during vendor-deps")
16
15
  .option("--require-reproducible", "Require reproducible build and fail if install scripts must run")
17
- .option("-t, --target <target>", "Target compilation platform (removed)")
18
- .option("--bytecode", "Bytecode compilation (removed)")
19
16
  .action(async (rawOptions, cmd) => {
20
17
  const options = getEffectiveOptions(rawOptions, cmd);
21
- // 彻底删除原有单文件二进制输出语义
22
- if (options.target !== undefined || options.bytecode !== undefined) {
23
- throw new ExecutionError("Unsupported build mode: '--target' and '--bytecode' standalone single-file binary compilation have been removed in ActionDock 2.0. Directory-based Node.js builds are now the standard distribution format.", undefined, "UNSUPPORTED_BUILD_MODE");
24
- }
25
- const root = options.package
26
- ? resolvePackageRoot(options.package)
27
- : findProjectRoot();
28
- if (!root) {
29
- if (options.package) {
30
- throw packageNotFoundError(options.package);
31
- }
32
- throw notInProjectError("Please specify -P, --package <id> or cd into a project directory.");
33
- }
18
+ const { root } = requirePackageRoot(options.package);
34
19
  const isMachine = Boolean(options.json);
35
20
  if (!isMachine) {
36
21
  writeStdout("Building Node.js delivery artifact...");
@@ -1,7 +1,8 @@
1
- import { loadProjectConfig, resolvePackageRoot, } from "@actiondock/core";
1
+ import { loadProjectConfig, } from "@actiondock/core";
2
+ import { resolvePackageRoot, } from "@actiondock/core/registry";
2
3
  import { ArgumentError, packageNotFoundError } from "../../errors.js";
3
4
  import { writeStdout } from "../../renderer.js";
4
- import { applyTargetOptions, getEffectiveOptions, withTarget } from "../../utils/index.js";
5
+ import { applyTargetOptions, getEffectiveOptions, withService } from "../../utils/index.js";
5
6
  /**
6
7
  * 注册 config delete 子命令:删除配置项。
7
8
  *
@@ -22,14 +23,14 @@ export function registerConfigDeleteCommand(configCmd, context) {
22
23
  throw new ArgumentError("Configuration key is required");
23
24
  }
24
25
  const root = resolvePackageRoot(options.package);
25
- await withTarget(options, context, async (target, resolved) => {
26
+ await withService(options, context, async (service, resolved) => {
26
27
  if (resolved.type === "remote") {
27
- await target.deleteConfig(options.package || "", key);
28
+ await service.management?.config.delete(options.package || "", key);
28
29
  writeStdout(`[OK] Configuration '${key}' deleted from remote server`, context);
29
30
  return;
30
31
  }
31
32
  if (options.global) {
32
- await target.deleteConfig("global", key);
33
+ await service.management?.config.delete("global", key);
33
34
  writeStdout(`[OK] Global configuration '${key}' deleted`, context);
34
35
  return;
35
36
  }
@@ -37,12 +38,12 @@ export function registerConfigDeleteCommand(configCmd, context) {
37
38
  if (options.package) {
38
39
  throw packageNotFoundError(options.package);
39
40
  }
40
- await target.deleteConfig("global", key);
41
+ await service.management?.config.delete("global", key);
41
42
  writeStdout(`[OK] Global configuration '${key}' deleted`, context);
42
43
  return;
43
44
  }
44
45
  const projConfig = loadProjectConfig(root);
45
- await target.deleteConfig(projConfig.id, key);
46
+ await service.management?.config.delete(projConfig.id, key);
46
47
  writeStdout(`[OK] Configuration '${key}' deleted for package '${projConfig.id}'`, context);
47
48
  });
48
49
  });
@@ -1,7 +1,7 @@
1
- import { fetchRemoteConfigEnv, isSecretConfigKey, loadProjectConfig, resolveEnvValue, resolvePackageRoot, } from "@actiondock/core";
2
- import { notInProjectError, packageNotFoundError } from "../../errors.js";
1
+ import { fetchRemoteConfigEnv, resolveEnvValue, } from "@actiondock/core/profile";
2
+ import { isSecretConfigKey, } from "@actiondock/core/project";
3
3
  import { renderConfigEnv, renderResult } from "../../renderer.js";
4
- import { applyTargetOptions, getEffectiveOptions, resolveTargetFromOptions } from "../../utils/index.js";
4
+ import { applyTargetOptions, getEffectiveOptions, requirePackageRoot, resolveTargetFromOptions } from "../../utils/index.js";
5
5
  /**
6
6
  * 注册 config env 子命令:诊断环境变量对声明配置的满足率。
7
7
  *
@@ -36,14 +36,10 @@ export function registerConfigEnvCommand(configCmd, context) {
36
36
  return;
37
37
  }
38
38
  // 本地项目分支
39
- const root = resolvePackageRoot(targetPkg);
40
- if (!root) {
41
- if (targetPkg) {
42
- throw packageNotFoundError(targetPkg);
43
- }
44
- throw notInProjectError("Usage: ad config env [package-id] or cd into a project directory.");
45
- }
46
- const projConfig = loadProjectConfig(root);
39
+ const { root, projConfig } = requirePackageRoot(targetPkg, {
40
+ loadConfig: true,
41
+ hint: "Usage: ad config env [package-id] or cd into a project directory.",
42
+ });
47
43
  const declared = projConfig.config || {};
48
44
  const envChecks = [];
49
45
  for (const [key, itemDef] of Object.entries(declared)) {
@@ -1,8 +1,11 @@
1
- import { fetchRemoteConfig, isSecretConfigKey, loadProjectConfig, maskSecretValue, resolvePackageRoot, } from "@actiondock/core";
1
+ import { fetchRemoteConfig, } from "@actiondock/core/profile";
2
+ import { isSecretConfigKey, maskSecretValue, } from "@actiondock/core/project";
3
+ import { loadProjectConfig, } from "@actiondock/core";
4
+ import { resolvePackageRoot, } from "@actiondock/core/registry";
2
5
  import { ArgumentError, packageNotFoundError } from "../../errors.js";
3
6
  import { renderResult } from "../../renderer.js";
4
7
  import { resolveMergedConfigEntry } from "../../services/config-merge.js";
5
- import { applyTargetOptions, getEffectiveOptions, resolveTargetFromOptions, withTarget, } from "../../utils/index.js";
8
+ import { applyTargetOptions, getEffectiveOptions, resolveTargetFromOptions, withService, } from "../../utils/index.js";
6
9
  /**
7
10
  * 注册 config get 子命令:查询指定配置键的生效值。
8
11
  *
@@ -49,12 +52,12 @@ export function registerConfigGetCommand(configCmd, context) {
49
52
  }
50
53
  // 本地分支
51
54
  const root = resolvePackageRoot(options.package);
52
- await withTarget(options, context, async (localTarget) => {
55
+ await withService(options, context, async (service) => {
53
56
  if (options.global || !root) {
54
57
  if (!options.global && options.package && !root) {
55
58
  throw packageNotFoundError(options.package);
56
59
  }
57
- const confView = await localTarget.getConfig("global", key);
60
+ const confView = await service.management?.config.get("global", key);
58
61
  const val = confView?.value;
59
62
  const isSecret = confView?.secret ?? isSecretConfigKey(key);
60
63
  const displayValue = !reveal && isSecret && val !== undefined ? maskSecretValue(val) : val;
@@ -74,7 +77,7 @@ export function registerConfigGetCommand(configCmd, context) {
74
77
  const projConfig = loadProjectConfig(root);
75
78
  const declaredItem = projConfig.config?.[key];
76
79
  // 合并优先级链单一事实源:项目包级 > 全局持久化 > 环境变量 > 声明默认值
77
- const { value: displayValue, source, secret: isSecret } = await resolveMergedConfigEntry(key, projConfig.id, declaredItem, localTarget, reveal);
80
+ const { value: displayValue, source, secret: isSecret } = await resolveMergedConfigEntry(key, projConfig.id, declaredItem, service, reveal);
78
81
  const payload = {
79
82
  key,
80
83
  value: displayValue,
@@ -1,8 +1,11 @@
1
- import { fetchRemoteConfig, filterWithFallbackInfo, findProjectRoot, isSecretConfigKey, loadProjectConfig, maskSecretValue, resolvePackageRoot, } from "@actiondock/core";
1
+ import { fetchRemoteConfig, } from "@actiondock/core/profile";
2
+ import { findProjectRoot, loadProjectConfig, } from "@actiondock/core";
3
+ import { filterWithFallbackInfo, isSecretConfigKey, maskSecretValue, } from "@actiondock/core/project";
4
+ import { resolvePackageRoot, } from "@actiondock/core/registry";
2
5
  import { packageNotFoundError } from "../../errors.js";
3
6
  import { renderConfigList, renderResult } from "../../renderer.js";
4
7
  import { buildMergedConfigEntries } from "../../services/config-merge.js";
5
- import { applyTargetOptions, getEffectiveOptions, resolveIntent, resolveTargetFromOptions, withTarget, } from "../../utils/index.js";
8
+ import { applyTargetOptions, getEffectiveOptions, remoteTargetLabel, resolveFallbackStrategy, resolveIntent, resolveTargetFromOptions, withService, } from "../../utils/index.js";
6
9
  /**
7
10
  * 构造单条配置项的展示视图(含掩码决策,reveal 为假且为敏感键时打码)。
8
11
  * 掩码决策收敛于命令层,renderer 仅负责值的字符串格式化。
@@ -31,13 +34,14 @@ export function registerConfigListCommand(configCmd, context) {
31
34
  .option("-g, --global", "Show only global configurations"))
32
35
  .option("-i, --intent <pattern>", "Regex or fuzzy intent filter; falls back to full list when no match")
33
36
  .option("--reveal, --show-secrets", "Reveal plain text values for secrets")
37
+ .option("--fallback", "Enable fallback to full list when no items match intent")
34
38
  .option("--no-fallback", "Disable fallback to full list when no items match intent")
35
39
  .option("--data-dir <path>", "Custom database storage directory")
36
40
  .option("--json", "Output as JSON")
37
41
  .action(async (patterns = [], rawOptions, cmd) => {
38
42
  const options = getEffectiveOptions(rawOptions, cmd);
39
43
  const effectiveIntent = resolveIntent(options.intent, patterns);
40
- const shouldFallback = options.fallback !== false;
44
+ const { shouldFallback } = resolveFallbackStrategy(options);
41
45
  const reveal = Boolean(options.reveal || options.showSecrets);
42
46
  // 远端服务分支不经过 Target 门面,直接查询远端配置接口
43
47
  const remoteTargetInfo = resolveTargetFromOptions(options, context);
@@ -49,42 +53,32 @@ export function registerConfigListCommand(configCmd, context) {
49
53
  const entries = Object.entries(res.values || {}).map(([k, v]) => toDisplayEntry(k, v, "remote", isSecretConfigKey(k, res.declared?.[k]), reveal, res.declared?.[k]?.description || ""));
50
54
  renderResult(entries, {
51
55
  json: options.json,
52
- humanFormatter: () => renderConfigList(entries, `Remote Server ${remoteTargetInfo.serverUrl}${remoteTargetInfo.profileName ? ` (Profile: ${remoteTargetInfo.profileName})` : ""}`, false, effectiveIntent),
56
+ humanFormatter: () => renderConfigList(entries, remoteTargetLabel(remoteTargetInfo), false, effectiveIntent),
53
57
  context,
54
58
  });
55
59
  return;
56
60
  }
57
- // 本地分支(通过 Target 门面统一访问)
58
- await withTarget(options, context, async (localTarget) => {
61
+ // 本地分支(通过 Service 门面统一访问)
62
+ await withService(options, context, async (service) => {
59
63
  const root = options.package ? resolvePackageRoot(options.package) : findProjectRoot();
60
- if (options.global) {
61
- const all = await localTarget.listConfig("global");
62
- const entries = all.map((item) => toDisplayEntry(item.key, item.value, "global", item.secret || isSecretConfigKey(item.key), reveal));
63
- const filterRes = filterWithFallbackInfo(entries, effectiveIntent, [(c) => c.key, (c) => c.value], shouldFallback);
64
- renderResult(filterRes.items, {
65
- json: options.json,
66
- humanFormatter: () => renderConfigList(filterRes.items, "Global Scope", filterRes.isFallback, effectiveIntent),
67
- context,
68
- });
69
- return;
70
- }
71
- if (!root) {
72
- if (options.package) {
64
+ // 全局作用域分支:显式 --global 或无工程回退时仅列举全局配置
65
+ if (options.global || !root) {
66
+ if (!options.global && options.package) {
73
67
  throw packageNotFoundError(options.package);
74
68
  }
75
- const all = await localTarget.listConfig("global");
69
+ const all = (await service.management?.config.list("global")) ?? [];
76
70
  const entries = all.map((item) => toDisplayEntry(item.key, item.value, "global", item.secret || isSecretConfigKey(item.key), reveal));
77
71
  const filterRes = filterWithFallbackInfo(entries, effectiveIntent, [(c) => c.key, (c) => c.value], shouldFallback);
78
72
  renderResult(filterRes.items, {
79
73
  json: options.json,
80
- humanFormatter: () => renderConfigList(filterRes.items, "Global Scope (No project found)", filterRes.isFallback, effectiveIntent),
74
+ humanFormatter: () => renderConfigList(filterRes.items, options.global ? "Global Scope" : "Global Scope (No project found)", filterRes.isFallback, effectiveIntent),
81
75
  context,
82
76
  });
83
77
  return;
84
78
  }
85
79
  const projConfig = loadProjectConfig(root);
86
80
  // 跨作用域合并视图单一事实源:项目包级 > 全局持久化 > 环境变量 > 声明默认值
87
- const merged = await buildMergedConfigEntries(root, localTarget, reveal);
81
+ const merged = await buildMergedConfigEntries(root, service, reveal);
88
82
  const filterRes = filterWithFallbackInfo(merged, effectiveIntent, [(c) => c.key, (c) => c.value, (c) => c.description, (c) => c.source], shouldFallback);
89
83
  renderResult(filterRes.items, {
90
84
  json: options.json,
@@ -3,7 +3,13 @@ import type { CliContext } from "../../types.js";
3
3
  /**
4
4
  * 注册 config schema 子命令:检查声明配置的解析状态。
5
5
  *
6
+ * 状态推导复用 config-merge 的跨作用域合并视图单一事实源
7
+ * (项目包级 > 全局持久化 > 环境变量 > 声明默认值):
8
+ * - source 命中 project、global 或 env 时状态为 SET;
9
+ * - source 为 default(仅声明默认值兜底)时状态为 DEFAULT;
10
+ * - 键无任何来源(不在合并视图中,即未声明默认值也未持久化)时状态为 MISSING。
11
+ *
6
12
  * @param configCmd config 命令实例
7
- * @param context 命令行上下文
13
+ * @param context CLI 上下文
8
14
  */
9
15
  export declare function registerConfigSchemaCommand(configCmd: Command, context?: CliContext): void;
@@ -1,80 +1,63 @@
1
- import { createActionDockTarget, isSecretConfigKey, loadProjectConfig, resolveEnvValue, resolvePackageRoot, } from "@actiondock/core";
2
- import { ArgumentError, ExecutionError, notInProjectError, packageNotFoundError } from "../../errors.js";
1
+ import { isSecretConfigKey, } from "@actiondock/core/project";
2
+ import { notInProjectError } from "../../errors.js";
3
3
  import { renderConfigSchema, renderResult, writeStdout } from "../../renderer.js";
4
- import { getEffectiveOptions } from "../../utils/index.js";
4
+ import { buildMergedConfigEntries } from "../../services/config-merge.js";
5
+ import { applyTargetOptions, getEffectiveOptions, requirePackageRoot, withService } from "../../utils/index.js";
5
6
  /**
6
7
  * 注册 config schema 子命令:检查声明配置的解析状态。
7
8
  *
9
+ * 状态推导复用 config-merge 的跨作用域合并视图单一事实源
10
+ * (项目包级 > 全局持久化 > 环境变量 > 声明默认值):
11
+ * - source 命中 project、global 或 env 时状态为 SET;
12
+ * - source 为 default(仅声明默认值兜底)时状态为 DEFAULT;
13
+ * - 键无任何来源(不在合并视图中,即未声明默认值也未持久化)时状态为 MISSING。
14
+ *
8
15
  * @param configCmd config 命令实例
9
- * @param context 命令行上下文
16
+ * @param context CLI 上下文
10
17
  */
11
18
  export function registerConfigSchemaCommand(configCmd, context) {
12
- configCmd
19
+ applyTargetOptions(configCmd
13
20
  .command("schema [identifier]")
14
21
  .alias("check")
15
22
  .description("Inspect declared configuration requirements and check resolution status")
16
- .option("-P, --package <id>", "Target package ID or path")
23
+ .option("-P, --package <id>", "Target package ID or path"))
17
24
  .option("--data-dir <path>", "Custom database storage directory")
18
25
  .option("--json", "Output as JSON")
19
26
  .action(async (identifier, rawOptions, cmd) => {
20
- try {
21
- const options = getEffectiveOptions(rawOptions, cmd);
22
- const targetPkg = identifier || options.package;
23
- const root = resolvePackageRoot(targetPkg);
24
- if (!root) {
25
- if (targetPkg) {
26
- throw packageNotFoundError(targetPkg);
27
- }
28
- throw notInProjectError("Usage: ad config schema [package-id] or cd into a project directory.");
29
- }
30
- const projConfig = loadProjectConfig(root);
31
- const declared = projConfig.config || {};
32
- const declaredKeys = Object.keys(declared);
33
- const target = await createActionDockTarget({
34
- type: "local",
35
- projectRoot: root,
36
- customHome: context?.customHome,
37
- dataDir: options.dataDir || context?.dataDir,
38
- });
39
- let globalConfig = [];
40
- let projectConfig = [];
41
- try {
42
- globalConfig = await target.listConfig("global");
43
- projectConfig = await target.listConfig(projConfig.id);
44
- }
45
- finally {
46
- await target.close();
47
- }
48
- const projectConfigMap = new Map(projectConfig.map((c) => [c.key, c]));
49
- const globalConfigMap = new Map(globalConfig.map((c) => [c.key, c]));
50
- const items = declaredKeys.map((key) => {
27
+ const options = getEffectiveOptions(rawOptions, cmd);
28
+ const targetPkg = identifier || options.package;
29
+ const { root, projConfig } = requirePackageRoot(targetPkg, {
30
+ loadConfig: true,
31
+ hint: "Usage: ad config schema [package-id] or cd into a project directory.",
32
+ });
33
+ const declared = projConfig.config || {};
34
+ // 远端目标显式拒绝:schema 状态推导依赖本地工程声明与环境变量解析,
35
+ // 远端作用域无法获得等价视图,静默落到本地会误导调用方
36
+ if (options.profile || options.server) {
37
+ throw notInProjectError("'ad config schema' only supports local projects. Remote targets are not supported for schema inspection.");
38
+ }
39
+ // 跨作用域合并视图单一事实源:与 config list 共享同一条优先级链
40
+ await withService(options, context, async (service) => {
41
+ const merged = await buildMergedConfigEntries(root, service, true);
42
+ const mergedByKey = new Map(merged.map((entry) => [entry.key, entry]));
43
+ const items = Object.keys(declared).map((key) => {
51
44
  const itemDef = declared[key];
52
45
  const isSecret = isSecretConfigKey(key, itemDef);
53
- let resolvedValue;
46
+ const mergedEntry = mergedByKey.get(key);
54
47
  let source = "missing";
55
48
  let status = "MISSING";
56
- const envResolved = resolveEnvValue(key, itemDef, projConfig.id);
57
- const projItem = projectConfigMap.get(key);
58
- const globItem = globalConfigMap.get(key);
59
- if (projItem && projItem.configured && projItem.source === "package") {
60
- resolvedValue = projItem.value;
61
- source = "project";
62
- status = "SET";
63
- }
64
- else if (globItem && globItem.configured) {
65
- resolvedValue = globItem.value;
66
- source = "global";
67
- status = "SET";
68
- }
69
- else if (envResolved !== undefined) {
70
- resolvedValue = envResolved.value;
71
- source = "env";
72
- status = "SET";
73
- }
74
- else if (itemDef.default !== undefined) {
75
- resolvedValue = itemDef.default;
76
- source = "default";
77
- status = "DEFAULT";
49
+ let resolvedValue;
50
+ if (mergedEntry) {
51
+ source = mergedEntry.source;
52
+ resolvedValue = mergedEntry.value;
53
+ // 合并视图的 default 来源仅是链尾兑底标签:
54
+ // 真正的 DEFAULT 状态要求声明确实携带默认值,否则视为 MISSING
55
+ if (mergedEntry.source !== "default") {
56
+ status = "SET";
57
+ }
58
+ else if (itemDef.default !== undefined) {
59
+ status = "DEFAULT";
60
+ }
78
61
  }
79
62
  return {
80
63
  key,
@@ -108,12 +91,6 @@ export function registerConfigSchemaCommand(configCmd, context) {
108
91
  if (!ok) {
109
92
  process.exitCode = 1;
110
93
  }
111
- }
112
- catch (err) {
113
- if (err instanceof ArgumentError || err instanceof ExecutionError) {
114
- throw err;
115
- }
116
- throw new ExecutionError(err.message);
117
- }
94
+ }, { localRoot: root });
118
95
  });
119
96
  }