@actionway/cli 0.17.1 → 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 +28 -2
- package/assets/skill/actionway/SKILL.md +1 -1
- package/dist/index.js +319 -32
- package/package.json +2 -2
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,8 +33,10 @@ 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
|
|
15
|
-
|
|
36
|
+
- 本壳另有 `init` / `doctor` / `update` / `login` / `logout` / `whoami`,cli-core
|
|
37
|
+
继续提供 wait/poll 与已冻结的共享 typed commands。
|
|
38
|
+
|
|
39
|
+
- **账户响应无本地换算**:`account` 响应同时保留精确整数 micros 与可直接展示的 USD 十进制字符串;钱包包含快照更新时间,交易包含状态、说明、完成时间和 signed balance delta。Usage/Transactions 支持 ISO 时间窗口、领域筛选和 opaque cursor,返回的 summary 覆盖完整筛选集合而非当前页。CLI 不自行换算资金,也不改变服务端返回的字段语义。
|
|
16
40
|
- **计费零逻辑**:报价、扣费、错误面裁剪全在 gateway;本壳只透传。旧
|
|
17
41
|
stock download 直连 CDN、Gateway 无法观察结局,因此在阶段二的
|
|
18
42
|
command-level 计费编排完成前明确 fail-closed,不把迁移缺口当免费能力。
|
|
@@ -39,6 +63,8 @@ pnpm cli update --check # 查新版(24h 缓存)
|
|
|
39
63
|
pnpm cli -- tools search "generate a product image"
|
|
40
64
|
pnpm cli -- tools inspect media.image.generate
|
|
41
65
|
pnpm cli -- account usage
|
|
66
|
+
pnpm cli -- account usage --from 2026-08-01 --to 2026-09-01 --service generate-image
|
|
67
|
+
pnpm cli -- account transactions --from 2026-08-01 --kind top_up
|
|
42
68
|
pnpm cli -- generate-video --print-schema
|
|
43
69
|
```
|
|
44
70
|
|
|
@@ -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 =
|
|
12283
|
-
|
|
12284
|
-
|
|
12285
|
-
|
|
12286
|
-
|
|
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
|
-
|
|
12294
|
-
npmExecutable(),
|
|
12353
|
+
runNpmText(
|
|
12295
12354
|
["install", "--global", "--no-audit", "--no-fund", "--no-update-notifier", `${PACKAGE_NAME2}@latest`],
|
|
12296
|
-
|
|
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;
|
|
@@ -12876,6 +12932,23 @@ function failAccount(error) {
|
|
|
12876
12932
|
}
|
|
12877
12933
|
fail({ code: "E_BACKEND", message: error instanceof Error ? error.message : String(error) });
|
|
12878
12934
|
}
|
|
12935
|
+
function accountEndpoint(path, options) {
|
|
12936
|
+
const parameters = new URLSearchParams();
|
|
12937
|
+
for (const [name, value] of [
|
|
12938
|
+
["limit", options.limit],
|
|
12939
|
+
["cursor", options.cursor],
|
|
12940
|
+
["from", options.from],
|
|
12941
|
+
["to", options.to],
|
|
12942
|
+
["status", options.status],
|
|
12943
|
+
["service_id", options.service],
|
|
12944
|
+
["pricing_kind", options.pricingKind],
|
|
12945
|
+
["kind", options.kind]
|
|
12946
|
+
]) {
|
|
12947
|
+
if (value !== void 0) parameters.set(name, value);
|
|
12948
|
+
}
|
|
12949
|
+
const query = parameters.toString();
|
|
12950
|
+
return `v1/account/${path}${query ? `?${query}` : ""}`;
|
|
12951
|
+
}
|
|
12879
12952
|
async function read(endpoint, getTransport3) {
|
|
12880
12953
|
try {
|
|
12881
12954
|
ok(await getTransport3().getBusiness(endpoint, { retries: 1 }));
|
|
@@ -12885,17 +12958,226 @@ async function read(endpoint, getTransport3) {
|
|
|
12885
12958
|
}
|
|
12886
12959
|
function registerAccountCommands(program2, getTransport3) {
|
|
12887
12960
|
const account = program2.command("account").description("Read your Actionway USD wallet, service usage, and wallet transactions.");
|
|
12888
|
-
account.command("usage").description("
|
|
12961
|
+
account.command("usage").description("Query held, charged, released, and free capability calls.").option("--limit <count>", "Maximum records in this page (1-100).").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>", "held, settled, or expired.").option("--service <service-id>", "Exact public serviceId.").option("--pricing-kind <kind>", "priced or free.").action((options) => read(accountEndpoint("usage", options), getTransport3));
|
|
12889
12962
|
account.command("wallet").description("Show the current available and held Actionway USD wallet balance.").action(() => read("v1/account/wallet", getTransport3));
|
|
12890
|
-
account.command("transactions").description("
|
|
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));
|
|
12964
|
+
}
|
|
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
|
+
});
|
|
12891
13173
|
}
|
|
12892
13174
|
|
|
12893
13175
|
// src/lib/install-session.ts
|
|
12894
13176
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
12895
13177
|
|
|
12896
13178
|
// src/lib/skill.ts
|
|
12897
|
-
import { cpSync, existsSync as
|
|
12898
|
-
import { dirname as
|
|
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";
|
|
12899
13181
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
12900
13182
|
var InitError = class extends Error {
|
|
12901
13183
|
hint;
|
|
@@ -12907,9 +13189,9 @@ var InitError = class extends Error {
|
|
|
12907
13189
|
};
|
|
12908
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.";
|
|
12909
13191
|
function resolveBundledSkill(moduleUrl = import.meta.url) {
|
|
12910
|
-
const here =
|
|
13192
|
+
const here = dirname4(fileURLToPath3(moduleUrl));
|
|
12911
13193
|
for (const candidate of [resolve2(here, "../assets/skill/actionway"), resolve2(here, "../../assets/skill/actionway")]) {
|
|
12912
|
-
if (
|
|
13194
|
+
if (existsSync3(join3(candidate, "SKILL.md"))) return realpathSync2(candidate);
|
|
12913
13195
|
}
|
|
12914
13196
|
throw new InitError("the bundled Actionway Skill is missing", "reinstall @actionway/cli, then run `actionway init` again");
|
|
12915
13197
|
}
|
|
@@ -12917,7 +13199,8 @@ function skillSourcePath(env = process.env) {
|
|
|
12917
13199
|
return join3(actionwayConfigDir(env), "skill", "actionway");
|
|
12918
13200
|
}
|
|
12919
13201
|
function stampSkillVersion(skillMarkdown, version) {
|
|
12920
|
-
const
|
|
13202
|
+
const newline = skillMarkdown.includes("\r\n") ? "\r\n" : "\n";
|
|
13203
|
+
const lines = skillMarkdown.split(/\r?\n/);
|
|
12921
13204
|
if (lines[0]?.trim() !== "---") return skillMarkdown;
|
|
12922
13205
|
let closing = lines.indexOf("---", 1);
|
|
12923
13206
|
if (closing < 0) return skillMarkdown;
|
|
@@ -12941,24 +13224,24 @@ function stampSkillVersion(skillMarkdown, version) {
|
|
|
12941
13224
|
for (let index = metadataIndex + 1; index < metadataEnd; index += 1) {
|
|
12942
13225
|
if (/^\s{2}version\s*:/.test(lines[index] ?? "")) {
|
|
12943
13226
|
lines[index] = versionLine;
|
|
12944
|
-
return lines.join(
|
|
13227
|
+
return lines.join(newline);
|
|
12945
13228
|
}
|
|
12946
13229
|
}
|
|
12947
13230
|
lines.splice(metadataIndex + 1, 0, versionLine);
|
|
12948
|
-
return lines.join(
|
|
13231
|
+
return lines.join(newline);
|
|
12949
13232
|
}
|
|
12950
13233
|
lines.splice(closing, 0, "metadata:", versionLine);
|
|
12951
|
-
return lines.join(
|
|
13234
|
+
return lines.join(newline);
|
|
12952
13235
|
}
|
|
12953
13236
|
function materializeSkill(options = {}) {
|
|
12954
13237
|
const env = options.env ?? process.env;
|
|
12955
13238
|
const source = options.source ? realpathSync2(options.source) : resolveBundledSkill();
|
|
12956
|
-
if (!
|
|
13239
|
+
if (!existsSync3(join3(source, "SKILL.md"))) {
|
|
12957
13240
|
throw new InitError("the Actionway Skill source is invalid", "reinstall @actionway/cli and retry init");
|
|
12958
13241
|
}
|
|
12959
13242
|
const version = options.version ?? readOwnVersion();
|
|
12960
13243
|
const target = skillSourcePath(env);
|
|
12961
|
-
mkdirSync3(
|
|
13244
|
+
mkdirSync3(dirname4(target), { recursive: true, mode: 448 });
|
|
12962
13245
|
rmSync2(target, { recursive: true, force: true });
|
|
12963
13246
|
cpSync(source, target, { recursive: true });
|
|
12964
13247
|
const skillPath = join3(target, "SKILL.md");
|
|
@@ -13123,12 +13406,12 @@ function registerInitCommand(program2) {
|
|
|
13123
13406
|
}
|
|
13124
13407
|
|
|
13125
13408
|
// src/commands/update.ts
|
|
13126
|
-
import { spawnSync as
|
|
13409
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
13127
13410
|
function refreshSkillWithNewBinary() {
|
|
13128
13411
|
try {
|
|
13129
13412
|
const binPath = process.argv[1];
|
|
13130
13413
|
if (!binPath) return { refreshed: false };
|
|
13131
|
-
const result =
|
|
13414
|
+
const result = spawnSync3(process.execPath, [binPath, "init", "--skill-only"], {
|
|
13132
13415
|
encoding: "utf8",
|
|
13133
13416
|
stdio: ["ignore", "pipe", "pipe"],
|
|
13134
13417
|
timeout: 3e4
|
|
@@ -13159,8 +13442,11 @@ function registerUpdateCommand(program2) {
|
|
|
13159
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."
|
|
13160
13443
|
});
|
|
13161
13444
|
} catch (error) {
|
|
13445
|
+
if (error instanceof CliError) {
|
|
13446
|
+
fail({ code: error.code, message: error.message, hint: error.hint, extra: error.extra });
|
|
13447
|
+
}
|
|
13162
13448
|
fail({
|
|
13163
|
-
code: "
|
|
13449
|
+
code: "E_UPDATE_FAILED",
|
|
13164
13450
|
message: error instanceof Error ? error.message : String(error),
|
|
13165
13451
|
hint: "retry later or reinstall @actionway/cli@latest"
|
|
13166
13452
|
});
|
|
@@ -13409,6 +13695,7 @@ function buildProgram() {
|
|
|
13409
13695
|
program2.version(version);
|
|
13410
13696
|
program2.description("Actionway CLI: run Actionway media and research capabilities from your local agent.");
|
|
13411
13697
|
registerInitCommand(program2);
|
|
13698
|
+
registerDoctorCommand(program2);
|
|
13412
13699
|
registerUpdateCommand(program2);
|
|
13413
13700
|
registerAuthCommands(program2);
|
|
13414
13701
|
registerAccountCommands(program2, getTransport2);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actionway/cli",
|
|
3
|
-
"version": "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": "
|
|
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"
|