@actiondock/cli 2.14.0 → 2.16.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.
@@ -1,6 +1,8 @@
1
- import { ArgumentError } from "../../errors.js";
1
+ import { loadProjectConfig, } from "@actiondock/core";
2
+ import { resolvePackageRoot, } from "@actiondock/core/registry";
3
+ import { ArgumentError, packageNotFoundError } from "../../errors.js";
2
4
  import { writeStdout } from "../../renderer.js";
3
- import { applyTargetOptions, getEffectiveOptions, resolveTargetPackageScope, withService, } from "../../utils/index.js";
5
+ import { applyTargetOptions, getEffectiveOptions, withService } from "../../utils/index.js";
4
6
  /**
5
7
  * 注册 config delete 子命令:删除配置项。
6
8
  *
@@ -20,22 +22,31 @@ export function registerConfigDeleteCommand(configCmd, context) {
20
22
  if (!key) {
21
23
  throw new ArgumentError("Configuration key is required");
22
24
  }
23
- const scope = resolveTargetPackageScope(options, context);
25
+ const root = resolvePackageRoot(options.package);
24
26
  await withService(options, context, async (service, resolved) => {
25
27
  if (resolved.type === "remote") {
26
- await service.management?.config.delete(scope.packageId, key);
28
+ await service.management?.config.delete(options.package || "", key);
27
29
  writeStdout(`[OK] Configuration '${key}' deleted from remote server`, context);
28
30
  return;
29
31
  }
30
- if (options.global || (!scope.root && !options.package)) {
32
+ if (options.global) {
31
33
  await service.management?.config.delete("global", key);
32
34
  writeStdout(`[OK] Global configuration '${key}' deleted`, context);
33
35
  return;
34
36
  }
35
- await service.management?.config.delete(scope.packageId, key);
36
- writeStdout(`[OK] Configuration '${key}' deleted for package '${scope.packageId}'`, context);
37
+ if (!root) {
38
+ if (options.package) {
39
+ throw packageNotFoundError(options.package);
40
+ }
41
+ await service.management?.config.delete("global", key);
42
+ writeStdout(`[OK] Global configuration '${key}' deleted`, context);
43
+ return;
44
+ }
45
+ const projConfig = loadProjectConfig(root);
46
+ await service.management?.config.delete(projConfig.id, key);
47
+ writeStdout(`[OK] Configuration '${key}' deleted for package '${projConfig.id}'`, context);
37
48
  },
38
49
  // 按实际查询范围打开存储:显式包寻址结果作为本地根目录传递
39
- { localRoot: scope.root });
50
+ { localRoot: root || undefined });
40
51
  });
41
52
  }
@@ -2,7 +2,7 @@ import { resolveEnvValue } from "@actiondock/core";
2
2
  import { fetchRemoteConfigEnv } from "@actiondock/core/profile";
3
3
  import { isSecretConfigKey, } from "@actiondock/core/project";
4
4
  import { renderConfigEnv, renderResult } from "../../renderer.js";
5
- import { applyTargetOptions, assertNotCompositeSkill, getEffectiveOptions, requirePackageRoot, resolveTargetFromOptions, } from "../../utils/index.js";
5
+ import { applyTargetOptions, getEffectiveOptions, requirePackageRoot, resolveTargetFromOptions } from "../../utils/index.js";
6
6
  /**
7
7
  * 注册 config env 子命令:诊断环境变量对声明配置的满足率。
8
8
  *
@@ -18,7 +18,7 @@ export function registerConfigEnvCommand(configCmd, context) {
18
18
  .action(async (identifier, rawOptions, cmd) => {
19
19
  const options = getEffectiveOptions(rawOptions, cmd);
20
20
  const targetPkg = identifier || options.package;
21
- // 1. 目标拓扑解析(远端服务分支完全隔离本地文件系统)
21
+ // 远端服务分支直接查询远端配置环境诊断接口
22
22
  const target = resolveTargetFromOptions(options, context);
23
23
  if (target.type === "remote") {
24
24
  const res = await fetchRemoteConfigEnv(target.serverUrl, target.token, targetPkg, {
@@ -37,7 +37,6 @@ export function registerConfigEnvCommand(configCmd, context) {
37
37
  return;
38
38
  }
39
39
  // 本地项目分支
40
- assertNotCompositeSkill(targetPkg);
41
40
  const { root, projConfig } = requirePackageRoot(targetPkg, {
42
41
  loadConfig: true,
43
42
  hint: "Usage: ad config env [package-id] or cd into a project directory.",
@@ -1,10 +1,11 @@
1
1
  import { fetchRemoteConfig, } from "@actiondock/core/profile";
2
2
  import { isSecretConfigKey, maskSecretValue, } from "@actiondock/core/project";
3
3
  import { loadProjectConfig, } from "@actiondock/core";
4
- import { ArgumentError } from "../../errors.js";
4
+ import { resolvePackageRoot, } from "@actiondock/core/registry";
5
+ import { ArgumentError, packageNotFoundError } from "../../errors.js";
5
6
  import { renderResult } from "../../renderer.js";
6
7
  import { resolveMergedConfigEntry } from "../../services/config-merge.js";
7
- import { applyTargetOptions, getEffectiveOptions, resolveTargetPackageScope, withService, } from "../../utils/index.js";
8
+ import { applyTargetOptions, getEffectiveOptions, resolveTargetFromOptions, withService, } from "../../utils/index.js";
8
9
  /**
9
10
  * 注册 config get 子命令:查询指定配置键的生效值。
10
11
  *
@@ -26,11 +27,12 @@ export function registerConfigGetCommand(configCmd, context) {
26
27
  throw new ArgumentError("Configuration key is required");
27
28
  }
28
29
  const reveal = Boolean(options.reveal || options.showSecrets);
29
- const scope = resolveTargetPackageScope(options, context);
30
- if (scope.isRemote) {
31
- const res = await fetchRemoteConfig(scope.resolved.serverUrl, scope.resolved.token, scope.packageId, {
30
+ // 远端服务分支不经过 Target 门面,直接查询远端配置接口
31
+ const target = resolveTargetFromOptions(options, context);
32
+ if (target.type === "remote") {
33
+ const res = await fetchRemoteConfig(target.serverUrl, target.token, options.package, {
32
34
  allowInsecureHttp: Boolean(options.allowInsecureHttp),
33
- insecure: scope.resolved.insecure,
35
+ insecure: target.insecure,
34
36
  });
35
37
  const val = res.values?.[key];
36
38
  const isSecret = isSecretConfigKey(key, res.declared?.[key]);
@@ -48,8 +50,13 @@ export function registerConfigGetCommand(configCmd, context) {
48
50
  });
49
51
  return;
50
52
  }
53
+ // 本地分支
54
+ const root = resolvePackageRoot(options.package);
51
55
  await withService(options, context, async (service) => {
52
- if (options.global || !scope.root) {
56
+ if (options.global || !root) {
57
+ if (!options.global && options.package && !root) {
58
+ throw packageNotFoundError(options.package);
59
+ }
53
60
  const confView = await service.management?.config.get("global", key);
54
61
  const val = confView?.value;
55
62
  const isSecret = confView?.secret ?? isSecretConfigKey(key);
@@ -67,10 +74,10 @@ export function registerConfigGetCommand(configCmd, context) {
67
74
  });
68
75
  return;
69
76
  }
70
- const projConfig = scope.projConfig || loadProjectConfig(scope.root);
77
+ const projConfig = loadProjectConfig(root);
71
78
  const declaredItem = projConfig.config?.[key];
72
79
  // 合并优先级链单一事实源:项目包级 > 全局持久化 > 环境变量 > 声明默认值
73
- const { value: displayValue, source, secret: isSecret } = await resolveMergedConfigEntry(key, scope.packageId, declaredItem, service, reveal);
80
+ const { value: displayValue, source, secret: isSecret } = await resolveMergedConfigEntry(key, projConfig.id, declaredItem, service, reveal);
74
81
  const payload = {
75
82
  key,
76
83
  value: displayValue,
@@ -84,6 +91,6 @@ export function registerConfigGetCommand(configCmd, context) {
84
91
  });
85
92
  },
86
93
  // 按实际查询范围打开存储:显式包寻址结果作为本地根目录传递
87
- { localRoot: scope.root });
94
+ { localRoot: root || undefined });
88
95
  });
89
96
  }
@@ -1,9 +1,10 @@
1
1
  import { fetchRemoteConfig, } from "@actiondock/core/profile";
2
2
  import { loadProjectConfig, } from "@actiondock/core";
3
3
  import { filterWithFallbackInfo, isSecretConfigKey, maskSecretValue, } from "@actiondock/core/project";
4
+ import { packageNotFoundError } from "../../errors.js";
4
5
  import { renderConfigList, renderResult } from "../../renderer.js";
5
6
  import { buildMergedConfigEntries } from "../../services/config-merge.js";
6
- import { applyTargetOptions, getEffectiveOptions, remoteTargetLabel, resolveFallbackStrategy, resolveIntent, resolveTargetPackageScope, withService, } from "../../utils/index.js";
7
+ import { applyTargetOptions, getEffectiveOptions, remoteTargetLabel, resolveFallbackStrategy, resolveIntent, resolveLocalPackageRoot, resolveTargetFromOptions, withService, } from "../../utils/index.js";
7
8
  /**
8
9
  * 构造单条配置项的展示视图(含掩码决策,reveal 为假且为敏感键时打码)。
9
10
  * 掩码决策收敛于命令层,renderer 仅负责值的字符串格式化。
@@ -41,24 +42,29 @@ export function registerConfigListCommand(configCmd, context) {
41
42
  const effectiveIntent = resolveIntent(options.intent, patterns);
42
43
  const { shouldFallback } = resolveFallbackStrategy(options);
43
44
  const reveal = Boolean(options.reveal || options.showSecrets);
44
- const scope = resolveTargetPackageScope(options, context);
45
- if (scope.isRemote) {
46
- const res = await fetchRemoteConfig(scope.resolved.serverUrl, scope.resolved.token, scope.packageId, {
45
+ // 远端服务分支不经过 Target 门面,直接查询远端配置接口
46
+ const remoteTargetInfo = resolveTargetFromOptions(options, context);
47
+ if (remoteTargetInfo.type === "remote") {
48
+ const res = await fetchRemoteConfig(remoteTargetInfo.serverUrl, remoteTargetInfo.token, options.package, {
47
49
  allowInsecureHttp: Boolean(options.allowInsecureHttp),
48
- insecure: scope.resolved.insecure,
50
+ insecure: remoteTargetInfo.insecure,
49
51
  });
50
52
  const entries = Object.entries(res.values || {}).map(([k, v]) => toDisplayEntry(k, v, "remote", isSecretConfigKey(k, res.declared?.[k]), reveal, res.declared?.[k]?.description || ""));
51
53
  renderResult(entries, {
52
54
  json: options.json,
53
- humanFormatter: () => renderConfigList(entries, remoteTargetLabel(scope.resolved), false, effectiveIntent),
55
+ humanFormatter: () => renderConfigList(entries, remoteTargetLabel(remoteTargetInfo), false, effectiveIntent),
54
56
  context,
55
57
  });
56
58
  return;
57
59
  }
58
60
  // 本地分支(通过 Service 门面统一访问):显式包寻址结果作为本地根目录传递(仅 local 分支求值)
59
61
  await withService(options, context, async (service) => {
62
+ const root = resolveLocalPackageRoot(options.package) || undefined;
60
63
  // 全局作用域分支:显式 --global 或无工程回退时仅列举全局配置
61
- if (options.global || !scope.root) {
64
+ if (options.global || !root) {
65
+ if (!options.global && options.package) {
66
+ throw packageNotFoundError(options.package);
67
+ }
62
68
  const all = (await service.management?.config.list("global")) ?? [];
63
69
  const entries = all.map((item) => toDisplayEntry(item.key, item.value, "global", item.secret || isSecretConfigKey(item.key), reveal));
64
70
  const filterRes = filterWithFallbackInfo(entries, effectiveIntent, [(c) => c.key, (c) => c.value], shouldFallback);
@@ -69,9 +75,9 @@ export function registerConfigListCommand(configCmd, context) {
69
75
  });
70
76
  return;
71
77
  }
72
- const projConfig = scope.projConfig || loadProjectConfig(scope.root);
78
+ const projConfig = loadProjectConfig(root);
73
79
  // 跨作用域合并视图单一事实源:项目包级 > 全局持久化 > 环境变量 > 声明默认值
74
- const merged = await buildMergedConfigEntries(scope.root, service, reveal);
80
+ const merged = await buildMergedConfigEntries(root, service, reveal);
75
81
  const filterRes = filterWithFallbackInfo(merged, effectiveIntent, [(c) => c.key, (c) => c.value, (c) => c.description, (c) => c.source], shouldFallback);
76
82
  renderResult(filterRes.items, {
77
83
  json: options.json,
@@ -79,7 +85,7 @@ export function registerConfigListCommand(configCmd, context) {
79
85
  context,
80
86
  });
81
87
  }, {
82
- localRoot: scope.root,
88
+ localRoot: () => resolveLocalPackageRoot(options.package) || undefined,
83
89
  });
84
90
  });
85
91
  }
@@ -2,7 +2,7 @@ import { isSecretConfigKey, } from "@actiondock/core/project";
2
2
  import { notInProjectError } from "../../errors.js";
3
3
  import { renderConfigSchema, renderResult } from "../../renderer.js";
4
4
  import { buildMergedConfigEntries } from "../../services/config-merge.js";
5
- import { applyTargetOptions, assertNotCompositeSkill, getEffectiveOptions, requirePackageRoot, withService, } from "../../utils/index.js";
5
+ import { applyTargetOptions, getEffectiveOptions, requirePackageRoot, withService } from "../../utils/index.js";
6
6
  /**
7
7
  * 注册 config schema 子命令:检查声明配置的解析状态。
8
8
  *
@@ -25,18 +25,17 @@ export function registerConfigSchemaCommand(configCmd, context) {
25
25
  .option("--json", "Output as JSON")
26
26
  .action(async (identifier, rawOptions, cmd) => {
27
27
  const options = getEffectiveOptions(rawOptions, cmd);
28
- // 远端目标显式拒绝:schema 状态推导依赖本地工程声明与环境变量解析,
29
- // 远端作用域无法获得等价视图,静默落到本地会误导调用方
30
- if (options.profile || options.server) {
31
- throw notInProjectError("'ad config schema' only supports local projects. Remote targets are not supported for schema inspection.");
32
- }
33
28
  const targetPkg = identifier || options.package;
34
- assertNotCompositeSkill(targetPkg);
35
29
  const { root, projConfig } = requirePackageRoot(targetPkg, {
36
30
  loadConfig: true,
37
31
  hint: "Usage: ad config schema [package-id] or cd into a project directory.",
38
32
  });
39
33
  const declared = projConfig.config || {};
34
+ // 远端目标显式拒绝:schema 状态推导依赖本地工程声明与环境变量解析,
35
+ // 远端作用域无法获得等价视图,静默落到本地会误导调用方
36
+ if (options.profile || options.server) {
37
+ throw notInProjectError("'ad config schema' only supports local projects. Remote targets are not supported for schema inspection.");
38
+ }
40
39
  // 跨作用域合并视图单一事实源:与 config list 共享同一条优先级链
41
40
  await withService(options, context, async (service) => {
42
41
  const merged = await buildMergedConfigEntries(root, service, true);
@@ -1,7 +1,9 @@
1
1
  import { isSecretConfigKey, } from "@actiondock/core/project";
2
- import { ArgumentError } from "../../errors.js";
2
+ import { loadProjectConfig, } from "@actiondock/core";
3
+ import { resolvePackageRoot, } from "@actiondock/core/registry";
4
+ import { ArgumentError, packageNotFoundError } from "../../errors.js";
3
5
  import { writeStdout } from "../../renderer.js";
4
- import { applyTargetOptions, getEffectiveOptions, resolveTargetPackageScope, withService, } from "../../utils/index.js";
6
+ import { applyTargetOptions, getEffectiveOptions, withService } from "../../utils/index.js";
5
7
  import { resolveConfigValueInput } from "../../prompt.js";
6
8
  /**
7
9
  * 注册 config set 子命令:写入配置值(支持标准输入与安全提示)。
@@ -22,8 +24,22 @@ export function registerConfigSetCommand(configCmd, context) {
22
24
  if (!key) {
23
25
  throw new ArgumentError("Configuration key is required");
24
26
  }
25
- const scope = resolveTargetPackageScope(options, context);
26
- const declaredDef = scope.projConfig?.config?.[key];
27
+ let root = null;
28
+ let projConfig = null;
29
+ let declaredDef;
30
+ if (!options.global && !options.profile && !options.server) {
31
+ root = resolvePackageRoot(options.package);
32
+ if (root) {
33
+ try {
34
+ projConfig = loadProjectConfig(root);
35
+ declaredDef = projConfig.config?.[key];
36
+ }
37
+ catch (err) {
38
+ // 清单损坏时必须让错误可见:静默忽略会导致写入错误的作用域
39
+ throw err;
40
+ }
41
+ }
42
+ }
27
43
  const isSecret = isSecretConfigKey(key, declaredDef);
28
44
  let effectiveValueStr = rawVal;
29
45
  if (effectiveValueStr === undefined || options.stdin) {
@@ -44,21 +60,27 @@ export function registerConfigSetCommand(configCmd, context) {
44
60
  // 目标解析与写入(远端分支直接写远端作用域,本地分支按全局/项目作用域写入)
45
61
  await withService(options, context, async (service, resolved) => {
46
62
  if (resolved.type === "remote") {
47
- await service.management?.config.set(scope.packageId, key, parsedVal);
63
+ await service.management?.config.set(options.package || "", key, parsedVal);
48
64
  writeStdout(`[OK] Configuration '${key}' updated on remote server`, context);
49
65
  return;
50
66
  }
51
- if (options.global || (!scope.root && !options.package)) {
67
+ if (options.global) {
68
+ await service.management?.config.set("global", key, parsedVal);
69
+ writeStdout(`[OK] Global configuration '${key}' updated`, context);
70
+ return;
71
+ }
72
+ if (!root) {
73
+ if (options.package) {
74
+ throw packageNotFoundError(options.package);
75
+ }
52
76
  await service.management?.config.set("global", key, parsedVal);
53
- writeStdout(!scope.root && !options.package
54
- ? `[OK] Global configuration '${key}' updated (no project in current directory)`
55
- : `[OK] Global configuration '${key}' updated`, context);
77
+ writeStdout(`[OK] Global configuration '${key}' updated (no project in current directory)`, context);
56
78
  return;
57
79
  }
58
- await service.management?.config.set(scope.packageId, key, parsedVal);
59
- writeStdout(`[OK] Configuration '${key}' updated for package '${scope.packageId}'`, context);
80
+ await service.management?.config.set(projConfig.id, key, parsedVal);
81
+ writeStdout(`[OK] Configuration '${key}' updated for package '${projConfig.id}'`, context);
60
82
  },
61
83
  // 按实际查询范围打开存储:显式包寻址结果作为本地根目录传递
62
- { localRoot: scope.root });
84
+ { localRoot: root || undefined });
63
85
  });
64
86
  }
@@ -1,8 +1,8 @@
1
1
  import { ACTION_NOT_FOUND, PACKAGE_NOT_FOUND, NOT_FOUND, } from "@actiondock/core";
2
2
  import { buildActionDescribePayload, } from "@actiondock/core/project";
3
- import { ArgumentError, ExecutionError } from "../errors.js";
3
+ import { ArgumentError, ExecutionError, packageNotFoundError } from "../errors.js";
4
4
  import { formatActionDetail, renderResult } from "../renderer.js";
5
- import { applyTargetOptions, getEffectiveOptions, resolveCommandTarget, withService, } from "../utils/index.js";
5
+ import { applyTargetOptions, getEffectiveOptions, resolveLocalPackageRoot, withService, } from "../utils/index.js";
6
6
  /**
7
7
  * 挂载 describe 子命令至指定 Commander 节点。
8
8
  *
@@ -22,9 +22,12 @@ export function attachDescribeCommand(parent, context) {
22
22
  if (!id) {
23
23
  throw new ArgumentError("Action ID is required for describe");
24
24
  }
25
- // 目标拓扑与动作引用解析(远端目标完全隔离本地文件系统)
26
- const target = resolveCommandTarget(options, context);
27
- const targetRef = target.resolveActionRef(id);
25
+ // 目标拓扑解析(仅 local 分支需要包寻址)
26
+ const targetPackageRoot = resolveLocalPackageRoot(options.package);
27
+ if (options.package && !targetPackageRoot) {
28
+ throw packageNotFoundError(options.package);
29
+ }
30
+ const targetRef = id;
28
31
  // 通过 Service 门面统一查询 Action 规范
29
32
  await withService(options, context, async (service) => {
30
33
  let spec;
@@ -47,7 +50,7 @@ export function attachDescribeCommand(parent, context) {
47
50
  humanFormatter: () => formatActionDetail(payload),
48
51
  context,
49
52
  });
50
- }, { localRoot: target.targetPackageRoot, scanLinkedPackages: !target.localSkill });
53
+ }, { localRoot: targetPackageRoot || undefined, scanLinkedPackages: true });
51
54
  });
52
55
  }
53
56
  /**
@@ -1,6 +1,7 @@
1
1
  import { filterWithFallbackInfo, } from "@actiondock/core/project";
2
+ import { packageNotFoundError } from "../errors.js";
2
3
  import { renderActionList, renderResult } from "../renderer.js";
3
- import { applyTargetOptions, getEffectiveOptions, remoteTargetSuffix, resolveCommandTarget, resolveFallbackStrategy, resolveIntent, withService, } from "../utils/index.js";
4
+ import { applyTargetOptions, getEffectiveOptions, remoteTargetSuffix, resolveFallbackStrategy, resolveIntent, resolveLocalPackageRoot, withService, } from "../utils/index.js";
4
5
  /**
5
6
  * 挂载 list 子命令至指定 Commander 节点。
6
7
  *
@@ -22,20 +23,22 @@ export function attachListCommand(parent, context) {
22
23
  const options = getEffectiveOptions(rawOptions, cmd);
23
24
  const effectiveIntent = resolveIntent(options.intent, patterns);
24
25
  const { isMachine, shouldFallback } = resolveFallbackStrategy(options);
25
- // 目标拓扑与动作引用解析(远端目标完全隔离本地文件系统)
26
- const target = resolveCommandTarget(options, context);
26
+ // 目标拓扑解析(仅 local 分支需要包寻址)
27
+ const targetPackageRoot = resolveLocalPackageRoot(options.package);
28
+ if (options.package && !targetPackageRoot) {
29
+ throw packageNotFoundError(options.package);
30
+ }
27
31
  // 通过 Service 门面统一获取 Action 列表
28
32
  await withService(options, context, async (service, resolved) => {
29
33
  let rawSummaries = await service.discovery.listActions();
30
- // 若指定了包 ID(非本地技能目录),则按包归属精确过滤(本地与远端统一生效):
34
+ // 若指定了目标包但本地未过滤,则按包归属精确过滤:
31
35
  // 仅保留全限定名命中目标包前缀、或 packageId 归属目标包的条目;
32
36
  // 无法归属到目标包的无前缀短 id 条目不放行,避免他包短 id 混入
33
- if (options.package && !target.localSkill) {
37
+ if (options.package && !options.profile && !options.server) {
34
38
  const targetPkg = options.package;
35
39
  rawSummaries = rawSummaries.filter((s) => {
36
- const pkgId = s.packageId;
37
- if (pkgId) {
38
- return (pkgId === targetPkg ||
40
+ if (s.packageId) {
41
+ return (s.packageId === targetPkg ||
39
42
  s.id === targetPkg ||
40
43
  s.id.startsWith(`${targetPkg}/`));
41
44
  }
@@ -43,17 +46,14 @@ export function attachListCommand(parent, context) {
43
46
  return s.id === targetPkg || s.id.startsWith(`${targetPkg}/`);
44
47
  });
45
48
  }
46
- const isCompositeSkill = target.localSkill?.type === "composite";
47
49
  const rawList = rawSummaries.map((s) => {
48
50
  let id = s.id;
49
- // 复合技能保持完全限定 ID,不剥离包名前缀
50
- if (!isCompositeSkill && (target.targetPackageRoot || options.package)) {
51
- const targetPkg = target.localSkill ? target.localSkill.packages[0].packageId : options.package;
52
- const pkgId = targetPkg || s.packageId;
51
+ if (targetPackageRoot || options.package) {
52
+ const pkgId = s.packageId || options.package;
53
53
  if (pkgId && id.startsWith(`${pkgId}/`)) {
54
54
  id = id.slice(pkgId.length + 1);
55
55
  }
56
- else if (target.targetPackageRoot && id.includes("/")) {
56
+ else if (targetPackageRoot && id.includes("/")) {
57
57
  id = id.slice(id.indexOf("/") + 1);
58
58
  }
59
59
  }
@@ -80,7 +80,7 @@ export function attachListCommand(parent, context) {
80
80
  }
81
81
  const title = resolved.type === "remote"
82
82
  ? `Actions ${remoteTargetSuffix(resolved)}`
83
- : target.targetPackageRoot
83
+ : targetPackageRoot
84
84
  ? `Actions in ${options.package || "current project"}`
85
85
  : "Available Actions";
86
86
  renderResult(filterRes.items, {
@@ -88,7 +88,7 @@ export function attachListCommand(parent, context) {
88
88
  humanFormatter: () => renderActionList(filterRes.items, title, filterRes.isFallback, effectiveIntent),
89
89
  context,
90
90
  });
91
- }, { localRoot: target.targetPackageRoot, scanLinkedPackages: !target.localSkill });
91
+ }, { localRoot: targetPackageRoot || undefined, scanLinkedPackages: true });
92
92
  });
93
93
  }
94
94
  /**
@@ -1,8 +1,9 @@
1
1
  import { validateActionInputValue, mapInputValidationFailure, } from "@actiondock/core/project";
2
+ import { resolveTarget, } from "@actiondock/core/profile";
2
3
  import { ACTION_NOT_FOUND, NOT_FOUND, resolveExecutionHint, } from "@actiondock/core";
3
- import { ArgumentError, ExecutionError, SigintError } from "../errors.js";
4
+ import { ArgumentError, ExecutionError, SigintError, packageNotFoundError } from "../errors.js";
4
5
  import { extractTextFieldPayload, resolveActionDefaultTextField, writeStderr, writeStdout, } from "../renderer.js";
5
- import { applyTargetOptions, getEffectiveOptions, resolveActionInput, resolveCommandTarget, resolveTimeoutMs, withService, } from "../utils/index.js";
6
+ import { applyTargetOptions, getEffectiveOptions, resolveActionInput, resolveLocalPackageRoot, resolveTimeoutMs, withService, } from "../utils/index.js";
6
7
  /**
7
8
  * 以原始纯文本形式渲染 Action 执行终态结果(默认纯文本模式)。
8
9
  *
@@ -110,13 +111,25 @@ export async function executeAction(id, options, context, flatArgs, control) {
110
111
  }
111
112
  }
112
113
  try {
113
- // 目标拓扑与动作引用解析(远端目标完全隔离本地文件系统)
114
- const target = resolveCommandTarget(options, context);
115
114
  // 异步执行仅支持远端目标,本地模式直接拒绝
116
- if (!target.isRemote && options.async) {
115
+ const resolvedTopology = resolveTarget({
116
+ profile: options.profile,
117
+ server: options.server,
118
+ token: options.token,
119
+ insecure: options.insecure,
120
+ }, context?.customHome);
121
+ if (resolvedTopology.type === "local" && options.async) {
117
122
  throw new ExecutionError("Async execution requires a long-running ActionDock server.\nUse --profile, --server, or start 'ad serve'.");
118
123
  }
119
- const targetRef = target.resolveActionRef(id);
124
+ // 目标拓扑解析(仅 local 分支需要包寻址)
125
+ const targetPackageRoot = resolveLocalPackageRoot(options.package);
126
+ if (options.package && !targetPackageRoot) {
127
+ throw packageNotFoundError(options.package);
128
+ }
129
+ let targetRef = id;
130
+ if (options.package && !id.includes("/")) {
131
+ targetRef = `${options.package}/${id}`;
132
+ }
120
133
  // 通过 Service 统一执行
121
134
  await withService(options, context, async (service) => {
122
135
  const isMachine = Boolean(options.json);
@@ -192,7 +205,7 @@ export async function executeAction(id, options, context, flatArgs, control) {
192
205
  return;
193
206
  }
194
207
  }
195
- }, { localRoot: target.targetPackageRoot || undefined, scanLinkedPackages: !target.localSkill, ownDataDir: true });
208
+ }, { localRoot: targetPackageRoot || undefined, scanLinkedPackages: true, ownDataDir: true });
196
209
  }
197
210
  catch (err) {
198
211
  // sigint 取消源、中断链路特征错误码或 SigintError 本身,均统一归并为 SigintError
@@ -1,11 +1,11 @@
1
1
  import { decodeStateKey, } from "@actiondock/sdk";
2
- import { loadProjectConfig, inspectLocalPackageDirectory, } from "@actiondock/core";
2
+ import { loadProjectConfig, } from "@actiondock/core";
3
3
  import { filterWithFallbackInfo, } from "@actiondock/core/project";
4
4
  import { listLinkedPackages, } from "@actiondock/core/registry";
5
5
  import { existsSync } from "node:fs";
6
6
  import { ArgumentError, ExecutionError, NO_PROJECT_NO_LINKED_MESSAGE } from "../errors.js";
7
7
  import { renderResult, renderStateList, writeStderr, writeStdout } from "../renderer.js";
8
- import { applyTargetOptions, assertNotCompositeSkill, getEffectiveOptions, getTargetRoot, remoteTargetLabel, requirePackageRoot, resolveFallbackStrategy, resolveIntent, resolveLocalPackageRoot, resolveTargetFromOptions, withService, } from "../utils/index.js";
8
+ import { applyTargetOptions, getEffectiveOptions, getTargetRoot, remoteTargetLabel, requirePackageRoot, resolveFallbackStrategy, resolveIntent, resolveLocalPackageRoot, resolveTargetFromOptions, withService, } from "../utils/index.js";
9
9
  /**
10
10
  * 统一预解码状态键寻址(get、set、delete 共享的单一事实源)。
11
11
  *
@@ -57,22 +57,6 @@ function resolveStateScope(resolved, options, rawKey) {
57
57
  localRoot: undefined,
58
58
  };
59
59
  }
60
- assertNotCompositeSkill(options.package);
61
- // 本地单包技能目录优先处理
62
- if (options.package) {
63
- const localSkill = inspectLocalPackageDirectory(options.package);
64
- if (localSkill?.type === "single") {
65
- const pkg = localSkill.packages[0];
66
- return {
67
- packageId: pkg.packageId,
68
- scopeSuffix: `in package '${pkg.packageId}'`,
69
- deleteSuffix: `from package '${pkg.packageId}'`,
70
- effectiveKey: rawKey || "",
71
- projConfig: pkg.projectConfig,
72
- localRoot: pkg.packageRoot,
73
- };
74
- }
75
- }
76
60
  // 本地项目模式:getTargetRoot 会从键中剥离 pkg/ 前缀,寻址基于解耦后的裸键
77
61
  const { root, key: effectiveKey } = getTargetRoot(options.package, rawKey);
78
62
  const projConfig = loadProjectConfig(root);
@@ -191,7 +175,6 @@ export function registerStateCommands(program, context) {
191
175
  return;
192
176
  }
193
177
  // 本地工程与链接包模式:按目标根目录决策输出范围
194
- assertNotCompositeSkill(options.package);
195
178
  const targetRoot = options.package
196
179
  ? requirePackageRoot(options.package).root
197
180
  : resolveLocalPackageRoot();
@@ -1,4 +1,4 @@
1
- import { loadProjectConfig, type ActionDockService, type LocalSkillDirectory } from "@actiondock/core";
1
+ import { type ActionDockService } from "@actiondock/core";
2
2
  import { type ResolvedTarget } from "@actiondock/core/profile";
3
3
  import type { Command } from "commander";
4
4
  import type { CliContext } from "../types.js";
@@ -33,68 +33,14 @@ export interface TargetResolutionOptions {
33
33
  /**
34
34
  * 目标拓扑解析单一入口:从命令选项视图解析远端/本地目标拓扑信息。
35
35
  *
36
- * 显式路径与包标识符分类规则:
37
- * - 显式路径形态(以 .、/、~ 开头或包含非 scoped 分隔符):
38
- * 严格作为本地技能或包目录解析;禁止与显式远端选项(--server 或非 local 的 --profile)组合,违规抛出 ArgumentError;
39
- * 不受隐式 Profile 或 ACTIONDOCK_SERVER_URL 环境变量改变目标,本地执行优先;
40
- * 若本地目录不存在或不合法,抛出 packageNotFoundError;
41
- * - 纯词法包标识符(如 pkg.a、@scope/pkg):
42
- * 若存在远端目标(显式 --server、非 local profile 或默认远端环境),严格作为远端包标识解析,绝不因本地 cwd 存在同名目录而劫持为本地执行;
43
- * 若无远端目标,正常按本地目标解析。
36
+ * 收拢全部解析字段(profile/server/token/insecure/allowInsecureHttp),
37
+ * 消除命令间逐字段内联复制导致的漂移(如漏传 allowInsecureHttp)。
44
38
  *
45
39
  * @param options 命令选项视图(支持直接传入 Commander 解析后的 options 对象)
46
40
  * @param context CLI 上下文
47
41
  * @returns 解析后的目标拓扑信息(仅信息解析,不创建 Service 实例)
48
42
  */
49
43
  export declare function resolveTargetFromOptions(options: TargetResolutionOptions, context?: CliContext): ResolvedTarget;
50
- /**
51
- * 命令目标与动作引用解析结果。
52
- */
53
- export interface CommandTargetResolution {
54
- /** 目标拓扑信息 */
55
- resolved: ResolvedTarget;
56
- /** 是否为远端目标 */
57
- isRemote: boolean;
58
- /** 本地封闭技能目录视图(非本地技能目录时为 null) */
59
- localSkill: LocalSkillDirectory | null;
60
- /** 本地包根目录路径(远端分支为 undefined,未找到为 undefined) */
61
- targetPackageRoot?: string;
62
- /** 统一计算目标 Action 引用的辅助函数 */
63
- resolveActionRef: (actionId: string) => string;
64
- }
65
- /**
66
- * 统一解析 CLI 命令的目标拓扑与包作用域(run, describe, list 等命令的单一事实源)。
67
- *
68
- * 保证:
69
- * - 优先判定目标拓扑;
70
- * - 远端目标(remote)完全隔离:不执行任何本地目录探测、清单加载或包根寻址;
71
- * - 远端目标动作引用保持原始词法包 ID 拼接(${options.package}/${actionId}),绝不被本地同名目录或清单 ID 篡改;
72
- * - 本地目标按本地封闭技能目录或工程包严格解析,不存在时透明抛出 packageNotFoundError。
73
- */
74
- export declare function resolveCommandTarget(options: TargetResolutionOptions, context?: CliContext): CommandTargetResolution;
75
- /**
76
- * 配置与状态管理目标作用域。
77
- */
78
- export interface TargetPackageScope {
79
- /** 解析后的目标拓扑 */
80
- resolved: ResolvedTarget;
81
- /** 是否为远端目标 */
82
- isRemote: boolean;
83
- /** 目标包标识(远端为 options.package 原值,本地为工程配置 id 或 "global") */
84
- packageId: string;
85
- /** 本地工程根目录(远端分支为 undefined) */
86
- root?: string;
87
- /** 本地工程配置(远端分支为 undefined) */
88
- projConfig?: ReturnType<typeof loadProjectConfig>;
89
- }
90
- /**
91
- * 校验并拒绝复合技能目录用于配置或状态管理命令(单一事实源)。
92
- */
93
- export declare function assertNotCompositeSkill(packageOption?: string): void;
94
- /**
95
- * 统一解析配置与状态命令的目标作用域(支持单包技能目录、本地工程、远端服务与 --profile local)。
96
- */
97
- export declare function resolveTargetPackageScope(options: TargetResolutionOptions, context?: CliContext): TargetPackageScope;
98
44
  /**
99
45
  * 高阶服务执行辅助:统一封装「目标解析 → Service 创建 → 业务回调 → try/finally close」样板。
100
46
  *
@@ -1,7 +1,7 @@
1
- import { connectActionDock, createActionDock, createNodePlatform, findProjectRoot, inspectLocalPackageDirectory, isExplicitPath, loadProjectConfig, } from "@actiondock/core";
1
+ import { connectActionDock, createActionDock, createNodePlatform, findProjectRoot, } from "@actiondock/core";
2
2
  import { resolvePackageRoot, } from "@actiondock/core/registry";
3
3
  import { resolveTarget, } from "@actiondock/core/profile";
4
- import { ArgumentError, packageNotFoundError } from "../errors.js";
4
+ import { ArgumentError } from "../errors.js";
5
5
  /**
6
6
  * 统一为 Commander 命令节点挂载目标环境解析相关选项(单一事实源)。
7
7
  * 包括:-p/--profile, -s/--server, -t/--token, -k/--insecure, --allow-insecure-http。
@@ -20,36 +20,14 @@ export function applyTargetOptions(command) {
20
20
  /**
21
21
  * 目标拓扑解析单一入口:从命令选项视图解析远端/本地目标拓扑信息。
22
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
- * 若无远端目标,正常按本地目标解析。
23
+ * 收拢全部解析字段(profile/server/token/insecure/allowInsecureHttp),
24
+ * 消除命令间逐字段内联复制导致的漂移(如漏传 allowInsecureHttp)。
31
25
  *
32
26
  * @param options 命令选项视图(支持直接传入 Commander 解析后的 options 对象)
33
27
  * @param context CLI 上下文
34
28
  * @returns 解析后的目标拓扑信息(仅信息解析,不创建 Service 实例)
35
29
  */
36
30
  export function resolveTargetFromOptions(options, context) {
37
- const isExplicit = options.package ? isExplicitPath(options.package) : false;
38
- if (isExplicit) {
39
- const hasExplicitServer = Boolean(options.server && options.server.trim());
40
- const hasExplicitProfile = Boolean(options.profile && options.profile.trim() && options.profile.trim() !== "local");
41
- if (hasExplicitServer || hasExplicitProfile) {
42
- 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
- }
44
- const localSkill = inspectLocalPackageDirectory(options.package);
45
- if (!localSkill) {
46
- throw packageNotFoundError(options.package);
47
- }
48
- return {
49
- type: "local",
50
- profileName: "local",
51
- };
52
- }
53
31
  return resolveTarget({
54
32
  profile: options.profile,
55
33
  server: options.server,
@@ -58,143 +36,6 @@ export function resolveTargetFromOptions(options, context) {
58
36
  allowInsecureHttp: options.allowInsecureHttp,
59
37
  }, context?.customHome);
60
38
  }
61
- /**
62
- * 统一解析 CLI 命令的目标拓扑与包作用域(run, describe, list 等命令的单一事实源)。
63
- *
64
- * 保证:
65
- * - 优先判定目标拓扑;
66
- * - 远端目标(remote)完全隔离:不执行任何本地目录探测、清单加载或包根寻址;
67
- * - 远端目标动作引用保持原始词法包 ID 拼接(${options.package}/${actionId}),绝不被本地同名目录或清单 ID 篡改;
68
- * - 本地目标按本地封闭技能目录或工程包严格解析,不存在时透明抛出 packageNotFoundError。
69
- */
70
- export function resolveCommandTarget(options, context) {
71
- const resolved = resolveTargetFromOptions(options, context);
72
- if (resolved.type === "remote") {
73
- return {
74
- resolved,
75
- isRemote: true,
76
- localSkill: null,
77
- targetPackageRoot: undefined,
78
- resolveActionRef: (actionId) => {
79
- if (options.package && !actionId.includes("/")) {
80
- return `${options.package}/${actionId}`;
81
- }
82
- return actionId;
83
- },
84
- };
85
- }
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
- return {
97
- resolved,
98
- isRemote: false,
99
- localSkill,
100
- targetPackageRoot: targetPackageRoot || undefined,
101
- resolveActionRef: (actionId) => {
102
- if (!options.package)
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
- },
116
- };
117
- }
118
- /**
119
- * 校验并拒绝复合技能目录用于配置或状态管理命令(单一事实源)。
120
- */
121
- export function assertNotCompositeSkill(packageOption) {
122
- if (!packageOption)
123
- return;
124
- const localSkill = inspectLocalPackageDirectory(packageOption);
125
- if (localSkill?.type === "composite") {
126
- throw new ArgumentError("Multi-package entries do not support config/state management; configure actions using --config and --data-dir.");
127
- }
128
- }
129
- /**
130
- * 统一解析配置与状态命令的目标作用域(支持单包技能目录、本地工程、远端服务与 --profile local)。
131
- */
132
- export function resolveTargetPackageScope(options, context) {
133
- // 1. 目标拓扑解析
134
- const resolved = resolveTargetFromOptions(options, context);
135
- // 2. 远端分支:直接消费 options.package,不触碰本地文件系统
136
- if (resolved.type === "remote") {
137
- return {
138
- resolved,
139
- isRemote: true,
140
- packageId: options.package || "",
141
- root: undefined,
142
- projConfig: undefined,
143
- };
144
- }
145
- // 3. 复合技能目录守卫(单一事实源)仅在本地分支生效
146
- assertNotCompositeSkill(options.package);
147
- // 4. 本地分支:统一解析单包目录、工程包或全局作用域
148
- if (options.package) {
149
- const localSkill = inspectLocalPackageDirectory(options.package);
150
- if (localSkill?.type === "single") {
151
- const pkg = localSkill.packages[0];
152
- return {
153
- resolved,
154
- isRemote: false,
155
- packageId: pkg.packageId,
156
- root: pkg.packageRoot,
157
- projConfig: pkg.projectConfig,
158
- };
159
- }
160
- const root = resolveLocalPackageRoot(options.package);
161
- if (!root) {
162
- throw packageNotFoundError(options.package);
163
- }
164
- const projConfig = loadProjectConfig(root);
165
- return {
166
- resolved,
167
- isRemote: false,
168
- packageId: projConfig.id,
169
- root,
170
- projConfig,
171
- };
172
- }
173
- // 未指定 -P:当前目录工程探测
174
- const root = findProjectRoot();
175
- if (root) {
176
- try {
177
- const projConfig = loadProjectConfig(root);
178
- return {
179
- resolved,
180
- isRemote: false,
181
- packageId: projConfig.id,
182
- root,
183
- projConfig,
184
- };
185
- }
186
- catch {
187
- // 当前目录配置损坏时仍允许全局操作
188
- }
189
- }
190
- return {
191
- resolved,
192
- isRemote: false,
193
- packageId: "global",
194
- root: undefined,
195
- projConfig: undefined,
196
- };
197
- }
198
39
  /**
199
40
  * 高阶服务执行辅助:统一封装「目标解析 → Service 创建 → 业务回调 → try/finally close」样板。
200
41
  *
@@ -215,13 +56,10 @@ export async function withService(options, context, fn, localOptions) {
215
56
  if (localOptions?.requireRemote && resolved.type === "local") {
216
57
  throw new ArgumentError("'ad runs cancel' is only supported for remote execution targets. Use --profile <name> or --server <url>.");
217
58
  }
218
- const localSkill = resolved.type === "local" && options.package
219
- ? inspectLocalPackageDirectory(options.package)
220
- : null;
221
59
  // localRoot 仅在 local 分支求值,避免远端模式下触发包寻址副作用
222
- const localRoot = resolved.type === "local" && !localSkill
60
+ const localRoot = resolved.type === "local"
223
61
  ? (typeof localOptions?.localRoot === "function" ? localOptions.localRoot() : localOptions?.localRoot) ??
224
- (options.package ? resolveLocalPackageRoot(options.package) : findProjectRoot()) ??
62
+ findProjectRoot() ??
225
63
  undefined
226
64
  : undefined;
227
65
  const recoverOrphans = localOptions?.recoverOrphans ?? (localOptions?.ownDataDir === true);
@@ -232,38 +70,20 @@ export async function withService(options, context, fn, localOptions) {
232
70
  insecure: resolved.insecure,
233
71
  allowInsecureHttp: resolved.allowInsecureHttp ?? options.allowInsecureHttp,
234
72
  })
235
- : localSkill
236
- ? await createActionDock({
237
- type: "local",
238
- packages: localSkill.packages.map((pkg) => ({
239
- packageRoot: pkg.packageRoot,
240
- projectConfig: pkg.projectConfig,
241
- dataDir: options.dataDir || context?.dataDir,
242
- })),
243
- customHome: context?.customHome,
244
- dataDir: options.dataDir || context?.dataDir,
245
- platform: createNodePlatform({
246
- customHome: context?.customHome,
247
- dataDir: options.dataDir || context?.dataDir,
248
- }),
249
- autoLoadCurrentProject: false,
250
- scanLinkedPackages: false,
251
- recoverOrphans,
252
- })
253
- : await createActionDock({
254
- type: "local",
255
- projectRoot: localRoot,
73
+ : await createActionDock({
74
+ type: "local",
75
+ projectRoot: localRoot,
76
+ customHome: context?.customHome,
77
+ dataDir: options.dataDir || context?.dataDir,
78
+ platform: createNodePlatform({
256
79
  customHome: context?.customHome,
257
80
  dataDir: options.dataDir || context?.dataDir,
258
- platform: createNodePlatform({
259
- customHome: context?.customHome,
260
- dataDir: options.dataDir || context?.dataDir,
261
- }),
262
- ...(localOptions?.scanLinkedPackages !== undefined
263
- ? { scanLinkedPackages: localOptions.scanLinkedPackages }
264
- : undefined),
265
- recoverOrphans,
266
- });
81
+ }),
82
+ ...(localOptions?.scanLinkedPackages !== undefined
83
+ ? { scanLinkedPackages: localOptions.scanLinkedPackages }
84
+ : undefined),
85
+ recoverOrphans,
86
+ });
267
87
  try {
268
88
  await fn(service, resolved);
269
89
  }
@@ -277,10 +97,6 @@ export async function withService(options, context, fn, localOptions) {
277
97
  */
278
98
  export function resolveLocalPackageRoot(pkgId) {
279
99
  if (pkgId) {
280
- const localSkill = inspectLocalPackageDirectory(pkgId);
281
- if (localSkill) {
282
- return localSkill.root;
283
- }
284
100
  return resolvePackageRoot(pkgId);
285
101
  }
286
102
  return findProjectRoot();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actiondock/cli",
3
- "version": "2.14.0",
3
+ "version": "2.16.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.14.0",
38
- "@actiondock/core": "^2.14.0",
39
- "@actiondock/mcp": "^2.14.0",
40
- "@actiondock/sdk": "^2.14.0",
37
+ "@actiondock/builder": "^2.16.0",
38
+ "@actiondock/core": "^2.16.0",
39
+ "@actiondock/mcp": "^2.16.0",
40
+ "@actiondock/sdk": "^2.16.0",
41
41
  "commander": "^13.1.0",
42
42
  "selfsigned": "^5.5.0"
43
43
  },