@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 +23 -1
- package/assets/skill/actionway/SKILL.md +1 -1
- package/dist/index.js +300 -30
- 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,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 =
|
|
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;
|
|
@@ -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
|
|
12915
|
-
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";
|
|
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 =
|
|
13192
|
+
const here = dirname4(fileURLToPath3(moduleUrl));
|
|
12928
13193
|
for (const candidate of [resolve2(here, "../assets/skill/actionway"), resolve2(here, "../../assets/skill/actionway")]) {
|
|
12929
|
-
if (
|
|
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
|
|
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(
|
|
13227
|
+
return lines.join(newline);
|
|
12962
13228
|
}
|
|
12963
13229
|
}
|
|
12964
13230
|
lines.splice(metadataIndex + 1, 0, versionLine);
|
|
12965
|
-
return lines.join(
|
|
13231
|
+
return lines.join(newline);
|
|
12966
13232
|
}
|
|
12967
13233
|
lines.splice(closing, 0, "metadata:", versionLine);
|
|
12968
|
-
return lines.join(
|
|
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 (!
|
|
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(
|
|
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
|
|
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 =
|
|
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: "
|
|
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.
|
|
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"
|