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