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/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')
@@ -52,7 +339,8 @@ program
52
339
  program
53
340
  .command('init')
54
341
  .description('Initialize FlowMind in current directory')
55
- .action(async () => {
342
+ .option('--ai <provider>', 'Initialize with AI provider (openai/anthropic/glm/mimo/qwen/ernie/deepseek/ollama)')
343
+ .action(async (options) => {
56
344
  showBanner();
57
345
 
58
346
  const spinner = ora('Initializing FlowMind...').start();
@@ -68,7 +356,7 @@ program
68
356
  const configPath = path.join(configDir, 'config.json');
69
357
  if (!await fs.pathExists(configPath)) {
70
358
  const defaultConfig = {
71
- version: '1.0.0',
359
+ version: '1.1.0',
72
360
  learning: {
73
361
  enabled: true,
74
362
  autoApply: true,
@@ -89,17 +377,100 @@ program
89
377
  await fs.writeJson(configPath, defaultConfig, { spaces: 2 });
90
378
  }
91
379
 
380
+ // Initialize AI if requested
381
+ if (options.ai) {
382
+ spinner.text = 'Configuring AI provider...';
383
+ const aiConfigPath = path.join(configDir, 'ai-config.json');
384
+
385
+ let aiConfig = {};
386
+ if (await fs.pathExists(aiConfigPath)) {
387
+ aiConfig = await fs.readJson(aiConfigPath);
388
+ }
389
+
390
+ // Set default provider
391
+ aiConfig.ai = aiConfig.ai || {};
392
+ aiConfig.ai.defaultProvider = options.ai;
393
+ aiConfig.ai.enabled = true;
394
+
395
+ // Prompt for API key based on provider
396
+ const apiKeyProviders = {
397
+ 'openai': { key: 'apiKey', message: 'Enter OpenAI API key:', env: 'OPENAI_API_KEY' },
398
+ 'anthropic': { key: 'apiKey', message: 'Enter Anthropic API key:', env: 'ANTHROPIC_API_KEY' },
399
+ 'glm': { key: 'apiKey', message: 'Enter Zhipu AI API key:', env: 'ZHIPU_API_KEY' },
400
+ 'mimo': { key: 'apiKey', message: 'Enter MiMo API key:', env: 'MIMO_API_KEY' },
401
+ 'qwen': { key: 'apiKey', message: 'Enter DashScope API key:', env: 'DASHSCOPE_API_KEY' },
402
+ 'ernie': { key: 'apiKey', message: 'Enter Baidu API key:', env: 'BAIDU_API_KEY' },
403
+ 'deepseek': { key: 'apiKey', message: 'Enter DeepSeek API key:', env: 'DEEPSEEK_API_KEY' }
404
+ };
405
+
406
+ const providerConfig = apiKeyProviders[options.ai];
407
+ if (providerConfig) {
408
+ const { apiKey } = await inquirer.prompt([
409
+ {
410
+ type: 'password',
411
+ name: 'apiKey',
412
+ message: providerConfig.message,
413
+ mask: '*'
414
+ }
415
+ ]);
416
+
417
+ aiConfig.ai.providers = aiConfig.ai.providers || {};
418
+ aiConfig.ai.providers[options.ai] = {
419
+ ...aiConfig.ai.providers[options.ai],
420
+ apiKey: apiKey,
421
+ enabled: true
422
+ };
423
+
424
+ // For ERNIE, also prompt for secret key
425
+ if (options.ai === 'ernie') {
426
+ const { secretKey } = await inquirer.prompt([
427
+ {
428
+ type: 'password',
429
+ name: 'secretKey',
430
+ message: 'Enter Baidu Secret key:',
431
+ mask: '*'
432
+ }
433
+ ]);
434
+ aiConfig.ai.providers[options.ai].secretKey = secretKey;
435
+ }
436
+ }
437
+
438
+ await fs.writeJson(aiConfigPath, aiConfig, { spaces: 2 });
439
+ console.log(chalk.green(`\n✓ AI provider configured: ${options.ai}`));
440
+ }
441
+
92
442
  spinner.succeed('FlowMind initialized successfully!');
93
443
 
94
444
  console.log(chalk.green('\n✓ Configuration created at:'), configDir);
95
445
  console.log(chalk.green('✓ Learning system ready'));
96
446
  console.log(chalk.green('✓ Scene mapping ready'));
97
447
 
448
+ if (options.ai) {
449
+ console.log(chalk.green(`✓ AI provider configured: ${options.ai}`));
450
+ }
451
+
98
452
  console.log(chalk.cyan('\nNext steps:'));
99
453
  console.log(' 1. Run', chalk.yellow('flowmind'), 'to start interactive mode');
100
454
  console.log(' 2. Or use', chalk.yellow('flowmind "your request"'), 'for single commands');
101
455
  console.log(' 3. FlowMind will learn from your corrections automatically');
102
456
 
457
+ if (!options.ai) {
458
+ console.log(chalk.cyan('\nTo enable AI features:'));
459
+ console.log(' Run', chalk.yellow('flowmind init --ai openai'), 'or', chalk.yellow('flowmind init --ai anthropic'));
460
+ console.log(' Or configure manually:', chalk.yellow('~/.flowmind/ai-config.json'));
461
+ }
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
+
103
474
  } catch (error) {
104
475
  spinner.fail('Failed to initialize FlowMind');
105
476
  console.error(chalk.red(error.message));
@@ -373,6 +744,32 @@ program
373
744
  }
374
745
  });
375
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
+
376
773
  // Config command
377
774
  program
378
775
  .command('config')
@@ -404,6 +801,82 @@ program
404
801
  }
405
802
  });
406
803
 
804
+ // AI command
805
+ program
806
+ .command('ai')
807
+ .description('Manage AI model configuration')
808
+ .option('-s, --status', 'Show AI model status')
809
+ .option('-l, --list', 'List available providers')
810
+ .option('-c, --config', 'Show AI configuration')
811
+ .option('-t, --test [provider]', 'Test AI provider connection')
812
+ .option('-j, --json', 'Output as JSON')
813
+ .action(async (options) => {
814
+ try {
815
+ const fm = await initFlowMind();
816
+
817
+ if (options.status) {
818
+ const status = fm.getAIStatus();
819
+ if (options.json) {
820
+ console.log(JSON.stringify(status, null, 2));
821
+ } else {
822
+ displayAIStatus(status);
823
+ }
824
+ } else if (options.list) {
825
+ const status = fm.getAIStatus();
826
+ const providers = Object.entries(status.providers).map(([name, info]) => ({
827
+ name,
828
+ ...info
829
+ }));
830
+ if (options.json) {
831
+ console.log(JSON.stringify({ providers }, null, 2));
832
+ } else {
833
+ console.log(chalk.cyan('\nAI Providers:'));
834
+ for (const provider of providers) {
835
+ const status = provider.initialized ? chalk.green('✓') : chalk.red('✗');
836
+ console.log(` ${status} ${provider.name}`);
837
+ if (provider.info?.model) {
838
+ console.log(` Model: ${provider.info.model}`);
839
+ }
840
+ }
841
+ }
842
+ } else if (options.config) {
843
+ const config = fm.config.get('ai', {});
844
+ if (options.json) {
845
+ console.log(JSON.stringify({ ai: config }, null, 2));
846
+ } else {
847
+ console.log(chalk.cyan('\nAI Configuration:'));
848
+ console.log(JSON.stringify(config, null, 2));
849
+ }
850
+ } else if (options.test !== undefined) {
851
+ const providerName = options.test || fm.ai.defaultProvider;
852
+ const provider = fm.ai.getProvider(providerName);
853
+ if (!provider) {
854
+ console.error(chalk.red(`Provider not found: ${providerName}`));
855
+ return;
856
+ }
857
+ console.log(chalk.cyan(`\nTesting ${providerName}...`));
858
+ try {
859
+ const result = await provider.complete('Hello, this is a test.', { maxTokens: 50 });
860
+ console.log(chalk.green('✓ Connection successful'));
861
+ console.log(chalk.white('Response:'), result.substring(0, 100) + '...');
862
+ } catch (error) {
863
+ console.log(chalk.red('✗ Connection failed'));
864
+ console.error(chalk.red(error.message));
865
+ }
866
+ } else {
867
+ // Default to status
868
+ const status = fm.getAIStatus();
869
+ if (options.json) {
870
+ console.log(JSON.stringify(status, null, 2));
871
+ } else {
872
+ displayAIStatus(status);
873
+ }
874
+ }
875
+ } catch (error) {
876
+ console.error(chalk.red('Error:'), error.message);
877
+ }
878
+ });
879
+
407
880
  // Interactive mode
408
881
  async function runInteractiveMode(fm) {
409
882
  showBanner();
@@ -802,6 +1275,39 @@ function displayResourceConfig(config) {
802
1275
  console.log(chalk.cyan('└─────────────────────────────────────────────────────┘'));
803
1276
  }
804
1277
 
1278
+ function displayAIStatus(status) {
1279
+ console.log(chalk.cyan('\n┌─────────────────────────────────────────────────────┐'));
1280
+ console.log(chalk.cyan('│ AI Model Status │'));
1281
+ console.log(chalk.cyan('├─────────────────────────────────────────────────────┤'));
1282
+
1283
+ const initialized = status.initialized ? chalk.green('✓') : chalk.red('✗');
1284
+ console.log(chalk.cyan(`│ Initialized: ${initialized}`));
1285
+ console.log(chalk.cyan(`│ Default Provider: ${status.defaultProvider || 'None'}`));
1286
+
1287
+ console.log(chalk.cyan('├─────────────────────────────────────────────────────┤'));
1288
+ console.log(chalk.cyan('│ Features:'));
1289
+
1290
+ const features = status.features || {};
1291
+ for (const [feature, enabled] of Object.entries(features)) {
1292
+ const statusIcon = enabled ? chalk.green('✓') : chalk.red('✗');
1293
+ console.log(chalk.cyan(`│ ${statusIcon} ${feature}`));
1294
+ }
1295
+
1296
+ console.log(chalk.cyan('├─────────────────────────────────────────────────────┤'));
1297
+ console.log(chalk.cyan('│ Providers:'));
1298
+
1299
+ const providers = status.providers || {};
1300
+ for (const [name, info] of Object.entries(providers)) {
1301
+ const statusIcon = info.initialized ? chalk.green('✓') : chalk.red('✗');
1302
+ console.log(chalk.cyan(`│ ${statusIcon} ${name}`));
1303
+ if (info.info?.model) {
1304
+ console.log(chalk.cyan(`│ Model: ${info.info.model}`));
1305
+ }
1306
+ }
1307
+
1308
+ console.log(chalk.cyan('└─────────────────────────────────────────────────────┘'));
1309
+ }
1310
+
805
1311
  function formatFileSize(bytes) {
806
1312
  if (bytes === 0) return '0 B';
807
1313
  const k = 1024;
@@ -832,6 +1338,136 @@ async function openInEditor(filePath) {
832
1338
  });
833
1339
  }
834
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
+
835
1471
  // Parse arguments
836
1472
  program.parse(process.argv);
837
1473