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 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
- realStdin.on('data', (chunk) => {
1384
- if (!stdinWrapper.destroyed) stdinWrapper.write(chunk);
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
- realStdin.on('data', (chunk) => {
1441
- if (!stdinWrapper.destroyed) stdinWrapper.write(chunk);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowmind",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
4
4
  "description": "The AI Agent That Learns How You Work - Stop repeating yourself, FlowMind learns your workflows and applies them automatically.",
5
5
  "main": "core/index.js",
6
6
  "bin": {
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]);