jeo-code 0.1.0 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +160 -0
- package/README.ko.md +160 -0
- package/README.md +115 -297
- package/README.zh.md +160 -0
- package/package.json +11 -6
- package/scripts/install.sh +28 -28
- package/scripts/uninstall.sh +17 -15
- package/src/AGENTS.md +50 -0
- package/src/agent/AGENTS.md +49 -0
- package/src/agent/bash-fixups.ts +103 -0
- package/src/agent/compaction.ts +410 -19
- package/src/agent/config-schema.ts +119 -5
- package/src/agent/context-files.ts +314 -17
- package/src/agent/dev/AGENTS.md +36 -0
- package/src/agent/dev/advanced-analyzer.ts +12 -0
- package/src/agent/dev/evolution-bridge.ts +82 -0
- package/src/agent/dev/evolution-logger.ts +41 -0
- package/src/agent/dev/self-analysis.ts +64 -0
- package/src/agent/dev/self-improve.ts +24 -0
- package/src/agent/dev/spec-automation.ts +49 -0
- package/src/agent/engine.ts +808 -54
- package/src/agent/hooks.ts +273 -0
- package/src/agent/loop.ts +21 -1
- package/src/agent/memory.ts +201 -0
- package/src/agent/model-recency.ts +32 -0
- package/src/agent/output-minimizer.ts +108 -0
- package/src/agent/output-util.ts +64 -0
- package/src/agent/plan.ts +187 -0
- package/src/agent/seed.ts +52 -0
- package/src/agent/session.ts +235 -21
- package/src/agent/state.ts +286 -39
- package/src/agent/step-budget.ts +232 -0
- package/src/agent/subagents.ts +223 -26
- package/src/agent/task-tool.ts +272 -0
- package/src/agent/todo-tool.ts +87 -0
- package/src/agent/tokenizer.ts +117 -0
- package/src/agent/tool-registry.ts +54 -0
- package/src/agent/tools.ts +624 -103
- package/src/agent/web-search.ts +538 -0
- package/src/ai/AGENTS.md +44 -0
- package/src/ai/index.ts +1 -0
- package/src/ai/model-catalog-compat.ts +3 -1
- package/src/ai/model-catalog.ts +74 -9
- package/src/ai/model-discovery.ts +215 -17
- package/src/ai/model-manager.ts +346 -32
- package/src/ai/model-picker.ts +1 -1
- package/src/ai/model-registry.ts +4 -2
- package/src/ai/pricing.ts +84 -0
- package/src/ai/provider-registry.ts +23 -0
- package/src/ai/provider-status.ts +60 -16
- package/src/ai/providers/AGENTS.md +42 -0
- package/src/ai/providers/anthropic.ts +250 -31
- package/src/ai/providers/antigravity.ts +219 -0
- package/src/ai/providers/errors.ts +15 -1
- package/src/ai/providers/gemini.ts +196 -13
- package/src/ai/providers/ollama.ts +37 -7
- package/src/ai/providers/openai-responses.ts +173 -0
- package/src/ai/providers/openai.ts +64 -12
- package/src/ai/sse.ts +4 -1
- package/src/ai/types.ts +18 -1
- package/src/auth/AGENTS.md +41 -0
- package/src/auth/callback-server.ts +6 -1
- package/src/auth/flows/AGENTS.md +32 -0
- package/src/auth/flows/antigravity.ts +151 -0
- package/src/auth/flows/google-project.ts +190 -0
- package/src/auth/flows/google.ts +39 -18
- package/src/auth/flows/index.ts +15 -5
- package/src/auth/flows/openai.ts +2 -2
- package/src/auth/oauth.ts +8 -0
- package/src/auth/refresh.ts +44 -27
- package/src/auth/storage.ts +149 -26
- package/src/auth/types.ts +1 -1
- package/src/autopilot.ts +362 -0
- package/src/bun-imports.d.ts +4 -0
- package/src/cli/AGENTS.md +39 -0
- package/src/cli/runner.ts +148 -14
- package/src/cli.ts +13 -4
- package/src/commands/AGENTS.md +40 -0
- package/src/commands/approve.ts +62 -3
- package/src/commands/auth.ts +167 -25
- package/src/commands/chat.ts +37 -8
- package/src/commands/deep-interview.ts +633 -175
- package/src/commands/doctor.ts +84 -37
- package/src/commands/evolve-core.ts +18 -0
- package/src/commands/evolve.ts +2 -1
- package/src/commands/export.ts +176 -0
- package/src/commands/gjc.ts +52 -0
- package/src/commands/launch.ts +3549 -240
- package/src/commands/mcp.ts +3 -3
- package/src/commands/ooo-seed.ts +19 -0
- package/src/commands/ralplan.ts +253 -35
- package/src/commands/resume.ts +1 -1
- package/src/commands/session.ts +183 -0
- package/src/commands/setup-helpers.ts +10 -3
- package/src/commands/setup.ts +57 -16
- package/src/commands/skills.ts +78 -18
- package/src/commands/state.ts +198 -0
- package/src/commands/status.ts +84 -0
- package/src/commands/team.ts +340 -212
- package/src/commands/ultragoal.ts +122 -61
- package/src/commands/update.ts +244 -0
- package/src/ledger.ts +270 -0
- package/src/mcp/AGENTS.md +38 -0
- package/src/mcp/server.ts +115 -14
- package/src/mcp/tools.ts +42 -22
- package/src/md-modules.d.ts +4 -0
- package/src/prompts/AGENTS.md +41 -0
- package/src/prompts/agents/AGENTS.md +35 -0
- package/src/prompts/agents/architect.md +35 -0
- package/src/prompts/agents/critic.md +37 -0
- package/src/prompts/agents/executor.md +36 -0
- package/src/prompts/agents/planner.md +37 -0
- package/src/prompts/skills/AGENTS.md +36 -0
- package/src/prompts/skills/deep-dive/AGENTS.md +31 -0
- package/src/prompts/skills/deep-dive/SKILL.md +13 -0
- package/src/prompts/skills/deep-interview/AGENTS.md +31 -0
- package/src/prompts/skills/deep-interview/SKILL.md +12 -0
- package/src/prompts/skills/gjc/AGENTS.md +31 -0
- package/src/prompts/skills/gjc/SKILL.md +15 -0
- package/src/prompts/skills/ralplan/AGENTS.md +31 -0
- package/src/prompts/skills/ralplan/SKILL.md +11 -0
- package/src/prompts/skills/team/AGENTS.md +31 -0
- package/src/prompts/skills/team/SKILL.md +11 -0
- package/src/prompts/skills/ultragoal/AGENTS.md +31 -0
- package/src/prompts/skills/ultragoal/SKILL.md +11 -0
- package/src/skills/AGENTS.md +38 -0
- package/src/skills/catalog.ts +565 -31
- package/src/tui/AGENTS.md +43 -0
- package/src/tui/app.ts +1181 -92
- package/src/tui/components/AGENTS.md +42 -0
- package/src/tui/components/ascii-art.ts +257 -15
- package/src/tui/components/autocomplete.ts +98 -16
- package/src/tui/components/autopilot-status.ts +65 -0
- package/src/tui/components/category-index.ts +49 -0
- package/src/tui/components/code-view.ts +54 -11
- package/src/tui/components/color.ts +171 -2
- package/src/tui/components/config-panel.ts +82 -15
- package/src/tui/components/duration.ts +38 -0
- package/src/tui/components/evolution.ts +3 -3
- package/src/tui/components/footer.ts +91 -42
- package/src/tui/components/forge.ts +426 -31
- package/src/tui/components/hints.ts +54 -0
- package/src/tui/components/hud.ts +73 -0
- package/src/tui/components/index.ts +4 -0
- package/src/tui/components/input-box.ts +150 -0
- package/src/tui/components/layout.ts +11 -3
- package/src/tui/components/live-model-picker.ts +108 -0
- package/src/tui/components/markdown-table.ts +140 -0
- package/src/tui/components/markdown-text.ts +97 -0
- package/src/tui/components/meter.ts +4 -1
- package/src/tui/components/model-picker.ts +3 -2
- package/src/tui/components/provider-picker.ts +3 -2
- package/src/tui/components/section.ts +70 -0
- package/src/tui/components/select-list.ts +40 -10
- package/src/tui/components/skill-picker.ts +25 -0
- package/src/tui/components/slash.ts +244 -21
- package/src/tui/components/status.ts +272 -11
- package/src/tui/components/step-timeline.ts +218 -0
- package/src/tui/components/stream.ts +26 -9
- package/src/tui/components/themes.ts +212 -6
- package/src/tui/components/todo-card.ts +47 -0
- package/src/tui/components/tool-list.ts +58 -12
- package/src/tui/components/transcript.ts +120 -0
- package/src/tui/components/update-box.ts +31 -0
- package/src/tui/components/welcome.ts +162 -0
- package/src/tui/components/width.ts +163 -0
- package/src/tui/monitoring/AGENTS.md +31 -0
- package/src/tui/monitoring/hud-view.ts +55 -0
- package/src/tui/renderer.ts +112 -3
- package/src/tui/terminal.ts +40 -33
- package/src/util/AGENTS.md +39 -0
- package/src/util/clipboard-image.ts +118 -0
- package/src/util/env.ts +12 -0
- package/src/util/provider-error.ts +78 -0
- package/src/util/retry.ts +91 -6
- package/src/util/update-check.ts +64 -0
- package/src/commands/models.ts +0 -104
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Cloud Code Assist project discovery — gjc parity.
|
|
3
|
+
*
|
|
4
|
+
* gemini-cli style: POST v1internal:loadCodeAssist to find an existing
|
|
5
|
+
* cloudaicompanionProject; when the account has none, onboard the default
|
|
6
|
+
* (free) tier via v1internal:onboardUser and poll the long-running operation
|
|
7
|
+
* until a managed project id is provisioned. This is how gemini-cli itself
|
|
8
|
+
* obtains a project id, so plain `jeo auth login gemini` users get Antigravity
|
|
9
|
+
* access without ever setting GOOGLE_CLOUD_PROJECT.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const CODE_ASSIST_ENDPOINT = "https://cloudcode-pa.googleapis.com";
|
|
13
|
+
const TIER_FREE = "free-tier";
|
|
14
|
+
const TIER_LEGACY = "legacy-tier";
|
|
15
|
+
const TIER_STANDARD = "standard-tier";
|
|
16
|
+
const DEFAULT_MAX_POLL_ATTEMPTS = 5;
|
|
17
|
+
const POLL_INTERVAL_MS = 2_000;
|
|
18
|
+
|
|
19
|
+
const GEMINI_CLI_METADATA = Object.freeze({
|
|
20
|
+
ideType: "IDE_UNSPECIFIED",
|
|
21
|
+
platform: "PLATFORM_UNSPECIFIED",
|
|
22
|
+
pluginType: "GEMINI",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/** Antigravity desktop-app discovery metadata (gjc parity). */
|
|
26
|
+
export const ANTIGRAVITY_DISCOVERY_METADATA = Object.freeze({
|
|
27
|
+
ideType: "ANTIGRAVITY",
|
|
28
|
+
platform: "PLATFORM_UNSPECIFIED",
|
|
29
|
+
pluginType: "GEMINI",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
interface LoadCodeAssistPayload {
|
|
33
|
+
cloudaicompanionProject?: string | { id?: string };
|
|
34
|
+
currentTier?: { id?: string };
|
|
35
|
+
allowedTiers?: Array<{ id?: string; isDefault?: boolean }>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface LongRunningOperationResponse {
|
|
39
|
+
name?: string;
|
|
40
|
+
done?: boolean;
|
|
41
|
+
response?: { cloudaicompanionProject?: string | { id?: string } };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface DiscoverProjectOptions {
|
|
45
|
+
fetchImpl?: typeof fetch;
|
|
46
|
+
sleep?: (ms: number) => Promise<void>;
|
|
47
|
+
env?: Record<string, string | undefined>;
|
|
48
|
+
onProgress?: (message: string) => void;
|
|
49
|
+
maxPollAttempts?: number;
|
|
50
|
+
/** Discovery metadata; defaults to the gemini-cli shape. Antigravity logins pass ANTIGRAVITY_DISCOVERY_METADATA. */
|
|
51
|
+
metadata?: Record<string, string>;
|
|
52
|
+
/** Extra request headers (e.g. the Antigravity User-Agent). */
|
|
53
|
+
extraHeaders?: Record<string, string>;
|
|
54
|
+
/** Outer abort (the turn's signal) — composed with the per-request timeout. */
|
|
55
|
+
signal?: AbortSignal;
|
|
56
|
+
/** Per-request deadline; a stalled discovery fetch must NEVER hang the turn
|
|
57
|
+
* forever (round-5 #2 — this path runs BEFORE the manager's guarded call). */
|
|
58
|
+
requestTimeoutMs?: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const DISCOVERY_REQUEST_TIMEOUT_MS = 30_000;
|
|
62
|
+
|
|
63
|
+
/** Per-request signal: outer turn abort + bounded timeout (whichever first). */
|
|
64
|
+
function boundedSignal(outer: AbortSignal | undefined, timeoutMs: number): AbortSignal {
|
|
65
|
+
const timer = AbortSignal.timeout(timeoutMs);
|
|
66
|
+
if (!outer) return timer;
|
|
67
|
+
return typeof AbortSignal.any === "function" ? AbortSignal.any([outer, timer]) : timer;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function readProjectId(value: string | { id?: string } | undefined): string | undefined {
|
|
71
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
72
|
+
if (value && typeof value === "object" && typeof value.id === "string" && value.id.length > 0) return value.id;
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function defaultTierId(allowedTiers?: Array<{ id?: string; isDefault?: boolean }>): string {
|
|
77
|
+
if (!allowedTiers || allowedTiers.length === 0) return TIER_LEGACY;
|
|
78
|
+
const def = allowedTiers.find(t => t.isDefault && typeof t.id === "string" && t.id.length > 0);
|
|
79
|
+
return def?.id ?? TIER_LEGACY;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Workspace/enterprise accounts cannot be auto-provisioned — surface the documented fix. */
|
|
83
|
+
const WORKSPACE_PROJECT_HINT =
|
|
84
|
+
"This Google account requires an explicit project: set GOOGLE_CLOUD_PROJECT or GOOGLE_CLOUD_PROJECT_ID " +
|
|
85
|
+
"(see https://goo.gle/gemini-cli-auth-docs#workspace-gca), then retry.";
|
|
86
|
+
|
|
87
|
+
function isVpcScAffectedUser(payload: unknown): boolean {
|
|
88
|
+
if (!payload || typeof payload !== "object" || !("error" in payload)) return false;
|
|
89
|
+
const details = (payload as { error?: { details?: Array<{ reason?: string }> } }).error?.details;
|
|
90
|
+
return Array.isArray(details) && details.some(d => d.reason === "SECURITY_POLICY_VIOLATED");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Discover (or provision) the Cloud Code Assist project for a Google OAuth
|
|
95
|
+
* access token. Returns the project id; throws with actionable guidance when
|
|
96
|
+
* the account genuinely needs a user-supplied project.
|
|
97
|
+
*/
|
|
98
|
+
export async function discoverGoogleProjectId(
|
|
99
|
+
accessToken: string,
|
|
100
|
+
opts: DiscoverProjectOptions = {},
|
|
101
|
+
): Promise<string> {
|
|
102
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
103
|
+
const sleep = opts.sleep ?? ((ms: number) => new Promise<void>(r => setTimeout(r, ms)));
|
|
104
|
+
const env = opts.env ?? process.env;
|
|
105
|
+
const maxPollAttempts = opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS;
|
|
106
|
+
const envProjectId = env.GOOGLE_CLOUD_PROJECT || env.GOOGLE_CLOUD_PROJECT_ID || undefined;
|
|
107
|
+
const metadata = opts.metadata ?? GEMINI_CLI_METADATA;
|
|
108
|
+
const requestTimeoutMs = opts.requestTimeoutMs ?? DISCOVERY_REQUEST_TIMEOUT_MS;
|
|
109
|
+
const nextSignal = () => boundedSignal(opts.signal, requestTimeoutMs);
|
|
110
|
+
|
|
111
|
+
const headers: Record<string, string> = {
|
|
112
|
+
authorization: `Bearer ${accessToken}`,
|
|
113
|
+
"content-type": "application/json",
|
|
114
|
+
...(opts.extraHeaders ?? {}),
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
opts.onProgress?.("Checking for an existing Cloud Code Assist project…");
|
|
118
|
+
const loadRes = await fetchImpl(`${CODE_ASSIST_ENDPOINT}/v1internal:loadCodeAssist`, {
|
|
119
|
+
method: "POST",
|
|
120
|
+
headers,
|
|
121
|
+
body: JSON.stringify({
|
|
122
|
+
cloudaicompanionProject: envProjectId,
|
|
123
|
+
metadata: { ...metadata, duetProject: envProjectId },
|
|
124
|
+
}),
|
|
125
|
+
signal: nextSignal(),
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
let data: LoadCodeAssistPayload;
|
|
129
|
+
if (!loadRes.ok) {
|
|
130
|
+
let errorPayload: unknown;
|
|
131
|
+
try {
|
|
132
|
+
errorPayload = await loadRes.clone().json();
|
|
133
|
+
} catch {
|
|
134
|
+
errorPayload = undefined;
|
|
135
|
+
}
|
|
136
|
+
if (isVpcScAffectedUser(errorPayload)) {
|
|
137
|
+
// VPC-SC blocks loadCodeAssist but chat still works on the standard tier.
|
|
138
|
+
data = { currentTier: { id: TIER_STANDARD } };
|
|
139
|
+
} else {
|
|
140
|
+
throw new Error(`loadCodeAssist failed (HTTP ${loadRes.status}): ${await loadRes.text()}`);
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
data = (await loadRes.json()) as LoadCodeAssistPayload;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Already onboarded: the payload names the project (or the env pins it).
|
|
147
|
+
if (data.currentTier) {
|
|
148
|
+
const existing = readProjectId(data.cloudaicompanionProject) ?? envProjectId;
|
|
149
|
+
if (existing) return existing;
|
|
150
|
+
throw new Error(WORKSPACE_PROJECT_HINT);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Not onboarded yet: provision the default tier (free tier needs no project).
|
|
154
|
+
const tierId = defaultTierId(data.allowedTiers) || TIER_FREE;
|
|
155
|
+
if (tierId !== TIER_FREE && tierId !== TIER_LEGACY && !envProjectId) {
|
|
156
|
+
throw new Error(WORKSPACE_PROJECT_HINT);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
opts.onProgress?.("Provisioning a Cloud Code Assist project (one-time, may take a moment)…");
|
|
160
|
+
const onboardBody: Record<string, unknown> = { tierId, metadata: { ...metadata } };
|
|
161
|
+
if (envProjectId && tierId !== TIER_FREE) {
|
|
162
|
+
onboardBody.cloudaicompanionProject = envProjectId;
|
|
163
|
+
(onboardBody.metadata as Record<string, unknown>).duetProject = envProjectId;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const onboardRes = await fetchImpl(`${CODE_ASSIST_ENDPOINT}/v1internal:onboardUser`, {
|
|
167
|
+
method: "POST",
|
|
168
|
+
headers,
|
|
169
|
+
body: JSON.stringify(onboardBody),
|
|
170
|
+
signal: nextSignal(),
|
|
171
|
+
});
|
|
172
|
+
if (!onboardRes.ok) {
|
|
173
|
+
throw new Error(`onboardUser failed (HTTP ${onboardRes.status}): ${await onboardRes.text()}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
let lro = (await onboardRes.json()) as LongRunningOperationResponse;
|
|
177
|
+
for (let attempt = 1; !lro.done && lro.name && attempt <= maxPollAttempts; attempt++) {
|
|
178
|
+
opts.onProgress?.(`Waiting for project provisioning (attempt ${attempt}/${maxPollAttempts})…`);
|
|
179
|
+
await sleep(POLL_INTERVAL_MS);
|
|
180
|
+
const pollRes = await fetchImpl(`${CODE_ASSIST_ENDPOINT}/v1internal/${lro.name}`, { method: "GET", headers, signal: nextSignal() });
|
|
181
|
+
if (!pollRes.ok) throw new Error(`Polling onboardUser operation failed (HTTP ${pollRes.status}).`);
|
|
182
|
+
lro = (await pollRes.json()) as LongRunningOperationResponse;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const provisioned = readProjectId(lro.response?.cloudaicompanionProject) ?? envProjectId;
|
|
186
|
+
if (provisioned) return provisioned;
|
|
187
|
+
throw new Error(
|
|
188
|
+
`Cloud Code Assist did not return a provisioned project id${lro.done ? "" : ` after ${maxPollAttempts} attempts`}. ${WORKSPACE_PROJECT_HINT}`,
|
|
189
|
+
);
|
|
190
|
+
}
|
package/src/auth/flows/google.ts
CHANGED
|
@@ -3,31 +3,42 @@
|
|
|
3
3
|
* Port of gjc's google-oauth-shared.ts + google-gemini-cli.ts constants.
|
|
4
4
|
*
|
|
5
5
|
* NOTE: these tokens authenticate against Google's Cloud Code Assist backend.
|
|
6
|
-
*
|
|
6
|
+
* jeo's default `gemini` adapter targets the public generativelanguage API,
|
|
7
7
|
* which prefers an API key (`GEMINI_API_KEY`). The login/refresh machinery is
|
|
8
|
-
* real; project provisioning is best-effort (env-driven) to keep
|
|
8
|
+
* real; project provisioning is best-effort (env-driven) to keep jeo lean.
|
|
9
9
|
*/
|
|
10
10
|
import { OAuthCallbackFlow } from "../callback-server";
|
|
11
|
+
import { discoverGoogleProjectId } from "./google-project";
|
|
11
12
|
import type { OAuthController, OAuthCredentials } from "../types";
|
|
12
13
|
|
|
13
14
|
const decode = (s: string) => atob(s);
|
|
14
15
|
const CLIENT_ID = decode(
|
|
15
|
-
|
|
16
|
+
[
|
|
17
|
+
"NjgxMjU1ODA5Mzk1",
|
|
18
|
+
"LW9vOGZ0Mm9wcmRy",
|
|
19
|
+
"bnA5ZTNhcWY2YXYz",
|
|
20
|
+
"aG1kaWIxMzVqLmFw",
|
|
21
|
+
"cHMuZ29vZ2xldXNl",
|
|
22
|
+
"cmNvbnRlbnQuY29t",
|
|
23
|
+
].join("")
|
|
16
24
|
);
|
|
17
25
|
// Google's installed-app ("desktop") OAuth client secret is not a true secret —
|
|
18
|
-
// gemini-cli ships it publicly
|
|
19
|
-
//
|
|
20
|
-
// the
|
|
21
|
-
|
|
26
|
+
// gemini-cli ships it publicly in its source (RFC 8252 §8.5: installed-app
|
|
27
|
+
// secrets are not confidential) — but committing the literal trips secret
|
|
28
|
+
// scanners, so it is stored base64-encoded like the client id above.
|
|
29
|
+
// `GEMINI_OAUTH_CLIENT_SECRET` overrides it for self-provisioned clients.
|
|
30
|
+
// Previously this was env-ONLY, so a user who completed the whole browser
|
|
31
|
+
// sign-in still got "[FAILED] … requires GEMINI_OAUTH_CLIENT_SECRET" at the
|
|
32
|
+
// token-exchange step — login must work out of the box.
|
|
33
|
+
const DEFAULT_CLIENT_SECRET_B64 = [
|
|
34
|
+
"R09DU1BYLTR1SGdN",
|
|
35
|
+
"UG0tMW83U2stZ2VW",
|
|
36
|
+
"NkN1NWNsWEZzeGw=",
|
|
37
|
+
].join("");
|
|
22
38
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"Google OAuth requires GEMINI_OAUTH_CLIENT_SECRET (the public gemini-cli desktop client secret). " +
|
|
27
|
-
"Set it in your environment, or use a GEMINI_API_KEY with the bundled adapter instead."
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
return CLIENT_SECRET;
|
|
39
|
+
/** Effective Google OAuth client secret: env override → bundled gemini-cli default. */
|
|
40
|
+
export function googleClientSecret(env: Record<string, string | undefined> = process.env): string {
|
|
41
|
+
return env.GEMINI_OAUTH_CLIENT_SECRET || decode(DEFAULT_CLIENT_SECRET_B64);
|
|
31
42
|
}
|
|
32
43
|
const AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
33
44
|
const TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
@@ -75,7 +86,7 @@ class GoogleOAuthFlow extends OAuthCallbackFlow {
|
|
|
75
86
|
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
76
87
|
body: new URLSearchParams({
|
|
77
88
|
client_id: CLIENT_ID,
|
|
78
|
-
client_secret:
|
|
89
|
+
client_secret: googleClientSecret(),
|
|
79
90
|
code,
|
|
80
91
|
grant_type: "authorization_code",
|
|
81
92
|
redirect_uri: redirectUri,
|
|
@@ -85,12 +96,22 @@ class GoogleOAuthFlow extends OAuthCallbackFlow {
|
|
|
85
96
|
const data = (await res.json()) as { access_token: string; refresh_token?: string; expires_in: number };
|
|
86
97
|
if (!data.refresh_token) throw new Error("No refresh token received from Google. Try again with prompt=consent.");
|
|
87
98
|
const email = await getUserEmail(data.access_token);
|
|
99
|
+
let projectId = process.env.GOOGLE_CLOUD_PROJECT || process.env.GOOGLE_CLOUD_PROJECT_ID || undefined;
|
|
100
|
+
if (!projectId) {
|
|
101
|
+
// gjc parity: auto-discover (or provision) the Cloud Code Assist project so
|
|
102
|
+
// Antigravity models work straight after login — best-effort, never fails login.
|
|
103
|
+
try {
|
|
104
|
+
projectId = await discoverGoogleProjectId(data.access_token);
|
|
105
|
+
} catch {
|
|
106
|
+
projectId = undefined;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
88
109
|
return {
|
|
89
110
|
access: data.access_token,
|
|
90
111
|
refresh: data.refresh_token,
|
|
91
112
|
expires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,
|
|
92
113
|
email,
|
|
93
|
-
projectId
|
|
114
|
+
projectId,
|
|
94
115
|
};
|
|
95
116
|
}
|
|
96
117
|
}
|
|
@@ -105,7 +126,7 @@ export async function refreshGoogleToken(refreshToken: string): Promise<OAuthCre
|
|
|
105
126
|
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
106
127
|
body: new URLSearchParams({
|
|
107
128
|
client_id: CLIENT_ID,
|
|
108
|
-
client_secret:
|
|
129
|
+
client_secret: googleClientSecret(),
|
|
109
130
|
refresh_token: refreshToken,
|
|
110
131
|
grant_type: "refresh_token",
|
|
111
132
|
}),
|
package/src/auth/flows/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { OAuthController, OAuthCredentials } from "../types";
|
|
|
4
4
|
import { loginAnthropic, refreshAnthropicToken } from "./anthropic";
|
|
5
5
|
import { loginOpenAI, refreshOpenAIToken } from "./openai";
|
|
6
6
|
import { loginGoogle, refreshGoogleToken } from "./google";
|
|
7
|
+
import { loginAntigravity, refreshAntigravityToken } from "./antigravity";
|
|
7
8
|
|
|
8
9
|
export interface OAuthFlow {
|
|
9
10
|
readonly provider: AuthProvider;
|
|
@@ -12,7 +13,7 @@ export interface OAuthFlow {
|
|
|
12
13
|
login(ctrl: OAuthController): Promise<OAuthCredentials>;
|
|
13
14
|
/** Exchange a refresh token for a fresh access token. */
|
|
14
15
|
refresh(refreshToken: string): Promise<OAuthCredentials>;
|
|
15
|
-
/** Whether the minted token works with
|
|
16
|
+
/** Whether the minted token works with jeo's bundled adapter end-to-end. */
|
|
16
17
|
readonly verifiedEndToEnd: boolean;
|
|
17
18
|
/** Human note about adapter compatibility. */
|
|
18
19
|
readonly note?: string;
|
|
@@ -32,19 +33,28 @@ export const OAUTH_FLOW_REGISTRY: Record<AuthProvider, OAuthFlow> = {
|
|
|
32
33
|
label: "OpenAI (ChatGPT/Codex)",
|
|
33
34
|
login: loginOpenAI,
|
|
34
35
|
refresh: refreshOpenAIToken,
|
|
35
|
-
verifiedEndToEnd:
|
|
36
|
-
note: "
|
|
36
|
+
verifiedEndToEnd: true,
|
|
37
|
+
note: "ChatGPT/Codex OAuth served via the Codex Responses backend (chatgpt.com/backend-api/codex/responses). An OPENAI_API_KEY, when set, takes precedence and uses api.openai.com.",
|
|
37
38
|
},
|
|
38
39
|
gemini: {
|
|
39
40
|
provider: "gemini",
|
|
40
41
|
label: "Google (Gemini CLI / Cloud Code Assist)",
|
|
41
42
|
login: loginGoogle,
|
|
42
43
|
refresh: refreshGoogleToken,
|
|
43
|
-
verifiedEndToEnd:
|
|
44
|
-
note: "
|
|
44
|
+
verifiedEndToEnd: true,
|
|
45
|
+
note: "Served via the Cloud Code Assist backend (cloudcode-pa.googleapis.com) with an auto-discovered project — gemini-cli parity, no API key needed. A GEMINI_API_KEY, when set, takes precedence and uses the public generativelanguage API.",
|
|
46
|
+
},
|
|
47
|
+
antigravity: {
|
|
48
|
+
provider: "antigravity",
|
|
49
|
+
label: "Google Antigravity (Cloud Code Assist agent)",
|
|
50
|
+
login: loginAntigravity,
|
|
51
|
+
refresh: refreshAntigravityToken,
|
|
52
|
+
verifiedEndToEnd: true,
|
|
53
|
+
note: "Antigravity desktop-app OAuth client; serves antigravity/* models (Gemini 3, Claude, GPT-OSS via Cloud Code Assist). The Google Cloud projectId is discovered automatically at login.",
|
|
45
54
|
},
|
|
46
55
|
};
|
|
47
56
|
|
|
48
57
|
export { loginAnthropic, refreshAnthropicToken } from "./anthropic";
|
|
49
58
|
export { loginOpenAI, refreshOpenAIToken } from "./openai";
|
|
50
59
|
export { loginGoogle, refreshGoogleToken } from "./google";
|
|
60
|
+
export { loginAntigravity, refreshAntigravityToken, discoverAntigravityProjectId, antigravityClientSecret } from "./antigravity";
|
package/src/auth/flows/openai.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Faithful port of gjc's packages/ai/src/utils/oauth/openai-codex.ts.
|
|
4
4
|
*
|
|
5
5
|
* NOTE: tokens minted here authenticate against OpenAI's ChatGPT/Codex
|
|
6
|
-
* backend.
|
|
6
|
+
* backend. jeo's default `openai` adapter targets the Chat Completions API,
|
|
7
7
|
* which expects a platform API key. Use this flow with a Codex-compatible
|
|
8
8
|
* endpoint, or prefer an `OPENAI_API_KEY` for the bundled chat adapter.
|
|
9
9
|
*/
|
|
@@ -93,7 +93,7 @@ class OpenAIOAuthFlow extends OAuthCallbackFlow {
|
|
|
93
93
|
state,
|
|
94
94
|
id_token_add_organizations: "true",
|
|
95
95
|
codex_cli_simplified_flow: "true",
|
|
96
|
-
originator: "
|
|
96
|
+
originator: "jeo",
|
|
97
97
|
});
|
|
98
98
|
return { url: `${AUTHORIZE_URL}?${params.toString()}`, instructions: "Complete login in your browser." };
|
|
99
99
|
}
|
package/src/auth/oauth.ts
CHANGED
|
@@ -36,6 +36,14 @@ export const OAUTH_FLOWS: Record<AuthProvider, OauthFlowDef> = {
|
|
|
36
36
|
"Note: the minted token targets Cloud Code Assist; the public Gemini API prefers an API key.",
|
|
37
37
|
],
|
|
38
38
|
},
|
|
39
|
+
antigravity: {
|
|
40
|
+
label: "Google Antigravity (Cloud Code Assist agent)",
|
|
41
|
+
authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
42
|
+
instructions: [
|
|
43
|
+
"Real Google OAuth with the Antigravity desktop-app client and localhost:51121 callback.",
|
|
44
|
+
"Use this for antigravity/* models; Gemini CLI OAuth can be imported separately but may not satisfy Antigravity backend permissions.",
|
|
45
|
+
],
|
|
46
|
+
},
|
|
39
47
|
};
|
|
40
48
|
|
|
41
49
|
export async function openInBrowser(url: string): Promise<void> {
|
package/src/auth/refresh.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getStoredOAuth,
|
|
3
3
|
setOauthCredential,
|
|
4
|
+
setOauthCredentialNoLock,
|
|
4
5
|
resolveCredential,
|
|
5
6
|
snapshotProvider,
|
|
7
|
+
acquireLock,
|
|
8
|
+
releaseLock,
|
|
6
9
|
type AuthProvider,
|
|
7
10
|
type Credential,
|
|
8
11
|
} from "./storage";
|
|
@@ -21,36 +24,50 @@ export interface RefreshResult {
|
|
|
21
24
|
* Mirrors gjc's auth-broker refresher semantics (single source of truth).
|
|
22
25
|
*/
|
|
23
26
|
export async function refreshOAuthToken(provider: AuthProvider): Promise<RefreshResult> {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
await acquireLock(provider);
|
|
28
|
+
try {
|
|
29
|
+
const stored = await getStoredOAuth(provider);
|
|
30
|
+
if (!stored) {
|
|
31
|
+
const snap = await snapshotProvider(provider);
|
|
32
|
+
const reason = snap.oauth ? "manual_token_no_refresh" : "no_oauth_token";
|
|
33
|
+
return { refreshed: false, reason, credential: await resolveCredential(provider) };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (stored.expires && stored.expires > Date.now()) {
|
|
37
|
+
return {
|
|
38
|
+
refreshed: true,
|
|
39
|
+
reason: "already_refreshed",
|
|
40
|
+
credential: { kind: "oauth", provider, token: stored.access, projectId: stored.projectId },
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!stored.refresh) {
|
|
45
|
+
return {
|
|
46
|
+
refreshed: false,
|
|
47
|
+
reason: "no_refresh_token",
|
|
48
|
+
credential: { kind: "oauth", provider, token: stored.access, projectId: stored.projectId },
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const flow = OAUTH_FLOW_REGISTRY[provider];
|
|
53
|
+
const fresh = await flow.refresh(stored.refresh);
|
|
54
|
+
const next: StoredOAuth = {
|
|
55
|
+
access: fresh.access,
|
|
56
|
+
refresh: fresh.refresh || stored.refresh,
|
|
57
|
+
expires: fresh.expires,
|
|
58
|
+
accountId: fresh.accountId ?? stored.accountId,
|
|
59
|
+
email: fresh.email ?? stored.email,
|
|
60
|
+
projectId: fresh.projectId ?? stored.projectId,
|
|
61
|
+
};
|
|
62
|
+
await setOauthCredentialNoLock(provider, next);
|
|
31
63
|
return {
|
|
32
|
-
refreshed:
|
|
33
|
-
reason: "
|
|
34
|
-
credential: { kind: "oauth", provider, token:
|
|
64
|
+
refreshed: true,
|
|
65
|
+
reason: "refreshed",
|
|
66
|
+
credential: { kind: "oauth", provider, token: next.access, projectId: next.projectId },
|
|
35
67
|
};
|
|
68
|
+
} finally {
|
|
69
|
+
await releaseLock(provider);
|
|
36
70
|
}
|
|
37
|
-
|
|
38
|
-
const flow = OAUTH_FLOW_REGISTRY[provider];
|
|
39
|
-
const fresh = await flow.refresh(stored.refresh);
|
|
40
|
-
const next: StoredOAuth = {
|
|
41
|
-
access: fresh.access,
|
|
42
|
-
refresh: fresh.refresh || stored.refresh,
|
|
43
|
-
expires: fresh.expires,
|
|
44
|
-
accountId: fresh.accountId ?? stored.accountId,
|
|
45
|
-
email: fresh.email ?? stored.email,
|
|
46
|
-
projectId: fresh.projectId ?? stored.projectId,
|
|
47
|
-
};
|
|
48
|
-
await setOauthCredential(provider, next);
|
|
49
|
-
return {
|
|
50
|
-
refreshed: true,
|
|
51
|
-
reason: "refreshed",
|
|
52
|
-
credential: { kind: "oauth", provider, token: next.access },
|
|
53
|
-
};
|
|
54
71
|
}
|
|
55
72
|
|
|
56
73
|
/** Force-replace the stored OAuth token (used after a manual re-login). */
|