@google/gemini-cli 0.19.0-nightly.20251124.e177314a4 → 0.19.0-preview.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.
Files changed (176) hide show
  1. package/README.md +1 -1
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.js +0 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/package.json +3 -3
  6. package/dist/src/commands/extensions/examples/mcp-server/package.json +1 -1
  7. package/dist/src/commands/extensions.test.d.ts +6 -0
  8. package/dist/src/commands/extensions.test.js +67 -0
  9. package/dist/src/commands/extensions.test.js.map +1 -0
  10. package/dist/src/commands/utils.test.d.ts +6 -0
  11. package/dist/src/commands/utils.test.js +35 -0
  12. package/dist/src/commands/utils.test.js.map +1 -0
  13. package/dist/src/config/config.js +1 -3
  14. package/dist/src/config/config.js.map +1 -1
  15. package/dist/src/config/config.test.js +3 -16
  16. package/dist/src/config/config.test.js.map +1 -1
  17. package/dist/src/config/extensions/github.js +20 -4
  18. package/dist/src/config/extensions/github.js.map +1 -1
  19. package/dist/src/config/extensions/github.test.js +6 -2
  20. package/dist/src/config/extensions/github.test.js.map +1 -1
  21. package/dist/src/config/settingsSchema.d.ts +9 -0
  22. package/dist/src/config/settingsSchema.js +9 -0
  23. package/dist/src/config/settingsSchema.js.map +1 -1
  24. package/dist/src/config/settingsSchema.test.js +11 -0
  25. package/dist/src/config/settingsSchema.test.js.map +1 -1
  26. package/dist/src/core/auth.test.d.ts +6 -0
  27. package/dist/src/core/auth.test.js +43 -0
  28. package/dist/src/core/auth.test.js.map +1 -0
  29. package/dist/src/core/initializer.test.d.ts +6 -0
  30. package/dist/src/core/initializer.test.js +101 -0
  31. package/dist/src/core/initializer.test.js.map +1 -0
  32. package/dist/src/core/theme.test.d.ts +6 -0
  33. package/dist/src/core/theme.test.js +46 -0
  34. package/dist/src/core/theme.test.js.map +1 -0
  35. package/dist/src/gemini.d.ts +1 -0
  36. package/dist/src/gemini.js +7 -2
  37. package/dist/src/gemini.js.map +1 -1
  38. package/dist/src/gemini.test.js +621 -1
  39. package/dist/src/gemini.test.js.map +1 -1
  40. package/dist/src/gemini_cleanup.test.d.ts +6 -0
  41. package/dist/src/gemini_cleanup.test.js +201 -0
  42. package/dist/src/gemini_cleanup.test.js.map +1 -0
  43. package/dist/src/generated/git-commit.d.ts +2 -2
  44. package/dist/src/generated/git-commit.js +2 -2
  45. package/dist/src/generated/git-commit.js.map +1 -1
  46. package/dist/src/nonInteractiveCli.test.js +336 -0
  47. package/dist/src/nonInteractiveCli.test.js.map +1 -1
  48. package/dist/src/services/BuiltinCommandLoader.js +2 -0
  49. package/dist/src/services/BuiltinCommandLoader.js.map +1 -1
  50. package/dist/src/services/BuiltinCommandLoader.test.js +1 -0
  51. package/dist/src/services/BuiltinCommandLoader.test.js.map +1 -1
  52. package/dist/src/test-utils/render.js +4 -0
  53. package/dist/src/test-utils/render.js.map +1 -1
  54. package/dist/src/ui/App.test.js +28 -14
  55. package/dist/src/ui/App.test.js.map +1 -1
  56. package/dist/src/ui/AppContainer.js +21 -2
  57. package/dist/src/ui/AppContainer.js.map +1 -1
  58. package/dist/src/ui/IdeIntegrationNudge.test.d.ts +6 -0
  59. package/dist/src/ui/IdeIntegrationNudge.test.js +147 -0
  60. package/dist/src/ui/IdeIntegrationNudge.test.js.map +1 -0
  61. package/dist/src/ui/auth/ApiAuthDialog.test.js +12 -17
  62. package/dist/src/ui/auth/ApiAuthDialog.test.js.map +1 -1
  63. package/dist/src/ui/auth/AuthDialog.test.js +120 -74
  64. package/dist/src/ui/auth/AuthDialog.test.js.map +1 -1
  65. package/dist/src/ui/auth/AuthInProgress.test.d.ts +6 -0
  66. package/dist/src/ui/auth/AuthInProgress.test.js +71 -0
  67. package/dist/src/ui/auth/AuthInProgress.test.js.map +1 -0
  68. package/dist/src/ui/auth/useAuth.test.d.ts +6 -0
  69. package/dist/src/ui/auth/useAuth.test.js +178 -0
  70. package/dist/src/ui/auth/useAuth.test.js.map +1 -0
  71. package/dist/src/ui/commands/extensionsCommand.js +28 -6
  72. package/dist/src/ui/commands/extensionsCommand.js.map +1 -1
  73. package/dist/src/ui/commands/extensionsCommand.test.js +32 -0
  74. package/dist/src/ui/commands/extensionsCommand.test.js.map +1 -1
  75. package/dist/src/ui/commands/resumeCommand.d.ts +7 -0
  76. package/dist/src/ui/commands/resumeCommand.js +16 -0
  77. package/dist/src/ui/commands/resumeCommand.js.map +1 -0
  78. package/dist/src/ui/commands/statsCommand.js +27 -16
  79. package/dist/src/ui/commands/statsCommand.js.map +1 -1
  80. package/dist/src/ui/commands/types.d.ts +1 -1
  81. package/dist/src/ui/commands/types.js.map +1 -1
  82. package/dist/src/ui/components/AlternateBufferQuittingDisplay.test.js +62 -11
  83. package/dist/src/ui/components/AlternateBufferQuittingDisplay.test.js.map +1 -1
  84. package/dist/src/ui/components/AnsiOutput.test.js +18 -23
  85. package/dist/src/ui/components/AnsiOutput.test.js.map +1 -1
  86. package/dist/src/ui/components/DialogManager.js +4 -0
  87. package/dist/src/ui/components/DialogManager.js.map +1 -1
  88. package/dist/src/ui/components/InputPrompt.test.js +6 -0
  89. package/dist/src/ui/components/InputPrompt.test.js.map +1 -1
  90. package/dist/src/ui/components/ProQuotaDialog.js +2 -2
  91. package/dist/src/ui/components/ProQuotaDialog.js.map +1 -1
  92. package/dist/src/ui/components/SessionBrowser.d.ts +2 -2
  93. package/dist/src/ui/components/SessionBrowser.js +11 -11
  94. package/dist/src/ui/components/SessionBrowser.js.map +1 -1
  95. package/dist/src/ui/components/SessionBrowser.test.js +15 -7
  96. package/dist/src/ui/components/SessionBrowser.test.js.map +1 -1
  97. package/dist/src/ui/contexts/UIActionsContext.d.ts +5 -0
  98. package/dist/src/ui/contexts/UIActionsContext.js.map +1 -1
  99. package/dist/src/ui/contexts/UIStateContext.d.ts +1 -0
  100. package/dist/src/ui/contexts/UIStateContext.js.map +1 -1
  101. package/dist/src/ui/hooks/slashCommandProcessor.d.ts +1 -0
  102. package/dist/src/ui/hooks/slashCommandProcessor.js +3 -0
  103. package/dist/src/ui/hooks/slashCommandProcessor.js.map +1 -1
  104. package/dist/src/ui/hooks/slashCommandProcessor.test.js +1 -0
  105. package/dist/src/ui/hooks/slashCommandProcessor.test.js.map +1 -1
  106. package/dist/src/ui/hooks/useQuotaAndFallback.js +9 -3
  107. package/dist/src/ui/hooks/useQuotaAndFallback.js.map +1 -1
  108. package/dist/src/ui/hooks/useQuotaAndFallback.test.js +25 -3
  109. package/dist/src/ui/hooks/useQuotaAndFallback.test.js.map +1 -1
  110. package/dist/src/ui/hooks/useSessionBrowser.js +5 -1
  111. package/dist/src/ui/hooks/useSessionBrowser.js.map +1 -1
  112. package/dist/src/ui/hooks/useSlashCompletion.test.js +4 -4
  113. package/dist/src/ui/hooks/useSlashCompletion.test.js.map +1 -1
  114. package/dist/src/ui/hooks/useToolScheduler.test.js +5 -1
  115. package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
  116. package/dist/src/ui/state/extensions.test.js +208 -51
  117. package/dist/src/ui/state/extensions.test.js.map +1 -1
  118. package/dist/src/ui/utils/kittyProtocolDetector.test.d.ts +6 -0
  119. package/dist/src/ui/utils/kittyProtocolDetector.test.js +113 -0
  120. package/dist/src/ui/utils/kittyProtocolDetector.test.js.map +1 -0
  121. package/dist/src/ui/utils/terminalSetup.js +39 -38
  122. package/dist/src/ui/utils/terminalSetup.js.map +1 -1
  123. package/dist/src/ui/utils/terminalSetup.test.d.ts +6 -0
  124. package/dist/src/ui/utils/terminalSetup.test.js +131 -0
  125. package/dist/src/ui/utils/terminalSetup.test.js.map +1 -0
  126. package/dist/src/ui/utils/ui-sizing.test.d.ts +6 -0
  127. package/dist/src/ui/utils/ui-sizing.test.js +56 -0
  128. package/dist/src/ui/utils/ui-sizing.test.js.map +1 -0
  129. package/dist/src/utils/checks.test.d.ts +6 -0
  130. package/dist/src/utils/checks.test.js +29 -0
  131. package/dist/src/utils/checks.test.js.map +1 -0
  132. package/dist/src/utils/cleanup.test.js +69 -16
  133. package/dist/src/utils/cleanup.test.js.map +1 -1
  134. package/dist/src/utils/dialogScopeUtils.test.d.ts +6 -0
  135. package/dist/src/utils/dialogScopeUtils.test.js +81 -0
  136. package/dist/src/utils/dialogScopeUtils.test.js.map +1 -0
  137. package/dist/src/utils/errors.test.js +62 -0
  138. package/dist/src/utils/errors.test.js.map +1 -1
  139. package/dist/src/utils/events.test.d.ts +6 -0
  140. package/dist/src/utils/events.test.js +24 -0
  141. package/dist/src/utils/events.test.js.map +1 -0
  142. package/dist/src/utils/handleAutoUpdate.test.js +103 -24
  143. package/dist/src/utils/handleAutoUpdate.test.js.map +1 -1
  144. package/dist/src/utils/math.test.d.ts +6 -0
  145. package/dist/src/utils/math.test.js +23 -0
  146. package/dist/src/utils/math.test.js.map +1 -0
  147. package/dist/src/utils/persistentState.test.d.ts +6 -0
  148. package/dist/src/utils/persistentState.test.js +68 -0
  149. package/dist/src/utils/persistentState.test.js.map +1 -0
  150. package/dist/src/utils/readStdin.js +1 -0
  151. package/dist/src/utils/readStdin.js.map +1 -1
  152. package/dist/src/utils/readStdin.test.js +25 -0
  153. package/dist/src/utils/readStdin.test.js.map +1 -1
  154. package/dist/src/utils/resolvePath.test.d.ts +6 -0
  155. package/dist/src/utils/resolvePath.test.js +31 -0
  156. package/dist/src/utils/resolvePath.test.js.map +1 -0
  157. package/dist/src/utils/sandbox.js +6 -137
  158. package/dist/src/utils/sandbox.js.map +1 -1
  159. package/dist/src/utils/sandbox.test.d.ts +6 -0
  160. package/dist/src/utils/sandbox.test.js +302 -0
  161. package/dist/src/utils/sandbox.test.js.map +1 -0
  162. package/dist/src/utils/sandboxUtils.d.ts +14 -0
  163. package/dist/src/utils/sandboxUtils.js +120 -0
  164. package/dist/src/utils/sandboxUtils.js.map +1 -0
  165. package/dist/src/utils/sandboxUtils.test.d.ts +6 -0
  166. package/dist/src/utils/sandboxUtils.test.js +119 -0
  167. package/dist/src/utils/sandboxUtils.test.js.map +1 -0
  168. package/dist/src/utils/updateEventEmitter.test.d.ts +6 -0
  169. package/dist/src/utils/updateEventEmitter.test.js +18 -0
  170. package/dist/src/utils/updateEventEmitter.test.js.map +1 -0
  171. package/dist/src/utils/version.test.d.ts +6 -0
  172. package/dist/src/utils/version.test.js +39 -0
  173. package/dist/src/utils/version.test.js.map +1 -0
  174. package/dist/tsconfig.tsbuildinfo +1 -1
  175. package/package.json +4 -4
  176. package/dist/google-gemini-cli-0.19.0-nightly.20251123.dadd606c0.tgz +0 -0
@@ -4,7 +4,9 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  import { describe, it, expect, vi, beforeEach, afterEach, } from 'vitest';
7
- import { main, setupUnhandledRejectionHandler, validateDnsResolutionOrder, startInteractiveUI, } from './gemini.js';
7
+ import { main, setupUnhandledRejectionHandler, validateDnsResolutionOrder, startInteractiveUI, getNodeMemoryArgs, } from './gemini.js';
8
+ import os from 'node:os';
9
+ import v8 from 'node:v8';
8
10
  import {} from './config/settings.js';
9
11
  import { appEvents, AppEvent } from './utils/events.js';
10
12
  import { debugLogger, } from '@google/gemini-cli-core';
@@ -126,6 +128,7 @@ vi.mock('./utils/sandbox.js', () => ({
126
128
  }));
127
129
  vi.mock('./utils/relaunch.js', () => ({
128
130
  relaunchAppInChildProcess: vi.fn(),
131
+ relaunchOnExitCode: vi.fn(),
129
132
  }));
130
133
  vi.mock('./config/sandboxConfig.js', () => ({
131
134
  loadSandboxConfig: vi.fn(),
@@ -279,6 +282,71 @@ describe('gemini.tsx main function', () => {
279
282
  processExitSpy.mockRestore();
280
283
  });
281
284
  });
285
+ describe('setWindowTitle', () => {
286
+ it('should set window title when hideWindowTitle is false', async () => {
287
+ // setWindowTitle is not exported, but we can test its effect if we had a way to call it.
288
+ // Since we can't easily call it directly without exporting it, we skip direct testing
289
+ // and rely on startInteractiveUI tests which call it.
290
+ });
291
+ });
292
+ describe('initializeOutputListenersAndFlush', () => {
293
+ afterEach(() => {
294
+ vi.restoreAllMocks();
295
+ });
296
+ it('should flush backlogs and setup listeners if no listeners exist', async () => {
297
+ const { coreEvents } = await import('@google/gemini-cli-core');
298
+ const { initializeOutputListenersAndFlush } = await import('./gemini.js');
299
+ // Mock listenerCount to return 0
300
+ vi.spyOn(coreEvents, 'listenerCount').mockReturnValue(0);
301
+ const drainSpy = vi.spyOn(coreEvents, 'drainBacklogs');
302
+ initializeOutputListenersAndFlush();
303
+ expect(drainSpy).toHaveBeenCalled();
304
+ // We can't easily check if listeners were added without access to the internal state of coreEvents,
305
+ // but we can verify that drainBacklogs was called.
306
+ });
307
+ });
308
+ describe('getNodeMemoryArgs', () => {
309
+ let osTotalMemSpy;
310
+ let v8GetHeapStatisticsSpy;
311
+ beforeEach(() => {
312
+ osTotalMemSpy = vi.spyOn(os, 'totalmem');
313
+ v8GetHeapStatisticsSpy = vi.spyOn(v8, 'getHeapStatistics');
314
+ delete process.env['GEMINI_CLI_NO_RELAUNCH'];
315
+ });
316
+ afterEach(() => {
317
+ vi.restoreAllMocks();
318
+ });
319
+ it('should return empty array if GEMINI_CLI_NO_RELAUNCH is set', () => {
320
+ process.env['GEMINI_CLI_NO_RELAUNCH'] = 'true';
321
+ expect(getNodeMemoryArgs(false)).toEqual([]);
322
+ });
323
+ it('should return empty array if current heap limit is sufficient', () => {
324
+ osTotalMemSpy.mockReturnValue(16 * 1024 * 1024 * 1024); // 16GB
325
+ v8GetHeapStatisticsSpy.mockReturnValue({
326
+ heap_size_limit: 8 * 1024 * 1024 * 1024, // 8GB
327
+ });
328
+ // Target is 50% of 16GB = 8GB. Current is 8GB. No relaunch needed.
329
+ expect(getNodeMemoryArgs(false)).toEqual([]);
330
+ });
331
+ it('should return memory args if current heap limit is insufficient', () => {
332
+ osTotalMemSpy.mockReturnValue(16 * 1024 * 1024 * 1024); // 16GB
333
+ v8GetHeapStatisticsSpy.mockReturnValue({
334
+ heap_size_limit: 4 * 1024 * 1024 * 1024, // 4GB
335
+ });
336
+ // Target is 50% of 16GB = 8GB. Current is 4GB. Relaunch needed.
337
+ expect(getNodeMemoryArgs(false)).toEqual(['--max-old-space-size=8192']);
338
+ });
339
+ it('should log debug info when isDebugMode is true', () => {
340
+ const debugSpy = vi.spyOn(debugLogger, 'debug');
341
+ osTotalMemSpy.mockReturnValue(16 * 1024 * 1024 * 1024);
342
+ v8GetHeapStatisticsSpy.mockReturnValue({
343
+ heap_size_limit: 4 * 1024 * 1024 * 1024,
344
+ });
345
+ getNodeMemoryArgs(true);
346
+ expect(debugSpy).toHaveBeenCalledWith(expect.stringContaining('Current heap size'));
347
+ expect(debugSpy).toHaveBeenCalledWith(expect.stringContaining('Need to relaunch with more memory'));
348
+ });
349
+ });
282
350
  describe('gemini.tsx main function kitty protocol', () => {
283
351
  let originalEnvNoRelaunch;
284
352
  let setRawModeSpy;
@@ -388,6 +456,547 @@ describe('gemini.tsx main function kitty protocol', () => {
388
456
  expect(setRawModeSpy).toHaveBeenCalledWith(true);
389
457
  expect(detectAndEnableKittyProtocol).toHaveBeenCalledTimes(1);
390
458
  });
459
+ it.each([
460
+ { flag: 'listExtensions' },
461
+ { flag: 'listSessions' },
462
+ { flag: 'deleteSession', value: 'session-id' },
463
+ ])('should handle --$flag flag', async ({ flag, value }) => {
464
+ const { loadCliConfig, parseArguments } = await import('./config/config.js');
465
+ const { loadSettings } = await import('./config/settings.js');
466
+ const { listSessions, deleteSession } = await import('./utils/sessions.js');
467
+ const processExitSpy = vi
468
+ .spyOn(process, 'exit')
469
+ .mockImplementation((code) => {
470
+ throw new MockProcessExitError(code);
471
+ });
472
+ vi.mocked(loadSettings).mockReturnValue({
473
+ merged: {
474
+ advanced: {},
475
+ security: { auth: {} },
476
+ ui: {},
477
+ },
478
+ setValue: vi.fn(),
479
+ forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
480
+ errors: [],
481
+ }); // eslint-disable-line @typescript-eslint/no-explicit-any
482
+ vi.mocked(parseArguments).mockResolvedValue({
483
+ promptInteractive: false,
484
+ }); // eslint-disable-line @typescript-eslint/no-explicit-any
485
+ const mockConfig = {
486
+ isInteractive: () => false,
487
+ getQuestion: () => '',
488
+ getSandbox: () => false,
489
+ getDebugMode: () => false,
490
+ getListExtensions: () => flag === 'listExtensions',
491
+ getListSessions: () => flag === 'listSessions',
492
+ getDeleteSession: () => (flag === 'deleteSession' ? value : undefined),
493
+ getExtensions: () => [{ name: 'ext1' }],
494
+ getPolicyEngine: vi.fn(),
495
+ getMessageBus: () => ({ subscribe: vi.fn() }),
496
+ initialize: vi.fn(),
497
+ getContentGeneratorConfig: vi.fn(),
498
+ getMcpServers: () => ({}),
499
+ getMcpClientManager: vi.fn(),
500
+ getIdeMode: () => false,
501
+ getExperimentalZedIntegration: () => false,
502
+ getScreenReader: () => false,
503
+ getGeminiMdFileCount: () => 0,
504
+ getProjectRoot: () => '/',
505
+ };
506
+ vi.mocked(loadCliConfig).mockResolvedValue(mockConfig);
507
+ vi.mock('./utils/sessions.js', () => ({
508
+ listSessions: vi.fn(),
509
+ deleteSession: vi.fn(),
510
+ }));
511
+ const debugLoggerLogSpy = vi
512
+ .spyOn(debugLogger, 'log')
513
+ .mockImplementation(() => { });
514
+ try {
515
+ await main();
516
+ }
517
+ catch (e) {
518
+ if (!(e instanceof MockProcessExitError))
519
+ throw e;
520
+ }
521
+ if (flag === 'listExtensions') {
522
+ expect(debugLoggerLogSpy).toHaveBeenCalledWith(expect.stringContaining('ext1'));
523
+ }
524
+ else if (flag === 'listSessions') {
525
+ expect(listSessions).toHaveBeenCalledWith(mockConfig);
526
+ }
527
+ else if (flag === 'deleteSession') {
528
+ expect(deleteSession).toHaveBeenCalledWith(mockConfig, value);
529
+ }
530
+ expect(processExitSpy).toHaveBeenCalledWith(0);
531
+ processExitSpy.mockRestore();
532
+ });
533
+ it('should handle sandbox activation', async () => {
534
+ const { loadCliConfig, parseArguments } = await import('./config/config.js');
535
+ const { loadSandboxConfig } = await import('./config/sandboxConfig.js');
536
+ const { start_sandbox } = await import('./utils/sandbox.js');
537
+ const { relaunchOnExitCode } = await import('./utils/relaunch.js');
538
+ const { loadSettings } = await import('./config/settings.js');
539
+ const processExitSpy = vi
540
+ .spyOn(process, 'exit')
541
+ .mockImplementation((code) => {
542
+ throw new MockProcessExitError(code);
543
+ });
544
+ vi.mocked(parseArguments).mockResolvedValue({
545
+ promptInteractive: false,
546
+ }); // eslint-disable-line @typescript-eslint/no-explicit-any
547
+ vi.mocked(loadSettings).mockReturnValue({
548
+ merged: {
549
+ advanced: {},
550
+ security: { auth: {} },
551
+ ui: {},
552
+ },
553
+ setValue: vi.fn(),
554
+ forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
555
+ errors: [],
556
+ }); // eslint-disable-line @typescript-eslint/no-explicit-any
557
+ const mockConfig = {
558
+ isInteractive: () => false,
559
+ getQuestion: () => '',
560
+ getSandbox: () => true,
561
+ getDebugMode: () => false,
562
+ getListExtensions: () => false,
563
+ getListSessions: () => false,
564
+ getDeleteSession: () => undefined,
565
+ getExtensions: () => [],
566
+ getPolicyEngine: vi.fn(),
567
+ getMessageBus: () => ({ subscribe: vi.fn() }),
568
+ initialize: vi.fn(),
569
+ getContentGeneratorConfig: vi.fn(),
570
+ getMcpServers: () => ({}),
571
+ getMcpClientManager: vi.fn(),
572
+ getIdeMode: () => false,
573
+ getExperimentalZedIntegration: () => false,
574
+ getScreenReader: () => false,
575
+ getGeminiMdFileCount: () => 0,
576
+ getProjectRoot: () => '/',
577
+ refreshAuth: vi.fn(),
578
+ };
579
+ vi.mocked(loadCliConfig).mockResolvedValue(mockConfig);
580
+ vi.mocked(loadSandboxConfig).mockResolvedValue({}); // eslint-disable-line @typescript-eslint/no-explicit-any
581
+ vi.mocked(relaunchOnExitCode).mockImplementation(async (fn) => {
582
+ await fn();
583
+ });
584
+ try {
585
+ await main();
586
+ }
587
+ catch (e) {
588
+ if (!(e instanceof MockProcessExitError))
589
+ throw e;
590
+ }
591
+ expect(start_sandbox).toHaveBeenCalled();
592
+ expect(processExitSpy).toHaveBeenCalledWith(0);
593
+ processExitSpy.mockRestore();
594
+ });
595
+ it('should exit with error when --prompt-interactive is used with piped input', async () => {
596
+ const { loadCliConfig, parseArguments } = await import('./config/config.js');
597
+ const { loadSettings } = await import('./config/settings.js');
598
+ const core = await import('@google/gemini-cli-core');
599
+ const processExitSpy = vi
600
+ .spyOn(process, 'exit')
601
+ .mockImplementation((code) => {
602
+ throw new MockProcessExitError(code);
603
+ });
604
+ const writeToStderrSpy = vi
605
+ .spyOn(core, 'writeToStderr')
606
+ .mockImplementation(() => true);
607
+ vi.mocked(loadSettings).mockReturnValue({
608
+ merged: { advanced: {}, security: { auth: {} }, ui: {} },
609
+ setValue: vi.fn(),
610
+ forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
611
+ errors: [],
612
+ }); // eslint-disable-line @typescript-eslint/no-explicit-any
613
+ vi.mocked(parseArguments).mockResolvedValue({
614
+ promptInteractive: true,
615
+ }); // eslint-disable-line @typescript-eslint/no-explicit-any
616
+ vi.mocked(loadCliConfig).mockResolvedValue({
617
+ isInteractive: () => false,
618
+ getQuestion: () => '',
619
+ getSandbox: () => false,
620
+ }); // eslint-disable-line @typescript-eslint/no-explicit-any
621
+ // Mock stdin to be non-TTY
622
+ Object.defineProperty(process.stdin, 'isTTY', {
623
+ value: false,
624
+ configurable: true,
625
+ });
626
+ try {
627
+ await main();
628
+ }
629
+ catch (e) {
630
+ if (!(e instanceof MockProcessExitError))
631
+ throw e;
632
+ }
633
+ expect(writeToStderrSpy).toHaveBeenCalledWith(expect.stringContaining('Error: The --prompt-interactive flag cannot be used'));
634
+ expect(processExitSpy).toHaveBeenCalledWith(1);
635
+ processExitSpy.mockRestore();
636
+ writeToStderrSpy.mockRestore();
637
+ Object.defineProperty(process.stdin, 'isTTY', {
638
+ value: true,
639
+ configurable: true,
640
+ }); // Restore TTY
641
+ });
642
+ it('should log warning when theme is not found', async () => {
643
+ const { loadCliConfig, parseArguments } = await import('./config/config.js');
644
+ const { loadSettings } = await import('./config/settings.js');
645
+ const { themeManager } = await import('./ui/themes/theme-manager.js');
646
+ const debugLoggerWarnSpy = vi
647
+ .spyOn(debugLogger, 'warn')
648
+ .mockImplementation(() => { });
649
+ const processExitSpy = vi
650
+ .spyOn(process, 'exit')
651
+ .mockImplementation((code) => {
652
+ throw new MockProcessExitError(code);
653
+ });
654
+ vi.mocked(loadSettings).mockReturnValue({
655
+ merged: {
656
+ advanced: {},
657
+ security: { auth: {} },
658
+ ui: { theme: 'non-existent-theme' },
659
+ },
660
+ setValue: vi.fn(),
661
+ forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
662
+ errors: [],
663
+ }); // eslint-disable-line @typescript-eslint/no-explicit-any
664
+ vi.mocked(parseArguments).mockResolvedValue({
665
+ promptInteractive: false,
666
+ }); // eslint-disable-line @typescript-eslint/no-explicit-any
667
+ vi.mocked(loadCliConfig).mockResolvedValue({
668
+ isInteractive: () => false,
669
+ getQuestion: () => 'test',
670
+ getSandbox: () => false,
671
+ getDebugMode: () => false,
672
+ getPolicyEngine: vi.fn(),
673
+ getMessageBus: () => ({ subscribe: vi.fn() }),
674
+ initialize: vi.fn(),
675
+ getContentGeneratorConfig: vi.fn(),
676
+ getMcpServers: () => ({}),
677
+ getMcpClientManager: vi.fn(),
678
+ getIdeMode: () => false,
679
+ getExperimentalZedIntegration: () => false,
680
+ getScreenReader: () => false,
681
+ getGeminiMdFileCount: () => 0,
682
+ getProjectRoot: () => '/',
683
+ getListExtensions: () => false,
684
+ getListSessions: () => false,
685
+ getDeleteSession: () => undefined,
686
+ getToolRegistry: vi.fn(),
687
+ getExtensions: () => [],
688
+ getModel: () => 'gemini-pro',
689
+ getEmbeddingModel: () => 'embedding-001',
690
+ getApprovalMode: () => 'default',
691
+ getCoreTools: () => [],
692
+ getTelemetryEnabled: () => false,
693
+ getTelemetryLogPromptsEnabled: () => false,
694
+ getFileFilteringRespectGitIgnore: () => true,
695
+ getOutputFormat: () => 'text',
696
+ getUsageStatisticsEnabled: () => false,
697
+ }); // eslint-disable-line @typescript-eslint/no-explicit-any
698
+ vi.spyOn(themeManager, 'setActiveTheme').mockReturnValue(false);
699
+ try {
700
+ await main();
701
+ }
702
+ catch (e) {
703
+ if (!(e instanceof MockProcessExitError))
704
+ throw e;
705
+ }
706
+ expect(debugLoggerWarnSpy).toHaveBeenCalledWith(expect.stringContaining('Warning: Theme "non-existent-theme" not found.'));
707
+ processExitSpy.mockRestore();
708
+ });
709
+ it('should handle session selector error', async () => {
710
+ const { loadCliConfig, parseArguments } = await import('./config/config.js');
711
+ const { loadSettings } = await import('./config/settings.js');
712
+ vi.mock('./utils/sessionUtils.js', () => ({
713
+ SessionSelector: class {
714
+ resolveSession = vi
715
+ .fn()
716
+ .mockRejectedValue(new Error('Session not found'));
717
+ },
718
+ }));
719
+ const processExitSpy = vi
720
+ .spyOn(process, 'exit')
721
+ .mockImplementation((code) => {
722
+ throw new MockProcessExitError(code);
723
+ });
724
+ const consoleErrorSpy = vi
725
+ .spyOn(console, 'error')
726
+ .mockImplementation(() => { });
727
+ vi.mocked(loadSettings).mockReturnValue({
728
+ merged: { advanced: {}, security: { auth: {} }, ui: { theme: 'test' } },
729
+ setValue: vi.fn(),
730
+ forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
731
+ errors: [],
732
+ }); // eslint-disable-line @typescript-eslint/no-explicit-any
733
+ vi.mocked(parseArguments).mockResolvedValue({
734
+ promptInteractive: false,
735
+ resume: 'session-id',
736
+ }); // eslint-disable-line @typescript-eslint/no-explicit-any
737
+ vi.mocked(loadCliConfig).mockResolvedValue({
738
+ isInteractive: () => true,
739
+ getQuestion: () => '',
740
+ getSandbox: () => false,
741
+ getDebugMode: () => false,
742
+ getPolicyEngine: vi.fn(),
743
+ getMessageBus: () => ({ subscribe: vi.fn() }),
744
+ initialize: vi.fn(),
745
+ getContentGeneratorConfig: vi.fn(),
746
+ getMcpServers: () => ({}),
747
+ getMcpClientManager: vi.fn(),
748
+ getIdeMode: () => false,
749
+ getExperimentalZedIntegration: () => false,
750
+ getScreenReader: () => false,
751
+ getGeminiMdFileCount: () => 0,
752
+ getProjectRoot: () => '/',
753
+ getListExtensions: () => false,
754
+ getListSessions: () => false,
755
+ getDeleteSession: () => undefined,
756
+ getToolRegistry: vi.fn(),
757
+ getExtensions: () => [],
758
+ getModel: () => 'gemini-pro',
759
+ getEmbeddingModel: () => 'embedding-001',
760
+ getApprovalMode: () => 'default',
761
+ getCoreTools: () => [],
762
+ getTelemetryEnabled: () => false,
763
+ getTelemetryLogPromptsEnabled: () => false,
764
+ getFileFilteringRespectGitIgnore: () => true,
765
+ getOutputFormat: () => 'text',
766
+ getUsageStatisticsEnabled: () => false,
767
+ }); // eslint-disable-line @typescript-eslint/no-explicit-any
768
+ try {
769
+ await main();
770
+ }
771
+ catch (e) {
772
+ if (!(e instanceof MockProcessExitError))
773
+ throw e;
774
+ }
775
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Error resuming session: Session not found'));
776
+ expect(processExitSpy).toHaveBeenCalledWith(1);
777
+ processExitSpy.mockRestore();
778
+ consoleErrorSpy.mockRestore();
779
+ });
780
+ it.skip('should log error when cleanupExpiredSessions fails', async () => {
781
+ const { loadCliConfig, parseArguments } = await import('./config/config.js');
782
+ const { loadSettings } = await import('./config/settings.js');
783
+ const { cleanupExpiredSessions } = await import('./utils/sessionCleanup.js');
784
+ vi.mocked(cleanupExpiredSessions).mockRejectedValue(new Error('Cleanup failed'));
785
+ const debugLoggerErrorSpy = vi
786
+ .spyOn(debugLogger, 'error')
787
+ .mockImplementation(() => { });
788
+ const processExitSpy = vi
789
+ .spyOn(process, 'exit')
790
+ .mockImplementation((code) => {
791
+ throw new MockProcessExitError(code);
792
+ });
793
+ vi.mocked(loadSettings).mockReturnValue({
794
+ merged: { advanced: {}, security: { auth: {} }, ui: {} },
795
+ setValue: vi.fn(),
796
+ forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
797
+ errors: [],
798
+ }); // eslint-disable-line @typescript-eslint/no-explicit-any
799
+ vi.mocked(parseArguments).mockResolvedValue({
800
+ promptInteractive: false,
801
+ }); // eslint-disable-line @typescript-eslint/no-explicit-any
802
+ vi.mocked(loadCliConfig).mockResolvedValue({
803
+ isInteractive: () => false,
804
+ getQuestion: () => 'test',
805
+ getSandbox: () => false,
806
+ getDebugMode: () => false,
807
+ getPolicyEngine: vi.fn(),
808
+ getMessageBus: () => ({ subscribe: vi.fn() }),
809
+ initialize: vi.fn(),
810
+ getContentGeneratorConfig: vi.fn(),
811
+ getMcpServers: () => ({}),
812
+ getMcpClientManager: vi.fn(),
813
+ getIdeMode: () => false,
814
+ getExperimentalZedIntegration: () => false,
815
+ getScreenReader: () => false,
816
+ getGeminiMdFileCount: () => 0,
817
+ getProjectRoot: () => '/',
818
+ getListExtensions: () => false,
819
+ getListSessions: () => false,
820
+ getDeleteSession: () => undefined,
821
+ getToolRegistry: vi.fn(),
822
+ getExtensions: () => [],
823
+ getModel: () => 'gemini-pro',
824
+ getEmbeddingModel: () => 'embedding-001',
825
+ getApprovalMode: () => 'default',
826
+ getCoreTools: () => [],
827
+ getTelemetryEnabled: () => false,
828
+ getTelemetryLogPromptsEnabled: () => false,
829
+ getFileFilteringRespectGitIgnore: () => true,
830
+ getOutputFormat: () => 'text',
831
+ getUsageStatisticsEnabled: () => false,
832
+ }); // eslint-disable-line @typescript-eslint/no-explicit-any
833
+ // The mock is already set up at the top of the test
834
+ try {
835
+ await main();
836
+ }
837
+ catch (e) {
838
+ if (!(e instanceof MockProcessExitError))
839
+ throw e;
840
+ }
841
+ expect(debugLoggerErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to cleanup expired sessions: Cleanup failed'));
842
+ expect(processExitSpy).toHaveBeenCalledWith(0); // Should not exit on cleanup failure
843
+ processExitSpy.mockRestore();
844
+ });
845
+ it('should handle refreshAuth failure', async () => {
846
+ const { loadCliConfig, parseArguments } = await import('./config/config.js');
847
+ const { loadSettings } = await import('./config/settings.js');
848
+ const { loadSandboxConfig } = await import('./config/sandboxConfig.js');
849
+ const processExitSpy = vi
850
+ .spyOn(process, 'exit')
851
+ .mockImplementation((code) => {
852
+ throw new MockProcessExitError(code);
853
+ });
854
+ const debugLoggerErrorSpy = vi
855
+ .spyOn(debugLogger, 'error')
856
+ .mockImplementation(() => { });
857
+ vi.mocked(loadSettings).mockReturnValue({
858
+ merged: {
859
+ advanced: {},
860
+ security: { auth: { selectedType: 'google' } },
861
+ ui: {},
862
+ },
863
+ setValue: vi.fn(),
864
+ forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
865
+ errors: [],
866
+ }); // eslint-disable-line @typescript-eslint/no-explicit-any
867
+ vi.mocked(loadSandboxConfig).mockResolvedValue({}); // eslint-disable-line @typescript-eslint/no-explicit-any
868
+ vi.mocked(parseArguments).mockResolvedValue({
869
+ promptInteractive: false,
870
+ }); // eslint-disable-line @typescript-eslint/no-explicit-any
871
+ vi.mocked(loadCliConfig).mockResolvedValue({
872
+ isInteractive: () => true,
873
+ getQuestion: () => '',
874
+ getSandbox: () => false,
875
+ getDebugMode: () => false,
876
+ getPolicyEngine: vi.fn(),
877
+ getMessageBus: () => ({ subscribe: vi.fn() }),
878
+ initialize: vi.fn(),
879
+ getContentGeneratorConfig: vi.fn(),
880
+ getMcpServers: () => ({}),
881
+ getMcpClientManager: vi.fn(),
882
+ getIdeMode: () => false,
883
+ getExperimentalZedIntegration: () => false,
884
+ getScreenReader: () => false,
885
+ getGeminiMdFileCount: () => 0,
886
+ getProjectRoot: () => '/',
887
+ getListExtensions: () => false,
888
+ getListSessions: () => false,
889
+ getDeleteSession: () => undefined,
890
+ getToolRegistry: vi.fn(),
891
+ getExtensions: () => [],
892
+ getModel: () => 'gemini-pro',
893
+ getEmbeddingModel: () => 'embedding-001',
894
+ getApprovalMode: () => 'default',
895
+ getCoreTools: () => [],
896
+ getTelemetryEnabled: () => false,
897
+ getTelemetryLogPromptsEnabled: () => false,
898
+ getFileFilteringRespectGitIgnore: () => true,
899
+ getOutputFormat: () => 'text',
900
+ getUsageStatisticsEnabled: () => false,
901
+ refreshAuth: vi.fn().mockRejectedValue(new Error('Auth refresh failed')),
902
+ }); // eslint-disable-line @typescript-eslint/no-explicit-any
903
+ try {
904
+ await main();
905
+ }
906
+ catch (e) {
907
+ if (!(e instanceof MockProcessExitError))
908
+ throw e;
909
+ }
910
+ expect(debugLoggerErrorSpy).toHaveBeenCalledWith('Error authenticating:', expect.any(Error));
911
+ expect(processExitSpy).toHaveBeenCalledWith(1);
912
+ processExitSpy.mockRestore();
913
+ });
914
+ it('should read from stdin in non-interactive mode', async () => {
915
+ const { loadCliConfig, parseArguments } = await import('./config/config.js');
916
+ const { loadSettings } = await import('./config/settings.js');
917
+ const { readStdin } = await import('./utils/readStdin.js');
918
+ const processExitSpy = vi
919
+ .spyOn(process, 'exit')
920
+ .mockImplementation((code) => {
921
+ throw new MockProcessExitError(code);
922
+ });
923
+ vi.mocked(loadSettings).mockReturnValue({
924
+ merged: { advanced: {}, security: { auth: {} }, ui: {} },
925
+ setValue: vi.fn(),
926
+ forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
927
+ errors: [],
928
+ }); // eslint-disable-line @typescript-eslint/no-explicit-any
929
+ vi.mocked(parseArguments).mockResolvedValue({
930
+ promptInteractive: false,
931
+ }); // eslint-disable-line @typescript-eslint/no-explicit-any
932
+ vi.mocked(loadCliConfig).mockResolvedValue({
933
+ isInteractive: () => false,
934
+ getQuestion: () => 'test-question',
935
+ getSandbox: () => false,
936
+ getDebugMode: () => false,
937
+ getPolicyEngine: vi.fn(),
938
+ getMessageBus: () => ({ subscribe: vi.fn() }),
939
+ initialize: vi.fn(),
940
+ getContentGeneratorConfig: vi.fn(),
941
+ getMcpServers: () => ({}),
942
+ getMcpClientManager: vi.fn(),
943
+ getIdeMode: () => false,
944
+ getExperimentalZedIntegration: () => false,
945
+ getScreenReader: () => false,
946
+ getGeminiMdFileCount: () => 0,
947
+ getProjectRoot: () => '/',
948
+ getListExtensions: () => false,
949
+ getListSessions: () => false,
950
+ getDeleteSession: () => undefined,
951
+ getToolRegistry: vi.fn(),
952
+ getExtensions: () => [],
953
+ getModel: () => 'gemini-pro',
954
+ getEmbeddingModel: () => 'embedding-001',
955
+ getApprovalMode: () => 'default',
956
+ getCoreTools: () => [],
957
+ getTelemetryEnabled: () => false,
958
+ getTelemetryLogPromptsEnabled: () => false,
959
+ getFileFilteringRespectGitIgnore: () => true,
960
+ getOutputFormat: () => 'text',
961
+ getUsageStatisticsEnabled: () => false,
962
+ }); // eslint-disable-line @typescript-eslint/no-explicit-any
963
+ vi.mock('./utils/readStdin.js', () => ({
964
+ readStdin: vi.fn().mockResolvedValue('stdin-data'),
965
+ }));
966
+ const runNonInteractiveSpy = vi.hoisted(() => vi.fn());
967
+ vi.mock('./nonInteractiveCli.js', () => ({
968
+ runNonInteractive: runNonInteractiveSpy,
969
+ }));
970
+ runNonInteractiveSpy.mockClear();
971
+ vi.mock('./validateNonInterActiveAuth.js', () => ({
972
+ validateNonInteractiveAuth: vi.fn().mockResolvedValue({}),
973
+ }));
974
+ // Mock stdin to be non-TTY
975
+ Object.defineProperty(process.stdin, 'isTTY', {
976
+ value: false,
977
+ configurable: true,
978
+ });
979
+ try {
980
+ await main();
981
+ }
982
+ catch (e) {
983
+ if (!(e instanceof MockProcessExitError))
984
+ throw e;
985
+ }
986
+ expect(readStdin).toHaveBeenCalled();
987
+ // In this test setup, runNonInteractive might be called on the mocked module,
988
+ // but we need to ensure we are checking the correct spy instance.
989
+ // Since vi.mock is hoisted, runNonInteractiveSpy is defined early.
990
+ expect(runNonInteractiveSpy).toHaveBeenCalled();
991
+ const callArgs = runNonInteractiveSpy.mock.calls[0][0];
992
+ expect(callArgs.input).toBe('test-question');
993
+ expect(processExitSpy).toHaveBeenCalledWith(0);
994
+ processExitSpy.mockRestore();
995
+ Object.defineProperty(process.stdin, 'isTTY', {
996
+ value: true,
997
+ configurable: true,
998
+ });
999
+ });
391
1000
  });
392
1001
  describe('validateDnsResolutionOrder', () => {
393
1002
  let debugLoggerWarnSpy;
@@ -483,6 +1092,17 @@ describe('startInteractiveUI', () => {
483
1092
  // Verify React element structure is valid (but don't deep dive into JSX internals)
484
1093
  expect(reactElement).toBeDefined();
485
1094
  });
1095
+ it('should enable mouse events when alternate buffer is enabled', async () => {
1096
+ const { enableMouseEvents } = await import('@google/gemini-cli-core');
1097
+ await startTestInteractiveUI(mockConfig, mockSettings, mockStartupWarnings, mockWorkspaceRoot, undefined, mockInitializationResult);
1098
+ expect(enableMouseEvents).toHaveBeenCalled();
1099
+ });
1100
+ it('should patch console', async () => {
1101
+ const { ConsolePatcher } = await import('./ui/utils/ConsolePatcher.js');
1102
+ const patchSpy = vi.spyOn(ConsolePatcher.prototype, 'patch');
1103
+ await startTestInteractiveUI(mockConfig, mockSettings, mockStartupWarnings, mockWorkspaceRoot, undefined, mockInitializationResult);
1104
+ expect(patchSpy).toHaveBeenCalled();
1105
+ });
486
1106
  it('should perform all startup tasks in correct order', async () => {
487
1107
  const { getCliVersion } = await import('./utils/version.js');
488
1108
  const { checkForUpdates } = await import('./ui/utils/updateCheck.js');