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 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
- - ✨ Presents a modern Ink dashboard with an interactive project list, icons, and live stdout/stderr logs.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-compass",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Ink-based project explorer that detects local repos and lets you build/test/run them without memorizing commands.",
5
5
  "main": "src/cli.js",
6
6
  "type": "module",
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) => [...prev.slice(-200), typeof line === 'string' ? line : JSON.stringify(line)]);
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
- if (!selectedProject) {
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(`${selectedProject.name} Β· ${commandMeta.label}`);
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: selectedProject.path,
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(`βœ“ ${commandMeta.label} finished`));
898
+ addLog(kleur.green(`βœ“ ${commandLabel} finished`));
879
899
  } catch (error) {
880
- addLog(kleur.red(`βœ— ${commandMeta.label} failed: ${error.shortMessage || error.message}`));
900
+ addLog(kleur.red(`βœ— ${commandLabel} failed: ${error.shortMessage || error.message}`));
881
901
  } finally {
882
902
  setRunning(false);
903
+ runningProcessRef.current = null;
883
904
  }
884
- }, [selectedProject, addLog, running]);
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.upArrow && projects.length > 0) {
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 for projects…'));
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
- Box,
1008
- {flexDirection: 'row'},
1009
- create(
1010
- Text,
1011
- {
1012
- color: isSelected ? 'cyan' : 'white',
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
- frameworkBadges && create(Text, {color: isSelected ? 'cyan' : 'gray'}, ` ${frameworkBadges}`)
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: 'yellow', bold: true}, `${selectedProject.icon} ${selectedProject.name}`),
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: `${command.shortcut}-${command.label}`}, `${command.shortcut}. ${command.label} ${command.source === 'custom' ? kleur.magenta('(custom)') : command.source === 'framework' ? kleur.cyan('(framework)') : ''}`)
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 motionNarrative = running ? 'Executing...' : lastAction ? `Last: ${lastAction}` : 'Idle in palette';
1073
- const rootName = path.basename(rootPath) || rootPath;
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 Β· ${rootName}`
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: motionNarrative
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: 22
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?.name, selectedProject?.type, selectedProject?.path, detailCommands.length, running, lastAction]);
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: 'white',
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-char-${index}`, color: ART_COLORS[index % ART_COLORS.length]}, char.repeat(2))
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('Terminal art mosaic Β· vibe meets productivity'))
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, {color: 'cyan'}, kleur.italic('Art-coded build atlas'))
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 πŸ”' : lastAction ? `Last: ${lastAction}` : 'Idle'),
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
- flexDirection: 'column',
1172
- width: 60,
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
- flexDirection: 'column',
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
- create(Text, {bold: true, color: 'yellow'}, 'Output'),
1204
- create(
1205
- Box,
1206
- {flexDirection: 'column', marginTop: 1, height: 12, overflow: 'hidden'},
1207
- ...logNodes
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);