project-compass 2.0.1 β†’ 2.1.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 CHANGED
@@ -1,18 +1,15 @@
1
- # Project Compass
1
+ # Project Compass (v2.1.0)
2
2
 
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.
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
 
5
5
  ## Highlights
6
6
 
7
- - πŸ” Scans directories for Node.js, Python, Rust, Go, Java, and Scala projects by looking at their manifest files.
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
- - πŸš€ 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 with an interactive stdin buffer, plus help tiles that stay in their own band; Shift+↑/↓ scrolls the log window, typing feeds stdin (buffer shows what you type, Enter submits, Ctrl+C aborts), Ctrl+L reruns the last command, Ctrl+H shows/hides the help cards, Ctrl+S opens the structure guide, and `?` pops an overlay with quick tips.
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.
12
- - 🧠 Modular detection now scans for Node, Python, Rust, Go, Java, Scala, PHP, Ruby, .NET, shell/Makefile projects plus custom workspaces, and surface setup hints (npm install, pip install, cargo fetch, etc.).
13
- - 🧠 Add bespoke commands via **C** in detail view and store them globally (`~/.project-compass/config.json`) so every workspace remembers your favorite invocations.
14
- - πŸ”Œ Extend detection via plugins (JSON specs under `~/.project-compass/plugins.json`) to teach Project Compass about extra frameworks or command sets.
15
- - πŸ“¦ Install globally and invoke `project-compass` from any folder to activate the UI instantly.
7
+ - πŸ” Scans directories for Node.js, Python, Rust, Go, Java, and Scala projects.
8
+ - 🎨 Futuristic layout with glyph-based art board and split Projects/Details rows.
9
+ - πŸš€ **New Keyboard-Centric UX**: Shortcuts now use **Shift** instead of Ctrl to avoid terminal interference.
10
+ - πŸ’‘ **Refined Output**: Improved stdin buffer with proper spacing and reliable scrolling (Shift+↑/↓).
11
+ - 🧠 **Smart Detection**: Support for 12+ frameworks with specialized build/run commands and setup hints.
12
+ - πŸ”Œ **Extensible**: Add custom commands with **C** and frameworks via `plugins.json`.
16
13
 
17
14
  ## Installation
18
15
 
@@ -26,83 +23,30 @@ npm install -g project-compass
26
23
  project-compass [--dir /path/to/workspace]
27
24
  ```
28
25
 
29
- ### Keyboard guide
26
+ ### Keyboard Guide
30
27
 
31
28
  | Key | Action |
32
29
  | --- | --- |
33
- | ↑ / ↓ | Move the project focus, Enter toggles the detail view |
34
- | B / T / R | Run the workspace build/test/run shortcuts |
35
- | 1‑9 | Execute the numbered commands listed in detail view |
36
- | C | Add a custom command (`label|cmd`) that saves to `~/.project-compass/config.json` |
37
- | Shift ↑ / ↓ | Scroll the output buffer |
38
- | Ctrl+L | Rerun the most recent command |
39
- | ? | Toggle the help overlay with navigation tips |
40
- | Ctrl+H | Show/hide the navigation/help cards beneath the logs |
41
- | Ctrl+S | Toggle the structure guide listing the manifests per language |
42
- | Ctrl+Q | Quit the app |
43
- | Typing while running | Sends stdin to the running command (buffer shows the text, Enter submits, Ctrl+C aborts) |
44
- | Ctrl+C | Interrupt a running command (works while streaming output) |
30
+ | ↑ / ↓ | Move focus, **Enter**: toggle details |
31
+ | B / T / R | Build / Test / Run |
32
+ | 1‑9 | Execute numbered detail commands |
33
+ | C | Add a custom command (`label|cmd`) |
34
+ | **Shift ↑ / ↓** | Scroll output buffer |
35
+ | **Shift+L** | Rerun last command |
36
+ | **Shift+H** | Toggle help cards |
37
+ | **Shift+S** | Toggle structure guide |
38
+ | **Shift+Q** | Quit app |
39
+ | ? | Toggle help overlay |
40
+ | Ctrl+C | Interrupt running command |
45
41
 
46
- ## Framework & plugin support
42
+ ## Layout & UX
47
43
 
48
- Project Compass detects a wide range of modern stacksβ€”**Next.js**, **React**, **Vue**, **NestJS**, **Angular**, **SvelteKit**, **Nuxt**, **Astro**, **Django**, **Flask**, **FastAPI**, and **Spring Boot**β€”and shows their badges in the detail view. When a framework is recognized, it injects framework-specific build/run/test commands (e.g., Next dev/build, Django runserver/test, Spring Boot run/test).
44
+ Project Compass features a split layout where Projects and Details stay paired while Output takes a full-width band. The stdin buffer (at the bottom) now has a clear distinction between the label and your input for better readability. The help cards (Shift+H) have been refactored for a cleaner, more readable look.
49
45
 
50
- You can teach it new frameworks by adding a `plugins.json` file in your config directory (`~/.project-compass/plugins.json`). Each entry can declare the languages, files, dependencies, and commands that identify the framework. A sample plugin entry looks like this:
46
+ ## Frameworks
51
47
 
52
- ```json
53
- {
54
- "plugins": [
55
- {
56
- "name": "Remix",
57
- "languages": ["Node.js"],
58
- "files": ["remix.config.js"],
59
- "dependencies": ["@remix-run/node"],
60
- "commands": {
61
- "run": "npm run dev",
62
- "build": "npm run build"
63
- }
64
- }
65
- ]
66
- }
67
- ```
68
-
69
- Each command value can be a string or an array of tokens. When a plugin matches a project, its commands appear in the detail view with a `framework` badge, and the shortcut keys (B/T/R or numeric) can execute them.
70
-
71
-
72
- ## Art board & detail view
73
-
74
- 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.
75
-
76
- ## Layout, output & help
77
-
78
- 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+↑/↓), feeds keystrokes into stdin while a command runs (type like normal, Enter submits, Ctrl+C aborts), and the buffer below shows exactly what you are typing. A trio of help tiles highlights navigation cues, command flow, and recent runs; they stay hidden by defaultβ€”press Ctrl+H to reveal the cards, Ctrl+S to show the structure guide that lists the manifest files for each language detection, `?` to open the overlay with extra tips, Ctrl+L to rerun the previous command, and Ctrl+Q to quit.
79
-
80
- ## Structure guide
81
-
82
- Press `S` to reveal the structure guide that lists which manifest files trigger each language detection (Node.js looks for `package.json`, Python looks for `pyproject.toml` or `requirements.txt`, Rust needs `Cargo.toml`, etc.). Use `H` to hide the help cards if you need every pixel for your output, then bring them back when you want a refresher.
83
-
84
- ## Detection & setup hints
85
-
86
- Project Compass now has a modular detection engine (`src/projectDetection.js`) that looks at manifests, frameworks, and optional plugins to identify what kind of project you are standing in. Every schema fills `extra.setupHints` (npm install, pip install, go mod tidy, etc.) and those hints show up under the detail view whenever a project needs bootstrapping.
87
-
88
- Try the sample Python project at `/mnt/ramdisk/daily_builds/test_python_proj` (includes `pyproject.toml`, `requirements.txt`, and `app.py`) so you can run it via Project Compass and confirm stdin/input behavior.
89
-
90
-
91
-
92
- ## Developer notes
93
-
94
- - `npm start` launches the Ink UI in the current directory.
95
- - `npm test` runs `node src/cli.js --mode test` to verify the scanner output.
96
- - Extend support for more languages by editing `SCHEMAS` or add plugin definitions under `~/.project-compass/plugins.json`.
97
- - Config lives at `~/.project-compass/config.json`. Drop custom commands there if you want to preseed them or share with teammates.
48
+ Detects **Next.js**, **React**, **Vue**, **NestJS**, **FastAPI**, **Django**, and more. Recognizes frameworks and injects specialized commands automatically.
98
49
 
99
50
  ## License
100
51
 
101
52
  MIT Β© 2026 Satyaa & Clawdy
102
-
103
- ## Release & packaging
104
-
105
- - Bump `package.json`/`package-lock.json` versions (e.g., `npm version 1.0.1 --no-git-tag-version`).
106
- - Run `npm run lint` and `npm run test` to validate the workspace before publishing.
107
- - Create the release artifact with `npm pack` (produces `project-compass-<version>.tgz` for uploading to GitHub Releases or npm).
108
- - Tag the repo `git tag v<version>` and push both commits and tags to publish the release.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-compass",
3
- "version": "2.0.1",
3
+ "version": "2.1.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",
Binary file
package/src/cli.js CHANGED
@@ -265,7 +265,7 @@ function Compass({rootPath}) {
265
265
  const normalizedInput = input?.toLowerCase();
266
266
  const ctrlCombo = (char) => key.ctrl && normalizedInput === char;
267
267
  const shiftCombo = (char) => key.shift && normalizedInput === char;
268
- const toggleShortcut = (char) => ctrlCombo(char) || shiftCombo(char);
268
+ const toggleShortcut = (char) => shiftCombo(char);
269
269
  if (toggleShortcut('h')) {
270
270
  setShowHelpCards((prev) => !prev);
271
271
  return;
@@ -306,11 +306,11 @@ function Compass({rootPath}) {
306
306
  }
307
307
 
308
308
  if (key.shift && key.upArrow) {
309
- scrollLogs(1);
309
+ scrollLogs(-1);
310
310
  return;
311
311
  }
312
312
  if (key.shift && key.downArrow) {
313
- scrollLogs(-1);
313
+ scrollLogs(1);
314
314
  return;
315
315
  }
316
316
 
@@ -318,7 +318,7 @@ function Compass({rootPath}) {
318
318
  setShowHelp((prev) => !prev);
319
319
  return;
320
320
  }
321
- if (ctrlCombo('l') && lastCommandRef.current) {
321
+ if (shiftCombo('l') && lastCommandRef.current) {
322
322
  runProjectCommand(lastCommandRef.current.commandMeta, lastCommandRef.current.project);
323
323
  return;
324
324
  }
@@ -338,7 +338,7 @@ function Compass({rootPath}) {
338
338
  setViewMode((prev) => (prev === 'detail' ? 'list' : 'detail'));
339
339
  return;
340
340
  }
341
- if (ctrlCombo('q')) {
341
+ if (shiftCombo('q')) {
342
342
  exit();
343
343
  return;
344
344
  }
@@ -525,19 +525,19 @@ const projectRows = [];
525
525
  color: 'magenta',
526
526
  body: [
527
527
  '↑ / ↓ move the project focus',
528
- 'Enter opens or closes details',
529
- 'Shift + ↑ / ↓ scrolls only the log buffer',
530
- 'Ctrl+H toggles cards, ? opens the overlay'
528
+ 'Enter toggles details view',
529
+ 'Shift+↑ / ↓ scroll output buffer',
530
+ 'Shift+H toggles help cards'
531
531
  ]
532
532
  },
533
533
  {
534
534
  label: 'Command flow',
535
535
  color: 'cyan',
536
536
  body: [
537
- 'B / T / R trigger build/test/run',
538
- '1-9 execute detail commands in order',
539
- 'Ctrl+L reruns the last command you launched',
540
- 'Ctrl+C aborts, typing feeds stdin (buffer mirrors it)'
537
+ 'B / T / R run build/test/run',
538
+ '1-9 execute detail commands',
539
+ 'Shift+L reruns last command',
540
+ 'Ctrl+C aborts; type feeds stdin'
541
541
  ]
542
542
  },
543
543
  {
@@ -545,8 +545,9 @@ const projectRows = [];
545
545
  color: 'yellow',
546
546
  body: [
547
547
  recentRuns.length ? `${recentRuns.length} runs recorded` : 'No runs yet Β· start with B/T/R',
548
- 'Ctrl+S shows the structure guide when unsure',
549
- 'Save custom commands with C β†’ label|cmd'
548
+ 'Shift+S toggles structure guide',
549
+ 'C save custom action',
550
+ 'Shift+Q quit application'
550
551
  ]
551
552
  }
552
553
  ];
@@ -566,16 +567,17 @@ const projectRows = [];
566
567
  marginBottom: 1,
567
568
  borderStyle: 'round',
568
569
  borderColor: card.color,
569
- padding: 1
570
+ padding: 1,
571
+ flexDirection: 'column'
570
572
  },
571
- create(Text, {color: card.color, bold: true}, card.label),
573
+ create(Text, {color: card.color, bold: true, marginBottom: 1}, card.label),
572
574
  ...card.body.map((line, lineIndex) =>
573
575
  create(Text, {key: `${card.label}-${lineIndex}`, dimColor: card.color === 'yellow'}, line)
574
576
  )
575
577
  )
576
578
  )
577
579
  )
578
- : create(Text, {dimColor: true, marginTop: 1}, 'Help cards hidden Β· press H to show navigation, command flow, and recent runs.');
580
+ : create(Text, {dimColor: true, marginTop: 1}, 'Help cards hidden Β· press Shift+H to show navigation, command flow, and recent runs.');
579
581
 
580
582
  const structureGuide = showStructureGuide
581
583
  ? create(
@@ -587,7 +589,7 @@ const projectRows = [];
587
589
  marginTop: 1,
588
590
  padding: 1
589
591
  },
590
- create(Text, {color: 'cyan', bold: true}, 'Project structure guide Β· press S to hide'),
592
+ create(Text, {color: 'cyan', bold: true}, 'Project structure guide Β· press Shift+S to hide'),
591
593
  ...SCHEMA_GUIDE.map((entry) =>
592
594
  create(Text, {key: entry.type, dimColor: true}, `β€’ ${entry.icon} ${entry.label}: ${entry.files.join(', ')}`)
593
595
  ),
@@ -606,18 +608,18 @@ const projectRows = [];
606
608
  padding: 1
607
609
  },
608
610
  create(Text, {color: 'cyan', bold: true}, 'Help overlay Β· press ? to hide'),
609
- create(Text, null, 'Shift+arrows scroll the output buffer.'),
610
- create(Text, null, 'Run commands and type to feed stdin (buffer mirrors your keystrokes, Enter submits, Ctrl+C aborts).'),
611
- create(Text, null, 'Ctrl+H toggles the navigation cards, Ctrl+S shows structure tips, Ctrl+L reruns the previous command, Ctrl+Q quits.'),
612
- create(Text, null, 'Projects + Details stay side-by-side while Output and the log buffer stay fixed in their own band.'),
613
- create(Text, null, 'Structure guide lists the manifests that trigger each language detection (press Ctrl+S to toggle).')
611
+ create(Text, null, 'Shift+↑/↓ scrolls the log buffer while commands stream; type to feed stdin (Enter submits, Ctrl+C aborts).'),
612
+ create(Text, null, 'B/T/R run build/test/run; 1-9 executes detail commands; Ctrl+L reruns the previous command.'),
613
+ create(Text, null, 'Ctrl+H toggles these help cards, Ctrl+S toggles the structure guide, ? toggles this overlay, Ctrl+Q quits.'),
614
+ create(Text, null, 'Projects + Details stay paired while Output keeps its own full-width band.'),
615
+ create(Text, null, 'Structure guide lists the manifests that trigger each language detection (Ctrl+S to toggle).')
614
616
  )
615
617
  : null;
616
618
 
617
- const toggleHint = showHelpCards ? 'Ctrl+H hides the help cards' : 'Ctrl+H shows the help cards';
619
+ const toggleHint = showHelpCards ? 'Shift+H hides the help cards' : 'Shift+H shows the help cards';
618
620
  const headerHint = viewMode === 'detail'
619
- ? `Detail mode Β· 1-${Math.max(detailedIndexed.length, 1)} to execute, C: add custom commands, Enter: back to list, Ctrl+Q: quit Β· ${toggleHint}, Ctrl+S shows structure guide`
620
- : `Quick run Β· B/T/R to build/test/run, Enter: view details, Ctrl+Q: quit Β· ${toggleHint}, Ctrl+S shows structure guide`;
621
+ ? `Detail mode Β· 1-${Math.max(detailedIndexed.length, 1)} to execute, C: add custom commands, Enter: back to list, Shift+Q: quit Β· ${toggleHint}, Shift+S toggles structure guide`
622
+ : `Quick run Β· B/T/R to build/test/run, Enter: view details, Shift+Q: quit Β· ${toggleHint}, Shift+S toggles structure guide`;
621
623
 
622
624
  return create(
623
625
  Box,
@@ -702,7 +704,7 @@ const projectRows = [];
702
704
  Box,
703
705
  {marginTop: 1, flexDirection: 'row', justifyContent: 'space-between'},
704
706
  create(Text, {dimColor: true}, running ? 'Type to feed stdin; Enter submits, Ctrl+C aborts.' : 'Run a command or press ? for extra help.'),
705
- create(Text, {dimColor: true}, `${toggleHint}, S toggles the structure guide`)
707
+ create(Text, {dimColor: true}, `${toggleHint}, Shift+S toggles the structure guide`)
706
708
  ),
707
709
  create(
708
710
  Box,
@@ -711,9 +713,9 @@ const projectRows = [];
711
713
  flexDirection: 'row',
712
714
  borderStyle: 'round',
713
715
  borderColor: running ? 'green' : 'gray',
714
- padding: 1
716
+ paddingX: 1
715
717
  },
716
- create(Text, {bold: true, color: running ? 'green' : 'white'}, running ? 'Stdin buffer' : 'Input ready'),
718
+ create(Text, {bold: true, color: running ? 'green' : 'white'}, running ? ' Stdin buffer ' : ' Input ready '),
717
719
  create(Text, {dimColor: true, marginLeft: 1}, running ? (stdinBuffer || '(type to send)') : 'Start a command to feed stdin')
718
720
  )
719
721
  ),
@@ -5,6 +5,12 @@ import {ensureConfigDir, PLUGIN_FILE} from './configPaths.js';
5
5
 
6
6
  const IGNORE_PATTERNS = ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**', '**/target/**'];
7
7
 
8
+ const PYTHON_ENTRY_FILES = ['main.py', 'app.py', 'src/main.py', 'src/app.py'];
9
+
10
+ function findPythonEntry(projectPath) {
11
+ return PYTHON_ENTRY_FILES.find((file) => hasProjectFile(projectPath, file)) || null;
12
+ }
13
+
8
14
  function hasProjectFile(projectPath, file) {
9
15
  return fs.existsSync(path.join(projectPath, file));
10
16
  }
@@ -316,10 +322,14 @@ const builtInFrameworks = [
316
322
  languages: ['Python'],
317
323
  priority: 105,
318
324
  match(project) {
319
- return dependencyMatches(project, 'flask') || hasProjectFile(project.path, 'app.py');
325
+ const entry = findPythonEntry(project.path);
326
+ return Boolean(entry && dependencyMatches(project, 'flask'));
320
327
  },
321
328
  commands(project) {
322
- const entry = hasProjectFile(project.path, 'app.py') ? 'app.py' : 'main.py';
329
+ const entry = findPythonEntry(project.path);
330
+ if (!entry) {
331
+ return {};
332
+ }
323
333
  return {
324
334
  run: {label: 'Flask app', command: ['python', entry], source: 'framework'},
325
335
  test: {label: 'Pytest', command: ['pytest'], source: 'framework'}
@@ -334,12 +344,17 @@ const builtInFrameworks = [
334
344
  languages: ['Python'],
335
345
  priority: 105,
336
346
  match(project) {
337
- return dependencyMatches(project, 'fastapi');
347
+ const entry = findPythonEntry(project.path);
348
+ return Boolean(entry && dependencyMatches(project, 'fastapi'));
338
349
  },
339
350
  commands(project) {
340
- const entry = hasProjectFile(project.path, 'main.py') ? 'main.py' : 'app.py';
351
+ const entry = findPythonEntry(project.path);
352
+ if (!entry) {
353
+ return {};
354
+ }
355
+ const moduleName = entry.split('.').slice(0, -1).join('.') || entry;
341
356
  return {
342
- run: {label: 'Uvicorn reload', command: ['uvicorn', `${entry.split('.')[0]}:app`, '--reload'], source: 'framework'},
357
+ run: {label: 'Uvicorn reload', command: ['uvicorn', `${moduleName}:app`, '--reload'], source: 'framework'},
343
358
  test: {label: 'Pytest', command: ['pytest'], source: 'framework'}
344
359
  };
345
360
  }
@@ -458,7 +473,7 @@ class SchemaRegistry {
458
473
  commands.test = {label: 'Unittest', command: ['python', '-m', 'unittest', 'discover']};
459
474
  }
460
475
 
461
- const entry = this.findPythonEntry(projectPath);
476
+ const entry = findPythonEntry(projectPath);
462
477
  if (entry) {
463
478
  commands.run = {label: 'Run', command: ['python', entry]};
464
479
  }
@@ -492,15 +507,6 @@ class SchemaRegistry {
492
507
  }
493
508
  };
494
509
  },
495
- findPythonEntry(projectPath) {
496
- const candidates = ['main.py', 'app.py', 'src/main.py', 'src/app.py'];
497
- for (const candidate of candidates) {
498
- if (hasProjectFile(projectPath, candidate)) {
499
- return candidate;
500
- }
501
- }
502
- return null;
503
- }
504
510
  },
505
511
  {
506
512
  type: 'rust',