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,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test fixtures for typo detection
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface CommandTypo {
|
|
6
|
+
typo: string;
|
|
7
|
+
correct: string;
|
|
8
|
+
fullInput?: string;
|
|
9
|
+
fullCorrected?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface SubcommandTypo {
|
|
13
|
+
command: string;
|
|
14
|
+
typo: string;
|
|
15
|
+
correct: string;
|
|
16
|
+
fullInput?: string;
|
|
17
|
+
fullCorrected?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Command typos (first word)
|
|
21
|
+
export const COMMAND_TYPOS: CommandTypo[] = [
|
|
22
|
+
// Transposition typos
|
|
23
|
+
{ typo: 'gti', correct: 'git', fullInput: 'gti status', fullCorrected: 'git status' },
|
|
24
|
+
{ typo: 'nmp', correct: 'npm', fullInput: 'nmp install', fullCorrected: 'npm install' },
|
|
25
|
+
{ typo: 'dcoker', correct: 'docker', fullInput: 'dcoker ps', fullCorrected: 'docker ps' },
|
|
26
|
+
{ typo: 'kubeclt', correct: 'kubectl', fullInput: 'kubeclt get pods', fullCorrected: 'kubectl get pods' },
|
|
27
|
+
|
|
28
|
+
// Missing character
|
|
29
|
+
{ typo: 'gi', correct: 'git', fullInput: 'gi status', fullCorrected: 'git status' },
|
|
30
|
+
{ typo: 'np', correct: 'npm', fullInput: 'np install', fullCorrected: 'npm install' },
|
|
31
|
+
|
|
32
|
+
// Extra character
|
|
33
|
+
{ typo: 'gitt', correct: 'git', fullInput: 'gitt status', fullCorrected: 'git status' },
|
|
34
|
+
{ typo: 'nppm', correct: 'npm', fullInput: 'nppm install', fullCorrected: 'npm install' },
|
|
35
|
+
|
|
36
|
+
// Substitution
|
|
37
|
+
{ typo: 'got', correct: 'git', fullInput: 'got status', fullCorrected: 'git status' },
|
|
38
|
+
{ typo: 'npa', correct: 'npm', fullInput: 'npa install', fullCorrected: 'npm install' },
|
|
39
|
+
|
|
40
|
+
// Common misspellings
|
|
41
|
+
{ typo: 'yarm', correct: 'yarn', fullInput: 'yarm add react', fullCorrected: 'yarn add react' },
|
|
42
|
+
{ typo: 'cargi', correct: 'cargo', fullInput: 'cargi build', fullCorrected: 'cargo build' },
|
|
43
|
+
{ typo: 'bew', correct: 'brew', fullInput: 'bew install node', fullCorrected: 'brew install node' },
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
// Subcommand typos (second word for commands with subcommands)
|
|
47
|
+
export const SUBCOMMAND_TYPOS: SubcommandTypo[] = [
|
|
48
|
+
// Git subcommands
|
|
49
|
+
{ command: 'git', typo: 'comit', correct: 'commit', fullInput: 'git comit -m "msg"', fullCorrected: 'git commit -m "msg"' },
|
|
50
|
+
{ command: 'git', typo: 'stauts', correct: 'status', fullInput: 'git stauts', fullCorrected: 'git status' },
|
|
51
|
+
{ command: 'git', typo: 'psuh', correct: 'push', fullInput: 'git psuh', fullCorrected: 'git push' },
|
|
52
|
+
{ command: 'git', typo: 'pul', correct: 'pull', fullInput: 'git pul', fullCorrected: 'git pull' },
|
|
53
|
+
{ command: 'git', typo: 'brnach', correct: 'branch', fullInput: 'git brnach', fullCorrected: 'git branch' },
|
|
54
|
+
{ command: 'git', typo: 'checkou', correct: 'checkout', fullInput: 'git checkou main', fullCorrected: 'git checkout main' },
|
|
55
|
+
{ command: 'git', typo: 'merg', correct: 'merge', fullInput: 'git merg main', fullCorrected: 'git merge main' },
|
|
56
|
+
|
|
57
|
+
// NPM subcommands
|
|
58
|
+
{ command: 'npm', typo: 'instal', correct: 'install', fullInput: 'npm instal react', fullCorrected: 'npm install react' },
|
|
59
|
+
{ command: 'npm', typo: 'uninsatll', correct: 'uninstall', fullInput: 'npm uninsatll lodash', fullCorrected: 'npm uninstall lodash' },
|
|
60
|
+
{ command: 'npm', typo: 'publsih', correct: 'publish', fullInput: 'npm publsih', fullCorrected: 'npm publish' },
|
|
61
|
+
|
|
62
|
+
// Docker subcommands
|
|
63
|
+
{ command: 'docker', typo: 'buld', correct: 'build', fullInput: 'docker buld .', fullCorrected: 'docker build .' },
|
|
64
|
+
{ command: 'docker', typo: 'imags', correct: 'images', fullInput: 'docker imags', fullCorrected: 'docker images' },
|
|
65
|
+
{ command: 'docker', typo: 'exce', correct: 'exec', fullInput: 'docker exce -it container bash', fullCorrected: 'docker exec -it container bash' },
|
|
66
|
+
|
|
67
|
+
// Yarn subcommands
|
|
68
|
+
{ command: 'yarn', typo: 'ad', correct: 'add', fullInput: 'yarn ad react', fullCorrected: 'yarn add react' },
|
|
69
|
+
{ command: 'yarn', typo: 'remov', correct: 'remove', fullInput: 'yarn remov lodash', fullCorrected: 'yarn remove lodash' },
|
|
70
|
+
|
|
71
|
+
// Cargo subcommands
|
|
72
|
+
{ command: 'cargo', typo: 'biuld', correct: 'build', fullInput: 'cargo biuld', fullCorrected: 'cargo build' },
|
|
73
|
+
{ command: 'cargo', typo: 'tets', correct: 'test', fullInput: 'cargo tets', fullCorrected: 'cargo test' },
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
// Words that should NOT be treated as typos (NL starter words)
|
|
77
|
+
export const NL_NOT_TYPOS = [
|
|
78
|
+
// Question words
|
|
79
|
+
'how', 'what', 'why', 'where', 'when', 'who', 'which', 'whose',
|
|
80
|
+
// Modal verbs
|
|
81
|
+
'can', 'could', 'would', 'should', 'will', 'shall', 'may', 'might', 'must',
|
|
82
|
+
// Be verbs
|
|
83
|
+
'is', 'are', 'was', 'were', 'am', 'be', 'been', 'being',
|
|
84
|
+
// Other verbs
|
|
85
|
+
'do', 'does', 'did', 'have', 'has', 'had',
|
|
86
|
+
// Pronouns
|
|
87
|
+
'i', 'you', 'he', 'she', 'it', 'we', 'they',
|
|
88
|
+
// Possessives
|
|
89
|
+
'my', 'your', 'his', 'her', 'its', 'our', 'their',
|
|
90
|
+
// Demonstratives
|
|
91
|
+
'this', 'that', 'these', 'those',
|
|
92
|
+
// Request words
|
|
93
|
+
'please', 'help', 'show', 'tell', 'explain', 'describe', 'list', 'find', 'search',
|
|
94
|
+
// Articles
|
|
95
|
+
'the', 'a', 'an',
|
|
96
|
+
// Conversational
|
|
97
|
+
'yes', 'no', 'ok', 'okay', 'sure', 'thanks', 'thank', 'sorry', 'hi', 'hello',
|
|
98
|
+
'hey', 'great', 'good', 'nice', 'cool', 'awesome', 'perfect', 'fine', 'right',
|
|
99
|
+
'yeah', 'yep', 'nope', 'maybe', 'probably', 'definitely', 'absolutely',
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
// Inputs that look like typos but are actually NL
|
|
103
|
+
export const NL_LOOKS_LIKE_TYPO = [
|
|
104
|
+
'how far is the moon',
|
|
105
|
+
'what time is it',
|
|
106
|
+
'why is the sky blue',
|
|
107
|
+
'can you help me',
|
|
108
|
+
'i need assistance',
|
|
109
|
+
'yes please',
|
|
110
|
+
'no thanks',
|
|
111
|
+
'ok let me try',
|
|
112
|
+
'sure go ahead',
|
|
113
|
+
'thanks for helping',
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
// Inputs that are clearly too far from any command to be typos
|
|
117
|
+
export const NOT_TYPOS_TOO_FAR = [
|
|
118
|
+
'foobar something',
|
|
119
|
+
'asdfgh',
|
|
120
|
+
'qwerty',
|
|
121
|
+
'xyz123',
|
|
122
|
+
'supercalifragilistic',
|
|
123
|
+
];
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
hasValidSubcommand,
|
|
4
|
+
commandHasSubcommands,
|
|
5
|
+
getSubcommands,
|
|
6
|
+
} from '../../../src/main/ai-detection.js';
|
|
7
|
+
|
|
8
|
+
describe('hasValidSubcommand', () => {
|
|
9
|
+
describe('git subcommands', () => {
|
|
10
|
+
const validGitSubcommands = [
|
|
11
|
+
'add', 'bisect', 'branch', 'checkout', 'cherry-pick', 'clone', 'commit',
|
|
12
|
+
'diff', 'fetch', 'grep', 'init', 'log', 'merge', 'mv', 'pull', 'push',
|
|
13
|
+
'rebase', 'remote', 'reset', 'restore', 'revert', 'rm', 'show', 'stash',
|
|
14
|
+
'status', 'switch', 'tag', 'worktree',
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
for (const sub of validGitSubcommands) {
|
|
18
|
+
it(`should return true for "git ${sub}"`, () => {
|
|
19
|
+
expect(hasValidSubcommand('git', sub)).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
it('should be case insensitive', () => {
|
|
24
|
+
expect(hasValidSubcommand('git', 'STATUS')).toBe(true);
|
|
25
|
+
expect(hasValidSubcommand('GIT', 'status')).toBe(true);
|
|
26
|
+
expect(hasValidSubcommand('Git', 'Commit')).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should return false for invalid git subcommands', () => {
|
|
30
|
+
expect(hasValidSubcommand('git', 'foo')).toBe(false);
|
|
31
|
+
expect(hasValidSubcommand('git', 'how')).toBe(false);
|
|
32
|
+
expect(hasValidSubcommand('git', 'please')).toBe(false);
|
|
33
|
+
expect(hasValidSubcommand('git', 'help')).toBe(false); // Not in the registry
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('npm subcommands', () => {
|
|
38
|
+
const validNpmSubcommands = [
|
|
39
|
+
'install', 'uninstall', 'update', 'publish', 'run', 'test', 'start',
|
|
40
|
+
'init', 'search', 'view', 'audit', 'cache', 'config',
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
for (const sub of validNpmSubcommands) {
|
|
44
|
+
it(`should return true for "npm ${sub}"`, () => {
|
|
45
|
+
expect(hasValidSubcommand('npm', sub)).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
it('should return false for invalid npm subcommands', () => {
|
|
50
|
+
expect(hasValidSubcommand('npm', 'foo')).toBe(false);
|
|
51
|
+
expect(hasValidSubcommand('npm', 'please')).toBe(false);
|
|
52
|
+
expect(hasValidSubcommand('npm', 'is')).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('docker subcommands', () => {
|
|
57
|
+
const validDockerSubcommands = [
|
|
58
|
+
'run', 'build', 'push', 'pull', 'images', 'ps', 'exec', 'logs',
|
|
59
|
+
'stop', 'start', 'restart', 'rm', 'rmi', 'network', 'volume',
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
for (const sub of validDockerSubcommands) {
|
|
63
|
+
it(`should return true for "docker ${sub}"`, () => {
|
|
64
|
+
expect(hasValidSubcommand('docker', sub)).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
it('should return false for invalid docker subcommands', () => {
|
|
69
|
+
expect(hasValidSubcommand('docker', 'foo')).toBe(false);
|
|
70
|
+
expect(hasValidSubcommand('docker', 'what')).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('kubectl subcommands', () => {
|
|
75
|
+
const validKubectlSubcommands = [
|
|
76
|
+
'get', 'describe', 'create', 'apply', 'delete', 'logs', 'exec',
|
|
77
|
+
'port-forward', 'config', 'cluster-info',
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
for (const sub of validKubectlSubcommands) {
|
|
81
|
+
it(`should return true for "kubectl ${sub}"`, () => {
|
|
82
|
+
expect(hasValidSubcommand('kubectl', sub)).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('brew subcommands', () => {
|
|
88
|
+
const validBrewSubcommands = [
|
|
89
|
+
'install', 'uninstall', 'update', 'upgrade', 'search', 'info',
|
|
90
|
+
'list', 'cleanup', 'doctor', 'services',
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
for (const sub of validBrewSubcommands) {
|
|
94
|
+
it(`should return true for "brew ${sub}"`, () => {
|
|
95
|
+
expect(hasValidSubcommand('brew', sub)).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('yarn subcommands', () => {
|
|
101
|
+
// Only include subcommands that are actually in the registry
|
|
102
|
+
const validYarnSubcommands = [
|
|
103
|
+
'add', 'remove', 'install', 'upgrade', 'init', 'run',
|
|
104
|
+
'cache', 'config', // 'test' and 'build' are npm scripts, not yarn subcommands
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
for (const sub of validYarnSubcommands) {
|
|
108
|
+
it(`should return true for "yarn ${sub}"`, () => {
|
|
109
|
+
expect(hasValidSubcommand('yarn', sub)).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
it('should return false for npm script aliases not in registry', () => {
|
|
114
|
+
// 'test' and 'build' are common npm scripts but not yarn subcommands
|
|
115
|
+
expect(hasValidSubcommand('yarn', 'test')).toBe(false);
|
|
116
|
+
expect(hasValidSubcommand('yarn', 'build')).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('cargo subcommands', () => {
|
|
121
|
+
const validCargoSubcommands = [
|
|
122
|
+
'build', 'run', 'test', 'check', 'clean', 'doc', 'new', 'init',
|
|
123
|
+
'publish', 'install', 'uninstall',
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
for (const sub of validCargoSubcommands) {
|
|
127
|
+
it(`should return true for "cargo ${sub}"`, () => {
|
|
128
|
+
expect(hasValidSubcommand('cargo', sub)).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('gh subcommands', () => {
|
|
134
|
+
const validGhSubcommands = [
|
|
135
|
+
'repo', 'issue', 'pr', 'release', 'workflow', 'run', 'gist',
|
|
136
|
+
'auth', 'config', 'api',
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
for (const sub of validGhSubcommands) {
|
|
140
|
+
it(`should return true for "gh ${sub}"`, () => {
|
|
141
|
+
expect(hasValidSubcommand('gh', sub)).toBe(true);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe('pnpm subcommands', () => {
|
|
147
|
+
// Only include subcommands that are actually in the registry
|
|
148
|
+
const validPnpmSubcommands = [
|
|
149
|
+
'add', 'install', 'remove', 'update', 'run', 'test',
|
|
150
|
+
'exec', 'dlx', 'store', // 'build' is not in registry
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
for (const sub of validPnpmSubcommands) {
|
|
154
|
+
it(`should return true for "pnpm ${sub}"`, () => {
|
|
155
|
+
expect(hasValidSubcommand('pnpm', sub)).toBe(true);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
it('should return false for npm script aliases not in registry', () => {
|
|
160
|
+
expect(hasValidSubcommand('pnpm', 'build')).toBe(false);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe('unregistered commands', () => {
|
|
165
|
+
it('should return false for commands not in registry', () => {
|
|
166
|
+
expect(hasValidSubcommand('ls', 'anything')).toBe(false);
|
|
167
|
+
expect(hasValidSubcommand('cat', 'file')).toBe(false);
|
|
168
|
+
expect(hasValidSubcommand('grep', 'pattern')).toBe(false);
|
|
169
|
+
expect(hasValidSubcommand('unknown', 'command')).toBe(false);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('commandHasSubcommands', () => {
|
|
175
|
+
it('should return true for registered commands', () => {
|
|
176
|
+
expect(commandHasSubcommands('git')).toBe(true);
|
|
177
|
+
expect(commandHasSubcommands('npm')).toBe(true);
|
|
178
|
+
expect(commandHasSubcommands('docker')).toBe(true);
|
|
179
|
+
expect(commandHasSubcommands('kubectl')).toBe(true);
|
|
180
|
+
expect(commandHasSubcommands('brew')).toBe(true);
|
|
181
|
+
expect(commandHasSubcommands('yarn')).toBe(true);
|
|
182
|
+
expect(commandHasSubcommands('cargo')).toBe(true);
|
|
183
|
+
expect(commandHasSubcommands('gh')).toBe(true);
|
|
184
|
+
expect(commandHasSubcommands('pnpm')).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should be case insensitive', () => {
|
|
188
|
+
expect(commandHasSubcommands('GIT')).toBe(true);
|
|
189
|
+
expect(commandHasSubcommands('Git')).toBe(true);
|
|
190
|
+
expect(commandHasSubcommands('NPM')).toBe(true);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should return false for unregistered commands', () => {
|
|
194
|
+
expect(commandHasSubcommands('ls')).toBe(false);
|
|
195
|
+
expect(commandHasSubcommands('cat')).toBe(false);
|
|
196
|
+
expect(commandHasSubcommands('grep')).toBe(false);
|
|
197
|
+
expect(commandHasSubcommands('unknown')).toBe(false);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe('getSubcommands', () => {
|
|
202
|
+
it('should return Set for registered commands', () => {
|
|
203
|
+
const gitSubs = getSubcommands('git');
|
|
204
|
+
expect(gitSubs).toBeInstanceOf(Set);
|
|
205
|
+
expect(gitSubs?.has('status')).toBe(true);
|
|
206
|
+
expect(gitSubs?.has('commit')).toBe(true);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should be case insensitive', () => {
|
|
210
|
+
expect(getSubcommands('GIT')).toBeInstanceOf(Set);
|
|
211
|
+
expect(getSubcommands('Git')).toBeInstanceOf(Set);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should return undefined for unregistered commands', () => {
|
|
215
|
+
expect(getSubcommands('ls')).toBeUndefined();
|
|
216
|
+
expect(getSubcommands('unknown')).toBeUndefined();
|
|
217
|
+
});
|
|
218
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { isCommandNotFound } from '../../../src/main/ai-detection.js';
|
|
3
|
+
|
|
4
|
+
describe('isCommandNotFound', () => {
|
|
5
|
+
describe('should match "command not found" patterns', () => {
|
|
6
|
+
it('should match bash style: "bash: foo: command not found"', () => {
|
|
7
|
+
expect(isCommandNotFound('bash: foo: command not found')).toBe(true);
|
|
8
|
+
expect(isCommandNotFound('bash: gti: command not found')).toBe(true);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should match zsh style: "zsh: command not found: foo"', () => {
|
|
12
|
+
expect(isCommandNotFound('zsh: command not found: foo')).toBe(true);
|
|
13
|
+
expect(isCommandNotFound('zsh: command not found: gti')).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should match sh style: "sh: foo: not found"', () => {
|
|
17
|
+
expect(isCommandNotFound('sh: foo: not found')).toBe(true);
|
|
18
|
+
expect(isCommandNotFound('sh: 1: gti: not found')).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should match generic "not found" pattern', () => {
|
|
22
|
+
expect(isCommandNotFound('foo: not found')).toBe(true);
|
|
23
|
+
expect(isCommandNotFound('command: not found')).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should match "unknown command" pattern', () => {
|
|
27
|
+
expect(isCommandNotFound('unknown command: foo')).toBe(true);
|
|
28
|
+
expect(isCommandNotFound('Unknown command "gti"')).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should match Windows style: "\'foo\' is not recognized..."', () => {
|
|
32
|
+
expect(isCommandNotFound("'foo' is not recognized as an internal or external command")).toBe(true);
|
|
33
|
+
expect(isCommandNotFound("'gti' is not recognized")).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should match "No such file or directory"', () => {
|
|
37
|
+
expect(isCommandNotFound('bash: ./script.sh: No such file or directory')).toBe(true);
|
|
38
|
+
expect(isCommandNotFound('/usr/bin/foo: No such file or directory')).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('should be case insensitive', () => {
|
|
43
|
+
it('should match regardless of case', () => {
|
|
44
|
+
expect(isCommandNotFound('COMMAND NOT FOUND')).toBe(true);
|
|
45
|
+
expect(isCommandNotFound('Command Not Found')).toBe(true);
|
|
46
|
+
expect(isCommandNotFound('NOT FOUND')).toBe(true);
|
|
47
|
+
expect(isCommandNotFound('Not Recognized')).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('should NOT match other error messages', () => {
|
|
52
|
+
it('should not match permission denied', () => {
|
|
53
|
+
expect(isCommandNotFound('Permission denied')).toBe(false);
|
|
54
|
+
expect(isCommandNotFound('bash: ./script.sh: Permission denied')).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should not match syntax errors', () => {
|
|
58
|
+
expect(isCommandNotFound('Syntax error near unexpected token')).toBe(false);
|
|
59
|
+
expect(isCommandNotFound('bash: syntax error')).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should not match connection errors', () => {
|
|
63
|
+
expect(isCommandNotFound('Connection refused')).toBe(false);
|
|
64
|
+
expect(isCommandNotFound('Connection timed out')).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should not match empty string', () => {
|
|
68
|
+
expect(isCommandNotFound('')).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should not match general error messages', () => {
|
|
72
|
+
expect(isCommandNotFound('Error: something went wrong')).toBe(false);
|
|
73
|
+
expect(isCommandNotFound('Failed to execute')).toBe(false);
|
|
74
|
+
expect(isCommandNotFound('Operation not permitted')).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should not match success messages', () => {
|
|
78
|
+
expect(isCommandNotFound('Command executed successfully')).toBe(false);
|
|
79
|
+
expect(isCommandNotFound('Build completed')).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('edge cases', () => {
|
|
84
|
+
it('should handle multiline stderr', () => {
|
|
85
|
+
const multiline = `some output
|
|
86
|
+
bash: foo: command not found
|
|
87
|
+
more output`;
|
|
88
|
+
expect(isCommandNotFound(multiline)).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should handle stderr with ANSI codes', () => {
|
|
92
|
+
expect(isCommandNotFound('\x1b[31mcommand not found\x1b[0m')).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should handle stderr with extra whitespace', () => {
|
|
96
|
+
expect(isCommandNotFound(' command not found ')).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('real-world examples', () => {
|
|
101
|
+
it('should match common real stderr outputs', () => {
|
|
102
|
+
const examples = [
|
|
103
|
+
'bash: gti: command not found',
|
|
104
|
+
'zsh: command not found: nmp',
|
|
105
|
+
'-bash: dcoker: command not found',
|
|
106
|
+
'sh: kubeclt: not found',
|
|
107
|
+
'/bin/sh: 1: foobar: not found',
|
|
108
|
+
"cmd.exe: 'asdf' is not recognized as an internal or external command",
|
|
109
|
+
'fish: Unknown command: xyz',
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
for (const example of examples) {
|
|
113
|
+
expect(isCommandNotFound(example)).toBe(true);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { buildTriagePrompt } from '../../../src/main/error-triage.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Tests for the error triage prompt builder.
|
|
6
|
+
*
|
|
7
|
+
* buildTriagePrompt creates the prompt sent to Claude for error triage,
|
|
8
|
+
* including command text, exit code, and recent terminal output.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
describe('buildTriagePrompt', () => {
|
|
12
|
+
describe('prompt structure', () => {
|
|
13
|
+
it('should include command, exit code, and output', () => {
|
|
14
|
+
const prompt = buildTriagePrompt('npm run build', 1, 'Error: Module not found');
|
|
15
|
+
|
|
16
|
+
expect(prompt).toContain('npm run build');
|
|
17
|
+
expect(prompt).toContain('Exit code: 1');
|
|
18
|
+
expect(prompt).toContain('Error: Module not found');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should include JSON response format instructions', () => {
|
|
22
|
+
const prompt = buildTriagePrompt('ls /nonexistent', 2, 'No such file');
|
|
23
|
+
|
|
24
|
+
expect(prompt).toContain('shouldNotify');
|
|
25
|
+
expect(prompt).toContain('message');
|
|
26
|
+
expect(prompt).toContain('JSON');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should include guidance for shouldNotify=true cases', () => {
|
|
30
|
+
const prompt = buildTriagePrompt('node app.js', 1, 'crash');
|
|
31
|
+
|
|
32
|
+
expect(prompt).toContain('Module/package not found');
|
|
33
|
+
expect(prompt).toContain('Permission denied');
|
|
34
|
+
expect(prompt).toContain('Syntax errors');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should include guidance for shouldNotify=false cases', () => {
|
|
38
|
+
const prompt = buildTriagePrompt('grep pattern file', 1, '');
|
|
39
|
+
|
|
40
|
+
expect(prompt).toContain('grep');
|
|
41
|
+
expect(prompt).toContain('Ctrl+C');
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('command handling', () => {
|
|
46
|
+
it('should handle null command', () => {
|
|
47
|
+
const prompt = buildTriagePrompt(null, 1, 'some error');
|
|
48
|
+
|
|
49
|
+
expect(prompt).toContain('Command: unknown');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should handle empty string command', () => {
|
|
53
|
+
const prompt = buildTriagePrompt('', 1, 'some error');
|
|
54
|
+
|
|
55
|
+
// Empty string is falsy, should fall back to "unknown"
|
|
56
|
+
expect(prompt).toContain('Command: unknown');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should include the actual command when provided', () => {
|
|
60
|
+
const prompt = buildTriagePrompt('python -m pytest tests/', 1, 'FAILED');
|
|
61
|
+
|
|
62
|
+
expect(prompt).toContain('Command: python -m pytest tests/');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('exit code handling', () => {
|
|
67
|
+
it('should include various exit codes', () => {
|
|
68
|
+
expect(buildTriagePrompt('cmd', 1, 'err')).toContain('Exit code: 1');
|
|
69
|
+
expect(buildTriagePrompt('cmd', 2, 'err')).toContain('Exit code: 2');
|
|
70
|
+
expect(buildTriagePrompt('cmd', 127, 'err')).toContain('Exit code: 127');
|
|
71
|
+
expect(buildTriagePrompt('cmd', 139, 'err')).toContain('Exit code: 139');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('output handling', () => {
|
|
76
|
+
it('should trim whitespace from output', () => {
|
|
77
|
+
const prompt = buildTriagePrompt('cmd', 1, ' \n error message \n ');
|
|
78
|
+
|
|
79
|
+
// The output should be trimmed
|
|
80
|
+
expect(prompt).toContain('error message');
|
|
81
|
+
// Should be wrapped in code fences
|
|
82
|
+
expect(prompt).toContain('```');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should handle empty output', () => {
|
|
86
|
+
const prompt = buildTriagePrompt('cmd', 1, '');
|
|
87
|
+
|
|
88
|
+
expect(prompt).toContain('Exit code: 1');
|
|
89
|
+
// Should still have code fence structure even if empty
|
|
90
|
+
expect(prompt).toContain('```');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should handle multi-line output', () => {
|
|
94
|
+
const output = [
|
|
95
|
+
'Error: Cannot find module "express"',
|
|
96
|
+
' at Function._resolveFilename (node:internal/modules/cjs/loader:1405:15)',
|
|
97
|
+
' at Function._load (node:internal/modules/cjs/loader:1215:37)',
|
|
98
|
+
].join('\n');
|
|
99
|
+
|
|
100
|
+
const prompt = buildTriagePrompt('node server.js', 1, output);
|
|
101
|
+
|
|
102
|
+
expect(prompt).toContain('Cannot find module');
|
|
103
|
+
expect(prompt).toContain('_resolveFilename');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should preserve output with special characters', () => {
|
|
107
|
+
const output = 'Error: Expected "}" but found "<EOF>"';
|
|
108
|
+
const prompt = buildTriagePrompt('node -e "{"', 1, output);
|
|
109
|
+
|
|
110
|
+
expect(prompt).toContain('Expected');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('default behavior guidance', () => {
|
|
115
|
+
it('should default to shouldNotify=true', () => {
|
|
116
|
+
const prompt = buildTriagePrompt('cmd', 1, 'error');
|
|
117
|
+
|
|
118
|
+
expect(prompt).toContain('DEFAULT: shouldNotify=true');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should mention that most non-zero exit codes indicate real problems', () => {
|
|
122
|
+
const prompt = buildTriagePrompt('cmd', 1, 'error');
|
|
123
|
+
|
|
124
|
+
expect(prompt).toContain('real problems');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should advise notifying when in doubt', () => {
|
|
128
|
+
const prompt = buildTriagePrompt('cmd', 1, 'error');
|
|
129
|
+
|
|
130
|
+
expect(prompt).toContain('When in doubt, notify');
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
});
|