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/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
- .action(async () => {
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.0.0',
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
- .action(async () => {
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
- displaySkills(skills);
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
- console.log(chalk.cyan(`│ ${skill.description.substring(0, 50)}...`));
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