@actiondock/cli 2.5.1 → 2.6.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.
- package/README.md +44 -3
- package/dist/commands/action/create.js +2 -2
- package/dist/commands/add.js +5 -5
- package/dist/commands/build.js +4 -5
- package/dist/commands/config/delete.js +2 -2
- package/dist/commands/config/env.js +6 -15
- package/dist/commands/config/get.js +8 -39
- package/dist/commands/config/list.js +8 -55
- package/dist/commands/config/schema.js +4 -6
- package/dist/commands/config/set.js +2 -2
- package/dist/commands/describe.js +2 -4
- package/dist/commands/doctor.js +3 -10
- package/dist/commands/export.js +4 -7
- package/dist/commands/generate.js +2 -4
- package/dist/commands/index.js +2 -5
- package/dist/commands/info.js +7 -15
- package/dist/commands/list.js +3 -5
- package/dist/commands/mcp.js +10 -3
- package/dist/commands/pack.js +4 -5
- package/dist/commands/playbook.js +14 -36
- package/dist/commands/profile.js +4 -11
- package/dist/commands/remove.js +13 -9
- package/dist/commands/run.d.ts +8 -0
- package/dist/commands/run.js +118 -33
- package/dist/commands/runs.js +10 -25
- package/dist/commands/serve.js +9 -1
- package/dist/commands/state-list.js +2 -5
- package/dist/commands/state.js +29 -16
- package/dist/commands/test.js +6 -2
- package/dist/commands/validate.js +3 -5
- package/dist/errors.d.ts +14 -1
- package/dist/errors.js +20 -2
- package/dist/index.js +1 -2
- package/dist/renderer.d.ts +0 -2
- package/dist/renderer.js +3 -15
- package/dist/services/config-merge.d.ts +41 -0
- package/dist/services/config-merge.js +93 -0
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/types.d.ts +0 -2
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/input.d.ts +46 -0
- package/dist/utils/input.js +89 -0
- package/dist/utils/spawn.d.ts +13 -0
- package/dist/utils/spawn.js +31 -0
- package/dist/utils/target.d.ts +21 -0
- package/dist/utils/target.js +29 -14
- package/dist/utils/utils.d.ts +2 -3
- package/dist/utils/utils.js +6 -6
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -51,7 +51,14 @@ ad add @actiondock/example-tools
|
|
|
51
51
|
|
|
52
52
|
- 本地执行 Action:
|
|
53
53
|
```bash
|
|
54
|
+
# 简单参数直接内联传递
|
|
54
55
|
ad run sample.greet --input '{"name": "Alice"}'
|
|
56
|
+
|
|
57
|
+
# 复杂参数或对象从文件读取
|
|
58
|
+
ad run sample.greet --input-file input.json
|
|
59
|
+
|
|
60
|
+
# 自动化与脚本通过标准输入传递
|
|
61
|
+
cat input.json | ad run sample.greet --input-file -
|
|
55
62
|
```
|
|
56
63
|
|
|
57
64
|
- 运行单元测试:
|
|
@@ -92,14 +99,48 @@ ad pack
|
|
|
92
99
|
- `-v, -V, --version`:打印版本号并退出。
|
|
93
100
|
- `-h, --help`:打印命令帮助并退出。
|
|
94
101
|
- `--json`:以标准 JSON 格式输出结果。
|
|
95
|
-
- `--
|
|
96
|
-
-
|
|
102
|
+
- `--data-dir <path>`:指定运行时数据库存储目录(包级 `runtime.db`、全局 `global.db` 与目录排他锁),实现多测试或多任务运行数据隔离。注意:本选项仅隔离 SQLite 数据文件,环境配置(profile)与全局注册表(registry)属于用户根目录资产,不受 `--data-dir` 影响。
|
|
103
|
+
- `ACTIONDOCK_HOME=<path>`(环境变量):指定 ActionDock 用户根目录基准(默认对应操作系统的用户主目录)。如需隔离环境配置 `profiles.json`、全局注册表 `registry.json` 或本地证书,请通过设置此环境变量实现沙箱与自动化测试隔离。
|
|
97
104
|
|
|
98
105
|
### 严格目标解析机制
|
|
99
106
|
|
|
100
107
|
- 严格目标定位:通过 `-P, --package <id|path>` 指定目标包。系统严格区分物理路径与注册表包标识符,若目标不存在则不向当前目录或父级目录隐式回退,直接以参数校验错误退出。
|
|
101
108
|
- 多目标检索契约:在执行多目标检索(如 `ad info` 或 `ad list`)时,若无任何匹配项,在机器模式下始终返回确定性空数组结构并以状态码 0 退出,不因空搜索产生异常中断。
|
|
102
109
|
|
|
110
|
+
### Action 执行入参契约
|
|
111
|
+
|
|
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 标记,且不设人为大小上限。
|
|
120
|
+
|
|
121
|
+
### Windows 与多终端传参建议
|
|
122
|
+
|
|
123
|
+
针对 Windows PowerShell、cmd 以及各终端中复杂 JSON 容易遇到的双引号转义问题,推荐按以下规范传参:
|
|
124
|
+
|
|
125
|
+
- 简单入参:使用 `--input`,如 `ad run greet --input '{"name":"Alice"}'`。
|
|
126
|
+
- 复杂结构:推荐先保存为 JSON 文件并使用 `--input-file`,如 `ad run complex-action --input-file input.json`。
|
|
127
|
+
- 动态生成输入:通过管道输出配合 `--input-file -` 传递,在 PowerShell 中可执行:
|
|
128
|
+
|
|
129
|
+
```powershell
|
|
130
|
+
$data = @{
|
|
131
|
+
name = "Alice"
|
|
132
|
+
options = @{
|
|
133
|
+
lang = "zh-CN"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
$data | ConvertTo-Json -Depth 100 | ad run complex-action --input-file -
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Action 输出模式与机器模式
|
|
140
|
+
|
|
141
|
+
- **默认原始纯文本输出**:`ad run` 默认采用面向终端人类阅读与 LLM Agent 上下文消费的原始输出模式。直接将执行结果正文内容(如文件 `content`、`text`、`message` 或标量字符串)输出至 stdout(保留真实换行且无 JSON 转义),相关元数据(如文件路径、行号范围、截断标记)独立输出至 stderr,符合 Unix 管道安全原则。执行失败时在 stderr 输出错误详情并以退出码 1 退出。
|
|
142
|
+
- **机器 JSON 格式 (`--json`)**:当需要以程序化方式消费、获取完整结构化数据或被外部系统集成时,传入 `--json`。此时输出标准 JSON 结果(包含 `ok`、`runId`、`data` 或 `error`)。
|
|
143
|
+
|
|
103
144
|
---
|
|
104
145
|
|
|
105
146
|
## 常用命令速查
|
|
@@ -112,7 +153,7 @@ ad pack
|
|
|
112
153
|
| `ad info [patterns...]` | 检索包元数据与能力清单,支持模式匹配与树形展示 |
|
|
113
154
|
| `ad list [patterns...]` | 列出包内所有已注册的 Action |
|
|
114
155
|
| `ad describe <id>` | 查看 Action 的详情、参数与模式规范(别名 `ad show`) |
|
|
115
|
-
| `ad run <id>` | 本地或远程执行指定 Action
|
|
156
|
+
| `ad run <id>` | 本地或远程执行指定 Action(默认输出原始纯文本,支持 `--json` 输出标准信封) |
|
|
116
157
|
| `ad validate [id]` | 校验 Action 规范与模式规范 |
|
|
117
158
|
| `ad doctor` | 执行运行环境与项目结构健康诊断 |
|
|
118
159
|
| `ad action create <id>` | 创建新 Action 源码(别名 `ad action new`) |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
3
|
import { assertPathWithinRoot, findProjectRoot, getPackageSlug, loadManifest, loadProjectConfig, saveManifest, writeActionTypes, } from "@actiondock/core";
|
|
4
|
-
import { ExecutionError } from "../../errors.js";
|
|
4
|
+
import { ExecutionError, notInProjectError } from "../../errors.js";
|
|
5
5
|
import { writeStdout } from "../../renderer.js";
|
|
6
6
|
import { attachDescribeCommand } from "../describe.js";
|
|
7
7
|
import { attachListCommand } from "../list.js";
|
|
@@ -89,7 +89,7 @@ export function registerActionCommands(program, context) {
|
|
|
89
89
|
export async function handleActionCreate(id, options, context) {
|
|
90
90
|
const root = findProjectRoot();
|
|
91
91
|
if (!root) {
|
|
92
|
-
throw
|
|
92
|
+
throw notInProjectError();
|
|
93
93
|
}
|
|
94
94
|
try {
|
|
95
95
|
const config = loadProjectConfig(root);
|
package/dist/commands/add.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import { ActionPackageResolver, beginTransaction, computeManifestDigest, findProjectRoot, getInstallCommand, loadLockfile, loadManifest, MANIFEST_FILE_NAME, parseJsonWithoutDuplicates, saveLockfile, saveManifest, } from "@actiondock/core";
|
|
4
|
-
import { ArgumentError, ExecutionError } from "../errors.js";
|
|
4
|
+
import { ArgumentError, ExecutionError, notInProjectError } from "../errors.js";
|
|
5
5
|
import { renderResult } from "../renderer.js";
|
|
6
|
-
import { getEffectiveOptions, spawnAsync } from "../utils/index.js";
|
|
6
|
+
import { assertSafePackageSpec, getEffectiveOptions, spawnAsync } from "../utils/index.js";
|
|
7
7
|
/**
|
|
8
8
|
* 从安装说明符中提取基础 npm 包名(去除版本号及前缀范围)。
|
|
9
9
|
*/
|
|
@@ -34,17 +34,18 @@ export function registerAddCommand(program, context) {
|
|
|
34
34
|
.option("-D, --dev", "Install package as development dependency")
|
|
35
35
|
.option("-P, --package <path>", "Target project directory path")
|
|
36
36
|
.option("--json", "Output as JSON")
|
|
37
|
-
.option("--envelope", "Wrap JSON output in standard envelope")
|
|
38
37
|
.action(async (packageSpec, rawOptions, cmd) => {
|
|
39
38
|
const options = getEffectiveOptions(rawOptions, cmd);
|
|
40
39
|
const root = options.package ? resolve(options.package) : findProjectRoot();
|
|
41
40
|
if (!root) {
|
|
42
|
-
throw
|
|
41
|
+
throw notInProjectError("Please specify -P, --package <path> or cd into a project directory.");
|
|
43
42
|
}
|
|
44
43
|
const manifest = loadManifest(root);
|
|
45
44
|
if (!manifest) {
|
|
46
45
|
throw new ArgumentError(`actiondock.json not found in ${root}`);
|
|
47
46
|
}
|
|
47
|
+
// 包说明符白名单前置校验:拒绝 shell 元字符,消除 win32 shell 拼接注入面
|
|
48
|
+
assertSafePackageSpec(packageSpec);
|
|
48
49
|
const npmPackageName = extractNpmPackageName(packageSpec);
|
|
49
50
|
// 获取排他修改锁并创建事务快照
|
|
50
51
|
const tx = await beginTransaction(root, `ad add ${packageSpec}`);
|
|
@@ -131,7 +132,6 @@ export function registerAddCommand(program, context) {
|
|
|
131
132
|
};
|
|
132
133
|
renderResult(resultPayload, {
|
|
133
134
|
json: options.json,
|
|
134
|
-
envelope: options.envelope,
|
|
135
135
|
humanFormatter: () => `Successfully added and locked '${resultPayload.packageId}' (v${resultPayload.version}) from ${resultPayload.npmPackage}.`,
|
|
136
136
|
context,
|
|
137
137
|
});
|
package/dist/commands/build.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { findProjectRoot, resolvePackageRoot } from "@actiondock/core";
|
|
2
|
-
import { ExecutionError } from "../errors.js";
|
|
2
|
+
import { ExecutionError, notInProjectError, packageNotFoundError } from "../errors.js";
|
|
3
3
|
import { renderResult, writeStdout } from "../renderer.js";
|
|
4
4
|
import { getEffectiveOptions, parseListOption } from "../utils/index.js";
|
|
5
5
|
export function registerBuildCommand(program, context) {
|
|
@@ -27,11 +27,11 @@ export function registerBuildCommand(program, context) {
|
|
|
27
27
|
: findProjectRoot();
|
|
28
28
|
if (!root) {
|
|
29
29
|
if (options.package) {
|
|
30
|
-
throw
|
|
30
|
+
throw packageNotFoundError(options.package);
|
|
31
31
|
}
|
|
32
|
-
throw
|
|
32
|
+
throw notInProjectError("Please specify -P, --package <id> or cd into a project directory.");
|
|
33
33
|
}
|
|
34
|
-
const isMachine = Boolean(options.json
|
|
34
|
+
const isMachine = Boolean(options.json);
|
|
35
35
|
if (!isMachine) {
|
|
36
36
|
writeStdout("Building Node.js delivery artifact...");
|
|
37
37
|
}
|
|
@@ -57,7 +57,6 @@ export function registerBuildCommand(program, context) {
|
|
|
57
57
|
}
|
|
58
58
|
renderResult(result, {
|
|
59
59
|
json: isMachine,
|
|
60
|
-
envelope: options.envelope,
|
|
61
60
|
humanFormatter: () => {
|
|
62
61
|
const lines = [];
|
|
63
62
|
lines.push(`[OK] Successfully built ${result.packageId} (v${result.version})`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { loadProjectConfig, resolvePackageRoot, } from "@actiondock/core";
|
|
2
|
-
import { ArgumentError } from "../../errors.js";
|
|
2
|
+
import { ArgumentError, packageNotFoundError } from "../../errors.js";
|
|
3
3
|
import { writeStdout } from "../../renderer.js";
|
|
4
4
|
import { applyTargetOptions, getEffectiveOptions, withTarget } from "../../utils/index.js";
|
|
5
5
|
/**
|
|
@@ -35,7 +35,7 @@ export function registerConfigDeleteCommand(configCmd, context) {
|
|
|
35
35
|
}
|
|
36
36
|
if (!root) {
|
|
37
37
|
if (options.package) {
|
|
38
|
-
throw
|
|
38
|
+
throw packageNotFoundError(options.package);
|
|
39
39
|
}
|
|
40
40
|
await target.deleteConfig("global", key);
|
|
41
41
|
writeStdout(`[OK] Global configuration '${key}' deleted`, context);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { fetchRemoteConfigEnv, isSecretConfigKey, loadProjectConfig, resolveEnvValue, resolvePackageRoot,
|
|
2
|
-
import {
|
|
1
|
+
import { fetchRemoteConfigEnv, isSecretConfigKey, loadProjectConfig, resolveEnvValue, resolvePackageRoot, } from "@actiondock/core";
|
|
2
|
+
import { notInProjectError, packageNotFoundError } from "../../errors.js";
|
|
3
3
|
import { renderConfigEnv, renderResult } from "../../renderer.js";
|
|
4
|
-
import { applyTargetOptions, getEffectiveOptions } from "../../utils/index.js";
|
|
4
|
+
import { applyTargetOptions, getEffectiveOptions, resolveTargetFromOptions } from "../../utils/index.js";
|
|
5
5
|
/**
|
|
6
6
|
* 注册 config env 子命令:诊断环境变量对声明配置的满足率。
|
|
7
7
|
*
|
|
@@ -14,18 +14,11 @@ export function registerConfigEnvCommand(configCmd, context) {
|
|
|
14
14
|
.description("Diagnose environment variable satisfaction for declared configuration")
|
|
15
15
|
.option("-P, --package <id>", "Target package ID or path"))
|
|
16
16
|
.option("--json", "Output as JSON")
|
|
17
|
-
.option("--envelope", "Wrap JSON output in standard envelope")
|
|
18
17
|
.action(async (identifier, rawOptions, cmd) => {
|
|
19
18
|
const options = getEffectiveOptions(rawOptions, cmd);
|
|
20
19
|
const targetPkg = identifier || options.package;
|
|
21
20
|
// 远端服务分支直接查询远端配置环境诊断接口
|
|
22
|
-
const target =
|
|
23
|
-
profile: options.profile,
|
|
24
|
-
server: options.server,
|
|
25
|
-
token: options.token,
|
|
26
|
-
insecure: options.insecure,
|
|
27
|
-
allowInsecureHttp: options.allowInsecureHttp,
|
|
28
|
-
}, context?.customHome);
|
|
21
|
+
const target = resolveTargetFromOptions(options, context);
|
|
29
22
|
if (target.type === "remote") {
|
|
30
23
|
const res = await fetchRemoteConfigEnv(target.serverUrl, target.token, targetPkg, {
|
|
31
24
|
allowInsecureHttp: Boolean(options.allowInsecureHttp),
|
|
@@ -33,7 +26,6 @@ export function registerConfigEnvCommand(configCmd, context) {
|
|
|
33
26
|
});
|
|
34
27
|
renderResult(res, {
|
|
35
28
|
json: options.json,
|
|
36
|
-
envelope: options.envelope,
|
|
37
29
|
humanFormatter: () => renderConfigEnv(res.envChecks || [], res.packageId),
|
|
38
30
|
context,
|
|
39
31
|
});
|
|
@@ -47,9 +39,9 @@ export function registerConfigEnvCommand(configCmd, context) {
|
|
|
47
39
|
const root = resolvePackageRoot(targetPkg);
|
|
48
40
|
if (!root) {
|
|
49
41
|
if (targetPkg) {
|
|
50
|
-
throw
|
|
42
|
+
throw packageNotFoundError(targetPkg);
|
|
51
43
|
}
|
|
52
|
-
throw
|
|
44
|
+
throw notInProjectError("Usage: ad config env [package-id] or cd into a project directory.");
|
|
53
45
|
}
|
|
54
46
|
const projConfig = loadProjectConfig(root);
|
|
55
47
|
const declared = projConfig.config || {};
|
|
@@ -76,7 +68,6 @@ export function registerConfigEnvCommand(configCmd, context) {
|
|
|
76
68
|
};
|
|
77
69
|
renderResult(payload, {
|
|
78
70
|
json: options.json,
|
|
79
|
-
envelope: options.envelope,
|
|
80
71
|
humanFormatter: () => renderConfigEnv(envChecks, projConfig.id),
|
|
81
72
|
context,
|
|
82
73
|
});
|
|
@@ -1,19 +1,8 @@
|
|
|
1
|
-
import { fetchRemoteConfig, isSecretConfigKey, loadProjectConfig, maskSecretValue,
|
|
2
|
-
import { ArgumentError } from "../../errors.js";
|
|
1
|
+
import { fetchRemoteConfig, isSecretConfigKey, loadProjectConfig, maskSecretValue, resolvePackageRoot, } from "@actiondock/core";
|
|
2
|
+
import { ArgumentError, packageNotFoundError } from "../../errors.js";
|
|
3
3
|
import { renderResult } from "../../renderer.js";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
* 解析远端目标拓扑信息(仅信息解析,不创建 Target 实例)。
|
|
7
|
-
*/
|
|
8
|
-
function resolveRemoteTargetInfo(options, context) {
|
|
9
|
-
return resolveTarget({
|
|
10
|
-
profile: options.profile,
|
|
11
|
-
server: options.server,
|
|
12
|
-
token: options.token,
|
|
13
|
-
insecure: options.insecure,
|
|
14
|
-
allowInsecureHttp: options.allowInsecureHttp,
|
|
15
|
-
}, context?.customHome);
|
|
16
|
-
}
|
|
4
|
+
import { resolveMergedConfigEntry } from "../../services/config-merge.js";
|
|
5
|
+
import { applyTargetOptions, getEffectiveOptions, resolveTargetFromOptions, withTarget, } from "../../utils/index.js";
|
|
17
6
|
/**
|
|
18
7
|
* 注册 config get 子命令:查询指定配置键的生效值。
|
|
19
8
|
*
|
|
@@ -29,7 +18,6 @@ export function registerConfigGetCommand(configCmd, context) {
|
|
|
29
18
|
.option("--reveal, --show-secrets", "Reveal plain text values for secrets")
|
|
30
19
|
.option("--data-dir <path>", "Custom database storage directory")
|
|
31
20
|
.option("--json", "Output as JSON")
|
|
32
|
-
.option("--envelope", "Wrap JSON output in standard envelope")
|
|
33
21
|
.action(async (key, rawOptions, cmd) => {
|
|
34
22
|
const options = getEffectiveOptions(rawOptions, cmd);
|
|
35
23
|
if (!key) {
|
|
@@ -37,7 +25,7 @@ export function registerConfigGetCommand(configCmd, context) {
|
|
|
37
25
|
}
|
|
38
26
|
const reveal = Boolean(options.reveal || options.showSecrets);
|
|
39
27
|
// 远端服务分支不经过 Target 门面,直接查询远端配置接口
|
|
40
|
-
const target =
|
|
28
|
+
const target = resolveTargetFromOptions(options, context);
|
|
41
29
|
if (target.type === "remote") {
|
|
42
30
|
const res = await fetchRemoteConfig(target.serverUrl, target.token, options.package, {
|
|
43
31
|
allowInsecureHttp: Boolean(options.allowInsecureHttp),
|
|
@@ -54,7 +42,6 @@ export function registerConfigGetCommand(configCmd, context) {
|
|
|
54
42
|
};
|
|
55
43
|
renderResult(payload, {
|
|
56
44
|
json: options.json,
|
|
57
|
-
envelope: options.envelope,
|
|
58
45
|
humanFormatter: () => (displayValue !== undefined ? String(displayValue) : ""),
|
|
59
46
|
context,
|
|
60
47
|
});
|
|
@@ -65,7 +52,7 @@ export function registerConfigGetCommand(configCmd, context) {
|
|
|
65
52
|
await withTarget(options, context, async (localTarget) => {
|
|
66
53
|
if (options.global || !root) {
|
|
67
54
|
if (!options.global && options.package && !root) {
|
|
68
|
-
throw
|
|
55
|
+
throw packageNotFoundError(options.package);
|
|
69
56
|
}
|
|
70
57
|
const confView = await localTarget.getConfig("global", key);
|
|
71
58
|
const val = confView?.value;
|
|
@@ -79,7 +66,6 @@ export function registerConfigGetCommand(configCmd, context) {
|
|
|
79
66
|
};
|
|
80
67
|
renderResult(payload, {
|
|
81
68
|
json: options.json,
|
|
82
|
-
envelope: options.envelope,
|
|
83
69
|
humanFormatter: () => (displayValue !== undefined ? String(displayValue) : ""),
|
|
84
70
|
context,
|
|
85
71
|
});
|
|
@@ -87,24 +73,8 @@ export function registerConfigGetCommand(configCmd, context) {
|
|
|
87
73
|
}
|
|
88
74
|
const projConfig = loadProjectConfig(root);
|
|
89
75
|
const declaredItem = projConfig.config?.[key];
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
let source = confView?.source === "package" ? "project" : (confView?.source || "default");
|
|
93
|
-
const envResolved = resolveEnvValue(key, declaredItem, projConfig.id);
|
|
94
|
-
if (confView && confView.configured) {
|
|
95
|
-
resolvedVal = confView.value;
|
|
96
|
-
source = confView.source === "package" ? "project" : confView.source;
|
|
97
|
-
}
|
|
98
|
-
else if (envResolved !== undefined) {
|
|
99
|
-
resolvedVal = envResolved.value;
|
|
100
|
-
source = "env";
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
resolvedVal = declaredItem?.default;
|
|
104
|
-
source = "default";
|
|
105
|
-
}
|
|
106
|
-
const isSecret = confView?.secret ?? isSecretConfigKey(key, declaredItem);
|
|
107
|
-
const displayValue = !reveal && isSecret && resolvedVal !== undefined ? maskSecretValue(resolvedVal) : resolvedVal;
|
|
76
|
+
// 合并优先级链单一事实源:项目包级 > 全局持久化 > 环境变量 > 声明默认值
|
|
77
|
+
const { value: displayValue, source, secret: isSecret } = await resolveMergedConfigEntry(key, projConfig.id, declaredItem, localTarget, reveal);
|
|
108
78
|
const payload = {
|
|
109
79
|
key,
|
|
110
80
|
value: displayValue,
|
|
@@ -113,7 +83,6 @@ export function registerConfigGetCommand(configCmd, context) {
|
|
|
113
83
|
};
|
|
114
84
|
renderResult(payload, {
|
|
115
85
|
json: options.json,
|
|
116
|
-
envelope: options.envelope,
|
|
117
86
|
humanFormatter: () => (displayValue !== undefined ? String(displayValue) : ""),
|
|
118
87
|
context,
|
|
119
88
|
});
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { fetchRemoteConfig, filterWithFallbackInfo, findProjectRoot, isSecretConfigKey, loadProjectConfig, maskSecretValue,
|
|
2
|
-
import {
|
|
1
|
+
import { fetchRemoteConfig, filterWithFallbackInfo, findProjectRoot, isSecretConfigKey, loadProjectConfig, maskSecretValue, resolvePackageRoot, } from "@actiondock/core";
|
|
2
|
+
import { packageNotFoundError } from "../../errors.js";
|
|
3
3
|
import { renderConfigList, renderResult } from "../../renderer.js";
|
|
4
|
-
import {
|
|
4
|
+
import { buildMergedConfigEntries } from "../../services/config-merge.js";
|
|
5
|
+
import { applyTargetOptions, getEffectiveOptions, resolveIntent, resolveTargetFromOptions, withTarget, } from "../../utils/index.js";
|
|
5
6
|
/**
|
|
6
7
|
* 构造单条配置项的展示视图(含掩码决策,reveal 为假且为敏感键时打码)。
|
|
7
8
|
* 掩码决策收敛于命令层,renderer 仅负责值的字符串格式化。
|
|
@@ -33,20 +34,13 @@ export function registerConfigListCommand(configCmd, context) {
|
|
|
33
34
|
.option("--no-fallback", "Disable fallback to full list when no items match intent")
|
|
34
35
|
.option("--data-dir <path>", "Custom database storage directory")
|
|
35
36
|
.option("--json", "Output as JSON")
|
|
36
|
-
.option("--envelope", "Wrap JSON output in standard envelope")
|
|
37
37
|
.action(async (patterns = [], rawOptions, cmd) => {
|
|
38
38
|
const options = getEffectiveOptions(rawOptions, cmd);
|
|
39
39
|
const effectiveIntent = resolveIntent(options.intent, patterns);
|
|
40
40
|
const shouldFallback = options.fallback !== false;
|
|
41
41
|
const reveal = Boolean(options.reveal || options.showSecrets);
|
|
42
42
|
// 远端服务分支不经过 Target 门面,直接查询远端配置接口
|
|
43
|
-
const remoteTargetInfo =
|
|
44
|
-
profile: options.profile,
|
|
45
|
-
server: options.server,
|
|
46
|
-
token: options.token,
|
|
47
|
-
insecure: options.insecure,
|
|
48
|
-
allowInsecureHttp: options.allowInsecureHttp,
|
|
49
|
-
}, context?.customHome);
|
|
43
|
+
const remoteTargetInfo = resolveTargetFromOptions(options, context);
|
|
50
44
|
if (remoteTargetInfo.type === "remote") {
|
|
51
45
|
const res = await fetchRemoteConfig(remoteTargetInfo.serverUrl, remoteTargetInfo.token, options.package, {
|
|
52
46
|
allowInsecureHttp: Boolean(options.allowInsecureHttp),
|
|
@@ -55,7 +49,6 @@ export function registerConfigListCommand(configCmd, context) {
|
|
|
55
49
|
const entries = Object.entries(res.values || {}).map(([k, v]) => toDisplayEntry(k, v, "remote", isSecretConfigKey(k, res.declared?.[k]), reveal, res.declared?.[k]?.description || ""));
|
|
56
50
|
renderResult(entries, {
|
|
57
51
|
json: options.json,
|
|
58
|
-
envelope: options.envelope,
|
|
59
52
|
humanFormatter: () => renderConfigList(entries, `Remote Server ${remoteTargetInfo.serverUrl}${remoteTargetInfo.profileName ? ` (Profile: ${remoteTargetInfo.profileName})` : ""}`, false, effectiveIntent),
|
|
60
53
|
context,
|
|
61
54
|
});
|
|
@@ -70,7 +63,6 @@ export function registerConfigListCommand(configCmd, context) {
|
|
|
70
63
|
const filterRes = filterWithFallbackInfo(entries, effectiveIntent, [(c) => c.key, (c) => c.value], shouldFallback);
|
|
71
64
|
renderResult(filterRes.items, {
|
|
72
65
|
json: options.json,
|
|
73
|
-
envelope: options.envelope,
|
|
74
66
|
humanFormatter: () => renderConfigList(filterRes.items, "Global Scope", filterRes.isFallback, effectiveIntent),
|
|
75
67
|
context,
|
|
76
68
|
});
|
|
@@ -78,63 +70,24 @@ export function registerConfigListCommand(configCmd, context) {
|
|
|
78
70
|
}
|
|
79
71
|
if (!root) {
|
|
80
72
|
if (options.package) {
|
|
81
|
-
throw
|
|
73
|
+
throw packageNotFoundError(options.package);
|
|
82
74
|
}
|
|
83
75
|
const all = await localTarget.listConfig("global");
|
|
84
76
|
const entries = all.map((item) => toDisplayEntry(item.key, item.value, "global", item.secret || isSecretConfigKey(item.key), reveal));
|
|
85
77
|
const filterRes = filterWithFallbackInfo(entries, effectiveIntent, [(c) => c.key, (c) => c.value], shouldFallback);
|
|
86
78
|
renderResult(filterRes.items, {
|
|
87
79
|
json: options.json,
|
|
88
|
-
envelope: options.envelope,
|
|
89
80
|
humanFormatter: () => renderConfigList(filterRes.items, "Global Scope (No project found)", filterRes.isFallback, effectiveIntent),
|
|
90
81
|
context,
|
|
91
82
|
});
|
|
92
83
|
return;
|
|
93
84
|
}
|
|
94
85
|
const projConfig = loadProjectConfig(root);
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
const projectConfigList = await localTarget.listConfig(projConfig.id);
|
|
98
|
-
const projectConfigMap = new Map(projectConfigList.map((c) => [c.key, c]));
|
|
99
|
-
const globalConfigMap = new Map(globalConfigList.map((c) => [c.key, c]));
|
|
100
|
-
const allKeys = new Set([
|
|
101
|
-
...Object.keys(declared),
|
|
102
|
-
...projectConfigList.map((c) => c.key),
|
|
103
|
-
...globalConfigList.map((c) => c.key),
|
|
104
|
-
]);
|
|
105
|
-
// 配置合并优先级:项目包级 > 全局持久化 > 环境变量 > 声明默认值。
|
|
106
|
-
// core 的 RuntimeConfig.describe 已是五层链事实源,但 Target 门面仅按作用域暴露
|
|
107
|
-
// listConfig(scope),不提供跨作用域合并视图;此处保持命令层合并逻辑,
|
|
108
|
-
// 与 app.getConfig 的 describe 委托链行为语义一致(overrides 层仅存在于 run 链路)。
|
|
109
|
-
const merged = Array.from(allKeys).map((k) => {
|
|
110
|
-
let rawValue;
|
|
111
|
-
let source = "default";
|
|
112
|
-
const envResolved = resolveEnvValue(k, declared[k], projConfig.id);
|
|
113
|
-
const projItem = projectConfigMap.get(k);
|
|
114
|
-
const globItem = globalConfigMap.get(k);
|
|
115
|
-
if (projItem && projItem.configured && projItem.source === "package") {
|
|
116
|
-
rawValue = projItem.value;
|
|
117
|
-
source = "project";
|
|
118
|
-
}
|
|
119
|
-
else if (globItem && globItem.configured) {
|
|
120
|
-
rawValue = globItem.value;
|
|
121
|
-
source = "global";
|
|
122
|
-
}
|
|
123
|
-
else if (envResolved !== undefined) {
|
|
124
|
-
rawValue = envResolved.value;
|
|
125
|
-
source = "env";
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
rawValue = declared[k]?.default;
|
|
129
|
-
source = "default";
|
|
130
|
-
}
|
|
131
|
-
const isSecret = isSecretConfigKey(k, declared[k]);
|
|
132
|
-
return toDisplayEntry(k, rawValue, source, isSecret, reveal, declared[k]?.description || "");
|
|
133
|
-
});
|
|
86
|
+
// 跨作用域合并视图单一事实源:项目包级 > 全局持久化 > 环境变量 > 声明默认值
|
|
87
|
+
const merged = await buildMergedConfigEntries(root, localTarget, reveal);
|
|
134
88
|
const filterRes = filterWithFallbackInfo(merged, effectiveIntent, [(c) => c.key, (c) => c.value, (c) => c.description, (c) => c.source], shouldFallback);
|
|
135
89
|
renderResult(filterRes.items, {
|
|
136
90
|
json: options.json,
|
|
137
|
-
envelope: options.envelope,
|
|
138
91
|
humanFormatter: () => renderConfigList(filterRes.items, `${projConfig.name} (${projConfig.id})`, filterRes.isFallback, effectiveIntent),
|
|
139
92
|
context,
|
|
140
93
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createActionDockTarget, isSecretConfigKey, loadProjectConfig, resolveEnvValue, resolvePackageRoot, } from "@actiondock/core";
|
|
2
|
-
import { ArgumentError, ExecutionError } from "../../errors.js";
|
|
2
|
+
import { ArgumentError, ExecutionError, notInProjectError, packageNotFoundError } from "../../errors.js";
|
|
3
3
|
import { renderConfigSchema, renderResult, writeStdout } from "../../renderer.js";
|
|
4
4
|
import { getEffectiveOptions } from "../../utils/index.js";
|
|
5
5
|
/**
|
|
@@ -16,7 +16,6 @@ export function registerConfigSchemaCommand(configCmd, context) {
|
|
|
16
16
|
.option("-P, --package <id>", "Target package ID or path")
|
|
17
17
|
.option("--data-dir <path>", "Custom database storage directory")
|
|
18
18
|
.option("--json", "Output as JSON")
|
|
19
|
-
.option("--envelope", "Wrap JSON output in standard envelope")
|
|
20
19
|
.action(async (identifier, rawOptions, cmd) => {
|
|
21
20
|
try {
|
|
22
21
|
const options = getEffectiveOptions(rawOptions, cmd);
|
|
@@ -24,9 +23,9 @@ export function registerConfigSchemaCommand(configCmd, context) {
|
|
|
24
23
|
const root = resolvePackageRoot(targetPkg);
|
|
25
24
|
if (!root) {
|
|
26
25
|
if (targetPkg) {
|
|
27
|
-
throw
|
|
26
|
+
throw packageNotFoundError(targetPkg);
|
|
28
27
|
}
|
|
29
|
-
throw
|
|
28
|
+
throw notInProjectError("Usage: ad config schema [package-id] or cd into a project directory.");
|
|
30
29
|
}
|
|
31
30
|
const projConfig = loadProjectConfig(root);
|
|
32
31
|
const declared = projConfig.config || {};
|
|
@@ -97,10 +96,9 @@ export function registerConfigSchemaCommand(configCmd, context) {
|
|
|
97
96
|
missingCount: missingRequired.length,
|
|
98
97
|
configs: items,
|
|
99
98
|
};
|
|
100
|
-
if (options.json
|
|
99
|
+
if (options.json) {
|
|
101
100
|
renderResult(result, {
|
|
102
101
|
json: options.json,
|
|
103
|
-
envelope: options.envelope,
|
|
104
102
|
context,
|
|
105
103
|
});
|
|
106
104
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isSecretConfigKey, loadProjectConfig, resolvePackageRoot, } from "@actiondock/core";
|
|
2
|
-
import { ArgumentError } from "../../errors.js";
|
|
2
|
+
import { ArgumentError, packageNotFoundError } from "../../errors.js";
|
|
3
3
|
import { writeStdout } from "../../renderer.js";
|
|
4
4
|
import { applyTargetOptions, getEffectiveOptions, withTarget } from "../../utils/index.js";
|
|
5
5
|
import { resolveConfigValueInput } from "../../prompt.js";
|
|
@@ -69,7 +69,7 @@ export function registerConfigSetCommand(configCmd, context) {
|
|
|
69
69
|
}
|
|
70
70
|
if (!root) {
|
|
71
71
|
if (options.package) {
|
|
72
|
-
throw
|
|
72
|
+
throw packageNotFoundError(options.package);
|
|
73
73
|
}
|
|
74
74
|
await target.setConfig("global", key, parsedVal);
|
|
75
75
|
writeStdout(`[OK] Global configuration '${key}' updated (no project in current directory)`, context);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ArgumentError, ExecutionError } from "../errors.js";
|
|
1
|
+
import { ArgumentError, ExecutionError, packageNotFoundError } from "../errors.js";
|
|
2
2
|
import { renderActionDetail, renderResult } from "../renderer.js";
|
|
3
3
|
import { applyTargetOptions, getEffectiveOptions, resolveLocalPackageRoot, withTarget, } from "../utils/index.js";
|
|
4
4
|
/**
|
|
@@ -15,7 +15,6 @@ export function attachDescribeCommand(parent, context) {
|
|
|
15
15
|
.option("-P, --package <id>", "Target package ID or path");
|
|
16
16
|
return applyTargetOptions(cmd)
|
|
17
17
|
.option("--json", "Output as JSON")
|
|
18
|
-
.option("--envelope", "Wrap JSON output in standard envelope")
|
|
19
18
|
.option("--data-dir <path>", "Custom database storage directory")
|
|
20
19
|
.action(async (id, rawOptions, cmd) => {
|
|
21
20
|
const options = getEffectiveOptions(rawOptions, cmd);
|
|
@@ -25,7 +24,7 @@ export function attachDescribeCommand(parent, context) {
|
|
|
25
24
|
// 目标拓扑解析(仅 local 分支需要包寻址)
|
|
26
25
|
const targetPackageRoot = resolveLocalPackageRoot(options.package);
|
|
27
26
|
if (options.package && !targetPackageRoot) {
|
|
28
|
-
throw
|
|
27
|
+
throw packageNotFoundError(options.package);
|
|
29
28
|
}
|
|
30
29
|
let targetRef = id;
|
|
31
30
|
if (options.package && !id.includes("/") && !id.includes(":")) {
|
|
@@ -57,7 +56,6 @@ export function attachDescribeCommand(parent, context) {
|
|
|
57
56
|
};
|
|
58
57
|
renderResult(detail, {
|
|
59
58
|
json: options.json,
|
|
60
|
-
envelope: options.envelope,
|
|
61
59
|
humanFormatter: () => renderActionDetail(detail),
|
|
62
60
|
context,
|
|
63
61
|
});
|
package/dist/commands/doctor.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { fetchRemoteDoctor,
|
|
1
|
+
import { fetchRemoteDoctor, runDoctorChecks } from "@actiondock/core";
|
|
2
2
|
import { ExecutionError } from "../errors.js";
|
|
3
3
|
import { renderResult } from "../renderer.js";
|
|
4
|
-
import { applyTargetOptions, getEffectiveOptions } from "../utils/index.js";
|
|
4
|
+
import { applyTargetOptions, getEffectiveOptions, resolveTargetFromOptions } from "../utils/index.js";
|
|
5
5
|
export function registerDoctorCommand(program, context) {
|
|
6
6
|
const cmd = program
|
|
7
7
|
.command("doctor")
|
|
@@ -9,16 +9,10 @@ export function registerDoctorCommand(program, context) {
|
|
|
9
9
|
.option("-P, --package <id|path>", "Target package ID or directory path for project diagnostics");
|
|
10
10
|
applyTargetOptions(cmd)
|
|
11
11
|
.option("--json", "Output diagnostics report in JSON format")
|
|
12
|
-
.option("--envelope", "Wrap JSON output in standard envelope")
|
|
13
12
|
.action(async (rawOptions, cmd) => {
|
|
14
13
|
try {
|
|
15
14
|
const options = getEffectiveOptions(rawOptions, cmd);
|
|
16
|
-
const target =
|
|
17
|
-
profile: options.profile,
|
|
18
|
-
server: options.server,
|
|
19
|
-
token: options.token,
|
|
20
|
-
insecure: options.insecure,
|
|
21
|
-
}, context?.customHome);
|
|
15
|
+
const target = resolveTargetFromOptions(options, context);
|
|
22
16
|
let report;
|
|
23
17
|
if (target.type === "remote") {
|
|
24
18
|
const remoteRes = await fetchRemoteDoctor(target.serverUrl, target.token, options.package, {
|
|
@@ -35,7 +29,6 @@ export function registerDoctorCommand(program, context) {
|
|
|
35
29
|
}
|
|
36
30
|
renderResult(report, {
|
|
37
31
|
json: options.json,
|
|
38
|
-
envelope: options.envelope,
|
|
39
32
|
humanFormatter: () => {
|
|
40
33
|
const lines = [];
|
|
41
34
|
const title = target.type === "remote"
|
package/dist/commands/export.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { basename } from "node:path";
|
|
3
3
|
import { discoverProjects, findProjectRoot, listLinkedPackages, resolvePackageRoot } from "@actiondock/core";
|
|
4
|
-
import { ExecutionError } from "../errors.js";
|
|
4
|
+
import { ExecutionError, notInProjectError, packageNotFoundError } from "../errors.js";
|
|
5
5
|
import { renderResult, writeStdout } from "../renderer.js";
|
|
6
6
|
import { getEffectiveOptions, parseListOption } from "../utils/index.js";
|
|
7
7
|
export function registerExportCommand(program, context) {
|
|
@@ -68,7 +68,7 @@ export function registerExportCommand(program, context) {
|
|
|
68
68
|
for (const pkgIdOrPath of options.package) {
|
|
69
69
|
const root = resolvePackageRoot(pkgIdOrPath);
|
|
70
70
|
if (!root) {
|
|
71
|
-
throw
|
|
71
|
+
throw packageNotFoundError(pkgIdOrPath);
|
|
72
72
|
}
|
|
73
73
|
if (!roots.includes(root)) {
|
|
74
74
|
roots.push(root);
|
|
@@ -81,13 +81,13 @@ export function registerExportCommand(program, context) {
|
|
|
81
81
|
roots.push(root);
|
|
82
82
|
}
|
|
83
83
|
else {
|
|
84
|
-
throw
|
|
84
|
+
throw notInProjectError("Please specify -P, --package <id>, --workspace, --all, or cd into a project directory.");
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
if (roots.length > 1 && (options.actions || options.playbook)) {
|
|
88
88
|
throw new ExecutionError("Filtering options (--actions, --playbook) can only be used when exporting a single package.");
|
|
89
89
|
}
|
|
90
|
-
const isMachine = Boolean(options.json
|
|
90
|
+
const isMachine = Boolean(options.json);
|
|
91
91
|
try {
|
|
92
92
|
const { exportCompositeSkill, exportSkill, exportSkillBatch } = await import("@actiondock/builder");
|
|
93
93
|
if (options.bundle !== undefined) {
|
|
@@ -109,7 +109,6 @@ export function registerExportCommand(program, context) {
|
|
|
109
109
|
});
|
|
110
110
|
renderResult(result, {
|
|
111
111
|
json: isMachine,
|
|
112
|
-
envelope: options.envelope,
|
|
113
112
|
humanFormatter: () => {
|
|
114
113
|
const lines = [];
|
|
115
114
|
lines.push(`[OK] Successfully exported Composite Skill: ${result.bundleName}`);
|
|
@@ -150,7 +149,6 @@ export function registerExportCommand(program, context) {
|
|
|
150
149
|
});
|
|
151
150
|
renderResult(batchRes, {
|
|
152
151
|
json: isMachine,
|
|
153
|
-
envelope: options.envelope,
|
|
154
152
|
humanFormatter: () => {
|
|
155
153
|
const lines = [];
|
|
156
154
|
lines.push(`[OK] Successfully batch exported ${batchRes.results.length} Skill packages to: ${batchRes.outDir}`);
|
|
@@ -183,7 +181,6 @@ export function registerExportCommand(program, context) {
|
|
|
183
181
|
});
|
|
184
182
|
renderResult(result, {
|
|
185
183
|
json: isMachine,
|
|
186
|
-
envelope: options.envelope,
|
|
187
184
|
humanFormatter: () => {
|
|
188
185
|
const lines = [];
|
|
189
186
|
lines.push(`[OK] Successfully exported ${result.mode === "node" ? "Node Directory" : "Source"} Skill: ${result.packageId} (v${result.version})`);
|