flowmind 1.4.1 → 1.4.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/bin/flowmind.js +39 -7
- package/package.json +1 -1
- package/tui/app.jsx +9 -2
- package/tui/components/ChatPanel.jsx +3 -3
- package/tui/components/Sidebar.jsx +2 -1
package/bin/flowmind.js
CHANGED
|
@@ -1351,6 +1351,7 @@ program
|
|
|
1351
1351
|
.description('Launch enhanced TUI with split panels, skill browser, and dragon display')
|
|
1352
1352
|
.action(async () => {
|
|
1353
1353
|
let stdinWrapper = null;
|
|
1354
|
+
let stdinForwarder = null;
|
|
1354
1355
|
try {
|
|
1355
1356
|
// Register .jsx extension for CJS
|
|
1356
1357
|
require('module')._extensions['.jsx'] = require('module')._extensions['.js'];
|
|
@@ -1378,11 +1379,18 @@ program
|
|
|
1378
1379
|
}
|
|
1379
1380
|
return stdinWrapper;
|
|
1380
1381
|
};
|
|
1381
|
-
// Forward real stdin data to the wrapper
|
|
1382
|
+
// Forward real stdin data to the wrapper (store reference for cleanup)
|
|
1382
1383
|
if (realStdin.readable) {
|
|
1383
|
-
|
|
1384
|
-
if (!stdinWrapper.destroyed)
|
|
1385
|
-
|
|
1384
|
+
stdinForwarder = (chunk) => {
|
|
1385
|
+
if (!stdinWrapper.destroyed) {
|
|
1386
|
+
try {
|
|
1387
|
+
stdinWrapper.write(chunk);
|
|
1388
|
+
} catch (e) {
|
|
1389
|
+
// Ignore write-after-destroy errors
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
};
|
|
1393
|
+
realStdin.on('data', stdinForwarder);
|
|
1386
1394
|
}
|
|
1387
1395
|
|
|
1388
1396
|
const { unmount, waitUntilExit } = render(
|
|
@@ -1397,6 +1405,16 @@ program
|
|
|
1397
1405
|
console.log(chalk.yellow('Try running: npm install ink@3 react ink-text-input ink-spinner'));
|
|
1398
1406
|
}
|
|
1399
1407
|
} finally {
|
|
1408
|
+
// Clean up stdin listener to prevent leak
|
|
1409
|
+
if (stdinForwarder) {
|
|
1410
|
+
process.stdin.removeListener('data', stdinForwarder);
|
|
1411
|
+
}
|
|
1412
|
+
// Restore stdin to normal mode
|
|
1413
|
+
try {
|
|
1414
|
+
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
1415
|
+
process.stdin.setRawMode(false);
|
|
1416
|
+
}
|
|
1417
|
+
} catch (e) { /* ignore */ }
|
|
1400
1418
|
if (stdinWrapper && !stdinWrapper.destroyed) {
|
|
1401
1419
|
stdinWrapper.destroy();
|
|
1402
1420
|
}
|
|
@@ -1409,6 +1427,7 @@ program
|
|
|
1409
1427
|
.description('Launch real-time monitoring dashboard for MCP activity and events')
|
|
1410
1428
|
.action(async () => {
|
|
1411
1429
|
let stdinWrapper = null;
|
|
1430
|
+
let stdinForwarder = null;
|
|
1412
1431
|
try {
|
|
1413
1432
|
// Register .jsx extension for CJS
|
|
1414
1433
|
require('module')._extensions['.jsx'] = require('module')._extensions['.js'];
|
|
@@ -1437,9 +1456,14 @@ program
|
|
|
1437
1456
|
return stdinWrapper;
|
|
1438
1457
|
};
|
|
1439
1458
|
if (realStdin.readable) {
|
|
1440
|
-
|
|
1441
|
-
if (!stdinWrapper.destroyed)
|
|
1442
|
-
|
|
1459
|
+
stdinForwarder = (chunk) => {
|
|
1460
|
+
if (!stdinWrapper.destroyed) {
|
|
1461
|
+
try {
|
|
1462
|
+
stdinWrapper.write(chunk);
|
|
1463
|
+
} catch (e) { /* ignore write-after-destroy */ }
|
|
1464
|
+
}
|
|
1465
|
+
};
|
|
1466
|
+
realStdin.on('data', stdinForwarder);
|
|
1443
1467
|
}
|
|
1444
1468
|
|
|
1445
1469
|
const { unmount, waitUntilExit } = render(
|
|
@@ -1454,6 +1478,14 @@ program
|
|
|
1454
1478
|
console.log(chalk.yellow('Try running: npm install ink@3 react'));
|
|
1455
1479
|
}
|
|
1456
1480
|
} finally {
|
|
1481
|
+
if (stdinForwarder) {
|
|
1482
|
+
process.stdin.removeListener('data', stdinForwarder);
|
|
1483
|
+
}
|
|
1484
|
+
try {
|
|
1485
|
+
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
1486
|
+
process.stdin.setRawMode(false);
|
|
1487
|
+
}
|
|
1488
|
+
} catch (e) { /* ignore */ }
|
|
1457
1489
|
if (stdinWrapper && !stdinWrapper.destroyed) {
|
|
1458
1490
|
stdinWrapper.destroy();
|
|
1459
1491
|
}
|
package/package.json
CHANGED
package/tui/app.jsx
CHANGED
|
@@ -8,6 +8,7 @@ const StatusBar = require('./components/StatusBar.jsx');
|
|
|
8
8
|
function App({ flowmind }) {
|
|
9
9
|
const [results, setResults] = React.useState([]);
|
|
10
10
|
const [isProcessing, setIsProcessing] = React.useState(false);
|
|
11
|
+
const [focusPanel, setFocusPanel] = React.useState('chat'); // 'chat' | 'sidebar'
|
|
11
12
|
const mountedRef = React.useRef(true);
|
|
12
13
|
const { exit } = useApp();
|
|
13
14
|
|
|
@@ -15,11 +16,16 @@ function App({ flowmind }) {
|
|
|
15
16
|
return () => { mountedRef.current = false; };
|
|
16
17
|
}, []);
|
|
17
18
|
|
|
19
|
+
// Ctrl+C always exits; Tab switches focus between panels
|
|
18
20
|
useInput((input, key) => {
|
|
19
21
|
if (key.ctrl && input === 'c') exit();
|
|
22
|
+
if (key.tab) {
|
|
23
|
+
setFocusPanel(prev => prev === 'chat' ? 'sidebar' : 'chat');
|
|
24
|
+
}
|
|
20
25
|
});
|
|
21
26
|
|
|
22
27
|
const handleCommand = React.useCallback(async (input, addResponse) => {
|
|
28
|
+
if (!mountedRef.current) return;
|
|
23
29
|
setIsProcessing(true);
|
|
24
30
|
try {
|
|
25
31
|
const result = await flowmind.process(input);
|
|
@@ -41,6 +47,7 @@ function App({ flowmind }) {
|
|
|
41
47
|
}, [flowmind]);
|
|
42
48
|
|
|
43
49
|
const handleSkillSelect = React.useCallback((skill) => {
|
|
50
|
+
if (!mountedRef.current) return;
|
|
44
51
|
try {
|
|
45
52
|
setResults(prev => [...prev, {
|
|
46
53
|
type: 'result',
|
|
@@ -55,9 +62,9 @@ function App({ flowmind }) {
|
|
|
55
62
|
return (
|
|
56
63
|
React.createElement(Box, { flexDirection: 'column', width: '100%', height: '100%' },
|
|
57
64
|
React.createElement(Box, { flexDirection: 'row', flexGrow: 1 },
|
|
58
|
-
React.createElement(Sidebar, { flowmind: flowmind, width: 30, onSkillSelect: handleSkillSelect }),
|
|
65
|
+
React.createElement(Sidebar, { flowmind: flowmind, width: 30, onSkillSelect: handleSkillSelect, focused: focusPanel === 'sidebar' }),
|
|
59
66
|
React.createElement(Box, { flexDirection: 'column', width: '70%', flexGrow: 1 },
|
|
60
|
-
React.createElement(ChatPanel, { onSubmit: handleCommand, isProcessing: isProcessing, onExit: exit }),
|
|
67
|
+
React.createElement(ChatPanel, { onSubmit: handleCommand, isProcessing: isProcessing, onExit: exit, focused: focusPanel === 'chat' }),
|
|
61
68
|
React.createElement(ResultPanel, { results: results })
|
|
62
69
|
)
|
|
63
70
|
),
|
|
@@ -3,7 +3,7 @@ const { Box, Text, useInput } = require('ink');
|
|
|
3
3
|
const TextInput = require('ink-text-input').default || require('ink-text-input');
|
|
4
4
|
const Spinner = require('ink-spinner').default || require('ink-spinner');
|
|
5
5
|
|
|
6
|
-
function ChatPanel({ onSubmit, isProcessing, onExit }) {
|
|
6
|
+
function ChatPanel({ onSubmit, isProcessing, onExit, focused }) {
|
|
7
7
|
const [input, setInput] = React.useState('');
|
|
8
8
|
const [history, setHistory] = React.useState([]);
|
|
9
9
|
const [cmdHistory, setCmdHistory] = React.useState([]);
|
|
@@ -15,9 +15,9 @@ function ChatPanel({ onSubmit, isProcessing, onExit }) {
|
|
|
15
15
|
return () => { mountedRef.current = false; };
|
|
16
16
|
}, []);
|
|
17
17
|
|
|
18
|
-
// Handle Up/Down arrow for command history
|
|
18
|
+
// Handle Up/Down arrow for command history (only when focused)
|
|
19
19
|
useInput((ch, key) => {
|
|
20
|
-
if (isProcessing) return;
|
|
20
|
+
if (!focused || isProcessing) return;
|
|
21
21
|
|
|
22
22
|
if (key.upArrow && cmdHistory.length > 0) {
|
|
23
23
|
const newIndex = historyIndex === -1
|
|
@@ -2,7 +2,7 @@ const React = require('react');
|
|
|
2
2
|
const { Box, Text, useInput } = require('ink');
|
|
3
3
|
const DragonTotem = require('./DragonTotem.jsx');
|
|
4
4
|
|
|
5
|
-
function Sidebar({ flowmind, width, onSkillSelect }) {
|
|
5
|
+
function Sidebar({ flowmind, width, onSkillSelect, focused }) {
|
|
6
6
|
const [selectedIndex, setSelectedIndex] = React.useState(0);
|
|
7
7
|
const [skills, setSkills] = React.useState([]);
|
|
8
8
|
const [honorData, setHonorData] = React.useState({ points: 0, level: 0, stats: {} });
|
|
@@ -24,6 +24,7 @@ function Sidebar({ flowmind, width, onSkillSelect }) {
|
|
|
24
24
|
}, [flowmind]);
|
|
25
25
|
|
|
26
26
|
useInput((input, key) => {
|
|
27
|
+
if (!focused) return; // Ignore input when sidebar is not focused
|
|
27
28
|
if (key.upArrow) setSelectedIndex(prev => Math.max(0, prev - 1));
|
|
28
29
|
else if (key.downArrow) setSelectedIndex(prev => Math.min(skills.length - 1, prev + 1));
|
|
29
30
|
else if (key.return && skills[selectedIndex] && onSkillSelect) onSkillSelect(skills[selectedIndex]);
|