flowmind 1.1.0 → 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.
- package/bin/flowmind.js +454 -0
- package/core/event-bus.js +17 -0
- package/core/honor-engine.js +255 -0
- package/core/index.js +42 -3
- package/core/learning-engine.js +29 -1
- package/core/skill-loader.js +9 -1
- package/dashboard/app.jsx +29 -0
- package/dashboard/components/ActivityFeed.jsx +86 -0
- package/dashboard/components/DragonPanel.jsx +67 -0
- package/dashboard/components/McpStatusBar.jsx +43 -0
- package/dashboard/components/StatsRow.jsx +65 -0
- package/mcp/server.js +63 -48
- package/package.json +11 -5
- package/tui/app.jsx +69 -0
- package/tui/components/ChatPanel.jsx +72 -0
- package/tui/components/DragonTotem.jsx +108 -0
- package/tui/components/ResultPanel.jsx +33 -0
- package/tui/components/Sidebar.jsx +70 -0
- package/tui/components/StatusBar.jsx +49 -0
package/bin/flowmind.js
CHANGED
|
@@ -11,7 +11,26 @@ const ora = require('ora');
|
|
|
11
11
|
const inquirer = require('inquirer');
|
|
12
12
|
const fs = require('fs-extra');
|
|
13
13
|
const path = require('path');
|
|
14
|
+
const { execSync } = require('child_process');
|
|
14
15
|
const FlowMind = require('../core');
|
|
16
|
+
const HonorEngine = require('../core/honor-engine');
|
|
17
|
+
|
|
18
|
+
// Global error handlers to prevent silent CLI crashes
|
|
19
|
+
process.on('uncaughtException', (err) => {
|
|
20
|
+
console.error(chalk.red('\nUncaught Exception:'), err.message);
|
|
21
|
+
if (process.stdin.isTTY && process.stdin.isRaw) {
|
|
22
|
+
process.stdin.setRawMode(false);
|
|
23
|
+
}
|
|
24
|
+
process.exit(1);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
process.on('unhandledRejection', (reason) => {
|
|
28
|
+
console.error(chalk.red('\nUnhandled Rejection:'), reason?.message || reason);
|
|
29
|
+
if (process.stdin.isTTY && process.stdin.isRaw) {
|
|
30
|
+
process.stdin.setRawMode(false);
|
|
31
|
+
}
|
|
32
|
+
process.exit(1);
|
|
33
|
+
});
|
|
15
34
|
|
|
16
35
|
// Package info
|
|
17
36
|
const packageJson = require('../package.json');
|
|
@@ -42,6 +61,274 @@ function showBanner() {
|
|
|
42
61
|
`));
|
|
43
62
|
}
|
|
44
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Dragon Totem ASCII Art by level - Chinese Dragon (中国龙)
|
|
66
|
+
*/
|
|
67
|
+
function getDragonArt(level) {
|
|
68
|
+
const arts = {
|
|
69
|
+
0: [
|
|
70
|
+
' ╭─────╮ ',
|
|
71
|
+
' ╱ ╭─╮ ╲ ',
|
|
72
|
+
' │ │ │ │ ',
|
|
73
|
+
' │ │ ◎ │ │ ',
|
|
74
|
+
' │ ╰─╯ │ ',
|
|
75
|
+
' ╲ ╱ ',
|
|
76
|
+
' ╰─────╯ ',
|
|
77
|
+
' 龙 蛋 (Egg) '
|
|
78
|
+
],
|
|
79
|
+
1: [
|
|
80
|
+
' ╭──╮ ',
|
|
81
|
+
' ╭────╯ ╰───╮ ',
|
|
82
|
+
' ╱ ◎ ╰─╯ ╲ ',
|
|
83
|
+
' ╱ ▽ ╲ ',
|
|
84
|
+
' ╲ ╱╲ ╱╲ ╱ ',
|
|
85
|
+
' ╲╱╱ ╲╱╱ ╲╱╲╱ ',
|
|
86
|
+
' 幼 龙 (Hatchling) '
|
|
87
|
+
],
|
|
88
|
+
2: [
|
|
89
|
+
' ╭─╮ ╭─╮ ',
|
|
90
|
+
' ╭────╯ ╰──╯ ╰───╮ ',
|
|
91
|
+
' ╱ ◎ ╰──╯ ╲ ',
|
|
92
|
+
' ╱ ╭────────╮ ╲ ',
|
|
93
|
+
' ╲ ╱ ╱╱╱╱╱╱╱╱ ╲ ╱ ',
|
|
94
|
+
' ╲───╯ ╱╱╱╱╱╱╱╱╱╱ ╰──╱ ',
|
|
95
|
+
' ╲ ╱╱╱╱╱╱╱╱╱╱╱╱ ╱ ',
|
|
96
|
+
' ╰─╯ ╰─╯ ',
|
|
97
|
+
' 少 年 龙 (Juvenile) '
|
|
98
|
+
],
|
|
99
|
+
3: [
|
|
100
|
+
' ╭───╮ ╭───╮ ',
|
|
101
|
+
' ╭───╯ ╰──╯ ╰───╮ ',
|
|
102
|
+
' ╱ ◎ ╰───╯ ╲ ',
|
|
103
|
+
'│ ╭──────────╮ │ ',
|
|
104
|
+
'│ ╱ ╱╱╱╱╱╱╱╱╱╱ ╲ │ ',
|
|
105
|
+
' ╲──╯ ╱╱╱╱╱╱╱╱╱╱╱╱ ╰───╯ ',
|
|
106
|
+
' ╲ ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ╱ ',
|
|
107
|
+
' ╲╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ',
|
|
108
|
+
' ╰───╯ ╰───╯ ',
|
|
109
|
+
' 成 年 龙 (Adult) '
|
|
110
|
+
],
|
|
111
|
+
4: [
|
|
112
|
+
' ╭───╮ ╭───╮ ',
|
|
113
|
+
'╭───╯ ╰──────╯ ╰───╮ ',
|
|
114
|
+
'│ ◎ ╰───╯ │ ',
|
|
115
|
+
'│ ╭────────────╮ │ ',
|
|
116
|
+
'│ ╱ ╱╱╱╱╱╱╱╱╱╱╱╱ ╲ │ ',
|
|
117
|
+
' ╲───╯ ╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ╰──╯ ',
|
|
118
|
+
' ╲ ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ╲ ',
|
|
119
|
+
' ╲─╯╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╰─╲ ',
|
|
120
|
+
' ╲╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ',
|
|
121
|
+
' ╰───╯ ╰───╯ ',
|
|
122
|
+
' 长 老 龙 (Elder) '
|
|
123
|
+
],
|
|
124
|
+
5: [
|
|
125
|
+
' ★ ╭───╮ ╭───╮ ★ ',
|
|
126
|
+
'╭─╯ ╰──╯ ╰──╯ ╰─╮ ',
|
|
127
|
+
'│ ◎ ╰───╯ │ ',
|
|
128
|
+
'│ ╭──────────────╮ │ ',
|
|
129
|
+
'│ ╱ ★╱╱╱╱╱╱╱╱╱╱★╱╱ ╲ │ ',
|
|
130
|
+
' ╲────╯ ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ╰───╯ ',
|
|
131
|
+
' ╲ ╱╱╱╱★╱╱╱╱╱╱╱╱★╱╱╱╱╱ ╲ ',
|
|
132
|
+
' ╲──╯╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╰──╲ ',
|
|
133
|
+
' ╲─╯╱╱╱★╱╱╱╱╱╱╱╱★╱╱╱╱╱╰──╲ ',
|
|
134
|
+
' ★ ╲╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ ★ ',
|
|
135
|
+
' ╰───╯ ╰───╯ ',
|
|
136
|
+
' 升 龙 (Ascended) '
|
|
137
|
+
]
|
|
138
|
+
};
|
|
139
|
+
return arts[level] || arts[0];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get dragon color function for a level
|
|
144
|
+
*/
|
|
145
|
+
function getDragonColor(level) {
|
|
146
|
+
const colors = {
|
|
147
|
+
0: (text) => chalk.gray(text),
|
|
148
|
+
1: (text) => chalk.cyan(text),
|
|
149
|
+
2: (text) => chalk.cyan.bold(text),
|
|
150
|
+
3: (text) => chalk.cyanBright(text),
|
|
151
|
+
4: (text) => chalk.cyanBright.bold(text),
|
|
152
|
+
5: (text) => chalk.cyanBright.bold(text)
|
|
153
|
+
};
|
|
154
|
+
return colors[level] || colors[0];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Get highlight color for Ascended level
|
|
159
|
+
*/
|
|
160
|
+
function getHighlightColor(level) {
|
|
161
|
+
if (level >= 5) {
|
|
162
|
+
return (text) => chalk.whiteBright(text);
|
|
163
|
+
}
|
|
164
|
+
return (text) => text;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Render dragon totem with honor data
|
|
169
|
+
*/
|
|
170
|
+
function renderDragonTotem(honorData) {
|
|
171
|
+
const info = getLevelInfo(honorData.points);
|
|
172
|
+
const dragonColor = getDragonColor(info.level);
|
|
173
|
+
const highlightColor = getHighlightColor(info.level);
|
|
174
|
+
const art = getDragonArt(info.level);
|
|
175
|
+
|
|
176
|
+
// Calculate max art line width for proper padding
|
|
177
|
+
const maxArtWidth = Math.max(...art.map(l => l.length));
|
|
178
|
+
const boxWidth = Math.max(maxArtWidth + 8, 55);
|
|
179
|
+
|
|
180
|
+
const border = '─'.repeat(boxWidth - 2);
|
|
181
|
+
const padRight = (text, width) => {
|
|
182
|
+
const padding = Math.max(0, width - text.length);
|
|
183
|
+
return ' '.repeat(padding);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
console.log('');
|
|
187
|
+
console.log(chalk.cyan(' ┌' + border + '┐'));
|
|
188
|
+
console.log(chalk.cyan(' │') + ' 🐉 Dragon Totem of Honor ' + padRight(' 🐉 Dragon Totem of Honor ', boxWidth - 2) + chalk.cyan('│'));
|
|
189
|
+
console.log(chalk.cyan(' ├' + border + '┤'));
|
|
190
|
+
|
|
191
|
+
for (const line of art) {
|
|
192
|
+
const padded = ' ' + line + padRight(' ' + line, boxWidth - 2);
|
|
193
|
+
console.log(chalk.cyan(' │') + dragonColor(padded) + chalk.cyan('│'));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
console.log(chalk.cyan(' ├' + border + '┤'));
|
|
197
|
+
|
|
198
|
+
const levelLine = ` Level: ${info.level} - ${info.name}`;
|
|
199
|
+
const pointsLine = ` Points: ${honorData.points}`;
|
|
200
|
+
const stateLine = ` State: ${info.state}`;
|
|
201
|
+
|
|
202
|
+
console.log(chalk.cyan(' │') + highlightColor(levelLine + padRight(levelLine, boxWidth - 2)) + chalk.cyan('│'));
|
|
203
|
+
console.log(chalk.cyan(' │') + highlightColor(pointsLine + padRight(pointsLine, boxWidth - 2)) + chalk.cyan('│'));
|
|
204
|
+
console.log(chalk.cyan(' │') + highlightColor(stateLine + padRight(stateLine, boxWidth - 2)) + chalk.cyan('│'));
|
|
205
|
+
|
|
206
|
+
if (info.nextLevel) {
|
|
207
|
+
const nextLine = ` Next: ${info.pointsToNext} points to ${info.nextLevelName}`;
|
|
208
|
+
console.log(chalk.cyan(' │') + chalk.gray(nextLine + padRight(nextLine, boxWidth - 2)) + chalk.cyan('│'));
|
|
209
|
+
} else {
|
|
210
|
+
const maxLine = ' Maximum level reached!';
|
|
211
|
+
console.log(chalk.cyan(' │') + chalk.yellow(maxLine + padRight(maxLine, boxWidth - 2)) + chalk.cyan('│'));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
console.log(chalk.cyan(' ├' + border + '┤'));
|
|
215
|
+
|
|
216
|
+
const statsTitle = ' Stats:';
|
|
217
|
+
console.log(chalk.cyan(' │') + chalk.white(statsTitle + padRight(statsTitle, boxWidth - 2)) + chalk.cyan('│'));
|
|
218
|
+
|
|
219
|
+
const stats1 = ` Skills Used: ${honorData.stats.skillUseCount}`;
|
|
220
|
+
const stats2 = ` New Skills: ${honorData.stats.newSkillCount}`;
|
|
221
|
+
const stats3 = ` Learnings: ${honorData.stats.learningCount}`;
|
|
222
|
+
|
|
223
|
+
console.log(chalk.cyan(' │') + chalk.gray(stats1 + padRight(stats1, boxWidth - 2)) + chalk.cyan('│'));
|
|
224
|
+
console.log(chalk.cyan(' │') + chalk.gray(stats2 + padRight(stats2, boxWidth - 2)) + chalk.cyan('│'));
|
|
225
|
+
console.log(chalk.cyan(' │') + chalk.gray(stats3 + padRight(stats3, boxWidth - 2)) + chalk.cyan('│'));
|
|
226
|
+
|
|
227
|
+
console.log(chalk.cyan(' └' + border + '┘'));
|
|
228
|
+
console.log('');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Get level info for points
|
|
233
|
+
*/
|
|
234
|
+
function getLevelInfo(points) {
|
|
235
|
+
const levels = [
|
|
236
|
+
{ level: 0, minPoints: 0, name: 'Egg', state: 'dormant' },
|
|
237
|
+
{ level: 1, minPoints: 1, name: 'Hatchling', state: 'awakening' },
|
|
238
|
+
{ level: 2, minPoints: 10, name: 'Juvenile', state: 'growing' },
|
|
239
|
+
{ level: 3, minPoints: 30, name: 'Adult', state: 'soaring' },
|
|
240
|
+
{ level: 4, minPoints: 60, name: 'Elder', state: 'wise' },
|
|
241
|
+
{ level: 5, minPoints: 100, name: 'Ascended', state: 'transcendent' }
|
|
242
|
+
];
|
|
243
|
+
|
|
244
|
+
let current = levels[0];
|
|
245
|
+
let next = levels[1];
|
|
246
|
+
|
|
247
|
+
for (const tier of levels) {
|
|
248
|
+
if (points >= tier.minPoints) {
|
|
249
|
+
current = tier;
|
|
250
|
+
next = levels[tier.level + 1] || null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
level: current.level,
|
|
256
|
+
name: current.name,
|
|
257
|
+
state: current.state,
|
|
258
|
+
nextLevel: next ? next.level : null,
|
|
259
|
+
nextLevelName: next ? next.name : null,
|
|
260
|
+
pointsToNext: next ? next.minPoints - points : 0
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Get hint for next level
|
|
266
|
+
*/
|
|
267
|
+
function getNextLevelHint(points) {
|
|
268
|
+
const info = getLevelInfo(points);
|
|
269
|
+
if (info.nextLevel) {
|
|
270
|
+
return `Earn ${info.pointsToNext} more points to reach ${info.nextLevelName} (Level ${info.nextLevel})`;
|
|
271
|
+
}
|
|
272
|
+
return 'You have reached the maximum dragon level!';
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Publish honor data
|
|
277
|
+
*/
|
|
278
|
+
async function publishHonor(options) {
|
|
279
|
+
const configDir = path.join(process.env.HOME || process.env.USERPROFILE, '.flowmind');
|
|
280
|
+
const honorPath = path.join(configDir, 'honor.json');
|
|
281
|
+
|
|
282
|
+
if (!await fs.pathExists(honorPath)) {
|
|
283
|
+
console.error(chalk.red('No honor data found. Run flowmind init first.'));
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const honorData = await fs.readJson(honorPath);
|
|
288
|
+
const info = getLevelInfo(honorData.points);
|
|
289
|
+
|
|
290
|
+
const exportData = {
|
|
291
|
+
version: honorData.version,
|
|
292
|
+
points: honorData.points,
|
|
293
|
+
level: info.level,
|
|
294
|
+
levelName: info.name,
|
|
295
|
+
state: info.state,
|
|
296
|
+
stats: honorData.stats,
|
|
297
|
+
knownSkillsCount: honorData.knownSkills ? honorData.knownSkills.length : 0,
|
|
298
|
+
recentHistory: (honorData.history || []).slice(-20),
|
|
299
|
+
exportedAt: new Date().toISOString()
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// Export to local JSON file
|
|
303
|
+
const outputPath = options.output || 'flowmind-honor.json';
|
|
304
|
+
await fs.writeJson(outputPath, exportData, { spaces: 2 });
|
|
305
|
+
console.log(chalk.green(`✓ Honor data exported to: ${outputPath}`));
|
|
306
|
+
|
|
307
|
+
// Create GitHub Gist if requested
|
|
308
|
+
if (options.gist) {
|
|
309
|
+
try {
|
|
310
|
+
// Check if gh CLI is available
|
|
311
|
+
try {
|
|
312
|
+
execSync('gh --version', { stdio: 'ignore' });
|
|
313
|
+
} catch {
|
|
314
|
+
console.error(chalk.red('GitHub CLI (gh) is not installed. Install it from https://cli.github.com/'));
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const gistContent = JSON.stringify(exportData, null, 2);
|
|
319
|
+
const gistDescription = `FlowMind Honor: Level ${info.level} (${info.name}) - ${honorData.points} points`;
|
|
320
|
+
|
|
321
|
+
// Create gist using gh CLI
|
|
322
|
+
const gistCommand = `gh gist create --public --desc "${gistDescription}" --filename "flowmind-honor.json" - <<< '${gistContent.replace(/'/g, "'\\''")}'`;
|
|
323
|
+
|
|
324
|
+
const gistUrl = execSync(gistCommand, { encoding: 'utf-8' }).trim();
|
|
325
|
+
console.log(chalk.green(`✓ GitHub Gist created: ${gistUrl}`));
|
|
326
|
+
} catch (error) {
|
|
327
|
+
console.error(chalk.red(`Failed to create Gist: ${error.message}`));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
45
332
|
// CLI Commands
|
|
46
333
|
program
|
|
47
334
|
.name('flowmind')
|
|
@@ -173,6 +460,17 @@ program
|
|
|
173
460
|
console.log(' Or configure manually:', chalk.yellow('~/.flowmind/ai-config.json'));
|
|
174
461
|
}
|
|
175
462
|
|
|
463
|
+
// Award honor point for init
|
|
464
|
+
try {
|
|
465
|
+
const honorEngine = new HonorEngine({ get: (key, def) => def });
|
|
466
|
+
honorEngine.honorPath = path.join(configDir, 'honor.json');
|
|
467
|
+
await honorEngine.init();
|
|
468
|
+
await honorEngine.award('init', 'FlowMind initialized');
|
|
469
|
+
console.log(chalk.green('\n✓ Honor system activated (+1 point)'));
|
|
470
|
+
} catch (honorError) {
|
|
471
|
+
// Non-blocking
|
|
472
|
+
}
|
|
473
|
+
|
|
176
474
|
} catch (error) {
|
|
177
475
|
spinner.fail('Failed to initialize FlowMind');
|
|
178
476
|
console.error(chalk.red(error.message));
|
|
@@ -446,6 +744,32 @@ program
|
|
|
446
744
|
}
|
|
447
745
|
});
|
|
448
746
|
|
|
747
|
+
// Honor command
|
|
748
|
+
program
|
|
749
|
+
.command('honor')
|
|
750
|
+
.description('Show honor points and dragon totem')
|
|
751
|
+
.option('-j, --json', 'Output as JSON')
|
|
752
|
+
.option('-p, --publish', 'Export honor data to JSON file')
|
|
753
|
+
.option('-g, --gist', 'Create GitHub Gist (with --publish)')
|
|
754
|
+
.option('-o, --output <file>', 'Output file path (default: flowmind-honor.json)')
|
|
755
|
+
.action(async (options) => {
|
|
756
|
+
try {
|
|
757
|
+
const fm = await initFlowMind();
|
|
758
|
+
const honorData = fm.getHonorData();
|
|
759
|
+
|
|
760
|
+
if (options.json) {
|
|
761
|
+
console.log(JSON.stringify(honorData, null, 2));
|
|
762
|
+
} else if (options.publish) {
|
|
763
|
+
await publishHonor({ output: options.output, gist: options.gist });
|
|
764
|
+
} else {
|
|
765
|
+
renderDragonTotem(honorData);
|
|
766
|
+
console.log(chalk.gray(` ${getNextLevelHint(honorData.points)}`));
|
|
767
|
+
}
|
|
768
|
+
} catch (error) {
|
|
769
|
+
console.error(chalk.red('Error:'), error.message);
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
|
|
449
773
|
// Config command
|
|
450
774
|
program
|
|
451
775
|
.command('config')
|
|
@@ -1014,6 +1338,136 @@ async function openInEditor(filePath) {
|
|
|
1014
1338
|
});
|
|
1015
1339
|
}
|
|
1016
1340
|
|
|
1341
|
+
// TUI command - Rich terminal interface using Ink
|
|
1342
|
+
program
|
|
1343
|
+
.command('tui')
|
|
1344
|
+
.description('Launch enhanced TUI with split panels, skill browser, and dragon display')
|
|
1345
|
+
.action(async () => {
|
|
1346
|
+
try {
|
|
1347
|
+
// Register .jsx extension for CJS
|
|
1348
|
+
require('module')._extensions['.jsx'] = require('module')._extensions['.js'];
|
|
1349
|
+
|
|
1350
|
+
const React = require('react');
|
|
1351
|
+
const { render } = require('ink');
|
|
1352
|
+
const App = require('../tui/app.jsx');
|
|
1353
|
+
|
|
1354
|
+
const fm = await initFlowMind();
|
|
1355
|
+
|
|
1356
|
+
const { unmount, waitUntilExit } = render(React.createElement(App, { flowmind: fm }));
|
|
1357
|
+
await waitUntilExit();
|
|
1358
|
+
unmount();
|
|
1359
|
+
} catch (error) {
|
|
1360
|
+
console.error(chalk.red('TUI Error:'), error.message);
|
|
1361
|
+
if (error.message.includes('Cannot find module')) {
|
|
1362
|
+
console.log(chalk.yellow('Try running: npm install ink@3 react ink-text-input ink-spinner'));
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
});
|
|
1366
|
+
|
|
1367
|
+
// Dashboard command - Hybrid monitoring dashboard
|
|
1368
|
+
program
|
|
1369
|
+
.command('dashboard')
|
|
1370
|
+
.description('Launch real-time monitoring dashboard for MCP activity and events')
|
|
1371
|
+
.action(async () => {
|
|
1372
|
+
try {
|
|
1373
|
+
// Register .jsx extension for CJS
|
|
1374
|
+
require('module')._extensions['.jsx'] = require('module')._extensions['.js'];
|
|
1375
|
+
|
|
1376
|
+
const React = require('react');
|
|
1377
|
+
const { render } = require('ink');
|
|
1378
|
+
const DashboardApp = require('../dashboard/app.jsx');
|
|
1379
|
+
const eventBus = require('../core/event-bus');
|
|
1380
|
+
|
|
1381
|
+
const fm = await initFlowMind();
|
|
1382
|
+
|
|
1383
|
+
const { unmount, waitUntilExit } = render(
|
|
1384
|
+
React.createElement(DashboardApp, { flowmind: fm, eventBus })
|
|
1385
|
+
);
|
|
1386
|
+
await waitUntilExit();
|
|
1387
|
+
unmount();
|
|
1388
|
+
} catch (error) {
|
|
1389
|
+
console.error(chalk.red('Dashboard Error:'), error.message);
|
|
1390
|
+
if (error.message.includes('Cannot find module')) {
|
|
1391
|
+
console.log(chalk.yellow('Try running: npm install ink@3 react'));
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
});
|
|
1395
|
+
|
|
1396
|
+
// Update command - Auto-update flowmind
|
|
1397
|
+
program
|
|
1398
|
+
.command('update')
|
|
1399
|
+
.description('Update FlowMind to the latest version')
|
|
1400
|
+
.option('--check', 'Only check for updates, do not install')
|
|
1401
|
+
.action(async (options) => {
|
|
1402
|
+
const { execSync } = require('child_process');
|
|
1403
|
+
const currentVersion = packageJson.version;
|
|
1404
|
+
|
|
1405
|
+
console.log(chalk.cyan(`\nCurrent version: ${currentVersion}`));
|
|
1406
|
+
|
|
1407
|
+
try {
|
|
1408
|
+
// Check latest version on npm
|
|
1409
|
+
const latestVersion = execSync('npm view flowmind version', { encoding: 'utf-8' }).trim();
|
|
1410
|
+
console.log(chalk.cyan(`Latest version: ${latestVersion}`));
|
|
1411
|
+
|
|
1412
|
+
if (currentVersion === latestVersion) {
|
|
1413
|
+
console.log(chalk.green('\n✓ You are already on the latest version!'));
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
// Compare versions
|
|
1418
|
+
const parse = (v) => v.split('.').map(Number);
|
|
1419
|
+
const curr = parse(currentVersion);
|
|
1420
|
+
const latest = parse(latestVersion);
|
|
1421
|
+
const isNewer = latest[0] > curr[0] || (latest[0] === curr[0] && latest[1] > curr[1]) || (latest[0] === curr[0] && latest[1] === curr[1] && latest[2] > curr[2]);
|
|
1422
|
+
|
|
1423
|
+
if (!isNewer) {
|
|
1424
|
+
console.log(chalk.green('\n✓ You are on a newer version than npm latest.'));
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
console.log(chalk.yellow(`\n⬆ Update available: ${currentVersion} → ${latestVersion}`));
|
|
1429
|
+
|
|
1430
|
+
if (options.check) {
|
|
1431
|
+
console.log(chalk.cyan('\nRun `flowmind update` to install.'));
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
// Detect install method
|
|
1436
|
+
const isGlobal = (() => {
|
|
1437
|
+
try {
|
|
1438
|
+
const globalRoot = execSync('npm root -g', { encoding: 'utf-8' }).trim();
|
|
1439
|
+
const localPath = require.resolve('../package.json');
|
|
1440
|
+
return localPath.startsWith(globalRoot);
|
|
1441
|
+
} catch { return false; }
|
|
1442
|
+
})();
|
|
1443
|
+
|
|
1444
|
+
const installCmd = isGlobal
|
|
1445
|
+
? `npm install -g flowmind@${latestVersion}`
|
|
1446
|
+
: `npm install flowmind@${latestVersion}`;
|
|
1447
|
+
|
|
1448
|
+
console.log(chalk.cyan(`\nInstalling: ${installCmd}`));
|
|
1449
|
+
const spinner = ora('Updating FlowMind...').start();
|
|
1450
|
+
|
|
1451
|
+
try {
|
|
1452
|
+
execSync(installCmd, { encoding: 'utf-8', stdio: 'pipe' });
|
|
1453
|
+
spinner.succeed(`FlowMind updated to ${latestVersion}!`);
|
|
1454
|
+
|
|
1455
|
+
console.log(chalk.green('\n✓ Update complete!'));
|
|
1456
|
+
console.log(chalk.gray(' Run `flowmind --version` to verify.'));
|
|
1457
|
+
} catch (installError) {
|
|
1458
|
+
spinner.fail('Update failed');
|
|
1459
|
+
console.error(chalk.red('\nInstall error:'), installError.message);
|
|
1460
|
+
console.log(chalk.yellow('\nTry manually:'));
|
|
1461
|
+
console.log(chalk.white(` ${installCmd}`));
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
} catch (error) {
|
|
1465
|
+
console.error(chalk.red('Failed to check for updates:'), error.message);
|
|
1466
|
+
console.log(chalk.yellow('\nYou can manually update with:'));
|
|
1467
|
+
console.log(chalk.white(' npm install -g flowmind@latest'));
|
|
1468
|
+
}
|
|
1469
|
+
});
|
|
1470
|
+
|
|
1017
1471
|
// Parse arguments
|
|
1018
1472
|
program.parse(process.argv);
|
|
1019
1473
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FlowMind Event Bus
|
|
3
|
+
* Shared singleton EventEmitter for cross-module event communication.
|
|
4
|
+
* Enables real-time monitoring (dashboard) and event-driven TUI updates.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const EventEmitter = require('events');
|
|
8
|
+
|
|
9
|
+
class FlowMindEventBus extends EventEmitter {
|
|
10
|
+
constructor() {
|
|
11
|
+
super();
|
|
12
|
+
this.setMaxListeners(50);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Singleton — shared across all modules
|
|
17
|
+
module.exports = new FlowMindEventBus();
|