@koreanhole/opencodex 3.2.6 → 3.2.8

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/AGENTS_INSTALL.md CHANGED
@@ -26,7 +26,7 @@ To use Gemini instead, authenticate it through its official CLI and select the
26
26
  provider:
27
27
 
28
28
  ```bash
29
- gemini # configure a Gemini Developer API or Vertex AI API key
29
+ gemini # complete the official OAuth login or configure an API key
30
30
  ocx provider add gemini-cli --set-default
31
31
  ocx start
32
32
  ocx codex sync
@@ -52,10 +52,14 @@ or through the documented default path; unrelated Codex settings are not changed
52
52
  mode-0600 request-scoped copy; remove that copy after the turn.
53
53
  - Do not import or replay Gemini OAuth/API-key material; the Gemini CLI remains
54
54
  the authentication authority.
55
- - Third-party use must use a Gemini Developer API key or Vertex AI GenAI API key.
56
- Gemini CLI's official FAQ identifies third-party use of its personal Google
57
- OAuth authentication as a terms violation. OAuth is not a supported
58
- opencodex setup path.
55
+ - Gemini headless turns support explicit Gemini Developer API, Vertex AI GenAI,
56
+ or the operator's existing Gemini CLI OAuth login. opencodex does not automate
57
+ OAuth login or copy OAuth tokens. OAuth reuse requires an account and tier
58
+ eligible for the installed CLI's service, and the operator remains responsible
59
+ for vendor terms.
60
+ - Explicit process-environment API-key, Vertex, or gateway configuration takes
61
+ precedence over automatic OAuth selection. An explicit
62
+ `GOOGLE_GENAI_USE_GCA=true` selects OAuth.
59
63
  - The proxy writes under `$OPENCODEX_HOME` (default `~/.opencodex`) and may update
60
64
  the active opencodex Codex model catalog selected by `model_catalog_json`.
61
65
 
@@ -86,6 +90,7 @@ bun run verify:upstream
86
90
  bun run typecheck
87
91
  bun test tests/e2e/cline-only.test.ts
88
92
  bun run test:e2e:gemini
93
+ bun run test:e2e:gemini-oauth
89
94
  bun run test:e2e:gemini-tool
90
95
  bun run test:e2e:gemini-reasoning
91
96
  bun run test:e2e:codex-restore
@@ -94,4 +99,6 @@ bun run test:e2e:codex-restore
94
99
  Report whether each evidence path used a deterministic fixture or a real
95
100
  vendor binary/account. The Gemini command above is a hard live gate: missing
96
101
  binaries, authentication, a Flash Lite model, or cleanup evidence must fail.
102
+ The OAuth gate additionally removes API-key/Vertex environment values and the
103
+ vendor `.env`.
97
104
  Never report a skipped or synthetic run as a real login.
package/README.md CHANGED
@@ -31,8 +31,11 @@ npm install -g @google/gemini-cli
31
31
  gemini
32
32
  ```
33
33
 
34
- For third-party clients, use a Gemini Developer API key or Vertex AI GenAI API
35
- key. Do not use personal Google OAuth for third-party software.
34
+ Gemini headless turns support an explicit Gemini Developer API key, Vertex AI
35
+ GenAI configuration, or the operator's existing Gemini CLI OAuth login. opencodex
36
+ does not automate Google OAuth. OAuth reuse requires an account and tier eligible
37
+ for the installed Gemini CLI's service; explicit API-key or Vertex environment
38
+ configuration takes precedence over automatic OAuth selection.
36
39
 
37
40
  ## Start
38
41
 
@@ -157,6 +160,7 @@ bun run typecheck
157
160
  bun test tests
158
161
  bun run test:e2e
159
162
  bun run test:e2e:gemini
163
+ bun run test:e2e:gemini-oauth
160
164
  bun run test:e2e:gemini-slow
161
165
  bun run test:e2e:gemini-parent-tool
162
166
  bun run privacy:scan
@@ -168,6 +172,9 @@ a real Codex client and a real authenticated Gemini CLI. It runs both the minima
168
172
  completion and a 12-second pre-event slow-start regression. It fails, not skips,
169
173
  on a missing binary, authentication, Flash Lite model, response marker, timeout,
170
174
  or child cleanup assertion. `test:e2e:gemini-slow` runs only the slow-start case.
175
+ `test:e2e:gemini-oauth` strips API-key and Vertex environment values, removes the
176
+ vendor `.env`, and requires a real OAuth-backed completion through Codex and the
177
+ installed Gemini CLI.
171
178
 
172
179
  Optional overrides are `CLINE_BIN_PATH`, `GEMINI_BIN_PATH`, and
173
180
  `CODEX_BIN_PATH`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koreanhole/opencodex",
3
- "version": "3.2.6",
3
+ "version": "3.2.8",
4
4
  "description": "Full opencodex runtime with Cline JSON and Gemini CLI headless provider adapters",
5
5
  "type": "module",
6
6
  "main": "./bin/package-main.mjs",
@@ -31,6 +31,7 @@
31
31
  "test": "bun test tests",
32
32
  "test:e2e": "bun test tests/e2e/cline-only.test.ts",
33
33
  "test:e2e:gemini": "bun test ./tests/e2e/gemini-codex.live.ts -t \"full-stack:\"",
34
+ "test:e2e:gemini-oauth": "bun test ./tests/e2e/gemini-codex.live.ts -t \"uses an existing Gemini CLI OAuth login without an API key\"",
34
35
  "test:e2e:gemini-slow": "bun test ./tests/e2e/gemini-codex.live.ts -t \"full-stack: a slow Gemini start\"",
35
36
  "test:e2e:gemini-tool": "bun test ./tests/e2e/gemini-codex.live.ts -t \"one short Gemini turn leaves the workspace unchanged\"",
36
37
  "test:e2e:gemini-multiturn": "bun test ./tests/e2e/gemini-codex.live.ts -t \"reuses a live Codex multi-turn session\"",
@@ -60,6 +60,8 @@ const FORWARDED_ENV_KEYS = [
60
60
  ] as const;
61
61
 
62
62
  const AUTH_FILE_NAMES = ["oauth_creds.json", "google_accounts.json", ".env"] as const;
63
+ const OAUTH_AUTH_FILE_NAMES = ["oauth_creds.json", "google_accounts.json"] as const;
64
+ type GeminiHeadlessAuth = "oauth" | "environment" | "default";
63
65
 
64
66
  export interface GeminiHeadlessDeps extends CliAgentTurnDeps {
65
67
  env?: NodeJS.ProcessEnv;
@@ -82,6 +84,10 @@ function nonEmpty(value: unknown): string | undefined {
82
84
  return typeof value === "string" && value.trim() ? value.trim() : undefined;
83
85
  }
84
86
 
87
+ function enabled(value: unknown): boolean {
88
+ return nonEmpty(value)?.toLowerCase() === "true";
89
+ }
90
+
85
91
  function asRecord(value: unknown): Record<string, unknown> | undefined {
86
92
  return value && typeof value === "object" && !Array.isArray(value)
87
93
  ? value as Record<string, unknown>
@@ -256,8 +262,12 @@ function geminiMcpSettings(
256
262
  function geminiSystemSettings(
257
263
  reasoning?: GeminiReasoningSelection,
258
264
  mcpServers?: Record<string, unknown>,
265
+ auth: GeminiHeadlessAuth = "default",
259
266
  ): string {
260
267
  const settings = JSON.parse(GEMINI_SYSTEM_SETTINGS) as Record<string, unknown>;
268
+ if (auth === "oauth") {
269
+ settings.security = { auth: { selectedType: "oauth-personal" } };
270
+ }
261
271
  settings.tools = { core: mcpServers ? ["mcp_opencodex_*"] : [] };
262
272
  settings.mcp = mcpServers
263
273
  ? { allowed: [GEMINI_MCP_SERVER_NAME], excluded: [] }
@@ -300,15 +310,34 @@ export async function stageGeminiHeadlessWorkspace(
300
310
  const catalog = options.catalog ?? [];
301
311
  const capturePath = catalog.length > 0 ? join(root, "tool-intents.ndjson") : undefined;
302
312
  const mcpServers = geminiMcpSettings(catalog, options.mcp, catalogPath, capturePath);
313
+ const sourceEnv = options.sourceEnv ?? process.env;
314
+ const realHome = nonEmpty(sourceEnv.HOME) ?? nonEmpty(sourceEnv.USERPROFILE) ?? homedir();
315
+ let oauthAvailable = false;
316
+ try {
317
+ await lstat(join(realHome, ".gemini", "oauth_creds.json"));
318
+ oauthAvailable = true;
319
+ } catch {
320
+ // No existing vendor OAuth login to reuse.
321
+ }
322
+ const auth: GeminiHeadlessAuth = enabled(sourceEnv.GOOGLE_GENAI_USE_GCA)
323
+ ? "oauth"
324
+ : nonEmpty(sourceEnv.GEMINI_API_KEY)
325
+ || nonEmpty(sourceEnv.GOOGLE_API_KEY)
326
+ || enabled(sourceEnv.GOOGLE_GENAI_USE_VERTEXAI)
327
+ || nonEmpty(sourceEnv.GOOGLE_GEMINI_BASE_URL)
328
+ ? "environment"
329
+ : oauthAvailable
330
+ ? "oauth"
331
+ : "default";
303
332
  const exclusive = options.root === undefined;
304
333
  const writeOptions = { encoding: "utf8" as const, mode: 0o600, ...(exclusive ? { flag: "wx" as const } : {}) };
305
334
  await mkdir(geminiConfigDir, { recursive: true, mode: 0o700 });
306
335
  await Promise.all([
307
- writeFile(settingsPath, geminiSystemSettings(reasoning, mcpServers), writeOptions),
336
+ writeFile(settingsPath, geminiSystemSettings(reasoning, mcpServers, auth), writeOptions),
308
337
  writeFile(catalogPath, JSON.stringify(catalog), writeOptions),
309
338
  ...(capturePath ? [writeFile(capturePath, "", writeOptions)] : []),
310
339
  ]);
311
- await linkGeminiAuth(geminiConfigDir, options.sourceEnv ?? process.env);
340
+ await linkGeminiAuth(geminiConfigDir, sourceEnv, auth);
312
341
  return {
313
342
  root,
314
343
  geminiHome,
@@ -319,10 +348,15 @@ export async function stageGeminiHeadlessWorkspace(
319
348
  };
320
349
  }
321
350
 
322
- async function linkGeminiAuth(targetDir: string, sourceEnv: NodeJS.ProcessEnv): Promise<void> {
351
+ async function linkGeminiAuth(
352
+ targetDir: string,
353
+ sourceEnv: NodeJS.ProcessEnv,
354
+ auth: GeminiHeadlessAuth,
355
+ ): Promise<void> {
323
356
  const realHome = nonEmpty(sourceEnv.HOME) ?? nonEmpty(sourceEnv.USERPROFILE) ?? homedir();
324
357
  const realGemini = join(realHome, ".gemini");
325
- await Promise.all(AUTH_FILE_NAMES.map(async name => {
358
+ const names = auth === "oauth" ? OAUTH_AUTH_FILE_NAMES : AUTH_FILE_NAMES;
359
+ await Promise.all(names.map(async name => {
326
360
  const target = join(realGemini, name);
327
361
  try {
328
362
  await lstat(target);