pub const PROGRAM: &str = r#""use strict";
const fs = require("node:fs");
const path = require("node:path");
const { DatabaseSync } = require("node:sqlite");
const KEYS = ["antigravityUnifiedStateSync.oauthToken", "antigravityUnifiedStateSync.userStatus"];
const [mode, from, to] = process.argv.slice(1);
const text = (value) => (typeof value === "string" ? value : Buffer.from(value).toString("utf8"));
const fields = (bytes) => {
const out = [];
let at = 0;
const varint = () => {
let value = 0n;
let shift = 0n;
for (;;) {
const byte = bytes[at++];
if (byte === undefined) throw new Error("short");
value |= BigInt(byte & 127) << shift;
shift += 7n;
if (byte < 128) return value;
}
};
while (at < bytes.length) {
const tag = Number(varint());
const kind = tag & 7;
if (kind === 0) out.push([tag >> 3, varint()]);
else if (kind === 2) {
const length = Number(varint());
if (at + length > bytes.length) throw new Error("short");
out.push([tag >> 3, bytes.subarray(at, at + length)]);
at += length;
} else if (kind === 1) at += 8;
else if (kind === 5) at += 4;
else throw new Error("wire type");
}
return out;
};
const field = (message, wanted) => {
try {
for (const [number, value] of fields(Buffer.from(message, "base64"))) {
if (number === wanted && typeof value !== "bigint") return text(value);
}
} catch (error) {
return "";
}
return "";
};
const entry = (topic, wanted) => {
try {
for (const [number, data] of fields(Buffer.from(topic, "base64"))) {
if (number !== 1 || typeof data === "bigint") continue;
let key = "";
let row = null;
for (const [part, value] of fields(data)) {
if (part === 1 && typeof value !== "bigint") key = text(value);
if (part === 2 && typeof value !== "bigint") row = value;
}
if (key === wanted && row) {
for (const [part, value] of fields(row)) if (part === 1 && typeof value !== "bigint") return text(value);
}
}
} catch (error) {
return "";
}
return "";
};
const entries = (topic) => {
const out = [];
try {
for (const [number, data] of fields(Buffer.from(topic, "base64"))) {
if (number !== 1 || typeof data === "bigint") continue;
let key = "";
let value = "";
for (const [part, piece] of fields(data)) {
if (part === 1 && typeof piece !== "bigint") key = text(piece);
if (part === 2 && typeof piece !== "bigint") {
for (const [held, row] of fields(piece)) if (held === 1 && typeof row !== "bigint") value = text(row);
}
}
if (key) out.push([key, value]);
}
} catch (error) {
return [];
}
return out;
};
const token = (login) => entry(login[KEYS[0]] || "", "oauthTokenInfoSentinelKey");
const signed = (login) => field(token(login), 1).length > 0 || field(token(login), 3).length > 0;
const account = (login) => field(entry(login[KEYS[1]] || "", "userStatusSentinelKey"), 7);
const whole = (login) => signed(login) && account(login).length > 0;
const read = (file) => {
const found = {};
if (!fs.existsSync(file)) return found;
const db = new DatabaseSync(file, { readOnly: true });
try {
const row = db.prepare("SELECT value FROM ItemTable WHERE key = ?");
for (const key of KEYS) {
const hit = row.get(key);
if (hit && hit.value !== null && hit.value !== undefined) found[key] = text(hit.value);
}
} catch (error) {
return {};
} finally {
db.close();
}
return found;
};
const varint = (number) => {
const out = [];
while (number > 127) {
out.push((number & 127) | 128);
number = Math.floor(number / 128);
}
out.push(number);
return out;
};
const bytes = (number, value) => [...varint((number << 3) | 2), ...varint(value.length), ...value];
const utf8 = (value) => [...Buffer.from(value, "utf8")];
const base64 = (value) => Buffer.from(value).toString("base64");
const topic = (entries) => base64(entries.flatMap(([key, value]) => bytes(1, [...bytes(1, utf8(key)), ...bytes(2, bytes(1, utf8(value)))])));
const whole_number = (number, value) => base64([...varint(number << 3), ...varint(value)]);
const AGENT = "antigravityUnifiedStateSync.agentPreferences";
const BROWSER = "antigravityUnifiedStateSync.browserPreferences";
const ONBOARDING = "antigravityOnboarding";
// What the agent panel asks of the agent: `Primitive.int32_value` (2) 3 is EAGER, 2 is TURBO,
// `bool_value` (1) true lets it at the files outside the workspace, and
// `PermissionGrantsConfig.allow` (1) is what the language server reads for everything.
const ANSWERS = {
terminalAutoExecutionPolicySentinelKey: whole_number(2, 3),
artifactReviewPolicySentinelKey: whole_number(2, 2),
allowAgentAccessNonWorkspaceFilesSentinelKey: base64([8, 1]),
};
// What the language server itself asks for to be told "everything": each action with the `*`
// target. One of them is `execute_url(localhost)`, the address of the workspace itself.
const EVERY = ["read_file(*)", "write_file(*)", "command(*)", "unsandboxed(*)", "mcp(*)", "read_url(*)", "execute_url(*)"];
const number = (message, wanted) => {
try {
for (const [at, value] of fields(Buffer.from(message, "base64"))) if (at === wanted && typeof value === "bigint") return String(value);
} catch (error) {
return "";
}
return "";
};
const listed = (message, wanted) => {
const out = [];
try {
for (const [at, value] of fields(Buffer.from(message, "base64"))) if (at === wanted && typeof value !== "bigint") out.push(text(value));
} catch (error) {
return [];
}
return out;
};
// The grants as the application reads them: what is allowed, then what is refused, and never an
// ask, since an ask is a question the person would have to be there for.
const grants = (allow, deny) => base64([
...allow.flatMap((one) => bytes(1, utf8(one))),
...deny.flatMap((one) => bytes(2, utf8(one))),
]);
// A topic with `wanted` in it and every other entry of the home exactly as it was: the
// settings the application keeps beside the approvals, such as the planning mode, are not ours to
// touch and would be lost by a row written from scratch.
const merged = (held, wanted) => {
const kept = entries(held);
const out = [];
for (const [key, value] of kept) out.push([key, wanted[key] === undefined ? value : wanted[key]]);
for (const [key, value] of Object.entries(wanted)) {
if (!kept.some(([was]) => was === key)) out.push([key, value]);
}
return topic(out);
};
const prepared = (db) => {
db.exec("CREATE TABLE IF NOT EXISTS ItemTable (key TEXT UNIQUE ON CONFLICT REPLACE, value BLOB)");
return db.prepare("INSERT OR REPLACE INTO ItemTable (key, value) VALUES (?, ?)");
};
const row = (file, key) => {
if (!fs.existsSync(file)) return "";
const db = new DatabaseSync(file, { readOnly: true });
try {
const hit = db.prepare("SELECT value FROM ItemTable WHERE key = ?").get(key);
return hit && hit.value !== null && hit.value !== undefined ? text(hit.value) : "";
} catch (error) {
return "";
} finally {
db.close();
}
};
if (mode === "approvals") {
const agent = row(from, AGENT);
const browser = row(from, BROWSER);
const granted = entry(agent, "permission_grants_global");
console.log("terminal=" + number(entry(agent, "terminalAutoExecutionPolicySentinelKey"), 2));
console.log("review=" + number(entry(agent, "artifactReviewPolicySentinelKey"), 2));
console.log("javascript=" + number(entry(browser, "browser_js_execution_config_sentinel_key"), 1));
console.log("files=" + number(entry(agent, "allowAgentAccessNonWorkspaceFilesSentinelKey"), 1));
console.log("allow=" + listed(granted, 1).join(","));
console.log("ask=" + listed(granted, 3).join(","));
console.log("onboarding=" + row(from, ONBOARDING));
process.exit(0);
}
if (mode === "seen") process.exit(whole(read(from)) ? 0 : 1);
if (mode === "take") {
const login = read(from);
if (!whole(login)) process.exit(1);
fs.mkdirSync(path.dirname(to), { recursive: true });
fs.writeFileSync(to + ".part", JSON.stringify(login), { mode: 0o600 });
fs.renameSync(to + ".part", to);
process.exit(0);
}
if (mode === "keep" || mode === "replace") {
const login = JSON.parse(fs.readFileSync(from, "utf8"));
if (!whole(login)) process.exit(1);
if (mode === "keep" && signed(read(to))) process.exit(0);
fs.mkdirSync(path.dirname(to), { recursive: true });
const db = new DatabaseSync(to);
const put = prepared(db);
for (const key of KEYS) put.run(key, login[key]);
db.close();
process.exit(0);
}
if (mode === "approve") {
fs.mkdirSync(path.dirname(from), { recursive: true });
const db = new DatabaseSync(from);
const put = prepared(db);
const agent = row(from, AGENT);
const granted = entry(agent, "permission_grants_global");
const allow = listed(granted, 1);
put.run(AGENT, merged(agent, {
...ANSWERS,
permission_grants_global: grants([...allow, ...EVERY.filter((one) => !allow.includes(one))], listed(granted, 2)),
}));
put.run(BROWSER, merged(row(from, BROWSER), { browser_js_execution_config_sentinel_key: whole_number(1, 4) }));
put.run(ONBOARDING, "true");
db.close();
process.exit(0);
}
process.exit(2);
"#;Expand description
The program that reads and writes the two rows, run by Node inside a container.
A row being there is not a login: the application writes the token’s row as soon as it has
looked whether it is signed in, holding only “signed out” when it is not (measured: a window
that never signed in had the row, with authStateWithContextSentinelKey in it and no token).
So the rows are read the way the application reads them. Each is a Topic of the application’s
state sync, data (1) a map of key (1) to a Row whose value (1) is the entry, all written
as base64 of the protocol buffer (exa.unified_state_sync_pb, read out of the descriptors in
main.js). A login is the token row’s oauthTokenInfoSentinelKey entry, an OAuthTokenInfo
with an access (1) or refresh (3) token, beside the account row’s userStatusSentinelKey
entry, a UserStatus with an e-mail (7) — the two things the extension asks for.
With a third word approve, the agent’s own approvals are merged into the database: what makes
the window’s agent run a command, write a file, go past a plan and act in its browser without
asking, and what the language server is told it may do on its own — see ANSWERED for the
rows and approve_command for where the program is run. A database that is not there is made
with them, as keep and replace make it for a login. approvals <database> prints every one
of those settings as the application would read them, one name=value line each, empty for one
that is not there.
seen <database> answers 0 when there is a login; take <database> <file> writes the two rows
into the file and fails when there is none; keep <file> <database> puts them into the database
unless it holds a token of its own, and replace <file> <database> puts them in whatever it
holds. A database that is not there is made, with the table the application makes. Written with
double quotes only, so that a shell can carry it inside single ones.