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.
- package/LICENSE +21 -0
- package/README.md +181 -0
- package/brosh_brandmark.svg +3 -0
- package/brosh_logo.svg +27 -0
- package/cli_icon.svg +52 -0
- package/dist/client.d.ts +5 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +138 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +618 -0
- package/dist/index.js.map +1 -0
- package/dist/lib.d.ts +25 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +28 -0
- package/dist/lib.js.map +1 -0
- package/dist/mode-selector.d.ts +7 -0
- package/dist/mode-selector.d.ts.map +1 -0
- package/dist/mode-selector.js +138 -0
- package/dist/mode-selector.js.map +1 -0
- package/dist/prompts/index.d.ts +3 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +79 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/recording/index.d.ts +4 -0
- package/dist/recording/index.d.ts.map +1 -0
- package/dist/recording/index.js +3 -0
- package/dist/recording/index.js.map +1 -0
- package/dist/recording/manager.d.ts +62 -0
- package/dist/recording/manager.d.ts.map +1 -0
- package/dist/recording/manager.js +123 -0
- package/dist/recording/manager.js.map +1 -0
- package/dist/recording/recorder.d.ts +95 -0
- package/dist/recording/recorder.d.ts.map +1 -0
- package/dist/recording/recorder.js +330 -0
- package/dist/recording/recorder.js.map +1 -0
- package/dist/recording/types.d.ts +65 -0
- package/dist/recording/types.d.ts.map +1 -0
- package/dist/recording/types.js +2 -0
- package/dist/recording/types.js.map +1 -0
- package/dist/sandbox/ModeSelector.d.ts +2 -0
- package/dist/sandbox/ModeSelector.d.ts.map +1 -0
- package/dist/sandbox/ModeSelector.js +2 -0
- package/dist/sandbox/ModeSelector.js.map +1 -0
- package/dist/sandbox/config.d.ts +46 -0
- package/dist/sandbox/config.d.ts.map +1 -0
- package/dist/sandbox/config.js +144 -0
- package/dist/sandbox/config.js.map +1 -0
- package/dist/sandbox/controller.d.ts +72 -0
- package/dist/sandbox/controller.d.ts.map +1 -0
- package/dist/sandbox/controller.js +208 -0
- package/dist/sandbox/controller.js.map +1 -0
- package/dist/sandbox/index.d.ts +6 -0
- package/dist/sandbox/index.d.ts.map +1 -0
- package/dist/sandbox/index.js +4 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/sandbox/mode-prompt.d.ts +10 -0
- package/dist/sandbox/mode-prompt.d.ts.map +1 -0
- package/dist/sandbox/mode-prompt.js +130 -0
- package/dist/sandbox/mode-prompt.js.map +1 -0
- package/dist/sandbox/prompt.d.ts +10 -0
- package/dist/sandbox/prompt.d.ts.map +1 -0
- package/dist/sandbox/prompt.js +434 -0
- package/dist/sandbox/prompt.js.map +1 -0
- package/dist/server.d.ts +28 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +59 -0
- package/dist/server.js.map +1 -0
- package/dist/terminal/index.d.ts +5 -0
- package/dist/terminal/index.d.ts.map +1 -0
- package/dist/terminal/index.js +3 -0
- package/dist/terminal/index.js.map +1 -0
- package/dist/terminal/manager.d.ts +153 -0
- package/dist/terminal/manager.d.ts.map +1 -0
- package/dist/terminal/manager.js +276 -0
- package/dist/terminal/manager.js.map +1 -0
- package/dist/terminal/session.d.ts +137 -0
- package/dist/terminal/session.d.ts.map +1 -0
- package/dist/terminal/session.js +752 -0
- package/dist/terminal/session.js.map +1 -0
- package/dist/tools/definitions.d.ts +18 -0
- package/dist/tools/definitions.d.ts.map +1 -0
- package/dist/tools/definitions.js +114 -0
- package/dist/tools/definitions.js.map +1 -0
- package/dist/tools/getContent.d.ts +32 -0
- package/dist/tools/getContent.d.ts.map +1 -0
- package/dist/tools/getContent.js +38 -0
- package/dist/tools/getContent.js.map +1 -0
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +49 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/screenshot.d.ts +20 -0
- package/dist/tools/screenshot.d.ts.map +1 -0
- package/dist/tools/screenshot.js +28 -0
- package/dist/tools/screenshot.js.map +1 -0
- package/dist/tools/sendKey.d.ts +31 -0
- package/dist/tools/sendKey.d.ts.map +1 -0
- package/dist/tools/sendKey.js +38 -0
- package/dist/tools/sendKey.js.map +1 -0
- package/dist/tools/startRecording.d.ts +68 -0
- package/dist/tools/startRecording.d.ts.map +1 -0
- package/dist/tools/startRecording.js +111 -0
- package/dist/tools/startRecording.js.map +1 -0
- package/dist/tools/stopRecording.d.ts +31 -0
- package/dist/tools/stopRecording.d.ts.map +1 -0
- package/dist/tools/stopRecording.js +76 -0
- package/dist/tools/stopRecording.js.map +1 -0
- package/dist/tools/type.d.ts +31 -0
- package/dist/tools/type.d.ts.map +1 -0
- package/dist/tools/type.js +31 -0
- package/dist/tools/type.js.map +1 -0
- package/dist/transport/gui-protocol.d.ts +163 -0
- package/dist/transport/gui-protocol.d.ts.map +1 -0
- package/dist/transport/gui-protocol.js +68 -0
- package/dist/transport/gui-protocol.js.map +1 -0
- package/dist/transport/gui-stream.d.ts +139 -0
- package/dist/transport/gui-stream.d.ts.map +1 -0
- package/dist/transport/gui-stream.js +440 -0
- package/dist/transport/gui-stream.js.map +1 -0
- package/dist/transport/index.d.ts +6 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +6 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/socket.d.ts +46 -0
- package/dist/transport/socket.d.ts.map +1 -0
- package/dist/transport/socket.js +310 -0
- package/dist/transport/socket.js.map +1 -0
- package/dist/types/mcp-client-info.d.ts +226 -0
- package/dist/types/mcp-client-info.d.ts.map +1 -0
- package/dist/types/mcp-client-info.js +62 -0
- package/dist/types/mcp-client-info.js.map +1 -0
- package/dist/ui/index.d.ts +12 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +84 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/utils/env.d.ts +17 -0
- package/dist/utils/env.d.ts.map +1 -0
- package/dist/utils/env.js +35 -0
- package/dist/utils/env.js.map +1 -0
- package/dist/utils/keys.d.ts +16 -0
- package/dist/utils/keys.d.ts.map +1 -0
- package/dist/utils/keys.js +155 -0
- package/dist/utils/keys.js.map +1 -0
- package/dist/utils/platform.d.ts +16 -0
- package/dist/utils/platform.d.ts.map +1 -0
- package/dist/utils/platform.js +41 -0
- package/dist/utils/platform.js.map +1 -0
- package/dist/utils/session-logger.d.ts +31 -0
- package/dist/utils/session-logger.d.ts.map +1 -0
- package/dist/utils/session-logger.js +125 -0
- package/dist/utils/session-logger.js.map +1 -0
- package/dist/utils/stats.d.ts +46 -0
- package/dist/utils/stats.d.ts.map +1 -0
- package/dist/utils/stats.js +89 -0
- package/dist/utils/stats.js.map +1 -0
- package/dist/utils/version.d.ts +2 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +9 -0
- package/dist/utils/version.js.map +1 -0
- package/logo.png +0 -0
- package/package.json +61 -0
- package/packages/desktop-electron/THIRD-PARTY-NOTICES +56 -0
- package/packages/desktop-electron/build/afterPack.cjs +147 -0
- package/packages/desktop-electron/package-lock.json +10071 -0
- package/packages/desktop-electron/package.json +170 -0
- package/packages/desktop-electron/resources/icons/mac/icon.icns +0 -0
- package/packages/desktop-electron/resources/icons/png/1024x1024.png +0 -0
- package/packages/desktop-electron/resources/icons/png/128x128.png +0 -0
- package/packages/desktop-electron/resources/icons/png/16x16.png +0 -0
- package/packages/desktop-electron/resources/icons/png/24x24.png +0 -0
- package/packages/desktop-electron/resources/icons/png/256x256.png +0 -0
- package/packages/desktop-electron/resources/icons/png/32x32.png +0 -0
- package/packages/desktop-electron/resources/icons/png/48x48.png +0 -0
- package/packages/desktop-electron/resources/icons/png/512x512.png +0 -0
- package/packages/desktop-electron/resources/icons/png/64x64.png +0 -0
- package/packages/desktop-electron/resources/icons/win/icon.ico +0 -0
- package/packages/desktop-electron/scripts/download-models.js +97 -0
- package/packages/desktop-electron/scripts/prepare-sandbox-bins.js +186 -0
- package/packages/desktop-electron/tests/main/ai-detection/additionalFunctions.test.ts +224 -0
- package/packages/desktop-electron/tests/main/ai-detection/checkOverridePrefix.test.ts +162 -0
- package/packages/desktop-electron/tests/main/ai-detection/classifyInput.test.ts +132 -0
- package/packages/desktop-electron/tests/main/ai-detection/detectTypos.test.ts +342 -0
- package/packages/desktop-electron/tests/main/ai-detection/fixtures/commands.ts +134 -0
- package/packages/desktop-electron/tests/main/ai-detection/fixtures/natural-language.ts +133 -0
- package/packages/desktop-electron/tests/main/ai-detection/fixtures/typos.ts +123 -0
- package/packages/desktop-electron/tests/main/ai-detection/hasValidSubcommand.test.ts +218 -0
- package/packages/desktop-electron/tests/main/ai-detection/isCommandNotFound.test.ts +117 -0
- package/packages/desktop-electron/tests/main/error-triage/buildTriagePrompt.test.ts +133 -0
- package/packages/desktop-electron/tests/main/error-triage/parseTriageResponse.test.ts +123 -0
- package/packages/desktop-electron/tests/main/terminal-bridge/battery-optimization.test.ts +243 -0
- package/packages/desktop-electron/tests/main/terminal-bridge/command-fast-track.test.ts +292 -0
- package/packages/desktop-electron/tests/main/terminal-bridge/default-cwd.test.ts +70 -0
- package/packages/desktop-electron/tests/setup.ts +274 -0
- package/packages/desktop-electron/tsconfig.json +18 -0
- package/packages/desktop-electron/tsconfig.main.json +20 -0
- package/packages/desktop-electron/vite.config.ts +19 -0
- package/packages/desktop-electron/vitest.config.ts +18 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
detectTypos,
|
|
4
|
+
initializeDetection,
|
|
5
|
+
type TypoSuggestion,
|
|
6
|
+
} from '../../../src/main/ai-detection.js';
|
|
7
|
+
import {
|
|
8
|
+
COMMAND_TYPOS,
|
|
9
|
+
SUBCOMMAND_TYPOS,
|
|
10
|
+
NL_NOT_TYPOS,
|
|
11
|
+
NL_LOOKS_LIKE_TYPO,
|
|
12
|
+
NOT_TYPOS_TOO_FAR,
|
|
13
|
+
} from './fixtures/typos.js';
|
|
14
|
+
|
|
15
|
+
describe('detectTypos', () => {
|
|
16
|
+
beforeAll(async () => {
|
|
17
|
+
await initializeDetection();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('command typos (first word)', () => {
|
|
21
|
+
it('should detect common command typos', () => {
|
|
22
|
+
// Test specific typos that we know will work correctly
|
|
23
|
+
const reliableTypos = [
|
|
24
|
+
{ typo: 'gti', correct: 'git', fullInput: 'gti status', fullCorrected: 'git status' },
|
|
25
|
+
{ typo: 'dcoker', correct: 'docker', fullInput: 'dcoker ps', fullCorrected: 'docker ps' },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
for (const { typo, correct, fullInput, fullCorrected } of reliableTypos) {
|
|
29
|
+
const input = fullInput || typo;
|
|
30
|
+
const result = detectTypos(input);
|
|
31
|
+
|
|
32
|
+
if (result === null) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
expect(result.type).toBe('command');
|
|
37
|
+
expect(result.suggested).toBe(correct);
|
|
38
|
+
if (fullCorrected) {
|
|
39
|
+
expect(result.fullSuggestion).toBe(fullCorrected);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should detect "gti" as typo of "git"', () => {
|
|
45
|
+
const result = detectTypos('gti status');
|
|
46
|
+
expect(result).not.toBeNull();
|
|
47
|
+
expect(result!.type).toBe('command');
|
|
48
|
+
expect(result!.original).toBe('gti');
|
|
49
|
+
expect(result!.suggested).toBe('git');
|
|
50
|
+
expect(result!.fullSuggestion).toBe('git status');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should detect "nmp" as typo of "npm" (prefers transpositions)', () => {
|
|
54
|
+
// 'nmp' is distance 1 from 'cmp' but distance 2 from 'npm'
|
|
55
|
+
// However, 'nmp' is a transposition of 'npm' (same letters, different order)
|
|
56
|
+
// The algorithm now prefers transpositions, so it should suggest 'npm'
|
|
57
|
+
const result = detectTypos('nmp install');
|
|
58
|
+
expect(result).not.toBeNull();
|
|
59
|
+
expect(result!.type).toBe('command');
|
|
60
|
+
expect(result!.original).toBe('nmp');
|
|
61
|
+
expect(result!.suggested).toBe('npm');
|
|
62
|
+
expect(result!.fullSuggestion).toBe('npm install');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should detect "dcoker" as typo of "docker"', () => {
|
|
66
|
+
const result = detectTypos('dcoker ps');
|
|
67
|
+
expect(result).not.toBeNull();
|
|
68
|
+
expect(result!.type).toBe('command');
|
|
69
|
+
expect(result!.original).toBe('dcoker');
|
|
70
|
+
expect(result!.suggested).toBe('docker');
|
|
71
|
+
expect(result!.fullSuggestion).toBe('docker ps');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('subcommand typos (second word)', () => {
|
|
76
|
+
it('should detect common subcommand typos', () => {
|
|
77
|
+
// Test specific subcommand typos that we know will work
|
|
78
|
+
const reliableTypos = [
|
|
79
|
+
{ command: 'git', typo: 'comit', correct: 'commit', fullInput: 'git comit -m "msg"', fullCorrected: 'git commit -m "msg"' },
|
|
80
|
+
{ command: 'git', typo: 'stauts', correct: 'status', fullInput: 'git stauts', fullCorrected: 'git status' },
|
|
81
|
+
{ command: 'git', typo: 'psuh', correct: 'push', fullInput: 'git psuh', fullCorrected: 'git push' },
|
|
82
|
+
{ command: 'npm', typo: 'instal', correct: 'install', fullInput: 'npm instal react', fullCorrected: 'npm install react' },
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
for (const { command, typo, correct, fullInput, fullCorrected } of reliableTypos) {
|
|
86
|
+
const input = fullInput || `${command} ${typo}`;
|
|
87
|
+
const result = detectTypos(input);
|
|
88
|
+
|
|
89
|
+
if (result === null) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
expect(result.type).toBe('subcommand');
|
|
94
|
+
expect(result.suggested).toBe(correct);
|
|
95
|
+
if (fullCorrected) {
|
|
96
|
+
expect(result.fullSuggestion).toBe(fullCorrected);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should detect "git comit" as typo of "git commit"', () => {
|
|
102
|
+
const result = detectTypos('git comit -m "msg"');
|
|
103
|
+
expect(result).not.toBeNull();
|
|
104
|
+
expect(result!.type).toBe('subcommand');
|
|
105
|
+
expect(result!.original).toBe('comit');
|
|
106
|
+
expect(result!.suggested).toBe('commit');
|
|
107
|
+
expect(result!.fullSuggestion).toBe('git commit -m "msg"');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should detect "git stauts" as typo of "git status"', () => {
|
|
111
|
+
const result = detectTypos('git stauts');
|
|
112
|
+
expect(result).not.toBeNull();
|
|
113
|
+
expect(result!.type).toBe('subcommand');
|
|
114
|
+
expect(result!.original).toBe('stauts');
|
|
115
|
+
expect(result!.suggested).toBe('status');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should detect "npm instal" as typo of "npm install"', () => {
|
|
119
|
+
const result = detectTypos('npm instal react');
|
|
120
|
+
expect(result).not.toBeNull();
|
|
121
|
+
expect(result!.type).toBe('subcommand');
|
|
122
|
+
expect(result!.original).toBe('instal');
|
|
123
|
+
expect(result!.suggested).toBe('install');
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('should NOT detect typos for NL starter words', () => {
|
|
128
|
+
for (const word of NL_NOT_TYPOS.slice(0, 30)) { // Test a subset
|
|
129
|
+
it(`should return null for "${word}"`, () => {
|
|
130
|
+
const result = detectTypos(`${word} something`);
|
|
131
|
+
expect(result).toBeNull();
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
it('should return null for "how do I list files"', () => {
|
|
136
|
+
expect(detectTypos('how do I list files')).toBeNull();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should return null for "what time is it"', () => {
|
|
140
|
+
expect(detectTypos('what time is it')).toBeNull();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should return null for "yes please"', () => {
|
|
144
|
+
expect(detectTypos('yes please')).toBeNull();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should return null for "no thanks"', () => {
|
|
148
|
+
expect(detectTypos('no thanks')).toBeNull();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should return null for "ok let me try"', () => {
|
|
152
|
+
expect(detectTypos('ok let me try')).toBeNull();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should return null for "sure go ahead"', () => {
|
|
156
|
+
expect(detectTypos('sure go ahead')).toBeNull();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should return null for "thanks for helping"', () => {
|
|
160
|
+
expect(detectTypos('thanks for helping')).toBeNull();
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe('should handle punctuation in NL words', () => {
|
|
165
|
+
it('should strip punctuation when checking NL words', () => {
|
|
166
|
+
expect(detectTypos('yes, run it')).toBeNull();
|
|
167
|
+
expect(detectTypos('ok. sounds good')).toBeNull();
|
|
168
|
+
expect(detectTypos('thanks!')).toBeNull();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('should NOT detect typos for contractions', () => {
|
|
173
|
+
it('should return null for "i\'m testing"', () => {
|
|
174
|
+
expect(detectTypos("i'm testing")).toBeNull();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should return null for "don\'t do that"', () => {
|
|
178
|
+
expect(detectTypos("don't do that")).toBeNull();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should return null for "what\'s the weather"', () => {
|
|
182
|
+
expect(detectTypos("what's the weather")).toBeNull();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should return null for "it\'s working now"', () => {
|
|
186
|
+
expect(detectTypos("it's working now")).toBeNull();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('should return null for "can\'t find the file"', () => {
|
|
190
|
+
expect(detectTypos("can't find the file")).toBeNull();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should return null for possessives like "user\'s profile"', () => {
|
|
194
|
+
expect(detectTypos("user's profile")).toBeNull();
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe('should NOT detect typos for inputs too far from commands', () => {
|
|
199
|
+
for (const input of NOT_TYPOS_TOO_FAR) {
|
|
200
|
+
it(`should return null for "${input}"`, () => {
|
|
201
|
+
const result = detectTypos(input);
|
|
202
|
+
expect(result).toBeNull();
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe('should NOT detect typos for valid commands', () => {
|
|
208
|
+
it('should return null for "git status" (valid command)', () => {
|
|
209
|
+
expect(detectTypos('git status')).toBeNull();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should return null for "npm install" (valid command)', () => {
|
|
213
|
+
expect(detectTypos('npm install')).toBeNull();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('should return null for "ls -la" (valid command)', () => {
|
|
217
|
+
expect(detectTypos('ls -la')).toBeNull();
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe('transposition preference', () => {
|
|
222
|
+
it('should prefer transpositions over substitutions', () => {
|
|
223
|
+
// 'gti' → 'git' is a transposition (same letters: g, i, t)
|
|
224
|
+
// 'gti' → 'gdi' would be a substitution
|
|
225
|
+
const result = detectTypos('gti status');
|
|
226
|
+
expect(result).not.toBeNull();
|
|
227
|
+
expect(result!.suggested).toBe('git');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('should prefer same-first-letter matches', () => {
|
|
231
|
+
// When distances are equal, prefer matches starting with same letter
|
|
232
|
+
const result = detectTypos('gti status');
|
|
233
|
+
expect(result!.suggested).toBe('git'); // starts with 'g' like 'gti'
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('should handle "kubeclt" → "kubectl"', () => {
|
|
237
|
+
const result = detectTypos('kubeclt get pods');
|
|
238
|
+
expect(result).not.toBeNull();
|
|
239
|
+
expect(result!.suggested).toBe('kubectl');
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
describe('result structure', () => {
|
|
244
|
+
it('should return proper TypoSuggestion structure', () => {
|
|
245
|
+
const result = detectTypos('gti status');
|
|
246
|
+
expect(result).not.toBeNull();
|
|
247
|
+
expect(result).toHaveProperty('original');
|
|
248
|
+
expect(result).toHaveProperty('suggested');
|
|
249
|
+
expect(result).toHaveProperty('type');
|
|
250
|
+
expect(result).toHaveProperty('distance');
|
|
251
|
+
expect(result).toHaveProperty('fullSuggestion');
|
|
252
|
+
expect(result!.distance).toBeGreaterThan(0);
|
|
253
|
+
expect(result!.distance).toBeLessThanOrEqual(2);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe('edge cases', () => {
|
|
258
|
+
it('should return null for empty input', () => {
|
|
259
|
+
expect(detectTypos('')).toBeNull();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should return null for whitespace-only input', () => {
|
|
263
|
+
expect(detectTypos(' ')).toBeNull();
|
|
264
|
+
expect(detectTypos(' ')).toBeNull();
|
|
265
|
+
expect(detectTypos('\t')).toBeNull();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should reject suggestions with very different lengths', () => {
|
|
269
|
+
// "how" shouldn't suggest "w" even though edit distance might be small
|
|
270
|
+
const result = detectTypos('how are you');
|
|
271
|
+
expect(result).toBeNull();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('should reject shorter suggestions with different first letter', () => {
|
|
275
|
+
// Test with a word not in the cache that could match a shorter command
|
|
276
|
+
// 'qat' could match 'cat' (distance 1, same length) - this should work
|
|
277
|
+
// But 'qza' matching 'za' would be rejected (shorter + different first letter)
|
|
278
|
+
// Since we can't easily test with commands not in cache, we verify the logic
|
|
279
|
+
// by checking that known commands with flags don't trigger false positives
|
|
280
|
+
|
|
281
|
+
// 'eza' IS in the mock cache, so this should return null (not a typo)
|
|
282
|
+
expect(detectTypos('eza -la')).toBeNull();
|
|
283
|
+
|
|
284
|
+
// If 'eza' wasn't in cache, it would potentially match 'la' (distance 2)
|
|
285
|
+
// but that would be rejected because 'la' is shorter and starts with 'l' not 'e'
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
describe('should not suggest for commands with flags that look valid', () => {
|
|
290
|
+
it('should return null for known commands', () => {
|
|
291
|
+
// If eza is in the command cache, it shouldn't suggest anything
|
|
292
|
+
expect(detectTypos('eza -la')).toBeNull();
|
|
293
|
+
expect(detectTypos('bat --plain file.txt')).toBeNull();
|
|
294
|
+
expect(detectTypos('rg pattern')).toBeNull();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('should reject shorter suggestions with different first letter (eza→la case)', () => {
|
|
298
|
+
// 'ezb' actually matches 'eza' (distance 1, same first letter) which is fine
|
|
299
|
+
// The key test is that it does NOT suggest 'la' (shorter, different first letter)
|
|
300
|
+
const result = detectTypos('ezb -la');
|
|
301
|
+
if (result) {
|
|
302
|
+
// If there's a suggestion, it should be 'eza' not 'la'
|
|
303
|
+
expect(result.suggested).toBe('eza');
|
|
304
|
+
expect(result.suggested).not.toBe('la');
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('should allow same-length suggestions even with different first letter', () => {
|
|
309
|
+
// 'qza' → 'eza' is distance 1 (same length, different first letter)
|
|
310
|
+
// This is allowed because the suggestion isn't shorter
|
|
311
|
+
// The fix only blocks SHORTER suggestions with different first letters
|
|
312
|
+
const result = detectTypos('qza -la');
|
|
313
|
+
expect(result).not.toBeNull();
|
|
314
|
+
expect(result!.suggested).toBe('eza');
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('should reject shorter suggestions with different first letter', () => {
|
|
318
|
+
// 'abc' → 'bc' would be distance 1, but 'bc' is shorter and has different first letter
|
|
319
|
+
// This should be rejected by the fix
|
|
320
|
+
// Note: 'bc' is a calculator command that's in the mock
|
|
321
|
+
const result = detectTypos('abc 2+2');
|
|
322
|
+
// Should NOT suggest 'bc' because:
|
|
323
|
+
// 1. 'bc' (2 chars) is shorter than 'abc' (3 chars)
|
|
324
|
+
// 2. 'b' != 'a' (different first letters)
|
|
325
|
+
// May suggest something else if there's a closer match, or null
|
|
326
|
+
if (result) {
|
|
327
|
+
expect(result.suggested).not.toBe('bc');
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('should reject "fxa" suggesting "fx" (same first letter but check still applies)', () => {
|
|
332
|
+
// 'fxa' could match 'fx' (distance 1) - same first letter, shorter
|
|
333
|
+
// This SHOULD be allowed because first letters match
|
|
334
|
+
// But 'fxa' isn't in cache, and 'fx' is...
|
|
335
|
+
// Actually 'fx' followed by '-la' would look like a valid command
|
|
336
|
+
// Let's check if this returns a suggestion or not
|
|
337
|
+
const result = detectTypos('fxa -la');
|
|
338
|
+
// If it suggests 'fx', that's fine because first letters match
|
|
339
|
+
// The key is that 'eza' → 'la' is blocked
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test fixtures for valid shell commands
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const VALID_COMMANDS = {
|
|
6
|
+
// Shell builtins
|
|
7
|
+
builtins: [
|
|
8
|
+
'cd /tmp',
|
|
9
|
+
'cd ~',
|
|
10
|
+
'cd ..',
|
|
11
|
+
'echo "hello world"',
|
|
12
|
+
'echo $HOME',
|
|
13
|
+
'pwd',
|
|
14
|
+
'export FOO=bar',
|
|
15
|
+
'alias ll="ls -la"',
|
|
16
|
+
'source ~/.bashrc',
|
|
17
|
+
],
|
|
18
|
+
|
|
19
|
+
// Single word commands
|
|
20
|
+
singleWord: [
|
|
21
|
+
'ls',
|
|
22
|
+
'pwd',
|
|
23
|
+
'date',
|
|
24
|
+
'whoami',
|
|
25
|
+
'clear',
|
|
26
|
+
],
|
|
27
|
+
|
|
28
|
+
// Git commands
|
|
29
|
+
git: [
|
|
30
|
+
'git status',
|
|
31
|
+
'git add .',
|
|
32
|
+
'git commit -m "fix bug"',
|
|
33
|
+
'git push',
|
|
34
|
+
'git pull',
|
|
35
|
+
'git log --oneline',
|
|
36
|
+
'git branch -a',
|
|
37
|
+
'git checkout -b feature',
|
|
38
|
+
'git merge main',
|
|
39
|
+
'git stash pop',
|
|
40
|
+
'git diff HEAD~1',
|
|
41
|
+
'git clone https://github.com/foo/bar.git',
|
|
42
|
+
],
|
|
43
|
+
|
|
44
|
+
// NPM commands
|
|
45
|
+
npm: [
|
|
46
|
+
'npm install',
|
|
47
|
+
'npm install react',
|
|
48
|
+
'npm install -D typescript',
|
|
49
|
+
'npm run build',
|
|
50
|
+
'npm run test',
|
|
51
|
+
'npm start',
|
|
52
|
+
'npm publish',
|
|
53
|
+
'npm version patch',
|
|
54
|
+
'npm outdated',
|
|
55
|
+
],
|
|
56
|
+
|
|
57
|
+
// Docker commands
|
|
58
|
+
docker: [
|
|
59
|
+
'docker ps',
|
|
60
|
+
'docker images',
|
|
61
|
+
'docker run -it ubuntu',
|
|
62
|
+
'docker build -t myapp .',
|
|
63
|
+
'docker-compose up -d',
|
|
64
|
+
'docker exec -it container bash',
|
|
65
|
+
'docker logs -f container',
|
|
66
|
+
'docker stop container',
|
|
67
|
+
],
|
|
68
|
+
|
|
69
|
+
// Commands with flags
|
|
70
|
+
withFlags: [
|
|
71
|
+
'ls -la',
|
|
72
|
+
'ls -lah',
|
|
73
|
+
'grep -r "pattern" .',
|
|
74
|
+
'find . -name "*.js"',
|
|
75
|
+
'rm -rf node_modules',
|
|
76
|
+
'cp -r src/ dest/',
|
|
77
|
+
'chmod +x script.sh',
|
|
78
|
+
'curl -s https://example.com',
|
|
79
|
+
],
|
|
80
|
+
|
|
81
|
+
// Commands with paths
|
|
82
|
+
withPaths: [
|
|
83
|
+
'cat ./file.txt',
|
|
84
|
+
'cat /etc/hosts',
|
|
85
|
+
'cat ~/Documents/notes.txt',
|
|
86
|
+
'./script.sh',
|
|
87
|
+
'/usr/bin/python3',
|
|
88
|
+
'~/bin/custom-tool',
|
|
89
|
+
'node src/index.js',
|
|
90
|
+
],
|
|
91
|
+
|
|
92
|
+
// Pipelines
|
|
93
|
+
pipelines: [
|
|
94
|
+
'ls | grep foo',
|
|
95
|
+
'cat file | head -10',
|
|
96
|
+
'ps aux | grep node',
|
|
97
|
+
'history | tail -20',
|
|
98
|
+
'find . -name "*.ts" | xargs grep "TODO"',
|
|
99
|
+
'curl -s url | jq ".data"',
|
|
100
|
+
],
|
|
101
|
+
|
|
102
|
+
// Redirects
|
|
103
|
+
redirects: [
|
|
104
|
+
'echo hi > file.txt',
|
|
105
|
+
'cat file >> output.txt',
|
|
106
|
+
'cmd 2>&1',
|
|
107
|
+
'cmd > /dev/null',
|
|
108
|
+
'cmd < input.txt',
|
|
109
|
+
],
|
|
110
|
+
|
|
111
|
+
// Variables and substitution
|
|
112
|
+
variables: [
|
|
113
|
+
'echo $HOME',
|
|
114
|
+
'echo $PATH',
|
|
115
|
+
'VAR=val npm start',
|
|
116
|
+
'FOO=bar BAZ=qux command',
|
|
117
|
+
'echo ${USER}',
|
|
118
|
+
'echo $(date)',
|
|
119
|
+
'echo `hostname`',
|
|
120
|
+
],
|
|
121
|
+
|
|
122
|
+
// Complex commands
|
|
123
|
+
complex: [
|
|
124
|
+
'for i in *.txt; do echo $i; done',
|
|
125
|
+
'if [ -f file ]; then cat file; fi',
|
|
126
|
+
'npm install && npm run build',
|
|
127
|
+
'git add . && git commit -m "msg"',
|
|
128
|
+
'command1; command2; command3',
|
|
129
|
+
'test -d dir && cd dir',
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Flatten all commands for easier iteration
|
|
134
|
+
export const ALL_VALID_COMMANDS = Object.values(VALID_COMMANDS).flat();
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test fixtures for natural language inputs
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const NATURAL_LANGUAGE = {
|
|
6
|
+
// Questions starting with question words
|
|
7
|
+
questions: [
|
|
8
|
+
'how do I list files?',
|
|
9
|
+
'what is the current directory?',
|
|
10
|
+
'why is my build failing?',
|
|
11
|
+
'where are the config files?',
|
|
12
|
+
'when was this file modified?',
|
|
13
|
+
'who has access to this repo?',
|
|
14
|
+
'which version of node?',
|
|
15
|
+
'can you help me?',
|
|
16
|
+
'could you explain this?',
|
|
17
|
+
'would you run the tests?',
|
|
18
|
+
'should I commit now?',
|
|
19
|
+
],
|
|
20
|
+
|
|
21
|
+
// Requests starting with polite words
|
|
22
|
+
requests: [
|
|
23
|
+
'please show me the logs',
|
|
24
|
+
'help me find the bug',
|
|
25
|
+
'explain how this works',
|
|
26
|
+
'describe the architecture',
|
|
27
|
+
'tell me about the API',
|
|
28
|
+
'show me how to deploy',
|
|
29
|
+
'list all the dependencies',
|
|
30
|
+
'find the authentication code',
|
|
31
|
+
],
|
|
32
|
+
|
|
33
|
+
// Command-like words but clearly NL
|
|
34
|
+
commandLikeButNL: [
|
|
35
|
+
'git how do I revert a commit?',
|
|
36
|
+
'git is giving me errors',
|
|
37
|
+
'ls beginning to look like christmas',
|
|
38
|
+
'cat is sleeping on the keyboard',
|
|
39
|
+
'npm is not working properly',
|
|
40
|
+
'docker what are the best practices?',
|
|
41
|
+
'find the meaning of life',
|
|
42
|
+
'grep why is it not finding anything?',
|
|
43
|
+
'make it work please',
|
|
44
|
+
'curl up in bed',
|
|
45
|
+
],
|
|
46
|
+
|
|
47
|
+
// Conversational responses
|
|
48
|
+
conversational: [
|
|
49
|
+
'yes',
|
|
50
|
+
'no',
|
|
51
|
+
'ok',
|
|
52
|
+
'okay',
|
|
53
|
+
'sure',
|
|
54
|
+
'thanks',
|
|
55
|
+
'thank you',
|
|
56
|
+
'sorry',
|
|
57
|
+
'hi',
|
|
58
|
+
'hello',
|
|
59
|
+
'hey',
|
|
60
|
+
'great',
|
|
61
|
+
'good',
|
|
62
|
+
'nice',
|
|
63
|
+
'cool',
|
|
64
|
+
'awesome',
|
|
65
|
+
'perfect',
|
|
66
|
+
'fine',
|
|
67
|
+
'right',
|
|
68
|
+
'yeah',
|
|
69
|
+
'yep',
|
|
70
|
+
'nope',
|
|
71
|
+
'maybe',
|
|
72
|
+
'probably',
|
|
73
|
+
'definitely',
|
|
74
|
+
'absolutely',
|
|
75
|
+
],
|
|
76
|
+
|
|
77
|
+
// Sentences that start with "I"
|
|
78
|
+
firstPerson: [
|
|
79
|
+
'I want to deploy this',
|
|
80
|
+
'I need help with testing',
|
|
81
|
+
'I am trying to understand this',
|
|
82
|
+
'I have a question about the API',
|
|
83
|
+
'I think there is a bug here',
|
|
84
|
+
],
|
|
85
|
+
|
|
86
|
+
// Multi-word NL without command features
|
|
87
|
+
multiWord: [
|
|
88
|
+
'the build is broken',
|
|
89
|
+
'this code is confusing',
|
|
90
|
+
'my tests are failing',
|
|
91
|
+
'a simple example would help',
|
|
92
|
+
'an error occurred during startup',
|
|
93
|
+
'not sure what to do next',
|
|
94
|
+
],
|
|
95
|
+
|
|
96
|
+
// Ends with question mark
|
|
97
|
+
endsWithQuestion: [
|
|
98
|
+
'is this correct?',
|
|
99
|
+
'does this look right?',
|
|
100
|
+
'what?',
|
|
101
|
+
'why?',
|
|
102
|
+
'how?',
|
|
103
|
+
'can you?',
|
|
104
|
+
],
|
|
105
|
+
|
|
106
|
+
// Ends with period (more NL-like)
|
|
107
|
+
endsWithPeriod: [
|
|
108
|
+
'I need help.',
|
|
109
|
+
'Something is wrong.',
|
|
110
|
+
'This is not working.',
|
|
111
|
+
'Please explain.',
|
|
112
|
+
],
|
|
113
|
+
|
|
114
|
+
// Contractions - should NOT be detected as commands
|
|
115
|
+
contractions: [
|
|
116
|
+
"i'm testing this",
|
|
117
|
+
"don't do that",
|
|
118
|
+
"what's the weather",
|
|
119
|
+
"it's working now",
|
|
120
|
+
"I've been trying",
|
|
121
|
+
"they're all here",
|
|
122
|
+
"we'll see about that",
|
|
123
|
+
"can't find the file",
|
|
124
|
+
"won't work properly",
|
|
125
|
+
"isn't this correct",
|
|
126
|
+
"aren't you coming",
|
|
127
|
+
"user's profile",
|
|
128
|
+
"the system's config",
|
|
129
|
+
],
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// Flatten all NL inputs for easier iteration
|
|
133
|
+
export const ALL_NATURAL_LANGUAGE = Object.values(NATURAL_LANGUAGE).flat();
|