@actionway/cli 0.18.1 → 0.18.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +3 -2
  2. package/dist/index.js +79 -33
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -27,8 +27,9 @@ actionway init
27
27
 
28
28
  - **auth 唯一途径:Clerk OAuth(PKCE)**。issuer / client_id 运行时从
29
29
  `{public origin}/api/auth/cli-config` 发现;浏览器授权走随机 `127.0.0.1` 回调端口;
30
- 凭据存 `~/.actionway/credentials.json`(0600 owner-only 文件,可用
31
- `ACTIONWAY_CONFIG_DIR` 重定向)。CLI 从不打印或存储 OAuth client secret。
30
+ 凭据存 `~/.actionway/credentials.json`(POSIX 上为 0600 owner-only 文件;
31
+ Windows chmod 无效,保密性依赖 `%USERPROFILE%` 的继承 ACL——不要把
32
+ `ACTIONWAY_CONFIG_DIR` 指到共享目录)。CLI 从不打印或存储 OAuth client secret。
32
33
  - **命令面 = 明码写出的注册表**(无 profile flag、无运行期分叉):
33
34
  - `tools search / inspect / call` 是长尾 capability 的规范发现与执行面;
34
35
  - 高频 typed command 继续提供参数与文件 UX,但投影到同一个 Tool Call;
package/dist/index.js CHANGED
@@ -12209,28 +12209,63 @@ function resultDetail(result) {
12209
12209
  const error = result.error instanceof Error ? result.error.message : "";
12210
12210
  return sanitizeDetail(error || result.stderr || result.stdout || `exit ${result.status ?? "signal"}`);
12211
12211
  }
12212
+ var HINTS = {
12213
+ E_NODE_UNSUPPORTED: "install Node.js 20 or newer, open a new terminal, and retry",
12214
+ E_NPM_NOT_FOUND: "install Node.js with npm and make sure npm/npm.cmd is available in PATH",
12215
+ E_GLOBAL_PREFIX: "use a user-writable npm global prefix; do not elevate without reviewing the target path",
12216
+ E_NPM_REGISTRY: "check the npm registry, @actionway scope mapping, proxy, and corporate CA configuration"
12217
+ };
12218
+ var NPM_ERROR_CODE_MAP = {
12219
+ EBADENGINE: "E_NODE_UNSUPPORTED",
12220
+ EACCES: "E_GLOBAL_PREFIX",
12221
+ EPERM: "E_GLOBAL_PREFIX",
12222
+ E401: "E_NPM_REGISTRY",
12223
+ E403: "E_NPM_REGISTRY",
12224
+ E404: "E_NPM_REGISTRY",
12225
+ E407: "E_NPM_REGISTRY",
12226
+ E408: "E_NPM_REGISTRY",
12227
+ E429: "E_NPM_REGISTRY",
12228
+ ETIMEDOUT: "E_NPM_REGISTRY",
12229
+ ECONNRESET: "E_NPM_REGISTRY",
12230
+ ECONNREFUSED: "E_NPM_REGISTRY",
12231
+ ENETUNREACH: "E_NPM_REGISTRY",
12232
+ EHOSTUNREACH: "E_NPM_REGISTRY",
12233
+ EAI_AGAIN: "E_NPM_REGISTRY",
12234
+ ENOTFOUND: "E_NPM_REGISTRY",
12235
+ SELF_SIGNED_CERT_IN_CHAIN: "E_NPM_REGISTRY",
12236
+ UNABLE_TO_GET_ISSUER_CERT_LOCALLY: "E_NPM_REGISTRY",
12237
+ UNABLE_TO_VERIFY_LEAF_SIGNATURE: "E_NPM_REGISTRY",
12238
+ CERT_HAS_EXPIRED: "E_NPM_REGISTRY",
12239
+ DEPTH_ZERO_SELF_SIGNED_CERT: "E_NPM_REGISTRY"
12240
+ // npm 自身的 ENOENT(缓存/rename 竞态等本地文件问题)可重试,
12241
+ // 不属于"npm 未安装"——那只由 spawn 层的 error/9009 判定。
12242
+ };
12243
+ function classifyNpmFailure(result, searchable) {
12244
+ const spawnError = result.error;
12245
+ if (spawnError?.code === "ENOENT") return "E_NPM_NOT_FOUND";
12246
+ if (result.status === 9009) return "E_NPM_NOT_FOUND";
12247
+ const codeToken = /npm (?:ERR!|error) code ([A-Z0-9_]+)/i.exec(searchable)?.[1]?.toUpperCase();
12248
+ if (codeToken) {
12249
+ const mapped = NPM_ERROR_CODE_MAP[codeToken];
12250
+ if (mapped) return mapped;
12251
+ if (/^(?:CERT_|ERR_TLS_|UNABLE_TO_)/.test(codeToken)) return "E_NPM_REGISTRY";
12252
+ return "E_UPDATE_FAILED";
12253
+ }
12254
+ if (/unsupported engine|not compatible with your version of node/i.test(searchable)) return "E_NODE_UNSUPPORTED";
12255
+ if (/not recognized as an internal or external command|npm(?:\.cmd)?: (?:command )?not found/i.test(searchable))
12256
+ return "E_NPM_NOT_FOUND";
12257
+ if (/permission denied|operation not permitted/i.test(searchable)) return "E_GLOBAL_PREFIX";
12258
+ if (/self[- ]signed certificate|unable to (?:get|verify) .*certificate|getaddrinfo|socket hang up|network timeout/i.test(searchable))
12259
+ return "E_NPM_REGISTRY";
12260
+ return "E_UPDATE_FAILED";
12261
+ }
12212
12262
  function npmFailure(result, operation) {
12213
12263
  const detail = resultDetail(result);
12214
12264
  const searchable = `${detail}
12215
12265
  ${result.stderr}
12216
12266
  ${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
- }
12267
+ const code = classifyNpmFailure(result, searchable);
12268
+ const hint = HINTS[code] ?? "retry from a normal host terminal or reinstall @actionway/cli@latest (use npm.cmd on Windows)";
12234
12269
  return new CliError(code, `${operation} failed: ${detail || "npm exited unsuccessfully"}`, hint);
12235
12270
  }
12236
12271
  function runNpmText(args, operation, options = {}) {
@@ -12965,9 +13000,10 @@ function registerAccountCommands(program2, getTransport3) {
12965
13000
 
12966
13001
  // src/commands/doctor.ts
12967
13002
  import { spawnSync as spawnSync2 } from "node:child_process";
12968
- import { accessSync, constants, existsSync as existsSync2, statSync } from "node:fs";
13003
+ import { existsSync as existsSync2, rmSync as rmSync2, statSync, writeFileSync as writeFileSync3 } from "node:fs";
12969
13004
  import { createServer as createServer2 } from "node:http";
12970
- import { dirname as dirname3, posix, win32 } from "node:path";
13005
+ import { randomBytes as randomBytes2 } from "node:crypto";
13006
+ import { dirname as dirname3, join as join3, posix, win32 } from "node:path";
12971
13007
  var PACKAGE_NAME3 = "@actionway/cli";
12972
13008
  function pass(id, message) {
12973
13009
  return { id, status: "pass", message };
@@ -13010,7 +13046,9 @@ function defaultWritable(path) {
13010
13046
  while (!existsSync2(candidate) && dirname3(candidate) !== candidate) candidate = dirname3(candidate);
13011
13047
  try {
13012
13048
  if (!statSync(candidate).isDirectory()) return false;
13013
- accessSync(candidate, constants.W_OK);
13049
+ const probe = join3(candidate, `.actionway-doctor-${randomBytes2(6).toString("hex")}`);
13050
+ writeFileSync3(probe, "");
13051
+ rmSync2(probe, { force: true });
13014
13052
  return true;
13015
13053
  } catch {
13016
13054
  return false;
@@ -13023,6 +13061,11 @@ function checkLoopback() {
13023
13061
  server.listen(0, "127.0.0.1", () => server.close((error) => error ? reject(error) : resolve3()));
13024
13062
  });
13025
13063
  }
13064
+ function defaultResolveCommand(name) {
13065
+ const result = process.platform === "win32" ? spawnSync2("where.exe", [name], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 5e3, windowsHide: true }) : spawnSync2("/bin/sh", ["-c", `command -v -- ${name}`], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 5e3 });
13066
+ if (result.error || result.status !== 0) return null;
13067
+ return result.stdout.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? null;
13068
+ }
13026
13069
  function powerShellPolicy() {
13027
13070
  const result = spawnSync2(
13028
13071
  "powershell.exe",
@@ -13048,6 +13091,7 @@ async function diagnoseActionway(options = {}, dependencies = {}) {
13048
13091
  const fetchOAuth = dependencies.fetchOAuth ?? fetchCliOAuthConfig;
13049
13092
  const loopback = dependencies.checkLoopback ?? checkLoopback;
13050
13093
  const readPolicy = dependencies.readPowerShellPolicy ?? powerShellPolicy;
13094
+ const resolveCommand = dependencies.resolveCommand ?? defaultResolveCommand;
13051
13095
  const checks = [];
13052
13096
  const configDir = actionwayConfigDir(env);
13053
13097
  const nodeMajor = Number.parseInt(nodeVersion.split(".")[0] ?? "", 10);
@@ -13068,11 +13112,11 @@ async function diagnoseActionway(options = {}, dependencies = {}) {
13068
13112
  checks.push(
13069
13113
  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
13114
  );
13071
- const registryResult = npm(["config", "get", "registry"], { env, platform, timeoutMs: 5e3 });
13115
+ const registryResult = npm(["config", "get", "registry"], { env, platform, timeoutMs: 15e3 });
13072
13116
  checks.push(
13073
13117
  npmCheck("npm", registryResult, "npm discovery", (registry) => pass("npm", `npm is available; registry is ${safeRegistry(registry)}.`))
13074
13118
  );
13075
- const scopeResult = npm(["config", "get", "@actionway:registry"], { env, platform, timeoutMs: 5e3 });
13119
+ const scopeResult = npm(["config", "get", "@actionway:registry"], { env, platform, timeoutMs: 15e3 });
13076
13120
  const scopeRegistry = commandOutput(scopeResult);
13077
13121
  if (scopeRegistry && !["null", "undefined"].includes(scopeRegistry.toLowerCase())) {
13078
13122
  const safe = safeRegistry(scopeRegistry);
@@ -13088,7 +13132,7 @@ async function diagnoseActionway(options = {}, dependencies = {}) {
13088
13132
  } else {
13089
13133
  checks.push(pass("scope_registry", "No custom @actionway npm registry mapping is configured."));
13090
13134
  }
13091
- const prefixResult = npm(["prefix", "--global"], { env, platform, timeoutMs: 5e3 });
13135
+ const prefixResult = npm(["prefix", "--global"], { env, platform, timeoutMs: 15e3 });
13092
13136
  const prefix = commandOutput(prefixResult);
13093
13137
  if (!prefix) {
13094
13138
  const error = npmFailure(prefixResult, "npm global prefix lookup");
@@ -13097,11 +13141,13 @@ async function diagnoseActionway(options = {}, dependencies = {}) {
13097
13141
  const api = pathApi(platform);
13098
13142
  const binDir = platform === "win32" ? prefix : api.join(prefix, "bin");
13099
13143
  const binName = platform === "win32" ? "actionway.cmd" : "actionway";
13144
+ let resolvedCache;
13145
+ const resolved = () => resolvedCache !== void 0 ? resolvedCache : resolvedCache = resolveCommand("actionway");
13100
13146
  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")
13147
+ pathContains(binDir, env, platform) ? pass("global_path", `npm global executable directory is in PATH: ${binDir}`) : resolved() ? pass("global_path", `actionway resolves from PATH at ${resolved()} (outside the npm global prefix ${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
13148
  );
13103
13149
  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")
13150
+ pathExists(api.join(binDir, binName)) ? pass("command", `${binName} is installed in the npm global prefix.`) : resolved() ? pass("command", `actionway command resolves at ${resolved()}.`) : fail2("command", `${binName} was not found in the npm global prefix.`, "reinstall @actionway/cli@latest in the same Node/npm environment")
13105
13151
  );
13106
13152
  }
13107
13153
  if (platform === "win32") {
@@ -13176,8 +13222,8 @@ function registerDoctorCommand(program2) {
13176
13222
  import { randomUUID as randomUUID5 } from "node:crypto";
13177
13223
 
13178
13224
  // src/lib/skill.ts
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";
13225
+ import { cpSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, realpathSync as realpathSync2, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "node:fs";
13226
+ import { dirname as dirname4, join as join4, resolve as resolve2 } from "node:path";
13181
13227
  import { fileURLToPath as fileURLToPath3 } from "node:url";
13182
13228
  var InitError = class extends Error {
13183
13229
  hint;
@@ -13191,12 +13237,12 @@ var ACTIONWAY_SKILL_INSTRUCTION = "Install this skill from `source` into your ag
13191
13237
  function resolveBundledSkill(moduleUrl = import.meta.url) {
13192
13238
  const here = dirname4(fileURLToPath3(moduleUrl));
13193
13239
  for (const candidate of [resolve2(here, "../assets/skill/actionway"), resolve2(here, "../../assets/skill/actionway")]) {
13194
- if (existsSync3(join3(candidate, "SKILL.md"))) return realpathSync2(candidate);
13240
+ if (existsSync3(join4(candidate, "SKILL.md"))) return realpathSync2(candidate);
13195
13241
  }
13196
13242
  throw new InitError("the bundled Actionway Skill is missing", "reinstall @actionway/cli, then run `actionway init` again");
13197
13243
  }
13198
13244
  function skillSourcePath(env = process.env) {
13199
- return join3(actionwayConfigDir(env), "skill", "actionway");
13245
+ return join4(actionwayConfigDir(env), "skill", "actionway");
13200
13246
  }
13201
13247
  function stampSkillVersion(skillMarkdown, version) {
13202
13248
  const newline = skillMarkdown.includes("\r\n") ? "\r\n" : "\n";
@@ -13236,16 +13282,16 @@ function stampSkillVersion(skillMarkdown, version) {
13236
13282
  function materializeSkill(options = {}) {
13237
13283
  const env = options.env ?? process.env;
13238
13284
  const source = options.source ? realpathSync2(options.source) : resolveBundledSkill();
13239
- if (!existsSync3(join3(source, "SKILL.md"))) {
13285
+ if (!existsSync3(join4(source, "SKILL.md"))) {
13240
13286
  throw new InitError("the Actionway Skill source is invalid", "reinstall @actionway/cli and retry init");
13241
13287
  }
13242
13288
  const version = options.version ?? readOwnVersion();
13243
13289
  const target = skillSourcePath(env);
13244
13290
  mkdirSync3(dirname4(target), { recursive: true, mode: 448 });
13245
- rmSync2(target, { recursive: true, force: true });
13291
+ rmSync3(target, { recursive: true, force: true });
13246
13292
  cpSync(source, target, { recursive: true });
13247
- const skillPath = join3(target, "SKILL.md");
13248
- writeFileSync3(skillPath, stampSkillVersion(readFileSync4(skillPath, "utf8"), version));
13293
+ const skillPath = join4(target, "SKILL.md");
13294
+ writeFileSync4(skillPath, stampSkillVersion(readFileSync4(skillPath, "utf8"), version));
13249
13295
  return { source: target, version, instruction: ACTIONWAY_SKILL_INSTRUCTION };
13250
13296
  }
13251
13297
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actionway/cli",
3
- "version": "0.18.1",
3
+ "version": "0.18.2",
4
4
  "description": "actionway CLI 的本地端壳:Clerk OAuth 鉴权 + cli-core 共享命令面 + init / update / skill 物化 / 版本三链路(终端用户本地 Codex / Claude Code 使用)",
5
5
  "type": "module",
6
6
  "bin": {