@actiondock/cli 2.0.11 → 2.2.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 (123) hide show
  1. package/README.md +41 -21
  2. package/bin/ad.js +29 -31
  3. package/dist/commands/action/create.d.ts +10 -0
  4. package/dist/commands/action/create.js +120 -0
  5. package/dist/commands/add.d.ts +7 -0
  6. package/dist/commands/add.js +144 -0
  7. package/dist/commands/build.d.ts +3 -0
  8. package/dist/commands/build.js +83 -0
  9. package/dist/commands/config/delete.d.ts +9 -0
  10. package/dist/commands/config/delete.js +52 -0
  11. package/dist/commands/config/env.d.ts +9 -0
  12. package/dist/commands/config/env.js +85 -0
  13. package/dist/commands/config/get.d.ts +9 -0
  14. package/dist/commands/config/get.js +120 -0
  15. package/dist/commands/config/index.d.ts +9 -0
  16. package/dist/commands/config/index.js +23 -0
  17. package/dist/commands/config/list.d.ts +9 -0
  18. package/dist/commands/config/list.js +137 -0
  19. package/dist/commands/config/schema.d.ts +9 -0
  20. package/dist/commands/config/schema.js +121 -0
  21. package/dist/commands/config/set.d.ts +9 -0
  22. package/dist/commands/config/set.js +85 -0
  23. package/dist/commands/describe.d.ts +9 -0
  24. package/dist/commands/describe.js +68 -0
  25. package/dist/commands/doctor.d.ts +3 -0
  26. package/dist/commands/doctor.js +82 -0
  27. package/dist/commands/export.d.ts +3 -0
  28. package/dist/commands/export.js +212 -0
  29. package/dist/commands/generate.d.ts +9 -0
  30. package/dist/commands/generate.js +44 -0
  31. package/dist/commands/index.d.ts +32 -0
  32. package/dist/commands/index.js +111 -0
  33. package/dist/commands/info.d.ts +9 -0
  34. package/dist/commands/info.js +210 -0
  35. package/dist/commands/init.d.ts +2 -0
  36. package/dist/commands/init.js +24 -0
  37. package/dist/commands/link.d.ts +2 -0
  38. package/dist/commands/link.js +71 -0
  39. package/dist/commands/list.d.ts +9 -0
  40. package/dist/commands/list.js +77 -0
  41. package/dist/commands/mcp.d.ts +9 -0
  42. package/dist/commands/mcp.js +172 -0
  43. package/dist/commands/new.d.ts +9 -0
  44. package/dist/commands/new.js +33 -0
  45. package/dist/commands/pack.d.ts +3 -0
  46. package/dist/commands/pack.js +63 -0
  47. package/dist/commands/playbook.d.ts +10 -0
  48. package/dist/commands/playbook.js +433 -0
  49. package/dist/commands/profile.d.ts +2 -0
  50. package/dist/commands/profile.js +253 -0
  51. package/dist/commands/remove.d.ts +7 -0
  52. package/dist/commands/remove.js +118 -0
  53. package/dist/commands/run.d.ts +13 -0
  54. package/dist/commands/run.js +146 -0
  55. package/dist/commands/runs.d.ts +9 -0
  56. package/dist/commands/runs.js +245 -0
  57. package/dist/commands/serve.d.ts +9 -0
  58. package/dist/commands/serve.js +129 -0
  59. package/dist/commands/state-list.d.ts +24 -0
  60. package/dist/commands/state-list.js +82 -0
  61. package/dist/commands/state.d.ts +9 -0
  62. package/dist/commands/state.js +310 -0
  63. package/dist/commands/test.d.ts +2 -0
  64. package/dist/commands/test.js +44 -0
  65. package/dist/commands/validate.d.ts +9 -0
  66. package/dist/commands/validate.js +106 -0
  67. package/dist/errors.d.ts +43 -0
  68. package/dist/errors.js +103 -0
  69. package/dist/index.d.ts +15 -0
  70. package/dist/index.js +47 -65969
  71. package/dist/prompt.d.ts +53 -0
  72. package/dist/prompt.js +192 -0
  73. package/dist/renderer.d.ts +197 -0
  74. package/dist/renderer.js +502 -0
  75. package/dist/services/index.d.ts +1 -0
  76. package/dist/services/index.js +1 -0
  77. package/dist/services/inspect.d.ts +23 -0
  78. package/dist/services/inspect.js +96 -0
  79. package/dist/standalone/index.d.ts +16 -0
  80. package/dist/standalone/index.js +22 -0
  81. package/dist/types.d.ts +132 -0
  82. package/dist/types.js +9 -0
  83. package/dist/utils/bytes.d.ts +1 -0
  84. package/dist/utils/bytes.js +1 -0
  85. package/dist/utils/duration.js +1 -0
  86. package/dist/utils/filter.d.ts +1 -0
  87. package/dist/utils/filter.js +1 -0
  88. package/dist/utils/index.d.ts +6 -0
  89. package/dist/utils/index.js +6 -0
  90. package/dist/utils/spawn.d.ts +27 -0
  91. package/dist/utils/spawn.js +35 -0
  92. package/dist/utils/target.d.ts +45 -0
  93. package/dist/utils/target.js +78 -0
  94. package/dist/utils/utils.d.ts +38 -0
  95. package/dist/utils/utils.js +111 -0
  96. package/package.json +20 -17
  97. package/src/commands/action/create.ts +0 -129
  98. package/src/commands/action/index.ts +0 -27
  99. package/src/commands/action/list.ts +0 -247
  100. package/src/commands/action/run.ts +0 -197
  101. package/src/commands/action/show.ts +0 -128
  102. package/src/commands/action/sync.ts +0 -76
  103. package/src/commands/action/validate.ts +0 -102
  104. package/src/commands/action.ts +0 -1
  105. package/src/commands/build.ts +0 -56
  106. package/src/commands/config.ts +0 -583
  107. package/src/commands/doctor.ts +0 -84
  108. package/src/commands/export.ts +0 -196
  109. package/src/commands/index.ts +0 -75
  110. package/src/commands/info.ts +0 -537
  111. package/src/commands/init.ts +0 -24
  112. package/src/commands/link.ts +0 -73
  113. package/src/commands/mcp.ts +0 -195
  114. package/src/commands/playbook.ts +0 -566
  115. package/src/commands/profile.ts +0 -296
  116. package/src/commands/runs.ts +0 -452
  117. package/src/commands/serve.ts +0 -132
  118. package/src/commands/state.ts +0 -590
  119. package/src/commands/test.ts +0 -48
  120. package/src/index.ts +0 -58
  121. package/src/utils/bytes.ts +0 -2
  122. package/src/utils/filter.ts +0 -2
  123. /package/{src/utils/duration.ts → dist/utils/duration.d.ts} +0 -0
@@ -0,0 +1,210 @@
1
+ import { fetchRemoteInfo, filterWithFallbackInfo, getRegistryStatus, resolvePackageRoot, resolveTarget, } from "@actiondock/core";
2
+ import { ArgumentError, ExecutionError } from "../errors.js";
3
+ import { projectDetailToJson, renderAggregatedPackages, renderProjectDetail, renderRegistryTree, renderResult, } from "../renderer.js";
4
+ import { getProjectDetailInfo, scanLocalAggregatedPackages } from "../services/index.js";
5
+ import { getEffectiveOptions, resolveFallbackStrategy, resolveIntent } from "../utils/index.js";
6
+ /**
7
+ * 将远端信息响应归一为本地工程详情视图。
8
+ */
9
+ function remoteInfoToDetail(remoteInfo) {
10
+ return {
11
+ id: remoteInfo.id,
12
+ name: remoteInfo.name || remoteInfo.id,
13
+ version: remoteInfo.version || "unknown",
14
+ description: remoteInfo.description,
15
+ projectRoot: remoteInfo.path || remoteInfo.projectRoot || "",
16
+ actionsDir: "remote",
17
+ playbooksDir: "remote",
18
+ actionsCount: remoteInfo.actionsCount || (remoteInfo.actions ? remoteInfo.actions.length : 0),
19
+ playbooksCount: remoteInfo.playbooksCount || (remoteInfo.playbooks ? remoteInfo.playbooks.length : 0),
20
+ actions: (remoteInfo.actionsDetail || remoteInfo.actions || []).map((a) => typeof a === "string" ? a : a.id),
21
+ playbooks: (remoteInfo.playbooksDetail || remoteInfo.playbooks || []).map((pb) => typeof pb === "string" ? pb : pb.id),
22
+ configDeclared: Object.keys(remoteInfo.configDeclared || {}),
23
+ configDef: remoteInfo.configDeclared,
24
+ };
25
+ }
26
+ /**
27
+ * 渲染工程详情输出(机器模式输出视图转换结果,人类模式渲染详情)。
28
+ */
29
+ function renderProjectDetailOutput(detail, options) {
30
+ renderResult(projectDetailToJson(detail), {
31
+ json: options.json,
32
+ envelope: options.envelope,
33
+ humanFormatter: () => renderProjectDetail(detail),
34
+ context: options.context,
35
+ });
36
+ }
37
+ /**
38
+ * 注册 info 命令。
39
+ *
40
+ * @param program Commander 实例
41
+ * @param context 命令行上下文
42
+ */
43
+ export function registerInfoCommand(program, context) {
44
+ program
45
+ .command("info [patterns...]")
46
+ .description("Display information about current project, linked package, or remote target")
47
+ .option("-i, --intent <pattern>", "Regex or fuzzy intent filter; falls back to full list when no match")
48
+ .option("-P, --package <id>", "Target package ID or path")
49
+ .option("--tree", "Display packages in hierarchical tree view grouped by workspace")
50
+ .option("-p, --profile <name>", "Query against a specific profile")
51
+ .option("-s, --server <url>", "Remote server URL")
52
+ .option("-t, --token <token>", "Auth token for remote server")
53
+ .option("--fallback", "Enable fallback to full list when no items match intent")
54
+ .option("--no-fallback", "Disable fallback to full list when no items match intent")
55
+ .option("--json", "Output information as JSON")
56
+ .option("--envelope", "Wrap JSON output in standard envelope")
57
+ .option("--data-dir <path>", "Custom database storage directory")
58
+ .action(async (patterns = [], rawOptions, cmd) => {
59
+ const options = getEffectiveOptions(rawOptions, cmd);
60
+ const effectiveIntent = resolveIntent(options.intent, patterns);
61
+ const { isMachine, shouldFallback } = resolveFallbackStrategy(options);
62
+ const outOpts = { json: options.json, envelope: options.envelope, context };
63
+ // 远端服务目标分支
64
+ const target = resolveTarget({
65
+ profile: options.profile,
66
+ server: options.server,
67
+ token: options.token,
68
+ }, context?.customHome);
69
+ if (target.type === "remote") {
70
+ const remoteInfo = await fetchRemoteInfo(target.serverUrl, target.token, {
71
+ intent: effectiveIntent,
72
+ package: options.package,
73
+ tree: Boolean(options.tree),
74
+ });
75
+ if (isMachine) {
76
+ renderResult(remoteInfo, outOpts);
77
+ return;
78
+ }
79
+ if (remoteInfo.type === "tree" || (options.tree && remoteInfo.workspaces)) {
80
+ renderResult(remoteInfo, {
81
+ humanFormatter: () => renderRegistryTree(remoteInfo),
82
+ context,
83
+ });
84
+ return;
85
+ }
86
+ if (remoteInfo.type === "package_detail" || (remoteInfo.id && !remoteInfo.packages)) {
87
+ const detail = remoteInfoToDetail(remoteInfo);
88
+ renderResult(detail, {
89
+ humanFormatter: () => renderProjectDetail(detail),
90
+ context,
91
+ });
92
+ return;
93
+ }
94
+ const packages = remoteInfo.packages || remoteInfo.linkedPackages || [];
95
+ renderResult(packages, {
96
+ humanFormatter: () => renderAggregatedPackages(packages),
97
+ context,
98
+ });
99
+ return;
100
+ }
101
+ // 本地注册表树状图分支
102
+ if (options.tree) {
103
+ const status = getRegistryStatus(context?.customHome);
104
+ renderResult(status, {
105
+ json: options.json,
106
+ envelope: options.envelope,
107
+ humanFormatter: () => renderRegistryTree(status),
108
+ context,
109
+ });
110
+ return;
111
+ }
112
+ // 显式指定 Package 参数分支
113
+ if (options.package) {
114
+ const directRoot = resolvePackageRoot(options.package);
115
+ if (directRoot) {
116
+ const detail = await getProjectDetailInfo(directRoot);
117
+ renderProjectDetailOutput(detail, outOpts);
118
+ return;
119
+ }
120
+ throw new ArgumentError(`Package '${options.package}' not found in linked packages or path`);
121
+ }
122
+ // 扫描本地候选包(当前工程根目录 + 全局已链接包)
123
+ const { currentRoot, aggregated } = scanLocalAggregatedPackages(context);
124
+ // 无关键字过滤场景
125
+ if (!effectiveIntent) {
126
+ if (currentRoot) {
127
+ const detail = await getProjectDetailInfo(currentRoot);
128
+ renderProjectDetailOutput(detail, outOpts);
129
+ return;
130
+ }
131
+ if (aggregated.length === 0) {
132
+ renderResult({ linkedPackages: [] }, {
133
+ ...outOpts,
134
+ humanFormatter: () => "No ActionDock project in current directory, and no packages linked.\nRun 'ad link' inside an Action package to register it.",
135
+ });
136
+ return;
137
+ }
138
+ renderResult({ linkedPackages: aggregated }, {
139
+ ...outOpts,
140
+ humanFormatter: () => renderAggregatedPackages(aggregated),
141
+ });
142
+ return;
143
+ }
144
+ // 有关键字过滤场景:单一位置参数且非显式 --intent 时优先尝试包寻址
145
+ if (patterns.length === 1 && !options.intent) {
146
+ const directRoot = resolvePackageRoot(patterns[0]);
147
+ if (directRoot) {
148
+ const detail = await getProjectDetailInfo(directRoot);
149
+ renderProjectDetailOutput(detail, outOpts);
150
+ return;
151
+ }
152
+ }
153
+ if (aggregated.length === 0) {
154
+ if (isMachine) {
155
+ renderResult({ linkedPackages: [], matchedCount: 0, isFallback: false }, outOpts);
156
+ return;
157
+ }
158
+ throw new ExecutionError(`No ActionDock project or linked packages available to match '${effectiveIntent}'`);
159
+ }
160
+ const filterRes = filterWithFallbackInfo(aggregated, effectiveIntent, [
161
+ (p) => p.id,
162
+ (p) => p.name,
163
+ (p) => p.description,
164
+ (p) => p.path,
165
+ (p) => p.actions,
166
+ (p) => p.playbooks,
167
+ ], shouldFallback);
168
+ // 无匹配项
169
+ if (filterRes.matchedCount === 0) {
170
+ if (!filterRes.isFallback) {
171
+ if (isMachine) {
172
+ renderResult({ linkedPackages: [], matchedCount: 0, isFallback: false }, outOpts);
173
+ return;
174
+ }
175
+ throw new ExecutionError(`No packages matched intent '${effectiveIntent}'`);
176
+ }
177
+ // 回退模式:展示全部包并附带提示
178
+ if (isMachine) {
179
+ renderResult({ linkedPackages: filterRes.items, isFallback: true, matchedCount: 0 }, outOpts);
180
+ return;
181
+ }
182
+ renderResult({ linkedPackages: filterRes.items, isFallback: true, matchedCount: 0 }, {
183
+ ...outOpts,
184
+ humanFormatter: () => `(No linked packages matched intent '${effectiveIntent}', showing all packages)\n\n` +
185
+ renderAggregatedPackages(filterRes.items),
186
+ });
187
+ return;
188
+ }
189
+ // 机器模式下搜索:稳定返回列表结构,不因为 matchedCount === 1 突变为详情
190
+ if (isMachine) {
191
+ renderResult({ linkedPackages: filterRes.items, matchedCount: filterRes.matchedCount, isFallback: false }, outOpts);
192
+ return;
193
+ }
194
+ // 人类终端交互:精确匹配单个包展开详情
195
+ if (filterRes.matchedCount === 1) {
196
+ const matchedPkg = filterRes.items[0];
197
+ const detail = await getProjectDetailInfo(matchedPkg.path);
198
+ renderProjectDetailOutput(detail, outOpts);
199
+ return;
200
+ }
201
+ // 匹配多个包:输出摘要列表
202
+ renderResult({ linkedPackages: filterRes.items }, {
203
+ ...outOpts,
204
+ humanFormatter: () => renderAggregatedPackages(filterRes.items, {
205
+ header: `ActionDock Linked Packages (${filterRes.matchedCount} matches for '${effectiveIntent}'):\n`,
206
+ showTip: true,
207
+ }),
208
+ });
209
+ });
210
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerInitCommand(program: Command): void;
@@ -0,0 +1,24 @@
1
+ import { initProject } from "@actiondock/core";
2
+ import { writeStdout } from "../renderer.js";
3
+ import { ExecutionError } from "../errors.js";
4
+ export function registerInitCommand(program) {
5
+ program
6
+ .command("init [directory]")
7
+ .description("Initialize a new ActionDock project")
8
+ .option("-i, --id <id>", "Project identifier")
9
+ .option("-n, --name <name>", "Project display name")
10
+ .option("-d, --desc <description>", "Project description")
11
+ .action((dir = ".", options) => {
12
+ try {
13
+ initProject(dir, {
14
+ id: options.id,
15
+ name: options.name,
16
+ description: options.desc,
17
+ });
18
+ writeStdout(`Successfully initialized ActionDock project in ${dir}`);
19
+ }
20
+ catch (err) {
21
+ throw new ExecutionError(err.message);
22
+ }
23
+ });
24
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerLinkCommands(program: Command): void;
@@ -0,0 +1,71 @@
1
+ import { linkPackage, pruneRegistry, unlinkPackage, } from "@actiondock/core";
2
+ import { ExecutionError } from "../errors.js";
3
+ import { writeStdout } from "../renderer.js";
4
+ export function registerLinkCommands(program) {
5
+ // ad link [path]
6
+ program
7
+ .command("link [path]")
8
+ .option("-r, --recursive", "Recursively discover and link packages in subdirectories")
9
+ .description("Link local Action package(s) or workspace directory into global registry for instant cross-directory execution")
10
+ .action(async (targetPath, options) => {
11
+ try {
12
+ const result = await linkPackage(targetPath || process.cwd(), undefined, {
13
+ recursive: options.recursive,
14
+ });
15
+ if (result.isWorkspace) {
16
+ writeStdout(`[OK] Linked workspace '${result.path}' (${result.entries.length} package${result.entries.length > 1 ? "s" : ""}):`);
17
+ for (const e of result.entries) {
18
+ writeStdout(` - ${e.id} (v${e.version}) -> ${e.path}`);
19
+ }
20
+ writeStdout("[INFO] Sub-packages added to this workspace will be automatically discovered.");
21
+ }
22
+ else {
23
+ writeStdout(`[OK] Linked package '${result.id}' (v${result.version}) from ${result.path}`);
24
+ }
25
+ }
26
+ catch (err) {
27
+ throw new ExecutionError(err.message);
28
+ }
29
+ });
30
+ // ad unlink [id|path]
31
+ program
32
+ .command("unlink [identifier]")
33
+ .option("-p, --prune", "Automatically scan and prune any stale/missing paths from registry")
34
+ .description("Unlink a package, workspace, or prune stale entries from global developer registry")
35
+ .action(async (identifier, options) => {
36
+ try {
37
+ if (options.prune || identifier === "--prune") {
38
+ const result = await pruneRegistry();
39
+ const totalPruned = result.prunedPackages.length + result.prunedWorkspaces.length;
40
+ if (totalPruned === 0) {
41
+ writeStdout("[OK] No stale registry entries found.");
42
+ }
43
+ else {
44
+ writeStdout(`[OK] Pruned ${result.prunedWorkspaces.length} workspace(s) and ${result.prunedPackages.length} package(s) from registry.`);
45
+ for (const ws of result.prunedWorkspaces) {
46
+ writeStdout(` - [workspace] ${ws.path}`);
47
+ }
48
+ for (const pkg of result.prunedPackages) {
49
+ writeStdout(` - [package] ${pkg.id} (${pkg.path})`);
50
+ }
51
+ }
52
+ return;
53
+ }
54
+ const removed = await unlinkPackage(identifier || process.cwd());
55
+ if (removed) {
56
+ if (removed.type === "workspace") {
57
+ writeStdout(`[OK] Unlinked workspace '${removed.path}' (${removed.packagesCount || 0} package${(removed.packagesCount || 0) > 1 ? "s" : ""} unlinked)`);
58
+ }
59
+ else {
60
+ writeStdout(`[OK] Unlinked package '${removed.id}' (${removed.path})`);
61
+ }
62
+ }
63
+ else {
64
+ writeStdout(`Package or workspace '${identifier || process.cwd()}' was not linked in registry`);
65
+ }
66
+ }
67
+ catch (err) {
68
+ throw new ExecutionError(err.message);
69
+ }
70
+ });
71
+ }
@@ -0,0 +1,9 @@
1
+ import { Command } from "commander";
2
+ import type { CliContext } from "../types.js";
3
+ /**
4
+ * 注册顶层统一 list 命令:列出当前工程、已链接包或远端服务中的可用 Action。
5
+ *
6
+ * @param program Commander 根程序对象
7
+ * @param context 命令行上下文
8
+ */
9
+ export declare function registerListCommand(program: Command, context?: CliContext): void;
@@ -0,0 +1,77 @@
1
+ import { filterWithFallbackInfo } from "@actiondock/core";
2
+ import { ArgumentError } from "../errors.js";
3
+ import { renderActionList, renderResult } from "../renderer.js";
4
+ import { getEffectiveOptions, resolveFallbackStrategy, resolveIntent, resolveLocalPackageRoot, withTarget } from "../utils/index.js";
5
+ /**
6
+ * 注册顶层统一 list 命令:列出当前工程、已链接包或远端服务中的可用 Action。
7
+ *
8
+ * @param program Commander 根程序对象
9
+ * @param context 命令行上下文
10
+ */
11
+ export function registerListCommand(program, context) {
12
+ program
13
+ .command("list [patterns...]")
14
+ .description("List actions in current project, linked packages, or remote profile")
15
+ .option("-i, --intent <pattern>", "Regex or fuzzy intent filter; falls back to full list when no match")
16
+ .option("-P, --package <id>", "Target package ID or path")
17
+ .option("-p, --profile <name>", "Query against a specific execution profile")
18
+ .option("-s, --server <url>", "Remote server URL")
19
+ .option("-t, --token <token>", "Auth token for remote server")
20
+ .option("--fallback", "Enable fallback to full list when no items match intent")
21
+ .option("--no-fallback", "Disable fallback to full list when no items match intent")
22
+ .option("--json", "Output as JSON")
23
+ .option("--envelope", "Wrap JSON output in standard envelope")
24
+ .option("--data-dir <path>", "Custom database storage directory")
25
+ .action(async (patterns = [], rawOptions, cmd) => {
26
+ const options = getEffectiveOptions(rawOptions, cmd);
27
+ const effectiveIntent = resolveIntent(options.intent, patterns);
28
+ const { isMachine, shouldFallback } = resolveFallbackStrategy(options);
29
+ // 目标拓扑解析(仅 local 分支需要包寻址)
30
+ const targetPackageRoot = resolveLocalPackageRoot(options.package);
31
+ if (options.package && !targetPackageRoot) {
32
+ throw new ArgumentError(`Package '${options.package}' not found in linked packages or path`);
33
+ }
34
+ // 通过 Target 门面统一获取 Action 列表
35
+ await withTarget(options, context, async (target, resolved) => {
36
+ let rawSummaries = await target.listActions();
37
+ // 若指定了目标包但本地未过滤,则精确匹配包标识
38
+ if (options.package && !options.profile && !options.server) {
39
+ rawSummaries = rawSummaries.filter((s) => s.id.startsWith(`${options.package}/`) ||
40
+ s.packageId === options.package ||
41
+ !s.id.includes("/"));
42
+ }
43
+ const rawList = rawSummaries.map((s) => {
44
+ let id = s.id;
45
+ if (targetPackageRoot || options.package) {
46
+ const pkgId = s.packageId || options.package;
47
+ if (pkgId && id.startsWith(`${pkgId}/`)) {
48
+ id = id.slice(pkgId.length + 1);
49
+ }
50
+ else if (targetPackageRoot && id.includes("/")) {
51
+ id = id.slice(id.indexOf("/") + 1);
52
+ }
53
+ }
54
+ return {
55
+ id,
56
+ description: s.description || "",
57
+ };
58
+ });
59
+ const filterRes = filterWithFallbackInfo(rawList, effectiveIntent, [(a) => a.id, (a) => a.description], shouldFallback);
60
+ if (filterRes.isFallback && isMachine) {
61
+ renderResult({ items: filterRes.items, isFallback: true, matchedCount: 0 }, { json: options.json, envelope: options.envelope, context });
62
+ return;
63
+ }
64
+ const title = resolved.type === "remote"
65
+ ? `Actions on remote server ${resolved.serverUrl}${resolved.profileName ? ` (Profile: ${resolved.profileName})` : ""}`
66
+ : targetPackageRoot
67
+ ? `Actions in ${options.package || "current project"}`
68
+ : "Available Actions";
69
+ renderResult(filterRes.items, {
70
+ json: options.json,
71
+ envelope: options.envelope,
72
+ humanFormatter: () => renderActionList(filterRes.items, title, filterRes.isFallback, effectiveIntent),
73
+ context,
74
+ });
75
+ }, { localRoot: targetPackageRoot || undefined, scanLinkedPackages: true });
76
+ });
77
+ }
@@ -0,0 +1,9 @@
1
+ import { Command } from "commander";
2
+ import type { CliContext } from "../types.js";
3
+ /**
4
+ * 注册 mcp 命令(STDIO 与 HTTP 传输服务模式)。
5
+ *
6
+ * @param program Commander 实例
7
+ * @param context 命令行上下文
8
+ */
9
+ export declare function registerMcpCommands(program: Command, context?: CliContext): void;
@@ -0,0 +1,172 @@
1
+ import { resolve } from "node:path";
2
+ import { findProjectRoot, formatHostForUrl, loadProjectConfig, parseDuration } from "@actiondock/core";
3
+ import { startMcpHttpServer, startMcpStdio } from "@actiondock/mcp";
4
+ import { ArgumentError, ExecutionError } from "../errors.js";
5
+ import { writeStdout } from "../renderer.js";
6
+ import { getEffectiveOptions, parseByteSize, parseListOption } from "../utils/index.js";
7
+ /**
8
+ * 注册 mcp 命令(STDIO 与 HTTP 传输服务模式)。
9
+ *
10
+ * @param program Commander 实例
11
+ * @param context 命令行上下文
12
+ */
13
+ export function registerMcpCommands(program, context) {
14
+ const mcpCommand = program
15
+ .command("mcp")
16
+ .description("Model Context Protocol (MCP) server for ActionDock Actions (STDIO default)")
17
+ .option("-d, --dir <path>", "Project root directory or directories (can be specified multiple times or comma-separated)", parseListOption, [])
18
+ .option("--package <package-id>", "Specific linked package ID(s) to serve (can be specified multiple times or comma-separated)", parseListOption, [])
19
+ .option("--all", "Serve all linked packages from global registry")
20
+ .option("--timeout <duration>", "Execution timeout (e.g. 30s, 5m, 500ms)")
21
+ .option("--allow-insecure-http", "Allow insecure HTTP connections with auth token (INSECURE)")
22
+ .action(async (rawOptions, cmd) => {
23
+ const options = getEffectiveOptions(rawOptions, cmd);
24
+ let timeoutMs;
25
+ if (options.timeout) {
26
+ try {
27
+ timeoutMs = parseDuration(options.timeout);
28
+ }
29
+ catch (err) {
30
+ throw new ArgumentError(`Invalid timeout format: ${err.message}`);
31
+ }
32
+ }
33
+ const projectRoots = options.dir && options.dir.length > 0
34
+ ? options.dir.map((d) => resolve(d))
35
+ : undefined;
36
+ const packageIds = options.package && options.package.length > 0 ? options.package : undefined;
37
+ const all = Boolean(options.all);
38
+ try {
39
+ await startMcpStdio({
40
+ projectRoots,
41
+ packageIds,
42
+ all,
43
+ timeoutMs,
44
+ });
45
+ }
46
+ catch (err) {
47
+ throw new ExecutionError(`Failed to start MCP STDIO server: ${err.message}`, err);
48
+ }
49
+ });
50
+ mcpCommand
51
+ .command("serve")
52
+ .description("Start the ActionDock MCP server over HTTP transport")
53
+ .option("-p, --port <port>", "Port to listen on (default: 5178)", "5178")
54
+ .option("-H, --host <host>", "Host address to bind to (default: 127.0.0.1)", "127.0.0.1")
55
+ .option("-t, --token <token>", "Authentication token for securing the endpoint (or set ACTIONDOCK_TOKEN)")
56
+ .option("--token-env <env>", "Environment variable name containing the authentication token")
57
+ .option("--allow-insecure-no-auth", "Allow non-loopback host binding without authentication token (INSECURE)")
58
+ .option("--allow-insecure-http", "Allow insecure HTTP connections with auth token (INSECURE)")
59
+ .option("--allow-query-token", "Allow passing authentication token via URL query parameter (INSECURE)")
60
+ .option("--cors-origin <origin>", "Allowed CORS origin (can be specified multiple times)", (val, prev = []) => [...prev, val], [])
61
+ .option("--max-body <size>", "Maximum allowed JSON request body size (e.g. 1mb, 500kb)", "1mb")
62
+ .option("-d, --dir <path>", "Project root directory or directories (can be specified multiple times or comma-separated)", parseListOption, [])
63
+ .option("--package <package-id>", "Specific linked package ID(s) to serve (can be specified multiple times or comma-separated)", parseListOption, [])
64
+ .option("--all", "Serve all linked packages from global registry")
65
+ .option("--timeout <duration>", "Execution timeout (e.g. 30s, 5m, 500ms)")
66
+ .action(async (rawOptions, cmd) => {
67
+ const options = getEffectiveOptions(rawOptions, cmd);
68
+ const port = parseInt(options.port, 10) || 5178;
69
+ const host = options.host || "127.0.0.1";
70
+ const token = options.token ||
71
+ (options.tokenEnv && typeof process !== "undefined" ? process.env?.[options.tokenEnv] : undefined) ||
72
+ (typeof process !== "undefined" ? process.env?.ACTIONDOCK_TOKEN : undefined);
73
+ const allowInsecureNoAuth = Boolean(options.allowInsecureNoAuth);
74
+ const allowInsecureHttp = Boolean(options.allowInsecureHttp);
75
+ const allowQueryToken = Boolean(options.allowQueryToken);
76
+ const corsOrigins = options.corsOrigin && options.corsOrigin.length > 0 ? options.corsOrigin : undefined;
77
+ let maxBodyBytes;
78
+ if (options.maxBody) {
79
+ try {
80
+ maxBodyBytes = parseByteSize(options.maxBody);
81
+ }
82
+ catch (err) {
83
+ throw new ArgumentError(`Invalid max-body format: ${err.message}`);
84
+ }
85
+ }
86
+ let timeoutMs;
87
+ if (options.timeout) {
88
+ try {
89
+ timeoutMs = parseDuration(options.timeout);
90
+ }
91
+ catch (err) {
92
+ throw new ArgumentError(`Invalid timeout format: ${err.message}`);
93
+ }
94
+ }
95
+ const projectRoots = options.dir && options.dir.length > 0
96
+ ? options.dir.map((d) => resolve(d))
97
+ : undefined;
98
+ const packageIds = options.package && options.package.length > 0 ? options.package : undefined;
99
+ const all = Boolean(options.all);
100
+ let targetDescription = "ActionDock MCP Server";
101
+ if (all) {
102
+ targetDescription = "All Linked Packages (Global Registry Mode)";
103
+ }
104
+ else if (packageIds && packageIds.length > 1) {
105
+ targetDescription = `Packages: ${packageIds.join(", ")}`;
106
+ }
107
+ else if (projectRoots && projectRoots.length > 1) {
108
+ targetDescription = `Directories: ${projectRoots.join(", ")}`;
109
+ }
110
+ else if (projectRoots && projectRoots.length === 1) {
111
+ try {
112
+ const config = loadProjectConfig(projectRoots[0]);
113
+ targetDescription = `${config.name} (${config.id})`;
114
+ }
115
+ catch {
116
+ targetDescription = projectRoots[0];
117
+ }
118
+ }
119
+ else if (packageIds && packageIds.length === 1) {
120
+ targetDescription = `Package: ${packageIds[0]}`;
121
+ }
122
+ else {
123
+ const currentRoot = findProjectRoot(process.cwd());
124
+ if (currentRoot) {
125
+ try {
126
+ const config = loadProjectConfig(currentRoot);
127
+ targetDescription = `${config.name} (${config.id})`;
128
+ }
129
+ catch {
130
+ targetDescription = currentRoot;
131
+ }
132
+ }
133
+ }
134
+ try {
135
+ const server = await startMcpHttpServer({
136
+ port,
137
+ host,
138
+ token,
139
+ allowInsecureNoAuth,
140
+ allowInsecureHttp,
141
+ allowQueryToken,
142
+ corsOrigins,
143
+ maxBodyBytes,
144
+ projectRoots,
145
+ packageIds,
146
+ all,
147
+ timeoutMs,
148
+ });
149
+ const displayHost = formatHostForUrl(host);
150
+ const actualMcpHost = formatHostForUrl(host === "0.0.0.0" ? "127.0.0.1" : host);
151
+ writeStdout(`\n======================================================`, context);
152
+ writeStdout(` ActionDock 2.0 MCP HTTP Server`, context);
153
+ writeStdout(`======================================================`, context);
154
+ writeStdout(` * Listening on: http://${displayHost}:${server.port}`, context);
155
+ writeStdout(` * MCP Endpoint: http://${actualMcpHost}:${server.port}/mcp`, context);
156
+ writeStdout(` * Target: ${targetDescription}`, context);
157
+ writeStdout(` * Authentication: ${token ? "Bearer Token Enabled" : "Disabled (Local)"}`, context);
158
+ writeStdout(` * CORS Origins: ${corsOrigins ? corsOrigins.join(", ") : "Disabled (Default)"}`, context);
159
+ writeStdout(`======================================================\n`, context);
160
+ writeStdout(`Press Ctrl+C to terminate.\n`, context);
161
+ const stopSignalHandler = () => {
162
+ writeStdout("\nStopping MCP HTTP server...", context);
163
+ server.stop();
164
+ };
165
+ process.once("SIGINT", stopSignalHandler);
166
+ process.once("SIGTERM", stopSignalHandler);
167
+ }
168
+ catch (err) {
169
+ throw new ExecutionError(`Failed to start ActionDock MCP HTTP server: ${err.message}`, err);
170
+ }
171
+ });
172
+ }
@@ -0,0 +1,9 @@
1
+ import type { Command } from "commander";
2
+ import type { CliContext } from "../types.js";
3
+ /**
4
+ * 注册顶层 new 脚手架命令(new action, new playbook)。
5
+ *
6
+ * @param program Commander 根程序对象
7
+ * @param context 命令行上下文
8
+ */
9
+ export declare function registerNewCommands(program: Command, context?: CliContext): void;
@@ -0,0 +1,33 @@
1
+ import { handleActionCreate } from "./action/create.js";
2
+ import { handlePlaybookCreate } from "./playbook.js";
3
+ /**
4
+ * 注册顶层 new 脚手架命令(new action, new playbook)。
5
+ *
6
+ * @param program Commander 根程序对象
7
+ * @param context 命令行上下文
8
+ */
9
+ export function registerNewCommands(program, context) {
10
+ const newCmd = program
11
+ .command("new")
12
+ .description("Scaffold a new Action or Playbook in current project");
13
+ // new action <id>
14
+ newCmd
15
+ .command("action <id>")
16
+ .alias("create")
17
+ .description("Scaffold a new Action definition file")
18
+ .option("-d, --desc <description>", "Action description")
19
+ .option("-f, --file <filePath>", "Target file path relative to actions dir")
20
+ .action(async (id, options) => {
21
+ await handleActionCreate(id, options, context);
22
+ });
23
+ // new playbook <id>
24
+ newCmd
25
+ .command("playbook <id>")
26
+ .description("Scaffold a new Playbook markdown file")
27
+ .option("-d, --desc <description>", "Playbook description")
28
+ .option("-a, --actions <actions...>", "Referenced action IDs")
29
+ .option("-f, --file <filePath>", "Target file path relative to playbooks dir")
30
+ .action((id, options) => {
31
+ handlePlaybookCreate(id, options, context);
32
+ });
33
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ import type { CliContext } from "../types.js";
3
+ export declare function registerPackCommand(program: Command, context?: CliContext): void;