@actionway/cli 0.18.0 → 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.
- package/README.md +26 -3
- package/assets/skill/actionway/SKILL.md +1 -1
- package/dist/index.js +350 -34
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -3,15 +3,38 @@
|
|
|
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
|
-
凭据存 `~/.actionway/credentials.json`(0600 owner-only
|
|
9
|
-
|
|
30
|
+
凭据存 `~/.actionway/credentials.json`(POSIX 上为 0600 owner-only 文件;
|
|
31
|
+
Windows 上 chmod 无效,保密性依赖 `%USERPROFILE%` 的继承 ACL——不要把
|
|
32
|
+
`ACTIONWAY_CONFIG_DIR` 指到共享目录)。CLI 从不打印或存储 OAuth client secret。
|
|
10
33
|
- **命令面 = 明码写出的注册表**(无 profile flag、无运行期分叉):
|
|
11
34
|
- `tools search / inspect / call` 是长尾 capability 的规范发现与执行面;
|
|
12
35
|
- 高频 typed command 继续提供参数与文件 UX,但投影到同一个 Tool Call;
|
|
13
36
|
- `account usage / wallet / transactions` 只读 Actionway Business 数据;
|
|
14
|
-
- 本壳另有 `init` / `update` / `login` / `logout` / `whoami`,cli-core
|
|
37
|
+
- 本壳另有 `init` / `doctor` / `update` / `login` / `logout` / `whoami`,cli-core
|
|
15
38
|
继续提供 wait/poll 与已冻结的共享 typed commands。
|
|
16
39
|
|
|
17
40
|
- **账户响应无本地换算**:`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,103 @@ 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
|
+
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
|
+
}
|
|
12262
|
+
function npmFailure(result, operation) {
|
|
12263
|
+
const detail = resultDetail(result);
|
|
12264
|
+
const searchable = `${detail}
|
|
12265
|
+
${result.stderr}
|
|
12266
|
+
${result.stdout}`;
|
|
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)";
|
|
12269
|
+
return new CliError(code, `${operation} failed: ${detail || "npm exited unsuccessfully"}`, hint);
|
|
12270
|
+
}
|
|
12271
|
+
function runNpmText(args, operation, options = {}) {
|
|
12272
|
+
const result = runNpm(args, options);
|
|
12273
|
+
if (result.error || result.status !== 0) throw npmFailure(result, operation);
|
|
12274
|
+
return result.stdout.trim();
|
|
12275
|
+
}
|
|
12276
|
+
|
|
12178
12277
|
// src/lib/update-state.ts
|
|
12179
12278
|
var PACKAGE_NAME2 = "@actionway/cli";
|
|
12180
12279
|
var UPDATE_STATE_BASENAME = "update.json";
|
|
@@ -12275,30 +12374,22 @@ function recordVersionSignal(signal, env = process.env, now = /* @__PURE__ */ ne
|
|
|
12275
12374
|
env
|
|
12276
12375
|
);
|
|
12277
12376
|
}
|
|
12278
|
-
function npmExecutable() {
|
|
12279
|
-
return process.platform === "win32" ? "npm.cmd" : "npm";
|
|
12280
|
-
}
|
|
12281
12377
|
function fetchLatestVersionFromNpm(env) {
|
|
12282
|
-
const output =
|
|
12283
|
-
|
|
12284
|
-
|
|
12285
|
-
|
|
12286
|
-
|
|
12287
|
-
}).trim();
|
|
12378
|
+
const output = runNpmText(
|
|
12379
|
+
["view", `${PACKAGE_NAME2}@latest`, "version"],
|
|
12380
|
+
`npm registry check for ${PACKAGE_NAME2}@latest`,
|
|
12381
|
+
{ env, timeoutMs: 15e3 }
|
|
12382
|
+
);
|
|
12288
12383
|
const version = output.split(/\r?\n/).at(-1)?.trim() ?? "";
|
|
12289
12384
|
if (!version) throw new Error(`npm returned no version for ${PACKAGE_NAME2}@latest`);
|
|
12290
12385
|
return version;
|
|
12291
12386
|
}
|
|
12292
12387
|
function installFromNpm(env) {
|
|
12293
|
-
|
|
12294
|
-
npmExecutable(),
|
|
12388
|
+
runNpmText(
|
|
12295
12389
|
["install", "--global", "--no-audit", "--no-fund", "--no-update-notifier", `${PACKAGE_NAME2}@latest`],
|
|
12296
|
-
|
|
12390
|
+
`npm global update for ${PACKAGE_NAME2}`,
|
|
12391
|
+
{ env, timeoutMs: 12e4 }
|
|
12297
12392
|
);
|
|
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
12393
|
}
|
|
12303
12394
|
function isFresh(state, now) {
|
|
12304
12395
|
if (!state.checked_at || !state.latest_version) return false;
|
|
@@ -12907,12 +12998,232 @@ function registerAccountCommands(program2, getTransport3) {
|
|
|
12907
12998
|
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
12999
|
}
|
|
12909
13000
|
|
|
13001
|
+
// src/commands/doctor.ts
|
|
13002
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
13003
|
+
import { existsSync as existsSync2, rmSync as rmSync2, statSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
13004
|
+
import { createServer as createServer2 } from "node:http";
|
|
13005
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
13006
|
+
import { dirname as dirname3, join as join3, posix, win32 } from "node:path";
|
|
13007
|
+
var PACKAGE_NAME3 = "@actionway/cli";
|
|
13008
|
+
function pass(id, message) {
|
|
13009
|
+
return { id, status: "pass", message };
|
|
13010
|
+
}
|
|
13011
|
+
function warn(id, message, hint) {
|
|
13012
|
+
return { id, status: "warn", message, hint };
|
|
13013
|
+
}
|
|
13014
|
+
function fail2(id, message, hint) {
|
|
13015
|
+
return { id, status: "fail", message, hint };
|
|
13016
|
+
}
|
|
13017
|
+
function commandOutput(result) {
|
|
13018
|
+
return !result.error && result.status === 0 ? result.stdout.trim() : null;
|
|
13019
|
+
}
|
|
13020
|
+
function safeRegistry(value) {
|
|
13021
|
+
try {
|
|
13022
|
+
const url = new URL(value);
|
|
13023
|
+
url.username = "";
|
|
13024
|
+
url.password = "";
|
|
13025
|
+
return url.toString();
|
|
13026
|
+
} catch {
|
|
13027
|
+
return value.trim() ? "[invalid registry URL]" : "[empty registry URL]";
|
|
13028
|
+
}
|
|
13029
|
+
}
|
|
13030
|
+
function pathApi(platform) {
|
|
13031
|
+
return platform === "win32" ? win32 : posix;
|
|
13032
|
+
}
|
|
13033
|
+
function samePath(left, right, platform) {
|
|
13034
|
+
const api = pathApi(platform);
|
|
13035
|
+
const clean = (value) => api.normalize(value.replace(/^"|"$/g, "").replace(/[\\/]+$/, ""));
|
|
13036
|
+
const a = clean(left);
|
|
13037
|
+
const b = clean(right);
|
|
13038
|
+
return platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
13039
|
+
}
|
|
13040
|
+
function pathContains(directory, env, platform) {
|
|
13041
|
+
const api = pathApi(platform);
|
|
13042
|
+
return (env.PATH ?? env.Path ?? "").split(api.delimiter).some((entry) => entry && samePath(entry, directory, platform));
|
|
13043
|
+
}
|
|
13044
|
+
function defaultWritable(path) {
|
|
13045
|
+
let candidate = path;
|
|
13046
|
+
while (!existsSync2(candidate) && dirname3(candidate) !== candidate) candidate = dirname3(candidate);
|
|
13047
|
+
try {
|
|
13048
|
+
if (!statSync(candidate).isDirectory()) return false;
|
|
13049
|
+
const probe = join3(candidate, `.actionway-doctor-${randomBytes2(6).toString("hex")}`);
|
|
13050
|
+
writeFileSync3(probe, "");
|
|
13051
|
+
rmSync2(probe, { force: true });
|
|
13052
|
+
return true;
|
|
13053
|
+
} catch {
|
|
13054
|
+
return false;
|
|
13055
|
+
}
|
|
13056
|
+
}
|
|
13057
|
+
function checkLoopback() {
|
|
13058
|
+
return new Promise((resolve3, reject) => {
|
|
13059
|
+
const server = createServer2();
|
|
13060
|
+
server.once("error", reject);
|
|
13061
|
+
server.listen(0, "127.0.0.1", () => server.close((error) => error ? reject(error) : resolve3()));
|
|
13062
|
+
});
|
|
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
|
+
}
|
|
13069
|
+
function powerShellPolicy() {
|
|
13070
|
+
const result = spawnSync2(
|
|
13071
|
+
"powershell.exe",
|
|
13072
|
+
["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", "Get-ExecutionPolicy"],
|
|
13073
|
+
{ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 5e3, windowsHide: true }
|
|
13074
|
+
);
|
|
13075
|
+
return result.status === 0 ? result.stdout.trim() : null;
|
|
13076
|
+
}
|
|
13077
|
+
function npmCheck(id, result, operation, success) {
|
|
13078
|
+
const output = commandOutput(result);
|
|
13079
|
+
if (output !== null) return success(output);
|
|
13080
|
+
const error = npmFailure(result, operation);
|
|
13081
|
+
return fail2(id, error.message, error.hint ?? "inspect the npm configuration and retry");
|
|
13082
|
+
}
|
|
13083
|
+
async function diagnoseActionway(options = {}, dependencies = {}) {
|
|
13084
|
+
const env = dependencies.env ?? process.env;
|
|
13085
|
+
const platform = dependencies.platform ?? process.platform;
|
|
13086
|
+
const architecture = dependencies.architecture ?? process.arch;
|
|
13087
|
+
const nodeVersion = dependencies.nodeVersion ?? process.versions.node;
|
|
13088
|
+
const npm = dependencies.runNpm ?? runNpm;
|
|
13089
|
+
const pathExists = dependencies.pathExists ?? existsSync2;
|
|
13090
|
+
const pathWritable = dependencies.pathWritable ?? defaultWritable;
|
|
13091
|
+
const fetchOAuth = dependencies.fetchOAuth ?? fetchCliOAuthConfig;
|
|
13092
|
+
const loopback = dependencies.checkLoopback ?? checkLoopback;
|
|
13093
|
+
const readPolicy = dependencies.readPowerShellPolicy ?? powerShellPolicy;
|
|
13094
|
+
const resolveCommand = dependencies.resolveCommand ?? defaultResolveCommand;
|
|
13095
|
+
const checks = [];
|
|
13096
|
+
const configDir = actionwayConfigDir(env);
|
|
13097
|
+
const nodeMajor = Number.parseInt(nodeVersion.split(".")[0] ?? "", 10);
|
|
13098
|
+
checks.push(
|
|
13099
|
+
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")
|
|
13100
|
+
);
|
|
13101
|
+
if (platform === "linux" && (env.WSL_DISTRO_NAME || env.WSL_INTEROP)) {
|
|
13102
|
+
checks.push(
|
|
13103
|
+
warn(
|
|
13104
|
+
"execution_environment",
|
|
13105
|
+
"WSL detected; browser loopback may not share the Windows host network namespace.",
|
|
13106
|
+
"run Actionway login from the Windows host if the browser callback cannot return"
|
|
13107
|
+
)
|
|
13108
|
+
);
|
|
13109
|
+
} else {
|
|
13110
|
+
checks.push(pass("execution_environment", `${platform}/${architecture} host environment detected.`));
|
|
13111
|
+
}
|
|
13112
|
+
checks.push(
|
|
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")
|
|
13114
|
+
);
|
|
13115
|
+
const registryResult = npm(["config", "get", "registry"], { env, platform, timeoutMs: 15e3 });
|
|
13116
|
+
checks.push(
|
|
13117
|
+
npmCheck("npm", registryResult, "npm discovery", (registry) => pass("npm", `npm is available; registry is ${safeRegistry(registry)}.`))
|
|
13118
|
+
);
|
|
13119
|
+
const scopeResult = npm(["config", "get", "@actionway:registry"], { env, platform, timeoutMs: 15e3 });
|
|
13120
|
+
const scopeRegistry = commandOutput(scopeResult);
|
|
13121
|
+
if (scopeRegistry && !["null", "undefined"].includes(scopeRegistry.toLowerCase())) {
|
|
13122
|
+
const safe = safeRegistry(scopeRegistry);
|
|
13123
|
+
checks.push(
|
|
13124
|
+
safe.startsWith("https://registry.npmjs.org/") ? pass("scope_registry", `@actionway packages use ${safe}.`) : warn(
|
|
13125
|
+
"scope_registry",
|
|
13126
|
+
`@actionway packages are mapped to ${safe}.`,
|
|
13127
|
+
"make sure that registry mirrors public @actionway/cli, or install from the public npm registry when policy permits"
|
|
13128
|
+
)
|
|
13129
|
+
);
|
|
13130
|
+
} else if (scopeResult.error || scopeResult.status !== 0) {
|
|
13131
|
+
checks.push(warn("scope_registry", "Could not read the @actionway npm scope mapping.", "inspect npm user configuration"));
|
|
13132
|
+
} else {
|
|
13133
|
+
checks.push(pass("scope_registry", "No custom @actionway npm registry mapping is configured."));
|
|
13134
|
+
}
|
|
13135
|
+
const prefixResult = npm(["prefix", "--global"], { env, platform, timeoutMs: 15e3 });
|
|
13136
|
+
const prefix = commandOutput(prefixResult);
|
|
13137
|
+
if (!prefix) {
|
|
13138
|
+
const error = npmFailure(prefixResult, "npm global prefix lookup");
|
|
13139
|
+
checks.push(fail2("global_prefix", error.message, error.hint ?? "inspect npm global configuration"));
|
|
13140
|
+
} else {
|
|
13141
|
+
const api = pathApi(platform);
|
|
13142
|
+
const binDir = platform === "win32" ? prefix : api.join(prefix, "bin");
|
|
13143
|
+
const binName = platform === "win32" ? "actionway.cmd" : "actionway";
|
|
13144
|
+
let resolvedCache;
|
|
13145
|
+
const resolved = () => resolvedCache !== void 0 ? resolvedCache : resolvedCache = resolveCommand("actionway");
|
|
13146
|
+
checks.push(
|
|
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")
|
|
13148
|
+
);
|
|
13149
|
+
checks.push(
|
|
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")
|
|
13151
|
+
);
|
|
13152
|
+
}
|
|
13153
|
+
if (platform === "win32") {
|
|
13154
|
+
const policy = readPolicy();
|
|
13155
|
+
checks.push(
|
|
13156
|
+
policy && ["Restricted", "AllSigned"].includes(policy) ? warn(
|
|
13157
|
+
"powershell_policy",
|
|
13158
|
+
`PowerShell execution policy is ${policy}; npm's actionway.ps1 shim may be blocked.`,
|
|
13159
|
+
"use actionway.cmd; do not weaken organization policy without approval"
|
|
13160
|
+
) : pass("powershell_policy", `PowerShell execution policy is ${policy || "not available"}; actionway.cmd remains the stable entrypoint.`)
|
|
13161
|
+
);
|
|
13162
|
+
}
|
|
13163
|
+
try {
|
|
13164
|
+
await loopback();
|
|
13165
|
+
checks.push(pass("loopback", "A random 127.0.0.1 callback port can be opened."));
|
|
13166
|
+
} catch (error) {
|
|
13167
|
+
checks.push(
|
|
13168
|
+
fail2(
|
|
13169
|
+
"loopback",
|
|
13170
|
+
`Cannot open the local OAuth callback: ${error instanceof Error ? error.message : String(error)}`,
|
|
13171
|
+
"run login from a normal host terminal and check firewall or sandbox policy"
|
|
13172
|
+
)
|
|
13173
|
+
);
|
|
13174
|
+
}
|
|
13175
|
+
if (options.network === false) {
|
|
13176
|
+
checks.push({ id: "npm_registry", status: "skip", message: "npm registry check was skipped." });
|
|
13177
|
+
checks.push({ id: "actionway_oauth", status: "skip", message: "Actionway OAuth discovery check was skipped." });
|
|
13178
|
+
} else {
|
|
13179
|
+
const packageResult = npm(["view", `${PACKAGE_NAME3}@latest`, "version"], { env, platform, timeoutMs: 15e3 });
|
|
13180
|
+
checks.push(
|
|
13181
|
+
npmCheck(
|
|
13182
|
+
"npm_registry",
|
|
13183
|
+
packageResult,
|
|
13184
|
+
"Actionway package lookup",
|
|
13185
|
+
(version) => pass("npm_registry", `${PACKAGE_NAME3}@latest is reachable (${version.split(/\r?\n/).at(-1)}).`)
|
|
13186
|
+
)
|
|
13187
|
+
);
|
|
13188
|
+
try {
|
|
13189
|
+
await fetchOAuth(resolveGatewayUrl(void 0, env));
|
|
13190
|
+
checks.push(pass("actionway_oauth", "Actionway OAuth discovery is reachable."));
|
|
13191
|
+
} catch (error) {
|
|
13192
|
+
checks.push(
|
|
13193
|
+
fail2(
|
|
13194
|
+
"actionway_oauth",
|
|
13195
|
+
`Actionway OAuth discovery failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
13196
|
+
"check actionway.ai access, proxy, DNS, and corporate CA configuration"
|
|
13197
|
+
)
|
|
13198
|
+
);
|
|
13199
|
+
}
|
|
13200
|
+
}
|
|
13201
|
+
return {
|
|
13202
|
+
ready: !checks.some((check) => check.status === "fail"),
|
|
13203
|
+
platform,
|
|
13204
|
+
architecture,
|
|
13205
|
+
node_version: nodeVersion,
|
|
13206
|
+
config_dir: configDir,
|
|
13207
|
+
checks
|
|
13208
|
+
};
|
|
13209
|
+
}
|
|
13210
|
+
function registerDoctorCommand(program2) {
|
|
13211
|
+
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) => {
|
|
13212
|
+
const report = await diagnoseActionway({ network: options.network !== false });
|
|
13213
|
+
const command = process.platform === "win32" ? "actionway.cmd" : "actionway";
|
|
13214
|
+
ok({
|
|
13215
|
+
...report,
|
|
13216
|
+
hint: report.ready ? `Actionway CLI prerequisites are ready. Run \`${command} init\`.` : `Resolve the failed checks, then run \`${command} doctor\` again.`
|
|
13217
|
+
});
|
|
13218
|
+
});
|
|
13219
|
+
}
|
|
13220
|
+
|
|
12910
13221
|
// src/lib/install-session.ts
|
|
12911
13222
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
12912
13223
|
|
|
12913
13224
|
// src/lib/skill.ts
|
|
12914
|
-
import { cpSync, existsSync as
|
|
12915
|
-
import { dirname as
|
|
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";
|
|
12916
13227
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
12917
13228
|
var InitError = class extends Error {
|
|
12918
13229
|
hint;
|
|
@@ -12924,17 +13235,18 @@ var InitError = class extends Error {
|
|
|
12924
13235
|
};
|
|
12925
13236
|
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
13237
|
function resolveBundledSkill(moduleUrl = import.meta.url) {
|
|
12927
|
-
const here =
|
|
13238
|
+
const here = dirname4(fileURLToPath3(moduleUrl));
|
|
12928
13239
|
for (const candidate of [resolve2(here, "../assets/skill/actionway"), resolve2(here, "../../assets/skill/actionway")]) {
|
|
12929
|
-
if (
|
|
13240
|
+
if (existsSync3(join4(candidate, "SKILL.md"))) return realpathSync2(candidate);
|
|
12930
13241
|
}
|
|
12931
13242
|
throw new InitError("the bundled Actionway Skill is missing", "reinstall @actionway/cli, then run `actionway init` again");
|
|
12932
13243
|
}
|
|
12933
13244
|
function skillSourcePath(env = process.env) {
|
|
12934
|
-
return
|
|
13245
|
+
return join4(actionwayConfigDir(env), "skill", "actionway");
|
|
12935
13246
|
}
|
|
12936
13247
|
function stampSkillVersion(skillMarkdown, version) {
|
|
12937
|
-
const
|
|
13248
|
+
const newline = skillMarkdown.includes("\r\n") ? "\r\n" : "\n";
|
|
13249
|
+
const lines = skillMarkdown.split(/\r?\n/);
|
|
12938
13250
|
if (lines[0]?.trim() !== "---") return skillMarkdown;
|
|
12939
13251
|
let closing = lines.indexOf("---", 1);
|
|
12940
13252
|
if (closing < 0) return skillMarkdown;
|
|
@@ -12958,28 +13270,28 @@ function stampSkillVersion(skillMarkdown, version) {
|
|
|
12958
13270
|
for (let index = metadataIndex + 1; index < metadataEnd; index += 1) {
|
|
12959
13271
|
if (/^\s{2}version\s*:/.test(lines[index] ?? "")) {
|
|
12960
13272
|
lines[index] = versionLine;
|
|
12961
|
-
return lines.join(
|
|
13273
|
+
return lines.join(newline);
|
|
12962
13274
|
}
|
|
12963
13275
|
}
|
|
12964
13276
|
lines.splice(metadataIndex + 1, 0, versionLine);
|
|
12965
|
-
return lines.join(
|
|
13277
|
+
return lines.join(newline);
|
|
12966
13278
|
}
|
|
12967
13279
|
lines.splice(closing, 0, "metadata:", versionLine);
|
|
12968
|
-
return lines.join(
|
|
13280
|
+
return lines.join(newline);
|
|
12969
13281
|
}
|
|
12970
13282
|
function materializeSkill(options = {}) {
|
|
12971
13283
|
const env = options.env ?? process.env;
|
|
12972
13284
|
const source = options.source ? realpathSync2(options.source) : resolveBundledSkill();
|
|
12973
|
-
if (!
|
|
13285
|
+
if (!existsSync3(join4(source, "SKILL.md"))) {
|
|
12974
13286
|
throw new InitError("the Actionway Skill source is invalid", "reinstall @actionway/cli and retry init");
|
|
12975
13287
|
}
|
|
12976
13288
|
const version = options.version ?? readOwnVersion();
|
|
12977
13289
|
const target = skillSourcePath(env);
|
|
12978
|
-
mkdirSync3(
|
|
12979
|
-
|
|
13290
|
+
mkdirSync3(dirname4(target), { recursive: true, mode: 448 });
|
|
13291
|
+
rmSync3(target, { recursive: true, force: true });
|
|
12980
13292
|
cpSync(source, target, { recursive: true });
|
|
12981
|
-
const skillPath =
|
|
12982
|
-
|
|
13293
|
+
const skillPath = join4(target, "SKILL.md");
|
|
13294
|
+
writeFileSync4(skillPath, stampSkillVersion(readFileSync4(skillPath, "utf8"), version));
|
|
12983
13295
|
return { source: target, version, instruction: ACTIONWAY_SKILL_INSTRUCTION };
|
|
12984
13296
|
}
|
|
12985
13297
|
|
|
@@ -13140,12 +13452,12 @@ function registerInitCommand(program2) {
|
|
|
13140
13452
|
}
|
|
13141
13453
|
|
|
13142
13454
|
// src/commands/update.ts
|
|
13143
|
-
import { spawnSync as
|
|
13455
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
13144
13456
|
function refreshSkillWithNewBinary() {
|
|
13145
13457
|
try {
|
|
13146
13458
|
const binPath = process.argv[1];
|
|
13147
13459
|
if (!binPath) return { refreshed: false };
|
|
13148
|
-
const result =
|
|
13460
|
+
const result = spawnSync3(process.execPath, [binPath, "init", "--skill-only"], {
|
|
13149
13461
|
encoding: "utf8",
|
|
13150
13462
|
stdio: ["ignore", "pipe", "pipe"],
|
|
13151
13463
|
timeout: 3e4
|
|
@@ -13176,8 +13488,11 @@ function registerUpdateCommand(program2) {
|
|
|
13176
13488
|
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
13489
|
});
|
|
13178
13490
|
} catch (error) {
|
|
13491
|
+
if (error instanceof CliError) {
|
|
13492
|
+
fail({ code: error.code, message: error.message, hint: error.hint, extra: error.extra });
|
|
13493
|
+
}
|
|
13179
13494
|
fail({
|
|
13180
|
-
code: "
|
|
13495
|
+
code: "E_UPDATE_FAILED",
|
|
13181
13496
|
message: error instanceof Error ? error.message : String(error),
|
|
13182
13497
|
hint: "retry later or reinstall @actionway/cli@latest"
|
|
13183
13498
|
});
|
|
@@ -13426,6 +13741,7 @@ function buildProgram() {
|
|
|
13426
13741
|
program2.version(version);
|
|
13427
13742
|
program2.description("Actionway CLI: run Actionway media and research capabilities from your local agent.");
|
|
13428
13743
|
registerInitCommand(program2);
|
|
13744
|
+
registerDoctorCommand(program2);
|
|
13429
13745
|
registerUpdateCommand(program2);
|
|
13430
13746
|
registerAuthCommands(program2);
|
|
13431
13747
|
registerAccountCommands(program2, getTransport2);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actionway/cli",
|
|
3
|
-
"version": "0.18.
|
|
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": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"node": ">=20"
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
|
-
"build": "
|
|
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"
|