project-compass 1.0.1 β 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -2
- package/package.json +1 -1
- package/project-compass-1.0.2.tgz +0 -0
- package/src/cli.js +217 -62
package/README.md
CHANGED
|
@@ -5,11 +5,11 @@ Project Compass is a futuristic CLI navigator built with [Ink](https://github.co
|
|
|
5
5
|
## Highlights
|
|
6
6
|
|
|
7
7
|
- π Scans directories for Node.js, Python, Rust, Go, Java, and Scala projects by looking at their manifest files.
|
|
8
|
-
-
|
|
8
|
+
- π¨ Combines the glyph-based art board with a Projects/Details row that keeps everything inside the viewport, while live stdout/stderr logs stay in their own band below.
|
|
9
9
|
- π Press **Enter** on any project to open the detail view, where you can inspect the type, manifest, frameworks, commands, and save custom actions.
|
|
10
|
+
- π‘ A dedicated output row + help tiles keep logs in one pane, support smooth refresh (Shift+β/β or mouse wheel to scroll, typing feeds stdin, Ctrl+C aborts, L reruns the last command) and include a `?` overlay for quick navigation tips.
|
|
10
11
|
- π― Built-in shortcuts (B/T/R) run the canonical build/test/run workflow, while numeric hotkeys (1, 2, 3...) execute whichever command is listed in the detail view.
|
|
11
12
|
- π§ Add bespoke commands via **C** in detail view and store them globally (`~/.project-compass/config.json`) so every workspace remembers your favorite invocations.
|
|
12
|
-
- π¨ The top art board layers glyphs, neon-like tiles, and highlighted metrics (Pulse, Focus, Rhythm) so the CLI feels like a curated gallery instead of a bland table.
|
|
13
13
|
- π Extend detection via plugins (JSON specs under `~/.project-compass/plugins.json`) to teach Project Compass about extra frameworks or command sets.
|
|
14
14
|
- π¦ Install globally and invoke `project-compass` from any folder to activate the UI instantly.
|
|
15
15
|
|
|
@@ -34,6 +34,10 @@ project-compass [--dir /path/to/workspace]
|
|
|
34
34
|
| B / T / R | Quick build / test / run actions (when available) |
|
|
35
35
|
| 1β9 | Execute the numbered command inside the detail view |
|
|
36
36
|
| C | Add a custom command (`label|cmd`) that saves to `~/.project-compass/config.json` |
|
|
37
|
+
| Shift β / β | Scroll the output buffer (Shift+arrows or mouse wheel) |
|
|
38
|
+
| L | Rerun the last executed command |
|
|
39
|
+
| ? | Toggle the help overlay with navigation tips |
|
|
40
|
+
| Ctrl+C | Interrupt a running command (works while streaming output) |
|
|
37
41
|
| Q | Quit |
|
|
38
42
|
|
|
39
43
|
## Framework & plugin support
|
|
@@ -66,6 +70,10 @@ Each command value can be a string or an array of tokens. When a plugin matches
|
|
|
66
70
|
|
|
67
71
|
Project Compass now opens with a rounded art board that shuffles your glyph row (ββββ
β with neon accents) and three branded tiles showing workspace pulse, the selected project focus, and the rhythm of commands. The detail view sits beside the project list as a gallery; border colors, badges, and the ambient header hint keep it feeling like a living installation rather than a vanilla CLI.
|
|
68
72
|
|
|
73
|
+
## Layout, output & help
|
|
74
|
+
|
|
75
|
+
Projects and details now occupy the same row, while the output panel takes its own full-width band beneath so long logs no longer stretch the rest of the UI. The output pane scrolls independently (Shift+β/β or mouse wheel), accepts keystrokes to feed stdin while a command runs, and honors Ctrl+C to abort. A trio of help tiles underneath highlights navigation cues, command flow, and recent runs, and pressing `?` opens an overlay with extra tips (for example, left-click cycles the project focus, L reruns the last command, and the help overlay reminds you about the new shortcuts).
|
|
76
|
+
|
|
69
77
|
## Developer notes
|
|
70
78
|
|
|
71
79
|
- `npm start` launches the Ink UI in the current directory.
|
|
@@ -76,6 +84,7 @@ Project Compass now opens with a rounded art board that shuffles your glyph row
|
|
|
76
84
|
## License
|
|
77
85
|
|
|
78
86
|
MIT Β© 2026 Satyaa & Clawdy
|
|
87
|
+
|
|
79
88
|
## Release & packaging
|
|
80
89
|
|
|
81
90
|
- Bump `package.json`/`package-lock.json` versions (e.g., `npm version 1.0.1 --no-git-tag-version`).
|
package/package.json
CHANGED
|
Binary file
|
package/src/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
|
2
|
+
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
|
3
3
|
import {render, Box, Text, useApp, useInput} from 'ink';
|
|
4
4
|
import fastGlob from 'fast-glob';
|
|
5
5
|
import path from 'path';
|
|
@@ -771,6 +771,8 @@ const ACTION_MAP = {
|
|
|
771
771
|
};
|
|
772
772
|
const ART_CHARS = ['β', 'β', 'β', 'β
', 'β'];
|
|
773
773
|
const ART_COLORS = ['magenta', 'blue', 'cyan', 'yellow', 'red'];
|
|
774
|
+
const OUTPUT_WINDOW_SIZE = 10;
|
|
775
|
+
const RECENT_RUN_LIMIT = 5;
|
|
774
776
|
|
|
775
777
|
function useScanner(rootPath) {
|
|
776
778
|
const [state, setState] = useState({projects: [], loading: true, error: null});
|
|
@@ -820,16 +822,25 @@ function Compass({rootPath}) {
|
|
|
820
822
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
821
823
|
const [viewMode, setViewMode] = useState('list');
|
|
822
824
|
const [logLines, setLogLines] = useState([]);
|
|
825
|
+
const [logOffset, setLogOffset] = useState(0);
|
|
823
826
|
const [running, setRunning] = useState(false);
|
|
824
827
|
const [lastAction, setLastAction] = useState(null);
|
|
825
828
|
const [customMode, setCustomMode] = useState(false);
|
|
826
829
|
const [customInput, setCustomInput] = useState('');
|
|
827
830
|
const [config, setConfig] = useState(() => loadConfig());
|
|
828
|
-
|
|
831
|
+
const [showHelp, setShowHelp] = useState(false);
|
|
832
|
+
const [recentRuns, setRecentRuns] = useState([]);
|
|
829
833
|
const selectedProject = projects[selectedIndex] || null;
|
|
834
|
+
const runningProcessRef = useRef(null);
|
|
835
|
+
const lastCommandRef = useRef(null);
|
|
830
836
|
|
|
831
837
|
const addLog = useCallback((line) => {
|
|
832
|
-
setLogLines((prev) =>
|
|
838
|
+
setLogLines((prev) => {
|
|
839
|
+
const normalized = typeof line === 'string' ? line : JSON.stringify(line);
|
|
840
|
+
const next = [...prev, normalized];
|
|
841
|
+
return next.length > 250 ? next.slice(next.length - 250) : next;
|
|
842
|
+
});
|
|
843
|
+
setLogOffset(0);
|
|
833
844
|
}, []);
|
|
834
845
|
|
|
835
846
|
const detailCommands = useMemo(() => buildDetailCommands(selectedProject, config), [selectedProject, config]);
|
|
@@ -843,8 +854,9 @@ function Compass({rootPath}) {
|
|
|
843
854
|
return map;
|
|
844
855
|
}, [detailedIndexed]);
|
|
845
856
|
|
|
846
|
-
const runProjectCommand = useCallback(async (commandMeta) => {
|
|
847
|
-
|
|
857
|
+
const runProjectCommand = useCallback(async (commandMeta, targetProject = selectedProject) => {
|
|
858
|
+
const project = targetProject || selectedProject;
|
|
859
|
+
if (!project) {
|
|
848
860
|
return;
|
|
849
861
|
}
|
|
850
862
|
if (!commandMeta || !Array.isArray(commandMeta.command) || commandMeta.command.length === 0) {
|
|
@@ -856,16 +868,24 @@ function Compass({rootPath}) {
|
|
|
856
868
|
return;
|
|
857
869
|
}
|
|
858
870
|
|
|
871
|
+
const commandLabel = commandMeta.label || commandMeta.command.join(' ');
|
|
872
|
+
lastCommandRef.current = {project, commandMeta};
|
|
859
873
|
setRunning(true);
|
|
860
|
-
setLastAction(`${
|
|
874
|
+
setLastAction(`${project.name} Β· ${commandLabel}`);
|
|
861
875
|
const fullCmd = commandMeta.command;
|
|
862
876
|
addLog(kleur.cyan(`> ${fullCmd.join(' ')}`));
|
|
877
|
+
setRecentRuns((prev) => {
|
|
878
|
+
const entry = {project: project.name, command: commandLabel, time: new Date().toLocaleTimeString()};
|
|
879
|
+
return [entry, ...prev].slice(0, RECENT_RUN_LIMIT);
|
|
880
|
+
});
|
|
863
881
|
|
|
864
882
|
try {
|
|
865
883
|
const subprocess = execa(fullCmd[0], fullCmd.slice(1), {
|
|
866
|
-
cwd:
|
|
867
|
-
env: process.env
|
|
884
|
+
cwd: project.path,
|
|
885
|
+
env: process.env,
|
|
886
|
+
stdin: 'pipe'
|
|
868
887
|
});
|
|
888
|
+
runningProcessRef.current = subprocess;
|
|
869
889
|
|
|
870
890
|
subprocess.stdout?.on('data', (chunk) => {
|
|
871
891
|
addLog(chunk.toString().trimEnd());
|
|
@@ -875,13 +895,14 @@ function Compass({rootPath}) {
|
|
|
875
895
|
});
|
|
876
896
|
|
|
877
897
|
await subprocess;
|
|
878
|
-
addLog(kleur.green(`β ${
|
|
898
|
+
addLog(kleur.green(`β ${commandLabel} finished`));
|
|
879
899
|
} catch (error) {
|
|
880
|
-
addLog(kleur.red(`β ${
|
|
900
|
+
addLog(kleur.red(`β ${commandLabel} failed: ${error.shortMessage || error.message}`));
|
|
881
901
|
} finally {
|
|
882
902
|
setRunning(false);
|
|
903
|
+
runningProcessRef.current = null;
|
|
883
904
|
}
|
|
884
|
-
}, [
|
|
905
|
+
}, [addLog, running, selectedProject]);
|
|
885
906
|
|
|
886
907
|
const handleAddCustomCommand = useCallback((label, commandTokens) => {
|
|
887
908
|
if (!selectedProject) {
|
|
@@ -951,11 +972,57 @@ function Compass({rootPath}) {
|
|
|
951
972
|
return;
|
|
952
973
|
}
|
|
953
974
|
|
|
954
|
-
if (key.
|
|
975
|
+
if (key.mouse) {
|
|
976
|
+
if (key.mouse === 'left' && projects.length > 0) {
|
|
977
|
+
setSelectedIndex((prev) => (prev + 1) % projects.length);
|
|
978
|
+
} else if (key.mouse === 'scrollUp') {
|
|
979
|
+
const maxScroll = Math.max(0, logLines.length - OUTPUT_WINDOW_SIZE);
|
|
980
|
+
setLogOffset((prev) => Math.min(maxScroll, prev + 1));
|
|
981
|
+
} else if (key.mouse === 'scrollDown') {
|
|
982
|
+
setLogOffset((prev) => Math.max(0, prev - 1));
|
|
983
|
+
}
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
if (running && runningProcessRef.current) {
|
|
988
|
+
if (key.ctrl && input === 'c') {
|
|
989
|
+
runningProcessRef.current.kill('SIGINT');
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
if (key.return) {
|
|
993
|
+
runningProcessRef.current.stdin?.write('\n');
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
if (input) {
|
|
997
|
+
runningProcessRef.current.stdin?.write(input);
|
|
998
|
+
}
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
if (key.shift && key.upArrow) {
|
|
1003
|
+
const maxScroll = Math.max(0, logLines.length - OUTPUT_WINDOW_SIZE);
|
|
1004
|
+
setLogOffset((prev) => Math.min(maxScroll, prev + 1));
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
if (key.shift && key.downArrow) {
|
|
1008
|
+
setLogOffset((prev) => Math.max(0, prev - 1));
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
if (input === '?') {
|
|
1013
|
+
setShowHelp((prev) => !prev);
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
if (input === 'l' && lastCommandRef.current) {
|
|
1017
|
+
runProjectCommand(lastCommandRef.current.commandMeta, lastCommandRef.current.project);
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
if (key.upArrow && !key.shift && projects.length > 0) {
|
|
955
1022
|
setSelectedIndex((prev) => (prev - 1 + projects.length) % projects.length);
|
|
956
1023
|
return;
|
|
957
1024
|
}
|
|
958
|
-
if (key.downArrow && projects.length > 0) {
|
|
1025
|
+
if (key.downArrow && !key.shift && projects.length > 0) {
|
|
959
1026
|
setSelectedIndex((prev) => (prev + 1) % projects.length);
|
|
960
1027
|
return;
|
|
961
1028
|
}
|
|
@@ -977,17 +1044,17 @@ function Compass({rootPath}) {
|
|
|
977
1044
|
}
|
|
978
1045
|
if (ACTION_MAP[input]) {
|
|
979
1046
|
const commandMeta = selectedProject?.commands?.[ACTION_MAP[input]];
|
|
980
|
-
runProjectCommand(commandMeta);
|
|
1047
|
+
runProjectCommand(commandMeta, selectedProject);
|
|
981
1048
|
return;
|
|
982
1049
|
}
|
|
983
1050
|
if (viewMode === 'detail' && detailShortcutMap.has(input)) {
|
|
984
|
-
runProjectCommand(detailShortcutMap.get(input));
|
|
1051
|
+
runProjectCommand(detailShortcutMap.get(input), selectedProject);
|
|
985
1052
|
}
|
|
986
1053
|
});
|
|
987
1054
|
|
|
988
1055
|
const projectRows = [];
|
|
989
1056
|
if (loading) {
|
|
990
|
-
projectRows.push(create(Text, {dimColor: true}, 'Scanning
|
|
1057
|
+
projectRows.push(create(Text, {dimColor: true}, 'Scanning projectsβ¦'));
|
|
991
1058
|
}
|
|
992
1059
|
if (error) {
|
|
993
1060
|
projectRows.push(create(Text, {color: 'red'}, `Unable to scan: ${error}`));
|
|
@@ -1002,21 +1069,17 @@ function Compass({rootPath}) {
|
|
|
1002
1069
|
projectRows.push(
|
|
1003
1070
|
create(
|
|
1004
1071
|
Box,
|
|
1005
|
-
{key: project.id, flexDirection: 'column', marginBottom: 1},
|
|
1072
|
+
{key: project.id, flexDirection: 'column', marginBottom: 1, padding: 1},
|
|
1006
1073
|
create(
|
|
1007
|
-
|
|
1008
|
-
{
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
bold: isSelected
|
|
1014
|
-
},
|
|
1015
|
-
`${project.icon} ${project.name}`
|
|
1016
|
-
),
|
|
1017
|
-
create(Text, {color: 'gray'}, ` ${project.type} Β· ${path.relative(rootPath, project.path) || '.'}`)
|
|
1074
|
+
Text,
|
|
1075
|
+
{
|
|
1076
|
+
color: isSelected ? 'cyan' : 'white',
|
|
1077
|
+
bold: isSelected
|
|
1078
|
+
},
|
|
1079
|
+
`${project.icon} ${project.name}`
|
|
1018
1080
|
),
|
|
1019
|
-
|
|
1081
|
+
create(Text, {dimColor: true}, ` ${project.type} Β· ${path.relative(rootPath, project.path) || '.'}`),
|
|
1082
|
+
frameworkBadges && create(Text, {dimColor: true}, ` ${frameworkBadges}`)
|
|
1020
1083
|
)
|
|
1021
1084
|
);
|
|
1022
1085
|
});
|
|
@@ -1025,7 +1088,7 @@ function Compass({rootPath}) {
|
|
|
1025
1088
|
const detailContent = [];
|
|
1026
1089
|
if (viewMode === 'detail' && selectedProject) {
|
|
1027
1090
|
detailContent.push(
|
|
1028
|
-
create(Text, {color: '
|
|
1091
|
+
create(Text, {color: 'cyan', bold: true}, `${selectedProject.icon} ${selectedProject.name}`),
|
|
1029
1092
|
create(Text, {dimColor: true}, `${selectedProject.type} Β· ${selectedProject.manifest || 'detected manifest'}`),
|
|
1030
1093
|
create(Text, {dimColor: true}, `Location: ${path.relative(rootPath, selectedProject.path) || '.'}`)
|
|
1031
1094
|
);
|
|
@@ -1044,7 +1107,7 @@ function Compass({rootPath}) {
|
|
|
1044
1107
|
detailContent.push(create(Text, {bold: true, marginTop: 1}, 'Commands'));
|
|
1045
1108
|
detailedIndexed.forEach((command) => {
|
|
1046
1109
|
detailContent.push(
|
|
1047
|
-
create(Text, {key:
|
|
1110
|
+
create(Text, {key: `detail-${command.shortcut}-${command.label}`}, `${command.shortcut}. ${command.label} ${command.source === 'custom' ? kleur.magenta('(custom)') : command.source === 'framework' ? kleur.cyan('(framework)') : ''}`)
|
|
1048
1111
|
);
|
|
1049
1112
|
detailContent.push(create(Text, {dimColor: true}, ` β³ ${command.command.join(' ')}`));
|
|
1050
1113
|
});
|
|
@@ -1060,24 +1123,20 @@ function Compass({rootPath}) {
|
|
|
1060
1123
|
detailContent.push(create(Text, {color: 'cyan'}, `Type label|cmd (Enter to save, Esc to cancel): ${customInput}`));
|
|
1061
1124
|
}
|
|
1062
1125
|
|
|
1063
|
-
const logNodes = logLines.length
|
|
1064
|
-
? logLines.map((line, index) => create(Text, {key: `${line}-${index}`}, line))
|
|
1065
|
-
: [create(Text, {dimColor: true}, 'Logs will appear here once you run a command.')];
|
|
1066
|
-
|
|
1067
1126
|
const projectCountLabel = `${projects.length} project${projects.length === 1 ? '' : 's'}`;
|
|
1068
1127
|
const artTileNodes = useMemo(() => {
|
|
1069
1128
|
const selectedName = selectedProject?.name || 'Awaiting selection';
|
|
1070
1129
|
const selectedType = selectedProject?.type || 'Unknown stack';
|
|
1071
1130
|
const selectedLocation = selectedProject?.path ? path.relative(rootPath, selectedProject.path) || '.' : 'β';
|
|
1072
|
-
const
|
|
1073
|
-
const
|
|
1131
|
+
const statusNarrative = running ? 'Running commands' : lastAction ? `Last: ${lastAction}` : 'Idle gallery';
|
|
1132
|
+
const workspaceName = path.basename(rootPath) || rootPath;
|
|
1074
1133
|
const tileDefinition = [
|
|
1075
1134
|
{
|
|
1076
1135
|
label: 'Pulse',
|
|
1077
1136
|
detail: projectCountLabel,
|
|
1078
1137
|
accent: 'magenta',
|
|
1079
1138
|
icon: 'β',
|
|
1080
|
-
subtext: `Workspace Β· ${
|
|
1139
|
+
subtext: `Workspace Β· ${workspaceName}`
|
|
1081
1140
|
},
|
|
1082
1141
|
{
|
|
1083
1142
|
label: 'Focus',
|
|
@@ -1091,7 +1150,7 @@ function Compass({rootPath}) {
|
|
|
1091
1150
|
detail: `${detailCommands.length} commands`,
|
|
1092
1151
|
accent: 'yellow',
|
|
1093
1152
|
icon: 'β ',
|
|
1094
|
-
subtext:
|
|
1153
|
+
subtext: statusNarrative
|
|
1095
1154
|
}
|
|
1096
1155
|
];
|
|
1097
1156
|
return tileDefinition.map((tile) =>
|
|
@@ -1104,14 +1163,14 @@ function Compass({rootPath}) {
|
|
|
1104
1163
|
marginRight: 1,
|
|
1105
1164
|
borderStyle: 'single',
|
|
1106
1165
|
borderColor: tile.accent,
|
|
1107
|
-
minWidth:
|
|
1166
|
+
minWidth: 24
|
|
1108
1167
|
},
|
|
1109
1168
|
create(Text, {color: tile.accent, bold: true}, `${tile.icon} ${tile.label}`),
|
|
1110
1169
|
create(Text, {bold: true}, tile.detail),
|
|
1111
1170
|
create(Text, {dimColor: true}, tile.subtext)
|
|
1112
1171
|
)
|
|
1113
1172
|
);
|
|
1114
|
-
}, [projectCountLabel, rootPath, selectedProject
|
|
1173
|
+
}, [projectCountLabel, rootPath, selectedProject, detailCommands.length, running, lastAction]);
|
|
1115
1174
|
|
|
1116
1175
|
const artBoard = create(
|
|
1117
1176
|
Box,
|
|
@@ -1119,14 +1178,20 @@ function Compass({rootPath}) {
|
|
|
1119
1178
|
flexDirection: 'column',
|
|
1120
1179
|
marginTop: 1,
|
|
1121
1180
|
borderStyle: 'round',
|
|
1122
|
-
borderColor: '
|
|
1181
|
+
borderColor: 'gray',
|
|
1123
1182
|
padding: 1
|
|
1124
1183
|
},
|
|
1125
1184
|
create(
|
|
1126
1185
|
Box,
|
|
1127
1186
|
{flexDirection: 'row', justifyContent: 'space-between'},
|
|
1187
|
+
create(Text, {color: 'magenta', bold: true}, 'Art-coded build atlas'),
|
|
1188
|
+
create(Text, {dimColor: true}, 'press ? for overlay help')
|
|
1189
|
+
),
|
|
1190
|
+
create(
|
|
1191
|
+
Box,
|
|
1192
|
+
{flexDirection: 'row', marginTop: 1},
|
|
1128
1193
|
...ART_CHARS.map((char, index) =>
|
|
1129
|
-
create(Text, {key: `art
|
|
1194
|
+
create(Text, {key: `art-${index}`, color: ART_COLORS[index % ART_COLORS.length]}, char.repeat(2))
|
|
1130
1195
|
)
|
|
1131
1196
|
),
|
|
1132
1197
|
create(
|
|
@@ -1134,9 +1199,86 @@ function Compass({rootPath}) {
|
|
|
1134
1199
|
{flexDirection: 'row', marginTop: 1},
|
|
1135
1200
|
...artTileNodes
|
|
1136
1201
|
),
|
|
1137
|
-
create(Text, {dimColor: true, marginTop: 1}, kleur.italic('
|
|
1202
|
+
create(Text, {dimColor: true, marginTop: 1}, kleur.italic('The art board now follows your layout and stays inside the window.'))
|
|
1203
|
+
);
|
|
1204
|
+
|
|
1205
|
+
const logWindowStart = Math.max(0, logLines.length - OUTPUT_WINDOW_SIZE - logOffset);
|
|
1206
|
+
const logWindowEnd = Math.max(0, logLines.length - logOffset);
|
|
1207
|
+
const visibleLogs = logLines.slice(logWindowStart, logWindowEnd);
|
|
1208
|
+
const logNodes = visibleLogs.length
|
|
1209
|
+
? visibleLogs.map((line, index) => create(Text, {key: `${logWindowStart + index}-${line}`}, line))
|
|
1210
|
+
: [create(Text, {dimColor: true}, 'Logs will appear here once you run a command.')];
|
|
1211
|
+
|
|
1212
|
+
const helpCards = [
|
|
1213
|
+
{
|
|
1214
|
+
label: 'Navigation',
|
|
1215
|
+
color: 'magenta',
|
|
1216
|
+
body: [
|
|
1217
|
+
'β/β select projects, Enter toggles detail view',
|
|
1218
|
+
'Shift+β/β or mouse wheel scrolls logs',
|
|
1219
|
+
'? toggles this overlay, left click cycles the list'
|
|
1220
|
+
]
|
|
1221
|
+
},
|
|
1222
|
+
{
|
|
1223
|
+
label: 'Command flow',
|
|
1224
|
+
color: 'cyan',
|
|
1225
|
+
body: [
|
|
1226
|
+
'B/T/R run build/test/run for the focused project',
|
|
1227
|
+
'1-9 execute the numbered detail commands',
|
|
1228
|
+
'L reruns the last command, Ctrl+C aborts it',
|
|
1229
|
+
'Type while a command is running to feed stdin'
|
|
1230
|
+
]
|
|
1231
|
+
},
|
|
1232
|
+
{
|
|
1233
|
+
label: 'Recent runs',
|
|
1234
|
+
color: 'yellow',
|
|
1235
|
+
body: recentRuns.length
|
|
1236
|
+
? recentRuns.map((run) => `${run.time} Β· ${run.project}: ${run.command}`)
|
|
1237
|
+
: ['No runs yet. Start one with B/T/R.']
|
|
1238
|
+
}
|
|
1239
|
+
];
|
|
1240
|
+
|
|
1241
|
+
const helpSection = create(
|
|
1242
|
+
Box,
|
|
1243
|
+
{marginTop: 1, flexDirection: 'row', justifyContent: 'space-between'},
|
|
1244
|
+
...helpCards.map((card, index) =>
|
|
1245
|
+
create(
|
|
1246
|
+
Box,
|
|
1247
|
+
{
|
|
1248
|
+
key: card.label,
|
|
1249
|
+
flexGrow: 1,
|
|
1250
|
+
flexBasis: 0,
|
|
1251
|
+
marginRight: index < helpCards.length - 1 ? 1 : 0,
|
|
1252
|
+
borderStyle: 'round',
|
|
1253
|
+
borderColor: card.color,
|
|
1254
|
+
padding: 1
|
|
1255
|
+
},
|
|
1256
|
+
create(Text, {color: card.color, bold: true}, card.label),
|
|
1257
|
+
...card.body.map((line, lineIndex) =>
|
|
1258
|
+
create(Text, {key: `${card.label}-${lineIndex}`, dimColor: card.color === 'yellow'}, line)
|
|
1259
|
+
)
|
|
1260
|
+
)
|
|
1261
|
+
)
|
|
1138
1262
|
);
|
|
1139
1263
|
|
|
1264
|
+
const helpOverlay = showHelp
|
|
1265
|
+
? create(
|
|
1266
|
+
Box,
|
|
1267
|
+
{
|
|
1268
|
+
flexDirection: 'column',
|
|
1269
|
+
borderStyle: 'double',
|
|
1270
|
+
borderColor: 'cyan',
|
|
1271
|
+
marginTop: 1,
|
|
1272
|
+
padding: 1
|
|
1273
|
+
},
|
|
1274
|
+
create(Text, {color: 'cyan', bold: true}, 'Help overlay Β· press ? to hide'),
|
|
1275
|
+
create(Text, null, 'Clicking cycles selections and scrolling the mouse wheel moves the output view.'),
|
|
1276
|
+
create(Text, null, 'When a command is running you can type to feed stdin, Enter sends newline, Ctrl+C aborts.'),
|
|
1277
|
+
create(Text, null, 'Layout now keeps Projects and Details side-by-side with Output below so nothing stretches.'),
|
|
1278
|
+
create(Text, null, 'Use L to rerun the previous command and Shift + arrows or the mouse wheel to scroll the output window.')
|
|
1279
|
+
)
|
|
1280
|
+
: null;
|
|
1281
|
+
|
|
1140
1282
|
const headerHint = viewMode === 'detail'
|
|
1141
1283
|
? `Detail mode Β· 1-${Math.max(detailedIndexed.length, 1)} to execute, C: add custom commands, Enter: back to list, q: quit`
|
|
1142
1284
|
: `Quick run Β· B/T/R to build/test/run, Enter: view details, q: quit`;
|
|
@@ -1152,25 +1294,24 @@ function Compass({rootPath}) {
|
|
|
1152
1294
|
{flexDirection: 'column'},
|
|
1153
1295
|
create(Text, {color: 'magenta', bold: true}, 'Project Compass'),
|
|
1154
1296
|
create(Text, {dimColor: true}, loading ? 'Scanning workspacesβ¦' : `${projectCountLabel} detected in ${rootPath}`),
|
|
1155
|
-
create(Text, {
|
|
1297
|
+
create(Text, {dimColor: true}, 'Mouse support: click to cycle, wheel to scroll output.')
|
|
1156
1298
|
),
|
|
1157
1299
|
create(
|
|
1158
1300
|
Box,
|
|
1159
1301
|
{flexDirection: 'column', alignItems: 'flex-end'},
|
|
1160
|
-
create(Text, {color: running ? 'yellow' : 'green'}, running ? 'Busy
|
|
1302
|
+
create(Text, {color: running ? 'yellow' : 'green'}, running ? 'Busy streaming...' : lastAction ? `Last action Β· ${lastAction}` : 'Idle'),
|
|
1161
1303
|
create(Text, {dimColor: true}, headerHint)
|
|
1162
1304
|
)
|
|
1163
1305
|
),
|
|
1164
1306
|
artBoard,
|
|
1165
1307
|
create(
|
|
1166
1308
|
Box,
|
|
1167
|
-
{marginTop: 1},
|
|
1309
|
+
{marginTop: 1, flexDirection: 'row', alignItems: 'stretch'},
|
|
1168
1310
|
create(
|
|
1169
1311
|
Box,
|
|
1170
1312
|
{
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
marginRight: 2,
|
|
1313
|
+
flex: 1,
|
|
1314
|
+
marginRight: 1,
|
|
1174
1315
|
borderStyle: 'round',
|
|
1175
1316
|
borderColor: 'magenta',
|
|
1176
1317
|
padding: 1
|
|
@@ -1181,36 +1322,50 @@ function Compass({rootPath}) {
|
|
|
1181
1322
|
create(
|
|
1182
1323
|
Box,
|
|
1183
1324
|
{
|
|
1184
|
-
|
|
1185
|
-
width: 44,
|
|
1186
|
-
marginRight: 2,
|
|
1325
|
+
flex: 1,
|
|
1187
1326
|
borderStyle: 'round',
|
|
1188
1327
|
borderColor: 'cyan',
|
|
1189
1328
|
padding: 1
|
|
1190
1329
|
},
|
|
1191
1330
|
create(Text, {bold: true, color: 'cyan'}, 'Details'),
|
|
1192
1331
|
...detailContent
|
|
1332
|
+
)
|
|
1333
|
+
),
|
|
1334
|
+
create(
|
|
1335
|
+
Box,
|
|
1336
|
+
{marginTop: 1, flexDirection: 'column'},
|
|
1337
|
+
create(
|
|
1338
|
+
Box,
|
|
1339
|
+
{flexDirection: 'row', justifyContent: 'space-between'},
|
|
1340
|
+
create(Text, {bold: true, color: 'yellow'}, `Output ${running ? 'Β· Running' : ''}`),
|
|
1341
|
+
create(Text, {dimColor: true}, logOffset ? `Scrolled ${logOffset} lines` : 'Live log view')
|
|
1193
1342
|
),
|
|
1194
1343
|
create(
|
|
1195
1344
|
Box,
|
|
1196
1345
|
{
|
|
1197
1346
|
flexDirection: 'column',
|
|
1198
|
-
flexGrow: 1,
|
|
1199
1347
|
borderStyle: 'round',
|
|
1200
1348
|
borderColor: 'yellow',
|
|
1201
|
-
padding: 1
|
|
1349
|
+
padding: 1,
|
|
1350
|
+
minHeight: OUTPUT_WINDOW_SIZE + 2,
|
|
1351
|
+
overflow: 'hidden'
|
|
1202
1352
|
},
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1353
|
+
...logNodes
|
|
1354
|
+
),
|
|
1355
|
+
create(
|
|
1356
|
+
Text,
|
|
1357
|
+
{dimColor: true, marginTop: 1},
|
|
1358
|
+
running
|
|
1359
|
+
? 'Type to feed stdin, Enter submits, Ctrl+C aborts, Shift+Arrows or mouse wheel scrolls the buffer.'
|
|
1360
|
+
: 'Run a command or press ? for extra help.'
|
|
1209
1361
|
)
|
|
1210
|
-
)
|
|
1362
|
+
),
|
|
1363
|
+
helpSection,
|
|
1364
|
+
helpOverlay
|
|
1211
1365
|
);
|
|
1212
1366
|
}
|
|
1213
1367
|
|
|
1368
|
+
|
|
1214
1369
|
function parseArgs() {
|
|
1215
1370
|
const args = {};
|
|
1216
1371
|
const tokens = process.argv.slice(2);
|