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.
- package/README_CN.md +248 -0
- package/bin/flowmind.js +638 -2
- package/config/ai-config.example.json +64 -0
- package/config/claude-mcp-config.example.json +12 -0
- package/core/ai/base-model.js +70 -0
- package/core/ai/index.js +29 -0
- package/core/ai/model-manager.js +320 -0
- package/core/ai/prompts/extraction.js +38 -0
- package/core/ai/prompts/index.js +11 -0
- package/core/ai/prompts/intent.js +43 -0
- package/core/ai/prompts/learning.js +46 -0
- package/core/ai/prompts/selection.js +38 -0
- package/core/ai/prompts/summary.js +35 -0
- package/core/ai/providers/anthropic.js +93 -0
- package/core/ai/providers/deepseek.js +80 -0
- package/core/ai/providers/ernie.js +111 -0
- package/core/ai/providers/glm.js +80 -0
- package/core/ai/providers/mimo.js +80 -0
- package/core/ai/providers/ollama.js +147 -0
- package/core/ai/providers/openai.js +82 -0
- package/core/ai/providers/qwen.js +80 -0
- package/core/event-bus.js +17 -0
- package/core/honor-engine.js +255 -0
- package/core/index.js +115 -13
- package/core/learning-engine.js +29 -1
- package/core/skill-loader.js +31 -9
- 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 +328 -0
- package/package.json +19 -7
- 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')
|
|
@@ -52,7 +339,8 @@ program
|
|
|
52
339
|
program
|
|
53
340
|
.command('init')
|
|
54
341
|
.description('Initialize FlowMind in current directory')
|
|
55
|
-
.
|
|
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.
|
|
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
|
|