@actiondock/cli 2.4.1 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -126,7 +126,7 @@ ad pack
126
126
  | `ad export skill` | 导出 Agent Skill 技能包(支持 `--mode source` 与 `--mode node`) |
127
127
  | `ad link` / `unlink` | 注册或注销工作区全局路由与符号链接 |
128
128
  | `ad profile` | 管理远程执行节点凭证与环境配置 |
129
- | `ad serve` | 启动远程 HTTP 执行调度微服务 |
129
+ | `ad serve` | 启动远程 HTTP/HTTPS 执行调度微服务(原生支持 `--https` 自签名与生产证书) |
130
130
  | `ad mcp` | 以 STDIO 或 HTTP 协议启动 MCP 服务 |
131
131
 
132
132
  ---
@@ -1,7 +1,7 @@
1
1
  import { loadProjectConfig, resolvePackageRoot, } from "@actiondock/core";
2
2
  import { ArgumentError } from "../../errors.js";
3
3
  import { writeStdout } from "../../renderer.js";
4
- import { getEffectiveOptions, withTarget } from "../../utils/index.js";
4
+ import { applyTargetOptions, getEffectiveOptions, withTarget } from "../../utils/index.js";
5
5
  /**
6
6
  * 注册 config delete 子命令:删除配置项。
7
7
  *
@@ -9,15 +9,12 @@ import { getEffectiveOptions, withTarget } from "../../utils/index.js";
9
9
  * @param context 命令行上下文
10
10
  */
11
11
  export function registerConfigDeleteCommand(configCmd, context) {
12
- configCmd
12
+ applyTargetOptions(configCmd
13
13
  .command("delete <key>")
14
14
  .alias("rm")
15
15
  .description("Delete configuration entry")
16
16
  .option("-P, --package <id>", "Target package ID or path")
17
- .option("-g, --global", "Delete from global configuration")
18
- .option("-p, --profile <name>", "Delete on a remote target")
19
- .option("-s, --server <url>", "Remote server URL")
20
- .option("-t, --token <token>", "Auth token for remote server")
17
+ .option("-g, --global", "Delete from global configuration"))
21
18
  .option("--data-dir <path>", "Custom database storage directory")
22
19
  .action(async (key, rawOptions, cmd) => {
23
20
  const options = getEffectiveOptions(rawOptions, cmd);
@@ -1,7 +1,7 @@
1
1
  import { fetchRemoteConfigEnv, isSecretConfigKey, loadProjectConfig, resolveEnvValue, resolvePackageRoot, resolveTarget, } from "@actiondock/core";
2
2
  import { ArgumentError } from "../../errors.js";
3
3
  import { renderConfigEnv, renderResult } from "../../renderer.js";
4
- import { getEffectiveOptions } from "../../utils/index.js";
4
+ import { applyTargetOptions, getEffectiveOptions } from "../../utils/index.js";
5
5
  /**
6
6
  * 注册 config env 子命令:诊断环境变量对声明配置的满足率。
7
7
  *
@@ -9,13 +9,10 @@ import { getEffectiveOptions } from "../../utils/index.js";
9
9
  * @param context 命令行上下文
10
10
  */
11
11
  export function registerConfigEnvCommand(configCmd, context) {
12
- configCmd
12
+ applyTargetOptions(configCmd
13
13
  .command("env [identifier]")
14
14
  .description("Diagnose environment variable satisfaction for declared configuration")
15
- .option("-P, --package <id>", "Target package ID or path")
16
- .option("-p, --profile <name>", "Query on a remote target")
17
- .option("-s, --server <url>", "Remote server URL")
18
- .option("-t, --token <token>", "Auth token for remote server")
15
+ .option("-P, --package <id>", "Target package ID or path"))
19
16
  .option("--json", "Output as JSON")
20
17
  .option("--envelope", "Wrap JSON output in standard envelope")
21
18
  .action(async (identifier, rawOptions, cmd) => {
@@ -26,9 +23,14 @@ export function registerConfigEnvCommand(configCmd, context) {
26
23
  profile: options.profile,
27
24
  server: options.server,
28
25
  token: options.token,
26
+ insecure: options.insecure,
27
+ allowInsecureHttp: options.allowInsecureHttp,
29
28
  }, context?.customHome);
30
29
  if (target.type === "remote") {
31
- const res = await fetchRemoteConfigEnv(target.serverUrl, target.token, targetPkg);
30
+ const res = await fetchRemoteConfigEnv(target.serverUrl, target.token, targetPkg, {
31
+ allowInsecureHttp: Boolean(options.allowInsecureHttp),
32
+ insecure: target.insecure,
33
+ });
32
34
  renderResult(res, {
33
35
  json: options.json,
34
36
  envelope: options.envelope,
@@ -1,7 +1,7 @@
1
1
  import { fetchRemoteConfig, isSecretConfigKey, loadProjectConfig, maskSecretValue, resolveEnvValue, resolvePackageRoot, resolveTarget, } from "@actiondock/core";
2
2
  import { ArgumentError } from "../../errors.js";
3
3
  import { renderResult } from "../../renderer.js";
4
- import { getEffectiveOptions, withTarget } from "../../utils/index.js";
4
+ import { applyTargetOptions, getEffectiveOptions, withTarget } from "../../utils/index.js";
5
5
  /**
6
6
  * 解析远端目标拓扑信息(仅信息解析,不创建 Target 实例)。
7
7
  */
@@ -10,6 +10,8 @@ function resolveRemoteTargetInfo(options, context) {
10
10
  profile: options.profile,
11
11
  server: options.server,
12
12
  token: options.token,
13
+ insecure: options.insecure,
14
+ allowInsecureHttp: options.allowInsecureHttp,
13
15
  }, context?.customHome);
14
16
  }
15
17
  /**
@@ -19,14 +21,11 @@ function resolveRemoteTargetInfo(options, context) {
19
21
  * @param context 命令行上下文
20
22
  */
21
23
  export function registerConfigGetCommand(configCmd, context) {
22
- configCmd
24
+ applyTargetOptions(configCmd
23
25
  .command("get <key>")
24
26
  .description("Get configuration value for key")
25
27
  .option("-P, --package <id>", "Target package ID or path")
26
- .option("-g, --global", "Get from global configuration")
27
- .option("-p, --profile <name>", "Query config on a remote target")
28
- .option("-s, --server <url>", "Remote server URL")
29
- .option("-t, --token <token>", "Auth token for remote server")
28
+ .option("-g, --global", "Get from global configuration"))
30
29
  .option("--reveal, --show-secrets", "Reveal plain text values for secrets")
31
30
  .option("--data-dir <path>", "Custom database storage directory")
32
31
  .option("--json", "Output as JSON")
@@ -40,7 +39,10 @@ export function registerConfigGetCommand(configCmd, context) {
40
39
  // 远端服务分支不经过 Target 门面,直接查询远端配置接口
41
40
  const target = resolveRemoteTargetInfo(options, context);
42
41
  if (target.type === "remote") {
43
- const res = await fetchRemoteConfig(target.serverUrl, target.token, options.package);
42
+ const res = await fetchRemoteConfig(target.serverUrl, target.token, options.package, {
43
+ allowInsecureHttp: Boolean(options.allowInsecureHttp),
44
+ insecure: target.insecure,
45
+ });
44
46
  const val = res.values?.[key];
45
47
  const isSecret = isSecretConfigKey(key, res.declared?.[key]);
46
48
  const displayValue = !reveal && isSecret && val !== undefined ? maskSecretValue(val) : val;
@@ -1,7 +1,7 @@
1
1
  import { fetchRemoteConfig, filterWithFallbackInfo, findProjectRoot, isSecretConfigKey, loadProjectConfig, maskSecretValue, resolveEnvValue, resolvePackageRoot, resolveTarget, } from "@actiondock/core";
2
2
  import { ArgumentError } from "../../errors.js";
3
3
  import { renderConfigList, renderResult } from "../../renderer.js";
4
- import { getEffectiveOptions, resolveIntent, withTarget } from "../../utils/index.js";
4
+ import { applyTargetOptions, getEffectiveOptions, resolveIntent, withTarget } from "../../utils/index.js";
5
5
  /**
6
6
  * 构造单条配置项的展示视图(含掩码决策,reveal 为假且为敏感键时打码)。
7
7
  * 掩码决策收敛于命令层,renderer 仅负责值的字符串格式化。
@@ -23,14 +23,11 @@ function toDisplayEntry(key, rawValue, source, isSecret, reveal, description) {
23
23
  * @param context 命令行上下文
24
24
  */
25
25
  export function registerConfigListCommand(configCmd, context) {
26
- configCmd
26
+ applyTargetOptions(configCmd
27
27
  .command("list [patterns...]")
28
28
  .description("List configuration entries (Global & Project)")
29
29
  .option("-P, --package <id>", "Target package ID or path")
30
- .option("-g, --global", "Show only global configurations")
31
- .option("-p, --profile <name>", "Query config on a remote target")
32
- .option("-s, --server <url>", "Remote server URL")
33
- .option("-t, --token <token>", "Auth token for remote server")
30
+ .option("-g, --global", "Show only global configurations"))
34
31
  .option("-i, --intent <pattern>", "Regex or fuzzy intent filter; falls back to full list when no match")
35
32
  .option("--reveal, --show-secrets", "Reveal plain text values for secrets")
36
33
  .option("--no-fallback", "Disable fallback to full list when no items match intent")
@@ -43,9 +40,18 @@ export function registerConfigListCommand(configCmd, context) {
43
40
  const shouldFallback = options.fallback !== false;
44
41
  const reveal = Boolean(options.reveal || options.showSecrets);
45
42
  // 远端服务分支不经过 Target 门面,直接查询远端配置接口
46
- const remoteTargetInfo = resolveTarget({ profile: options.profile, server: options.server, token: options.token }, context?.customHome);
43
+ const remoteTargetInfo = resolveTarget({
44
+ profile: options.profile,
45
+ server: options.server,
46
+ token: options.token,
47
+ insecure: options.insecure,
48
+ allowInsecureHttp: options.allowInsecureHttp,
49
+ }, context?.customHome);
47
50
  if (remoteTargetInfo.type === "remote") {
48
- const res = await fetchRemoteConfig(remoteTargetInfo.serverUrl, remoteTargetInfo.token, options.package);
51
+ const res = await fetchRemoteConfig(remoteTargetInfo.serverUrl, remoteTargetInfo.token, options.package, {
52
+ allowInsecureHttp: Boolean(options.allowInsecureHttp),
53
+ insecure: remoteTargetInfo.insecure,
54
+ });
49
55
  const entries = Object.entries(res.values || {}).map(([k, v]) => toDisplayEntry(k, v, "remote", isSecretConfigKey(k, res.declared?.[k]), reveal, res.declared?.[k]?.description || ""));
50
56
  renderResult(entries, {
51
57
  json: options.json,
@@ -1,7 +1,7 @@
1
1
  import { isSecretConfigKey, loadProjectConfig, resolvePackageRoot, } from "@actiondock/core";
2
2
  import { ArgumentError } from "../../errors.js";
3
3
  import { writeStdout } from "../../renderer.js";
4
- import { getEffectiveOptions, withTarget } from "../../utils/index.js";
4
+ import { applyTargetOptions, getEffectiveOptions, withTarget } from "../../utils/index.js";
5
5
  import { resolveConfigValueInput } from "../../prompt.js";
6
6
  /**
7
7
  * 注册 config set 子命令:写入配置值(支持标准输入与安全提示)。
@@ -10,14 +10,11 @@ import { resolveConfigValueInput } from "../../prompt.js";
10
10
  * @param context 命令行上下文
11
11
  */
12
12
  export function registerConfigSetCommand(configCmd, context) {
13
- configCmd
13
+ applyTargetOptions(configCmd
14
14
  .command("set <key> [value]")
15
15
  .description("Set configuration value (supports stdin and secure prompt)")
16
16
  .option("-P, --package <id>", "Target package ID or path")
17
- .option("-g, --global", "Set in global configuration")
18
- .option("-p, --profile <name>", "Configure on a remote target")
19
- .option("-s, --server <url>", "Remote server URL")
20
- .option("-t, --token <token>", "Auth token for remote server")
17
+ .option("-g, --global", "Set in global configuration"))
21
18
  .option("--stdin", "Read value from standard input")
22
19
  .option("--data-dir <path>", "Custom database storage directory")
23
20
  .action(async (key, rawVal, rawOptions, cmd) => {
@@ -1,6 +1,6 @@
1
1
  import { ArgumentError, ExecutionError } from "../errors.js";
2
2
  import { renderActionDetail, renderResult } from "../renderer.js";
3
- import { getEffectiveOptions, resolveLocalPackageRoot, withTarget, } from "../utils/index.js";
3
+ import { applyTargetOptions, getEffectiveOptions, resolveLocalPackageRoot, withTarget, } from "../utils/index.js";
4
4
  /**
5
5
  * 挂载 describe 子命令至指定 Commander 节点。
6
6
  *
@@ -8,14 +8,12 @@ import { getEffectiveOptions, resolveLocalPackageRoot, withTarget, } from "../ut
8
8
  * @param context 命令行上下文
9
9
  */
10
10
  export function attachDescribeCommand(parent, context) {
11
- return parent
11
+ const cmd = parent
12
12
  .command("describe <id>")
13
13
  .alias("show")
14
14
  .description("Show action definition, schema, and description")
15
- .option("-P, --package <id>", "Target package ID or path")
16
- .option("-p, --profile <name>", "Query against a specific profile")
17
- .option("-s, --server <url>", "Remote server URL")
18
- .option("-t, --token <token>", "Auth token for remote server")
15
+ .option("-P, --package <id>", "Target package ID or path");
16
+ return applyTargetOptions(cmd)
19
17
  .option("--json", "Output as JSON")
20
18
  .option("--envelope", "Wrap JSON output in standard envelope")
21
19
  .option("--data-dir <path>", "Custom database storage directory")
@@ -1,15 +1,13 @@
1
1
  import { fetchRemoteDoctor, resolveTarget, runDoctorChecks } from "@actiondock/core";
2
2
  import { ExecutionError } from "../errors.js";
3
3
  import { renderResult } from "../renderer.js";
4
- import { getEffectiveOptions } from "../utils/index.js";
4
+ import { applyTargetOptions, getEffectiveOptions } from "../utils/index.js";
5
5
  export function registerDoctorCommand(program, context) {
6
- program
6
+ const cmd = program
7
7
  .command("doctor")
8
8
  .description("Check ActionDock environment, registry health, and project diagnostics")
9
- .option("-P, --package <id|path>", "Target package ID or directory path for project diagnostics")
10
- .option("-p, --profile <name>", "Query doctor diagnostics on a remote target")
11
- .option("-s, --server <url>", "Remote server URL")
12
- .option("-t, --token <token>", "Auth token for remote server")
9
+ .option("-P, --package <id|path>", "Target package ID or directory path for project diagnostics");
10
+ applyTargetOptions(cmd)
13
11
  .option("--json", "Output diagnostics report in JSON format")
14
12
  .option("--envelope", "Wrap JSON output in standard envelope")
15
13
  .action(async (rawOptions, cmd) => {
@@ -19,10 +17,14 @@ export function registerDoctorCommand(program, context) {
19
17
  profile: options.profile,
20
18
  server: options.server,
21
19
  token: options.token,
20
+ insecure: options.insecure,
22
21
  }, context?.customHome);
23
22
  let report;
24
23
  if (target.type === "remote") {
25
- const remoteRes = await fetchRemoteDoctor(target.serverUrl, target.token, options.package);
24
+ const remoteRes = await fetchRemoteDoctor(target.serverUrl, target.token, options.package, {
25
+ allowInsecureHttp: Boolean(options.allowInsecureHttp),
26
+ insecure: target.insecure,
27
+ });
26
28
  report = remoteRes.report || remoteRes;
27
29
  }
28
30
  else {
@@ -2,7 +2,7 @@ import { fetchRemoteInfo, filterWithFallbackInfo, getRegistryStatus, resolvePack
2
2
  import { ArgumentError, ExecutionError } from "../errors.js";
3
3
  import { projectDetailToJson, renderAggregatedPackages, renderProjectDetail, renderRegistryTree, renderResult, } from "../renderer.js";
4
4
  import { getProjectDetailInfo, scanLocalAggregatedPackages } from "../services/index.js";
5
- import { getEffectiveOptions, resolveFallbackStrategy, resolveIntent } from "../utils/index.js";
5
+ import { applyTargetOptions, getEffectiveOptions, resolveFallbackStrategy, resolveIntent } from "../utils/index.js";
6
6
  /**
7
7
  * 将远端信息响应归一为本地工程详情视图。
8
8
  */
@@ -41,15 +41,13 @@ function renderProjectDetailOutput(detail, options) {
41
41
  * @param context 命令行上下文
42
42
  */
43
43
  export function registerInfoCommand(program, context) {
44
- program
44
+ const cmd = program
45
45
  .command("info [patterns...]")
46
46
  .description("Display information about current project, linked package, or remote target")
47
47
  .option("-i, --intent <pattern>", "Regex or fuzzy intent filter; falls back to full list when no match")
48
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")
49
+ .option("--tree", "Display packages in hierarchical tree view grouped by workspace");
50
+ applyTargetOptions(cmd)
53
51
  .option("--fallback", "Enable fallback to full list when no items match intent")
54
52
  .option("--no-fallback", "Disable fallback to full list when no items match intent")
55
53
  .option("--json", "Output information as JSON")
@@ -65,12 +63,15 @@ export function registerInfoCommand(program, context) {
65
63
  profile: options.profile,
66
64
  server: options.server,
67
65
  token: options.token,
66
+ insecure: options.insecure,
68
67
  }, context?.customHome);
69
68
  if (target.type === "remote") {
70
69
  const remoteInfo = await fetchRemoteInfo(target.serverUrl, target.token, {
71
70
  intent: effectiveIntent,
72
71
  package: options.package,
73
72
  tree: Boolean(options.tree),
73
+ allowInsecureHttp: Boolean(options.allowInsecureHttp),
74
+ insecure: target.insecure,
74
75
  });
75
76
  if (isMachine) {
76
77
  renderResult(remoteInfo, outOpts);
@@ -1,7 +1,7 @@
1
1
  import { filterWithFallbackInfo } from "@actiondock/core";
2
2
  import { ArgumentError } from "../errors.js";
3
3
  import { renderActionList, renderResult } from "../renderer.js";
4
- import { getEffectiveOptions, resolveFallbackStrategy, resolveIntent, resolveLocalPackageRoot, withTarget } from "../utils/index.js";
4
+ import { applyTargetOptions, getEffectiveOptions, resolveFallbackStrategy, resolveIntent, resolveLocalPackageRoot, withTarget, } from "../utils/index.js";
5
5
  /**
6
6
  * 挂载 list 子命令至指定 Commander 节点。
7
7
  *
@@ -9,14 +9,12 @@ import { getEffectiveOptions, resolveFallbackStrategy, resolveIntent, resolveLoc
9
9
  * @param context 命令行上下文
10
10
  */
11
11
  export function attachListCommand(parent, context) {
12
- return parent
12
+ const cmd = parent
13
13
  .command("list [patterns...]")
14
14
  .description("List actions in current project, linked packages, or remote profile")
15
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")
16
+ .option("-P, --package <id>", "Target package ID or path");
17
+ return applyTargetOptions(cmd)
20
18
  .option("--fallback", "Enable fallback to full list when no items match intent")
21
19
  .option("--no-fallback", "Disable fallback to full list when no items match intent")
22
20
  .option("--json", "Output as JSON")
@@ -3,7 +3,7 @@ import { dirname, isAbsolute, relative, resolve } from "node:path";
3
3
  import { assertPathWithinRoot, fetchRemotePlaybooks, fetchRemotePlaybookShow, filterWithFallbackInfo, findProjectRoot, listLinkedPackages, loadActions, loadManifest, loadPlaybooks, loadProjectConfig, resolveActionProject, resolvePackageRoot, resolvePlaybookProject, resolveTarget, saveManifest, } from "@actiondock/core";
4
4
  import { ArgumentError, CliError, ExecutionError } from "../errors.js";
5
5
  import { renderPlaybookDetail, renderPlaybookList, renderResult, writeStdout, } from "../renderer.js";
6
- import { getEffectiveOptions, resolveIntent } from "../utils/index.js";
6
+ import { applyTargetOptions, getEffectiveOptions, resolveIntent } from "../utils/index.js";
7
7
  /**
8
8
  * 注册 Playbook 命令集合(list, show, validate, create)。
9
9
  *
@@ -15,14 +15,11 @@ export function registerPlaybookCommands(program, context) {
15
15
  .command("playbook")
16
16
  .description("Manage task Playbooks (Task SOPs for AI Agents)");
17
17
  // playbook list
18
- pbCmd
18
+ applyTargetOptions(pbCmd
19
19
  .command("list [patterns...]")
20
20
  .description("List playbooks in current project or linked packages")
21
21
  .option("-i, --intent <pattern>", "Regex or fuzzy intent filter; falls back to full list when no match")
22
- .option("-P, --package <id>", "Target package ID or path")
23
- .option("-p, --profile <name>", "Query against a specific profile")
24
- .option("-s, --server <url>", "Remote server URL")
25
- .option("-t, --token <token>", "Auth token for remote server")
22
+ .option("-P, --package <id>", "Target package ID or path"))
26
23
  .option("--no-fallback", "Disable fallback to full list when no items match intent")
27
24
  .option("--json", "Output as JSON")
28
25
  .option("--envelope", "Wrap JSON output in standard envelope")
@@ -36,11 +33,15 @@ export function registerPlaybookCommands(program, context) {
36
33
  profile: options.profile,
37
34
  server: options.server,
38
35
  token: options.token,
36
+ insecure: options.insecure,
37
+ allowInsecureHttp: options.allowInsecureHttp,
39
38
  }, context?.customHome);
40
39
  if (target.type === "remote") {
41
40
  const remotePbs = await fetchRemotePlaybooks(target.serverUrl, target.token, {
42
41
  intent: effectiveIntent,
43
42
  package: options.package,
43
+ allowInsecureHttp: Boolean(options.allowInsecureHttp),
44
+ insecure: target.insecure,
44
45
  });
45
46
  renderResult(remotePbs, {
46
47
  json: options.json,
@@ -162,13 +163,10 @@ export function registerPlaybookCommands(program, context) {
162
163
  });
163
164
  });
164
165
  // playbook show <id>
165
- pbCmd
166
+ applyTargetOptions(pbCmd
166
167
  .command("show <id>")
167
168
  .description("Show playbook content and metadata")
168
- .option("-P, --package <id>", "Target package ID or path")
169
- .option("-p, --profile <name>", "Query against a specific profile")
170
- .option("-s, --server <url>", "Remote server URL")
171
- .option("-t, --token <token>", "Auth token for remote server")
169
+ .option("-P, --package <id>", "Target package ID or path"))
172
170
  .option("--json", "Output as JSON")
173
171
  .option("--envelope", "Wrap JSON output in standard envelope")
174
172
  .action(async (id, rawOptions, cmd) => {
@@ -181,9 +179,14 @@ export function registerPlaybookCommands(program, context) {
181
179
  profile: options.profile,
182
180
  server: options.server,
183
181
  token: options.token,
182
+ insecure: options.insecure,
183
+ allowInsecureHttp: options.allowInsecureHttp,
184
184
  }, context?.customHome);
185
185
  if (target.type === "remote") {
186
- const detail = await fetchRemotePlaybookShow(target.serverUrl, id, target.token);
186
+ const detail = await fetchRemotePlaybookShow(target.serverUrl, id, target.token, {
187
+ allowInsecureHttp: Boolean(options.allowInsecureHttp),
188
+ insecure: target.insecure,
189
+ });
187
190
  renderResult(detail, {
188
191
  json: options.json,
189
192
  envelope: options.envelope,
@@ -1,4 +1,4 @@
1
- import { addProfile, checkRemoteHealth, filterWithFallbackInfo, getProfile, listProfiles, loadProfiles, maskSecretValue, removeProfile, resolveProfileToken, resolveTarget, toSnakeUpperCase, useProfile, } from "@actiondock/core";
1
+ import { addProfile, checkRemoteHealth, filterWithFallbackInfo, getProfile, listProfiles, loadProfiles, maskSecretValue, removeProfile, resolveProfileToken, resolveTarget, toSnakeUpperCase, updateProfile, useProfile, } from "@actiondock/core";
2
2
  import { ArgumentError, CliError, ExecutionError, } from "../errors.js";
3
3
  import { renderResult, writeStdout } from "../renderer.js";
4
4
  import { getEffectiveOptions, resolveFallbackStrategy, resolveIntent } from "../utils/index.js";
@@ -34,9 +34,10 @@ export function registerProfileCommands(program) {
34
34
  tokenConfigured: resolved.source !== "none",
35
35
  tokenSource: resolved.source,
36
36
  token: reveal ? resolved.token : (resolved.token ? maskSecretValue(resolved.token) : undefined),
37
+ insecure: Boolean(p.entry.insecure),
37
38
  };
38
39
  });
39
- const filterRes = filterWithFallbackInfo(enriched, effectiveIntent, [(p) => p.name, (p) => p.serverUrl, (p) => p.description, (p) => p.tokenSource], shouldFallback);
40
+ const filterRes = filterWithFallbackInfo(enriched, effectiveIntent, [(p) => p.name, (p) => p.serverUrl, (p) => p.description, (p) => p.tokenSource, (p) => (p.insecure ? "insecure" : "")], shouldFallback);
40
41
  if (filterRes.isFallback && isMachine) {
41
42
  renderResult({ items: filterRes.items, isFallback: true, matchedCount: 0 }, { json: options.json, envelope: options.envelope });
42
43
  return;
@@ -67,8 +68,9 @@ export function registerProfileCommands(program) {
67
68
  if (reveal && item.token) {
68
69
  tokenInfo += ` = ${item.token}`;
69
70
  }
71
+ const insecureTag = item.insecure ? " [insecure]" : "";
70
72
  const desc = item.description ? ` - ${item.description}` : "";
71
- lines.push(`${currentMarker}${item.name.padEnd(20)} ${item.serverUrl}${tokenInfo}${desc}`);
73
+ lines.push(`${currentMarker}${item.name.padEnd(20)} ${item.serverUrl}${tokenInfo}${insecureTag}${desc}`);
72
74
  }
73
75
  lines.push("\nUse 'ad profile use <name>' to switch or 'ad run <action> --profile <name>' to execute on a specific target.");
74
76
  return lines.join("\n");
@@ -89,6 +91,7 @@ export function registerProfileCommands(program) {
89
91
  .option("-t, --token <token>", "Authentication token for the remote server (deprecated; prefer --token-env)")
90
92
  .option("--token-env <env>", "Environment variable name containing the authentication token")
91
93
  .option("-d, --desc <description>", "Description of this profile/machine")
94
+ .option("-k, --insecure", "Allow insecure server connections (skip TLS certificate validation)")
92
95
  .action((name, options) => {
93
96
  try {
94
97
  if (options.token) {
@@ -99,8 +102,44 @@ export function registerProfileCommands(program) {
99
102
  token: options.token,
100
103
  tokenEnv: options.tokenEnv,
101
104
  description: options.desc,
105
+ insecure: options.insecure ? true : undefined,
102
106
  });
103
- writeStdout(`[OK] Profile '${name}' configured for server: ${options.server}`);
107
+ writeStdout(`[OK] Profile '${name}' configured for server: ${options.server}${options.insecure ? " (insecure: true)" : ""}`);
108
+ }
109
+ catch (err) {
110
+ throw new ExecutionError(err.message);
111
+ }
112
+ });
113
+ // ad profile update <name>
114
+ profileCmd
115
+ .command("update <name>")
116
+ .description("Update an existing remote execution profile")
117
+ .option("-s, --server <url>", "Remote ActionDock server URL (e.g. http://1.2.3.4:5177)")
118
+ .option("-t, --token <token>", "Authentication token for the remote server (deprecated; prefer --token-env)")
119
+ .option("--token-env <env>", "Environment variable name containing the authentication token")
120
+ .option("-d, --desc <description>", "Description of this profile/machine")
121
+ .option("-k, --insecure", "Allow insecure server connections (skip TLS certificate validation)")
122
+ .option("--no-insecure", "Enforce strict TLS certificate validation")
123
+ .action((name, options) => {
124
+ try {
125
+ if (options.token) {
126
+ console.warn("Warning: storing tokens directly in profiles.json is deprecated. Use --token-env or standard environment variables (e.g. ACTIONDOCK_<PROFILE>_TOKEN) instead.");
127
+ }
128
+ let insecure;
129
+ if (options.insecure === true) {
130
+ insecure = true;
131
+ }
132
+ else if (options.insecure === false) {
133
+ insecure = false;
134
+ }
135
+ updateProfile(name, {
136
+ serverUrl: options.server,
137
+ token: options.token,
138
+ tokenEnv: options.tokenEnv,
139
+ description: options.desc,
140
+ insecure,
141
+ });
142
+ writeStdout(`[OK] Profile '${name}' updated`);
104
143
  }
105
144
  catch (err) {
106
145
  throw new ExecutionError(err.message);
@@ -148,6 +187,7 @@ export function registerProfileCommands(program) {
148
187
  tokenSource: resolved.source,
149
188
  tokenEnv: entry?.tokenEnv,
150
189
  token: displayToken,
190
+ insecure: Boolean(entry?.insecure),
151
191
  description: entry?.description || "",
152
192
  };
153
193
  renderResult(data, {
@@ -174,6 +214,9 @@ export function registerProfileCommands(program) {
174
214
  if (data.tokenConfigured) {
175
215
  lines.push(`Token Value: ${data.token}`);
176
216
  }
217
+ if (data.serverUrl !== "local") {
218
+ lines.push(`TLS Verify: ${data.insecure ? "Disabled (Insecure)" : "Strict (Default)"}`);
219
+ }
177
220
  if (data.description) {
178
221
  lines.push(`Description: ${data.description}`);
179
222
  }
@@ -215,12 +258,18 @@ export function registerProfileCommands(program) {
215
258
  profileCmd
216
259
  .command("test [name]")
217
260
  .description("Test connection latency and health of a profile")
261
+ .option("-k, --insecure", "Allow insecure TLS connections (skip TLS certificate validation)")
262
+ .option("--allow-insecure-http", "Allow insecure HTTP connections with auth token")
218
263
  .option("--json", "Output as JSON")
219
264
  .option("--envelope", "Wrap JSON output in standard envelope")
220
265
  .action(async (name, rawOptions, cmd) => {
221
266
  try {
222
267
  const options = getEffectiveOptions(rawOptions, cmd);
223
- const target = resolveTarget({ profile: name });
268
+ const target = resolveTarget({
269
+ profile: name,
270
+ insecure: options.insecure !== undefined ? Boolean(options.insecure) : undefined,
271
+ allowInsecureHttp: options.allowInsecureHttp,
272
+ });
224
273
  if (target.type === "local") {
225
274
  renderResult({ ok: true, type: "local", message: "Local execution" }, {
226
275
  json: options.json,
@@ -229,7 +278,10 @@ export function registerProfileCommands(program) {
229
278
  });
230
279
  return;
231
280
  }
232
- const health = await checkRemoteHealth(target.serverUrl, target.token);
281
+ const health = await checkRemoteHealth(target.serverUrl, target.token, 5000, {
282
+ allowInsecureHttp: Boolean(options.allowInsecureHttp),
283
+ insecure: target.insecure,
284
+ });
233
285
  renderResult(health, {
234
286
  json: options.json,
235
287
  envelope: options.envelope,
@@ -2,7 +2,7 @@ import { readFileSync } from "node:fs";
2
2
  import { parseDuration, resolveTarget } from "@actiondock/core";
3
3
  import { ArgumentError, ExecutionError, SigintError } from "../errors.js";
4
4
  import { writeStdout } from "../renderer.js";
5
- import { getEffectiveOptions, resolveLocalPackageRoot, withTarget } from "../utils/index.js";
5
+ import { applyTargetOptions, getEffectiveOptions, resolveLocalPackageRoot, withTarget, } from "../utils/index.js";
6
6
  /**
7
7
  * 统一执行 Action 核心逻辑(使用 ActionDockTarget 门面)。
8
8
  */
@@ -58,6 +58,7 @@ export async function executeAction(id, options, context) {
58
58
  profile: options.profile,
59
59
  server: options.server,
60
60
  token: options.token,
61
+ insecure: options.insecure,
61
62
  }, context?.customHome);
62
63
  if (resolvedTopology.type === "local" && options.async) {
63
64
  throw new ExecutionError("Async execution requires a long-running ActionDock server.\nUse --profile, --server, or start 'ad serve'.");
@@ -123,16 +124,14 @@ export async function executeAction(id, options, context) {
123
124
  * @param context 命令行上下文
124
125
  */
125
126
  export function attachRunCommand(parent, context) {
126
- return parent
127
+ const cmd = parent
127
128
  .command("run <id>")
128
129
  .description("Execute an action (from current project, linked packages, or remote profile)")
129
130
  .option("-P, --package <id>", "Target package ID or path")
130
131
  .option("-i, --input <json>", "Input as JSON string")
131
132
  .option("-f, --input-file <path>", "Input from JSON file")
132
- .option("-c, --config <key=value...>", "Temporary config override (repeatable)")
133
- .option("-p, --profile <name>", "Execute against a specific profile")
134
- .option("-s, --server <url>", "Remote server URL")
135
- .option("-t, --token <token>", "Auth token for remote server")
133
+ .option("-c, --config <key=value...>", "Temporary config override (repeatable)");
134
+ return applyTargetOptions(cmd)
136
135
  .option("--timeout <duration>", "Execution timeout (e.g. 30s, 5m, 500ms)")
137
136
  .option("--request-id <id>", "Idempotency request ID for deduplication")
138
137
  .option("--async", "Execute asynchronously in background (requires remote server or profile)")