@actiondock/cli 2.14.0 → 2.15.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/dist/commands/info.js +97 -18
- package/dist/commands/playbook.js +148 -105
- package/dist/renderers/project.d.ts +4 -0
- package/dist/renderers/project.js +16 -0
- package/dist/services/inspect.d.ts +14 -1
- package/dist/services/inspect.js +54 -12
- package/dist/utils/target.d.ts +41 -5
- package/dist/utils/target.js +154 -60
- package/package.json +5 -5
package/dist/commands/info.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isExplicitPath, } from "@actiondock/core";
|
|
2
2
|
import { filterWithFallbackInfo, } from "@actiondock/core/project";
|
|
3
|
+
import { fetchRemoteInfo, } from "@actiondock/core/profile";
|
|
3
4
|
import { getRegistryStatus, resolvePackageRoot, } from "@actiondock/core/registry";
|
|
4
|
-
import { ExecutionError, NO_PROJECT_NO_LINKED_MESSAGE, packageNotFoundError } from "../errors.js";
|
|
5
|
-
import { projectDetailToJson, renderAggregatedPackages, renderProjectDetail, renderRegistryTree, renderResult, } from "../renderer.js";
|
|
6
|
-
import { getProjectDetailInfo, scanLocalAggregatedPackages } from "../services/index.js";
|
|
7
|
-
import { applyTargetOptions, getEffectiveOptions, resolveFallbackStrategy, resolveIntent,
|
|
5
|
+
import { ArgumentError, ExecutionError, NO_PROJECT_NO_LINKED_MESSAGE, packageNotFoundError } from "../errors.js";
|
|
6
|
+
import { projectDetailToJson, renderAggregatedPackages, renderCompositeSkillDetail, renderProjectDetail, renderRegistryTree, renderResult, } from "../renderer.js";
|
|
7
|
+
import { buildCompositeDetails, filterProjectDetails, getProjectDetailInfo, scanLocalAggregatedPackages, } from "../services/index.js";
|
|
8
|
+
import { applyTargetOptions, getEffectiveOptions, resolveCommandTarget, resolveFallbackStrategy, resolveIntent, } from "../utils/index.js";
|
|
8
9
|
/**
|
|
9
10
|
* 将远端信息响应归一为本地工程详情视图。
|
|
10
11
|
*/
|
|
@@ -51,9 +52,49 @@ function remoteInfoToDetail(remoteInfo) {
|
|
|
51
52
|
* 渲染工程详情输出(机器模式输出视图转换结果,人类模式渲染详情)。
|
|
52
53
|
*/
|
|
53
54
|
function renderProjectDetailOutput(detail, options) {
|
|
54
|
-
|
|
55
|
+
const { matchedCount, isFallback } = filterProjectDetails([detail], options.effectiveIntent, Boolean(options.shouldFallback));
|
|
56
|
+
if (matchedCount === 0 && !isFallback) {
|
|
57
|
+
if (options.isMachine) {
|
|
58
|
+
renderResult({ root: detail.projectRoot, packages: [], matchedCount: 0, isFallback: false }, options);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
throw new ExecutionError(`No packages matched intent '${options.effectiveIntent}'`);
|
|
62
|
+
}
|
|
63
|
+
renderResult({
|
|
64
|
+
...projectDetailToJson(detail),
|
|
65
|
+
...(options.effectiveIntent ? { matchedCount, isFallback } : {}),
|
|
66
|
+
}, {
|
|
67
|
+
json: options.json,
|
|
68
|
+
humanFormatter: () => (isFallback ? `(No packages matched intent '${options.effectiveIntent}', showing selected package)\n\n` : "") +
|
|
69
|
+
renderProjectDetail(detail),
|
|
70
|
+
context: options.context,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 渲染复合技能整体输出(机器模式输出聚合结构,人类模式渲染各子包卡片)。
|
|
75
|
+
*/
|
|
76
|
+
function renderCompositeSkillOutput(root, details, options) {
|
|
77
|
+
const outOpts = { json: options.json, context: options.context };
|
|
78
|
+
const { items: filteredDetails, isFallback, matchedCount } = filterProjectDetails(details, options.effectiveIntent, Boolean(options.shouldFallback));
|
|
79
|
+
if (options.effectiveIntent && matchedCount === 0 && !isFallback) {
|
|
80
|
+
if (options.isMachine) {
|
|
81
|
+
renderResult({ root, packages: [], matchedCount: 0, isFallback: false }, outOpts);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
throw new ExecutionError(`No packages matched intent '${options.effectiveIntent}' in skill directory`);
|
|
85
|
+
}
|
|
86
|
+
if (options.isMachine) {
|
|
87
|
+
const machineResult = {
|
|
88
|
+
root,
|
|
89
|
+
packages: filteredDetails.map(projectDetailToJson),
|
|
90
|
+
...(options.effectiveIntent ? { matchedCount, isFallback } : {}),
|
|
91
|
+
};
|
|
92
|
+
renderResult(machineResult, outOpts);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
renderResult(filteredDetails, {
|
|
55
96
|
json: options.json,
|
|
56
|
-
humanFormatter: () =>
|
|
97
|
+
humanFormatter: () => renderCompositeSkillDetail(root, filteredDetails, isFallback, options.effectiveIntent),
|
|
57
98
|
context: options.context,
|
|
58
99
|
});
|
|
59
100
|
}
|
|
@@ -77,18 +118,25 @@ export function registerInfoCommand(program, context) {
|
|
|
77
118
|
.option("--data-dir <path>", "Custom database storage directory")
|
|
78
119
|
.action(async (patterns = [], rawOptions, cmd) => {
|
|
79
120
|
const options = getEffectiveOptions(rawOptions, cmd);
|
|
121
|
+
// 唯一显式路径位置参数属于寻址目标,必须先于远端与树状图分支裁决。
|
|
122
|
+
if (!options.package && !options.intent && patterns.length === 1 && isExplicitPath(patterns[0])) {
|
|
123
|
+
options.package = patterns[0];
|
|
124
|
+
patterns = [];
|
|
125
|
+
}
|
|
80
126
|
const effectiveIntent = resolveIntent(options.intent, patterns);
|
|
81
127
|
const { isMachine, shouldFallback } = resolveFallbackStrategy(options);
|
|
82
128
|
const outOpts = { json: options.json, context };
|
|
129
|
+
const detailOpts = { ...outOpts, effectiveIntent, shouldFallback, isMachine };
|
|
130
|
+
// info 仅查询单包清单,复合目录仍保留封闭集合校验。
|
|
131
|
+
const target = resolveCommandTarget(options, context, { metadataOnly: true });
|
|
83
132
|
// 远端服务目标分支
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const remoteInfo = await fetchRemoteInfo(target.serverUrl, target.token, {
|
|
133
|
+
if (target.isRemote) {
|
|
134
|
+
const remoteInfo = await fetchRemoteInfo(target.resolved.serverUrl, target.resolved.token, {
|
|
87
135
|
intent: effectiveIntent,
|
|
88
136
|
package: options.package,
|
|
89
137
|
tree: Boolean(options.tree),
|
|
90
138
|
allowInsecureHttp: Boolean(options.allowInsecureHttp),
|
|
91
|
-
insecure: target.insecure,
|
|
139
|
+
insecure: target.resolved.insecure,
|
|
92
140
|
});
|
|
93
141
|
if (isMachine) {
|
|
94
142
|
renderResult(remoteInfo, outOpts);
|
|
@@ -116,8 +164,11 @@ export function registerInfoCommand(program, context) {
|
|
|
116
164
|
});
|
|
117
165
|
return;
|
|
118
166
|
}
|
|
119
|
-
//
|
|
167
|
+
// 本地注册表树状图分支(若与显式技能或包组合,给出明确 ArgumentError)
|
|
120
168
|
if (options.tree) {
|
|
169
|
+
if (options.package || target.localSkill) {
|
|
170
|
+
throw new ArgumentError("Tree view (--tree) is not supported with explicit skill directories or packages; use 'ad info -P <path>' without --tree.");
|
|
171
|
+
}
|
|
121
172
|
const status = getRegistryStatus(context?.customHome);
|
|
122
173
|
renderResult(status, {
|
|
123
174
|
json: options.json,
|
|
@@ -126,12 +177,30 @@ export function registerInfoCommand(program, context) {
|
|
|
126
177
|
});
|
|
127
178
|
return;
|
|
128
179
|
}
|
|
129
|
-
//
|
|
180
|
+
// 显式复合技能分支
|
|
181
|
+
if (target.localSkill?.type === "composite") {
|
|
182
|
+
const details = await buildCompositeDetails(target.localSkill);
|
|
183
|
+
renderCompositeSkillOutput(target.localSkill.root, details, {
|
|
184
|
+
json: options.json,
|
|
185
|
+
context,
|
|
186
|
+
effectiveIntent,
|
|
187
|
+
shouldFallback,
|
|
188
|
+
isMachine,
|
|
189
|
+
});
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
// 显式单包技能分支
|
|
193
|
+
if (target.localSkill?.type === "single") {
|
|
194
|
+
const pkg = target.localSkill.packages[0];
|
|
195
|
+
const detail = await getProjectDetailInfo(pkg.packageRoot);
|
|
196
|
+
renderProjectDetailOutput(detail, detailOpts);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
// 显式指定 Package 参数分支(普通包 ID 或路径)
|
|
130
200
|
if (options.package) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
renderProjectDetailOutput(detail, outOpts);
|
|
201
|
+
if (target.targetPackageRoot) {
|
|
202
|
+
const detail = await getProjectDetailInfo(target.targetPackageRoot);
|
|
203
|
+
renderProjectDetailOutput(detail, detailOpts);
|
|
135
204
|
return;
|
|
136
205
|
}
|
|
137
206
|
throw packageNotFoundError(options.package);
|
|
@@ -163,8 +232,18 @@ export function registerInfoCommand(program, context) {
|
|
|
163
232
|
}
|
|
164
233
|
// 有关键字过滤场景:单一位置参数且非显式 --intent 时优先尝试包寻址
|
|
165
234
|
if (patterns.length === 1 && !options.intent) {
|
|
166
|
-
const directRoot = resolvePackageRoot(patterns[0]);
|
|
235
|
+
const directRoot = resolvePackageRoot(patterns[0], undefined, context?.customHome);
|
|
167
236
|
if (directRoot) {
|
|
237
|
+
const directTarget = resolveCommandTarget({ ...options, package: directRoot }, context, { metadataOnly: true });
|
|
238
|
+
if (directTarget.localSkill?.type === "composite") {
|
|
239
|
+
const details = await buildCompositeDetails(directTarget.localSkill);
|
|
240
|
+
renderCompositeSkillOutput(directTarget.localSkill.root, details, {
|
|
241
|
+
...outOpts,
|
|
242
|
+
shouldFallback,
|
|
243
|
+
isMachine,
|
|
244
|
+
});
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
168
247
|
const detail = await getProjectDetailInfo(directRoot);
|
|
169
248
|
renderProjectDetailOutput(detail, outOpts);
|
|
170
249
|
return;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
3
|
-
import { assertPathWithinRoot,
|
|
4
|
-
import { DefaultActionCatalog,
|
|
3
|
+
import { assertPathWithinRoot, loadManifest, loadPlaybooks, saveManifest, filterWithFallbackInfo, } from "@actiondock/core/project";
|
|
4
|
+
import { DefaultActionCatalog, resolveAction, resolvePlaybook, } from "@actiondock/core/graph";
|
|
5
5
|
import { fetchRemotePlaybooks, fetchRemotePlaybookShow, } from "@actiondock/core/profile";
|
|
6
6
|
import { loadProjectConfig, } from "@actiondock/core";
|
|
7
7
|
import { listLinkedPackages, } from "@actiondock/core/registry";
|
|
8
8
|
import { ArgumentError, CliError, ExecutionError, NO_PROJECT_NO_LINKED_MESSAGE, wrapAsExecutionError, } from "../errors.js";
|
|
9
|
-
import { renderPlaybookDetail, renderPlaybookList, renderResult, writeStdout, } from "../renderer.js";
|
|
10
|
-
import { applyTargetOptions, getEffectiveOptions, remoteTargetSuffix, requirePackageRoot, resolveFallbackStrategy, resolveIntent,
|
|
9
|
+
import { renderPlaybookDetail, renderPlaybookList, renderResult, writeStderr, writeStdout, } from "../renderer.js";
|
|
10
|
+
import { applyTargetOptions, buildScopedPackageGraph, getEffectiveOptions, remoteTargetSuffix, requirePackageRoot, resolveCommandTarget, resolveFallbackStrategy, resolveIntent, } from "../utils/index.js";
|
|
11
11
|
/**
|
|
12
12
|
* 注册 Playbook 命令集合(list, show, validate, create)。
|
|
13
13
|
*
|
|
@@ -34,14 +34,15 @@ export function registerPlaybookCommands(program, context) {
|
|
|
34
34
|
const hints = [
|
|
35
35
|
"Tip: Run 'ad playbook show <id>' to inspect procedure steps before execution.",
|
|
36
36
|
];
|
|
37
|
-
// 1.
|
|
38
|
-
const target =
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
// 1. 目标拓扑统一解析
|
|
38
|
+
const target = resolveCommandTarget(options, context);
|
|
39
|
+
// 2. 远端服务分支
|
|
40
|
+
if (target.isRemote) {
|
|
41
|
+
const remotePbs = await fetchRemotePlaybooks(target.resolved.serverUrl, target.resolved.token, {
|
|
41
42
|
intent: effectiveIntent,
|
|
42
43
|
package: options.package,
|
|
43
44
|
allowInsecureHttp: Boolean(options.allowInsecureHttp),
|
|
44
|
-
insecure: target.insecure,
|
|
45
|
+
insecure: target.resolved.insecure,
|
|
45
46
|
});
|
|
46
47
|
if (isMachine) {
|
|
47
48
|
renderResult({ items: remotePbs, hints }, { json: options.json, context });
|
|
@@ -49,24 +50,44 @@ export function registerPlaybookCommands(program, context) {
|
|
|
49
50
|
}
|
|
50
51
|
renderResult(remotePbs, {
|
|
51
52
|
json: options.json,
|
|
52
|
-
humanFormatter: () => renderPlaybookList(remotePbs, `Playbooks ${remoteTargetSuffix(target)}`, false, effectiveIntent),
|
|
53
|
+
humanFormatter: () => renderPlaybookList(remotePbs, `Playbooks ${remoteTargetSuffix(target.resolved)}`, false, effectiveIntent),
|
|
53
54
|
context,
|
|
54
55
|
});
|
|
55
56
|
return;
|
|
56
57
|
}
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
58
|
+
// 3. 本地模式(单包技能、复合技能或本地工程)
|
|
59
|
+
if (target.localSkill || target.targetPackageRoot) {
|
|
60
|
+
const rawList = [];
|
|
61
|
+
let title;
|
|
62
|
+
if (target.localSkill) {
|
|
63
|
+
const isComposite = target.localSkill.type === "composite";
|
|
64
|
+
for (const pkg of target.localSkill.packages) {
|
|
65
|
+
const playbooks = loadPlaybooks(pkg.packageRoot, pkg.projectConfig.playbooksDir, pkg.projectConfig);
|
|
66
|
+
for (const pb of playbooks.values()) {
|
|
67
|
+
rawList.push({
|
|
68
|
+
id: isComposite ? `${pkg.packageId}/${pb.id}` : pb.id,
|
|
69
|
+
description: pb.description,
|
|
70
|
+
packageId: pkg.packageId,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
title = isComposite
|
|
75
|
+
? `Playbooks in ${options.package || target.localSkill.root}`
|
|
76
|
+
: `Playbooks in ${target.localSkill.packages[0].packageId} (${target.localSkill.packages[0].packageRoot})`;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
const config = loadProjectConfig(target.targetPackageRoot);
|
|
80
|
+
const playbooks = loadPlaybooks(target.targetPackageRoot, config.playbooksDir, config);
|
|
81
|
+
for (const pb of playbooks.values()) {
|
|
82
|
+
rawList.push({
|
|
83
|
+
id: pb.id,
|
|
84
|
+
description: pb.description,
|
|
85
|
+
packageId: config.id,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
title = `Playbooks in ${config.id} (${target.targetPackageRoot})`;
|
|
89
|
+
}
|
|
90
|
+
const filterRes = filterWithFallbackInfo(rawList, effectiveIntent, [(p) => p.id, (p) => p.description || ""], shouldFallback);
|
|
70
91
|
if (isMachine) {
|
|
71
92
|
if (filterRes.isFallback) {
|
|
72
93
|
renderResult({ items: filterRes.items, isFallback: true, matchedCount: 0, hints }, { json: options.json, context });
|
|
@@ -81,7 +102,7 @@ export function registerPlaybookCommands(program, context) {
|
|
|
81
102
|
}
|
|
82
103
|
renderResult(filterRes.items, {
|
|
83
104
|
json: options.json,
|
|
84
|
-
humanFormatter: () => renderPlaybookList(filterRes.items,
|
|
105
|
+
humanFormatter: () => renderPlaybookList(filterRes.items, title, filterRes.isFallback, effectiveIntent),
|
|
85
106
|
context,
|
|
86
107
|
});
|
|
87
108
|
return;
|
|
@@ -102,25 +123,22 @@ export function registerPlaybookCommands(program, context) {
|
|
|
102
123
|
}
|
|
103
124
|
const aggregated = [];
|
|
104
125
|
for (const pkg of linkedList) {
|
|
105
|
-
if (!existsSync(pkg.path))
|
|
126
|
+
if (!existsSync(pkg.path)) {
|
|
127
|
+
writeStderr(`Skipping inaccessible linked package '${pkg.id}' at '${pkg.path}'`, context);
|
|
106
128
|
continue;
|
|
107
|
-
try {
|
|
108
|
-
const config = loadProjectConfig(pkg.path);
|
|
109
|
-
const pbs = loadPlaybooks(pkg.path, config.playbooksDir);
|
|
110
|
-
const list = Array.from(pbs.values()).map((p) => ({
|
|
111
|
-
id: p.id,
|
|
112
|
-
description: p.description,
|
|
113
|
-
}));
|
|
114
|
-
aggregated.push({
|
|
115
|
-
packageId: pkg.id,
|
|
116
|
-
packageName: pkg.name,
|
|
117
|
-
path: pkg.path,
|
|
118
|
-
playbooks: list,
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
catch {
|
|
122
|
-
// 单个链接包清单损坏或 Playbook 加载失败时跳过,继续聚合其余包
|
|
123
129
|
}
|
|
130
|
+
const config = loadProjectConfig(pkg.path);
|
|
131
|
+
const pbs = loadPlaybooks(pkg.path, config.playbooksDir, config);
|
|
132
|
+
const list = Array.from(pbs.values()).map((p) => ({
|
|
133
|
+
id: p.id,
|
|
134
|
+
description: p.description,
|
|
135
|
+
}));
|
|
136
|
+
aggregated.push({
|
|
137
|
+
packageId: pkg.id,
|
|
138
|
+
packageName: pkg.name,
|
|
139
|
+
path: pkg.path,
|
|
140
|
+
playbooks: list,
|
|
141
|
+
});
|
|
124
142
|
}
|
|
125
143
|
let filteredPackages = [];
|
|
126
144
|
let isFallback = false;
|
|
@@ -183,13 +201,14 @@ export function registerPlaybookCommands(program, context) {
|
|
|
183
201
|
if (!id) {
|
|
184
202
|
throw new ArgumentError("Playbook ID is required for show");
|
|
185
203
|
}
|
|
186
|
-
// 1.
|
|
187
|
-
const target =
|
|
188
|
-
|
|
204
|
+
// 1. 目标拓扑统一解析
|
|
205
|
+
const target = resolveCommandTarget(options, context);
|
|
206
|
+
// 2. 远端服务分支
|
|
207
|
+
if (target.isRemote) {
|
|
189
208
|
try {
|
|
190
|
-
const detail = await fetchRemotePlaybookShow(target.serverUrl, id, target.token, {
|
|
209
|
+
const detail = await fetchRemotePlaybookShow(target.resolved.serverUrl, target.resolvePlaybookRef(id), target.resolved.token, {
|
|
191
210
|
allowInsecureHttp: Boolean(options.allowInsecureHttp),
|
|
192
|
-
insecure: target.insecure,
|
|
211
|
+
insecure: target.resolved.insecure,
|
|
193
212
|
});
|
|
194
213
|
renderResult(detail, {
|
|
195
214
|
json: options.json,
|
|
@@ -203,20 +222,15 @@ export function registerPlaybookCommands(program, context) {
|
|
|
203
222
|
throw new ArgumentError(`${err.message}\n${hint}`, err?.details, err?.code, hint);
|
|
204
223
|
}
|
|
205
224
|
}
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
if (options.package && !id.includes("/")) {
|
|
209
|
-
requirePackageRoot(options.package);
|
|
210
|
-
showTarget = `${options.package}/${id}`;
|
|
211
|
-
}
|
|
225
|
+
// 3. 本地分支(单包技能、复合技能、本地工程或已链接包)
|
|
226
|
+
const showTarget = target.resolvePlaybookRef(id);
|
|
212
227
|
let resolved;
|
|
213
228
|
try {
|
|
214
|
-
const graph =
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
})
|
|
219
|
-
resolved = resolvePlaybook(showTarget, { graph });
|
|
229
|
+
const graph = buildScopedPackageGraph(target, context);
|
|
230
|
+
const caller = target.localSkill
|
|
231
|
+
? (target.localSkill.type === "single" ? target.localSkill.packages[0].packageId : undefined)
|
|
232
|
+
: (target.targetPackageRoot ? loadProjectConfig(target.targetPackageRoot).id : undefined);
|
|
233
|
+
resolved = resolvePlaybook(showTarget, { graph, caller });
|
|
220
234
|
}
|
|
221
235
|
catch (err) {
|
|
222
236
|
const hint = "Tip: Run 'ad playbook list' to discover available playbooks.";
|
|
@@ -249,36 +263,73 @@ export function registerPlaybookCommands(program, context) {
|
|
|
249
263
|
.option("--json", "Output as JSON")
|
|
250
264
|
.action(async (id, rawOptions, cmd) => {
|
|
251
265
|
const options = getEffectiveOptions(rawOptions, cmd);
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
266
|
+
// 保持本地校验定位,profile 始终为 local
|
|
267
|
+
const target = resolveCommandTarget({ ...options, profile: "local" }, context);
|
|
268
|
+
const graph = buildScopedPackageGraph(target, context);
|
|
269
|
+
const catalog = new DefaultActionCatalog(graph);
|
|
255
270
|
const targets = [];
|
|
256
|
-
if (
|
|
271
|
+
if (target.localSkill) {
|
|
272
|
+
if (id) {
|
|
273
|
+
const showTarget = target.resolvePlaybookRef(id);
|
|
274
|
+
const caller = target.localSkill.type === "single" ? target.localSkill.packages[0].packageId : undefined;
|
|
275
|
+
let resolved;
|
|
276
|
+
try {
|
|
277
|
+
resolved = resolvePlaybook(showTarget, { graph, caller });
|
|
278
|
+
}
|
|
279
|
+
catch (err) {
|
|
280
|
+
throw new ArgumentError(err.message, err?.details, err?.code);
|
|
281
|
+
}
|
|
282
|
+
targets.push({
|
|
283
|
+
root: resolved.projectRoot,
|
|
284
|
+
packageId: resolved.packageId,
|
|
285
|
+
playbooks: [resolved.playbook],
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
for (const pkg of target.localSkill.packages) {
|
|
290
|
+
const playbooks = loadPlaybooks(pkg.packageRoot, pkg.projectConfig.playbooksDir);
|
|
291
|
+
targets.push({
|
|
292
|
+
root: pkg.packageRoot,
|
|
293
|
+
packageId: pkg.packageId,
|
|
294
|
+
playbooks: Array.from(playbooks.values()),
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
else if (target.targetPackageRoot) {
|
|
300
|
+
const root = target.targetPackageRoot;
|
|
257
301
|
const config = loadProjectConfig(root);
|
|
258
|
-
const playbooks = loadPlaybooks(root, config.playbooksDir);
|
|
259
302
|
if (id) {
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
303
|
+
const showTarget = target.resolvePlaybookRef(id);
|
|
304
|
+
let resolved;
|
|
305
|
+
try {
|
|
306
|
+
resolved = resolvePlaybook(showTarget, { graph, caller: config.id });
|
|
263
307
|
}
|
|
264
|
-
|
|
308
|
+
catch (err) {
|
|
309
|
+
throw new ArgumentError(err.message, err?.details, err?.code);
|
|
310
|
+
}
|
|
311
|
+
targets.push({
|
|
312
|
+
root: resolved.projectRoot,
|
|
313
|
+
packageId: resolved.packageId,
|
|
314
|
+
playbooks: [resolved.playbook],
|
|
315
|
+
});
|
|
265
316
|
}
|
|
266
317
|
else {
|
|
267
|
-
|
|
318
|
+
const playbooks = loadPlaybooks(root, config.playbooksDir);
|
|
319
|
+
targets.push({
|
|
320
|
+
root,
|
|
321
|
+
packageId: config.id,
|
|
322
|
+
playbooks: Array.from(playbooks.values()),
|
|
323
|
+
});
|
|
268
324
|
}
|
|
269
325
|
}
|
|
270
326
|
else if (id) {
|
|
271
327
|
let resolved;
|
|
272
328
|
try {
|
|
273
|
-
const graph = new PackageGraphBuilder({
|
|
274
|
-
root: resolveLocalPackageRoot() || undefined,
|
|
275
|
-
customHome: context?.customHome,
|
|
276
|
-
allowDevLinks: true,
|
|
277
|
-
}).buildSync();
|
|
278
329
|
resolved = resolvePlaybook(id, { graph });
|
|
279
330
|
}
|
|
280
331
|
catch (err) {
|
|
281
|
-
throw new ArgumentError(err.message);
|
|
332
|
+
throw new ArgumentError(err.message, err?.details, err?.code);
|
|
282
333
|
}
|
|
283
334
|
targets.push({
|
|
284
335
|
root: resolved.projectRoot,
|
|
@@ -292,34 +343,22 @@ export function registerPlaybookCommands(program, context) {
|
|
|
292
343
|
throw new ArgumentError(`${NO_PROJECT_NO_LINKED_MESSAGE} Please run 'ad link' inside a package first.`);
|
|
293
344
|
}
|
|
294
345
|
for (const pkg of linkedList) {
|
|
295
|
-
if (!existsSync(pkg.path))
|
|
346
|
+
if (!existsSync(pkg.path)) {
|
|
347
|
+
writeStderr(`Skipping inaccessible linked package '${pkg.id}' at '${pkg.path}'`, context);
|
|
296
348
|
continue;
|
|
297
|
-
try {
|
|
298
|
-
const config = loadProjectConfig(pkg.path);
|
|
299
|
-
const playbooks = loadPlaybooks(pkg.path, config.playbooksDir);
|
|
300
|
-
targets.push({
|
|
301
|
-
root: pkg.path,
|
|
302
|
-
packageId: pkg.id,
|
|
303
|
-
playbooks: Array.from(playbooks.values()),
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
catch {
|
|
307
|
-
// 单个链接包清单损坏或 Playbook 加载失败时跳过,不阻断整体校验
|
|
308
349
|
}
|
|
350
|
+
const config = loadProjectConfig(pkg.path);
|
|
351
|
+
const playbooks = loadPlaybooks(pkg.path, config.playbooksDir, config);
|
|
352
|
+
targets.push({
|
|
353
|
+
root: pkg.path,
|
|
354
|
+
packageId: pkg.id,
|
|
355
|
+
playbooks: Array.from(playbooks.values()),
|
|
356
|
+
});
|
|
309
357
|
}
|
|
310
358
|
}
|
|
311
359
|
const results = [];
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
customHome: context?.customHome,
|
|
315
|
-
allowDevLinks: true,
|
|
316
|
-
}).buildSync();
|
|
317
|
-
const catalog = new DefaultActionCatalog(graph);
|
|
318
|
-
for (const target of targets) {
|
|
319
|
-
const config = loadProjectConfig(target.root);
|
|
320
|
-
const manifest = loadManifest(target.root);
|
|
321
|
-
const actions = await loadActions(target.root, config.actionsDir);
|
|
322
|
-
for (const pb of target.playbooks) {
|
|
360
|
+
for (const targetItem of targets) {
|
|
361
|
+
for (const pb of targetItem.playbooks) {
|
|
323
362
|
const warnings = [];
|
|
324
363
|
const errors = [];
|
|
325
364
|
if (!pb.id) {
|
|
@@ -330,26 +369,30 @@ export function registerPlaybookCommands(program, context) {
|
|
|
330
369
|
}
|
|
331
370
|
if (pb.actions && Array.isArray(pb.actions)) {
|
|
332
371
|
for (const actRef of pb.actions) {
|
|
333
|
-
if (actRef.
|
|
372
|
+
if (typeof actRef !== "string" || !actRef.trim()) {
|
|
373
|
+
errors.push(`Referenced action must be a non-empty string, received ${JSON.stringify(actRef)}`);
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
const trimmed = actRef.trim();
|
|
377
|
+
if (trimmed.includes("/")) {
|
|
334
378
|
try {
|
|
335
|
-
resolveAction(
|
|
379
|
+
resolveAction(trimmed, { graph, catalog, caller: targetItem.packageId });
|
|
336
380
|
}
|
|
337
381
|
catch (e) {
|
|
338
|
-
errors.push(`Referenced cross-package action '${
|
|
382
|
+
errors.push(`Referenced cross-package action '${trimmed}' not resolvable: ${e.message}. Hint: Action '${trimmed}' not found. Run 'ad action create ${trimmed}' to create local action, or 'ad add <pkg>' to install external dependency.`);
|
|
339
383
|
}
|
|
340
384
|
}
|
|
341
385
|
else {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
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.`);
|
|
386
|
+
// 短引用只接受所属包清单登记的 Action,不能由源码文件或其他包补位。
|
|
387
|
+
if (!catalog.get(targetItem.packageId, trimmed)) {
|
|
388
|
+
errors.push(`Referenced action '${trimmed}' not found in project actions. Hint: Action '${trimmed}' not found. Run 'ad action create ${trimmed}' to create local action, or 'ad add <pkg>' to install external dependency.`);
|
|
346
389
|
}
|
|
347
390
|
}
|
|
348
391
|
}
|
|
349
392
|
}
|
|
350
393
|
results.push({
|
|
351
394
|
id: pb.id,
|
|
352
|
-
packageId:
|
|
395
|
+
packageId: targetItem.packageId,
|
|
353
396
|
valid: errors.length === 0,
|
|
354
397
|
warnings,
|
|
355
398
|
errors,
|
|
@@ -43,3 +43,7 @@ export type RegistryTreeView = RegistryStatusReport | (Record<string, unknown> &
|
|
|
43
43
|
* 格式化渲染注册表层级树结构。
|
|
44
44
|
*/
|
|
45
45
|
export declare function renderRegistryTree(status: RegistryTreeView): string;
|
|
46
|
+
/**
|
|
47
|
+
* 格式化渲染复合技能各子包的自省卡片列表。
|
|
48
|
+
*/
|
|
49
|
+
export declare function renderCompositeSkillDetail(root: string, filteredDetails: ProjectDetailInfo[], isFallback?: boolean, effectiveIntent?: string): string;
|
|
@@ -194,3 +194,19 @@ export function renderRegistryTree(status) {
|
|
|
194
194
|
}
|
|
195
195
|
return lines.join("\n");
|
|
196
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* 格式化渲染复合技能各子包的自省卡片列表。
|
|
199
|
+
*/
|
|
200
|
+
export function renderCompositeSkillDetail(root, filteredDetails, isFallback, effectiveIntent) {
|
|
201
|
+
const lines = [];
|
|
202
|
+
if (isFallback) {
|
|
203
|
+
lines.push(`(No packages in skill matched intent '${effectiveIntent}', showing all packages)\n`);
|
|
204
|
+
}
|
|
205
|
+
lines.push(`ActionDock Composite Skill: ${root}`);
|
|
206
|
+
lines.push(`Packages (${filteredDetails.length}):\n`);
|
|
207
|
+
for (const d of filteredDetails) {
|
|
208
|
+
lines.push(renderProjectDetail(d));
|
|
209
|
+
lines.push("");
|
|
210
|
+
}
|
|
211
|
+
return lines.join("\n").trimEnd();
|
|
212
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type LocalSkillDirectory } from "@actiondock/core/project";
|
|
1
2
|
import type { AggregatedPackage, CliContext, ProjectDetailInfo } from "../types.js";
|
|
2
3
|
/**
|
|
3
4
|
* 装配单个工程的详情自省信息(清单、动作映射与 Playbook 映射)。
|
|
@@ -5,6 +6,18 @@ import type { AggregatedPackage, CliContext, ProjectDetailInfo } from "../types.
|
|
|
5
6
|
* @param root 工程根目录
|
|
6
7
|
*/
|
|
7
8
|
export declare function getProjectDetailInfo(root: string): Promise<ProjectDetailInfo>;
|
|
9
|
+
/**
|
|
10
|
+
* 装配复合技能各子包的自省详情,将 Action 与 Playbook 的调用索引提升为完全限定 ID。
|
|
11
|
+
*/
|
|
12
|
+
export declare function buildCompositeDetails(localSkill: LocalSkillDirectory): Promise<ProjectDetailInfo[]>;
|
|
13
|
+
/**
|
|
14
|
+
* 单包与复合技能共享相同的详情过滤规则(单一事实源)。
|
|
15
|
+
*/
|
|
16
|
+
export declare function filterProjectDetails(details: ProjectDetailInfo[], effectiveIntent?: string, shouldFallback?: boolean): {
|
|
17
|
+
items: ProjectDetailInfo[];
|
|
18
|
+
matchedCount: number;
|
|
19
|
+
isFallback: boolean;
|
|
20
|
+
};
|
|
8
21
|
/**
|
|
9
22
|
* 装配单个包的摘要聚合信息(不含动作与 Playbook 明细映射)。
|
|
10
23
|
*
|
|
@@ -13,7 +26,7 @@ export declare function getProjectDetailInfo(root: string): Promise<ProjectDetai
|
|
|
13
26
|
export declare function getPackageSummary(root: string): AggregatedPackage;
|
|
14
27
|
/**
|
|
15
28
|
* 扫描本地候选包集合(当前工程根目录优先,叠加全局已链接包,按物理路径去重)。
|
|
16
|
-
*
|
|
29
|
+
* 失效或损坏链接给出明确诊断记录,不静默吞没也不中断其余条目聚合。
|
|
17
30
|
*
|
|
18
31
|
* @param context CLI 上下文(提供 customHome 定位全局注册表)
|
|
19
32
|
*/
|
package/dist/services/inspect.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { findProjectRoot, loadProjectConfig, } from "@actiondock/core";
|
|
3
3
|
import { listLinkedPackages, } from "@actiondock/core/registry";
|
|
4
|
-
import { loadManifest, loadPlaybooks, } from "@actiondock/core/project";
|
|
4
|
+
import { filterWithFallbackInfo, loadManifest, loadPlaybooks, } from "@actiondock/core/project";
|
|
5
5
|
/**
|
|
6
6
|
* 装配单个工程的详情自省信息(清单、动作映射与 Playbook 映射)。
|
|
7
7
|
*
|
|
@@ -38,6 +38,51 @@ export async function getProjectDetailInfo(root) {
|
|
|
38
38
|
playbooksMap: playbooks,
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* 装配复合技能各子包的自省详情,将 Action 与 Playbook 的调用索引提升为完全限定 ID。
|
|
43
|
+
*/
|
|
44
|
+
export async function buildCompositeDetails(localSkill) {
|
|
45
|
+
const details = [];
|
|
46
|
+
for (const pkg of localSkill.packages) {
|
|
47
|
+
const detail = await getProjectDetailInfo(pkg.packageRoot);
|
|
48
|
+
const qualifiedActionsMap = new Map();
|
|
49
|
+
if (detail.actionsMap) {
|
|
50
|
+
for (const [actId, act] of detail.actionsMap.entries()) {
|
|
51
|
+
const fullId = `${pkg.packageId}/${actId}`;
|
|
52
|
+
qualifiedActionsMap.set(fullId, { id: fullId, description: act.description });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const qualifiedPlaybooksMap = new Map();
|
|
56
|
+
if (detail.playbooksMap) {
|
|
57
|
+
for (const [pbId, pb] of detail.playbooksMap.entries()) {
|
|
58
|
+
const fullId = `${pkg.packageId}/${pbId}`;
|
|
59
|
+
qualifiedPlaybooksMap.set(fullId, { ...pb, id: fullId });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
detail.actionsMap = qualifiedActionsMap;
|
|
63
|
+
detail.actions = Array.from(qualifiedActionsMap.keys());
|
|
64
|
+
detail.playbooksMap = qualifiedPlaybooksMap;
|
|
65
|
+
detail.playbooks = Array.from(qualifiedPlaybooksMap.keys());
|
|
66
|
+
details.push(detail);
|
|
67
|
+
}
|
|
68
|
+
return details;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 单包与复合技能共享相同的详情过滤规则(单一事实源)。
|
|
72
|
+
*/
|
|
73
|
+
export function filterProjectDetails(details, effectiveIntent, shouldFallback = false) {
|
|
74
|
+
if (!effectiveIntent) {
|
|
75
|
+
return { items: details, matchedCount: details.length, isFallback: false };
|
|
76
|
+
}
|
|
77
|
+
return filterWithFallbackInfo(details, effectiveIntent, [
|
|
78
|
+
(d) => d.id,
|
|
79
|
+
(d) => d.name,
|
|
80
|
+
(d) => d.description || "",
|
|
81
|
+
(d) => d.projectRoot,
|
|
82
|
+
(d) => d.actions.join(" "),
|
|
83
|
+
(d) => d.playbooks.join(" "),
|
|
84
|
+
], Boolean(shouldFallback));
|
|
85
|
+
}
|
|
41
86
|
/**
|
|
42
87
|
* 装配单个包的摘要聚合信息(不含动作与 Playbook 明细映射)。
|
|
43
88
|
*
|
|
@@ -63,7 +108,7 @@ export function getPackageSummary(root) {
|
|
|
63
108
|
}
|
|
64
109
|
/**
|
|
65
110
|
* 扫描本地候选包集合(当前工程根目录优先,叠加全局已链接包,按物理路径去重)。
|
|
66
|
-
*
|
|
111
|
+
* 失效或损坏链接给出明确诊断记录,不静默吞没也不中断其余条目聚合。
|
|
67
112
|
*
|
|
68
113
|
* @param context CLI 上下文(提供 customHome 定位全局注册表)
|
|
69
114
|
*/
|
|
@@ -73,25 +118,22 @@ export function scanLocalAggregatedPackages(context) {
|
|
|
73
118
|
const aggregated = [];
|
|
74
119
|
const seenPaths = new Set();
|
|
75
120
|
if (currentRoot) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
seenPaths.add(currentRoot);
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
// 忽略异常工程根目录(清单损坏)
|
|
82
|
-
}
|
|
121
|
+
aggregated.push(getPackageSummary(currentRoot));
|
|
122
|
+
seenPaths.add(currentRoot);
|
|
83
123
|
}
|
|
84
124
|
for (const pkg of linkedList) {
|
|
85
|
-
if (!existsSync(pkg.path))
|
|
125
|
+
if (!existsSync(pkg.path)) {
|
|
126
|
+
process.stderr.write(`[actiondock] Diagnostic: Skipping inaccessible linked package '${pkg.id}' at '${pkg.path}'\n`);
|
|
86
127
|
continue;
|
|
128
|
+
}
|
|
87
129
|
if (seenPaths.has(pkg.path))
|
|
88
130
|
continue;
|
|
89
131
|
try {
|
|
90
132
|
aggregated.push(getPackageSummary(pkg.path));
|
|
91
133
|
seenPaths.add(pkg.path);
|
|
92
134
|
}
|
|
93
|
-
catch {
|
|
94
|
-
|
|
135
|
+
catch (err) {
|
|
136
|
+
process.stderr.write(`[actiondock] Diagnostic: Skipping invalid linked package '${pkg.id}' at '${pkg.path}': ${err?.message || String(err)}\n`);
|
|
95
137
|
}
|
|
96
138
|
}
|
|
97
139
|
return { currentRoot, aggregated };
|
package/dist/utils/target.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { loadProjectConfig, type ActionDockService, type LocalSkillDirectory } from "@actiondock/core";
|
|
2
2
|
import { type ResolvedTarget } from "@actiondock/core/profile";
|
|
3
|
+
import { type PackageGraph } from "@actiondock/core/graph";
|
|
3
4
|
import type { Command } from "commander";
|
|
4
5
|
import type { CliContext } from "../types.js";
|
|
5
6
|
/**
|
|
@@ -30,6 +31,10 @@ export interface TargetResolutionOptions {
|
|
|
30
31
|
/** 是否允许向非回环地址发送明文 HTTP 请求(对应 --allow-insecure-http) */
|
|
31
32
|
allowInsecureHttp?: boolean;
|
|
32
33
|
}
|
|
34
|
+
/** 仅供清单查询使用的内部解析策略;执行命令默认保持封闭校验。 */
|
|
35
|
+
export interface TargetResolutionStrategy {
|
|
36
|
+
readonly metadataOnly?: boolean;
|
|
37
|
+
}
|
|
33
38
|
/**
|
|
34
39
|
* 目标拓扑解析单一入口:从命令选项视图解析远端/本地目标拓扑信息。
|
|
35
40
|
*
|
|
@@ -44,9 +49,37 @@ export interface TargetResolutionOptions {
|
|
|
44
49
|
*
|
|
45
50
|
* @param options 命令选项视图(支持直接传入 Commander 解析后的 options 对象)
|
|
46
51
|
* @param context CLI 上下文
|
|
52
|
+
* @param strategy 内部解析策略;仅清单查询可跳过单包闭包校验
|
|
47
53
|
* @returns 解析后的目标拓扑信息(仅信息解析,不创建 Service 实例)
|
|
48
54
|
*/
|
|
49
|
-
export declare function resolveTargetFromOptions(options: TargetResolutionOptions, context?: CliContext): ResolvedTarget;
|
|
55
|
+
export declare function resolveTargetFromOptions(options: TargetResolutionOptions, context?: CliContext, strategy?: TargetResolutionStrategy): ResolvedTarget;
|
|
56
|
+
/**
|
|
57
|
+
* 统一计算目标资源引用(Action 或 Playbook)的辅助函数(单一事实源)。
|
|
58
|
+
*
|
|
59
|
+
* 规则:
|
|
60
|
+
* - 远端分支:options.package 存在且资源标识未限定时,前缀拼接 options.package;
|
|
61
|
+
* - 本地单包技能:资源标识未限定时,前缀拼接真实 packageId(而非路径目录名);
|
|
62
|
+
* - 本地复合技能:保持短名,供后续图解析裁决唯一匹配或多包歧义;完全限定名直接透传;
|
|
63
|
+
* - 普通工程包:非技能且 options.package 存在时,未限定则拼接 options.package。
|
|
64
|
+
*
|
|
65
|
+
* @param resourceId 资源标识符(动作名或规程名)
|
|
66
|
+
* @param options 命令选项视图
|
|
67
|
+
* @param localSkill 本地技能目录视图
|
|
68
|
+
*/
|
|
69
|
+
export declare function resolveResourceRef(resourceId: string, options: TargetResolutionOptions, localSkill?: LocalSkillDirectory | null): string;
|
|
70
|
+
/**
|
|
71
|
+
* 统一计算目标 Playbook 引用的辅助函数。
|
|
72
|
+
*/
|
|
73
|
+
export declare function resolvePlaybookRef(playbookId: string, options: TargetResolutionOptions, localSkill?: LocalSkillDirectory | null): string;
|
|
74
|
+
/**
|
|
75
|
+
* 构造与目标作用域严格对应的 PackageGraph(单一事实源)。
|
|
76
|
+
*
|
|
77
|
+
* 封闭保证:
|
|
78
|
+
* - 当目标为 LocalSkillDirectory 时,仅以 localSkill.packages 显式包集合构建封闭图;
|
|
79
|
+
* 绝不扫描 cwd 也不扫描全局 Registry/开发软链,防止外部同名包或污染泄漏;
|
|
80
|
+
* - 当目标为普通本地工程或包 ID 时,维持原有的基于项目根与全局链接表的拓扑图构建模式。
|
|
81
|
+
*/
|
|
82
|
+
export declare function buildScopedPackageGraph(target: CommandTargetResolution, context?: CliContext): PackageGraph;
|
|
50
83
|
/**
|
|
51
84
|
* 命令目标与动作引用解析结果。
|
|
52
85
|
*/
|
|
@@ -55,12 +88,14 @@ export interface CommandTargetResolution {
|
|
|
55
88
|
resolved: ResolvedTarget;
|
|
56
89
|
/** 是否为远端目标 */
|
|
57
90
|
isRemote: boolean;
|
|
58
|
-
/**
|
|
91
|
+
/** 本地技能目录或单包元数据作用域(未寻址本地包时为 null) */
|
|
59
92
|
localSkill: LocalSkillDirectory | null;
|
|
60
93
|
/** 本地包根目录路径(远端分支为 undefined,未找到为 undefined) */
|
|
61
94
|
targetPackageRoot?: string;
|
|
62
95
|
/** 统一计算目标 Action 引用的辅助函数 */
|
|
63
96
|
resolveActionRef: (actionId: string) => string;
|
|
97
|
+
/** 统一计算目标 Playbook 引用的辅助函数 */
|
|
98
|
+
resolvePlaybookRef: (playbookId: string) => string;
|
|
64
99
|
}
|
|
65
100
|
/**
|
|
66
101
|
* 统一解析 CLI 命令的目标拓扑与包作用域(run, describe, list 等命令的单一事实源)。
|
|
@@ -68,10 +103,11 @@ export interface CommandTargetResolution {
|
|
|
68
103
|
* 保证:
|
|
69
104
|
* - 优先判定目标拓扑;
|
|
70
105
|
* - 远端目标(remote)完全隔离:不执行任何本地目录探测、清单加载或包根寻址;
|
|
71
|
-
* -
|
|
72
|
-
* -
|
|
106
|
+
* - 远端目标动作与规程引用保持原始词法包 ID 拼接(${options.package}/${id}),绝不被本地同名目录或清单 ID 篡改;
|
|
107
|
+
* - 本地目标默认按封闭技能目录或工程包严格解析;元数据策略仅跳过单包闭包校验;
|
|
108
|
+
* - 显式目标不存在时透明抛出 packageNotFoundError。
|
|
73
109
|
*/
|
|
74
|
-
export declare function resolveCommandTarget(options: TargetResolutionOptions, context?: CliContext): CommandTargetResolution;
|
|
110
|
+
export declare function resolveCommandTarget(options: TargetResolutionOptions, context?: CliContext, strategy?: TargetResolutionStrategy): CommandTargetResolution;
|
|
75
111
|
/**
|
|
76
112
|
* 配置与状态管理目标作用域。
|
|
77
113
|
*/
|
package/dist/utils/target.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { connectActionDock, createActionDock, createNodePlatform, findProjectRoot, inspectLocalPackageDirectory, isExplicitPath, loadProjectConfig, } from "@actiondock/core";
|
|
1
|
+
import { connectActionDock, createActionDock, createNodePlatform, findProjectRoot, inspectLocalPackageDirectory, isExplicitPath, loadProjectConfig, queryLocalPackageDirectory, } from "@actiondock/core";
|
|
2
2
|
import { resolvePackageRoot, } from "@actiondock/core/registry";
|
|
3
3
|
import { resolveTarget, } from "@actiondock/core/profile";
|
|
4
|
+
import { PackageGraphBuilder, } from "@actiondock/core/graph";
|
|
4
5
|
import { ArgumentError, packageNotFoundError } from "../errors.js";
|
|
5
6
|
/**
|
|
6
7
|
* 统一为 Commander 命令节点挂载目标环境解析相关选项(单一事实源)。
|
|
@@ -17,23 +18,20 @@ export function applyTargetOptions(command) {
|
|
|
17
18
|
.option("-k, --insecure", "Allow insecure server connections (skip TLS certificate validation)")
|
|
18
19
|
.option("--allow-insecure-http", "Allow sending auth token over unencrypted HTTP to non-loopback hosts");
|
|
19
20
|
}
|
|
21
|
+
/** 本地寻址策略只有一个实现入口,查询与严格闭包均委托核心目录能力。 */
|
|
22
|
+
function inspectCommandDirectory(packageOption, context, strategy) {
|
|
23
|
+
if (!strategy?.metadataOnly)
|
|
24
|
+
return inspectLocalPackageDirectory(packageOption);
|
|
25
|
+
const directory = queryLocalPackageDirectory(packageOption);
|
|
26
|
+
if (directory)
|
|
27
|
+
return directory;
|
|
28
|
+
const root = resolvePackageRoot(packageOption, undefined, context?.customHome);
|
|
29
|
+
return root ? queryLocalPackageDirectory(root) : null;
|
|
30
|
+
}
|
|
20
31
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* 显式路径与包标识符分类规则:
|
|
24
|
-
* - 显式路径形态(以 .、/、~ 开头或包含非 scoped 分隔符):
|
|
25
|
-
* 严格作为本地技能或包目录解析;禁止与显式远端选项(--server 或非 local 的 --profile)组合,违规抛出 ArgumentError;
|
|
26
|
-
* 不受隐式 Profile 或 ACTIONDOCK_SERVER_URL 环境变量改变目标,本地执行优先;
|
|
27
|
-
* 若本地目录不存在或不合法,抛出 packageNotFoundError;
|
|
28
|
-
* - 纯词法包标识符(如 pkg.a、@scope/pkg):
|
|
29
|
-
* 若存在远端目标(显式 --server、非 local profile 或默认远端环境),严格作为远端包标识解析,绝不因本地 cwd 存在同名目录而劫持为本地执行;
|
|
30
|
-
* 若无远端目标,正常按本地目标解析。
|
|
31
|
-
*
|
|
32
|
-
* @param options 命令选项视图(支持直接传入 Commander 解析后的 options 对象)
|
|
33
|
-
* @param context CLI 上下文
|
|
34
|
-
* @returns 解析后的目标拓扑信息(仅信息解析,不创建 Service 实例)
|
|
32
|
+
* 内部统一解析目标拓扑与包作用域,单次调用闭包完成全部发现,无跨调用缓存。
|
|
35
33
|
*/
|
|
36
|
-
|
|
34
|
+
function resolveCommandTargetInternal(options, context, strategy) {
|
|
37
35
|
const isExplicit = options.package ? isExplicitPath(options.package) : false;
|
|
38
36
|
if (isExplicit) {
|
|
39
37
|
const hasExplicitServer = Boolean(options.server && options.server.trim());
|
|
@@ -41,22 +39,138 @@ export function resolveTargetFromOptions(options, context) {
|
|
|
41
39
|
if (hasExplicitServer || hasExplicitProfile) {
|
|
42
40
|
throw new ArgumentError("Cannot combine local package/skill directory with remote target options (--server or --profile). Specify a package ID for remote targets, or remove remote target options for local directory execution.");
|
|
43
41
|
}
|
|
44
|
-
const localSkill =
|
|
42
|
+
const localSkill = inspectCommandDirectory(options.package, context, strategy);
|
|
45
43
|
if (!localSkill) {
|
|
46
44
|
throw packageNotFoundError(options.package);
|
|
47
45
|
}
|
|
48
46
|
return {
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
resolved: {
|
|
48
|
+
type: "local",
|
|
49
|
+
profileName: "local",
|
|
50
|
+
},
|
|
51
|
+
localSkill,
|
|
52
|
+
targetPackageRoot: localSkill.root,
|
|
51
53
|
};
|
|
52
54
|
}
|
|
53
|
-
|
|
55
|
+
const resolved = resolveTarget({
|
|
54
56
|
profile: options.profile,
|
|
55
57
|
server: options.server,
|
|
56
58
|
token: options.token,
|
|
57
59
|
insecure: options.insecure,
|
|
58
60
|
allowInsecureHttp: options.allowInsecureHttp,
|
|
59
61
|
}, context?.customHome);
|
|
62
|
+
if (resolved.type === "remote") {
|
|
63
|
+
return {
|
|
64
|
+
resolved,
|
|
65
|
+
localSkill: null,
|
|
66
|
+
targetPackageRoot: undefined,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// 本地分支(options.package 为短名或未传)
|
|
70
|
+
if (options.package) {
|
|
71
|
+
const localSkill = inspectCommandDirectory(options.package, context, strategy);
|
|
72
|
+
const targetPackageRoot = localSkill
|
|
73
|
+
? localSkill.root
|
|
74
|
+
: strategy?.metadataOnly
|
|
75
|
+
? resolvePackageRoot(options.package, undefined, context?.customHome)
|
|
76
|
+
: resolveLocalPackageRoot(options.package);
|
|
77
|
+
if (!targetPackageRoot) {
|
|
78
|
+
throw packageNotFoundError(options.package);
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
resolved,
|
|
82
|
+
localSkill,
|
|
83
|
+
targetPackageRoot,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const projectRoot = findProjectRoot();
|
|
87
|
+
return {
|
|
88
|
+
resolved,
|
|
89
|
+
localSkill: null,
|
|
90
|
+
targetPackageRoot: projectRoot || undefined,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 目标拓扑解析单一入口:从命令选项视图解析远端/本地目标拓扑信息。
|
|
95
|
+
*
|
|
96
|
+
* 显式路径与包标识符分类规则:
|
|
97
|
+
* - 显式路径形态(以 .、/、~ 开头或包含非 scoped 分隔符):
|
|
98
|
+
* 严格作为本地技能或包目录解析;禁止与显式远端选项(--server 或非 local 的 --profile)组合,违规抛出 ArgumentError;
|
|
99
|
+
* 不受隐式 Profile 或 ACTIONDOCK_SERVER_URL 环境变量改变目标,本地执行优先;
|
|
100
|
+
* 若本地目录不存在或不合法,抛出 packageNotFoundError;
|
|
101
|
+
* - 纯词法包标识符(如 pkg.a、@scope/pkg):
|
|
102
|
+
* 若存在远端目标(显式 --server、非 local profile 或默认远端环境),严格作为远端包标识解析,绝不因本地 cwd 存在同名目录而劫持为本地执行;
|
|
103
|
+
* 若无远端目标,正常按本地目标解析。
|
|
104
|
+
*
|
|
105
|
+
* @param options 命令选项视图(支持直接传入 Commander 解析后的 options 对象)
|
|
106
|
+
* @param context CLI 上下文
|
|
107
|
+
* @param strategy 内部解析策略;仅清单查询可跳过单包闭包校验
|
|
108
|
+
* @returns 解析后的目标拓扑信息(仅信息解析,不创建 Service 实例)
|
|
109
|
+
*/
|
|
110
|
+
export function resolveTargetFromOptions(options, context, strategy) {
|
|
111
|
+
return resolveCommandTargetInternal(options, context, strategy).resolved;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* 统一计算目标资源引用(Action 或 Playbook)的辅助函数(单一事实源)。
|
|
115
|
+
*
|
|
116
|
+
* 规则:
|
|
117
|
+
* - 远端分支:options.package 存在且资源标识未限定时,前缀拼接 options.package;
|
|
118
|
+
* - 本地单包技能:资源标识未限定时,前缀拼接真实 packageId(而非路径目录名);
|
|
119
|
+
* - 本地复合技能:保持短名,供后续图解析裁决唯一匹配或多包歧义;完全限定名直接透传;
|
|
120
|
+
* - 普通工程包:非技能且 options.package 存在时,未限定则拼接 options.package。
|
|
121
|
+
*
|
|
122
|
+
* @param resourceId 资源标识符(动作名或规程名)
|
|
123
|
+
* @param options 命令选项视图
|
|
124
|
+
* @param localSkill 本地技能目录视图
|
|
125
|
+
*/
|
|
126
|
+
export function resolveResourceRef(resourceId, options, localSkill) {
|
|
127
|
+
if (!options.package)
|
|
128
|
+
return resourceId;
|
|
129
|
+
if (localSkill) {
|
|
130
|
+
if (localSkill.type === "single" && !resourceId.includes("/")) {
|
|
131
|
+
return `${localSkill.packages[0].packageId}/${resourceId}`;
|
|
132
|
+
}
|
|
133
|
+
// 复合技能短名不拼目录名,保持 resourceId 供后续裁决唯一匹配或多包歧义
|
|
134
|
+
return resourceId;
|
|
135
|
+
}
|
|
136
|
+
if (!resourceId.includes("/")) {
|
|
137
|
+
return `${options.package}/${resourceId}`;
|
|
138
|
+
}
|
|
139
|
+
return resourceId;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* 统一计算目标 Playbook 引用的辅助函数。
|
|
143
|
+
*/
|
|
144
|
+
export function resolvePlaybookRef(playbookId, options, localSkill) {
|
|
145
|
+
return resolveResourceRef(playbookId, options, localSkill);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* 构造与目标作用域严格对应的 PackageGraph(单一事实源)。
|
|
149
|
+
*
|
|
150
|
+
* 封闭保证:
|
|
151
|
+
* - 当目标为 LocalSkillDirectory 时,仅以 localSkill.packages 显式包集合构建封闭图;
|
|
152
|
+
* 绝不扫描 cwd 也不扫描全局 Registry/开发软链,防止外部同名包或污染泄漏;
|
|
153
|
+
* - 当目标为普通本地工程或包 ID 时,维持原有的基于项目根与全局链接表的拓扑图构建模式。
|
|
154
|
+
*/
|
|
155
|
+
export function buildScopedPackageGraph(target, context) {
|
|
156
|
+
if (target.localSkill) {
|
|
157
|
+
const isSingle = target.localSkill.type === "single";
|
|
158
|
+
const discovered = target.localSkill.packages.map((pkg) => ({
|
|
159
|
+
id: pkg.packageId,
|
|
160
|
+
root: pkg.packageRoot,
|
|
161
|
+
manifest: pkg.projectConfig,
|
|
162
|
+
isCurrentProject: isSingle,
|
|
163
|
+
}));
|
|
164
|
+
return new PackageGraphBuilder({
|
|
165
|
+
packages: discovered,
|
|
166
|
+
root: isSingle ? target.localSkill.packages[0].packageId : undefined,
|
|
167
|
+
}).buildSync();
|
|
168
|
+
}
|
|
169
|
+
return new PackageGraphBuilder({
|
|
170
|
+
root: target.targetPackageRoot,
|
|
171
|
+
customHome: context?.customHome,
|
|
172
|
+
allowDevLinks: true,
|
|
173
|
+
}).buildSync();
|
|
60
174
|
}
|
|
61
175
|
/**
|
|
62
176
|
* 统一解析 CLI 命令的目标拓扑与包作用域(run, describe, list 等命令的单一事实源)。
|
|
@@ -64,55 +178,35 @@ export function resolveTargetFromOptions(options, context) {
|
|
|
64
178
|
* 保证:
|
|
65
179
|
* - 优先判定目标拓扑;
|
|
66
180
|
* - 远端目标(remote)完全隔离:不执行任何本地目录探测、清单加载或包根寻址;
|
|
67
|
-
* -
|
|
68
|
-
* -
|
|
181
|
+
* - 远端目标动作与规程引用保持原始词法包 ID 拼接(${options.package}/${id}),绝不被本地同名目录或清单 ID 篡改;
|
|
182
|
+
* - 本地目标默认按封闭技能目录或工程包严格解析;元数据策略仅跳过单包闭包校验;
|
|
183
|
+
* - 显式目标不存在时透明抛出 packageNotFoundError。
|
|
69
184
|
*/
|
|
70
|
-
export function resolveCommandTarget(options, context) {
|
|
71
|
-
const
|
|
72
|
-
if (resolved.type === "remote") {
|
|
185
|
+
export function resolveCommandTarget(options, context, strategy) {
|
|
186
|
+
const result = resolveCommandTargetInternal(options, context, strategy);
|
|
187
|
+
if (result.resolved.type === "remote") {
|
|
188
|
+
const resolveRemoteRef = (id) => {
|
|
189
|
+
if (options.package && !id.includes("/")) {
|
|
190
|
+
return `${options.package}/${id}`;
|
|
191
|
+
}
|
|
192
|
+
return id;
|
|
193
|
+
};
|
|
73
194
|
return {
|
|
74
|
-
resolved,
|
|
195
|
+
resolved: result.resolved,
|
|
75
196
|
isRemote: true,
|
|
76
197
|
localSkill: null,
|
|
77
198
|
targetPackageRoot: undefined,
|
|
78
|
-
resolveActionRef:
|
|
79
|
-
|
|
80
|
-
return `${options.package}/${actionId}`;
|
|
81
|
-
}
|
|
82
|
-
return actionId;
|
|
83
|
-
},
|
|
199
|
+
resolveActionRef: resolveRemoteRef,
|
|
200
|
+
resolvePlaybookRef: resolveRemoteRef,
|
|
84
201
|
};
|
|
85
202
|
}
|
|
86
|
-
// 本地分支
|
|
87
|
-
const localSkill = options.package ? inspectLocalPackageDirectory(options.package) : null;
|
|
88
|
-
const targetPackageRoot = localSkill
|
|
89
|
-
? localSkill.root
|
|
90
|
-
: options.package
|
|
91
|
-
? resolveLocalPackageRoot(options.package)
|
|
92
|
-
: findProjectRoot();
|
|
93
|
-
if (options.package && !targetPackageRoot) {
|
|
94
|
-
throw packageNotFoundError(options.package);
|
|
95
|
-
}
|
|
96
203
|
return {
|
|
97
|
-
resolved,
|
|
204
|
+
resolved: result.resolved,
|
|
98
205
|
isRemote: false,
|
|
99
|
-
localSkill,
|
|
100
|
-
targetPackageRoot: targetPackageRoot
|
|
101
|
-
resolveActionRef: (actionId) =>
|
|
102
|
-
|
|
103
|
-
return actionId;
|
|
104
|
-
if (localSkill) {
|
|
105
|
-
if (localSkill.type === "single" && !actionId.includes("/")) {
|
|
106
|
-
return `${localSkill.packages[0].packageId}/${actionId}`;
|
|
107
|
-
}
|
|
108
|
-
// 复合技能短名不拼目录名,保持 actionId 供 Host resolveAction 裁决唯一匹配或多包歧义
|
|
109
|
-
return actionId;
|
|
110
|
-
}
|
|
111
|
-
if (!actionId.includes("/")) {
|
|
112
|
-
return `${options.package}/${actionId}`;
|
|
113
|
-
}
|
|
114
|
-
return actionId;
|
|
115
|
-
},
|
|
206
|
+
localSkill: result.localSkill,
|
|
207
|
+
targetPackageRoot: result.targetPackageRoot,
|
|
208
|
+
resolveActionRef: (actionId) => resolveResourceRef(actionId, options, result.localSkill),
|
|
209
|
+
resolvePlaybookRef: (playbookId) => resolvePlaybookRef(playbookId, options, result.localSkill),
|
|
116
210
|
};
|
|
117
211
|
}
|
|
118
212
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actiondock/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.15.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.
|
|
38
|
-
"@actiondock/core": "^2.
|
|
39
|
-
"@actiondock/mcp": "^2.
|
|
40
|
-
"@actiondock/sdk": "^2.
|
|
37
|
+
"@actiondock/builder": "^2.15.0",
|
|
38
|
+
"@actiondock/core": "^2.15.0",
|
|
39
|
+
"@actiondock/mcp": "^2.15.0",
|
|
40
|
+
"@actiondock/sdk": "^2.15.0",
|
|
41
41
|
"commander": "^13.1.0",
|
|
42
42
|
"selfsigned": "^5.5.0"
|
|
43
43
|
},
|