@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 +21 -0
- package/README.md +97 -0
- package/package.json +20 -0
- package/public/app.js +261 -0
- package/public/index.html +97 -0
- package/public/style.css +504 -0
- package/server.js +136 -0
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>
|
package/public/style.css
ADDED
|
@@ -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
|
+
});
|