@koreanhole/opencodex 3.2.13 → 3.2.14

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 # complete the official OAuth login or configure an API key
29
+ gemini # complete the official OAuth login (no API key required) or configure an API key
30
30
  ocx provider add gemini-cli --set-default
31
31
  ocx start
32
32
  ocx codex sync
package/README.md CHANGED
@@ -36,7 +36,10 @@ Gemini headless turns support an explicit Gemini Developer API key, Vertex AI
36
36
  GenAI configuration, or the operator's existing Gemini CLI OAuth login. opencodex
37
37
  does not automate Google OAuth. OAuth reuse requires an account and tier eligible
38
38
  for the installed Gemini CLI's service; explicit API-key or Vertex environment
39
- configuration takes precedence over automatic OAuth selection.
39
+ configuration takes precedence over automatic OAuth selection. With no explicit
40
+ environment authentication, OAuth is selected automatically, including logins
41
+ stored in the vendor keychain or under a custom `GEMINI_CLI_HOME`; no API key is
42
+ required.
40
43
 
41
44
  Cline 2.2.0 is the minimum supported release. Newer options are used only when
42
45
  the installed binary advertises them.
@@ -170,6 +173,7 @@ bun test tests
170
173
  bun run test:e2e
171
174
  bun run test:e2e:cline-live
172
175
  bun run test:e2e:gemini
176
+ bun run test:e2e:gemini-oauth-only
173
177
  bun run test:e2e:gemini-slow
174
178
  bun run test:e2e:gemini-parent-tool
175
179
  bun run privacy:scan
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koreanhole/opencodex",
3
- "version": "3.2.13",
3
+ "version": "3.2.14",
4
4
  "description": "Full opencodex runtime with Cline 2.2.0+ JSON and Gemini CLI headless provider adapters",
5
5
  "type": "module",
6
6
  "main": "./bin/package-main.mjs",
@@ -32,6 +32,7 @@
32
32
  "test:e2e": "bun test tests/e2e/cline-only.test.ts",
33
33
  "test:e2e:cline-live": "bun test ./tests/e2e/cline-live.live.ts",
34
34
  "test:e2e:gemini": "bun test ./tests/e2e/gemini-codex.live.ts -t \"full-stack:\"",
35
+ "test:e2e:gemini-oauth-only": "bun test ./tests/e2e/gemini-oauth-only.test.ts",
35
36
  "test:e2e:gemini-slow": "bun test ./tests/e2e/gemini-codex.live.ts -t \"full-stack: a slow Gemini start\"",
36
37
  "test:e2e:gemini-tool": "bun test ./tests/e2e/gemini-codex.live.ts -t \"one short Gemini turn leaves the workspace unchanged\"",
37
38
  "test:e2e:gemini-multiturn": "bun test ./tests/e2e/gemini-codex.live.ts -t \"reuses a live Codex multi-turn session\"",
@@ -1,4 +1,5 @@
1
1
  import { spawn as nodeSpawn, type ChildProcess } from "node:child_process";
2
+ import { existsSync } from "node:fs";
2
3
  import { link, lstat, mkdir, mkdtemp, readFile, rm, stat, symlink, writeFile } from "node:fs/promises";
3
4
  import { homedir, tmpdir } from "node:os";
4
5
  import { join, resolve } from "node:path";
@@ -47,6 +48,8 @@ const FORWARDED_ENV_KEYS = [
47
48
  "GOOGLE_CLOUD_LOCATION",
48
49
  "GOOGLE_APPLICATION_CREDENTIALS",
49
50
  "GOOGLE_GEMINI_BASE_URL",
51
+ "GEMINI_FORCE_ENCRYPTED_FILE_STORAGE",
52
+ "GEMINI_FORCE_FILE_STORAGE",
50
53
  "GEMINI_API_KEY_AUTH_MECHANISM",
51
54
  "GOOGLE_GENAI_API_VERSION",
52
55
  "GEMINI_SYSTEM_MD",
@@ -61,7 +64,7 @@ const FORWARDED_ENV_KEYS = [
61
64
 
62
65
  const AUTH_FILE_NAMES = ["oauth_creds.json", "google_accounts.json", ".env"] as const;
63
66
  const OAUTH_AUTH_FILE_NAMES = ["oauth_creds.json", "google_accounts.json"] as const;
64
- type GeminiHeadlessAuth = "oauth" | "environment" | "default";
67
+ type GeminiHeadlessAuth = "oauth" | "environment";
65
68
 
66
69
  export interface GeminiHeadlessDeps extends CliAgentTurnDeps {
67
70
  env?: NodeJS.ProcessEnv;
@@ -98,6 +101,30 @@ function asString(value: unknown): string | undefined {
98
101
  return typeof value === "string" ? value : undefined;
99
102
  }
100
103
 
104
+ function geminiHeadlessAuth(sourceEnv: NodeJS.ProcessEnv): GeminiHeadlessAuth {
105
+ if (enabled(sourceEnv.GOOGLE_GENAI_USE_GCA)) return "oauth";
106
+ if (
107
+ nonEmpty(sourceEnv.GEMINI_API_KEY)
108
+ || nonEmpty(sourceEnv.GOOGLE_API_KEY)
109
+ || enabled(sourceEnv.GOOGLE_GENAI_USE_VERTEXAI)
110
+ || nonEmpty(sourceEnv.GOOGLE_GEMINI_BASE_URL)
111
+ ) return "environment";
112
+ if (
113
+ existsSync(join(geminiVendorHome(sourceEnv), ".gemini", "oauth_creds.json"))
114
+ || enabled(sourceEnv.GEMINI_FORCE_ENCRYPTED_FILE_STORAGE)
115
+ ) return "oauth";
116
+ // Preserve the legacy vendor .env fallback when it is the only auth source.
117
+ if (existsSync(join(geminiVendorHome(sourceEnv), ".gemini", ".env"))) return "environment";
118
+ return "oauth";
119
+ }
120
+
121
+ function geminiVendorHome(sourceEnv: NodeJS.ProcessEnv): string {
122
+ return nonEmpty(sourceEnv.GEMINI_CLI_HOME)
123
+ ?? nonEmpty(sourceEnv.HOME)
124
+ ?? nonEmpty(sourceEnv.USERPROFILE)
125
+ ?? homedir();
126
+ }
127
+
101
128
  function safeMessage(value: unknown, fallback: string): string {
102
129
  const raw = typeof value === "string"
103
130
  ? value
@@ -134,6 +161,7 @@ export function buildGeminiHeadlessEnv(
134
161
  const value = nonEmpty(sourceEnv[key]);
135
162
  if (value) env[key] = value;
136
163
  }
164
+ if (geminiHeadlessAuth(sourceEnv) === "oauth") env.GOOGLE_GENAI_USE_GCA = "true";
137
165
  Object.assign(env, {
138
166
  GEMINI_CLI_TRUST_WORKSPACE: "true",
139
167
  GEMINI_CLI_NO_RELAUNCH: "1",
@@ -262,7 +290,7 @@ function geminiMcpSettings(
262
290
  function geminiSystemSettings(
263
291
  reasoning?: GeminiReasoningSelection,
264
292
  mcpServers?: Record<string, unknown>,
265
- auth: GeminiHeadlessAuth = "default",
293
+ auth: GeminiHeadlessAuth = "oauth",
266
294
  ): string {
267
295
  const settings = JSON.parse(GEMINI_SYSTEM_SETTINGS) as Record<string, unknown>;
268
296
  if (auth === "oauth") {
@@ -311,24 +339,7 @@ export async function stageGeminiHeadlessWorkspace(
311
339
  const capturePath = catalog.length > 0 ? join(root, "tool-intents.ndjson") : undefined;
312
340
  const mcpServers = geminiMcpSettings(catalog, options.mcp, catalogPath, capturePath);
313
341
  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";
342
+ const auth = geminiHeadlessAuth(sourceEnv);
332
343
  const exclusive = options.root === undefined;
333
344
  const writeOptions = { encoding: "utf8" as const, mode: 0o600, ...(exclusive ? { flag: "wx" as const } : {}) };
334
345
  await mkdir(geminiConfigDir, { recursive: true, mode: 0o700 });
@@ -353,8 +364,7 @@ async function linkGeminiAuth(
353
364
  sourceEnv: NodeJS.ProcessEnv,
354
365
  auth: GeminiHeadlessAuth,
355
366
  ): Promise<void> {
356
- const realHome = nonEmpty(sourceEnv.HOME) ?? nonEmpty(sourceEnv.USERPROFILE) ?? homedir();
357
- const realGemini = join(realHome, ".gemini");
367
+ const realGemini = join(geminiVendorHome(sourceEnv), ".gemini");
358
368
  const names = auth === "oauth" ? OAUTH_AUTH_FILE_NAMES : AUTH_FILE_NAMES;
359
369
  await Promise.all(names.map(async name => {
360
370
  const target = join(realGemini, name);
package/src/cli/index.ts CHANGED
@@ -71,7 +71,7 @@ function help(): string {
71
71
  " help Show this help",
72
72
  "",
73
73
  "Providers: cline-cli, gemini-cli",
74
- "Gemini authentication is managed by Gemini CLI (`gemini` or GEMINI_API_KEY).",
74
+ "Gemini authentication is managed by Gemini CLI (OAuth login or GEMINI_API_KEY).",
75
75
  ].join("\n");
76
76
  }
77
77
 
@@ -148,7 +148,7 @@ async function providerCommand(args: string[]): Promise<number> {
148
148
  async function loginCommand(args: string[]): Promise<number> {
149
149
  const provider = args.shift();
150
150
  if (provider === GEMINI_PROVIDER_ID) {
151
- throw new ConfigError("Gemini authentication is managed by Gemini CLI. Run `gemini` to log in or set GEMINI_API_KEY for the CLI.");
151
+ throw new ConfigError("Gemini authentication is managed by Gemini CLI. Run `gemini` to log in with Google; no API key is required. Alternatively, set GEMINI_API_KEY for the CLI.");
152
152
  }
153
153
  if (provider !== CLINE_PROVIDER_ID) throw new ConfigError("Only `login cline-cli` is supported; authenticate Gemini through Gemini CLI.");
154
154
  const json = takeFlag(args, "--json");