codeep 1.1.12 → 1.1.13

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 (45) hide show
  1. package/bin/codeep.js +1 -1
  2. package/dist/config/index.js +10 -10
  3. package/dist/renderer/App.d.ts +430 -0
  4. package/dist/renderer/App.js +2712 -0
  5. package/dist/renderer/ChatUI.d.ts +71 -0
  6. package/dist/renderer/ChatUI.js +286 -0
  7. package/dist/renderer/Input.d.ts +72 -0
  8. package/dist/renderer/Input.js +371 -0
  9. package/dist/renderer/Screen.d.ts +79 -0
  10. package/dist/renderer/Screen.js +278 -0
  11. package/dist/renderer/ansi.d.ts +99 -0
  12. package/dist/renderer/ansi.js +176 -0
  13. package/dist/renderer/components/Box.d.ts +64 -0
  14. package/dist/renderer/components/Box.js +90 -0
  15. package/dist/renderer/components/Help.d.ts +30 -0
  16. package/dist/renderer/components/Help.js +195 -0
  17. package/dist/renderer/components/Intro.d.ts +12 -0
  18. package/dist/renderer/components/Intro.js +128 -0
  19. package/dist/renderer/components/Login.d.ts +42 -0
  20. package/dist/renderer/components/Login.js +178 -0
  21. package/dist/renderer/components/Modal.d.ts +43 -0
  22. package/dist/renderer/components/Modal.js +207 -0
  23. package/dist/renderer/components/Permission.d.ts +20 -0
  24. package/dist/renderer/components/Permission.js +113 -0
  25. package/dist/renderer/components/SelectScreen.d.ts +26 -0
  26. package/dist/renderer/components/SelectScreen.js +101 -0
  27. package/dist/renderer/components/Settings.d.ts +37 -0
  28. package/dist/renderer/components/Settings.js +333 -0
  29. package/dist/renderer/components/Status.d.ts +18 -0
  30. package/dist/renderer/components/Status.js +78 -0
  31. package/dist/renderer/demo-app.d.ts +6 -0
  32. package/dist/renderer/demo-app.js +85 -0
  33. package/dist/renderer/demo.d.ts +6 -0
  34. package/dist/renderer/demo.js +52 -0
  35. package/dist/renderer/index.d.ts +16 -0
  36. package/dist/renderer/index.js +17 -0
  37. package/dist/renderer/main.d.ts +6 -0
  38. package/dist/renderer/main.js +1634 -0
  39. package/dist/utils/agent.d.ts +21 -0
  40. package/dist/utils/agent.js +29 -0
  41. package/dist/utils/clipboard.d.ts +15 -0
  42. package/dist/utils/clipboard.js +95 -0
  43. package/package.json +7 -11
  44. package/dist/utils/console.d.ts +0 -55
  45. package/dist/utils/console.js +0 -188
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Modal overlay component
3
+ * Renders a box with content on top of existing screen
4
+ */
5
+ import { fg, style } from '../ansi.js';
6
+ import { createBox, centerBox } from './Box.js';
7
+ // Primary color: #f02a30 (Codeep red)
8
+ const PRIMARY_COLOR = fg.rgb(240, 42, 48);
9
+ const PRIMARY_BRIGHT = fg.rgb(255, 80, 85);
10
+ /**
11
+ * Render a modal on the screen
12
+ */
13
+ export function renderModal(screen, options) {
14
+ const { width: screenWidth, height: screenHeight } = screen.getSize();
15
+ // Calculate dimensions
16
+ const contentWidth = Math.max(...options.content.map(l => l.length), options.title.length + 4);
17
+ const modalWidth = options.width || Math.min(contentWidth + 4, screenWidth - 4);
18
+ const modalHeight = options.height || Math.min(options.content.length + 4, screenHeight - 4);
19
+ // Calculate position
20
+ let x, y;
21
+ if (options.centered !== false) {
22
+ const pos = centerBox(screenWidth, screenHeight, modalWidth, modalHeight);
23
+ x = pos.x;
24
+ y = pos.y;
25
+ }
26
+ else {
27
+ x = options.x || 0;
28
+ y = options.y || 0;
29
+ }
30
+ // Draw box
31
+ const boxLines = createBox({
32
+ x,
33
+ y,
34
+ width: modalWidth,
35
+ height: modalHeight,
36
+ style: options.boxStyle || 'rounded',
37
+ title: options.title,
38
+ borderColor: options.borderColor || PRIMARY_COLOR,
39
+ titleColor: options.titleColor || PRIMARY_BRIGHT,
40
+ });
41
+ for (const line of boxLines) {
42
+ screen.writeLine(line.y, line.text, line.style);
43
+ }
44
+ // Draw content
45
+ const contentStartY = y + 1;
46
+ const contentStartX = x + 2;
47
+ const maxContentWidth = modalWidth - 4;
48
+ for (let i = 0; i < options.content.length && i < modalHeight - 2; i++) {
49
+ const line = options.content[i];
50
+ const truncated = line.length > maxContentWidth
51
+ ? line.slice(0, maxContentWidth - 1) + '…'
52
+ : line;
53
+ screen.write(contentStartX, contentStartY + i, truncated, options.contentColor || '');
54
+ }
55
+ }
56
+ /**
57
+ * Render a help/info modal with key bindings
58
+ */
59
+ export function renderHelpModal(screen, title, items, footer) {
60
+ const { width: screenWidth, height: screenHeight } = screen.getSize();
61
+ // Format content
62
+ const content = [];
63
+ const keyWidth = Math.max(...items.map(i => i.key.length)) + 2;
64
+ for (const item of items) {
65
+ const paddedKey = item.key.padEnd(keyWidth);
66
+ content.push(`${paddedKey}${item.description}`);
67
+ }
68
+ if (footer) {
69
+ content.push('');
70
+ content.push(footer);
71
+ }
72
+ // Calculate size
73
+ const contentWidth = Math.max(...content.map(l => l.length), title.length + 4);
74
+ const modalWidth = Math.min(contentWidth + 6, screenWidth - 4);
75
+ const modalHeight = Math.min(content.length + 4, screenHeight - 4);
76
+ const { x, y } = centerBox(screenWidth, screenHeight, modalWidth, modalHeight);
77
+ // Draw box
78
+ const boxLines = createBox({
79
+ x,
80
+ y,
81
+ width: modalWidth,
82
+ height: modalHeight,
83
+ style: 'rounded',
84
+ title,
85
+ borderColor: PRIMARY_COLOR,
86
+ titleColor: PRIMARY_BRIGHT,
87
+ });
88
+ for (const line of boxLines) {
89
+ screen.writeLine(line.y, line.text, line.style);
90
+ }
91
+ // Draw content with syntax highlighting for keys
92
+ const contentStartY = y + 1;
93
+ const contentStartX = x + 2;
94
+ for (let i = 0; i < content.length && i < modalHeight - 2; i++) {
95
+ const line = content[i];
96
+ // Highlight key part (before the description)
97
+ if (i < items.length) {
98
+ const item = items[i];
99
+ screen.write(contentStartX, contentStartY + i, item.key.padEnd(keyWidth), fg.yellow);
100
+ screen.write(contentStartX + keyWidth, contentStartY + i, item.description, fg.white);
101
+ }
102
+ else {
103
+ screen.write(contentStartX, contentStartY + i, line, fg.gray);
104
+ }
105
+ }
106
+ }
107
+ /**
108
+ * Render a confirmation modal with Yes/No options
109
+ */
110
+ export function renderConfirmModal(screen, title, message, selectedOption, confirmLabel = 'Yes', cancelLabel = 'No') {
111
+ const { width: screenWidth, height: screenHeight } = screen.getSize();
112
+ // Calculate size
113
+ const contentWidth = Math.max(...message.map(l => l.length), title.length + 4, confirmLabel.length + cancelLabel.length + 12);
114
+ const modalWidth = Math.min(contentWidth + 8, screenWidth - 4);
115
+ const modalHeight = Math.min(message.length + 6, screenHeight - 4);
116
+ const { x, y } = centerBox(screenWidth, screenHeight, modalWidth, modalHeight);
117
+ // Draw box
118
+ const boxLines = createBox({
119
+ x,
120
+ y,
121
+ width: modalWidth,
122
+ height: modalHeight,
123
+ style: 'rounded',
124
+ title,
125
+ borderColor: fg.yellow,
126
+ titleColor: fg.yellow + style.bold,
127
+ });
128
+ for (const line of boxLines) {
129
+ screen.writeLine(line.y, line.text, line.style);
130
+ }
131
+ // Draw message
132
+ const contentStartY = y + 1;
133
+ const contentStartX = x + 2;
134
+ for (let i = 0; i < message.length; i++) {
135
+ screen.write(contentStartX, contentStartY + i, message[i], fg.white);
136
+ }
137
+ // Draw buttons
138
+ const buttonsY = y + modalHeight - 2;
139
+ const yesButton = selectedOption === 'yes'
140
+ ? `${style.inverse} ${confirmLabel} ${style.reset}`
141
+ : ` ${confirmLabel} `;
142
+ const noButton = selectedOption === 'no'
143
+ ? `${style.inverse} ${cancelLabel} ${style.reset}`
144
+ : ` ${cancelLabel} `;
145
+ const buttonsWidth = confirmLabel.length + cancelLabel.length + 8;
146
+ const buttonsX = x + Math.floor((modalWidth - buttonsWidth) / 2);
147
+ screen.write(buttonsX, buttonsY, yesButton, selectedOption === 'yes' ? PRIMARY_BRIGHT : fg.gray);
148
+ screen.write(buttonsX + confirmLabel.length + 4, buttonsY, noButton, selectedOption === 'no' ? PRIMARY_BRIGHT : fg.gray);
149
+ // Help text
150
+ const helpText = '←/→ Select | Enter Confirm | Esc Cancel';
151
+ const helpX = x + Math.floor((modalWidth - helpText.length) / 2);
152
+ screen.write(helpX, y + modalHeight - 1, helpText, fg.gray);
153
+ }
154
+ /**
155
+ * Render a list selection modal
156
+ */
157
+ export function renderListModal(screen, title, items, selectedIndex, footer) {
158
+ const { width: screenWidth, height: screenHeight } = screen.getSize();
159
+ // Calculate max visible items (leave room for border, title, footer)
160
+ const maxVisibleItems = Math.max(3, screenHeight - 10);
161
+ const visibleItemCount = Math.min(items.length, maxVisibleItems);
162
+ // Calculate size - include footer in width calculation
163
+ const contentWidth = Math.max(...items.map(l => l.length + 4), title.length + 4, footer ? footer.length + 2 : 0);
164
+ const modalWidth = Math.min(contentWidth + 6, screenWidth - 4);
165
+ // Height: 2 for borders + visibleItemCount + 1 for padding + (footer ? 1 : 0)
166
+ const modalHeight = visibleItemCount + 3 + (footer ? 1 : 0);
167
+ const { x, y } = centerBox(screenWidth, screenHeight, modalWidth, modalHeight);
168
+ // Draw box
169
+ const boxLines = createBox({
170
+ x,
171
+ y,
172
+ width: modalWidth,
173
+ height: modalHeight,
174
+ style: 'rounded',
175
+ title,
176
+ borderColor: PRIMARY_COLOR,
177
+ titleColor: PRIMARY_BRIGHT,
178
+ });
179
+ for (const line of boxLines) {
180
+ screen.writeLine(line.y, line.text, line.style);
181
+ }
182
+ // Calculate visible range (scroll if needed)
183
+ let startIndex = 0;
184
+ if (items.length > visibleItemCount) {
185
+ startIndex = Math.max(0, Math.min(selectedIndex - Math.floor(visibleItemCount / 2), items.length - visibleItemCount));
186
+ }
187
+ // Draw items
188
+ const contentStartY = y + 1;
189
+ const contentStartX = x + 2;
190
+ const visibleItems = items.slice(startIndex, startIndex + visibleItemCount);
191
+ for (let i = 0; i < visibleItems.length; i++) {
192
+ const item = visibleItems[i];
193
+ const actualIndex = startIndex + i;
194
+ const isSelected = actualIndex === selectedIndex;
195
+ const prefix = isSelected ? '► ' : ' ';
196
+ const itemStyle = isSelected ? PRIMARY_BRIGHT + style.bold : fg.white;
197
+ screen.write(contentStartX, contentStartY + i, prefix + item, itemStyle);
198
+ }
199
+ // Draw footer (truncate if needed)
200
+ if (footer) {
201
+ const maxFooterWidth = modalWidth - 4;
202
+ const displayFooter = footer.length > maxFooterWidth
203
+ ? footer.slice(0, maxFooterWidth - 1) + '…'
204
+ : footer;
205
+ screen.write(contentStartX, y + modalHeight - 2, displayFooter, fg.gray);
206
+ }
207
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Permission screen for granting folder access
3
+ */
4
+ import { Screen } from '../Screen';
5
+ export type PermissionLevel = 'none' | 'read' | 'write';
6
+ export interface PermissionOptions {
7
+ projectPath: string;
8
+ isProject: boolean;
9
+ currentPermission: PermissionLevel;
10
+ onSelect: (permission: PermissionLevel) => void;
11
+ onCancel: () => void;
12
+ }
13
+ /**
14
+ * Render permission screen
15
+ */
16
+ export declare function renderPermissionScreen(screen: Screen, options: PermissionOptions, selectedIndex: number): void;
17
+ /**
18
+ * Get permission options array for easy indexing
19
+ */
20
+ export declare function getPermissionOptions(): PermissionLevel[];
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Permission screen for granting folder access
3
+ */
4
+ import { fg, style } from '../ansi.js';
5
+ import { createBox, centerBox } from './Box.js';
6
+ // Primary color: #f02a30 (Codeep red)
7
+ const PRIMARY_COLOR = fg.rgb(240, 42, 48);
8
+ const PRIMARY_BRIGHT = fg.rgb(255, 80, 85);
9
+ /**
10
+ * Render permission screen
11
+ */
12
+ export function renderPermissionScreen(screen, options, selectedIndex) {
13
+ const { width, height } = screen.getSize();
14
+ screen.clear();
15
+ // Title
16
+ const title = '═══ Folder Access ═══';
17
+ const titleX = Math.floor((width - title.length) / 2);
18
+ screen.write(titleX, 1, title, PRIMARY_COLOR + style.bold);
19
+ // Box
20
+ const boxWidth = Math.min(60, width - 4);
21
+ const boxHeight = 14;
22
+ const { x: boxX, y: boxY } = centerBox(width, height, boxWidth, boxHeight);
23
+ const boxLines = createBox({
24
+ x: boxX,
25
+ y: boxY,
26
+ width: boxWidth,
27
+ height: boxHeight,
28
+ style: 'rounded',
29
+ borderColor: PRIMARY_COLOR,
30
+ });
31
+ for (const line of boxLines) {
32
+ screen.writeLine(line.y, line.text, line.style);
33
+ }
34
+ // Content
35
+ const contentX = boxX + 3;
36
+ let contentY = boxY + 2;
37
+ // Project path
38
+ const displayPath = truncatePath(options.projectPath, boxWidth - 8);
39
+ screen.write(contentX, contentY, 'Project:', fg.gray);
40
+ screen.write(contentX + 9, contentY, displayPath, fg.white);
41
+ contentY += 2;
42
+ // Description
43
+ if (options.isProject) {
44
+ screen.write(contentX, contentY, 'This looks like a project folder.', fg.white);
45
+ }
46
+ else {
47
+ screen.write(contentX, contentY, 'Grant access to enable AI assistance.', fg.white);
48
+ }
49
+ contentY += 2;
50
+ // Options
51
+ const permissionOptions = [
52
+ {
53
+ level: 'read',
54
+ label: 'Read Only',
55
+ desc: 'AI can read files, no modifications'
56
+ },
57
+ {
58
+ level: 'write',
59
+ label: 'Read & Write',
60
+ desc: 'AI can read and modify files (Agent mode)'
61
+ },
62
+ {
63
+ level: 'none',
64
+ label: 'No Access',
65
+ desc: 'Chat without project context'
66
+ },
67
+ ];
68
+ for (let i = 0; i < permissionOptions.length; i++) {
69
+ const opt = permissionOptions[i];
70
+ const isSelected = i === selectedIndex;
71
+ const prefix = isSelected ? '► ' : ' ';
72
+ // Label
73
+ const labelStyle = isSelected ? PRIMARY_BRIGHT + style.bold : fg.white;
74
+ screen.write(contentX, contentY, prefix + opt.label, labelStyle);
75
+ // Description on same line
76
+ const descX = contentX + 20;
77
+ screen.write(descX, contentY, opt.desc, fg.gray);
78
+ contentY++;
79
+ }
80
+ // Current permission indicator
81
+ contentY++;
82
+ if (options.currentPermission !== 'none') {
83
+ screen.write(contentX, contentY, `Current: ${options.currentPermission}`, fg.yellow);
84
+ }
85
+ // Footer
86
+ const footerY = height - 2;
87
+ screen.write(2, footerY, '↑↓ Navigate | Enter Select | Esc Skip', fg.gray);
88
+ screen.showCursor(false);
89
+ screen.fullRender();
90
+ }
91
+ /**
92
+ * Get permission options array for easy indexing
93
+ */
94
+ export function getPermissionOptions() {
95
+ return ['read', 'write', 'none'];
96
+ }
97
+ /**
98
+ * Truncate path for display
99
+ */
100
+ function truncatePath(path, maxLen) {
101
+ if (path.length <= maxLen)
102
+ return path;
103
+ const parts = path.split('/');
104
+ let result = parts[parts.length - 1];
105
+ for (let i = parts.length - 2; i >= 0; i--) {
106
+ const newResult = parts[i] + '/' + result;
107
+ if (newResult.length + 3 > maxLen) {
108
+ return '.../' + result;
109
+ }
110
+ result = newResult;
111
+ }
112
+ return result;
113
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Generic fullscreen selection component
3
+ * Used for Language, Provider, Model, Protocol selection
4
+ */
5
+ import { Screen } from '../Screen';
6
+ export interface SelectItem {
7
+ key: string;
8
+ label: string;
9
+ description?: string;
10
+ }
11
+ export interface SelectScreenState {
12
+ selectedIndex: number;
13
+ }
14
+ /**
15
+ * Render a fullscreen selection screen
16
+ */
17
+ export declare function renderSelectScreen(screen: Screen, title: string, items: SelectItem[], state: SelectScreenState, currentValue?: string): void;
18
+ /**
19
+ * Handle selection screen key
20
+ */
21
+ export declare function handleSelectKey(key: string, state: SelectScreenState, itemCount: number): {
22
+ handled: boolean;
23
+ close: boolean;
24
+ select: boolean;
25
+ newState: SelectScreenState;
26
+ };
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Generic fullscreen selection component
3
+ * Used for Language, Provider, Model, Protocol selection
4
+ */
5
+ import { fg, style } from '../ansi.js';
6
+ // Primary color: #f02a30 (Codeep red)
7
+ const PRIMARY_COLOR = fg.rgb(240, 42, 48);
8
+ const PRIMARY_BRIGHT = fg.rgb(255, 80, 85);
9
+ /**
10
+ * Render a fullscreen selection screen
11
+ */
12
+ export function renderSelectScreen(screen, title, items, state, currentValue) {
13
+ const { width, height } = screen.getSize();
14
+ screen.clear();
15
+ // Title
16
+ const titleText = `═══ ${title} ═══`;
17
+ const titleX = Math.floor((width - titleText.length) / 2);
18
+ screen.write(titleX, 0, titleText, PRIMARY_COLOR + style.bold);
19
+ // Calculate visible items with scrolling
20
+ const startY = 2;
21
+ const maxVisible = height - 5; // Leave room for title and footer
22
+ let scrollOffset = 0;
23
+ if (items.length > maxVisible) {
24
+ // Keep selected item in view
25
+ if (state.selectedIndex >= maxVisible - 2) {
26
+ scrollOffset = Math.min(state.selectedIndex - Math.floor(maxVisible / 2), items.length - maxVisible);
27
+ }
28
+ }
29
+ const visibleItems = items.slice(scrollOffset, scrollOffset + maxVisible);
30
+ for (let i = 0; i < visibleItems.length; i++) {
31
+ const item = visibleItems[i];
32
+ const actualIndex = scrollOffset + i;
33
+ const isSelected = actualIndex === state.selectedIndex;
34
+ const isCurrent = item.key === currentValue;
35
+ const y = startY + i;
36
+ // Selection indicator
37
+ const prefix = isSelected ? '► ' : ' ';
38
+ // Current value indicator
39
+ const currentIndicator = isCurrent ? ' ✓' : '';
40
+ // Label
41
+ const labelColor = isSelected ? PRIMARY_BRIGHT + style.bold : isCurrent ? fg.green : fg.white;
42
+ screen.write(2, y, prefix, isSelected ? PRIMARY_COLOR : '');
43
+ screen.write(4, y, item.label + currentIndicator, labelColor);
44
+ // Description (if any)
45
+ if (item.description && isSelected) {
46
+ const descX = Math.max(4 + item.label.length + currentIndicator.length + 2, 30);
47
+ if (descX + item.description.length < width - 2) {
48
+ screen.write(descX, y, item.description, fg.gray);
49
+ }
50
+ }
51
+ }
52
+ // Scroll indicators
53
+ if (scrollOffset > 0) {
54
+ screen.write(width - 3, startY, '▲', fg.gray);
55
+ }
56
+ if (scrollOffset + maxVisible < items.length) {
57
+ screen.write(width - 3, startY + maxVisible - 1, '▼', fg.gray);
58
+ }
59
+ // Footer
60
+ const footerY = height - 1;
61
+ screen.write(2, footerY, '↑/↓ Navigate | Enter Select | Esc Cancel', fg.gray);
62
+ screen.showCursor(false);
63
+ screen.fullRender();
64
+ }
65
+ /**
66
+ * Handle selection screen key
67
+ */
68
+ export function handleSelectKey(key, state, itemCount) {
69
+ const newState = { ...state };
70
+ if (key === 'escape') {
71
+ return { handled: true, close: true, select: false, newState };
72
+ }
73
+ if (key === 'up') {
74
+ newState.selectedIndex = Math.max(0, state.selectedIndex - 1);
75
+ return { handled: true, close: false, select: false, newState };
76
+ }
77
+ if (key === 'down') {
78
+ newState.selectedIndex = Math.min(itemCount - 1, state.selectedIndex + 1);
79
+ return { handled: true, close: false, select: false, newState };
80
+ }
81
+ if (key === 'enter') {
82
+ return { handled: true, close: true, select: true, newState };
83
+ }
84
+ if (key === 'pageup') {
85
+ newState.selectedIndex = Math.max(0, state.selectedIndex - 10);
86
+ return { handled: true, close: false, select: false, newState };
87
+ }
88
+ if (key === 'pagedown') {
89
+ newState.selectedIndex = Math.min(itemCount - 1, state.selectedIndex + 10);
90
+ return { handled: true, close: false, select: false, newState };
91
+ }
92
+ if (key === 'home') {
93
+ newState.selectedIndex = 0;
94
+ return { handled: true, close: false, select: false, newState };
95
+ }
96
+ if (key === 'end') {
97
+ newState.selectedIndex = itemCount - 1;
98
+ return { handled: true, close: false, select: false, newState };
99
+ }
100
+ return { handled: false, close: false, select: false, newState };
101
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Settings screen component
3
+ */
4
+ import { Screen } from '../Screen';
5
+ export interface SettingItem {
6
+ key: string;
7
+ label: string;
8
+ getValue: () => string | number | boolean;
9
+ type: 'number' | 'select';
10
+ min?: number;
11
+ max?: number;
12
+ step?: number;
13
+ options?: {
14
+ value: string | number | boolean;
15
+ label: string;
16
+ }[];
17
+ }
18
+ export declare const SETTINGS: SettingItem[];
19
+ export interface SettingsState {
20
+ selectedIndex: number;
21
+ editing: boolean;
22
+ editValue: string;
23
+ }
24
+ /**
25
+ * Render settings screen
26
+ */
27
+ export declare function renderSettingsScreen(screen: Screen, state: SettingsState, hasWriteAccess: boolean, hasProjectContext: boolean): void;
28
+ /**
29
+ * Handle settings key
30
+ * Returns: { handled: boolean, close: boolean, notify?: string }
31
+ */
32
+ export declare function handleSettingsKey(key: string, ctrl: boolean, state: SettingsState): {
33
+ handled: boolean;
34
+ close: boolean;
35
+ notify?: string;
36
+ newState: SettingsState;
37
+ };