project-compass 3.3.8 → 3.4.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/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "project-compass",
3
- "version": "3.3.8",
4
- "description": "Ink-based project explorer that detects local repos and lets you build/test/run them without memorizing commands.",
3
+ "version": "3.4.0",
4
+ "description": "Futuristic project navigator and runner for Node, Python, Rust, and Go",
5
5
  "main": "src/cli.js",
6
- "type": "module",
7
6
  "bin": {
8
7
  "project-compass": "src/cli.js"
9
8
  },
9
+ "type": "module",
10
10
  "scripts": {
11
11
  "start": "node src/cli.js",
12
12
  "test": "node src/cli.js --mode test",
@@ -14,21 +14,22 @@
14
14
  },
15
15
  "keywords": [
16
16
  "cli",
17
+ "navigator",
17
18
  "ink",
18
- "project",
19
19
  "runner",
20
- "dashboard"
20
+ "projects"
21
21
  ],
22
22
  "author": "Satyaa & Clawdy",
23
23
  "license": "MIT",
24
24
  "dependencies": {
25
- "execa": "^9.6.1",
25
+ "execa": "^9.5.2",
26
26
  "fast-glob": "^3.3.3",
27
- "ink": "^6.6.0",
28
- "kleur": "^4.1.5"
27
+ "ink": "^5.1.0",
28
+ "kleur": "^4.1.5",
29
+ "react": "^19.0.0"
29
30
  },
30
31
  "devDependencies": {
31
- "@eslint/js": "^10.0.1",
32
- "eslint": "^10.0.0"
32
+ "eslint": "^9.19.0",
33
+ "@eslint/js": "^9.19.0"
33
34
  }
34
35
  }
package/src/cli.js CHANGED
@@ -5,9 +5,15 @@ import path from 'path';
5
5
  import fs from 'fs';
6
6
  import kleur from 'kleur';
7
7
  import {execa} from 'execa';
8
- import {discoverProjects, SCHEMA_GUIDE, checkBinary} from './projectDetection.js';
8
+ import {discoverProjects, SCHEMA_GUIDE} from './projectDetection.js';
9
9
  import {CONFIG_PATH, ensureConfigDir} from './configPaths.js';
10
10
 
11
+ // Modular Components
12
+ import Studio from './components/Studio.js';
13
+ import TaskManager from './components/TaskManager.js';
14
+ import PackageRegistry from './components/PackageRegistry.js';
15
+ import ProjectArchitect from './components/ProjectArchitect.js';
16
+
11
17
  const create = React.createElement;
12
18
  const ART_CHARS = ['▁', '▃', '▄', '▅', '▇'];
13
19
  const ART_COLORS = ['magenta', 'blue', 'cyan', 'yellow', 'red'];
@@ -90,81 +96,6 @@ function buildDetailCommands(project, config) {
90
96
  return [...builtins, ...custom];
91
97
  }
92
98
 
93
- const Studio = memo(() => {
94
- const [runtimes, setRuntimes] = useState([]);
95
- const [loading, setLoading] = useState(true);
96
-
97
- useEffect(() => {
98
- const checks = [
99
- {name: 'Node.js', binary: 'node', versionCmd: ['-v']},
100
- {name: 'npm', binary: 'npm', versionCmd: ['-v']},
101
- {name: 'Python', binary: process.platform === 'win32' ? 'python' : 'python3', versionCmd: ['--version']},
102
- {name: 'Rust (Cargo)', binary: 'cargo', versionCmd: ['--version']},
103
- {name: 'Go', binary: 'go', versionCmd: ['version']},
104
- {name: 'Java', binary: 'java', versionCmd: ['-version']},
105
- {name: 'PHP', binary: 'php', versionCmd: ['-v']},
106
- {name: 'Ruby', binary: 'ruby', versionCmd: ['-v']},
107
- {name: '.NET', binary: 'dotnet', versionCmd: ['--version']}
108
- ];
109
-
110
- (async () => {
111
- const results = await Promise.all(checks.map(async (lang) => {
112
- if (!checkBinary(lang.binary)) {
113
- return {...lang, status: 'missing', version: 'not installed'};
114
- }
115
- try {
116
- const {stdout, stderr} = await execa(lang.binary, lang.versionCmd);
117
- const version = (stdout || stderr || '').split('\n')[0].trim();
118
- return {...lang, status: 'ok', version};
119
- } catch {
120
- return {...lang, status: 'error', version: 'failed to check'};
121
- }
122
- }));
123
- setRuntimes(results);
124
- setLoading(false);
125
- })();
126
- }, []);
127
-
128
- return create(
129
- Box,
130
- {flexDirection: 'column', borderStyle: 'double', borderColor: 'blue', padding: 1},
131
- create(Text, {bold: true, color: 'blue'}, '💎 Omni-Studio | Environment Intelligence'),
132
- create(Text, {dimColor: true, marginBottom: 1}, 'Overview of installed languages and build tools.'),
133
- loading
134
- ? create(Text, {dimColor: true}, 'Gathering intelligence...')
135
- : create(
136
- Box,
137
- {flexDirection: 'column'},
138
- ...runtimes.map(r => create(
139
- Box,
140
- {key: r.name, marginBottom: 0},
141
- create(Text, {width: 20, color: r.status === 'ok' ? 'green' : 'red'}, `${r.status === 'ok' ? '✓' : '✗'} ${r.name}`),
142
- create(Text, {dimColor: r.status !== 'ok'}, `: ${r.version}`)
143
- )),
144
- create(Text, {marginTop: 1, color: 'yellow'}, '🛠️ Interactive Project Creator coming soon in v3.0'),
145
- create(Text, {dimColor: true}, 'Press Shift+A to return to Navigator.')
146
- )
147
- );
148
- });
149
-
150
- const TaskManager = memo(({tasks, activeTaskId, renameMode, renameInput, renameCursor}) => {
151
- return create(
152
- Box,
153
- {flexDirection: 'column', borderStyle: 'round', borderColor: 'yellow', padding: 1},
154
- create(Text, {bold: true, color: 'yellow'}, '🛰️ Task Manager | Background Processes'),
155
- create(Text, {dimColor: true, marginBottom: 1}, 'Up/Down: focus, Shift+K: Force Kill, Shift+R: Rename'),
156
- ...tasks.map(t => create(
157
- Box,
158
- {key: t.id, marginBottom: 0, flexDirection: 'column'},
159
- t.id === activeTaskId && renameMode
160
- ? create(Box, {flexDirection: 'row'}, create(Text, {color: 'cyan'}, '→ Rename to: '), create(CursorText, {value: renameInput, cursorIndex: renameCursor}))
161
- : create(Text, {color: t.id === activeTaskId ? 'cyan' : 'white', bold: t.id === activeTaskId}, `${t.id === activeTaskId ? '→' : ' '} [${t.status.toUpperCase()}] ${t.name}`)
162
- )),
163
- !tasks.length && create(Text, {dimColor: true}, 'No active or background tasks.'),
164
- create(Text, {marginTop: 1, dimColor: true}, 'Press Enter or Shift+T to return to Navigator.')
165
- );
166
- });
167
-
168
99
  function CursorText({value, cursorIndex, active = true}) {
169
100
  const before = value.slice(0, cursorIndex);
170
101
  const charAt = value[cursorIndex] || ' ';
@@ -427,34 +358,15 @@ function Compass({rootPath, initialView = 'navigator'}) {
427
358
  const normalizedInput = input?.toLowerCase();
428
359
  const shiftCombo = (char) => key.shift && normalizedInput === char;
429
360
 
430
- if (shiftCombo('h')) {
431
- setConfig(prev => {
432
- const next = {...prev, showHelpCards: !prev.showHelpCards};
433
- saveConfig(next);
434
- return next;
435
- });
436
- return;
437
- }
438
- if (shiftCombo('s')) {
439
- setConfig(prev => {
440
- const next = {...prev, showStructureGuide: !prev.showStructureGuide};
441
- saveConfig(next);
442
- return next;
443
- });
444
- return;
445
- }
361
+ if (shiftCombo('h')) { setConfig(prev => { const next = {...prev, showHelpCards: !prev.showHelpCards}; saveConfig(next); return next; }); return; }
362
+ if (shiftCombo('s')) { setConfig(prev => { const next = {...prev, showStructureGuide: !prev.showStructureGuide}; saveConfig(next); return next; }); return; }
446
363
  if (shiftCombo('a')) { setMainView((prev) => (prev === 'navigator' ? 'studio' : 'navigator')); return; }
364
+ if (shiftCombo('p')) { setMainView((prev) => (prev === 'navigator' ? 'registry' : 'navigator')); return; }
365
+ if (shiftCombo('n')) { setMainView((prev) => (prev === 'navigator' ? 'architect' : 'navigator')); return; }
447
366
  if (shiftCombo('x')) { setTasks(prev => prev.map(t => t.id === activeTaskId ? {...t, logs: []} : t)); setLogOffset(0); return; }
448
367
  if (shiftCombo('e')) { exportLogs(); return; }
449
368
  if (shiftCombo('d')) { setActiveTaskId(null); return; }
450
- if (shiftCombo('b')) {
451
- setConfig(prev => {
452
- const next = {...prev, showArtBoard: !prev.showArtBoard};
453
- saveConfig(next);
454
- return next;
455
- });
456
- return;
457
- }
369
+ if (shiftCombo('b')) { setConfig(prev => { const next = {...prev, showArtBoard: !prev.showArtBoard}; saveConfig(next); return next; }); return; }
458
370
 
459
371
  if (shiftCombo('t')) {
460
372
  setMainView((prev) => {
@@ -485,6 +397,11 @@ function Compass({rootPath, initialView = 'navigator'}) {
485
397
  return;
486
398
  }
487
399
 
400
+ if (mainView === 'registry' || mainView === 'architect') {
401
+ // Inputs handled inside components
402
+ return;
403
+ }
404
+
488
405
  if (running && activeTaskId && runningProcessMap.current.has(activeTaskId)) {
489
406
  if (isCtrlC) { handleKillTask(activeTaskId); setStdinBuffer(''); setStdinCursor(0); return; }
490
407
  if (key.return) {
@@ -612,18 +529,14 @@ function Compass({rootPath, initialView = 'navigator'}) {
612
529
  create(Text, {dimColor: true}, tile.subtext)
613
530
  )), [projectCountLabel, rootPath, selectedProject, tasks.length, running]);
614
531
 
615
- const helpCards = [
616
- {label: 'Navigation', color: 'magenta', body: ['↑ / ↓ move focus, Enter: details', 'Shift+↑ / ↓ scroll output', 'Shift+H toggle help cards', 'Shift+D detach from task']},
617
- {label: 'Commands', color: 'cyan', body: ['B / T / R build/test/run', '1-9 / S+A-Z numbered commands', 'Shift+L rerun last command', 'Shift+X clear / Shift+E export']},
618
- {label: 'Orbit & Studio', color: 'yellow', body: ['Shift+T task manager', 'Shift+A studio / Shift+B art board', 'Shift+S structure / Shift+Q quit']}
619
- ];
620
-
621
532
  if (quitConfirm) {
622
533
  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'));
623
534
  }
624
535
 
625
536
  if (mainView === 'studio') return create(Studio);
626
- if (mainView === 'tasks') return create(TaskManager, {tasks, activeTaskId, renameMode, renameInput, renameCursor});
537
+ if (mainView === 'tasks') return create(TaskManager, {tasks, activeTaskId, setActiveTaskId, renameMode, renameInput, renameCursor, CursorText});
538
+ if (mainView === 'registry') return create(PackageRegistry, {selectedProject, onRunCommand: runProjectCommand, CursorText});
539
+ if (mainView === 'architect') return create(ProjectArchitect, {onRunCommand: runProjectCommand, CursorText});
627
540
 
628
541
  return create(Box, {flexDirection: 'column', padding: 1},
629
542
  create(Box, {justifyContent: 'space-between'},
@@ -646,11 +559,15 @@ function Compass({rootPath, initialView = 'navigator'}) {
646
559
  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')),
647
560
  create(OutputPanel, {activeTask, logOffset}),
648
561
  create(Box, {marginTop: 1, flexDirection: 'row', justifyContent: 'space-between'}, create(Text, {dimColor: true}, running ? 'Type to feed stdin; Enter: submit, Ctrl+C: abort.' : 'Run a command or press Shift+T to switch tasks.'), create(Text, {dimColor: true}, `${toggleHint}, Shift+S: Structure Guide`)),
649
- create(Box, {marginTop: 1, flexDirection: 'row', borderStyle: 'round', borderColor: running ? 'green' : 'gray', paddingX: 1}, create(Text, {bold: true, color: running ? 'green' : 'white'}, running ? ' Stdin buffer ' : ' Input ready '), create(Box, {marginLeft: 1}, create(CursorText, {value: stdinBuffer || (running ? '' : 'Start a command to feed stdin'), cursorIndex: stdinCursor, active: running})))
562
+ create(Box, {marginTop: 1, flexDirection: 'row', borderStyle: 'round', borderColor: running ? 'green' : 'gray', paddingX: 1}, create(Text, {bold: true, color: 'green'}, running ? ' Stdin buffer ' : ' Input ready '), create(Box, {marginLeft: 1}, create(CursorText, {value: stdinBuffer || (running ? '' : 'Start a command to feed stdin'), cursorIndex: stdinCursor, active: running})))
650
563
  ),
651
- config.showHelpCards && create(Box, {marginTop: 1, flexDirection: 'row', justifyContent: 'space-between', flexWrap: 'wrap'}, ...helpCards.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))))),
564
+ config.showHelpCards && create(Box, {marginTop: 1, flexDirection: 'row', justifyContent: 'space-between', flexWrap: 'wrap'}, [
565
+ {label: 'Navigation', color: 'magenta', body: ['↑ / ↓ move focus, Enter: details', 'Shift+↑ / ↓ scroll output', 'Shift+H toggle help cards', 'Shift+D detach from task']},
566
+ {label: 'Management', color: 'cyan', body: ['Shift+P Package Registry', 'Shift+N Project Architect', 'Shift+X clear / Shift+E export']},
567
+ {label: 'Orbit & Studio', color: 'yellow', body: ['Shift+T task manager', 'Shift+A studio / Shift+B art board', 'Shift+S structure / Shift+Q quit']}
568
+ ].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))))),
652
569
  config.showStructureGuide && create(Box, {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(', ')}`))),
653
- showHelp && create(Box, {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.'))
570
+ showHelp && create(Box, {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.'))
654
571
  );
655
572
  }
656
573
 
@@ -686,6 +603,8 @@ async function main() {
686
603
  console.log(' Enter Toggle detail view for selected project');
687
604
  console.log(' Shift+A Switch to Omni-Studio (Environment Health)');
688
605
  console.log(' Shift+T Open Orbit Task Manager (Manage background processes)');
606
+ console.log(' Shift+P Open Package Registry (Add/Remove packages)');
607
+ console.log(' Shift+N Open Project Architect (Scaffold new projects)');
689
608
  console.log(' Shift+D Detach from active task (Keep it running in background)');
690
609
  console.log(' Shift+B Toggle Art Board visibility');
691
610
  console.log(' Shift+H Toggle Help Cards visibility');
@@ -0,0 +1,109 @@
1
+ import React, {useState, memo} from 'react';
2
+ import {Box, Text, useInput} from 'ink';
3
+
4
+ const create = React.createElement;
5
+
6
+ const PackageRegistry = memo(({selectedProject, onRunCommand, CursorText}) => {
7
+ const [mode, setMode] = useState('list'); // list | add | remove
8
+ const [input, setInput] = useState('');
9
+ const [cursor, setCursor] = useState(0);
10
+
11
+ const projectType = selectedProject?.type || 'Unknown';
12
+ const deps = selectedProject?.metadata?.dependencies || [];
13
+
14
+ useInput((inputStr, key) => {
15
+ if (mode === 'add' || mode === 'remove') {
16
+ if (key.return) {
17
+ if (input.trim()) {
18
+ const cmd = mode === 'add' ? getAddCmd(projectType, input.trim()) : getRemoveCmd(projectType, input.trim());
19
+ if (cmd) onRunCommand({label: `${mode === 'add' ? 'Add' : 'Remove'} ${input}`, command: cmd});
20
+ }
21
+ setMode('list'); setInput(''); setCursor(0);
22
+ return;
23
+ }
24
+ if (key.escape) { setMode('list'); setInput(''); setCursor(0); return; }
25
+ if (key.backspace || key.delete) {
26
+ if (cursor > 0) {
27
+ setInput(prev => prev.slice(0, cursor - 1) + prev.slice(cursor));
28
+ setCursor(c => Math.max(0, c - 1));
29
+ }
30
+ return;
31
+ }
32
+ if (key.leftArrow) { setCursor(c => Math.max(0, c - 1)); return; }
33
+ if (key.rightArrow) { setCursor(c => Math.min(input.length, c + 1)); return; }
34
+ if (inputStr) {
35
+ setInput(prev => prev.slice(0, cursor) + inputStr + prev.slice(cursor));
36
+ setCursor(c => c + inputStr.length);
37
+ }
38
+ return;
39
+ }
40
+
41
+ if (inputStr.toLowerCase() === 'a') { setMode('add'); setInput(''); setCursor(0); }
42
+ if (inputStr.toLowerCase() === 'r') { setMode('remove'); setInput(''); setCursor(0); }
43
+ if (inputStr.toLowerCase() === 'v' && projectType === 'Python') {
44
+ onRunCommand({label: 'Create venv', command: ['python3', '-m', 'venv', '.venv']});
45
+ }
46
+ });
47
+
48
+ const getAddCmd = (type, pkg) => {
49
+ if (type === 'Node.js') return ['npm', 'install', pkg];
50
+ if (type === 'Python') return ['pip', 'install', pkg];
51
+ if (type === 'Rust') return ['cargo', 'add', pkg];
52
+ if (type === '.NET') return ['dotnet', 'add', 'package', pkg];
53
+ if (type === 'PHP') return ['composer', 'require', pkg];
54
+ return null;
55
+ };
56
+
57
+ const getRemoveCmd = (type, pkg) => {
58
+ if (type === 'Node.js') return ['npm', 'uninstall', pkg];
59
+ if (type === 'Python') return ['pip', 'uninstall', '-y', pkg];
60
+ if (type === 'Rust') return ['cargo', 'remove', pkg];
61
+ if (type === '.NET') return ['dotnet', 'remove', 'package', pkg];
62
+ if (type === 'PHP') return ['composer', 'remove', pkg];
63
+ return null;
64
+ };
65
+
66
+ return create(
67
+ Box,
68
+ {flexDirection: 'column', borderStyle: 'round', borderColor: 'magenta', padding: 1},
69
+ create(
70
+ Box,
71
+ {justifyContent: 'space-between'},
72
+ create(Text, {bold: true, color: 'magenta'}, `📦 Package Registry | ${selectedProject?.name}`),
73
+ create(Text, {dimColor: true}, projectType)
74
+ ),
75
+ create(Text, {dimColor: true, marginBottom: 1}, 'Manage dependencies and environments for this project.'),
76
+ mode === 'list'
77
+ ? create(
78
+ Box,
79
+ {flexDirection: 'column'},
80
+ create(Text, {bold: true}, `Installed Dependencies (${deps.length}):`),
81
+ create(
82
+ Box,
83
+ {flexDirection: 'row', flexWrap: 'wrap'},
84
+ ...deps.map(d => create(Text, {key: d, dimColor: true}, ` ${d} `))
85
+ ),
86
+ create(
87
+ Box,
88
+ {marginTop: 1, flexDirection: 'column'},
89
+ create(Text, {color: 'cyan'}, 'A: Add Package | R: Remove Package'),
90
+ projectType === 'Python' && create(Text, {color: 'yellow'}, 'V: Create .venv'),
91
+ create(Text, {dimColor: true, marginTop: 1}, 'Press Shift+P to return to Navigator.')
92
+ )
93
+ )
94
+ : create(
95
+ Box,
96
+ {flexDirection: 'column'},
97
+ create(Text, {bold: true, color: 'cyan'}, mode === 'add' ? 'ADD NEW PACKAGE' : 'REMOVE PACKAGE'),
98
+ create(
99
+ Box,
100
+ {flexDirection: 'row'},
101
+ create(Text, null, 'Enter package name: '),
102
+ create(CursorText, {value: input, cursorIndex: cursor})
103
+ ),
104
+ create(Text, {dimColor: true, marginTop: 1}, 'Enter: Confirm, Esc: Cancel')
105
+ )
106
+ );
107
+ });
108
+
109
+ export default PackageRegistry;
@@ -0,0 +1,83 @@
1
+ import React, {useState, memo} from 'react';
2
+ import {Box, Text, useInput} from 'ink';
3
+
4
+ const create = React.createElement;
5
+
6
+ const ProjectArchitect = memo(({onRunCommand, CursorText}) => {
7
+ const [step, setStep] = useState('framework'); // framework | name
8
+ const [selectedIdx, setSelectedIdx] = useState(0);
9
+ const [name, setName] = useState('');
10
+ const [cursor, setCursor] = useState(0);
11
+
12
+ const frameworks = [
13
+ {name: 'Next.js', cmd: (n) => ['npx', 'create-next-app@latest', n]},
14
+ {name: 'React (Vite)', cmd: (n) => ['npm', 'create', 'vite@latest', n, '--', '--template', 'react']},
15
+ {name: 'Vue (Vite)', cmd: (n) => ['npm', 'create', 'vite@latest', n, '--', '--template', 'vue']},
16
+ {name: 'Rust (Binary)', cmd: (n) => ['cargo', 'new', n]},
17
+ {name: 'Python (Basic)', cmd: (n) => ['mkdir', n]},
18
+ {name: 'Go Module', cmd: (n) => ['mkdir', n, '&&', 'cd', n, '&&', 'go', 'mod', 'init', n]}
19
+ ];
20
+
21
+ useInput((inputStr, key) => {
22
+ if (step === 'framework') {
23
+ if (key.upArrow) setSelectedIdx(prev => (prev - 1 + frameworks.length) % frameworks.length);
24
+ if (key.downArrow) setSelectedIdx(prev => (prev + 1) % frameworks.length);
25
+ if (key.return) setStep('name');
26
+ return;
27
+ }
28
+
29
+ if (step === 'name') {
30
+ if (key.return) {
31
+ if (name.trim()) {
32
+ const f = frameworks[selectedIdx];
33
+ onRunCommand({label: `Create ${f.name} project: ${name}`, command: f.cmd(name.trim())});
34
+ }
35
+ setStep('framework'); setName(''); setCursor(0);
36
+ return;
37
+ }
38
+ if (key.escape) { setStep('framework'); setName(''); setCursor(0); return; }
39
+ if (key.backspace || key.delete) {
40
+ if (cursor > 0) {
41
+ setName(prev => prev.slice(0, cursor - 1) + prev.slice(cursor));
42
+ setCursor(c => Math.max(0, c - 1));
43
+ }
44
+ return;
45
+ }
46
+ if (key.leftArrow) { setCursor(c => Math.max(0, c - 1)); return; }
47
+ if (key.rightArrow) { setCursor(c => Math.min(name.length, c + 1)); return; }
48
+ if (inputStr) {
49
+ setName(prev => prev.slice(0, cursor) + inputStr + prev.slice(cursor));
50
+ setCursor(c => c + inputStr.length);
51
+ }
52
+ }
53
+ });
54
+
55
+ return create(
56
+ Box,
57
+ {flexDirection: 'column', borderStyle: 'round', borderColor: 'cyan', padding: 1},
58
+ create(Text, {bold: true, color: 'cyan'}, '🏗️ Project Architect | Generator'),
59
+ create(Text, {dimColor: true, marginBottom: 1}, 'Create new projects from industry-standard templates.'),
60
+ step === 'framework'
61
+ ? create(
62
+ Box,
63
+ {flexDirection: 'column'},
64
+ create(Text, {bold: true}, 'Select Framework:'),
65
+ ...frameworks.map((f, i) => create(Text, {key: f.name, color: i === selectedIdx ? 'cyan' : 'white'}, `${i === selectedIdx ? '→' : ' '} ${f.name}`)),
66
+ create(Text, {dimColor: true, marginTop: 1}, 'Enter: Pick Framework, Shift+N: Return to Navigator.')
67
+ )
68
+ : create(
69
+ Box,
70
+ {flexDirection: 'column'},
71
+ create(Text, {bold: true, color: 'cyan'}, 'PROJECT NAME'),
72
+ create(
73
+ Box,
74
+ {flexDirection: 'row'},
75
+ create(Text, null, 'Enter name: '),
76
+ create(CursorText, {value: name, cursorIndex: cursor})
77
+ ),
78
+ create(Text, {dimColor: true, marginTop: 1}, 'Enter: Create Project, Esc: Back to Frameworks')
79
+ )
80
+ );
81
+ });
82
+
83
+ export default ProjectArchitect;
@@ -0,0 +1,65 @@
1
+ import React, {useState, useEffect, memo} from 'react';
2
+ import {Box, Text} from 'ink';
3
+ import {execa} from 'execa';
4
+ import {checkBinary} from '../projectDetection.js';
5
+
6
+ const create = React.createElement;
7
+
8
+ const Studio = memo(() => {
9
+ const [runtimes, setRuntimes] = useState([]);
10
+ const [loading, setLoading] = useState(true);
11
+
12
+ useEffect(() => {
13
+ const checks = [
14
+ {name: 'Node.js', binary: 'node', versionCmd: ['-v']},
15
+ {name: 'npm', binary: 'npm', versionCmd: ['-v']},
16
+ {name: 'Python', binary: process.platform === 'win32' ? 'python' : 'python3', versionCmd: ['--version']},
17
+ {name: 'Rust (Cargo)', binary: 'cargo', versionCmd: ['--version']},
18
+ {name: 'Go', binary: 'go', versionCmd: ['version']},
19
+ {name: 'Java', binary: 'java', versionCmd: ['-version']},
20
+ {name: 'PHP', binary: 'php', versionCmd: ['-v']},
21
+ {name: 'Ruby', binary: 'ruby', versionCmd: ['-v']},
22
+ {name: '.NET', binary: 'dotnet', versionCmd: ['--version']}
23
+ ];
24
+
25
+ (async () => {
26
+ const results = await Promise.all(checks.map(async (lang) => {
27
+ if (!checkBinary(lang.binary)) {
28
+ return {...lang, status: 'missing', version: 'not installed'};
29
+ }
30
+ try {
31
+ const {stdout, stderr} = await execa(lang.binary, lang.versionCmd);
32
+ const version = (stdout || stderr || '').split('\n')[0].trim();
33
+ return {...lang, status: 'ok', version};
34
+ } catch {
35
+ return {...lang, status: 'error', version: 'failed to check'};
36
+ }
37
+ }));
38
+ setRuntimes(results);
39
+ setLoading(false);
40
+ })();
41
+ }, []);
42
+
43
+ return create(
44
+ Box,
45
+ {flexDirection: 'column', borderStyle: 'double', borderColor: 'blue', padding: 1},
46
+ create(Text, {bold: true, color: 'blue'}, '💎 Omni-Studio | Environment Intelligence'),
47
+ create(Text, {dimColor: true, marginBottom: 1}, 'Overview of installed languages and build tools.'),
48
+ loading
49
+ ? create(Text, {dimColor: true}, 'Gathering intelligence...')
50
+ : create(
51
+ Box,
52
+ {flexDirection: 'column'},
53
+ ...runtimes.map(r => create(
54
+ Box,
55
+ {key: r.name, marginBottom: 0},
56
+ create(Text, {width: 20, color: r.status === 'ok' ? 'green' : 'red'}, `${r.status === 'ok' ? '✓' : '✗'} ${r.name}`),
57
+ create(Text, {dimColor: r.status !== 'ok'}, `: ${r.version}`)
58
+ )),
59
+ create(Text, {marginTop: 1, color: 'yellow'}, '🛠️ Interactive Project Creator coming soon in v3.0'),
60
+ create(Text, {dimColor: true}, 'Press Shift+A to return to Navigator.')
61
+ )
62
+ );
63
+ });
64
+
65
+ export default Studio;
@@ -0,0 +1,24 @@
1
+ import React, {memo} from 'react';
2
+ import {Box, Text} from 'ink';
3
+
4
+ const create = React.createElement;
5
+
6
+ const TaskManager = memo(({tasks, activeTaskId, renameMode, renameInput, renameCursor, CursorText}) => {
7
+ return create(
8
+ Box,
9
+ {flexDirection: 'column', borderStyle: 'round', borderColor: 'yellow', padding: 1},
10
+ create(Text, {bold: true, color: 'yellow'}, '🛰️ Orbit Task Manager | Background Processes'),
11
+ create(Text, {dimColor: true, marginBottom: 1}, 'Up/Down: focus, Shift+K: Force Kill, Shift+R: Rename'),
12
+ ...tasks.map(t => create(
13
+ Box,
14
+ {key: t.id, marginBottom: 0, flexDirection: 'column'},
15
+ t.id === activeTaskId && renameMode
16
+ ? create(Box, {flexDirection: 'row'}, create(Text, {color: 'cyan'}, '→ Rename to: '), create(CursorText, {value: renameInput, cursorIndex: renameCursor}))
17
+ : create(Text, {color: t.id === activeTaskId ? 'cyan' : 'white', bold: t.id === activeTaskId}, `${t.id === activeTaskId ? '→' : ' '} [${t.status.toUpperCase()}] ${t.name}`)
18
+ )),
19
+ !tasks.length && create(Text, {dimColor: true}, 'No active or background tasks.'),
20
+ create(Text, {marginTop: 1, dimColor: true}, 'Press Enter or Shift+T to return to Navigator.')
21
+ );
22
+ });
23
+
24
+ export default TaskManager;