@actionway/cli-dev 0.0.0-dev.33070923659.68d2eb14d93c → 0.0.0-dev.33147618708.51de2d32a24e
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 +6 -3
- package/dist/index.js +127 -221
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ actionway doctor
|
|
|
22
22
|
actionway init
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
`doctor` 只输出脱敏后的 Node/npm、registry、global PATH
|
|
25
|
+
`doctor` 只输出脱敏后的 Node/npm、registry、global PATH、配置目录
|
|
26
26
|
和 OAuth discovery 检查;它不会修改 npm、代理、CA、PATH 或 PowerShell policy。
|
|
27
27
|
|
|
28
28
|
### Development 通道
|
|
@@ -48,8 +48,11 @@ actionway --agent-host=workbuddy tools search "generate a product image"
|
|
|
48
48
|
|
|
49
49
|
不带 `--agent-host` 表示用户直接使用 CLI。该声明不写入共享凭据,所以同一个用户和全局 CLI 可以交替被多个 Agent 使用。CLI 同时从每次进程的 Node.js 运行环境识别 Windows、macOS、WSL 或 Linux;这些字段只作为 Site 侧产品分析事件属性,不参与鉴权、计费或 Gateway 路由。
|
|
50
50
|
|
|
51
|
-
- **auth 唯一途径:Clerk OAuth(PKCE
|
|
52
|
-
`{public origin}/api/auth/cli-config`
|
|
51
|
+
- **auth 唯一途径:Clerk OAuth(PKCE)+ device 配对**。issuer / client_id 运行时从
|
|
52
|
+
`{public origin}/api/auth/cli-config` 发现;登录不依赖本机浏览器回调——CLI 展示
|
|
53
|
+
短码与 `{public origin}/cli/activate` 验证 URL,用户在任意设备的浏览器上登录并
|
|
54
|
+
批准后 CLI 轮询取回一次性授权码并在本地完成 PKCE token 交换(远端 / headless /
|
|
55
|
+
WSL 环境可直接使用,`--no-browser` 仅打印 URL 不尝试开浏览器);
|
|
53
56
|
凭据存 `~/.actionway/credentials.json`(POSIX 上为 0600 owner-only 文件;
|
|
54
57
|
Windows 上 chmod 无效,保密性依赖 `%USERPROFILE%` 的继承 ACL——不要把
|
|
55
58
|
`ACTIONWAY_CONFIG_DIR` 指到共享目录)。CLI 从不打印或存储 OAuth client secret。
|
package/dist/index.js
CHANGED
|
@@ -12069,7 +12069,6 @@ var {
|
|
|
12069
12069
|
import { spawn } from "node:child_process";
|
|
12070
12070
|
import { createHash, randomBytes } from "node:crypto";
|
|
12071
12071
|
import { chmodSync as chmodSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
12072
|
-
import { createServer } from "node:http";
|
|
12073
12072
|
import { join as join2 } from "node:path";
|
|
12074
12073
|
|
|
12075
12074
|
// src/lib/update-state.ts
|
|
@@ -12415,6 +12414,9 @@ var REFRESH_SKEW_S = 30;
|
|
|
12415
12414
|
var TOKEN_TIMEOUT_MS = 2e4;
|
|
12416
12415
|
var DISCOVERY_TIMEOUT_MS = 1e4;
|
|
12417
12416
|
var SESSION_TIMEOUT_MS = 2e4;
|
|
12417
|
+
var DEVICE_START_TIMEOUT_MS = 1e4;
|
|
12418
|
+
var DEVICE_POLL_TIMEOUT_MS = 1e4;
|
|
12419
|
+
var DEFAULT_POLL_INTERVAL_S = 3;
|
|
12418
12420
|
var DEFAULT_LOGIN_TIMEOUT_MS = 12e5;
|
|
12419
12421
|
function stripSlash(url) {
|
|
12420
12422
|
return url.replace(/\/+$/, "");
|
|
@@ -12524,116 +12526,80 @@ function randomBase64Url() {
|
|
|
12524
12526
|
function pkceChallenge(verifier) {
|
|
12525
12527
|
return createHash("sha256").update(verifier).digest("base64url");
|
|
12526
12528
|
}
|
|
12527
|
-
|
|
12528
|
-
|
|
12529
|
-
|
|
12530
|
-
|
|
12531
|
-
|
|
12532
|
-
|
|
12533
|
-
|
|
12534
|
-
|
|
12535
|
-
|
|
12536
|
-
|
|
12537
|
-
|
|
12538
|
-
|
|
12539
|
-
|
|
12540
|
-
|
|
12541
|
-
"
|
|
12542
|
-
|
|
12543
|
-
|
|
12544
|
-
|
|
12545
|
-
|
|
12546
|
-
|
|
12547
|
-
|
|
12548
|
-
|
|
12549
|
-
|
|
12550
|
-
|
|
12551
|
-
|
|
12552
|
-
|
|
12529
|
+
async function readDeviceJson(response) {
|
|
12530
|
+
let value;
|
|
12531
|
+
try {
|
|
12532
|
+
value = await response.json();
|
|
12533
|
+
} catch {
|
|
12534
|
+
throw new CliError("E_BACKEND", `Actionway sign-in returned an invalid response (${response.status})`);
|
|
12535
|
+
}
|
|
12536
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
12537
|
+
throw new CliError("E_BACKEND", `Actionway sign-in returned an invalid response (${response.status})`);
|
|
12538
|
+
}
|
|
12539
|
+
return value;
|
|
12540
|
+
}
|
|
12541
|
+
function deviceErrorDetail(value) {
|
|
12542
|
+
const error = value.error;
|
|
12543
|
+
const code = typeof error?.code === "string" ? error.code : "";
|
|
12544
|
+
const message = typeof error?.message === "string" ? error.message : "";
|
|
12545
|
+
return [code, message].filter(Boolean).join(": ");
|
|
12546
|
+
}
|
|
12547
|
+
async function startDevicePairing(gateway, codeChallenge, fetchImpl = fetch) {
|
|
12548
|
+
const response = await fetchImpl(`${stripSlash(gateway)}/api/auth/cli-device/start`, {
|
|
12549
|
+
method: "POST",
|
|
12550
|
+
headers: { Accept: "application/json", "Content-Type": "application/json" },
|
|
12551
|
+
body: JSON.stringify({ code_challenge: codeChallenge }),
|
|
12552
|
+
signal: AbortSignal.timeout(DEVICE_START_TIMEOUT_MS)
|
|
12553
|
+
});
|
|
12554
|
+
const value = await readDeviceJson(response);
|
|
12555
|
+
if (!response.ok) {
|
|
12556
|
+
const detail = deviceErrorDetail(value);
|
|
12557
|
+
throw new CliError(
|
|
12558
|
+
"E_BACKEND",
|
|
12559
|
+
`Actionway sign-in is unavailable (${response.status}${detail ? `; ${detail}` : ""})`,
|
|
12560
|
+
"check auth_context.gateway_url is the intended Actionway environment and run `actionway login` again"
|
|
12561
|
+
);
|
|
12562
|
+
}
|
|
12563
|
+
if (typeof value.device_code !== "string" || !value.device_code || typeof value.user_code !== "string" || !value.user_code || typeof value.verification_url !== "string" || typeof value.verification_url_complete !== "string" || typeof value.redirect_uri !== "string") {
|
|
12564
|
+
throw new CliError("E_BACKEND", "Actionway sign-in returned an invalid pairing response");
|
|
12565
|
+
}
|
|
12566
|
+
return {
|
|
12567
|
+
deviceCode: value.device_code,
|
|
12568
|
+
userCode: value.user_code,
|
|
12569
|
+
verificationUrl: value.verification_url,
|
|
12570
|
+
verificationUrlComplete: value.verification_url_complete,
|
|
12571
|
+
redirectUri: value.redirect_uri,
|
|
12572
|
+
expiresInS: typeof value.expires_in === "number" && value.expires_in > 0 ? value.expires_in : 900,
|
|
12573
|
+
intervalS: typeof value.interval === "number" && value.interval >= 1 && value.interval <= 30 ? value.interval : DEFAULT_POLL_INTERVAL_S
|
|
12574
|
+
};
|
|
12575
|
+
}
|
|
12576
|
+
async function pollDevicePairing(gateway, deviceCode, fetchImpl = fetch) {
|
|
12577
|
+
const response = await fetchImpl(`${stripSlash(gateway)}/api/auth/cli-device/poll`, {
|
|
12578
|
+
method: "POST",
|
|
12579
|
+
headers: { Accept: "application/json", "Content-Type": "application/json" },
|
|
12580
|
+
body: JSON.stringify({ device_code: deviceCode }),
|
|
12581
|
+
signal: AbortSignal.timeout(DEVICE_POLL_TIMEOUT_MS)
|
|
12582
|
+
});
|
|
12583
|
+
const value = await readDeviceJson(response);
|
|
12584
|
+
if (!response.ok) {
|
|
12585
|
+
const detail = deviceErrorDetail(value);
|
|
12586
|
+
throw new CliError("E_BACKEND", `Actionway sign-in polling failed (${response.status}${detail ? `; ${detail}` : ""})`);
|
|
12587
|
+
}
|
|
12588
|
+
switch (value.status) {
|
|
12589
|
+
case "pending":
|
|
12590
|
+
return { status: "pending" };
|
|
12591
|
+
case "approved":
|
|
12592
|
+
if (typeof value.code !== "string" || !value.code) {
|
|
12593
|
+
throw new CliError("E_BACKEND", "Actionway sign-in returned an invalid approval response");
|
|
12594
|
+
}
|
|
12595
|
+
return { status: "approved", code: value.code };
|
|
12596
|
+
case "denied":
|
|
12597
|
+
return { status: "denied" };
|
|
12598
|
+
case "expired":
|
|
12599
|
+
return { status: "expired" };
|
|
12600
|
+
default:
|
|
12601
|
+
throw new CliError("E_BACKEND", "Actionway sign-in returned an unknown pairing status");
|
|
12553
12602
|
}
|
|
12554
|
-
};
|
|
12555
|
-
function callbackLocale(acceptLanguage) {
|
|
12556
|
-
const header = Array.isArray(acceptLanguage) ? acceptLanguage.join(",") : acceptLanguage || "";
|
|
12557
|
-
const languages = header.split(",").map((entry, index) => {
|
|
12558
|
-
const [tag = "", ...parameters] = entry.trim().toLowerCase().split(";");
|
|
12559
|
-
const quality = parameters.find((value) => value.trim().startsWith("q="));
|
|
12560
|
-
const parsedQuality = quality ? Number.parseFloat(quality.split("=")[1] || "0") : 1;
|
|
12561
|
-
return { tag, quality: Number.isFinite(parsedQuality) ? parsedQuality : 0, index };
|
|
12562
|
-
}).filter(({ quality }) => quality > 0).sort((left, right) => right.quality - left.quality || left.index - right.index);
|
|
12563
|
-
for (const { tag } of languages) {
|
|
12564
|
-
if (tag === "zh" || tag.startsWith("zh-")) return "zh-CN";
|
|
12565
|
-
if (tag === "en" || tag.startsWith("en-")) return "en";
|
|
12566
|
-
}
|
|
12567
|
-
return "en";
|
|
12568
|
-
}
|
|
12569
|
-
function callbackPage(status, locale = "en") {
|
|
12570
|
-
const success = status === "success";
|
|
12571
|
-
const copy = CALLBACK_COPY[locale];
|
|
12572
|
-
const title = success ? copy.successTitle : copy.errorTitle;
|
|
12573
|
-
const message = success ? copy.successMessage : copy.errorMessage;
|
|
12574
|
-
const statusText = success ? copy.successStatus : copy.errorStatus;
|
|
12575
|
-
const accent = success ? "#a6f46f" : "#ff8b81";
|
|
12576
|
-
const accentRgb = success ? "166,244,111" : "255,139,129";
|
|
12577
|
-
const icon = success ? '<path d="m8.5 12.5 2.2 2.2 4.8-5.2"/>' : '<path d="M12 8v5m0 3.5v.01"/>';
|
|
12578
|
-
return `<!doctype html>
|
|
12579
|
-
<html lang="${locale}">
|
|
12580
|
-
<head>
|
|
12581
|
-
<meta charset="utf-8">
|
|
12582
|
-
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
12583
|
-
<meta name="color-scheme" content="dark">
|
|
12584
|
-
<title>Actionway \xB7 ${title}</title>
|
|
12585
|
-
<style>
|
|
12586
|
-
:root{color-scheme:dark}*{box-sizing:border-box}html,body{margin:0;min-height:100%}
|
|
12587
|
-
body{min-height:100vh;display:grid;place-items:center;overflow:hidden;padding:32px;color:#f4f6f1;background:#070908;font-family:Inter,ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
|
12588
|
-
body:before{content:"";position:fixed;inset:-20%;pointer-events:none;background:radial-gradient(circle at 22% 18%,rgba(${accentRgb},.13),transparent 25%),radial-gradient(circle at 82% 76%,rgba(109,130,255,.1),transparent 24%);filter:blur(8px)}
|
|
12589
|
-
body:after{content:"";position:fixed;inset:0;pointer-events:none;opacity:.32;background-image:linear-gradient(rgba(255,255,255,.045) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.045) 1px,transparent 1px);background-size:48px 48px;mask-image:radial-gradient(circle at center,black,transparent 78%)}
|
|
12590
|
-
main{position:relative;width:min(100%,720px);overflow:hidden;border:1px solid rgba(255,255,255,.11);border-radius:30px;background:linear-gradient(145deg,rgba(28,31,29,.92),rgba(13,15,14,.96));box-shadow:0 44px 120px rgba(0,0,0,.62),inset 0 1px 0 rgba(255,255,255,.07);animation:enter .7s cubic-bezier(.2,.8,.2,1) both}
|
|
12591
|
-
main:before{content:"";position:absolute;inset:0;pointer-events:none;background:linear-gradient(110deg,transparent 24%,rgba(255,255,255,.035) 44%,transparent 60%)}
|
|
12592
|
-
.topbar{position:relative;display:flex;align-items:center;justify-content:space-between;padding:22px 26px;border-bottom:1px solid rgba(255,255,255,.08)}
|
|
12593
|
-
.brand{display:flex;align-items:center;gap:11px;font-size:15px;font-weight:650;letter-spacing:-.02em}
|
|
12594
|
-
.brand-mark{position:relative;display:grid;place-items:center;width:29px;height:29px;border:1px solid rgba(255,255,255,.15);border-radius:9px;color:#111;background:#f1f4ed;font:750 10px/1 ui-monospace,SFMono-Regular,Menlo,monospace;box-shadow:0 8px 22px rgba(0,0,0,.24)}
|
|
12595
|
-
.secure{display:flex;align-items:center;gap:8px;color:#8e978f;font:600 10px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.12em;text-transform:uppercase}
|
|
12596
|
-
.secure-dot{width:6px;height:6px;border-radius:50%;background:${accent};box-shadow:0 0 16px rgba(${accentRgb},.8);animation:pulse 2.2s ease-in-out infinite}
|
|
12597
|
-
.content{position:relative;padding:54px 58px 42px}
|
|
12598
|
-
.orb{position:absolute;top:42px;right:48px;display:grid;place-items:center;width:96px;height:96px;border:1px solid rgba(${accentRgb},.28);border-radius:50%;color:${accent};background:radial-gradient(circle at 35% 28%,rgba(255,255,255,.12),rgba(${accentRgb},.06) 46%,rgba(0,0,0,.08));box-shadow:0 0 0 12px rgba(${accentRgb},.035),0 0 70px rgba(${accentRgb},.13);animation:float 4.8s ease-in-out infinite}
|
|
12599
|
-
.orb:before,.orb:after{content:"";position:absolute;border:1px solid rgba(${accentRgb},.14);border-radius:50%}.orb:before{inset:-12px}.orb:after{inset:11px}
|
|
12600
|
-
.orb svg{position:relative;z-index:1;width:42px;height:42px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
|
|
12601
|
-
.eyebrow{margin:0 0 20px;color:${accent};font:650 11px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.16em;text-transform:uppercase}
|
|
12602
|
-
h1{max-width:500px;margin:0;color:#f4f6f1;font-size:clamp(48px,7vw,74px);font-weight:620;line-height:.98;letter-spacing:-.065em}
|
|
12603
|
-
body[data-locale="en"] h1{font-size:clamp(42px,6vw,64px);letter-spacing:-.05em}
|
|
12604
|
-
h1 span{color:#7d857e}
|
|
12605
|
-
.message{max-width:450px;margin:26px 0 38px;color:#9ba39c;font-size:16px;line-height:1.75}
|
|
12606
|
-
.terminal{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:15px;padding:17px 18px;border:1px solid rgba(255,255,255,.09);border-radius:15px;background:rgba(4,6,5,.5);box-shadow:inset 0 1px rgba(255,255,255,.025)}
|
|
12607
|
-
.prompt{color:${accent};font:700 18px/1 ui-monospace,SFMono-Regular,Menlo,monospace}
|
|
12608
|
-
.terminal strong,.terminal small{display:block;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.terminal strong{color:#d7dcd5;font-size:12px}.terminal small{margin-top:5px;color:#677068;font-size:10px;letter-spacing:.04em}
|
|
12609
|
-
.terminal-check{display:grid;place-items:center;width:26px;height:26px;border-radius:50%;color:#071006;background:${accent};font-size:13px;font-weight:900;box-shadow:0 0 22px rgba(${accentRgb},.2)}
|
|
12610
|
-
footer{position:relative;display:flex;justify-content:space-between;gap:18px;padding:19px 26px;border-top:1px solid rgba(255,255,255,.075);color:#5f6760;font:550 9px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;text-transform:uppercase;letter-spacing:.13em}
|
|
12611
|
-
@keyframes enter{from{opacity:0;transform:translateY(18px) scale(.985)}to{opacity:1;transform:none}}@keyframes float{50%{transform:translateY(-7px)}}@keyframes pulse{50%{opacity:.45;box-shadow:0 0 8px rgba(${accentRgb},.35)}}
|
|
12612
|
-
@media(max-width:640px){body{padding:16px;overflow:auto}main{border-radius:23px}.topbar{padding:18px 20px}.content{padding:38px 26px 30px}.orb{position:relative;top:auto;right:auto;width:76px;height:76px;margin:0 0 36px}h1{font-size:48px}.message{font-size:15px}footer{padding:17px 20px;flex-direction:column;gap:5px}}
|
|
12613
|
-
@media(prefers-reduced-motion:reduce){main,.orb,.secure-dot{animation:none}}
|
|
12614
|
-
</style>
|
|
12615
|
-
</head>
|
|
12616
|
-
<body data-locale="${locale}">
|
|
12617
|
-
<main>
|
|
12618
|
-
<header class="topbar">
|
|
12619
|
-
<div class="brand"><span class="brand-mark">AW</span><span>Actionway</span></div>
|
|
12620
|
-
<div class="secure"><span class="secure-dot"></span><span>${copy.secure}</span></div>
|
|
12621
|
-
</header>
|
|
12622
|
-
<section class="content">
|
|
12623
|
-
<div class="orb"><svg viewBox="0 0 24 24" aria-hidden="true">${icon}</svg></div>
|
|
12624
|
-
<p class="eyebrow">${copy.eyebrow}</p>
|
|
12625
|
-
<h1>${title}<br><span>Actionway CLI</span></h1>
|
|
12626
|
-
<p class="message">${message}</p>
|
|
12627
|
-
<div class="terminal">
|
|
12628
|
-
<span class="prompt">\u203A</span>
|
|
12629
|
-
<span><strong>${statusText}</strong><small>${copy.session}</small></span>
|
|
12630
|
-
<span class="terminal-check">${success ? "\u2713" : "!"}</span>
|
|
12631
|
-
</div>
|
|
12632
|
-
</section>
|
|
12633
|
-
<footer><span>${copy.callback}</span><span>${copy.flow}</span></footer>
|
|
12634
|
-
</main>
|
|
12635
|
-
</body>
|
|
12636
|
-
</html>`;
|
|
12637
12603
|
}
|
|
12638
12604
|
function openBrowser(url, onNotice) {
|
|
12639
12605
|
onNotice(`Opening your browser. If it does not open, use:
|
|
@@ -12644,62 +12610,6 @@ ${url}`);
|
|
|
12644
12610
|
child.once("error", () => void 0);
|
|
12645
12611
|
child.unref();
|
|
12646
12612
|
}
|
|
12647
|
-
async function waitForCode(state, timeoutMs) {
|
|
12648
|
-
let resolveCode;
|
|
12649
|
-
let rejectCode;
|
|
12650
|
-
const code = new Promise((resolve3, reject) => {
|
|
12651
|
-
resolveCode = resolve3;
|
|
12652
|
-
rejectCode = reject;
|
|
12653
|
-
});
|
|
12654
|
-
const server = createServer((request, response) => {
|
|
12655
|
-
const url = new URL(request.url || "/", "http://127.0.0.1");
|
|
12656
|
-
if (request.method !== "GET" || url.pathname !== "/callback") {
|
|
12657
|
-
response.writeHead(404).end();
|
|
12658
|
-
return;
|
|
12659
|
-
}
|
|
12660
|
-
const receivedCode = url.searchParams.get("code");
|
|
12661
|
-
const oauthError = url.searchParams.get("error_description") || url.searchParams.get("error");
|
|
12662
|
-
const locale = callbackLocale(request.headers["accept-language"]);
|
|
12663
|
-
if (oauthError || url.searchParams.get("state") !== state || !receivedCode) {
|
|
12664
|
-
response.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
|
|
12665
|
-
response.end(callbackPage("error", locale));
|
|
12666
|
-
rejectCode(new CliError("E_BACKEND", oauthError || "OAuth callback validation failed."));
|
|
12667
|
-
return;
|
|
12668
|
-
}
|
|
12669
|
-
response.writeHead(200, {
|
|
12670
|
-
"Cache-Control": "no-store",
|
|
12671
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
12672
|
-
"Referrer-Policy": "no-referrer"
|
|
12673
|
-
});
|
|
12674
|
-
response.end(callbackPage("success", locale), () => {
|
|
12675
|
-
resolveCode(receivedCode);
|
|
12676
|
-
server.close();
|
|
12677
|
-
});
|
|
12678
|
-
});
|
|
12679
|
-
await new Promise((resolve3, reject) => {
|
|
12680
|
-
server.once("error", reject);
|
|
12681
|
-
server.listen(0, "127.0.0.1", resolve3);
|
|
12682
|
-
});
|
|
12683
|
-
const port = server.address().port;
|
|
12684
|
-
const timer = setTimeout(() => {
|
|
12685
|
-
rejectCode(
|
|
12686
|
-
new CliError(
|
|
12687
|
-
"E_BACKEND",
|
|
12688
|
-
`login timed out after ${Math.round(timeoutMs / 1e3)} seconds`,
|
|
12689
|
-
"run `actionway login` again and ask the user to finish the browser sign-in promptly; pass --timeout-seconds <n> to allow more time"
|
|
12690
|
-
)
|
|
12691
|
-
);
|
|
12692
|
-
server.close();
|
|
12693
|
-
}, timeoutMs);
|
|
12694
|
-
return {
|
|
12695
|
-
redirectUri: `http://127.0.0.1:${port}/callback`,
|
|
12696
|
-
code: code.finally(() => clearTimeout(timer)),
|
|
12697
|
-
close: () => {
|
|
12698
|
-
clearTimeout(timer);
|
|
12699
|
-
return new Promise((resolve3) => server.listening ? server.close(() => resolve3()) : resolve3());
|
|
12700
|
-
}
|
|
12701
|
-
};
|
|
12702
|
-
}
|
|
12703
12613
|
async function readJsonObject(response) {
|
|
12704
12614
|
const value = await response.json();
|
|
12705
12615
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -12792,9 +12702,13 @@ async function fetchCliSession(gateway, accessToken, fetchImpl = fetch) {
|
|
|
12792
12702
|
walletId: value.walletId
|
|
12793
12703
|
};
|
|
12794
12704
|
}
|
|
12705
|
+
function defaultSleep(ms) {
|
|
12706
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
12707
|
+
}
|
|
12795
12708
|
async function login(options = {}) {
|
|
12796
12709
|
const env = options.env ?? process.env;
|
|
12797
12710
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
12711
|
+
const sleep = options.sleepImpl ?? defaultSleep;
|
|
12798
12712
|
const onNotice = options.onNotice ?? ((message) => process.stderr.write(`${message}
|
|
12799
12713
|
`));
|
|
12800
12714
|
const gateway = resolveGatewayUrl(options.gateway, env);
|
|
@@ -12841,31 +12755,56 @@ async function login(options = {}) {
|
|
|
12841
12755
|
issuer ||= discovered.issuer;
|
|
12842
12756
|
}
|
|
12843
12757
|
const timeoutMs = options.timeoutSeconds !== void 0 && Number.isFinite(options.timeoutSeconds) && options.timeoutSeconds > 0 ? options.timeoutSeconds * 1e3 : DEFAULT_LOGIN_TIMEOUT_MS;
|
|
12844
|
-
const verifier = randomBase64Url();
|
|
12845
|
-
const state = randomBase64Url();
|
|
12846
|
-
const callback = await waitForCode(state, timeoutMs);
|
|
12847
12758
|
try {
|
|
12848
|
-
const
|
|
12849
|
-
|
|
12850
|
-
|
|
12851
|
-
|
|
12852
|
-
|
|
12853
|
-
|
|
12854
|
-
|
|
12855
|
-
|
|
12856
|
-
|
|
12857
|
-
|
|
12858
|
-
|
|
12859
|
-
|
|
12860
|
-
|
|
12759
|
+
const verifier = randomBase64Url();
|
|
12760
|
+
const pairing = await startDevicePairing(gateway, pkceChallenge(verifier), fetchImpl);
|
|
12761
|
+
onNotice(
|
|
12762
|
+
`Sign in to Actionway on any device:
|
|
12763
|
+
${pairing.verificationUrl}
|
|
12764
|
+
One-time code: ${pairing.userCode}`
|
|
12765
|
+
);
|
|
12766
|
+
if (options.noBrowser) onNotice(`Open this URL to approve the sign-in:
|
|
12767
|
+
${pairing.verificationUrlComplete}`);
|
|
12768
|
+
else openBrowser(pairing.verificationUrlComplete, onNotice);
|
|
12769
|
+
const deadline = Date.now() + Math.min(timeoutMs, pairing.expiresInS * 1e3);
|
|
12770
|
+
let approvalCode = null;
|
|
12771
|
+
for (; ; ) {
|
|
12772
|
+
const poll = await pollDevicePairing(gateway, pairing.deviceCode, fetchImpl);
|
|
12773
|
+
if (poll.status === "approved") {
|
|
12774
|
+
approvalCode = poll.code;
|
|
12775
|
+
break;
|
|
12776
|
+
}
|
|
12777
|
+
if (poll.status === "denied") {
|
|
12778
|
+
throw new CliError(
|
|
12779
|
+
"E_BACKEND",
|
|
12780
|
+
"the sign-in request was denied in the browser",
|
|
12781
|
+
"run `actionway login` again and approve the request on the activation page"
|
|
12782
|
+
);
|
|
12783
|
+
}
|
|
12784
|
+
if (poll.status === "expired") {
|
|
12785
|
+
throw new CliError(
|
|
12786
|
+
"E_BACKEND",
|
|
12787
|
+
"the sign-in code expired before it was approved",
|
|
12788
|
+
"run `actionway login` again and enter the fresh code promptly"
|
|
12789
|
+
);
|
|
12790
|
+
}
|
|
12791
|
+
if (Date.now() >= deadline) {
|
|
12792
|
+
throw new CliError(
|
|
12793
|
+
"E_BACKEND",
|
|
12794
|
+
`login timed out after ${Math.round(timeoutMs / 1e3)} seconds`,
|
|
12795
|
+
"run `actionway login` again and ask the user to approve the sign-in promptly; pass --timeout-seconds <n> to allow more time"
|
|
12796
|
+
);
|
|
12797
|
+
}
|
|
12798
|
+
await sleep(pairing.intervalS * 1e3);
|
|
12799
|
+
}
|
|
12861
12800
|
const tokens = await tokenRequest(
|
|
12862
12801
|
issuer,
|
|
12863
12802
|
new URLSearchParams({
|
|
12864
12803
|
grant_type: "authorization_code",
|
|
12865
12804
|
client_id: clientId,
|
|
12866
|
-
code:
|
|
12805
|
+
code: approvalCode,
|
|
12867
12806
|
code_verifier: verifier,
|
|
12868
|
-
redirect_uri:
|
|
12807
|
+
redirect_uri: pairing.redirectUri
|
|
12869
12808
|
}),
|
|
12870
12809
|
fetchImpl
|
|
12871
12810
|
);
|
|
@@ -12894,8 +12833,6 @@ ${authorizeUrl}`);
|
|
|
12894
12833
|
return { credentials, account };
|
|
12895
12834
|
} catch (error) {
|
|
12896
12835
|
return withAuthContext(error);
|
|
12897
|
-
} finally {
|
|
12898
|
-
await callback.close();
|
|
12899
12836
|
}
|
|
12900
12837
|
}
|
|
12901
12838
|
function whoamiSnapshot(env = process.env) {
|
|
@@ -12946,7 +12883,7 @@ function failFromError2(err, fallbackHint) {
|
|
|
12946
12883
|
});
|
|
12947
12884
|
}
|
|
12948
12885
|
function registerAuthCommands(program2) {
|
|
12949
|
-
program2.command("login").description("Authenticate with Actionway in
|
|
12886
|
+
program2.command("login").description("Authenticate with Actionway by approving a one-time code in any browser (Clerk OAuth with PKCE).").option("--gateway <url>", "Actionway public origin").option("--client-id <id>", "Actionway OAuth client id (must be paired with --issuer; default: both discovered from the public origin)").option("--issuer <url>", "Actionway OAuth issuer (must be paired with --client-id; default: both discovered from the public origin)").option("--timeout-seconds <n>", "how long to wait for the sign-in to be approved").option("--no-browser", "print the verification URL instead of opening a browser (use on remote/headless machines)").action(async (opts) => {
|
|
12950
12887
|
try {
|
|
12951
12888
|
const { credentials, account } = await login({
|
|
12952
12889
|
...opts.gateway ? { gateway: opts.gateway } : {},
|
|
@@ -13035,7 +12972,6 @@ function registerAccountCommands(program2, getTransport2) {
|
|
|
13035
12972
|
// src/commands/doctor.ts
|
|
13036
12973
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
13037
12974
|
import { existsSync as existsSync2, rmSync as rmSync2, statSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
13038
|
-
import { createServer as createServer2 } from "node:http";
|
|
13039
12975
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
13040
12976
|
import { dirname as dirname3, join as join3, posix, win32 } from "node:path";
|
|
13041
12977
|
function pass(id, message) {
|
|
@@ -13087,13 +13023,6 @@ function defaultWritable(path) {
|
|
|
13087
13023
|
return false;
|
|
13088
13024
|
}
|
|
13089
13025
|
}
|
|
13090
|
-
function checkLoopback() {
|
|
13091
|
-
return new Promise((resolve3, reject) => {
|
|
13092
|
-
const server = createServer2();
|
|
13093
|
-
server.once("error", reject);
|
|
13094
|
-
server.listen(0, "127.0.0.1", () => server.close((error) => error ? reject(error) : resolve3()));
|
|
13095
|
-
});
|
|
13096
|
-
}
|
|
13097
13026
|
function defaultResolveCommand(name) {
|
|
13098
13027
|
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 });
|
|
13099
13028
|
if (result.error || result.status !== 0) return null;
|
|
@@ -13122,7 +13051,6 @@ async function diagnoseActionway(options = {}, dependencies = {}) {
|
|
|
13122
13051
|
const pathExists = dependencies.pathExists ?? existsSync2;
|
|
13123
13052
|
const pathWritable = dependencies.pathWritable ?? defaultWritable;
|
|
13124
13053
|
const fetchOAuth = dependencies.fetchOAuth ?? fetchCliOAuthConfig;
|
|
13125
|
-
const loopback = dependencies.checkLoopback ?? checkLoopback;
|
|
13126
13054
|
const readPolicy = dependencies.readPowerShellPolicy ?? powerShellPolicy;
|
|
13127
13055
|
const resolveCommand = dependencies.resolveCommand ?? defaultResolveCommand;
|
|
13128
13056
|
const checks = [];
|
|
@@ -13132,17 +13060,7 @@ async function diagnoseActionway(options = {}, dependencies = {}) {
|
|
|
13132
13060
|
checks.push(
|
|
13133
13061
|
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")
|
|
13134
13062
|
);
|
|
13135
|
-
|
|
13136
|
-
checks.push(
|
|
13137
|
-
warn(
|
|
13138
|
-
"execution_environment",
|
|
13139
|
-
"WSL detected; browser loopback may not share the Windows host network namespace.",
|
|
13140
|
-
"run Actionway login from the Windows host if the browser callback cannot return"
|
|
13141
|
-
)
|
|
13142
|
-
);
|
|
13143
|
-
} else {
|
|
13144
|
-
checks.push(pass("execution_environment", `${platform}/${architecture} host environment detected.`));
|
|
13145
|
-
}
|
|
13063
|
+
checks.push(pass("execution_environment", `${platform}/${architecture} host environment detected.`));
|
|
13146
13064
|
checks.push(
|
|
13147
13065
|
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")
|
|
13148
13066
|
);
|
|
@@ -13204,18 +13122,6 @@ async function diagnoseActionway(options = {}, dependencies = {}) {
|
|
|
13204
13122
|
)
|
|
13205
13123
|
);
|
|
13206
13124
|
}
|
|
13207
|
-
try {
|
|
13208
|
-
await loopback();
|
|
13209
|
-
checks.push(pass("loopback", "A random 127.0.0.1 callback port can be opened."));
|
|
13210
|
-
} catch (error) {
|
|
13211
|
-
checks.push(
|
|
13212
|
-
fail2(
|
|
13213
|
-
"loopback",
|
|
13214
|
-
`Cannot open the local OAuth callback: ${error instanceof Error ? error.message : String(error)}`,
|
|
13215
|
-
"run login from a normal host terminal and check firewall or sandbox policy"
|
|
13216
|
-
)
|
|
13217
|
-
);
|
|
13218
|
-
}
|
|
13219
13125
|
if (options.network === false) {
|
|
13220
13126
|
checks.push({ id: "npm_registry", status: "skip", message: "npm registry check was skipped." });
|
|
13221
13127
|
checks.push({ id: "actionway_oauth", status: "skip", message: "Actionway OAuth discovery check was skipped." });
|
|
@@ -13259,7 +13165,7 @@ async function diagnoseActionway(options = {}, dependencies = {}) {
|
|
|
13259
13165
|
}
|
|
13260
13166
|
function registerDoctorCommand(program2) {
|
|
13261
13167
|
const distribution = cliDistribution();
|
|
13262
|
-
program2.command("doctor").description("Diagnose Node, npm, PATH, registry, configuration, and
|
|
13168
|
+
program2.command("doctor").description("Diagnose Node, npm, PATH, registry, configuration, and Actionway OAuth discovery readiness.").option("--no-network", "skip npm registry and Actionway OAuth discovery checks").action(async (options) => {
|
|
13263
13169
|
const report = await diagnoseActionway({ network: options.network !== false });
|
|
13264
13170
|
const command = process.platform === "win32" ? `${distribution.commandName}.cmd` : distribution.commandName;
|
|
13265
13171
|
ok({
|
|
@@ -13521,7 +13427,7 @@ function registerInitCommand(program2, getClientContext = () => ({ runtimeOs: "o
|
|
|
13521
13427
|
const distribution = cliDistribution();
|
|
13522
13428
|
program2.command("init").description(
|
|
13523
13429
|
"Complete browser authentication and stage the Actionway Skill for your agent to install. The CLI never writes into an agent's own configuration directory."
|
|
13524
|
-
).option("--gateway <url>", "Actionway public origin").option("--client-id <id>", "Actionway OAuth client id (must be paired with --issuer)").option("--issuer <url>", "Actionway OAuth issuer (must be paired with --client-id)").option("--timeout-seconds <n>", "how long to wait for
|
|
13430
|
+
).option("--gateway <url>", "Actionway public origin").option("--client-id <id>", "Actionway OAuth client id (must be paired with --issuer)").option("--issuer <url>", "Actionway OAuth issuer (must be paired with --client-id)").option("--timeout-seconds <n>", "how long to wait for the sign-in to be approved").option("--install-session-id <uuid>", "onboarding install session id").option("--no-browser", "print the verification URL instead of opening a browser (use on remote/headless machines)").addOption(new Option("--skill-only").hideHelp()).action(async (opts) => {
|
|
13525
13431
|
try {
|
|
13526
13432
|
const clientContext = getClientContext();
|
|
13527
13433
|
const invocation = clientContext.agentHost ? `${distribution.commandName} --agent-host=${clientContext.agentHost}` : distribution.commandName;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actionway/cli-dev",
|
|
3
|
-
"version": "0.0.0-dev.
|
|
3
|
+
"version": "0.0.0-dev.33147618708.51de2d32a24e",
|
|
4
4
|
"description": "Actionway development CLI for dev.actionway.ai",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"tsx": "^4.20.0",
|
|
20
20
|
"typescript": "7.0.2",
|
|
21
21
|
"vitest": "^3.2.7",
|
|
22
|
-
"@actionway/
|
|
23
|
-
"@actionway/
|
|
22
|
+
"@actionway/contracts": "0.1.0",
|
|
23
|
+
"@actionway/cli-core": "0.1.0"
|
|
24
24
|
},
|
|
25
25
|
"engines": {
|
|
26
26
|
"node": ">=20"
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
},
|
|
37
37
|
"actionway": {
|
|
38
38
|
"channel": "development",
|
|
39
|
-
"source_sha": "
|
|
40
|
-
"run_id":
|
|
41
|
-
"run_url": "https://github.com/PawLogic/actionway/actions/runs/
|
|
39
|
+
"source_sha": "51de2d32a24ed30e1f745e0c7b6613b3e9267d6b",
|
|
40
|
+
"run_id": 33147618708,
|
|
41
|
+
"run_url": "https://github.com/PawLogic/actionway/actions/runs/33147618708",
|
|
42
42
|
"gateway_url": "https://dev.actionway.ai"
|
|
43
43
|
}
|
|
44
44
|
}
|