project-compass 3.3.9 → 3.4.1

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 CHANGED
@@ -25,7 +25,7 @@ npm install -g project-compass
25
25
  ## Usage
26
26
 
27
27
  ```bash
28
- project-compass [--dir /path/to/workspace] [--studio]
28
+ project-compass [--dir /path/to/workspace] [--studio] [--version]
29
29
  ```
30
30
 
31
31
  ### Keyboard Guide
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "project-compass",
3
- "version": "3.3.9",
4
- "description": "Ink-based project explorer that detects local repos and lets you build/test/run them without memorizing commands.",
3
+ "version": "3.4.1",
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": "^18.2.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
@@ -2,12 +2,19 @@
2
2
  import React, {useCallback, useEffect, useMemo, useRef, useState, memo} from 'react';
3
3
  import {render, Box, Text, useApp, useInput} from 'ink';
4
4
  import path from 'path';
5
+ import {fileURLToPath} from 'url';
5
6
  import fs from 'fs';
6
7
  import kleur from 'kleur';
7
8
  import {execa} from 'execa';
8
- import {discoverProjects, SCHEMA_GUIDE, checkBinary} from './projectDetection.js';
9
+ import {discoverProjects, SCHEMA_GUIDE} from './projectDetection.js';
9
10
  import {CONFIG_PATH, ensureConfigDir} from './configPaths.js';
10
11
 
12
+ // Modular Components
13
+ import Studio from './components/Studio.js';
14
+ import TaskManager from './components/TaskManager.js';
15
+ import PackageRegistry from './components/PackageRegistry.js';
16
+ import ProjectArchitect from './components/ProjectArchitect.js';
17
+
11
18
  const create = React.createElement;
12
19
  const ART_CHARS = ['▁', '▃', '▄', '▅', '▇'];
13
20
  const ART_COLORS = ['magenta', 'blue', 'cyan', 'yellow', 'red'];
@@ -90,81 +97,6 @@ function buildDetailCommands(project, config) {
90
97
  return [...builtins, ...custom];
91
98
  }
92
99
 
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
100
  function CursorText({value, cursorIndex, active = true}) {
169
101
  const before = value.slice(0, cursorIndex);
170
102
  const charAt = value[cursorIndex] || ' ';
@@ -427,34 +359,15 @@ function Compass({rootPath, initialView = 'navigator'}) {
427
359
  const normalizedInput = input?.toLowerCase();
428
360
  const shiftCombo = (char) => key.shift && normalizedInput === char;
429
361
 
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
- }
362
+ if (shiftCombo('h')) { setConfig(prev => { const next = {...prev, showHelpCards: !prev.showHelpCards}; saveConfig(next); return next; }); return; }
363
+ if (shiftCombo('s')) { setConfig(prev => { const next = {...prev, showStructureGuide: !prev.showStructureGuide}; saveConfig(next); return next; }); return; }
446
364
  if (shiftCombo('a')) { setMainView((prev) => (prev === 'navigator' ? 'studio' : 'navigator')); return; }
365
+ if (shiftCombo('p')) { setMainView((prev) => (prev === 'navigator' ? 'registry' : 'navigator')); return; }
366
+ if (shiftCombo('n')) { setMainView((prev) => (prev === 'navigator' ? 'architect' : 'navigator')); return; }
447
367
  if (shiftCombo('x')) { setTasks(prev => prev.map(t => t.id === activeTaskId ? {...t, logs: []} : t)); setLogOffset(0); return; }
448
368
  if (shiftCombo('e')) { exportLogs(); return; }
449
369
  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
- }
370
+ if (shiftCombo('b')) { setConfig(prev => { const next = {...prev, showArtBoard: !prev.showArtBoard}; saveConfig(next); return next; }); return; }
458
371
 
459
372
  if (shiftCombo('t')) {
460
373
  setMainView((prev) => {
@@ -485,6 +398,11 @@ function Compass({rootPath, initialView = 'navigator'}) {
485
398
  return;
486
399
  }
487
400
 
401
+ if (mainView === 'registry' || mainView === 'architect') {
402
+ // Inputs handled inside components
403
+ return;
404
+ }
405
+
488
406
  if (running && activeTaskId && runningProcessMap.current.has(activeTaskId)) {
489
407
  if (isCtrlC) { handleKillTask(activeTaskId); setStdinBuffer(''); setStdinCursor(0); return; }
490
408
  if (key.return) {
@@ -612,18 +530,14 @@ function Compass({rootPath, initialView = 'navigator'}) {
612
530
  create(Text, {dimColor: true}, tile.subtext)
613
531
  )), [projectCountLabel, rootPath, selectedProject, tasks.length, running]);
614
532
 
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
533
  if (quitConfirm) {
622
534
  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
535
  }
624
536
 
625
537
  if (mainView === 'studio') return create(Studio);
626
- if (mainView === 'tasks') return create(TaskManager, {tasks, activeTaskId, renameMode, renameInput, renameCursor});
538
+ if (mainView === 'tasks') return create(TaskManager, {tasks, activeTaskId, setActiveTaskId, renameMode, renameInput, renameCursor, CursorText});
539
+ if (mainView === 'registry') return create(PackageRegistry, {selectedProject, onRunCommand: runProjectCommand, CursorText});
540
+ if (mainView === 'architect') return create(ProjectArchitect, {onRunCommand: runProjectCommand, CursorText});
627
541
 
628
542
  return create(Box, {flexDirection: 'column', padding: 1},
629
543
  create(Box, {justifyContent: 'space-between'},
@@ -646,11 +560,15 @@ function Compass({rootPath, initialView = 'navigator'}) {
646
560
  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
561
  create(OutputPanel, {activeTask, logOffset}),
648
562
  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})))
563
+ 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
564
  ),
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))))),
565
+ config.showHelpCards && create(Box, {marginTop: 1, flexDirection: 'row', justifyContent: 'space-between', flexWrap: 'wrap'}, [
566
+ {label: 'Navigation', color: 'magenta', body: ['↑ / ↓ move focus, Enter: details', 'Shift+↑ / ↓ scroll output', 'Shift+H toggle help cards', 'Shift+D detach from task']},
567
+ {label: 'Management', color: 'cyan', body: ['Shift+P Package Registry', 'Shift+N Project Architect', 'Shift+X clear / Shift+E export']},
568
+ {label: 'Orbit & Studio', color: 'yellow', body: ['Shift+T task manager', 'Shift+A studio / Shift+B art board', 'Shift+S structure / Shift+Q quit']}
569
+ ].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
570
  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.'))
571
+ 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
572
  );
655
573
  }
656
574
 
@@ -663,6 +581,7 @@ function parseArgs() {
663
581
  if ((token === '--dir' || token === '--path') && tokens[i + 1]) { args.root = tokens[i + 1]; i += 1; }
664
582
  else if (token === '--mode' && tokens[i + 1]) { args.mode = tokens[i + 1]; i += 1; }
665
583
  else if (token === '--help' || token === '-h') args.help = true;
584
+ else if (token === '--version' || token === '-v') args.version = true;
666
585
  else if (token === '--studio') args.view = 'studio';
667
586
  }
668
587
  return args;
@@ -670,6 +589,12 @@ function parseArgs() {
670
589
 
671
590
  async function main() {
672
591
  const args = parseArgs();
592
+ if (args.version) {
593
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
594
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
595
+ console.log(`v${pkg.version}`);
596
+ return;
597
+ }
673
598
  if (args.help) {
674
599
  console.log(kleur.cyan('Project Compass · Ink project navigator/runner'));
675
600
  console.log('');
@@ -686,6 +611,8 @@ async function main() {
686
611
  console.log(' Enter Toggle detail view for selected project');
687
612
  console.log(' Shift+A Switch to Omni-Studio (Environment Health)');
688
613
  console.log(' Shift+T Open Orbit Task Manager (Manage background processes)');
614
+ console.log(' Shift+P Open Package Registry (Add/Remove packages)');
615
+ console.log(' Shift+N Open Project Architect (Scaffold new projects)');
689
616
  console.log(' Shift+D Detach from active task (Keep it running in background)');
690
617
  console.log(' Shift+B Toggle Art Board visibility');
691
618
  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;