@nimbuslab/cli 0.16.3 → 0.16.5

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.
@@ -1,1026 +0,0 @@
1
- import * as p from "@clack/prompts"
2
- import pc from "picocolors"
3
- import { existsSync } from "node:fs"
4
- import { join } from "node:path"
5
- import { homedir } from "node:os"
6
-
7
- const HOME = homedir()
8
- const LOLA_DIR = join(HOME, ".lola")
9
- const LOLA_REPO = "git@github.com:nimbuslab/lola.git"
10
- const USER_MEMORY = join(HOME, ".claude", "USER_MEMORY.md")
11
- const LOLA_MEMORY_URL = "https://lola.nimbuslab.com.br/sse"
12
- const LOLA_MEMORY_NAME = "lola-memory"
13
-
14
- // Gemini CLI
15
- const GEMINI_DIR = join(HOME, ".gemini")
16
- const GEMINI_SETTINGS = join(GEMINI_DIR, "GEMINI.md")
17
-
18
- // Tipos de agent
19
- type AgentType = "claude" | "gemini" | "all"
20
-
21
- // Helpers de detecção
22
- function hasClaudeCLI(): boolean {
23
- const result = Bun.spawnSync(["which", "claude"], { stdout: "pipe" })
24
- return result.exitCode === 0
25
- }
26
-
27
- function hasGeminiCLI(): boolean {
28
- const result = Bun.spawnSync(["which", "gemini"], { stdout: "pipe" })
29
- return result.exitCode === 0
30
- }
31
-
32
- async function installLolaMemoryMCP(): Promise<void> {
33
- console.log()
34
- console.log(pc.cyan(" Configurando lola-memory MCP..."))
35
-
36
- // Verificar se claude CLI existe
37
- const claudeCheck = Bun.spawnSync(["which", "claude"], { stdout: "pipe" })
38
- if (claudeCheck.exitCode !== 0) {
39
- console.log(pc.yellow(" Claude CLI nao encontrado, pulando MCP"))
40
- console.log(pc.dim(" Instale o Claude CLI e rode 'nimbus lola install' novamente"))
41
- return
42
- }
43
-
44
- // Verificar se MCP já está configurado
45
- const mcpList = Bun.spawnSync(["claude", "mcp", "list"], { stdout: "pipe", stderr: "pipe" })
46
- const mcpOutput = mcpList.stdout.toString()
47
-
48
- if (mcpOutput.includes(LOLA_MEMORY_NAME)) {
49
- console.log(pc.green(" MCP lola-memory ja configurado"))
50
- return
51
- }
52
-
53
- // Adicionar MCP lola-memory
54
- console.log(pc.dim(" Adicionando MCP lola-memory..."))
55
-
56
- const result = Bun.spawnSync([
57
- "claude", "mcp", "add",
58
- "-t", "sse",
59
- "-s", "user",
60
- LOLA_MEMORY_NAME,
61
- "--",
62
- "bunx", "mcp-remote", LOLA_MEMORY_URL
63
- ], {
64
- stdout: "inherit",
65
- stderr: "inherit",
66
- })
67
-
68
- if (result.exitCode !== 0) {
69
- console.log(pc.yellow(" Erro ao adicionar MCP, pode ser adicionado manualmente:"))
70
- console.log(pc.dim(` claude mcp add -t sse -s user ${LOLA_MEMORY_NAME} -- bunx mcp-remote ${LOLA_MEMORY_URL}`))
71
- return
72
- }
73
-
74
- console.log(pc.green(" MCP lola-memory instalado!"))
75
- console.log()
76
- console.log(pc.dim(" Comandos disponiveis:"))
77
- console.log(pc.dim(" remember \"query\" - Buscar conhecimento"))
78
- console.log(pc.dim(" learn \"content\" - Salvar conhecimento"))
79
- console.log(pc.dim(" memory_stats - Ver estatisticas"))
80
- }
81
-
82
- // ============================================
83
- // GEMINI CLI
84
- // ============================================
85
-
86
- async function installGeminiCLI(): Promise<boolean> {
87
- console.log()
88
- console.log(pc.cyan(" Instalando Gemini CLI..."))
89
-
90
- const result = Bun.spawnSync(["npm", "install", "-g", "@google/gemini-cli"], {
91
- stdout: "inherit",
92
- stderr: "inherit",
93
- })
94
-
95
- if (result.exitCode !== 0) {
96
- console.log(pc.red(" Erro ao instalar Gemini CLI"))
97
- console.log(pc.dim(" Tente manualmente: npm install -g @google/gemini-cli"))
98
- return false
99
- }
100
-
101
- console.log(pc.green(" Gemini CLI instalado!"))
102
- return true
103
- }
104
-
105
- async function installGeminiMCP(): Promise<void> {
106
- console.log()
107
- console.log(pc.cyan(" Configurando MCP no Gemini..."))
108
-
109
- const settingsPath = join(GEMINI_DIR, "settings.json")
110
-
111
- // Criar diretório se não existir
112
- if (!existsSync(GEMINI_DIR)) {
113
- await Bun.$`mkdir -p ${GEMINI_DIR}`
114
- }
115
-
116
- // Ler settings existente ou criar novo
117
- let settings: Record<string, unknown> = {}
118
- if (existsSync(settingsPath)) {
119
- try {
120
- const content = await Bun.file(settingsPath).text()
121
- settings = JSON.parse(content)
122
- } catch {
123
- // Se falhar, começa do zero
124
- }
125
- }
126
-
127
- // Verificar se já tem lola-memory configurado
128
- const mcpServers = (settings.mcpServers || {}) as Record<string, unknown>
129
- if (mcpServers[LOLA_MEMORY_NAME]) {
130
- console.log(pc.green(" MCP lola-memory ja configurado no Gemini"))
131
- return
132
- }
133
-
134
- // Adicionar lola-memory
135
- mcpServers[LOLA_MEMORY_NAME] = {
136
- url: LOLA_MEMORY_URL
137
- }
138
- settings.mcpServers = mcpServers
139
-
140
- // Salvar
141
- await Bun.write(settingsPath, JSON.stringify(settings, null, 2))
142
- console.log(pc.green(" MCP lola-memory adicionado ao Gemini!"))
143
- }
144
-
145
- async function installGeminiSystemPrompt(): Promise<void> {
146
- console.log()
147
- console.log(pc.cyan(" Configurando GEMINI.md..."))
148
-
149
- // Criar diretório se não existir
150
- if (!existsSync(GEMINI_DIR)) {
151
- await Bun.$`mkdir -p ${GEMINI_DIR}`
152
- }
153
-
154
- // Copiar o agent da Lola (adaptar claude.md para gemini)
155
- const lolaAgent = join(LOLA_DIR, "agents", "claude.md")
156
-
157
- if (!existsSync(lolaAgent)) {
158
- console.log(pc.yellow(" Agent Lola nao encontrado"))
159
- console.log(pc.dim(" Rode 'nimbus lola install' primeiro"))
160
- return
161
- }
162
-
163
- // Ler o agent do Claude
164
- let content = await Bun.file(lolaAgent).text()
165
-
166
- // Remover frontmatter do Claude (não suportado no Gemini)
167
- content = content.replace(/^---[\s\S]*?---\n/, "")
168
-
169
- // Adaptar menções específicas
170
- content = content.replace(/Claude Code/g, "Gemini CLI")
171
- content = content.replace(/Claude CLI/g, "Gemini CLI")
172
-
173
- // Adicionar header para Gemini
174
- const geminiContent = `# Lola - Code Agent (Gemini)
175
-
176
- > Este arquivo configura a Lola para o Gemini CLI
177
- > Gerado automaticamente por: nimbus lola install
178
-
179
- ---
180
-
181
- ${content}`
182
-
183
- await Bun.write(GEMINI_SETTINGS, geminiContent)
184
- console.log(pc.green(" GEMINI.md criado!"))
185
- }
186
-
187
- async function createGeminiCommand(): Promise<void> {
188
- const isWindows = process.platform === "win32"
189
- const lolaAgent = GEMINI_SETTINGS
190
-
191
- console.log()
192
- console.log(pc.cyan(" Configurando comando lola-gemini..."))
193
-
194
- if (isWindows) {
195
- // Windows: adicionar função ao PowerShell profile
196
- const ps7ProfileDir = join(HOME, "Documents", "PowerShell")
197
- const profileName = "Microsoft.PowerShell_profile.ps1"
198
- const profilePath = join(ps7ProfileDir, profileName)
199
-
200
- const lolaGeminiFunction = `
201
- # Lola (Gemini) - Code Agent da nimbuslab
202
- function lola-gemini {
203
- param([Parameter(ValueFromRemainingArguments=$true)]$args)
204
- gemini @args
205
- }
206
- `
207
-
208
- if (!existsSync(ps7ProfileDir)) {
209
- await Bun.$`mkdir -p ${ps7ProfileDir}`
210
- }
211
-
212
- let profileContent = ""
213
- if (existsSync(profilePath)) {
214
- profileContent = await Bun.file(profilePath).text()
215
- }
216
-
217
- if (!profileContent.includes("function lola-gemini")) {
218
- await Bun.write(profilePath, profileContent + lolaGeminiFunction)
219
- console.log(pc.green(" Funcao lola-gemini adicionada ao PowerShell"))
220
- } else {
221
- console.log(pc.green(" Funcao lola-gemini ja existe"))
222
- }
223
- } else {
224
- // Linux/macOS: criar script em ~/.local/bin
225
- const binDir = join(HOME, ".local", "bin")
226
- const lolaGeminiScript = join(binDir, "lola-gemini")
227
-
228
- if (!existsSync(lolaGeminiScript)) {
229
- await Bun.$`mkdir -p ${binDir}`
230
-
231
- const unixScript = `#!/bin/bash
232
- # lola-gemini - Code Agent da nimbuslab (Gemini)
233
-
234
- if [[ "\$1" == "-h" || "\$1" == "--help" ]]; then
235
- echo "lola-gemini - Code Agent da nimbuslab (Gemini)"
236
- echo "Uso: lola-gemini [args]"
237
- echo " lola-gemini Abre Gemini CLI com a Lola"
238
- exit 0
239
- fi
240
-
241
- exec gemini "\$@"
242
- `
243
- await Bun.write(lolaGeminiScript, unixScript)
244
- await Bun.$`chmod +x ${lolaGeminiScript}`
245
-
246
- console.log(pc.green(` Script lola-gemini criado em ${lolaGeminiScript}`))
247
- } else {
248
- console.log(pc.green(" Script lola-gemini ja existe"))
249
- }
250
- }
251
- }
252
-
253
- async function setupGemini(): Promise<void> {
254
- // Verificar/instalar Gemini CLI
255
- if (!hasGeminiCLI()) {
256
- const installed = await installGeminiCLI()
257
- if (!installed) return
258
- } else {
259
- console.log(pc.green(" Gemini CLI ja instalado"))
260
- }
261
-
262
- // Configurar MCP
263
- await installGeminiMCP()
264
-
265
- // Configurar GEMINI.md
266
- await installGeminiSystemPrompt()
267
-
268
- // Criar comando lola-gemini
269
- await createGeminiCommand()
270
-
271
- console.log()
272
- console.log(pc.green(" Gemini configurado!"))
273
- console.log()
274
- console.log(pc.bold(" Para usar:"))
275
- console.log(pc.dim(" lola-gemini ") + pc.white("# Iniciar sessao com Gemini"))
276
- console.log(pc.dim(" gemini ") + pc.white("# Gemini puro (sem Lola)"))
277
- console.log()
278
- console.log(pc.dim(" Na primeira execucao, faca login com sua conta Google."))
279
- }
280
-
281
-
282
- async function suggestImprovement(message: string): Promise<void> {
283
- if (!message) {
284
- console.log(pc.red(" Erro: forneça uma mensagem"))
285
- console.log(pc.dim(" Uso: nimbus lola suggest \"sua sugestao aqui\""))
286
- process.exit(1)
287
- }
288
-
289
- // Verificar gh cli
290
- const ghCheck = Bun.spawnSync(["which", "gh"])
291
- if (ghCheck.exitCode !== 0) {
292
- console.log(pc.red(" Erro: GitHub CLI (gh) nao encontrado"))
293
- console.log(pc.dim(" Instale: https://cli.github.com"))
294
- process.exit(1)
295
- }
296
-
297
- // Verificar autenticacao
298
- const authCheck = Bun.spawnSync(["gh", "auth", "status"], {
299
- stdout: "pipe",
300
- stderr: "pipe",
301
- })
302
- if (authCheck.exitCode !== 0) {
303
- console.log(pc.red(" Erro: GitHub CLI nao autenticado"))
304
- console.log(pc.dim(" Execute: gh auth login"))
305
- process.exit(1)
306
- }
307
-
308
- // Pegar info do usuario
309
- const gitUser = Bun.spawnSync(["git", "config", "user.name"], { stdout: "pipe" })
310
- const gitEmail = Bun.spawnSync(["git", "config", "user.email"], { stdout: "pipe" })
311
-
312
- const userName = gitUser.stdout.toString().trim() || "Dev"
313
- const userEmail = gitEmail.stdout.toString().trim() || ""
314
-
315
- console.log()
316
- console.log(pc.cyan(" Criando sugestao..."))
317
-
318
- const date = new Date().toISOString().split("T")[0]
319
- const body = `## Sugestao
320
-
321
- ${message}
322
-
323
- ---
324
-
325
- **Enviado por:** ${userName}
326
- **Email:** ${userEmail}
327
- **Data:** ${date}
328
-
329
- ---
330
- *Criado via \`nimbus lola suggest\`*`
331
-
332
- const title = `Sugestao: ${message.slice(0, 50)}${message.length > 50 ? "..." : ""}`
333
-
334
- const result = Bun.spawnSync([
335
- "gh", "issue", "create",
336
- "--repo", "nimbuslab/lola",
337
- "--title", title,
338
- "--body", body,
339
- "--label", "sugestao",
340
- ], {
341
- stdout: "inherit",
342
- stderr: "inherit",
343
- })
344
-
345
- if (result.exitCode !== 0) {
346
- console.log(pc.red(" Erro ao criar issue"))
347
- process.exit(1)
348
- }
349
-
350
- console.log()
351
- console.log(pc.green(" Sugestao enviada! Hugo vai revisar."))
352
- console.log()
353
- }
354
-
355
- const PROFILES = {
356
- millennial: {
357
- label: "Millennial",
358
- hint: "Nerd 90s/2000s, DBZ, Matrix, MSN",
359
- emoji: "",
360
- },
361
- genz: {
362
- label: "Gen Z",
363
- hint: "Direto, girias atuais, slay",
364
- emoji: "",
365
- },
366
- profissional: {
367
- label: "Profissional",
368
- hint: "100% tecnico, sem referencias",
369
- emoji: "",
370
- },
371
- nerd: {
372
- label: "Nerd",
373
- hint: "Star Trek, vim jokes, deep tech",
374
- emoji: "",
375
- },
376
- chill: {
377
- label: "Chill",
378
- hint: "Relaxado, vibes cafe, positivo",
379
- emoji: "",
380
- },
381
- }
382
-
383
- async function onboardDev(): Promise<void> {
384
- console.log()
385
- console.log(pc.cyan(" Lola - Onboarding"))
386
- console.log(pc.dim(" ================="))
387
- console.log()
388
-
389
- // Verificar se Lola está instalada
390
- if (!existsSync(LOLA_DIR)) {
391
- console.log(pc.yellow(" Lola nao instalada. Instalando primeiro..."))
392
- console.log()
393
- await installLolaBase()
394
- console.log()
395
- }
396
-
397
- // Pegar nome do git config
398
- const gitUser = Bun.spawnSync(["git", "config", "user.name"], { stdout: "pipe" })
399
- const defaultName = gitUser.stdout.toString().trim() || ""
400
-
401
- p.intro(pc.bgCyan(pc.black(" Bem-vindo a nimbuslab! ")))
402
-
403
- // Nome do dev
404
- const devName = await p.text({
405
- message: "Qual seu nome?",
406
- placeholder: "Seu nome",
407
- initialValue: defaultName,
408
- validate: (v) => v ? undefined : "Nome e obrigatorio",
409
- })
410
-
411
- if (p.isCancel(devName)) {
412
- p.cancel("Onboarding cancelado")
413
- process.exit(0)
414
- }
415
-
416
- // Perfil preferido
417
- const profile = await p.select({
418
- message: "Qual perfil da Lola voce prefere?",
419
- options: Object.entries(PROFILES).map(([value, { label, hint }]) => ({
420
- value,
421
- label,
422
- hint,
423
- })),
424
- })
425
-
426
- if (p.isCancel(profile)) {
427
- p.cancel("Onboarding cancelado")
428
- process.exit(0)
429
- }
430
-
431
- // Atualizar USER_MEMORY.md
432
- const claudeDir = join(HOME, ".claude")
433
- await Bun.$`mkdir -p ${claudeDir}`
434
-
435
- const content = `# User Memory
436
-
437
- Memoria persistente para sessoes Claude Code
438
-
439
- ---
440
-
441
- ## Dev
442
-
443
- **Nome:** ${devName}
444
- **Maquina:** ${process.env.HOSTNAME || "local"}
445
- **Onboarding:** ${new Date().toISOString().split("T")[0]}
446
-
447
- ---
448
-
449
- ## Configuracoes da Lola
450
-
451
- \`\`\`
452
- lola_profile: ${profile}
453
- \`\`\`
454
-
455
- ---
456
-
457
- ## Ultima Sessao
458
-
459
- (sera preenchido automaticamente)
460
-
461
- ---
462
- `
463
-
464
- await Bun.write(USER_MEMORY, content)
465
-
466
- p.outro(pc.green("Onboarding concluido!"))
467
-
468
- // Resumo
469
- console.log()
470
- console.log(pc.bold(" Resumo:"))
471
- console.log(pc.dim(" Nome: ") + pc.white(devName as string))
472
- console.log(pc.dim(" Perfil: ") + pc.white(PROFILES[profile as keyof typeof PROFILES].label))
473
- console.log()
474
- console.log(pc.bold(" Proximos passos:"))
475
- console.log(pc.dim(" 1. ") + pc.white("lola") + pc.dim(" - Iniciar sessao com a Lola"))
476
- console.log(pc.dim(" 2. ") + pc.white("nimbus create meu-projeto --fast") + pc.dim(" - Criar projeto"))
477
- console.log(pc.dim(" 3. ") + pc.white("lola suggest \"sua ideia\"") + pc.dim(" - Sugerir melhoria"))
478
- console.log()
479
- console.log(pc.dim(" Docs: ~/.lola/README.md"))
480
- console.log()
481
- }
482
-
483
- // Quiz sobre a nimbuslab
484
- interface QuizQuestion {
485
- question: string
486
- options: { value: string; label: string }[]
487
- correct: string
488
- explanation: string
489
- }
490
-
491
- const QUIZ_QUESTIONS: QuizQuestion[] = [
492
- {
493
- question: "Quais sao os 4 valores da nimbuslab?",
494
- options: [
495
- { value: "a", label: "Velocidade, Qualidade, Preco, Entrega" },
496
- { value: "b", label: "Cocriacao, Inovacao, Evolucao, Humanizacao" },
497
- { value: "c", label: "Codigo, Design, Marketing, Vendas" },
498
- { value: "d", label: "Agil, Lean, Scrum, Kanban" },
499
- ],
500
- correct: "b",
501
- explanation: "Cocriacao (construimos junto), Inovacao (buscamos o novo), Evolucao (aprendizado continuo), Humanizacao (tecnologia a servico das pessoas).",
502
- },
503
- {
504
- question: "Quais sao os 4 pilares da filosofia fast?",
505
- options: [
506
- { value: "a", label: "Analise, Planejamento, Execucao, Documentacao" },
507
- { value: "b", label: "Design, Codigo, Teste, Deploy" },
508
- { value: "c", label: "Briefing, Prototipo, Desenvolvimento, Lancamento" },
509
- { value: "d", label: "Discovery, Definition, Development, Delivery" },
510
- ],
511
- correct: "a",
512
- explanation: "Filosofia fast: 1. Analise (entender), 2. Planejamento (definir caminho), 3. Execucao (implementar), 4. Documentacao (registrar).",
513
- },
514
- {
515
- question: "Qual package manager a nimbuslab usa como padrao?",
516
- options: [
517
- { value: "a", label: "npm" },
518
- { value: "b", label: "yarn" },
519
- { value: "c", label: "pnpm" },
520
- { value: "d", label: "bun" },
521
- ],
522
- correct: "d",
523
- explanation: "Bun e o package manager padrao. Mais rapido e com menos dependencias.",
524
- },
525
- {
526
- question: "Qual o estilo padrao do shadcn/ui na nimbuslab?",
527
- options: [
528
- { value: "a", label: "new-york" },
529
- { value: "b", label: "default" },
530
- { value: "c", label: "minimal" },
531
- { value: "d", label: "custom" },
532
- ],
533
- correct: "b",
534
- explanation: "Usamos o estilo 'default' do shadcn/ui. Nunca 'new-york'.",
535
- },
536
- {
537
- question: "Em projetos externos (stealth mode), a Lola deve:",
538
- options: [
539
- { value: "a", label: "Sempre mencionar a nimbuslab nos commits" },
540
- { value: "b", label: "Usar assinatura 'Co-authored-by: Lola'" },
541
- { value: "c", label: "Nunca mencionar nimbuslab, Lola ou IA" },
542
- { value: "d", label: "Adicionar badge da nimbuslab no README" },
543
- ],
544
- correct: "c",
545
- explanation: "Stealth mode: commits sem mencao a nimbuslab/Lola/IA. O cliente nao precisa saber dos bastidores.",
546
- },
547
- {
548
- question: "Qual a versao minima do Next.js usada na stack nimbuslab?",
549
- options: [
550
- { value: "a", label: "Next.js 13" },
551
- { value: "b", label: "Next.js 14" },
552
- { value: "c", label: "Next.js 15" },
553
- { value: "d", label: "Next.js 16" },
554
- ],
555
- correct: "d",
556
- explanation: "Stack atual: Next.js 16+ com App Router e Turbopack.",
557
- },
558
- {
559
- question: "Quem pode aprovar e mergear mudancas no repositorio da Lola?",
560
- options: [
561
- { value: "a", label: "Qualquer dev da nimbuslab" },
562
- { value: "b", label: "Apenas o Hugo" },
563
- { value: "c", label: "Qualquer pessoa com acesso ao repo" },
564
- { value: "d", label: "A propria Lola via automacao" },
565
- ],
566
- correct: "b",
567
- explanation: "Apenas o Hugo pode aprovar e mergear. Devs sugerem via 'lola suggest', Hugo revisa.",
568
- },
569
- {
570
- question: "Em commits da nimbuslab, qual o idioma correto?",
571
- options: [
572
- { value: "a", label: "Ingles" },
573
- { value: "b", label: "Portugues (BR)" },
574
- { value: "c", label: "Depende do projeto" },
575
- { value: "d", label: "Spanglish" },
576
- ],
577
- correct: "b",
578
- explanation: "Commits e PRs em Portugues (BR). Codigo e comentarios em Ingles.",
579
- },
580
- ]
581
-
582
- async function runQuiz(): Promise<void> {
583
- console.log()
584
- console.log(pc.cyan(" Quiz nimbuslab"))
585
- console.log(pc.dim(" =============="))
586
- console.log()
587
- console.log(pc.dim(" Teste seus conhecimentos sobre a nimbuslab!"))
588
- console.log(pc.dim(" 8 perguntas sobre valores, filosofia e stack."))
589
- console.log()
590
-
591
- let score = 0
592
- const results: { question: string; correct: boolean; explanation: string }[] = []
593
-
594
- for (let i = 0; i < QUIZ_QUESTIONS.length; i++) {
595
- const q = QUIZ_QUESTIONS[i]!
596
-
597
- const answer = await p.select({
598
- message: `${i + 1}. ${q.question}`,
599
- options: q.options,
600
- })
601
-
602
- if (p.isCancel(answer)) {
603
- p.cancel("Quiz cancelado")
604
- process.exit(0)
605
- }
606
-
607
- const isCorrect = answer === q.correct
608
- if (isCorrect) {
609
- score++
610
- console.log(pc.green(" Correto!"))
611
- } else {
612
- console.log(pc.red(" Incorreto."))
613
- }
614
- console.log(pc.dim(` ${q.explanation}`))
615
- console.log()
616
-
617
- results.push({
618
- question: q.question,
619
- correct: isCorrect,
620
- explanation: q.explanation,
621
- })
622
- }
623
-
624
- // Resultado final
625
- const percentage = Math.round((score / QUIZ_QUESTIONS.length) * 100)
626
- const passed = percentage >= 75
627
-
628
- console.log(pc.bold(" Resultado:"))
629
- console.log()
630
-
631
- if (passed) {
632
- console.log(pc.green(` ${score}/${QUIZ_QUESTIONS.length} (${percentage}%) - Aprovado!`))
633
- console.log()
634
- if (percentage === 100) {
635
- console.log(pc.cyan(" Perfeito! Voce conhece bem a nimbuslab."))
636
- } else {
637
- console.log(pc.cyan(" Muito bem! Voce esta pronto para trabalhar."))
638
- }
639
- } else {
640
- console.log(pc.yellow(` ${score}/${QUIZ_QUESTIONS.length} (${percentage}%) - Precisa revisar`))
641
- console.log()
642
- console.log(pc.dim(" Revise os conceitos em:"))
643
- console.log(pc.dim(" ~/.lola/core/values.md"))
644
- console.log(pc.dim(" ~/.lola/core/philosophy.md"))
645
- console.log(pc.dim(" ~/.lola/modules/stack.md"))
646
- }
647
- console.log()
648
- }
649
-
650
- async function installInteractive(): Promise<void> {
651
- console.log()
652
- console.log(pc.cyan(" Lola - Code Agent da nimbuslab"))
653
- console.log(pc.dim(" ==============================="))
654
- console.log()
655
-
656
- // Detectar CLIs instaladas
657
- const hasClaude = hasClaudeCLI()
658
- const hasGemini = hasGeminiCLI()
659
-
660
- console.log(pc.dim(" Detectando agents..."))
661
- console.log(pc.dim(` Claude CLI: ${hasClaude ? pc.green("instalado") : pc.yellow("nao encontrado")}`))
662
- console.log(pc.dim(` Gemini CLI: ${hasGemini ? pc.green("instalado") : pc.yellow("nao encontrado")}`))
663
- console.log()
664
-
665
- // Perguntar qual agent configurar
666
- const agentChoice = await p.select({
667
- message: "Qual agent deseja configurar?",
668
- options: [
669
- {
670
- value: "all",
671
- label: "Todos (Recomendado)",
672
- hint: "Configura Claude e Gemini",
673
- },
674
- {
675
- value: "claude",
676
- label: "Apenas Claude",
677
- hint: hasClaude ? "Ja instalado" : "Sera instalado",
678
- },
679
- {
680
- value: "gemini",
681
- label: "Apenas Gemini",
682
- hint: hasGemini ? "Ja instalado" : "Sera instalado",
683
- },
684
- ],
685
- })
686
-
687
- if (p.isCancel(agentChoice)) {
688
- p.cancel("Instalacao cancelada")
689
- process.exit(0)
690
- }
691
-
692
- const choice = agentChoice as AgentType
693
-
694
- // Instalar ~/.lola primeiro (necessário para ambos)
695
- await installLolaBase()
696
-
697
- // Configurar agents escolhidos
698
- if (choice === "claude" || choice === "all") {
699
- console.log()
700
- console.log(pc.bgBlue(pc.white(" CLAUDE ")))
701
- await setupClaude()
702
- }
703
-
704
- if (choice === "gemini" || choice === "all") {
705
- console.log()
706
- console.log(pc.bgMagenta(pc.white(" GEMINI ")))
707
- await setupGemini()
708
- }
709
-
710
- // Resumo final
711
- console.log()
712
- console.log(pc.green(" ==============================="))
713
- console.log(pc.green(" Instalacao concluida!"))
714
- console.log(pc.green(" ==============================="))
715
- console.log()
716
-
717
- if (choice === "claude" || choice === "all") {
718
- console.log(pc.dim(" lola ") + pc.white("# Claude com Lola"))
719
- }
720
- if (choice === "gemini" || choice === "all") {
721
- console.log(pc.dim(" lola-gemini ") + pc.white("# Gemini com Lola"))
722
- }
723
- console.log()
724
- console.log(pc.bold(" lola-memory (compartilhado entre agents):"))
725
- console.log(pc.dim(" remember, learn, memory_stats"))
726
- console.log()
727
- }
728
-
729
- async function installLolaBase(): Promise<void> {
730
- console.log()
731
- console.log(pc.cyan(" Instalando base Lola (~/.lola)..."))
732
-
733
- const isUpdate = existsSync(LOLA_DIR)
734
-
735
- if (isUpdate) {
736
- console.log(pc.dim(` Lola ja instalada em ${LOLA_DIR}`))
737
- console.log(pc.dim(" Atualizando..."))
738
-
739
- // Verificar se tem mudancas locais
740
- const statusCheck = Bun.spawnSync(["git", "status", "--porcelain"], {
741
- cwd: LOLA_DIR,
742
- stdout: "pipe",
743
- })
744
- const hasLocalChanges = statusCheck.stdout.toString().trim().length > 0
745
-
746
- // Stash se tiver mudancas locais
747
- if (hasLocalChanges) {
748
- console.log(pc.dim(" Salvando mudancas locais..."))
749
- Bun.spawnSync(["git", "stash", "--quiet"], {
750
- cwd: LOLA_DIR,
751
- stdout: "pipe",
752
- stderr: "pipe",
753
- })
754
- }
755
-
756
- // Pull
757
- const result = Bun.spawnSync(["git", "pull", "--quiet"], {
758
- cwd: LOLA_DIR,
759
- stdout: "inherit",
760
- stderr: "inherit",
761
- })
762
-
763
- // Restaurar mudancas locais se tinha
764
- if (hasLocalChanges) {
765
- console.log(pc.dim(" Restaurando mudancas locais..."))
766
- const stashPop = Bun.spawnSync(["git", "stash", "pop", "--quiet"], {
767
- cwd: LOLA_DIR,
768
- stdout: "pipe",
769
- stderr: "pipe",
770
- })
771
-
772
- if (stashPop.exitCode !== 0) {
773
- console.log(pc.yellow(" Aviso: conflito ao restaurar mudancas locais"))
774
- console.log(pc.dim(" Verifique ~/.lola e resolva manualmente: git stash pop"))
775
- }
776
- }
777
-
778
- if (result.exitCode !== 0) {
779
- console.log(pc.red(" Erro ao atualizar Lola"))
780
- process.exit(1)
781
- }
782
-
783
- console.log(pc.green(" Atualizado!"))
784
- } else {
785
- console.log(pc.dim(` Clonando em ${LOLA_DIR}...`))
786
-
787
- const result = Bun.spawnSync(["git", "clone", "--quiet", LOLA_REPO, LOLA_DIR], {
788
- stdout: "inherit",
789
- stderr: "inherit",
790
- })
791
-
792
- if (result.exitCode !== 0) {
793
- console.log(pc.red(" Erro ao clonar repositorio"))
794
- console.log(pc.dim(" Verifique se tem acesso ao repo nimbuslab/lola"))
795
- process.exit(1)
796
- }
797
-
798
- console.log(pc.green(" Instalado!"))
799
- }
800
- }
801
-
802
- async function setupClaude(): Promise<void> {
803
- if (!hasClaudeCLI()) {
804
- console.log(pc.yellow(" Claude CLI nao encontrado"))
805
- console.log(pc.dim(" Instale: https://claude.ai/download"))
806
- console.log(pc.dim(" Depois rode 'nimbus lola install' novamente"))
807
- return
808
- }
809
-
810
- console.log(pc.green(" Claude CLI encontrado"))
811
-
812
- // Criar comando lola
813
- const isWindows = process.platform === "win32"
814
- const lolaAgent = join(LOLA_DIR, "agents", "claude.md")
815
-
816
- console.log()
817
- console.log(pc.cyan(" Configurando comando lola..."))
818
-
819
- if (isWindows) {
820
- const ps5ProfileDir = join(HOME, "Documents", "WindowsPowerShell")
821
- const ps7ProfileDir = join(HOME, "Documents", "PowerShell")
822
- const profileName = "Microsoft.PowerShell_profile.ps1"
823
-
824
- const lolaFunction = `
825
- # Lola - Code Agent da nimbuslab
826
- function lola {
827
- param([Parameter(ValueFromRemainingArguments=$true)]$args)
828
- $agent = "$env:USERPROFILE\\.lola\\agents\\claude.md"
829
- if (Test-Path $agent) {
830
- claude --append-system-prompt-file $agent @args
831
- } else {
832
- Write-Host "Agente Lola nao encontrado. Rode: nimbus lola install"
833
- }
834
- }
835
- `
836
-
837
- const profiles = [
838
- { dir: ps5ProfileDir, name: "PowerShell 5.x" },
839
- { dir: ps7ProfileDir, name: "PowerShell 7+" },
840
- ]
841
-
842
- let addedToAny = false
843
-
844
- for (const { dir, name } of profiles) {
845
- const profilePath = join(dir, profileName)
846
-
847
- if (!existsSync(dir)) {
848
- await Bun.$`mkdir -p ${dir}`
849
- }
850
-
851
- let profileContent = ""
852
- if (existsSync(profilePath)) {
853
- profileContent = await Bun.file(profilePath).text()
854
- }
855
-
856
- if (!profileContent.includes("function lola")) {
857
- await Bun.write(profilePath, profileContent + lolaFunction)
858
- console.log(pc.green(` Funcao lola adicionada ao ${name} profile`))
859
- addedToAny = true
860
- }
861
- }
862
-
863
- if (addedToAny) {
864
- console.log(pc.yellow(" Reinicie o PowerShell para usar o comando 'lola'"))
865
- } else {
866
- console.log(pc.green(" Funcao lola ja existe"))
867
- }
868
- } else {
869
- const binDir = join(HOME, ".local", "bin")
870
- const lolaScript = join(binDir, "lola")
871
-
872
- if (!existsSync(lolaScript)) {
873
- await Bun.$`mkdir -p ${binDir}`
874
-
875
- const unixScript = `#!/bin/bash
876
- # lola - Code Agent da nimbuslab
877
- LOLA_AGENT="${lolaAgent}"
878
-
879
- if [[ "\$1" == "-h" || "\$1" == "--help" ]]; then
880
- echo "lola - Code Agent da nimbuslab"
881
- echo "Uso: lola [args]"
882
- echo " lola Abre Claude CLI com a Lola"
883
- echo " lola --resume Resume sessao anterior"
884
- exit 0
885
- fi
886
-
887
- if [[ -f "\$LOLA_AGENT" ]]; then
888
- exec claude --append-system-prompt-file "\$LOLA_AGENT" "\$@"
889
- else
890
- echo "Agente Lola nao encontrado: \$LOLA_AGENT"
891
- echo "Rode: nimbus lola install"
892
- exit 1
893
- fi
894
- `
895
- await Bun.write(lolaScript, unixScript)
896
- await Bun.$`chmod +x ${lolaScript}`
897
-
898
- console.log(pc.green(` Script lola criado em ${lolaScript}`))
899
-
900
- const pathEnv = process.env.PATH || ""
901
- if (!pathEnv.includes(".local/bin")) {
902
- console.log()
903
- console.log(pc.yellow(" IMPORTANTE: Adicione ~/.local/bin ao seu PATH"))
904
- console.log(pc.dim(" export PATH=\"$HOME/.local/bin:$PATH\""))
905
- }
906
- } else {
907
- console.log(pc.green(" Script lola ja existe"))
908
- }
909
- }
910
-
911
- // Instalar MCP lola-memory
912
- await installLolaMemoryMCP()
913
-
914
- // Verificar USER_MEMORY.md
915
- const claudeDir = join(HOME, ".claude")
916
- if (!existsSync(USER_MEMORY)) {
917
- console.log()
918
- console.log(pc.cyan(" Configurando USER_MEMORY.md..."))
919
-
920
- const gitUserResult = Bun.spawnSync(["git", "config", "user.name"], { stdout: "pipe" })
921
- const gitEmailResult = Bun.spawnSync(["git", "config", "user.email"], { stdout: "pipe" })
922
-
923
- const gitUser = gitUserResult.stdout.toString().trim() || "Dev"
924
- const gitEmail = gitEmailResult.stdout.toString().trim() || ""
925
- const hostname = process.env.HOSTNAME || process.env.COMPUTERNAME || "local"
926
- const today = new Date().toISOString().split("T")[0]
927
-
928
- await Bun.$`mkdir -p ${claudeDir}`
929
-
930
- const content = `# User Memory
931
-
932
- Memoria persistente para sessoes Claude Code
933
-
934
- ---
935
-
936
- ## Dev
937
-
938
- **Nome:** ${gitUser}
939
- **Email:** ${gitEmail}
940
- **Maquina:** ${hostname}
941
- **Instalacao:** ${today}
942
-
943
- ---
944
-
945
- ## Configuracoes da Lola
946
-
947
- \`\`\`
948
- lola_profile: millennial
949
- \`\`\`
950
-
951
- ---
952
-
953
- ## Ultima Sessao
954
-
955
- (sera preenchido automaticamente)
956
-
957
- ---
958
- `
959
- await Bun.write(USER_MEMORY, content)
960
- console.log(pc.green(` USER_MEMORY.md criado para ${gitUser}!`))
961
- }
962
-
963
- console.log()
964
- console.log(pc.green(" Claude configurado!"))
965
- }
966
-
967
- // Aliases para subcomandos
968
- const SUBCOMMAND_ALIASES: Record<string, string> = {
969
- i: "install",
970
- s: "suggest",
971
- o: "onboard",
972
- q: "quiz",
973
- h: "help",
974
- sync: "install",
975
- "--help": "help",
976
- "-h": "help",
977
- }
978
-
979
- export async function lola(args: string[]) {
980
- const rawSubcommand = args[0]
981
- const subcommand = rawSubcommand ? (SUBCOMMAND_ALIASES[rawSubcommand] || rawSubcommand) : null
982
-
983
- if (!subcommand || subcommand === "install") {
984
- await installInteractive()
985
- } else if (subcommand === "onboard") {
986
- await onboardDev()
987
- } else if (subcommand === "quiz") {
988
- await runQuiz()
989
- } else if (subcommand === "suggest") {
990
- const message = args.slice(1).join(" ")
991
- await suggestImprovement(message)
992
- } else if (subcommand === "help") {
993
- showLolaHelp()
994
- } else {
995
- console.log(pc.red(` Subcomando desconhecido: ${rawSubcommand}`))
996
- showLolaHelp()
997
- process.exit(1)
998
- }
999
- }
1000
-
1001
- function showLolaHelp() {
1002
- console.log()
1003
- console.log(pc.bold(" Lola - Code Agent da nimbuslab"))
1004
- console.log()
1005
- console.log(pc.bold(" Uso:") + " nimbus lola [subcomando]")
1006
- console.log()
1007
- console.log(pc.bold(" Subcomandos:"))
1008
- console.log(pc.dim(" install, i ") + pc.white("Instalar/atualizar Lola + MCP"))
1009
- console.log(pc.dim(" onboard, o ") + pc.white("Configurar perfil (novos devs)"))
1010
- console.log(pc.dim(" quiz, q ") + pc.white("Quiz sobre a nimbuslab"))
1011
- console.log(pc.dim(" suggest, s ") + pc.white("Sugerir melhoria (cria issue)"))
1012
- console.log(pc.dim(" help, h ") + pc.white("Mostrar esta ajuda"))
1013
- console.log()
1014
- console.log(pc.bold(" Exemplos:"))
1015
- console.log(pc.dim(" $ nimbus lola i"))
1016
- console.log(pc.dim(" $ nimbus lola o"))
1017
- console.log(pc.dim(" $ nimbus lola q"))
1018
- console.log(pc.dim(" $ nimbus lola s \"adicionar suporte a X\""))
1019
- console.log()
1020
- console.log(pc.bold(" lola-memory (dentro do Claude):"))
1021
- console.log(pc.dim(" remember \"query\" ") + pc.white("Buscar conhecimento"))
1022
- console.log(pc.dim(" learn \"content\" ") + pc.white("Salvar conhecimento"))
1023
- console.log(pc.dim(" memory_stats ") + pc.white("Ver estatisticas"))
1024
- console.log(pc.dim(" get_context ") + pc.white("Perfil + memorias recentes"))
1025
- console.log()
1026
- }