brosh 0.1.5 → 0.1.6
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/README.github.md +22 -46
- package/package.json +2 -2
- package/packages/desktop-electron/package-lock.json +5 -5
- package/packages/desktop-electron/package.json +1 -1
- package/packages/desktop-electron/vite.config.ts +3 -0
- package/vendor/xterm-headless-5.5.0.tgz +0 -0
- package/vendor/xterm-xterm-5.5.0.tgz +0 -0
- package/website/index.html +3 -3
- package/website/install.sh.asc +13 -13
- package/packages/desktop-electron/tests/main/error-triage/buildTriagePrompt.test.ts +0 -133
- package/packages/desktop-electron/tests/main/error-triage/parseTriageResponse.test.ts +0 -123
package/README.github.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<strong>
|
|
2
|
+
<strong>Built for Claude coders.</strong>
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<img src="https://img.shields.io/badge/platform-macOS%20%7C%20Linux-blue" alt="Platforms">
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
|
-

|
|
12
12
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
@@ -41,64 +41,40 @@ cd brosh/packages/desktop-electron && npm install && npm run start
|
|
|
41
41
|
|
|
42
42
|
**CLI only:** `npm install -g brosh` or `brew install elleryfamilia/brosh/brosh-cli` -- [more install options](./docs/installation.md)
|
|
43
43
|
|
|
44
|
-
## Features
|
|
45
|
-
|
|
46
|
-
- [**Claude Code integration**](#claude-code-integration) -- Natural language detection, model switching, "Continue in Claude" handoff, built-in MCP server
|
|
47
|
-
- [**Split panes & tabs**](#terminal) -- Horizontal/vertical splits with draggable dividers, multi-tab interface
|
|
48
|
-
- [**Git sidebar**](#git-integration) -- Visual commit graph, file change tracking, Monaco diff editor
|
|
49
|
-
- [**Sandbox mode**](#sandbox-mode) -- Restrict filesystem and network access per session
|
|
50
|
-
- [**Themes & customization**](#terminal) -- 9 themes, 25+ fonts, cursor styles, window opacity, scrollback
|
|
51
|
-
- [**MCP tools**](#mcp-integration) -- Give Claude Code direct access to type, read, and screenshot your terminal
|
|
52
|
-
- [**Session recording**](#developer-tools) -- Record to asciicast format, play back with asciinema
|
|
53
|
-
- **Cross-platform** -- macOS, Linux
|
|
54
|
-
|
|
55
44
|
---
|
|
56
45
|
|
|
57
|
-
##
|
|
58
|
-
|
|
59
|
-
brosh is purpose-built for working with Claude Code. Every part of the terminal is designed to make AI-assisted development seamless.
|
|
60
|
-
|
|
61
|
-
- **Natural language detection** -- An ML classifier distinguishes commands from questions in real time. Type `git push` and it runs. Type `how do I rebase onto main?` and Claude answers.
|
|
62
|
-
- **Model switching** -- Switch between Sonnet, Opus, and Haiku from the status bar.
|
|
63
|
-
- **MCP server built in** -- Claude Code connects over MCP and can see your terminal, run commands, and read output. No extra setup.
|
|
64
|
-
- **Continue in Claude** -- Start a conversation in the terminal and seamlessly continue it in the Claude Code CLI with full context.
|
|
65
|
-
- **Smart status bar** -- Git branch, active Claude model, MCP connection status, and session info at a glance.
|
|
46
|
+
## As much or as little as you'd like
|
|
66
47
|
|
|
67
|
-
|
|
48
|
+
brosh gives you a terminal that grows with your workflow. Start minimal, add AI when you need it.
|
|
68
49
|
|
|
69
|
-
|
|
50
|
+
### Just a terminal
|
|
70
51
|
|
|
71
|
-
|
|
52
|
+
At its simplest, brosh is a fast, themeable terminal with split panes, tabs, and a smart status bar.
|
|
72
53
|
|
|
73
|
-
|
|
74
|
-
- Draggable panel dividers for custom layouts
|
|
75
|
-
- 9 built-in themes and 25+ fonts
|
|
76
|
-
- Cursor style options, window opacity, and configurable scrollback
|
|
77
|
-
- Find bar for searching terminal output
|
|
54
|
+

|
|
78
55
|
|
|
79
|
-
|
|
56
|
+
### Add Claude Code
|
|
80
57
|
|
|
81
|
-
|
|
58
|
+
Open a side-by-side pane and Claude Code is right there -- connected to your terminal over MCP, ready to help.
|
|
82
59
|
|
|
83
|
-

|
|
84
61
|
|
|
85
|
-
|
|
62
|
+
### Plugins when you need them
|
|
86
63
|
|
|
87
|
-
|
|
64
|
+
Git, Context, Plans, Files -- built-in plugins live in the status bar and open as sidebars when you need them. Here the Context plugin gives Claude visibility into your project's CLAUDE.md files, documentation, and codebase structure.
|
|
88
65
|
|
|
89
|
-
|
|
66
|
+

|
|
90
67
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-

|
|
94
|
-
|
|
95
|
-
See [docs/sandbox.md](./docs/sandbox.md) for configuration details.
|
|
96
|
-
|
|
97
|
-
## Developer Tools
|
|
68
|
+
## Features
|
|
98
69
|
|
|
99
|
-
-
|
|
100
|
-
-
|
|
101
|
-
-
|
|
70
|
+
- **Claude Code integration** -- Built-in MCP server, model switching, natural language detection, "Continue in Claude" handoff
|
|
71
|
+
- **Built-in plugins** -- Git, Context, Plans, and Files sidebars available when you need them
|
|
72
|
+
- **Split panes & tabs** -- Horizontal/vertical splits with draggable dividers, multi-tab interface
|
|
73
|
+
- **Sandbox mode** -- Restrict filesystem and network access per session
|
|
74
|
+
- **Themes & customization** -- 9 themes, 25+ fonts, cursor styles, window opacity, scrollback
|
|
75
|
+
- **MCP tools** -- Claude Code can type, read, and screenshot your terminal directly
|
|
76
|
+
- **Session recording** -- Record to asciicast format, play back with asciinema
|
|
77
|
+
- **Cross-platform** -- macOS, Linux
|
|
102
78
|
|
|
103
79
|
## MCP Integration
|
|
104
80
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brosh",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Terminal MCP bridge for AI-powered development",
|
|
5
5
|
"author": "Ellery Familia",
|
|
6
6
|
"repository": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@anthropic-ai/sandbox-runtime": "^0.0.32",
|
|
37
37
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
38
|
-
"@xterm/headless": "
|
|
38
|
+
"@xterm/headless": "file:vendor/xterm-headless-5.5.0.tgz",
|
|
39
39
|
"ink": "^5.0.1",
|
|
40
40
|
"node-pty": "^1.2.0-beta.8",
|
|
41
41
|
"react": "^18.3.1",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@xterm/addon-search": "^0.16.0",
|
|
32
32
|
"@xterm/addon-web-links": "^0.11.0",
|
|
33
33
|
"@xterm/addon-webgl": "^0.18.0",
|
|
34
|
-
"@xterm/xterm": "
|
|
34
|
+
"@xterm/xterm": "file:../../vendor/xterm-xterm-5.5.0.tgz",
|
|
35
35
|
"brosh": "file:../..",
|
|
36
36
|
"bytenode": "^1.5.7",
|
|
37
37
|
"chokidar": "^5.0.0",
|
|
@@ -65,12 +65,12 @@
|
|
|
65
65
|
}
|
|
66
66
|
},
|
|
67
67
|
"../..": {
|
|
68
|
-
"version": "0.1.
|
|
68
|
+
"version": "0.1.6",
|
|
69
69
|
"license": "MIT",
|
|
70
70
|
"dependencies": {
|
|
71
71
|
"@anthropic-ai/sandbox-runtime": "^0.0.32",
|
|
72
72
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
73
|
-
"@xterm/headless": "
|
|
73
|
+
"@xterm/headless": "file:vendor/xterm-headless-5.5.0.tgz",
|
|
74
74
|
"ink": "^5.0.1",
|
|
75
75
|
"node-pty": "^1.2.0-beta.8",
|
|
76
76
|
"react": "^18.3.1",
|
|
@@ -3550,8 +3550,8 @@
|
|
|
3550
3550
|
},
|
|
3551
3551
|
"node_modules/@xterm/xterm": {
|
|
3552
3552
|
"version": "5.5.0",
|
|
3553
|
-
"resolved": "
|
|
3554
|
-
"integrity": "sha512-
|
|
3553
|
+
"resolved": "file:../../vendor/xterm-xterm-5.5.0.tgz",
|
|
3554
|
+
"integrity": "sha512-4JDLfPkqA6w7JE06IM5oGaRkceyBTNC1Mjpn3mUjSOR/qcktEUqr1D7nsHhym/nTbLowub++/dGPsJ4741R6mA==",
|
|
3555
3555
|
"license": "MIT"
|
|
3556
3556
|
},
|
|
3557
3557
|
"node_modules/7zip-bin": {
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"@xterm/addon-search": "^0.16.0",
|
|
59
59
|
"@xterm/addon-web-links": "^0.11.0",
|
|
60
60
|
"@xterm/addon-webgl": "^0.18.0",
|
|
61
|
-
"@xterm/xterm": "
|
|
61
|
+
"@xterm/xterm": "file:../../vendor/xterm-xterm-5.5.0.tgz",
|
|
62
62
|
"brosh": "file:../..",
|
|
63
63
|
"bytenode": "^1.5.7",
|
|
64
64
|
"chokidar": "^5.0.0",
|
|
Binary file
|
|
Binary file
|
package/website/index.html
CHANGED
|
@@ -3,18 +3,18 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>brosh -
|
|
6
|
+
<title>brosh - Built for Claude coders</title>
|
|
7
7
|
<meta name="description" content="A modern terminal emulator with built-in AI integration via MCP. Natural language detection, split panes, Git sidebar, sandbox mode, and more.">
|
|
8
8
|
|
|
9
9
|
<!-- Open Graph -->
|
|
10
10
|
<meta property="og:type" content="website">
|
|
11
|
-
<meta property="og:title" content="brosh -
|
|
11
|
+
<meta property="og:title" content="brosh - Built for Claude coders">
|
|
12
12
|
<meta property="og:description" content="A modern terminal emulator with built-in AI integration via MCP. Built for Claude coders.">
|
|
13
13
|
<meta property="og:image" content="assets/images/brosh_startscreen.png">
|
|
14
14
|
|
|
15
15
|
<!-- Twitter Card -->
|
|
16
16
|
<meta name="twitter:card" content="summary_large_image">
|
|
17
|
-
<meta name="twitter:title" content="brosh -
|
|
17
|
+
<meta name="twitter:title" content="brosh - Built for Claude coders">
|
|
18
18
|
<meta name="twitter:description" content="A modern terminal emulator with built-in AI integration via MCP. Built for Claude coders.">
|
|
19
19
|
<meta name="twitter:image" content="assets/images/brosh_startscreen.png">
|
|
20
20
|
|
package/website/install.sh.asc
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
-----BEGIN PGP SIGNATURE-----
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
=
|
|
3
|
+
iQIzBAABCgAdFiEEX8WeGtXPO7P8xoFftW8bjWv2UfkFAmmcrWwACgkQtW8bjWv2
|
|
4
|
+
Ufmocg//SlriDRiQt7Pg3iD40XZ3Rg9mVkRoxUVGW/vxiq4xPoTX1XrMC47hBF3O
|
|
5
|
+
OR5h6YylCO8RS/+7LbS7hTmSanpeDGaOObMlMVVN5Vmk1u5Bw6lAOsjBd1z5TyZw
|
|
6
|
+
tmDYiHeXiG1nPolDuGWytJoI3MA3jzh3bcU3UlRhNAVMAXOI9y32U4XHlGUTc2N2
|
|
7
|
+
HT+yIhIcB14cGSCnNQOxnyQvgtuLWAMrUfK2jsugMbdSGJ74yibuOGlstoNih7eg
|
|
8
|
+
rtnDEn9bahIkNlxSIIBqrgCfMS5EKSYiF49cQ+kdcJBE94MUgllpnFAL1bwcrEt/
|
|
9
|
+
vY1zpiLcmyd/eUE/cCHYOjJ6vccRjJAx+I/j7aHQGRGxBmeHW8ie77fGQ9IuDbit
|
|
10
|
+
/P2NG7qYPsCLhM+y7UGKa4nwjtAjAi2zwkwOKT8FRrexcnhh1m8Pa/FsbRG1itck
|
|
11
|
+
UQpsl416n2U29EwYGUyXb4ijvKMyQHACmvISgKM/Cxm1eBpDBsvEgkUArNEBzWv0
|
|
12
|
+
pZjAF2ssztiFBB1HpKR6TsPeXmQMLIIwPuxQ2CDlZavjBmRvYHnLMSOCe0V6hSjp
|
|
13
|
+
ndvw1q3oBMlj5omUhJpWPMgZhNkaVLJn9bU7ZwHyjljWZyUbin7H7G9Ayy58+rsl
|
|
14
|
+
yE9ZiDnpe9PT0+hRw40h7NGII5B9JEFoFqGHhpJ47UELXP09ah4=
|
|
15
|
+
=nCSH
|
|
16
16
|
-----END PGP SIGNATURE-----
|
|
@@ -1,133 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Tests for triage response parsing.
|
|
5
|
-
*
|
|
6
|
-
* parseTriageResponse is not exported directly, so we test it
|
|
7
|
-
* through the module's behavior by examining the expected parsing patterns.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
// Since parseTriageResponse is private, test the expected parsing patterns
|
|
11
|
-
describe('Triage Response Parsing Patterns', () => {
|
|
12
|
-
describe('envelope format (claude --output-format json)', () => {
|
|
13
|
-
it('should parse standard envelope with result string', () => {
|
|
14
|
-
const envelope = {
|
|
15
|
-
result: '{"shouldNotify": true, "message": "Module not found: express"}',
|
|
16
|
-
};
|
|
17
|
-
const innerJson = envelope.result;
|
|
18
|
-
const parsed = JSON.parse(innerJson);
|
|
19
|
-
|
|
20
|
-
expect(parsed.shouldNotify).toBe(true);
|
|
21
|
-
expect(parsed.message).toBe('Module not found: express');
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('should handle direct format (no envelope)', () => {
|
|
25
|
-
const direct = {
|
|
26
|
-
shouldNotify: false,
|
|
27
|
-
message: '',
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
expect(typeof direct.shouldNotify).toBe('boolean');
|
|
31
|
-
expect(direct.shouldNotify).toBe(false);
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe('inner JSON parsing', () => {
|
|
36
|
-
it('should parse clean JSON', () => {
|
|
37
|
-
const json = '{"shouldNotify": true, "message": "Permission denied on /etc/config"}';
|
|
38
|
-
const parsed = JSON.parse(json);
|
|
39
|
-
|
|
40
|
-
expect(parsed.shouldNotify).toBe(true);
|
|
41
|
-
expect(parsed.message).toContain('Permission denied');
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('should handle markdown code fence wrapping', () => {
|
|
45
|
-
const wrapped = '```json\n{"shouldNotify": true, "message": "Syntax error"}\n```';
|
|
46
|
-
const cleaned = wrapped
|
|
47
|
-
.replace(/^```(?:json)?\s*\n?/, '')
|
|
48
|
-
.replace(/\n?```\s*$/, '');
|
|
49
|
-
const parsed = JSON.parse(cleaned);
|
|
50
|
-
|
|
51
|
-
expect(parsed.shouldNotify).toBe(true);
|
|
52
|
-
expect(parsed.message).toBe('Syntax error');
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('should handle code fence without language tag', () => {
|
|
56
|
-
const wrapped = '```\n{"shouldNotify": false, "message": ""}\n```';
|
|
57
|
-
const cleaned = wrapped
|
|
58
|
-
.replace(/^```(?:json)?\s*\n?/, '')
|
|
59
|
-
.replace(/\n?```\s*$/, '');
|
|
60
|
-
const parsed = JSON.parse(cleaned);
|
|
61
|
-
|
|
62
|
-
expect(parsed.shouldNotify).toBe(false);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('should reject missing shouldNotify field', () => {
|
|
66
|
-
const json = '{"message": "some error"}';
|
|
67
|
-
const parsed = JSON.parse(json);
|
|
68
|
-
|
|
69
|
-
expect(typeof parsed.shouldNotify).not.toBe('boolean');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('should handle empty message for shouldNotify=false', () => {
|
|
73
|
-
const json = '{"shouldNotify": false, "message": ""}';
|
|
74
|
-
const parsed = JSON.parse(json);
|
|
75
|
-
|
|
76
|
-
expect(parsed.shouldNotify).toBe(false);
|
|
77
|
-
expect(parsed.message).toBe('');
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
describe('error scenarios', () => {
|
|
82
|
-
it('should handle empty stdout', () => {
|
|
83
|
-
const trimmed = ''.trim();
|
|
84
|
-
expect(trimmed).toBe('');
|
|
85
|
-
// parseTriageResponse returns null for empty input
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('should handle malformed JSON', () => {
|
|
89
|
-
const malformed = '{"shouldNotify": true, message: broken}';
|
|
90
|
-
expect(() => JSON.parse(malformed)).toThrow();
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('should handle non-JSON responses', () => {
|
|
94
|
-
const text = 'The command failed because the module was not found.';
|
|
95
|
-
expect(() => JSON.parse(text)).toThrow();
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe('TriageResult contract', () => {
|
|
100
|
-
it('should always have boolean shouldNotify', () => {
|
|
101
|
-
const results = [
|
|
102
|
-
{ shouldNotify: true, message: 'Error occurred' },
|
|
103
|
-
{ shouldNotify: false, message: '' },
|
|
104
|
-
];
|
|
105
|
-
|
|
106
|
-
for (const result of results) {
|
|
107
|
-
expect(typeof result.shouldNotify).toBe('boolean');
|
|
108
|
-
expect(typeof result.message).toBe('string');
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('should coerce message to string', () => {
|
|
113
|
-
// The actual code does String(parsed.message || "")
|
|
114
|
-
const coerce = (val: unknown) => String(val || '');
|
|
115
|
-
|
|
116
|
-
expect(coerce('hello')).toBe('hello');
|
|
117
|
-
expect(coerce(undefined)).toBe('');
|
|
118
|
-
expect(coerce(null)).toBe('');
|
|
119
|
-
expect(coerce('')).toBe('');
|
|
120
|
-
expect(coerce(42)).toBe('42');
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
});
|