@actionway/cli 0.18.0 → 0.18.1

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
@@ -3,6 +3,28 @@
3
3
  actionway CLI 的本地端壳(迁移文档 `docs/DL_TO_ACTIONWAY_CLI_MIGRATION.md` §3.2 / §3.4):
4
4
  终端用户在自己机器上的 Coding Agent(Codex / Claude Code)通过它调用 Actionway 能力。
5
5
 
6
+ ## 安装
7
+
8
+ 要求 Node.js 20 或更新版本;CI 覆盖 Node.js 20 和 22。Windows 使用 npm 生成的 `.cmd` 入口,避免 PowerShell
9
+ 执行策略阻止 `.ps1` shim:
10
+
11
+ ```powershell
12
+ npm.cmd install --global @actionway/cli@latest
13
+ actionway.cmd doctor
14
+ actionway.cmd init
15
+ ```
16
+
17
+ macOS / Linux:
18
+
19
+ ```bash
20
+ npm install --global @actionway/cli@latest
21
+ actionway doctor
22
+ actionway init
23
+ ```
24
+
25
+ `doctor` 只输出脱敏后的 Node/npm、registry、global PATH、配置目录、loopback
26
+ 和 OAuth discovery 检查;它不会修改 npm、代理、CA、PATH 或 PowerShell policy。
27
+
6
28
  - **auth 唯一途径:Clerk OAuth(PKCE)**。issuer / client_id 运行时从
7
29
  `{public origin}/api/auth/cli-config` 发现;浏览器授权走随机 `127.0.0.1` 回调端口;
8
30
  凭据存 `~/.actionway/credentials.json`(0600 owner-only 文件,可用
@@ -11,7 +33,7 @@ actionway CLI 的本地端壳(迁移文档 `docs/DL_TO_ACTIONWAY_CLI_MIGRATION
11
33
  - `tools search / inspect / call` 是长尾 capability 的规范发现与执行面;
12
34
  - 高频 typed command 继续提供参数与文件 UX,但投影到同一个 Tool Call;
13
35
  - `account usage / wallet / transactions` 只读 Actionway Business 数据;
14
- - 本壳另有 `init` / `update` / `login` / `logout` / `whoami`,cli-core
36
+ - 本壳另有 `init` / `doctor` / `update` / `login` / `logout` / `whoami`,cli-core
15
37
  继续提供 wait/poll 与已冻结的共享 typed commands。
16
38
 
17
39
  - **账户响应无本地换算**:`account` 响应同时保留精确整数 micros 与可直接展示的 USD 十进制字符串;钱包包含快照更新时间,交易包含状态、说明、完成时间和 signed balance delta。Usage/Transactions 支持 ISO 时间窗口、领域筛选和 opaque cursor,返回的 summary 覆盖完整筛选集合而非当前页。CLI 不自行换算资金,也不改变服务端返回的字段语义。
@@ -11,7 +11,7 @@ Use the installed `actionway` CLI and its server-backed Capability Registry as t
11
11
 
12
12
  ## Prepare
13
13
 
14
- 1. Run Actionway commands with network access and permission to read `~/.actionway`. If a sandboxed command reports `E_NOT_AUTHENTICATED` for an already logged-in user, rerun the same command with host permission before starting login again.
14
+ 1. Run Actionway commands with network access and permission to read `~/.actionway`. On Windows, use the generated `actionway.cmd` entrypoint when PowerShell policy blocks `actionway.ps1`. If setup or update fails, run `actionway.cmd doctor` on Windows or `actionway doctor` elsewhere from the host terminal and preserve its failed check IDs; do not weaken registry, proxy, CA, PATH, or PowerShell policy without user approval. If a sandboxed command reports `E_NOT_AUTHENTICATED` for an already logged-in user, rerun the same command with host permission before starting login again.
15
15
  2. If the user supplied a pending `job_ref`, skip updating and immediately run `actionway wait --job-ref=<uuid>`.
16
16
  3. Otherwise, once per Agent session, run `actionway update --check`. Continue with the installed CLI if the check is unavailable. If an update is available, run `actionway update` before starting a new call, then refresh this Skill from the returned `skill_source` for the next Agent session.
17
17
  4. Compare this Skill's `metadata.version` with successful command output `cli_version`. If they differ, replace the installed Actionway Skill with the directory at `skill_source`, tell the user once that the refreshed instructions apply from the next session, and continue the current task. Follow any output `notes`, especially minimum-version instructions.
package/dist/index.js CHANGED
@@ -3040,6 +3040,9 @@ function exitCodeFor(code) {
3040
3040
  case "E_FILE_MISSING":
3041
3041
  case "E_FILE_PARSE":
3042
3042
  case "E_UNKNOWN_OPTION":
3043
+ case "E_NODE_UNSUPPORTED":
3044
+ case "E_NPM_NOT_FOUND":
3045
+ case "E_GLOBAL_PREFIX":
3043
3046
  case "E_DEPRECATED":
3044
3047
  return 2;
3045
3048
  default:
@@ -12145,7 +12148,6 @@ import { createServer } from "node:http";
12145
12148
  import { join as join2 } from "node:path";
12146
12149
 
12147
12150
  // src/lib/update-state.ts
12148
- import { execFileSync, spawnSync } from "node:child_process";
12149
12151
  import { chmodSync, existsSync, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
12150
12152
  import { homedir } from "node:os";
12151
12153
  import { join } from "node:path";
@@ -12175,6 +12177,68 @@ function readOwnVersion(moduleUrl = import.meta.url) {
12175
12177
  throw new Error(`cannot determine the installed ${PACKAGE_NAME} version`);
12176
12178
  }
12177
12179
 
12180
+ // src/lib/npm-runner.ts
12181
+ import { spawnSync } from "node:child_process";
12182
+ var DEFAULT_MAX_BUFFER = 1024 * 1024;
12183
+ function npmInvocation(args, platform = process.platform, env = process.env) {
12184
+ if (platform === "win32") {
12185
+ const commandProcessor = (env.ComSpec ?? env.COMSPEC ?? "cmd.exe").trim().replace(/^"|"$/g, "") || "cmd.exe";
12186
+ return {
12187
+ command: commandProcessor,
12188
+ args: ["/d", "/s", "/c", "npm.cmd", ...args]
12189
+ };
12190
+ }
12191
+ return { command: "npm", args: [...args] };
12192
+ }
12193
+ function runNpm(args, options = {}) {
12194
+ const env = options.env ?? process.env;
12195
+ const invocation = npmInvocation(args, options.platform ?? process.platform, env);
12196
+ return spawnSync(invocation.command, invocation.args, {
12197
+ encoding: "utf8",
12198
+ env,
12199
+ stdio: ["ignore", "pipe", "pipe"],
12200
+ timeout: options.timeoutMs ?? 15e3,
12201
+ maxBuffer: DEFAULT_MAX_BUFFER,
12202
+ windowsHide: true
12203
+ });
12204
+ }
12205
+ function sanitizeDetail(value) {
12206
+ return value.replace(/(\/\/[^:\s]+:)(?:_authToken|_password|username)=[^\s]+/gi, "$1[redacted]").replace(/((?:_authToken|_password|username)\s*[=:]\s*)[^\s]+/gi, "$1[redacted]").replace(/(authorization\s*:\s*(?:bearer|basic)\s+)[^\s]+/gi, "$1[redacted]").replace(/(https?:\/\/)[^@/\s]+@/gi, "$1[redacted]@").trim().slice(0, 500);
12207
+ }
12208
+ function resultDetail(result) {
12209
+ const error = result.error instanceof Error ? result.error.message : "";
12210
+ return sanitizeDetail(error || result.stderr || result.stdout || `exit ${result.status ?? "signal"}`);
12211
+ }
12212
+ function npmFailure(result, operation) {
12213
+ const detail = resultDetail(result);
12214
+ const searchable = `${detail}
12215
+ ${result.stderr}
12216
+ ${result.stdout}`;
12217
+ let code = "E_UPDATE_FAILED";
12218
+ let hint = "retry from a normal host terminal or reinstall @actionway/cli@latest (use npm.cmd on Windows)";
12219
+ if (/EBADENGINE|unsupported engine|not compatible with your version of node/i.test(searchable)) {
12220
+ code = "E_NODE_UNSUPPORTED";
12221
+ hint = "install Node.js 20 or newer, open a new terminal, and retry";
12222
+ } else if (/ENOENT|not recognized as an internal or external command|npm(?:\.cmd)?: not found/i.test(searchable)) {
12223
+ code = "E_NPM_NOT_FOUND";
12224
+ hint = "install Node.js with npm and make sure npm/npm.cmd is available in PATH";
12225
+ } else if (/EACCES|EPERM|permission denied|operation not permitted/i.test(searchable)) {
12226
+ code = "E_GLOBAL_PREFIX";
12227
+ hint = "use a user-writable npm global prefix; do not elevate without reviewing the target path";
12228
+ } else if (/E401|E403|E404|E407|SELF_SIGNED_CERT|CERT_|UNABLE_TO_GET_ISSUER|UNABLE_TO_VERIFY_LEAF_SIGNATURE|ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENETUNREACH|EHOSTUNREACH|EAI_AGAIN|ENOTFOUND|registry/i.test(
12229
+ searchable
12230
+ )) {
12231
+ code = "E_NPM_REGISTRY";
12232
+ hint = "check the npm registry, @actionway scope mapping, proxy, and corporate CA configuration";
12233
+ }
12234
+ return new CliError(code, `${operation} failed: ${detail || "npm exited unsuccessfully"}`, hint);
12235
+ }
12236
+ function runNpmText(args, operation, options = {}) {
12237
+ const result = runNpm(args, options);
12238
+ if (result.error || result.status !== 0) throw npmFailure(result, operation);
12239
+ return result.stdout.trim();
12240
+ }
12241
+
12178
12242
  // src/lib/update-state.ts
12179
12243
  var PACKAGE_NAME2 = "@actionway/cli";
12180
12244
  var UPDATE_STATE_BASENAME = "update.json";
@@ -12275,30 +12339,22 @@ function recordVersionSignal(signal, env = process.env, now = /* @__PURE__ */ ne
12275
12339
  env
12276
12340
  );
12277
12341
  }
12278
- function npmExecutable() {
12279
- return process.platform === "win32" ? "npm.cmd" : "npm";
12280
- }
12281
12342
  function fetchLatestVersionFromNpm(env) {
12282
- const output = execFileSync(npmExecutable(), ["view", `${PACKAGE_NAME2}@latest`, "version"], {
12283
- encoding: "utf8",
12284
- env,
12285
- stdio: ["ignore", "pipe", "pipe"],
12286
- timeout: 15e3
12287
- }).trim();
12343
+ const output = runNpmText(
12344
+ ["view", `${PACKAGE_NAME2}@latest`, "version"],
12345
+ `npm registry check for ${PACKAGE_NAME2}@latest`,
12346
+ { env, timeoutMs: 15e3 }
12347
+ );
12288
12348
  const version = output.split(/\r?\n/).at(-1)?.trim() ?? "";
12289
12349
  if (!version) throw new Error(`npm returned no version for ${PACKAGE_NAME2}@latest`);
12290
12350
  return version;
12291
12351
  }
12292
12352
  function installFromNpm(env) {
12293
- const result = spawnSync(
12294
- npmExecutable(),
12353
+ runNpmText(
12295
12354
  ["install", "--global", "--no-audit", "--no-fund", "--no-update-notifier", `${PACKAGE_NAME2}@latest`],
12296
- { encoding: "utf8", env, stdio: ["ignore", "pipe", "pipe"], timeout: 12e4 }
12355
+ `npm global update for ${PACKAGE_NAME2}`,
12356
+ { env, timeoutMs: 12e4 }
12297
12357
  );
12298
- if (result.status !== 0) {
12299
- const detail = (result.stderr || result.stdout || `exit ${result.status ?? "signal"}`).trim().slice(0, 500);
12300
- throw new Error(`npm update failed: ${detail}`);
12301
- }
12302
12358
  }
12303
12359
  function isFresh(state, now) {
12304
12360
  if (!state.checked_at || !state.latest_version) return false;
@@ -12907,12 +12963,221 @@ function registerAccountCommands(program2, getTransport3) {
12907
12963
  account.command("transactions").description("Query wallet top-ups, charges, promotions, refunds, and other fund movements.").option("--limit <count>", "Maximum records in this page (1-50).").option("--cursor <cursor>", "Opaque nextCursor returned by the previous page.").option("--from <date>", "Inclusive ISO date or timestamp (UTC for date-only values).").option("--to <date>", "Exclusive ISO date or timestamp (UTC for date-only values).").option("--status <status>", "pending, succeeded, or failed.").option("--kind <kind>", "top_up, charge, refund, dispute, or promotion kind.").action((options) => read(accountEndpoint("transactions", options), getTransport3));
12908
12964
  }
12909
12965
 
12966
+ // src/commands/doctor.ts
12967
+ import { spawnSync as spawnSync2 } from "node:child_process";
12968
+ import { accessSync, constants, existsSync as existsSync2, statSync } from "node:fs";
12969
+ import { createServer as createServer2 } from "node:http";
12970
+ import { dirname as dirname3, posix, win32 } from "node:path";
12971
+ var PACKAGE_NAME3 = "@actionway/cli";
12972
+ function pass(id, message) {
12973
+ return { id, status: "pass", message };
12974
+ }
12975
+ function warn(id, message, hint) {
12976
+ return { id, status: "warn", message, hint };
12977
+ }
12978
+ function fail2(id, message, hint) {
12979
+ return { id, status: "fail", message, hint };
12980
+ }
12981
+ function commandOutput(result) {
12982
+ return !result.error && result.status === 0 ? result.stdout.trim() : null;
12983
+ }
12984
+ function safeRegistry(value) {
12985
+ try {
12986
+ const url = new URL(value);
12987
+ url.username = "";
12988
+ url.password = "";
12989
+ return url.toString();
12990
+ } catch {
12991
+ return value.trim() ? "[invalid registry URL]" : "[empty registry URL]";
12992
+ }
12993
+ }
12994
+ function pathApi(platform) {
12995
+ return platform === "win32" ? win32 : posix;
12996
+ }
12997
+ function samePath(left, right, platform) {
12998
+ const api = pathApi(platform);
12999
+ const clean = (value) => api.normalize(value.replace(/^"|"$/g, "").replace(/[\\/]+$/, ""));
13000
+ const a = clean(left);
13001
+ const b = clean(right);
13002
+ return platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
13003
+ }
13004
+ function pathContains(directory, env, platform) {
13005
+ const api = pathApi(platform);
13006
+ return (env.PATH ?? env.Path ?? "").split(api.delimiter).some((entry) => entry && samePath(entry, directory, platform));
13007
+ }
13008
+ function defaultWritable(path) {
13009
+ let candidate = path;
13010
+ while (!existsSync2(candidate) && dirname3(candidate) !== candidate) candidate = dirname3(candidate);
13011
+ try {
13012
+ if (!statSync(candidate).isDirectory()) return false;
13013
+ accessSync(candidate, constants.W_OK);
13014
+ return true;
13015
+ } catch {
13016
+ return false;
13017
+ }
13018
+ }
13019
+ function checkLoopback() {
13020
+ return new Promise((resolve3, reject) => {
13021
+ const server = createServer2();
13022
+ server.once("error", reject);
13023
+ server.listen(0, "127.0.0.1", () => server.close((error) => error ? reject(error) : resolve3()));
13024
+ });
13025
+ }
13026
+ function powerShellPolicy() {
13027
+ const result = spawnSync2(
13028
+ "powershell.exe",
13029
+ ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", "Get-ExecutionPolicy"],
13030
+ { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 5e3, windowsHide: true }
13031
+ );
13032
+ return result.status === 0 ? result.stdout.trim() : null;
13033
+ }
13034
+ function npmCheck(id, result, operation, success) {
13035
+ const output = commandOutput(result);
13036
+ if (output !== null) return success(output);
13037
+ const error = npmFailure(result, operation);
13038
+ return fail2(id, error.message, error.hint ?? "inspect the npm configuration and retry");
13039
+ }
13040
+ async function diagnoseActionway(options = {}, dependencies = {}) {
13041
+ const env = dependencies.env ?? process.env;
13042
+ const platform = dependencies.platform ?? process.platform;
13043
+ const architecture = dependencies.architecture ?? process.arch;
13044
+ const nodeVersion = dependencies.nodeVersion ?? process.versions.node;
13045
+ const npm = dependencies.runNpm ?? runNpm;
13046
+ const pathExists = dependencies.pathExists ?? existsSync2;
13047
+ const pathWritable = dependencies.pathWritable ?? defaultWritable;
13048
+ const fetchOAuth = dependencies.fetchOAuth ?? fetchCliOAuthConfig;
13049
+ const loopback = dependencies.checkLoopback ?? checkLoopback;
13050
+ const readPolicy = dependencies.readPowerShellPolicy ?? powerShellPolicy;
13051
+ const checks = [];
13052
+ const configDir = actionwayConfigDir(env);
13053
+ const nodeMajor = Number.parseInt(nodeVersion.split(".")[0] ?? "", 10);
13054
+ checks.push(
13055
+ Number.isFinite(nodeMajor) && nodeMajor >= 20 ? pass("node", `Node.js ${nodeVersion} is supported.`) : fail2("node", `Node.js ${nodeVersion || "unknown"} is unsupported.`, "install Node.js 20 or newer and reopen the terminal")
13056
+ );
13057
+ if (platform === "linux" && (env.WSL_DISTRO_NAME || env.WSL_INTEROP)) {
13058
+ checks.push(
13059
+ warn(
13060
+ "execution_environment",
13061
+ "WSL detected; browser loopback may not share the Windows host network namespace.",
13062
+ "run Actionway login from the Windows host if the browser callback cannot return"
13063
+ )
13064
+ );
13065
+ } else {
13066
+ checks.push(pass("execution_environment", `${platform}/${architecture} host environment detected.`));
13067
+ }
13068
+ checks.push(
13069
+ pathWritable(configDir) ? pass("config_dir", `Configuration directory is writable: ${configDir}`) : fail2("config_dir", `Configuration directory is not writable: ${configDir}`, "set ACTIONWAY_CONFIG_DIR to a user-writable directory")
13070
+ );
13071
+ const registryResult = npm(["config", "get", "registry"], { env, platform, timeoutMs: 5e3 });
13072
+ checks.push(
13073
+ npmCheck("npm", registryResult, "npm discovery", (registry) => pass("npm", `npm is available; registry is ${safeRegistry(registry)}.`))
13074
+ );
13075
+ const scopeResult = npm(["config", "get", "@actionway:registry"], { env, platform, timeoutMs: 5e3 });
13076
+ const scopeRegistry = commandOutput(scopeResult);
13077
+ if (scopeRegistry && !["null", "undefined"].includes(scopeRegistry.toLowerCase())) {
13078
+ const safe = safeRegistry(scopeRegistry);
13079
+ checks.push(
13080
+ safe.startsWith("https://registry.npmjs.org/") ? pass("scope_registry", `@actionway packages use ${safe}.`) : warn(
13081
+ "scope_registry",
13082
+ `@actionway packages are mapped to ${safe}.`,
13083
+ "make sure that registry mirrors public @actionway/cli, or install from the public npm registry when policy permits"
13084
+ )
13085
+ );
13086
+ } else if (scopeResult.error || scopeResult.status !== 0) {
13087
+ checks.push(warn("scope_registry", "Could not read the @actionway npm scope mapping.", "inspect npm user configuration"));
13088
+ } else {
13089
+ checks.push(pass("scope_registry", "No custom @actionway npm registry mapping is configured."));
13090
+ }
13091
+ const prefixResult = npm(["prefix", "--global"], { env, platform, timeoutMs: 5e3 });
13092
+ const prefix = commandOutput(prefixResult);
13093
+ if (!prefix) {
13094
+ const error = npmFailure(prefixResult, "npm global prefix lookup");
13095
+ checks.push(fail2("global_prefix", error.message, error.hint ?? "inspect npm global configuration"));
13096
+ } else {
13097
+ const api = pathApi(platform);
13098
+ const binDir = platform === "win32" ? prefix : api.join(prefix, "bin");
13099
+ const binName = platform === "win32" ? "actionway.cmd" : "actionway";
13100
+ checks.push(
13101
+ pathContains(binDir, env, platform) ? pass("global_path", `npm global executable directory is in PATH: ${binDir}`) : fail2("global_path", `npm global executable directory is not in PATH: ${binDir}`, "add it to the user PATH and open a new terminal")
13102
+ );
13103
+ checks.push(
13104
+ pathExists(api.join(binDir, binName)) ? pass("command", `${binName} is installed in the npm global prefix.`) : fail2("command", `${binName} was not found in the npm global prefix.`, "reinstall @actionway/cli@latest in the same Node/npm environment")
13105
+ );
13106
+ }
13107
+ if (platform === "win32") {
13108
+ const policy = readPolicy();
13109
+ checks.push(
13110
+ policy && ["Restricted", "AllSigned"].includes(policy) ? warn(
13111
+ "powershell_policy",
13112
+ `PowerShell execution policy is ${policy}; npm's actionway.ps1 shim may be blocked.`,
13113
+ "use actionway.cmd; do not weaken organization policy without approval"
13114
+ ) : pass("powershell_policy", `PowerShell execution policy is ${policy || "not available"}; actionway.cmd remains the stable entrypoint.`)
13115
+ );
13116
+ }
13117
+ try {
13118
+ await loopback();
13119
+ checks.push(pass("loopback", "A random 127.0.0.1 callback port can be opened."));
13120
+ } catch (error) {
13121
+ checks.push(
13122
+ fail2(
13123
+ "loopback",
13124
+ `Cannot open the local OAuth callback: ${error instanceof Error ? error.message : String(error)}`,
13125
+ "run login from a normal host terminal and check firewall or sandbox policy"
13126
+ )
13127
+ );
13128
+ }
13129
+ if (options.network === false) {
13130
+ checks.push({ id: "npm_registry", status: "skip", message: "npm registry check was skipped." });
13131
+ checks.push({ id: "actionway_oauth", status: "skip", message: "Actionway OAuth discovery check was skipped." });
13132
+ } else {
13133
+ const packageResult = npm(["view", `${PACKAGE_NAME3}@latest`, "version"], { env, platform, timeoutMs: 15e3 });
13134
+ checks.push(
13135
+ npmCheck(
13136
+ "npm_registry",
13137
+ packageResult,
13138
+ "Actionway package lookup",
13139
+ (version) => pass("npm_registry", `${PACKAGE_NAME3}@latest is reachable (${version.split(/\r?\n/).at(-1)}).`)
13140
+ )
13141
+ );
13142
+ try {
13143
+ await fetchOAuth(resolveGatewayUrl(void 0, env));
13144
+ checks.push(pass("actionway_oauth", "Actionway OAuth discovery is reachable."));
13145
+ } catch (error) {
13146
+ checks.push(
13147
+ fail2(
13148
+ "actionway_oauth",
13149
+ `Actionway OAuth discovery failed: ${error instanceof Error ? error.message : String(error)}`,
13150
+ "check actionway.ai access, proxy, DNS, and corporate CA configuration"
13151
+ )
13152
+ );
13153
+ }
13154
+ }
13155
+ return {
13156
+ ready: !checks.some((check) => check.status === "fail"),
13157
+ platform,
13158
+ architecture,
13159
+ node_version: nodeVersion,
13160
+ config_dir: configDir,
13161
+ checks
13162
+ };
13163
+ }
13164
+ function registerDoctorCommand(program2) {
13165
+ program2.command("doctor").description("Diagnose Node, npm, PATH, registry, configuration, and local OAuth callback readiness.").option("--no-network", "skip npm registry and Actionway OAuth discovery checks").action(async (options) => {
13166
+ const report = await diagnoseActionway({ network: options.network !== false });
13167
+ const command = process.platform === "win32" ? "actionway.cmd" : "actionway";
13168
+ ok({
13169
+ ...report,
13170
+ hint: report.ready ? `Actionway CLI prerequisites are ready. Run \`${command} init\`.` : `Resolve the failed checks, then run \`${command} doctor\` again.`
13171
+ });
13172
+ });
13173
+ }
13174
+
12910
13175
  // src/lib/install-session.ts
12911
13176
  import { randomUUID as randomUUID5 } from "node:crypto";
12912
13177
 
12913
13178
  // src/lib/skill.ts
12914
- import { cpSync, existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync4, realpathSync as realpathSync2, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
12915
- import { dirname as dirname3, join as join3, resolve as resolve2 } from "node:path";
13179
+ import { cpSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, realpathSync as realpathSync2, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
13180
+ import { dirname as dirname4, join as join3, resolve as resolve2 } from "node:path";
12916
13181
  import { fileURLToPath as fileURLToPath3 } from "node:url";
12917
13182
  var InitError = class extends Error {
12918
13183
  hint;
@@ -12924,9 +13189,9 @@ var InitError = class extends Error {
12924
13189
  };
12925
13190
  var ACTIONWAY_SKILL_INSTRUCTION = "Install this skill from `source` into your agent's skill directory and make sure it loads in future sessions. If a skill named `actionway` already exists there, replace it only when it is an earlier Actionway skill (check its frontmatter metadata.version); otherwise ask the user first.";
12926
13191
  function resolveBundledSkill(moduleUrl = import.meta.url) {
12927
- const here = dirname3(fileURLToPath3(moduleUrl));
13192
+ const here = dirname4(fileURLToPath3(moduleUrl));
12928
13193
  for (const candidate of [resolve2(here, "../assets/skill/actionway"), resolve2(here, "../../assets/skill/actionway")]) {
12929
- if (existsSync2(join3(candidate, "SKILL.md"))) return realpathSync2(candidate);
13194
+ if (existsSync3(join3(candidate, "SKILL.md"))) return realpathSync2(candidate);
12930
13195
  }
12931
13196
  throw new InitError("the bundled Actionway Skill is missing", "reinstall @actionway/cli, then run `actionway init` again");
12932
13197
  }
@@ -12934,7 +13199,8 @@ function skillSourcePath(env = process.env) {
12934
13199
  return join3(actionwayConfigDir(env), "skill", "actionway");
12935
13200
  }
12936
13201
  function stampSkillVersion(skillMarkdown, version) {
12937
- const lines = skillMarkdown.split("\n");
13202
+ const newline = skillMarkdown.includes("\r\n") ? "\r\n" : "\n";
13203
+ const lines = skillMarkdown.split(/\r?\n/);
12938
13204
  if (lines[0]?.trim() !== "---") return skillMarkdown;
12939
13205
  let closing = lines.indexOf("---", 1);
12940
13206
  if (closing < 0) return skillMarkdown;
@@ -12958,24 +13224,24 @@ function stampSkillVersion(skillMarkdown, version) {
12958
13224
  for (let index = metadataIndex + 1; index < metadataEnd; index += 1) {
12959
13225
  if (/^\s{2}version\s*:/.test(lines[index] ?? "")) {
12960
13226
  lines[index] = versionLine;
12961
- return lines.join("\n");
13227
+ return lines.join(newline);
12962
13228
  }
12963
13229
  }
12964
13230
  lines.splice(metadataIndex + 1, 0, versionLine);
12965
- return lines.join("\n");
13231
+ return lines.join(newline);
12966
13232
  }
12967
13233
  lines.splice(closing, 0, "metadata:", versionLine);
12968
- return lines.join("\n");
13234
+ return lines.join(newline);
12969
13235
  }
12970
13236
  function materializeSkill(options = {}) {
12971
13237
  const env = options.env ?? process.env;
12972
13238
  const source = options.source ? realpathSync2(options.source) : resolveBundledSkill();
12973
- if (!existsSync2(join3(source, "SKILL.md"))) {
13239
+ if (!existsSync3(join3(source, "SKILL.md"))) {
12974
13240
  throw new InitError("the Actionway Skill source is invalid", "reinstall @actionway/cli and retry init");
12975
13241
  }
12976
13242
  const version = options.version ?? readOwnVersion();
12977
13243
  const target = skillSourcePath(env);
12978
- mkdirSync3(dirname3(target), { recursive: true, mode: 448 });
13244
+ mkdirSync3(dirname4(target), { recursive: true, mode: 448 });
12979
13245
  rmSync2(target, { recursive: true, force: true });
12980
13246
  cpSync(source, target, { recursive: true });
12981
13247
  const skillPath = join3(target, "SKILL.md");
@@ -13140,12 +13406,12 @@ function registerInitCommand(program2) {
13140
13406
  }
13141
13407
 
13142
13408
  // src/commands/update.ts
13143
- import { spawnSync as spawnSync2 } from "node:child_process";
13409
+ import { spawnSync as spawnSync3 } from "node:child_process";
13144
13410
  function refreshSkillWithNewBinary() {
13145
13411
  try {
13146
13412
  const binPath = process.argv[1];
13147
13413
  if (!binPath) return { refreshed: false };
13148
- const result = spawnSync2(process.execPath, [binPath, "init", "--skill-only"], {
13414
+ const result = spawnSync3(process.execPath, [binPath, "init", "--skill-only"], {
13149
13415
  encoding: "utf8",
13150
13416
  stdio: ["ignore", "pipe", "pipe"],
13151
13417
  timeout: 3e4
@@ -13176,8 +13442,11 @@ function registerUpdateCommand(program2) {
13176
13442
  hint: skillRefresh.refreshed ? "The new CLI will be used by the next actionway command. Refresh the skill installed in your agent's skill directory from `skill.source`; the updated instructions apply from the next session." : "The new CLI will be used by the next actionway command. Run `actionway init` to refresh the staged skill, then reinstall it into your agent's skill directory."
13177
13443
  });
13178
13444
  } catch (error) {
13445
+ if (error instanceof CliError) {
13446
+ fail({ code: error.code, message: error.message, hint: error.hint, extra: error.extra });
13447
+ }
13179
13448
  fail({
13180
- code: "E_BACKEND",
13449
+ code: "E_UPDATE_FAILED",
13181
13450
  message: error instanceof Error ? error.message : String(error),
13182
13451
  hint: "retry later or reinstall @actionway/cli@latest"
13183
13452
  });
@@ -13426,6 +13695,7 @@ function buildProgram() {
13426
13695
  program2.version(version);
13427
13696
  program2.description("Actionway CLI: run Actionway media and research capabilities from your local agent.");
13428
13697
  registerInitCommand(program2);
13698
+ registerDoctorCommand(program2);
13429
13699
  registerUpdateCommand(program2);
13430
13700
  registerAuthCommands(program2);
13431
13701
  registerAccountCommands(program2, getTransport2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actionway/cli",
3
- "version": "0.18.0",
3
+ "version": "0.18.1",
4
4
  "description": "actionway CLI 的本地端壳:Clerk OAuth 鉴权 + cli-core 共享命令面 + init / update / skill 物化 / 版本三链路(终端用户本地 Codex / Claude Code 使用)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -26,7 +26,7 @@
26
26
  "node": ">=20"
27
27
  },
28
28
  "scripts": {
29
- "build": "esbuild src/index.ts --bundle --platform=node --format=esm --target=node20 --outfile=dist/index.js --banner:js='import { createRequire } from \"node:module\"; const require = createRequire(import.meta.url);'",
29
+ "build": "node scripts/build.mjs",
30
30
  "dev": "tsx src/index.ts",
31
31
  "test": "vitest --run",
32
32
  "typecheck": "tsc -p tsconfig.json --noEmit"