@machina.ai/cell-cli 1.19.4-rc3 → 1.20.2-rc1

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 (198) hide show
  1. package/dist/package.json +3 -3
  2. package/dist/src/commands/extensions/link.d.ts +1 -0
  3. package/dist/src/commands/extensions/link.js +15 -2
  4. package/dist/src/commands/extensions/link.js.map +1 -1
  5. package/dist/src/commands/extensions/link.test.js +6 -0
  6. package/dist/src/commands/extensions/link.test.js.map +1 -1
  7. package/dist/src/commands/mcp/list.js +1 -1
  8. package/dist/src/commands/mcp/list.js.map +1 -1
  9. package/dist/src/config/config.js +4 -1
  10. package/dist/src/config/config.js.map +1 -1
  11. package/dist/src/config/config.test.js +23 -1
  12. package/dist/src/config/config.test.js.map +1 -1
  13. package/dist/src/config/extension.test.js +1 -1
  14. package/dist/src/config/extension.test.js.map +1 -1
  15. package/dist/src/config/extensions/consent.js +2 -2
  16. package/dist/src/config/extensions/consent.js.map +1 -1
  17. package/dist/src/config/extensions/github.js +1 -1
  18. package/dist/src/config/extensions/github.js.map +1 -1
  19. package/dist/src/config/extensions/storage.js +1 -1
  20. package/dist/src/config/extensions/storage.js.map +1 -1
  21. package/dist/src/config/settings.js +11 -0
  22. package/dist/src/config/settings.js.map +1 -1
  23. package/dist/src/config/settingsSchema.d.ts +18 -0
  24. package/dist/src/config/settingsSchema.js +18 -0
  25. package/dist/src/config/settingsSchema.js.map +1 -1
  26. package/dist/src/core/initializer.js +3 -1
  27. package/dist/src/core/initializer.js.map +1 -1
  28. package/dist/src/gemini.js +24 -11
  29. package/dist/src/gemini.js.map +1 -1
  30. package/dist/src/gemini.test.js +190 -125
  31. package/dist/src/gemini.test.js.map +1 -1
  32. package/dist/src/gemini_cleanup.test.js +1 -1
  33. package/dist/src/gemini_cleanup.test.js.map +1 -1
  34. package/dist/src/generated/git-commit.d.ts +2 -2
  35. package/dist/src/generated/git-commit.js +2 -2
  36. package/dist/src/nonInteractiveCli.js +3 -2
  37. package/dist/src/nonInteractiveCli.js.map +1 -1
  38. package/dist/src/nonInteractiveCli.test.js +4 -0
  39. package/dist/src/nonInteractiveCli.test.js.map +1 -1
  40. package/dist/src/services/BuiltinCommandLoader.js +3 -0
  41. package/dist/src/services/BuiltinCommandLoader.js.map +1 -1
  42. package/dist/src/ui/AppContainer.js +18 -36
  43. package/dist/src/ui/AppContainer.js.map +1 -1
  44. package/dist/src/ui/AppContainer.test.js +34 -51
  45. package/dist/src/ui/AppContainer.test.js.map +1 -1
  46. package/dist/src/ui/auth/AuthDialog.js +11 -4
  47. package/dist/src/ui/auth/AuthDialog.js.map +1 -1
  48. package/dist/src/ui/auth/AuthDialog.test.js +15 -4
  49. package/dist/src/ui/auth/AuthDialog.test.js.map +1 -1
  50. package/dist/src/ui/auth/useAuth.js +6 -1
  51. package/dist/src/ui/auth/useAuth.js.map +1 -1
  52. package/dist/src/ui/auth/useAuth.test.js +11 -0
  53. package/dist/src/ui/auth/useAuth.test.js.map +1 -1
  54. package/dist/src/ui/commands/aboutCommand.js +1 -0
  55. package/dist/src/ui/commands/aboutCommand.js.map +1 -1
  56. package/dist/src/ui/commands/authCommand.js +1 -0
  57. package/dist/src/ui/commands/authCommand.js.map +1 -1
  58. package/dist/src/ui/commands/bugCommand.js +1 -0
  59. package/dist/src/ui/commands/bugCommand.js.map +1 -1
  60. package/dist/src/ui/commands/chatCommand.js +6 -0
  61. package/dist/src/ui/commands/chatCommand.js.map +1 -1
  62. package/dist/src/ui/commands/clearCommand.js +1 -0
  63. package/dist/src/ui/commands/clearCommand.js.map +1 -1
  64. package/dist/src/ui/commands/compressCommand.js +1 -0
  65. package/dist/src/ui/commands/compressCommand.js.map +1 -1
  66. package/dist/src/ui/commands/copyCommand.js +1 -0
  67. package/dist/src/ui/commands/copyCommand.js.map +1 -1
  68. package/dist/src/ui/commands/corgiCommand.js +1 -0
  69. package/dist/src/ui/commands/corgiCommand.js.map +1 -1
  70. package/dist/src/ui/commands/directoryCommand.js +1 -0
  71. package/dist/src/ui/commands/directoryCommand.js.map +1 -1
  72. package/dist/src/ui/commands/docsCommand.js +1 -0
  73. package/dist/src/ui/commands/docsCommand.js.map +1 -1
  74. package/dist/src/ui/commands/editorCommand.js +1 -0
  75. package/dist/src/ui/commands/editorCommand.js.map +1 -1
  76. package/dist/src/ui/commands/extensionsCommand.js +7 -0
  77. package/dist/src/ui/commands/extensionsCommand.js.map +1 -1
  78. package/dist/src/ui/commands/helpCommand.js +1 -0
  79. package/dist/src/ui/commands/helpCommand.js.map +1 -1
  80. package/dist/src/ui/commands/ideCommand.js +6 -0
  81. package/dist/src/ui/commands/ideCommand.js.map +1 -1
  82. package/dist/src/ui/commands/initCommand.js +1 -0
  83. package/dist/src/ui/commands/initCommand.js.map +1 -1
  84. package/dist/src/ui/commands/mcpCommand.js +6 -0
  85. package/dist/src/ui/commands/mcpCommand.js.map +1 -1
  86. package/dist/src/ui/commands/memoryCommand.js +5 -0
  87. package/dist/src/ui/commands/memoryCommand.js.map +1 -1
  88. package/dist/src/ui/commands/modelCommand.js +1 -0
  89. package/dist/src/ui/commands/modelCommand.js.map +1 -1
  90. package/dist/src/ui/commands/permissionsCommand.js +2 -0
  91. package/dist/src/ui/commands/permissionsCommand.js.map +1 -1
  92. package/dist/src/ui/commands/policiesCommand.js +2 -0
  93. package/dist/src/ui/commands/policiesCommand.js.map +1 -1
  94. package/dist/src/ui/commands/privacyCommand.js +1 -0
  95. package/dist/src/ui/commands/privacyCommand.js.map +1 -1
  96. package/dist/src/ui/commands/profileCommand.js +1 -0
  97. package/dist/src/ui/commands/profileCommand.js.map +1 -1
  98. package/dist/src/ui/commands/quitCommand.js +1 -0
  99. package/dist/src/ui/commands/quitCommand.js.map +1 -1
  100. package/dist/src/ui/commands/restoreCommand.js +1 -0
  101. package/dist/src/ui/commands/restoreCommand.js.map +1 -1
  102. package/dist/src/ui/commands/resumeCommand.js +1 -0
  103. package/dist/src/ui/commands/resumeCommand.js.map +1 -1
  104. package/dist/src/ui/commands/settingsCommand.js +1 -0
  105. package/dist/src/ui/commands/settingsCommand.js.map +1 -1
  106. package/dist/src/ui/commands/setupGithubCommand.js +4 -1
  107. package/dist/src/ui/commands/setupGithubCommand.js.map +1 -1
  108. package/dist/src/ui/commands/setupGithubCommand.test.js +55 -14
  109. package/dist/src/ui/commands/setupGithubCommand.test.js.map +1 -1
  110. package/dist/src/ui/commands/statsCommand.js +19 -5
  111. package/dist/src/ui/commands/statsCommand.js.map +1 -1
  112. package/dist/src/ui/commands/terminalSetupCommand.js +1 -0
  113. package/dist/src/ui/commands/terminalSetupCommand.js.map +1 -1
  114. package/dist/src/ui/commands/themeCommand.js +1 -0
  115. package/dist/src/ui/commands/themeCommand.js.map +1 -1
  116. package/dist/src/ui/commands/toolsCommand.js +1 -0
  117. package/dist/src/ui/commands/toolsCommand.js.map +1 -1
  118. package/dist/src/ui/commands/types.d.ts +7 -0
  119. package/dist/src/ui/commands/vimCommand.js +1 -0
  120. package/dist/src/ui/commands/vimCommand.js.map +1 -1
  121. package/dist/src/ui/components/AsciiArt.d.ts +6 -6
  122. package/dist/src/ui/components/AsciiArt.js +6 -6
  123. package/dist/src/ui/components/ConfigInitDisplay.js +15 -2
  124. package/dist/src/ui/components/ConfigInitDisplay.js.map +1 -1
  125. package/dist/src/ui/components/ConfigInitDisplay.test.js +34 -4
  126. package/dist/src/ui/components/ConfigInitDisplay.test.js.map +1 -1
  127. package/dist/src/ui/components/FolderTrustDialog.js +5 -2
  128. package/dist/src/ui/components/FolderTrustDialog.js.map +1 -1
  129. package/dist/src/ui/components/FolderTrustDialog.test.js +2 -1
  130. package/dist/src/ui/components/FolderTrustDialog.test.js.map +1 -1
  131. package/dist/src/ui/components/HistoryItemDisplay.js +1 -1
  132. package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -1
  133. package/dist/src/ui/components/InputPrompt.d.ts +1 -1
  134. package/dist/src/ui/components/InputPrompt.js +24 -18
  135. package/dist/src/ui/components/InputPrompt.js.map +1 -1
  136. package/dist/src/ui/components/InputPrompt.test.js +151 -17
  137. package/dist/src/ui/components/InputPrompt.test.js.map +1 -1
  138. package/dist/src/ui/components/StatsDisplay.d.ts +2 -0
  139. package/dist/src/ui/components/StatsDisplay.js +36 -5
  140. package/dist/src/ui/components/StatsDisplay.js.map +1 -1
  141. package/dist/src/ui/components/StatsDisplay.test.js +51 -1
  142. package/dist/src/ui/components/StatsDisplay.test.js.map +1 -1
  143. package/dist/src/ui/contexts/UIActionsContext.d.ts +1 -1
  144. package/dist/src/ui/hooks/useCommandCompletion.d.ts +8 -1
  145. package/dist/src/ui/hooks/useCommandCompletion.js +50 -6
  146. package/dist/src/ui/hooks/useCommandCompletion.js.map +1 -1
  147. package/dist/src/ui/hooks/useCommandCompletion.test.js +4 -5
  148. package/dist/src/ui/hooks/useCommandCompletion.test.js.map +1 -1
  149. package/dist/src/ui/hooks/useFolderTrust.js +5 -3
  150. package/dist/src/ui/hooks/useFolderTrust.js.map +1 -1
  151. package/dist/src/ui/hooks/useFolderTrust.test.js +4 -4
  152. package/dist/src/ui/hooks/useFolderTrust.test.js.map +1 -1
  153. package/dist/src/ui/hooks/useMessageQueue.d.ts +1 -1
  154. package/dist/src/ui/hooks/useMessageQueue.js +8 -12
  155. package/dist/src/ui/hooks/useMessageQueue.js.map +1 -1
  156. package/dist/src/ui/hooks/useMessageQueue.test.js +7 -19
  157. package/dist/src/ui/hooks/useMessageQueue.test.js.map +1 -1
  158. package/dist/src/ui/hooks/useSlashCompletion.d.ts +1 -0
  159. package/dist/src/ui/hooks/useSlashCompletion.js +18 -0
  160. package/dist/src/ui/hooks/useSlashCompletion.js.map +1 -1
  161. package/dist/src/ui/types.d.ts +2 -1
  162. package/dist/src/ui/types.js.map +1 -1
  163. package/dist/src/ui/utils/InlineMarkdownRenderer.js +1 -1
  164. package/dist/src/ui/utils/InlineMarkdownRenderer.test.d.ts +6 -0
  165. package/dist/src/ui/utils/InlineMarkdownRenderer.test.js +21 -0
  166. package/dist/src/ui/utils/InlineMarkdownRenderer.test.js.map +1 -0
  167. package/dist/src/ui/utils/commandUtils.d.ts +12 -0
  168. package/dist/src/ui/utils/commandUtils.js +17 -0
  169. package/dist/src/ui/utils/commandUtils.js.map +1 -1
  170. package/dist/src/ui/utils/textOutput.d.ts +2 -0
  171. package/dist/src/ui/utils/textOutput.js +5 -1
  172. package/dist/src/ui/utils/textOutput.js.map +1 -1
  173. package/dist/src/utils/handleAutoUpdate.test.js +1 -1
  174. package/dist/src/utils/handleAutoUpdate.test.js.map +1 -1
  175. package/dist/src/utils/sandbox.js +2 -2
  176. package/dist/src/utils/sandbox.js.map +1 -1
  177. package/dist/src/utils/sessionCleanup.integration.test.js +3 -3
  178. package/dist/src/utils/sessionCleanup.integration.test.js.map +1 -1
  179. package/dist/src/utils/sessionUtils.d.ts +7 -0
  180. package/dist/src/utils/sessionUtils.js +11 -0
  181. package/dist/src/utils/sessionUtils.js.map +1 -1
  182. package/dist/src/utils/sessionUtils.test.js +201 -1
  183. package/dist/src/utils/sessionUtils.test.js.map +1 -1
  184. package/dist/src/validateNonInterActiveAuth.js +3 -3
  185. package/dist/src/validateNonInterActiveAuth.js.map +1 -1
  186. package/dist/src/zed-integration/acp.js +4 -4
  187. package/dist/src/zed-integration/acp.js.map +1 -1
  188. package/dist/src/zed-integration/acp.test.js +0 -5
  189. package/dist/src/zed-integration/acp.test.js.map +1 -1
  190. package/dist/src/zed-integration/schema.d.ts +0 -46
  191. package/dist/src/zed-integration/schema.js +0 -1
  192. package/dist/src/zed-integration/schema.js.map +1 -1
  193. package/dist/src/zed-integration/zedIntegration.js +4 -7
  194. package/dist/src/zed-integration/zedIntegration.js.map +1 -1
  195. package/dist/src/zed-integration/zedIntegration.test.js +0 -5
  196. package/dist/src/zed-integration/zedIntegration.test.js.map +1 -1
  197. package/dist/tsconfig.tsbuildinfo +1 -1
  198. package/package.json +3 -3
@@ -7,6 +7,7 @@ import { describe, it, expect, vi, beforeEach, afterEach, } from 'vitest';
7
7
  import { main, setupUnhandledRejectionHandler, validateDnsResolutionOrder, startInteractiveUI, getNodeMemoryArgs, } from './gemini.js';
8
8
  import os from 'node:os';
9
9
  import v8 from 'node:v8';
10
+ import {} from './config/config.js';
10
11
  import {} from './config/settings.js';
11
12
  import { appEvents, AppEvent } from './utils/events.js';
12
13
  import { debugLogger, } from '@google/gemini-cli-core';
@@ -32,7 +33,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
32
33
  recordSlowRender: vi.fn(),
33
34
  writeToStdout: vi.fn((...args) => process.stdout.write(...args)),
34
35
  patchStdio: vi.fn(() => () => { }),
35
- createInkStdio: vi.fn(() => ({
36
+ createWorkingStdio: vi.fn(() => ({
36
37
  stdout: {
37
38
  write: vi.fn((...args) => process.stdout.write(...args)),
38
39
  columns: 80,
@@ -176,10 +177,11 @@ describe('gemini.tsx main function', () => {
176
177
  delete process.env['SANDBOX'];
177
178
  }
178
179
  const currentListeners = process.listeners('unhandledRejection');
179
- const addedListener = currentListeners.find((listener) => !initialUnhandledRejectionListeners.includes(listener));
180
- if (addedListener) {
181
- process.removeListener('unhandledRejection', addedListener);
182
- }
180
+ currentListeners.forEach((listener) => {
181
+ if (!initialUnhandledRejectionListeners.includes(listener)) {
182
+ process.removeListener('unhandledRejection', listener);
183
+ }
184
+ });
183
185
  vi.restoreAllMocks();
184
186
  });
185
187
  it('verifies that we dont load the config before relaunchAppInChildProcess', async () => {
@@ -612,53 +614,6 @@ describe('gemini.tsx main function kitty protocol', () => {
612
614
  expect(processExitSpy).toHaveBeenCalledWith(0);
613
615
  processExitSpy.mockRestore();
614
616
  });
615
- it('should exit with error when --prompt-interactive is used with piped input', async () => {
616
- const { loadCliConfig, parseArguments } = await import('./config/config.js');
617
- const { loadSettings } = await import('./config/settings.js');
618
- const core = await import('@google/gemini-cli-core');
619
- const processExitSpy = vi
620
- .spyOn(process, 'exit')
621
- .mockImplementation((code) => {
622
- throw new MockProcessExitError(code);
623
- });
624
- const writeToStderrSpy = vi
625
- .spyOn(core, 'writeToStderr')
626
- .mockImplementation(() => true);
627
- vi.mocked(loadSettings).mockReturnValue({
628
- merged: { advanced: {}, security: { auth: {} }, ui: {} },
629
- setValue: vi.fn(),
630
- forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
631
- errors: [],
632
- }); // eslint-disable-line @typescript-eslint/no-explicit-any
633
- vi.mocked(parseArguments).mockResolvedValue({
634
- promptInteractive: true,
635
- }); // eslint-disable-line @typescript-eslint/no-explicit-any
636
- vi.mocked(loadCliConfig).mockResolvedValue({
637
- isInteractive: () => false,
638
- getQuestion: () => '',
639
- getSandbox: () => false,
640
- }); // eslint-disable-line @typescript-eslint/no-explicit-any
641
- // Mock stdin to be non-TTY
642
- Object.defineProperty(process.stdin, 'isTTY', {
643
- value: false,
644
- configurable: true,
645
- });
646
- try {
647
- await main();
648
- }
649
- catch (e) {
650
- if (!(e instanceof MockProcessExitError))
651
- throw e;
652
- }
653
- expect(writeToStderrSpy).toHaveBeenCalledWith(expect.stringContaining('Error: The --prompt-interactive flag cannot be used'));
654
- expect(processExitSpy).toHaveBeenCalledWith(1);
655
- processExitSpy.mockRestore();
656
- writeToStderrSpy.mockRestore();
657
- Object.defineProperty(process.stdin, 'isTTY', {
658
- value: true,
659
- configurable: true,
660
- }); // Restore TTY
661
- });
662
617
  it('should log warning when theme is not found', async () => {
663
618
  const { loadCliConfig, parseArguments } = await import('./config/config.js');
664
619
  const { loadSettings } = await import('./config/settings.js');
@@ -729,12 +684,11 @@ describe('gemini.tsx main function kitty protocol', () => {
729
684
  it('should handle session selector error', async () => {
730
685
  const { loadCliConfig, parseArguments } = await import('./config/config.js');
731
686
  const { loadSettings } = await import('./config/settings.js');
732
- vi.mock('./utils/sessionUtils.js', () => ({
733
- SessionSelector: class {
734
- resolveSession = vi
735
- .fn()
736
- .mockRejectedValue(new Error('Session not found'));
737
- },
687
+ const { SessionSelector } = await import('./utils/sessionUtils.js');
688
+ vi.mocked(SessionSelector).mockImplementation(() => ({
689
+ resolveSession: vi
690
+ .fn()
691
+ .mockRejectedValue(new Error('Session not found')),
738
692
  }));
739
693
  const processExitSpy = vi
740
694
  .spyOn(process, 'exit')
@@ -793,7 +747,7 @@ describe('gemini.tsx main function kitty protocol', () => {
793
747
  throw e;
794
748
  }
795
749
  expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Error resuming session: Session not found'));
796
- expect(processExitSpy).toHaveBeenCalledWith(1);
750
+ expect(processExitSpy).toHaveBeenCalledWith(42);
797
751
  processExitSpy.mockRestore();
798
752
  consoleErrorSpy.mockRestore();
799
753
  });
@@ -862,35 +816,27 @@ describe('gemini.tsx main function kitty protocol', () => {
862
816
  expect(processExitSpy).toHaveBeenCalledWith(0); // Should not exit on cleanup failure
863
817
  processExitSpy.mockRestore();
864
818
  });
865
- it('should handle refreshAuth failure', async () => {
819
+ it('should read from stdin in non-interactive mode', async () => {
866
820
  const { loadCliConfig, parseArguments } = await import('./config/config.js');
867
821
  const { loadSettings } = await import('./config/settings.js');
868
- const { loadSandboxConfig } = await import('./config/sandboxConfig.js');
822
+ const { readStdin } = await import('./utils/readStdin.js');
869
823
  const processExitSpy = vi
870
824
  .spyOn(process, 'exit')
871
825
  .mockImplementation((code) => {
872
826
  throw new MockProcessExitError(code);
873
827
  });
874
- const debugLoggerErrorSpy = vi
875
- .spyOn(debugLogger, 'error')
876
- .mockImplementation(() => { });
877
828
  vi.mocked(loadSettings).mockReturnValue({
878
- merged: {
879
- advanced: {},
880
- security: { auth: { selectedType: 'google' } },
881
- ui: {},
882
- },
829
+ merged: { advanced: {}, security: { auth: {} }, ui: {} },
883
830
  setValue: vi.fn(),
884
831
  forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
885
832
  errors: [],
886
833
  }); // eslint-disable-line @typescript-eslint/no-explicit-any
887
- vi.mocked(loadSandboxConfig).mockResolvedValue({}); // eslint-disable-line @typescript-eslint/no-explicit-any
888
834
  vi.mocked(parseArguments).mockResolvedValue({
889
835
  promptInteractive: false,
890
836
  }); // eslint-disable-line @typescript-eslint/no-explicit-any
891
837
  vi.mocked(loadCliConfig).mockResolvedValue({
892
- isInteractive: () => true,
893
- getQuestion: () => '',
838
+ isInteractive: () => false,
839
+ getQuestion: () => 'test-question',
894
840
  getSandbox: () => false,
895
841
  getDebugMode: () => false,
896
842
  getPolicyEngine: vi.fn(),
@@ -918,8 +864,23 @@ describe('gemini.tsx main function kitty protocol', () => {
918
864
  getFileFilteringRespectGitIgnore: () => true,
919
865
  getOutputFormat: () => 'text',
920
866
  getUsageStatisticsEnabled: () => false,
921
- refreshAuth: vi.fn().mockRejectedValue(new Error('Auth refresh failed')),
922
867
  }); // eslint-disable-line @typescript-eslint/no-explicit-any
868
+ vi.mock('./utils/readStdin.js', () => ({
869
+ readStdin: vi.fn().mockResolvedValue('stdin-data'),
870
+ }));
871
+ const runNonInteractiveSpy = vi.hoisted(() => vi.fn());
872
+ vi.mock('./nonInteractiveCli.js', () => ({
873
+ runNonInteractive: runNonInteractiveSpy,
874
+ }));
875
+ runNonInteractiveSpy.mockClear();
876
+ vi.mock('./validateNonInterActiveAuth.js', () => ({
877
+ validateNonInteractiveAuth: vi.fn().mockResolvedValue({}),
878
+ }));
879
+ // Mock stdin to be non-TTY
880
+ Object.defineProperty(process.stdin, 'isTTY', {
881
+ value: false,
882
+ configurable: true,
883
+ });
923
884
  try {
924
885
  await main();
925
886
  }
@@ -927,49 +888,172 @@ describe('gemini.tsx main function kitty protocol', () => {
927
888
  if (!(e instanceof MockProcessExitError))
928
889
  throw e;
929
890
  }
930
- expect(debugLoggerErrorSpy).toHaveBeenCalledWith('Error authenticating:', expect.any(Error));
931
- expect(processExitSpy).toHaveBeenCalledWith(1);
891
+ expect(readStdin).toHaveBeenCalled();
892
+ // In this test setup, runNonInteractive might be called on the mocked module,
893
+ // but we need to ensure we are checking the correct spy instance.
894
+ // Since vi.mock is hoisted, runNonInteractiveSpy is defined early.
895
+ expect(runNonInteractiveSpy).toHaveBeenCalled();
896
+ const callArgs = runNonInteractiveSpy.mock.calls[0][0];
897
+ expect(callArgs.input).toBe('test-question');
898
+ expect(processExitSpy).toHaveBeenCalledWith(0);
932
899
  processExitSpy.mockRestore();
900
+ Object.defineProperty(process.stdin, 'isTTY', {
901
+ value: true,
902
+ configurable: true,
903
+ });
933
904
  });
934
- it('should read from stdin in non-interactive mode', async () => {
935
- const { loadCliConfig, parseArguments } = await import('./config/config.js');
936
- const { loadSettings } = await import('./config/settings.js');
937
- const { readStdin } = await import('./utils/readStdin.js');
938
- const processExitSpy = vi
939
- .spyOn(process, 'exit')
940
- .mockImplementation((code) => {
905
+ });
906
+ describe('gemini.tsx main function exit codes', () => {
907
+ let originalEnvNoRelaunch;
908
+ beforeEach(() => {
909
+ originalEnvNoRelaunch = process.env['CELL_CLI_NO_RELAUNCH'];
910
+ process.env['CELL_CLI_NO_RELAUNCH'] = 'true';
911
+ vi.spyOn(process, 'exit').mockImplementation((code) => {
941
912
  throw new MockProcessExitError(code);
942
913
  });
914
+ // Mock stderr to avoid cluttering output
915
+ vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
916
+ });
917
+ afterEach(() => {
918
+ if (originalEnvNoRelaunch !== undefined) {
919
+ process.env['CELL_CLI_NO_RELAUNCH'] = originalEnvNoRelaunch;
920
+ }
921
+ else {
922
+ delete process.env['CELL_CLI_NO_RELAUNCH'];
923
+ }
924
+ vi.restoreAllMocks();
925
+ });
926
+ it('should exit with 42 for invalid input combination (prompt-interactive with non-TTY)', async () => {
927
+ const { loadCliConfig, parseArguments } = await import('./config/config.js');
928
+ const { loadSettings } = await import('./config/settings.js');
929
+ vi.mocked(loadCliConfig).mockResolvedValue({});
943
930
  vi.mocked(loadSettings).mockReturnValue({
944
- merged: { advanced: {}, security: { auth: {} }, ui: {} },
945
- setValue: vi.fn(),
946
- forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
931
+ merged: { security: { auth: {} }, ui: {} },
947
932
  errors: [],
948
- }); // eslint-disable-line @typescript-eslint/no-explicit-any
933
+ });
949
934
  vi.mocked(parseArguments).mockResolvedValue({
950
- promptInteractive: false,
951
- }); // eslint-disable-line @typescript-eslint/no-explicit-any
935
+ promptInteractive: true,
936
+ });
937
+ Object.defineProperty(process.stdin, 'isTTY', {
938
+ value: false,
939
+ configurable: true,
940
+ });
941
+ try {
942
+ await main();
943
+ expect.fail('Should have thrown MockProcessExitError');
944
+ }
945
+ catch (e) {
946
+ expect(e).toBeInstanceOf(MockProcessExitError);
947
+ expect(e.code).toBe(42);
948
+ }
949
+ });
950
+ it('should exit with 41 for auth failure during sandbox setup', async () => {
951
+ const { loadCliConfig, parseArguments } = await import('./config/config.js');
952
+ const { loadSettings } = await import('./config/settings.js');
953
+ const { loadSandboxConfig } = await import('./config/sandboxConfig.js');
954
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
955
+ vi.mocked(loadSandboxConfig).mockResolvedValue({});
956
+ vi.mocked(loadCliConfig).mockResolvedValue({
957
+ refreshAuth: vi.fn().mockRejectedValue(new Error('Auth failed')),
958
+ });
959
+ vi.mocked(loadSettings).mockReturnValue({
960
+ merged: {
961
+ security: { auth: { selectedType: 'google', useExternal: false } },
962
+ ui: {},
963
+ },
964
+ errors: [],
965
+ });
966
+ vi.mocked(parseArguments).mockResolvedValue({});
967
+ vi.mock('./config/auth.js', () => ({
968
+ validateAuthMethod: vi.fn().mockReturnValue(null),
969
+ }));
970
+ try {
971
+ await main();
972
+ expect.fail('Should have thrown MockProcessExitError');
973
+ }
974
+ catch (e) {
975
+ expect(e).toBeInstanceOf(MockProcessExitError);
976
+ expect(e.code).toBe(41);
977
+ }
978
+ });
979
+ it('should exit with 42 for session resume failure', async () => {
980
+ const { loadCliConfig, parseArguments } = await import('./config/config.js');
981
+ const { loadSettings } = await import('./config/settings.js');
952
982
  vi.mocked(loadCliConfig).mockResolvedValue({
953
983
  isInteractive: () => false,
954
- getQuestion: () => 'test-question',
984
+ getQuestion: () => 'test',
955
985
  getSandbox: () => false,
956
986
  getDebugMode: () => false,
957
- getPolicyEngine: vi.fn(),
958
- getMessageBus: () => ({ subscribe: vi.fn() }),
959
- initialize: vi.fn(),
960
- getContentGeneratorConfig: vi.fn(),
987
+ getListExtensions: () => false,
988
+ getListSessions: () => false,
989
+ getDeleteSession: () => undefined,
961
990
  getMcpServers: () => ({}),
962
991
  getMcpClientManager: vi.fn(),
992
+ initialize: vi.fn(),
963
993
  getIdeMode: () => false,
964
994
  getExperimentalZedIntegration: () => false,
965
995
  getScreenReader: () => false,
966
996
  getGeminiMdFileCount: () => 0,
967
- getProjectRoot: () => '/',
997
+ getPolicyEngine: vi.fn(),
998
+ getMessageBus: () => ({ subscribe: vi.fn() }),
999
+ getToolRegistry: vi.fn(),
1000
+ getContentGeneratorConfig: vi.fn(),
1001
+ getModel: () => 'gemini-pro',
1002
+ getEmbeddingModel: () => 'embedding-001',
1003
+ getApprovalMode: () => 'default',
1004
+ getCoreTools: () => [],
1005
+ getTelemetryEnabled: () => false,
1006
+ getTelemetryLogPromptsEnabled: () => false,
1007
+ getFileFilteringRespectGitIgnore: () => true,
1008
+ getOutputFormat: () => 'text',
1009
+ getExtensions: () => [],
1010
+ getUsageStatisticsEnabled: () => false,
1011
+ });
1012
+ vi.mocked(loadSettings).mockReturnValue({
1013
+ merged: { security: { auth: {} }, ui: {} },
1014
+ errors: [],
1015
+ });
1016
+ vi.mocked(parseArguments).mockResolvedValue({
1017
+ resume: 'invalid-session',
1018
+ });
1019
+ vi.mock('./utils/sessionUtils.js', () => ({
1020
+ SessionSelector: vi.fn().mockImplementation(() => ({
1021
+ resolveSession: vi
1022
+ .fn()
1023
+ .mockRejectedValue(new Error('Session not found')),
1024
+ })),
1025
+ }));
1026
+ try {
1027
+ await main();
1028
+ expect.fail('Should have thrown MockProcessExitError');
1029
+ }
1030
+ catch (e) {
1031
+ expect(e).toBeInstanceOf(MockProcessExitError);
1032
+ expect(e.code).toBe(42);
1033
+ }
1034
+ });
1035
+ it('should exit with 42 for no input provided', async () => {
1036
+ const { loadCliConfig, parseArguments } = await import('./config/config.js');
1037
+ const { loadSettings } = await import('./config/settings.js');
1038
+ vi.mocked(loadCliConfig).mockResolvedValue({
1039
+ isInteractive: () => false,
1040
+ getQuestion: () => '',
1041
+ getSandbox: () => false,
1042
+ getDebugMode: () => false,
968
1043
  getListExtensions: () => false,
969
1044
  getListSessions: () => false,
970
1045
  getDeleteSession: () => undefined,
1046
+ getMcpServers: () => ({}),
1047
+ getMcpClientManager: vi.fn(),
1048
+ initialize: vi.fn(),
1049
+ getIdeMode: () => false,
1050
+ getExperimentalZedIntegration: () => false,
1051
+ getScreenReader: () => false,
1052
+ getGeminiMdFileCount: () => 0,
1053
+ getPolicyEngine: vi.fn(),
1054
+ getMessageBus: () => ({ subscribe: vi.fn() }),
971
1055
  getToolRegistry: vi.fn(),
972
- getExtensions: () => [],
1056
+ getContentGeneratorConfig: vi.fn(),
973
1057
  getModel: () => 'gemini-pro',
974
1058
  getEmbeddingModel: () => 'embedding-001',
975
1059
  getApprovalMode: () => 'default',
@@ -978,44 +1062,26 @@ describe('gemini.tsx main function kitty protocol', () => {
978
1062
  getTelemetryLogPromptsEnabled: () => false,
979
1063
  getFileFilteringRespectGitIgnore: () => true,
980
1064
  getOutputFormat: () => 'text',
1065
+ getExtensions: () => [],
981
1066
  getUsageStatisticsEnabled: () => false,
982
- }); // eslint-disable-line @typescript-eslint/no-explicit-any
983
- vi.mock('./utils/readStdin.js', () => ({
984
- readStdin: vi.fn().mockResolvedValue('stdin-data'),
985
- }));
986
- const runNonInteractiveSpy = vi.hoisted(() => vi.fn());
987
- vi.mock('./nonInteractiveCli.js', () => ({
988
- runNonInteractive: runNonInteractiveSpy,
989
- }));
990
- runNonInteractiveSpy.mockClear();
991
- vi.mock('./validateNonInterActiveAuth.js', () => ({
992
- validateNonInteractiveAuth: vi.fn().mockResolvedValue({}),
993
- }));
994
- // Mock stdin to be non-TTY
1067
+ });
1068
+ vi.mocked(loadSettings).mockReturnValue({
1069
+ merged: { security: { auth: {} }, ui: {} },
1070
+ errors: [],
1071
+ });
1072
+ vi.mocked(parseArguments).mockResolvedValue({});
995
1073
  Object.defineProperty(process.stdin, 'isTTY', {
996
- value: false,
1074
+ value: true, // Simulate TTY so it doesn't try to read stdin
997
1075
  configurable: true,
998
1076
  });
999
1077
  try {
1000
1078
  await main();
1079
+ expect.fail('Should have thrown MockProcessExitError');
1001
1080
  }
1002
1081
  catch (e) {
1003
- if (!(e instanceof MockProcessExitError))
1004
- throw e;
1082
+ expect(e).toBeInstanceOf(MockProcessExitError);
1083
+ expect(e.code).toBe(42);
1005
1084
  }
1006
- expect(readStdin).toHaveBeenCalled();
1007
- // In this test setup, runNonInteractive might be called on the mocked module,
1008
- // but we need to ensure we are checking the correct spy instance.
1009
- // Since vi.mock is hoisted, runNonInteractiveSpy is defined early.
1010
- expect(runNonInteractiveSpy).toHaveBeenCalled();
1011
- const callArgs = runNonInteractiveSpy.mock.calls[0][0];
1012
- expect(callArgs.input).toBe('test-question');
1013
- expect(processExitSpy).toHaveBeenCalledWith(0);
1014
- processExitSpy.mockRestore();
1015
- Object.defineProperty(process.stdin, 'isTTY', {
1016
- value: true,
1017
- configurable: true,
1018
- });
1019
1085
  });
1020
1086
  });
1021
1087
  describe('validateDnsResolutionOrder', () => {
@@ -1098,7 +1164,6 @@ describe('startInteractiveUI', () => {
1098
1164
  const renderSpy = vi.mocked(render);
1099
1165
  await startTestInteractiveUI(mockConfig, mockSettings, mockStartupWarnings, mockWorkspaceRoot, undefined, mockInitializationResult);
1100
1166
  // Verify render was called with correct options
1101
- expect(renderSpy).toHaveBeenCalledTimes(1);
1102
1167
  const [reactElement, options] = renderSpy.mock.calls[0];
1103
1168
  // Verify render options
1104
1169
  expect(options).toEqual(expect.objectContaining({