dsh-agy 0.2.2 → 0.2.4
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 +41 -4
- package/lib/{session-KoIh3xdB.mjs → accounts-DbTTxX_z.mjs} +1125 -546
- package/lib/accounts-DbTTxX_z.mjs.map +1 -0
- package/lib/{models-1rf_Wfo2.mjs → catalog-iuikPZIb.mjs} +29 -81
- package/lib/catalog-iuikPZIb.mjs.map +1 -0
- package/lib/cli/index.d.mts.map +1 -1
- package/lib/cli/index.mjs +149 -12
- package/lib/cli/index.mjs.map +1 -1
- package/lib/{exchange-DBRzR2M-.mjs → exchange-BvrLu2E4.mjs} +8 -5
- package/lib/exchange-BvrLu2E4.mjs.map +1 -0
- package/lib/{import-C4CRBTbi.mjs → import-DSvpbB0T.mjs} +30 -8
- package/lib/import-DSvpbB0T.mjs.map +1 -0
- package/lib/index.d.mts.map +1 -1
- package/lib/index.mjs +6 -1
- package/lib/index.mjs.map +1 -1
- package/lib/models-DRWVi33p.mjs +136 -0
- package/lib/models-DRWVi33p.mjs.map +1 -0
- package/lib/{plugin-common-CQoDUeqE.mjs → plugin-common-D7lc-W6b.mjs} +79 -11
- package/lib/plugin-common-D7lc-W6b.mjs.map +1 -0
- package/lib/proxy-DQPIwUov.mjs +269 -0
- package/lib/proxy-DQPIwUov.mjs.map +1 -0
- package/lib/{translate-BQuSQW35.mjs → translate-COjBTH4-.mjs} +27 -6
- package/lib/translate-COjBTH4-.mjs.map +1 -0
- package/lib/web/plugin.d.mts.map +1 -1
- package/lib/web/plugin.mjs +202 -8
- package/lib/web/plugin.mjs.map +1 -1
- package/package.json +4 -1
- package/lib/exchange-DBRzR2M-.mjs.map +0 -1
- package/lib/import-C4CRBTbi.mjs.map +0 -1
- package/lib/models-1rf_Wfo2.mjs.map +0 -1
- package/lib/plugin-common-CQoDUeqE.mjs.map +0 -1
- package/lib/session-KoIh3xdB.mjs.map +0 -1
- package/lib/translate-BQuSQW35.mjs.map +0 -1
package/lib/cli/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { c as
|
|
2
|
-
import { t as
|
|
3
|
-
import {
|
|
1
|
+
import { a as deriveKey, c as resolveDshHome, d as isAgyDisabled, i as createAesGcmCodec, l as resolveMasterKeyCodec, n as maskProxyUrl, o as loadMasterKey, t as JsonAccountStore, u as AgySessionManager, y as AGY_DEFAULT_REDIRECT_URI } from "../accounts-DbTTxX_z.mjs";
|
|
2
|
+
import { r as normalizeProxyUrl, t as isProxyReachable } from "../proxy-DQPIwUov.mjs";
|
|
3
|
+
import { t as exchangeAntigravity } from "../exchange-BvrLu2E4.mjs";
|
|
4
|
+
import { n as upsertImportedAccount, r as authorizeAntigravity, t as importManySources } from "../import-DSvpbB0T.mjs";
|
|
4
5
|
import { r as encodeCredentialBlob } from "../blob-D1e7_uT1.mjs";
|
|
5
6
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
6
7
|
import { join } from "node:path";
|
|
@@ -129,6 +130,16 @@ function createReadOnlyStoreOrExit() {
|
|
|
129
130
|
process.exit(1);
|
|
130
131
|
}
|
|
131
132
|
}
|
|
133
|
+
function resolveProxyOption(raw) {
|
|
134
|
+
if (raw === void 0) return void 0;
|
|
135
|
+
if (!raw.trim()) return "";
|
|
136
|
+
try {
|
|
137
|
+
return normalizeProxyUrl(raw);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error(`Invalid proxy URL: ${error instanceof Error ? error.message : String(error)}`);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
132
143
|
async function ask(question) {
|
|
133
144
|
const rl = createInterface({
|
|
134
145
|
input: stdin,
|
|
@@ -145,6 +156,8 @@ function isRemoteSession() {
|
|
|
145
156
|
return !!(process.env.SSH_CONNECTION || process.env.SSH_CLIENT || process.env.SSH_TTY);
|
|
146
157
|
}
|
|
147
158
|
async function loginCommand(options) {
|
|
159
|
+
const proxyInput = resolveProxyOption(options.proxy);
|
|
160
|
+
const normalizedProxy = proxyInput === "" ? "" : proxyInput;
|
|
148
161
|
const store = createStore();
|
|
149
162
|
const redirectUri = `http://localhost:${options.port}/oauth-callback`;
|
|
150
163
|
if (!options.headless && isRemoteSession()) {
|
|
@@ -195,6 +208,7 @@ async function loginCommand(options) {
|
|
|
195
208
|
process.exit(1);
|
|
196
209
|
}
|
|
197
210
|
if (options.blob) {
|
|
211
|
+
if (normalizedProxy !== void 0) console.log("(Note: --proxy is ignored with --blob; proxy is only stored with the account)");
|
|
198
212
|
const blob = encodeCredentialBlob("agy", {
|
|
199
213
|
access_token: result.access,
|
|
200
214
|
refresh_token: result.refresh.split("|")[0],
|
|
@@ -202,6 +216,7 @@ async function loginCommand(options) {
|
|
|
202
216
|
});
|
|
203
217
|
console.log(`\nPaste this blob into the remote dashboard/CLI:\n\n${blob}\n`);
|
|
204
218
|
} else {
|
|
219
|
+
const proxyForUpsert = normalizedProxy !== void 0 ? normalizedProxy : void 0;
|
|
205
220
|
const { account, created } = await upsertImportedAccount(store, {
|
|
206
221
|
accessToken: result.access,
|
|
207
222
|
refreshToken: result.refresh.split("|")[0],
|
|
@@ -209,9 +224,14 @@ async function loginCommand(options) {
|
|
|
209
224
|
expiresAt: new Date(result.expires).toISOString(),
|
|
210
225
|
authMethod: "oauth",
|
|
211
226
|
email: result.email ?? null,
|
|
212
|
-
projectId: result.projectId || null
|
|
213
|
-
|
|
214
|
-
|
|
227
|
+
projectId: result.projectId || null,
|
|
228
|
+
clientId: result.clientId || null
|
|
229
|
+
}, {
|
|
230
|
+
overwriteExisting: true,
|
|
231
|
+
...proxyForUpsert !== void 0 ? { proxy: proxyForUpsert } : {}
|
|
232
|
+
});
|
|
233
|
+
const masked = maskProxyUrl(account.proxy);
|
|
234
|
+
console.log(`${created ? "Added" : "Updated"} account: ${account.email ?? "(no email)"} (project: ${result.projectId || "default"})${masked ? ` proxy: ${masked}` : ""}`);
|
|
215
235
|
}
|
|
216
236
|
await callback?.close();
|
|
217
237
|
}
|
|
@@ -227,10 +247,11 @@ async function statusCommand() {
|
|
|
227
247
|
for (const [index, account] of storage.accounts.entries()) {
|
|
228
248
|
const marker = index === storage.activeIndex ? "★" : " ";
|
|
229
249
|
const state = account.enabled === false ? "disabled" : account.verificationRequired ? "verification-required" : account.coolingDownUntil && account.coolingDownUntil > Date.now() ? "cooling" : "active";
|
|
230
|
-
|
|
250
|
+
const maskedProxy = maskProxyUrl(account.proxy);
|
|
251
|
+
console.log(` ${marker} [${index}] ${account.email ?? "(no email)"} — ${state}${account.projectId ? ` (project: ${account.projectId})` : ""}${maskedProxy ? ` proxy: ${maskedProxy}` : ""}`);
|
|
231
252
|
const session = await sessions.getSession().catch(() => void 0);
|
|
232
253
|
if (session && session.index === index) try {
|
|
233
|
-
const { fetchAvailableModels } = await import("../models-
|
|
254
|
+
const { fetchAvailableModels } = await import("../models-DRWVi33p.mjs").then((n) => n.r);
|
|
234
255
|
const discovered = await fetchAvailableModels(session.auth.access, session.account.projectId);
|
|
235
256
|
const entries = Object.entries(discovered.models ?? {});
|
|
236
257
|
if (entries.length > 0) {
|
|
@@ -250,6 +271,7 @@ async function statusCommand() {
|
|
|
250
271
|
}
|
|
251
272
|
async function importCommand(options) {
|
|
252
273
|
const store = createStore();
|
|
274
|
+
const proxyForUpsert = resolveProxyOption(options.proxy);
|
|
253
275
|
let items;
|
|
254
276
|
if (options.files && options.files.length > 0) items = options.files.map((file) => {
|
|
255
277
|
const raw = readFileSync(file, "utf8");
|
|
@@ -262,10 +284,12 @@ async function importCommand(options) {
|
|
|
262
284
|
source: await ask("Paste the agy token JSON (or blob with --blob): "),
|
|
263
285
|
kind: options.blob ? "blob" : "json"
|
|
264
286
|
}];
|
|
265
|
-
const
|
|
287
|
+
const importOpts = {
|
|
266
288
|
email: options.email,
|
|
267
289
|
overwriteExisting: options.overwrite
|
|
268
|
-
}
|
|
290
|
+
};
|
|
291
|
+
if (proxyForUpsert !== void 0) importOpts.proxy = proxyForUpsert;
|
|
292
|
+
const result = await importManySources(items, store, importOpts);
|
|
269
293
|
console.log(`Imported ${result.imported}, replaced ${result.replaced}${result.errors.length > 0 ? `, ${result.errors.length} failed` : ""}`);
|
|
270
294
|
for (const error of result.errors) console.log(` ! ${error}`);
|
|
271
295
|
}
|
|
@@ -311,6 +335,29 @@ async function verifyCommand(options) {
|
|
|
311
335
|
else console.log(`[${index}] ${account.email ?? ""} — FAILED: ${result.error}`);
|
|
312
336
|
}
|
|
313
337
|
}
|
|
338
|
+
async function healthCommand(options) {
|
|
339
|
+
const store = createReadOnlyStoreOrExit();
|
|
340
|
+
const sessions = new AgySessionManager({
|
|
341
|
+
store,
|
|
342
|
+
onHealthReport: (results) => {
|
|
343
|
+
for (const result of results) if (result.ok) console.log(`[${result.index}] ${result.email ?? ""} — OK`);
|
|
344
|
+
else console.log(`[${result.index}] ${result.email ?? ""} — FAILED: ${result.error}`);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
const indices = options.index?.map((value) => Number(value));
|
|
348
|
+
if (options.interval) {
|
|
349
|
+
const intervalMs = Number(options.interval);
|
|
350
|
+
if (!Number.isFinite(intervalMs) || intervalMs <= 0) {
|
|
351
|
+
console.error("Error: --interval must be a positive number of milliseconds.");
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
354
|
+
console.log(`Health probe every ${intervalMs}ms (Ctrl+C to stop).`);
|
|
355
|
+
sessions.startHealthProbe(intervalMs, { unref: false });
|
|
356
|
+
await new Promise(() => {});
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if ((await sessions.checkAccounts(indices)).length === 0) console.log("No enabled accounts — run `dsh-agy login` first.");
|
|
360
|
+
}
|
|
314
361
|
async function logoutCommand(options) {
|
|
315
362
|
await createReadOnlyStoreOrExit().mutate((storage) => {
|
|
316
363
|
const index = options.index !== void 0 ? Number(options.index) : options.email ? storage.accounts.findIndex((a) => a.email?.toLowerCase() === options.email.toLowerCase()) : storage.activeIndex;
|
|
@@ -320,10 +367,84 @@ async function logoutCommand(options) {
|
|
|
320
367
|
console.log(`Removed account: ${removed?.email ?? `[${index}]`}`);
|
|
321
368
|
});
|
|
322
369
|
}
|
|
370
|
+
async function proxySetCommand(options) {
|
|
371
|
+
const idx = Number(options.index);
|
|
372
|
+
if (!Number.isInteger(idx) || idx < 0) {
|
|
373
|
+
console.error("Invalid --index (must be >= 0)");
|
|
374
|
+
process.exit(1);
|
|
375
|
+
}
|
|
376
|
+
const raw = options.proxy ?? "";
|
|
377
|
+
if (!raw.trim()) {
|
|
378
|
+
console.error("Missing --proxy value");
|
|
379
|
+
process.exit(1);
|
|
380
|
+
}
|
|
381
|
+
let normalized;
|
|
382
|
+
try {
|
|
383
|
+
normalized = normalizeProxyUrl(raw);
|
|
384
|
+
} catch (error) {
|
|
385
|
+
console.error(`Invalid proxy URL: ${error instanceof Error ? error.message : String(error)}`);
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
await createStore().mutate((storage) => {
|
|
389
|
+
const account = storage.accounts[idx];
|
|
390
|
+
if (!account) throw new Error(`account not found (index ${idx})`);
|
|
391
|
+
account.proxy = normalized;
|
|
392
|
+
});
|
|
393
|
+
console.log(`[${idx}] proxy set to ${maskProxyUrl(normalized)}`);
|
|
394
|
+
}
|
|
395
|
+
async function proxyClearCommand(options) {
|
|
396
|
+
const idx = Number(options.index);
|
|
397
|
+
if (!Number.isInteger(idx) || idx < 0) {
|
|
398
|
+
console.error("Invalid --index (must be >= 0)");
|
|
399
|
+
process.exit(1);
|
|
400
|
+
}
|
|
401
|
+
await createStore().mutate((storage) => {
|
|
402
|
+
const account = storage.accounts[idx];
|
|
403
|
+
if (!account) throw new Error(`account not found (index ${idx})`);
|
|
404
|
+
delete account.proxy;
|
|
405
|
+
});
|
|
406
|
+
console.log(`[${idx}] proxy cleared`);
|
|
407
|
+
}
|
|
408
|
+
async function proxyTestCommand(options) {
|
|
409
|
+
const idx = Number(options.index);
|
|
410
|
+
if (!Number.isInteger(idx) || idx < 0) {
|
|
411
|
+
console.error("Invalid --index (must be >= 0)");
|
|
412
|
+
process.exit(1);
|
|
413
|
+
}
|
|
414
|
+
const account = (await createReadOnlyStoreOrExit().load()).accounts[idx];
|
|
415
|
+
if (!account) {
|
|
416
|
+
console.error(`account not found (index ${idx})`);
|
|
417
|
+
process.exit(1);
|
|
418
|
+
}
|
|
419
|
+
if (!account.proxy) {
|
|
420
|
+
console.log(`[${idx}] no proxy configured`);
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
const masked = maskProxyUrl(account.proxy);
|
|
424
|
+
if (await isProxyReachable(account.proxy)) console.log(`[${idx}] ${masked} — ok`);
|
|
425
|
+
else console.log(`[${idx}] ${masked} — proxy_unreachable`);
|
|
426
|
+
}
|
|
427
|
+
async function proxyListCommand() {
|
|
428
|
+
const storage = await createReadOnlyStoreOrExit().load();
|
|
429
|
+
if (storage.accounts.length === 0) {
|
|
430
|
+
console.log("No agy accounts.");
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
for (const [index, account] of storage.accounts.entries()) {
|
|
434
|
+
const masked = maskProxyUrl(account.proxy);
|
|
435
|
+
console.log(` [${index}] ${account.email ?? "(no email)"} — ${masked ?? "(no proxy)"}`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
323
438
|
function createProgram() {
|
|
324
439
|
const program = new Command();
|
|
325
440
|
program.name("dsh-agy").description("Google Antigravity (agy) account management for DeepSeek Harness").version(PACKAGE_VERSION);
|
|
326
|
-
program.
|
|
441
|
+
program.hook("preAction", () => {
|
|
442
|
+
if (isAgyDisabled()) {
|
|
443
|
+
console.error("dsh-agy is disabled (DSH_AGY_DISABLE=1).");
|
|
444
|
+
process.exit(1);
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
program.command("login").description("OAuth login (browser, or headless paste)").option("--headless", "print the URL and wait for a pasted redirect URL", false).option("--blob", "print a paste-credential blob instead of storing the account", false).option("--port <n>", "loopback callback port", "51121").option("--project <id>", "bind the login to a specific project").option("--timeout <ms>", "callback timeout", "300000").option("--proxy <url>", "per-account proxy URL (http/https/socks5); empty to clear").action(async (options) => {
|
|
327
448
|
await loginCommand({
|
|
328
449
|
...options,
|
|
329
450
|
timeout: options.timeout
|
|
@@ -332,7 +453,7 @@ function createProgram() {
|
|
|
332
453
|
program.command("status").description("List accounts and their health").action(async () => {
|
|
333
454
|
await statusCommand();
|
|
334
455
|
});
|
|
335
|
-
program.command("import").description("Import agy token files or paste credentials").argument("[files...]", "paths to agy auth.json token files (multiple allowed)").option("--blob", "the pasted value is a credential blob", false).option("--email <email>", "account email (skips userinfo verification)").option("--overwrite", "replace an existing account with the same email", false).action(async (files, options) => {
|
|
456
|
+
program.command("import").description("Import agy token files or paste credentials").argument("[files...]", "paths to agy auth.json token files (multiple allowed)").option("--blob", "the pasted value is a credential blob", false).option("--email <email>", "account email (skips userinfo verification)").option("--overwrite", "replace an existing account with the same email", false).option("--proxy <url>", "per-account proxy URL (http/https/socks5); empty to clear").action(async (files, options) => {
|
|
336
457
|
await importCommand({
|
|
337
458
|
...options,
|
|
338
459
|
files
|
|
@@ -344,9 +465,25 @@ function createProgram() {
|
|
|
344
465
|
program.command("verify").description("Verify account credentials (refresh + userinfo)").option("--index <n>", "verify one account by index; default all").action(async (options) => {
|
|
345
466
|
await verifyCommand(options);
|
|
346
467
|
});
|
|
468
|
+
program.command("health").description("Check every enabled account (refresh + userinfo), optionally on an interval").option("--interval <ms>", "repeat on an interval instead of once").option("--index <n...>", "check only these accounts").action(async (options) => {
|
|
469
|
+
await healthCommand(options);
|
|
470
|
+
});
|
|
347
471
|
program.command("logout").description("Remove an account").option("--index <n>", "account index (default: active)").option("--email <email>", "account email").action(async (options) => {
|
|
348
472
|
await logoutCommand(options);
|
|
349
473
|
});
|
|
474
|
+
const proxy = program.command("proxy").description("Manage per-account proxies");
|
|
475
|
+
proxy.command("set").description("Set proxy for an account").requiredOption("--index <n>", "account index").requiredOption("--proxy <url>", "proxy URL (http/https/socks5)").action(async (options) => {
|
|
476
|
+
await proxySetCommand(options);
|
|
477
|
+
});
|
|
478
|
+
proxy.command("clear").description("Clear proxy for an account").requiredOption("--index <n>", "account index").action(async (options) => {
|
|
479
|
+
await proxyClearCommand(options);
|
|
480
|
+
});
|
|
481
|
+
proxy.command("test").description("Test proxy reachability for an account (TCP 2s fast-fail)").requiredOption("--index <n>", "account index").action(async (options) => {
|
|
482
|
+
await proxyTestCommand(options);
|
|
483
|
+
});
|
|
484
|
+
proxy.command("list").description("List accounts with masked proxies").action(async () => {
|
|
485
|
+
await proxyListCommand();
|
|
486
|
+
});
|
|
350
487
|
return program;
|
|
351
488
|
}
|
|
352
489
|
//#endregion
|
package/lib/cli/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/cli/callback-server.ts","../../src/cli/index.ts"],"sourcesContent":["/** Loopback OAuth callback server for the standalone CLI login flow. */\n\nimport { createServer } from 'node:http'\nimport type { Server } from 'node:http'\n\nexport interface CallbackResult {\n code: string\n state: string\n url: string\n}\n\nexport interface CallbackHandle {\n result: Promise<CallbackResult>\n /** Resolves once the loopback listener is bound; rejects on bind errors. */\n ready: Promise<void>\n close(): Promise<void>\n}\n\nconst SUCCESS_HTML = `<!doctype html><html><head><meta charset=\"utf-8\"><title>Login successful</title></head>\n<body style=\"font-family:system-ui;text-align:center;padding:3rem\">\n<h2>✓ Authentication successful</h2>\n<p>You can close this tab and return to the terminal.</p>\n</body></html>`\n\n/** Listen on 127.0.0.1:<port>/oauth-callback and resolve with the code+state. */\nexport function startCallbackServer(options: { port?: number; timeoutMs?: number } = {}): CallbackHandle {\n const port = options.port ?? 51121\n const timeoutMs = options.timeoutMs ?? 300_000\n\n let resolveResult: (value: CallbackResult) => void\n let rejectResult: (reason: Error) => void\n const result = new Promise<CallbackResult>((resolve, reject) => {\n resolveResult = resolve\n rejectResult = reject\n })\n\n let resolveReady: () => void\n let rejectReady: (reason: Error) => void\n const ready = new Promise<void>((resolve, reject) => {\n resolveReady = resolve\n rejectReady = reject\n })\n\n const server: Server = createServer((req, res) => {\n const url = new URL(req.url ?? '/', 'http://localhost')\n if (url.pathname === '/oauth-callback') {\n const code = url.searchParams.get('code')\n const state = url.searchParams.get('state')\n if (!code || !state) {\n res.writeHead(400, { 'Content-Type': 'text/html' })\n res.end('<h2>Missing code or state</h2>')\n return\n }\n res.writeHead(200, { 'Content-Type': 'text/html', connection: 'close' })\n res.end(SUCCESS_HTML)\n resolveResult({ code, state, url: url.toString() })\n // Give the browser a moment to render before the server closes.\n setTimeout(() => server.close(), 1500)\n return\n }\n res.writeHead(404)\n res.end('Not found')\n })\n\n server.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code === 'EADDRINUSE') {\n rejectResult(new Error(`Port ${port} is already in use — pass --port to pick another.`))\n } else {\n rejectResult(error)\n }\n rejectReady(error)\n })\n\n server.listen(port, '127.0.0.1', () => {\n resolveReady()\n })\n\n const timeout = setTimeout(() => {\n rejectResult(new Error('Authentication timed out — no callback received.'))\n server.close()\n }, timeoutMs)\n\n const handle: CallbackHandle = {\n result,\n ready,\n async close() {\n clearTimeout(timeout)\n if (server.listening) {\n // The success page's keep-alive connection would otherwise hold\n // server.close() (and the CLI process) open indefinitely.\n server.closeAllConnections()\n await new Promise<void>((resolve) => server.close(() => resolve()))\n }\n },\n }\n return handle\n}\n\n/** Open a URL in the system browser (best effort). */\nexport async function openBrowser(url: string): Promise<boolean> {\n const { spawn } = await import('node:child_process')\n const platform = process.platform\n const command = platform === 'darwin' ? ['open', url]\n : platform === 'win32' ? ['cmd', '/c', 'start', '', url]\n : ['xdg-open', url]\n return new Promise<boolean>((resolve) => {\n const child = spawn(command[0]!, command.slice(1), { stdio: 'ignore' })\n child.on('error', () => resolve(false))\n child.on('spawn', () => resolve(true))\n })\n}\n","/**\n * dsh-agy CLI: login / status / import / verify / logout.\n * Standalone bin — the dsh launcher has no plugin subcommand slot, and the\n * remote paste-blob flow must run on machines without a harness.\n */\n\nimport { Command } from 'commander'\nimport { readFileSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { createInterface } from 'node:readline/promises'\nimport { stdin as input, stdout as output } from 'node:process'\nimport { authorizeAntigravity } from '../oauth/authorize.ts'\nimport { exchangeAntigravity } from '../oauth/exchange.ts'\nimport { encodeCredentialBlob } from '../oauth/blob.ts'\nimport { AGY_DEFAULT_REDIRECT_URI } from '../oauth/constants.ts'\nimport { createAesGcmCodec, deriveKey, loadMasterKey, resolveDshHome, resolveMasterKeyCodec } from '../store/keyring.ts'\nimport type { SecretCodec } from '../store/keyring.ts'\nimport { JsonAccountStore } from '../store/accounts.ts'\nimport { AgySessionManager } from '../session.ts'\nimport { startCallbackServer, openBrowser } from './callback-server.ts'\nimport { importManySources, upsertImportedAccount } from './import.ts'\n\n/** Package version, read from the shipped package.json — never hard-coded twice. */\nconst { version: PACKAGE_VERSION } = JSON.parse(\n readFileSync(new URL('../../package.json', import.meta.url), 'utf8'),\n) as { version: string }\n\nfunction createStore(options: { readOnly?: boolean } = {}): JsonAccountStore {\n const dshHome = resolveDshHome()\n let codec: SecretCodec\n if (options.readOnly) {\n // Read-only commands must never create the master key or the credentials\n // document: they would fail for no reason on a read-only HOME and write\n // files nobody asked for anywhere else.\n const masterKey = loadMasterKey(dshHome)\n if (!masterKey) {\n throw new Error('No agy account store found — run `dsh-agy login` first.')\n }\n codec = createAesGcmCodec(deriveKey(masterKey))\n } else {\n codec = resolveMasterKeyCodec(dshHome).codec\n }\n return new JsonAccountStore({ file: `${dshHome}/agy-accounts.json`, codec })\n}\n\n/** Read-only store with a friendly error when no credentials exist yet. */\nfunction createReadOnlyStoreOrExit(): JsonAccountStore {\n try {\n return createStore({ readOnly: true })\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error))\n process.exit(1)\n }\n}\n\nasync function ask(question: string): Promise<string> {\n const rl = createInterface({ input, output })\n try {\n return (await rl.question(question)).trim()\n } finally {\n rl.close()\n }\n}\n\n/** Remote/SSH sessions cannot reach a local browser; auto-select headless paste. */\nfunction isRemoteSession(): boolean {\n return !!(process.env.SSH_CONNECTION || process.env.SSH_CLIENT || process.env.SSH_TTY)\n}\n\nasync function loginCommand(options: { headless: boolean; blob: boolean; port: number; project?: string; timeout?: string }) {\n const store = createStore()\n const redirectUri = `http://localhost:${options.port}/oauth-callback`\n if (!options.headless && isRemoteSession()) {\n console.log('(SSH session detected — using headless paste flow; pass --headless explicitly to force)')\n options.headless = true\n }\n\n let callback: Awaited<ReturnType<typeof startCallbackServer>> | undefined\n if (!options.headless) {\n // Bind the loopback listener BEFORE the URL is shown: a local process\n // squatting on the fixed port could otherwise capture code+state (the\n // state carries the PKCE verifier) and steal the refresh token.\n callback = startCallbackServer({ port: options.port, timeoutMs: Number(options.timeout) || 300_000 })\n try {\n await callback.ready\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error))\n process.exit(1)\n }\n }\n\n const { url, verifier } = await authorizeAntigravity(redirectUri, options.project ?? '')\n console.log(`\\nOpen this URL in a browser to authorize:\\n\\n ${url}\\n`)\n\n let code: string\n let state: string\n\n if (options.headless) {\n const pasted = await ask('After approving, paste the full redirected URL here: ')\n const parsed = new URL(pasted)\n code = parsed.searchParams.get('code') ?? ''\n state = parsed.searchParams.get('state') ?? ''\n if (!code || !state) {\n console.error('Error: pasted URL is missing code or state.')\n process.exit(1)\n }\n } else {\n const opened = await openBrowser(url)\n if (!opened) console.log('(Could not open a browser automatically — open the URL manually.)')\n let callbackResult: { code: string; state: string; url: string }\n try {\n callbackResult = await callback!.result\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error))\n process.exit(1)\n }\n code = callbackResult.code\n state = callbackResult.state\n }\n\n // Bind the exchange to the verifier we issued: a state from any other login\n // (pasted from another session, or fabricated) must be rejected.\n const result = await exchangeAntigravity(code, state, redirectUri, verifier)\n if (result.type === 'failed') {\n console.error(`Login failed: ${result.error}`)\n process.exit(1)\n }\n\n if (options.blob) {\n const blob = encodeCredentialBlob('agy', {\n access_token: result.access,\n refresh_token: result.refresh.split('|')[0],\n expires_in: Math.max(0, Math.round((result.expires - Date.now()) / 1000)),\n })\n console.log(`\\nPaste this blob into the remote dashboard/CLI:\\n\\n${blob}\\n`)\n } else {\n const { account, created } = await upsertImportedAccount(store, {\n accessToken: result.access,\n refreshToken: result.refresh.split('|')[0]!,\n tokenType: 'Bearer',\n expiresAt: new Date(result.expires).toISOString(),\n authMethod: 'oauth',\n email: result.email ?? null,\n projectId: result.projectId || null,\n }, { overwriteExisting: true })\n console.log(`${created ? 'Added' : 'Updated'} account: ${account.email ?? '(no email)'} (project: ${result.projectId || 'default'})`)\n }\n\n await callback?.close()\n}\n\nasync function statusCommand() {\n const store = createReadOnlyStoreOrExit()\n const storage = await store.load()\n if (storage.accounts.length === 0) {\n console.log('No agy accounts. Run `dsh-agy login` first.')\n return\n }\n console.log(`\\n${storage.accounts.length} account(s), active index ${storage.activeIndex}:\\n`)\n const sessions = new AgySessionManager({ store })\n for (const [index, account] of storage.accounts.entries()) {\n const marker = index === storage.activeIndex ? '★' : ' '\n const state = account.enabled === false ? 'disabled'\n : account.verificationRequired ? 'verification-required'\n : account.coolingDownUntil && account.coolingDownUntil > Date.now() ? 'cooling'\n : 'active'\n console.log(` ${marker} [${index}] ${account.email ?? '(no email)'} — ${state}${account.projectId ? ` (project: ${account.projectId})` : ''}`)\n\n // Best-effort quota summary via fetchAvailableModels (fresh access token).\n const session = await sessions.getSession().catch(() => undefined)\n if (session && session.index === index) {\n try {\n const { fetchAvailableModels } = await import('../adapter/models.ts')\n const discovered = await fetchAvailableModels(session.auth.access, session.account.projectId)\n const entries = Object.entries(discovered.models ?? {})\n if (entries.length > 0) {\n const withQuota = entries\n .map(([id, entry]) => ({ id, ...entry.quotaInfo }))\n .filter((e) => typeof e.remainingFraction === 'number')\n .sort((a, b) => (a.remainingFraction ?? 0) - (b.remainingFraction ?? 0))\n if (withQuota.length > 0) {\n const lowest = withQuota[0]!\n console.log(` models: ${entries.length}, lowest quota: ${Math.round((lowest.remainingFraction ?? 0) * 100)}% (${lowest.id})${lowest.resetTime ? `, resets ${new Date(lowest.resetTime).toISOString()}` : ''}`)\n } else {\n console.log(` models: ${entries.length} (no per-model quota reported)`)\n }\n }\n } catch (error) {\n console.log(` quota: unavailable (${error instanceof Error ? error.message : String(error)})`)\n }\n }\n }\n}\n\nasync function importCommand(options: { blob: boolean; files?: string[]; email?: string; overwrite: boolean }) {\n const store = createStore()\n let items: Array<{ source: unknown; kind: 'json' | 'blob' }>\n if (options.files && options.files.length > 0) {\n items = options.files.map((file) => {\n const raw = readFileSync(file, 'utf8')\n return { source: options.blob ? raw : (JSON.parse(raw) as unknown), kind: options.blob ? 'blob' : 'json' }\n })\n } else {\n const pasted = await ask('Paste the agy token JSON (or blob with --blob): ')\n items = [{ source: pasted, kind: options.blob ? 'blob' : 'json' }]\n }\n const result = await importManySources(items, store, {\n email: options.email,\n overwriteExisting: options.overwrite,\n })\n console.log(`Imported ${result.imported}, replaced ${result.replaced}${result.errors.length > 0 ? `, ${result.errors.length} failed` : ''}`)\n for (const error of result.errors) console.log(` ! ${error}`)\n}\n\nasync function exportCommand(options: { index?: string; out?: string }) {\n const store = createReadOnlyStoreOrExit()\n const sessions = new AgySessionManager({ store })\n const storage = await store.load()\n const indices = options.index !== undefined\n ? [Number(options.index)]\n : storage.accounts.map((_, i) => i)\n\n let exported = 0\n for (const index of indices) {\n const account = storage.accounts[index]\n if (!account) {\n console.log(`[${index}] not found`)\n continue\n }\n const result = await sessions.exportBlob(index)\n if (!result.blob) {\n console.log(`[${index}] ${account.email ?? ''} — FAILED: ${result.error}`)\n continue\n }\n if (options.out) {\n const file = join(options.out, `dsh-agy-${index}.blob`)\n writeFileSync(file, result.blob + '\\n')\n console.log(`[${index}] ${account.email ?? ''} — wrote ${file}`)\n } else {\n console.log(result.blob)\n }\n exported++\n }\n if (!options.out) console.log(`\\n${exported} blob(s) exported — one line each, paste into a remote import`)\n}\n\nasync function verifyCommand(options: { index?: string }) {\n const store = createReadOnlyStoreOrExit()\n const sessions = new AgySessionManager({ store })\n const storage = await store.load()\n const indices = options.index !== undefined\n ? [Number(options.index)]\n : storage.accounts.map((_, i) => i)\n\n for (const index of indices) {\n const account = storage.accounts[index]\n if (!account) {\n console.log(`[${index}] not found`)\n continue\n }\n const result = await sessions.verifyAccount(index)\n if (result.ok) {\n console.log(`[${index}] ${account.email ?? ''} — OK${result.email && result.email !== account.email ? ` (userinfo: ${result.email})` : ''}`)\n } else {\n console.log(`[${index}] ${account.email ?? ''} — FAILED: ${result.error}`)\n }\n }\n}\n\nasync function logoutCommand(options: { index?: string; email?: string }) {\n const store = createReadOnlyStoreOrExit()\n await store.mutate((storage) => {\n const index = options.index !== undefined\n ? Number(options.index)\n : options.email\n ? storage.accounts.findIndex((a) => a.email?.toLowerCase() === options.email!.toLowerCase())\n : storage.activeIndex\n if (index < 0 || index >= storage.accounts.length) {\n throw new Error(`account not found (index ${index})`)\n }\n const [removed] = storage.accounts.splice(index, 1)\n if (storage.activeIndex >= storage.accounts.length) storage.activeIndex = 0\n console.log(`Removed account: ${removed?.email ?? `[${index}]`}`)\n })\n}\n\nexport function createProgram(): Command {\n const program = new Command()\n program\n .name('dsh-agy')\n .description('Google Antigravity (agy) account management for DeepSeek Harness')\n .version(PACKAGE_VERSION)\n\n program\n .command('login')\n .description('OAuth login (browser, or headless paste)')\n .option('--headless', 'print the URL and wait for a pasted redirect URL', false)\n .option('--blob', 'print a paste-credential blob instead of storing the account', false)\n .option('--port <n>', 'loopback callback port', '51121')\n .option('--project <id>', 'bind the login to a specific project')\n .option('--timeout <ms>', 'callback timeout', '300000')\n .action(async (options) => {\n await loginCommand({ ...options, timeout: options.timeout })\n })\n\n program\n .command('status')\n .description('List accounts and their health')\n .action(async () => {\n await statusCommand()\n })\n\n program\n .command('import')\n .description('Import agy token files or paste credentials')\n .argument('[files...]', 'paths to agy auth.json token files (multiple allowed)')\n .option('--blob', 'the pasted value is a credential blob', false)\n .option('--email <email>', 'account email (skips userinfo verification)')\n .option('--overwrite', 'replace an existing account with the same email', false)\n .action(async (files: string[] | undefined, options: { blob: boolean; email?: string; overwrite: boolean }) => {\n await importCommand({ ...options, files })\n })\n\n program\n .command('export')\n .description('Export account credentials as paste blobs')\n .option('--index <n>', 'export one account by index; default all')\n .option('--out <dir>', 'write one dsh-agy-<index>.blob file per account into this directory (default: print to stdout)')\n .action(async (options: { index?: string; out?: string }) => {\n await exportCommand(options)\n })\n\n program\n .command('verify')\n .description('Verify account credentials (refresh + userinfo)')\n .option('--index <n>', 'verify one account by index; default all')\n .action(async (options: { index?: string }) => {\n await verifyCommand(options)\n })\n\n program\n .command('logout')\n .description('Remove an account')\n .option('--index <n>', 'account index (default: active)')\n .option('--email <email>', 'account email')\n .action(async (options: { index?: string; email?: string }) => {\n await logoutCommand(options)\n })\n\n return program\n}\n\nexport { AGY_DEFAULT_REDIRECT_URI }\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/cli/callback-server.ts","../../src/cli/index.ts"],"sourcesContent":["/** Loopback OAuth callback server for the standalone CLI login flow. */\n\nimport { createServer } from 'node:http'\nimport type { Server } from 'node:http'\n\nexport interface CallbackResult {\n code: string\n state: string\n url: string\n}\n\nexport interface CallbackHandle {\n result: Promise<CallbackResult>\n /** Resolves once the loopback listener is bound; rejects on bind errors. */\n ready: Promise<void>\n close(): Promise<void>\n}\n\nconst SUCCESS_HTML = `<!doctype html><html><head><meta charset=\"utf-8\"><title>Login successful</title></head>\n<body style=\"font-family:system-ui;text-align:center;padding:3rem\">\n<h2>✓ Authentication successful</h2>\n<p>You can close this tab and return to the terminal.</p>\n</body></html>`\n\n/** Listen on 127.0.0.1:<port>/oauth-callback and resolve with the code+state. */\nexport function startCallbackServer(options: { port?: number; timeoutMs?: number } = {}): CallbackHandle {\n const port = options.port ?? 51121\n const timeoutMs = options.timeoutMs ?? 300_000\n\n let resolveResult: (value: CallbackResult) => void\n let rejectResult: (reason: Error) => void\n const result = new Promise<CallbackResult>((resolve, reject) => {\n resolveResult = resolve\n rejectResult = reject\n })\n\n let resolveReady: () => void\n let rejectReady: (reason: Error) => void\n const ready = new Promise<void>((resolve, reject) => {\n resolveReady = resolve\n rejectReady = reject\n })\n\n const server: Server = createServer((req, res) => {\n const url = new URL(req.url ?? '/', 'http://localhost')\n if (url.pathname === '/oauth-callback') {\n const code = url.searchParams.get('code')\n const state = url.searchParams.get('state')\n if (!code || !state) {\n res.writeHead(400, { 'Content-Type': 'text/html' })\n res.end('<h2>Missing code or state</h2>')\n return\n }\n res.writeHead(200, { 'Content-Type': 'text/html', connection: 'close' })\n res.end(SUCCESS_HTML)\n resolveResult({ code, state, url: url.toString() })\n // Give the browser a moment to render before the server closes.\n setTimeout(() => server.close(), 1500)\n return\n }\n res.writeHead(404)\n res.end('Not found')\n })\n\n server.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code === 'EADDRINUSE') {\n rejectResult(new Error(`Port ${port} is already in use — pass --port to pick another.`))\n } else {\n rejectResult(error)\n }\n rejectReady(error)\n })\n\n server.listen(port, '127.0.0.1', () => {\n resolveReady()\n })\n\n const timeout = setTimeout(() => {\n rejectResult(new Error('Authentication timed out — no callback received.'))\n server.close()\n }, timeoutMs)\n\n const handle: CallbackHandle = {\n result,\n ready,\n async close() {\n clearTimeout(timeout)\n if (server.listening) {\n // The success page's keep-alive connection would otherwise hold\n // server.close() (and the CLI process) open indefinitely.\n server.closeAllConnections()\n await new Promise<void>((resolve) => server.close(() => resolve()))\n }\n },\n }\n return handle\n}\n\n/** Open a URL in the system browser (best effort). */\nexport async function openBrowser(url: string): Promise<boolean> {\n const { spawn } = await import('node:child_process')\n const platform = process.platform\n const command = platform === 'darwin' ? ['open', url]\n : platform === 'win32' ? ['cmd', '/c', 'start', '', url]\n : ['xdg-open', url]\n return new Promise<boolean>((resolve) => {\n const child = spawn(command[0]!, command.slice(1), { stdio: 'ignore' })\n child.on('error', () => resolve(false))\n child.on('spawn', () => resolve(true))\n })\n}\n","/**\n * dsh-agy CLI: login / status / import / verify / logout.\n * Standalone bin — the dsh launcher has no plugin subcommand slot, and the\n * remote paste-blob flow must run on machines without a harness.\n */\n\nimport { Command } from 'commander'\nimport { readFileSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { createInterface } from 'node:readline/promises'\nimport { stdin as input, stdout as output } from 'node:process'\nimport { authorizeAntigravity } from '../oauth/authorize.ts'\nimport { exchangeAntigravity } from '../oauth/exchange.ts'\nimport { encodeCredentialBlob } from '../oauth/blob.ts'\nimport { AGY_DEFAULT_REDIRECT_URI } from '../oauth/constants.ts'\nimport { createAesGcmCodec, deriveKey, loadMasterKey, resolveDshHome, resolveMasterKeyCodec } from '../store/keyring.ts'\nimport type { SecretCodec } from '../store/keyring.ts'\nimport { JsonAccountStore, maskProxyUrl } from '../store/accounts.ts'\nimport { AgySessionManager } from '../session.ts'\nimport { isAgyDisabled } from '../runtime/risk.ts'\nimport { startCallbackServer, openBrowser } from './callback-server.ts'\nimport { importManySources, upsertImportedAccount } from './import.ts'\nimport { isProxyReachable, normalizeProxyUrl } from '../proxy.ts'\n\n/** Package version, read from the shipped package.json — never hard-coded twice. */\nconst { version: PACKAGE_VERSION } = JSON.parse(\n readFileSync(new URL('../../package.json', import.meta.url), 'utf8'),\n) as { version: string }\n\nfunction createStore(options: { readOnly?: boolean } = {}): JsonAccountStore {\n const dshHome = resolveDshHome()\n let codec: SecretCodec\n if (options.readOnly) {\n // Read-only commands must never create the master key or the credentials\n // document: they would fail for no reason on a read-only HOME and write\n // files nobody asked for anywhere else.\n const masterKey = loadMasterKey(dshHome)\n if (!masterKey) {\n throw new Error('No agy account store found — run `dsh-agy login` first.')\n }\n codec = createAesGcmCodec(deriveKey(masterKey))\n } else {\n codec = resolveMasterKeyCodec(dshHome).codec\n }\n return new JsonAccountStore({ file: `${dshHome}/agy-accounts.json`, codec })\n}\n\n/** Read-only store with a friendly error when no credentials exist yet. */\nfunction createReadOnlyStoreOrExit(): JsonAccountStore {\n try {\n return createStore({ readOnly: true })\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error))\n process.exit(1)\n }\n}\n\nfunction resolveProxyOption(raw: string | undefined): string | undefined {\n if (raw === undefined) return undefined\n if (!raw.trim()) return ''\n try {\n return normalizeProxyUrl(raw)\n } catch (error) {\n console.error(`Invalid proxy URL: ${error instanceof Error ? error.message : String(error)}`)\n process.exit(1)\n }\n}\n\nasync function ask(question: string): Promise<string> {\n const rl = createInterface({ input, output })\n try {\n return (await rl.question(question)).trim()\n } finally {\n rl.close()\n }\n}\n\n/** Remote/SSH sessions cannot reach a local browser; auto-select headless paste. */\nfunction isRemoteSession(): boolean {\n return !!(process.env.SSH_CONNECTION || process.env.SSH_CLIENT || process.env.SSH_TTY)\n}\n\nasync function loginCommand(options: { headless: boolean; blob: boolean; port: number; project?: string; timeout?: string; proxy?: string }) {\n const proxyInput = resolveProxyOption(options.proxy)\n // proxy \"\" sentinel means clear; normalizeProxyUrl already handled\n const normalizedProxy = proxyInput === '' ? '' : proxyInput\n const store = createStore()\n const redirectUri = `http://localhost:${options.port}/oauth-callback`\n if (!options.headless && isRemoteSession()) {\n console.log('(SSH session detected — using headless paste flow; pass --headless explicitly to force)')\n options.headless = true\n }\n\n let callback: Awaited<ReturnType<typeof startCallbackServer>> | undefined\n if (!options.headless) {\n // Bind the loopback listener BEFORE the URL is shown: a local process\n // squatting on the fixed port could otherwise capture code+state (the\n // state carries the PKCE verifier) and steal the refresh token.\n callback = startCallbackServer({ port: options.port, timeoutMs: Number(options.timeout) || 300_000 })\n try {\n await callback.ready\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error))\n process.exit(1)\n }\n }\n\n const { url, verifier } = await authorizeAntigravity(redirectUri, options.project ?? '')\n console.log(`\\nOpen this URL in a browser to authorize:\\n\\n ${url}\\n`)\n\n let code: string\n let state: string\n\n if (options.headless) {\n const pasted = await ask('After approving, paste the full redirected URL here: ')\n const parsed = new URL(pasted)\n code = parsed.searchParams.get('code') ?? ''\n state = parsed.searchParams.get('state') ?? ''\n if (!code || !state) {\n console.error('Error: pasted URL is missing code or state.')\n process.exit(1)\n }\n } else {\n const opened = await openBrowser(url)\n if (!opened) console.log('(Could not open a browser automatically — open the URL manually.)')\n let callbackResult: { code: string; state: string; url: string }\n try {\n callbackResult = await callback!.result\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error))\n process.exit(1)\n }\n code = callbackResult.code\n state = callbackResult.state\n }\n\n // Bind the exchange to the verifier we issued: a state from any other login\n // (pasted from another session, or fabricated) must be rejected.\n const result = await exchangeAntigravity(code, state, redirectUri, verifier)\n if (result.type === 'failed') {\n console.error(`Login failed: ${result.error}`)\n process.exit(1)\n }\n\n if (options.blob) {\n if (normalizedProxy !== undefined) {\n console.log('(Note: --proxy is ignored with --blob; proxy is only stored with the account)')\n }\n const blob = encodeCredentialBlob('agy', {\n access_token: result.access,\n refresh_token: result.refresh.split('|')[0],\n expires_in: Math.max(0, Math.round((result.expires - Date.now()) / 1000)),\n })\n console.log(`\\nPaste this blob into the remote dashboard/CLI:\\n\\n${blob}\\n`)\n } else {\n const proxyForUpsert = normalizedProxy !== undefined ? normalizedProxy : undefined\n const { account, created } = await upsertImportedAccount(store, {\n accessToken: result.access,\n refreshToken: result.refresh.split('|')[0]!,\n tokenType: 'Bearer',\n expiresAt: new Date(result.expires).toISOString(),\n authMethod: 'oauth',\n email: result.email ?? null,\n projectId: result.projectId || null,\n clientId: result.clientId || null,\n }, { overwriteExisting: true, ...(proxyForUpsert !== undefined ? { proxy: proxyForUpsert } : {}) })\n const masked = maskProxyUrl(account.proxy)\n console.log(`${created ? 'Added' : 'Updated'} account: ${account.email ?? '(no email)'} (project: ${result.projectId || 'default'})${masked ? ` proxy: ${masked}` : ''}`)\n }\n\n await callback?.close()\n}\n\nasync function statusCommand() {\n const store = createReadOnlyStoreOrExit()\n const storage = await store.load()\n if (storage.accounts.length === 0) {\n console.log('No agy accounts. Run `dsh-agy login` first.')\n return\n }\n console.log(`\\n${storage.accounts.length} account(s), active index ${storage.activeIndex}:\\n`)\n const sessions = new AgySessionManager({ store })\n for (const [index, account] of storage.accounts.entries()) {\n const marker = index === storage.activeIndex ? '★' : ' '\n const state = account.enabled === false ? 'disabled'\n : account.verificationRequired ? 'verification-required'\n : account.coolingDownUntil && account.coolingDownUntil > Date.now() ? 'cooling'\n : 'active'\n const maskedProxy = maskProxyUrl(account.proxy)\n console.log(` ${marker} [${index}] ${account.email ?? '(no email)'} — ${state}${account.projectId ? ` (project: ${account.projectId})` : ''}${maskedProxy ? ` proxy: ${maskedProxy}` : ''}`)\n\n // Best-effort quota summary via fetchAvailableModels (fresh access token).\n const session = await sessions.getSession().catch(() => undefined)\n if (session && session.index === index) {\n try {\n const { fetchAvailableModels } = await import('../adapter/models.ts')\n const discovered = await fetchAvailableModels(session.auth.access, session.account.projectId)\n const entries = Object.entries(discovered.models ?? {})\n if (entries.length > 0) {\n const withQuota = entries\n .map(([id, entry]) => ({ id, ...entry.quotaInfo }))\n .filter((e) => typeof e.remainingFraction === 'number')\n .sort((a, b) => (a.remainingFraction ?? 0) - (b.remainingFraction ?? 0))\n if (withQuota.length > 0) {\n const lowest = withQuota[0]!\n console.log(` models: ${entries.length}, lowest quota: ${Math.round((lowest.remainingFraction ?? 0) * 100)}% (${lowest.id})${lowest.resetTime ? `, resets ${new Date(lowest.resetTime).toISOString()}` : ''}`)\n } else {\n console.log(` models: ${entries.length} (no per-model quota reported)`)\n }\n }\n } catch (error) {\n console.log(` quota: unavailable (${error instanceof Error ? error.message : String(error)})`)\n }\n }\n }\n}\n\nasync function importCommand(options: { blob: boolean; files?: string[]; email?: string; overwrite: boolean; proxy?: string }) {\n const store = createStore()\n const proxyForUpsert = resolveProxyOption(options.proxy)\n let items: Array<{ source: unknown; kind: 'json' | 'blob' }>\n if (options.files && options.files.length > 0) {\n items = options.files.map((file) => {\n const raw = readFileSync(file, 'utf8')\n return { source: options.blob ? raw : (JSON.parse(raw) as unknown), kind: options.blob ? 'blob' : 'json' }\n })\n } else {\n const pasted = await ask('Paste the agy token JSON (or blob with --blob): ')\n items = [{ source: pasted, kind: options.blob ? 'blob' : 'json' }]\n }\n const importOpts: { email?: string; overwriteExisting?: boolean; proxy?: string } = {\n email: options.email,\n overwriteExisting: options.overwrite,\n }\n if (proxyForUpsert !== undefined) importOpts.proxy = proxyForUpsert\n const result = await importManySources(items, store, importOpts)\n console.log(`Imported ${result.imported}, replaced ${result.replaced}${result.errors.length > 0 ? `, ${result.errors.length} failed` : ''}`)\n for (const error of result.errors) console.log(` ! ${error}`)\n}\n\nasync function exportCommand(options: { index?: string; out?: string }) {\n const store = createReadOnlyStoreOrExit()\n const sessions = new AgySessionManager({ store })\n const storage = await store.load()\n const indices = options.index !== undefined\n ? [Number(options.index)]\n : storage.accounts.map((_, i) => i)\n\n let exported = 0\n for (const index of indices) {\n const account = storage.accounts[index]\n if (!account) {\n console.log(`[${index}] not found`)\n continue\n }\n const result = await sessions.exportBlob(index)\n if (!result.blob) {\n console.log(`[${index}] ${account.email ?? ''} — FAILED: ${result.error}`)\n continue\n }\n if (options.out) {\n const file = join(options.out, `dsh-agy-${index}.blob`)\n writeFileSync(file, result.blob + '\\n')\n console.log(`[${index}] ${account.email ?? ''} — wrote ${file}`)\n } else {\n console.log(result.blob)\n }\n exported++\n }\n if (!options.out) console.log(`\\n${exported} blob(s) exported — one line each, paste into a remote import`)\n}\n\nasync function verifyCommand(options: { index?: string }) {\n const store = createReadOnlyStoreOrExit()\n const sessions = new AgySessionManager({ store })\n const storage = await store.load()\n const indices = options.index !== undefined\n ? [Number(options.index)]\n : storage.accounts.map((_, i) => i)\n\n for (const index of indices) {\n const account = storage.accounts[index]\n if (!account) {\n console.log(`[${index}] not found`)\n continue\n }\n const result = await sessions.verifyAccount(index)\n if (result.ok) {\n console.log(`[${index}] ${account.email ?? ''} — OK${result.email && result.email !== account.email ? ` (userinfo: ${result.email})` : ''}`)\n } else {\n console.log(`[${index}] ${account.email ?? ''} — FAILED: ${result.error}`)\n }\n }\n}\n\nasync function healthCommand(options: { interval?: string; index?: string[] }) {\n const store = createReadOnlyStoreOrExit()\n const sessions = new AgySessionManager({\n store,\n onHealthReport: (results) => {\n for (const result of results) {\n if (result.ok) console.log(`[${result.index}] ${result.email ?? ''} — OK`)\n else console.log(`[${result.index}] ${result.email ?? ''} — FAILED: ${result.error}`)\n }\n },\n })\n const indices = options.index?.map((value) => Number(value))\n\n if (options.interval) {\n const intervalMs = Number(options.interval)\n if (!Number.isFinite(intervalMs) || intervalMs <= 0) {\n console.error('Error: --interval must be a positive number of milliseconds.')\n process.exit(1)\n }\n console.log(`Health probe every ${intervalMs}ms (Ctrl+C to stop).`)\n sessions.startHealthProbe(intervalMs, { unref: false })\n await new Promise<void>(() => {}) // keep the loop alive\n return\n }\n\n const results = await sessions.checkAccounts(indices)\n if (results.length === 0) console.log('No enabled accounts — run `dsh-agy login` first.')\n}\n\nasync function logoutCommand(options: { index?: string; email?: string }) {\n const store = createReadOnlyStoreOrExit()\n await store.mutate((storage) => {\n const index = options.index !== undefined\n ? Number(options.index)\n : options.email\n ? storage.accounts.findIndex((a) => a.email?.toLowerCase() === options.email!.toLowerCase())\n : storage.activeIndex\n if (index < 0 || index >= storage.accounts.length) {\n throw new Error(`account not found (index ${index})`)\n }\n const [removed] = storage.accounts.splice(index, 1)\n if (storage.activeIndex >= storage.accounts.length) storage.activeIndex = 0\n console.log(`Removed account: ${removed?.email ?? `[${index}]`}`)\n })\n}\n\nasync function proxySetCommand(options: { index: string; proxy: string }) {\n const idx = Number(options.index)\n if (!Number.isInteger(idx) || idx < 0) {\n console.error('Invalid --index (must be >= 0)')\n process.exit(1)\n }\n const raw = options.proxy ?? ''\n if (!raw.trim()) {\n console.error('Missing --proxy value')\n process.exit(1)\n }\n let normalized: string\n try {\n normalized = normalizeProxyUrl(raw)\n } catch (error) {\n console.error(`Invalid proxy URL: ${error instanceof Error ? error.message : String(error)}`)\n process.exit(1)\n }\n const store = createStore()\n await store.mutate((storage) => {\n const account = storage.accounts[idx]\n if (!account) throw new Error(`account not found (index ${idx})`)\n account.proxy = normalized\n })\n console.log(`[${idx}] proxy set to ${maskProxyUrl(normalized)}`)\n}\n\nasync function proxyClearCommand(options: { index: string }) {\n const idx = Number(options.index)\n if (!Number.isInteger(idx) || idx < 0) {\n console.error('Invalid --index (must be >= 0)')\n process.exit(1)\n }\n const store = createStore()\n await store.mutate((storage) => {\n const account = storage.accounts[idx]\n if (!account) throw new Error(`account not found (index ${idx})`)\n delete (account as { proxy?: string }).proxy\n })\n console.log(`[${idx}] proxy cleared`)\n}\n\nasync function proxyTestCommand(options: { index: string }) {\n const idx = Number(options.index)\n if (!Number.isInteger(idx) || idx < 0) {\n console.error('Invalid --index (must be >= 0)')\n process.exit(1)\n }\n const store = createReadOnlyStoreOrExit()\n const storage = await store.load()\n const account = storage.accounts[idx]\n if (!account) {\n console.error(`account not found (index ${idx})`)\n process.exit(1)\n }\n if (!account.proxy) {\n console.log(`[${idx}] no proxy configured`)\n return\n }\n const masked = maskProxyUrl(account.proxy)\n const reachable = await isProxyReachable(account.proxy)\n if (reachable) console.log(`[${idx}] ${masked} — ok`)\n else console.log(`[${idx}] ${masked} — proxy_unreachable`)\n}\n\nasync function proxyListCommand() {\n const store = createReadOnlyStoreOrExit()\n const storage = await store.load()\n if (storage.accounts.length === 0) {\n console.log('No agy accounts.')\n return\n }\n for (const [index, account] of storage.accounts.entries()) {\n const masked = maskProxyUrl(account.proxy)\n console.log(` [${index}] ${account.email ?? '(no email)'} — ${masked ?? '(no proxy)'}`)\n }\n}\n\nexport function createProgram(): Command {\n const program = new Command()\n program\n .name('dsh-agy')\n .description('Google Antigravity (agy) account management for DeepSeek Harness')\n .version(PACKAGE_VERSION)\n\n program.hook('preAction', () => {\n if (isAgyDisabled()) {\n console.error('dsh-agy is disabled (DSH_AGY_DISABLE=1).')\n process.exit(1)\n }\n })\n\n program\n .command('login')\n .description('OAuth login (browser, or headless paste)')\n .option('--headless', 'print the URL and wait for a pasted redirect URL', false)\n .option('--blob', 'print a paste-credential blob instead of storing the account', false)\n .option('--port <n>', 'loopback callback port', '51121')\n .option('--project <id>', 'bind the login to a specific project')\n .option('--timeout <ms>', 'callback timeout', '300000')\n .option('--proxy <url>', 'per-account proxy URL (http/https/socks5); empty to clear')\n .action(async (options) => {\n await loginCommand({ ...options, timeout: options.timeout })\n })\n\n program\n .command('status')\n .description('List accounts and their health')\n .action(async () => {\n await statusCommand()\n })\n\n program\n .command('import')\n .description('Import agy token files or paste credentials')\n .argument('[files...]', 'paths to agy auth.json token files (multiple allowed)')\n .option('--blob', 'the pasted value is a credential blob', false)\n .option('--email <email>', 'account email (skips userinfo verification)')\n .option('--overwrite', 'replace an existing account with the same email', false)\n .option('--proxy <url>', 'per-account proxy URL (http/https/socks5); empty to clear')\n .action(async (files: string[] | undefined, options: { blob: boolean; email?: string; overwrite: boolean; proxy?: string }) => {\n await importCommand({ ...options, files })\n })\n\n program\n .command('export')\n .description('Export account credentials as paste blobs')\n .option('--index <n>', 'export one account by index; default all')\n .option('--out <dir>', 'write one dsh-agy-<index>.blob file per account into this directory (default: print to stdout)')\n .action(async (options: { index?: string; out?: string }) => {\n await exportCommand(options)\n })\n\n program\n .command('verify')\n .description('Verify account credentials (refresh + userinfo)')\n .option('--index <n>', 'verify one account by index; default all')\n .action(async (options: { index?: string }) => {\n await verifyCommand(options)\n })\n\n program\n .command('health')\n .description('Check every enabled account (refresh + userinfo), optionally on an interval')\n .option('--interval <ms>', 'repeat on an interval instead of once')\n .option('--index <n...>', 'check only these accounts')\n .action(async (options: { interval?: string; index?: string[] }) => {\n await healthCommand(options)\n })\n\n program\n .command('logout')\n .description('Remove an account')\n .option('--index <n>', 'account index (default: active)')\n .option('--email <email>', 'account email')\n .action(async (options: { index?: string; email?: string }) => {\n await logoutCommand(options)\n })\n\n const proxy = program.command('proxy').description('Manage per-account proxies')\n proxy\n .command('set')\n .description('Set proxy for an account')\n .requiredOption('--index <n>', 'account index')\n .requiredOption('--proxy <url>', 'proxy URL (http/https/socks5)')\n .action(async (options: { index: string; proxy: string }) => {\n await proxySetCommand(options)\n })\n proxy\n .command('clear')\n .description('Clear proxy for an account')\n .requiredOption('--index <n>', 'account index')\n .action(async (options: { index: string }) => {\n await proxyClearCommand(options)\n })\n proxy\n .command('test')\n .description('Test proxy reachability for an account (TCP 2s fast-fail)')\n .requiredOption('--index <n>', 'account index')\n .action(async (options: { index: string }) => {\n await proxyTestCommand(options)\n })\n proxy\n .command('list')\n .description('List accounts with masked proxies')\n .action(async () => {\n await proxyListCommand()\n })\n\n return program\n}\n\nexport { AGY_DEFAULT_REDIRECT_URI }\n"],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
-
import { C as OAUTH_TOKEN_URL, D as getAgyBootstrapUserAgent, E as getAgyBootstrapClientMetadata, O as
|
|
2
|
+
import { C as OAUTH_TOKEN_URL, D as getAgyBootstrapUserAgent, E as getAgyBootstrapClientMetadata, O as resolveAgyClientCredentials, b as AGY_ENDPOINT_FALLBACKS, m as calculateTokenExpiry, w as OAUTH_USERINFO_URL } from "./accounts-DbTTxX_z.mjs";
|
|
3
|
+
import { i as proxiedFetch } from "./proxy-DQPIwUov.mjs";
|
|
3
4
|
import { createHash, randomBytes } from "node:crypto";
|
|
4
5
|
//#region src/oauth/pkce.ts
|
|
5
6
|
/** Minimal PKCE (RFC 7636) with S256, hand-rolled on node:crypto (no extra dependency). */
|
|
@@ -187,6 +188,7 @@ async function exchangeAntigravity(code, state, redirectUri, expectedVerifier) {
|
|
|
187
188
|
error: "State does not match the issued authorization"
|
|
188
189
|
};
|
|
189
190
|
const startTime = Date.now();
|
|
191
|
+
const { clientId, clientSecret } = resolveAgyClientCredentials();
|
|
190
192
|
const tokenResponse = await proxiedFetch(OAUTH_TOKEN_URL, {
|
|
191
193
|
method: "POST",
|
|
192
194
|
headers: {
|
|
@@ -195,8 +197,8 @@ async function exchangeAntigravity(code, state, redirectUri, expectedVerifier) {
|
|
|
195
197
|
"User-Agent": getAgyBootstrapUserAgent()
|
|
196
198
|
},
|
|
197
199
|
body: new URLSearchParams({
|
|
198
|
-
client_id:
|
|
199
|
-
client_secret:
|
|
200
|
+
client_id: clientId,
|
|
201
|
+
client_secret: clientSecret,
|
|
200
202
|
code,
|
|
201
203
|
grant_type: "authorization_code",
|
|
202
204
|
redirect_uri: redirectUri,
|
|
@@ -225,7 +227,8 @@ async function exchangeAntigravity(code, state, redirectUri, expectedVerifier) {
|
|
|
225
227
|
access: tokenPayload.access_token,
|
|
226
228
|
expires: calculateTokenExpiry(startTime, tokenPayload.expires_in),
|
|
227
229
|
email: userInfo.email,
|
|
228
|
-
projectId: effectiveProjectId || ""
|
|
230
|
+
projectId: effectiveProjectId || "",
|
|
231
|
+
clientId
|
|
229
232
|
};
|
|
230
233
|
} catch (error) {
|
|
231
234
|
return {
|
|
@@ -237,4 +240,4 @@ async function exchangeAntigravity(code, state, redirectUri, expectedVerifier) {
|
|
|
237
240
|
//#endregion
|
|
238
241
|
export { generatePkcePair as i, exchange_exports as n, encodeState as r, exchangeAntigravity as t };
|
|
239
242
|
|
|
240
|
-
//# sourceMappingURL=exchange-
|
|
243
|
+
//# sourceMappingURL=exchange-BvrLu2E4.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exchange-BvrLu2E4.mjs","names":["record"],"sources":["../src/oauth/pkce.ts","../src/oauth/exchange.ts"],"sourcesContent":["/** Minimal PKCE (RFC 7636) with S256, hand-rolled on node:crypto (no extra dependency). */\n\nimport { createHash, randomBytes } from 'node:crypto'\n\nfunction base64url(input: Buffer): string {\n return input.toString('base64url')\n}\n\n/** Generate a PKCE verifier (43-char URL-safe random) and its S256 challenge. */\nexport function generatePkcePair(): { verifier: string; challenge: string } {\n const verifier = base64url(randomBytes(32))\n const challenge = base64url(createHash('sha256').update(verifier).digest())\n return { verifier, challenge }\n}\n\n/** Encode an object into a URL-safe base64 string (used for the OAuth state). */\nexport function encodeState(payload: unknown): string {\n return Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url')\n}\n\n/** Decode a URL-safe base64 OAuth state back into its structured representation. */\nexport function decodeState<T>(state: string): T {\n const normalized = state.replace(/-/g, '+').replace(/_/g, '/')\n const padded = normalized.padEnd(\n normalized.length + (((4 - (normalized.length % 4)) % 4)),\n '=',\n )\n const json = Buffer.from(padded, 'base64').toString('utf8')\n return JSON.parse(json) as T\n}\n","/**\n * Exchange an authorization code for agy tokens, then resolve identity and\n * project via the Code Assist backend (userinfo + loadCodeAssist, multi-endpoint\n * fallback, best-effort).\n */\n\nimport type { TokenExchangeFailure, TokenExchangeResult } from '../types.ts'\nimport { calculateTokenExpiry } from './auth.ts'\nimport {\n AGY_ENDPOINT_FALLBACKS,\n OAUTH_TOKEN_URL,\n OAUTH_USERINFO_URL,\n getAgyBootstrapClientMetadata,\n getAgyBootstrapUserAgent,\n resolveAgyClientCredentials,\n} from './constants.ts'\nimport { decodeState } from './pkce.ts'\nimport { proxiedFetch } from '../proxy.ts'\n\nconst FETCH_TIMEOUT_MS = 10_000\n\ninterface OAuthState {\n verifier: string\n projectId?: string\n}\n\ninterface TokenPayload {\n access_token: string\n expires_in?: number\n refresh_token?: string\n}\n\ninterface UserInfo {\n email?: string\n}\n\nasync function fetchWithTimeout(url: string, options: RequestInit, timeoutMs = FETCH_TIMEOUT_MS): Promise<Response> {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), timeoutMs)\n try {\n return await proxiedFetch(url, { ...options, signal: controller.signal })\n } finally {\n clearTimeout(timeout)\n }\n}\n\ninterface LoadCodeAssistData {\n cloudaicompanionProject?: unknown\n subscriptionInfo?: unknown\n}\n\n/** Build the metadata payload shared by loadCodeAssist and onboardUser.\n * Only `ideType` is sent: `platform`/`pluginType` values are rejected by the\n * backend's enum validation (verified live: INVALID_ARGUMENT on \"MACOS\"), and\n * the official clients send ideType alone (OmniRoute capture). */\nfunction bootstrapMetadata(): Record<string, string> {\n return { ideType: 'ANTIGRAVITY' }\n}\n\nfunction extractProjectId(data: LoadCodeAssistData): string {\n const project = data.cloudaicompanionProject\n if (typeof project === 'string' && project) return project\n const record = project as { id?: string } | undefined\n return record && typeof record.id === 'string' && record.id ? record.id : ''\n}\n\n/**\n * Extract the subscription tier id used for onboardUser, mirroring OmniRoute's\n * codeAssistSubscription: paid → current → default allowed → legacy-tier.\n */\nexport function extractOnboardTierId(subscriptionInfo: unknown): string {\n const subscription = (subscriptionInfo ?? {}) as Record<string, unknown>\n const tierOf = (value: unknown, field: 'name' | 'id'): string | null => {\n const record = value as Record<string, unknown> | undefined\n const picked = record?.[field]\n return typeof picked === 'string' && picked.trim() ? picked.trim() : null\n }\n\n const paidId = tierOf(subscription.paidTier, 'id')\n if (paidId) return paidId\n\n const ineligible = Array.isArray(subscription.ineligibleTiers) && subscription.ineligibleTiers.length > 0\n if (!ineligible) {\n const currentId = tierOf(subscription.currentTier, 'id')\n if (currentId) return currentId\n }\n\n if (Array.isArray(subscription.allowedTiers)) {\n for (const tierValue of subscription.allowedTiers) {\n const tier = tierValue as Record<string, unknown>\n if (tier.isDefault) {\n const defaultId = tierOf(tier, 'id')\n if (defaultId) return defaultId\n }\n }\n }\n\n const currentId = tierOf(subscription.currentTier, 'id')\n if (currentId) return currentId\n return 'legacy-tier'\n}\n\n/** Resolve project id + tier id via loadCodeAssist across fallback endpoints. */\nexport async function loadCodeAssist(accessToken: string): Promise<{ projectId: string; tierId: string }> {\n const errors: string[] = []\n const loadHeaders: Record<string, string> = {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n 'Client-Metadata': getAgyBootstrapClientMetadata(),\n }\n\n for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {\n try {\n const url = `${baseEndpoint}/v1internal:loadCodeAssist`\n const response = await fetchWithTimeout(url, {\n method: 'POST',\n headers: loadHeaders,\n body: JSON.stringify({ metadata: bootstrapMetadata() }),\n })\n\n if (!response.ok) {\n const message = await response.text().catch(() => '')\n errors.push(`loadCodeAssist ${response.status} at ${baseEndpoint}${message ? `: ${message}` : ''}`)\n continue\n }\n\n const data = (await response.json()) as LoadCodeAssistData\n const projectId = extractProjectId(data)\n if (projectId) {\n return { projectId, tierId: extractOnboardTierId(data.subscriptionInfo) }\n }\n errors.push(`loadCodeAssist missing project id at ${baseEndpoint}`)\n } catch (error) {\n errors.push(\n `loadCodeAssist error at ${baseEndpoint}: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n }\n\n return { projectId: '', tierId: 'legacy-tier' }\n}\n\n/**\n * Onboard a new account (no Cloud Code project yet): POST onboardUser with the\n * tier id, then retry loadCodeAssist. Mirrors OmniRoute's postExchange flow\n * (bounded inline onboarding, 10 attempts × 5s).\n */\nexport async function onboardAndDiscoverProject(\n accessToken: string,\n tierId: string,\n options: { maxAttempts?: number; retryDelayMs?: number } = {},\n): Promise<{ projectId: string; tierId: string }> {\n // Bounded onboarding: 3 attempts with jittered delay (3-7s). Fixed-rhythm\n // long retry loops read as scripted automation to the upstream (OmniRoute\n // #9939 ban-safety hardening capped 10x5s to 3x3-7s).\n const maxAttempts = options.maxAttempts ?? 3\n const retryDelayMs = options.retryDelayMs ?? 3000 + Math.floor(Math.random() * 4000)\n const headers: Record<string, string> = {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n 'Client-Metadata': getAgyBootstrapClientMetadata(),\n }\n const metadata = bootstrapMetadata()\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {\n const response = await fetchWithTimeout(`${baseEndpoint}/v1internal:onboardUser`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ tier_id: tierId, metadata }),\n })\n if (!response.ok) continue\n const result = (await response.json()) as { done?: boolean }\n if (result.done === true) {\n const discovered = await loadCodeAssist(accessToken)\n if (discovered.projectId) return discovered\n }\n }\n } catch {\n // transient — retry\n }\n await new Promise((resolve) => setTimeout(resolve, retryDelayMs))\n }\n\n return { projectId: '', tierId }\n}\n\n/** Full bootstrap for a fresh account: discover the project, onboarding if needed. */\nexport async function bootstrapAccount(\n accessToken: string,\n options: { maxAttempts?: number; retryDelayMs?: number } = {},\n): Promise<{ projectId: string; tierId: string }> {\n const discovered = await loadCodeAssist(accessToken)\n if (discovered.projectId) return discovered\n return onboardAndDiscoverProject(accessToken, discovered.tierId, options)\n}\n\n/** Resolve the account's Cloud Code project id via loadCodeAssist across fallback endpoints. */\nexport async function fetchProjectID(accessToken: string): Promise<string> {\n return (await loadCodeAssist(accessToken)).projectId\n}\n\n/**\n * Exchange the authorization code (from the loopback callback) for access/refresh\n * tokens, resolve the email, and discover the project id.\n * @param code - the `code` query parameter from the redirect.\n * @param state - the `state` query parameter (encodes verifier + projectId).\n * @param redirectUri - must match the authorize() redirectUri.\n */\nexport async function exchangeAntigravity(\n code: string,\n state: string,\n redirectUri: string,\n expectedVerifier?: string,\n): Promise<TokenExchangeResult> {\n try {\n const { verifier, projectId } = decodeState<OAuthState>(state)\n if (!verifier) {\n return { type: 'failed', error: 'Missing PKCE verifier in state' }\n }\n // The caller issued this authorization URL (the CLI holds the verifier;\n // the web entry stores it per state). A state from a different login — or\n // one fabricated by an attacker — must not be exchangeable into the store.\n if (expectedVerifier && verifier !== expectedVerifier) {\n return { type: 'failed', error: 'State does not match the issued authorization' }\n }\n\n const startTime = Date.now()\n const { clientId, clientSecret } = resolveAgyClientCredentials()\n const tokenResponse = await proxiedFetch(OAUTH_TOKEN_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',\n Accept: '*/*',\n 'User-Agent': getAgyBootstrapUserAgent(),\n },\n body: new URLSearchParams({\n client_id: clientId,\n client_secret: clientSecret,\n code,\n grant_type: 'authorization_code',\n redirect_uri: redirectUri,\n code_verifier: verifier,\n }),\n })\n\n if (!tokenResponse.ok) {\n const errorText = await tokenResponse.text()\n return { type: 'failed', error: errorText }\n }\n\n const tokenPayload = (await tokenResponse.json()) as TokenPayload\n\n const userInfoResponse = await proxiedFetch(`${OAUTH_USERINFO_URL}?alt=json`, {\n headers: {\n Authorization: `Bearer ${tokenPayload.access_token}`,\n 'User-Agent': getAgyBootstrapUserAgent(),\n },\n })\n const userInfo: UserInfo = userInfoResponse.ok\n ? ((await userInfoResponse.json()) as UserInfo)\n : {}\n\n const refreshToken = tokenPayload.refresh_token\n if (!refreshToken) {\n return { type: 'failed', error: 'Missing refresh token in response' }\n }\n\n const effectiveProjectId = projectId || (await bootstrapAccount(tokenPayload.access_token)).projectId\n\n return {\n type: 'success',\n refresh: `${refreshToken}|${effectiveProjectId || ''}`,\n access: tokenPayload.access_token,\n expires: calculateTokenExpiry(startTime, tokenPayload.expires_in),\n email: userInfo.email,\n projectId: effectiveProjectId || '',\n clientId,\n }\n } catch (error) {\n const failure: TokenExchangeFailure = {\n type: 'failed',\n error: error instanceof Error ? error.message : 'Unknown error',\n }\n return failure\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { D as getAgyBootstrapUserAgent, E as getAgyBootstrapClientMetadata, O as
|
|
2
|
-
import { i as
|
|
1
|
+
import { D as getAgyBootstrapUserAgent, E as getAgyBootstrapClientMetadata, O as resolveAgyClientCredentials, S as OAUTH_AUTHORIZE_URL, b as AGY_ENDPOINT_FALLBACKS, x as AGY_SCOPES } from "./accounts-DbTTxX_z.mjs";
|
|
2
|
+
import { i as proxiedFetch, r as normalizeProxyUrl } from "./proxy-DQPIwUov.mjs";
|
|
3
|
+
import { i as generatePkcePair, r as encodeState } from "./exchange-BvrLu2E4.mjs";
|
|
3
4
|
import { n as decodeCredentialBlob } from "./blob-D1e7_uT1.mjs";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
4
6
|
//#region src/oauth/authorize.ts
|
|
5
7
|
/** Build the Antigravity OAuth authorization URL with PKCE and project metadata. */
|
|
6
8
|
/**
|
|
@@ -13,7 +15,8 @@ import { n as decodeCredentialBlob } from "./blob-D1e7_uT1.mjs";
|
|
|
13
15
|
async function authorizeAntigravity(redirectUri, projectId = "") {
|
|
14
16
|
const { verifier, challenge } = generatePkcePair();
|
|
15
17
|
const url = new URL(OAUTH_AUTHORIZE_URL);
|
|
16
|
-
|
|
18
|
+
const { clientId } = resolveAgyClientCredentials();
|
|
19
|
+
url.searchParams.set("client_id", clientId);
|
|
17
20
|
url.searchParams.set("response_type", "code");
|
|
18
21
|
url.searchParams.set("redirect_uri", redirectUri);
|
|
19
22
|
url.searchParams.set("scope", AGY_SCOPES.join(" "));
|
|
@@ -81,7 +84,8 @@ function parseAndValidateAgyToken(raw) {
|
|
|
81
84
|
refreshToken,
|
|
82
85
|
tokenType: toNonEmptyString(token.token_type) ?? "Bearer",
|
|
83
86
|
expiresAt,
|
|
84
|
-
authMethod: toNonEmptyString(doc.auth_method) ?? toNonEmptyString(token.auth_method)
|
|
87
|
+
authMethod: toNonEmptyString(doc.auth_method) ?? toNonEmptyString(token.auth_method),
|
|
88
|
+
clientId: toNonEmptyString(token.client_id) ?? toNonEmptyString(doc.client_id) ?? toNonEmptyString(token.clientId) ?? toNonEmptyString(doc.clientId)
|
|
85
89
|
};
|
|
86
90
|
}
|
|
87
91
|
/** Best-effort enrichment: email (userinfo) + projectId (loadCodeAssist), time-boxed. */
|
|
@@ -126,7 +130,8 @@ async function enrichWithAntigravityBackend(parsed) {
|
|
|
126
130
|
return {
|
|
127
131
|
...parsed,
|
|
128
132
|
email,
|
|
129
|
-
projectId
|
|
133
|
+
projectId,
|
|
134
|
+
clientId: parsed.clientId ?? null
|
|
130
135
|
};
|
|
131
136
|
}
|
|
132
137
|
/** Parse either a raw token document or a paste blob into an enriched account. */
|
|
@@ -161,15 +166,24 @@ async function importManySources(items, store, options = {}) {
|
|
|
161
166
|
async function upsertImportedAccount(store, enriched, options = {}) {
|
|
162
167
|
const resolvedEmail = options.email || enriched.email;
|
|
163
168
|
const refresh = `${enriched.refreshToken}|${enriched.projectId ?? ""}`;
|
|
169
|
+
let normalizedProxy;
|
|
170
|
+
let proxyProvided = options.proxy !== void 0;
|
|
171
|
+
if (proxyProvided) {
|
|
172
|
+
const raw = options.proxy ?? "";
|
|
173
|
+
if (!raw.trim()) normalizedProxy = void 0;
|
|
174
|
+
else normalizedProxy = normalizeProxyUrl(raw);
|
|
175
|
+
}
|
|
164
176
|
return store.mutate((storage) => {
|
|
165
177
|
const existingIndex = resolvedEmail ? storage.accounts.findIndex((a) => a.email?.toLowerCase() === resolvedEmail.toLowerCase()) : -1;
|
|
166
178
|
if (existingIndex !== -1) {
|
|
167
179
|
if (!options.overwriteExisting) throw new AgyAuthFileError("An agy account for this email already exists. Pass --overwrite to replace it.", 409, "duplicate_account");
|
|
168
180
|
const existing = storage.accounts[existingIndex];
|
|
169
|
-
|
|
181
|
+
const updated = {
|
|
170
182
|
...existing,
|
|
183
|
+
id: existing.id || randomUUID(),
|
|
171
184
|
refresh,
|
|
172
185
|
email: existing.email ?? resolvedEmail ?? void 0,
|
|
186
|
+
clientId: enriched.clientId ? enriched.clientId : existing.clientId,
|
|
173
187
|
addedAt: existing.addedAt,
|
|
174
188
|
lastUsed: Date.now(),
|
|
175
189
|
enabled: true,
|
|
@@ -177,18 +191,26 @@ async function upsertImportedAccount(store, enriched, options = {}) {
|
|
|
177
191
|
verificationRequiredAt: void 0,
|
|
178
192
|
verificationRequiredReason: void 0
|
|
179
193
|
};
|
|
194
|
+
if (proxyProvided) {
|
|
195
|
+
if (normalizedProxy) updated.proxy = normalizedProxy;
|
|
196
|
+
else delete updated.proxy;
|
|
197
|
+
}
|
|
198
|
+
storage.accounts[existingIndex] = updated;
|
|
180
199
|
return {
|
|
181
200
|
account: storage.accounts[existingIndex],
|
|
182
201
|
created: false
|
|
183
202
|
};
|
|
184
203
|
}
|
|
185
204
|
const account = {
|
|
205
|
+
id: randomUUID(),
|
|
186
206
|
email: resolvedEmail ?? void 0,
|
|
187
207
|
refresh,
|
|
188
208
|
projectId: enriched.projectId ?? void 0,
|
|
209
|
+
clientId: enriched.clientId ?? void 0,
|
|
189
210
|
addedAt: Date.now(),
|
|
190
211
|
lastUsed: Date.now(),
|
|
191
|
-
enabled: true
|
|
212
|
+
enabled: true,
|
|
213
|
+
...normalizedProxy ? { proxy: normalizedProxy } : {}
|
|
192
214
|
};
|
|
193
215
|
storage.accounts.push(account);
|
|
194
216
|
if (storage.activeIndex >= storage.accounts.length - 1 && storage.accounts.length === 1) storage.activeIndex = 0;
|
|
@@ -201,4 +223,4 @@ async function upsertImportedAccount(store, enriched, options = {}) {
|
|
|
201
223
|
//#endregion
|
|
202
224
|
export { upsertImportedAccount as n, authorizeAntigravity as r, importManySources as t };
|
|
203
225
|
|
|
204
|
-
//# sourceMappingURL=import-
|
|
226
|
+
//# sourceMappingURL=import-DSvpbB0T.mjs.map
|