@vjixy/vibel 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 JIHAD Al AKL
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # 🚀 Vibe Launcher
2
+
3
+ Vibe Launcher is a lightweight, modern project manager tailored specifically for keeping track of your "vibe coded" projects. Designed with a sleek, premium glassmorphic interface, it allows you to centralize your workflow without getting bogged down by heavy configurations.
4
+
5
+ ## 🌟 Features
6
+
7
+ - **Clean Architecture:** Built on Node.js and Express, utilizing a simple local JSON file (`projects.json`) for persistence. No database installation or complex setups required!
8
+ - **Beautiful UI:** A dynamic, visually striking single-page application (SPA) featuring glassmorphism, fluid animations, and modern typography.
9
+ - **Project Tracking:** Easily add projects with their absolute file paths and optional logos.
10
+ - **Multiple Execution Commands:** Define custom runtime scripts for each project (e.g., frontend server, backend server, tests) that run cleanly in their own visible Command Prompt windows.
11
+ - **Integrated IDE Launcher:** Open your projects instantly in your editor of choice (e.g., `code`, `cursor`, `antigravity`).
12
+
13
+ ## 🛠️ Tech Stack
14
+
15
+ - **Backend:** Node.js, Express.js
16
+ - **Frontend:** Vanilla HTML, CSS, JavaScript (Zero Build Steps!)
17
+ - **Database:** Local JSON File (`projects.json` - ignored from version control)
18
+ - **Styling:** Custom CSS with [Phosphor Icons](https://phosphoricons.com/) and [Google Fonts](https://fonts.google.com/).
19
+
20
+ ## 🚀 Getting Started
21
+
22
+ ### Prerequisites
23
+
24
+ You only need **Node.js** installed on your system.
25
+
26
+ ### Installation
27
+
28
+ #### Option 1: Global CLI (Recommended)
29
+
30
+ To install Vibe Launcher globally so you can start it from any terminal window simply by typing `vibel`:
31
+
32
+ 1. Clone or download this repository.
33
+ 2. Navigate to the project folder:
34
+ ```bash
35
+ cd "path/to/VibeLauncher"
36
+ ```
37
+ 3. Install dependencies and the global package simultaneously:
38
+ ```bash
39
+ npm install -g .
40
+ ```
41
+ Or install it directly from NPM registry:
42
+ ```bash
43
+ npm install -g @vjixy/vibel
44
+ ```
45
+
46
+ #### Option 2: Local Development
47
+
48
+ If you prefer to run it locally without installing it globally:
49
+
50
+ 1. Clone or download this repository.
51
+ 2. Navigate to the project folder and install dependencies:
52
+ ```bash
53
+ npm install
54
+ ```
55
+
56
+ ### Running the App
57
+
58
+ If you installed it globally via Option 1, open any terminal and just type:
59
+ ```bash
60
+ vibel
61
+ ```
62
+
63
+ If you installed it locally via Option 2, start the launcher from your project terminal:
64
+ ```bash
65
+ npm start
66
+ ```
67
+
68
+ The application will launch on your local server. Open your browser and go to:
69
+ **http://localhost:3000**
70
+
71
+ ## 📂 Project Structure
72
+
73
+ ```
74
+ VibeLauncher/
75
+ ├── public/ # Frontend assets
76
+ │ ├── index.html # Main interface
77
+ │ ├── style.css # Glassmorphic and animations styles
78
+ │ └── app.js # Client-side logic and API calls
79
+ ├── server.js # Express API and command execution logic
80
+ ├── projects.json # Local database (auto-generated on first run)
81
+ ├── package.json # Node.js dependencies and scripts
82
+ └── README.md # Project documentation
83
+ ```
84
+
85
+ ## 🪄 How to Use
86
+
87
+ 1. Click **"New Project"** in the top right.
88
+ 2. Enter the **Project Name** and the **Absolute Path** to the project on your machine.
89
+ 3. Configure your **IDE Command** (e.g., `code` for VS Code, `antigravity`, or `cursor`).
90
+ 4. Add as many **Execution Commands** as you need. For example:
91
+ - Name: `Backend`, Command: `npm run server`
92
+ - Name: `Frontend`, Command: `npm start`
93
+ 5. Save the project!
94
+ 6. Use the intuitive buttons on your project cards to pop open your IDE or trigger processes in new visible terminal windows.
95
+
96
+ ---
97
+ *Built with ❤️ for rapid, beautiful vibe coding.*
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@vjixy/vibel",
3
+ "version": "1.0.0",
4
+ "description": "Vibe Launcher CLI project manager",
5
+ "main": "server.js",
6
+ "bin": {
7
+ "vibel": "./server.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node server.js",
11
+ "test": "echo \"Error: no test specified\" && exit 1"
12
+ },
13
+ "keywords": [],
14
+ "author": "",
15
+ "license": "ISC",
16
+ "dependencies": {
17
+ "cors": "^2.8.6",
18
+ "express": "^5.2.1"
19
+ }
20
+ }
package/public/app.js ADDED
@@ -0,0 +1,261 @@
1
+ const API_BASE = '/api/projects';
2
+
3
+ // DOM Elements
4
+ const projectsGrid = document.getElementById('projectsGrid');
5
+ const addModal = document.getElementById('addModal');
6
+ const addProjectBtn = document.getElementById('addProjectBtn');
7
+ const closeModalBtn = document.getElementById('closeModalBtn');
8
+ const cancelModalBtn = document.getElementById('cancelModalBtn');
9
+ const projectForm = document.getElementById('projectForm');
10
+ const commandsList = document.getElementById('commandsList');
11
+ const addCmdBtn = document.getElementById('addCmdBtn');
12
+
13
+ // State
14
+ let projects = [];
15
+
16
+ // Search Input
17
+ const searchInput = document.getElementById('searchInput');
18
+ let searchQuery = '';
19
+
20
+ // Fetch and Render Projects
21
+ async function fetchProjects() {
22
+ try {
23
+ const res = await fetch(API_BASE);
24
+ projects = await res.json();
25
+ renderProjects();
26
+ } catch (e) {
27
+ console.error('Failed to load projects:', e);
28
+ projectsGrid.innerHTML = '<div class="loading">Error loading projects.</div>';
29
+ }
30
+ }
31
+
32
+ function renderProjects() {
33
+ let filteredProjects = projects;
34
+ if (searchQuery) {
35
+ const q = searchQuery.toLowerCase();
36
+ filteredProjects = projects.filter(p =>
37
+ p.name.toLowerCase().includes(q) ||
38
+ p.path.toLowerCase().includes(q)
39
+ );
40
+ }
41
+
42
+ if (filteredProjects.length === 0) {
43
+ projectsGrid.innerHTML = `
44
+ <div class="empty-state" style="grid-column: 1/-1; text-align: center; padding: 4rem; color: var(--text-muted);">
45
+ <i class="ph-duotone ph-rocket" style="font-size: 4rem; margin-bottom: 1rem; color: var(--primary);"></i>
46
+ <h2>No Vibe Projects Found</h2>
47
+ <p>${projects.length === 0 ? 'Click "New Project" to start tracking your magical creations.' : 'Try adjusting your search query.'}</p>
48
+ </div>
49
+ `;
50
+ return;
51
+ }
52
+
53
+ projectsGrid.innerHTML = '';
54
+ filteredProjects.forEach((p, index) => {
55
+ const card = document.createElement('div');
56
+ card.className = 'project-card';
57
+ card.style.animationDelay = `${index * 0.1}s`;
58
+
59
+ const logoHtml = p.logo
60
+ ? `<img src="${p.logo}" alt="${p.name} logo">`
61
+ : `<i class="ph-bold ph-code"></i>`;
62
+
63
+ let cmdButtons = '';
64
+ if (p.commands && p.commands.length > 0) {
65
+ p.commands.forEach((c, i) => {
66
+ cmdButtons += `
67
+ <button class="btn cmd-btn" onclick="executeCommand('${p.id}', ${i})">
68
+ <i class="ph-bold ph-play"></i> ${c.name}
69
+ </button>
70
+ `;
71
+ });
72
+ }
73
+
74
+ // Action grouping string
75
+ const actionHtml = `
76
+ <div class="actions-area">
77
+ <button class="btn cmd-btn ide-btn" onclick="openIDE('${p.id}')">
78
+ <i class="ph-bold ph-terminal-window"></i> Open ${p.ide || 'IDE'}
79
+ </button>
80
+ ${cmdButtons}
81
+ </div>
82
+ `;
83
+
84
+ card.innerHTML = `
85
+ <div class="card-actions-top">
86
+ <button class="icon-btn edit-btn" onclick="editProject('${p.id}')">
87
+ <i class="ph-bold ph-pencil-simple"></i>
88
+ </button>
89
+ <button class="icon-btn delete-btn" onclick="deleteProject('${p.id}')">
90
+ <i class="ph-bold ph-trash"></i>
91
+ </button>
92
+ </div>
93
+ <div class="card-header">
94
+ <div class="logo-display">${logoHtml}</div>
95
+ <div class="card-title">
96
+ <h3>${p.name}</h3>
97
+ <p>${p.path}</p>
98
+ </div>
99
+ </div>
100
+ ${actionHtml}
101
+ `;
102
+ projectsGrid.appendChild(card);
103
+ });
104
+ }
105
+
106
+ // Actions
107
+ async function executeCommand(id, cmdIndex) {
108
+ try {
109
+ const res = await fetch(`${API_BASE}/${id}/execute`, {
110
+ method: 'POST',
111
+ headers: { 'Content-Type': 'application/json' },
112
+ body: JSON.stringify({ commandIndex: cmdIndex })
113
+ });
114
+ const data = await res.json();
115
+ if (!data.success) alert(data.error);
116
+ } catch (e) {
117
+ console.error(e);
118
+ alert("Failed to execute code.");
119
+ }
120
+ }
121
+
122
+ async function openIDE(id) {
123
+ try {
124
+ const res = await fetch(`${API_BASE}/${id}/open-ide`, {
125
+ method: 'POST'
126
+ });
127
+ const data = await res.json();
128
+ if (!data.success) alert(data.error);
129
+ } catch (e) {
130
+ console.error(e);
131
+ alert("Failed to open IDE.");
132
+ }
133
+ }
134
+
135
+ async function deleteProject(id) {
136
+ if (!confirm('Are you sure you want to delete this project?')) return;
137
+ try {
138
+ await fetch(`${API_BASE}/${id}`, { method: 'DELETE' });
139
+ fetchProjects();
140
+ } catch (e) {
141
+ console.error(e);
142
+ }
143
+ }
144
+
145
+ function editProject(id) {
146
+ const project = projects.find(p => p.id === id);
147
+ if (!project) return;
148
+
149
+ document.getElementById('modalTitle').textContent = 'Edit Project';
150
+ document.getElementById('editProjectId').value = project.id;
151
+ document.getElementById('pName').value = project.name || '';
152
+ document.getElementById('pPath').value = project.path || '';
153
+ document.getElementById('pLogo').value = project.logo || '';
154
+ document.getElementById('pIde').value = project.ide || 'code';
155
+
156
+ commandsList.innerHTML = '';
157
+ if (project.commands && project.commands.length > 0) {
158
+ project.commands.forEach(cmd => {
159
+ const row = addCommandRow();
160
+ row.querySelector('.cmd-name').value = cmd.name;
161
+ row.querySelector('.cmd-val').value = cmd.cmd;
162
+ });
163
+ } else {
164
+ addCommandRow();
165
+ }
166
+
167
+ addModal.classList.remove('hidden');
168
+ }
169
+
170
+ // Modal Managemenet
171
+ function openModal() {
172
+ document.getElementById('modalTitle').textContent = 'Add New Project';
173
+ document.getElementById('editProjectId').value = '';
174
+ addModal.classList.remove('hidden');
175
+ // Reset Form
176
+ projectForm.reset();
177
+ commandsList.innerHTML = '';
178
+ addCommandRow(); // Add one default blank row
179
+ }
180
+
181
+ function closeModal() {
182
+ addModal.classList.add('hidden');
183
+ }
184
+
185
+ // Dynamic Command Rows
186
+ function addCommandRow() {
187
+ const row = document.createElement('div');
188
+ row.className = 'cmd-row';
189
+ row.innerHTML = `
190
+ <input type="text" placeholder="Name (e.g. backend)" class="cmd-name" required>
191
+ <input type="text" placeholder="Command (e.g. npm run dev)" class="cmd-val" required>
192
+ <button type="button" class="icon-btn btn-remove-cmd" style="color: #ef4444;"><i class="ph-bold ph-minus-circle"></i></button>
193
+ `;
194
+ commandsList.appendChild(row);
195
+
196
+ row.querySelector('.btn-remove-cmd').addEventListener('click', () => {
197
+ row.remove();
198
+ });
199
+ return row;
200
+ }
201
+
202
+ // Event Listeners
203
+ searchInput.addEventListener('input', (e) => {
204
+ searchQuery = e.target.value;
205
+ renderProjects();
206
+ });
207
+ addProjectBtn.addEventListener('click', openModal);
208
+ closeModalBtn.addEventListener('click', closeModal);
209
+ cancelModalBtn.addEventListener('click', closeModal);
210
+ addCmdBtn.addEventListener('click', addCommandRow);
211
+
212
+ addModal.addEventListener('click', (e) => {
213
+ if (e.target === addModal) closeModal();
214
+ });
215
+
216
+ projectForm.addEventListener('submit', async (e) => {
217
+ e.preventDefault();
218
+
219
+ // gather commands
220
+ const commands = [];
221
+ const rows = commandsList.querySelectorAll('.cmd-row');
222
+ rows.forEach(r => {
223
+ const name = r.querySelector('.cmd-name').value;
224
+ const cmd = r.querySelector('.cmd-val').value;
225
+ if (name && cmd) {
226
+ commands.push({ name, cmd });
227
+ }
228
+ });
229
+
230
+ const body = {
231
+ name: document.getElementById('pName').value,
232
+ path: document.getElementById('pPath').value,
233
+ logo: document.getElementById('pLogo').value,
234
+ ide: document.getElementById('pIde').value,
235
+ commands
236
+ };
237
+
238
+ const editId = document.getElementById('editProjectId').value;
239
+ const isEdit = !!editId;
240
+ const url = isEdit ? `${API_BASE}/${editId}` : API_BASE;
241
+ const method = isEdit ? 'PUT' : 'POST';
242
+
243
+ try {
244
+ const res = await fetch(url, {
245
+ method: method,
246
+ headers: { 'Content-Type': 'application/json' },
247
+ body: JSON.stringify(body)
248
+ });
249
+ if (res.ok) {
250
+ closeModal();
251
+ fetchProjects();
252
+ } else {
253
+ alert("Failed to save project.");
254
+ }
255
+ } catch (err) {
256
+ console.error(err);
257
+ }
258
+ });
259
+
260
+ // Init
261
+ fetchProjects();
@@ -0,0 +1,97 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Vibe Launcher</title>
8
+ <!-- Google Fonts -->
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet">
12
+ <!-- Phosphor Icons for UI -->
13
+ <script src="https://unpkg.com/@phosphor-icons/web"></script>
14
+ <link rel="stylesheet" href="style.css">
15
+ </head>
16
+
17
+ <body>
18
+ <div class="bg-effects">
19
+ <div class="blob blob-1"></div>
20
+ <div class="blob blob-2"></div>
21
+ <div class="blob blob-3"></div>
22
+ </div>
23
+
24
+ <main class="container">
25
+ <header class="app-header">
26
+ <div class="logo-area">
27
+ <i class="ph-fill ph-rocket-launch"></i>
28
+ <h1>Vibe Launcher</h1>
29
+ </div>
30
+ <div class="header-actions">
31
+ <div class="search-box">
32
+ <i class="ph-bold ph-magnifying-glass"></i>
33
+ <input type="text" id="searchInput" placeholder="Search projects...">
34
+ </div>
35
+ <button id="addProjectBtn" class="btn primary-btn">
36
+ <i class="ph-bold ph-plus"></i> New Project
37
+ </button>
38
+ </div>
39
+ </header>
40
+
41
+ <div id="projectsGrid" class="projects-grid">
42
+ <!-- Projects injected via JS -->
43
+ <div class="loading">Loading your vibe code...</div>
44
+ </div>
45
+ </main>
46
+
47
+ <!-- Modal for adding/editing project -->
48
+ <div id="addModal" class="modal-overlay hidden">
49
+ <div class="modal">
50
+ <div class="modal-header">
51
+ <h2 id="modalTitle">Add New Project</h2>
52
+ <button id="closeModalBtn" class="icon-btn"><i class="ph-bold ph-x"></i></button>
53
+ </div>
54
+ <div class="modal-body">
55
+ <form id="projectForm">
56
+ <input type="hidden" id="editProjectId" value="">
57
+ <div class="form-group">
58
+ <label>Project Name</label>
59
+ <input type="text" id="pName" required placeholder="e.g. Magic App">
60
+ </div>
61
+ <div class="form-group">
62
+ <label>Location (Absolute path)</label>
63
+ <input type="text" id="pPath" required placeholder="C:\Users\username\Projects\MagicApp">
64
+ </div>
65
+ <div class="form-group">
66
+ <label>Logo URL (Optional)</label>
67
+ <input type="url" id="pLogo" placeholder="https://example.com/logo.png">
68
+ </div>
69
+ <div class="form-group">
70
+ <label>IDE Command</label>
71
+ <input type="text" id="pIde" required placeholder="code, cursor, or antigravity">
72
+ </div>
73
+
74
+ <div class="commands-section">
75
+ <div class="commands-header">
76
+ <h3>Execution Commands</h3>
77
+ <button type="button" id="addCmdBtn" class="text-btn"><i class="ph-bold ph-plus"></i> Add
78
+ Command</button>
79
+ </div>
80
+ <div id="commandsList">
81
+ <!-- Commands added here -->
82
+ </div>
83
+ </div>
84
+
85
+ <div class="modal-actions">
86
+ <button type="button" id="cancelModalBtn" class="btn secondary-btn">Cancel</button>
87
+ <button type="submit" class="btn primary-btn">Save Project</button>
88
+ </div>
89
+ </form>
90
+ </div>
91
+ </div>
92
+ </div>
93
+
94
+ <script src="app.js"></script>
95
+ </body>
96
+
97
+ </html>
@@ -0,0 +1,504 @@
1
+ :root {
2
+ --bg-base: #0a0a0f;
3
+ --bg-glass: rgba(20, 20, 30, 0.6);
4
+ --border-glass: rgba(255, 255, 255, 0.08);
5
+ --primary: #6366f1;
6
+ --primary-hover: #4f46e5;
7
+ --accent: #ec4899;
8
+ --text-main: #f8fafc;
9
+ --text-muted: #94a3b8;
10
+ --card-hover: rgba(255, 255, 255, 0.03);
11
+
12
+ --blur: blur(16px);
13
+ --radius: 16px;
14
+ --shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
15
+ }
16
+
17
+ * {
18
+ margin: 0;
19
+ padding: 0;
20
+ box-sizing: border-box;
21
+ font-family: 'Outfit', sans-serif;
22
+ }
23
+
24
+ body {
25
+ background-color: var(--bg-base);
26
+ color: var(--text-main);
27
+ min-height: 100vh;
28
+ overflow-x: hidden;
29
+ position: relative;
30
+ -webkit-font-smoothing: antialiased;
31
+ }
32
+
33
+ /* Background Animated Blobs */
34
+ .bg-effects {
35
+ position: fixed;
36
+ top: 0;
37
+ left: 0;
38
+ right: 0;
39
+ bottom: 0;
40
+ z-index: -1;
41
+ overflow: hidden;
42
+ }
43
+
44
+ .blob {
45
+ position: absolute;
46
+ border-radius: 50%;
47
+ filter: blur(80px);
48
+ opacity: 0.5;
49
+ animation: float 20s infinite alternate;
50
+ }
51
+
52
+ .blob-1 {
53
+ width: 400px;
54
+ height: 400px;
55
+ background: var(--primary);
56
+ top: -10%;
57
+ left: -10%;
58
+ }
59
+
60
+ .blob-2 {
61
+ width: 300px;
62
+ height: 300px;
63
+ background: var(--accent);
64
+ bottom: -10%;
65
+ right: -5%;
66
+ animation-delay: -5s;
67
+ }
68
+
69
+ .blob-3 {
70
+ width: 250px;
71
+ height: 250px;
72
+ background: #8b5cf6;
73
+ top: 40%;
74
+ left: 60%;
75
+ animation-delay: -10s;
76
+ }
77
+
78
+ @keyframes float {
79
+ 0% {
80
+ transform: translate(0, 0) scale(1);
81
+ }
82
+
83
+ 100% {
84
+ transform: translate(30px, -50px) scale(1.1);
85
+ }
86
+ }
87
+
88
+ /* Container */
89
+ .container {
90
+ max-width: 1200px;
91
+ margin: 0 auto;
92
+ padding: 2rem;
93
+ }
94
+
95
+ /* Header */
96
+ .app-header {
97
+ display: flex;
98
+ justify-content: space-between;
99
+ align-items: center;
100
+ margin-bottom: 3rem;
101
+ padding: 1rem 2rem;
102
+ background: var(--bg-glass);
103
+ backdrop-filter: var(--blur);
104
+ border: 1px solid var(--border-glass);
105
+ border-radius: var(--radius);
106
+ box-shadow: var(--shadow);
107
+ }
108
+
109
+ .logo-area {
110
+ display: flex;
111
+ align-items: center;
112
+ gap: 0.75rem;
113
+ }
114
+
115
+ .logo-area i {
116
+ font-size: 2rem;
117
+ color: var(--primary);
118
+ background: linear-gradient(135deg, var(--primary), var(--accent));
119
+ -webkit-background-clip: text;
120
+ -webkit-text-fill-color: transparent;
121
+ }
122
+
123
+ .logo-area h1 {
124
+ font-size: 1.5rem;
125
+ font-weight: 700;
126
+ letter-spacing: -0.5px;
127
+ }
128
+
129
+ .header-actions {
130
+ display: flex;
131
+ align-items: center;
132
+ gap: 1.5rem;
133
+ }
134
+
135
+ .search-box {
136
+ position: relative;
137
+ display: flex;
138
+ align-items: center;
139
+ }
140
+
141
+ .search-box i {
142
+ position: absolute;
143
+ left: 1rem;
144
+ color: var(--text-muted);
145
+ }
146
+
147
+ .search-box input {
148
+ background: rgba(255, 255, 255, 0.05);
149
+ border: 1px solid var(--border-glass);
150
+ border-radius: 20px;
151
+ padding: 0.6rem 1rem 0.6rem 2.5rem;
152
+ color: var(--text-main);
153
+ font-size: 0.95rem;
154
+ width: 250px;
155
+ outline: none;
156
+ transition: all 0.3s ease;
157
+ }
158
+
159
+ .search-box input:focus {
160
+ width: 300px;
161
+ border-color: var(--primary);
162
+ background: rgba(255, 255, 255, 0.08);
163
+ }
164
+
165
+ /* Buttons */
166
+ .btn {
167
+ display: inline-flex;
168
+ align-items: center;
169
+ gap: 0.5rem;
170
+ padding: 0.75rem 1.5rem;
171
+ border-radius: 8px;
172
+ font-weight: 500;
173
+ font-size: 0.95rem;
174
+ cursor: pointer;
175
+ transition: all 0.3s ease;
176
+ border: none;
177
+ outline: none;
178
+ }
179
+
180
+ .primary-btn {
181
+ background: var(--primary);
182
+ color: white;
183
+ box-shadow: 0 4px 15px rgba(99, 102, 241, 0.4);
184
+ }
185
+
186
+ .primary-btn:hover {
187
+ background: var(--primary-hover);
188
+ transform: translateY(-2px);
189
+ box-shadow: 0 6px 20px rgba(99, 102, 241, 0.6);
190
+ }
191
+
192
+ .secondary-btn {
193
+ background: transparent;
194
+ color: var(--text-main);
195
+ border: 1px solid var(--border-glass);
196
+ }
197
+
198
+ .secondary-btn:hover {
199
+ background: rgba(255, 255, 255, 0.05);
200
+ }
201
+
202
+ .text-btn {
203
+ background: transparent;
204
+ color: var(--primary);
205
+ border: none;
206
+ cursor: pointer;
207
+ font-weight: 500;
208
+ display: inline-flex;
209
+ align-items: center;
210
+ gap: 0.25rem;
211
+ transition: color 0.2s;
212
+ }
213
+
214
+ .text-btn:hover {
215
+ color: var(--text-main);
216
+ }
217
+
218
+ .icon-btn {
219
+ background: transparent;
220
+ border: none;
221
+ color: var(--text-muted);
222
+ cursor: pointer;
223
+ font-size: 1.25rem;
224
+ transition: color 0.2s;
225
+ }
226
+
227
+ .icon-btn:hover {
228
+ color: var(--text-main);
229
+ }
230
+
231
+ /* Grid */
232
+ .projects-grid {
233
+ display: grid;
234
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
235
+ gap: 1.5rem;
236
+ }
237
+
238
+ /* Card */
239
+ .project-card {
240
+ background: var(--bg-glass);
241
+ backdrop-filter: var(--blur);
242
+ border: 1px solid var(--border-glass);
243
+ border-radius: var(--radius);
244
+ padding: 1.5rem;
245
+ transition: all 0.3s ease;
246
+ position: relative;
247
+ display: flex;
248
+ flex-direction: column;
249
+ gap: 1rem;
250
+ }
251
+
252
+ .project-card:hover {
253
+ transform: translateY(-5px);
254
+ background: rgba(25, 25, 35, 0.7);
255
+ border-color: rgba(255, 255, 255, 0.15);
256
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
257
+ }
258
+
259
+ .card-header {
260
+ display: flex;
261
+ align-items: center;
262
+ gap: 1rem;
263
+ }
264
+
265
+ .logo-display {
266
+ width: 48px;
267
+ height: 48px;
268
+ border-radius: 12px;
269
+ background: rgba(255, 255, 255, 0.05);
270
+ display: flex;
271
+ align-items: center;
272
+ justify-content: center;
273
+ overflow: hidden;
274
+ flex-shrink: 0;
275
+ border: 1px solid var(--border-glass);
276
+ }
277
+
278
+ .logo-display img {
279
+ width: 100%;
280
+ height: 100%;
281
+ object-fit: cover;
282
+ }
283
+
284
+ .logo-display i {
285
+ font-size: 1.5rem;
286
+ color: var(--text-muted);
287
+ }
288
+
289
+ .card-title {
290
+ flex: 1;
291
+ overflow: hidden;
292
+ }
293
+
294
+ .card-title h3 {
295
+ font-size: 1.2rem;
296
+ white-space: nowrap;
297
+ overflow: hidden;
298
+ text-overflow: ellipsis;
299
+ margin-bottom: 0.25rem;
300
+ }
301
+
302
+ .card-title p {
303
+ font-size: 0.8rem;
304
+ color: var(--text-muted);
305
+ white-space: nowrap;
306
+ overflow: hidden;
307
+ text-overflow: ellipsis;
308
+ }
309
+
310
+ .card-actions-top {
311
+ position: absolute;
312
+ top: 1rem;
313
+ right: 1rem;
314
+ display: flex;
315
+ gap: 0.5rem;
316
+ opacity: 0;
317
+ transition: opacity 0.2s;
318
+ }
319
+
320
+ .project-card:hover .card-actions-top {
321
+ opacity: 1;
322
+ }
323
+
324
+ .card-actions-top button {
325
+ color: var(--text-muted);
326
+ }
327
+
328
+ .card-actions-top button:hover {
329
+ color: var(--primary);
330
+ }
331
+
332
+ .delete-btn:hover {
333
+ color: #ef4444 !important;
334
+ }
335
+
336
+ .actions-area {
337
+ display: flex;
338
+ flex-wrap: wrap;
339
+ gap: 0.5rem;
340
+ margin-top: auto;
341
+ }
342
+
343
+ .cmd-btn {
344
+ display: inline-flex;
345
+ align-items: center;
346
+ gap: 0.4rem;
347
+ padding: 0.5rem 1rem;
348
+ background: rgba(255, 255, 255, 0.05);
349
+ border: 1px solid var(--border-glass);
350
+ border-radius: 20px;
351
+ color: var(--text-main);
352
+ font-size: 0.85rem;
353
+ cursor: pointer;
354
+ transition: all 0.2s;
355
+ }
356
+
357
+ .cmd-btn:hover {
358
+ background: var(--primary);
359
+ border-color: var(--primary);
360
+ }
361
+
362
+ .ide-btn {
363
+ background: rgba(236, 72, 153, 0.1);
364
+ border-color: rgba(236, 72, 153, 0.2);
365
+ color: #fbcfe8;
366
+ }
367
+
368
+ .ide-btn:hover {
369
+ background: var(--accent);
370
+ border-color: var(--accent);
371
+ color: white;
372
+ }
373
+
374
+ /* Modal */
375
+ .modal-overlay {
376
+ position: fixed;
377
+ top: 0;
378
+ left: 0;
379
+ right: 0;
380
+ bottom: 0;
381
+ background: rgba(0, 0, 0, 0.6);
382
+ backdrop-filter: blur(8px);
383
+ display: flex;
384
+ align-items: center;
385
+ justify-content: center;
386
+ z-index: 100;
387
+ opacity: 1;
388
+ transition: opacity 0.3s ease;
389
+ }
390
+
391
+ .modal-overlay.hidden {
392
+ opacity: 0;
393
+ pointer-events: none;
394
+ }
395
+
396
+ .modal {
397
+ background: #111116;
398
+ border: 1px solid var(--border-glass);
399
+ border-radius: var(--radius);
400
+ width: 90%;
401
+ max-width: 500px;
402
+ max-height: 90vh;
403
+ overflow-y: auto;
404
+ box-shadow: var(--shadow);
405
+ transform: translateY(0);
406
+ transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
407
+ }
408
+
409
+ .modal-overlay.hidden .modal {
410
+ transform: translateY(20px) scale(0.95);
411
+ }
412
+
413
+ .modal-header {
414
+ padding: 1.5rem;
415
+ border-bottom: 1px solid var(--border-glass);
416
+ display: flex;
417
+ justify-content: space-between;
418
+ align-items: center;
419
+ }
420
+
421
+ .modal-body {
422
+ padding: 1.5rem;
423
+ }
424
+
425
+ .form-group {
426
+ margin-bottom: 1.25rem;
427
+ }
428
+
429
+ .form-group label {
430
+ display: block;
431
+ margin-bottom: 0.5rem;
432
+ font-size: 0.9rem;
433
+ color: var(--text-muted);
434
+ }
435
+
436
+ .form-group input {
437
+ width: 100%;
438
+ padding: 0.75rem 1rem;
439
+ background: rgba(255, 255, 255, 0.03);
440
+ border: 1px solid var(--border-glass);
441
+ border-radius: 8px;
442
+ color: white;
443
+ font-size: 1rem;
444
+ outline: none;
445
+ transition: border-color 0.2s;
446
+ }
447
+
448
+ .form-group input:focus {
449
+ border-color: var(--primary);
450
+ background: rgba(255, 255, 255, 0.05);
451
+ }
452
+
453
+ .commands-section {
454
+ margin-top: 1.5rem;
455
+ padding-top: 1.5rem;
456
+ border-top: 1px solid var(--border-glass);
457
+ }
458
+
459
+ .commands-header {
460
+ display: flex;
461
+ justify-content: space-between;
462
+ align-items: center;
463
+ margin-bottom: 1rem;
464
+ }
465
+
466
+ .cmd-row {
467
+ display: flex;
468
+ gap: 0.5rem;
469
+ margin-bottom: 0.5rem;
470
+ align-items: center;
471
+ }
472
+
473
+ .cmd-row input {
474
+ flex: 1;
475
+ padding: 0.5rem;
476
+ background: rgba(255, 255, 255, 0.03);
477
+ border: 1px solid var(--border-glass);
478
+ border-radius: 6px;
479
+ color: white;
480
+ }
481
+
482
+ .modal-actions {
483
+ margin-top: 2rem;
484
+ display: flex;
485
+ justify-content: flex-end;
486
+ gap: 1rem;
487
+ }
488
+
489
+ /* Animations */
490
+ @keyframes fadeIn {
491
+ from {
492
+ opacity: 0;
493
+ transform: translateY(10px);
494
+ }
495
+
496
+ to {
497
+ opacity: 1;
498
+ transform: translateY(0);
499
+ }
500
+ }
501
+
502
+ .project-card {
503
+ animation: fadeIn 0.4s ease forwards;
504
+ }
package/server.js ADDED
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env node
2
+ const express = require('express');
3
+ const cors = require('cors');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const { exec } = require('child_process');
7
+
8
+ // For global install, we want to store projects in a reliable place
9
+ // like the user's home directory so it's persisted across updates
10
+ const os = require('os');
11
+ const DATA_FILE = path.join(os.homedir(), 'vibelancher_projects.json');
12
+
13
+ const app = express();
14
+ const PORT = 3000;
15
+
16
+ app.use(cors());
17
+ app.use(express.json());
18
+ app.use(express.static(path.join(__dirname, 'public')));
19
+
20
+ // Helper to read data
21
+ function readData() {
22
+ if (!fs.existsSync(DATA_FILE)) {
23
+ return [];
24
+ }
25
+ const raw = fs.readFileSync(DATA_FILE);
26
+ try {
27
+ return JSON.parse(raw);
28
+ } catch (e) {
29
+ return [];
30
+ }
31
+ }
32
+
33
+ // Helper to write data
34
+ function writeData(data) {
35
+ fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2));
36
+ }
37
+
38
+ // Get all projects
39
+ app.get('/api/projects', (req, res) => {
40
+ res.json(readData());
41
+ });
42
+
43
+ // Add a new project
44
+ app.post('/api/projects', (req, res) => {
45
+ const projects = readData();
46
+ const newProject = {
47
+ id: Date.now().toString(),
48
+ name: req.body.name || 'Unnamed Project',
49
+ path: req.body.path || '',
50
+ logo: req.body.logo || '',
51
+ ide: req.body.ide || 'code',
52
+ commands: req.body.commands || []
53
+ };
54
+ projects.push(newProject);
55
+ writeData(projects);
56
+ res.json(newProject);
57
+ });
58
+
59
+ // Delete a project
60
+ app.delete('/api/projects/:id', (req, res) => {
61
+ let projects = readData();
62
+ projects = projects.filter(p => p.id !== req.params.id);
63
+ writeData(projects);
64
+ res.json({ success: true });
65
+ });
66
+
67
+ // Update an existing project
68
+ app.put('/api/projects/:id', (req, res) => {
69
+ const projects = readData();
70
+ const index = projects.findIndex(p => p.id === req.params.id);
71
+ if (index === -1) {
72
+ return res.status(404).json({ error: 'Project not found' });
73
+ }
74
+
75
+ projects[index] = {
76
+ id: req.params.id, // Preserve ID
77
+ name: req.body.name || projects[index].name,
78
+ path: req.body.path || projects[index].path,
79
+ logo: req.body.logo || projects[index].logo,
80
+ ide: req.body.ide || projects[index].ide,
81
+ commands: req.body.commands || projects[index].commands
82
+ };
83
+
84
+ writeData(projects);
85
+ res.json(projects[index]);
86
+ });
87
+
88
+ // Execute a project command
89
+ app.post('/api/projects/:id/execute', (req, res) => {
90
+ const projects = readData();
91
+ const project = projects.find(p => p.id === req.params.id);
92
+ if (!project) {
93
+ return res.status(404).json({ error: 'Project not found' });
94
+ }
95
+
96
+ const { commandIndex } = req.body;
97
+ const cmdObj = project.commands[commandIndex];
98
+ if (!cmdObj) {
99
+ return res.status(400).json({ error: 'Command not found' });
100
+ }
101
+
102
+ // Launch in a new command prompt window on Windows
103
+ // /k keeps the window open
104
+ const fullCmd = `start "" cmd.exe /k "cd /d "${project.path}" && ${cmdObj.cmd}"`;
105
+ exec(fullCmd, (error) => {
106
+ if (error) {
107
+ console.error('Execution error:', error);
108
+ return res.status(500).json({ error: error.message });
109
+ }
110
+ });
111
+ res.json({ success: true, message: `Executed ${cmdObj.name}` });
112
+ });
113
+
114
+ // Open IDE
115
+ app.post('/api/projects/:id/open-ide', (req, res) => {
116
+ const projects = readData();
117
+ const project = projects.find(p => p.id === req.params.id);
118
+ if (!project) {
119
+ return res.status(404).json({ error: 'Project not found' });
120
+ }
121
+
122
+ // Launch IDE
123
+ const fullCmd = `start "" cmd.exe /c "cd /d "${project.path}" && ${project.ide} ."`;
124
+ exec(fullCmd, (error) => {
125
+ if (error) {
126
+ console.error('Execution error:', error);
127
+ return res.status(500).json({ error: error.message });
128
+ }
129
+ });
130
+
131
+ res.json({ success: true, message: `Opened with ${project.ide}` });
132
+ });
133
+
134
+ app.listen(PORT, () => {
135
+ console.log(`Vibe Launcher API listening at http://localhost:${PORT}`);
136
+ });