project-compass 3.6.7 → 3.8.1

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/AGENTS.md CHANGED
File without changes
package/ARCHITECTURE.md CHANGED
File without changes
package/CONTRIBUTING.md CHANGED
File without changes
package/LICENSE CHANGED
File without changes
package/README.md CHANGED
@@ -19,9 +19,15 @@ npm install -g project-compass
19
19
  ## 🌟 Premium Features
20
20
 
21
21
  ### 🌌 The Navigator (Main Interface)
22
- - **Automatic Discovery**: Instantly scans and identifies Node.js, Python, Rust, Go, Java, Scala, PHP, and Ruby projects.
22
+ - **Automatic Discovery**: Instantly identifies **Node.js (Next.js, React, Vue, NestJS, Angular, SvelteKit, Nuxt, Astro)**, **Python (Django, Flask, FastAPI)**, **Rust (Rocket, Actix)**, **Go**, **Java (Spring Boot)**, **.NET (ASP.NET Core)**, **PHP (Laravel)**, and **Ruby**.
23
+ - **Package Manager Intelligence**: Context-aware detection of `pnpm`, `bun`, `yarn`, and `npm`. No hardcoded commands—Compass uses your project's preferred tool.
23
24
  - **Deep Detail View**: Press `Enter` to reveal project manifests, detected frameworks, and available scripts.
24
25
  - **Custom Actions**: Save persistent, project-specific commands with `Shift+C` (e.g., `deploy|npm run deploy --prod`).
26
+ - **Macro Commands**: High-speed access to core workflows:
27
+ - `B`: Build
28
+ - `T`: Test
29
+ - `R`: Run / Start
30
+ - `I`: Install Dependencies (New!)
25
31
  - **Live Output Panel**: Stream real-time logs from active processes with dedicated scrolling (`Shift+↑/↓`).
26
32
 
27
33
  ### 🛰️ Orbit Task Manager (`Shift+T`)
@@ -35,12 +41,17 @@ npm install -g project-compass
35
41
  ### 📦 Package Registry (`Shift+P`)
36
42
  - **Context-Aware Management**: Add or remove dependencies without leaving the app.
37
43
  - **Internal Switcher**: Quick-swap projects within the registry view using `S`.
38
- - **Multi-Runtime Support**: Handles `npm`, `pip`, and more based on project type.
44
+ - **Multi-Runtime Support**: Handles `npm`, `pnpm`, `bun`, `pip`, and more based on project type.
39
45
 
40
46
  ![Package Registry](https://raw.githubusercontent.com/CrimsonDevil333333/project-compass/master/assets/screenshots/registry.jpg)
41
47
 
42
48
  ### 🏗️ Project Architect (`Shift+N`)
43
- - **Rapid Scaffolding**: Create new projects from scratch using industry-standard templates.
49
+ - **Rapid Scaffolding**: Create new projects from scratch using industry-standard templates:
50
+ - **Next.js** (Standard & Bun variants)
51
+ - **React/Vue** (Vite-powered with pnpm/npm support)
52
+ - **Rust** (Cargo binary)
53
+ - **Django** (Python web framework)
54
+ - **Go** (Module initialization)
44
55
  - **Interactive Prompts**: Safe, guided setup for directory structure and initial manifests.
45
56
 
46
57
  ![Project Architect](https://raw.githubusercontent.com/CrimsonDevil333333/project-compass/master/assets/screenshots/architect.jpg)
@@ -58,6 +69,7 @@ npm install -g project-compass
58
69
  | :--- | :--- |
59
70
  | `↑ / ↓` | Move project focus |
60
71
  | `Enter` | Toggle project Detail View |
72
+ | `B / T / R / I`| Macro: Build, Test, Run, **Install** |
61
73
  | `Shift + T` | **Orbit**: Task Manager |
62
74
  | `Shift + P` | **Registry**: Package Manager |
63
75
  | `Shift + N` | **Architect**: Project Creator |
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/commands.md CHANGED
@@ -8,6 +8,7 @@ This document lists all supported languages, frameworks, and their built-in comm
8
8
  | --- | --- |
9
9
  | ↑ / ↓ | Move project focus |
10
10
  | Enter | Toggle deep detail view / Switch back from sub-views |
11
+ | **B / T / R / I**| **Macro Launch**: Build, Test, Run, **Install** |
11
12
  | **Esc** | **Global Back**: Return to Main Navigator from any view |
12
13
  | **Shift+A** | Open **Omni-Studio** (Environment & Runtime audit) |
13
14
  | **Shift+T** | Open **Orbit Task Manager** (Manage background processes) |
@@ -30,7 +31,7 @@ This document lists all supported languages, frameworks, and their built-in comm
30
31
 
31
32
  Compass scans for the following manifests and requires their binaries in your PATH:
32
33
 
33
- - **Node.js** (`node`, `npm`): `package.json`
34
+ - **Node.js** (`node`, `npm`, `pnpm`, `bun`): `package.json`
34
35
  - **Python** (`python3`, `pip`): `pyproject.toml`, `requirements.txt`, `Pipfile`, `setup.py`
35
36
  - **Rust** (`cargo`): `Cargo.toml`
36
37
  - **Go** (`go`): `go.mod`
@@ -81,6 +82,13 @@ Compass scans for the following manifests and requires their binaries in your PA
81
82
 
82
83
  ## Project Architect Shortcuts (Shift+N)
83
84
 
84
- - **↑ / ↓**: Select a project template (Next.js, Vite, Rust, Go, etc.).
85
+ - **↑ / ↓**: Select a project template:
86
+ - Next.js (npm / Bun)
87
+ - React (Vite - pnpm / npm)
88
+ - Vue (Vite)
89
+ - Rust (Cargo Binary)
90
+ - Django (startproject)
91
+ - Python (Basic)
92
+ - Go (mod init)
85
93
  - **Enter**: Confirm selection and move to next step.
86
94
  - **Esc / Shift+N**: Exit architect mode.
package/eslint.config.cjs CHANGED
File without changes
@@ -0,0 +1,28 @@
1
+ # 2026-03-02 - Project Compass Final Polish
2
+
3
+ ## Task: Finalize and Polish project-compass (v3.7.0)
4
+ - **Status**: In Progress
5
+ - **Location**: /mnt/ramdisk/project-compass/
6
+
7
+ ### Work Log:
8
+ - Initializing final polish sub-agent.
9
+ - Goal: Production-grade refactor, documentation update, and framework support verification.
10
+ - **Documentation Update**:
11
+ - Revamped `README.md` and `commands.md`.
12
+ - Included all new framework support (Next.js Bun, Django, Rust, etc.).
13
+ - Documented the new 'Install' (I) macro command.
14
+ - **Refactor (src/projectDetection.js)**:
15
+ - Updated `getPackageManager` to return 'npm' as default but prioritized `bun.lockb`, `pnpm-lock.yaml`.
16
+ - Hardcoded 'npm' replaced with dynamic `pm` everywhere in Node.js framework detection.
17
+ - Extended Next.js detection to include `.mjs` and `.ts` config files.
18
+ - Updated Vite, Tailwind, and Prisma commands to use project-specific package managers and `npx`-equivalent logic.
19
+ - **CLI Enhancement (src/cli.js)**:
20
+ - Added 'i' (install) to `ACTION_MAP`.
21
+ - Updated help menu and reserved keys to include 'i'.
22
+ - **Architect Polish (src/components/ProjectArchitect.js)**:
23
+ - Added Next.js (Bun), React (pnpm), and Django templates.
24
+ - Ensured robust command generation for scaffolding.
25
+ - **Verification**:
26
+ - Confirmed `metadata.packageManager` is used in all critical Node.js paths.
27
+ - Bumbed version to 3.7.0 in `package.json`.
28
+ - **Status**: Production Ready.
@@ -0,0 +1,22 @@
1
+ # Changelog - 2026-03-03
2
+
3
+ ## [3.8.0] - Architectural Overhaul
4
+
5
+ ### Added
6
+ - **Paginated Navigator**: Implemented pagination for the project list. Now handles large workspaces (50+ projects) gracefully without breaking the TUI layout.
7
+ - **Configurable Limits**: Added `maxVisibleProjects` setting to `config.json` (default: 8).
8
+ - **Modular Architecture**:
9
+ - Broke down the massive `projectDetection.js` into specialized detectors in `src/detectors/` (Node, Python, Rust, Go, Java, Generic).
10
+ - Modularized TUI logic from `cli.js` into standalone React components: `Navigator.js`, `Header.js`, and `Footer.js`.
11
+ - **Production Hardening**: Added robust error boundaries and try-catch blocks in the project discovery pipeline to ensure one corrupt manifest doesn't crash the entire CLI.
12
+
13
+ ### Refactored
14
+ - **src/projectDetection.js**: Now acts as a clean orchestrator for modular detectors.
15
+ - **src/cli.js**: Significantly reduced complexity by delegating UI rendering to sub-components.
16
+ - **State Management**: Introduced `src/store/` (preparing for full context-based state management).
17
+
18
+ ### Technical Details
19
+ - New file structure:
20
+ - `src/detectors/node.js`, `python.js`, `rust.js`, etc.
21
+ - `src/components/Navigator.js`, `Header.js`, `Footer.js`.
22
+ - `src/detectors/utils.js` for shared detection logic.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-compass",
3
- "version": "3.6.7",
3
+ "version": "3.8.1",
4
4
  "description": "Futuristic project navigator and runner for Node, Python, Rust, and Go",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -14,6 +14,9 @@ import Studio from './components/Studio.js';
14
14
  import TaskManager from './components/TaskManager.js';
15
15
  import PackageRegistry from './components/PackageRegistry.js';
16
16
  import ProjectArchitect from './components/ProjectArchitect.js';
17
+ import Navigator from './components/Navigator.js';
18
+ import Header from './components/Header.js';
19
+ import Footer from './components/Footer.js';
17
20
 
18
21
  const create = React.createElement;
19
22
  const ART_CHARS = ['▁', '▃', '▄', '▅', '▇'];
@@ -26,7 +29,8 @@ const HELP_CARD_MIN_WIDTH = 28;
26
29
  const ACTION_MAP = {
27
30
  b: 'build',
28
31
  t: 'test',
29
- r: 'run'
32
+ r: 'run',
33
+ i: 'install'
30
34
  };
31
35
 
32
36
  function saveConfig(config) {
@@ -48,13 +52,14 @@ function loadConfig() {
48
52
  showArtBoard: true,
49
53
  showHelpCards: false,
50
54
  showStructureGuide: false,
55
+ maxVisibleProjects: 8,
51
56
  ...parsed,
52
57
  };
53
58
  }
54
59
  } catch (error) {
55
60
  console.error(`Ignoring corrupt config: ${error.message}`);
56
61
  }
57
- return {customCommands: {}, showArtBoard: true, showHelpCards: false, showStructureGuide: false};
62
+ return {customCommands: {}, showArtBoard: true, showHelpCards: false, showStructureGuide: false, maxVisibleProjects: 8};
58
63
  }
59
64
 
60
65
  function useScanner(rootPath) {
@@ -444,6 +449,11 @@ function Compass({rootPath, initialView = 'navigator'}) {
444
449
  if (key.shift && key.upArrow) { scrollLogs(1); return; }
445
450
  if (key.shift && key.downArrow) { scrollLogs(-1); return; }
446
451
 
452
+ const pageStep = Math.max(1, config.maxVisibleProjects || 8);
453
+ const clampIndex = (value) => Math.max(0, Math.min(projects.length - 1, value));
454
+ if (key.pageUp && projects.length > 0) { console.clear(); setSelectedIndex((prev) => clampIndex(prev - pageStep)); return; }
455
+ if (key.pageDown && projects.length > 0) { console.clear(); setSelectedIndex((prev) => clampIndex(prev + pageStep)); return; }
456
+
447
457
  if (normalizedInput === '?') { console.clear(); setShowHelp((prev) => !prev); return; }
448
458
  if (shiftCombo('l') && lastCommandRef.current) { runProjectCommand(lastCommandRef.current.commandMeta, lastCommandRef.current.project); return; }
449
459
 
@@ -472,7 +482,7 @@ function Compass({rootPath, initialView = 'navigator'}) {
472
482
  runProjectCommand(detailShortcutMap.get(normalizedInput), selectedProject);
473
483
  return;
474
484
  }
475
- const reserved = ['a', 'p', 'n', 'x', 'e', 'd', 'b', 't', 'q', 'h', 's', 'l', 'c'];
485
+ const reserved = ['a', 'p', 'n', 'x', 'e', 'd', 'b', 't', 'q', 'h', 's', 'l', 'c', 'i'];
476
486
  if (key.shift && !reserved.includes(normalizedInput)) {
477
487
  runProjectCommand(detailShortcutMap.get(normalizedInput), selectedProject);
478
488
  return;
@@ -486,30 +496,6 @@ function Compass({rootPath, initialView = 'navigator'}) {
486
496
  const orbitHint = mainView === 'tasks' ? 'Tasks View' : `Orbit: ${tasks.length} tasks`;
487
497
  const artHint = config.showArtBoard ? 'Shift+B hide art' : 'Shift+B show art';
488
498
 
489
- const projectRows = useMemo(() => {
490
- if (loading) return [create(Text, {key: 'scanning', dimColor: true}, 'Scanning projects…')];
491
- if (error) return [create(Text, {key: 'error', color: 'red'}, `Unable to scan: ${error}`)];
492
- if (projects.length === 0) return [create(Text, {key: 'empty', dimColor: true}, 'No recognizable project manifests found.')];
493
-
494
- return projects.map((project, index) => {
495
- const isSelected = index === selectedIndex;
496
- const frameworkBadges = (project.frameworks || []).map((frame) => `${frame.icon} ${frame.name}`).join(', ');
497
- const hasMissingRuntime = project.missingBinaries && project.missingBinaries.length > 0;
498
- return create(
499
- Box,
500
- {key: project.id, flexDirection: 'column', marginBottom: 1, padding: 1},
501
- create(
502
- Box,
503
- {flexDirection: 'row'},
504
- create(Text, {color: isSelected ? 'cyan' : 'white', bold: isSelected}, `${project.icon} ${project.name}`),
505
- hasMissingRuntime && create(Text, {color: 'red', bold: true}, ' ⚠️ Runtime missing')
506
- ),
507
- create(Text, {dimColor: true}, ` ${project.type} · ${path.relative(rootPath, project.path) || '.'}`),
508
- frameworkBadges && create(Text, {dimColor: true}, ` ${frameworkBadges}`)
509
- );
510
- });
511
- }, [loading, error, projects, selectedIndex, rootPath]);
512
-
513
499
  const detailContent = useMemo(() => {
514
500
  if (viewMode !== 'detail' || !selectedProject) {
515
501
  return [create(Text, {key: 'e-h', dimColor: true}, 'Press Enter on a project to reveal details.')];
@@ -567,27 +553,23 @@ function Compass({rootPath, initialView = 'navigator'}) {
567
553
  case 'architect': return create(ProjectArchitect, {rootPath, onRunCommand: runProjectCommand, CursorText, onReturn: () => setMainView('navigator')});
568
554
  default: {
569
555
  const navigatorBody = [
570
- create(Box, {key: 'header', justifyContent: 'space-between'},
571
- create(Box, {flexDirection: 'column'}, create(Text, {color: 'magenta', bold: true}, 'Project Compass'), create(Text, {dimColor: true}, `${projectCountLabel} detected in ${rootPath}`)),
572
- create(Box, {flexDirection: 'column', alignItems: 'flex-end'},
573
- create(Text, {color: running ? 'yellow' : 'green'}, statusHint),
574
- create(Text, {dimColor: true}, `${toggleHint} · ${orbitHint} · ${artHint} · Shift+Q: Quit`)
575
- )
576
- ),
556
+ create(Header, {projectCountLabel, rootPath, running, statusHint, toggleHint, orbitHint, artHint}),
577
557
  config.showArtBoard && create(Box, {key: 'artboard', flexDirection: 'column', marginTop: 1, borderStyle: 'round', borderColor: 'gray', padding: 1},
578
558
  create(Box, {flexDirection: 'row', justifyContent: 'space-between'}, create(Text, {color: 'magenta', bold: true}, 'Art-coded build atlas'), create(Text, {dimColor: true}, 'press ? for overlay help')),
579
559
  create(Box, {flexDirection: 'row', marginTop: 1}, ...ART_CHARS.map((char, i) => create(Text, {key: i, color: ART_COLORS[i % ART_COLORS.length]}, char.repeat(2)))),
580
560
  create(Box, {flexDirection: 'row', marginTop: 1}, ...artTileNodes)
581
561
  ),
582
562
  create(Box, {key: 'projects-row', marginTop: 1, flexDirection: 'row', alignItems: 'stretch', width: '100%', flexWrap: 'wrap'},
583
- create(Box, {flexGrow: 1, flexBasis: 0, minWidth: PROJECTS_MIN_WIDTH, marginRight: 1, borderStyle: 'round', borderColor: 'magenta', padding: 1}, create(Text, {bold: true, color: 'magenta'}, 'Projects'), create(Box, {flexDirection: 'column', marginTop: 1}, ...projectRows)),
563
+ create(Box, {flexGrow: 1, flexBasis: 0, minWidth: PROJECTS_MIN_WIDTH, marginRight: 1, borderStyle: 'round', borderColor: 'magenta', padding: 1},
564
+ create(Text, {bold: true, color: 'magenta'}, 'Projects'),
565
+ create(Box, {flexDirection: 'column', marginTop: 1}, create(Navigator, {projects, selectedIndex, rootPath, loading, error, maxVisibleProjects: config.maxVisibleProjects}))
566
+ ),
584
567
  create(Box, {flexGrow: 1.3, flexBasis: 0, minWidth: DETAILS_MIN_WIDTH, borderStyle: 'round', borderColor: 'cyan', padding: 1, flexDirection: 'column'}, create(Text, {bold: true, color: 'cyan'}, 'Details'), ...detailContent)
585
568
  ),
586
569
  create(Box, {key: 'output-row', marginTop: 1, flexDirection: 'column'},
587
570
  create(Box, {flexDirection: 'row', justifyContent: 'space-between'}, create(Text, {bold: true, color: 'yellow'}, `Output: ${activeTask?.name || 'None'}`), create(Text, {dimColor: true}, logOffset ? `Scrolled ${logOffset} lines` : 'Live log view')),
588
571
  create(OutputPanel, {activeTask, logOffset}),
589
- create(Box, {marginTop: 1, flexDirection: 'row', justifyContent: 'space-between'}, create(Text, {dimColor: true}, running ? 'Type to feed stdin; Enter: submit.' : 'Run a command or press Shift+T to switch tasks.'), create(Text, {dimColor: true}, `${toggleHint}, Shift+S: Structure Guide`)),
590
- create(Box, {marginTop: 1, flexDirection: 'row', borderStyle: 'round', borderColor: running ? 'green' : 'gray', paddingX: 1}, create(Text, {bold: true, color: running ? 'green' : 'white'}, running ? ' Stdin buffer ' : ' Input ready '), create(Box, {marginLeft: 1}, create(CursorText, {value: stdinBuffer || (running ? '' : 'Start a command to feed stdin'), cursorIndex: stdinCursor, active: running})))
572
+ create(Footer, {toggleHint, running, stdinBuffer, stdinCursor, CursorText})
591
573
  ),
592
574
  config.showHelpCards && create(Box, {key: 'help-cards', marginTop: 1, flexDirection: 'row', justifyContent: 'space-between', flexWrap: 'wrap'}, [
593
575
  {label: 'Navigation', color: 'magenta', body: ['↑ / ↓ move focus, Enter: details', 'Shift+↑ / ↓ scroll output', 'Shift+H toggle help cards', 'Shift+D detach from task']},
@@ -647,7 +629,7 @@ async function main() {
647
629
  console.log(' Enter Toggle deep detail view (manifests, scripts, frameworks)');
648
630
  console.log(' Shift+C Add a persistent custom command to the focused project');
649
631
  console.log(' 1-9 Quick-run numbered scripts in detail view');
650
- console.log(' B/T/R Macro run: Build / Test / Run');
632
+ console.log(' B/T/R/I Macro run: Build / Test / Run / Install');
651
633
  console.log('');
652
634
  console.log(kleur.bold(kleur.green('🛠️ Workspace Tools:')));
653
635
  console.log(' Shift+B Toggle Art-coded Build Atlas');
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+
4
+ export default function Footer({ toggleHint, running, stdinBuffer, stdinCursor, CursorText }) {
5
+ return React.createElement(
6
+ Box,
7
+ { flexDirection: 'column', marginTop: 1 },
8
+ React.createElement(
9
+ Box,
10
+ { flexDirection: 'row', justifyContent: 'space-between' },
11
+ React.createElement(Text, { dimColor: true }, running ? 'Type to feed stdin; Enter: submit.' : 'Run a command or press Shift+T to switch tasks.'),
12
+ React.createElement(Text, { dimColor: true }, `${toggleHint}, Shift+S: Structure Guide`)
13
+ ),
14
+ React.createElement(
15
+ Box,
16
+ { marginTop: 1, flexDirection: 'row', borderStyle: 'round', borderColor: running ? 'green' : 'gray', paddingX: 1 },
17
+ React.createElement(Text, { bold: true, color: running ? 'green' : 'white' }, running ? ' Stdin buffer ' : ' Input ready '),
18
+ React.createElement(
19
+ Box,
20
+ { marginLeft: 1 },
21
+ React.createElement(CursorText, { value: stdinBuffer || (running ? '' : 'Start a command to feed stdin'), cursorIndex: stdinCursor, active: running })
22
+ )
23
+ )
24
+ );
25
+ }
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+
4
+ export default function Header({ projectCountLabel, rootPath, running, statusHint, toggleHint, orbitHint, artHint }) {
5
+ return React.createElement(
6
+ Box,
7
+ { justifyContent: 'space-between' },
8
+ React.createElement(
9
+ Box,
10
+ { flexDirection: 'column' },
11
+ React.createElement(Text, { color: 'magenta', bold: true }, 'Project Compass'),
12
+ React.createElement(Text, { dimColor: true }, `${projectCountLabel} detected in ${rootPath}`)
13
+ ),
14
+ React.createElement(
15
+ Box,
16
+ { flexDirection: 'column', alignItems: 'flex-end' },
17
+ React.createElement(Text, { color: running ? 'yellow' : 'green' }, statusHint),
18
+ React.createElement(Text, { dimColor: true }, `${toggleHint} · ${orbitHint} · ${artHint} · Shift+Q: Quit`)
19
+ )
20
+ );
21
+ }
@@ -0,0 +1,56 @@
1
+ import React, { useMemo } from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import path from 'path';
4
+
5
+ export default function Navigator({
6
+ projects,
7
+ selectedIndex,
8
+ rootPath,
9
+ loading,
10
+ error,
11
+ maxVisibleProjects = 8
12
+ }) {
13
+ const page = Math.floor(selectedIndex / maxVisibleProjects);
14
+ const start = page * maxVisibleProjects;
15
+ const end = start + maxVisibleProjects;
16
+ const visibleProjects = projects.slice(start, end);
17
+
18
+ const projectRows = useMemo(() => {
19
+ if (loading) return [React.createElement(Text, { key: 'scanning', dimColor: true }, 'Scanning projects…')];
20
+ if (error) return [React.createElement(Text, { key: 'error', color: 'red' }, `Unable to scan: ${error}`)];
21
+ if (projects.length === 0) return [React.createElement(Text, { key: 'empty', dimColor: true }, 'No recognizable project manifests found.')];
22
+
23
+ return visibleProjects.map((project, index) => {
24
+ const absoluteIndex = start + index;
25
+ const isSelected = absoluteIndex === selectedIndex;
26
+ const frameworkBadges = (project.frameworks || []).map((frame) => `${frame.icon} ${frame.name}`).join(', ');
27
+ const hasMissingRuntime = project.missingBinaries && project.missingBinaries.length > 0;
28
+
29
+ return React.createElement(
30
+ Box,
31
+ { key: project.id, flexDirection: 'column', marginBottom: 1, padding: 1 },
32
+ React.createElement(
33
+ Box,
34
+ { flexDirection: 'row' },
35
+ React.createElement(Text, { color: isSelected ? 'cyan' : 'white', bold: isSelected }, `${project.icon} ${project.name}`),
36
+ hasMissingRuntime && React.createElement(Text, { color: 'red', bold: true }, ' ⚠️ Runtime missing')
37
+ ),
38
+ React.createElement(Text, { dimColor: true }, ` ${project.type} · ${path.relative(rootPath, project.path) || '.'}`),
39
+ frameworkBadges && React.createElement(Text, { dimColor: true }, ` ${frameworkBadges}`)
40
+ );
41
+ });
42
+ }, [loading, error, projects.length, visibleProjects, selectedIndex, start, rootPath]);
43
+
44
+ const totalPages = Math.ceil(projects.length / maxVisibleProjects);
45
+
46
+ return React.createElement(
47
+ Box,
48
+ { flexDirection: 'column' },
49
+ ...projectRows,
50
+ projects.length > maxVisibleProjects && React.createElement(
51
+ Box,
52
+ { marginTop: 1, justifyContent: 'center' },
53
+ React.createElement(Text, { dimColor: true }, `Page ${page + 1} of ${totalPages} (Total: ${projects.length})`)
54
+ )
55
+ );
56
+ }
@@ -2,6 +2,42 @@ import React, {useState, memo} from 'react';
2
2
  import {Box, Text, useInput} from 'ink';
3
3
 
4
4
  const create = React.createElement;
5
+ const NODE_PACKAGE_COMMANDS = {
6
+ npm: { add: ['npm', 'install'], remove: ['npm', 'uninstall'] },
7
+ pnpm: { add: ['pnpm', 'add'], remove: ['pnpm', 'remove'] },
8
+ yarn: { add: ['yarn', 'add'], remove: ['yarn', 'remove'] },
9
+ bun: { add: ['bun', 'add'], remove: ['bun', 'remove'] }
10
+ };
11
+
12
+ const resolveNodePackageCommand = (project, pkg, action) => {
13
+ if (!project || !pkg) return null;
14
+ const manager = (project.metadata?.packageManager || 'npm').toLowerCase();
15
+ const template = (NODE_PACKAGE_COMMANDS[manager] || NODE_PACKAGE_COMMANDS.npm)[action];
16
+ return template ? [...template, pkg] : null;
17
+ };
18
+
19
+ const getAddCmd = (project, pkg) => {
20
+ if (!project || !pkg) return null;
21
+ const type = project.type;
22
+ if (type === 'Node.js') return resolveNodePackageCommand(project, pkg, 'add');
23
+ if (type === 'Python') return ['pip', 'install', pkg];
24
+ if (type === 'Rust') return ['cargo', 'add', pkg];
25
+ if (type === '.NET') return ['dotnet', 'add', 'package', pkg];
26
+ if (type === 'PHP') return ['composer', 'require', pkg];
27
+ return null;
28
+ };
29
+
30
+ const getRemoveCmd = (project, pkg) => {
31
+ if (!project || !pkg) return null;
32
+ const type = project.type;
33
+ if (type === 'Node.js') return resolveNodePackageCommand(project, pkg, 'remove');
34
+ if (type === 'Python') return ['pip', 'uninstall', '-y', pkg];
35
+ if (type === 'Rust') return ['cargo', 'remove', pkg];
36
+ if (type === '.NET') return ['dotnet', 'remove', 'package', pkg];
37
+ if (type === 'PHP') return ['composer', 'remove', pkg];
38
+ return null;
39
+ };
40
+
5
41
 
6
42
  const PackageRegistry = memo(({selectedProject, projects = [], onRunCommand, CursorText, onSelectProject}) => {
7
43
  const [view, setView] = useState(selectedProject ? 'manage' : 'select'); // select | manage
@@ -29,7 +65,7 @@ const PackageRegistry = memo(({selectedProject, projects = [], onRunCommand, Cur
29
65
  if (mode === 'add' || mode === 'remove') {
30
66
  if (key.return) {
31
67
  if (input.trim()) {
32
- const cmd = mode === 'add' ? getAddCmd(projectType, input.trim()) : getRemoveCmd(projectType, input.trim());
68
+ const cmd = mode === 'add' ? getAddCmd(activeProject, input.trim()) : getRemoveCmd(activeProject, input.trim());
33
69
  if (cmd) onRunCommand({label: `${mode === 'add' ? 'Add' : 'Remove'} ${input}`, command: cmd}, activeProject);
34
70
  }
35
71
  setMode('list'); setInput(''); setCursor(0);
@@ -60,23 +96,7 @@ const PackageRegistry = memo(({selectedProject, projects = [], onRunCommand, Cur
60
96
  }
61
97
  });
62
98
 
63
- const getAddCmd = (type, pkg) => {
64
- if (type === 'Node.js') return ['npm', 'install', pkg];
65
- if (type === 'Python') return ['pip', 'install', pkg];
66
- if (type === 'Rust') return ['cargo', 'add', pkg];
67
- if (type === '.NET') return ['dotnet', 'add', 'package', pkg];
68
- if (type === 'PHP') return ['composer', 'require', pkg];
69
- return null;
70
- };
71
99
 
72
- const getRemoveCmd = (type, pkg) => {
73
- if (type === 'Node.js') return ['npm', 'uninstall', pkg];
74
- if (type === 'Python') return ['pip', 'uninstall', '-y', pkg];
75
- if (type === 'Rust') return ['cargo', 'remove', pkg];
76
- if (type === '.NET') return ['dotnet', 'remove', 'package', pkg];
77
- if (type === 'PHP') return ['composer', 'remove', pkg];
78
- return null;
79
- };
80
100
 
81
101
  if (view === 'select') {
82
102
  return create(
@@ -13,9 +13,12 @@ const ProjectArchitect = memo(({rootPath, onRunCommand, CursorText, onReturn}) =
13
13
 
14
14
  const frameworks = [
15
15
  {name: 'Next.js', cmd: (p, n) => ['npx', 'create-next-app@latest', path.join(p, n)]},
16
- {name: 'React (Vite)', cmd: (p, n) => ['npm', 'create', 'vite@latest', path.join(p, n), '--', '--template', 'react']},
16
+ {name: 'Next.js (Bun)', cmd: (p, n) => ['bun', 'create', 'next-app', path.join(p, n)]},
17
+ {name: 'React (Vite/pnpm)', cmd: (p, n) => ['pnpm', 'create', 'vite', path.join(p, n), '--template', 'react']},
18
+ {name: 'React (Vite/npm)', cmd: (p, n) => ['npm', 'create', 'vite@latest', path.join(p, n), '--', '--template', 'react']},
17
19
  {name: 'Vue (Vite)', cmd: (p, n) => ['npm', 'create', 'vite@latest', path.join(p, n), '--', '--template', 'vue']},
18
20
  {name: 'Rust (Binary)', cmd: (p, n) => ['cargo', 'new', path.join(p, n)]},
21
+ {name: 'Django Project', cmd: (p, n) => ['django-admin', 'startproject', n, path.join(p, n)]},
19
22
  {name: 'Python (Basic)', cmd: (p, n) => ['mkdir', '-p', path.join(p, n)]},
20
23
  {name: 'Go Module', cmd: (p, n) => ['mkdir', '-p', path.join(p, n), '&&', 'cd', path.join(p, n), '&&', 'go', 'mod', 'init', n]}
21
24
  ];
File without changes
File without changes
File without changes