project-compass 2.5.1 โ†’ 2.7.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.
Files changed (3) hide show
  1. package/README.md +4 -2
  2. package/package.json +1 -1
  3. package/src/cli.js +123 -22
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Project Compass (v2.5.0)
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 โ†‘ / โ†“** | Scroll output buffer |
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-compass",
3
- "version": "2.5.1",
3
+ "version": "2.7.0",
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",
package/src/cli.js CHANGED
@@ -152,6 +152,27 @@ function Studio() {
152
152
  );
153
153
  }
154
154
 
155
+ function CursorText({value, cursorIndex, active = true}) {
156
+ const [visible, setVisible] = useState(true);
157
+ useEffect(() => {
158
+ if (!active) return;
159
+ const interval = setInterval(() => setVisible(v => !v), 500);
160
+ return () => clearInterval(interval);
161
+ }, [active]);
162
+
163
+ const before = value.slice(0, cursorIndex);
164
+ const charAt = value[cursorIndex] || ' ';
165
+ const after = value.slice(cursorIndex + 1);
166
+
167
+ return create(
168
+ Text,
169
+ null,
170
+ before,
171
+ active && visible ? create(Text, {backgroundColor: 'white', color: 'black'}, charAt) : charAt,
172
+ after
173
+ );
174
+ }
175
+
155
176
  function Compass({rootPath, initialView = 'navigator'}) {
156
177
  const {exit} = useApp();
157
178
  const {projects, loading, error} = useScanner(rootPath);
@@ -164,10 +185,12 @@ function Compass({rootPath, initialView = 'navigator'}) {
164
185
  const [lastAction, setLastAction] = useState(null);
165
186
  const [customMode, setCustomMode] = useState(false);
166
187
  const [customInput, setCustomInput] = useState('');
188
+ const [customCursor, setCustomCursor] = useState(0);
167
189
  const [config, setConfig] = useState(() => loadConfig());
168
190
  const [showHelpCards, setShowHelpCards] = useState(false);
169
191
  const [showStructureGuide, setShowStructureGuide] = useState(false);
170
192
  const [stdinBuffer, setStdinBuffer] = useState('');
193
+ const [stdinCursor, setStdinCursor] = useState(0);
171
194
  const [showHelp, setShowHelp] = useState(false);
172
195
  const [recentRuns, setRecentRuns] = useState([]);
173
196
  const selectedProject = projects[selectedIndex] || null;
@@ -177,9 +200,9 @@ function Compass({rootPath, initialView = 'navigator'}) {
177
200
  const addLog = useCallback((line) => {
178
201
  setLogLines((prev) => {
179
202
  const normalized = typeof line === 'string' ? line : JSON.stringify(line);
180
- const appended = [...prev, normalized];
181
- const next = appended.length > 500 ? appended.slice(appended.length - 500) : appended;
182
- return next;
203
+ const lines = normalized.split(/\r?\n/).filter(l => l.trim().length > 0);
204
+ const appended = [...prev, ...lines];
205
+ return appended.length > 500 ? appended.slice(appended.length - 500) : appended;
183
206
  });
184
207
  }, []);
185
208
 
@@ -228,10 +251,10 @@ function Compass({rootPath, initialView = 'navigator'}) {
228
251
  runningProcessRef.current = subprocess;
229
252
 
230
253
  subprocess.stdout?.on('data', (chunk) => {
231
- addLog(chunk.toString().trimEnd());
254
+ addLog(chunk.toString());
232
255
  });
233
256
  subprocess.stderr?.on('data', (chunk) => {
234
- addLog(kleur.red(chunk.toString().trimEnd()));
257
+ addLog(kleur.red(chunk.toString()));
235
258
  });
236
259
 
237
260
  await subprocess;
@@ -241,6 +264,7 @@ function Compass({rootPath, initialView = 'navigator'}) {
241
264
  } finally {
242
265
  setRunning(false);
243
266
  setStdinBuffer('');
267
+ setStdinCursor(0);
244
268
  runningProcessRef.current = null;
245
269
  }
246
270
  }, [addLog, running, selectedProject]);
@@ -276,6 +300,7 @@ function Compass({rootPath, initialView = 'navigator'}) {
276
300
  addLog(kleur.gray('Canceled custom command (empty).'));
277
301
  setCustomMode(false);
278
302
  setCustomInput('');
303
+ setCustomCursor(0);
279
304
  return;
280
305
  }
281
306
  const [labelPart, commandPart] = raw.split('|');
@@ -284,14 +309,29 @@ function Compass({rootPath, initialView = 'navigator'}) {
284
309
  addLog(kleur.red('Custom command needs at least one token.'));
285
310
  setCustomMode(false);
286
311
  setCustomInput('');
312
+ setCustomCursor(0);
287
313
  return;
288
314
  }
289
315
  const label = commandPart ? labelPart.trim() : `Custom ${selectedProject.name}`;
290
316
  handleAddCustomCommand(label || 'Custom', commandTokens);
291
317
  setCustomMode(false);
292
318
  setCustomInput('');
319
+ setCustomCursor(0);
293
320
  }, [customInput, selectedProject, handleAddCustomCommand, addLog]);
294
321
 
322
+ const exportLogs = useCallback(() => {
323
+ if (!logLines.length) {
324
+ return;
325
+ }
326
+ try {
327
+ const exportPath = path.resolve(process.cwd(), `compass-logs-${Date.now()}.txt`);
328
+ fs.writeFileSync(exportPath, logLines.join('\n'));
329
+ addLog(kleur.green(`โœ“ Logs exported to ${exportPath}`));
330
+ } catch (err) {
331
+ addLog(kleur.red(`โœ— Export failed: ${err.message}`));
332
+ }
333
+ }, [logLines, addLog]);
334
+
295
335
  useInput((input, key) => {
296
336
  if (customMode) {
297
337
  if (key.return) {
@@ -301,14 +341,27 @@ function Compass({rootPath, initialView = 'navigator'}) {
301
341
  if (key.escape) {
302
342
  setCustomMode(false);
303
343
  setCustomInput('');
344
+ setCustomCursor(0);
345
+ return;
346
+ }
347
+ if (key.backspace || key.delete) {
348
+ if (customCursor > 0) {
349
+ setCustomInput((prev) => prev.slice(0, customCursor - 1) + prev.slice(customCursor));
350
+ setCustomCursor(c => Math.max(0, c - 1));
351
+ }
304
352
  return;
305
353
  }
306
- if (key.backspace) {
307
- setCustomInput((prev) => prev.slice(0, -1));
354
+ if (key.leftArrow) {
355
+ setCustomCursor(c => Math.max(0, c - 1));
356
+ return;
357
+ }
358
+ if (key.rightArrow) {
359
+ setCustomCursor(c => Math.min(customInput.length, c + 1));
308
360
  return;
309
361
  }
310
362
  if (input) {
311
- setCustomInput((prev) => prev + input);
363
+ setCustomInput((prev) => prev.slice(0, customCursor) + input + prev.slice(customCursor));
364
+ setCustomCursor(c => c + input.length);
312
365
  }
313
366
  return;
314
367
  }
@@ -334,6 +387,10 @@ function Compass({rootPath, initialView = 'navigator'}) {
334
387
  setLogOffset(0);
335
388
  return;
336
389
  }
390
+ if (shiftCombo('e')) {
391
+ exportLogs();
392
+ return;
393
+ }
337
394
 
338
395
  const scrollLogs = (delta) => {
339
396
  setLogOffset((prev) => {
@@ -346,21 +403,33 @@ function Compass({rootPath, initialView = 'navigator'}) {
346
403
  if (key.ctrl && input === 'c') {
347
404
  runningProcessRef.current.kill('SIGINT');
348
405
  setStdinBuffer('');
406
+ setStdinCursor(0);
349
407
  return;
350
408
  }
351
409
  if (key.return) {
352
- runningProcessRef.current.stdin?.write('\n');
410
+ runningProcessRef.current.stdin?.write(stdinBuffer + '\n');
353
411
  setStdinBuffer('');
412
+ setStdinCursor(0);
413
+ return;
414
+ }
415
+ if (key.backspace || key.delete) {
416
+ if (stdinCursor > 0) {
417
+ setStdinBuffer(prev => prev.slice(0, stdinCursor - 1) + prev.slice(stdinCursor));
418
+ setStdinCursor(c => Math.max(0, c - 1));
419
+ }
354
420
  return;
355
421
  }
356
- if (key.backspace) {
357
- runningProcessRef.current.stdin?.write('\x08');
358
- setStdinBuffer((prev) => prev.slice(0, -1));
422
+ if (key.leftArrow) {
423
+ setStdinCursor(c => Math.max(0, c - 1));
424
+ return;
425
+ }
426
+ if (key.rightArrow) {
427
+ setStdinCursor(c => Math.min(stdinBuffer.length, c + 1));
359
428
  return;
360
429
  }
361
430
  if (input) {
362
- runningProcessRef.current.stdin?.write(input);
363
- setStdinBuffer((prev) => prev + input);
431
+ setStdinBuffer(prev => prev.slice(0, stdinCursor) + input + prev.slice(stdinCursor));
432
+ setStdinCursor(c => c + input.length);
364
433
  }
365
434
  return;
366
435
  }
@@ -405,6 +474,7 @@ function Compass({rootPath, initialView = 'navigator'}) {
405
474
  if (shiftCombo('c') && viewMode === 'detail' && selectedProject) {
406
475
  setCustomMode(true);
407
476
  setCustomInput('');
477
+ setCustomCursor(0);
408
478
  return;
409
479
  }
410
480
  const actionKey = normalizedInput && ACTION_MAP[normalizedInput];
@@ -512,7 +582,14 @@ function Compass({rootPath, initialView = 'navigator'}) {
512
582
  }
513
583
 
514
584
  if (customMode) {
515
- detailContent.push(create(Text, {key: 'custom-input', color: 'cyan'}, `Type label|cmd (Enter to save, Esc to cancel): ${customInput}`));
585
+ detailContent.push(
586
+ create(
587
+ Box,
588
+ {key: 'custom-input-box', flexDirection: 'row'},
589
+ create(Text, {color: 'cyan'}, 'Type label|cmd (Enter: save, Esc: cancel): '),
590
+ create(CursorText, {value: customInput, cursorIndex: customCursor})
591
+ )
592
+ );
516
593
  }
517
594
 
518
595
  const artTileNodes = useMemo(() => {
@@ -618,7 +695,7 @@ function Compass({rootPath, initialView = 'navigator'}) {
618
695
  'B / T / R build/test/run',
619
696
  '1-9 run detail commands',
620
697
  'Shift+L rerun last command',
621
- 'Shift+X clear output logs'
698
+ 'Shift+X clear / Shift+E export'
622
699
  ]
623
700
  },
624
701
  {
@@ -689,9 +766,9 @@ function Compass({rootPath, initialView = 'navigator'}) {
689
766
  padding: 1
690
767
  },
691
768
  create(Text, {color: 'cyan', bold: true}, 'Help overlay ยท press ? to hide'),
692
- create(Text, null, 'Shift+โ†‘/โ†“ scrolls the log buffer; Shift+X clears logs; Shift+A opens Omni-Studio.'),
693
- create(Text, null, 'B/T/R run build/test/run; 1-9 executes detail commands; Shift+L reruns the previous command.'),
694
- create(Text, null, 'Shift+H toggles help cards, Shift+S structure guide, ? overlay, Shift+Q quits.'),
769
+ create(Text, null, 'Shift+โ†‘/โ†“ scrolls logs; Shift+X clears; Shift+E exports to file; Shift+A Omni-Studio.'),
770
+ create(Text, null, 'B/T/R run build/test/run; 1-9 detail commands; Shift+L reruns previous command.'),
771
+ create(Text, null, 'Shift+H help cards, Shift+S structure guide, ? overlay, Shift+Q quits.'),
695
772
  create(Text, null, 'Projects + Details stay paired while Output keeps its own full-width band.'),
696
773
  create(Text, null, 'Structure guide lists the manifests that trigger each language detection.')
697
774
  )
@@ -801,7 +878,7 @@ function Compass({rootPath, initialView = 'navigator'}) {
801
878
  paddingX: 1
802
879
  },
803
880
  create(Text, {bold: true, color: running ? 'green' : 'white'}, running ? ' Stdin buffer ' : ' Input ready '),
804
- create(Text, {dimColor: true, marginLeft: 1}, running ? (stdinBuffer || '(type to send)') : 'Start a command to feed stdin')
881
+ create(Box, {marginLeft: 1}, create(CursorText, {value: stdinBuffer || (running ? '' : 'Start a command to feed stdin'), cursorIndex: stdinCursor, active: running}))
805
882
  )
806
883
  ),
807
884
  helpSection,
@@ -834,8 +911,32 @@ function parseArgs() {
834
911
  async function main() {
835
912
  const args = parseArgs();
836
913
  if (args.help) {
837
- console.log('Project Compass ยท Ink project runner');
838
- console.log('Usage: project-compass [--dir <path>] [--mode test] [--studio]');
914
+ console.log(kleur.cyan('Project Compass ยท Ink project navigator/runner'));
915
+ console.log('');
916
+ console.log(kleur.bold('Usage:'));
917
+ console.log(' project-compass [--dir <path>] [--studio]');
918
+ console.log('');
919
+ console.log(kleur.bold('Arguments:'));
920
+ console.log(' --dir, --path <path> Specify root workspace directory to scan');
921
+ console.log(' --studio Launch directly into Omni-Studio mode');
922
+ console.log(' --help, -h Show this help menu');
923
+ console.log('');
924
+ console.log(kleur.bold('Core Keybinds:'));
925
+ console.log(' โ†‘ / โ†“ Move project focus');
926
+ console.log(' Enter Toggle detail view for selected project');
927
+ console.log(' Shift+A Switch to Omni-Studio (Environment Health)');
928
+ console.log(' Shift+X Clear the output log buffer');
929
+ console.log(' Shift+E Export current logs to a .txt file');
930
+ console.log(' Shift+โ†‘ / โ†“ Scroll the output logs back/forward');
931
+ console.log(' Shift+Q Quit application');
932
+ console.log('');
933
+ console.log(kleur.bold('Execution shortcuts:'));
934
+ console.log(' B / T / R Quick run: Build / Test / Run');
935
+ console.log(' 1-9 Run numbered commands in detail view');
936
+ console.log(' Shift+L Rerun the last executed command');
937
+ console.log(' Shift+C Add a custom command (in detail view)');
938
+ console.log('');
939
+ console.log(kleur.dim('Documentation: https://github.com/CrimsonDevil333333/project-compass'));
839
940
  return;
840
941
  }
841
942
  const rootPath = args.root ? path.resolve(args.root) : process.cwd();