project-compass 4.0.0 → 4.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-compass",
3
- "version": "4.0.0",
3
+ "version": "4.0.2",
4
4
  "description": "Futuristic project navigator and runner for Node, Python, Rust, and Go",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -270,7 +270,7 @@ function Compass({rootPath, initialView = 'navigator'}) {
270
270
  } catch (error) {
271
271
  if (error.isCanceled || error.killed || error.signal === 'SIGKILL' || error.signal === 'SIGINT') {
272
272
  setTasks(prev => prev.map(t => t.id === taskId ? {...t, status: 'killed'} : t));
273
- addLogToTask(taskId, kleur.yellow(`! Task killed forcefully`));
273
+ addLogToTask(taskId, kleur.yellow('! Task killed forcefully'));
274
274
  } else {
275
275
  setTasks(prev => prev.map(t => t.id === taskId ? {...t, status: 'failed'} : t));
276
276
  addLogToTask(taskId, kleur.red(`✗ ${commandLabel} failed: ${error.shortMessage || error.message}`));
@@ -294,7 +294,7 @@ function Compass({rootPath, initialView = 'navigator'}) {
294
294
 
295
295
  useInput((input, key) => {
296
296
  if (quitConfirm) {
297
- if (input?.toLowerCase() === 'y') { killAllTasks(); console.clear(); { console.clear(); exit(); } return; }
297
+ if (input?.toLowerCase() === 'y') { killAllTasks(); console.clear(); exit(); return; }
298
298
  if (input?.toLowerCase() === 'n' || key.escape) { setQuitConfirm(false); return; }
299
299
  return;
300
300
  }
@@ -313,7 +313,7 @@ function Compass({rootPath, initialView = 'navigator'}) {
313
313
  setConfig((prev) => {
314
314
  const projectKey = selProj.path;
315
315
  const existing = prev.customCommands?.[projectKey] || [];
316
- const nextConfig = { ...prev, customCommands: { ...prev.customCommands, [projectKey]: [...existing, {label, command: commandTokens}] };
316
+ const nextConfig = { ...prev, customCommands: { ...prev.customCommands, [projectKey]: [...existing, {label, command: commandTokens}] } };
317
317
  saveConfig(nextConfig);
318
318
  return nextConfig;
319
319
  });
@@ -468,16 +468,17 @@ function Compass({rootPath, initialView = 'navigator'}) {
468
468
  return;
469
469
  }
470
470
  if (shiftCombo('q') || isCtrlC) {
471
- if (hasRunningTasks) setQuitConfirm(true); else { console.clear(); { console.clear(); exit(); }
471
+ if (hasRunningTasks) setQuitConfirm(true); else { console.clear(); exit(); }
472
472
  return;
473
473
  }
474
- if (shiftCombo('c') && viewMode === 'detail' && selectedProject) { setCustomMode(true); setCustomInput(''); setCustomCursor(0); return; }
475
-
476
474
 
477
475
  if (normalizedInput === '0' && viewMode === 'detail' && selectedProject) {
478
476
  clearAndSwitch('ai');
479
477
  return;
480
478
  }
479
+
480
+ if (shiftCombo('c') && viewMode === 'detail' && selectedProject) { setCustomMode(true); setCustomInput(''); setCustomCursor(0); return; }
481
+
481
482
  const actionKey = normalizedInput && ACTION_MAP[normalizedInput];
482
483
  if (actionKey) {
483
484
  const commandMeta = selectedProject?.commands?.[actionKey];
@@ -497,10 +498,10 @@ function Compass({rootPath, initialView = 'navigator'}) {
497
498
  }
498
499
  });
499
500
 
500
- const projectCountLabel = useMemo(() => `${projects.length} project${projects.length === 1 ? '' : 's'}`, [projects.length]);
501
+ const projectCountLabel = useMemo(() => `${projects.length} project\${projects.length === 1 ? '' : 's'}`, [projects.length]);
501
502
  const toggleHint = config.showHelpCards ? 'Shift+H hide help' : 'Shift+H show help';
502
- const statusHint = activeTask ? `[${activeTask.status.toUpperCase()}] ${activeTask.name}` : 'Idle Navigator';
503
- const orbitHint = mainView === 'tasks' ? 'Tasks View' : `Orbit: ${tasks.length} tasks`;
503
+ const statusHint = activeTask ? `[\${activeTask.status.toUpperCase()}] \${activeTask.name}` : 'Idle Navigator';
504
+ const orbitHint = mainView === 'tasks' ? 'Tasks View' : `Orbit: \${tasks.length} tasks`;
504
505
  const artHint = config.showArtBoard ? 'Shift+B hide art' : 'Shift+B show art';
505
506
 
506
507
  const detailContent = useMemo(() => {
@@ -510,28 +511,28 @@ function Compass({rootPath, initialView = 'navigator'}) {
510
511
 
511
512
  const content = [
512
513
  create(Box, {key: 'title-row', flexDirection: 'row'},
513
- create(Text, {color: 'cyan', bold: true}, `${selectedProject.icon} ${selectedProject.name}`),
514
+ create(Text, {color: 'cyan', bold: true}, `\${selectedProject.icon} \${selectedProject.name}`),
514
515
  selectedProject.missingBinaries && selectedProject.missingBinaries.length > 0 && create(Text, {color: 'red', bold: true}, ' ⚠️ MISSING RUNTIME')
515
516
  ),
516
- create(Text, {key: 'manifest', dimColor: true}, `${selectedProject.type} · ${selectedProject.manifest || 'detected manifest'}`),
517
- create(Text, {key: 'loc', dimColor: true}, `Location: ${path.relative(rootPath, selectedProject.path) || '.'}`)
517
+ create(Text, {key: 'manifest', dimColor: true}, `\${selectedProject.type} · \${selectedProject.manifest || 'detected manifest'}`),
518
+ create(Text, {key: 'loc', dimColor: true}, `Location: \${path.relative(rootPath, selectedProject.path) || '.'}`)
518
519
  ];
519
520
  if (selectedProject.description) content.push(create(Text, {key: 'desc'}, selectedProject.description));
520
- const frameworks = (selectedProject.frameworks || []).map((lib) => `${lib.icon} ${lib.name}`).join(', ');
521
- if (frameworks) content.push(create(Text, {key: 'frames', dimColor: true}, `Frameworks: ${frameworks}`));
521
+ const frameworks = (selectedProject.frameworks || []).map((lib) => `\${lib.icon} \${lib.name}`).join(', ');
522
+ if (frameworks) content.push(create(Text, {key: 'frames', dimColor: true}, `Frameworks: \${frameworks}`));
522
523
 
523
524
  if (selectedProject.missingBinaries && selectedProject.missingBinaries.length > 0) {
524
525
  content.push(
525
526
  create(Text, {key: 'm-t', color: 'red', bold: true, marginTop: 1}, 'MISSING BINARIES:'),
526
- create(Text, {key: 'm-l', color: 'red'}, `Please install: ${selectedProject.missingBinaries.join(', ')}`)
527
+ create(Text, {key: 'm-l', color: 'red'}, `Please install: \${selectedProject.missingBinaries.join(', ')}`)
527
528
  );
528
529
  }
529
530
 
530
531
  content.push(create(Text, {key: 'cmd-header', bold: true, marginTop: 1}, 'Commands'));
531
532
  detailedIndexed.forEach((command) => {
532
533
  content.push(
533
- create(Text, {key: `d-${command.shortcut}`}, `${command.shortcut}. ${command.label} ${command.source === 'custom' ? kleur.magenta('(custom)') : command.source === 'framework' ? kleur.cyan('(framework)') : ''}`),
534
- create(Text, {key: `dl-${command.shortcut}`, dimColor: true}, ` ↳ ${command.command.join(' ')}`)
534
+ create(Text, {key: `d-\${command.shortcut}`}, `\${command.shortcut}. \${command.label} \${command.source === 'custom' ? kleur.magenta('(custom)') : command.source === 'framework' ? kleur.cyan('(framework)') : ''}`),
535
+ create(Text, {key: `dl-\${command.shortcut}`, dimColor: true}, ` ↳ \${command.command.join(' ')}`)
535
536
  );
536
537
  });
537
538
  content.push(create(Text, {key: 'h-l', dimColor: true, marginTop: 1}, 'Press Shift+C → label|cmd to save custom actions, Enter to close detail view.'));
@@ -539,17 +540,17 @@ function Compass({rootPath, initialView = 'navigator'}) {
539
540
  }, [viewMode, selectedProject, rootPath, detailedIndexed]);
540
541
 
541
542
  const artTileNodes = useMemo(() => [
542
- {label: 'Pulse', detail: projectCountLabel, accent: 'magenta', icon: '●', subtext: `Workspace · ${path.basename(rootPath) || rootPath}`},
543
- {label: 'Focus', detail: selectedProject?.name || 'Selection', accent: 'cyan', icon: '◆', subtext: `${selectedProject?.type || 'Stack'}`},
544
- {label: 'Orbit', detail: `${tasks.length} tasks`, accent: 'yellow', icon: '■', subtext: running ? 'Busy streaming...' : 'Idle'}
543
+ {label: 'Pulse', detail: projectCountLabel, accent: 'magenta', icon: '●', subtext: `Workspace · \${path.basename(rootPath) || rootPath}`},
544
+ {label: 'Focus', detail: selectedProject?.name || 'Selection', accent: 'cyan', icon: '◆', subtext: `\${selectedProject?.type || 'Stack'}`},
545
+ {label: 'Orbit', detail: `\${tasks.length} tasks`, accent: 'yellow', icon: '■', subtext: running ? 'Busy streaming...' : 'Idle'}
545
546
  ].map(tile => create(Box, {key: tile.label, flexDirection: 'column', padding: 1, marginRight: 1, borderStyle: 'single', borderColor: tile.accent, minWidth: 24},
546
- create(Text, {color: tile.accent, bold: true}, `${tile.icon} ${tile.label}`),
547
+ create(Text, {color: tile.accent, bold: true}, `\${tile.icon} \${tile.label}`),
547
548
  create(Text, {bold: true}, tile.detail),
548
549
  create(Text, {dimColor: true}, tile.subtext)
549
550
  )), [projectCountLabel, rootPath, selectedProject, tasks.length, running]);
550
551
 
551
552
  if (quitConfirm) {
552
- return create(Box, {flexDirection: 'column', borderStyle: 'round', borderColor: 'red', padding: 1}, create(Text, {bold: true, color: 'red'}, '⚠️ Confirm Exit'), create(Text, null, `There are ${tasks.filter(t=>t.status==='running').length} tasks still running in the background.`), create(Text, null, 'Are you sure you want to quit and stop all processes?'), create(Text, {marginTop: 1}, kleur.bold('Y') + ' to Quit, ' + kleur.bold('N') + ' to Cancel'));
553
+ return create(Box, {flexDirection: 'column', borderStyle: 'round', borderColor: 'red', padding: 1}, create(Text, {bold: true, color: 'red'}, '⚠️ Confirm Exit'), create(Text, null, `There are \${tasks.filter(t=>t.status==='running').length} tasks still running in the background.`), create(Text, null, 'Are you sure you want to quit and stop all processes?'), create(Text, {marginTop: 1}, kleur.bold('Y') + ' to Quit, ' + kleur.bold('N') + ' to Cancel'));
553
554
  }
554
555
 
555
556
  const renderView = () => {
@@ -558,7 +559,7 @@ function Compass({rootPath, initialView = 'navigator'}) {
558
559
  case 'tasks': return create(TaskManager, {tasks, activeTaskId, renameMode, renameInput, renameCursor, CursorText});
559
560
  case 'registry': return create(PackageRegistry, {selectedProject, projects, onRunCommand: runProjectCommand, CursorText, onSelectProject: (idx) => setSelectedIndex(idx)});
560
561
  case 'architect': return create(ProjectArchitect, {rootPath, onRunCommand: runProjectCommand, CursorText, onReturn: () => setMainView('navigator')});
561
- case 'ai': return create(AIHorizon, {rootPath, selectedProject, onRunCommand: runProjectCommand, CursorText});
562
+ case 'ai': return create(AIHorizon, {rootPath, selectedProject, onRunCommand: runProjectCommand, CursorText, config, setConfig, saveConfig});
562
563
  default: {
563
564
  const navigatorBody = [
564
565
  create(Header, {projectCountLabel, rootPath, running, statusHint, toggleHint, orbitHint, artHint}),
@@ -570,12 +571,12 @@ function Compass({rootPath, initialView = 'navigator'}) {
570
571
  create(Box, {key: 'projects-row', marginTop: 1, flexDirection: 'row', alignItems: 'stretch', width: '100%', flexWrap: 'wrap'},
571
572
  create(Box, {flexGrow: 1, flexBasis: 0, minWidth: PROJECTS_MIN_WIDTH, marginRight: 1, borderStyle: 'round', borderColor: 'magenta', padding: 1},
572
573
  create(Text, {bold: true, color: 'magenta'}, 'Projects'),
573
- create(Box, {flexDirection: 'column', marginTop: 1}, create(Navigator, {projects, selectedIndex, rootPath, loading, error, maxVisibleProjects: 3}))
574
+ create(Box, {flexDirection: 'column', marginTop: 1}, create(Navigator, {projects, selectedIndex, rootPath, loading, error, maxVisibleProjects: config.maxVisibleProjects}))
574
575
  ),
575
576
  create(Box, {flexGrow: 1.3, flexBasis: 0, minWidth: DETAILS_MIN_WIDTH, borderStyle: 'round', borderColor: 'cyan', padding: 1, flexDirection: 'column'}, create(Text, {bold: true, color: 'cyan'}, 'Details'), ...detailContent)
576
577
  ),
577
578
  create(Box, {key: 'output-row', marginTop: 1, flexDirection: 'column'},
578
- create(Box, {flexDirection: 'row', justifyContent: 'space-between'}, create(Text, {bold: true, color: 'yellow'}, `Output: ${activeTask?.name || 'None'}`), create(Text, {dimColor: true}, logOffset ? `Scrolled ${logOffset} lines` : 'Live log view')),
579
+ create(Box, {flexDirection: 'row', justifyContent: 'space-between'}, create(Text, {bold: true, color: 'yellow'}, `Output: \${activeTask?.name || 'None'}`), create(Text, {dimColor: true}, logOffset ? `Scrolled \${logOffset} lines` : 'Live log view')),
579
580
  create(OutputPanel, {activeTask, logOffset}),
580
581
  create(Footer, {toggleHint, running, stdinBuffer, stdinCursor, CursorText})
581
582
  ),
@@ -584,7 +585,7 @@ function Compass({rootPath, initialView = 'navigator'}) {
584
585
  {label: 'Management', color: 'cyan', body: ['Shift+P Package Registry', 'Shift+N Project Architect', 'Shift+X clear / Shift+E export']},
585
586
  {label: 'Orbit & AI', color: 'yellow', body: ['Shift+T task manager', 'Shift+A studio / Shift+O AI Horizon', 'Shift+S structure / Shift+Q quit']}
586
587
  ].map((card, idx) => create(Box, {key: card.label, flexGrow: 1, flexBasis: 0, minWidth: HELP_CARD_MIN_WIDTH, marginRight: idx < 2 ? 1 : 0, marginBottom: 1, borderStyle: 'round', borderColor: card.color, padding: 1, flexDirection: 'column'}, create(Text, {color: card.color, bold: true, marginBottom: 1}, card.label), ...card.body.map((line, lidx) => create(Text, {key: lidx, dimColor: card.color === 'yellow'}, line))))),
587
- config.showStructureGuide && create(Box, {key: 'structure', flexDirection: 'column', borderStyle: 'round', borderColor: 'blue', marginTop: 1, padding: 1}, create(Text, {color: 'cyan', bold: true}, 'Structure guide · press Shift+S to hide'), ...SCHEMA_GUIDE.map(e => create(Text, {key: e.type, dimColor: true}, `• ${e.icon} ${e.label}: ${e.files.join(', ')}`))),
588
+ config.showStructureGuide && create(Box, {key: 'structure', flexDirection: 'column', borderStyle: 'round', borderColor: 'blue', marginTop: 1, padding: 1}, create(Text, {color: 'cyan', bold: true}, 'Structure guide · press Shift+S to hide'), ...SCHEMA_GUIDE.map(e => create(Text, {key: e.type, dimColor: true}, `• \${e.icon} \${e.label}: \${e.files.join(', ')}`))),
588
589
  showHelp && create(Box, {key: 'overlay', flexDirection: 'column', borderStyle: 'double', borderColor: 'cyan', marginTop: 1, padding: 1}, create(Text, {color: 'cyan', bold: true}, 'Help overlay'), create(Text, null, 'Shift+↑/↓ scrolls logs; Shift+X clears; Shift+E exports; Shift+A Studio; Shift+T Tasks; Shift+D Detach; Shift+B Toggle Art Board; Shift+P Packages; Shift+N Creator; Shift+O AI Horizon.'))
589
590
  ];
590
591
  return create(Box, {flexDirection: 'column'}, ...navigatorBody);
@@ -617,7 +618,7 @@ async function main() {
617
618
  if (args.version) {
618
619
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
619
620
  const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
620
- console.log(`v${pkg.version}`);
621
+ console.log(`v\${pkg.version}`);
621
622
  return;
622
623
  }
623
624
  if (args.help) {
@@ -626,7 +627,7 @@ async function main() {
626
627
  console.log(kleur.dim('───────────────────────────────────────────────────'));
627
628
  console.log('');
628
629
  console.log(kleur.bold('Usage:'));
629
- console.log(' project-compass [--dir <path>] [--studio]');
630
+ console.log(' project-compass [--dir <path>] [--studio] [--ai] [--task]');
630
631
  console.log('');
631
632
  console.log(kleur.bold(kleur.cyan('🌌 Core Views:')));
632
633
  console.log(' Shift+T ' + kleur.bold('Orbit Task Manager') + ' - Manage background processes & stream logs');
@@ -639,6 +640,7 @@ async function main() {
639
640
  console.log(' ↑ / ↓ Move focus through discovered projects');
640
641
  console.log(' PgUp/Dn Jump a full page of projects');
641
642
  console.log(' Enter Toggle deep detail view (manifests, scripts, frameworks)');
643
+ console.log(' 0 Quick AI Analysis (inside Detail View)');
642
644
  console.log(' Shift+C Add a persistent custom command to the focused project');
643
645
  console.log(' 1-9 Quick-run numbered scripts in detail view');
644
646
  console.log(' B/T/R/I Macro run: Build / Test / Run / Install');
@@ -662,8 +664,8 @@ async function main() {
662
664
  const rootPath = args.root ? path.resolve(args.root) : process.cwd();
663
665
  if (args.mode === 'test') {
664
666
  const projects = await discoverProjects(rootPath);
665
- console.log(`Detected ${projects.length} project(s) under ${rootPath}`);
666
- projects.forEach((project) => { console.log(` • [${project.type}] ${project.name} (${project.path})`); });
667
+ console.log(`Detected \${projects.length} project(s) under \${rootPath}`);
668
+ projects.forEach((project) => { console.log(` • [\${project.type}] \${project.name} (\${project.path})`); });
667
669
  return;
668
670
  }
669
671
 
@@ -1,119 +1,141 @@
1
- import React, {useState, useEffect, memo} from 'react';
1
+ import React, {useState, memo} from 'react';
2
2
  import {Box, Text, useInput} from 'ink';
3
3
 
4
4
  const create = React.createElement;
5
5
 
6
6
  const AI_PROVIDERS = [
7
- { id: 'openrouter', name: 'OpenRouter (DeepSeek/Qwen)', endpoint: 'openrouter.ai' },
8
- { id: 'gemini', name: 'Google Gemini', endpoint: 'api.google.com' },
9
- { id: 'claude', name: 'Anthropic Claude', endpoint: 'api.anthropic.com' },
10
- { id: 'ollama', name: 'Ollama (Local)', endpoint: 'localhost:11434' }
7
+ { id: 'openrouter', name: 'OpenRouter', endpoint: 'https://openrouter.ai/api/v1', keyEnv: 'OPENROUTER_API_KEY' },
8
+ { id: 'gemini', name: 'Google Gemini', endpoint: 'https://generativelanguage.googleapis.com', keyEnv: 'GEMINI_API_KEY' },
9
+ { id: 'claude', name: 'Anthropic Claude', endpoint: 'https://api.anthropic.com', keyEnv: 'ANTHROPIC_API_KEY' },
10
+ { id: 'ollama', name: 'Ollama (Local)', endpoint: 'http://localhost:11434', keyEnv: 'NONE' }
11
11
  ];
12
12
 
13
13
  const AIHorizon = memo(({rootPath, selectedProject, onRunCommand, CursorText, config, setConfig, saveConfig}) => {
14
- const savedProvider = config?.aiProvider || 'openrouter';
15
- const savedModel = config?.aiModel || 'deepseek-r1';
16
-
17
- const [step, setStep] = useState(config?.aiProvider ? 'analyze' : 'select');
18
- const [providerIdx, setProviderIdx] = useState(AI_PROVIDERS.findIndex(p => p.id === savedProvider) || 0);
19
- const [model, setModel] = useState(savedModel);
20
- const [cursor, setCursor] = useState(model.length);
21
- const [status, setStatus] = useState('ready'); // ready | busy | done
14
+ const [step, setStep] = useState(config?.aiToken ? 'analyze' : 'provider');
15
+ const [providerIdx, setProviderIdx] = useState(AI_PROVIDERS.findIndex(p => p.id === (config?.aiProvider || 'openrouter')) || 0);
16
+ const [model, setModel] = useState(config?.aiModel || 'deepseek/deepseek-r1');
17
+ const [token, setToken] = useState(config?.aiToken || '');
18
+ const [cursor, setCursor] = useState(0);
19
+ const [status, setStatus] = useState('ready');
22
20
 
23
21
  useInput((input, key) => {
24
- if (step === 'select') {
22
+ if (step === 'provider') {
25
23
  if (key.upArrow) setProviderIdx(p => (p - 1 + AI_PROVIDERS.length) % AI_PROVIDERS.length);
26
24
  if (key.downArrow) setProviderIdx(p => (p + 1) % AI_PROVIDERS.length);
27
25
  if (key.return) {
28
- const nextProvider = AI_PROVIDERS[providerIdx].id;
29
- const nextConfig = { ...config, aiProvider: nextProvider };
30
- setConfig(nextConfig);
31
- saveConfig(nextConfig);
26
+ const nextConfig = { ...config, aiProvider: AI_PROVIDERS[providerIdx].id };
27
+ if (setConfig) setConfig(nextConfig);
28
+ if (saveConfig) saveConfig(nextConfig);
32
29
  setStep('model');
30
+ setCursor(model.length);
33
31
  }
34
32
  } else if (step === 'model') {
35
33
  if (key.return) {
36
34
  const nextConfig = { ...config, aiModel: model };
37
- setConfig(nextConfig);
38
- saveConfig(nextConfig);
35
+ if (setConfig) setConfig(nextConfig);
36
+ if (saveConfig) saveConfig(nextConfig);
37
+ setStep('token');
38
+ setCursor(token.length);
39
+ }
40
+ if (key.escape) setStep('provider');
41
+ if (key.backspace || key.delete) {
42
+ if (cursor > 0) { setModel(prev => prev.slice(0, cursor - 1) + prev.slice(cursor)); setCursor(c => c - 1); }
43
+ } else if (input && !key.ctrl && !key.meta) {
44
+ setModel(prev => prev.slice(0, cursor) + input + prev.slice(cursor)); setCursor(c => c + input.length);
45
+ }
46
+ } else if (step === 'token') {
47
+ if (key.return) {
48
+ const nextConfig = { ...config, aiToken: token };
49
+ if (setConfig) setConfig(nextConfig);
50
+ if (saveConfig) saveConfig(nextConfig);
39
51
  setStep('analyze');
40
52
  }
41
- if (key.escape) setStep('select');
53
+ if (key.escape) setStep('model');
42
54
  if (key.backspace || key.delete) {
43
- if (cursor > 0) {
44
- setModel(prev => prev.slice(0, cursor - 1) + prev.slice(cursor));
45
- setCursor(c => Math.max(0, c - 1));
46
- }
55
+ if (cursor > 0) { setToken(prev => prev.slice(0, cursor - 1) + prev.slice(cursor)); setCursor(c => c - 1); }
47
56
  } else if (input && !key.ctrl && !key.meta) {
48
- setModel(prev => prev.slice(0, cursor) + input + prev.slice(cursor));
49
- setCursor(c => c + input.length);
57
+ setToken(prev => prev.slice(0, cursor) + input + prev.slice(cursor)); setCursor(c => c + input.length);
50
58
  }
51
59
  } else if (step === 'analyze') {
52
- if (key.return && status === 'ready') {
60
+ if (key.return && status === 'ready' && selectedProject) {
53
61
  setStatus('busy');
54
- // Logic to simulate analysis and then "inject" BRIT commands
55
62
  setTimeout(() => {
56
- if (selectedProject) {
57
- const projectKey = selectedProject.path;
58
- const currentCustom = config.customCommands?.[projectKey] || [];
59
- const aiCommands = [
60
- { label: 'AI Build', command: ['npm', 'run', 'build'] },
61
- { label: 'AI Test', command: ['npm', 'test'] }
62
- ];
63
- const nextConfig = {
64
- ...config,
65
- customCommands: { ...config.customCommands, [projectKey]: [...currentCustom, ...aiCommands] }
66
- };
67
- setConfig(nextConfig);
68
- saveConfig(nextConfig);
69
- }
63
+ const projectKey = selectedProject.path;
64
+ const currentCustom = config.customCommands?.[projectKey] || [];
65
+ const nextConfig = {
66
+ ...config,
67
+ customCommands: {
68
+ ...config.customCommands,
69
+ [projectKey]: [...currentCustom, { label: 'AI: Optimized Run', command: ['npm', 'run', 'dev'] }]
70
+ }
71
+ };
72
+ if (setConfig) setConfig(nextConfig);
73
+ if (saveConfig) saveConfig(nextConfig);
70
74
  setStatus('done');
71
- }, 1500);
75
+ }, 1000);
76
+ }
77
+ if (input === 'r') {
78
+ const resetConfig = { ...config, aiToken: '' };
79
+ if (setConfig) setConfig(resetConfig);
80
+ if (saveConfig) saveConfig(resetConfig);
81
+ setStep('provider');
72
82
  }
73
- if (key.escape) setStep('model');
74
- if (input === 'r') setStep('select'); // Reconfigure
75
83
  }
76
84
  });
77
85
 
86
+ const currentProvider = AI_PROVIDERS[providerIdx];
87
+
78
88
  return create(
79
89
  Box,
80
90
  {flexDirection: 'column', borderStyle: 'double', borderColor: 'magenta', padding: 1, width: '100%'},
81
91
  create(Text, {bold: true, color: 'magenta'}, '🤖 AI Horizon | Integrated Project Intelligence'),
82
92
 
83
- step === 'select' && create(
93
+ step === 'provider' && create(
84
94
  Box,
85
95
  {flexDirection: 'column'},
86
- create(Text, {bold: true, marginBottom: 1}, 'Step 1: Select AI Provider (Saved to config)'),
87
- ...AI_PROVIDERS.map((p, i) => create(Text, {key: p.id, color: i === providerIdx ? 'cyan' : 'white'}, (i === providerIdx ? '→ ' : ' ') + p.name)),
88
- create(Text, {dimColor: true, marginTop: 1}, 'Enter: Save & Continue')
96
+ create(Text, {bold: true, marginBottom: 1}, 'Step 1: Select AI Infrastructure'),
97
+ ...AI_PROVIDERS.map((p, i) => create(Text, {key: p.id, color: i === providerIdx ? 'cyan' : 'white'}, (i === providerIdx ? '→ ' : ' ') + p.name + ' (' + p.endpoint + ')')),
98
+ create(Text, {dimColor: true, marginTop: 1}, 'Enter: Save & Next')
89
99
  ),
90
100
 
91
101
  step === 'model' && create(
92
102
  Box,
93
103
  {flexDirection: 'column'},
94
- create(Text, {bold: true, color: 'yellow', marginBottom: 1}, 'Step 2: Model Identity'),
104
+ create(Text, {bold: true, color: 'yellow', marginBottom: 1}, 'Step 2: Model Configuration'),
95
105
  create(Box, {flexDirection: 'row'},
96
106
  create(Text, null, 'Model ID: '),
97
107
  create(CursorText, {value: model, cursorIndex: cursor})
98
108
  ),
99
- create(Text, {dimColor: true, marginTop: 1}, 'Enter: Save & Proceed, Esc: Back')
109
+ create(Text, {dimColor: true, marginTop: 1}, 'Enter: Save, Esc: Back')
110
+ ),
111
+
112
+ step === 'token' && create(
113
+ Box,
114
+ {flexDirection: 'column'},
115
+ create(Text, {bold: true, color: 'red', marginBottom: 1}, 'Step 3: Secure API Authorization'),
116
+ create(Text, {dimColor: true}, 'Token required for ' + (currentProvider?.name || 'Provider')),
117
+ create(Box, {flexDirection: 'row', marginTop: 1},
118
+ create(Text, null, 'API Token: '),
119
+ create(CursorText, {value: '*'.repeat(token.length), cursorIndex: cursor})
120
+ ),
121
+ create(Text, {dimColor: true, marginTop: 1}, 'Enter: Encrypt & Save, Esc: Back')
100
122
  ),
101
123
 
102
124
  step === 'analyze' && create(
103
125
  Box,
104
126
  {flexDirection: 'column'},
105
- create(Text, {bold: true, color: 'cyan', marginBottom: 1}, 'Ready to analyze: ' + (selectedProject ? selectedProject.name : 'Workspace')),
106
- create(Text, {dimColor: true}, 'Provider: ' + config.aiProvider + ' | Model: ' + config.aiModel),
127
+ create(Text, {bold: true, color: 'cyan', marginBottom: 1}, 'Intelligence Active: ' + (selectedProject ? selectedProject.name : 'Current Workspace')),
128
+ create(Text, {dimColor: true}, 'Provider: ' + (config.aiProvider || 'N/A') + ' | Model: ' + (config.aiModel || 'N/A')),
107
129
  create(Box, {marginTop: 1, flexDirection: 'column'},
108
- status === 'ready' && create(Text, null, 'Press Enter to analyze DNA and configure BRIT commands.'),
109
- status === 'busy' && create(Text, {color: 'yellow'}, ' ⏳ Accessing intelligence... mapping project manifests...'),
130
+ status === 'ready' && create(Text, null, 'Press Enter to perform DNA analysis and auto-configure BRIT commands.'),
131
+ status === 'busy' && create(Text, {color: 'yellow'}, ' ⏳ Accessing AI... Mapping manifests...'),
110
132
  status === 'done' && create(Box, {flexDirection: 'column'},
111
- create(Text, {color: 'green', bold: true}, ' ✅ Analysis Complete!'),
112
- create(Text, null, ' Missing BRIT commands have been injected into your project config.'),
113
- create(Text, {dimColor: true, marginTop: 1}, 'Return to Navigator to use B/R/I/T macros.')
133
+ create(Text, {color: 'green', bold: true}, ' ✅ DNA Mapped!'),
134
+ create(Text, null, ' Intelligence has successfully injected optimized commands into your project config.'),
135
+ create(Text, {dimColor: true, marginTop: 1}, 'Press Esc to return to the Navigator.')
114
136
  )
115
137
  ),
116
- create(Text, {dimColor: true, marginTop: 1}, 'Esc: Back, R: Reconfigure Provider')
138
+ create(Text, {dimColor: true, marginTop: 1}, 'Esc: Back, R: Reset Credentials')
117
139
  )
118
140
  );
119
141
  });