project-compass 2.1.0 โ†’ 2.3.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,15 +1,15 @@
1
- # Project Compass (v2.1.0)
1
+ # Project Compass (v2.3.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
 
5
5
  ## Highlights
6
6
 
7
- - ๐Ÿ” Scans directories for Node.js, Python, Rust, Go, Java, and Scala projects.
7
+ - ๐Ÿ” Scans directories for Node.js, Python, Rust, Go, Java, Scala, PHP, Ruby, and .NET projects.
8
8
  - ๐ŸŽจ Futuristic layout with glyph-based art board and split Projects/Details rows.
9
9
  - ๐Ÿš€ **New Keyboard-Centric UX**: Shortcuts now use **Shift** instead of Ctrl to avoid terminal interference.
10
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`.
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
+ - ๐Ÿ”Œ **Extensible**: Add custom commands with **Shift+C** and frameworks via `plugins.json`.
13
13
 
14
14
  ## Installation
15
15
 
@@ -30,7 +30,7 @@ project-compass [--dir /path/to/workspace]
30
30
  | โ†‘ / โ†“ | Move focus, **Enter**: toggle details |
31
31
  | B / T / R | Build / Test / Run |
32
32
  | 1โ€‘9 | Execute numbered detail commands |
33
- | C | Add a custom command (`label|cmd`) |
33
+ | **Shift+C** | Add a custom command (`label|cmd`) |
34
34
  | **Shift โ†‘ / โ†“** | Scroll output buffer |
35
35
  | **Shift+L** | Rerun last command |
36
36
  | **Shift+H** | Toggle help cards |
@@ -45,7 +45,13 @@ Project Compass features a split layout where Projects and Details stay paired w
45
45
 
46
46
  ## Frameworks
47
47
 
48
- Detects **Next.js**, **React**, **Vue**, **NestJS**, **FastAPI**, **Django**, and more. Recognizes frameworks and injects specialized commands automatically.
48
+ Supports a wide array of modern stacks:
49
+ - **Node.js**: Next.js, React, Vue, NestJS, Vite, Prisma, Tailwind
50
+ - **Python**: FastAPI, Django, Flask
51
+ - **Java/Kotlin**: Spring Boot (Maven & Gradle)
52
+ - **Rust**: Rocket, Actix Web
53
+ - **.NET**: ASP.NET Core
54
+ - **PHP**: Laravel
49
55
 
50
56
  ## License
51
57
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-compass",
3
- "version": "2.1.0",
3
+ "version": "2.3.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
@@ -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
 
@@ -342,7 +342,7 @@ function Compass({rootPath}) {
342
342
  exit();
343
343
  return;
344
344
  }
345
- if (normalizedInput === 'c' && viewMode === 'detail' && selectedProject) {
345
+ if (shiftCombo('c') && viewMode === 'detail' && selectedProject) {
346
346
  setCustomMode(true);
347
347
  setCustomInput('');
348
348
  return;
@@ -412,19 +412,19 @@ const projectRows = [];
412
412
  detailContent.push(create(Text, {bold: true, marginTop: 1}, 'Commands'));
413
413
  detailedIndexed.forEach((command) => {
414
414
  detailContent.push(
415
- create(Text, {key: `detail-${command.shortcut}-${command.label}`}, `${command.shortcut}. ${command.label} ${command.source === 'custom' ? kleur.magenta('(custom)') : command.source === 'framework' ? kleur.cyan('(framework)') : ''}`)
415
+ 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)') : ''}`)
416
416
  );
417
417
  detailContent.push(create(Text, {dimColor: true}, ` โ†ณ ${command.command.join(' ')}`));
418
418
  });
419
419
  if (!detailedIndexed.length) {
420
- detailContent.push(create(Text, {dimColor: true}, 'No built-in commands yet. Add a custom command with C.'));
420
+ detailContent.push(create(Text, {dimColor: true}, 'No built-in commands yet. Add a custom command with Shift+C.'));
421
421
  }
422
422
  const setupHints = selectedProject.extra?.setupHints || [];
423
423
  if (setupHints.length) {
424
424
  detailContent.push(create(Text, {dimColor: true, marginTop: 1}, 'Setup hints:'));
425
425
  setupHints.forEach((hint) => detailContent.push(create(Text, {dimColor: true}, ` โ€ข ${hint}`)));
426
426
  }
427
- detailContent.push(create(Text, {dimColor: true}, 'Press C โ†’ label|cmd to save custom actions, Enter to close detail view.'));
427
+ detailContent.push(create(Text, {dimColor: true}, 'Press Shift+C โ†’ label|cmd to save custom actions, Enter to close detail view.'));
428
428
  } else {
429
429
  detailContent.push(create(Text, {dimColor: true}, 'Press Enter on a project to reveal details (icons, commands, frameworks, custom actions).'));
430
430
  }
@@ -546,7 +546,7 @@ const projectRows = [];
546
546
  body: [
547
547
  recentRuns.length ? `${recentRuns.length} runs recorded` : 'No runs yet ยท start with B/T/R',
548
548
  'Shift+S toggles structure guide',
549
- 'C save custom action',
549
+ 'Shift+C save custom action',
550
550
  'Shift+Q quit application'
551
551
  ]
552
552
  }
@@ -618,7 +618,7 @@ const projectRows = [];
618
618
 
619
619
  const toggleHint = showHelpCards ? 'Shift+H hides the help cards' : 'Shift+H shows the help cards';
620
620
  const headerHint = viewMode === 'detail'
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`
621
+ ? `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`
622
622
  : `Quick run ยท B/T/R to build/test/run, Enter: view details, Shift+Q: quit ยท ${toggleHint}, Shift+S toggles structure guide`;
623
623
 
624
624
  return create(
@@ -323,7 +323,7 @@ const builtInFrameworks = [
323
323
  priority: 105,
324
324
  match(project) {
325
325
  const entry = findPythonEntry(project.path);
326
- return Boolean(entry && dependencyMatches(project, 'flask'));
326
+ return Boolean(entry && (dependencyMatches(project, 'flask') || dependencyMatches(project, 'flask-restful') || dependencyMatches(project, 'flask-cors')));
327
327
  },
328
328
  commands(project) {
329
329
  const entry = findPythonEntry(project.path);
@@ -345,7 +345,7 @@ const builtInFrameworks = [
345
345
  priority: 105,
346
346
  match(project) {
347
347
  const entry = findPythonEntry(project.path);
348
- return Boolean(entry && dependencyMatches(project, 'fastapi'));
348
+ return Boolean(entry && (dependencyMatches(project, 'fastapi') || dependencyMatches(project, 'pydantic') || dependencyMatches(project, 'uvicorn')));
349
349
  },
350
350
  commands(project) {
351
351
  const entry = findPythonEntry(project.path);
@@ -359,18 +359,82 @@ const builtInFrameworks = [
359
359
  };
360
360
  }
361
361
  },
362
+ {
363
+ id: 'vite',
364
+ name: 'Vite',
365
+ icon: 'โšก',
366
+ description: 'Vite-powered frontend',
367
+ languages: ['Node.js'],
368
+ priority: 100,
369
+ match(project) {
370
+ return hasProjectFile(project.path, 'vite.config.js') || hasProjectFile(project.path, 'vite.config.ts') || dependencyMatches(project, 'vite');
371
+ },
372
+ commands(project) {
373
+ const commands = {};
374
+ const add = (key, label, fallback) => {
375
+ const tokens = resolveScriptCommand(project, key, fallback);
376
+ if (tokens) {
377
+ commands[key] = {label, command: tokens, source: 'framework'};
378
+ }
379
+ };
380
+ add('run', 'Vite dev', () => ['npx', 'vite']);
381
+ add('build', 'Vite build', () => ['npx', 'vite', 'build']);
382
+ add('preview', 'Vite preview', () => ['npx', 'vite', 'preview']);
383
+ return commands;
384
+ }
385
+ },
386
+ {
387
+ id: 'tailwind',
388
+ name: 'Tailwind CSS',
389
+ icon: '๐ŸŽจ',
390
+ description: 'Tailwind utility-first CSS',
391
+ languages: ['Node.js'],
392
+ priority: 50,
393
+ match(project) {
394
+ return hasProjectFile(project.path, 'tailwind.config.js') || hasProjectFile(project.path, 'tailwind.config.ts') || dependencyMatches(project, 'tailwindcss');
395
+ },
396
+ commands() { return {}; }
397
+ },
398
+ {
399
+ id: 'prisma',
400
+ name: 'Prisma',
401
+ icon: 'โ—ฎ',
402
+ description: 'Prisma ORM',
403
+ languages: ['Node.js'],
404
+ priority: 50,
405
+ match(project) {
406
+ return hasProjectFile(project.path, 'prisma/schema.prisma') || dependencyMatches(project, '@prisma/client');
407
+ },
408
+ commands() {
409
+ return {
410
+ generate: {label: 'Prisma generate', command: ['npx', 'prisma', 'generate'], source: 'framework'},
411
+ studio: {label: 'Prisma studio', command: ['npx', 'prisma', 'studio'], source: 'framework'}
412
+ };
413
+ }
414
+ },
362
415
  {
363
416
  id: 'spring',
364
417
  name: 'Spring Boot',
365
418
  icon: '๐ŸŒฑ',
366
419
  description: 'Spring Boot apps',
367
- languages: ['Java'],
420
+ languages: ['Java', 'Kotlin'],
368
421
  priority: 105,
369
422
  match(project) {
370
- return dependencyMatches(project, 'spring-boot-starter') || hasProjectFile(project.path, 'src/main/java');
423
+ return dependencyMatches(project, 'spring-boot-starter') ||
424
+ dependencyMatches(project, 'spring-boot-autoconfigure') ||
425
+ hasProjectFile(project.path, 'src/main/resources/application.properties') ||
426
+ hasProjectFile(project.path, 'src/main/resources/application.yml');
371
427
  },
372
428
  commands(project) {
373
429
  const hasMvnw = hasProjectFile(project.path, 'mvnw');
430
+ const hasGradlew = hasProjectFile(project.path, 'gradlew');
431
+ if (hasGradlew) {
432
+ return {
433
+ run: {label: 'Gradle BootRun', command: ['./gradlew', 'bootRun'], source: 'framework'},
434
+ build: {label: 'Gradle Build', command: ['./gradlew', 'build'], source: 'framework'},
435
+ test: {label: 'Gradle Test', command: ['./gradlew', 'test'], source: 'framework'}
436
+ };
437
+ }
374
438
  const base = hasMvnw ? './mvnw' : 'mvn';
375
439
  return {
376
440
  run: {label: 'Spring Boot run', command: [base, 'spring-boot:run'], source: 'framework'},
@@ -378,6 +442,77 @@ const builtInFrameworks = [
378
442
  test: {label: 'Maven test', command: [base, 'test'], source: 'framework'}
379
443
  };
380
444
  }
445
+ },
446
+ {
447
+ id: 'rocket',
448
+ name: 'Rocket',
449
+ icon: '๐Ÿš€',
450
+ description: 'Rocket Rust Web',
451
+ languages: ['Rust'],
452
+ priority: 105,
453
+ match(project) {
454
+ return dependencyMatches(project, 'rocket');
455
+ },
456
+ commands() {
457
+ return {
458
+ run: {label: 'Rocket Run', command: ['cargo', 'run'], source: 'framework'},
459
+ test: {label: 'Rocket Test', command: ['cargo', 'test'], source: 'framework'}
460
+ };
461
+ }
462
+ },
463
+ {
464
+ id: 'actix',
465
+ name: 'Actix Web',
466
+ icon: '๐Ÿฆ€',
467
+ description: 'Actix Rust Web',
468
+ languages: ['Rust'],
469
+ priority: 105,
470
+ match(project) {
471
+ return dependencyMatches(project, 'actix-web');
472
+ },
473
+ commands() {
474
+ return {
475
+ run: {label: 'Actix Run', command: ['cargo', 'run'], source: 'framework'},
476
+ test: {label: 'Actix Test', command: ['cargo', 'test'], source: 'framework'}
477
+ };
478
+ }
479
+ },
480
+ {
481
+ id: 'aspnet',
482
+ name: 'ASP.NET Core',
483
+ icon: '๐ŸŒ',
484
+ description: 'ASP.NET Core Web App',
485
+ languages: ['.NET'],
486
+ priority: 105,
487
+ match(project) {
488
+ return hasProjectFile(project.path, 'Program.cs') &&
489
+ (hasProjectFile(project.path, 'appsettings.json') || hasProjectFile(project.path, 'web.config'));
490
+ },
491
+ commands() {
492
+ return {
493
+ run: {label: 'dotnet run', command: ['dotnet', 'run'], source: 'framework'},
494
+ watch: {label: 'dotnet watch', command: ['dotnet', 'watch', 'run'], source: 'framework'},
495
+ test: {label: 'dotnet test', command: ['dotnet', 'test'], source: 'framework'}
496
+ };
497
+ }
498
+ },
499
+ {
500
+ id: 'laravel',
501
+ name: 'Laravel',
502
+ icon: '๐Ÿงก',
503
+ description: 'Laravel PHP Framework',
504
+ languages: ['PHP'],
505
+ priority: 105,
506
+ match(project) {
507
+ return hasProjectFile(project.path, 'artisan') || dependencyMatches(project, 'laravel/framework');
508
+ },
509
+ commands() {
510
+ return {
511
+ run: {label: 'Artisan Serve', command: ['php', 'artisan', 'serve'], source: 'framework'},
512
+ test: {label: 'Artisan Test', command: ['php', 'artisan', 'test'], source: 'framework'},
513
+ migrate: {label: 'Artisan Migrate', command: ['php', 'artisan', 'migrate'], source: 'framework'}
514
+ };
515
+ }
381
516
  }
382
517
  ];
383
518