snow-ai 0.3.23 → 0.3.25

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.
@@ -1,6 +1,7 @@
1
1
  import React, { useState, useMemo } from 'react';
2
2
  import { Box, Text } from 'ink';
3
3
  import SelectInput from 'ink-select-input';
4
+ import { isSensitiveCommand } from '../../utils/sensitiveCommandManager.js';
4
5
  // Helper function to format argument values with truncation
5
6
  function formatArgumentValue(value, maxLength = 100) {
6
7
  if (value === null || value === undefined) {
@@ -35,11 +36,28 @@ function formatArgumentsAsTree(args, toolName) {
35
36
  return keys.map((key, index) => ({
36
37
  key,
37
38
  value: formatArgumentValue(args[key]),
38
- isLast: index === keys.length - 1
39
+ isLast: index === keys.length - 1,
39
40
  }));
40
41
  }
41
- export default function ToolConfirmation({ toolName, toolArguments, allTools, onConfirm }) {
42
+ export default function ToolConfirmation({ toolName, toolArguments, allTools, onConfirm, }) {
42
43
  const [hasSelected, setHasSelected] = useState(false);
44
+ // Check if this is a sensitive command (for terminal-execute)
45
+ const sensitiveCommandCheck = useMemo(() => {
46
+ if (toolName !== 'terminal-execute' || !toolArguments) {
47
+ return { isSensitive: false };
48
+ }
49
+ try {
50
+ const parsed = JSON.parse(toolArguments);
51
+ const command = parsed.command;
52
+ if (command && typeof command === 'string') {
53
+ return isSensitiveCommand(command);
54
+ }
55
+ }
56
+ catch {
57
+ // Ignore parse errors
58
+ }
59
+ return { isSensitive: false };
60
+ }, [toolName, toolArguments]);
43
61
  // Parse and format tool arguments for display (single tool)
44
62
  const formattedArgs = useMemo(() => {
45
63
  if (!toolArguments)
@@ -61,31 +79,38 @@ export default function ToolConfirmation({ toolName, toolArguments, allTools, on
61
79
  const parsed = JSON.parse(tool.function.arguments);
62
80
  return {
63
81
  name: tool.function.name,
64
- args: formatArgumentsAsTree(parsed, tool.function.name)
82
+ args: formatArgumentsAsTree(parsed, tool.function.name),
65
83
  };
66
84
  }
67
85
  catch {
68
86
  return {
69
87
  name: tool.function.name,
70
- args: []
88
+ args: [],
71
89
  };
72
90
  }
73
91
  });
74
92
  }, [allTools]);
75
- const items = [
76
- {
77
- label: 'Approve (once)',
78
- value: 'approve'
79
- },
80
- {
81
- label: 'Always approve this tool',
82
- value: 'approve_always'
83
- },
84
- {
85
- label: 'Reject (end session)',
86
- value: 'reject'
93
+ // Conditionally show "Always approve" based on sensitive command check
94
+ const items = useMemo(() => {
95
+ const baseItems = [
96
+ {
97
+ label: 'Approve (once)',
98
+ value: 'approve',
99
+ },
100
+ ];
101
+ // Only show "Always approve" if NOT a sensitive command
102
+ if (!sensitiveCommandCheck.isSensitive) {
103
+ baseItems.push({
104
+ label: 'Always approve this tool',
105
+ value: 'approve_always',
106
+ });
87
107
  }
88
- ];
108
+ baseItems.push({
109
+ label: 'Reject (end session)',
110
+ value: 'reject',
111
+ });
112
+ return baseItems;
113
+ }, [sensitiveCommandCheck.isSensitive]);
89
114
  const handleSelect = (item) => {
90
115
  if (!hasSelected) {
91
116
  setHasSelected(true);
@@ -98,8 +123,21 @@ export default function ToolConfirmation({ toolName, toolArguments, allTools, on
98
123
  !formattedAllTools && (React.createElement(React.Fragment, null,
99
124
  React.createElement(Box, { marginBottom: 1 },
100
125
  React.createElement(Text, null,
101
- "Tool: ",
126
+ "Tool:",
127
+ ' ',
102
128
  React.createElement(Text, { bold: true, color: "cyan" }, toolName))),
129
+ sensitiveCommandCheck.isSensitive && (React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
130
+ React.createElement(Box, { marginBottom: 1 },
131
+ React.createElement(Text, { bold: true, color: "red" }, "SENSITIVE COMMAND DETECTED")),
132
+ React.createElement(Box, { flexDirection: "column", gap: 0 },
133
+ React.createElement(Box, null,
134
+ React.createElement(Text, { dimColor: true }, "Pattern: "),
135
+ React.createElement(Text, { color: "magenta", bold: true }, sensitiveCommandCheck.matchedCommand?.pattern)),
136
+ React.createElement(Box, { marginTop: 0 },
137
+ React.createElement(Text, { dimColor: true }, "Reason: "),
138
+ React.createElement(Text, { color: "white" }, sensitiveCommandCheck.matchedCommand?.description))),
139
+ React.createElement(Box, { marginTop: 1, paddingX: 1, paddingY: 0 },
140
+ React.createElement(Text, { color: "yellow", italic: true }, "This command requires confirmation even in YOLO/Always-Approved mode")))),
103
141
  formattedArgs && formattedArgs.length > 0 && (React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
104
142
  React.createElement(Text, { dimColor: true }, "Arguments:"),
105
143
  formattedArgs.map((arg, index) => (React.createElement(Box, { key: index, flexDirection: "column" },
@@ -107,12 +145,14 @@ export default function ToolConfirmation({ toolName, toolArguments, allTools, on
107
145
  arg.isLast ? '└─' : '├─',
108
146
  " ",
109
147
  arg.key,
110
- ": ",
148
+ ":",
149
+ ' ',
111
150
  React.createElement(Text, { color: "white" }, arg.value))))))))),
112
151
  formattedAllTools && (React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
113
152
  React.createElement(Box, { marginBottom: 1 },
114
153
  React.createElement(Text, null,
115
- "Tools: ",
154
+ "Tools:",
155
+ ' ',
116
156
  React.createElement(Text, { bold: true, color: "cyan" },
117
157
  formattedAllTools.length,
118
158
  " tools in parallel"))),
@@ -125,11 +165,12 @@ export default function ToolConfirmation({ toolName, toolArguments, allTools, on
125
165
  arg.isLast ? '└─' : '├─',
126
166
  " ",
127
167
  arg.key,
128
- ": ",
168
+ ":",
169
+ ' ',
129
170
  React.createElement(Text, { color: "white" }, arg.value))))))))))),
130
171
  React.createElement(Box, { marginBottom: 1 },
131
172
  React.createElement(Text, { dimColor: true }, "Select action:")),
132
- !hasSelected && (React.createElement(SelectInput, { items: items, onSelect: handleSelect })),
173
+ !hasSelected && React.createElement(SelectInput, { items: items, onSelect: handleSelect }),
133
174
  hasSelected && (React.createElement(Box, null,
134
175
  React.createElement(Text, { color: "green" }, "Confirmed")))));
135
176
  }
@@ -13,7 +13,6 @@ import MarkdownRenderer from '../components/MarkdownRenderer.js';
13
13
  import ToolConfirmation from '../components/ToolConfirmation.js';
14
14
  import DiffViewer from '../components/DiffViewer.js';
15
15
  import ToolResultPreview from '../components/ToolResultPreview.js';
16
- import TodoTree from '../components/TodoTree.js';
17
16
  import FileRollbackConfirmation from '../components/FileRollbackConfirmation.js';
18
17
  import ShimmerText from '../components/ShimmerText.js';
19
18
  import { getOpenAiConfig } from '../../utils/apiConfig.js';
@@ -50,7 +49,6 @@ import '../../utils/commands/todoPicker.js';
50
49
  export default function ChatScreen({ skipWelcome }) {
51
50
  const [messages, setMessages] = useState([]);
52
51
  const [isSaving] = useState(false);
53
- const [currentTodos, setCurrentTodos] = useState([]);
54
52
  const [pendingMessages, setPendingMessages] = useState([]);
55
53
  const pendingMessagesRef = useRef([]);
56
54
  const hasAttemptedAutoVscodeConnect = useRef(false);
@@ -570,7 +568,6 @@ export default function ChatScreen({ skipWelcome }) {
570
568
  saveMessage,
571
569
  setMessages,
572
570
  setStreamTokenCount: streamingState.setStreamTokenCount,
573
- setCurrentTodos,
574
571
  requestToolConfirmation,
575
572
  isToolAutoApproved,
576
573
  addMultipleToAlwaysApproved,
@@ -754,7 +751,6 @@ export default function ChatScreen({ skipWelcome }) {
754
751
  saveMessage,
755
752
  setMessages,
756
753
  setStreamTokenCount: streamingState.setStreamTokenCount,
757
- setCurrentTodos,
758
754
  requestToolConfirmation,
759
755
  isToolAutoApproved,
760
756
  addMultipleToAlwaysApproved,
@@ -939,7 +935,7 @@ export default function ChatScreen({ skipWelcome }) {
939
935
  React.createElement(Text, { color: "gray", dimColor: true },
940
936
  "\u2514\u2500 ",
941
937
  message.commandName),
942
- message.content && (React.createElement(Text, { color: "white" }, message.content)))) : message.showTodoTree ? (React.createElement(TodoTree, { todos: currentTodos })) : (React.createElement(React.Fragment, null,
938
+ message.content && (React.createElement(Text, { color: "white" }, message.content)))) : (React.createElement(React.Fragment, null,
943
939
  message.role === 'user' || isToolMessage ? (React.createElement(Text, { color: message.role === 'user'
944
940
  ? 'gray'
945
941
  : message.content.startsWith('⚡')
@@ -455,7 +455,7 @@ export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
455
455
  "Anthropic Beta:"),
456
456
  React.createElement(Box, { marginLeft: 3 },
457
457
  React.createElement(Text, { color: "gray" },
458
- anthropicBeta ? ' Enabled' : ' Disabled',
458
+ anthropicBeta ? '[✓] Enabled' : '[ ] Disabled',
459
459
  " (Press Enter to toggle)"))));
460
460
  case 'thinkingEnabled':
461
461
  return (React.createElement(Box, { key: field, flexDirection: "column" },
@@ -464,7 +464,7 @@ export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
464
464
  "Thinking Enabled:"),
465
465
  React.createElement(Box, { marginLeft: 3 },
466
466
  React.createElement(Text, { color: "gray" },
467
- thinkingEnabled ? ' Enabled' : ' Disabled',
467
+ thinkingEnabled ? '[✓] Enabled' : '[ ] Disabled',
468
468
  " (Press Enter to toggle)"))));
469
469
  case 'thinkingBudgetTokens':
470
470
  return (React.createElement(Box, { key: field, flexDirection: "column" },
@@ -484,7 +484,7 @@ export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
484
484
  "Gemini Thinking Enabled:"),
485
485
  React.createElement(Box, { marginLeft: 3 },
486
486
  React.createElement(Text, { color: "gray" },
487
- geminiThinkingEnabled ? ' Enabled' : ' Disabled',
487
+ geminiThinkingEnabled ? '[✓] Enabled' : '[ ] Disabled',
488
488
  " (Press Enter to toggle)"))));
489
489
  case 'geminiThinkingBudget':
490
490
  return (React.createElement(Box, { key: field, flexDirection: "column" },
@@ -504,8 +504,9 @@ export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
504
504
  "Responses Reasoning Enabled:"),
505
505
  React.createElement(Box, { marginLeft: 3 },
506
506
  React.createElement(Text, { color: "gray" },
507
- responsesReasoningEnabled ? ' Enabled' : ' Disabled',
508
- " (Press Enter to toggle)"))));
507
+ responsesReasoningEnabled ? '[✓] Enabled' : '[ ] Disabled',
508
+ ' ',
509
+ "(Press Enter to toggle)"))));
509
510
  case 'responsesReasoningEffort':
510
511
  return (React.createElement(Box, { key: field, flexDirection: "column" },
511
512
  React.createElement(Text, { color: isActive ? 'green' : 'white' },
@@ -964,7 +965,8 @@ export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
964
965
  currentField === 'advancedModel' && 'Advanced Model',
965
966
  currentField === 'basicModel' && 'Basic Model',
966
967
  currentField === 'compactModelName' && 'Compact Model',
967
- currentField === 'responsesReasoningEffort' && 'Responses Reasoning Effort',
968
+ currentField === 'responsesReasoningEffort' &&
969
+ 'Responses Reasoning Effort',
968
970
  ":"),
969
971
  React.createElement(Box, { marginLeft: 3, marginTop: 1 },
970
972
  currentField === 'profile' && (React.createElement(Box, { flexDirection: "column" },
@@ -339,7 +339,6 @@ export default function HeadlessModeScreen({ prompt, onComplete }) {
339
339
  saveMessage,
340
340
  setMessages,
341
341
  setStreamTokenCount: streamingState.setStreamTokenCount,
342
- setCurrentTodos: () => { }, // No-op in headless mode
343
342
  requestToolConfirmation,
344
343
  isToolAutoApproved,
345
344
  addMultipleToAlwaysApproved,
@@ -4,5 +4,5 @@ type Props = {
4
4
  onSave: () => void;
5
5
  inlineMode?: boolean;
6
6
  };
7
- export default function ProxyConfigScreen({ onBack, onSave, inlineMode }: Props): React.JSX.Element;
7
+ export default function ProxyConfigScreen({ onBack, onSave, inlineMode, }: Props): React.JSX.Element;
8
8
  export {};
@@ -4,7 +4,7 @@ import Gradient from 'ink-gradient';
4
4
  import { Alert } from '@inkjs/ui';
5
5
  import TextInput from 'ink-text-input';
6
6
  import { getProxyConfig, updateProxyConfig, } from '../../utils/apiConfig.js';
7
- export default function ProxyConfigScreen({ onBack, onSave, inlineMode = false }) {
7
+ export default function ProxyConfigScreen({ onBack, onSave, inlineMode = false, }) {
8
8
  const [enabled, setEnabled] = useState(false);
9
9
  const [port, setPort] = useState('7890');
10
10
  const [browserPath, setBrowserPath] = useState('');
@@ -86,7 +86,7 @@ export default function ProxyConfigScreen({ onBack, onSave, inlineMode = false }
86
86
  }
87
87
  });
88
88
  return (React.createElement(Box, { flexDirection: "column", padding: 1 },
89
- !inlineMode && (React.createElement(Box, { marginBottom: 1, borderStyle: "double", borderColor: "cyan", paddingX: 2, paddingY: 1 },
89
+ !inlineMode && (React.createElement(Box, { marginBottom: 1, borderStyle: "double", borderColor: 'cyan', paddingX: 2, paddingY: 1 },
90
90
  React.createElement(Box, { flexDirection: "column" },
91
91
  React.createElement(Gradient, { name: "rainbow" }, "Proxy Configuration"),
92
92
  React.createElement(Text, { color: "gray", dimColor: true }, "Configure system proxy for web search and fetch")))),
@@ -98,7 +98,7 @@ export default function ProxyConfigScreen({ onBack, onSave, inlineMode = false }
98
98
  "Enable Proxy:"),
99
99
  React.createElement(Box, { marginLeft: 3 },
100
100
  React.createElement(Text, { color: "gray" },
101
- enabled ? ' Enabled' : ' Disabled',
101
+ enabled ? '[✓] Enabled' : '[ ] Disabled',
102
102
  " (Press Enter to toggle)")))),
103
103
  React.createElement(Box, { marginBottom: 1 },
104
104
  React.createElement(Box, { flexDirection: "column" },
@@ -131,13 +131,13 @@ export default function ProxyConfigScreen({ onBack, onSave, inlineMode = false }
131
131
  "Browser Path Examples: ",
132
132
  React.createElement(Newline, null),
133
133
  React.createElement(Text, { color: 'blue' }, "\u2022 Windows: C:\\Program Files(x86)\\Microsoft\\Edge\\Application\\msedge.exe"),
134
- " ",
134
+ ' ',
135
135
  React.createElement(Newline, null),
136
136
  React.createElement(Text, { color: 'green' }, "\u2022 macOS: /Applications/Google Chrome.app/Contents/MacOS/Google Chrome"),
137
- " ",
137
+ ' ',
138
138
  React.createElement(Newline, null),
139
139
  React.createElement(Text, { color: 'yellow' }, "\u2022 Linux: /usr/bin/chromium-browser"),
140
- " ",
140
+ ' ',
141
141
  React.createElement(Newline, null),
142
142
  "Leave empty to auto-detect system browser (Edge/Chrome)"))));
143
143
  }
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ type Props = {
3
+ onBack: () => void;
4
+ inlineMode?: boolean;
5
+ };
6
+ export default function SensitiveCommandConfigScreen({ onBack, inlineMode, }: Props): React.JSX.Element;
7
+ export {};
@@ -0,0 +1,262 @@
1
+ import React, { useState, useCallback, useEffect } from 'react';
2
+ import { Box, Text, useInput } from 'ink';
3
+ import TextInput from 'ink-text-input';
4
+ import { Alert } from '@inkjs/ui';
5
+ import { getAllSensitiveCommands, toggleSensitiveCommand, addSensitiveCommand, removeSensitiveCommand, resetToDefaults, } from '../../utils/sensitiveCommandManager.js';
6
+ // Focus event handling
7
+ const focusEventTokenRegex = /(?:\x1b)?\[[0-9;]*[IO]/g;
8
+ const isFocusEventInput = (value) => {
9
+ if (!value)
10
+ return false;
11
+ if (value === '\x1b[I' ||
12
+ value === '\x1b[O' ||
13
+ value === '[I' ||
14
+ value === '[O') {
15
+ return true;
16
+ }
17
+ const trimmed = value.trim();
18
+ if (!trimmed)
19
+ return false;
20
+ const tokens = trimmed.match(focusEventTokenRegex);
21
+ if (!tokens)
22
+ return false;
23
+ const normalized = trimmed.replace(/\s+/g, '');
24
+ const tokensCombined = tokens.join('');
25
+ return tokensCombined === normalized;
26
+ };
27
+ const stripFocusArtifacts = (value) => {
28
+ if (!value)
29
+ return '';
30
+ return value
31
+ .replace(/\x1b\[[0-9;]*[IO]/g, '')
32
+ .replace(/\[[0-9;]*[IO]/g, '')
33
+ .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
34
+ };
35
+ export default function SensitiveCommandConfigScreen({ onBack, inlineMode = false, }) {
36
+ const [commands, setCommands] = useState([]);
37
+ const [selectedIndex, setSelectedIndex] = useState(0);
38
+ const [viewMode, setViewMode] = useState('list');
39
+ const [showSuccess, setShowSuccess] = useState(false);
40
+ const [successMessage, setSuccessMessage] = useState('');
41
+ // Confirmation states
42
+ const [confirmDelete, setConfirmDelete] = useState(false);
43
+ const [confirmReset, setConfirmReset] = useState(false);
44
+ // Add custom command fields
45
+ const [customPattern, setCustomPattern] = useState('');
46
+ const [customDescription, setCustomDescription] = useState('');
47
+ const [addField, setAddField] = useState('pattern');
48
+ // Load commands
49
+ const loadCommands = useCallback(() => {
50
+ const allCommands = getAllSensitiveCommands();
51
+ setCommands(allCommands);
52
+ }, []);
53
+ useEffect(() => {
54
+ loadCommands();
55
+ }, [loadCommands]);
56
+ // Handle list view input
57
+ const handleListInput = useCallback((input, key) => {
58
+ if (key.escape) {
59
+ // Cancel any pending confirmations
60
+ if (confirmDelete || confirmReset) {
61
+ setConfirmDelete(false);
62
+ setConfirmReset(false);
63
+ return;
64
+ }
65
+ onBack();
66
+ return;
67
+ }
68
+ if (key.upArrow) {
69
+ setSelectedIndex(prev => Math.max(0, prev - 1));
70
+ // Clear confirmations when navigating
71
+ setConfirmDelete(false);
72
+ setConfirmReset(false);
73
+ }
74
+ else if (key.downArrow) {
75
+ setSelectedIndex(prev => Math.min(commands.length - 1, prev + 1));
76
+ // Clear confirmations when navigating
77
+ setConfirmDelete(false);
78
+ setConfirmReset(false);
79
+ }
80
+ else if (input === ' ') {
81
+ // Toggle command
82
+ const cmd = commands[selectedIndex];
83
+ if (cmd) {
84
+ toggleSensitiveCommand(cmd.id);
85
+ loadCommands();
86
+ setSuccessMessage(cmd.enabled
87
+ ? `Disabled: ${cmd.pattern}`
88
+ : `Enabled: ${cmd.pattern}`);
89
+ setShowSuccess(true);
90
+ setTimeout(() => setShowSuccess(false), 2000);
91
+ }
92
+ }
93
+ else if (input === 'a' || input === 'A') {
94
+ // Add custom command
95
+ setViewMode('add');
96
+ setCustomPattern('');
97
+ setCustomDescription('');
98
+ setAddField('pattern');
99
+ }
100
+ else if (input === 'd' || input === 'D') {
101
+ // Delete custom command - requires confirmation
102
+ const cmd = commands[selectedIndex];
103
+ if (cmd && !cmd.isPreset) {
104
+ if (!confirmDelete) {
105
+ // First press - ask for confirmation
106
+ setConfirmDelete(true);
107
+ setConfirmReset(false);
108
+ }
109
+ else {
110
+ // Second press - execute delete
111
+ removeSensitiveCommand(cmd.id);
112
+ loadCommands();
113
+ setSelectedIndex(prev => Math.min(prev, commands.length - 2));
114
+ setSuccessMessage(`Deleted: ${cmd.pattern}`);
115
+ setShowSuccess(true);
116
+ setTimeout(() => setShowSuccess(false), 2000);
117
+ setConfirmDelete(false);
118
+ }
119
+ }
120
+ }
121
+ else if (input === 'r' || input === 'R') {
122
+ // Reset to defaults - requires confirmation
123
+ if (!confirmReset) {
124
+ // First press - ask for confirmation
125
+ setConfirmReset(true);
126
+ setConfirmDelete(false);
127
+ }
128
+ else {
129
+ // Second press - execute reset
130
+ resetToDefaults();
131
+ loadCommands();
132
+ setSelectedIndex(0);
133
+ setSuccessMessage('Reset to default commands');
134
+ setShowSuccess(true);
135
+ setTimeout(() => setShowSuccess(false), 2000);
136
+ setConfirmReset(false);
137
+ }
138
+ }
139
+ }, [
140
+ commands,
141
+ selectedIndex,
142
+ onBack,
143
+ loadCommands,
144
+ confirmDelete,
145
+ confirmReset,
146
+ ]);
147
+ // Handle add view input
148
+ const handleAddInput = useCallback((_input, key) => {
149
+ if (key.escape) {
150
+ setViewMode('list');
151
+ return;
152
+ }
153
+ if (key.tab) {
154
+ setAddField(prev => (prev === 'pattern' ? 'description' : 'pattern'));
155
+ }
156
+ }, []);
157
+ // Use input hook
158
+ useInput((input, key) => {
159
+ if (viewMode === 'list') {
160
+ handleListInput(input, key);
161
+ }
162
+ else {
163
+ handleAddInput(input, key);
164
+ }
165
+ }, { isActive: true });
166
+ // Handle pattern input change
167
+ const handlePatternChange = useCallback((value) => {
168
+ if (!isFocusEventInput(value)) {
169
+ setCustomPattern(stripFocusArtifacts(value));
170
+ }
171
+ }, []);
172
+ // Handle description input change
173
+ const handleDescriptionChange = useCallback((value) => {
174
+ if (!isFocusEventInput(value)) {
175
+ setCustomDescription(stripFocusArtifacts(value));
176
+ }
177
+ }, []);
178
+ // Handle add submit
179
+ const handleAddSubmit = useCallback(() => {
180
+ if (addField === 'pattern') {
181
+ setAddField('description');
182
+ }
183
+ else {
184
+ // Submit
185
+ if (customPattern.trim() && customDescription.trim()) {
186
+ try {
187
+ addSensitiveCommand(customPattern.trim(), customDescription.trim());
188
+ loadCommands();
189
+ setViewMode('list');
190
+ setSuccessMessage(`Added: ${customPattern}`);
191
+ setShowSuccess(true);
192
+ setTimeout(() => setShowSuccess(false), 2000);
193
+ }
194
+ catch (error) {
195
+ // Handle error
196
+ }
197
+ }
198
+ }
199
+ }, [addField, customPattern, customDescription, loadCommands]);
200
+ if (viewMode === 'add') {
201
+ return (React.createElement(Box, { flexDirection: "column", paddingX: inlineMode ? 0 : 2, paddingY: 1 },
202
+ React.createElement(Text, { bold: true, color: "cyan" }, "Add Custom Sensitive Command"),
203
+ React.createElement(Box, { marginTop: 1 }),
204
+ React.createElement(Text, { dimColor: true }, "Pattern (supports wildcards, e.g., \"rm*\"):"),
205
+ React.createElement(Box, null,
206
+ React.createElement(Text, { color: addField === 'pattern' ? 'cyan' : 'gray' }, "\u276F "),
207
+ React.createElement(TextInput, { value: customPattern, onChange: handlePatternChange, onSubmit: handleAddSubmit, focus: addField === 'pattern' })),
208
+ React.createElement(Box, { marginTop: 1 }),
209
+ React.createElement(Text, { dimColor: true }, "Description:"),
210
+ React.createElement(Box, null,
211
+ React.createElement(Text, { color: addField === 'description' ? 'cyan' : 'gray' }, "\u276F "),
212
+ React.createElement(TextInput, { value: customDescription, onChange: handleDescriptionChange, onSubmit: handleAddSubmit, focus: addField === 'description' })),
213
+ React.createElement(Box, { marginTop: 1 }),
214
+ React.createElement(Text, { dimColor: true }, "Tab: Switch \u2022 Enter: Submit \u2022 Esc: Cancel")));
215
+ }
216
+ // Calculate visible range for scrolling
217
+ const viewportHeight = 13;
218
+ const startIndex = Math.max(0, selectedIndex - Math.floor(viewportHeight / 2));
219
+ const endIndex = Math.min(commands.length, startIndex + viewportHeight);
220
+ const adjustedStart = Math.max(0, endIndex - viewportHeight);
221
+ const selectedCmd = commands[selectedIndex];
222
+ return (React.createElement(Box, { flexDirection: "column", paddingX: inlineMode ? 0 : 2, paddingY: 1 },
223
+ React.createElement(Text, { bold: true, color: "cyan" }, "Sensitive Command Protection"),
224
+ React.createElement(Text, { dimColor: true }, "Configure commands that require confirmation even in YOLO/Always-Approved mode"),
225
+ showSuccess && (React.createElement(Box, { marginTop: 1 },
226
+ React.createElement(Alert, { variant: "success" }, successMessage))),
227
+ React.createElement(Box, { marginTop: 1 }),
228
+ commands.length === 0 ? (React.createElement(Text, { dimColor: true }, "No commands configured")) : (commands.map((cmd, index) => {
229
+ // Only render items in the visible viewport
230
+ if (index < adjustedStart || index >= endIndex) {
231
+ return null;
232
+ }
233
+ return (React.createElement(Text, { key: cmd.id, color: selectedIndex === index
234
+ ? 'cyan'
235
+ : cmd.enabled
236
+ ? 'white'
237
+ : 'gray', bold: selectedIndex === index, dimColor: !cmd.enabled },
238
+ selectedIndex === index ? '❯ ' : ' ',
239
+ "[",
240
+ cmd.enabled ? '✓' : ' ',
241
+ "]",
242
+ ' ',
243
+ cmd.pattern,
244
+ !cmd.isPreset && React.createElement(Text, { color: "yellow" }, " (custom)")));
245
+ })),
246
+ React.createElement(Box, { marginTop: 1 }),
247
+ selectedCmd && !confirmDelete && !confirmReset && (React.createElement(Text, { dimColor: true },
248
+ selectedCmd.description,
249
+ " (",
250
+ selectedCmd.enabled ? 'Enabled' : 'Disabled',
251
+ ")",
252
+ !selectedCmd.isPreset && ' [Custom]')),
253
+ confirmDelete && selectedCmd && (React.createElement(Text, { bold: true, color: "yellow" },
254
+ "\u26A0\uFE0F Press D again to confirm deletion of \"",
255
+ selectedCmd.pattern,
256
+ "\"")),
257
+ confirmReset && (React.createElement(Text, { bold: true, color: "yellow" }, "\u26A0\uFE0F Press R again to confirm reset to default commands")),
258
+ React.createElement(Box, { marginTop: 1 }),
259
+ React.createElement(Text, { dimColor: true }, confirmDelete || confirmReset
260
+ ? 'Press the same key again to confirm • Esc: Cancel'
261
+ : '↑↓: Navigate • Space: Toggle • A: Add • D: Delete • R: Reset • Esc: Back')));
262
+ }
@@ -353,7 +353,7 @@ export default function SubAgentConfigScreen({ onBack, onSave, inlineMode = fals
353
353
  return (React.createElement(Box, { key: tool },
354
354
  React.createElement(Text, { color: isCurrentTool ? 'cyan' : 'white', bold: isCurrentTool },
355
355
  isCurrentTool ? '❯ ' : ' ',
356
- selectedTools.has(tool) ? '' : '',
356
+ selectedTools.has(tool) ? '[✓]' : '[ ]',
357
357
  " ",
358
358
  tool)));
359
359
  })))));
@@ -9,6 +9,7 @@ import ConfigScreen from './ConfigScreen.js';
9
9
  import ProxyConfigScreen from './ProxyConfigScreen.js';
10
10
  import SubAgentConfigScreen from './SubAgentConfigScreen.js';
11
11
  import SubAgentListScreen from './SubAgentListScreen.js';
12
+ import SensitiveCommandConfigScreen from './SensitiveCommandConfigScreen.js';
12
13
  export default function WelcomeScreen({ version = '1.0.0', onMenuSelect, }) {
13
14
  const [infoText, setInfoText] = useState('Start a new chat conversation');
14
15
  const [inlineView, setInlineView] = useState('menu');
@@ -54,6 +55,11 @@ export default function WelcomeScreen({ version = '1.0.0', onMenuSelect, }) {
54
55
  value: 'subagent',
55
56
  infoText: 'Configure sub-agents with custom tool permissions',
56
57
  },
58
+ {
59
+ label: 'Sensitive Commands',
60
+ value: 'sensitive-commands',
61
+ infoText: 'Configure commands that require confirmation even in YOLO mode',
62
+ },
57
63
  {
58
64
  label: 'Exit',
59
65
  value: 'exit',
@@ -75,6 +81,9 @@ export default function WelcomeScreen({ version = '1.0.0', onMenuSelect, }) {
75
81
  else if (value === 'subagent') {
76
82
  setInlineView('subagent-list');
77
83
  }
84
+ else if (value === 'sensitive-commands') {
85
+ setInlineView('sensitive-commands');
86
+ }
78
87
  else {
79
88
  // Pass through to parent for other actions (chat, exit, etc.)
80
89
  onMenuSelect?.(value);
@@ -107,11 +116,11 @@ export default function WelcomeScreen({ version = '1.0.0', onMenuSelect, }) {
107
116
  const handler = setTimeout(() => {
108
117
  stdout.write(ansiEscapes.clearTerminal);
109
118
  setRemountKey(prev => prev + 1); // Force re-render
110
- }, 0); // Wait for resize to stabilize
119
+ }, 200); // Add debounce delay to avoid rapid re-renders
111
120
  return () => {
112
121
  clearTimeout(handler);
113
122
  };
114
- }, [terminalWidth, stdout]);
123
+ }, [terminalWidth]); // Remove stdout from dependencies to avoid loops
115
124
  return (React.createElement(Box, { flexDirection: "column", width: terminalWidth },
116
125
  React.createElement(Static, { key: remountKey, items: [
117
126
  React.createElement(Box, { key: "welcome-header", flexDirection: "row", paddingLeft: 2, paddingTop: 1, paddingBottom: 0, width: terminalWidth },
@@ -137,5 +146,7 @@ export default function WelcomeScreen({ version = '1.0.0', onMenuSelect, }) {
137
146
  inlineView === 'subagent-add' && (React.createElement(Box, { paddingX: 1 },
138
147
  React.createElement(SubAgentConfigScreen, { onBack: () => setInlineView('subagent-list'), onSave: handleSubAgentSave, inlineMode: true }))),
139
148
  inlineView === 'subagent-edit' && (React.createElement(Box, { paddingX: 1 },
140
- React.createElement(SubAgentConfigScreen, { onBack: () => setInlineView('subagent-list'), onSave: handleSubAgentSave, agentId: editingAgentId, inlineMode: true })))));
149
+ React.createElement(SubAgentConfigScreen, { onBack: () => setInlineView('subagent-list'), onSave: handleSubAgentSave, agentId: editingAgentId, inlineMode: true }))),
150
+ inlineView === 'sensitive-commands' && (React.createElement(Box, { paddingX: 1 },
151
+ React.createElement(SensitiveCommandConfigScreen, { onBack: handleBackToMenu, inlineMode: true })))));
141
152
  }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Patch cli-highlight to gracefully handle unknown languages
3
+ * This must be loaded BEFORE any modules that use cli-highlight
4
+ */
5
+ export {};