dsh-agy 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +174 -0
- package/bin/dsh-agy.js +6 -0
- package/cordis.patch.yml +13 -0
- package/lib/blob-D1e7_uT1.mjs +63 -0
- package/lib/blob-D1e7_uT1.mjs.map +1 -0
- package/lib/cli/index.d.mts +10 -0
- package/lib/cli/index.d.mts.map +1 -0
- package/lib/cli/index.mjs +317 -0
- package/lib/cli/index.mjs.map +1 -0
- package/lib/exchange-MhFUiBAm.mjs +240 -0
- package/lib/exchange-MhFUiBAm.mjs.map +1 -0
- package/lib/import-DnKzgr-0.mjs +187 -0
- package/lib/import-DnKzgr-0.mjs.map +1 -0
- package/lib/index.d.mts +8 -0
- package/lib/index.d.mts.map +1 -0
- package/lib/index.mjs +15 -0
- package/lib/index.mjs.map +1 -0
- package/lib/models-Cu1g7AXV.mjs +222 -0
- package/lib/models-Cu1g7AXV.mjs.map +1 -0
- package/lib/parse-CiEzD9d_.mjs +174 -0
- package/lib/parse-CiEzD9d_.mjs.map +1 -0
- package/lib/plugin-common-I56kmGjr.mjs +270 -0
- package/lib/plugin-common-I56kmGjr.mjs.map +1 -0
- package/lib/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/lib/session-tawi2_uh.mjs +1221 -0
- package/lib/session-tawi2_uh.mjs.map +1 -0
- package/lib/translate-h2bjH7ub.mjs +148 -0
- package/lib/translate-h2bjH7ub.mjs.map +1 -0
- package/lib/web/plugin.d.mts +8 -0
- package/lib/web/plugin.d.mts.map +1 -0
- package/lib/web/plugin.mjs +1618 -0
- package/lib/web/plugin.mjs.map +1 -0
- package/package.json +81 -0
|
@@ -0,0 +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 } from 'node:fs'\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 { parseImportSource, 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; file?: string; email?: string; overwrite: boolean }) {\n const store = createStore()\n let source: unknown\n if (options.file) {\n source = JSON.parse(readFileSync(options.file, 'utf8')) as unknown\n } else {\n const pasted = await ask('Paste the agy token JSON (or blob with --blob): ')\n source = pasted\n }\n const kind = options.blob ? 'blob' : 'json'\n const enriched = await parseImportSource(source, kind)\n const { account, created } = await upsertImportedAccount(store, enriched, {\n email: options.email,\n overwriteExisting: options.overwrite,\n })\n console.log(`${created ? 'Imported' : 'Replaced'} account: ${account.email ?? '(no email)'}${enriched.projectId ? ` (project: ${enriched.projectId})` : ''}`)\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 an agy token file or paste credentials')\n .argument('[file]', 'path to an agy auth.json token file')\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 (file: string | undefined, options: { blob: boolean; email?: string; overwrite: boolean }) => {\n await importCommand({ ...options, file })\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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"}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import { C as OAUTH_TOKEN_URL, D as getAgyBootstrapUserAgent, E as getAgyBootstrapClientMetadata, _ as AGY_CLIENT_ID, b as AGY_ENDPOINT_FALLBACKS, f as calculateTokenExpiry, v as AGY_CLIENT_SECRET, w as OAUTH_USERINFO_URL } from "./session-tawi2_uh.mjs";
|
|
3
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
4
|
+
//#region src/oauth/pkce.ts
|
|
5
|
+
/** Minimal PKCE (RFC 7636) with S256, hand-rolled on node:crypto (no extra dependency). */
|
|
6
|
+
function base64url(input) {
|
|
7
|
+
return input.toString("base64url");
|
|
8
|
+
}
|
|
9
|
+
/** Generate a PKCE verifier (43-char URL-safe random) and its S256 challenge. */
|
|
10
|
+
function generatePkcePair() {
|
|
11
|
+
const verifier = base64url(randomBytes(32));
|
|
12
|
+
return {
|
|
13
|
+
verifier,
|
|
14
|
+
challenge: base64url(createHash("sha256").update(verifier).digest())
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** Encode an object into a URL-safe base64 string (used for the OAuth state). */
|
|
18
|
+
function encodeState(payload) {
|
|
19
|
+
return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
|
|
20
|
+
}
|
|
21
|
+
/** Decode a URL-safe base64 OAuth state back into its structured representation. */
|
|
22
|
+
function decodeState(state) {
|
|
23
|
+
const normalized = state.replace(/-/g, "+").replace(/_/g, "/");
|
|
24
|
+
const padded = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
|
|
25
|
+
const json = Buffer.from(padded, "base64").toString("utf8");
|
|
26
|
+
return JSON.parse(json);
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/oauth/exchange.ts
|
|
30
|
+
var exchange_exports = /* @__PURE__ */ __exportAll({
|
|
31
|
+
bootstrapAccount: () => bootstrapAccount,
|
|
32
|
+
exchangeAntigravity: () => exchangeAntigravity,
|
|
33
|
+
extractOnboardTierId: () => extractOnboardTierId,
|
|
34
|
+
loadCodeAssist: () => loadCodeAssist,
|
|
35
|
+
onboardAndDiscoverProject: () => onboardAndDiscoverProject
|
|
36
|
+
});
|
|
37
|
+
const FETCH_TIMEOUT_MS = 1e4;
|
|
38
|
+
async function fetchWithTimeout(url, options, timeoutMs = FETCH_TIMEOUT_MS) {
|
|
39
|
+
const controller = new AbortController();
|
|
40
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
41
|
+
try {
|
|
42
|
+
return await fetch(url, {
|
|
43
|
+
...options,
|
|
44
|
+
signal: controller.signal
|
|
45
|
+
});
|
|
46
|
+
} finally {
|
|
47
|
+
clearTimeout(timeout);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** Build the metadata payload shared by loadCodeAssist and onboardUser.
|
|
51
|
+
* Only `ideType` is sent: `platform`/`pluginType` values are rejected by the
|
|
52
|
+
* backend's enum validation (verified live: INVALID_ARGUMENT on "MACOS"), and
|
|
53
|
+
* the official clients send ideType alone (OmniRoute capture). */
|
|
54
|
+
function bootstrapMetadata() {
|
|
55
|
+
return { ideType: "ANTIGRAVITY" };
|
|
56
|
+
}
|
|
57
|
+
function extractProjectId(data) {
|
|
58
|
+
const project = data.cloudaicompanionProject;
|
|
59
|
+
if (typeof project === "string" && project) return project;
|
|
60
|
+
const record = project;
|
|
61
|
+
return record && typeof record.id === "string" && record.id ? record.id : "";
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Extract the subscription tier id used for onboardUser, mirroring OmniRoute's
|
|
65
|
+
* codeAssistSubscription: paid → current → default allowed → legacy-tier.
|
|
66
|
+
*/
|
|
67
|
+
function extractOnboardTierId(subscriptionInfo) {
|
|
68
|
+
const subscription = subscriptionInfo ?? {};
|
|
69
|
+
const tierOf = (value, field) => {
|
|
70
|
+
const picked = value?.[field];
|
|
71
|
+
return typeof picked === "string" && picked.trim() ? picked.trim() : null;
|
|
72
|
+
};
|
|
73
|
+
const paidId = tierOf(subscription.paidTier, "id");
|
|
74
|
+
if (paidId) return paidId;
|
|
75
|
+
if (!(Array.isArray(subscription.ineligibleTiers) && subscription.ineligibleTiers.length > 0)) {
|
|
76
|
+
const currentId = tierOf(subscription.currentTier, "id");
|
|
77
|
+
if (currentId) return currentId;
|
|
78
|
+
}
|
|
79
|
+
if (Array.isArray(subscription.allowedTiers)) for (const tierValue of subscription.allowedTiers) {
|
|
80
|
+
const tier = tierValue;
|
|
81
|
+
if (tier.isDefault) {
|
|
82
|
+
const defaultId = tierOf(tier, "id");
|
|
83
|
+
if (defaultId) return defaultId;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const currentId = tierOf(subscription.currentTier, "id");
|
|
87
|
+
if (currentId) return currentId;
|
|
88
|
+
return "legacy-tier";
|
|
89
|
+
}
|
|
90
|
+
/** Resolve project id + tier id via loadCodeAssist across fallback endpoints. */
|
|
91
|
+
async function loadCodeAssist(accessToken) {
|
|
92
|
+
const errors = [];
|
|
93
|
+
const loadHeaders = {
|
|
94
|
+
Authorization: `Bearer ${accessToken}`,
|
|
95
|
+
"Content-Type": "application/json",
|
|
96
|
+
"User-Agent": getAgyBootstrapUserAgent(),
|
|
97
|
+
"Client-Metadata": getAgyBootstrapClientMetadata()
|
|
98
|
+
};
|
|
99
|
+
for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) try {
|
|
100
|
+
const response = await fetchWithTimeout(`${baseEndpoint}/v1internal:loadCodeAssist`, {
|
|
101
|
+
method: "POST",
|
|
102
|
+
headers: loadHeaders,
|
|
103
|
+
body: JSON.stringify({ metadata: bootstrapMetadata() })
|
|
104
|
+
});
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
const message = await response.text().catch(() => "");
|
|
107
|
+
errors.push(`loadCodeAssist ${response.status} at ${baseEndpoint}${message ? `: ${message}` : ""}`);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const data = await response.json();
|
|
111
|
+
const projectId = extractProjectId(data);
|
|
112
|
+
if (projectId) return {
|
|
113
|
+
projectId,
|
|
114
|
+
tierId: extractOnboardTierId(data.subscriptionInfo)
|
|
115
|
+
};
|
|
116
|
+
errors.push(`loadCodeAssist missing project id at ${baseEndpoint}`);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
errors.push(`loadCodeAssist error at ${baseEndpoint}: ${error instanceof Error ? error.message : String(error)}`);
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
projectId: "",
|
|
122
|
+
tierId: "legacy-tier"
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Onboard a new account (no Cloud Code project yet): POST onboardUser with the
|
|
127
|
+
* tier id, then retry loadCodeAssist. Mirrors OmniRoute's postExchange flow
|
|
128
|
+
* (bounded inline onboarding, 10 attempts × 5s).
|
|
129
|
+
*/
|
|
130
|
+
async function onboardAndDiscoverProject(accessToken, tierId, options = {}) {
|
|
131
|
+
const maxAttempts = options.maxAttempts ?? 3;
|
|
132
|
+
const retryDelayMs = options.retryDelayMs ?? 3e3 + Math.floor(Math.random() * 4e3);
|
|
133
|
+
const headers = {
|
|
134
|
+
Authorization: `Bearer ${accessToken}`,
|
|
135
|
+
"Content-Type": "application/json",
|
|
136
|
+
"User-Agent": getAgyBootstrapUserAgent(),
|
|
137
|
+
"Client-Metadata": getAgyBootstrapClientMetadata()
|
|
138
|
+
};
|
|
139
|
+
const metadata = bootstrapMetadata();
|
|
140
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
141
|
+
try {
|
|
142
|
+
for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {
|
|
143
|
+
const response = await fetchWithTimeout(`${baseEndpoint}/v1internal:onboardUser`, {
|
|
144
|
+
method: "POST",
|
|
145
|
+
headers,
|
|
146
|
+
body: JSON.stringify({
|
|
147
|
+
tier_id: tierId,
|
|
148
|
+
metadata
|
|
149
|
+
})
|
|
150
|
+
});
|
|
151
|
+
if (!response.ok) continue;
|
|
152
|
+
if ((await response.json()).done === true) {
|
|
153
|
+
const discovered = await loadCodeAssist(accessToken);
|
|
154
|
+
if (discovered.projectId) return discovered;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} catch {}
|
|
158
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
projectId: "",
|
|
162
|
+
tierId
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/** Full bootstrap for a fresh account: discover the project, onboarding if needed. */
|
|
166
|
+
async function bootstrapAccount(accessToken, options = {}) {
|
|
167
|
+
const discovered = await loadCodeAssist(accessToken);
|
|
168
|
+
if (discovered.projectId) return discovered;
|
|
169
|
+
return onboardAndDiscoverProject(accessToken, discovered.tierId, options);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Exchange the authorization code (from the loopback callback) for access/refresh
|
|
173
|
+
* tokens, resolve the email, and discover the project id.
|
|
174
|
+
* @param code - the `code` query parameter from the redirect.
|
|
175
|
+
* @param state - the `state` query parameter (encodes verifier + projectId).
|
|
176
|
+
* @param redirectUri - must match the authorize() redirectUri.
|
|
177
|
+
*/
|
|
178
|
+
async function exchangeAntigravity(code, state, redirectUri, expectedVerifier) {
|
|
179
|
+
try {
|
|
180
|
+
const { verifier, projectId } = decodeState(state);
|
|
181
|
+
if (!verifier) return {
|
|
182
|
+
type: "failed",
|
|
183
|
+
error: "Missing PKCE verifier in state"
|
|
184
|
+
};
|
|
185
|
+
if (expectedVerifier && verifier !== expectedVerifier) return {
|
|
186
|
+
type: "failed",
|
|
187
|
+
error: "State does not match the issued authorization"
|
|
188
|
+
};
|
|
189
|
+
const startTime = Date.now();
|
|
190
|
+
const tokenResponse = await fetch(OAUTH_TOKEN_URL, {
|
|
191
|
+
method: "POST",
|
|
192
|
+
headers: {
|
|
193
|
+
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
|
194
|
+
Accept: "*/*",
|
|
195
|
+
"User-Agent": getAgyBootstrapUserAgent()
|
|
196
|
+
},
|
|
197
|
+
body: new URLSearchParams({
|
|
198
|
+
client_id: AGY_CLIENT_ID,
|
|
199
|
+
client_secret: AGY_CLIENT_SECRET,
|
|
200
|
+
code,
|
|
201
|
+
grant_type: "authorization_code",
|
|
202
|
+
redirect_uri: redirectUri,
|
|
203
|
+
code_verifier: verifier
|
|
204
|
+
})
|
|
205
|
+
});
|
|
206
|
+
if (!tokenResponse.ok) return {
|
|
207
|
+
type: "failed",
|
|
208
|
+
error: await tokenResponse.text()
|
|
209
|
+
};
|
|
210
|
+
const tokenPayload = await tokenResponse.json();
|
|
211
|
+
const userInfoResponse = await fetch(`${OAUTH_USERINFO_URL}?alt=json`, { headers: {
|
|
212
|
+
Authorization: `Bearer ${tokenPayload.access_token}`,
|
|
213
|
+
"User-Agent": getAgyBootstrapUserAgent()
|
|
214
|
+
} });
|
|
215
|
+
const userInfo = userInfoResponse.ok ? await userInfoResponse.json() : {};
|
|
216
|
+
const refreshToken = tokenPayload.refresh_token;
|
|
217
|
+
if (!refreshToken) return {
|
|
218
|
+
type: "failed",
|
|
219
|
+
error: "Missing refresh token in response"
|
|
220
|
+
};
|
|
221
|
+
const effectiveProjectId = projectId || (await bootstrapAccount(tokenPayload.access_token)).projectId;
|
|
222
|
+
return {
|
|
223
|
+
type: "success",
|
|
224
|
+
refresh: `${refreshToken}|${effectiveProjectId || ""}`,
|
|
225
|
+
access: tokenPayload.access_token,
|
|
226
|
+
expires: calculateTokenExpiry(startTime, tokenPayload.expires_in),
|
|
227
|
+
email: userInfo.email,
|
|
228
|
+
projectId: effectiveProjectId || ""
|
|
229
|
+
};
|
|
230
|
+
} catch (error) {
|
|
231
|
+
return {
|
|
232
|
+
type: "failed",
|
|
233
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
//#endregion
|
|
238
|
+
export { generatePkcePair as i, exchange_exports as n, encodeState as r, exchangeAntigravity as t };
|
|
239
|
+
|
|
240
|
+
//# sourceMappingURL=exchange-MhFUiBAm.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exchange-MhFUiBAm.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_CLIENT_ID,\n AGY_CLIENT_SECRET,\n AGY_ENDPOINT_FALLBACKS,\n OAUTH_TOKEN_URL,\n OAUTH_USERINFO_URL,\n getAgyBootstrapClientMetadata,\n getAgyBootstrapUserAgent,\n} from './constants.ts'\nimport { decodeState } from './pkce.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 fetch(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 tokenResponse = await fetch(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: AGY_CLIENT_ID,\n client_secret: AGY_CLIENT_SECRET,\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 fetch(`${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 }\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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"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { D as getAgyBootstrapUserAgent, E as getAgyBootstrapClientMetadata, S as OAUTH_AUTHORIZE_URL, _ as AGY_CLIENT_ID, b as AGY_ENDPOINT_FALLBACKS, x as AGY_SCOPES } from "./session-tawi2_uh.mjs";
|
|
2
|
+
import { i as generatePkcePair, r as encodeState } from "./exchange-MhFUiBAm.mjs";
|
|
3
|
+
import { n as decodeCredentialBlob } from "./blob-D1e7_uT1.mjs";
|
|
4
|
+
//#region src/oauth/authorize.ts
|
|
5
|
+
/** Build the Antigravity OAuth authorization URL with PKCE and project metadata. */
|
|
6
|
+
/**
|
|
7
|
+
* Build the Google OAuth authorization URL for the Antigravity client.
|
|
8
|
+
* The `state` payload carries the PKCE verifier and an optional project id so a
|
|
9
|
+
* headless callback can be pasted back verbatim.
|
|
10
|
+
* @param redirectUri - loopback callback URI (harness webServer route or CLI listener).
|
|
11
|
+
* @param projectId - optional Antigravity project id to bind this login to.
|
|
12
|
+
*/
|
|
13
|
+
async function authorizeAntigravity(redirectUri, projectId = "") {
|
|
14
|
+
const { verifier, challenge } = generatePkcePair();
|
|
15
|
+
const url = new URL(OAUTH_AUTHORIZE_URL);
|
|
16
|
+
url.searchParams.set("client_id", AGY_CLIENT_ID);
|
|
17
|
+
url.searchParams.set("response_type", "code");
|
|
18
|
+
url.searchParams.set("redirect_uri", redirectUri);
|
|
19
|
+
url.searchParams.set("scope", AGY_SCOPES.join(" "));
|
|
20
|
+
url.searchParams.set("code_challenge", challenge);
|
|
21
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
22
|
+
const state = encodeState({
|
|
23
|
+
verifier,
|
|
24
|
+
projectId: projectId || ""
|
|
25
|
+
});
|
|
26
|
+
url.searchParams.set("state", state);
|
|
27
|
+
url.searchParams.set("access_type", "offline");
|
|
28
|
+
url.searchParams.set("prompt", "consent");
|
|
29
|
+
return {
|
|
30
|
+
url: url.toString(),
|
|
31
|
+
verifier,
|
|
32
|
+
projectId: projectId || "",
|
|
33
|
+
state
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/cli/import.ts
|
|
38
|
+
/**
|
|
39
|
+
* Import agy credentials: parse the `agy` CLI auth.json token file or a
|
|
40
|
+
* paste credential blob, enrich with the Code Assist backend (email/project),
|
|
41
|
+
* and persist into the account store. Adapted from OmniRoute's agyAuthImport
|
|
42
|
+
* (MIT, see NOTICE.md).
|
|
43
|
+
*/
|
|
44
|
+
var AgyAuthFileError = class extends Error {
|
|
45
|
+
status;
|
|
46
|
+
code;
|
|
47
|
+
constructor(message, status = 400, code = "invalid_request") {
|
|
48
|
+
super(message);
|
|
49
|
+
this.name = "AgyAuthFileError";
|
|
50
|
+
this.status = status;
|
|
51
|
+
this.code = code;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
function toRecord(value) {
|
|
55
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
56
|
+
}
|
|
57
|
+
function toNonEmptyString(value) {
|
|
58
|
+
if (typeof value !== "string") return null;
|
|
59
|
+
const trimmed = value.trim();
|
|
60
|
+
return trimmed ? trimmed : null;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Parse the agy CLI token file: tokens nest under `.token` with an ISO
|
|
64
|
+
* `expiry` string and no id_token; a flat top-level shape is accepted too.
|
|
65
|
+
*/
|
|
66
|
+
function parseAndValidateAgyToken(raw) {
|
|
67
|
+
const doc = toRecord(raw);
|
|
68
|
+
const token = toRecord(doc.token ?? doc);
|
|
69
|
+
const accessToken = toNonEmptyString(token.access_token);
|
|
70
|
+
const refreshToken = toNonEmptyString(token.refresh_token);
|
|
71
|
+
if (!accessToken) throw new AgyAuthFileError("access_token is missing or empty in the agy token file", 400, "missing_access_token");
|
|
72
|
+
if (!refreshToken) throw new AgyAuthFileError("refresh_token is missing or empty in the agy token file", 400, "missing_refresh_token");
|
|
73
|
+
let expiresAt = null;
|
|
74
|
+
const isoExpiry = toNonEmptyString(token.expiry) ?? toNonEmptyString(token.expires_at);
|
|
75
|
+
if (isoExpiry) {
|
|
76
|
+
const ms = new Date(isoExpiry).getTime();
|
|
77
|
+
expiresAt = Number.isNaN(ms) ? null : new Date(ms).toISOString();
|
|
78
|
+
} else if (typeof token.expiry_date === "number" && Number.isFinite(token.expiry_date)) expiresAt = new Date(token.expiry_date).toISOString();
|
|
79
|
+
return {
|
|
80
|
+
accessToken,
|
|
81
|
+
refreshToken,
|
|
82
|
+
tokenType: toNonEmptyString(token.token_type) ?? "Bearer",
|
|
83
|
+
expiresAt,
|
|
84
|
+
authMethod: toNonEmptyString(doc.auth_method) ?? toNonEmptyString(token.auth_method)
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/** Best-effort enrichment: email (userinfo) + projectId (loadCodeAssist), time-boxed. */
|
|
88
|
+
async function enrichWithAntigravityBackend(parsed) {
|
|
89
|
+
let email = null;
|
|
90
|
+
let projectId = null;
|
|
91
|
+
const controller = new AbortController();
|
|
92
|
+
const timer = setTimeout(() => controller.abort(), 8e3);
|
|
93
|
+
try {
|
|
94
|
+
const res = await fetch("https://www.googleapis.com/oauth2/v1/userinfo?alt=json", {
|
|
95
|
+
headers: { Authorization: `Bearer ${parsed.accessToken}` },
|
|
96
|
+
signal: controller.signal
|
|
97
|
+
});
|
|
98
|
+
if (res.ok) email = toNonEmptyString(toRecord(await res.json()).email);
|
|
99
|
+
} catch {} finally {
|
|
100
|
+
clearTimeout(timer);
|
|
101
|
+
}
|
|
102
|
+
const loadController = new AbortController();
|
|
103
|
+
const loadTimer = setTimeout(() => loadController.abort(), 8e3);
|
|
104
|
+
try {
|
|
105
|
+
const headers = {
|
|
106
|
+
Authorization: `Bearer ${parsed.accessToken}`,
|
|
107
|
+
"Content-Type": "application/json",
|
|
108
|
+
"User-Agent": getAgyBootstrapUserAgent(),
|
|
109
|
+
"Client-Metadata": getAgyBootstrapClientMetadata()
|
|
110
|
+
};
|
|
111
|
+
for (const endpoint of AGY_ENDPOINT_FALLBACKS) try {
|
|
112
|
+
const res = await fetch(`${endpoint}/v1internal:loadCodeAssist`, {
|
|
113
|
+
method: "POST",
|
|
114
|
+
headers,
|
|
115
|
+
body: JSON.stringify({ metadata: { ideType: "ANTIGRAVITY" } }),
|
|
116
|
+
signal: loadController.signal
|
|
117
|
+
});
|
|
118
|
+
if (!res.ok) continue;
|
|
119
|
+
const project = toRecord(await res.json()).cloudaicompanionProject;
|
|
120
|
+
projectId = (typeof project === "string" ? toNonEmptyString(project) : null) ?? toNonEmptyString(toRecord(project).id);
|
|
121
|
+
if (projectId) break;
|
|
122
|
+
} catch {}
|
|
123
|
+
} catch {} finally {
|
|
124
|
+
clearTimeout(loadTimer);
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
...parsed,
|
|
128
|
+
email,
|
|
129
|
+
projectId
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/** Parse either a raw token document or a paste blob into an enriched account. */
|
|
133
|
+
async function parseImportSource(source, kind) {
|
|
134
|
+
if (kind === "blob") {
|
|
135
|
+
if (typeof source !== "string") throw new AgyAuthFileError("blob must be a string", 400, "invalid_blob");
|
|
136
|
+
return enrichWithAntigravityBackend(parseAndValidateAgyToken({
|
|
137
|
+
token: decodeCredentialBlob(source).tokens,
|
|
138
|
+
auth_method: "paste-blob"
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
return enrichWithAntigravityBackend(parseAndValidateAgyToken(source));
|
|
142
|
+
}
|
|
143
|
+
/** Upsert the account into the store, deduping by email. */
|
|
144
|
+
async function upsertImportedAccount(store, enriched, options = {}) {
|
|
145
|
+
const resolvedEmail = options.email || enriched.email;
|
|
146
|
+
const refresh = `${enriched.refreshToken}|${enriched.projectId ?? ""}`;
|
|
147
|
+
return store.mutate((storage) => {
|
|
148
|
+
const existingIndex = resolvedEmail ? storage.accounts.findIndex((a) => a.email?.toLowerCase() === resolvedEmail.toLowerCase()) : -1;
|
|
149
|
+
if (existingIndex !== -1) {
|
|
150
|
+
if (!options.overwriteExisting) throw new AgyAuthFileError("An agy account for this email already exists. Pass --overwrite to replace it.", 409, "duplicate_account");
|
|
151
|
+
const existing = storage.accounts[existingIndex];
|
|
152
|
+
storage.accounts[existingIndex] = {
|
|
153
|
+
...existing,
|
|
154
|
+
refresh,
|
|
155
|
+
email: existing.email ?? resolvedEmail ?? void 0,
|
|
156
|
+
addedAt: existing.addedAt,
|
|
157
|
+
lastUsed: Date.now(),
|
|
158
|
+
enabled: true,
|
|
159
|
+
verificationRequired: false,
|
|
160
|
+
verificationRequiredAt: void 0,
|
|
161
|
+
verificationRequiredReason: void 0
|
|
162
|
+
};
|
|
163
|
+
return {
|
|
164
|
+
account: storage.accounts[existingIndex],
|
|
165
|
+
created: false
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const account = {
|
|
169
|
+
email: resolvedEmail ?? void 0,
|
|
170
|
+
refresh,
|
|
171
|
+
projectId: enriched.projectId ?? void 0,
|
|
172
|
+
addedAt: Date.now(),
|
|
173
|
+
lastUsed: Date.now(),
|
|
174
|
+
enabled: true
|
|
175
|
+
};
|
|
176
|
+
storage.accounts.push(account);
|
|
177
|
+
if (storage.activeIndex >= storage.accounts.length - 1 && storage.accounts.length === 1) storage.activeIndex = 0;
|
|
178
|
+
return {
|
|
179
|
+
account,
|
|
180
|
+
created: true
|
|
181
|
+
};
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
//#endregion
|
|
185
|
+
export { upsertImportedAccount as n, authorizeAntigravity as r, parseImportSource as t };
|
|
186
|
+
|
|
187
|
+
//# sourceMappingURL=import-DnKzgr-0.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import-DnKzgr-0.mjs","names":[],"sources":["../src/oauth/authorize.ts","../src/cli/import.ts"],"sourcesContent":["/** Build the Antigravity OAuth authorization URL with PKCE and project metadata. */\n\nimport {\n AGY_CLIENT_ID,\n AGY_SCOPES,\n OAUTH_AUTHORIZE_URL,\n} from './constants.ts'\nimport { encodeState, generatePkcePair } from './pkce.ts'\n\nexport interface AntigravityAuthorization {\n url: string\n verifier: string\n projectId: string\n /** Raw state payload embedded in the URL (for server-side binding). */\n state: string\n}\n\n/**\n * Build the Google OAuth authorization URL for the Antigravity client.\n * The `state` payload carries the PKCE verifier and an optional project id so a\n * headless callback can be pasted back verbatim.\n * @param redirectUri - loopback callback URI (harness webServer route or CLI listener).\n * @param projectId - optional Antigravity project id to bind this login to.\n */\nexport async function authorizeAntigravity(\n redirectUri: string,\n projectId = '',\n): Promise<AntigravityAuthorization> {\n const { verifier, challenge } = generatePkcePair()\n\n const url = new URL(OAUTH_AUTHORIZE_URL)\n url.searchParams.set('client_id', AGY_CLIENT_ID)\n url.searchParams.set('response_type', 'code')\n url.searchParams.set('redirect_uri', redirectUri)\n url.searchParams.set('scope', AGY_SCOPES.join(' '))\n url.searchParams.set('code_challenge', challenge)\n url.searchParams.set('code_challenge_method', 'S256')\n const state = encodeState({ verifier, projectId: projectId || '' })\n url.searchParams.set('state', state)\n url.searchParams.set('access_type', 'offline')\n url.searchParams.set('prompt', 'consent')\n\n return { url: url.toString(), verifier, projectId: projectId || '', state }\n}\n","/**\n * Import agy credentials: parse the `agy` CLI auth.json token file or a\n * paste credential blob, enrich with the Code Assist backend (email/project),\n * and persist into the account store. Adapted from OmniRoute's agyAuthImport\n * (MIT, see NOTICE.md).\n */\n\nimport { AGY_ENDPOINT_FALLBACKS, getAgyBootstrapClientMetadata, getAgyBootstrapUserAgent } from '../oauth/constants.ts'\nimport { decodeCredentialBlob } from '../oauth/blob.ts'\nimport type { AccountStore } from '../store/accounts.ts'\nimport type { ManagedAccount } from '../types.ts'\n\nexport class AgyAuthFileError extends Error {\n status: number\n code: string\n\n constructor(message: string, status = 400, code = 'invalid_request') {\n super(message)\n this.name = 'AgyAuthFileError'\n this.status = status\n this.code = code\n }\n}\n\nexport interface ParsedAgyAuth {\n accessToken: string\n refreshToken: string\n tokenType: string\n expiresAt: string | null\n authMethod: string | null\n}\n\nexport interface EnrichedAgyAuth extends ParsedAgyAuth {\n email: string | null\n projectId: string | null\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : {}\n}\n\nfunction toNonEmptyString(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed ? trimmed : null\n}\n\n/**\n * Parse the agy CLI token file: tokens nest under `.token` with an ISO\n * `expiry` string and no id_token; a flat top-level shape is accepted too.\n */\nexport function parseAndValidateAgyToken(raw: unknown): ParsedAgyAuth {\n const doc = toRecord(raw)\n const token = toRecord(doc.token ?? doc)\n\n const accessToken = toNonEmptyString(token.access_token)\n const refreshToken = toNonEmptyString(token.refresh_token)\n if (!accessToken) throw new AgyAuthFileError('access_token is missing or empty in the agy token file', 400, 'missing_access_token')\n if (!refreshToken) throw new AgyAuthFileError('refresh_token is missing or empty in the agy token file', 400, 'missing_refresh_token')\n\n let expiresAt: string | null = null\n const isoExpiry = toNonEmptyString(token.expiry) ?? toNonEmptyString(token.expires_at)\n if (isoExpiry) {\n const ms = new Date(isoExpiry).getTime()\n expiresAt = Number.isNaN(ms) ? null : new Date(ms).toISOString()\n } else if (typeof token.expiry_date === 'number' && Number.isFinite(token.expiry_date)) {\n expiresAt = new Date(token.expiry_date).toISOString()\n }\n\n return {\n accessToken,\n refreshToken,\n tokenType: toNonEmptyString(token.token_type) ?? 'Bearer',\n expiresAt,\n authMethod: toNonEmptyString(doc.auth_method) ?? toNonEmptyString(token.auth_method),\n }\n}\n\n/** Best-effort enrichment: email (userinfo) + projectId (loadCodeAssist), time-boxed. */\nexport async function enrichWithAntigravityBackend(parsed: ParsedAgyAuth): Promise<EnrichedAgyAuth> {\n let email: string | null = null\n let projectId: string | null = null\n\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), 8000)\n try {\n const res = await fetch('https://www.googleapis.com/oauth2/v1/userinfo?alt=json', {\n headers: { Authorization: `Bearer ${parsed.accessToken}` },\n signal: controller.signal,\n })\n if (res.ok) {\n email = toNonEmptyString(toRecord(await res.json()).email)\n }\n } catch {\n // best effort — email stays null\n } finally {\n clearTimeout(timer)\n }\n\n const loadController = new AbortController()\n const loadTimer = setTimeout(() => loadController.abort(), 8000)\n try {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${parsed.accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n 'Client-Metadata': getAgyBootstrapClientMetadata(),\n }\n for (const endpoint of AGY_ENDPOINT_FALLBACKS) {\n try {\n const res = await fetch(`${endpoint}/v1internal:loadCodeAssist`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ metadata: { ideType: 'ANTIGRAVITY' } }),\n signal: loadController.signal,\n })\n if (!res.ok) continue\n const data = toRecord(await res.json())\n const project = data.cloudaicompanionProject\n projectId = (typeof project === 'string' ? toNonEmptyString(project) : null)\n ?? toNonEmptyString(toRecord(project).id)\n if (projectId) break\n } catch {\n // try next endpoint\n }\n }\n } catch {\n // best effort — projectId stays null\n } finally {\n clearTimeout(loadTimer)\n }\n\n return { ...parsed, email, projectId }\n}\n\n/** Parse either a raw token document or a paste blob into an enriched account. */\nexport async function parseImportSource(source: unknown, kind: 'json' | 'blob'): Promise<EnrichedAgyAuth> {\n if (kind === 'blob') {\n if (typeof source !== 'string') throw new AgyAuthFileError('blob must be a string', 400, 'invalid_blob')\n const blob = decodeCredentialBlob(source)\n return enrichWithAntigravityBackend(\n parseAndValidateAgyToken({ token: blob.tokens, auth_method: 'paste-blob' }),\n )\n }\n return enrichWithAntigravityBackend(parseAndValidateAgyToken(source))\n}\n\n/** Upsert the account into the store, deduping by email. */\nexport async function upsertImportedAccount(\n store: AccountStore,\n enriched: EnrichedAgyAuth,\n options: { email?: string; overwriteExisting?: boolean } = {},\n): Promise<{ account: ManagedAccount; created: boolean }> {\n const resolvedEmail = options.email || enriched.email\n const refresh = `${enriched.refreshToken}|${enriched.projectId ?? ''}`\n\n return store.mutate((storage) => {\n const existingIndex = resolvedEmail\n ? storage.accounts.findIndex((a) => a.email?.toLowerCase() === resolvedEmail.toLowerCase())\n : -1\n\n if (existingIndex !== -1) {\n if (!options.overwriteExisting) {\n throw new AgyAuthFileError(\n 'An agy account for this email already exists. Pass --overwrite to replace it.',\n 409,\n 'duplicate_account',\n )\n }\n const existing = storage.accounts[existingIndex]!\n storage.accounts[existingIndex] = {\n ...existing,\n refresh,\n email: existing.email ?? resolvedEmail ?? undefined,\n addedAt: existing.addedAt,\n lastUsed: Date.now(),\n enabled: true,\n verificationRequired: false,\n verificationRequiredAt: undefined,\n verificationRequiredReason: undefined,\n }\n return { account: storage.accounts[existingIndex]!, created: false }\n }\n\n const account: ManagedAccount = {\n email: resolvedEmail ?? undefined,\n refresh,\n projectId: enriched.projectId ?? undefined,\n addedAt: Date.now(),\n lastUsed: Date.now(),\n enabled: true,\n }\n storage.accounts.push(account)\n if (storage.activeIndex >= storage.accounts.length - 1 && storage.accounts.length === 1) {\n storage.activeIndex = 0\n }\n return { account, created: true }\n })\n}\n"],"mappings":"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"}
|
package/lib/index.d.mts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
2
|
+
//#region src/index.d.ts
|
|
3
|
+
declare const name = "dsh-agy";
|
|
4
|
+
declare const inject: string[];
|
|
5
|
+
declare function apply(ctx: Context): void;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { apply, inject, name };
|
|
8
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;cAUa;cAEA;iBAEG,MAAM,KAAK"}
|
package/lib/index.mjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { t as createAgyRuntime } from "./plugin-common-I56kmGjr.mjs";
|
|
2
|
+
//#region src/index.ts
|
|
3
|
+
const name = "dsh-agy";
|
|
4
|
+
const inject = ["llm"];
|
|
5
|
+
function apply(ctx) {
|
|
6
|
+
ctx.effect(async () => {
|
|
7
|
+
const { adapter } = await createAgyRuntime(ctx);
|
|
8
|
+
const registration = ctx.llm.registerAdapter(["agy"], adapter);
|
|
9
|
+
return () => registration();
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { apply, inject, name };
|
|
14
|
+
|
|
15
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * dsh-agy main plugin entry: registers the `agy` LLM provider route and\n * initializes the account store. The /agy web UI is registered by the sibling\n * `dsh-agy/web` entry (it must wait for ctx.webServer to activate). No plugin\n * Config — all product decisions are fixed by design (no user configuration surface).\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport { AGY_PROVIDER, createAgyRuntime } from './plugin-common.ts'\n\nexport const name = 'dsh-agy'\n\nexport const inject = ['llm']\n\nexport function apply(ctx: Context): void {\n ctx.effect(async () => {\n const { adapter } = await createAgyRuntime(ctx)\n const registration = ctx.llm.registerAdapter([AGY_PROVIDER], adapter)\n return () => registration()\n })\n}\n"],"mappings":";;AAUA,MAAa,OAAO;AAEpB,MAAa,SAAS,CAAC,KAAK;AAE5B,SAAgB,MAAM,KAAoB;CACxC,IAAI,OAAO,YAAY;EACrB,MAAM,EAAE,YAAY,MAAM,iBAAiB,GAAG;EAC9C,MAAM,eAAe,IAAI,IAAI,gBAAgB,CAAA,KAAa,GAAG,OAAO;EACpE,aAAa,aAAa;CAC5B,CAAC;AACH"}
|