levante 0.1.4 → 0.2.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/dist/cli-2pkertsv.js +936 -0
- package/dist/cli-92c6g8hy.js +263 -0
- package/dist/cli-hfteq0z5.js +76 -0
- package/dist/cli-k9cte8rh.js +888 -0
- package/dist/cli-kysyzfpx.js +251 -0
- package/dist/cli-pz51hgax.js +936 -0
- package/dist/cli-r8kj2y2g.js +888 -0
- package/dist/cli-wayz3w81.js +1881 -0
- package/dist/cli-xty0cr8k.js +13612 -0
- package/dist/cli.js +514 -2016
- package/dist/config/schema.js +1 -1
- package/dist/index-55bhcbyf.js +19 -0
- package/dist/index-c7tmqtec.js +19 -0
- package/dist/index-d4by4amj.js +19 -0
- package/dist/index-fbvbnntc.js +19 -0
- package/dist/index-pzzp72dd.js +27 -0
- package/dist/index.js +2 -2
- package/dist/mcp.js +3 -3
- package/package.json +3 -1
- package/scripts/auth/setup-auth.mjs +4 -11
- package/scripts/codegen-env.mjs +358 -76
- package/scripts/companion/open-url.mjs +20 -0
- package/scripts/companion/server.mjs +258 -0
- package/scripts/companion/state.mjs +97 -0
- package/scripts/companion/ui/companion.html +681 -0
- package/scripts/companion/ui/pill.js +65 -0
- package/scripts/ui.mjs +98 -0
|
@@ -0,0 +1,936 @@
|
|
|
1
|
+
// ../cli-auth/dist/index.js
|
|
2
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
3
|
+
import { createServer } from "node:http";
|
|
4
|
+
import process7 from "node:process";
|
|
5
|
+
import { Buffer } from "node:buffer";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { promisify as promisify5 } from "node:util";
|
|
9
|
+
import childProcess from "node:child_process";
|
|
10
|
+
import fs5, { constants as fsConstants2 } from "node:fs/promises";
|
|
11
|
+
import process3 from "node:process";
|
|
12
|
+
import fs4, { constants as fsConstants } from "node:fs/promises";
|
|
13
|
+
import process2 from "node:process";
|
|
14
|
+
import os from "node:os";
|
|
15
|
+
import fs3 from "node:fs";
|
|
16
|
+
import fs2 from "node:fs";
|
|
17
|
+
import fs from "node:fs";
|
|
18
|
+
import { promisify as promisify4 } from "node:util";
|
|
19
|
+
import process6 from "node:process";
|
|
20
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
21
|
+
import { promisify } from "node:util";
|
|
22
|
+
import process4 from "node:process";
|
|
23
|
+
import { execFile } from "node:child_process";
|
|
24
|
+
import process5 from "node:process";
|
|
25
|
+
import { promisify as promisify2 } from "node:util";
|
|
26
|
+
import { execFile as execFile2, execFileSync } from "node:child_process";
|
|
27
|
+
import { promisify as promisify3 } from "node:util";
|
|
28
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
29
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
30
|
+
import { join } from "node:path";
|
|
31
|
+
import { homedir } from "node:os";
|
|
32
|
+
var __create = Object.create;
|
|
33
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
34
|
+
var __defProp = Object.defineProperty;
|
|
35
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
36
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
37
|
+
function __accessProp(key) {
|
|
38
|
+
return this[key];
|
|
39
|
+
}
|
|
40
|
+
var __toESMCache_node;
|
|
41
|
+
var __toESMCache_esm;
|
|
42
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
43
|
+
var canCache = mod != null && typeof mod === "object";
|
|
44
|
+
if (canCache) {
|
|
45
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
46
|
+
var cached = cache.get(mod);
|
|
47
|
+
if (cached)
|
|
48
|
+
return cached;
|
|
49
|
+
}
|
|
50
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
51
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
52
|
+
for (let key of __getOwnPropNames(mod))
|
|
53
|
+
if (!__hasOwnProp.call(to, key))
|
|
54
|
+
__defProp(to, key, {
|
|
55
|
+
get: __accessProp.bind(mod, key),
|
|
56
|
+
enumerable: true
|
|
57
|
+
});
|
|
58
|
+
if (canCache)
|
|
59
|
+
cache.set(mod, to);
|
|
60
|
+
return to;
|
|
61
|
+
};
|
|
62
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
63
|
+
var require_picocolors = __commonJS((exports, module) => {
|
|
64
|
+
var p = process || {};
|
|
65
|
+
var argv = p.argv || [];
|
|
66
|
+
var env = p.env || {};
|
|
67
|
+
var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
|
|
68
|
+
var formatter = (open, close, replace = open) => (input) => {
|
|
69
|
+
let string = "" + input, index = string.indexOf(close, open.length);
|
|
70
|
+
return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
|
|
71
|
+
};
|
|
72
|
+
var replaceClose = (string, close, replace, index) => {
|
|
73
|
+
let result = "", cursor = 0;
|
|
74
|
+
do {
|
|
75
|
+
result += string.substring(cursor, index) + replace;
|
|
76
|
+
cursor = index + close.length;
|
|
77
|
+
index = string.indexOf(close, cursor);
|
|
78
|
+
} while (~index);
|
|
79
|
+
return result + string.substring(cursor);
|
|
80
|
+
};
|
|
81
|
+
var createColors = (enabled = isColorSupported) => {
|
|
82
|
+
let f = enabled ? formatter : () => String;
|
|
83
|
+
return {
|
|
84
|
+
isColorSupported: enabled,
|
|
85
|
+
reset: f("\x1B[0m", "\x1B[0m"),
|
|
86
|
+
bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
|
|
87
|
+
dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
|
|
88
|
+
italic: f("\x1B[3m", "\x1B[23m"),
|
|
89
|
+
underline: f("\x1B[4m", "\x1B[24m"),
|
|
90
|
+
inverse: f("\x1B[7m", "\x1B[27m"),
|
|
91
|
+
hidden: f("\x1B[8m", "\x1B[28m"),
|
|
92
|
+
strikethrough: f("\x1B[9m", "\x1B[29m"),
|
|
93
|
+
black: f("\x1B[30m", "\x1B[39m"),
|
|
94
|
+
red: f("\x1B[31m", "\x1B[39m"),
|
|
95
|
+
green: f("\x1B[32m", "\x1B[39m"),
|
|
96
|
+
yellow: f("\x1B[33m", "\x1B[39m"),
|
|
97
|
+
blue: f("\x1B[34m", "\x1B[39m"),
|
|
98
|
+
magenta: f("\x1B[35m", "\x1B[39m"),
|
|
99
|
+
cyan: f("\x1B[36m", "\x1B[39m"),
|
|
100
|
+
white: f("\x1B[37m", "\x1B[39m"),
|
|
101
|
+
gray: f("\x1B[90m", "\x1B[39m"),
|
|
102
|
+
bgBlack: f("\x1B[40m", "\x1B[49m"),
|
|
103
|
+
bgRed: f("\x1B[41m", "\x1B[49m"),
|
|
104
|
+
bgGreen: f("\x1B[42m", "\x1B[49m"),
|
|
105
|
+
bgYellow: f("\x1B[43m", "\x1B[49m"),
|
|
106
|
+
bgBlue: f("\x1B[44m", "\x1B[49m"),
|
|
107
|
+
bgMagenta: f("\x1B[45m", "\x1B[49m"),
|
|
108
|
+
bgCyan: f("\x1B[46m", "\x1B[49m"),
|
|
109
|
+
bgWhite: f("\x1B[47m", "\x1B[49m"),
|
|
110
|
+
blackBright: f("\x1B[90m", "\x1B[39m"),
|
|
111
|
+
redBright: f("\x1B[91m", "\x1B[39m"),
|
|
112
|
+
greenBright: f("\x1B[92m", "\x1B[39m"),
|
|
113
|
+
yellowBright: f("\x1B[93m", "\x1B[39m"),
|
|
114
|
+
blueBright: f("\x1B[94m", "\x1B[39m"),
|
|
115
|
+
magentaBright: f("\x1B[95m", "\x1B[39m"),
|
|
116
|
+
cyanBright: f("\x1B[96m", "\x1B[39m"),
|
|
117
|
+
whiteBright: f("\x1B[97m", "\x1B[39m"),
|
|
118
|
+
bgBlackBright: f("\x1B[100m", "\x1B[49m"),
|
|
119
|
+
bgRedBright: f("\x1B[101m", "\x1B[49m"),
|
|
120
|
+
bgGreenBright: f("\x1B[102m", "\x1B[49m"),
|
|
121
|
+
bgYellowBright: f("\x1B[103m", "\x1B[49m"),
|
|
122
|
+
bgBlueBright: f("\x1B[104m", "\x1B[49m"),
|
|
123
|
+
bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
|
|
124
|
+
bgCyanBright: f("\x1B[106m", "\x1B[49m"),
|
|
125
|
+
bgWhiteBright: f("\x1B[107m", "\x1B[49m")
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
module.exports = createColors();
|
|
129
|
+
module.exports.createColors = createColors;
|
|
130
|
+
});
|
|
131
|
+
var import_picocolors2 = __toESM(require_picocolors(), 1);
|
|
132
|
+
var isDockerCached;
|
|
133
|
+
function hasDockerEnv() {
|
|
134
|
+
try {
|
|
135
|
+
fs.statSync("/.dockerenv");
|
|
136
|
+
return true;
|
|
137
|
+
} catch {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function hasDockerCGroup() {
|
|
142
|
+
try {
|
|
143
|
+
return fs.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
|
|
144
|
+
} catch {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function isDocker() {
|
|
149
|
+
if (isDockerCached === undefined) {
|
|
150
|
+
isDockerCached = hasDockerEnv() || hasDockerCGroup();
|
|
151
|
+
}
|
|
152
|
+
return isDockerCached;
|
|
153
|
+
}
|
|
154
|
+
var cachedResult;
|
|
155
|
+
var hasContainerEnv = () => {
|
|
156
|
+
try {
|
|
157
|
+
fs2.statSync("/run/.containerenv");
|
|
158
|
+
return true;
|
|
159
|
+
} catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
function isInsideContainer() {
|
|
164
|
+
if (cachedResult === undefined) {
|
|
165
|
+
cachedResult = hasContainerEnv() || isDocker();
|
|
166
|
+
}
|
|
167
|
+
return cachedResult;
|
|
168
|
+
}
|
|
169
|
+
var isWsl = () => {
|
|
170
|
+
if (process2.platform !== "linux") {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
if (os.release().toLowerCase().includes("microsoft")) {
|
|
174
|
+
if (isInsideContainer()) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
if (fs3.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft")) {
|
|
181
|
+
return !isInsideContainer();
|
|
182
|
+
}
|
|
183
|
+
} catch {}
|
|
184
|
+
if (fs3.existsSync("/proc/sys/fs/binfmt_misc/WSLInterop") || fs3.existsSync("/run/WSL")) {
|
|
185
|
+
return !isInsideContainer();
|
|
186
|
+
}
|
|
187
|
+
return false;
|
|
188
|
+
};
|
|
189
|
+
var is_wsl_default = process2.env.__IS_WSL_TEST__ ? isWsl : isWsl();
|
|
190
|
+
var wslDrivesMountPoint = (() => {
|
|
191
|
+
const defaultMountPoint = "/mnt/";
|
|
192
|
+
let mountPoint;
|
|
193
|
+
return async function() {
|
|
194
|
+
if (mountPoint) {
|
|
195
|
+
return mountPoint;
|
|
196
|
+
}
|
|
197
|
+
const configFilePath = "/etc/wsl.conf";
|
|
198
|
+
let isConfigFileExists = false;
|
|
199
|
+
try {
|
|
200
|
+
await fs4.access(configFilePath, fsConstants.F_OK);
|
|
201
|
+
isConfigFileExists = true;
|
|
202
|
+
} catch {}
|
|
203
|
+
if (!isConfigFileExists) {
|
|
204
|
+
return defaultMountPoint;
|
|
205
|
+
}
|
|
206
|
+
const configContent = await fs4.readFile(configFilePath, { encoding: "utf8" });
|
|
207
|
+
const configMountPoint = /(?<!#.*)root\s*=\s*(?<mountPoint>.*)/g.exec(configContent);
|
|
208
|
+
if (!configMountPoint) {
|
|
209
|
+
return defaultMountPoint;
|
|
210
|
+
}
|
|
211
|
+
mountPoint = configMountPoint.groups.mountPoint.trim();
|
|
212
|
+
mountPoint = mountPoint.endsWith("/") ? mountPoint : `${mountPoint}/`;
|
|
213
|
+
return mountPoint;
|
|
214
|
+
};
|
|
215
|
+
})();
|
|
216
|
+
var powerShellPathFromWsl = async () => {
|
|
217
|
+
const mountPoint = await wslDrivesMountPoint();
|
|
218
|
+
return `${mountPoint}c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe`;
|
|
219
|
+
};
|
|
220
|
+
var powerShellPath = async () => {
|
|
221
|
+
if (is_wsl_default) {
|
|
222
|
+
return powerShellPathFromWsl();
|
|
223
|
+
}
|
|
224
|
+
return `${process3.env.SYSTEMROOT || process3.env.windir || String.raw`C:\Windows`}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;
|
|
225
|
+
};
|
|
226
|
+
function defineLazyProperty(object, propertyName, valueGetter) {
|
|
227
|
+
const define = (value) => Object.defineProperty(object, propertyName, { value, enumerable: true, writable: true });
|
|
228
|
+
Object.defineProperty(object, propertyName, {
|
|
229
|
+
configurable: true,
|
|
230
|
+
enumerable: true,
|
|
231
|
+
get() {
|
|
232
|
+
const result = valueGetter();
|
|
233
|
+
define(result);
|
|
234
|
+
return result;
|
|
235
|
+
},
|
|
236
|
+
set(value) {
|
|
237
|
+
define(value);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
return object;
|
|
241
|
+
}
|
|
242
|
+
var execFileAsync = promisify(execFile);
|
|
243
|
+
async function defaultBrowserId() {
|
|
244
|
+
if (process4.platform !== "darwin") {
|
|
245
|
+
throw new Error("macOS only");
|
|
246
|
+
}
|
|
247
|
+
const { stdout } = await execFileAsync("defaults", ["read", "com.apple.LaunchServices/com.apple.launchservices.secure", "LSHandlers"]);
|
|
248
|
+
const match = /LSHandlerRoleAll = "(?!-)(?<id>[^"]+?)";\s+?LSHandlerURLScheme = (?:http|https);/.exec(stdout);
|
|
249
|
+
const browserId = match?.groups.id ?? "com.apple.Safari";
|
|
250
|
+
if (browserId === "com.apple.safari") {
|
|
251
|
+
return "com.apple.Safari";
|
|
252
|
+
}
|
|
253
|
+
return browserId;
|
|
254
|
+
}
|
|
255
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
256
|
+
async function runAppleScript(script, { humanReadableOutput = true, signal } = {}) {
|
|
257
|
+
if (process5.platform !== "darwin") {
|
|
258
|
+
throw new Error("macOS only");
|
|
259
|
+
}
|
|
260
|
+
const outputArguments = humanReadableOutput ? [] : ["-ss"];
|
|
261
|
+
const execOptions = {};
|
|
262
|
+
if (signal) {
|
|
263
|
+
execOptions.signal = signal;
|
|
264
|
+
}
|
|
265
|
+
const { stdout } = await execFileAsync2("osascript", ["-e", script, outputArguments], execOptions);
|
|
266
|
+
return stdout.trim();
|
|
267
|
+
}
|
|
268
|
+
async function bundleName(bundleId) {
|
|
269
|
+
return runAppleScript(`tell application "Finder" to set app_path to application file id "${bundleId}" as string
|
|
270
|
+
tell application "System Events" to get value of property list item "CFBundleName" of property list file (app_path & ":Contents:Info.plist")`);
|
|
271
|
+
}
|
|
272
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
273
|
+
var windowsBrowserProgIds = {
|
|
274
|
+
MSEdgeHTM: { name: "Edge", id: "com.microsoft.edge" },
|
|
275
|
+
MSEdgeBHTML: { name: "Edge Beta", id: "com.microsoft.edge.beta" },
|
|
276
|
+
MSEdgeDHTML: { name: "Edge Dev", id: "com.microsoft.edge.dev" },
|
|
277
|
+
AppXq0fevzme2pys62n3e0fbqa7peapykr8v: { name: "Edge", id: "com.microsoft.edge.old" },
|
|
278
|
+
ChromeHTML: { name: "Chrome", id: "com.google.chrome" },
|
|
279
|
+
ChromeBHTML: { name: "Chrome Beta", id: "com.google.chrome.beta" },
|
|
280
|
+
ChromeDHTML: { name: "Chrome Dev", id: "com.google.chrome.dev" },
|
|
281
|
+
ChromiumHTM: { name: "Chromium", id: "org.chromium.Chromium" },
|
|
282
|
+
BraveHTML: { name: "Brave", id: "com.brave.Browser" },
|
|
283
|
+
BraveBHTML: { name: "Brave Beta", id: "com.brave.Browser.beta" },
|
|
284
|
+
BraveDHTML: { name: "Brave Dev", id: "com.brave.Browser.dev" },
|
|
285
|
+
BraveSSHTM: { name: "Brave Nightly", id: "com.brave.Browser.nightly" },
|
|
286
|
+
FirefoxURL: { name: "Firefox", id: "org.mozilla.firefox" },
|
|
287
|
+
OperaStable: { name: "Opera", id: "com.operasoftware.Opera" },
|
|
288
|
+
VivaldiHTM: { name: "Vivaldi", id: "com.vivaldi.Vivaldi" },
|
|
289
|
+
"IE.HTTP": { name: "Internet Explorer", id: "com.microsoft.ie" }
|
|
290
|
+
};
|
|
291
|
+
var _windowsBrowserProgIdMap = new Map(Object.entries(windowsBrowserProgIds));
|
|
292
|
+
|
|
293
|
+
class UnknownBrowserError extends Error {
|
|
294
|
+
}
|
|
295
|
+
async function defaultBrowser(_execFileAsync = execFileAsync3) {
|
|
296
|
+
const { stdout } = await _execFileAsync("reg", [
|
|
297
|
+
"QUERY",
|
|
298
|
+
" HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\http\\UserChoice",
|
|
299
|
+
"/v",
|
|
300
|
+
"ProgId"
|
|
301
|
+
]);
|
|
302
|
+
const match = /ProgId\s*REG_SZ\s*(?<id>\S+)/.exec(stdout);
|
|
303
|
+
if (!match) {
|
|
304
|
+
throw new UnknownBrowserError(`Cannot find Windows browser in stdout: ${JSON.stringify(stdout)}`);
|
|
305
|
+
}
|
|
306
|
+
const { id } = match.groups;
|
|
307
|
+
const dotIndex = id.lastIndexOf(".");
|
|
308
|
+
const hyphenIndex = id.lastIndexOf("-");
|
|
309
|
+
const baseIdByDot = dotIndex === -1 ? undefined : id.slice(0, dotIndex);
|
|
310
|
+
const baseIdByHyphen = hyphenIndex === -1 ? undefined : id.slice(0, hyphenIndex);
|
|
311
|
+
return windowsBrowserProgIds[id] ?? windowsBrowserProgIds[baseIdByDot] ?? windowsBrowserProgIds[baseIdByHyphen] ?? { name: id, id };
|
|
312
|
+
}
|
|
313
|
+
var execFileAsync4 = promisify4(execFile4);
|
|
314
|
+
var titleize = (string) => string.toLowerCase().replaceAll(/(?:^|\s|-)\S/g, (x) => x.toUpperCase());
|
|
315
|
+
async function defaultBrowser2() {
|
|
316
|
+
if (process6.platform === "darwin") {
|
|
317
|
+
const id = await defaultBrowserId();
|
|
318
|
+
const name = await bundleName(id);
|
|
319
|
+
return { name, id };
|
|
320
|
+
}
|
|
321
|
+
if (process6.platform === "linux") {
|
|
322
|
+
const { stdout } = await execFileAsync4("xdg-mime", ["query", "default", "x-scheme-handler/http"]);
|
|
323
|
+
const id = stdout.trim();
|
|
324
|
+
const name = titleize(id.replace(/.desktop$/, "").replace("-", " "));
|
|
325
|
+
return { name, id };
|
|
326
|
+
}
|
|
327
|
+
if (process6.platform === "win32") {
|
|
328
|
+
return defaultBrowser();
|
|
329
|
+
}
|
|
330
|
+
throw new Error("Only macOS, Linux, and Windows are supported");
|
|
331
|
+
}
|
|
332
|
+
var execFile5 = promisify5(childProcess.execFile);
|
|
333
|
+
var __dirname2 = path.dirname(fileURLToPath(import.meta.url));
|
|
334
|
+
var localXdgOpenPath = path.join(__dirname2, "xdg-open");
|
|
335
|
+
var { platform, arch } = process7;
|
|
336
|
+
async function getWindowsDefaultBrowserFromWsl() {
|
|
337
|
+
const powershellPath = await powerShellPath();
|
|
338
|
+
const rawCommand = String.raw`(Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice").ProgId`;
|
|
339
|
+
const encodedCommand = Buffer.from(rawCommand, "utf16le").toString("base64");
|
|
340
|
+
const { stdout } = await execFile5(powershellPath, [
|
|
341
|
+
"-NoProfile",
|
|
342
|
+
"-NonInteractive",
|
|
343
|
+
"-ExecutionPolicy",
|
|
344
|
+
"Bypass",
|
|
345
|
+
"-EncodedCommand",
|
|
346
|
+
encodedCommand
|
|
347
|
+
], { encoding: "utf8" });
|
|
348
|
+
const progId = stdout.trim();
|
|
349
|
+
const browserMap = {
|
|
350
|
+
ChromeHTML: "com.google.chrome",
|
|
351
|
+
BraveHTML: "com.brave.Browser",
|
|
352
|
+
MSEdgeHTM: "com.microsoft.edge",
|
|
353
|
+
FirefoxURL: "org.mozilla.firefox"
|
|
354
|
+
};
|
|
355
|
+
return browserMap[progId] ? { id: browserMap[progId] } : {};
|
|
356
|
+
}
|
|
357
|
+
var pTryEach = async (array, mapper) => {
|
|
358
|
+
let latestError;
|
|
359
|
+
for (const item of array) {
|
|
360
|
+
try {
|
|
361
|
+
return await mapper(item);
|
|
362
|
+
} catch (error) {
|
|
363
|
+
latestError = error;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
throw latestError;
|
|
367
|
+
};
|
|
368
|
+
var baseOpen = async (options) => {
|
|
369
|
+
options = {
|
|
370
|
+
wait: false,
|
|
371
|
+
background: false,
|
|
372
|
+
newInstance: false,
|
|
373
|
+
allowNonzeroExitCode: false,
|
|
374
|
+
...options
|
|
375
|
+
};
|
|
376
|
+
if (Array.isArray(options.app)) {
|
|
377
|
+
return pTryEach(options.app, (singleApp) => baseOpen({
|
|
378
|
+
...options,
|
|
379
|
+
app: singleApp
|
|
380
|
+
}));
|
|
381
|
+
}
|
|
382
|
+
let { name: app, arguments: appArguments = [] } = options.app ?? {};
|
|
383
|
+
appArguments = [...appArguments];
|
|
384
|
+
if (Array.isArray(app)) {
|
|
385
|
+
return pTryEach(app, (appName) => baseOpen({
|
|
386
|
+
...options,
|
|
387
|
+
app: {
|
|
388
|
+
name: appName,
|
|
389
|
+
arguments: appArguments
|
|
390
|
+
}
|
|
391
|
+
}));
|
|
392
|
+
}
|
|
393
|
+
if (app === "browser" || app === "browserPrivate") {
|
|
394
|
+
const ids = {
|
|
395
|
+
"com.google.chrome": "chrome",
|
|
396
|
+
"google-chrome.desktop": "chrome",
|
|
397
|
+
"com.brave.Browser": "brave",
|
|
398
|
+
"org.mozilla.firefox": "firefox",
|
|
399
|
+
"firefox.desktop": "firefox",
|
|
400
|
+
"com.microsoft.msedge": "edge",
|
|
401
|
+
"com.microsoft.edge": "edge",
|
|
402
|
+
"com.microsoft.edgemac": "edge",
|
|
403
|
+
"microsoft-edge.desktop": "edge"
|
|
404
|
+
};
|
|
405
|
+
const flags = {
|
|
406
|
+
chrome: "--incognito",
|
|
407
|
+
brave: "--incognito",
|
|
408
|
+
firefox: "--private-window",
|
|
409
|
+
edge: "--inPrivate"
|
|
410
|
+
};
|
|
411
|
+
const browser = is_wsl_default ? await getWindowsDefaultBrowserFromWsl() : await defaultBrowser2();
|
|
412
|
+
if (browser.id in ids) {
|
|
413
|
+
const browserName = ids[browser.id];
|
|
414
|
+
if (app === "browserPrivate") {
|
|
415
|
+
appArguments.push(flags[browserName]);
|
|
416
|
+
}
|
|
417
|
+
return baseOpen({
|
|
418
|
+
...options,
|
|
419
|
+
app: {
|
|
420
|
+
name: apps[browserName],
|
|
421
|
+
arguments: appArguments
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
throw new Error(`${browser.name} is not supported as a default browser`);
|
|
426
|
+
}
|
|
427
|
+
let command;
|
|
428
|
+
const cliArguments = [];
|
|
429
|
+
const childProcessOptions = {};
|
|
430
|
+
if (platform === "darwin") {
|
|
431
|
+
command = "open";
|
|
432
|
+
if (options.wait) {
|
|
433
|
+
cliArguments.push("--wait-apps");
|
|
434
|
+
}
|
|
435
|
+
if (options.background) {
|
|
436
|
+
cliArguments.push("--background");
|
|
437
|
+
}
|
|
438
|
+
if (options.newInstance) {
|
|
439
|
+
cliArguments.push("--new");
|
|
440
|
+
}
|
|
441
|
+
if (app) {
|
|
442
|
+
cliArguments.push("-a", app);
|
|
443
|
+
}
|
|
444
|
+
} else if (platform === "win32" || is_wsl_default && !isInsideContainer() && !app) {
|
|
445
|
+
command = await powerShellPath();
|
|
446
|
+
cliArguments.push("-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand");
|
|
447
|
+
if (!is_wsl_default) {
|
|
448
|
+
childProcessOptions.windowsVerbatimArguments = true;
|
|
449
|
+
}
|
|
450
|
+
const encodedArguments = ["Start"];
|
|
451
|
+
if (options.wait) {
|
|
452
|
+
encodedArguments.push("-Wait");
|
|
453
|
+
}
|
|
454
|
+
if (app) {
|
|
455
|
+
encodedArguments.push(`"\`"${app}\`""`);
|
|
456
|
+
if (options.target) {
|
|
457
|
+
appArguments.push(options.target);
|
|
458
|
+
}
|
|
459
|
+
} else if (options.target) {
|
|
460
|
+
encodedArguments.push(`"${options.target}"`);
|
|
461
|
+
}
|
|
462
|
+
if (appArguments.length > 0) {
|
|
463
|
+
appArguments = appArguments.map((argument) => `"\`"${argument}\`""`);
|
|
464
|
+
encodedArguments.push("-ArgumentList", appArguments.join(","));
|
|
465
|
+
}
|
|
466
|
+
options.target = Buffer.from(encodedArguments.join(" "), "utf16le").toString("base64");
|
|
467
|
+
} else {
|
|
468
|
+
if (app) {
|
|
469
|
+
command = app;
|
|
470
|
+
} else {
|
|
471
|
+
const isBundled = !__dirname2 || __dirname2 === "/";
|
|
472
|
+
let exeLocalXdgOpen = false;
|
|
473
|
+
try {
|
|
474
|
+
await fs5.access(localXdgOpenPath, fsConstants2.X_OK);
|
|
475
|
+
exeLocalXdgOpen = true;
|
|
476
|
+
} catch {}
|
|
477
|
+
const useSystemXdgOpen = process7.versions.electron ?? (platform === "android" || isBundled || !exeLocalXdgOpen);
|
|
478
|
+
command = useSystemXdgOpen ? "xdg-open" : localXdgOpenPath;
|
|
479
|
+
}
|
|
480
|
+
if (appArguments.length > 0) {
|
|
481
|
+
cliArguments.push(...appArguments);
|
|
482
|
+
}
|
|
483
|
+
if (!options.wait) {
|
|
484
|
+
childProcessOptions.stdio = "ignore";
|
|
485
|
+
childProcessOptions.detached = true;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
if (platform === "darwin" && appArguments.length > 0) {
|
|
489
|
+
cliArguments.push("--args", ...appArguments);
|
|
490
|
+
}
|
|
491
|
+
if (options.target) {
|
|
492
|
+
cliArguments.push(options.target);
|
|
493
|
+
}
|
|
494
|
+
const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);
|
|
495
|
+
if (options.wait) {
|
|
496
|
+
return new Promise((resolve, reject) => {
|
|
497
|
+
subprocess.once("error", reject);
|
|
498
|
+
subprocess.once("close", (exitCode) => {
|
|
499
|
+
if (!options.allowNonzeroExitCode && exitCode > 0) {
|
|
500
|
+
reject(new Error(`Exited with code ${exitCode}`));
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
resolve(subprocess);
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
subprocess.unref();
|
|
508
|
+
return subprocess;
|
|
509
|
+
};
|
|
510
|
+
var open = (target, options) => {
|
|
511
|
+
if (typeof target !== "string") {
|
|
512
|
+
throw new TypeError("Expected a `target`");
|
|
513
|
+
}
|
|
514
|
+
return baseOpen({
|
|
515
|
+
...options,
|
|
516
|
+
target
|
|
517
|
+
});
|
|
518
|
+
};
|
|
519
|
+
function detectArchBinary(binary) {
|
|
520
|
+
if (typeof binary === "string" || Array.isArray(binary)) {
|
|
521
|
+
return binary;
|
|
522
|
+
}
|
|
523
|
+
const { [arch]: archBinary } = binary;
|
|
524
|
+
if (!archBinary) {
|
|
525
|
+
throw new Error(`${arch} is not supported`);
|
|
526
|
+
}
|
|
527
|
+
return archBinary;
|
|
528
|
+
}
|
|
529
|
+
function detectPlatformBinary({ [platform]: platformBinary }, { wsl }) {
|
|
530
|
+
if (wsl && is_wsl_default) {
|
|
531
|
+
return detectArchBinary(wsl);
|
|
532
|
+
}
|
|
533
|
+
if (!platformBinary) {
|
|
534
|
+
throw new Error(`${platform} is not supported`);
|
|
535
|
+
}
|
|
536
|
+
return detectArchBinary(platformBinary);
|
|
537
|
+
}
|
|
538
|
+
var apps = {};
|
|
539
|
+
defineLazyProperty(apps, "chrome", () => detectPlatformBinary({
|
|
540
|
+
darwin: "google chrome",
|
|
541
|
+
win32: "chrome",
|
|
542
|
+
linux: ["google-chrome", "google-chrome-stable", "chromium"]
|
|
543
|
+
}, {
|
|
544
|
+
wsl: {
|
|
545
|
+
ia32: "/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe",
|
|
546
|
+
x64: ["/mnt/c/Program Files/Google/Chrome/Application/chrome.exe", "/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe"]
|
|
547
|
+
}
|
|
548
|
+
}));
|
|
549
|
+
defineLazyProperty(apps, "brave", () => detectPlatformBinary({
|
|
550
|
+
darwin: "brave browser",
|
|
551
|
+
win32: "brave",
|
|
552
|
+
linux: ["brave-browser", "brave"]
|
|
553
|
+
}, {
|
|
554
|
+
wsl: {
|
|
555
|
+
ia32: "/mnt/c/Program Files (x86)/BraveSoftware/Brave-Browser/Application/brave.exe",
|
|
556
|
+
x64: ["/mnt/c/Program Files/BraveSoftware/Brave-Browser/Application/brave.exe", "/mnt/c/Program Files (x86)/BraveSoftware/Brave-Browser/Application/brave.exe"]
|
|
557
|
+
}
|
|
558
|
+
}));
|
|
559
|
+
defineLazyProperty(apps, "firefox", () => detectPlatformBinary({
|
|
560
|
+
darwin: "firefox",
|
|
561
|
+
win32: String.raw`C:\Program Files\Mozilla Firefox\firefox.exe`,
|
|
562
|
+
linux: "firefox"
|
|
563
|
+
}, {
|
|
564
|
+
wsl: "/mnt/c/Program Files/Mozilla Firefox/firefox.exe"
|
|
565
|
+
}));
|
|
566
|
+
defineLazyProperty(apps, "edge", () => detectPlatformBinary({
|
|
567
|
+
darwin: "microsoft edge",
|
|
568
|
+
win32: "msedge",
|
|
569
|
+
linux: ["microsoft-edge", "microsoft-edge-dev"]
|
|
570
|
+
}, {
|
|
571
|
+
wsl: "/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe"
|
|
572
|
+
}));
|
|
573
|
+
defineLazyProperty(apps, "browser", () => "browser");
|
|
574
|
+
defineLazyProperty(apps, "browserPrivate", () => "browserPrivate");
|
|
575
|
+
var open_default = open;
|
|
576
|
+
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
577
|
+
var AUTH_DIR = join(homedir(), ".qai");
|
|
578
|
+
var AUTH_FILE = join(AUTH_DIR, "auth.json");
|
|
579
|
+
function emptyStore() {
|
|
580
|
+
return { version: 1, sessions: [], activeSessionIndex: -1 };
|
|
581
|
+
}
|
|
582
|
+
function loadStore() {
|
|
583
|
+
if (!existsSync(AUTH_FILE))
|
|
584
|
+
return emptyStore();
|
|
585
|
+
try {
|
|
586
|
+
const data = JSON.parse(readFileSync(AUTH_FILE, "utf-8"));
|
|
587
|
+
if (data.version !== 1)
|
|
588
|
+
return emptyStore();
|
|
589
|
+
return data;
|
|
590
|
+
} catch {
|
|
591
|
+
return emptyStore();
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
function saveStore(store) {
|
|
595
|
+
mkdirSync(AUTH_DIR, { recursive: true });
|
|
596
|
+
writeFileSync(AUTH_FILE, JSON.stringify(store, null, 2), { mode: 384 });
|
|
597
|
+
}
|
|
598
|
+
function getActiveSession() {
|
|
599
|
+
const store = loadStore();
|
|
600
|
+
if (store.activeSessionIndex < 0 || store.activeSessionIndex >= store.sessions.length) {
|
|
601
|
+
return null;
|
|
602
|
+
}
|
|
603
|
+
return store.sessions[store.activeSessionIndex];
|
|
604
|
+
}
|
|
605
|
+
function saveSession(session) {
|
|
606
|
+
const store = loadStore();
|
|
607
|
+
const existingIdx = store.sessions.findIndex((s) => s.webappUrl === session.webappUrl && s.scope.appId === session.scope.appId);
|
|
608
|
+
if (existingIdx >= 0) {
|
|
609
|
+
store.sessions[existingIdx] = session;
|
|
610
|
+
store.activeSessionIndex = existingIdx;
|
|
611
|
+
} else {
|
|
612
|
+
store.sessions.push(session);
|
|
613
|
+
store.activeSessionIndex = store.sessions.length - 1;
|
|
614
|
+
}
|
|
615
|
+
saveStore(store);
|
|
616
|
+
}
|
|
617
|
+
function clearSession(webappUrl) {
|
|
618
|
+
const store = loadStore();
|
|
619
|
+
if (webappUrl) {
|
|
620
|
+
store.sessions = store.sessions.filter((s) => s.webappUrl !== webappUrl);
|
|
621
|
+
} else {
|
|
622
|
+
store.sessions = [];
|
|
623
|
+
}
|
|
624
|
+
store.activeSessionIndex = store.sessions.length > 0 ? 0 : -1;
|
|
625
|
+
saveStore(store);
|
|
626
|
+
}
|
|
627
|
+
var LOGIN_TIMEOUT_MS = 120000;
|
|
628
|
+
function generateCodeVerifier() {
|
|
629
|
+
return randomBytes(32).toString("base64url");
|
|
630
|
+
}
|
|
631
|
+
function generateCodeChallenge(verifier) {
|
|
632
|
+
return createHash("sha256").update(verifier).digest("base64url");
|
|
633
|
+
}
|
|
634
|
+
function generateState() {
|
|
635
|
+
return randomBytes(16).toString("hex");
|
|
636
|
+
}
|
|
637
|
+
function htmlPage(body) {
|
|
638
|
+
return `<!DOCTYPE html>
|
|
639
|
+
<html lang="en">
|
|
640
|
+
<head>
|
|
641
|
+
<meta charset="utf-8">
|
|
642
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
643
|
+
<title>QA Intelligence CLI</title>
|
|
644
|
+
<style>
|
|
645
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
646
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0a0e17; color: #e2e8f0; min-height: 100vh; display: flex; align-items: center; justify-content: center; }
|
|
647
|
+
.card { background: #131a2b; border: 1px solid #1e293b; border-radius: 16px; padding: 48px; max-width: 480px; width: 100%; text-align: center; box-shadow: 0 25px 50px rgba(0,0,0,0.4); }
|
|
648
|
+
.icon { font-size: 48px; margin-bottom: 16px; }
|
|
649
|
+
.icon.success { color: #22d3ee; }
|
|
650
|
+
.icon.error { color: #f87171; }
|
|
651
|
+
h2 { font-size: 22px; font-weight: 600; margin-bottom: 24px; }
|
|
652
|
+
.detail { display: flex; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid #1e293b; font-size: 14px; }
|
|
653
|
+
.detail:last-child { border-bottom: none; }
|
|
654
|
+
.detail .label { color: #64748b; }
|
|
655
|
+
.detail .value { color: #e2e8f0; font-weight: 500; }
|
|
656
|
+
.scope { background: #0c1322; border: 1px solid #1e293b; border-radius: 8px; padding: 16px; margin: 20px 0; }
|
|
657
|
+
.close-hint { color: #475569; font-size: 13px; margin-top: 24px; }
|
|
658
|
+
.error-msg { color: #f87171; background: #1c1117; border: 1px solid #7f1d1d; border-radius: 8px; padding: 12px 16px; margin-top: 16px; font-size: 14px; }
|
|
659
|
+
</style>
|
|
660
|
+
</head>
|
|
661
|
+
<body><div class="card">${body}</div></body>
|
|
662
|
+
</html>`;
|
|
663
|
+
}
|
|
664
|
+
function successPage(email, orgSlug, projectSlug, appSlug) {
|
|
665
|
+
return htmlPage(`
|
|
666
|
+
<div class="icon success">✓</div>
|
|
667
|
+
<h2>Authenticated</h2>
|
|
668
|
+
<div class="scope">
|
|
669
|
+
<div class="detail"><span class="label">User</span><span class="value">${email}</span></div>
|
|
670
|
+
<div class="detail"><span class="label">Organization</span><span class="value">${orgSlug}</span></div>
|
|
671
|
+
<div class="detail"><span class="label">Project</span><span class="value">${projectSlug}</span></div>
|
|
672
|
+
<div class="detail"><span class="label">App</span><span class="value">${appSlug}</span></div>
|
|
673
|
+
</div>
|
|
674
|
+
<p class="close-hint">You can close this window and return to your terminal.</p>
|
|
675
|
+
`);
|
|
676
|
+
}
|
|
677
|
+
function errorPage(title, message) {
|
|
678
|
+
return htmlPage(`
|
|
679
|
+
<div class="icon error">✗</div>
|
|
680
|
+
<h2>${title}</h2>
|
|
681
|
+
<div class="error-msg">${message}</div>
|
|
682
|
+
<p class="close-hint">Check your terminal for details.</p>
|
|
683
|
+
`);
|
|
684
|
+
}
|
|
685
|
+
async function login(opts) {
|
|
686
|
+
const { webappUrl } = opts;
|
|
687
|
+
const codeVerifier = generateCodeVerifier();
|
|
688
|
+
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
689
|
+
const state = generateState();
|
|
690
|
+
return new Promise((resolve, reject) => {
|
|
691
|
+
const server = createServer(async (req, res) => {
|
|
692
|
+
const url = new URL(req.url, `http://localhost`);
|
|
693
|
+
if (url.pathname !== "/callback") {
|
|
694
|
+
res.writeHead(404);
|
|
695
|
+
res.end("Not found");
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
const code = url.searchParams.get("code");
|
|
699
|
+
const returnedState = url.searchParams.get("state");
|
|
700
|
+
const error = url.searchParams.get("error");
|
|
701
|
+
if (error) {
|
|
702
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
703
|
+
res.end(errorPage("Authentication Failed", error));
|
|
704
|
+
server.close();
|
|
705
|
+
reject(new Error(`Auth failed: ${error}`));
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
if (!code || returnedState !== state) {
|
|
709
|
+
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
|
|
710
|
+
res.end(errorPage("Invalid Callback", "Missing authorization code or state mismatch."));
|
|
711
|
+
server.close();
|
|
712
|
+
reject(new Error("Invalid callback: missing code or state mismatch"));
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
try {
|
|
716
|
+
const exchangeRes = await fetch(`${webappUrl}/api/cli/token/exchange`, {
|
|
717
|
+
method: "POST",
|
|
718
|
+
headers: { "Content-Type": "application/json" },
|
|
719
|
+
body: JSON.stringify({ code, code_verifier: codeVerifier })
|
|
720
|
+
});
|
|
721
|
+
if (!exchangeRes.ok) {
|
|
722
|
+
const errBody = await exchangeRes.text();
|
|
723
|
+
throw new Error(`Token exchange failed (${exchangeRes.status}): ${errBody}`);
|
|
724
|
+
}
|
|
725
|
+
const data = await exchangeRes.json();
|
|
726
|
+
const session = {
|
|
727
|
+
webappUrl,
|
|
728
|
+
accessToken: data.access_token,
|
|
729
|
+
refreshToken: data.refresh_token,
|
|
730
|
+
expiresAt: new Date(Date.now() + data.expires_in * 1000).toISOString(),
|
|
731
|
+
refreshExpiresAt: new Date(Date.now() + data.refresh_expires_in * 1000).toISOString(),
|
|
732
|
+
user: data.user,
|
|
733
|
+
scope: data.scope,
|
|
734
|
+
createdAt: new Date().toISOString()
|
|
735
|
+
};
|
|
736
|
+
saveSession(session);
|
|
737
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
738
|
+
res.end(successPage(data.user.email, data.scope.orgSlug, data.scope.projectSlug, data.scope.appSlug));
|
|
739
|
+
server.close();
|
|
740
|
+
resolve(session);
|
|
741
|
+
} catch (err) {
|
|
742
|
+
res.writeHead(500, { "Content-Type": "text/html; charset=utf-8" });
|
|
743
|
+
res.end(errorPage("Authentication Error", "Token exchange failed. Check your terminal for details."));
|
|
744
|
+
server.close();
|
|
745
|
+
reject(err);
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
server.listen(0, "127.0.0.1", () => {
|
|
749
|
+
const addr = server.address();
|
|
750
|
+
if (!addr || typeof addr === "string") {
|
|
751
|
+
reject(new Error("Failed to start local server"));
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
const port = addr.port;
|
|
755
|
+
const authUrl = `${webappUrl}/cli/auth?redirect_port=${port}&code_challenge=${codeChallenge}&state=${state}`;
|
|
756
|
+
console.log(import_picocolors.default.dim(`Opening browser for authentication...`));
|
|
757
|
+
console.log(import_picocolors.default.dim(`If the browser doesn't open, visit:`));
|
|
758
|
+
console.log(import_picocolors.default.cyan(authUrl));
|
|
759
|
+
open_default(authUrl).catch(() => {});
|
|
760
|
+
});
|
|
761
|
+
setTimeout(() => {
|
|
762
|
+
server.close();
|
|
763
|
+
reject(new Error("Login timed out after 120 seconds"));
|
|
764
|
+
}, LOGIN_TIMEOUT_MS);
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
class AuthClient {
|
|
769
|
+
session;
|
|
770
|
+
constructor(session) {
|
|
771
|
+
this.session = session;
|
|
772
|
+
}
|
|
773
|
+
static fromSession() {
|
|
774
|
+
const session = getActiveSession();
|
|
775
|
+
if (!session)
|
|
776
|
+
return null;
|
|
777
|
+
return new AuthClient(session);
|
|
778
|
+
}
|
|
779
|
+
async refreshToken() {
|
|
780
|
+
try {
|
|
781
|
+
const res = await fetch(`${this.session.webappUrl}/api/cli/token/refresh`, {
|
|
782
|
+
method: "POST",
|
|
783
|
+
headers: {
|
|
784
|
+
"Content-Type": "application/json",
|
|
785
|
+
Authorization: `Bearer ${this.session.refreshToken}`
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
if (!res.ok)
|
|
789
|
+
return false;
|
|
790
|
+
const data = await res.json();
|
|
791
|
+
this.session = {
|
|
792
|
+
...this.session,
|
|
793
|
+
accessToken: data.access_token,
|
|
794
|
+
expiresAt: new Date(Date.now() + data.expires_in * 1000).toISOString()
|
|
795
|
+
};
|
|
796
|
+
saveSession(this.session);
|
|
797
|
+
return true;
|
|
798
|
+
} catch {
|
|
799
|
+
return false;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
isExpired() {
|
|
803
|
+
return new Date(this.session.expiresAt).getTime() < Date.now() + 60000;
|
|
804
|
+
}
|
|
805
|
+
async request(path2, options = {}) {
|
|
806
|
+
if (this.isExpired()) {
|
|
807
|
+
const refreshed = await this.refreshToken();
|
|
808
|
+
if (!refreshed) {
|
|
809
|
+
clearSession(this.session.webappUrl);
|
|
810
|
+
throw new Error("Session expired. Please run `auth login` again.");
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
const url = `${this.session.webappUrl}${path2}`;
|
|
814
|
+
const res = await fetch(url, {
|
|
815
|
+
...options,
|
|
816
|
+
headers: {
|
|
817
|
+
...options.headers,
|
|
818
|
+
Authorization: `Bearer ${this.session.accessToken}`,
|
|
819
|
+
"Content-Type": "application/json"
|
|
820
|
+
}
|
|
821
|
+
});
|
|
822
|
+
if (res.status === 401) {
|
|
823
|
+
const refreshed = await this.refreshToken();
|
|
824
|
+
if (!refreshed) {
|
|
825
|
+
clearSession(this.session.webappUrl);
|
|
826
|
+
throw new Error("Session expired. Please run `auth login` again.");
|
|
827
|
+
}
|
|
828
|
+
return fetch(url, {
|
|
829
|
+
...options,
|
|
830
|
+
headers: {
|
|
831
|
+
...options.headers,
|
|
832
|
+
Authorization: `Bearer ${this.session.accessToken}`,
|
|
833
|
+
"Content-Type": "application/json"
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
return res;
|
|
838
|
+
}
|
|
839
|
+
async get(path2) {
|
|
840
|
+
const res = await this.request(path2);
|
|
841
|
+
if (!res.ok) {
|
|
842
|
+
const body = await res.text();
|
|
843
|
+
throw new Error(`GET ${path2} failed (${res.status}): ${body}`);
|
|
844
|
+
}
|
|
845
|
+
return res.json();
|
|
846
|
+
}
|
|
847
|
+
async post(path2, body) {
|
|
848
|
+
const res = await this.request(path2, {
|
|
849
|
+
method: "POST",
|
|
850
|
+
body: body ? JSON.stringify(body) : undefined
|
|
851
|
+
});
|
|
852
|
+
if (!res.ok) {
|
|
853
|
+
const errBody = await res.text();
|
|
854
|
+
throw new Error(`POST ${path2} failed (${res.status}): ${errBody}`);
|
|
855
|
+
}
|
|
856
|
+
return res.json();
|
|
857
|
+
}
|
|
858
|
+
async del(path2) {
|
|
859
|
+
const res = await this.request(path2, { method: "DELETE" });
|
|
860
|
+
if (!res.ok) {
|
|
861
|
+
const body = await res.text();
|
|
862
|
+
throw new Error(`DELETE ${path2} failed (${res.status}): ${body}`);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
getSession() {
|
|
866
|
+
return this.session;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
var DEFAULT_WEBAPP_URL = "https://qaligent.space";
|
|
870
|
+
function registerAuthCommands(program, opts) {
|
|
871
|
+
const defaultUrl = opts?.defaultWebappUrl ?? DEFAULT_WEBAPP_URL;
|
|
872
|
+
const authCmd = program.command("auth").description("Manage QA Intelligence authentication");
|
|
873
|
+
authCmd.command("login").description("Authenticate with QA Intelligence").option("--url <url>", "Webapp URL", defaultUrl).action(async (cmdOpts) => {
|
|
874
|
+
console.log(import_picocolors2.default.bold(`Authenticating with QA Intelligence...
|
|
875
|
+
`));
|
|
876
|
+
try {
|
|
877
|
+
const session = await login({ webappUrl: cmdOpts.url });
|
|
878
|
+
console.log("");
|
|
879
|
+
console.log(import_picocolors2.default.green("✓") + " Authenticated as " + import_picocolors2.default.bold(session.user.email));
|
|
880
|
+
console.log(import_picocolors2.default.green("✓") + " Linked to: " + import_picocolors2.default.cyan(`${session.scope.orgSlug} / ${session.scope.projectSlug} / ${session.scope.appSlug}`));
|
|
881
|
+
console.log(import_picocolors2.default.dim(` Token expires: ${new Date(session.expiresAt).toLocaleDateString()}`));
|
|
882
|
+
} catch (err) {
|
|
883
|
+
console.error(import_picocolors2.default.red("✗ Authentication failed:"), err instanceof Error ? err.message : err);
|
|
884
|
+
process.exit(1);
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
authCmd.command("logout").description("Sign out and revoke CLI token").action(async () => {
|
|
888
|
+
const session = getActiveSession();
|
|
889
|
+
if (!session) {
|
|
890
|
+
console.log(import_picocolors2.default.yellow("Not currently authenticated."));
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
try {
|
|
894
|
+
const client = new AuthClient(session);
|
|
895
|
+
await client.del("/api/cli/token");
|
|
896
|
+
} catch {}
|
|
897
|
+
clearSession(session.webappUrl);
|
|
898
|
+
console.log(import_picocolors2.default.green("✓") + " Signed out successfully.");
|
|
899
|
+
});
|
|
900
|
+
authCmd.command("status").description("Show current authentication status").action(async () => {
|
|
901
|
+
const session = getActiveSession();
|
|
902
|
+
if (!session) {
|
|
903
|
+
console.log(import_picocolors2.default.yellow("Not authenticated."));
|
|
904
|
+
console.log(import_picocolors2.default.dim(`Run ${import_picocolors2.default.cyan(`${program.name()} auth login`)} to authenticate.`));
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
const expired = new Date(session.expiresAt).getTime() < Date.now();
|
|
908
|
+
console.log(import_picocolors2.default.bold(`Authentication Status
|
|
909
|
+
`));
|
|
910
|
+
console.log(` User: ${session.user.email} (${session.user.name})`);
|
|
911
|
+
console.log(` Org: ${session.scope.orgSlug}`);
|
|
912
|
+
console.log(` Project: ${session.scope.projectSlug}`);
|
|
913
|
+
console.log(` App: ${session.scope.appSlug}`);
|
|
914
|
+
console.log(` Webapp: ${session.webappUrl}`);
|
|
915
|
+
console.log(` Expires: ${new Date(session.expiresAt).toLocaleString()} ${expired ? import_picocolors2.default.red("(expired)") : import_picocolors2.default.green("(valid)")}`);
|
|
916
|
+
if (expired) {
|
|
917
|
+
console.log("");
|
|
918
|
+
console.log(import_picocolors2.default.dim(`Token expired. Run ${import_picocolors2.default.cyan(`${program.name()} auth login`)} to re-authenticate.`));
|
|
919
|
+
}
|
|
920
|
+
});
|
|
921
|
+
authCmd.command("switch").description("Switch scope (re-authenticate with different org/project/app)").option("--url <url>", "Webapp URL", defaultUrl).action(async (cmdOpts) => {
|
|
922
|
+
console.log(import_picocolors2.default.bold(`Switching scope...
|
|
923
|
+
`));
|
|
924
|
+
console.log(import_picocolors2.default.dim(`This will open a browser to select a new org/project/app.
|
|
925
|
+
`));
|
|
926
|
+
try {
|
|
927
|
+
const session = await login({ webappUrl: cmdOpts.url });
|
|
928
|
+
console.log("");
|
|
929
|
+
console.log(import_picocolors2.default.green("✓") + " Switched to: " + import_picocolors2.default.cyan(`${session.scope.orgSlug} / ${session.scope.projectSlug} / ${session.scope.appSlug}`));
|
|
930
|
+
} catch (err) {
|
|
931
|
+
console.error(import_picocolors2.default.red("✗ Switch failed:"), err instanceof Error ? err.message : err);
|
|
932
|
+
process.exit(1);
|
|
933
|
+
}
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
export { loadStore, getActiveSession, saveSession, clearSession, login, AuthClient, registerAuthCommands };
|