@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 +1 -1
- package/README.md +5 -1
- package/package.json +2 -1
- package/src/adapters/cli-agent/gemini/headless.ts +32 -22
- package/src/cli/index.ts +2 -2
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.
|
|
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"
|
|
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 = "
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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");
|