@actiondock/cli 2.11.0 → 2.12.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 CHANGED
@@ -152,8 +152,13 @@ $data | ConvertTo-Json -Depth 100 | ad run complex-action --input-file -
152
152
 
153
153
  ### Action 输出模式与机器模式
154
154
 
155
- - **默认原始纯文本输出**:`ad run` 默认采用面向终端人类阅读与 LLM Agent 上下文消费的原始输出模式。直接将执行结果正文内容(如文件 `content`、`text`、`message` 或标量字符串)输出至 stdout(保留真实换行且无 JSON 转义),相关元数据(如文件路径、行号范围、截断标记)独立输出至 stderr,符合 Unix 管道安全原则。业务执行失败时在 stderr 输出错误详情并以退出码 1 退出。
156
- - **机器 JSON 格式**(`--json`):当需要以程序化方式消费、获取完整结构化数据或被外部系统集成时,传入 `--json`。面向智能体调用推荐使用该模式;此时输出标准 JSON 结果(包含 `ok`、`runId`、`data` 或 `error`);若参数解析出错输出标准错误信封并以退出码 2 退出。
155
+ - **默认纯文本输出**:`ad run` 默认采用纯文本模式。标量直接输出;结构化对象完整输出为标准 JSON 格式到 stdout;不进行私有业务字段自动嗅探。
156
+ - **显式正文输出与默认注解**:
157
+ - 显式指定正文字段:通过 `ad run <id> --text-field <field> -- ...` 提取结果中特定顶层自有字符串字段输出至 stdout(保留真实换行且无 JSON 转义),其余字段以 JSON 对象形式输出至 stderr(元数据为空时不输出)。
158
+ - 声明式默认正文:Action 清单中声明 `annotations["actiondock.cli"] = { "textField": "<field>" }` 时,未指定 `--text-field` 时自动按声明提取正文与元数据;显式 `--text-field` 优先于默认注解。
159
+ - 严格校验:正文字段缺失、非字符串或返回数据非对象时,明确输出错误并以退出码 1 退出,不静默回退,不改写运行记录。
160
+ - 参数互斥:`--text-field` 与 `--json`、`--async` 严格互斥,在执行前校验并以退出码 2 退出。
161
+ - **机器模式**(`--json`):当需要以程序化方式消费、获取完整结构化数据或被外部系统集成时,传入 `--json`。此时始终输出完整执行信封(包含 `ok`、`runId`、`data` 或 `error`),忽略默认正文注解;若参数解析出错输出标准错误信封并以退出码 2 退出。
157
162
 
158
163
  ---
159
164
 
@@ -167,7 +172,7 @@ $data | ConvertTo-Json -Depth 100 | ad run complex-action --input-file -
167
172
  | `ad info [patterns...]` | 检索包元数据与能力清单,支持模式匹配与树形展示 |
168
173
  | `ad list [patterns...]` | 列出包内所有已注册的 Action |
169
174
  | `ad describe <id>` | 编码顾问:查看 Action 的详情、模式字段明细、Flat 编码指引与建议赋值 |
170
- | `ad run <id>` | 本地或远程执行指定 Action(规范语法 `ad run <id> [options] -- <assignments...>`,支持 `--json` 输出标准信封) |
175
+ | `ad run <id>` | 本地或远程执行指定 Action(规范语法 `ad run <id> [options] -- <assignments...>`,支持 `--text-field` 提取正文与 `--json` 输出标准信封) |
171
176
  | `ad validate [id]` | 校验 Action 规范与模式规范 |
172
177
  | `ad doctor` | 执行运行环境与项目结构健康诊断 |
173
178
  | `ad action create <id>` | 创建新 Action 源码 |
@@ -199,8 +199,9 @@ ${returnBody}
199
199
  writeStdout(`\nParameter contract:`, context);
200
200
  writeStdout(` ad describe ${id}`, context);
201
201
  writeStdout(`\nUsage:`, context);
202
- writeStdout(` Pass parameters according to describe assignment template: ad run ${id} --json -- ASSIGNMENT...`, context);
203
- writeStdout(` Complex input or large text: ad run ${id} --json --input-file input.json`, context);
202
+ writeStdout(` Pass parameters according to describe assignment template: ad run ${id} -- ASSIGNMENT...`, context);
203
+ writeStdout(` Complex input or large text: ad run ${id} --input-file input.json`, context);
204
+ writeStdout(` Append --json only when structured extraction is needed`, context);
204
205
  }
205
206
  catch (err) {
206
207
  throw wrapAsExecutionError(err);
@@ -2,14 +2,18 @@ import { type InvocationControl } from "@actiondock/core/server";
2
2
  import type { ExecutionResult } from "@actiondock/sdk";
3
3
  import { Command } from "commander";
4
4
  import type { CliContext } from "../types.js";
5
+ export interface RawExecutionRenderOptions {
6
+ textField?: string;
7
+ }
5
8
  /**
6
9
  * 以原始纯文本形式渲染 Action 执行终态结果(默认纯文本模式)。
7
10
  *
8
11
  * 设计契约:
9
12
  * - 通用纯文本透传:标量直接输出,结构化对象按标准规范呈现,杜绝私有业务字段嗅探。
13
+ * - 显式或声明式正文输出:指定 textField 时仅输出该自有字符串字段至 stdout,剩余字段以 JSON 对象形式输出至 stderr。
10
14
  * - 标准输出仅承载业务有效载荷,保持原生排版与真实换行,供用户调阅或下游管道消费。
11
15
  */
12
- export declare function renderRawExecutionResult(targetRef: string, result: ExecutionResult, context?: CliContext): void;
16
+ export declare function renderRawExecutionResult(targetRef: string, result: ExecutionResult, context?: CliContext, options?: RawExecutionRenderOptions): void;
13
17
  /**
14
18
  * 统一执行 Action 核心逻辑(使用 ActionDockService 服务端口)。
15
19
  *
@@ -1,18 +1,27 @@
1
1
  import { validateActionInputValue, mapInputValidationFailure, } from "@actiondock/core/project";
2
2
  import { resolveTarget, } from "@actiondock/core/profile";
3
- import { resolveExecutionHint } from "@actiondock/core";
3
+ import { ACTION_NOT_FOUND, NOT_FOUND, resolveExecutionHint, } from "@actiondock/core";
4
4
  import { ArgumentError, ExecutionError, SigintError, packageNotFoundError } from "../errors.js";
5
- import { writeStderr, writeStdout } from "../renderer.js";
5
+ import { extractTextFieldPayload, resolveActionDefaultTextField, writeStderr, writeStdout, } from "../renderer.js";
6
6
  import { applyTargetOptions, getEffectiveOptions, resolveActionInput, resolveLocalPackageRoot, resolveTimeoutMs, withService, } from "../utils/index.js";
7
7
  /**
8
8
  * 以原始纯文本形式渲染 Action 执行终态结果(默认纯文本模式)。
9
9
  *
10
10
  * 设计契约:
11
11
  * - 通用纯文本透传:标量直接输出,结构化对象按标准规范呈现,杜绝私有业务字段嗅探。
12
+ * - 显式或声明式正文输出:指定 textField 时仅输出该自有字符串字段至 stdout,剩余字段以 JSON 对象形式输出至 stderr。
12
13
  * - 标准输出仅承载业务有效载荷,保持原生排版与真实换行,供用户调阅或下游管道消费。
13
14
  */
14
- export function renderRawExecutionResult(targetRef, result, context) {
15
+ export function renderRawExecutionResult(targetRef, result, context, options) {
15
16
  if (result.ok) {
17
+ if (options?.textField !== undefined) {
18
+ const { text, metadata } = extractTextFieldPayload(result.data, options.textField);
19
+ writeStdout(text, context);
20
+ if (metadata) {
21
+ writeStderr(JSON.stringify(metadata, null, 2), context);
22
+ }
23
+ return;
24
+ }
16
25
  const data = result.data;
17
26
  let rawText;
18
27
  if (typeof data === "string") {
@@ -58,6 +67,17 @@ export async function executeAction(id, options, context, flatArgs, control) {
58
67
  if (!id) {
59
68
  throw new ArgumentError("Action ID is required for run");
60
69
  }
70
+ if (options.textField !== undefined) {
71
+ if (typeof options.textField !== "string" || options.textField.trim() === "") {
72
+ throw new ArgumentError("The --text-field option requires a non-empty field name.");
73
+ }
74
+ if (options.json) {
75
+ throw new ArgumentError("Cannot specify both --text-field and --json.");
76
+ }
77
+ if (options.async) {
78
+ throw new ArgumentError("Cannot specify both --text-field and --async.");
79
+ }
80
+ }
61
81
  const effectiveControl = control ?? context?.control;
62
82
  const effectiveSignal = effectiveControl?.signal;
63
83
  const effectiveFlatArgs = flatArgs ?? options.flatArgs;
@@ -131,6 +151,21 @@ export async function executeAction(id, options, context, flatArgs, control) {
131
151
  }
132
152
  }
133
153
  else {
154
+ let effectiveTextField = options.textField;
155
+ if (!isMachine && effectiveTextField === undefined) {
156
+ try {
157
+ const spec = await service.discovery.describeAction(targetRef);
158
+ effectiveTextField = resolveActionDefaultTextField(spec.annotations);
159
+ }
160
+ catch (err) {
161
+ if (err?.code === ACTION_NOT_FOUND || err?.code === NOT_FOUND) {
162
+ // 若动作未找到,继续交由 execution.run 生成统一的执行与提示结果
163
+ }
164
+ else {
165
+ throw err;
166
+ }
167
+ }
168
+ }
134
169
  const result = await service.execution.run(targetRef, input, {
135
170
  signal: effectiveSignal,
136
171
  timeoutMs,
@@ -152,7 +187,7 @@ export async function executeAction(id, options, context, flatArgs, control) {
152
187
  }
153
188
  }
154
189
  else {
155
- renderRawExecutionResult(targetRef, result, context);
190
+ renderRawExecutionResult(targetRef, result, context, { textField: effectiveTextField });
156
191
  }
157
192
  if (!result.ok) {
158
193
  if (context) {
@@ -203,6 +238,10 @@ Flat Input Syntax & Examples:
203
238
  # Nested object properties
204
239
  ad run <id> -- user.name="alice" user.age:=30
205
240
 
241
+ Text Field Output:
242
+ # Extract specific text field to stdout (remaining fields as JSON to stderr)
243
+ ad run <id> --text-field content -- path="README.md"
244
+
206
245
  Complex or multiline inputs:
207
246
  # Pass via JSON file with --input-file to avoid shell escaping issues
208
247
  ad run <id> --input-file input.json`;
@@ -229,6 +268,7 @@ export function attachRunCommand(parent, context) {
229
268
  .option("--async", "Execute asynchronously in background (requires remote server or profile)")
230
269
  .option("--data-dir <path>", "Custom database directory")
231
270
  .option("--json", "Output as JSON")
271
+ .option("--text-field <field>", "Extract and output a specific top-level string field as raw text")
232
272
  .addHelpText("after", RUN_HELP_AFTER_TEXT)
233
273
  .action(async (id, params, rawOptions, cmd) => {
234
274
  let flatArgs;
@@ -1,5 +1,32 @@
1
1
  import type { ActionSpec } from "@actiondock/core";
2
2
  import { buildActionDescribePayload } from "@actiondock/core/project";
3
+ /**
4
+ * 解析 Action 注解中的默认正文输出字段声明。
5
+ *
6
+ * 契约规范:
7
+ * - 注解键名固定为 actiondock.cli,值必须为非空非数组对象。
8
+ * - 正文字段键名为 textField,值必须为非空字符串。
9
+ * - 无相关注解或未声明 textField 时返回 undefined。
10
+ * - 注解存在但类型非法时抛出 ExecutionError。
11
+ */
12
+ export declare function resolveActionDefaultTextField(annotations?: Record<string, unknown>): string | undefined;
13
+ /**
14
+ * 结构化正文与元数据抽取结果。
15
+ */
16
+ export interface TextFieldExtractionResult {
17
+ text: string;
18
+ metadata?: Record<string, unknown>;
19
+ }
20
+ /**
21
+ * 从 Action 执行数据中抽取指定自有顶层正文字段及剩余元数据。
22
+ *
23
+ * 契约规范:
24
+ * - 仅支持结果数据的顶层自有字符串字段,拒绝通过原型链提取。
25
+ * - 结果数据本身必须为对象且不能为数组。
26
+ * - 目标字段必须存在且值为字符串(空字符串为合法有效正文)。
27
+ * - 剩余元数据安全构造,杜绝原型污染;元数据为空时返回 undefined。
28
+ */
29
+ export declare function extractTextFieldPayload(data: unknown, textField: string): TextFieldExtractionResult;
3
30
  /**
4
31
  * 格式化渲染 Action 列表。
5
32
  */
@@ -1,4 +1,79 @@
1
1
  import { buildActionDescribePayload, ACTION_DESCRIBE_SYNTAX_REFERENCE, } from "@actiondock/core/project";
2
+ import { ExecutionError } from "../errors.js";
3
+ /**
4
+ * 解析 Action 注解中的默认正文输出字段声明。
5
+ *
6
+ * 契约规范:
7
+ * - 注解键名固定为 actiondock.cli,值必须为非空非数组对象。
8
+ * - 正文字段键名为 textField,值必须为非空字符串。
9
+ * - 无相关注解或未声明 textField 时返回 undefined。
10
+ * - 注解存在但类型非法时抛出 ExecutionError。
11
+ */
12
+ export function resolveActionDefaultTextField(annotations) {
13
+ if (!annotations || typeof annotations !== "object") {
14
+ return undefined;
15
+ }
16
+ if (!Object.hasOwn(annotations, "actiondock.cli")) {
17
+ return undefined;
18
+ }
19
+ const cli = annotations["actiondock.cli"];
20
+ if (cli === undefined) {
21
+ return undefined;
22
+ }
23
+ if (typeof cli !== "object" || cli === null || Array.isArray(cli)) {
24
+ throw new ExecutionError(`Invalid 'actiondock.cli' annotation: expected an object, but received ${cli === null ? "null" : Array.isArray(cli) ? "an array" : typeof cli}.`, undefined, "INVALID_ANNOTATION");
25
+ }
26
+ if (!Object.hasOwn(cli, "textField") || cli.textField === undefined) {
27
+ return undefined;
28
+ }
29
+ const textField = cli.textField;
30
+ if (typeof textField !== "string") {
31
+ throw new ExecutionError(`Invalid 'actiondock.cli.textField' annotation: expected a string, but received ${textField === null ? "null" : Array.isArray(textField) ? "an array" : typeof textField}.`, undefined, "INVALID_ANNOTATION");
32
+ }
33
+ if (textField.trim() === "") {
34
+ throw new ExecutionError("Invalid 'actiondock.cli.textField' annotation: cannot be an empty string.", undefined, "INVALID_ANNOTATION");
35
+ }
36
+ return textField;
37
+ }
38
+ /**
39
+ * 从 Action 执行数据中抽取指定自有顶层正文字段及剩余元数据。
40
+ *
41
+ * 契约规范:
42
+ * - 仅支持结果数据的顶层自有字符串字段,拒绝通过原型链提取。
43
+ * - 结果数据本身必须为对象且不能为数组。
44
+ * - 目标字段必须存在且值为字符串(空字符串为合法有效正文)。
45
+ * - 剩余元数据安全构造,杜绝原型污染;元数据为空时返回 undefined。
46
+ */
47
+ export function extractTextFieldPayload(data, textField) {
48
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
49
+ throw new ExecutionError(`CLI output format error: expected result data to be an object, but received ${data === null ? "null" : Array.isArray(data) ? "an array" : typeof data}.`, undefined, "OUTPUT_FORMAT_ERROR");
50
+ }
51
+ if (!Object.hasOwn(data, textField)) {
52
+ throw new ExecutionError(`CLI output format error: text field '${textField}' was not found in result data.`, undefined, "OUTPUT_FORMAT_ERROR");
53
+ }
54
+ const textValue = data[textField];
55
+ if (typeof textValue !== "string") {
56
+ throw new ExecutionError(`CLI output format error: text field '${textField}' must be a string, but received ${textValue === null ? "null" : Array.isArray(textValue) ? "an array" : typeof textValue}.`, undefined, "OUTPUT_FORMAT_ERROR");
57
+ }
58
+ const metadata = {};
59
+ let hasMetadata = false;
60
+ for (const key of Object.keys(data)) {
61
+ if (key === textField) {
62
+ continue;
63
+ }
64
+ Object.defineProperty(metadata, key, {
65
+ value: data[key],
66
+ enumerable: true,
67
+ writable: true,
68
+ configurable: true,
69
+ });
70
+ hasMetadata = true;
71
+ }
72
+ return {
73
+ text: textValue,
74
+ metadata: hasMetadata ? metadata : undefined,
75
+ };
76
+ }
2
77
  /**
3
78
  * 格式化渲染 Action 列表。
4
79
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actiondock/cli",
3
- "version": "2.11.0",
3
+ "version": "2.12.0",
4
4
  "description": "ActionDock CLI facade for developing, testing, building, and exporting standalone Agent Actions and Skills",
5
5
  "type": "module",
6
6
  "bin": {
@@ -34,10 +34,10 @@
34
34
  "test": "node ../../scripts/run-tests.ts"
35
35
  },
36
36
  "dependencies": {
37
- "@actiondock/builder": "^2.11.0",
38
- "@actiondock/core": "^2.11.0",
39
- "@actiondock/mcp": "^2.11.0",
40
- "@actiondock/sdk": "^2.11.0",
37
+ "@actiondock/builder": "^2.12.0",
38
+ "@actiondock/core": "^2.12.0",
39
+ "@actiondock/mcp": "^2.12.0",
40
+ "@actiondock/sdk": "^2.12.0",
41
41
  "commander": "^13.1.0",
42
42
  "selfsigned": "^5.5.0"
43
43
  },