project-compass 2.5.0 → 2.6.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/README.md +4 -2
- package/package.json +1 -1
- package/src/cli.js +85 -44
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Project Compass (v2.
|
|
1
|
+
# Project Compass (v2.6.0)
|
|
2
2
|
|
|
3
3
|
Project Compass is a futuristic CLI navigator built with [Ink](https://github.com/vadimdemedes/ink) that scans your current folder tree for familiar code projects and gives you one-keystroke access to build, test, or run them.
|
|
4
4
|
|
|
@@ -11,6 +11,7 @@ Project Compass is a futuristic CLI navigator built with [Ink](https://github.co
|
|
|
11
11
|
- 🧠 **Smart Detection**: Support for 20+ frameworks including **Spring Boot** (Maven/Gradle), **ASP.NET Core**, **Rocket/Actix** (Rust), **Laravel** (PHP), **Vite**, **Prisma**, and more.
|
|
12
12
|
- ⚠️ **Runtime Health**: Automatically checks if the required language/runtime (e.g., `node`, `python`, `cargo`) is installed and warns you if it's missing.
|
|
13
13
|
- 💎 **Omni-Studio**: A new interactive environment intelligence mode to see all installed runtimes and versions.
|
|
14
|
+
- 📂 **Log Management**: Clear output with **Shift+X** or export logs to a text file with **Shift+E**.
|
|
14
15
|
- 🔌 **Extensible**: Add custom commands with **Shift+C** and frameworks via `plugins.json`.
|
|
15
16
|
|
|
16
17
|
## Installation
|
|
@@ -35,7 +36,8 @@ project-compass [--dir /path/to/workspace] [--studio]
|
|
|
35
36
|
| **Shift+A** | Open **Omni-Studio** (Environment View) |
|
|
36
37
|
| **Shift+C** | Add a custom command (`label|cmd`) |
|
|
37
38
|
| **Shift+X** | **Clear output logs** |
|
|
38
|
-
| **Shift
|
|
39
|
+
| **Shift+E** | **Export logs to .txt** |
|
|
40
|
+
| **Shift ↑ / ↓** | Scroll output buffer (Intuitive Direction) |
|
|
39
41
|
| **Shift+L** | Rerun last command |
|
|
40
42
|
| **Shift+H** | Toggle help cards |
|
|
41
43
|
| **Shift+S** | Toggle structure guide |
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -177,9 +177,9 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
177
177
|
const addLog = useCallback((line) => {
|
|
178
178
|
setLogLines((prev) => {
|
|
179
179
|
const normalized = typeof line === 'string' ? line : JSON.stringify(line);
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
return
|
|
180
|
+
const lines = normalized.split(/\r?\n/).filter(l => l.trim().length > 0);
|
|
181
|
+
const appended = [...prev, ...lines];
|
|
182
|
+
return appended.length > 500 ? appended.slice(appended.length - 500) : appended;
|
|
183
183
|
});
|
|
184
184
|
}, []);
|
|
185
185
|
|
|
@@ -228,10 +228,10 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
228
228
|
runningProcessRef.current = subprocess;
|
|
229
229
|
|
|
230
230
|
subprocess.stdout?.on('data', (chunk) => {
|
|
231
|
-
addLog(chunk.toString()
|
|
231
|
+
addLog(chunk.toString());
|
|
232
232
|
});
|
|
233
233
|
subprocess.stderr?.on('data', (chunk) => {
|
|
234
|
-
addLog(kleur.red(chunk.toString()
|
|
234
|
+
addLog(kleur.red(chunk.toString()));
|
|
235
235
|
});
|
|
236
236
|
|
|
237
237
|
await subprocess;
|
|
@@ -292,6 +292,19 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
292
292
|
setCustomInput('');
|
|
293
293
|
}, [customInput, selectedProject, handleAddCustomCommand, addLog]);
|
|
294
294
|
|
|
295
|
+
const exportLogs = useCallback(() => {
|
|
296
|
+
if (!logLines.length) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
try {
|
|
300
|
+
const exportPath = path.resolve(process.cwd(), `compass-logs-${Date.now()}.txt`);
|
|
301
|
+
fs.writeFileSync(exportPath, logLines.join('\n'));
|
|
302
|
+
addLog(kleur.green(`✓ Logs exported to ${exportPath}`));
|
|
303
|
+
} catch (err) {
|
|
304
|
+
addLog(kleur.red(`✗ Export failed: ${err.message}`));
|
|
305
|
+
}
|
|
306
|
+
}, [logLines, addLog]);
|
|
307
|
+
|
|
295
308
|
useInput((input, key) => {
|
|
296
309
|
if (customMode) {
|
|
297
310
|
if (key.return) {
|
|
@@ -334,6 +347,10 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
334
347
|
setLogOffset(0);
|
|
335
348
|
return;
|
|
336
349
|
}
|
|
350
|
+
if (shiftCombo('e')) {
|
|
351
|
+
exportLogs();
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
337
354
|
|
|
338
355
|
const scrollLogs = (delta) => {
|
|
339
356
|
setLogOffset((prev) => {
|
|
@@ -366,11 +383,11 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
366
383
|
}
|
|
367
384
|
|
|
368
385
|
if (key.shift && key.upArrow) {
|
|
369
|
-
scrollLogs(
|
|
386
|
+
scrollLogs(1);
|
|
370
387
|
return;
|
|
371
388
|
}
|
|
372
389
|
if (key.shift && key.downArrow) {
|
|
373
|
-
scrollLogs(1);
|
|
390
|
+
scrollLogs(-1);
|
|
374
391
|
return;
|
|
375
392
|
}
|
|
376
393
|
|
|
@@ -418,21 +435,18 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
418
435
|
}
|
|
419
436
|
});
|
|
420
437
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
const projectRows = [];
|
|
438
|
+
const projectCountLabel = `${projects.length} project${projects.length === 1 ? '' : 's'}`;
|
|
439
|
+
const projectRows = [];
|
|
426
440
|
if (loading) {
|
|
427
|
-
projectRows.push(create(Text, {dimColor: true}, 'Scanning projects…'));
|
|
441
|
+
projectRows.push(create(Text, {key: 'scanning', dimColor: true}, 'Scanning projects…'));
|
|
428
442
|
}
|
|
429
443
|
if (error) {
|
|
430
|
-
projectRows.push(create(Text, {color: 'red'}, `Unable to scan: ${error}`));
|
|
444
|
+
projectRows.push(create(Text, {key: 'error', color: 'red'}, `Unable to scan: ${error}`));
|
|
431
445
|
}
|
|
432
446
|
if (!loading && !error && projects.length === 0) {
|
|
433
|
-
projectRows.push(create(Text, {dimColor: true}, 'No recognizable project manifests found.'));
|
|
447
|
+
projectRows.push(create(Text, {key: 'empty', dimColor: true}, 'No recognizable project manifests found.'));
|
|
434
448
|
}
|
|
435
|
-
if (!loading) {
|
|
449
|
+
if (!loading && mainView === 'navigator') {
|
|
436
450
|
projects.forEach((project, index) => {
|
|
437
451
|
const isSelected = index === selectedIndex;
|
|
438
452
|
const frameworkBadges = (project.frameworks || []).map((frame) => `${frame.icon} ${frame.name}`).join(', ');
|
|
@@ -466,59 +480,58 @@ const projectRows = [];
|
|
|
466
480
|
detailContent.push(
|
|
467
481
|
create(
|
|
468
482
|
Box,
|
|
469
|
-
{flexDirection: 'row'},
|
|
483
|
+
{key: 'title-row', flexDirection: 'row'},
|
|
470
484
|
create(Text, {color: 'cyan', bold: true}, `${selectedProject.icon} ${selectedProject.name}`),
|
|
471
485
|
selectedProject.missingBinaries && selectedProject.missingBinaries.length > 0 && create(Text, {color: 'red', bold: true}, ' ⚠️ MISSING RUNTIME')
|
|
472
486
|
),
|
|
473
|
-
create(Text, {dimColor: true}, `${selectedProject.type} · ${selectedProject.manifest || 'detected manifest'}`),
|
|
474
|
-
create(Text, {dimColor: true}, `Location: ${path.relative(rootPath, selectedProject.path) || '.'}`)
|
|
487
|
+
create(Text, {key: 'manifest', dimColor: true}, `${selectedProject.type} · ${selectedProject.manifest || 'detected manifest'}`),
|
|
488
|
+
create(Text, {key: 'loc', dimColor: true}, `Location: ${path.relative(rootPath, selectedProject.path) || '.'}`)
|
|
475
489
|
);
|
|
476
490
|
if (selectedProject.description) {
|
|
477
|
-
detailContent.push(create(Text,
|
|
491
|
+
detailContent.push(create(Text, {key: 'desc'}, selectedProject.description));
|
|
478
492
|
}
|
|
479
493
|
const frameworks = (selectedProject.frameworks || []).map((lib) => `${lib.icon} ${lib.name}`).join(', ');
|
|
480
494
|
if (frameworks) {
|
|
481
|
-
detailContent.push(create(Text, {dimColor: true}, `Frameworks: ${frameworks}`));
|
|
495
|
+
detailContent.push(create(Text, {key: 'frames', dimColor: true}, `Frameworks: ${frameworks}`));
|
|
482
496
|
}
|
|
483
497
|
if (selectedProject.extra?.scripts && selectedProject.extra.scripts.length) {
|
|
484
|
-
detailContent.push(create(Text, {dimColor: true}, `Scripts: ${selectedProject.extra.scripts.join(', ')}`));
|
|
498
|
+
detailContent.push(create(Text, {key: 'scripts', dimColor: true}, `Scripts: ${selectedProject.extra.scripts.join(', ')}`));
|
|
485
499
|
}
|
|
486
500
|
|
|
487
501
|
if (selectedProject.missingBinaries && selectedProject.missingBinaries.length > 0) {
|
|
488
502
|
detailContent.push(
|
|
489
|
-
create(Text, {color: 'red', bold: true, marginTop: 1}, 'MISSING BINARIES:'),
|
|
490
|
-
create(Text, {color: 'red'}, `Please install: ${selectedProject.missingBinaries.join(', ')}`),
|
|
491
|
-
create(Text, {dimColor: true}, 'Project commands may fail until these are in your PATH.')
|
|
503
|
+
create(Text, {key: 'missing-title', color: 'red', bold: true, marginTop: 1}, 'MISSING BINARIES:'),
|
|
504
|
+
create(Text, {key: 'missing-list', color: 'red'}, `Please install: ${selectedProject.missingBinaries.join(', ')}`),
|
|
505
|
+
create(Text, {key: 'missing-hint', dimColor: true}, 'Project commands may fail until these are in your PATH.')
|
|
492
506
|
);
|
|
493
507
|
}
|
|
494
508
|
|
|
495
|
-
detailContent.push(create(Text, {dimColor: true, marginTop: 1}, `Custom commands stored in ${CONFIG_PATH}`));
|
|
496
|
-
detailContent.push(create(Text, {dimColor: true, marginBottom: 1}, `Extend frameworks via ${PLUGIN_FILE}`));
|
|
497
|
-
detailContent.push(create(Text, {bold: true, marginTop: 1}, 'Commands'));
|
|
509
|
+
detailContent.push(create(Text, {key: 'config-path', dimColor: true, marginTop: 1}, `Custom commands stored in ${CONFIG_PATH}`));
|
|
510
|
+
detailContent.push(create(Text, {key: 'plugin-path', dimColor: true, marginBottom: 1}, `Extend frameworks via ${PLUGIN_FILE}`));
|
|
511
|
+
detailContent.push(create(Text, {key: 'cmd-header', bold: true, marginTop: 1}, 'Commands'));
|
|
498
512
|
detailedIndexed.forEach((command) => {
|
|
499
513
|
detailContent.push(
|
|
500
514
|
create(Text, {key: `detail-${command.shortcut}-${command.label}`}, `${command.shortcut}. ${command.label} ${command.source === 'custom' ? kleur.magenta('(custom)') : command.source === 'framework' ? kleur.cyan('(framework)') : command.source === 'plugin' ? kleur.green('(plugin)') : ''}`)
|
|
501
515
|
);
|
|
502
|
-
detailContent.push(create(Text, {dimColor: true}, ` ↳ ${command.command.join(' ')}`));
|
|
516
|
+
detailContent.push(create(Text, {key: `detail-line-${command.shortcut}-${command.label}`, dimColor: true}, ` ↳ ${command.command.join(' ')}`));
|
|
503
517
|
});
|
|
504
518
|
if (!detailedIndexed.length) {
|
|
505
|
-
detailContent.push(create(Text, {dimColor: true}, 'No built-in commands yet. Add a custom command with Shift+C.'));
|
|
519
|
+
detailContent.push(create(Text, {key: 'no-cmds', dimColor: true}, 'No built-in commands yet. Add a custom command with Shift+C.'));
|
|
506
520
|
}
|
|
507
521
|
const setupHints = selectedProject.extra?.setupHints || [];
|
|
508
522
|
if (setupHints.length) {
|
|
509
|
-
detailContent.push(create(Text, {dimColor: true, marginTop: 1}, 'Setup hints:'));
|
|
510
|
-
setupHints.forEach((hint) => detailContent.push(create(Text, {dimColor: true}, ` • ${hint}`)));
|
|
523
|
+
detailContent.push(create(Text, {key: 'setup-header', dimColor: true, marginTop: 1}, 'Setup hints:'));
|
|
524
|
+
setupHints.forEach((hint, hidx) => detailContent.push(create(Text, {key: `hint-${hidx}`, dimColor: true}, ` • ${hint}`)));
|
|
511
525
|
}
|
|
512
|
-
detailContent.push(create(Text, {dimColor: true}, 'Press Shift+C → label|cmd to save custom actions, Enter to close detail view.'));
|
|
526
|
+
detailContent.push(create(Text, {key: 'hint-line', dimColor: true}, 'Press Shift+C → label|cmd to save custom actions, Enter to close detail view.'));
|
|
513
527
|
} else {
|
|
514
|
-
detailContent.push(create(Text, {dimColor: true}, 'Press Enter on a project to reveal details (icons, commands, frameworks, custom actions).'));
|
|
528
|
+
detailContent.push(create(Text, {key: 'enter-hint', dimColor: true}, 'Press Enter on a project to reveal details (icons, commands, frameworks, custom actions).'));
|
|
515
529
|
}
|
|
516
530
|
|
|
517
531
|
if (customMode) {
|
|
518
|
-
detailContent.push(create(Text, {color: 'cyan'}, `Type label|cmd (Enter to save, Esc to cancel): ${customInput}`));
|
|
532
|
+
detailContent.push(create(Text, {key: 'custom-input', color: 'cyan'}, `Type label|cmd (Enter to save, Esc to cancel): ${customInput}`));
|
|
519
533
|
}
|
|
520
534
|
|
|
521
|
-
const projectCountLabel = `${projects.length} project${projects.length === 1 ? '' : 's'}`;
|
|
522
535
|
const artTileNodes = useMemo(() => {
|
|
523
536
|
const selectedName = selectedProject?.name || 'Awaiting selection';
|
|
524
537
|
const selectedType = selectedProject?.type || 'Unknown stack';
|
|
@@ -602,7 +615,7 @@ const projectRows = [];
|
|
|
602
615
|
const visibleLogs = logLines.slice(logWindowStart, logWindowEnd);
|
|
603
616
|
const logNodes = visibleLogs.length
|
|
604
617
|
? visibleLogs.map((line, index) => create(Text, {key: index}, line))
|
|
605
|
-
: [create(Text, {dimColor: true}, 'Logs will appear here once you run a command.')];
|
|
618
|
+
: [create(Text, {key: 'no-logs', dimColor: true}, 'Logs will appear here once you run a command.')];
|
|
606
619
|
|
|
607
620
|
const helpCards = [
|
|
608
621
|
{
|
|
@@ -622,7 +635,7 @@ const projectRows = [];
|
|
|
622
635
|
'B / T / R build/test/run',
|
|
623
636
|
'1-9 run detail commands',
|
|
624
637
|
'Shift+L rerun last command',
|
|
625
|
-
'Shift+X clear
|
|
638
|
+
'Shift+X clear / Shift+E export'
|
|
626
639
|
]
|
|
627
640
|
},
|
|
628
641
|
{
|
|
@@ -662,7 +675,7 @@ const projectRows = [];
|
|
|
662
675
|
)
|
|
663
676
|
)
|
|
664
677
|
)
|
|
665
|
-
: create(Text, {dimColor: true, marginTop: 1}, 'Help cards hidden · press Shift+H to show navigation, command flow, and recent runs.');
|
|
678
|
+
: create(Text, {key: 'help-hint', dimColor: true, marginTop: 1}, 'Help cards hidden · press Shift+H to show navigation, command flow, and recent runs.');
|
|
666
679
|
|
|
667
680
|
const structureGuide = showStructureGuide
|
|
668
681
|
? create(
|
|
@@ -693,14 +706,18 @@ const projectRows = [];
|
|
|
693
706
|
padding: 1
|
|
694
707
|
},
|
|
695
708
|
create(Text, {color: 'cyan', bold: true}, 'Help overlay · press ? to hide'),
|
|
696
|
-
create(Text, null, 'Shift+↑/↓ scrolls
|
|
697
|
-
create(Text, null, 'B/T/R run build/test/run; 1-9
|
|
698
|
-
create(Text, null, 'Shift+H
|
|
709
|
+
create(Text, null, 'Shift+↑/↓ scrolls logs; Shift+X clears; Shift+E exports to file; Shift+A Omni-Studio.'),
|
|
710
|
+
create(Text, null, 'B/T/R run build/test/run; 1-9 detail commands; Shift+L reruns previous command.'),
|
|
711
|
+
create(Text, null, 'Shift+H help cards, Shift+S structure guide, ? overlay, Shift+Q quits.'),
|
|
699
712
|
create(Text, null, 'Projects + Details stay paired while Output keeps its own full-width band.'),
|
|
700
713
|
create(Text, null, 'Structure guide lists the manifests that trigger each language detection.')
|
|
701
714
|
)
|
|
702
715
|
: null;
|
|
703
716
|
|
|
717
|
+
if (mainView === 'studio') {
|
|
718
|
+
return create(Studio);
|
|
719
|
+
}
|
|
720
|
+
|
|
704
721
|
const toggleHint = showHelpCards ? 'Shift+H hides the help cards' : 'Shift+H shows the help cards';
|
|
705
722
|
const headerHint = viewMode === 'detail'
|
|
706
723
|
? `Detail mode · 1-${Math.max(detailedIndexed.length, 1)} to execute, Shift+C: add custom commands, Enter: back to list, Shift+Q: quit · ${toggleHint}, Shift+S toggles structure guide`
|
|
@@ -834,8 +851,32 @@ function parseArgs() {
|
|
|
834
851
|
async function main() {
|
|
835
852
|
const args = parseArgs();
|
|
836
853
|
if (args.help) {
|
|
837
|
-
console.log('Project Compass · Ink project runner');
|
|
838
|
-
console.log('
|
|
854
|
+
console.log(kleur.cyan('Project Compass · Ink project navigator/runner'));
|
|
855
|
+
console.log('');
|
|
856
|
+
console.log(kleur.bold('Usage:'));
|
|
857
|
+
console.log(' project-compass [--dir <path>] [--studio]');
|
|
858
|
+
console.log('');
|
|
859
|
+
console.log(kleur.bold('Arguments:'));
|
|
860
|
+
console.log(' --dir, --path <path> Specify root workspace directory to scan');
|
|
861
|
+
console.log(' --studio Launch directly into Omni-Studio mode');
|
|
862
|
+
console.log(' --help, -h Show this help menu');
|
|
863
|
+
console.log('');
|
|
864
|
+
console.log(kleur.bold('Core Keybinds:'));
|
|
865
|
+
console.log(' ↑ / ↓ Move project focus');
|
|
866
|
+
console.log(' Enter Toggle detail view for selected project');
|
|
867
|
+
console.log(' Shift+A Switch to Omni-Studio (Environment Health)');
|
|
868
|
+
console.log(' Shift+X Clear the output log buffer');
|
|
869
|
+
console.log(' Shift+E Export current logs to a .txt file');
|
|
870
|
+
console.log(' Shift+↑ / ↓ Scroll the output logs back/forward');
|
|
871
|
+
console.log(' Shift+Q Quit application');
|
|
872
|
+
console.log('');
|
|
873
|
+
console.log(kleur.bold('Execution shortcuts:'));
|
|
874
|
+
console.log(' B / T / R Quick run: Build / Test / Run');
|
|
875
|
+
console.log(' 1-9 Run numbered commands in detail view');
|
|
876
|
+
console.log(' Shift+L Rerun the last executed command');
|
|
877
|
+
console.log(' Shift+C Add a custom command (in detail view)');
|
|
878
|
+
console.log('');
|
|
879
|
+
console.log(kleur.dim('Documentation: https://github.com/CrimsonDevil333333/project-compass'));
|
|
839
880
|
return;
|
|
840
881
|
}
|
|
841
882
|
const rootPath = args.root ? path.resolve(args.root) : process.cwd();
|