nex-app 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,255 @@
1
+ /**
2
+ * JavaScript da UI do Installer
3
+ */
4
+
5
+ let currentStep = 1
6
+ const totalSteps = 4
7
+ let selectedAgents = []
8
+ let allAgents = []
9
+
10
+ // Inicialização
11
+ document.addEventListener('DOMContentLoaded', async () => {
12
+ await loadAgents()
13
+ setupEventListeners()
14
+ updateStepVisibility()
15
+ })
16
+
17
+ // Carregar agentes do Marketplace
18
+ async function loadAgents() {
19
+ try {
20
+ const response = await fetch('/api/agents/search')
21
+ const data = await response.json()
22
+ allAgents = data.agents || []
23
+ renderAgentResults(allAgents)
24
+ } catch (error) {
25
+ console.error('Erro ao carregar agentes:', error)
26
+ document.getElementById('agent-results').innerHTML =
27
+ '<p class="error">Erro ao carregar agentes. Continuando sem agentes...</p>'
28
+ }
29
+ }
30
+
31
+ // Setup de event listeners
32
+ function setupEventListeners() {
33
+ // Navegação de steps
34
+ document.getElementById('next-btn').addEventListener('click', nextStep)
35
+ document.getElementById('prev-btn').addEventListener('click', prevStep)
36
+
37
+ // Busca de agentes
38
+ document.getElementById('agent-search').addEventListener('input', handleAgentSearch)
39
+
40
+ // Submit do formulário
41
+ document.getElementById('installer-form').addEventListener('submit', handleSubmit)
42
+ }
43
+
44
+ // Navegação entre steps
45
+ function nextStep() {
46
+ if (validateCurrentStep()) {
47
+ if (currentStep < totalSteps) {
48
+ currentStep++
49
+ updateStepVisibility()
50
+ }
51
+ }
52
+ }
53
+
54
+ function prevStep() {
55
+ if (currentStep > 1) {
56
+ currentStep--
57
+ updateStepVisibility()
58
+ }
59
+ }
60
+
61
+ // Validar step atual
62
+ function validateCurrentStep() {
63
+ const currentStepEl = document.querySelector(`.step[data-step="${currentStep}"]`)
64
+
65
+ // Verificar se o step existe
66
+ if (!currentStepEl) {
67
+ console.warn(`Step ${currentStep} não encontrado`)
68
+ return true // Permite avançar se step não existe
69
+ }
70
+
71
+ const requiredInputs = currentStepEl.querySelectorAll('input[required], select[required]')
72
+
73
+ for (const input of requiredInputs) {
74
+ if (!input.value.trim()) {
75
+ input.focus()
76
+ return false
77
+ }
78
+ }
79
+
80
+ return true
81
+ }
82
+
83
+ // Atualizar visibilidade dos steps
84
+ function updateStepVisibility() {
85
+ // Esconder todos os steps
86
+ document.querySelectorAll('.step').forEach(step => {
87
+ step.classList.remove('active')
88
+ })
89
+
90
+ // Mostrar step atual
91
+ const currentStepEl = document.querySelector(`.step[data-step="${currentStep}"]`)
92
+ if (currentStepEl) {
93
+ currentStepEl.classList.add('active')
94
+ }
95
+
96
+ // Atualizar botões
97
+ document.getElementById('prev-btn').disabled = currentStep === 1
98
+ document.getElementById('next-btn').classList.toggle('hidden', currentStep === totalSteps)
99
+ document.getElementById('submit-btn').classList.toggle('hidden', currentStep !== totalSteps)
100
+ }
101
+
102
+ // Busca de agentes
103
+ function handleAgentSearch(e) {
104
+ const query = e.target.value.toLowerCase()
105
+ const filtered = allAgents.filter(agent =>
106
+ agent.name.toLowerCase().includes(query) ||
107
+ agent.tagline.toLowerCase().includes(query) ||
108
+ agent.agent_id.toLowerCase().includes(query)
109
+ )
110
+ renderAgentResults(filtered)
111
+ }
112
+
113
+ // Renderizar resultados de agentes
114
+ function renderAgentResults(agents) {
115
+ const container = document.getElementById('agent-results')
116
+
117
+ if (agents.length === 0) {
118
+ container.innerHTML = '<p class="empty">Nenhum agente encontrado</p>'
119
+ return
120
+ }
121
+
122
+ container.innerHTML = agents.map(agent => {
123
+ const isSelected = selectedAgents.includes(agent.agent_id)
124
+ return `
125
+ <div class="agent-card ${isSelected ? 'selected' : ''}" data-agent-id="${agent.agent_id}">
126
+ <label>
127
+ <input
128
+ type="checkbox"
129
+ ${isSelected ? 'checked' : ''}
130
+ onchange="toggleAgent('${agent.agent_id}')"
131
+ >
132
+ <div class="agent-info">
133
+ <h4>${agent.icon} ${agent.name}</h4>
134
+ <p>${agent.tagline}</p>
135
+ <small>${agent.total_installs || 0} instalações • ⭐ ${agent.average_rating || 'N/A'}/5</small>
136
+ </div>
137
+ </label>
138
+ </div>
139
+ `
140
+ }).join('')
141
+ }
142
+
143
+ // Toggle de agente
144
+ window.toggleAgent = function(agentId) {
145
+ const index = selectedAgents.indexOf(agentId)
146
+ if (index > -1) {
147
+ selectedAgents.splice(index, 1)
148
+ } else {
149
+ selectedAgents.push(agentId)
150
+ }
151
+ updateSelectedAgents()
152
+ renderAgentResults(allAgents.filter(a =>
153
+ document.getElementById('agent-search').value === '' ||
154
+ a.name.toLowerCase().includes(document.getElementById('agent-search').value.toLowerCase())
155
+ ))
156
+ }
157
+
158
+ // Atualizar lista de agentes selecionados
159
+ function updateSelectedAgents() {
160
+ const container = document.getElementById('selected-list')
161
+
162
+ if (selectedAgents.length === 0) {
163
+ container.innerHTML = '<p class="empty">Nenhum agente selecionado</p>'
164
+ return
165
+ }
166
+
167
+ container.innerHTML = selectedAgents.map(agentId => {
168
+ const agent = allAgents.find(a => a.agent_id === agentId)
169
+ if (!agent) return ''
170
+ return `
171
+ <div class="selected-agent">
172
+ <span>${agent.icon} ${agent.name}</span>
173
+ <button type="button" onclick="toggleAgent('${agentId}')" class="remove-btn">×</button>
174
+ </div>
175
+ `
176
+ }).join('')
177
+ }
178
+
179
+ // Submit do formulário
180
+ async function handleSubmit(e) {
181
+ e.preventDefault()
182
+
183
+ // Esconder formulário e mostrar progresso
184
+ document.getElementById('installer-form').classList.add('hidden')
185
+ document.getElementById('progress').classList.remove('hidden')
186
+
187
+ const formData = new FormData(e.target)
188
+ const data = {
189
+ template: formData.get('template'),
190
+ agents: selectedAgents,
191
+ packageManager: formData.get('packageManager'),
192
+ cursorIntegration: formData.get('cursorIntegration') === 'on',
193
+ options: {}
194
+ }
195
+
196
+ try {
197
+ const response = await fetch('/api/generate', {
198
+ method: 'POST',
199
+ headers: {
200
+ 'Content-Type': 'application/json'
201
+ },
202
+ body: JSON.stringify(data)
203
+ })
204
+
205
+ const result = await response.json()
206
+
207
+ if (result.success) {
208
+ showSuccess(result)
209
+ } else {
210
+ showError(result.error || 'Erro ao gerar projeto')
211
+ }
212
+ } catch (error) {
213
+ showError(error.message)
214
+ }
215
+ }
216
+
217
+ // Mostrar sucesso
218
+ function showSuccess(result) {
219
+ document.getElementById('progress').classList.add('hidden')
220
+ document.getElementById('success').classList.remove('hidden')
221
+
222
+ const formData = new FormData(document.getElementById('installer-form'))
223
+ const packageManager = formData.get('packageManager')
224
+ const projectPath = result.path || 'diretório atual'
225
+
226
+ document.getElementById('success-message').innerHTML = `
227
+ <p>NEX inicializado em: <code>${projectPath}</code></p>
228
+ <p class="hint">NEX foi inicializado no diretório atual</p>
229
+ `
230
+
231
+ document.getElementById('next-steps-commands').textContent = `${packageManager} install
232
+ ${packageManager} start
233
+
234
+ # Para instalar agentes:
235
+ nex agent install <agent-id>
236
+
237
+ # Para ver agentes disponíveis:
238
+ nex agent list --all`
239
+ }
240
+
241
+ // Mostrar erro
242
+ function showError(message) {
243
+ document.getElementById('progress').classList.add('hidden')
244
+ document.getElementById('progress-message').textContent = message
245
+ document.getElementById('progress').classList.remove('hidden')
246
+ document.getElementById('progress').classList.add('error')
247
+
248
+ // Adicionar botão para voltar
249
+ const progressEl = document.getElementById('progress')
250
+ progressEl.innerHTML += `
251
+ <button onclick="location.reload()" class="btn btn-primary" style="margin-top: 1rem;">
252
+ Tentar Novamente
253
+ </button>
254
+ `
255
+ }
@@ -0,0 +1,416 @@
1
+ /**
2
+ * Estilos do Installer Web
3
+ */
4
+
5
+ * {
6
+ margin: 0;
7
+ padding: 0;
8
+ box-sizing: border-box;
9
+ }
10
+
11
+ body {
12
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
13
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
14
+ min-height: 100vh;
15
+ padding: 2rem;
16
+ color: #333;
17
+ }
18
+
19
+ .container {
20
+ max-width: 800px;
21
+ margin: 0 auto;
22
+ background: white;
23
+ border-radius: 12px;
24
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
25
+ padding: 2rem;
26
+ }
27
+
28
+ header {
29
+ text-align: center;
30
+ margin-bottom: 2rem;
31
+ padding-bottom: 1.5rem;
32
+ border-bottom: 2px solid #f0f0f0;
33
+ }
34
+
35
+ header h1 {
36
+ font-size: 2rem;
37
+ color: #667eea;
38
+ margin-bottom: 0.5rem;
39
+ }
40
+
41
+ header p {
42
+ color: #666;
43
+ font-size: 1.1rem;
44
+ }
45
+
46
+ .step {
47
+ display: none;
48
+ animation: fadeIn 0.3s;
49
+ }
50
+
51
+ .step.active {
52
+ display: block;
53
+ }
54
+
55
+ @keyframes fadeIn {
56
+ from { opacity: 0; transform: translateY(10px); }
57
+ to { opacity: 1; transform: translateY(0); }
58
+ }
59
+
60
+ .step h2 {
61
+ font-size: 1.5rem;
62
+ margin-bottom: 1.5rem;
63
+ color: #333;
64
+ }
65
+
66
+ .step .subtitle {
67
+ color: #666;
68
+ margin-bottom: 1rem;
69
+ font-size: 0.95rem;
70
+ }
71
+
72
+ input[type="text"],
73
+ input[type="email"],
74
+ select {
75
+ width: 100%;
76
+ padding: 0.75rem;
77
+ border: 2px solid #e0e0e0;
78
+ border-radius: 8px;
79
+ font-size: 1rem;
80
+ transition: border-color 0.3s;
81
+ }
82
+
83
+ input[type="text"]:focus,
84
+ select:focus {
85
+ outline: none;
86
+ border-color: #667eea;
87
+ }
88
+
89
+ .hint {
90
+ display: block;
91
+ margin-top: 0.5rem;
92
+ color: #999;
93
+ font-size: 0.85rem;
94
+ }
95
+
96
+ /* Template Grid */
97
+ .template-grid {
98
+ display: grid;
99
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
100
+ gap: 1rem;
101
+ margin-top: 1rem;
102
+ }
103
+
104
+ .template-card {
105
+ display: block;
106
+ cursor: pointer;
107
+ }
108
+
109
+ .template-card input[type="radio"] {
110
+ display: none;
111
+ }
112
+
113
+ .card-content {
114
+ padding: 1.5rem;
115
+ border: 2px solid #e0e0e0;
116
+ border-radius: 8px;
117
+ transition: all 0.3s;
118
+ text-align: center;
119
+ }
120
+
121
+ .template-card input[type="radio"]:checked + .card-content {
122
+ border-color: #667eea;
123
+ background: #f0f4ff;
124
+ }
125
+
126
+ .card-content h3 {
127
+ font-size: 1.2rem;
128
+ margin-bottom: 0.5rem;
129
+ color: #333;
130
+ }
131
+
132
+ .card-content p {
133
+ color: #666;
134
+ font-size: 0.9rem;
135
+ }
136
+
137
+ /* Agent Search */
138
+ .agent-search {
139
+ margin-bottom: 1.5rem;
140
+ }
141
+
142
+ .agent-results {
143
+ max-height: 300px;
144
+ overflow-y: auto;
145
+ margin-bottom: 1.5rem;
146
+ border: 1px solid #e0e0e0;
147
+ border-radius: 8px;
148
+ padding: 1rem;
149
+ }
150
+
151
+ .agent-card {
152
+ padding: 1rem;
153
+ border: 1px solid #e0e0e0;
154
+ border-radius: 8px;
155
+ margin-bottom: 0.5rem;
156
+ transition: all 0.3s;
157
+ }
158
+
159
+ .agent-card:hover {
160
+ border-color: #667eea;
161
+ background: #f9f9f9;
162
+ }
163
+
164
+ .agent-card.selected {
165
+ border-color: #667eea;
166
+ background: #f0f4ff;
167
+ }
168
+
169
+ .agent-card label {
170
+ display: flex;
171
+ align-items: center;
172
+ cursor: pointer;
173
+ gap: 1rem;
174
+ }
175
+
176
+ .agent-card input[type="checkbox"] {
177
+ width: 20px;
178
+ height: 20px;
179
+ cursor: pointer;
180
+ }
181
+
182
+ .agent-info h4 {
183
+ font-size: 1.1rem;
184
+ margin-bottom: 0.25rem;
185
+ color: #333;
186
+ }
187
+
188
+ .agent-info p {
189
+ color: #666;
190
+ font-size: 0.9rem;
191
+ margin-bottom: 0.25rem;
192
+ }
193
+
194
+ .agent-info small {
195
+ color: #999;
196
+ font-size: 0.85rem;
197
+ }
198
+
199
+ .selected-agents {
200
+ margin-top: 1.5rem;
201
+ padding-top: 1.5rem;
202
+ border-top: 1px solid #e0e0e0;
203
+ }
204
+
205
+ .selected-agents h3 {
206
+ font-size: 1.1rem;
207
+ margin-bottom: 1rem;
208
+ color: #333;
209
+ }
210
+
211
+ .selected-list {
212
+ display: flex;
213
+ flex-wrap: wrap;
214
+ gap: 0.5rem;
215
+ }
216
+
217
+ .selected-agent {
218
+ display: flex;
219
+ align-items: center;
220
+ gap: 0.5rem;
221
+ padding: 0.5rem 1rem;
222
+ background: #f0f4ff;
223
+ border: 1px solid #667eea;
224
+ border-radius: 20px;
225
+ font-size: 0.9rem;
226
+ }
227
+
228
+ .remove-btn {
229
+ background: #667eea;
230
+ color: white;
231
+ border: none;
232
+ border-radius: 50%;
233
+ width: 20px;
234
+ height: 20px;
235
+ cursor: pointer;
236
+ font-size: 1rem;
237
+ line-height: 1;
238
+ padding: 0;
239
+ }
240
+
241
+ .remove-btn:hover {
242
+ background: #5568d3;
243
+ }
244
+
245
+ /* Checkbox Label */
246
+ .checkbox-label {
247
+ display: flex;
248
+ align-items: center;
249
+ gap: 0.75rem;
250
+ cursor: pointer;
251
+ padding: 1rem;
252
+ border: 2px solid #e0e0e0;
253
+ border-radius: 8px;
254
+ transition: all 0.3s;
255
+ }
256
+
257
+ .checkbox-label:hover {
258
+ border-color: #667eea;
259
+ }
260
+
261
+ .checkbox-label input[type="checkbox"] {
262
+ width: 20px;
263
+ height: 20px;
264
+ cursor: pointer;
265
+ }
266
+
267
+ /* Actions */
268
+ .actions {
269
+ display: flex;
270
+ justify-content: space-between;
271
+ margin-top: 2rem;
272
+ padding-top: 2rem;
273
+ border-top: 2px solid #f0f0f0;
274
+ }
275
+
276
+ .btn {
277
+ padding: 0.75rem 1.5rem;
278
+ border: none;
279
+ border-radius: 8px;
280
+ font-size: 1rem;
281
+ cursor: pointer;
282
+ transition: all 0.3s;
283
+ font-weight: 500;
284
+ }
285
+
286
+ .btn-primary {
287
+ background: #667eea;
288
+ color: white;
289
+ }
290
+
291
+ .btn-primary:hover {
292
+ background: #5568d3;
293
+ transform: translateY(-2px);
294
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
295
+ }
296
+
297
+ .btn-secondary {
298
+ background: #f0f0f0;
299
+ color: #333;
300
+ }
301
+
302
+ .btn-secondary:hover:not(:disabled) {
303
+ background: #e0e0e0;
304
+ }
305
+
306
+ .btn:disabled {
307
+ opacity: 0.5;
308
+ cursor: not-allowed;
309
+ }
310
+
311
+ .hidden {
312
+ display: none !important;
313
+ }
314
+
315
+ /* Progress */
316
+ .progress {
317
+ text-align: center;
318
+ padding: 3rem;
319
+ }
320
+
321
+ .spinner {
322
+ width: 50px;
323
+ height: 50px;
324
+ border: 4px solid #f0f0f0;
325
+ border-top-color: #667eea;
326
+ border-radius: 50%;
327
+ animation: spin 1s linear infinite;
328
+ margin: 0 auto 1rem;
329
+ }
330
+
331
+ @keyframes spin {
332
+ to { transform: rotate(360deg); }
333
+ }
334
+
335
+ .progress p {
336
+ font-size: 1.1rem;
337
+ color: #333;
338
+ margin-bottom: 0.5rem;
339
+ }
340
+
341
+ .progress.error {
342
+ color: #e74c3c;
343
+ }
344
+
345
+ /* Success */
346
+ .success {
347
+ text-align: center;
348
+ padding: 2rem;
349
+ }
350
+
351
+ .success h2 {
352
+ color: #27ae60;
353
+ margin-bottom: 1rem;
354
+ }
355
+
356
+ .next-steps {
357
+ margin-top: 2rem;
358
+ text-align: left;
359
+ background: #f9f9f9;
360
+ padding: 1.5rem;
361
+ border-radius: 8px;
362
+ }
363
+
364
+ .next-steps h3 {
365
+ margin-bottom: 1rem;
366
+ color: #333;
367
+ }
368
+
369
+ .next-steps pre {
370
+ background: #2d2d2d;
371
+ color: #f8f8f2;
372
+ padding: 1rem;
373
+ border-radius: 4px;
374
+ overflow-x: auto;
375
+ font-family: 'Courier New', monospace;
376
+ }
377
+
378
+ .empty {
379
+ color: #999;
380
+ font-style: italic;
381
+ text-align: center;
382
+ padding: 2rem;
383
+ }
384
+
385
+ .error {
386
+ color: #e74c3c;
387
+ text-align: center;
388
+ padding: 1rem;
389
+ background: #fee;
390
+ border-radius: 8px;
391
+ margin: 1rem 0;
392
+ }
393
+
394
+ /* Responsive */
395
+ @media (max-width: 600px) {
396
+ body {
397
+ padding: 1rem;
398
+ }
399
+
400
+ .container {
401
+ padding: 1.5rem;
402
+ }
403
+
404
+ .template-grid {
405
+ grid-template-columns: 1fr;
406
+ }
407
+
408
+ .actions {
409
+ flex-direction: column;
410
+ gap: 0.5rem;
411
+ }
412
+
413
+ .btn {
414
+ width: 100%;
415
+ }
416
+ }