@aexhq/cli 0.35.0 → 0.37.0
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 +30 -7
- package/package.json +10 -35
- package/src/config.mjs +34 -0
- package/src/index.mjs +72 -0
- package/src/login.mjs +47 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -203
package/README.md
CHANGED
|
@@ -1,12 +1,35 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Aex CLI
|
|
2
|
+
|
|
3
|
+
Node.js 22 or newer. The CLI uses `@aexhq/sdk` and the same public Aex HTTP API as
|
|
4
|
+
the dashboard. No dashboard cookies, site credentials or operator credentials are needed.
|
|
2
5
|
|
|
3
6
|
```sh
|
|
4
|
-
npm install
|
|
7
|
+
npm install -g @aexhq/cli
|
|
5
8
|
aex login
|
|
6
|
-
aex
|
|
7
|
-
aex
|
|
8
|
-
aex
|
|
9
|
+
aex keys create "My application"
|
|
10
|
+
aex keys list
|
|
11
|
+
aex keys rename KEY_ID "New name"
|
|
12
|
+
aex keys revoke KEY_ID
|
|
13
|
+
aex account
|
|
14
|
+
aex billing
|
|
15
|
+
aex usage
|
|
16
|
+
aex docs
|
|
17
|
+
aex logout
|
|
9
18
|
```
|
|
10
19
|
|
|
11
|
-
|
|
12
|
-
|
|
20
|
+
`login` opens your browser for Google sign-in or registration. Confirm the account on
|
|
21
|
+
the Aex page, then return to your terminal. The browser must run on the same computer
|
|
22
|
+
as the CLI. `--no-browser` prints the URL instead of opening it. A loopback listener,
|
|
23
|
+
state and S256 PKCE bind a one-use, 60-second code to the initiating CLI. Account
|
|
24
|
+
credentials expire after seven days; run `login` again. `logout` revokes this session.
|
|
25
|
+
|
|
26
|
+
Results are JSON on stdout; errors and login progress go to stderr. A created key's
|
|
27
|
+
token is returned once. Existing keys expose only their metadata and prefix.
|
|
28
|
+
Billing currently reports free preview hosting with customer-paid model usage.
|
|
29
|
+
|
|
30
|
+
The credential file is `~/.aex/account.json` on Unix or `%LOCALAPPDATA%/.aex/account.json`
|
|
31
|
+
on Windows, restricted to the current user (and Windows SYSTEM). `AEX_CONFIG_DIR`
|
|
32
|
+
overrides its directory. For automation, supply `AEX_ACCOUNT_TOKEN`; a workload API
|
|
33
|
+
key cannot manage account credentials. `AEX_API_URL` or `--api-url` changes the API
|
|
34
|
+
origin; stored credentials cannot be reused against a different origin. `--site-url`
|
|
35
|
+
selects the matching login website for a self-hosted installation.
|
package/package.json
CHANGED
|
@@ -1,39 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aexhq/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
5
|
-
"license": "
|
|
6
|
-
"homepage": "https://aex.dev",
|
|
7
|
-
"repository": {
|
|
8
|
-
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/aexhq/aex.git",
|
|
10
|
-
"directory": "packages/cli"
|
|
11
|
-
},
|
|
3
|
+
"version": "0.37.0",
|
|
4
|
+
"description": "Aex account and API key management from the terminal",
|
|
5
|
+
"license": "MIT",
|
|
12
6
|
"type": "module",
|
|
13
|
-
"engines": {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
},
|
|
21
|
-
"bin": {
|
|
22
|
-
"aex": "./dist/index.js"
|
|
23
|
-
},
|
|
24
|
-
"files": [
|
|
25
|
-
"dist",
|
|
26
|
-
"README.md"
|
|
27
|
-
],
|
|
28
|
-
"scripts": {
|
|
29
|
-
"build": "tsc -p tsconfig.json",
|
|
30
|
-
"test": "tsc -p tsconfig.json && node --test"
|
|
31
|
-
},
|
|
32
|
-
"dependencies": {
|
|
33
|
-
"@aexhq/sdk": "0.67.0"
|
|
34
|
-
},
|
|
35
|
-
"devDependencies": {
|
|
36
|
-
"@types/node": "^24.0.0",
|
|
37
|
-
"typescript": "^5.9.2"
|
|
38
|
-
}
|
|
7
|
+
"engines": { "node": ">=22" },
|
|
8
|
+
"bin": { "aex": "src/index.mjs" },
|
|
9
|
+
"files": ["src", "README.md"],
|
|
10
|
+
"repository": { "type": "git", "url": "git+https://github.com/aexhq/aex.git", "directory": "packages/cli" },
|
|
11
|
+
"publishConfig": { "access": "public", "tag": "next" },
|
|
12
|
+
"dependencies": { "@aexhq/sdk": "0.70.0" },
|
|
13
|
+
"scripts": { "test": "node --test test/*.test.mjs" }
|
|
39
14
|
}
|
package/src/config.mjs
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { mkdir, chmod, writeFile, rename, readFile, rm } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { execFile } from "node:child_process";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
|
|
8
|
+
const directory = () => process.env.AEX_CONFIG_DIR ?? join(process.platform === "win32" ? process.env.LOCALAPPDATA ?? homedir() : homedir(), ".aex");
|
|
9
|
+
export function origin(value) {
|
|
10
|
+
const url = new URL(value);
|
|
11
|
+
if ((url.protocol !== "https:" && !(url.protocol === "http:" && url.hostname === "127.0.0.1")) || url.username || url.password || url.pathname !== "/" || url.search || url.hash) throw new Error("Use an HTTPS origin (or http://127.0.0.1:PORT for local testing).");
|
|
12
|
+
return url.origin;
|
|
13
|
+
}
|
|
14
|
+
export async function saveSession(session) {
|
|
15
|
+
const dir = directory();
|
|
16
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
17
|
+
if (process.platform === "win32") {
|
|
18
|
+
const exec = promisify(execFile);
|
|
19
|
+
const { stdout } = await exec("whoami.exe", ["/user", "/fo", "csv", "/nh"], { windowsHide: true });
|
|
20
|
+
const sid = stdout.match(/S-1-5-\d+(?:-\d+)+/)?.[0];
|
|
21
|
+
if (!sid) throw new Error("Cannot determine Windows account for credential permissions.");
|
|
22
|
+
await exec("icacls.exe", [dir, "/inheritance:r", "/grant:r", `*${sid}:(OI)(CI)F`, "*S-1-5-18:(OI)(CI)F"], { windowsHide: true });
|
|
23
|
+
} else await chmod(dir, 0o700);
|
|
24
|
+
const temporary = join(dir, `${randomUUID()}.tmp`);
|
|
25
|
+
try {
|
|
26
|
+
await writeFile(temporary, JSON.stringify(session) + "\n", { mode: 0o600, flag: "wx" });
|
|
27
|
+
await rename(temporary, join(dir, "account.json"));
|
|
28
|
+
} finally { await rm(temporary, { force: true }); }
|
|
29
|
+
}
|
|
30
|
+
export async function readSession() {
|
|
31
|
+
try { return JSON.parse(await readFile(join(directory(), "account.json"), "utf8")); }
|
|
32
|
+
catch (error) { if (error.code === "ENOENT") throw new Error("Sign in first with aex login."); throw error; }
|
|
33
|
+
}
|
|
34
|
+
export async function removeSession() { await rm(join(directory(), "account.json"), { force: true }); }
|
package/src/index.mjs
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
import { Aex } from "@aexhq/sdk";
|
|
4
|
+
import { login, openBrowser } from "./login.mjs";
|
|
5
|
+
import { origin, readSession, saveSession, removeSession } from "./config.mjs";
|
|
6
|
+
|
|
7
|
+
const help = `Usage: aex <command>
|
|
8
|
+
|
|
9
|
+
login Open browser sign-in and save an account session
|
|
10
|
+
logout Revoke this session and remove local credentials
|
|
11
|
+
keys list List API keys (secrets are never returned)
|
|
12
|
+
keys create <name> Create a key; save the returned token now
|
|
13
|
+
keys rename <id> <name> Rename a key
|
|
14
|
+
keys revoke <id> Revoke a key
|
|
15
|
+
account Show account, limits and billing status
|
|
16
|
+
billing Show billing status
|
|
17
|
+
usage Show current usage
|
|
18
|
+
docs Open the documentation
|
|
19
|
+
|
|
20
|
+
Results are JSON on stdout; login progress and errors go to stderr.
|
|
21
|
+
Options: --no-browser, --api-url <origin>, --site-url <origin>, --help
|
|
22
|
+
Automation: AEX_ACCOUNT_TOKEN and optional AEX_API_URL.
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
async function main() {
|
|
26
|
+
const { values, positionals } = parseArgs({ allowPositionals: true, options: { help: { type: "boolean", short: "h" }, "no-browser": { type: "boolean" }, "api-url": { type: "string" }, "site-url": { type: "string" } } });
|
|
27
|
+
if (values.help || !positionals.length) { process.stdout.write(help); return; }
|
|
28
|
+
const [command, operation, ...args] = positionals;
|
|
29
|
+
const expected = { "keys list": 0, "keys create": 1, "keys rename": 2, "keys revoke": 1 };
|
|
30
|
+
if (command === "keys" ? expected[`keys ${operation}`] !== args.length : !["login", "logout", "account", "billing", "usage", "docs"].includes(command) || positionals.length !== 1) throw new Error("Unknown command or incorrect arguments. Run aex --help.");
|
|
31
|
+
const explicitApi = values["api-url"] ?? process.env.AEX_API_URL;
|
|
32
|
+
const siteUrl = origin(values["site-url"] ?? "https://aex.dev");
|
|
33
|
+
if (command === "docs") {
|
|
34
|
+
const url = `${siteUrl}/docs`; process.stdout.write(url + "\n");
|
|
35
|
+
if (!values["no-browser"]) await openBrowser(url);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (command === "login") {
|
|
39
|
+
const apiUrl = origin(explicitApi ?? "https://api.aex.dev");
|
|
40
|
+
const controller = new AbortController(), timeout = setTimeout(() => controller.abort(), 600_000);
|
|
41
|
+
const cancel = () => controller.abort(); process.once("SIGINT", cancel);
|
|
42
|
+
try {
|
|
43
|
+
const session = await login({ apiUrl, siteUrl, signal: controller.signal, onReady: async url => {
|
|
44
|
+
process.stderr.write(`Sign in using this browser on this computer:\n${url}\n`);
|
|
45
|
+
if (!values["no-browser"]) await openBrowser(url).catch(() => process.stderr.write("Could not open a browser. Open the URL above to continue.\n"));
|
|
46
|
+
} });
|
|
47
|
+
const client = new Aex({ accountToken: session.token, baseUrl: apiUrl, timeoutMs: 15_000 });
|
|
48
|
+
try { await saveSession({ ...session, apiUrl }); }
|
|
49
|
+
catch (error) { await client.account.logout(); throw error; }
|
|
50
|
+
process.stdout.write(JSON.stringify(await client.account.get(), null, 2) + "\n");
|
|
51
|
+
} finally { clearTimeout(timeout); process.removeListener("SIGINT", cancel); }
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const session = process.env.AEX_ACCOUNT_TOKEN ? { token: process.env.AEX_ACCOUNT_TOKEN, apiUrl: origin(explicitApi ?? "https://api.aex.dev") } : await readSession();
|
|
55
|
+
const apiUrl = origin(explicitApi ?? session.apiUrl);
|
|
56
|
+
if (apiUrl !== session.apiUrl) throw new Error("Saved credentials belong to a different API origin. Run aex login for this origin.");
|
|
57
|
+
const client = new Aex({ accountToken: session.token, baseUrl: apiUrl, timeoutMs: 15_000 });
|
|
58
|
+
let result;
|
|
59
|
+
if (command === "logout") {
|
|
60
|
+
try { await client.account.logout(); } catch (error) { if (error.status !== 401) throw error; }
|
|
61
|
+
if (!process.env.AEX_ACCOUNT_TOKEN) await removeSession();
|
|
62
|
+
result = { signed_out: true };
|
|
63
|
+
} else if (command === "account") result = await client.account.get();
|
|
64
|
+
else if (command === "billing") result = { billing: (await client.account.get()).billing };
|
|
65
|
+
else if (command === "usage") result = await client.account.usage();
|
|
66
|
+
else if (operation === "list") result = await client.keys.list();
|
|
67
|
+
else if (operation === "create") result = await client.keys.create({ name: args[0] });
|
|
68
|
+
else if (operation === "rename") result = await client.keys.update(args[0], { name: args[1] });
|
|
69
|
+
else { await client.keys.delete(args[0]); result = { revoked: args[0] }; }
|
|
70
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
71
|
+
}
|
|
72
|
+
main().catch(error => { process.stderr.write(`aex: ${error.message}\n`); process.exitCode = 1; });
|
package/src/login.mjs
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
3
|
+
import { execFile } from "node:child_process";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { Aex } from "@aexhq/sdk";
|
|
6
|
+
|
|
7
|
+
const exec = promisify(execFile);
|
|
8
|
+
export async function openBrowser(url) {
|
|
9
|
+
const [command, args] = process.platform === "win32"
|
|
10
|
+
? ["rundll32.exe", ["url.dll,FileProtocolHandler", url]]
|
|
11
|
+
: process.platform === "darwin" ? ["open", [url]] : ["xdg-open", [url]];
|
|
12
|
+
await exec(command, args, { windowsHide: true });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function login({ apiUrl, siteUrl, onReady, signal = AbortSignal.timeout(600_000) }) {
|
|
16
|
+
const verifier = randomBytes(32).toString("base64url"), state = randomBytes(32).toString("base64url");
|
|
17
|
+
const server = createServer();
|
|
18
|
+
await new Promise((resolve, reject) => { server.once("error", reject); server.listen(0, "127.0.0.1", resolve); });
|
|
19
|
+
const redirect = `http://127.0.0.1:${server.address().port}/callback`;
|
|
20
|
+
const url = new URL("/cli", siteUrl);
|
|
21
|
+
url.search = new URLSearchParams({ redirect_uri: redirect, state, code_challenge: createHash("sha256").update(verifier).digest("base64url") }).toString();
|
|
22
|
+
try {
|
|
23
|
+
return await new Promise((resolve, reject) => {
|
|
24
|
+
let exchanging = false;
|
|
25
|
+
const abort = () => reject(new Error("Login cancelled or timed out. Run aex login again."));
|
|
26
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
27
|
+
server.once("close", () => signal.removeEventListener("abort", abort));
|
|
28
|
+
server.on("request", async (request, response) => {
|
|
29
|
+
response.setHeader("content-type", "text/plain; charset=utf-8");
|
|
30
|
+
response.setHeader("cache-control", "no-store");
|
|
31
|
+
response.setHeader("referrer-policy", "no-referrer");
|
|
32
|
+
const callback = new URL(request.url, redirect);
|
|
33
|
+
if (request.method !== "GET" || request.headers.host !== new URL(redirect).host || callback.pathname !== "/callback" || callback.searchParams.get("state") !== state || !callback.searchParams.get("code") || exchanging) {
|
|
34
|
+
response.writeHead(400).end("Invalid login callback."); return;
|
|
35
|
+
}
|
|
36
|
+
exchanging = true;
|
|
37
|
+
try {
|
|
38
|
+
const session = await Aex.exchangeLogin({ code: callback.searchParams.get("code"), code_verifier: verifier, redirect_uri: redirect }, { baseUrl: apiUrl, timeoutMs: 15_000 });
|
|
39
|
+
response.end("Signed in to Aex. You can close this tab and return to your terminal.");
|
|
40
|
+
resolve(session);
|
|
41
|
+
} catch (error) { response.writeHead(400).end("Login failed. Return to your terminal and try again."); reject(error); }
|
|
42
|
+
});
|
|
43
|
+
if (signal.aborted) abort();
|
|
44
|
+
else Promise.resolve().then(() => onReady(url.toString())).catch(reject);
|
|
45
|
+
});
|
|
46
|
+
} finally { server.closeAllConnections(); await new Promise(resolve => server.close(resolve)); }
|
|
47
|
+
}
|
package/dist/index.d.ts
DELETED
package/dist/index.js
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { chmod, mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
5
|
-
import process from "node:process";
|
|
6
|
-
import { Aex, AexError } from "@aexhq/sdk";
|
|
7
|
-
const HELP = `Aex — the session backend for AI apps
|
|
8
|
-
|
|
9
|
-
Usage:
|
|
10
|
-
aex login
|
|
11
|
-
aex doctor
|
|
12
|
-
aex session list
|
|
13
|
-
aex session get <session-id>
|
|
14
|
-
aex session send <session-id> <message>
|
|
15
|
-
aex session events <session-id>
|
|
16
|
-
aex session cancel <session-id>
|
|
17
|
-
aex session end <session-id>
|
|
18
|
-
aex session delete <session-id>
|
|
19
|
-
|
|
20
|
-
The API defaults to https://api.aex.dev.`;
|
|
21
|
-
async function main(argv) {
|
|
22
|
-
const [command, ...rest] = argv;
|
|
23
|
-
if (command === undefined || command === "help" || command === "--help" || command === "-h") {
|
|
24
|
-
process.stdout.write(`${HELP}\n`);
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
if (command === "login") {
|
|
28
|
-
await login();
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
const aex = await client();
|
|
32
|
-
if (command === "doctor") {
|
|
33
|
-
await aex.sessions.list();
|
|
34
|
-
process.stdout.write("Aex API: ok\n");
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
if (command !== "session")
|
|
38
|
-
usage(`Unknown command: ${command}`);
|
|
39
|
-
await sessionCommand(aex, rest);
|
|
40
|
-
}
|
|
41
|
-
async function sessionCommand(aex, argv) {
|
|
42
|
-
const [command, id, ...tail] = argv;
|
|
43
|
-
switch (command) {
|
|
44
|
-
case "list": {
|
|
45
|
-
print(await aex.sessions.list());
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
case "get": {
|
|
49
|
-
requireId(id, command);
|
|
50
|
-
print((await aex.sessions.get(id)).state);
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
case "send": {
|
|
54
|
-
requireId(id, command);
|
|
55
|
-
const message = tail.join(" ").trim();
|
|
56
|
-
if (message === "")
|
|
57
|
-
usage("session send requires a message");
|
|
58
|
-
const session = await aex.sessions.get(id);
|
|
59
|
-
print(await session.send(message));
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
case "events": {
|
|
63
|
-
requireId(id, command);
|
|
64
|
-
const session = await aex.sessions.get(id);
|
|
65
|
-
const controller = new AbortController();
|
|
66
|
-
process.once("SIGINT", () => controller.abort());
|
|
67
|
-
try {
|
|
68
|
-
for await (const event of session.events()) {
|
|
69
|
-
process.stdout.write(`${JSON.stringify(event)}\n`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
catch (error) {
|
|
73
|
-
if (!controller.signal.aborted)
|
|
74
|
-
throw error;
|
|
75
|
-
}
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
case "cancel": {
|
|
79
|
-
requireId(id, command);
|
|
80
|
-
const session = await aex.sessions.get(id);
|
|
81
|
-
await session.cancel();
|
|
82
|
-
print(session.state);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
case "end": {
|
|
86
|
-
requireId(id, command);
|
|
87
|
-
const session = await aex.sessions.get(id);
|
|
88
|
-
print(await session.end());
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
case "delete": {
|
|
92
|
-
requireId(id, command);
|
|
93
|
-
const session = await aex.sessions.get(id);
|
|
94
|
-
await session.delete();
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
default:
|
|
98
|
-
usage(command === undefined ? "Missing session command" : `Unknown session command: ${command}`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
async function client() {
|
|
102
|
-
const apiKey = process.env.AEX_API_KEY ?? (await readConfig())?.apiKey;
|
|
103
|
-
if (apiKey === undefined || apiKey === "") {
|
|
104
|
-
throw new Error("No Aex API key. Run `aex login` first.");
|
|
105
|
-
}
|
|
106
|
-
return new Aex({
|
|
107
|
-
apiKey,
|
|
108
|
-
...(process.env.AEX_BASE_URL === undefined ? {} : { baseUrl: process.env.AEX_BASE_URL }),
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
async function login() {
|
|
112
|
-
const apiKey = (await readSecret("Paste Aex API key: ")).trim();
|
|
113
|
-
if (!/^aex_sk_[A-Za-z0-9]{40,64}$/.test(apiKey)) {
|
|
114
|
-
throw new Error("That does not look like an Aex session API key.");
|
|
115
|
-
}
|
|
116
|
-
const path = configPath();
|
|
117
|
-
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
118
|
-
const temporary = `${path}.${process.pid}.tmp`;
|
|
119
|
-
await writeFile(temporary, `${JSON.stringify({ apiKey }, null, 2)}\n`, {
|
|
120
|
-
encoding: "utf8",
|
|
121
|
-
mode: 0o600,
|
|
122
|
-
});
|
|
123
|
-
await rename(temporary, path);
|
|
124
|
-
await chmod(path, 0o600).catch(() => undefined);
|
|
125
|
-
process.stdout.write("Saved.\n");
|
|
126
|
-
}
|
|
127
|
-
async function readConfig() {
|
|
128
|
-
try {
|
|
129
|
-
const value = JSON.parse(await readFile(configPath(), "utf8"));
|
|
130
|
-
return typeof value.apiKey === "string" ? { apiKey: value.apiKey } : undefined;
|
|
131
|
-
}
|
|
132
|
-
catch (error) {
|
|
133
|
-
if (error.code === "ENOENT")
|
|
134
|
-
return undefined;
|
|
135
|
-
throw error;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
function configPath() {
|
|
139
|
-
return process.env.AEX_CONFIG ?? join(homedir(), ".aex", "config.json");
|
|
140
|
-
}
|
|
141
|
-
async function readSecret(prompt) {
|
|
142
|
-
if (!process.stdin.isTTY) {
|
|
143
|
-
const chunks = [];
|
|
144
|
-
for await (const chunk of process.stdin)
|
|
145
|
-
chunks.push(Buffer.from(chunk));
|
|
146
|
-
return Buffer.concat(chunks).toString("utf8");
|
|
147
|
-
}
|
|
148
|
-
process.stdout.write(prompt);
|
|
149
|
-
process.stdin.setRawMode(true);
|
|
150
|
-
process.stdin.resume();
|
|
151
|
-
return new Promise((resolveValue, reject) => {
|
|
152
|
-
let value = "";
|
|
153
|
-
const cleanup = () => {
|
|
154
|
-
process.stdin.off("data", onData);
|
|
155
|
-
process.stdin.setRawMode(false);
|
|
156
|
-
process.stdin.pause();
|
|
157
|
-
process.stdout.write("\n");
|
|
158
|
-
};
|
|
159
|
-
const onData = (chunk) => {
|
|
160
|
-
for (const byte of chunk) {
|
|
161
|
-
if (byte === 3) {
|
|
162
|
-
cleanup();
|
|
163
|
-
reject(new Error("Login cancelled."));
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
if (byte === 13 || byte === 10) {
|
|
167
|
-
cleanup();
|
|
168
|
-
resolveValue(value);
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
if (byte === 8 || byte === 127) {
|
|
172
|
-
if (value.length > 0) {
|
|
173
|
-
value = value.slice(0, -1);
|
|
174
|
-
process.stdout.write("\b \b");
|
|
175
|
-
}
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
value += String.fromCharCode(byte);
|
|
179
|
-
process.stdout.write("*");
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
process.stdin.on("data", onData);
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
function requireId(id, command) {
|
|
186
|
-
if (id === undefined || id === "")
|
|
187
|
-
usage(`session ${command} requires a session id`);
|
|
188
|
-
}
|
|
189
|
-
function print(value) {
|
|
190
|
-
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
191
|
-
}
|
|
192
|
-
function usage(message) {
|
|
193
|
-
throw new Error(`${message}\n\n${HELP}`);
|
|
194
|
-
}
|
|
195
|
-
main(process.argv.slice(2)).catch((error) => {
|
|
196
|
-
if (error instanceof AexError) {
|
|
197
|
-
process.stderr.write(`${error.name}: ${error.message}${error.code === undefined ? "" : ` (${error.code})`}\n`);
|
|
198
|
-
}
|
|
199
|
-
else {
|
|
200
|
-
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
201
|
-
}
|
|
202
|
-
process.exitCode = 1;
|
|
203
|
-
});
|