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.
Files changed (33) hide show
  1. package/README.md +41 -4
  2. package/lib/{session-KoIh3xdB.mjs → accounts-DbTTxX_z.mjs} +1125 -546
  3. package/lib/accounts-DbTTxX_z.mjs.map +1 -0
  4. package/lib/{models-1rf_Wfo2.mjs → catalog-iuikPZIb.mjs} +29 -81
  5. package/lib/catalog-iuikPZIb.mjs.map +1 -0
  6. package/lib/cli/index.d.mts.map +1 -1
  7. package/lib/cli/index.mjs +149 -12
  8. package/lib/cli/index.mjs.map +1 -1
  9. package/lib/{exchange-DBRzR2M-.mjs → exchange-BvrLu2E4.mjs} +8 -5
  10. package/lib/exchange-BvrLu2E4.mjs.map +1 -0
  11. package/lib/{import-C4CRBTbi.mjs → import-DSvpbB0T.mjs} +30 -8
  12. package/lib/import-DSvpbB0T.mjs.map +1 -0
  13. package/lib/index.d.mts.map +1 -1
  14. package/lib/index.mjs +6 -1
  15. package/lib/index.mjs.map +1 -1
  16. package/lib/models-DRWVi33p.mjs +136 -0
  17. package/lib/models-DRWVi33p.mjs.map +1 -0
  18. package/lib/{plugin-common-CQoDUeqE.mjs → plugin-common-D7lc-W6b.mjs} +79 -11
  19. package/lib/plugin-common-D7lc-W6b.mjs.map +1 -0
  20. package/lib/proxy-DQPIwUov.mjs +269 -0
  21. package/lib/proxy-DQPIwUov.mjs.map +1 -0
  22. package/lib/{translate-BQuSQW35.mjs → translate-COjBTH4-.mjs} +27 -6
  23. package/lib/translate-COjBTH4-.mjs.map +1 -0
  24. package/lib/web/plugin.d.mts.map +1 -1
  25. package/lib/web/plugin.mjs +202 -8
  26. package/lib/web/plugin.mjs.map +1 -1
  27. package/package.json +4 -1
  28. package/lib/exchange-DBRzR2M-.mjs.map +0 -1
  29. package/lib/import-C4CRBTbi.mjs.map +0 -1
  30. package/lib/models-1rf_Wfo2.mjs.map +0 -1
  31. package/lib/plugin-common-CQoDUeqE.mjs.map +0 -1
  32. package/lib/session-KoIh3xdB.mjs.map +0 -1
  33. package/lib/translate-BQuSQW35.mjs.map +0 -1
package/lib/cli/index.mjs CHANGED
@@ -1,6 +1,7 @@
1
- import { c as loadMasterKey, d as resolveMasterKeyCodec, i as JsonAccountStore, o as createAesGcmCodec, s as deriveKey, t as AgySessionManager, u as resolveDshHome, y as AGY_DEFAULT_REDIRECT_URI } from "../session-KoIh3xdB.mjs";
2
- import { t as exchangeAntigravity } from "../exchange-DBRzR2M-.mjs";
3
- import { n as upsertImportedAccount, r as authorizeAntigravity, t as importManySources } from "../import-C4CRBTbi.mjs";
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
- }, { overwriteExisting: true });
214
- console.log(`${created ? "Added" : "Updated"} account: ${account.email ?? "(no email)"} (project: ${result.projectId || "default"})`);
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
- console.log(` ${marker} [${index}] ${account.email ?? "(no email)"} — ${state}${account.projectId ? ` (project: ${account.projectId})` : ""}`);
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-1rf_Wfo2.mjs").then((n) => n.n);
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 result = await importManySources(items, store, {
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.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").action(async (options) => {
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
@@ -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 proxiedFetch, _ as AGY_CLIENT_ID, b as AGY_ENDPOINT_FALLBACKS, f as calculateTokenExpiry, v as AGY_CLIENT_SECRET, w as OAUTH_USERINFO_URL } from "./session-KoIh3xdB.mjs";
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: AGY_CLIENT_ID,
199
- client_secret: AGY_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-DBRzR2M-.mjs.map
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 proxiedFetch, S as OAUTH_AUTHORIZE_URL, _ as AGY_CLIENT_ID, b as AGY_ENDPOINT_FALLBACKS, x as AGY_SCOPES } from "./session-KoIh3xdB.mjs";
2
- import { i as generatePkcePair, r as encodeState } from "./exchange-DBRzR2M-.mjs";
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
- url.searchParams.set("client_id", AGY_CLIENT_ID);
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
- storage.accounts[existingIndex] = {
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-C4CRBTbi.mjs.map
226
+ //# sourceMappingURL=import-DSvpbB0T.mjs.map