@actiondock/cli 2.9.1 → 2.10.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
@@ -170,7 +170,7 @@ $data | ConvertTo-Json -Depth 100 | ad run complex-action --input-file -
170
170
  | `ad run <id>` | 本地或远程执行指定 Action(规范语法 `ad run <id> [options] -- <assignments...>`,支持 `--json` 输出标准信封) |
171
171
  | `ad validate [id]` | 校验 Action 规范与模式规范 |
172
172
  | `ad doctor` | 执行运行环境与项目结构健康诊断 |
173
- | `ad action create <id>` | 创建新 Action 源码(别名 `ad action new`) |
173
+ | `ad action create <id>` | 创建新 Action 源码 |
174
174
  | `ad playbook list` / `show` | 查看智能体操作规程 Playbook |
175
175
  | `ad config list` / `get` / `set` | 管理包运行时配置与环境变量绑定 |
176
176
  | `ad state list` / `get` / `set` / `delete` / `clear` | 查看与维护 SQLite 持久化状态 |
@@ -1,6 +1,6 @@
1
1
  import { isSecretConfigKey, } from "@actiondock/core/project";
2
2
  import { notInProjectError } from "../../errors.js";
3
- import { renderConfigSchema, renderResult, writeStdout } from "../../renderer.js";
3
+ import { renderConfigSchema, renderResult } from "../../renderer.js";
4
4
  import { buildMergedConfigEntries } from "../../services/config-merge.js";
5
5
  import { applyTargetOptions, getEffectiveOptions, requirePackageRoot, withService } from "../../utils/index.js";
6
6
  /**
@@ -78,17 +78,23 @@ export function registerConfigSchemaCommand(configCmd, context) {
78
78
  ok,
79
79
  missingCount: missingRequired.length,
80
80
  configs: items,
81
+ ...(!ok
82
+ ? {
83
+ hints: [
84
+ "Tip: Run 'ad config set <KEY> <val>' to configure required settings.",
85
+ ],
86
+ }
87
+ : {}),
81
88
  };
82
- if (options.json) {
83
- renderResult(result, {
84
- json: options.json,
85
- context,
86
- });
87
- }
88
- else {
89
- writeStdout(renderConfigSchema(items, projConfig.id, root) + "\n", context);
90
- }
89
+ renderResult(result, {
90
+ json: options.json,
91
+ humanFormatter: () => renderConfigSchema(items, projConfig.id, root) + "\n",
92
+ context,
93
+ });
91
94
  if (!ok) {
95
+ if (context) {
96
+ context.exitCode = 1;
97
+ }
92
98
  process.exitCode = 1;
93
99
  }
94
100
  }, { localRoot: root });
@@ -38,7 +38,10 @@ export function attachDescribeCommand(parent, context) {
38
38
  catch (err) {
39
39
  const code = err?.code;
40
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
+ const hint = (code === ACTION_NOT_FOUND || code === NOT_FOUND)
42
+ ? "Tip: Run 'ad list' to discover available actions."
43
+ : (err?.hint || err?.details?.hint);
44
+ throw new ArgumentError(err?.message || String(err), err?.details, code, hint);
42
45
  }
43
46
  throw new ExecutionError(err?.message || String(err), err?.details, code);
44
47
  }
@@ -77,6 +77,26 @@ export function createCliProgram(context) {
77
77
  program.exitOverride((err) => {
78
78
  throw err;
79
79
  });
80
+ if (context?.stdout || context?.stderr) {
81
+ program.configureOutput({
82
+ writeOut: (str) => {
83
+ if (context.stdout)
84
+ context.stdout(str);
85
+ else
86
+ process.stdout.write(str);
87
+ },
88
+ writeErr: (str) => {
89
+ if (context.stderr)
90
+ context.stderr(str);
91
+ else
92
+ process.stderr.write(str);
93
+ },
94
+ });
95
+ }
96
+ program.addHelpText("after", `
97
+ Workflow Guidance:
98
+ For multi-step workflows, run 'ad playbook list' before invoking atomic actions.
99
+ Run 'ad info [intent]' to discover packages and playbooks by keyword.`);
80
100
  registerInitCommand(program);
81
101
  registerAddCommand(program, context);
82
102
  registerRemoveCommand(program, context);
@@ -9,6 +9,26 @@ import { applyTargetOptions, getEffectiveOptions, resolveFallbackStrategy, resol
9
9
  * 将远端信息响应归一为本地工程详情视图。
10
10
  */
11
11
  function remoteInfoToDetail(remoteInfo) {
12
+ const actionsMap = new Map();
13
+ const rawActions = remoteInfo.actionsDetail || remoteInfo.actions || [];
14
+ for (const a of rawActions) {
15
+ if (typeof a === "string") {
16
+ actionsMap.set(a, { id: a });
17
+ }
18
+ else if (a && typeof a === "object") {
19
+ actionsMap.set(a.id, { id: a.id, description: a.description });
20
+ }
21
+ }
22
+ const playbooksMap = new Map();
23
+ const rawPlaybooks = remoteInfo.playbooksDetail || remoteInfo.playbooks || [];
24
+ for (const pb of rawPlaybooks) {
25
+ if (typeof pb === "string") {
26
+ playbooksMap.set(pb, { id: pb });
27
+ }
28
+ else if (pb && typeof pb === "object") {
29
+ playbooksMap.set(pb.id, { id: pb.id, description: pb.description });
30
+ }
31
+ }
12
32
  return {
13
33
  id: remoteInfo.id,
14
34
  name: remoteInfo.name || remoteInfo.id,
@@ -17,12 +37,14 @@ function remoteInfoToDetail(remoteInfo) {
17
37
  projectRoot: remoteInfo.path || remoteInfo.projectRoot || "",
18
38
  actionsDir: "remote",
19
39
  playbooksDir: "remote",
20
- actionsCount: remoteInfo.actionsCount || (remoteInfo.actions ? remoteInfo.actions.length : 0),
21
- playbooksCount: remoteInfo.playbooksCount || (remoteInfo.playbooks ? remoteInfo.playbooks.length : 0),
22
- actions: (remoteInfo.actionsDetail || remoteInfo.actions || []).map((a) => typeof a === "string" ? a : a.id),
23
- playbooks: (remoteInfo.playbooksDetail || remoteInfo.playbooks || []).map((pb) => typeof pb === "string" ? pb : pb.id),
40
+ actionsCount: actionsMap.size,
41
+ playbooksCount: playbooksMap.size,
42
+ actions: Array.from(actionsMap.keys()),
43
+ playbooks: Array.from(playbooksMap.keys()),
24
44
  configDeclared: Object.keys(remoteInfo.configDeclared || {}),
25
45
  configDef: remoteInfo.configDeclared,
46
+ actionsMap,
47
+ playbooksMap,
26
48
  };
27
49
  }
28
50
  /**
@@ -116,6 +138,9 @@ export function registerInfoCommand(program, context) {
116
138
  }
117
139
  // 扫描本地候选包(当前工程根目录 + 全局已链接包)
118
140
  const { currentRoot, aggregated } = scanLocalAggregatedPackages(context);
141
+ const infoHints = [
142
+ "Tip: Run 'ad info <package-id>' to view detailed package configuration and schema.",
143
+ ];
119
144
  // 无关键字过滤场景
120
145
  if (!effectiveIntent) {
121
146
  if (currentRoot) {
@@ -130,7 +155,7 @@ export function registerInfoCommand(program, context) {
130
155
  });
131
156
  return;
132
157
  }
133
- renderResult({ linkedPackages: aggregated }, {
158
+ renderResult({ linkedPackages: aggregated, hints: infoHints }, {
134
159
  ...outOpts,
135
160
  humanFormatter: () => renderAggregatedPackages(aggregated),
136
161
  });
@@ -147,7 +172,7 @@ export function registerInfoCommand(program, context) {
147
172
  }
148
173
  if (aggregated.length === 0) {
149
174
  if (isMachine) {
150
- renderResult({ linkedPackages: [], matchedCount: 0, isFallback: false }, outOpts);
175
+ renderResult({ linkedPackages: [], matchedCount: 0, isFallback: false, hints: infoHints }, outOpts);
151
176
  return;
152
177
  }
153
178
  throw new ExecutionError(`No ActionDock project or linked packages available to match '${effectiveIntent}'`);
@@ -164,14 +189,14 @@ export function registerInfoCommand(program, context) {
164
189
  if (filterRes.matchedCount === 0) {
165
190
  if (!filterRes.isFallback) {
166
191
  if (isMachine) {
167
- renderResult({ linkedPackages: [], matchedCount: 0, isFallback: false }, outOpts);
192
+ renderResult({ linkedPackages: [], matchedCount: 0, isFallback: false, hints: infoHints }, outOpts);
168
193
  return;
169
194
  }
170
195
  throw new ExecutionError(`No packages matched intent '${effectiveIntent}'`);
171
196
  }
172
197
  // 回退模式:展示全部包并附带提示
173
198
  if (isMachine) {
174
- renderResult({ linkedPackages: filterRes.items, isFallback: true, matchedCount: 0 }, outOpts);
199
+ renderResult({ linkedPackages: filterRes.items, isFallback: true, matchedCount: 0, hints: infoHints }, outOpts);
175
200
  return;
176
201
  }
177
202
  renderResult({ linkedPackages: filterRes.items, isFallback: true, matchedCount: 0 }, {
@@ -183,7 +208,7 @@ export function registerInfoCommand(program, context) {
183
208
  }
184
209
  // 机器模式下搜索:稳定返回列表结构,不因为 matchedCount === 1 突变为详情
185
210
  if (isMachine) {
186
- renderResult({ linkedPackages: filterRes.items, matchedCount: filterRes.matchedCount, isFallback: false }, outOpts);
211
+ renderResult({ linkedPackages: filterRes.items, matchedCount: filterRes.matchedCount, isFallback: false, hints: infoHints }, outOpts);
187
212
  return;
188
213
  }
189
214
  // 人类终端交互:精确匹配单个包展开详情
@@ -63,8 +63,19 @@ export function attachListCommand(parent, context) {
63
63
  };
64
64
  });
65
65
  const filterRes = filterWithFallbackInfo(rawList, effectiveIntent, [(a) => a.id, (a) => a.description], shouldFallback);
66
- if (filterRes.isFallback && isMachine) {
67
- renderResult({ items: filterRes.items, isFallback: true, matchedCount: 0 }, { json: options.json, context });
66
+ const hints = [
67
+ "Tip: For composite or multi-step tasks, check 'ad playbook list' for standard operating procedures.",
68
+ ];
69
+ if (isMachine) {
70
+ if (filterRes.isFallback) {
71
+ renderResult({ items: filterRes.items, isFallback: true, matchedCount: 0, hints }, { json: options.json, context });
72
+ return;
73
+ }
74
+ renderResult({
75
+ items: filterRes.items,
76
+ ...(filterRes.matchedCount !== undefined ? { matchedCount: filterRes.matchedCount } : {}),
77
+ hints,
78
+ }, { json: options.json, context });
68
79
  return;
69
80
  }
70
81
  const title = resolved.type === "remote"
@@ -31,6 +31,9 @@ export function registerPlaybookCommands(program, context) {
31
31
  const options = getEffectiveOptions(rawOptions, cmd);
32
32
  const effectiveIntent = resolveIntent(options.intent, patterns);
33
33
  const { shouldFallback, isMachine } = resolveFallbackStrategy(options);
34
+ const hints = [
35
+ "Tip: Run 'ad playbook show <id>' to inspect procedure steps before execution.",
36
+ ];
34
37
  // 1. 远端服务分支
35
38
  const target = resolveTargetFromOptions(options, context);
36
39
  if (target.type === "remote") {
@@ -40,6 +43,10 @@ export function registerPlaybookCommands(program, context) {
40
43
  allowInsecureHttp: Boolean(options.allowInsecureHttp),
41
44
  insecure: target.insecure,
42
45
  });
46
+ if (isMachine) {
47
+ renderResult({ items: remotePbs, hints }, { json: options.json, context });
48
+ return;
49
+ }
43
50
  renderResult(remotePbs, {
44
51
  json: options.json,
45
52
  humanFormatter: () => renderPlaybookList(remotePbs, `Playbooks ${remoteTargetSuffix(target)}`, false, effectiveIntent),
@@ -67,8 +74,16 @@ export function registerPlaybookCommands(program, context) {
67
74
  packageId: config.id,
68
75
  }));
69
76
  const filterRes = filterWithFallbackInfo(rawList, effectiveIntent, [(p) => p.id, (p) => p.description], shouldFallback);
70
- if (filterRes.isFallback && isMachine) {
71
- renderResult({ items: filterRes.items, isFallback: true, matchedCount: 0 }, { json: options.json, context });
77
+ if (isMachine) {
78
+ if (filterRes.isFallback) {
79
+ renderResult({ items: filterRes.items, isFallback: true, matchedCount: 0, hints }, { json: options.json, context });
80
+ return;
81
+ }
82
+ renderResult({
83
+ items: filterRes.items,
84
+ ...(filterRes.matchedCount !== undefined ? { matchedCount: filterRes.matchedCount } : {}),
85
+ hints,
86
+ }, { json: options.json, context });
72
87
  return;
73
88
  }
74
89
  renderResult(filterRes.items, {
@@ -81,6 +96,10 @@ export function registerPlaybookCommands(program, context) {
81
96
  // 3. 扫描所有已链接的外部包
82
97
  const linkedList = listLinkedPackages(context?.customHome);
83
98
  if (linkedList.length === 0) {
99
+ if (isMachine) {
100
+ renderResult({ packages: [], hints }, { json: options.json, context });
101
+ return;
102
+ }
84
103
  renderResult([], {
85
104
  json: options.json,
86
105
  humanFormatter: () => NO_PROJECT_NO_LINKED_MESSAGE,
@@ -136,8 +155,12 @@ export function registerPlaybookCommands(program, context) {
136
155
  isFallback = true;
137
156
  }
138
157
  }
139
- if (isFallback && isMachine) {
140
- renderResult({ packages: filteredPackages, isFallback: true, matchedCount: 0 }, { json: options.json, context });
158
+ if (isMachine) {
159
+ if (isFallback) {
160
+ renderResult({ packages: filteredPackages, isFallback: true, matchedCount: 0, hints }, { json: options.json, context });
161
+ return;
162
+ }
163
+ renderResult({ packages: filteredPackages, hints }, { json: options.json, context });
141
164
  return;
142
165
  }
143
166
  renderResult(filteredPackages, {
@@ -150,6 +173,7 @@ export function registerPlaybookCommands(program, context) {
150
173
  lines.push(` - ${pb.id.padEnd(26)} ${pb.description || ""}`);
151
174
  }
152
175
  }
176
+ lines.push("\nTip: Run 'ad playbook show <id>' to inspect procedure steps before execution.");
153
177
  return lines.join("\n");
154
178
  },
155
179
  context,
@@ -169,16 +193,22 @@ export function registerPlaybookCommands(program, context) {
169
193
  // 1. 远端服务分支
170
194
  const target = resolveTargetFromOptions(options, context);
171
195
  if (target.type === "remote") {
172
- const detail = await fetchRemotePlaybookShow(target.serverUrl, id, target.token, {
173
- allowInsecureHttp: Boolean(options.allowInsecureHttp),
174
- insecure: target.insecure,
175
- });
176
- renderResult(detail, {
177
- json: options.json,
178
- humanFormatter: () => renderPlaybookDetail(detail),
179
- context,
180
- });
181
- return;
196
+ try {
197
+ const detail = await fetchRemotePlaybookShow(target.serverUrl, id, target.token, {
198
+ allowInsecureHttp: Boolean(options.allowInsecureHttp),
199
+ insecure: target.insecure,
200
+ });
201
+ renderResult(detail, {
202
+ json: options.json,
203
+ humanFormatter: () => renderPlaybookDetail(detail),
204
+ context,
205
+ });
206
+ return;
207
+ }
208
+ catch (err) {
209
+ const hint = "Tip: Run 'ad playbook list' to discover available playbooks.";
210
+ throw new ArgumentError(`${err.message}\n${hint}`, err?.details, err?.code, hint);
211
+ }
182
212
  }
183
213
  // 2. 本地项目模式
184
214
  let showTarget = id;
@@ -199,9 +229,13 @@ export function registerPlaybookCommands(program, context) {
199
229
  resolved = resolvePlaybook(showTarget, { graph });
200
230
  }
201
231
  catch (err) {
202
- throw new ArgumentError(err.message);
232
+ const hint = "Tip: Run 'ad playbook list' to discover available playbooks.";
233
+ throw new ArgumentError(`${err.message}\n${hint}`, err?.details, err?.code, hint);
203
234
  }
204
235
  const pb = resolved.playbook;
236
+ const hints = [
237
+ "Tip: Follow steps sequentially. Invoke constituent actions using 'ad run <action> [options] -- <assignments...>'.",
238
+ ];
205
239
  const detail = {
206
240
  id: pb.id,
207
241
  packageId: resolved.packageId,
@@ -209,6 +243,7 @@ export function registerPlaybookCommands(program, context) {
209
243
  actions: pb.actions,
210
244
  filePath: pb.filePath,
211
245
  content: pb.content,
246
+ hints,
212
247
  };
213
248
  renderResult(detail, {
214
249
  json: options.json,
@@ -317,14 +352,14 @@ export function registerPlaybookCommands(program, context) {
317
352
  resolveAction(actRef, { graph, catalog, caller: target.packageId });
318
353
  }
319
354
  catch (e) {
320
- errors.push(`Referenced cross-package action '${actRef}' not resolvable: ${e.message}`);
355
+ errors.push(`Referenced cross-package action '${actRef}' not resolvable: ${e.message}. Hint: Action '${actRef}' not found. Run 'ad action create ${actRef}' to create local action, or 'ad add <pkg>' to install external dependency.`);
321
356
  }
322
357
  }
323
358
  else {
324
359
  const foundInActions = actions.has(actRef);
325
360
  const foundInManifest = Boolean(manifest?.actions?.[actRef]);
326
361
  if (!foundInActions && !foundInManifest) {
327
- errors.push(`Referenced action '${actRef}' not found in project actions`);
362
+ errors.push(`Referenced action '${actRef}' not found in project actions. Hint: Action '${actRef}' not found. Run 'ad action create ${actRef}' to create local action, or 'ad add <pkg>' to install external dependency.`);
328
363
  }
329
364
  }
330
365
  }
@@ -1,5 +1,6 @@
1
1
  import { validateActionInputValue, mapInputValidationFailure, } from "@actiondock/core/project";
2
2
  import { resolveTarget, } from "@actiondock/core/profile";
3
+ import { resolveExecutionHint, } from "@actiondock/core";
3
4
  import { ArgumentError, ExecutionError, SigintError, packageNotFoundError } from "../errors.js";
4
5
  import { writeStderr, writeStdout } from "../renderer.js";
5
6
  import { applyTargetOptions, getEffectiveOptions, resolveActionInput, resolveLocalPackageRoot, resolveTimeoutMs, withService, } from "../utils/index.js";
@@ -87,6 +88,10 @@ export function renderRawExecutionResult(targetRef, result, context) {
87
88
  ? result.error.details
88
89
  : JSON.stringify(result.error.details, null, 2), context);
89
90
  }
91
+ const hint = resolveExecutionHint(targetRef, result.error);
92
+ if (hint) {
93
+ writeStderr(hint, context);
94
+ }
90
95
  if (context) {
91
96
  context.exitCode = 1;
92
97
  }
@@ -189,7 +194,14 @@ export async function executeAction(id, options, context, flatArgs, control) {
189
194
  throw new SigintError();
190
195
  }
191
196
  if (isMachine) {
192
- writeStdout(JSON.stringify(result, null, 2), context);
197
+ if (!result.ok) {
198
+ const hint = resolveExecutionHint(targetRef, result.error);
199
+ const machineOutput = hint !== undefined ? { ...result, hint } : result;
200
+ writeStdout(JSON.stringify(machineOutput, null, 2), context);
201
+ }
202
+ else {
203
+ writeStdout(JSON.stringify(result, null, 2), context);
204
+ }
193
205
  }
194
206
  else {
195
207
  renderRawExecutionResult(targetRef, result, context);
@@ -260,6 +272,9 @@ export function attachRunCommand(parent, context) {
260
272
  .option("-i, --input <json>", "Action input as inline JSON")
261
273
  .option("-f, --input-file <path>", "Action input from JSON file, or '-' for stdin")
262
274
  .option("-c, --config <key=value...>", "Temporary config override (repeatable)");
275
+ cmd.unknownOption = function (flag) {
276
+ this.error(`unknown option '${flag}'\nHint: Separate action inputs from CLI options using '--', e.g.: ad run <id> [options] -- <param>=<val> or <param>:=<json>.`, { code: "commander.unknownOption" });
277
+ };
263
278
  return applyTargetOptions(cmd)
264
279
  .option("--timeout <duration>", "Execution timeout (e.g. 30s, 5m, 500ms)")
265
280
  .option("--request-id <id>", "Idempotency request ID for deduplication")
package/dist/errors.d.ts CHANGED
@@ -6,19 +6,20 @@ export declare class CliError extends Error {
6
6
  readonly exitCode: ExitCodeValue;
7
7
  readonly code: string;
8
8
  readonly details?: unknown;
9
- constructor(message: string, exitCode?: ExitCodeValue, code?: string, details?: unknown);
9
+ readonly hint?: string;
10
+ constructor(message: string, exitCode?: ExitCodeValue, code?: string, details?: unknown, hint?: string);
10
11
  }
11
12
  /**
12
13
  * 命令行参数与选项校验错误(退出码为 2)。
13
14
  */
14
15
  export declare class ArgumentError extends CliError {
15
- constructor(message: string, details?: unknown, code?: string);
16
+ constructor(message: string, details?: unknown, code?: string, hint?: string);
16
17
  }
17
18
  /**
18
19
  * 业务逻辑或框架执行失败错误(退出码为 1)。
19
20
  */
20
21
  export declare class ExecutionError extends CliError {
21
- constructor(message: string, details?: unknown, code?: string);
22
+ constructor(message: string, details?: unknown, code?: string, hint?: string);
22
23
  }
23
24
  /**
24
25
  * 将任意异常统一包裹为 CLI 执行错误(错误码保留辅助)。
@@ -48,6 +49,7 @@ export interface FormattedError {
48
49
  message: string;
49
50
  exitCode: ExitCodeValue;
50
51
  details?: unknown;
52
+ hint?: string;
51
53
  }
52
54
  /**
53
55
  * 目标包寻址失败错误(-P 指定的包不在链接注册表也无法按路径寻址)。
package/dist/errors.js CHANGED
@@ -30,20 +30,22 @@ export class CliError extends Error {
30
30
  exitCode;
31
31
  code;
32
32
  details;
33
- constructor(message, exitCode = ExitCode.FAILURE, code = "CLI_ERROR", details) {
33
+ hint;
34
+ constructor(message, exitCode = ExitCode.FAILURE, code = "CLI_ERROR", details, hint) {
34
35
  super(message);
35
36
  this.name = "CliError";
36
37
  this.exitCode = exitCode;
37
38
  this.code = code;
38
39
  this.details = details;
40
+ this.hint = hint;
39
41
  }
40
42
  }
41
43
  /**
42
44
  * 命令行参数与选项校验错误(退出码为 2)。
43
45
  */
44
46
  export class ArgumentError extends CliError {
45
- constructor(message, details, code = "INVALID_ARGUMENT") {
46
- super(message, ExitCode.INVALID_ARGUMENT, code, details);
47
+ constructor(message, details, code = "INVALID_ARGUMENT", hint) {
48
+ super(message, ExitCode.INVALID_ARGUMENT, code, details, hint);
47
49
  this.name = "ArgumentError";
48
50
  }
49
51
  }
@@ -51,8 +53,8 @@ export class ArgumentError extends CliError {
51
53
  * 业务逻辑或框架执行失败错误(退出码为 1)。
52
54
  */
53
55
  export class ExecutionError extends CliError {
54
- constructor(message, details, code = "EXECUTION_FAILURE") {
55
- super(message, ExitCode.FAILURE, code, details);
56
+ constructor(message, details, code = "EXECUTION_FAILURE", hint) {
57
+ super(message, ExitCode.FAILURE, code, details, hint);
56
58
  this.name = "ExecutionError";
57
59
  }
58
60
  }
@@ -96,7 +98,9 @@ export class SigintError extends CliError {
96
98
  * 目标包寻址失败错误(-P 指定的包不在链接注册表也无法按路径寻址)。
97
99
  */
98
100
  export function packageNotFoundError(pkg) {
99
- return new ArgumentError(`Package '${pkg}' not found in linked packages or path`);
101
+ const base = `Package '${pkg}' not found in linked packages or path`;
102
+ const hint = `Hint: Package '${pkg}' not found. Run 'ad add ${pkg}' to install project dependency, or 'ad link <path>' for local development.`;
103
+ return new ArgumentError(base, undefined, "INVALID_ARGUMENT", hint);
100
104
  }
101
105
  /**
102
106
  * 当前目录不在 ActionDock 工程内错误。
@@ -104,7 +108,9 @@ export function packageNotFoundError(pkg) {
104
108
  */
105
109
  export function notInProjectError(hint) {
106
110
  const base = "Not in an ActionDock project (actiondock.json not found)";
107
- return new ArgumentError(hint ? `${base}.\n${hint}` : base);
111
+ const effectiveHint = hint ??
112
+ "Hint: Run 'ad init' to start a new project, specify '-P <id|path>' for an existing package, or run 'ad link <path>' to register it.";
113
+ return new ArgumentError(base, undefined, "INVALID_ARGUMENT", effectiveHint);
108
114
  }
109
115
  /**
110
116
  * 当前目录无工程且全局注册表无任何链接包提示文案(非错误场景使用)。
@@ -122,6 +128,7 @@ export function formatError(err) {
122
128
  message: err.message,
123
129
  exitCode: err.exitCode,
124
130
  details: err.details,
131
+ hint: err.hint,
125
132
  };
126
133
  }
127
134
  if (err instanceof InputError) {
@@ -130,6 +137,7 @@ export function formatError(err) {
130
137
  message: err.message,
131
138
  exitCode: ExitCode.INVALID_ARGUMENT,
132
139
  details: err.details,
140
+ hint: err.hint || err.details?.hint,
133
141
  };
134
142
  }
135
143
  if (err instanceof ActionDockError) {
@@ -145,6 +153,7 @@ export function formatError(err) {
145
153
  message: err.message,
146
154
  exitCode: isArgument ? ExitCode.INVALID_ARGUMENT : ExitCode.FAILURE,
147
155
  details: err.details,
156
+ hint: err.hint || err.details?.hint,
148
157
  };
149
158
  }
150
159
  // Commander.js 原生错误处理
@@ -156,6 +165,7 @@ export function formatError(err) {
156
165
  message: err.message || String(err),
157
166
  exitCode: ExitCode.INVALID_ARGUMENT,
158
167
  details: err.details,
168
+ hint: err.hint,
159
169
  };
160
170
  }
161
171
  const commanderErr = err;
@@ -166,10 +176,15 @@ export function formatError(err) {
166
176
  code.startsWith("commander.optionMissingArgument") ||
167
177
  code.startsWith("commander.invalidArgument") ||
168
178
  code.startsWith("commander.excessArguments")) {
179
+ const hint = commanderErr.hint ||
180
+ (code.startsWith("commander.unknownOption")
181
+ ? "Hint: Separate action inputs from CLI options using '--', e.g.: ad run <id> [options] -- <param>=<val> or <param>:=<json>."
182
+ : undefined);
169
183
  return {
170
184
  code: CLI_CODE_INVALID_ARGUMENT,
171
185
  message: commanderErr.message,
172
186
  exitCode: ExitCode.INVALID_ARGUMENT,
187
+ hint,
173
188
  };
174
189
  }
175
190
  // 正常退出(如 --help 或 --version)
@@ -187,6 +202,7 @@ export function formatError(err) {
187
202
  message: err.message,
188
203
  exitCode: ExitCode.FAILURE,
189
204
  details: err.details,
205
+ hint: err.hint || err.details?.hint,
190
206
  };
191
207
  }
192
208
  return {
@@ -1,6 +1,6 @@
1
1
  import type { RunRecord } from "@actiondock/sdk";
2
2
  import { type RegistryStatusReport } from "@actiondock/core/registry";
3
- import type { Envelope, ProjectDetailInfo, AggregatedPackage, EnvCheckItem, CliContext } from "./types.js";
3
+ import type { Envelope, ProjectDetailInfo, ProjectDetailJson, AggregatedPackage, EnvCheckItem, CliContext } from "./types.js";
4
4
  /**
5
5
  * 构造标准成功结果信封。
6
6
  *
@@ -16,7 +16,7 @@ export declare function createSuccessEnvelope<T>(data: T, meta?: Record<string,
16
16
  * @param details 附加错误细节
17
17
  * @param meta 附加元数据
18
18
  */
19
- export declare function createErrorEnvelope(code: string, message: string, details?: unknown, meta?: Record<string, unknown>): Envelope<never>;
19
+ export declare function createErrorEnvelope(code: string, message: string, details?: unknown, meta?: Record<string, unknown>, hint?: string): Envelope<never>;
20
20
  /**
21
21
  * 序列化数据为格式化 JSON 字符串。
22
22
  *
@@ -48,22 +48,9 @@ export declare function renderError(err: unknown, options: {
48
48
  context?: CliContext;
49
49
  }): void;
50
50
  /**
51
- * 将工程详情信息转换为机器输出视图(剔除 Map 明细与定义字典)。
51
+ * 将工程详情信息转换为机器输出视图(结构化配置契约、规程与动作索引)。
52
52
  */
53
- export declare function projectDetailToJson(info: ProjectDetailInfo): {
54
- id: string;
55
- name: string;
56
- version: string;
57
- description: string | undefined;
58
- projectRoot: string;
59
- actionsDir: string;
60
- playbooksDir: string;
61
- actionsCount: number;
62
- playbooksCount: number;
63
- actions: string[];
64
- playbooks: string[];
65
- configDeclared: string[];
66
- };
53
+ export declare function projectDetailToJson(info: ProjectDetailInfo): ProjectDetailJson;
67
54
  /**
68
55
  * 格式化渲染单个项目的元数据与详情信息。
69
56
  */
package/dist/renderer.js CHANGED
@@ -24,7 +24,11 @@ export function createSuccessEnvelope(data, meta) {
24
24
  * @param details 附加错误细节
25
25
  * @param meta 附加元数据
26
26
  */
27
- export function createErrorEnvelope(code, message, details, meta) {
27
+ export function createErrorEnvelope(code, message, details, meta, hint) {
28
+ const effectiveHint = hint ??
29
+ (details && typeof details === "object" && typeof details.hint === "string"
30
+ ? details.hint
31
+ : undefined);
28
32
  const result = {
29
33
  ok: false,
30
34
  error: {
@@ -33,6 +37,9 @@ export function createErrorEnvelope(code, message, details, meta) {
33
37
  ...(details !== undefined ? { details } : {}),
34
38
  },
35
39
  };
40
+ if (effectiveHint !== undefined) {
41
+ result.hint = effectiveHint;
42
+ }
36
43
  if (meta && Object.keys(meta).length > 0) {
37
44
  result.meta = meta;
38
45
  }
@@ -93,31 +100,79 @@ export function renderError(err, options) {
93
100
  const formatted = formatError(err);
94
101
  const isMachine = Boolean(options.json);
95
102
  if (isMachine) {
96
- const errorEnv = createErrorEnvelope(formatted.code, formatted.message, formatted.details);
103
+ const errorEnv = createErrorEnvelope(formatted.code, formatted.message, formatted.details, undefined, formatted.hint);
97
104
  writeStdout(formatJson(errorEnv), options.context);
98
105
  }
99
106
  else {
100
107
  writeStderr(`Error: ${formatted.message}`, options.context);
108
+ if (formatted.hint) {
109
+ writeStderr(formatted.hint, options.context);
110
+ }
101
111
  }
102
112
  }
103
113
  /**
104
- * 将工程详情信息转换为机器输出视图(剔除 Map 明细与定义字典)。
114
+ * 将工程详情信息转换为机器输出视图(结构化配置契约、规程与动作索引)。
105
115
  */
106
116
  export function projectDetailToJson(info) {
107
- return {
117
+ const config = {};
118
+ if (info.configDeclared && info.configDeclared.length > 0) {
119
+ for (const key of info.configDeclared) {
120
+ const item = info.configDef?.[key];
121
+ config[key] = {
122
+ ...(item?.description !== undefined ? { description: item.description } : {}),
123
+ ...(item?.default !== undefined ? { default: item.default } : {}),
124
+ secret: Boolean(item?.secret),
125
+ };
126
+ }
127
+ }
128
+ const playbooks = [];
129
+ if (info.playbooksMap && info.playbooksMap.size > 0) {
130
+ for (const [id, pb] of info.playbooksMap.entries()) {
131
+ playbooks.push({
132
+ id,
133
+ ...(pb?.description ? { description: pb.description } : {}),
134
+ });
135
+ }
136
+ }
137
+ else if (info.playbooks && info.playbooks.length > 0) {
138
+ for (const pbId of info.playbooks) {
139
+ playbooks.push({ id: pbId });
140
+ }
141
+ }
142
+ const actions = [];
143
+ if (info.actionsMap && info.actionsMap.size > 0) {
144
+ for (const [id, act] of info.actionsMap.entries()) {
145
+ actions.push({
146
+ id,
147
+ ...(act?.description ? { description: act.description } : {}),
148
+ });
149
+ }
150
+ }
151
+ else if (info.actions && info.actions.length > 0) {
152
+ for (const actId of info.actions) {
153
+ actions.push({ id: actId });
154
+ }
155
+ }
156
+ const hints = [
157
+ "Tip: Run 'ad playbook show <id>' to inspect procedure steps before execution.",
158
+ ];
159
+ if (info.configDeclared && info.configDeclared.length > 0) {
160
+ hints.push("Tip: Run 'ad config set <KEY> <val>' to configure required settings.");
161
+ }
162
+ const result = {
108
163
  id: info.id,
109
164
  name: info.name || info.id,
110
165
  version: info.version || "0.0.0",
111
- description: info.description,
112
- projectRoot: info.projectRoot,
113
- actionsDir: info.actionsDir,
114
- playbooksDir: info.playbooksDir,
115
- actionsCount: info.actionsCount,
116
- playbooksCount: info.playbooksCount,
117
- actions: info.actions,
118
- playbooks: info.playbooks,
119
- configDeclared: info.configDeclared,
166
+ root: info.projectRoot,
167
+ config,
168
+ playbooks,
169
+ actions,
170
+ hints,
120
171
  };
172
+ if (info.description) {
173
+ result.description = info.description;
174
+ }
175
+ return result;
121
176
  }
122
177
  /**
123
178
  * 格式化渲染单个项目的元数据与详情信息。
@@ -130,36 +185,52 @@ export function renderProjectDetail(info) {
130
185
  lines.push(`Description: ${info.description}`);
131
186
  }
132
187
  lines.push(`Root: ${info.projectRoot}`);
188
+ if (info.configDeclared.length > 0) {
189
+ lines.push(`\nDeclared Config Keys (${info.configDeclared.length}):`);
190
+ for (const k of info.configDeclared) {
191
+ const item = info.configDef?.[k];
192
+ const isSec = item?.secret ? " [secret]" : "";
193
+ const def = item?.default !== undefined ? ` (default: ${JSON.stringify(item.default)})` : "";
194
+ lines.push(` - ${k.padEnd(24)} ${item?.description || ""}${def}${isSec}`);
195
+ }
196
+ }
197
+ lines.push(`\nPlaybooks (${info.playbooksCount}):`);
198
+ if (info.playbooksMap && info.playbooksMap.size > 0) {
199
+ for (const [id, pb] of info.playbooksMap.entries()) {
200
+ lines.push(` - ${id.padEnd(28)} ${pb.description || ""}`);
201
+ }
202
+ }
203
+ else if (info.playbooks && info.playbooks.length > 0) {
204
+ for (const pbId of info.playbooks) {
205
+ lines.push(` - ${pbId}`);
206
+ }
207
+ }
208
+ else {
209
+ lines.push(" (no playbooks declared)");
210
+ }
133
211
  lines.push(`\nActions (${info.actionsCount}):`);
212
+ const actionList = [];
134
213
  if (info.actionsMap) {
135
214
  for (const [id, act] of info.actionsMap.entries()) {
136
- lines.push(` - ${id.padEnd(28)} ${act.description || ""}`);
215
+ actionList.push({ id, description: act.description || "" });
137
216
  }
138
217
  }
139
- else {
218
+ else if (info.actions) {
140
219
  for (const actId of info.actions) {
141
- lines.push(` - ${actId}`);
220
+ actionList.push({ id: actId, description: "" });
142
221
  }
143
222
  }
144
- lines.push(`\nPlaybooks (${info.playbooksCount}):`);
145
- if (info.playbooksMap) {
146
- for (const [id, pb] of info.playbooksMap.entries()) {
147
- lines.push(` - ${id.padEnd(28)} ${pb.description || ""}`);
148
- }
223
+ if (actionList.length === 0) {
224
+ lines.push(" (no actions declared)");
149
225
  }
150
226
  else {
151
- for (const pbId of info.playbooks) {
152
- lines.push(` - ${pbId}`);
227
+ for (const a of actionList) {
228
+ lines.push(` - ${a.id.padEnd(28)} ${a.description}`);
153
229
  }
154
230
  }
231
+ lines.push("\nTip: Run 'ad playbook show <id>' to inspect procedure steps before execution.");
155
232
  if (info.configDeclared.length > 0) {
156
- lines.push(`\nDeclared Config Keys:`);
157
- for (const k of info.configDeclared) {
158
- const item = info.configDef?.[k];
159
- const isSec = item?.secret ? " [secret]" : "";
160
- const def = item?.default !== undefined ? ` (default: ${JSON.stringify(item.default)})` : "";
161
- lines.push(` - ${k.padEnd(24)} ${item?.description || ""}${def}${isSec}`);
162
- }
233
+ lines.push("Tip: Run 'ad config set <KEY> <val>' to configure required settings.");
163
234
  }
164
235
  return lines.join("\n");
165
236
  }
@@ -252,6 +323,7 @@ export function renderActionList(items, title = "Actions", isFallback = false, i
252
323
  lines.push(` - ${a.id.padEnd(28)} ${a.description}`);
253
324
  }
254
325
  }
326
+ lines.push("\nTip: For composite or multi-step tasks, check 'ad playbook list' for standard operating procedures.");
255
327
  return lines.join("\n");
256
328
  }
257
329
  /**
@@ -294,6 +366,7 @@ export function renderPlaybookList(items, title = "Playbooks", isFallback = fals
294
366
  lines.push(` - ${p.id.padEnd(26)} ${p.description}${pkgDesc}`);
295
367
  }
296
368
  }
369
+ lines.push("\nTip: Run 'ad playbook show <id>' to inspect procedure steps before execution.");
297
370
  return lines.join("\n");
298
371
  }
299
372
  /**
@@ -314,6 +387,7 @@ export function renderPlaybookDetail(pb) {
314
387
  lines.push("--- Content ---");
315
388
  lines.push(pb.content);
316
389
  }
390
+ lines.push("\nTip: Follow steps sequentially. Invoke constituent actions using 'ad run <action> [options] -- <assignments...>'.");
317
391
  return lines.join("\n");
318
392
  }
319
393
  /**
package/dist/types.d.ts CHANGED
@@ -27,6 +27,7 @@ export interface Envelope<T = unknown> {
27
27
  message: string;
28
28
  details?: unknown;
29
29
  };
30
+ hint?: string;
30
31
  meta?: Record<string, unknown>;
31
32
  }
32
33
  /**
@@ -108,6 +109,42 @@ export interface ProjectDetailInfo {
108
109
  }>;
109
110
  playbooksMap?: Map<string, any>;
110
111
  }
112
+ /**
113
+ * 机器视图工程配置声明条目。
114
+ */
115
+ export interface ProjectDetailConfigItem {
116
+ description?: string;
117
+ default?: unknown;
118
+ secret: boolean;
119
+ }
120
+ /**
121
+ * 机器视图规程条目。
122
+ */
123
+ export interface ProjectDetailPlaybookItem {
124
+ id: string;
125
+ description?: string;
126
+ }
127
+ /**
128
+ * 机器视图动作条目。
129
+ */
130
+ export interface ProjectDetailActionItem {
131
+ id: string;
132
+ description?: string;
133
+ }
134
+ /**
135
+ * 机器模式工程详情输出结构。
136
+ */
137
+ export interface ProjectDetailJson {
138
+ id: string;
139
+ name: string;
140
+ version: string;
141
+ description?: string;
142
+ root: string;
143
+ config: Record<string, ProjectDetailConfigItem>;
144
+ playbooks: ProjectDetailPlaybookItem[];
145
+ actions: ProjectDetailActionItem[];
146
+ hints?: string[];
147
+ }
111
148
  /**
112
149
  * 聚合 Package 信息结构。
113
150
  */
@@ -3,7 +3,7 @@ import { type ActionDockManifest, type ProjectTransaction } from "@actiondock/co
3
3
  /**
4
4
  * 「未显式指定包且当前目录无工程」的统一补救提示(单一事实源)。
5
5
  */
6
- export declare const PACKAGE_HINT_MESSAGE = "Please specify -P, --package <id> or cd into a project directory.";
6
+ export declare const PACKAGE_HINT_MESSAGE = "Hint: Run 'ad init' to start a new project, specify '-P <id|path>' for an existing package, or run 'ad link <path>' to register it.";
7
7
  /**
8
8
  * 远端目标拓扑的最小字段视图(与 core 的 ResolvedTarget 远端分支兼容)。
9
9
  */
@@ -6,7 +6,7 @@ import { ArgumentError, ExecutionError, notInProjectError, packageNotFoundError,
6
6
  /**
7
7
  * 「未显式指定包且当前目录无工程」的统一补救提示(单一事实源)。
8
8
  */
9
- export const PACKAGE_HINT_MESSAGE = "Please specify -P, --package <id> or cd into a project directory.";
9
+ export const PACKAGE_HINT_MESSAGE = "Hint: Run 'ad init' to start a new project, specify '-P <id|path>' for an existing package, or run 'ad link <path>' to register it.";
10
10
  /**
11
11
  * 拼接远端目标标题后缀(单一事实源)。
12
12
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actiondock/cli",
3
- "version": "2.9.1",
3
+ "version": "2.10.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.9.1",
38
- "@actiondock/core": "^2.9.1",
39
- "@actiondock/mcp": "^2.9.1",
40
- "@actiondock/sdk": "^2.9.1",
37
+ "@actiondock/builder": "^2.10.0",
38
+ "@actiondock/core": "^2.10.0",
39
+ "@actiondock/mcp": "^2.10.0",
40
+ "@actiondock/sdk": "^2.10.0",
41
41
  "commander": "^13.1.0",
42
42
  "selfsigned": "^5.5.0"
43
43
  },