flowmind 1.0.1 → 1.2.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.
Files changed (39) hide show
  1. package/README_CN.md +248 -0
  2. package/bin/flowmind.js +638 -2
  3. package/config/ai-config.example.json +64 -0
  4. package/config/claude-mcp-config.example.json +12 -0
  5. package/core/ai/base-model.js +70 -0
  6. package/core/ai/index.js +29 -0
  7. package/core/ai/model-manager.js +320 -0
  8. package/core/ai/prompts/extraction.js +38 -0
  9. package/core/ai/prompts/index.js +11 -0
  10. package/core/ai/prompts/intent.js +43 -0
  11. package/core/ai/prompts/learning.js +46 -0
  12. package/core/ai/prompts/selection.js +38 -0
  13. package/core/ai/prompts/summary.js +35 -0
  14. package/core/ai/providers/anthropic.js +93 -0
  15. package/core/ai/providers/deepseek.js +80 -0
  16. package/core/ai/providers/ernie.js +111 -0
  17. package/core/ai/providers/glm.js +80 -0
  18. package/core/ai/providers/mimo.js +80 -0
  19. package/core/ai/providers/ollama.js +147 -0
  20. package/core/ai/providers/openai.js +82 -0
  21. package/core/ai/providers/qwen.js +80 -0
  22. package/core/event-bus.js +17 -0
  23. package/core/honor-engine.js +255 -0
  24. package/core/index.js +115 -13
  25. package/core/learning-engine.js +29 -1
  26. package/core/skill-loader.js +31 -9
  27. package/dashboard/app.jsx +29 -0
  28. package/dashboard/components/ActivityFeed.jsx +86 -0
  29. package/dashboard/components/DragonPanel.jsx +67 -0
  30. package/dashboard/components/McpStatusBar.jsx +43 -0
  31. package/dashboard/components/StatsRow.jsx +65 -0
  32. package/mcp/server.js +328 -0
  33. package/package.json +19 -7
  34. package/tui/app.jsx +69 -0
  35. package/tui/components/ChatPanel.jsx +72 -0
  36. package/tui/components/DragonTotem.jsx +108 -0
  37. package/tui/components/ResultPanel.jsx +33 -0
  38. package/tui/components/Sidebar.jsx +70 -0
  39. package/tui/components/StatusBar.jsx +49 -0
package/tui/app.jsx ADDED
@@ -0,0 +1,69 @@
1
+ const React = require('react');
2
+ const { Box, Text, useApp, useInput } = require('ink');
3
+ const Sidebar = require('./components/Sidebar.jsx');
4
+ const ChatPanel = require('./components/ChatPanel.jsx');
5
+ const ResultPanel = require('./components/ResultPanel.jsx');
6
+ const StatusBar = require('./components/StatusBar.jsx');
7
+
8
+ function App({ flowmind }) {
9
+ const [results, setResults] = React.useState([]);
10
+ const [isProcessing, setIsProcessing] = React.useState(false);
11
+ const mountedRef = React.useRef(true);
12
+ const { exit } = useApp();
13
+
14
+ React.useEffect(() => {
15
+ return () => { mountedRef.current = false; };
16
+ }, []);
17
+
18
+ useInput((input, key) => {
19
+ if (key.ctrl && input === 'c') exit();
20
+ });
21
+
22
+ const handleCommand = React.useCallback(async (input, addResponse) => {
23
+ setIsProcessing(true);
24
+ try {
25
+ const result = await flowmind.process(input);
26
+ if (!mountedRef.current) return;
27
+ if (result.type === 'result') {
28
+ const text = typeof result.data === 'string' ? result.data : JSON.stringify(result.data, null, 2);
29
+ addResponse(text.substring(0, 200) + (text.length > 200 ? '...' : ''));
30
+ } else if (result.type === 'learning') {
31
+ addResponse(result.message || 'Learning recorded');
32
+ } else if (result.type === 'error') {
33
+ addResponse('Error: ' + result.message);
34
+ }
35
+ setResults(prev => [...prev, result]);
36
+ } catch (e) {
37
+ if (mountedRef.current) addResponse('Error: ' + e.message);
38
+ } finally {
39
+ if (mountedRef.current) setIsProcessing(false);
40
+ }
41
+ }, [flowmind]);
42
+
43
+ const handleSkillSelect = React.useCallback((skill) => {
44
+ try {
45
+ setResults(prev => [...prev, {
46
+ type: 'result',
47
+ data: { name: skill.name, description: skill.definition?.description || 'No description', category: skill.category || 'general', path: skill.path },
48
+ metadata: { skill: skill.name }
49
+ }]);
50
+ } catch (e) {
51
+ // ignore skill select errors
52
+ }
53
+ }, []);
54
+
55
+ return (
56
+ React.createElement(Box, { flexDirection: 'column', width: '100%', height: '100%' },
57
+ React.createElement(Box, { flexDirection: 'row', flexGrow: 1 },
58
+ React.createElement(Sidebar, { flowmind: flowmind, width: 30, onSkillSelect: handleSkillSelect }),
59
+ React.createElement(Box, { flexDirection: 'column', width: '70%', flexGrow: 1 },
60
+ React.createElement(ChatPanel, { onSubmit: handleCommand, isProcessing: isProcessing, onExit: exit }),
61
+ React.createElement(ResultPanel, { results: results })
62
+ )
63
+ ),
64
+ React.createElement(StatusBar, { flowmind: flowmind })
65
+ )
66
+ );
67
+ }
68
+
69
+ module.exports = App;
@@ -0,0 +1,72 @@
1
+ const React = require('react');
2
+ const { Box, Text } = require('ink');
3
+ const TextInput = require('ink-text-input').default || require('ink-text-input');
4
+ const Spinner = require('ink-spinner').default || require('ink-spinner');
5
+
6
+ function ChatPanel({ onSubmit, isProcessing, onExit }) {
7
+ const [input, setInput] = React.useState('');
8
+ const [history, setHistory] = React.useState([]);
9
+ const mountedRef = React.useRef(true);
10
+
11
+ React.useEffect(() => {
12
+ return () => { mountedRef.current = false; };
13
+ }, []);
14
+
15
+ const handleSubmit = (value) => {
16
+ if (!value.trim()) return;
17
+ setHistory(prev => [...prev, { role: 'user', text: value }]);
18
+ setInput('');
19
+ if (value.toLowerCase() === 'exit' || value.toLowerCase() === 'quit') {
20
+ if (onExit) onExit();
21
+ return;
22
+ }
23
+ try {
24
+ onSubmit(value, (response) => {
25
+ if (mountedRef.current) {
26
+ setHistory(prev => [...prev, { role: 'flowmind', text: response }]);
27
+ }
28
+ });
29
+ } catch (e) {
30
+ if (mountedRef.current) {
31
+ setHistory(prev => [...prev, { role: 'flowmind', text: 'Error: ' + e.message }]);
32
+ }
33
+ }
34
+ };
35
+
36
+ const displayHistory = history.slice(-20);
37
+
38
+ return (
39
+ React.createElement(Box, { flexDirection: 'column', borderStyle: 'single', borderColor: 'green', paddingX: 1 },
40
+ React.createElement(Text, { bold: true, color: 'green' }, 'Command Input'),
41
+ React.createElement(Box, { flexDirection: 'column', marginTop: 1, minHeight: 6 },
42
+ displayHistory.length === 0 && React.createElement(Text, { color: 'gray' }, 'Type a command to get started. Type "exit" to quit.'),
43
+ displayHistory.map((msg, i) =>
44
+ React.createElement(Box, { key: i, flexDirection: 'column' },
45
+ msg.role === 'user'
46
+ ? React.createElement(Text, null,
47
+ React.createElement(Text, { color: 'green', bold: true }, '> '),
48
+ React.createElement(Text, { color: 'white' }, msg.text)
49
+ )
50
+ : React.createElement(Text, null,
51
+ React.createElement(Text, { color: 'cyan', bold: true }, '< '),
52
+ React.createElement(Text, { color: 'cyan' }, msg.text)
53
+ )
54
+ )
55
+ )
56
+ ),
57
+ React.createElement(Box, { marginTop: 1 },
58
+ isProcessing
59
+ ? React.createElement(Text, { color: 'yellow' },
60
+ React.createElement(Spinner, { type: 'dots' }),
61
+ ' Processing...'
62
+ )
63
+ : React.createElement(Box, null,
64
+ React.createElement(Text, { color: 'green', bold: true }, '> '),
65
+ React.createElement(TextInput, { value: input, onChange: setInput, onSubmit: handleSubmit })
66
+ )
67
+ )
68
+ )
69
+ );
70
+ }
71
+
72
+ module.exports = ChatPanel;
@@ -0,0 +1,108 @@
1
+ const React = require('react');
2
+ const { Box, Text } = require('ink');
3
+
4
+ const DRAGON_ARTS = {
5
+ 0: [
6
+ ' ╭─────╮ ',
7
+ ' ╱ ╭─╮ ╲ ',
8
+ ' │ │ │ │ ',
9
+ ' │ │ ◎ │ │ ',
10
+ ' │ ╰─╯ │ ',
11
+ ' ╲ ╱ ',
12
+ ' ╰─────╯ ',
13
+ ],
14
+ 1: [
15
+ ' ╭──╮ ',
16
+ ' ╭────╯ ╰───╮ ',
17
+ ' ╱ ◎ ╰─╯ ╲ ',
18
+ ' ╱ ▽ ╲ ',
19
+ ' ╲ ╱╲ ╱╲ ╱ ',
20
+ ' ╲╱╱ ╲╱╱ ╲╱╲╱ ',
21
+ ],
22
+ 2: [
23
+ ' ╭─╮ ╭─╮ ',
24
+ ' ╭────╯ ╰──╯ ╰───╮ ',
25
+ ' ╱ ◎ ╰──╯ ╲ ',
26
+ ' ╱ ╭────────╮ ╲ ',
27
+ ' ╲ ╱ ╱╱╱╱╱╱╱╱ ╲ ╱ ',
28
+ ' ╲───╯ ╱╱╱╱╱╱╱╱╱╱ ╰──╱ ',
29
+ ' ╰─╯ ╰─╯ ',
30
+ ],
31
+ 3: [
32
+ ' ╭───╮ ╭───╮ ',
33
+ ' ╭───╯ ╰──╯ ╰───╮ ',
34
+ ' ╱ ◎ ╰───╯ ╲ ',
35
+ '│ ╭──────────╮ │ ',
36
+ '│ ╱ ╱╱╱╱╱╱╱╱╱╱ ╲ │ ',
37
+ ' ╲──╯ ╱╱╱╱╱╱╱╱╱╱╱╱ ╰───╯ ',
38
+ ' ╲ ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ╱ ',
39
+ ' ╲╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ',
40
+ ' ╰───╯ ╰───╯ ',
41
+ ],
42
+ 4: [
43
+ ' ╭───╮ ╭───╮ ',
44
+ '╭───╯ ╰──────╯ ╰───╮ ',
45
+ '│ ◎ ╰───╯ │ ',
46
+ '│ ╭────────────╮ │ ',
47
+ '│ ╱ ╱╱╱╱╱╱╱╱╱╱╱╱ ╲ │ ',
48
+ ' ╲───╯ ╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ╰──╯ ',
49
+ ' ╲ ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ╲ ',
50
+ ' ╲─╯╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╰─╲ ',
51
+ ' ╲╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ',
52
+ ' ╰───╯ ╰───╯ ',
53
+ ],
54
+ 5: [
55
+ ' ★ ╭───╮ ╭───╮ ★ ',
56
+ '╭─╯ ╰──╯ ╰──╯ ╰─╮ ',
57
+ '│ ◎ ╰───╯ │ ',
58
+ '│ ╭──────────────╮ │ ',
59
+ '│ ╱ ★╱╱╱╱╱╱╱╱╱╱★╱╱ ╲ │ ',
60
+ ' ╲────╯ ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ╰───╯ ',
61
+ ' ╲ ╱╱╱╱★╱╱╱╱╱╱╱╱★╱╱╱╱╱ ╲ ',
62
+ ' ╲──╯╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╰──╲ ',
63
+ ' ╲─╯╱╱╱★╱╱╱╱╱╱╱╱★╱╱╱╱╱╰──╲ ',
64
+ ' ★ ╲╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ★ ',
65
+ ' ╰───╯ ╰───╯ ',
66
+ ],
67
+ };
68
+
69
+ const LEVEL_NAMES = ['Egg', 'Hatchling', 'Juvenile', 'Adult', 'Elder', 'Ascended'];
70
+ const LEVEL_STATES = ['dormant', 'awakening', 'growing', 'soaring', 'wise', 'transcendent'];
71
+ const LEVEL_COLORS = ['gray', 'cyan', 'cyan', 'cyanBright', 'cyanBright', 'cyanBright'];
72
+
73
+ function DragonTotem({ honorData, compact }) {
74
+ const points = honorData?.points || 0;
75
+ const level = honorData?.level || 0;
76
+ const art = DRAGON_ARTS[level] || DRAGON_ARTS[0];
77
+ const color = LEVEL_COLORS[level] || 'gray';
78
+ const levelName = LEVEL_NAMES[level] || 'Unknown';
79
+ const state = LEVEL_STATES[level] || 'unknown';
80
+ const nextLevelPoints = [1, 10, 30, 60, 100];
81
+ const nextPoints = nextLevelPoints[level] || null;
82
+ const pointsToNext = nextPoints !== null ? nextPoints - points : 0;
83
+ const lines = compact ? art.slice(0, 4) : art;
84
+
85
+ return (
86
+ React.createElement(Box, { flexDirection: 'column', paddingX: 1 },
87
+ React.createElement(Text, { bold: true, color: 'cyan' }, 'Dragon Totem'),
88
+ React.createElement(Box, { flexDirection: 'column', marginTop: 1 },
89
+ lines.map((line, i) => React.createElement(Text, { key: i, color: color }, line))
90
+ ),
91
+ React.createElement(Box, { flexDirection: 'column', marginTop: 1 },
92
+ React.createElement(Text, null,
93
+ React.createElement(Text, { color: 'yellow' }, 'Lv' + level),
94
+ React.createElement(Text, { color: 'white' }, ' ' + levelName),
95
+ React.createElement(Text, { color: 'gray' }, ' (' + state + ')')
96
+ ),
97
+ React.createElement(Text, null,
98
+ React.createElement(Text, { color: 'yellow' }, '' + points),
99
+ React.createElement(Text, { color: 'gray' }, ' pts')
100
+ ),
101
+ pointsToNext > 0 && React.createElement(Text, { color: 'gray' }, pointsToNext + ' pts to ' + LEVEL_NAMES[level + 1]),
102
+ pointsToNext <= 0 && level >= 5 && React.createElement(Text, { color: 'yellow' }, 'Max level!')
103
+ )
104
+ )
105
+ );
106
+ }
107
+
108
+ module.exports = DragonTotem;
@@ -0,0 +1,33 @@
1
+ const React = require('react');
2
+ const { Box, Text } = require('ink');
3
+
4
+ function ResultPanel({ results }) {
5
+ const displayResults = results.slice(-30);
6
+
7
+ return (
8
+ React.createElement(Box, { flexDirection: 'column', borderStyle: 'single', borderColor: 'yellow', paddingX: 1, flexGrow: 1 },
9
+ React.createElement(Text, { bold: true, color: 'yellow' }, 'Results'),
10
+ React.createElement(Box, { flexDirection: 'column', marginTop: 1, overflow: 'hidden' },
11
+ displayResults.length === 0 && React.createElement(Text, { color: 'gray' }, 'No results yet. Execute a command to see output here.'),
12
+ displayResults.map((result, i) =>
13
+ React.createElement(Box, { key: i, flexDirection: 'column', marginBottom: 1 },
14
+ result.type === 'result' && React.createElement(React.Fragment, null,
15
+ result.metadata?.skill && React.createElement(Text, null,
16
+ React.createElement(Text, { color: 'gray' }, 'Skill: '),
17
+ React.createElement(Text, { color: 'green' }, result.metadata.skill),
18
+ result.metadata?.duration && React.createElement(Text, { color: 'gray' }, ' (' + result.metadata.duration + 'ms)')
19
+ ),
20
+ React.createElement(Text, { color: 'white' },
21
+ typeof result.data === 'string' ? result.data : JSON.stringify(result.data, null, 2)
22
+ )
23
+ ),
24
+ result.type === 'learning' && React.createElement(Text, { color: 'cyan' }, result.message),
25
+ result.type === 'error' && React.createElement(Text, { color: 'red' }, 'Error: ' + result.message)
26
+ )
27
+ )
28
+ )
29
+ )
30
+ );
31
+ }
32
+
33
+ module.exports = ResultPanel;
@@ -0,0 +1,70 @@
1
+ const React = require('react');
2
+ const { Box, Text, useInput } = require('ink');
3
+ const DragonTotem = require('./DragonTotem.jsx');
4
+
5
+ function Sidebar({ flowmind, width, onSkillSelect }) {
6
+ const [selectedIndex, setSelectedIndex] = React.useState(0);
7
+ const [skills, setSkills] = React.useState([]);
8
+ const [honorData, setHonorData] = React.useState({ points: 0, level: 0, stats: {} });
9
+
10
+ React.useEffect(() => {
11
+ if (flowmind) {
12
+ try {
13
+ const list = flowmind.skills.list() || [];
14
+ setSkills(list);
15
+ } catch (e) {
16
+ setSkills([]);
17
+ }
18
+ try {
19
+ setHonorData(flowmind.getHonorData());
20
+ } catch (e) {
21
+ // keep default honorData
22
+ }
23
+ }
24
+ }, [flowmind]);
25
+
26
+ useInput((input, key) => {
27
+ if (key.upArrow) setSelectedIndex(prev => Math.max(0, prev - 1));
28
+ else if (key.downArrow) setSelectedIndex(prev => Math.min(skills.length - 1, prev + 1));
29
+ else if (key.return && skills[selectedIndex] && onSkillSelect) onSkillSelect(skills[selectedIndex]);
30
+ });
31
+
32
+ const barWidth = Math.max(10, width - 4);
33
+ const progress = honorData.points > 0 ? Math.min(1, honorData.points / 100) : 0;
34
+ const filled = Math.round(progress * barWidth);
35
+ const progressBar = '\u2588'.repeat(filled) + '\u2591'.repeat(barWidth - filled);
36
+
37
+ return (
38
+ React.createElement(Box, { flexDirection: 'column', width: width, borderStyle: 'single', borderColor: 'cyan', paddingX: 1 },
39
+ React.createElement(DragonTotem, { honorData: honorData, compact: true }),
40
+ React.createElement(Box, { flexDirection: 'column', marginTop: 1 },
41
+ React.createElement(Text, { bold: true, color: 'cyan' }, 'Progress'),
42
+ React.createElement(Text, { color: 'green' }, progressBar),
43
+ React.createElement(Text, { color: 'gray' }, honorData.points + '/100 pts')
44
+ ),
45
+ React.createElement(Box, { flexDirection: 'column', marginTop: 1 },
46
+ React.createElement(Text, { bold: true, color: 'cyan' }, 'Skills (' + skills.length + ')'),
47
+ React.createElement(Box, { flexDirection: 'column', marginTop: 1 },
48
+ skills.length === 0 && React.createElement(Text, { color: 'gray' }, 'No skills loaded'),
49
+ skills.map((skill, i) => {
50
+ const isSelected = i === selectedIndex;
51
+ const category = skill.category || 'general';
52
+ const prefix = isSelected ? '\u25B6 ' : ' ';
53
+ return React.createElement(Text, { key: skill.name },
54
+ React.createElement(Text, { color: isSelected ? 'green' : 'white' }, prefix + skill.name),
55
+ React.createElement(Text, { color: 'gray' }, ' [' + category + ']')
56
+ );
57
+ })
58
+ )
59
+ ),
60
+ React.createElement(Box, { flexDirection: 'column', marginTop: 1 },
61
+ React.createElement(Text, { bold: true, color: 'cyan' }, 'Stats'),
62
+ React.createElement(Text, { color: 'gray' }, 'Skills used: ' + (honorData.stats?.skillUseCount || 0)),
63
+ React.createElement(Text, { color: 'gray' }, 'Learnings: ' + (honorData.stats?.learningCount || 0)),
64
+ React.createElement(Text, { color: 'gray' }, 'New skills: ' + (honorData.stats?.newSkillCount || 0))
65
+ )
66
+ )
67
+ );
68
+ }
69
+
70
+ module.exports = Sidebar;
@@ -0,0 +1,49 @@
1
+ const React = require('react');
2
+ const { Box, Text } = require('ink');
3
+
4
+ const LEVEL_NAMES = ['Egg', 'Hatchling', 'Juvenile', 'Adult', 'Elder', 'Ascended'];
5
+
6
+ function StatusBar({ flowmind }) {
7
+ const [aiStatus, setAiStatus] = React.useState(null);
8
+ const [componentStatus, setComponentStatus] = React.useState(null);
9
+ const [honorData, setHonorData] = React.useState(null);
10
+
11
+ React.useEffect(() => {
12
+ if (flowmind) {
13
+ try {
14
+ setAiStatus(flowmind.getAIStatus());
15
+ setComponentStatus(flowmind.getComponentStatus());
16
+ setHonorData(flowmind.getHonorData());
17
+ } catch (e) { /* Non-blocking */ }
18
+ }
19
+ }, [flowmind]);
20
+
21
+ const aiName = aiStatus?.defaultProvider || 'none';
22
+ const aiOk = aiStatus?.initialized || false;
23
+ const componentCount = componentStatus ? Object.keys(componentStatus).length : 0;
24
+ const activeCount = componentStatus ? Object.values(componentStatus).filter(c => c.active).length : 0;
25
+ const level = honorData?.level || 0;
26
+ const points = honorData?.points || 0;
27
+
28
+ return (
29
+ React.createElement(Box, { borderStyle: 'single', borderColor: 'gray', paddingX: 1, justifyContent: 'space-between' },
30
+ React.createElement(Text, null,
31
+ React.createElement(Text, { color: 'gray' }, 'AI: '),
32
+ React.createElement(Text, { color: aiOk ? 'green' : 'red' }, aiName),
33
+ React.createElement(Text, { color: aiOk ? 'green' : 'red' }, aiOk ? ' \u25CF' : ' \u25CB')
34
+ ),
35
+ React.createElement(Text, null,
36
+ React.createElement(Text, { color: 'gray' }, 'Components: '),
37
+ React.createElement(Text, { color: 'white' }, activeCount + '/' + componentCount),
38
+ React.createElement(Text, { color: 'green' }, ' active')
39
+ ),
40
+ React.createElement(Text, null,
41
+ React.createElement(Text, { color: 'gray' }, 'Honor: '),
42
+ React.createElement(Text, { color: 'yellow' }, LEVEL_NAMES[level]),
43
+ React.createElement(Text, { color: 'gray' }, ' (' + points + ' pts)')
44
+ )
45
+ )
46
+ );
47
+ }
48
+
49
+ module.exports = StatusBar;