flowmind 1.0.0 → 1.1.0
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.md +81 -2
- package/README_CN.md +329 -2
- package/bin/flowmind.js +574 -8
- 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/index.js +74 -11
- package/core/mcp-compatibility.js +4 -4
- package/core/providers/yapi/yapi-adapter.js +2 -2
- package/core/providers/yuque/yuque-adapter.js +2 -2
- package/core/skill-loader.js +53 -11
- package/mcp/server.js +313 -0
- package/package.json +9 -3
- package/scripts/migrate-config.js +2 -2
- package/skills/yapi-sync-interface/SKILL.md +1 -1
- package/skills/yuque-sync-design/SKILL.md +6 -6
package/bin/flowmind.js
CHANGED
|
@@ -52,7 +52,8 @@ program
|
|
|
52
52
|
program
|
|
53
53
|
.command('init')
|
|
54
54
|
.description('Initialize FlowMind in current directory')
|
|
55
|
-
.
|
|
55
|
+
.option('--ai <provider>', 'Initialize with AI provider (openai/anthropic/glm/mimo/qwen/ernie/deepseek/ollama)')
|
|
56
|
+
.action(async (options) => {
|
|
56
57
|
showBanner();
|
|
57
58
|
|
|
58
59
|
const spinner = ora('Initializing FlowMind...').start();
|
|
@@ -68,7 +69,7 @@ program
|
|
|
68
69
|
const configPath = path.join(configDir, 'config.json');
|
|
69
70
|
if (!await fs.pathExists(configPath)) {
|
|
70
71
|
const defaultConfig = {
|
|
71
|
-
version: '1.
|
|
72
|
+
version: '1.1.0',
|
|
72
73
|
learning: {
|
|
73
74
|
enabled: true,
|
|
74
75
|
autoApply: true,
|
|
@@ -89,17 +90,89 @@ program
|
|
|
89
90
|
await fs.writeJson(configPath, defaultConfig, { spaces: 2 });
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
// Initialize AI if requested
|
|
94
|
+
if (options.ai) {
|
|
95
|
+
spinner.text = 'Configuring AI provider...';
|
|
96
|
+
const aiConfigPath = path.join(configDir, 'ai-config.json');
|
|
97
|
+
|
|
98
|
+
let aiConfig = {};
|
|
99
|
+
if (await fs.pathExists(aiConfigPath)) {
|
|
100
|
+
aiConfig = await fs.readJson(aiConfigPath);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Set default provider
|
|
104
|
+
aiConfig.ai = aiConfig.ai || {};
|
|
105
|
+
aiConfig.ai.defaultProvider = options.ai;
|
|
106
|
+
aiConfig.ai.enabled = true;
|
|
107
|
+
|
|
108
|
+
// Prompt for API key based on provider
|
|
109
|
+
const apiKeyProviders = {
|
|
110
|
+
'openai': { key: 'apiKey', message: 'Enter OpenAI API key:', env: 'OPENAI_API_KEY' },
|
|
111
|
+
'anthropic': { key: 'apiKey', message: 'Enter Anthropic API key:', env: 'ANTHROPIC_API_KEY' },
|
|
112
|
+
'glm': { key: 'apiKey', message: 'Enter Zhipu AI API key:', env: 'ZHIPU_API_KEY' },
|
|
113
|
+
'mimo': { key: 'apiKey', message: 'Enter MiMo API key:', env: 'MIMO_API_KEY' },
|
|
114
|
+
'qwen': { key: 'apiKey', message: 'Enter DashScope API key:', env: 'DASHSCOPE_API_KEY' },
|
|
115
|
+
'ernie': { key: 'apiKey', message: 'Enter Baidu API key:', env: 'BAIDU_API_KEY' },
|
|
116
|
+
'deepseek': { key: 'apiKey', message: 'Enter DeepSeek API key:', env: 'DEEPSEEK_API_KEY' }
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const providerConfig = apiKeyProviders[options.ai];
|
|
120
|
+
if (providerConfig) {
|
|
121
|
+
const { apiKey } = await inquirer.prompt([
|
|
122
|
+
{
|
|
123
|
+
type: 'password',
|
|
124
|
+
name: 'apiKey',
|
|
125
|
+
message: providerConfig.message,
|
|
126
|
+
mask: '*'
|
|
127
|
+
}
|
|
128
|
+
]);
|
|
129
|
+
|
|
130
|
+
aiConfig.ai.providers = aiConfig.ai.providers || {};
|
|
131
|
+
aiConfig.ai.providers[options.ai] = {
|
|
132
|
+
...aiConfig.ai.providers[options.ai],
|
|
133
|
+
apiKey: apiKey,
|
|
134
|
+
enabled: true
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// For ERNIE, also prompt for secret key
|
|
138
|
+
if (options.ai === 'ernie') {
|
|
139
|
+
const { secretKey } = await inquirer.prompt([
|
|
140
|
+
{
|
|
141
|
+
type: 'password',
|
|
142
|
+
name: 'secretKey',
|
|
143
|
+
message: 'Enter Baidu Secret key:',
|
|
144
|
+
mask: '*'
|
|
145
|
+
}
|
|
146
|
+
]);
|
|
147
|
+
aiConfig.ai.providers[options.ai].secretKey = secretKey;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
await fs.writeJson(aiConfigPath, aiConfig, { spaces: 2 });
|
|
152
|
+
console.log(chalk.green(`\n✓ AI provider configured: ${options.ai}`));
|
|
153
|
+
}
|
|
154
|
+
|
|
92
155
|
spinner.succeed('FlowMind initialized successfully!');
|
|
93
156
|
|
|
94
157
|
console.log(chalk.green('\n✓ Configuration created at:'), configDir);
|
|
95
158
|
console.log(chalk.green('✓ Learning system ready'));
|
|
96
159
|
console.log(chalk.green('✓ Scene mapping ready'));
|
|
97
160
|
|
|
161
|
+
if (options.ai) {
|
|
162
|
+
console.log(chalk.green(`✓ AI provider configured: ${options.ai}`));
|
|
163
|
+
}
|
|
164
|
+
|
|
98
165
|
console.log(chalk.cyan('\nNext steps:'));
|
|
99
166
|
console.log(' 1. Run', chalk.yellow('flowmind'), 'to start interactive mode');
|
|
100
167
|
console.log(' 2. Or use', chalk.yellow('flowmind "your request"'), 'for single commands');
|
|
101
168
|
console.log(' 3. FlowMind will learn from your corrections automatically');
|
|
102
169
|
|
|
170
|
+
if (!options.ai) {
|
|
171
|
+
console.log(chalk.cyan('\nTo enable AI features:'));
|
|
172
|
+
console.log(' Run', chalk.yellow('flowmind init --ai openai'), 'or', chalk.yellow('flowmind init --ai anthropic'));
|
|
173
|
+
console.log(' Or configure manually:', chalk.yellow('~/.flowmind/ai-config.json'));
|
|
174
|
+
}
|
|
175
|
+
|
|
103
176
|
} catch (error) {
|
|
104
177
|
spinner.fail('Failed to initialize FlowMind');
|
|
105
178
|
console.error(chalk.red(error.message));
|
|
@@ -215,15 +288,145 @@ program
|
|
|
215
288
|
}
|
|
216
289
|
});
|
|
217
290
|
|
|
218
|
-
// Skills command
|
|
291
|
+
// Skills command (enhanced)
|
|
219
292
|
program
|
|
220
293
|
.command('skills')
|
|
221
294
|
.description('List available skills')
|
|
222
|
-
.
|
|
295
|
+
.option('-j, --json', 'Output as JSON (for tool integration)')
|
|
296
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
297
|
+
.option('-c, --category <category>', 'Filter by category')
|
|
298
|
+
.action(async (options) => {
|
|
223
299
|
try {
|
|
224
300
|
const fm = await initFlowMind();
|
|
225
301
|
const skills = fm.skills.list();
|
|
226
|
-
|
|
302
|
+
|
|
303
|
+
// Filter by category if specified
|
|
304
|
+
const filtered = options.category
|
|
305
|
+
? skills.filter(s => s.category === options.category)
|
|
306
|
+
: skills;
|
|
307
|
+
|
|
308
|
+
if (options.json) {
|
|
309
|
+
// JSON output for codex/claude integration
|
|
310
|
+
console.log(JSON.stringify({ skills: filtered }, null, 2));
|
|
311
|
+
} else {
|
|
312
|
+
displaySkills(filtered, options.verbose);
|
|
313
|
+
}
|
|
314
|
+
} catch (error) {
|
|
315
|
+
console.error(chalk.red('Error:'), error.message);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// Skill command (view/modify single skill)
|
|
320
|
+
program
|
|
321
|
+
.command('skill <name>')
|
|
322
|
+
.description('View or modify skill configuration')
|
|
323
|
+
.option('-i, --info', 'Show skill info (default)')
|
|
324
|
+
.option('-c, --config', 'Show/edit skill configuration')
|
|
325
|
+
.option('-s, --set <key> <value>', 'Set config value')
|
|
326
|
+
.option('-r, --read', 'Read SKILL.md content')
|
|
327
|
+
.option('-e, --edit', 'Open SKILL.md in editor')
|
|
328
|
+
.option('-j, --json', 'Output as JSON (for tool integration)')
|
|
329
|
+
.action(async (name, options) => {
|
|
330
|
+
try {
|
|
331
|
+
const fm = await initFlowMind();
|
|
332
|
+
const skill = fm.skills.get(name);
|
|
333
|
+
|
|
334
|
+
if (!skill) {
|
|
335
|
+
console.error(chalk.red(`Skill not found: ${name}`));
|
|
336
|
+
console.log(chalk.cyan('\nAvailable skills:'));
|
|
337
|
+
fm.skills.list().forEach(s => console.log(` - ${s.name}`));
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Default to info if no option specified
|
|
342
|
+
if (!options.config && !options.read && !options.edit && !options.set) {
|
|
343
|
+
options.info = true;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (options.info) {
|
|
347
|
+
await showSkillInfo(skill, options.json);
|
|
348
|
+
} else if (options.read) {
|
|
349
|
+
await readSkillMd(skill);
|
|
350
|
+
} else if (options.edit) {
|
|
351
|
+
await editSkillMd(skill);
|
|
352
|
+
} else if (options.config) {
|
|
353
|
+
await showSkillConfig(skill, fm, options.json);
|
|
354
|
+
} else if (options.set) {
|
|
355
|
+
// options.set is the key, need value from next arg
|
|
356
|
+
const value = options.set;
|
|
357
|
+
const key = options.set;
|
|
358
|
+
// Get key and value from command line
|
|
359
|
+
const args = process.argv.slice(3);
|
|
360
|
+
if (args.length >= 2) {
|
|
361
|
+
await setSkillConfig(skill, fm, args[0], args[1]);
|
|
362
|
+
} else {
|
|
363
|
+
console.error(chalk.red('Usage: flowmind skill <name> --set <key> <value>'));
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
} catch (error) {
|
|
367
|
+
console.error(chalk.red('Error:'), error.message);
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// Resource command (local resource files)
|
|
372
|
+
program
|
|
373
|
+
.command('resource')
|
|
374
|
+
.alias('res')
|
|
375
|
+
.description('Manage local resource files')
|
|
376
|
+
.option('-l, --list [dir]', 'List resource directory')
|
|
377
|
+
.option('-s, --show <file>', 'Show file content')
|
|
378
|
+
.option('-e, --edit <file>', 'Edit file')
|
|
379
|
+
.option('-c, --config', 'Show resource configuration')
|
|
380
|
+
.option('-j, --json', 'Output as JSON (for tool integration)')
|
|
381
|
+
.action(async (options) => {
|
|
382
|
+
try {
|
|
383
|
+
const fm = await initFlowMind();
|
|
384
|
+
const resourceConfig = fm.config.get('resources', {});
|
|
385
|
+
const configDir = path.join(process.env.HOME || process.env.USERPROFILE, '.flowmind');
|
|
386
|
+
|
|
387
|
+
if (options.config) {
|
|
388
|
+
// Show resource configuration
|
|
389
|
+
if (options.json) {
|
|
390
|
+
console.log(JSON.stringify({ resources: resourceConfig }, null, 2));
|
|
391
|
+
} else {
|
|
392
|
+
displayResourceConfig(resourceConfig);
|
|
393
|
+
}
|
|
394
|
+
} else if (options.show) {
|
|
395
|
+
// Show file content
|
|
396
|
+
const filePath = resolveResourcePath(options.show, configDir);
|
|
397
|
+
if (await fs.pathExists(filePath)) {
|
|
398
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
399
|
+
if (options.json) {
|
|
400
|
+
console.log(JSON.stringify({ file: options.show, content }, null, 2));
|
|
401
|
+
} else {
|
|
402
|
+
console.log(chalk.cyan(`\n📄 ${options.show}`));
|
|
403
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
404
|
+
console.log(content);
|
|
405
|
+
}
|
|
406
|
+
} else {
|
|
407
|
+
console.error(chalk.red(`File not found: ${options.show}`));
|
|
408
|
+
}
|
|
409
|
+
} else if (options.edit) {
|
|
410
|
+
// Edit file
|
|
411
|
+
const filePath = resolveResourcePath(options.edit, configDir);
|
|
412
|
+
await openInEditor(filePath);
|
|
413
|
+
} else {
|
|
414
|
+
// List directory (default)
|
|
415
|
+
const targetDir = options.list && typeof options.list === 'string'
|
|
416
|
+
? resolveResourcePath(options.list, configDir)
|
|
417
|
+
: configDir;
|
|
418
|
+
|
|
419
|
+
if (await fs.pathExists(targetDir)) {
|
|
420
|
+
const files = await listResourceFiles(targetDir, configDir);
|
|
421
|
+
if (options.json) {
|
|
422
|
+
console.log(JSON.stringify({ directory: targetDir, files }, null, 2));
|
|
423
|
+
} else {
|
|
424
|
+
displayResourceFiles(files, targetDir);
|
|
425
|
+
}
|
|
426
|
+
} else {
|
|
427
|
+
console.error(chalk.red(`Directory not found: ${targetDir}`));
|
|
428
|
+
}
|
|
429
|
+
}
|
|
227
430
|
} catch (error) {
|
|
228
431
|
console.error(chalk.red('Error:'), error.message);
|
|
229
432
|
}
|
|
@@ -274,6 +477,82 @@ program
|
|
|
274
477
|
}
|
|
275
478
|
});
|
|
276
479
|
|
|
480
|
+
// AI command
|
|
481
|
+
program
|
|
482
|
+
.command('ai')
|
|
483
|
+
.description('Manage AI model configuration')
|
|
484
|
+
.option('-s, --status', 'Show AI model status')
|
|
485
|
+
.option('-l, --list', 'List available providers')
|
|
486
|
+
.option('-c, --config', 'Show AI configuration')
|
|
487
|
+
.option('-t, --test [provider]', 'Test AI provider connection')
|
|
488
|
+
.option('-j, --json', 'Output as JSON')
|
|
489
|
+
.action(async (options) => {
|
|
490
|
+
try {
|
|
491
|
+
const fm = await initFlowMind();
|
|
492
|
+
|
|
493
|
+
if (options.status) {
|
|
494
|
+
const status = fm.getAIStatus();
|
|
495
|
+
if (options.json) {
|
|
496
|
+
console.log(JSON.stringify(status, null, 2));
|
|
497
|
+
} else {
|
|
498
|
+
displayAIStatus(status);
|
|
499
|
+
}
|
|
500
|
+
} else if (options.list) {
|
|
501
|
+
const status = fm.getAIStatus();
|
|
502
|
+
const providers = Object.entries(status.providers).map(([name, info]) => ({
|
|
503
|
+
name,
|
|
504
|
+
...info
|
|
505
|
+
}));
|
|
506
|
+
if (options.json) {
|
|
507
|
+
console.log(JSON.stringify({ providers }, null, 2));
|
|
508
|
+
} else {
|
|
509
|
+
console.log(chalk.cyan('\nAI Providers:'));
|
|
510
|
+
for (const provider of providers) {
|
|
511
|
+
const status = provider.initialized ? chalk.green('✓') : chalk.red('✗');
|
|
512
|
+
console.log(` ${status} ${provider.name}`);
|
|
513
|
+
if (provider.info?.model) {
|
|
514
|
+
console.log(` Model: ${provider.info.model}`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
} else if (options.config) {
|
|
519
|
+
const config = fm.config.get('ai', {});
|
|
520
|
+
if (options.json) {
|
|
521
|
+
console.log(JSON.stringify({ ai: config }, null, 2));
|
|
522
|
+
} else {
|
|
523
|
+
console.log(chalk.cyan('\nAI Configuration:'));
|
|
524
|
+
console.log(JSON.stringify(config, null, 2));
|
|
525
|
+
}
|
|
526
|
+
} else if (options.test !== undefined) {
|
|
527
|
+
const providerName = options.test || fm.ai.defaultProvider;
|
|
528
|
+
const provider = fm.ai.getProvider(providerName);
|
|
529
|
+
if (!provider) {
|
|
530
|
+
console.error(chalk.red(`Provider not found: ${providerName}`));
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
console.log(chalk.cyan(`\nTesting ${providerName}...`));
|
|
534
|
+
try {
|
|
535
|
+
const result = await provider.complete('Hello, this is a test.', { maxTokens: 50 });
|
|
536
|
+
console.log(chalk.green('✓ Connection successful'));
|
|
537
|
+
console.log(chalk.white('Response:'), result.substring(0, 100) + '...');
|
|
538
|
+
} catch (error) {
|
|
539
|
+
console.log(chalk.red('✗ Connection failed'));
|
|
540
|
+
console.error(chalk.red(error.message));
|
|
541
|
+
}
|
|
542
|
+
} else {
|
|
543
|
+
// Default to status
|
|
544
|
+
const status = fm.getAIStatus();
|
|
545
|
+
if (options.json) {
|
|
546
|
+
console.log(JSON.stringify(status, null, 2));
|
|
547
|
+
} else {
|
|
548
|
+
displayAIStatus(status);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
} catch (error) {
|
|
552
|
+
console.error(chalk.red('Error:'), error.message);
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
|
|
277
556
|
// Interactive mode
|
|
278
557
|
async function runInteractiveMode(fm) {
|
|
279
558
|
showBanner();
|
|
@@ -432,22 +711,309 @@ function displayScenes(scenes) {
|
|
|
432
711
|
console.log(chalk.cyan('└─────────────────────────────────────────────────────┘'));
|
|
433
712
|
}
|
|
434
713
|
|
|
435
|
-
function displaySkills(skills) {
|
|
714
|
+
function displaySkills(skills, verbose = false) {
|
|
436
715
|
console.log(chalk.cyan('\n┌─────────────────────────────────────────────────────┐'));
|
|
437
716
|
console.log(chalk.cyan('│ Available Skills │'));
|
|
438
717
|
console.log(chalk.cyan('├─────────────────────────────────────────────────────┤'));
|
|
439
718
|
|
|
440
719
|
for (const skill of skills) {
|
|
441
|
-
console.log(chalk.cyan(`│ ${skill.name}`));
|
|
720
|
+
console.log(chalk.cyan(`│ ${chalk.bold(skill.name)}`));
|
|
442
721
|
if (skill.description) {
|
|
443
|
-
|
|
722
|
+
const desc = verbose ? skill.description : skill.description.substring(0, 50) + '...';
|
|
723
|
+
console.log(chalk.cyan(`│ ${desc}`));
|
|
724
|
+
}
|
|
725
|
+
if (verbose && skill.category) {
|
|
726
|
+
console.log(chalk.cyan(`│ Category: ${skill.category}`));
|
|
727
|
+
}
|
|
728
|
+
console.log(chalk.cyan('├─────────────────────────────────────────────────────┤'));
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
console.log(chalk.cyan('└─────────────────────────────────────────────────────┘'));
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// Skill info display
|
|
735
|
+
async function showSkillInfo(skill, asJson = false) {
|
|
736
|
+
const info = {
|
|
737
|
+
name: skill.name,
|
|
738
|
+
path: skill.path,
|
|
739
|
+
description: skill.definition?.description || 'No description',
|
|
740
|
+
version: skill.definition?.version || skill.definition?.metadata?.version || '1.0.0',
|
|
741
|
+
author: skill.definition?.author || skill.definition?.metadata?.author || 'unknown',
|
|
742
|
+
category: skill.definition?.category || skill.definition?.metadata?.category || 'general',
|
|
743
|
+
componentDependencies: skill.definition?.componentDependencies || [],
|
|
744
|
+
triggers: skill.definition?.triggers || []
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
if (asJson) {
|
|
748
|
+
console.log(JSON.stringify(info, null, 2));
|
|
749
|
+
} else {
|
|
750
|
+
console.log(chalk.cyan('\n┌─────────────────────────────────────────────────────┐'));
|
|
751
|
+
console.log(chalk.cyan(`│ ${chalk.bold(info.name)}`));
|
|
752
|
+
console.log(chalk.cyan('├─────────────────────────────────────────────────────┤'));
|
|
753
|
+
console.log(chalk.cyan(`│ Description: ${info.description}`));
|
|
754
|
+
console.log(chalk.cyan(`│ Version: ${info.version}`));
|
|
755
|
+
console.log(chalk.cyan(`│ Author: ${info.author}`));
|
|
756
|
+
console.log(chalk.cyan(`│ Category: ${info.category}`));
|
|
757
|
+
console.log(chalk.cyan(`│ Path: ${info.path}`));
|
|
758
|
+
|
|
759
|
+
if (info.componentDependencies.length > 0) {
|
|
760
|
+
console.log(chalk.cyan('├─────────────────────────────────────────────────────┤'));
|
|
761
|
+
console.log(chalk.cyan('│ Component Dependencies:'));
|
|
762
|
+
for (const dep of info.componentDependencies) {
|
|
763
|
+
console.log(chalk.cyan(`│ - ${dep}`));
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
if (info.triggers.length > 0) {
|
|
768
|
+
console.log(chalk.cyan('├─────────────────────────────────────────────────────┤'));
|
|
769
|
+
console.log(chalk.cyan('│ Trigger Patterns:'));
|
|
770
|
+
for (const trigger of info.triggers.slice(0, 10)) {
|
|
771
|
+
console.log(chalk.cyan(`│ - ${trigger}`));
|
|
772
|
+
}
|
|
773
|
+
if (info.triggers.length > 10) {
|
|
774
|
+
console.log(chalk.cyan(`│ ... and ${info.triggers.length - 10} more`));
|
|
775
|
+
}
|
|
444
776
|
}
|
|
777
|
+
|
|
778
|
+
console.log(chalk.cyan('└─────────────────────────────────────────────────────┘'));
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Read SKILL.md content
|
|
783
|
+
async function readSkillMd(skill) {
|
|
784
|
+
const skillMdPath = path.join(skill.path, 'SKILL.md');
|
|
785
|
+
if (await fs.pathExists(skillMdPath)) {
|
|
786
|
+
const content = await fs.readFile(skillMdPath, 'utf-8');
|
|
787
|
+
console.log(chalk.cyan(`\n📄 ${skill.name}/SKILL.md`));
|
|
788
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
789
|
+
console.log(content);
|
|
790
|
+
} else {
|
|
791
|
+
console.error(chalk.red('SKILL.md not found'));
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Edit SKILL.md
|
|
796
|
+
async function editSkillMd(skill) {
|
|
797
|
+
const skillMdPath = path.join(skill.path, 'SKILL.md');
|
|
798
|
+
await openInEditor(skillMdPath);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// Show skill configuration
|
|
802
|
+
async function showSkillConfig(skill, fm, asJson = false) {
|
|
803
|
+
const configKey = skill.name;
|
|
804
|
+
const config = fm.config.get(configKey, {});
|
|
805
|
+
|
|
806
|
+
if (asJson) {
|
|
807
|
+
console.log(JSON.stringify({ skill: skill.name, config }, null, 2));
|
|
808
|
+
} else {
|
|
809
|
+
console.log(chalk.cyan(`\n┌─────────────────────────────────────────────────────┐`));
|
|
810
|
+
console.log(chalk.cyan(`│ ${skill.name} Configuration`));
|
|
445
811
|
console.log(chalk.cyan('├─────────────────────────────────────────────────────┤'));
|
|
812
|
+
|
|
813
|
+
if (Object.keys(config).length === 0) {
|
|
814
|
+
console.log(chalk.cyan('│ No configuration set'));
|
|
815
|
+
console.log(chalk.cyan('│'));
|
|
816
|
+
console.log(chalk.cyan('│ Default configuration from SKILL.md:'));
|
|
817
|
+
|
|
818
|
+
// Extract default config from SKILL.md
|
|
819
|
+
const skillMdPath = path.join(skill.path, 'SKILL.md');
|
|
820
|
+
if (await fs.pathExists(skillMdPath)) {
|
|
821
|
+
const content = await fs.readFile(skillMdPath, 'utf-8');
|
|
822
|
+
const configMatch = content.match(/## Configuration\n([\s\S]*?)(?=\n##|$)/);
|
|
823
|
+
if (configMatch) {
|
|
824
|
+
const configSection = configMatch[1].trim();
|
|
825
|
+
const lines = configSection.split('\n').slice(0, 15);
|
|
826
|
+
for (const line of lines) {
|
|
827
|
+
console.log(chalk.cyan(`│ ${line}`));
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
} else {
|
|
832
|
+
const configJson = JSON.stringify(config, null, 2);
|
|
833
|
+
configJson.split('\n').forEach(line => {
|
|
834
|
+
console.log(chalk.cyan(`│ ${line}`));
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
console.log(chalk.cyan('└─────────────────────────────────────────────────────┘'));
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// Set skill configuration
|
|
843
|
+
async function setSkillConfig(skill, fm, key, value) {
|
|
844
|
+
const configKey = skill.name;
|
|
845
|
+
const config = fm.config.get(configKey, {});
|
|
846
|
+
|
|
847
|
+
// Support nested keys like "security.enabled"
|
|
848
|
+
const keys = key.split('.');
|
|
849
|
+
let current = config;
|
|
850
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
851
|
+
if (!current[keys[i]]) current[keys[i]] = {};
|
|
852
|
+
current = current[keys[i]];
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// Try to parse value as JSON, fallback to string
|
|
856
|
+
let parsedValue;
|
|
857
|
+
try {
|
|
858
|
+
parsedValue = JSON.parse(value);
|
|
859
|
+
} catch {
|
|
860
|
+
parsedValue = value;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
current[keys[keys.length - 1]] = parsedValue;
|
|
864
|
+
fm.config.set(configKey, config);
|
|
865
|
+
await fm.config.save();
|
|
866
|
+
|
|
867
|
+
console.log(chalk.green(`✓ Set ${skill.name}.${key} = ${value}`));
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Resource file helpers
|
|
871
|
+
function resolveResourcePath(filePath, configDir) {
|
|
872
|
+
// If absolute path, use as-is
|
|
873
|
+
if (path.isAbsolute(filePath)) {
|
|
874
|
+
return filePath;
|
|
875
|
+
}
|
|
876
|
+
// Otherwise resolve relative to config dir
|
|
877
|
+
return path.join(configDir, filePath);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
async function listResourceFiles(dir, configDir) {
|
|
881
|
+
const files = [];
|
|
882
|
+
const entries = await fs.readdir(dir);
|
|
883
|
+
|
|
884
|
+
for (const entry of entries) {
|
|
885
|
+
const fullPath = path.join(dir, entry);
|
|
886
|
+
const stat = await fs.stat(fullPath);
|
|
887
|
+
const relativePath = path.relative(configDir, fullPath);
|
|
888
|
+
|
|
889
|
+
if (stat.isDirectory()) {
|
|
890
|
+
files.push({
|
|
891
|
+
name: entry,
|
|
892
|
+
path: relativePath,
|
|
893
|
+
type: 'directory',
|
|
894
|
+
size: 0
|
|
895
|
+
});
|
|
896
|
+
} else {
|
|
897
|
+
files.push({
|
|
898
|
+
name: entry,
|
|
899
|
+
path: relativePath,
|
|
900
|
+
type: 'file',
|
|
901
|
+
size: stat.size,
|
|
902
|
+
modified: stat.mtime
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
return files.sort((a, b) => {
|
|
908
|
+
if (a.type === b.type) return a.name.localeCompare(b.name);
|
|
909
|
+
return a.type === 'directory' ? -1 : 1;
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
function displayResourceFiles(files, dir) {
|
|
914
|
+
console.log(chalk.cyan(`\n📁 ${dir}`));
|
|
915
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
916
|
+
|
|
917
|
+
if (files.length === 0) {
|
|
918
|
+
console.log(chalk.cyan(' (empty)'));
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
for (const file of files) {
|
|
923
|
+
if (file.type === 'directory') {
|
|
924
|
+
console.log(chalk.blue(` 📁 ${file.name}/`));
|
|
925
|
+
} else {
|
|
926
|
+
const size = formatFileSize(file.size);
|
|
927
|
+
console.log(chalk.white(` 📄 ${file.name}`) + chalk.gray(` (${size})`));
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
function displayResourceConfig(config) {
|
|
933
|
+
console.log(chalk.cyan('\n┌─────────────────────────────────────────────────────┐'));
|
|
934
|
+
console.log(chalk.cyan('│ Resource Configuration │'));
|
|
935
|
+
console.log(chalk.cyan('├─────────────────────────────────────────────────────┤'));
|
|
936
|
+
|
|
937
|
+
const entries = Object.entries(config);
|
|
938
|
+
if (entries.length === 0) {
|
|
939
|
+
console.log(chalk.cyan('│ No resources configured'));
|
|
940
|
+
} else {
|
|
941
|
+
for (const [key, value] of entries) {
|
|
942
|
+
const enabled = value.enabled !== false;
|
|
943
|
+
const status = enabled ? chalk.green('✓') : chalk.red('✗');
|
|
944
|
+
console.log(chalk.cyan(`│ ${status} ${key}`));
|
|
945
|
+
if (value.path) {
|
|
946
|
+
console.log(chalk.cyan(`│ Path: ${value.path}`));
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
console.log(chalk.cyan('└─────────────────────────────────────────────────────┘'));
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
function displayAIStatus(status) {
|
|
955
|
+
console.log(chalk.cyan('\n┌─────────────────────────────────────────────────────┐'));
|
|
956
|
+
console.log(chalk.cyan('│ AI Model Status │'));
|
|
957
|
+
console.log(chalk.cyan('├─────────────────────────────────────────────────────┤'));
|
|
958
|
+
|
|
959
|
+
const initialized = status.initialized ? chalk.green('✓') : chalk.red('✗');
|
|
960
|
+
console.log(chalk.cyan(`│ Initialized: ${initialized}`));
|
|
961
|
+
console.log(chalk.cyan(`│ Default Provider: ${status.defaultProvider || 'None'}`));
|
|
962
|
+
|
|
963
|
+
console.log(chalk.cyan('├─────────────────────────────────────────────────────┤'));
|
|
964
|
+
console.log(chalk.cyan('│ Features:'));
|
|
965
|
+
|
|
966
|
+
const features = status.features || {};
|
|
967
|
+
for (const [feature, enabled] of Object.entries(features)) {
|
|
968
|
+
const statusIcon = enabled ? chalk.green('✓') : chalk.red('✗');
|
|
969
|
+
console.log(chalk.cyan(`│ ${statusIcon} ${feature}`));
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
console.log(chalk.cyan('├─────────────────────────────────────────────────────┤'));
|
|
973
|
+
console.log(chalk.cyan('│ Providers:'));
|
|
974
|
+
|
|
975
|
+
const providers = status.providers || {};
|
|
976
|
+
for (const [name, info] of Object.entries(providers)) {
|
|
977
|
+
const statusIcon = info.initialized ? chalk.green('✓') : chalk.red('✗');
|
|
978
|
+
console.log(chalk.cyan(`│ ${statusIcon} ${name}`));
|
|
979
|
+
if (info.info?.model) {
|
|
980
|
+
console.log(chalk.cyan(`│ Model: ${info.info.model}`));
|
|
981
|
+
}
|
|
446
982
|
}
|
|
447
983
|
|
|
448
984
|
console.log(chalk.cyan('└─────────────────────────────────────────────────────┘'));
|
|
449
985
|
}
|
|
450
986
|
|
|
987
|
+
function formatFileSize(bytes) {
|
|
988
|
+
if (bytes === 0) return '0 B';
|
|
989
|
+
const k = 1024;
|
|
990
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
991
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
992
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
async function openInEditor(filePath) {
|
|
996
|
+
const { spawn } = require('child_process');
|
|
997
|
+
const editor = process.env.EDITOR || process.env.VISUAL || 'vi';
|
|
998
|
+
|
|
999
|
+
console.log(chalk.cyan(`Opening ${filePath} in ${editor}...`));
|
|
1000
|
+
|
|
1001
|
+
return new Promise((resolve, reject) => {
|
|
1002
|
+
const child = spawn(editor, [filePath], {
|
|
1003
|
+
stdio: 'inherit',
|
|
1004
|
+
shell: true
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
child.on('close', (code) => {
|
|
1008
|
+
if (code === 0) {
|
|
1009
|
+
resolve();
|
|
1010
|
+
} else {
|
|
1011
|
+
reject(new Error(`Editor exited with code ${code}`));
|
|
1012
|
+
}
|
|
1013
|
+
});
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
|
|
451
1017
|
// Parse arguments
|
|
452
1018
|
program.parse(process.argv);
|
|
453
1019
|
|