@koreanhole/opencodex 3.0.0 → 3.1.0
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.md +9 -4
- package/package.json +2 -2
- package/src/adapters/cli-agent/cline/adapter.ts +107 -90
- package/src/adapters/cli-agent/cline/json.ts +652 -0
- package/src/adapters/cli-agent/cline/settings.ts +64 -5
- package/src/adapters/cli-agent/cline/stream.ts +201 -1
- package/src/adapters/cli-agent/gemini/acp.ts +6 -1
- package/src/adapters/cli-agent/gemini/adapter.ts +1 -1
- package/src/adapters/cli-agent/gemini/live-models.ts +1 -1
- package/src/adapters/cli-agent/types.ts +1 -0
- package/src/cli/entry.ts +8 -0
|
@@ -6,8 +6,10 @@ import { baseScopedEnv } from "../env";
|
|
|
6
6
|
/** Built-in, subagent, and team tools that a model-only Cline turn must never enable. */
|
|
7
7
|
export const CLINE_DISABLED_TOOL_IDS = Object.freeze([
|
|
8
8
|
"read_files",
|
|
9
|
+
"list_files",
|
|
9
10
|
"search_codebase",
|
|
10
11
|
"run_commands",
|
|
12
|
+
"run_terminal_command",
|
|
11
13
|
"fetch_web_content",
|
|
12
14
|
"apply_patch",
|
|
13
15
|
"editor",
|
|
@@ -68,6 +70,10 @@ export interface ClineSelection {
|
|
|
68
70
|
model?: string;
|
|
69
71
|
}
|
|
70
72
|
|
|
73
|
+
export type ClineMcpLaunch =
|
|
74
|
+
| { kind: "module"; modulePath: string }
|
|
75
|
+
| { kind: "entrypoint"; entrypoint: string };
|
|
76
|
+
|
|
71
77
|
interface ClineSelectionCandidate {
|
|
72
78
|
providerId: string;
|
|
73
79
|
model?: string;
|
|
@@ -261,12 +267,13 @@ export async function assertClineSelectionSafe(providerSettingsPath: string): Pr
|
|
|
261
267
|
* The vendor store is advisory: absence or another schema is Cline's business.
|
|
262
268
|
* Only an explicit route back through this proxy remains a hard refusal.
|
|
263
269
|
*/
|
|
264
|
-
export async function assertClineAdvisoryDestinationSafe(providerSettingsPath: string): Promise<
|
|
270
|
+
export async function assertClineAdvisoryDestinationSafe(providerSettingsPath: string): Promise<ClineSelection | undefined> {
|
|
265
271
|
try {
|
|
266
|
-
await assertClineSelectionSafe(providerSettingsPath);
|
|
272
|
+
return await assertClineSelectionSafe(providerSettingsPath);
|
|
267
273
|
} catch (error) {
|
|
268
274
|
if (error instanceof ClineSettingsError && error.code === "cli_provider_recursion") throw error;
|
|
269
275
|
if (!(error instanceof ClineSettingsError)) throw error;
|
|
276
|
+
return undefined;
|
|
270
277
|
}
|
|
271
278
|
}
|
|
272
279
|
|
|
@@ -285,8 +292,25 @@ function clineGlobalSettings(): Record<string, unknown> {
|
|
|
285
292
|
};
|
|
286
293
|
}
|
|
287
294
|
|
|
288
|
-
function
|
|
289
|
-
return
|
|
295
|
+
function clineMcpArgs(launch: ClineMcpLaunch, catalogPath: string): string[] {
|
|
296
|
+
return launch.kind === "module"
|
|
297
|
+
? [launch.modulePath, catalogPath]
|
|
298
|
+
: [launch.entrypoint, catalogPath];
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function clineMcpSettings(launch: ClineMcpLaunch | undefined, catalogPath: string): Record<string, unknown> {
|
|
302
|
+
if (!launch) return { mcpServers: {} };
|
|
303
|
+
return {
|
|
304
|
+
mcpServers: {
|
|
305
|
+
opencodex: {
|
|
306
|
+
transport: {
|
|
307
|
+
type: "stdio",
|
|
308
|
+
command: process.execPath,
|
|
309
|
+
args: clineMcpArgs(launch, catalogPath),
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
};
|
|
290
314
|
}
|
|
291
315
|
|
|
292
316
|
/** Share a staged file at a vendor-default path. */
|
|
@@ -346,6 +370,7 @@ async function linkClineSettings(input: {
|
|
|
346
370
|
export async function stageClineTurn(options: {
|
|
347
371
|
providerSettingsPath: string;
|
|
348
372
|
catalog: readonly Record<string, unknown>[];
|
|
373
|
+
mcpLaunch?: ClineMcpLaunch;
|
|
349
374
|
}): Promise<ClineStagedTurn> {
|
|
350
375
|
const root = await mkdtemp(join(tmpdir(), "ocx-cline-cli-"));
|
|
351
376
|
const configDir = join(root, "config");
|
|
@@ -378,7 +403,7 @@ export async function stageClineTurn(options: {
|
|
|
378
403
|
]);
|
|
379
404
|
await Promise.all([
|
|
380
405
|
writeFile(globalSettingsPath, `${JSON.stringify(clineGlobalSettings(), null, 2)}\n`, { encoding: "utf8", mode: 0o600, flag: "wx" }),
|
|
381
|
-
writeFile(mcpSettingsPath, `${JSON.stringify(clineMcpSettings(), null, 2)}\n`, { encoding: "utf8", mode: 0o600, flag: "wx" }),
|
|
406
|
+
writeFile(mcpSettingsPath, `${JSON.stringify(clineMcpSettings(options.mcpLaunch, catalogPath), null, 2)}\n`, { encoding: "utf8", mode: 0o600, flag: "wx" }),
|
|
382
407
|
writeFile(catalogPath, JSON.stringify(options.catalog), { encoding: "utf8", mode: 0o600, flag: "wx" }),
|
|
383
408
|
]);
|
|
384
409
|
const copiedProviderStore = await copyClineProviderSettings(options.providerSettingsPath, providerSettingsPath);
|
|
@@ -409,6 +434,40 @@ export async function stageClineTurn(options: {
|
|
|
409
434
|
}
|
|
410
435
|
}
|
|
411
436
|
|
|
437
|
+
/** Merge newly discovered built-in IDs into the private staged disable list. */
|
|
438
|
+
export async function mergeClineDisabledTools(
|
|
439
|
+
staged: Pick<ClineStagedTurn, "globalSettingsPath">,
|
|
440
|
+
ids: readonly string[],
|
|
441
|
+
): Promise<void> {
|
|
442
|
+
let parsed: unknown;
|
|
443
|
+
try {
|
|
444
|
+
parsed = JSON.parse(await readFile(staged.globalSettingsPath, "utf8"));
|
|
445
|
+
} catch {
|
|
446
|
+
throw new ClineSettingsError(
|
|
447
|
+
"Cline global settings are malformed; the tool preflight cannot be repaired.",
|
|
448
|
+
500,
|
|
449
|
+
"cline_cli_setup_failed",
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
const settings = asRecord(parsed);
|
|
453
|
+
if (!settings) {
|
|
454
|
+
throw new ClineSettingsError(
|
|
455
|
+
"Cline global settings are malformed; the tool preflight cannot be repaired.",
|
|
456
|
+
500,
|
|
457
|
+
"cline_cli_setup_failed",
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
const current = Array.isArray(settings.disabledTools)
|
|
461
|
+
? settings.disabledTools.filter((value): value is string => typeof value === "string")
|
|
462
|
+
: [];
|
|
463
|
+
const merged = [...new Set([...current, ...ids])];
|
|
464
|
+
await writeFile(
|
|
465
|
+
staged.globalSettingsPath,
|
|
466
|
+
`${JSON.stringify({ ...settings, disabledTools: merged }, null, 2)}\n`,
|
|
467
|
+
{ encoding: "utf8", mode: 0o600 },
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
|
|
412
471
|
export function buildClineChildEnv(staged: ClineStagedTurn, sourceEnv: NodeJS.ProcessEnv = process.env): Record<string, string> {
|
|
413
472
|
const env = baseScopedEnv();
|
|
414
473
|
env.HOME = staged.homeDir;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import type { AdapterEvent } from "../../../types";
|
|
1
|
+
import type { AdapterEvent, OcxUsage } from "../../../types";
|
|
2
2
|
import type { CliAgentStreamState } from "../types";
|
|
3
3
|
import { mapToolIntent, ToolIntentError } from "../tool-intent";
|
|
4
|
+
import { redactSecretString } from "../../../lib/redact";
|
|
5
|
+
|
|
6
|
+
const MAX_ERROR_CHARS = 2_048;
|
|
4
7
|
|
|
5
8
|
export class ClineStreamProtocolError extends Error {
|
|
6
9
|
readonly code = "cli_protocol_error";
|
|
@@ -42,3 +45,200 @@ export function mapClineToolIntent(
|
|
|
42
45
|
throw error;
|
|
43
46
|
}
|
|
44
47
|
}
|
|
48
|
+
|
|
49
|
+
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
50
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
51
|
+
? value as Record<string, unknown>
|
|
52
|
+
: undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function asString(value: unknown): string | undefined {
|
|
56
|
+
return typeof value === "string" ? value : undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function safeMessage(value: unknown): string {
|
|
60
|
+
const raw = typeof value === "string"
|
|
61
|
+
? value
|
|
62
|
+
: value instanceof Error
|
|
63
|
+
? value.message
|
|
64
|
+
: value === undefined
|
|
65
|
+
? "Cline reported an error."
|
|
66
|
+
: JSON.stringify(value) ?? String(value);
|
|
67
|
+
return redactSecretString(raw).slice(0, MAX_ERROR_CHARS);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function classifyError(message: string): { status: number; errorType: string; code: string; retryable: boolean } {
|
|
71
|
+
if (/not logged in|please (?:run )?\/?login|sign in|authentication (?:failed|required)|unauthorized|invalid (?:api )?key/i.test(message)) {
|
|
72
|
+
return { status: 401, errorType: "authentication_error", code: "cline_cli_not_logged_in", retryable: false };
|
|
73
|
+
}
|
|
74
|
+
if (/rate limit|too many requests|quota|credit|usage limit|daily limit/i.test(message)) {
|
|
75
|
+
return { status: 429, errorType: "rate_limit_error", code: "rate_limit_exceeded", retryable: true };
|
|
76
|
+
}
|
|
77
|
+
if (/model (?:is )?(?:not found|unavailable|unsupported)|invalid model/i.test(message)) {
|
|
78
|
+
return { status: 400, errorType: "invalid_request_error", code: "model_not_found", retryable: false };
|
|
79
|
+
}
|
|
80
|
+
return { status: 502, errorType: "upstream_error", code: "cli_protocol_error", retryable: false };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function usageFrom(value: unknown): OcxUsage | undefined {
|
|
84
|
+
const record = asRecord(value);
|
|
85
|
+
if (!record) return undefined;
|
|
86
|
+
const count = (input: unknown): number =>
|
|
87
|
+
typeof input === "number" && Number.isFinite(input) && input >= 0 ? Math.floor(input) : 0;
|
|
88
|
+
const inputTokens = count(record.inputTokens);
|
|
89
|
+
const outputTokens = count(record.outputTokens);
|
|
90
|
+
const cacheRead = count(record.cacheReadTokens);
|
|
91
|
+
const cacheWrite = count(record.cacheWriteTokens);
|
|
92
|
+
if (inputTokens === 0 && outputTokens === 0 && cacheRead === 0 && cacheWrite === 0) return undefined;
|
|
93
|
+
return {
|
|
94
|
+
inputTokens,
|
|
95
|
+
outputTokens,
|
|
96
|
+
totalTokens: inputTokens + outputTokens,
|
|
97
|
+
...(cacheRead > 0 ? { cachedInputTokens: cacheRead, cacheReadInputTokens: cacheRead } : {}),
|
|
98
|
+
...(cacheWrite > 0 ? { cacheCreationInputTokens: cacheWrite } : {}),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function mapAgentEvent(
|
|
103
|
+
event: Record<string, unknown>,
|
|
104
|
+
state: CliAgentStreamState,
|
|
105
|
+
options: ClineStreamOptions,
|
|
106
|
+
): AdapterEvent[] {
|
|
107
|
+
const type = asString(event.type);
|
|
108
|
+
if (type === "content_start") {
|
|
109
|
+
const contentType = asString(event.contentType);
|
|
110
|
+
if (contentType === "text") {
|
|
111
|
+
const text = asString(event.text);
|
|
112
|
+
if (!text) return [];
|
|
113
|
+
state.sawText = true;
|
|
114
|
+
return [{ type: "text_delta", text }];
|
|
115
|
+
}
|
|
116
|
+
if (contentType === "reasoning") {
|
|
117
|
+
const thinking = asString(event.reasoning);
|
|
118
|
+
if (!thinking) return [];
|
|
119
|
+
state.sawThinking = true;
|
|
120
|
+
return [{ type: "thinking_delta", thinking }];
|
|
121
|
+
}
|
|
122
|
+
if (contentType === "tool") {
|
|
123
|
+
const vendorName = asString(event.toolName);
|
|
124
|
+
if (!vendorName) throw new ClineStreamProtocolError("Cline returned a tool call without a name.");
|
|
125
|
+
return mapClineToolIntent(vendorName, asString(event.toolCallId), event.input, state, options);
|
|
126
|
+
}
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (type === "content_end") {
|
|
131
|
+
const contentType = asString(event.contentType);
|
|
132
|
+
if (contentType === "text" && !state.sawText) {
|
|
133
|
+
const text = asString(event.text);
|
|
134
|
+
if (text) {
|
|
135
|
+
state.sawText = true;
|
|
136
|
+
return [{ type: "text_delta", text }];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (contentType === "reasoning" && !state.sawThinking) {
|
|
140
|
+
const thinking = asString(event.reasoning);
|
|
141
|
+
if (thinking) {
|
|
142
|
+
state.sawThinking = true;
|
|
143
|
+
return [{ type: "thinking_delta", thinking }];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (type === "usage") {
|
|
150
|
+
const usage = usageFrom(event);
|
|
151
|
+
if (usage) state.usage = usage;
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (type === "error") {
|
|
156
|
+
if (event.recoverable === true) return [];
|
|
157
|
+
const message = safeMessage(asRecord(event.error)?.message ?? event.error ?? "Cline reported an error.");
|
|
158
|
+
state.lastError = message;
|
|
159
|
+
state.terminal = true;
|
|
160
|
+
return [{ type: "error", message, ...classifyError(message) }];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (type === "done") {
|
|
164
|
+
state.terminal = true;
|
|
165
|
+
if (state.toolCalls > 0) return [];
|
|
166
|
+
const events: AdapterEvent[] = [];
|
|
167
|
+
if (!state.sawText) {
|
|
168
|
+
const text = asString(event.text);
|
|
169
|
+
if (text) {
|
|
170
|
+
state.sawText = true;
|
|
171
|
+
events.push({ type: "text_delta", text });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const usage = usageFrom(event.usage) ?? state.usage;
|
|
175
|
+
events.push({ type: "done", stopReason: "stop", ...(usage ? { usage } : {}) });
|
|
176
|
+
return events;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Map one bounded Cline JSON frame to provider-neutral events. */
|
|
183
|
+
export function mapClineMessage(
|
|
184
|
+
message: Record<string, unknown>,
|
|
185
|
+
state: CliAgentStreamState,
|
|
186
|
+
options: ClineStreamOptions,
|
|
187
|
+
): AdapterEvent[] {
|
|
188
|
+
if (state.terminal) return [];
|
|
189
|
+
const type = asString(message.type);
|
|
190
|
+
if (type === "agent_event") {
|
|
191
|
+
const event = asRecord(message.event);
|
|
192
|
+
return event ? mapAgentEvent(event, state, options) : [];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (type === "run_result") {
|
|
196
|
+
if (state.toolCalls > 0) return [];
|
|
197
|
+
const usage = usageFrom(message.usage) ?? state.usage;
|
|
198
|
+
if (usage) state.usage = usage;
|
|
199
|
+
const finishReason = asString(message.finishReason);
|
|
200
|
+
if (finishReason && finishReason !== "completed" && finishReason !== "stop") {
|
|
201
|
+
const messageText = safeMessage(message.text ?? `Cline finished with ${finishReason}.`);
|
|
202
|
+
state.terminal = true;
|
|
203
|
+
return [{ type: "error", message: messageText, ...classifyError(messageText) }];
|
|
204
|
+
}
|
|
205
|
+
state.terminal = true;
|
|
206
|
+
const events: AdapterEvent[] = [];
|
|
207
|
+
if (!state.sawText) {
|
|
208
|
+
const text = asString(message.text);
|
|
209
|
+
if (text) {
|
|
210
|
+
state.sawText = true;
|
|
211
|
+
events.push({ type: "text_delta", text });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
events.push({ type: "done", stopReason: "stop", ...(usage ? { usage } : {}) });
|
|
215
|
+
return events;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (type === "run_aborted") {
|
|
219
|
+
state.terminal = true;
|
|
220
|
+
return [{
|
|
221
|
+
type: "error",
|
|
222
|
+
message: safeMessage(message.message ?? "Cline aborted the turn."),
|
|
223
|
+
status: 499,
|
|
224
|
+
errorType: "invalid_request_error",
|
|
225
|
+
code: "client_cancelled",
|
|
226
|
+
retryable: false,
|
|
227
|
+
}];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (type === "error") {
|
|
231
|
+
const detail = safeMessage(message.message ?? "Cline reported an error.");
|
|
232
|
+
state.lastError = detail;
|
|
233
|
+
state.terminal = true;
|
|
234
|
+
return [{ type: "error", message: detail, ...classifyError(detail) }];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return [];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Convert a nonzero process exit into a stable terminal error. */
|
|
241
|
+
export function classifyClineFailure(exitCode: number, stderr: string): Extract<AdapterEvent, { type: "error" }> {
|
|
242
|
+
const detail = safeMessage(stderr.trim() || `Cline exited with code ${exitCode}.`);
|
|
243
|
+
return { type: "error", message: detail, ...classifyError(detail) };
|
|
244
|
+
}
|
|
@@ -177,7 +177,10 @@ async function runGeminiHelp(binary: string, env: Record<string, string>, deps:
|
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
/** Build the Gemini ACP environment while leaving vendor auth in its real home. */
|
|
180
|
-
export function buildGeminiAcpEnv(
|
|
180
|
+
export function buildGeminiAcpEnv(
|
|
181
|
+
sourceEnv: NodeJS.ProcessEnv = process.env,
|
|
182
|
+
platform: NodeJS.Platform = process.platform,
|
|
183
|
+
): Record<string, string> {
|
|
181
184
|
const env = baseScopedEnv();
|
|
182
185
|
const realHome = nonEmpty(sourceEnv.HOME) ?? nonEmpty(sourceEnv.USERPROFILE) ?? homedir();
|
|
183
186
|
env.HOME = realHome;
|
|
@@ -192,6 +195,8 @@ export function buildGeminiAcpEnv(sourceEnv: NodeJS.ProcessEnv = process.env): R
|
|
|
192
195
|
NO_UPDATE_NOTIFIER: "1",
|
|
193
196
|
NO_COLOR: "1",
|
|
194
197
|
});
|
|
198
|
+
// Windows Node needs the OS store for corporate TLS roots.
|
|
199
|
+
if (platform === "win32") env.NODE_USE_SYSTEM_CA = "1";
|
|
195
200
|
return env;
|
|
196
201
|
}
|
|
197
202
|
|
|
@@ -287,7 +287,7 @@ export async function runGeminiTurn(
|
|
|
287
287
|
...(deps.reapTimeoutMs !== undefined ? { reapTimeoutMs: deps.reapTimeoutMs } : {}),
|
|
288
288
|
...(deps.killWindowsProcessTree ? { killWindowsProcessTree: deps.killWindowsProcessTree } : {}),
|
|
289
289
|
};
|
|
290
|
-
const acpEnv = buildGeminiAcpEnv(sourceEnv);
|
|
290
|
+
const acpEnv = buildGeminiAcpEnv(sourceEnv, deps.platform ?? process.platform);
|
|
291
291
|
let mode;
|
|
292
292
|
try {
|
|
293
293
|
mode = await (deps.probeAcp ?? probeGeminiAcpSupport)(binary, acpEnv, acpDeps);
|
|
@@ -105,7 +105,7 @@ export async function discoverGeminiLiveModels(options: GeminiLiveModelsOptions
|
|
|
105
105
|
?? { ok: false, reason: "backoff", detail: "Gemini model discovery is cooling down after a failure.", fingerprint };
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
const env = buildGeminiAcpEnv(sourceEnv);
|
|
108
|
+
const env = buildGeminiAcpEnv(sourceEnv, options.deps?.platform ?? process.platform);
|
|
109
109
|
const mode = await probeGeminiAcpSupport(binary, env, { ...options.deps, env: sourceEnv });
|
|
110
110
|
if (!mode.acp) {
|
|
111
111
|
failureCache.set(fingerprint, timestamp);
|
package/src/cli/entry.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { ConfigError } from "../config";
|
|
|
22
22
|
import { UpstreamPackageError, resolveUpstreamPackage } from "../upstream/package";
|
|
23
23
|
import { loadUpstreamCli } from "../upstream/cli";
|
|
24
24
|
import { runAdapterCli } from "./index";
|
|
25
|
+
import packageInfo from "../../package.json";
|
|
25
26
|
|
|
26
27
|
type AdapterCli = (argv: string[]) => Promise<number>;
|
|
27
28
|
type UpstreamLoader = () => Promise<void>;
|
|
@@ -42,6 +43,7 @@ function isAdapterId(value: string | undefined): value is AdapterProviderId {
|
|
|
42
43
|
export function isAdapterOwnedCommand(argv: readonly string[]): boolean {
|
|
43
44
|
const command = argv[0];
|
|
44
45
|
if (command === "__cli-agent-mcp") return true;
|
|
46
|
+
if (command === "version" || command === "--version" || command === "-v") return true;
|
|
45
47
|
if (command === "update") return true;
|
|
46
48
|
if (command === "system" && argv.slice(1).includes("update")) return true;
|
|
47
49
|
if (command === "login" && (argv[1] === "cline-cli" || argv[1] === "gemini-cli")) return true;
|
|
@@ -111,8 +113,14 @@ function printDistributionUpdate(argv: readonly string[]): number {
|
|
|
111
113
|
return 2;
|
|
112
114
|
}
|
|
113
115
|
|
|
116
|
+
function printVersion(): number {
|
|
117
|
+
console.log("opencodex " + packageInfo.version);
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
114
121
|
async function runAdapterCommand(argv: string[], env: NodeJS.ProcessEnv, adapterCli: AdapterCli): Promise<number> {
|
|
115
122
|
if (argv[0] === "__cli-agent-mcp") return adapterCli(argv);
|
|
123
|
+
if (argv[0] === "version" || argv[0] === "--version" || argv[0] === "-v") return printVersion();
|
|
116
124
|
if (argv[0] === "login") return adapterCli(argv);
|
|
117
125
|
if (argv[0] === "update" || (argv[0] === "system" && argv.slice(1).includes("update"))) {
|
|
118
126
|
return printDistributionUpdate(argv);
|