project-compass 4.3.6 → 4.3.7
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/AGENTS.md +1121 -22
- package/ARCHITECTURE.md +826 -10
- package/CONTRIBUTING.md +974 -10
- package/PROJECT_CONTEXT.md +404 -0
- package/README.md +594 -67
- package/commands.md +841 -104
- package/package.json +5 -4
- package/src/cli.js +310 -169
- package/src/components/AIHorizon.js +138 -255
- package/src/components/Footer.js +8 -64
- package/src/components/Header.js +8 -47
- package/src/components/Navigator.js +16 -70
- package/src/components/PackageRegistry.js +4 -3
- package/src/components/ProjectArchitect.js +6 -1
- package/src/components/TaskManager.js +12 -70
- package/src/detectors/dotnet.js +3 -2
- package/src/detectors/frameworks.js +28 -28
- package/src/detectors/go.js +6 -6
- package/src/detectors/java.js +2 -1
- package/src/detectors/node.js +3 -2
- package/src/detectors/php.js +1 -1
- package/src/detectors/python.js +11 -4
- package/src/detectors/ruby.js +1 -1
- package/src/detectors/rust.js +2 -2
- package/src/detectors/utils.js +6 -7
- package/src/projectDetection.js +41 -5
- package/src/store/useProjectStore.js +0 -32
package/src/cli.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/* global setInterval, setTimeout, clearInterval, clearTimeout */
|
|
3
2
|
import React, {useCallback, useEffect, useMemo, useRef, useState, memo} from 'react';
|
|
4
3
|
import {render, Box, Text, useApp, useInput} from 'ink';
|
|
5
4
|
import path from 'path';
|
|
@@ -7,7 +6,7 @@ import {fileURLToPath} from 'url';
|
|
|
7
6
|
import fs from 'fs';
|
|
8
7
|
import kleur from 'kleur';
|
|
9
8
|
import {execa} from 'execa';
|
|
10
|
-
import {discoverProjects} from './projectDetection.js';
|
|
9
|
+
import {discoverProjects, SCHEMA_GUIDE} from './projectDetection.js';
|
|
11
10
|
import {CONFIG_PATH, ensureConfigDir} from './configPaths.js';
|
|
12
11
|
|
|
13
12
|
// Modular Components
|
|
@@ -34,6 +33,12 @@ const ACTION_MAP = {
|
|
|
34
33
|
r: 'run',
|
|
35
34
|
i: 'install'
|
|
36
35
|
};
|
|
36
|
+
const COMMAND_FALLBACKS = {
|
|
37
|
+
build: ['compile', 'dist', 'bundle', 'package'],
|
|
38
|
+
test: ['check', 'spec', 'unit', 'coverage'],
|
|
39
|
+
run: ['start', 'serve', 'dev'],
|
|
40
|
+
install: ['setup', 'bootstrap', 'fetch']
|
|
41
|
+
};
|
|
37
42
|
|
|
38
43
|
function saveConfig(config) {
|
|
39
44
|
try {
|
|
@@ -124,38 +129,22 @@ const OutputPanel = memo(({activeTask, logOffset}) => {
|
|
|
124
129
|
const logWindowEnd = Math.max(0, logs.length - logOffset);
|
|
125
130
|
const visibleLogs = logs.slice(logWindowStart, logWindowEnd);
|
|
126
131
|
|
|
127
|
-
const statusColor = activeTask?.status === 'running' ? 'green' :
|
|
128
|
-
activeTask?.status === 'failed' ? 'red' :
|
|
129
|
-
activeTask?.status === 'killed' ? 'yellow' : 'cyan';
|
|
130
|
-
|
|
131
|
-
const scrollIndicator = logs.length > OUTPUT_WINDOW_SIZE
|
|
132
|
-
? ` ↕ ${Math.min(logs.length - logOffset, logs.length)}/${logs.length}`
|
|
133
|
-
: '';
|
|
134
|
-
|
|
135
132
|
const logNodes = visibleLogs.length
|
|
136
|
-
? visibleLogs.map((line, i) => create(Text, {key: i
|
|
137
|
-
: [create(Text, {key: 'empty', dimColor: true}, '
|
|
133
|
+
? visibleLogs.map((line, i) => create(Text, {key: i}, line))
|
|
134
|
+
: [create(Text, {key: 'empty', dimColor: true}, 'Select a task or run a command to see logs.')];
|
|
138
135
|
|
|
139
136
|
return create(
|
|
140
137
|
Box,
|
|
141
138
|
{
|
|
142
139
|
flexDirection: 'column',
|
|
143
|
-
borderStyle:
|
|
144
|
-
borderColor:
|
|
145
|
-
|
|
146
|
-
paddingY: 0,
|
|
140
|
+
borderStyle: 'round',
|
|
141
|
+
borderColor: 'yellow',
|
|
142
|
+
padding: 1,
|
|
147
143
|
minHeight: OUTPUT_WINDOW_HEIGHT,
|
|
148
144
|
maxHeight: OUTPUT_WINDOW_HEIGHT,
|
|
149
145
|
height: OUTPUT_WINDOW_HEIGHT,
|
|
150
146
|
overflow: 'hidden'
|
|
151
147
|
},
|
|
152
|
-
// Header with log count
|
|
153
|
-
logs.length > 0 && create(
|
|
154
|
-
Box,
|
|
155
|
-
{ flexDirection: 'row', justifyContent: 'space-between', marginBottom: 0 },
|
|
156
|
-
create(Text, { dimColor: true, bold: true }, ` ${activeTask?.status?.toUpperCase() || 'IDLE'} `),
|
|
157
|
-
create(Text, { dimColor: true }, `Lines: ${logs.length}${scrollIndicator}`)
|
|
158
|
-
),
|
|
159
148
|
...logNodes
|
|
160
149
|
);
|
|
161
150
|
});
|
|
@@ -181,20 +170,9 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
181
170
|
const [stdinBuffer, setStdinBuffer] = useState('');
|
|
182
171
|
const [stdinCursor, setStdinCursor] = useState(0);
|
|
183
172
|
const [showHelp, setShowHelp] = useState(false);
|
|
184
|
-
const [startup, setStartup] = useState(true);
|
|
185
|
-
const [startupTick, setStartupTick] = useState(0);
|
|
186
173
|
const runningProcessMap = useRef(new Map());
|
|
187
174
|
const lastCommandRef = useRef(null);
|
|
188
175
|
|
|
189
|
-
useEffect(() => {
|
|
190
|
-
if (!startup) return;
|
|
191
|
-
const timer = setInterval(() => {
|
|
192
|
-
setStartupTick(t => t + 1);
|
|
193
|
-
}, 80);
|
|
194
|
-
const hideTimer = setTimeout(() => setStartup(false), 2400);
|
|
195
|
-
return () => { clearInterval(timer); clearTimeout(hideTimer); };
|
|
196
|
-
}, [startup]);
|
|
197
|
-
|
|
198
176
|
const activeTask = useMemo(() => tasks.find(t => t.id === activeTaskId), [tasks, activeTaskId]);
|
|
199
177
|
const running = activeTask?.status === 'running';
|
|
200
178
|
const hasRunningTasks = useMemo(() => tasks.some(t => t.status === 'running'), [tasks]);
|
|
@@ -266,13 +244,63 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
266
244
|
if (!project) return;
|
|
267
245
|
if (!commandMeta || !Array.isArray(commandMeta.command) || commandMeta.command.length === 0) return;
|
|
268
246
|
|
|
247
|
+
let finalCommand = [...commandMeta.command];
|
|
248
|
+
const port = config.projectMeta?.[project.path]?.port || project.metadata?.port;
|
|
249
|
+
let portApplied = false;
|
|
250
|
+
if (port) {
|
|
251
|
+
const cmdStr = finalCommand.join(' ');
|
|
252
|
+
const portStr = String(port);
|
|
253
|
+
const patterns = [
|
|
254
|
+
{ match: 'uvicorn', flag: '--port' },
|
|
255
|
+
{ match: 'gunicorn', flag: '--bind' },
|
|
256
|
+
{ match: 'gunicorn', flag: '-b' },
|
|
257
|
+
{ match: 'hypercorn', flag: '-b' },
|
|
258
|
+
{ match: 'next dev', flag: '-p' },
|
|
259
|
+
{ match: 'vite', flag: '--port' },
|
|
260
|
+
{ match: 'flask', flag: '--port' },
|
|
261
|
+
{ match: 'webpack', flag: '--port' },
|
|
262
|
+
{ match: 'serve', flag: '-l' },
|
|
263
|
+
{ match: 'django', flag: '--port' },
|
|
264
|
+
{ match: 'react-scripts start', flag: '--port' }
|
|
265
|
+
];
|
|
266
|
+
for (const { match, flag } of patterns) {
|
|
267
|
+
if (cmdStr.includes(match.toLowerCase())) {
|
|
268
|
+
const flagIdx = finalCommand.indexOf(flag);
|
|
269
|
+
if (flagIdx !== -1 && flagIdx + 1 < finalCommand.length) {
|
|
270
|
+
finalCommand[flagIdx + 1] = portStr;
|
|
271
|
+
portApplied = true;
|
|
272
|
+
} else if (flagIdx === -1) {
|
|
273
|
+
finalCommand.push(flag, portStr);
|
|
274
|
+
portApplied = true;
|
|
275
|
+
}
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (!portApplied && cmdStr.includes('runserver')) {
|
|
280
|
+
const hasPort = finalCommand.some(t => /^\d{4,5}$/.test(t));
|
|
281
|
+
if (!hasPort) {
|
|
282
|
+
finalCommand.push(portStr);
|
|
283
|
+
portApplied = true;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (!portApplied && (cmdStr.match(/(?:node|bun|deno)\s/))) {
|
|
287
|
+
const hasPortEnv = finalCommand.some(t => t.startsWith('--port=')) || finalCommand.includes('PORT');
|
|
288
|
+
if (!hasPortEnv) {
|
|
289
|
+
finalCommand.unshift(`PORT=${portStr}`);
|
|
290
|
+
portApplied = true;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
269
295
|
const commandLabel = commandMeta.label || commandMeta.command.join(' ');
|
|
270
296
|
const taskId = `task-${Date.now()}`;
|
|
297
|
+
const logLines = [kleur.cyan(`> ${finalCommand.join(' ')}`)];
|
|
298
|
+
if (portApplied) logLines.push(kleur.dim(`Port ${port} applied`));
|
|
271
299
|
const newTask = {
|
|
272
300
|
id: taskId,
|
|
273
301
|
name: `${project.name} · ${commandLabel}`,
|
|
274
302
|
status: 'running',
|
|
275
|
-
logs:
|
|
303
|
+
logs: logLines,
|
|
276
304
|
project: project.name
|
|
277
305
|
};
|
|
278
306
|
|
|
@@ -281,7 +309,7 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
281
309
|
lastCommandRef.current = {project, commandMeta};
|
|
282
310
|
|
|
283
311
|
try {
|
|
284
|
-
const subprocess = execa(
|
|
312
|
+
const subprocess = execa(finalCommand[0], finalCommand.slice(1), {
|
|
285
313
|
cwd: project.path,
|
|
286
314
|
env: process.env,
|
|
287
315
|
stdin: 'pipe',
|
|
@@ -307,7 +335,7 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
307
335
|
} finally {
|
|
308
336
|
runningProcessMap.current.delete(taskId);
|
|
309
337
|
}
|
|
310
|
-
}, [addLogToTask, selectedProject]);
|
|
338
|
+
}, [addLogToTask, selectedProject, config]);
|
|
311
339
|
|
|
312
340
|
const exportLogs = useCallback(() => {
|
|
313
341
|
const taskToExport = tasks.find(t => t.id === activeTaskId);
|
|
@@ -323,18 +351,8 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
323
351
|
|
|
324
352
|
useInput((input, key) => {
|
|
325
353
|
|
|
326
|
-
if (key.shift && (input === 'R' || input === 'r')) {
|
|
327
|
-
if (viewMode === 'detail' && selectedProject) {
|
|
328
|
-
setPortConfigMode(true);
|
|
329
|
-
const currentPort = config.projectMeta?.[selectedProject.path]?.port || '3000';
|
|
330
|
-
setCustomInput(String(currentPort));
|
|
331
|
-
setCustomCursor(String(currentPort).length);
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
354
|
if (quitConfirm) {
|
|
337
|
-
if (input?.toLowerCase() === 'y') { killAllTasks(); exit(); return; }
|
|
355
|
+
if (input?.toLowerCase() === 'y') { killAllTasks(); process.stdout.write('\x1b[2J\x1b[0;0H'); exit(); return; }
|
|
338
356
|
if (input?.toLowerCase() === 'n' || key.escape) { setQuitConfirm(false); return; }
|
|
339
357
|
return;
|
|
340
358
|
}
|
|
@@ -436,24 +454,26 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
436
454
|
const shiftCombo = (char) => key.shift && normalizedInput === char;
|
|
437
455
|
|
|
438
456
|
const clearAndSwitch = (view) => {
|
|
457
|
+
console.clear();
|
|
439
458
|
setMainView(view);
|
|
440
459
|
setViewMode('list');
|
|
441
460
|
setShowHelp(false);
|
|
442
461
|
};
|
|
443
462
|
|
|
444
|
-
if (shiftCombo('h')) { setConfig(prev => { const next = {...prev, showHelpCards: !prev.showHelpCards}; saveConfig(next); return next; }); return; }
|
|
445
|
-
if (shiftCombo('s')) { setConfig(prev => { const next = {...prev, showStructureGuide: !prev.showStructureGuide}; saveConfig(next); return next; }); return; }
|
|
463
|
+
if (shiftCombo('h')) { console.clear(); setConfig(prev => { const next = {...prev, showHelpCards: !prev.showHelpCards}; saveConfig(next); return next; }); return; }
|
|
464
|
+
if (shiftCombo('s')) { console.clear(); setConfig(prev => { const next = {...prev, showStructureGuide: !prev.showStructureGuide}; saveConfig(next); return next; }); return; }
|
|
446
465
|
if (shiftCombo('a')) { clearAndSwitch(mainView === 'navigator' ? 'studio' : 'navigator'); return; }
|
|
447
466
|
if (shiftCombo('p')) { clearAndSwitch(mainView === 'navigator' ? 'registry' : 'navigator'); return; }
|
|
448
467
|
if (shiftCombo('n')) { clearAndSwitch(mainView === 'navigator' ? 'architect' : 'navigator'); return; }
|
|
449
468
|
if (shiftCombo('o')) { clearAndSwitch(mainView === 'navigator' ? 'ai' : 'navigator'); return; }
|
|
450
|
-
if (shiftCombo('x')) { setTasks(prev => prev.map(t => t.id === activeTaskId ? {...t, logs: []} : t)); setLogOffset(0); return; }
|
|
469
|
+
if (shiftCombo('x')) { console.clear(); setTasks(prev => prev.map(t => t.id === activeTaskId ? {...t, logs: []} : t)); setLogOffset(0); return; }
|
|
451
470
|
if (shiftCombo('e')) { exportLogs(); return; }
|
|
452
|
-
if (shiftCombo('d')) { setActiveTaskId(null); return; }
|
|
453
|
-
if (shiftCombo('b')) { setConfig(prev => { const next = {...prev, showArtBoard: !prev.showArtBoard}; saveConfig(next); return next; }); return; }
|
|
471
|
+
if (shiftCombo('d')) { console.clear(); setActiveTaskId(null); return; }
|
|
472
|
+
if (shiftCombo('b')) { console.clear(); setConfig(prev => { const next = {...prev, showArtBoard: !prev.showArtBoard}; saveConfig(next); return next; }); return; }
|
|
454
473
|
|
|
455
474
|
if (shiftCombo('t')) {
|
|
456
475
|
setMainView((prev) => {
|
|
476
|
+
console.clear();
|
|
457
477
|
if (prev === 'tasks') return 'navigator';
|
|
458
478
|
if (tasks.length > 0 && !activeTaskId) setActiveTaskId(tasks[0].id);
|
|
459
479
|
return 'tasks';
|
|
@@ -528,6 +548,7 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
528
548
|
const next = prev - pageLimit;
|
|
529
549
|
return next < 0 ? 0 : next;
|
|
530
550
|
});
|
|
551
|
+
console.clear();
|
|
531
552
|
return;
|
|
532
553
|
}
|
|
533
554
|
|
|
@@ -541,22 +562,24 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
541
562
|
}
|
|
542
563
|
return next;
|
|
543
564
|
});
|
|
565
|
+
console.clear();
|
|
544
566
|
return;
|
|
545
567
|
}
|
|
546
568
|
|
|
547
569
|
|
|
548
|
-
if (normalizedInput === '?') { setShowHelp((prev) => !prev); return; }
|
|
570
|
+
if (normalizedInput === '?') { console.clear(); setShowHelp((prev) => !prev); return; }
|
|
549
571
|
if (shiftCombo('l') && lastCommandRef.current) { runProjectCommand(lastCommandRef.current.commandMeta, lastCommandRef.current.project); return; }
|
|
550
572
|
|
|
551
|
-
if (key.upArrow && !key.shift && projects.length > 0) { setSelectedIndex((prev) => (prev - 1
|
|
552
|
-
if (key.downArrow && !key.shift && projects.length > 0) { setSelectedIndex((prev) => (prev + 1)
|
|
573
|
+
if (key.upArrow && !key.shift && projects.length > 0) { setSelectedIndex((prev) => Math.max(0, prev - 1)); return; }
|
|
574
|
+
if (key.downArrow && !key.shift && projects.length > 0) { setSelectedIndex((prev) => Math.min(projects.length - 1, prev + 1)); return; }
|
|
553
575
|
if (key.return) {
|
|
554
576
|
if (!selectedProject) return;
|
|
577
|
+
console.clear();
|
|
555
578
|
setViewMode((prev) => (prev === 'detail' ? 'list' : 'detail'));
|
|
556
579
|
return;
|
|
557
580
|
}
|
|
558
581
|
if (shiftCombo('q') || isCtrlC) {
|
|
559
|
-
if (hasRunningTasks) setQuitConfirm(true); else { exit(); }
|
|
582
|
+
if (hasRunningTasks) setQuitConfirm(true); else { process.stdout.write('\x1b[2J\x1b[0;0H'); exit(); }
|
|
560
583
|
return;
|
|
561
584
|
}
|
|
562
585
|
|
|
@@ -566,16 +589,39 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
566
589
|
}
|
|
567
590
|
|
|
568
591
|
|
|
569
|
-
if (shiftCombo('
|
|
570
|
-
setPortConfigMode(true);
|
|
592
|
+
if (shiftCombo('m') && viewMode === 'detail' && selectedProject) {
|
|
593
|
+
setPortConfigMode(true);
|
|
594
|
+
const port = config.projectMeta?.[selectedProject.path]?.port || selectedProject.metadata?.port || '3000';
|
|
595
|
+
setCustomInput(String(port));
|
|
596
|
+
setCustomCursor(String(port).length);
|
|
571
597
|
return;
|
|
572
598
|
}
|
|
573
599
|
if (shiftCombo('c') && viewMode === 'detail' && selectedProject) { setCustomMode(true); setCustomInput(''); setCustomCursor(0); return; }
|
|
574
600
|
|
|
575
601
|
const actionKey = normalizedInput && ACTION_MAP[normalizedInput];
|
|
576
|
-
if (actionKey) {
|
|
577
|
-
|
|
578
|
-
|
|
602
|
+
if (actionKey && selectedProject) {
|
|
603
|
+
if (!key.shift) {
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
const findCommand = (project, key) => {
|
|
607
|
+
let meta = project.commands?.[key];
|
|
608
|
+
if (meta) return meta;
|
|
609
|
+
const alts = COMMAND_FALLBACKS[key] || [];
|
|
610
|
+
for (const alt of alts) {
|
|
611
|
+
meta = project.commands?.[alt];
|
|
612
|
+
if (meta) return meta;
|
|
613
|
+
}
|
|
614
|
+
return null;
|
|
615
|
+
};
|
|
616
|
+
const commandMeta = findCommand(selectedProject, actionKey);
|
|
617
|
+
if (commandMeta) {
|
|
618
|
+
runProjectCommand(commandMeta, selectedProject);
|
|
619
|
+
} else {
|
|
620
|
+
const msg = kleur.yellow(`! No ${actionKey} command available for ${selectedProject.name}`);
|
|
621
|
+
const taskId = `task-${Date.now()}`;
|
|
622
|
+
setTasks(prev => [...prev, { id: taskId, name: `Notification`, status: 'failed', logs: [msg], project: '' }]);
|
|
623
|
+
globalThis.setTimeout(() => setTasks(prev => prev.filter(t => t.id !== taskId)), 3000);
|
|
624
|
+
}
|
|
579
625
|
return;
|
|
580
626
|
}
|
|
581
627
|
if (viewMode === 'detail' && normalizedInput && detailShortcutMap.has(normalizedInput)) {
|
|
@@ -628,7 +674,7 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
628
674
|
create(Text, {key: `dl-${command.shortcut}`, dimColor: true}, ` ↳ ${command.command.join(' ')}`)
|
|
629
675
|
);
|
|
630
676
|
});
|
|
631
|
-
content.push(create(Text, {key: 'h-l', dimColor: true, marginTop: 1}, '
|
|
677
|
+
content.push(create(Text, {key: 'h-l', dimColor: true, marginTop: 1}, 'Shift+C: custom cmd · Shift+M: port · Enter: close detail view.'));
|
|
632
678
|
return content;
|
|
633
679
|
}, [viewMode, selectedProject, rootPath, detailedIndexed]);
|
|
634
680
|
|
|
@@ -653,27 +699,9 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
653
699
|
case 'registry': return create(PackageRegistry, {selectedProject, projects, onRunCommand: runProjectCommand, CursorText, onSelectProject: (idx) => setSelectedIndex(idx)});
|
|
654
700
|
case 'architect': return create(ProjectArchitect, {rootPath, onRunCommand: runProjectCommand, CursorText, onReturn: () => setMainView('navigator')});
|
|
655
701
|
case 'ai': return create(AIHorizon, {rootPath, selectedProject, onRunCommand: runProjectCommand, CursorText, config, setConfig, saveConfig});
|
|
656
|
-
|
|
657
|
-
const quickActions = selectedProject ? [
|
|
658
|
-
{ key: 'B', label: 'Build', color: 'magenta' },
|
|
659
|
-
{ key: 'T', label: 'Test', color: 'cyan' },
|
|
660
|
-
{ key: 'R', label: 'Run', color: 'green' },
|
|
661
|
-
{ key: 'I', label: 'Install', color: 'blue' },
|
|
662
|
-
{ key: '0', label: 'AI', color: 'yellow' }
|
|
663
|
-
] : [];
|
|
664
|
-
|
|
702
|
+
default: {
|
|
665
703
|
const navigatorBody = [
|
|
666
704
|
create(Header, {projectCountLabel, rootPath, running, statusHint, toggleHint, orbitHint, artHint}),
|
|
667
|
-
// Quick Actions Bar
|
|
668
|
-
quickActions.length > 0 && create(Box, {key: 'quick-actions', flexDirection: 'row', marginBottom: 0, paddingX: 1, paddingY: 0, borderStyle: 'single', borderColor: 'cyan'},
|
|
669
|
-
create(Text, {dimColor: true}, 'Quick: '),
|
|
670
|
-
...quickActions.map((action, idx) =>
|
|
671
|
-
create(Text, {key: action.key, color: action.color},
|
|
672
|
-
`${idx > 0 ? ' · ' : ''}[${action.key}] ${action.label}`
|
|
673
|
-
)
|
|
674
|
-
),
|
|
675
|
-
create(Text, {dimColor: true}, ' · [?] Help')
|
|
676
|
-
),
|
|
677
705
|
config.showArtBoard && create(Box, {key: 'artboard', flexDirection: 'column', marginTop: 1, borderStyle: 'round', borderColor: 'gray', padding: 1},
|
|
678
706
|
create(Box, {flexDirection: 'row', justifyContent: 'space-between'}, create(Text, {color: 'magenta', bold: true}, 'Art-coded build atlas'), create(Text, {dimColor: true}, 'press ? for overlay help')),
|
|
679
707
|
create(Box, {flexDirection: 'row', marginTop: 1}, ...ART_CHARS.map((char, i) => create(Text, {key: i, color: ART_COLORS[i % ART_COLORS.length]}, char.repeat(2)))),
|
|
@@ -696,73 +724,47 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
696
724
|
{label: 'Management', color: 'cyan', body: ['Shift+P Package Registry', 'Shift+N Project Architect', 'Shift+X clear / Shift+E export']},
|
|
697
725
|
{label: 'Orbit & AI', color: 'yellow', body: ['Shift+T: Tasks, Shift+O: AI, 0: Analyze', 'Shift+A studio / Shift+O AI Horizon', 'Shift+S structure / Shift+Q quit']}
|
|
698
726
|
].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))))),
|
|
699
|
-
config.showStructureGuide && create(Box, {key: 'structure', flexDirection: 'column', borderStyle: 'round', borderColor: 'blue', marginTop: 1, padding: 1},
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
),
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
p.type === 'Python' ? 'green' :
|
|
709
|
-
p.type === 'Rust' ? 'red' :
|
|
710
|
-
p.type === 'Go' ? 'blue' :
|
|
711
|
-
p.type === 'Java' ? 'yellow' :
|
|
712
|
-
p.type === 'PHP' ? 'purple' :
|
|
713
|
-
p.type === 'Ruby' ? 'red' : 'gray',
|
|
714
|
-
paddingX: 1, paddingY: 0},
|
|
715
|
-
create(Box, {flexDirection: 'row', alignItems: 'center'},
|
|
716
|
-
create(Text, {bold: true, color: selectedIndex === idx ? 'cyan' : 'white'},
|
|
717
|
-
`${selectedIndex === idx ? '→ ' : ' '}${p.icon} ${p.name}`),
|
|
718
|
-
p.missingBinaries && p.missingBinaries.length > 0 && create(Text, {color: 'red'}, ' ⚠')
|
|
719
|
-
),
|
|
720
|
-
create(Text, {dimColor: true}, ` ${p.type} · ${path.relative(rootPath, p.path) || '.'}`),
|
|
721
|
-
p.frameworks && p.frameworks.length > 0 && create(Text, {dimColor: true}, ` Frameworks: ${p.frameworks.map(f => `${f.icon} ${f.name}`).join(', ')}`),
|
|
722
|
-
p.description && create(Text, {dimColor: true}, ` ${p.description}`)
|
|
723
|
-
)
|
|
724
|
-
)
|
|
725
|
-
),
|
|
726
|
-
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.'))
|
|
727
|
+
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(', ')}`))),
|
|
728
|
+
showHelp && create(Box, {key: 'overlay', flexDirection: 'column', borderStyle: 'double', borderColor: 'cyan', marginTop: 1, padding: 1},
|
|
729
|
+
create(Text, {color: 'cyan', bold: true}, '📖 Help Overview'),
|
|
730
|
+
create(Text, null, 'Shift+T Tasks · Shift+P Packages · Shift+N Architect · Shift+O AI · Shift+A Studio'),
|
|
731
|
+
create(Text, null, 'Shift+B/T/R/I Build/Test/Run/Install Shift+C custom cmd Shift+M port'),
|
|
732
|
+
create(Text, null, 'Shift+K kill · Shift+R rename · Shift+D detach · Shift+X clear · Shift+E export · Shift+L rerun'),
|
|
733
|
+
create(Text, null, 'Shift+H help cards · Shift+S structure · Shift+B art · Shift+Q quit'),
|
|
734
|
+
create(Text, null, '↑/↓ navigate · Enter detail · Esc back · ? close')
|
|
735
|
+
)
|
|
727
736
|
];
|
|
728
737
|
return create(Box, {flexDirection: 'column'}, ...navigatorBody);
|
|
729
738
|
}
|
|
730
739
|
}
|
|
731
740
|
};
|
|
732
741
|
|
|
733
|
-
// Startup banner with animation
|
|
734
|
-
if (startup) {
|
|
735
|
-
const frame = startupTick % 240;
|
|
736
|
-
const progress = Math.min(frame / 240 * 100, 100);
|
|
737
|
-
const barLength = 30;
|
|
738
|
-
const filled = Math.floor(barLength * progress / 100);
|
|
739
|
-
const bar = '█'.repeat(filled) + '░'.repeat(barLength - filled);
|
|
740
|
-
const spinner = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'][startupTick % 10];
|
|
741
|
-
|
|
742
|
-
return create(Box, {flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%'},
|
|
743
|
-
create(Box, {marginBottom: 1},
|
|
744
|
-
create(Text, {color: 'magenta', bold: true}, '🧭 '),
|
|
745
|
-
create(Text, {color: 'cyan', bold: true}, 'PROJECT COMPASS'),
|
|
746
|
-
create(Text, {color: 'magenta', bold: true}, ' 🧭')
|
|
747
|
-
),
|
|
748
|
-
create(Box, {marginBottom: 1},
|
|
749
|
-
create(Text, {dimColor: true}, 'Initializing command center...')
|
|
750
|
-
),
|
|
751
|
-
create(Box, {marginBottom: 1},
|
|
752
|
-
create(Text, {color: 'cyan'}, bar),
|
|
753
|
-
create(Text, {dimColor: true}, ` ${Math.floor(progress)}%`)
|
|
754
|
-
),
|
|
755
|
-
create(Box, {},
|
|
756
|
-
create(Text, {color: 'yellow'}, ` ${spinner} `),
|
|
757
|
-
create(Text, {dimColor: true}, 'Scanning projects...')
|
|
758
|
-
)
|
|
759
|
-
);
|
|
760
|
-
}
|
|
761
|
-
|
|
762
742
|
return create(Box, {flexDirection: 'column', padding: 1, width: '100%'}, renderView());
|
|
763
743
|
}
|
|
764
744
|
|
|
765
745
|
|
|
746
|
+
function getAddCmd(project, pkg) {
|
|
747
|
+
if (!project || !pkg) return null;
|
|
748
|
+
const type = project.type;
|
|
749
|
+
if (type === 'Node.js') return [project.metadata?.packageManager || 'npm', 'install', pkg];
|
|
750
|
+
if (type === 'Python') return ['pip', 'install', pkg];
|
|
751
|
+
if (type === 'Rust') return ['cargo', 'add', pkg];
|
|
752
|
+
if (type === '.NET') return ['dotnet', 'add', 'package', pkg];
|
|
753
|
+
if (type === 'PHP') return ['composer', 'require', pkg];
|
|
754
|
+
return null;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function getRemoveCmd(project, pkg) {
|
|
758
|
+
if (!project || !pkg) return null;
|
|
759
|
+
const type = project.type;
|
|
760
|
+
if (type === 'Node.js') return [project.metadata?.packageManager || 'npm', 'uninstall', pkg];
|
|
761
|
+
if (type === 'Python') return ['pip', 'uninstall', '-y', pkg];
|
|
762
|
+
if (type === 'Rust') return ['cargo', 'remove', pkg];
|
|
763
|
+
if (type === '.NET') return ['dotnet', 'remove', 'package', pkg];
|
|
764
|
+
if (type === 'PHP') return ['composer', 'remove', pkg];
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
|
|
766
768
|
function parseArgs() {
|
|
767
769
|
const args = {};
|
|
768
770
|
const tokens = process.argv.slice(2);
|
|
@@ -775,6 +777,16 @@ function parseArgs() {
|
|
|
775
777
|
else if (token === '--studio') args.view = 'studio';
|
|
776
778
|
else if (token === '--ai') args.view = 'ai';
|
|
777
779
|
else if (token === '--task' || token === '--tasks') args.view = 'tasks';
|
|
780
|
+
else if (token === '--list-projects') args.listProjects = true;
|
|
781
|
+
else if (token === '--json') args.json = true;
|
|
782
|
+
else if (token === '--project-info' && tokens[i + 1]) { args.projectInfo = parseInt(tokens[i + 1], 10); i += 1; }
|
|
783
|
+
else if (token === '--run' && tokens[i + 1]) { args.runCommand = tokens[i + 1]; i += 1; }
|
|
784
|
+
else if (token === '--add-pkg' && tokens[i + 1]) { args.addPkg = tokens[i + 1]; i += 1; }
|
|
785
|
+
else if (token === '--remove-pkg' && tokens[i + 1]) { args.removePkg = tokens[i + 1]; i += 1; }
|
|
786
|
+
else if (token === '--scaffold' && tokens[i + 1]) { args.scaffold = tokens[i + 1]; i += 1; }
|
|
787
|
+
else if (token === '--name' && tokens[i + 1]) { args.name = tokens[i + 1]; i += 1; }
|
|
788
|
+
else if (token === '--studio-check') args.studioCheck = true;
|
|
789
|
+
else if (token === '--ai-analyze') args.aiAnalyze = true;
|
|
778
790
|
}
|
|
779
791
|
return args;
|
|
780
792
|
}
|
|
@@ -792,45 +804,174 @@ async function main() {
|
|
|
792
804
|
console.log(kleur.dim('───────────────────────────────────────────────────'));
|
|
793
805
|
console.log('');
|
|
794
806
|
console.log(kleur.bold('Usage:'));
|
|
795
|
-
console.log(' project-compass
|
|
796
|
-
console.log('');
|
|
797
|
-
console.log(
|
|
798
|
-
console.log('
|
|
799
|
-
console.log('
|
|
800
|
-
console.log('
|
|
801
|
-
console.log('
|
|
802
|
-
console.log('
|
|
807
|
+
console.log(' project-compass Launch TUI');
|
|
808
|
+
console.log(' project-compass [--dir <path>] [--studio|--ai|--task]');
|
|
809
|
+
console.log(' project-compass --list-projects [--json] List detected projects');
|
|
810
|
+
console.log(' project-compass --project-info <idx> [--json] Show project details');
|
|
811
|
+
console.log(' project-compass --run "<cmd>" [--dir <path>] Run a command');
|
|
812
|
+
console.log(' project-compass --add-pkg <name> [--dir] Add package');
|
|
813
|
+
console.log(' project-compass --remove-pkg <name> [--dir] Remove package');
|
|
814
|
+
console.log(' project-compass --scaffold <template> --name <n> [--dir] Scaffold project');
|
|
815
|
+
console.log(' project-compass --studio-check Check runtimes');
|
|
816
|
+
console.log(' project-compass --version / --help Version / help');
|
|
803
817
|
console.log('');
|
|
804
|
-
console.log(kleur.bold(kleur.
|
|
805
|
-
console.log('
|
|
806
|
-
console.log('
|
|
807
|
-
console.log('
|
|
808
|
-
console.log('
|
|
809
|
-
console.log('
|
|
810
|
-
console.log('
|
|
811
|
-
console.log('
|
|
818
|
+
console.log(kleur.bold(kleur.cyan('🌌 CLI Arguments:')));
|
|
819
|
+
console.log(' --dir <path> Working directory for scanning');
|
|
820
|
+
console.log(' --studio Launch in Studio view');
|
|
821
|
+
console.log(' --ai Launch in AI Horizon view');
|
|
822
|
+
console.log(' --task Launch in Task Manager view');
|
|
823
|
+
console.log(' --list-projects List all detected projects');
|
|
824
|
+
console.log(' --json JSON output (with --list-projects, --project-info)');
|
|
825
|
+
console.log(' --project-info Project details by index');
|
|
826
|
+
console.log(' --run Execute command in project directory');
|
|
827
|
+
console.log(' --add-pkg Add a package dependency');
|
|
828
|
+
console.log(' --remove-pkg Remove a package dependency');
|
|
829
|
+
console.log(' --scaffold Scaffold from template (' + Object.keys({
|
|
830
|
+
nextjs: 1, 'nextjs-bun': 1, 'react-vite': 1, 'react-vite-npm': 1,
|
|
831
|
+
'vue-vite': 1, rust: 1, django: 1, 'python-basic': 1, go: 1
|
|
832
|
+
}).join(', ') + ')');
|
|
833
|
+
console.log(' --name Project name (with --scaffold)');
|
|
834
|
+
console.log(' --studio-check Environment runtime audit');
|
|
812
835
|
console.log('');
|
|
813
|
-
console.log(kleur.bold(kleur.
|
|
814
|
-
console.log(' Shift+
|
|
815
|
-
console.log('
|
|
816
|
-
console.log(' Shift+
|
|
817
|
-
console.log(' Shift
|
|
818
|
-
console.log('
|
|
819
|
-
console.log(' Shift+
|
|
836
|
+
console.log(kleur.bold(kleur.yellow('🎮 TUI Keyboard Shortcuts:')));
|
|
837
|
+
console.log(' Core Views: Shift+T(asks) Shift+P(ackages) Shift+N(ew)');
|
|
838
|
+
console.log(' Shift+O(AI) Shift+A(Studio)');
|
|
839
|
+
console.log(' Detail View: Shift+B(uild) Shift+T(est) Shift+R(un) Shift+I(nstall)');
|
|
840
|
+
console.log(' Shift+C(custom cmd) Shift+M(port) 0(AI) 1-9(scripts)');
|
|
841
|
+
console.log(' Navigation: ↑/↓ PgUp/Dn Enter(detail) Esc(back) ?(help)');
|
|
842
|
+
console.log(' Workspace: Shift+H(help) Shift+S(structure) Shift+B(art)');
|
|
843
|
+
console.log(' Tasks: Shift+K(kill) Shift+R(rename) Shift+D(etach)');
|
|
844
|
+
console.log(' Shift+X(clear) Shift+E(xport) Shift+L(rerun)');
|
|
845
|
+
console.log(' System: Shift+Q(quit)');
|
|
820
846
|
console.log('');
|
|
821
|
-
console.log(kleur.bold(kleur.
|
|
822
|
-
console.log('
|
|
823
|
-
console.log('
|
|
847
|
+
console.log(kleur.bold(kleur.green('📦 Scaffold Templates:')));
|
|
848
|
+
console.log(' nextjs, nextjs-bun, react-vite, react-vite-npm,');
|
|
849
|
+
console.log(' vue-vite, rust, django, python-basic, go');
|
|
824
850
|
console.log('');
|
|
825
851
|
console.log(kleur.dim('Documentation: https://github.com/CrimsonDevil333333/project-compass'));
|
|
826
|
-
console.log(kleur.
|
|
852
|
+
console.log(kleur.dim('Crafted for performance by Satyaa & Clawdy'));
|
|
827
853
|
return;
|
|
828
854
|
}
|
|
829
855
|
const rootPath = args.root ? path.resolve(args.root) : process.cwd();
|
|
830
|
-
|
|
856
|
+
|
|
857
|
+
if (args.listProjects || args.mode === 'test') {
|
|
858
|
+
const projects = await discoverProjects(rootPath);
|
|
859
|
+
if (args.json) {
|
|
860
|
+
console.log(JSON.stringify(projects, (key, value) => key === 'commands' ? Object.keys(value) : value, 2));
|
|
861
|
+
} else {
|
|
862
|
+
console.log(`Detected ${projects.length} project(s) under ${rootPath}`);
|
|
863
|
+
projects.forEach((project) => { console.log(` • [${project.type}] ${project.name} (${project.path})`); });
|
|
864
|
+
}
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
if (args.projectInfo !== undefined) {
|
|
869
|
+
const projects = await discoverProjects(rootPath);
|
|
870
|
+
const project = projects[args.projectInfo];
|
|
871
|
+
if (!project) { console.error(`Project index ${args.projectInfo} not found. Total: ${projects.length}`); process.exit(1); }
|
|
872
|
+
if (args.json) {
|
|
873
|
+
console.log(JSON.stringify(project, null, 2));
|
|
874
|
+
} else {
|
|
875
|
+
console.log(`Name: ${project.name}`);
|
|
876
|
+
console.log(`Type: ${project.type}`);
|
|
877
|
+
console.log(`Path: ${project.path}`);
|
|
878
|
+
console.log(`Frameworks: ${(project.frameworks || []).map(f => f.name).join(', ') || 'none'}`);
|
|
879
|
+
console.log(`Manifest: ${project.manifest}`);
|
|
880
|
+
console.log('Commands:');
|
|
881
|
+
Object.entries(project.commands || {}).forEach(([key, cmd]) => console.log(` ${key}: ${cmd.label || key} → ${cmd.command.join(' ')}`));
|
|
882
|
+
}
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
if (args.runCommand) {
|
|
887
|
+
const projects = await discoverProjects(rootPath);
|
|
888
|
+
const targetDir = projects.length > 0 ? (args.root ? projects.find(p => p.path.startsWith(rootPath)) || projects[0] : projects[0]).path : rootPath;
|
|
889
|
+
console.log(`Running "${args.runCommand}" in ${targetDir}...`);
|
|
890
|
+
const subprocess = execa(args.runCommand, { cwd: targetDir, shell: true, stdio: 'inherit' });
|
|
891
|
+
await subprocess;
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
if (args.addPkg) {
|
|
896
|
+
const projects = await discoverProjects(rootPath);
|
|
897
|
+
const target = projects[0];
|
|
898
|
+
if (!target) { console.error('No projects detected'); process.exit(1); }
|
|
899
|
+
const cmd = getAddCmd(target, args.addPkg);
|
|
900
|
+
if (!cmd) { console.error(`Cannot add package: unsupported project type ${target.type}`); process.exit(1); }
|
|
901
|
+
console.log(`Adding ${args.addPkg} to ${target.name}...`);
|
|
902
|
+
const subprocess = execa(cmd[0], cmd.slice(1), { cwd: target.path, stdio: 'inherit' });
|
|
903
|
+
await subprocess;
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
if (args.removePkg) {
|
|
831
908
|
const projects = await discoverProjects(rootPath);
|
|
832
|
-
|
|
833
|
-
|
|
909
|
+
const target = projects[0];
|
|
910
|
+
if (!target) { console.error('No projects detected'); process.exit(1); }
|
|
911
|
+
const cmd = getRemoveCmd(target, args.removePkg);
|
|
912
|
+
if (!cmd) { console.error(`Cannot remove package: unsupported project type ${target.type}`); process.exit(1); }
|
|
913
|
+
console.log(`Removing ${args.removePkg} from ${target.name}...`);
|
|
914
|
+
const subprocess = execa(cmd[0], cmd.slice(1), { cwd: target.path, stdio: 'inherit' });
|
|
915
|
+
await subprocess;
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
if (args.scaffold) {
|
|
920
|
+
const template = args.scaffold;
|
|
921
|
+
const projectName = args.name || 'my-project';
|
|
922
|
+
const targetPath = args.root ? path.resolve(args.root, projectName) : path.resolve(process.cwd(), projectName);
|
|
923
|
+
const scaffoldCmds = {
|
|
924
|
+
'nextjs': ['npx', 'create-next-app@latest', targetPath],
|
|
925
|
+
'nextjs-bun': ['bun', 'create', 'next-app', targetPath],
|
|
926
|
+
'react-vite': ['pnpm', 'create', 'vite', targetPath, '--template', 'react'],
|
|
927
|
+
'react-vite-npm': ['npm', 'create', 'vite@latest', targetPath, '--', '--template', 'react'],
|
|
928
|
+
'vue-vite': ['npm', 'create', 'vite@latest', targetPath, '--', '--template', 'vue'],
|
|
929
|
+
'rust': ['cargo', 'new', targetPath],
|
|
930
|
+
'django': ['django-admin', 'startproject', projectName, targetPath],
|
|
931
|
+
'python-basic': ['mkdir', '-p', targetPath],
|
|
932
|
+
'go': ['mkdir', '-p', targetPath, '&&', 'cd', targetPath, '&&', 'go', 'mod', 'init', projectName]
|
|
933
|
+
};
|
|
934
|
+
const cmd = scaffoldCmds[template];
|
|
935
|
+
if (!cmd) { console.error(`Unknown template: ${template}. Available: ${Object.keys(scaffoldCmds).join(', ')}`); process.exit(1); }
|
|
936
|
+
console.log(`Scaffolding ${template} at ${targetPath}...`);
|
|
937
|
+
if (template === 'go') {
|
|
938
|
+
await execa('mkdir', ['-p', targetPath]);
|
|
939
|
+
await execa('go', ['mod', 'init', projectName], { cwd: targetPath });
|
|
940
|
+
} else {
|
|
941
|
+
const subprocess = execa(cmd[0], cmd.slice(1), { stdio: 'inherit' });
|
|
942
|
+
await subprocess;
|
|
943
|
+
}
|
|
944
|
+
console.log(`✓ Project created at ${targetPath}`);
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
if (args.studioCheck) {
|
|
949
|
+
console.log('Environment check running...');
|
|
950
|
+
const checks = [
|
|
951
|
+
{name: 'Node.js', binary: 'node', versionCmd: ['-v']},
|
|
952
|
+
{name: 'npm', binary: 'npm', versionCmd: ['-v']},
|
|
953
|
+
{name: 'Python', binary: process.platform === 'win32' ? 'python' : 'python3', versionCmd: ['--version']},
|
|
954
|
+
{name: 'Rust (Cargo)', binary: 'cargo', versionCmd: ['--version']},
|
|
955
|
+
{name: 'Go', binary: 'go', versionCmd: ['version']},
|
|
956
|
+
{name: 'Java', binary: 'java', versionCmd: ['-version']},
|
|
957
|
+
{name: 'PHP', binary: 'php', versionCmd: ['-v']},
|
|
958
|
+
{name: 'Ruby', binary: 'ruby', versionCmd: ['-v']},
|
|
959
|
+
{name: '.NET', binary: 'dotnet', versionCmd: ['--version']}
|
|
960
|
+
];
|
|
961
|
+
for (const lang of checks) {
|
|
962
|
+
try {
|
|
963
|
+
const { stdout, stderr } = await execa(lang.binary, lang.versionCmd);
|
|
964
|
+
const version = (stdout || stderr || '').split('\n')[0].trim();
|
|
965
|
+
console.log(` ✓ ${lang.name}: ${version}`);
|
|
966
|
+
} catch {
|
|
967
|
+
console.log(` ✗ ${lang.name}: not installed`);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
if (args.aiAnalyze) {
|
|
974
|
+
console.log('AI Analysis is only available in TUI mode. Run: project-compass');
|
|
834
975
|
return;
|
|
835
976
|
}
|
|
836
977
|
|