brosh 0.2.2

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 (200) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +181 -0
  3. package/brosh_brandmark.svg +3 -0
  4. package/brosh_logo.svg +27 -0
  5. package/cli_icon.svg +52 -0
  6. package/dist/client.d.ts +5 -0
  7. package/dist/client.d.ts.map +1 -0
  8. package/dist/client.js +138 -0
  9. package/dist/client.js.map +1 -0
  10. package/dist/index.d.ts +3 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +618 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/lib.d.ts +25 -0
  15. package/dist/lib.d.ts.map +1 -0
  16. package/dist/lib.js +28 -0
  17. package/dist/lib.js.map +1 -0
  18. package/dist/mode-selector.d.ts +7 -0
  19. package/dist/mode-selector.d.ts.map +1 -0
  20. package/dist/mode-selector.js +138 -0
  21. package/dist/mode-selector.js.map +1 -0
  22. package/dist/prompts/index.d.ts +3 -0
  23. package/dist/prompts/index.d.ts.map +1 -0
  24. package/dist/prompts/index.js +79 -0
  25. package/dist/prompts/index.js.map +1 -0
  26. package/dist/recording/index.d.ts +4 -0
  27. package/dist/recording/index.d.ts.map +1 -0
  28. package/dist/recording/index.js +3 -0
  29. package/dist/recording/index.js.map +1 -0
  30. package/dist/recording/manager.d.ts +62 -0
  31. package/dist/recording/manager.d.ts.map +1 -0
  32. package/dist/recording/manager.js +123 -0
  33. package/dist/recording/manager.js.map +1 -0
  34. package/dist/recording/recorder.d.ts +95 -0
  35. package/dist/recording/recorder.d.ts.map +1 -0
  36. package/dist/recording/recorder.js +330 -0
  37. package/dist/recording/recorder.js.map +1 -0
  38. package/dist/recording/types.d.ts +65 -0
  39. package/dist/recording/types.d.ts.map +1 -0
  40. package/dist/recording/types.js +2 -0
  41. package/dist/recording/types.js.map +1 -0
  42. package/dist/sandbox/ModeSelector.d.ts +2 -0
  43. package/dist/sandbox/ModeSelector.d.ts.map +1 -0
  44. package/dist/sandbox/ModeSelector.js +2 -0
  45. package/dist/sandbox/ModeSelector.js.map +1 -0
  46. package/dist/sandbox/config.d.ts +46 -0
  47. package/dist/sandbox/config.d.ts.map +1 -0
  48. package/dist/sandbox/config.js +144 -0
  49. package/dist/sandbox/config.js.map +1 -0
  50. package/dist/sandbox/controller.d.ts +72 -0
  51. package/dist/sandbox/controller.d.ts.map +1 -0
  52. package/dist/sandbox/controller.js +208 -0
  53. package/dist/sandbox/controller.js.map +1 -0
  54. package/dist/sandbox/index.d.ts +6 -0
  55. package/dist/sandbox/index.d.ts.map +1 -0
  56. package/dist/sandbox/index.js +4 -0
  57. package/dist/sandbox/index.js.map +1 -0
  58. package/dist/sandbox/mode-prompt.d.ts +10 -0
  59. package/dist/sandbox/mode-prompt.d.ts.map +1 -0
  60. package/dist/sandbox/mode-prompt.js +130 -0
  61. package/dist/sandbox/mode-prompt.js.map +1 -0
  62. package/dist/sandbox/prompt.d.ts +10 -0
  63. package/dist/sandbox/prompt.d.ts.map +1 -0
  64. package/dist/sandbox/prompt.js +434 -0
  65. package/dist/sandbox/prompt.js.map +1 -0
  66. package/dist/server.d.ts +28 -0
  67. package/dist/server.d.ts.map +1 -0
  68. package/dist/server.js +59 -0
  69. package/dist/server.js.map +1 -0
  70. package/dist/terminal/index.d.ts +5 -0
  71. package/dist/terminal/index.d.ts.map +1 -0
  72. package/dist/terminal/index.js +3 -0
  73. package/dist/terminal/index.js.map +1 -0
  74. package/dist/terminal/manager.d.ts +153 -0
  75. package/dist/terminal/manager.d.ts.map +1 -0
  76. package/dist/terminal/manager.js +276 -0
  77. package/dist/terminal/manager.js.map +1 -0
  78. package/dist/terminal/session.d.ts +137 -0
  79. package/dist/terminal/session.d.ts.map +1 -0
  80. package/dist/terminal/session.js +752 -0
  81. package/dist/terminal/session.js.map +1 -0
  82. package/dist/tools/definitions.d.ts +18 -0
  83. package/dist/tools/definitions.d.ts.map +1 -0
  84. package/dist/tools/definitions.js +114 -0
  85. package/dist/tools/definitions.js.map +1 -0
  86. package/dist/tools/getContent.d.ts +32 -0
  87. package/dist/tools/getContent.d.ts.map +1 -0
  88. package/dist/tools/getContent.js +38 -0
  89. package/dist/tools/getContent.js.map +1 -0
  90. package/dist/tools/index.d.ts +4 -0
  91. package/dist/tools/index.d.ts.map +1 -0
  92. package/dist/tools/index.js +49 -0
  93. package/dist/tools/index.js.map +1 -0
  94. package/dist/tools/screenshot.d.ts +20 -0
  95. package/dist/tools/screenshot.d.ts.map +1 -0
  96. package/dist/tools/screenshot.js +28 -0
  97. package/dist/tools/screenshot.js.map +1 -0
  98. package/dist/tools/sendKey.d.ts +31 -0
  99. package/dist/tools/sendKey.d.ts.map +1 -0
  100. package/dist/tools/sendKey.js +38 -0
  101. package/dist/tools/sendKey.js.map +1 -0
  102. package/dist/tools/startRecording.d.ts +68 -0
  103. package/dist/tools/startRecording.d.ts.map +1 -0
  104. package/dist/tools/startRecording.js +111 -0
  105. package/dist/tools/startRecording.js.map +1 -0
  106. package/dist/tools/stopRecording.d.ts +31 -0
  107. package/dist/tools/stopRecording.d.ts.map +1 -0
  108. package/dist/tools/stopRecording.js +76 -0
  109. package/dist/tools/stopRecording.js.map +1 -0
  110. package/dist/tools/type.d.ts +31 -0
  111. package/dist/tools/type.d.ts.map +1 -0
  112. package/dist/tools/type.js +31 -0
  113. package/dist/tools/type.js.map +1 -0
  114. package/dist/transport/gui-protocol.d.ts +163 -0
  115. package/dist/transport/gui-protocol.d.ts.map +1 -0
  116. package/dist/transport/gui-protocol.js +68 -0
  117. package/dist/transport/gui-protocol.js.map +1 -0
  118. package/dist/transport/gui-stream.d.ts +139 -0
  119. package/dist/transport/gui-stream.d.ts.map +1 -0
  120. package/dist/transport/gui-stream.js +440 -0
  121. package/dist/transport/gui-stream.js.map +1 -0
  122. package/dist/transport/index.d.ts +6 -0
  123. package/dist/transport/index.d.ts.map +1 -0
  124. package/dist/transport/index.js +6 -0
  125. package/dist/transport/index.js.map +1 -0
  126. package/dist/transport/socket.d.ts +46 -0
  127. package/dist/transport/socket.d.ts.map +1 -0
  128. package/dist/transport/socket.js +310 -0
  129. package/dist/transport/socket.js.map +1 -0
  130. package/dist/types/mcp-client-info.d.ts +226 -0
  131. package/dist/types/mcp-client-info.d.ts.map +1 -0
  132. package/dist/types/mcp-client-info.js +62 -0
  133. package/dist/types/mcp-client-info.js.map +1 -0
  134. package/dist/ui/index.d.ts +12 -0
  135. package/dist/ui/index.d.ts.map +1 -0
  136. package/dist/ui/index.js +84 -0
  137. package/dist/ui/index.js.map +1 -0
  138. package/dist/utils/env.d.ts +17 -0
  139. package/dist/utils/env.d.ts.map +1 -0
  140. package/dist/utils/env.js +35 -0
  141. package/dist/utils/env.js.map +1 -0
  142. package/dist/utils/keys.d.ts +16 -0
  143. package/dist/utils/keys.d.ts.map +1 -0
  144. package/dist/utils/keys.js +155 -0
  145. package/dist/utils/keys.js.map +1 -0
  146. package/dist/utils/platform.d.ts +16 -0
  147. package/dist/utils/platform.d.ts.map +1 -0
  148. package/dist/utils/platform.js +41 -0
  149. package/dist/utils/platform.js.map +1 -0
  150. package/dist/utils/session-logger.d.ts +31 -0
  151. package/dist/utils/session-logger.d.ts.map +1 -0
  152. package/dist/utils/session-logger.js +125 -0
  153. package/dist/utils/session-logger.js.map +1 -0
  154. package/dist/utils/stats.d.ts +46 -0
  155. package/dist/utils/stats.d.ts.map +1 -0
  156. package/dist/utils/stats.js +89 -0
  157. package/dist/utils/stats.js.map +1 -0
  158. package/dist/utils/version.d.ts +2 -0
  159. package/dist/utils/version.d.ts.map +1 -0
  160. package/dist/utils/version.js +9 -0
  161. package/dist/utils/version.js.map +1 -0
  162. package/logo.png +0 -0
  163. package/package.json +61 -0
  164. package/packages/desktop-electron/THIRD-PARTY-NOTICES +56 -0
  165. package/packages/desktop-electron/build/afterPack.cjs +147 -0
  166. package/packages/desktop-electron/package-lock.json +10071 -0
  167. package/packages/desktop-electron/package.json +170 -0
  168. package/packages/desktop-electron/resources/icons/mac/icon.icns +0 -0
  169. package/packages/desktop-electron/resources/icons/png/1024x1024.png +0 -0
  170. package/packages/desktop-electron/resources/icons/png/128x128.png +0 -0
  171. package/packages/desktop-electron/resources/icons/png/16x16.png +0 -0
  172. package/packages/desktop-electron/resources/icons/png/24x24.png +0 -0
  173. package/packages/desktop-electron/resources/icons/png/256x256.png +0 -0
  174. package/packages/desktop-electron/resources/icons/png/32x32.png +0 -0
  175. package/packages/desktop-electron/resources/icons/png/48x48.png +0 -0
  176. package/packages/desktop-electron/resources/icons/png/512x512.png +0 -0
  177. package/packages/desktop-electron/resources/icons/png/64x64.png +0 -0
  178. package/packages/desktop-electron/resources/icons/win/icon.ico +0 -0
  179. package/packages/desktop-electron/scripts/download-models.js +97 -0
  180. package/packages/desktop-electron/scripts/prepare-sandbox-bins.js +186 -0
  181. package/packages/desktop-electron/tests/main/ai-detection/additionalFunctions.test.ts +224 -0
  182. package/packages/desktop-electron/tests/main/ai-detection/checkOverridePrefix.test.ts +162 -0
  183. package/packages/desktop-electron/tests/main/ai-detection/classifyInput.test.ts +132 -0
  184. package/packages/desktop-electron/tests/main/ai-detection/detectTypos.test.ts +342 -0
  185. package/packages/desktop-electron/tests/main/ai-detection/fixtures/commands.ts +134 -0
  186. package/packages/desktop-electron/tests/main/ai-detection/fixtures/natural-language.ts +133 -0
  187. package/packages/desktop-electron/tests/main/ai-detection/fixtures/typos.ts +123 -0
  188. package/packages/desktop-electron/tests/main/ai-detection/hasValidSubcommand.test.ts +218 -0
  189. package/packages/desktop-electron/tests/main/ai-detection/isCommandNotFound.test.ts +117 -0
  190. package/packages/desktop-electron/tests/main/error-triage/buildTriagePrompt.test.ts +133 -0
  191. package/packages/desktop-electron/tests/main/error-triage/parseTriageResponse.test.ts +123 -0
  192. package/packages/desktop-electron/tests/main/terminal-bridge/battery-optimization.test.ts +243 -0
  193. package/packages/desktop-electron/tests/main/terminal-bridge/command-fast-track.test.ts +292 -0
  194. package/packages/desktop-electron/tests/main/terminal-bridge/default-cwd.test.ts +70 -0
  195. package/packages/desktop-electron/tests/setup.ts +274 -0
  196. package/packages/desktop-electron/tsconfig.json +18 -0
  197. package/packages/desktop-electron/tsconfig.main.json +20 -0
  198. package/packages/desktop-electron/vite.config.ts +19 -0
  199. package/packages/desktop-electron/vitest.config.ts +18 -0
  200. package/tsconfig.json +19 -0
@@ -0,0 +1,70 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import * as os from 'os';
3
+
4
+ /**
5
+ * Tests for default cwd behavior in terminal sessions.
6
+ *
7
+ * When creating a new terminal session, if no cwd is specified,
8
+ * the session should start in the user's home directory.
9
+ */
10
+
11
+ describe('Default CWD Behavior', () => {
12
+ describe('cwd fallback logic', () => {
13
+ // This tests the logic pattern used in terminal-bridge.ts:
14
+ // const cwd = options?.cwd ?? os.homedir();
15
+
16
+ it('should return provided cwd when specified', () => {
17
+ const options = { cwd: '/custom/path' };
18
+ const cwd = options?.cwd ?? os.homedir();
19
+ expect(cwd).toBe('/custom/path');
20
+ });
21
+
22
+ it('should return home directory when cwd is undefined', () => {
23
+ const options: { cwd?: string } = { cwd: undefined };
24
+ const cwd = options?.cwd ?? os.homedir();
25
+ expect(cwd).toBe(os.homedir());
26
+ });
27
+
28
+ it('should return home directory when options is undefined', () => {
29
+ const options: { cwd?: string } | undefined = undefined;
30
+ const cwd = options?.cwd ?? os.homedir();
31
+ expect(cwd).toBe(os.homedir());
32
+ });
33
+
34
+ it('should return home directory when options is empty object', () => {
35
+ const options: { cwd?: string } = {};
36
+ const cwd = options?.cwd ?? os.homedir();
37
+ expect(cwd).toBe(os.homedir());
38
+ });
39
+
40
+ it('should preserve empty string cwd if explicitly set', () => {
41
+ // Empty string is falsy but is still a valid value with ??
42
+ const options = { cwd: '' };
43
+ const cwd = options?.cwd ?? os.homedir();
44
+ // Note: ?? only falls back on null/undefined, not empty string
45
+ expect(cwd).toBe('');
46
+ });
47
+
48
+ it('should handle null cwd by falling back to home', () => {
49
+ const options = { cwd: null as unknown as string };
50
+ const cwd = options?.cwd ?? os.homedir();
51
+ expect(cwd).toBe(os.homedir());
52
+ });
53
+ });
54
+
55
+ describe('os.homedir()', () => {
56
+ it('should return a non-empty string', () => {
57
+ const home = os.homedir();
58
+ expect(home).toBeTruthy();
59
+ expect(typeof home).toBe('string');
60
+ expect(home.length).toBeGreaterThan(0);
61
+ });
62
+
63
+ it('should return an absolute path', () => {
64
+ const home = os.homedir();
65
+ // On Unix systems, absolute paths start with /
66
+ // On Windows, they start with a drive letter like C:\
67
+ expect(home.startsWith('/') || /^[A-Z]:\\/i.test(home)).toBe(true);
68
+ });
69
+ });
70
+ });
@@ -0,0 +1,274 @@
1
+ import { vi } from 'vitest';
2
+ import * as path from 'path';
3
+
4
+ // Mock the ML classifier to provide predictable classifications in tests
5
+ // This simulates the actual ML model behavior without loading the model
6
+ vi.mock('../src/main/ml-classifier.js', () => {
7
+ // Simple heuristics to simulate ML classification
8
+ const classifyWithML = async (input: string) => {
9
+ const trimmed = input.trim().toLowerCase();
10
+
11
+ // Empty input
12
+ if (!trimmed) return null;
13
+
14
+ // Questions (ends with ? or starts with question words)
15
+ const questionWords = ['how', 'what', 'why', 'where', 'when', 'who', 'which', 'can', 'could', 'would', 'should'];
16
+ const firstWord = trimmed.split(/\s+/)[0];
17
+ if (trimmed.endsWith('?') || questionWords.includes(firstWord)) {
18
+ return {
19
+ classification: 'NATURAL_LANGUAGE' as const,
20
+ confidence: 0.95,
21
+ scores: { command: 0.05, naturalLanguage: 0.95 },
22
+ inferenceTimeMs: 5,
23
+ };
24
+ }
25
+
26
+ // First-person statements
27
+ const firstPersonWords = ['i', "i'm", "i've", "i'd", 'my', 'me'];
28
+ if (firstPersonWords.includes(firstWord)) {
29
+ return {
30
+ classification: 'NATURAL_LANGUAGE' as const,
31
+ confidence: 0.85,
32
+ scores: { command: 0.15, naturalLanguage: 0.85 },
33
+ inferenceTimeMs: 5,
34
+ };
35
+ }
36
+
37
+ // Default to command - the ML model primarily identifies commands
38
+ // and the non-command heuristics above handle NL cases
39
+ return {
40
+ classification: 'COMMAND' as const,
41
+ confidence: 0.9,
42
+ scores: { command: 0.9, naturalLanguage: 0.1 },
43
+ inferenceTimeMs: 5,
44
+ };
45
+ };
46
+
47
+ return {
48
+ classifyWithML,
49
+ preloadModel: vi.fn().mockResolvedValue(true),
50
+ getModelStatus: vi.fn().mockReturnValue({
51
+ loaded: true,
52
+ loading: false,
53
+ error: null,
54
+ modelName: 'brosh-ky-mock',
55
+ }),
56
+ };
57
+ });
58
+
59
+ // Comprehensive list of common commands for realistic testing
60
+ const MOCK_COMMANDS = [
61
+ // Core Unix utilities
62
+ 'ls', 'cd', 'pwd', 'echo', 'cat', 'grep', 'find', 'mkdir', 'rm', 'cp', 'mv',
63
+ 'chmod', 'chown', 'chgrp', 'ln', 'touch', 'stat', 'file', 'realpath', 'basename', 'dirname',
64
+
65
+ // Text processing
66
+ 'sed', 'awk', 'sort', 'uniq', 'head', 'tail', 'wc', 'cut', 'paste', 'tr', 'rev',
67
+ 'tee', 'xargs', 'yes', 'true', 'false',
68
+
69
+ // File viewing
70
+ 'less', 'more', 'diff', 'cmp', 'comm', 'strings', 'hexdump', 'od',
71
+
72
+ // Search and find
73
+ 'locate', 'updatedb', 'which', 'whereis', 'type', 'command',
74
+
75
+ // Version control
76
+ 'git', 'svn', 'hg', 'cvs',
77
+
78
+ // Package managers
79
+ 'npm', 'npx', 'yarn', 'pnpm', 'bun',
80
+ 'pip', 'pip3', 'pipx', 'poetry', 'conda',
81
+ 'brew', 'apt', 'apt-get', 'yum', 'dnf', 'pacman', 'zypper',
82
+ 'cargo', 'rustup',
83
+ 'go', 'gem', 'bundle',
84
+
85
+ // Container/orchestration
86
+ 'docker', 'docker-compose', 'podman', 'kubectl', 'helm', 'minikube', 'kind',
87
+
88
+ // Cloud CLIs
89
+ 'aws', 'gcloud', 'az', 'terraform', 'pulumi',
90
+ 'gh', 'hub', 'gitlab',
91
+
92
+ // Languages/runtimes
93
+ 'node', 'deno', 'bun',
94
+ 'python', 'python3', 'python2',
95
+ 'ruby', 'irb', 'rake',
96
+ 'perl', 'php',
97
+ 'java', 'javac', 'jar',
98
+ 'rustc', 'swift', 'kotlin',
99
+
100
+ // Build tools
101
+ 'make', 'cmake', 'ninja', 'meson',
102
+ 'gcc', 'g++', 'clang', 'clang++', 'ld',
103
+ 'mvn', 'gradle', 'ant',
104
+
105
+ // Editors
106
+ 'vim', 'nvim', 'vi', 'nano', 'emacs', 'code', 'subl', 'atom',
107
+
108
+ // Network
109
+ 'ssh', 'scp', 'sftp', 'rsync', 'curl', 'wget', 'httpie',
110
+ 'ping', 'traceroute', 'netstat', 'ss', 'ip', 'ifconfig',
111
+ 'nc', 'netcat', 'socat', 'nmap',
112
+ 'dig', 'nslookup', 'host', 'whois',
113
+
114
+ // Archive/compression
115
+ 'tar', 'gzip', 'gunzip', 'bzip2', 'xz', 'unzip', 'zip', '7z', 'rar',
116
+
117
+ // Process management
118
+ 'ps', 'top', 'htop', 'kill', 'pkill', 'killall', 'pgrep',
119
+ 'nice', 'renice', 'nohup', 'timeout',
120
+ 'jobs', 'fg', 'bg', 'wait', 'disown',
121
+
122
+ // System info
123
+ 'df', 'du', 'free', 'uname', 'uptime', 'hostname', 'hostnamectl',
124
+ 'lscpu', 'lsmem', 'lsblk', 'lsusb', 'lspci',
125
+ 'w', 'who', 'whoami', 'id', 'groups', 'users', 'last', 'lastlog',
126
+
127
+ // Date/time
128
+ 'date', 'cal', 'time', 'timedatectl',
129
+
130
+ // Documentation
131
+ 'man', 'info', 'help', 'apropos', 'whatis',
132
+
133
+ // Shell utilities
134
+ 'env', 'export', 'source', 'alias', 'unalias', 'set', 'unset',
135
+ 'history', 'fc', 'exit', 'logout', 'clear', 'reset',
136
+ 'test', 'expr', 'bc', 'dc', 'printf', 'read',
137
+
138
+ // Terminal multiplexers
139
+ 'tmux', 'screen', 'mosh', 'byobu',
140
+
141
+ // Modern CLI tools
142
+ 'jq', 'yq', 'fx',
143
+ 'bat', 'exa', 'eza', 'lsd', 'tree',
144
+ 'rg', 'ag', 'ack',
145
+ 'fd', 'fzf', 'sk',
146
+ 'delta', 'difft',
147
+ 'tldr', 'cheat',
148
+ 'zoxide', 'autojump', 'fasd',
149
+ 'direnv', 'asdf', 'mise',
150
+
151
+ // Database clients
152
+ 'mysql', 'psql', 'sqlite3', 'mongo', 'redis-cli',
153
+
154
+ // Testing/linting
155
+ 'jest', 'mocha', 'pytest', 'rspec',
156
+ 'eslint', 'prettier', 'black', 'flake8', 'mypy',
157
+ 'shellcheck', 'shfmt',
158
+ ];
159
+
160
+ // Mock child_process to return a predictable set of commands
161
+ vi.mock('child_process', () => ({
162
+ execSync: vi.fn((cmd: string) => {
163
+ // Return common commands for command cache initialization
164
+ if (cmd.includes('compgen') || cmd.includes('commands')) {
165
+ return MOCK_COMMANDS.join('\n');
166
+ }
167
+ // Return some common aliases
168
+ if (cmd.includes('alias')) {
169
+ return [
170
+ 'alias ll="ls -la"',
171
+ 'alias la="ls -a"',
172
+ 'alias l="ls -CF"',
173
+ 'alias g="git"',
174
+ 'alias gst="git status"',
175
+ 'alias gco="git checkout"',
176
+ 'alias gp="git push"',
177
+ 'alias gl="git pull"',
178
+ 'alias dc="docker-compose"',
179
+ 'alias k="kubectl"',
180
+ ].join('\n');
181
+ }
182
+ // Return shell functions for function cache initialization
183
+ if (cmd.includes('functions') || cmd.includes('declare -F')) {
184
+ return MOCK_FUNCTIONS.join('\n');
185
+ }
186
+ // Handle `command -v <cmd>` queries
187
+ if (cmd.startsWith('command -v ')) {
188
+ const cmdToCheck = cmd.replace('command -v ', '').trim();
189
+ // Empty or invalid command should throw
190
+ if (!cmdToCheck || cmdToCheck.length === 0) {
191
+ throw new Error('command not found');
192
+ }
193
+ // Check if command is in our mock list or dynamic list
194
+ const allKnown = [...MOCK_COMMANDS, 'newcmd', 'latestool', 'll', 'la', 'l', 'g', 'gst', 'gco', 'gp', 'gl', 'dc', 'k'];
195
+ if (allKnown.includes(cmdToCheck)) {
196
+ return `/usr/bin/${cmdToCheck}`;
197
+ }
198
+ // Unknown command throws
199
+ throw new Error('command not found');
200
+ }
201
+ return '';
202
+ }),
203
+ exec: vi.fn(),
204
+ }));
205
+
206
+ // Mock fs to simulate PATH lookups for commands not in the initial cache
207
+ // This allows testing the "checkPathDirectly" fallback
208
+ vi.mock('fs', async () => {
209
+ const actual = await vi.importActual('fs');
210
+ return {
211
+ ...actual,
212
+ existsSync: vi.fn((filePath: string) => {
213
+ // Don't match empty or invalid paths
214
+ if (!filePath || filePath.length < 2) return false;
215
+
216
+ // Simulate finding commands in PATH
217
+ // Extract command name from path like "/usr/bin/eza"
218
+ const cmd = path.basename(filePath);
219
+
220
+ // Don't match empty command names
221
+ if (!cmd || cmd.length === 0) return false;
222
+
223
+ // Commands in MOCK_COMMANDS are "found" in PATH
224
+ if (MOCK_COMMANDS.includes(cmd)) {
225
+ return true;
226
+ }
227
+ // Also check for commands that might be discovered dynamically
228
+ const dynamicCommands = ['newcmd', 'latestool'];
229
+ if (dynamicCommands.includes(cmd)) {
230
+ return true;
231
+ }
232
+ return false;
233
+ }),
234
+ statSync: vi.fn((filePath: string) => {
235
+ if (!filePath || filePath.length < 2) {
236
+ throw new Error('ENOENT');
237
+ }
238
+ const cmd = path.basename(filePath);
239
+ if (!cmd || cmd.length === 0) {
240
+ throw new Error('ENOENT');
241
+ }
242
+ if (MOCK_COMMANDS.includes(cmd) || ['newcmd', 'latestool'].includes(cmd)) {
243
+ return { isFile: () => true };
244
+ }
245
+ throw new Error('ENOENT');
246
+ }),
247
+ };
248
+ });
249
+
250
+ // Mock shell functions (like those loaded from .zshrc plugins)
251
+ const MOCK_FUNCTIONS = [
252
+ // Powerlevel10k
253
+ 'p10k',
254
+ // Node version manager
255
+ 'nvm',
256
+ // Python version manager
257
+ 'pyenv',
258
+ // Ruby version manager
259
+ 'rbenv',
260
+ // Direnv
261
+ 'direnv',
262
+ // Zoxide
263
+ 'z',
264
+ '__zoxide_z',
265
+ // FZF
266
+ 'fzf-history-widget',
267
+ // Some internal functions (should be filtered out)
268
+ '_p10k_worker_start',
269
+ '_nvm_auto',
270
+ '-my-hidden-func',
271
+ ];
272
+
273
+ // Export for tests that need to verify mock data
274
+ export { MOCK_COMMANDS, MOCK_FUNCTIONS };
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "jsx": "react-jsx",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "noEmit": true
15
+ },
16
+ "include": ["src/renderer/**/*"],
17
+ "exclude": ["node_modules", "dist"]
18
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": ["ES2022"],
7
+ "outDir": "dist/main",
8
+ "rootDir": "src/main",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+ "sourceMap": true
17
+ },
18
+ "include": ["src/main/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }
@@ -0,0 +1,19 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import { resolve } from 'path';
4
+
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ base: './',
8
+ root: 'src/renderer',
9
+ build: {
10
+ outDir: '../../dist/renderer',
11
+ emptyOutDir: true,
12
+ rollupOptions: {
13
+ input: resolve(__dirname, 'src/renderer/index.html'),
14
+ },
15
+ },
16
+ server: {
17
+ port: 5173,
18
+ },
19
+ });
@@ -0,0 +1,18 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: 'node',
6
+ include: ['tests/**/*.test.ts'],
7
+ setupFiles: ['./tests/setup.ts'],
8
+ coverage: {
9
+ provider: 'v8',
10
+ include: ['src/main/ai-detection.ts'],
11
+ thresholds: {
12
+ lines: 80,
13
+ functions: 80,
14
+ branches: 75,
15
+ },
16
+ },
17
+ },
18
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": true,
13
+ "declarationMap": true,
14
+ "sourceMap": true,
15
+ "jsx": "react"
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }