ocerebro 0.1.8 → 0.2.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/cerebro/cerebro_setup.py +575 -584
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/cli/main.py +5 -0
package/cerebro/cerebro_setup.py
CHANGED
|
@@ -1,584 +1,575 @@
|
|
|
1
|
-
"""Setup Automático do OCerebro
|
|
2
|
-
|
|
3
|
-
Detecta e configura o Claude Desktop e Claude Code automaticamente.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import json
|
|
7
|
-
import os
|
|
8
|
-
import subprocess
|
|
9
|
-
import sys
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
from typing import Optional
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def find_all_claude_configs() -> dict:
|
|
15
|
-
"""Encontra todas as configurações do Claude (Desktop e Code).
|
|
16
|
-
|
|
17
|
-
Returns:
|
|
18
|
-
dict com chaves "desktop" e "code", cada uma contendo Path | None
|
|
19
|
-
"""
|
|
20
|
-
result = {"desktop": None, "code": None}
|
|
21
|
-
|
|
22
|
-
# Claude Desktop: claude_desktop.json
|
|
23
|
-
desktop_locations = []
|
|
24
|
-
if sys.platform == "win32":
|
|
25
|
-
appdata = os.environ.get("APPDATA", "")
|
|
26
|
-
desktop_locations.extend([
|
|
27
|
-
Path(appdata) / "Claude" / "claude_desktop.json",
|
|
28
|
-
Path.home() / "AppData" / "Roaming" / "Claude" / "claude_desktop.json",
|
|
29
|
-
Path.home() / ".claude" / "claude_desktop.json",
|
|
30
|
-
])
|
|
31
|
-
elif sys.platform == "darwin":
|
|
32
|
-
desktop_locations.extend([
|
|
33
|
-
Path.home() / "Library" / "Application Support" / "Claude" / "claude_desktop.json",
|
|
34
|
-
Path.home() / ".claude" / "claude_desktop.json",
|
|
35
|
-
])
|
|
36
|
-
elif sys.platform == "linux":
|
|
37
|
-
xdg_config = os.environ.get("XDG_CONFIG_HOME", str(Path.home() / ".config"))
|
|
38
|
-
desktop_locations.extend([
|
|
39
|
-
Path(xdg_config) / "Claude" / "claude_desktop.json",
|
|
40
|
-
Path.home() / ".claude" / "claude_desktop.json",
|
|
41
|
-
])
|
|
42
|
-
|
|
43
|
-
# Claude Code: settings.json
|
|
44
|
-
code_locations = []
|
|
45
|
-
if sys.platform == "win32":
|
|
46
|
-
appdata = os.environ.get("APPDATA", "")
|
|
47
|
-
code_locations.extend([
|
|
48
|
-
Path(appdata) / "Claude" / "settings.json",
|
|
49
|
-
Path.home() / "AppData" / "Roaming" / "Claude" / "settings.json",
|
|
50
|
-
])
|
|
51
|
-
elif sys.platform == "darwin":
|
|
52
|
-
code_locations.extend([
|
|
53
|
-
Path.home() / "Library" / "Application Support" / "Claude" / "settings.json",
|
|
54
|
-
])
|
|
55
|
-
elif sys.platform == "linux":
|
|
56
|
-
xdg_config = os.environ.get("XDG_CONFIG_HOME", str(Path.home() / ".config"))
|
|
57
|
-
code_locations.extend([
|
|
58
|
-
Path(xdg_config) / "Claude" / "settings.json",
|
|
59
|
-
])
|
|
60
|
-
|
|
61
|
-
# Universal (funciona para ambos)
|
|
62
|
-
universal_claude = Path.home() / ".claude"
|
|
63
|
-
if universal_claude.exists():
|
|
64
|
-
if result["desktop"] is None and (universal_claude / "claude_desktop.json").exists():
|
|
65
|
-
result["desktop"] = universal_claude / "claude_desktop.json"
|
|
66
|
-
if result["code"] is None and (universal_claude / "settings.json").exists():
|
|
67
|
-
result["code"] = universal_claude / "settings.json"
|
|
68
|
-
|
|
69
|
-
# Encontra primeiro desktop que existe
|
|
70
|
-
if result["desktop"] is None:
|
|
71
|
-
for loc in desktop_locations:
|
|
72
|
-
if loc.exists():
|
|
73
|
-
result["desktop"] = loc
|
|
74
|
-
break
|
|
75
|
-
else:
|
|
76
|
-
# Nenhum existe, retorna o primeiro para criar
|
|
77
|
-
result["desktop"] = desktop_locations[0] if desktop_locations else None
|
|
78
|
-
|
|
79
|
-
# Encontra primeiro code que existe
|
|
80
|
-
if result["code"] is None:
|
|
81
|
-
for loc in code_locations:
|
|
82
|
-
if loc.exists():
|
|
83
|
-
result["code"] = loc
|
|
84
|
-
break
|
|
85
|
-
else:
|
|
86
|
-
# Nenhum existe, retorna o primeiro para criar
|
|
87
|
-
result["code"] = code_locations[0] if code_locations else None
|
|
88
|
-
|
|
89
|
-
return result
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def find_claude_desktop_config() -> Path | None:
|
|
93
|
-
"""Encontra o arquivo claude_desktop.json em várias localizações.
|
|
94
|
-
|
|
95
|
-
Legado: use find_all_claude_configs() para suporte completo.
|
|
96
|
-
"""
|
|
97
|
-
configs = find_all_claude_configs()
|
|
98
|
-
return configs.get("desktop")
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def get_ocerebro_path() -> Path:
|
|
102
|
-
"""Retorna o caminho absoluto do OCerebro instalado"""
|
|
103
|
-
# Tenta encontrar o package instalado via pip
|
|
104
|
-
try:
|
|
105
|
-
import cerebro
|
|
106
|
-
cerebro_path = Path(cerebro.__file__).parent
|
|
107
|
-
return cerebro_path.resolve()
|
|
108
|
-
except ImportError:
|
|
109
|
-
pass
|
|
110
|
-
|
|
111
|
-
# Fallback: usa pip show para encontrar o Location
|
|
112
|
-
try:
|
|
113
|
-
result = subprocess.run(
|
|
114
|
-
[sys.executable, "-m", "pip", "show", "ocerebro"],
|
|
115
|
-
capture_output=True,
|
|
116
|
-
text=True
|
|
117
|
-
)
|
|
118
|
-
if result.returncode == 0:
|
|
119
|
-
for line in result.stdout.splitlines():
|
|
120
|
-
if line.startswith("Location:"):
|
|
121
|
-
return Path(line.split(":", 1)[1].strip())
|
|
122
|
-
except Exception:
|
|
123
|
-
pass
|
|
124
|
-
|
|
125
|
-
# Último fallback: usa o path do próprio arquivo
|
|
126
|
-
return Path(__file__).parent.resolve()
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
def generate_mcp_config(ocerebro_path: Path) -> dict:
|
|
130
|
-
"""Gera configuração MCP para o OCerebro com suporte robusto a paths.
|
|
131
|
-
|
|
132
|
-
SECURITY: Não salva API keys no config file.
|
|
133
|
-
As variáveis de ambiente são herdadas do sistema.
|
|
134
|
-
Configure no seu shell: ~/.bashrc ou ~/.zshrc
|
|
135
|
-
"""
|
|
136
|
-
|
|
137
|
-
# Determina o comando Python
|
|
138
|
-
python_cmd = sys.executable
|
|
139
|
-
|
|
140
|
-
# Estratégia 1: usa python -m ocerebro.mcp (robusto para pip install)
|
|
141
|
-
mcp_config = {
|
|
142
|
-
"command": python_cmd,
|
|
143
|
-
"args": ["-m", "src.mcp.server"],
|
|
144
|
-
"cwd": str(ocerebro_path.parent),
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
# Estratégia 2: path direto se arquivo existe
|
|
148
|
-
mcp_server = ocerebro_path / "src" / "mcp" / "server.py"
|
|
149
|
-
if not mcp_server.exists():
|
|
150
|
-
mcp_server = ocerebro_path.parent / "src" / "mcp" / "server.py"
|
|
151
|
-
|
|
152
|
-
if mcp_server.exists():
|
|
153
|
-
mcp_config["args"] = [str(mcp_server)]
|
|
154
|
-
|
|
155
|
-
# SECURITY: NÃO salvar API keys no config
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
backup_path
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
return
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
def
|
|
298
|
-
"""Encontra o
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
""
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
"
|
|
534
|
-
|
|
535
|
-
print(f"
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
success
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
project
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
print()
|
|
577
|
-
|
|
578
|
-
setup_ocerebro_dir()
|
|
579
|
-
setup_hooks()
|
|
580
|
-
setup_claude(auto=True)
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
if __name__ == "__main__":
|
|
584
|
-
main()
|
|
1
|
+
"""Setup Automático do OCerebro
|
|
2
|
+
|
|
3
|
+
Detecta e configura o Claude Desktop e Claude Code automaticamente.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def find_all_claude_configs() -> dict:
|
|
15
|
+
"""Encontra todas as configurações do Claude (Desktop e Code).
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
dict com chaves "desktop" e "code", cada uma contendo Path | None
|
|
19
|
+
"""
|
|
20
|
+
result = {"desktop": None, "code": None}
|
|
21
|
+
|
|
22
|
+
# Claude Desktop: claude_desktop.json
|
|
23
|
+
desktop_locations = []
|
|
24
|
+
if sys.platform == "win32":
|
|
25
|
+
appdata = os.environ.get("APPDATA", "")
|
|
26
|
+
desktop_locations.extend([
|
|
27
|
+
Path(appdata) / "Claude" / "claude_desktop.json",
|
|
28
|
+
Path.home() / "AppData" / "Roaming" / "Claude" / "claude_desktop.json",
|
|
29
|
+
Path.home() / ".claude" / "claude_desktop.json",
|
|
30
|
+
])
|
|
31
|
+
elif sys.platform == "darwin":
|
|
32
|
+
desktop_locations.extend([
|
|
33
|
+
Path.home() / "Library" / "Application Support" / "Claude" / "claude_desktop.json",
|
|
34
|
+
Path.home() / ".claude" / "claude_desktop.json",
|
|
35
|
+
])
|
|
36
|
+
elif sys.platform == "linux":
|
|
37
|
+
xdg_config = os.environ.get("XDG_CONFIG_HOME", str(Path.home() / ".config"))
|
|
38
|
+
desktop_locations.extend([
|
|
39
|
+
Path(xdg_config) / "Claude" / "claude_desktop.json",
|
|
40
|
+
Path.home() / ".claude" / "claude_desktop.json",
|
|
41
|
+
])
|
|
42
|
+
|
|
43
|
+
# Claude Code: settings.json
|
|
44
|
+
code_locations = []
|
|
45
|
+
if sys.platform == "win32":
|
|
46
|
+
appdata = os.environ.get("APPDATA", "")
|
|
47
|
+
code_locations.extend([
|
|
48
|
+
Path(appdata) / "Claude" / "settings.json",
|
|
49
|
+
Path.home() / "AppData" / "Roaming" / "Claude" / "settings.json",
|
|
50
|
+
])
|
|
51
|
+
elif sys.platform == "darwin":
|
|
52
|
+
code_locations.extend([
|
|
53
|
+
Path.home() / "Library" / "Application Support" / "Claude" / "settings.json",
|
|
54
|
+
])
|
|
55
|
+
elif sys.platform == "linux":
|
|
56
|
+
xdg_config = os.environ.get("XDG_CONFIG_HOME", str(Path.home() / ".config"))
|
|
57
|
+
code_locations.extend([
|
|
58
|
+
Path(xdg_config) / "Claude" / "settings.json",
|
|
59
|
+
])
|
|
60
|
+
|
|
61
|
+
# Universal (funciona para ambos)
|
|
62
|
+
universal_claude = Path.home() / ".claude"
|
|
63
|
+
if universal_claude.exists():
|
|
64
|
+
if result["desktop"] is None and (universal_claude / "claude_desktop.json").exists():
|
|
65
|
+
result["desktop"] = universal_claude / "claude_desktop.json"
|
|
66
|
+
if result["code"] is None and (universal_claude / "settings.json").exists():
|
|
67
|
+
result["code"] = universal_claude / "settings.json"
|
|
68
|
+
|
|
69
|
+
# Encontra primeiro desktop que existe
|
|
70
|
+
if result["desktop"] is None:
|
|
71
|
+
for loc in desktop_locations:
|
|
72
|
+
if loc.exists():
|
|
73
|
+
result["desktop"] = loc
|
|
74
|
+
break
|
|
75
|
+
else:
|
|
76
|
+
# Nenhum existe, retorna o primeiro para criar
|
|
77
|
+
result["desktop"] = desktop_locations[0] if desktop_locations else None
|
|
78
|
+
|
|
79
|
+
# Encontra primeiro code que existe
|
|
80
|
+
if result["code"] is None:
|
|
81
|
+
for loc in code_locations:
|
|
82
|
+
if loc.exists():
|
|
83
|
+
result["code"] = loc
|
|
84
|
+
break
|
|
85
|
+
else:
|
|
86
|
+
# Nenhum existe, retorna o primeiro para criar
|
|
87
|
+
result["code"] = code_locations[0] if code_locations else None
|
|
88
|
+
|
|
89
|
+
return result
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def find_claude_desktop_config() -> Path | None:
|
|
93
|
+
"""Encontra o arquivo claude_desktop.json em várias localizações.
|
|
94
|
+
|
|
95
|
+
Legado: use find_all_claude_configs() para suporte completo.
|
|
96
|
+
"""
|
|
97
|
+
configs = find_all_claude_configs()
|
|
98
|
+
return configs.get("desktop")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def get_ocerebro_path() -> Path:
|
|
102
|
+
"""Retorna o caminho absoluto do OCerebro instalado"""
|
|
103
|
+
# Tenta encontrar o package instalado via pip
|
|
104
|
+
try:
|
|
105
|
+
import cerebro
|
|
106
|
+
cerebro_path = Path(cerebro.__file__).parent
|
|
107
|
+
return cerebro_path.resolve()
|
|
108
|
+
except ImportError:
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
# Fallback: usa pip show para encontrar o Location
|
|
112
|
+
try:
|
|
113
|
+
result = subprocess.run(
|
|
114
|
+
[sys.executable, "-m", "pip", "show", "ocerebro"],
|
|
115
|
+
capture_output=True,
|
|
116
|
+
text=True
|
|
117
|
+
)
|
|
118
|
+
if result.returncode == 0:
|
|
119
|
+
for line in result.stdout.splitlines():
|
|
120
|
+
if line.startswith("Location:"):
|
|
121
|
+
return Path(line.split(":", 1)[1].strip())
|
|
122
|
+
except Exception:
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
# Último fallback: usa o path do próprio arquivo
|
|
126
|
+
return Path(__file__).parent.resolve()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def generate_mcp_config(ocerebro_path: Path) -> dict:
|
|
130
|
+
"""Gera configuração MCP para o OCerebro com suporte robusto a paths.
|
|
131
|
+
|
|
132
|
+
SECURITY: Não salva API keys no config file.
|
|
133
|
+
As variáveis de ambiente são herdadas do sistema.
|
|
134
|
+
Configure no seu shell: ~/.bashrc ou ~/.zshrc
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
# Determina o comando Python
|
|
138
|
+
python_cmd = sys.executable
|
|
139
|
+
|
|
140
|
+
# Estratégia 1: usa python -m ocerebro.mcp (robusto para pip install)
|
|
141
|
+
mcp_config = {
|
|
142
|
+
"command": python_cmd,
|
|
143
|
+
"args": ["-m", "src.mcp.server"],
|
|
144
|
+
"cwd": str(ocerebro_path.parent),
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
# Estratégia 2: path direto se arquivo existe
|
|
148
|
+
mcp_server = ocerebro_path / "src" / "mcp" / "server.py"
|
|
149
|
+
if not mcp_server.exists():
|
|
150
|
+
mcp_server = ocerebro_path.parent / "src" / "mcp" / "server.py"
|
|
151
|
+
|
|
152
|
+
if mcp_server.exists():
|
|
153
|
+
mcp_config["args"] = [str(mcp_server)]
|
|
154
|
+
|
|
155
|
+
# SECURITY: NÃO salvar API keys no config
|
|
156
|
+
mcp_config["env"] = {}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
"mcpServers": {
|
|
160
|
+
"ocerebro": mcp_config
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def backup_config(config_path: Path) -> Path | None:
|
|
166
|
+
"""Cria backup do arquivo de configuração"""
|
|
167
|
+
if not config_path.exists():
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
backup_path = config_path.with_suffix(".json.bak")
|
|
171
|
+
backup_path.write_text(config_path.read_text(encoding="utf-8"), encoding="utf-8")
|
|
172
|
+
print(f"[OK] Backup criado: {backup_path}")
|
|
173
|
+
return backup_path
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def merge_configs(existing: dict, new: dict) -> dict:
|
|
177
|
+
"""Faz merge das configurações MCP"""
|
|
178
|
+
result = existing.copy()
|
|
179
|
+
|
|
180
|
+
if "mcpServers" not in result:
|
|
181
|
+
result["mcpServers"] = {}
|
|
182
|
+
|
|
183
|
+
if "mcpServers" in new:
|
|
184
|
+
for name, config in new["mcpServers"].items():
|
|
185
|
+
result["mcpServers"][name] = config
|
|
186
|
+
|
|
187
|
+
return result
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def setup_slash_commands(project_path: Path | None = None, global_commands: bool = True) -> bool:
|
|
191
|
+
"""Cria slash commands /cerebro no .claude/commands/ do projeto e global."""
|
|
192
|
+
|
|
193
|
+
if project_path:
|
|
194
|
+
commands_dir = project_path / ".claude" / "commands"
|
|
195
|
+
commands_dir.mkdir(parents=True, exist_ok=True)
|
|
196
|
+
|
|
197
|
+
dream_cmd = commands_dir / "cerebro-dream.md"
|
|
198
|
+
if not dream_cmd.exists():
|
|
199
|
+
dream_cmd.write_text("""---
|
|
200
|
+
description: Extrair memórias da sessão atual
|
|
201
|
+
---
|
|
202
|
+
Execute: ocerebro dream --since 7 --apply
|
|
203
|
+
Mostre o relatório completo do que foi salvo.
|
|
204
|
+
""", encoding="utf-8")
|
|
205
|
+
print(f"[OK] Slash command criado: {dream_cmd}")
|
|
206
|
+
|
|
207
|
+
status_cmd = commands_dir / "cerebro-status.md"
|
|
208
|
+
if not status_cmd.exists():
|
|
209
|
+
status_cmd.write_text("""---
|
|
210
|
+
description: Ver status da memória do projeto
|
|
211
|
+
---
|
|
212
|
+
Execute: ocerebro status
|
|
213
|
+
Liste quantas memórias existem por tipo.
|
|
214
|
+
""", encoding="utf-8")
|
|
215
|
+
print(f"[OK] Slash command criado: {status_cmd}")
|
|
216
|
+
|
|
217
|
+
gc_cmd = commands_dir / "cerebro-gc.md"
|
|
218
|
+
if not gc_cmd.exists():
|
|
219
|
+
gc_cmd.write_text("""---
|
|
220
|
+
description: Limpeza de memórias antigas
|
|
221
|
+
---
|
|
222
|
+
Execute: ocerebro gc --threshold 30
|
|
223
|
+
Mostre o que será arquivado antes de confirmar.
|
|
224
|
+
""", encoding="utf-8")
|
|
225
|
+
print(f"[OK] Slash command criado: {gc_cmd}")
|
|
226
|
+
|
|
227
|
+
# Slash commands globais em ~/.claude/commands/
|
|
228
|
+
if global_commands:
|
|
229
|
+
global_commands_dir = Path.home() / ".claude" / "commands"
|
|
230
|
+
global_commands_dir.mkdir(parents=True, exist_ok=True)
|
|
231
|
+
|
|
232
|
+
dream_global = global_commands_dir / "cerebro-dream.md"
|
|
233
|
+
if not dream_global.exists():
|
|
234
|
+
dream_global.write_text("""---
|
|
235
|
+
description: Extrair memórias da sessão atual (global)
|
|
236
|
+
---
|
|
237
|
+
Execute: ocerebro dream --since 7 --apply
|
|
238
|
+
Mostre o relatório completo do que foi salvo.
|
|
239
|
+
""", encoding="utf-8")
|
|
240
|
+
print(f"[OK] Slash command global criado: {dream_global}")
|
|
241
|
+
|
|
242
|
+
status_global = global_commands_dir / "cerebro-status.md"
|
|
243
|
+
if not status_global.exists():
|
|
244
|
+
status_global.write_text("""---
|
|
245
|
+
description: Ver status da memória (global)
|
|
246
|
+
---
|
|
247
|
+
Execute: ocerebro status
|
|
248
|
+
Liste quantas memórias existem por tipo.
|
|
249
|
+
""", encoding="utf-8")
|
|
250
|
+
print(f"[OK] Slash command global criado: {status_global}")
|
|
251
|
+
|
|
252
|
+
gc_global = global_commands_dir / "cerebro-gc.md"
|
|
253
|
+
if not gc_global.exists():
|
|
254
|
+
gc_global.write_text("""---
|
|
255
|
+
description: Limpeza de memórias antigas (global)
|
|
256
|
+
---
|
|
257
|
+
Execute: ocerebro gc --threshold 30
|
|
258
|
+
Mostre o que será arquivado antes de confirmar.
|
|
259
|
+
""", encoding="utf-8")
|
|
260
|
+
print(f"[OK] Slash command global criado: {gc_global}")
|
|
261
|
+
|
|
262
|
+
return True
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def find_python_with_ocerebro() -> str:
|
|
266
|
+
"""Encontra o executável Python onde ocerebro está instalado."""
|
|
267
|
+
candidates = []
|
|
268
|
+
|
|
269
|
+
try:
|
|
270
|
+
import ocerebro # noqa: F401
|
|
271
|
+
candidates.append(sys.executable)
|
|
272
|
+
except ImportError:
|
|
273
|
+
pass
|
|
274
|
+
|
|
275
|
+
for cmd in ["python", "python3"]:
|
|
276
|
+
try:
|
|
277
|
+
result = subprocess.run(
|
|
278
|
+
[cmd, "-c", "import ocerebro; print('ok')"],
|
|
279
|
+
capture_output=True,
|
|
280
|
+
text=True,
|
|
281
|
+
timeout=5
|
|
282
|
+
)
|
|
283
|
+
if result.returncode == 0:
|
|
284
|
+
result_path = subprocess.run(
|
|
285
|
+
[cmd, "-c", "import sys; print(sys.executable)"],
|
|
286
|
+
capture_output=True,
|
|
287
|
+
text=True
|
|
288
|
+
)
|
|
289
|
+
if result_path.returncode == 0:
|
|
290
|
+
candidates.append(result_path.stdout.strip())
|
|
291
|
+
except Exception:
|
|
292
|
+
continue
|
|
293
|
+
|
|
294
|
+
return candidates[0] if candidates else sys.executable
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def get_claude_code_settings_path() -> Path | None:
|
|
298
|
+
"""Encontra o settings.json do Claude Code."""
|
|
299
|
+
home_settings = Path.home() / ".claude" / "settings.json"
|
|
300
|
+
if home_settings.exists():
|
|
301
|
+
return home_settings
|
|
302
|
+
|
|
303
|
+
if sys.platform == "win32":
|
|
304
|
+
appdata = os.environ.get("APPDATA", "")
|
|
305
|
+
if appdata:
|
|
306
|
+
appdata_settings = Path(appdata) / "Claude" / "settings.json"
|
|
307
|
+
if appdata_settings.exists():
|
|
308
|
+
return appdata_settings
|
|
309
|
+
|
|
310
|
+
return home_settings
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def get_claude_desktop_settings_path() -> Path | None:
|
|
314
|
+
"""Encontra o claude_desktop.json do Claude Desktop."""
|
|
315
|
+
if sys.platform == "win32":
|
|
316
|
+
appdata = os.environ.get("APPDATA", "")
|
|
317
|
+
if appdata:
|
|
318
|
+
return Path(appdata) / "Claude" / "claude_desktop.json"
|
|
319
|
+
elif sys.platform == "darwin":
|
|
320
|
+
return Path.home() / "Library" / "Application Support" / "Claude" / "claude_desktop.json"
|
|
321
|
+
return None
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def setup_claude(auto: bool = True) -> bool:
|
|
325
|
+
"""Configura MCP Server automaticamente."""
|
|
326
|
+
print("=" * 60)
|
|
327
|
+
print("OCerebro - Configurando MCP Server")
|
|
328
|
+
print("=" * 60)
|
|
329
|
+
print()
|
|
330
|
+
|
|
331
|
+
python_cmd = find_python_with_ocerebro()
|
|
332
|
+
print(f"[1/5] Python detectado: {python_cmd}")
|
|
333
|
+
|
|
334
|
+
# Encontra caminho do OCerebro
|
|
335
|
+
ocerebro_path = get_ocerebro_path()
|
|
336
|
+
|
|
337
|
+
# Gera configuração MCP
|
|
338
|
+
mcp_config = {
|
|
339
|
+
"command": python_cmd,
|
|
340
|
+
"args": ["-m", "src.mcp.server"],
|
|
341
|
+
"cwd": str(ocerebro_path),
|
|
342
|
+
"env": {}
|
|
343
|
+
}
|
|
344
|
+
print(f"[2/5] Configuração MCP gerada")
|
|
345
|
+
|
|
346
|
+
configured = []
|
|
347
|
+
errors = []
|
|
348
|
+
|
|
349
|
+
claude_code_path = get_claude_code_settings_path()
|
|
350
|
+
claude_desktop_path = get_claude_desktop_settings_path()
|
|
351
|
+
|
|
352
|
+
if auto:
|
|
353
|
+
targets = []
|
|
354
|
+
if claude_code_path and claude_code_path.exists():
|
|
355
|
+
targets.append(("code", claude_code_path))
|
|
356
|
+
if claude_desktop_path and claude_desktop_path.exists():
|
|
357
|
+
targets.append(("desktop", claude_desktop_path))
|
|
358
|
+
if not targets:
|
|
359
|
+
targets.append(("code", claude_code_path))
|
|
360
|
+
else:
|
|
361
|
+
print("Qual versão do Claude você usa?")
|
|
362
|
+
print(" 1. Claude Desktop")
|
|
363
|
+
print(" 2. Claude Code (claude.ai/code)")
|
|
364
|
+
print(" 3. Ambos")
|
|
365
|
+
choice = input("\nEscolha [1/2/3] (padrão: 2): ").strip() or "2"
|
|
366
|
+
|
|
367
|
+
targets = []
|
|
368
|
+
if choice in ["1", "3"] and claude_desktop_path:
|
|
369
|
+
targets.append(("desktop", claude_desktop_path))
|
|
370
|
+
if choice in ["2", "3"] and claude_code_path:
|
|
371
|
+
targets.append(("code", claude_code_path))
|
|
372
|
+
if not targets:
|
|
373
|
+
targets.append(("code", claude_code_path))
|
|
374
|
+
|
|
375
|
+
for target_type, config_path in targets:
|
|
376
|
+
print(f"[3/5] Configurando {target_type}...")
|
|
377
|
+
|
|
378
|
+
try:
|
|
379
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
380
|
+
|
|
381
|
+
existing_config = {}
|
|
382
|
+
if config_path.exists():
|
|
383
|
+
try:
|
|
384
|
+
existing_config = json.loads(config_path.read_text(encoding="utf-8"))
|
|
385
|
+
backup_config(config_path)
|
|
386
|
+
except json.JSONDecodeError:
|
|
387
|
+
print(f" Aviso: Config existente inválida, criando nova")
|
|
388
|
+
|
|
389
|
+
if "mcpServers" not in existing_config:
|
|
390
|
+
existing_config["mcpServers"] = {}
|
|
391
|
+
existing_config["mcpServers"]["ocerebro"] = mcp_config
|
|
392
|
+
|
|
393
|
+
if "mcp" not in existing_config:
|
|
394
|
+
existing_config["mcp"] = {}
|
|
395
|
+
existing_config["mcp"]["enabled"] = True
|
|
396
|
+
|
|
397
|
+
# Adiciona hook para dream automatico ao final da sessao
|
|
398
|
+
if "hooks" not in existing_config:
|
|
399
|
+
existing_config["hooks"] = {}
|
|
400
|
+
|
|
401
|
+
# Hook Stop: roda dream ao final de cada sessao
|
|
402
|
+
existing_config["hooks"]["Stop"] = [
|
|
403
|
+
{
|
|
404
|
+
"matcher": "",
|
|
405
|
+
"hooks": [
|
|
406
|
+
{
|
|
407
|
+
"type": "command",
|
|
408
|
+
"command": f"{python_cmd} -m src.cli.main dream --since 1 --apply --silent"
|
|
409
|
+
}
|
|
410
|
+
]
|
|
411
|
+
}
|
|
412
|
+
]
|
|
413
|
+
|
|
414
|
+
config_path.write_text(
|
|
415
|
+
json.dumps(existing_config, indent=2, ensure_ascii=False),
|
|
416
|
+
encoding="utf-8"
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
configured.append(target_type)
|
|
420
|
+
print(f" [OK] {target_type}: {config_path}")
|
|
421
|
+
|
|
422
|
+
except Exception as e:
|
|
423
|
+
errors.append(f"{target_type}: {e}")
|
|
424
|
+
print(f" [ERRO] {target_type}: {e}")
|
|
425
|
+
|
|
426
|
+
print()
|
|
427
|
+
print("=" * 60)
|
|
428
|
+
print("SETUP CONCLUÍDO!")
|
|
429
|
+
print("=" * 60)
|
|
430
|
+
|
|
431
|
+
if configured:
|
|
432
|
+
print(f"\n[OK] MCP Server configurado em: {', '.join(configured)}")
|
|
433
|
+
print("\nPróximos passos:")
|
|
434
|
+
print(" 1. Reinicie o Claude (feche e abra novamente)")
|
|
435
|
+
print(" 2. As ferramentas estarão disponíveis:")
|
|
436
|
+
for tool in ["cerebro_memory", "cerebro_search", "cerebro_checkpoint",
|
|
437
|
+
"cerebro_promote", "cerebro_status", "cerebro_hooks",
|
|
438
|
+
"cerebro_diff", "cerebro_dream", "cerebro_remember", "cerebro_gc"]:
|
|
439
|
+
print(f" - {tool}")
|
|
440
|
+
print("\nPara testar, digite no Claude:")
|
|
441
|
+
print(" /help (deve mostrar cerebro_*)")
|
|
442
|
+
print(" ou: Use cerebro_status")
|
|
443
|
+
else:
|
|
444
|
+
print("\n[ERRO] Não foi possível configurar automaticamente.")
|
|
445
|
+
print("Configure manualmente adicionando ao seu settings.json:")
|
|
446
|
+
print(json.dumps({"mcpServers": {"ocerebro": mcp_config}}, indent=2))
|
|
447
|
+
|
|
448
|
+
if errors:
|
|
449
|
+
print(f"\nErros encontrados: {len(errors)}")
|
|
450
|
+
for err in errors:
|
|
451
|
+
print(f" - {err}")
|
|
452
|
+
|
|
453
|
+
print()
|
|
454
|
+
return len(configured) > 0
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def setup_hooks(project_path: Path | None = None) -> bool:
|
|
458
|
+
"""Cria arquivo de exemplo hooks.yaml no projeto"""
|
|
459
|
+
|
|
460
|
+
if project_path is None:
|
|
461
|
+
project_path = Path.cwd()
|
|
462
|
+
|
|
463
|
+
hooks_yaml = project_path / "hooks.yaml"
|
|
464
|
+
|
|
465
|
+
if hooks_yaml.exists():
|
|
466
|
+
print(f" hooks.yaml já existe em {project_path}")
|
|
467
|
+
return False
|
|
468
|
+
|
|
469
|
+
example_config = """# OCerebro Hooks Configuration
|
|
470
|
+
hooks:
|
|
471
|
+
- name: error_notification
|
|
472
|
+
event_type: error
|
|
473
|
+
module_path: hooks/error_hook.py
|
|
474
|
+
function: on_error
|
|
475
|
+
config:
|
|
476
|
+
notify_severity: [\"critical\", \"high\"]
|
|
477
|
+
|
|
478
|
+
- name: llm_cost_tracker
|
|
479
|
+
event_type: tool_call
|
|
480
|
+
event_subtype: llm
|
|
481
|
+
module_path: hooks/cost_hook.py
|
|
482
|
+
function: on_llm_call
|
|
483
|
+
config:
|
|
484
|
+
monthly_budget: 100.0
|
|
485
|
+
alert_at_percentage: 80
|
|
486
|
+
"""
|
|
487
|
+
|
|
488
|
+
hooks_yaml.write_text(example_config, encoding="utf-8")
|
|
489
|
+
|
|
490
|
+
hooks_dir = project_path / "hooks"
|
|
491
|
+
hooks_dir.mkdir(exist_ok=True)
|
|
492
|
+
(hooks_dir / "__init__.py").write_text('"""Hooks customizados do projeto"""', encoding="utf-8")
|
|
493
|
+
|
|
494
|
+
print(f"[OK] hooks.yaml criado em {project_path}")
|
|
495
|
+
print(f"[OK] Diretório hooks/ criado")
|
|
496
|
+
|
|
497
|
+
return True
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def setup_ocerebro_dir(project_path: Path | None = None) -> bool:
|
|
501
|
+
"""Cria diretório .ocerebro no projeto."""
|
|
502
|
+
|
|
503
|
+
if project_path is None:
|
|
504
|
+
project_path = Path.cwd()
|
|
505
|
+
|
|
506
|
+
project_path = project_path.resolve()
|
|
507
|
+
home = Path.home().resolve()
|
|
508
|
+
cwd = Path.cwd().resolve()
|
|
509
|
+
|
|
510
|
+
if not (str(project_path).startswith(str(home)) or
|
|
511
|
+
str(project_path).startswith(str(cwd))):
|
|
512
|
+
print(f"❌ Erro: path '{project_path}' fora do diretório permitido.")
|
|
513
|
+
return False
|
|
514
|
+
|
|
515
|
+
ocerebro_dir = project_path / ".ocerebro"
|
|
516
|
+
|
|
517
|
+
if ocerebro_dir.exists():
|
|
518
|
+
print(f"[OK] Diretório .ocerebro já existe")
|
|
519
|
+
return True
|
|
520
|
+
|
|
521
|
+
(ocerebro_dir / "raw").mkdir(parents=True)
|
|
522
|
+
(ocerebro_dir / "working").mkdir(parents=True)
|
|
523
|
+
(ocerebro_dir / "official").mkdir(parents=True)
|
|
524
|
+
(ocerebro_dir / "index").mkdir(parents=True)
|
|
525
|
+
(ocerebro_dir / "config").mkdir(parents=True)
|
|
526
|
+
|
|
527
|
+
gitignore = ocerebro_dir / ".gitignore"
|
|
528
|
+
gitignore.write_text("raw/\nworking/\nindex/\nconfig/local.yaml\n", encoding="utf-8")
|
|
529
|
+
|
|
530
|
+
print(f"[OK] Diretório .ocerebro criado em {project_path}")
|
|
531
|
+
print(f" - raw/ (eventos brutos)")
|
|
532
|
+
print(f" - working/ (rascunhos)")
|
|
533
|
+
print(f" - official/ (memória permanente)")
|
|
534
|
+
print(f" - index/ (banco de dados)")
|
|
535
|
+
print(f" - config/ (configurações)")
|
|
536
|
+
|
|
537
|
+
return True
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def main():
|
|
541
|
+
"""Função principal de setup"""
|
|
542
|
+
|
|
543
|
+
if len(sys.argv) > 1:
|
|
544
|
+
subcommand = sys.argv[1]
|
|
545
|
+
|
|
546
|
+
if subcommand == "claude":
|
|
547
|
+
success = setup_claude(auto=True)
|
|
548
|
+
sys.exit(0 if success else 1)
|
|
549
|
+
|
|
550
|
+
elif subcommand == "hooks":
|
|
551
|
+
project = Path(sys.argv[2]) if len(sys.argv) > 2 else None
|
|
552
|
+
success = setup_hooks(project)
|
|
553
|
+
sys.exit(0 if success else 1)
|
|
554
|
+
|
|
555
|
+
elif subcommand == "init":
|
|
556
|
+
project = Path(sys.argv[2]) if len(sys.argv) > 2 else Path.cwd()
|
|
557
|
+
setup_ocerebro_dir(project)
|
|
558
|
+
setup_hooks(project)
|
|
559
|
+
setup_slash_commands(project=project)
|
|
560
|
+
setup_claude(auto=True)
|
|
561
|
+
sys.exit(0)
|
|
562
|
+
|
|
563
|
+
else:
|
|
564
|
+
print(f"Subcomando desconhecido: {subcommand}")
|
|
565
|
+
sys.exit(1)
|
|
566
|
+
|
|
567
|
+
print("Executando setup completo...")
|
|
568
|
+
print()
|
|
569
|
+
setup_ocerebro_dir()
|
|
570
|
+
setup_hooks()
|
|
571
|
+
setup_claude(auto=True)
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
if __name__ == "__main__":
|
|
575
|
+
main()
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
package/src/cli/main.py
CHANGED
|
@@ -363,6 +363,7 @@ def main():
|
|
|
363
363
|
dream_parser = subparsers.add_parser("dream", help="Extração automática de memórias")
|
|
364
364
|
dream_parser.add_argument("--since", type=int, default=7, dest="since_days")
|
|
365
365
|
dream_parser.add_argument("--apply", action="store_true", dest="apply")
|
|
366
|
+
dream_parser.add_argument("--silent", action="store_true", dest="silent", help="Não imprimir output (para hooks)")
|
|
366
367
|
|
|
367
368
|
# Comando: remember
|
|
368
369
|
remember_parser = subparsers.add_parser("remember", help="Revisão e promoção de memórias")
|
|
@@ -434,6 +435,8 @@ def main():
|
|
|
434
435
|
)
|
|
435
436
|
elif args.command == "dream":
|
|
436
437
|
result = cli.dream(since_days=args.since_days, dry_run=not args.apply)
|
|
438
|
+
if getattr(args, 'silent', False):
|
|
439
|
+
sys.exit(0)
|
|
437
440
|
elif args.command == "remember":
|
|
438
441
|
result = cli.remember(dry_run=not args.apply)
|
|
439
442
|
elif args.command == "gc":
|
|
@@ -442,6 +445,8 @@ def main():
|
|
|
442
445
|
parser.print_help()
|
|
443
446
|
sys.exit(1)
|
|
444
447
|
|
|
448
|
+
if getattr(args, 'silent', False):
|
|
449
|
+
sys.exit(0)
|
|
445
450
|
print(result)
|
|
446
451
|
|
|
447
452
|
|