@cerulin/chell 0.2.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.
Files changed (53) hide show
  1. package/README.md +75 -0
  2. package/bin/chell-mcp.mjs +33 -0
  3. package/bin/chell.mjs +37 -0
  4. package/dist/codex/chellMcpStdioBridge.cjs +80 -0
  5. package/dist/codex/chellMcpStdioBridge.d.cts +2 -0
  6. package/dist/codex/chellMcpStdioBridge.d.mts +2 -0
  7. package/dist/codex/chellMcpStdioBridge.mjs +78 -0
  8. package/dist/index-B443j7JQ.mjs +6714 -0
  9. package/dist/index-qS668VWY.cjs +6730 -0
  10. package/dist/index.cjs +42 -0
  11. package/dist/index.d.cts +1 -0
  12. package/dist/index.d.mts +1 -0
  13. package/dist/index.mjs +39 -0
  14. package/dist/lib.cjs +32 -0
  15. package/dist/lib.d.cts +891 -0
  16. package/dist/lib.d.mts +891 -0
  17. package/dist/lib.mjs +22 -0
  18. package/dist/runCodex-DHtm7TWT.cjs +2020 -0
  19. package/dist/runCodex-DLbjgnc4.mjs +2017 -0
  20. package/dist/runGemini-C03RUmvr.mjs +788 -0
  21. package/dist/runGemini-fdb5jxAA.cjs +791 -0
  22. package/dist/types-DBjv5m4J.cjs +2499 -0
  23. package/dist/types-fM_iFuNp.mjs +2452 -0
  24. package/package.json +131 -0
  25. package/scripts/claude_local_launcher.cjs +98 -0
  26. package/scripts/claude_remote_launcher.cjs +13 -0
  27. package/scripts/codex_local_launcher.cjs +155 -0
  28. package/scripts/codex_preload.cjs +56 -0
  29. package/scripts/codex_remote_launcher.cjs +129 -0
  30. package/scripts/obfuscate-dist.mjs +73 -0
  31. package/scripts/pack-chell.cjs +32 -0
  32. package/scripts/publish-scoped.ps1 +58 -0
  33. package/scripts/ripgrep_launcher.cjs +33 -0
  34. package/scripts/unpack-tools.cjs +163 -0
  35. package/tools/archives/difftastic-LICENSE +21 -0
  36. package/tools/archives/difftastic-arm64-darwin.tar.gz +0 -0
  37. package/tools/archives/difftastic-arm64-linux.tar.gz +0 -0
  38. package/tools/archives/difftastic-x64-darwin.tar.gz +0 -0
  39. package/tools/archives/difftastic-x64-linux.tar.gz +0 -0
  40. package/tools/archives/difftastic-x64-win32.tar.gz +0 -0
  41. package/tools/archives/ripgrep-LICENSE +3 -0
  42. package/tools/archives/ripgrep-arm64-darwin.tar.gz +0 -0
  43. package/tools/archives/ripgrep-arm64-linux.tar.gz +0 -0
  44. package/tools/archives/ripgrep-x64-darwin.tar.gz +0 -0
  45. package/tools/archives/ripgrep-x64-linux.tar.gz +0 -0
  46. package/tools/archives/ripgrep-x64-win32.tar.gz +0 -0
  47. package/tools/licenses/difftastic-LICENSE +21 -0
  48. package/tools/licenses/ripgrep-LICENSE +3 -0
  49. package/tools/unpacked/difft +0 -0
  50. package/tools/unpacked/difft.exe +0 -0
  51. package/tools/unpacked/rg +0 -0
  52. package/tools/unpacked/rg.exe +0 -0
  53. package/tools/unpacked/ripgrep.node +0 -0
@@ -0,0 +1,788 @@
1
+ import { useStdout, useInput, Box, Text, render } from 'ink';
2
+ import React, { useState, useRef, useEffect, useCallback } from 'react';
3
+ import { l as logger, A as ApiClient, r as readSettings, p as projectPath, c as configuration, b as packageJson, u as updateActivityTimestamp } from './types-fM_iFuNp.mjs';
4
+ import { spawn } from 'child_process';
5
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, unlinkSync } from 'fs';
6
+ import { join } from 'path';
7
+ import { homedir } from 'os';
8
+ import { randomUUID } from 'crypto';
9
+ import { randomUUID as randomUUID$1 } from 'node:crypto';
10
+ import { i as initialMachineMetadata, M as MessageQueue2, h as hashObject, r as registerKillSessionHandler, a as MessageBuffer, s as startChellServer } from './index-B443j7JQ.mjs';
11
+ import os from 'node:os';
12
+ import { join as join$1 } from 'node:path';
13
+ import 'axios';
14
+ import 'chalk';
15
+ import 'node:fs';
16
+ import 'node:fs/promises';
17
+ import 'zod';
18
+ import 'tweetnacl';
19
+ import 'node:events';
20
+ import 'socket.io-client';
21
+ import 'util';
22
+ import 'fs/promises';
23
+ import 'url';
24
+ import 'node-pty';
25
+ import 'expo-server-sdk';
26
+ import 'node:child_process';
27
+ import 'node:readline';
28
+ import 'node:url';
29
+ import 'ps-list';
30
+ import 'cross-spawn';
31
+ import 'qrcode-terminal';
32
+ import 'open';
33
+ import 'fastify';
34
+ import 'fastify-type-provider-zod';
35
+ import '@modelcontextprotocol/sdk/server/mcp.js';
36
+ import 'node:http';
37
+ import '@modelcontextprotocol/sdk/server/streamableHttp.js';
38
+ import 'http';
39
+ import 'readline';
40
+
41
+ class GeminiClient {
42
+ originalSettings = null;
43
+ settingsPath;
44
+ handler = null;
45
+ isRunning = false;
46
+ sessionId = null;
47
+ config = null;
48
+ checkpointDir = null;
49
+ constructor() {
50
+ const geminiHome = process.env.GEMINI_HOME || join(homedir(), ".gemini");
51
+ this.settingsPath = join(geminiHome, "settings.json");
52
+ }
53
+ /**
54
+ * Set event handler for Gemini events
55
+ */
56
+ setHandler(handler) {
57
+ this.handler = handler;
58
+ }
59
+ /**
60
+ * Inject chell MCP server into Gemini settings
61
+ * Backs up existing settings and merges our MCP server config
62
+ */
63
+ injectMcpServerConfig(chellServerUrl, bridgeCommand) {
64
+ if (existsSync(this.settingsPath)) {
65
+ this.originalSettings = readFileSync(this.settingsPath, "utf-8");
66
+ logger.debug("[GeminiClient] Backed up existing settings");
67
+ }
68
+ let existingSettings = {};
69
+ if (this.originalSettings) {
70
+ try {
71
+ existingSettings = JSON.parse(this.originalSettings);
72
+ } catch (error) {
73
+ logger.debug("[GeminiClient] Failed to parse existing settings, using empty config");
74
+ }
75
+ }
76
+ const updatedSettings = {
77
+ ...existingSettings,
78
+ mcpServers: {
79
+ ...existingSettings.mcpServers,
80
+ chell: {
81
+ command: "node",
82
+ args: [bridgeCommand, "--url", chellServerUrl],
83
+ trust: true
84
+ }
85
+ }
86
+ };
87
+ mkdirSync(join(this.settingsPath, ".."), { recursive: true });
88
+ writeFileSync(this.settingsPath, JSON.stringify(updatedSettings, null, 2));
89
+ logger.debug("[GeminiClient] Injected MCP server config into:", this.settingsPath);
90
+ }
91
+ /**
92
+ * Start Gemini session (stores config, injects MCP, creates checkpoint dir)
93
+ */
94
+ async startSession(config, chellServerUrl, bridgeCommand, options) {
95
+ if (this.isRunning) {
96
+ throw new Error("Session already running");
97
+ }
98
+ this.sessionId = randomUUID();
99
+ this.config = config;
100
+ this.isRunning = true;
101
+ const geminiHome = process.env.GEMINI_HOME || join(homedir(), ".gemini");
102
+ this.checkpointDir = join(geminiHome, "chell-checkpoints", this.sessionId);
103
+ mkdirSync(this.checkpointDir, { recursive: true });
104
+ this.injectMcpServerConfig(chellServerUrl, bridgeCommand);
105
+ logger.debug("[GeminiClient] Session initialized with checkpoint dir:", this.checkpointDir);
106
+ }
107
+ /**
108
+ * Send user message to Gemini (spawns one-shot process)
109
+ */
110
+ async sendMessage(message) {
111
+ if (!this.isRunning || !this.config) {
112
+ throw new Error("No active session");
113
+ }
114
+ logger.debug("[GeminiClient] Running one-shot Gemini:", message);
115
+ const args = [
116
+ "--output-format",
117
+ "json",
118
+ "--checkpointing",
119
+ // Enable checkpointing for conversation history
120
+ "--prompt",
121
+ message
122
+ ];
123
+ if (this.config.model) {
124
+ args.push("-m", this.config.model);
125
+ }
126
+ if (this.config.yolo) {
127
+ args.push("--yolo");
128
+ }
129
+ if (this.config.debug) {
130
+ args.push("--debug");
131
+ }
132
+ if (this.config.includeDirectories && this.config.includeDirectories.length > 0) {
133
+ args.push("--include-directories", this.config.includeDirectories.join(","));
134
+ }
135
+ return new Promise((resolve, reject) => {
136
+ const proc = spawn("gemini", args, {
137
+ cwd: this.checkpointDir || process.cwd(),
138
+ // Run in checkpoint dir to use same checkpoint files
139
+ env: {
140
+ ...process.env,
141
+ DISABLE_PROGRESS_BAR: "1",
142
+ NO_COLOR: "1",
143
+ FORCE_COLOR: "0"
144
+ }
145
+ });
146
+ let stdout = "";
147
+ let stderr = "";
148
+ proc.stdout?.on("data", (data) => {
149
+ stdout += data.toString();
150
+ });
151
+ proc.stderr?.on("data", (data) => {
152
+ stderr += data.toString();
153
+ logger.debug("[GeminiClient] stderr:", data.toString());
154
+ });
155
+ proc.on("exit", (code) => {
156
+ if (code !== 0) {
157
+ logger.debug("[GeminiClient] Process exited with code:", code);
158
+ this.handler?.({
159
+ type: "error",
160
+ message: `Gemini failed: ${stderr || "Unknown error"}`
161
+ });
162
+ reject(new Error(`Gemini exited with code ${code}`));
163
+ return;
164
+ }
165
+ try {
166
+ const response = JSON.parse(stdout);
167
+ logger.debug("[GeminiClient] Response:", response);
168
+ const text = response.response || response.text || JSON.stringify(response);
169
+ this.handler?.({
170
+ type: "message",
171
+ role: "assistant",
172
+ content: text
173
+ });
174
+ resolve();
175
+ } catch (error) {
176
+ logger.debug("[GeminiClient] Failed to parse JSON:", stdout);
177
+ if (stdout.trim()) {
178
+ this.handler?.({
179
+ type: "message",
180
+ role: "assistant",
181
+ content: stdout.trim()
182
+ });
183
+ }
184
+ resolve();
185
+ }
186
+ });
187
+ proc.on("error", (error) => {
188
+ logger.debug("[GeminiClient] Process error:", error);
189
+ reject(error);
190
+ });
191
+ });
192
+ }
193
+ /**
194
+ * Abort current task (no-op for one-shot mode)
195
+ */
196
+ abort() {
197
+ logger.debug("[GeminiClient] Abort called (no-op in one-shot mode)");
198
+ }
199
+ /**
200
+ * Check if session is active
201
+ */
202
+ hasActiveSession() {
203
+ return this.isRunning;
204
+ }
205
+ /**
206
+ * Get current session ID
207
+ */
208
+ getSessionId() {
209
+ return this.sessionId;
210
+ }
211
+ /**
212
+ * Kill session and cleanup
213
+ */
214
+ async disconnect() {
215
+ logger.debug("[GeminiClient] Disconnecting");
216
+ if (this.originalSettings !== null) {
217
+ try {
218
+ writeFileSync(this.settingsPath, this.originalSettings);
219
+ logger.debug("[GeminiClient] Restored original settings");
220
+ } catch (error) {
221
+ logger.debug("[GeminiClient] Failed to restore settings:", error);
222
+ }
223
+ this.originalSettings = null;
224
+ } else {
225
+ try {
226
+ if (existsSync(this.settingsPath)) {
227
+ unlinkSync(this.settingsPath);
228
+ logger.debug("[GeminiClient] Removed injected settings");
229
+ }
230
+ } catch (error) {
231
+ logger.debug("[GeminiClient] Failed to remove settings:", error);
232
+ }
233
+ }
234
+ if (this.checkpointDir) {
235
+ try {
236
+ logger.debug("[GeminiClient] Keeping checkpoint dir for potential resume:", this.checkpointDir);
237
+ } catch (error) {
238
+ logger.debug("[GeminiClient] Error with checkpoint cleanup:", error);
239
+ }
240
+ this.checkpointDir = null;
241
+ }
242
+ this.isRunning = false;
243
+ this.sessionId = null;
244
+ this.config = null;
245
+ logger.debug("[GeminiClient] Disconnected");
246
+ }
247
+ }
248
+
249
+ class GeminiPermissionHandler {
250
+ session;
251
+ pendingRequests = /* @__PURE__ */ new Map();
252
+ constructor(session) {
253
+ this.session = session;
254
+ this.setupRpcHandler();
255
+ }
256
+ /**
257
+ * Handle tool permission request
258
+ */
259
+ async handleToolCall(callId, toolName, input) {
260
+ return new Promise((resolve, reject) => {
261
+ this.pendingRequests.set(callId, {
262
+ resolve,
263
+ reject,
264
+ toolName,
265
+ input
266
+ });
267
+ this.session.updateAgentState((currentState) => ({
268
+ ...currentState,
269
+ requests: {
270
+ ...currentState.requests,
271
+ [callId]: {
272
+ tool: toolName,
273
+ arguments: input,
274
+ createdAt: Date.now()
275
+ }
276
+ }
277
+ }));
278
+ logger.debug(`[Gemini] Permission request sent for tool: ${toolName} (${callId})`);
279
+ });
280
+ }
281
+ /**
282
+ * Setup RPC handler for permission responses
283
+ */
284
+ setupRpcHandler() {
285
+ this.session.rpcHandlerManager.registerHandler(
286
+ "permission",
287
+ async (response) => {
288
+ const pending = this.pendingRequests.get(response.id);
289
+ if (!pending) {
290
+ logger.debug("[Gemini] Permission request not found or already resolved");
291
+ return;
292
+ }
293
+ this.pendingRequests.delete(response.id);
294
+ const result = response.approved ? { decision: "approved" } : { decision: "denied" };
295
+ pending.resolve(result);
296
+ this.session.updateAgentState((currentState) => {
297
+ const request = currentState.requests?.[response.id];
298
+ if (!request) return currentState;
299
+ const { [response.id]: _, ...remainingRequests } = currentState.requests || {};
300
+ return {
301
+ ...currentState,
302
+ requests: remainingRequests,
303
+ completedRequests: {
304
+ ...currentState.completedRequests,
305
+ [response.id]: {
306
+ ...request,
307
+ completedAt: Date.now(),
308
+ status: response.approved ? "approved" : "denied",
309
+ decision: result.decision
310
+ }
311
+ }
312
+ };
313
+ });
314
+ logger.debug(`[Gemini] Permission ${response.approved ? "approved" : "denied"} for ${pending.toolName}`);
315
+ }
316
+ );
317
+ }
318
+ /**
319
+ * Reset handler state (on abort or task completion)
320
+ */
321
+ reset() {
322
+ for (const [id, pending] of this.pendingRequests.entries()) {
323
+ pending.reject(new Error("Permission handler reset"));
324
+ }
325
+ this.pendingRequests.clear();
326
+ this.session.updateAgentState((currentState) => {
327
+ const pendingRequests = currentState.requests || {};
328
+ const completedRequests = { ...currentState.completedRequests };
329
+ for (const [id, request] of Object.entries(pendingRequests)) {
330
+ completedRequests[id] = {
331
+ ...request,
332
+ completedAt: Date.now(),
333
+ status: "canceled",
334
+ reason: "Session reset"
335
+ };
336
+ }
337
+ return {
338
+ ...currentState,
339
+ requests: {},
340
+ completedRequests
341
+ };
342
+ });
343
+ logger.debug("[Gemini] Permission handler reset");
344
+ }
345
+ }
346
+
347
+ const GeminiDisplay = ({ messageBuffer, logPath, onExit }) => {
348
+ const [messages, setMessages] = useState([]);
349
+ const [confirmationMode, setConfirmationMode] = useState(false);
350
+ const [actionInProgress, setActionInProgress] = useState(false);
351
+ const confirmationTimeoutRef = useRef(null);
352
+ const { stdout } = useStdout();
353
+ const terminalWidth = stdout.columns || 80;
354
+ const terminalHeight = stdout.rows || 24;
355
+ useEffect(() => {
356
+ setMessages(messageBuffer.getMessages());
357
+ const unsubscribe = messageBuffer.onUpdate((newMessages) => {
358
+ setMessages(newMessages);
359
+ });
360
+ return () => {
361
+ unsubscribe();
362
+ if (confirmationTimeoutRef.current) {
363
+ clearTimeout(confirmationTimeoutRef.current);
364
+ }
365
+ };
366
+ }, [messageBuffer]);
367
+ const resetConfirmation = useCallback(() => {
368
+ setConfirmationMode(false);
369
+ if (confirmationTimeoutRef.current) {
370
+ clearTimeout(confirmationTimeoutRef.current);
371
+ confirmationTimeoutRef.current = null;
372
+ }
373
+ }, []);
374
+ const setConfirmationWithTimeout = useCallback(() => {
375
+ setConfirmationMode(true);
376
+ if (confirmationTimeoutRef.current) {
377
+ clearTimeout(confirmationTimeoutRef.current);
378
+ }
379
+ confirmationTimeoutRef.current = setTimeout(() => {
380
+ resetConfirmation();
381
+ }, 15e3);
382
+ }, [resetConfirmation]);
383
+ useInput(useCallback(async (input, key) => {
384
+ if (actionInProgress) return;
385
+ if (key.ctrl && input === "c") {
386
+ if (confirmationMode) {
387
+ resetConfirmation();
388
+ setActionInProgress(true);
389
+ await new Promise((resolve) => setTimeout(resolve, 100));
390
+ onExit?.();
391
+ } else {
392
+ setConfirmationWithTimeout();
393
+ }
394
+ return;
395
+ }
396
+ if (confirmationMode) {
397
+ resetConfirmation();
398
+ }
399
+ }, [confirmationMode, actionInProgress, onExit, setConfirmationWithTimeout, resetConfirmation]));
400
+ const getMessageColor = (type) => {
401
+ switch (type) {
402
+ case "user":
403
+ return "magenta";
404
+ case "assistant":
405
+ return "cyan";
406
+ case "system":
407
+ return "blue";
408
+ case "tool":
409
+ return "yellow";
410
+ case "result":
411
+ return "green";
412
+ case "status":
413
+ return "gray";
414
+ default:
415
+ return "white";
416
+ }
417
+ };
418
+ const formatMessage = (msg) => {
419
+ const lines = msg.content.split("\n");
420
+ const maxLineLength = terminalWidth - 10;
421
+ return lines.map((line) => {
422
+ if (line.length <= maxLineLength) return line;
423
+ const chunks = [];
424
+ for (let i = 0; i < line.length; i += maxLineLength) {
425
+ chunks.push(line.slice(i, i + maxLineLength));
426
+ }
427
+ return chunks.join("\n");
428
+ }).join("\n");
429
+ };
430
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", width: terminalWidth, height: terminalHeight }, /* @__PURE__ */ React.createElement(
431
+ Box,
432
+ {
433
+ flexDirection: "column",
434
+ width: terminalWidth,
435
+ height: terminalHeight - 4,
436
+ borderStyle: "round",
437
+ borderColor: "gray",
438
+ paddingX: 1,
439
+ overflow: "hidden"
440
+ },
441
+ /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "gray", bold: true }, "\u{1F916} Gemini Agent Messages"), /* @__PURE__ */ React.createElement(Text, { color: "gray", dimColor: true }, "\u2500".repeat(Math.min(terminalWidth - 4, 60)))),
442
+ /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", height: terminalHeight - 10, overflow: "hidden" }, messages.length === 0 ? /* @__PURE__ */ React.createElement(Text, { color: "gray", dimColor: true }, "Waiting for messages...") : (
443
+ // Show only the last messages that fit in the available space
444
+ messages.slice(-Math.max(1, terminalHeight - 10)).map((msg) => /* @__PURE__ */ React.createElement(Box, { key: msg.id, flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { color: getMessageColor(msg.type), dimColor: true }, formatMessage(msg))))
445
+ ))
446
+ ), /* @__PURE__ */ React.createElement(
447
+ Box,
448
+ {
449
+ width: terminalWidth,
450
+ borderStyle: "round",
451
+ borderColor: actionInProgress ? "gray" : confirmationMode ? "red" : "green",
452
+ paddingX: 2,
453
+ justifyContent: "center",
454
+ alignItems: "center",
455
+ flexDirection: "column"
456
+ },
457
+ /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", alignItems: "center" }, actionInProgress ? /* @__PURE__ */ React.createElement(Text, { color: "gray", bold: true }, "Exiting agent...") : confirmationMode ? /* @__PURE__ */ React.createElement(Text, { color: "red", bold: true }, "\u26A0\uFE0F Press Ctrl-C again to exit the agent") : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Text, { color: "green", bold: true }, "\u{1F916} Gemini Agent Running \u2022 Ctrl-C to exit")), process.env.DEBUG && logPath && /* @__PURE__ */ React.createElement(Text, { color: "gray", dimColor: true }, "Debug logs: ", logPath))
458
+ ));
459
+ };
460
+
461
+ function emitReadyIfIdle({ pending, queueSize, shouldExit, sendReady, notify }) {
462
+ if (shouldExit) {
463
+ return false;
464
+ }
465
+ if (queueSize() > 0) {
466
+ return false;
467
+ }
468
+ sendReady();
469
+ notify?.();
470
+ return true;
471
+ }
472
+ async function runGemini(opts) {
473
+ const sessionTag = randomUUID$1();
474
+ const api = new ApiClient(opts.token, opts.secret);
475
+ logger.debug(`[gemini] Starting Gemini session`);
476
+ const settings = await readSettings();
477
+ let machineId = settings?.machineId;
478
+ if (!machineId) {
479
+ console.error(`[START] No machine ID found in settings, which is unexpected since authAndSetupMachineIfNeeded should have created it. Please report this issue.`);
480
+ process.exit(1);
481
+ }
482
+ logger.debug(`Using machineId: ${machineId}`);
483
+ await api.getOrCreateMachine({
484
+ machineId,
485
+ metadata: initialMachineMetadata
486
+ });
487
+ let state = {
488
+ controlledByUser: false
489
+ };
490
+ let metadata = {
491
+ path: process.cwd(),
492
+ host: os.hostname(),
493
+ version: packageJson.version,
494
+ os: os.platform(),
495
+ machineId,
496
+ homeDir: os.homedir(),
497
+ happyHomeDir: configuration.happyHomeDir,
498
+ happyLibDir: projectPath(),
499
+ happyToolsDir: join$1(projectPath(), "tools", "unpacked"),
500
+ startedFromDaemon: false,
501
+ hostPid: process.pid,
502
+ startedBy: "terminal",
503
+ lifecycleState: "running",
504
+ lifecycleStateSince: Date.now(),
505
+ flavor: "gemini"
506
+ };
507
+ const response = await api.getOrCreateSession({ tag: sessionTag, metadata, state });
508
+ const session = api.sessionSyncClient(response);
509
+ const messageQueue = new MessageQueue2((mode) => hashObject({
510
+ permissionMode: mode.permissionMode,
511
+ model: mode.model
512
+ }));
513
+ let currentPermissionMode = void 0;
514
+ let currentModel = void 0;
515
+ session.onUserMessage((message) => {
516
+ let messagePermissionMode = currentPermissionMode;
517
+ if (message.meta?.permissionMode) {
518
+ const validModes = ["default", "read-only", "safe-yolo", "yolo"];
519
+ if (validModes.includes(message.meta.permissionMode)) {
520
+ messagePermissionMode = message.meta.permissionMode;
521
+ currentPermissionMode = messagePermissionMode;
522
+ logger.debug(`[Gemini] Permission mode updated: ${currentPermissionMode}`);
523
+ }
524
+ }
525
+ let messageModel = currentModel;
526
+ if (message.meta?.hasOwnProperty("model")) {
527
+ messageModel = message.meta.model || void 0;
528
+ currentModel = messageModel;
529
+ logger.debug(`[Gemini] Model updated: ${messageModel || "default"}`);
530
+ }
531
+ const enhancedMode = {
532
+ permissionMode: messagePermissionMode || "default",
533
+ model: messageModel
534
+ };
535
+ messageQueue.push(message.content.text, enhancedMode);
536
+ });
537
+ let thinking = false;
538
+ session.keepAlive(thinking, "remote");
539
+ const keepAliveInterval = setInterval(() => {
540
+ session.keepAlive(thinking, "remote");
541
+ }, 2e3);
542
+ const sendReady = () => {
543
+ session.sendSessionEvent({ type: "ready" });
544
+ };
545
+ let abortController = new AbortController();
546
+ let shouldExit = false;
547
+ async function handleAbort() {
548
+ logger.debug("[Gemini] Abort requested");
549
+ try {
550
+ abortController.abort();
551
+ messageQueue.reset();
552
+ permissionHandler.reset();
553
+ if (client.hasActiveSession()) {
554
+ client.abort();
555
+ }
556
+ logger.debug("[Gemini] Abort completed");
557
+ } catch (error) {
558
+ logger.debug("[Gemini] Error during abort:", error);
559
+ } finally {
560
+ abortController = new AbortController();
561
+ }
562
+ }
563
+ const handleKillSession = async () => {
564
+ logger.debug("[Gemini] Kill session requested");
565
+ await handleAbort();
566
+ try {
567
+ if (session) {
568
+ session.updateMetadata((currentMetadata) => ({
569
+ ...currentMetadata,
570
+ lifecycleState: "archived",
571
+ lifecycleStateSince: Date.now(),
572
+ archivedBy: "cli",
573
+ archiveReason: "User terminated"
574
+ }));
575
+ session.sendSessionDeath();
576
+ await session.flush();
577
+ await session.close();
578
+ }
579
+ chellServer.stop();
580
+ logger.debug("[Gemini] Session termination complete");
581
+ process.exit(0);
582
+ } catch (error) {
583
+ logger.debug("[Gemini] Error during termination:", error);
584
+ process.exit(1);
585
+ }
586
+ };
587
+ session.rpcHandlerManager.registerHandler("abort", handleAbort);
588
+ registerKillSessionHandler(session.rpcHandlerManager, handleKillSession);
589
+ const messageBuffer = new MessageBuffer();
590
+ const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
591
+ let inkInstance = null;
592
+ if (hasTTY) {
593
+ console.clear();
594
+ inkInstance = render(React.createElement(GeminiDisplay, {
595
+ messageBuffer,
596
+ logPath: process.env.DEBUG ? logger.getLogPath() : void 0,
597
+ onExit: async () => {
598
+ logger.debug("[gemini]: Exiting via Ctrl-C");
599
+ shouldExit = true;
600
+ await handleAbort();
601
+ }
602
+ }), {
603
+ exitOnCtrlC: false,
604
+ patchConsole: false
605
+ });
606
+ }
607
+ if (hasTTY) {
608
+ process.stdin.resume();
609
+ if (process.stdin.isTTY) {
610
+ process.stdin.setRawMode(true);
611
+ }
612
+ process.stdin.setEncoding("utf8");
613
+ let inputBuffer = "";
614
+ process.stdin.on("data", (data) => {
615
+ for (let i = 0; i < data.length; i++) {
616
+ const char = data[i];
617
+ const code = data.charCodeAt(i);
618
+ if (code === 3) continue;
619
+ if (code === 127 || code === 8) {
620
+ if (inputBuffer.length > 0) {
621
+ inputBuffer = inputBuffer.slice(0, -1);
622
+ }
623
+ continue;
624
+ }
625
+ if (char === "\r" || char === "\n") {
626
+ const trimmed = inputBuffer.trim();
627
+ if (trimmed) {
628
+ const enhancedMode = {
629
+ permissionMode: currentPermissionMode || "default",
630
+ model: currentModel
631
+ };
632
+ messageQueue.push(trimmed, enhancedMode);
633
+ messageBuffer.addMessage(trimmed, "user");
634
+ logger.debug(`[Gemini] Local input: ${trimmed}`);
635
+ try {
636
+ session.sendClaudeSessionMessage({
637
+ type: "user",
638
+ uuid: randomUUID$1(),
639
+ message: {
640
+ content: [{ type: "text", text: trimmed }]
641
+ }
642
+ });
643
+ } catch (e) {
644
+ logger.debug("[Gemini] Failed to mirror input:", e);
645
+ }
646
+ }
647
+ inputBuffer = "";
648
+ continue;
649
+ }
650
+ if (code >= 32 && code < 127) {
651
+ inputBuffer += char;
652
+ }
653
+ }
654
+ });
655
+ }
656
+ const client = new GeminiClient();
657
+ const permissionHandler = new GeminiPermissionHandler(session);
658
+ const chellServer = await startChellServer(session);
659
+ const bridgeCommand = join$1(projectPath(), "bin", "chell-mcp.mjs");
660
+ client.setHandler((event) => {
661
+ logger.debug(`[Gemini] Event: ${event.type}`);
662
+ updateActivityTimestamp();
663
+ if (event.type === "message") {
664
+ messageBuffer.addMessage(event.content, event.role);
665
+ } else if (event.type === "tool_use") {
666
+ messageBuffer.addMessage(`Using tool: ${event.toolName}`, "tool");
667
+ } else if (event.type === "tool_result") {
668
+ const output = event.output ? JSON.stringify(event.output).substring(0, 200) : "Done";
669
+ messageBuffer.addMessage(`Result: ${output}`, "result");
670
+ } else if (event.type === "error") {
671
+ messageBuffer.addMessage(`Error: ${event.message}`, "status");
672
+ } else if (event.type === "task_started") {
673
+ messageBuffer.addMessage("Task started", "status");
674
+ if (!thinking) {
675
+ thinking = true;
676
+ session.keepAlive(thinking, "remote");
677
+ }
678
+ } else if (event.type === "task_complete") {
679
+ messageBuffer.addMessage("Task completed", "status");
680
+ if (thinking) {
681
+ thinking = false;
682
+ session.keepAlive(thinking, "remote");
683
+ }
684
+ sendReady();
685
+ }
686
+ if (event.type === "message") {
687
+ session.sendCodexMessage({
688
+ type: "message",
689
+ message: event.content,
690
+ id: randomUUID$1()
691
+ });
692
+ try {
693
+ session.sendClaudeSessionMessage({
694
+ type: "assistant",
695
+ uuid: randomUUID$1(),
696
+ message: {
697
+ content: [{ type: "text", text: event.content }]
698
+ }
699
+ });
700
+ } catch (e) {
701
+ logger.debug("[Gemini] Failed to mirror message:", e);
702
+ }
703
+ }
704
+ });
705
+ try {
706
+ let sessionStarted = false;
707
+ while (!shouldExit) {
708
+ const waitSignal = abortController.signal;
709
+ const batch = await messageQueue.waitForMessagesAndGetAsString(waitSignal);
710
+ if (!batch) {
711
+ if (waitSignal.aborted && !shouldExit) {
712
+ logger.debug("[gemini]: Wait aborted while idle");
713
+ continue;
714
+ }
715
+ break;
716
+ }
717
+ messageBuffer.addMessage(batch.message, "user");
718
+ try {
719
+ if (!sessionStarted) {
720
+ const config = {
721
+ model: batch.mode.model || "gemini-2.5-flash",
722
+ yolo: batch.mode.permissionMode === "yolo",
723
+ debug: !!process.env.DEBUG
724
+ };
725
+ await client.startSession(
726
+ config,
727
+ chellServer.url,
728
+ bridgeCommand,
729
+ { signal: abortController.signal }
730
+ );
731
+ sessionStarted = true;
732
+ }
733
+ await client.sendMessage(batch.message);
734
+ } catch (error) {
735
+ logger.warn("Error in gemini session:", error);
736
+ const isAbortError = error instanceof Error && error.name === "AbortError";
737
+ if (isAbortError) {
738
+ messageBuffer.addMessage("Aborted by user", "status");
739
+ session.sendSessionEvent({ type: "message", message: "Aborted by user" });
740
+ } else {
741
+ messageBuffer.addMessage("Process exited unexpectedly", "status");
742
+ session.sendSessionEvent({ type: "message", message: "Process exited unexpectedly" });
743
+ }
744
+ } finally {
745
+ permissionHandler.reset();
746
+ thinking = false;
747
+ session.keepAlive(thinking, "remote");
748
+ emitReadyIfIdle({
749
+ pending: null,
750
+ queueSize: () => messageQueue.size(),
751
+ shouldExit,
752
+ sendReady
753
+ });
754
+ }
755
+ }
756
+ } finally {
757
+ logger.debug("[gemini]: Final cleanup");
758
+ try {
759
+ session.sendSessionDeath();
760
+ await session.flush();
761
+ await session.close();
762
+ } catch (e) {
763
+ logger.debug("[gemini]: Error closing session", e);
764
+ }
765
+ await client.disconnect();
766
+ chellServer.stop();
767
+ if (process.stdin.isTTY) {
768
+ try {
769
+ process.stdin.setRawMode(false);
770
+ } catch {
771
+ }
772
+ }
773
+ if (hasTTY) {
774
+ try {
775
+ process.stdin.pause();
776
+ } catch {
777
+ }
778
+ }
779
+ clearInterval(keepAliveInterval);
780
+ if (inkInstance) {
781
+ inkInstance.unmount();
782
+ }
783
+ messageBuffer.clear();
784
+ logger.debug("[gemini]: Final cleanup completed");
785
+ }
786
+ }
787
+
788
+ export { emitReadyIfIdle, runGemini };