codemate-cli 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,749 @@
1
+ # codemate/commands/kb_commands.py
2
+ """Knowledge Base Management Commands"""
3
+
4
+ import os
5
+ import uuid
6
+ import json
7
+ import time
8
+ import platform
9
+ import socketio
10
+ import httpx
11
+ from pathlib import Path
12
+ from typing import Optional, List, Tuple, Set
13
+ from rich.console import Console
14
+ from rich.panel import Panel
15
+ from rich.prompt import Prompt, Confirm
16
+ from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
17
+ from rich.table import Table
18
+ from rich import box
19
+
20
+ console = Console()
21
+
22
+
23
+ class KBCommands:
24
+ """Handler for Knowledge Base operations"""
25
+
26
+ def __init__(self, config, client):
27
+ self.config = config
28
+ self.client = client
29
+ self.socket_url = "http://localhost:45224"
30
+ self.api_url = "http://localhost:45223"
31
+
32
+ def _normalize_path(self, path_str: str) -> Path:
33
+ """Normalize path for cross-platform compatibility"""
34
+ path_str = os.path.expanduser(path_str)
35
+ path = Path(path_str).resolve()
36
+ return path
37
+
38
+ def _validate_path(self, path: Path) -> Tuple[bool, Optional[str]]:
39
+ """
40
+ Validate if path exists and is accessible
41
+
42
+ Returns:
43
+ Tuple of (is_valid, error_message)
44
+ """
45
+ if not path.exists():
46
+ return False, f"Path does not exist: {path}"
47
+
48
+ if not path.is_dir():
49
+ return False, f"Path is not a directory: {path}"
50
+
51
+ if not os.access(path, os.R_OK):
52
+ return False, f"No read permission for: {path}"
53
+
54
+ try:
55
+ if not any(path.iterdir()):
56
+ return False, f"Directory is empty: {path}"
57
+ except PermissionError:
58
+ return False, f"Cannot access directory contents: {path}"
59
+
60
+ return True, None
61
+
62
+ def _parse_gitignore_patterns(self, gitignore_path: Path) -> Set[str]:
63
+ """Parse .gitignore file and return set of patterns"""
64
+ patterns = set()
65
+
66
+ try:
67
+ with open(gitignore_path, 'r', encoding='utf-8') as f:
68
+ for line in f:
69
+ line = line.strip()
70
+ if line and not line.startswith('#'):
71
+ patterns.add(line)
72
+ except Exception as e:
73
+ console.print(f"[yellow]Warning: Could not read .gitignore: {e}[/yellow]")
74
+
75
+ return patterns
76
+
77
+ def _should_exclude(self, path: Path, exclude_patterns: Set[str], base_path: Path) -> bool:
78
+ """Check if path matches any exclude pattern"""
79
+ relative_path = path.relative_to(base_path)
80
+ path_str = str(relative_path).replace('\\', '/')
81
+
82
+ for pattern in exclude_patterns:
83
+ if pattern.endswith('/'):
84
+ if path.is_dir() and (path_str + '/').startswith(pattern):
85
+ return True
86
+ if path_str.startswith(pattern.rstrip('/')):
87
+ return True
88
+ elif '*' in pattern:
89
+ import fnmatch
90
+ if fnmatch.fnmatch(path_str, pattern):
91
+ return True
92
+ if fnmatch.fnmatch(path.name, pattern):
93
+ return True
94
+ else:
95
+ if path_str == pattern or path.name == pattern:
96
+ return True
97
+ if pattern in path_str.split('/'):
98
+ return True
99
+
100
+ return False
101
+
102
+ def _collect_files(
103
+ self,
104
+ base_path: Path,
105
+ progress_callback=None
106
+ ) -> List[str]:
107
+ """
108
+ Recursively collect all files from directory, excluding patterns
109
+
110
+ Returns:
111
+ List of absolute file paths as strings
112
+ """
113
+ files = []
114
+ total_scanned = 0
115
+
116
+ default_excludes = {
117
+ '.git/', '__pycache__/', 'node_modules/', '.venv/', 'venv/',
118
+ '.env', '.pyc', '.DS_Store', 'Thumbs.db', '.idea/', '.vscode/'
119
+ }
120
+
121
+ for root, dirs, filenames in os.walk(base_path):
122
+ root_path = Path(root)
123
+
124
+ dirs[:] = [
125
+ d for d in dirs
126
+ if not self._should_exclude(root_path / d, default_excludes, base_path)
127
+ ]
128
+
129
+ for filename in filenames:
130
+ file_path = root_path / filename
131
+ total_scanned += 1
132
+
133
+ if self._should_exclude(file_path, default_excludes, base_path):
134
+ continue
135
+
136
+ try:
137
+ if file_path.stat().st_size > 10_000_000:
138
+ continue
139
+ except:
140
+ continue
141
+
142
+ files.append(str(file_path))
143
+
144
+ if progress_callback and total_scanned % 100 == 0:
145
+ progress_callback(len(files), total_scanned)
146
+
147
+ return files
148
+
149
+ def _validate_url(self, url: str) -> bool:
150
+ """Validate if URL has proper format"""
151
+ url = url.strip()
152
+ if not url:
153
+ return False
154
+ return url.startswith(('http://', 'https://'))
155
+
156
+ async def create_kb(self) -> bool:
157
+ """Interactive knowledge base creation flow"""
158
+ console.print()
159
+ console.print(Panel(
160
+ "[cyan]📚 Create Knowledge Base[/cyan]\n\n"
161
+ "[white]Choose the type of knowledge base you want to create.[/white]",
162
+ border_style="cyan",
163
+ padding=(1, 2),
164
+ box=box.ROUNDED
165
+ ))
166
+ console.print()
167
+
168
+ # Step 0: Choose KB type
169
+ console.print("[bold cyan]Step 1:[/bold cyan] Select knowledge base type")
170
+ console.print()
171
+
172
+ type_table = Table(
173
+ show_header=True,
174
+ header_style="bold magenta",
175
+ border_style="blue",
176
+ box=box.ROUNDED
177
+ )
178
+ type_table.add_column("#", style="dim", width=4)
179
+ type_table.add_column("Type", style="cyan", width=15)
180
+ type_table.add_column("Description", style="white", width=50)
181
+
182
+ type_table.add_row("1", "codebase", "Index a local directory/project with code files")
183
+ type_table.add_row("2", "docs", "Index documentation from URL(s)")
184
+
185
+ console.print(type_table)
186
+ console.print()
187
+
188
+ kb_type_input = Prompt.ask(
189
+ "[cyan]Select type[/cyan]",
190
+ choices=["1", "2", "codebase", "docs"],
191
+ default="1"
192
+ )
193
+
194
+ # Normalize input
195
+ if kb_type_input in ["1", "codebase"]:
196
+ kb_type = "codebase"
197
+ return await self._create_codebase_kb()
198
+ else:
199
+ kb_type = "docs"
200
+ return await self._create_docs_kb()
201
+
202
+ async def _create_codebase_kb(self) -> bool:
203
+ """Create a codebase knowledge base from local directory"""
204
+ console.print()
205
+ console.print("[dim]═" * console.width + "[/dim]")
206
+ console.print()
207
+
208
+ # Step 1: Get directory path
209
+ console.print("[bold cyan]Step 2:[/bold cyan] Specify the directory path")
210
+ console.print("[dim]Enter the full path to the directory you want to index[/dim]")
211
+ console.print()
212
+
213
+ path_input = Prompt.ask(
214
+ "[cyan]Directory path[/cyan]",
215
+ default="."
216
+ )
217
+
218
+ # Normalize and validate path
219
+ try:
220
+ kb_path = self._normalize_path(path_input)
221
+ is_valid, error_msg = self._validate_path(kb_path)
222
+
223
+ if not is_valid:
224
+ console.print()
225
+ console.print(Panel(
226
+ f"[red]❌ Invalid Path[/red]\n\n{error_msg}",
227
+ border_style="red",
228
+ padding=(1, 2)
229
+ ))
230
+ return False
231
+
232
+ console.print(f"[green]✓[/green] Valid path: [cyan]{kb_path}[/cyan]")
233
+ console.print()
234
+
235
+ except Exception as e:
236
+ console.print()
237
+ console.print(Panel(
238
+ f"[red]❌ Error validating path[/red]\n\n{str(e)}",
239
+ border_style="red",
240
+ padding=(1, 2)
241
+ ))
242
+ return False
243
+
244
+ # Step 2: Get KB name
245
+ default_name = kb_path.name
246
+ console.print("[bold cyan]Step 3:[/bold cyan] Name your knowledge base")
247
+ console.print(f"[dim]Default: {default_name}[/dim]")
248
+ console.print()
249
+
250
+ kb_name = Prompt.ask(
251
+ "[cyan]Knowledge base name[/cyan]",
252
+ default=default_name
253
+ )
254
+ console.print()
255
+
256
+ # Step 3: Get description
257
+ console.print("[bold cyan]Step 4:[/bold cyan] Add a description [dim](optional)[/dim]")
258
+ kb_description = Prompt.ask(
259
+ "[cyan]Description[/cyan]",
260
+ default=""
261
+ )
262
+ console.print()
263
+ console.print("[dim]─" * console.width + "[/dim]")
264
+ console.print()
265
+
266
+ # Show summary
267
+ summary = Table(
268
+ show_header=False,
269
+ border_style="cyan",
270
+ box=box.ROUNDED,
271
+ padding=(0, 1)
272
+ )
273
+ summary.add_column(style="cyan bold", width=20)
274
+ summary.add_column(style="white")
275
+
276
+ summary.add_row("Type:", "codebase")
277
+ summary.add_row("Path:", str(kb_path))
278
+ summary.add_row("Name:", kb_name)
279
+ summary.add_row("Description:", kb_description or "[dim](none)[/dim]")
280
+
281
+ console.print(Panel(
282
+ summary,
283
+ title="[bold cyan]📋 Summary[/bold cyan]",
284
+ border_style="cyan",
285
+ padding=(1, 2)
286
+ ))
287
+ console.print()
288
+
289
+ # Confirm creation
290
+ if not Confirm.ask("[cyan]Create this knowledge base?[/cyan]", default=True):
291
+ console.print("[yellow]Cancelled.[/yellow]")
292
+ return False
293
+
294
+ console.print()
295
+ console.print("[dim]─" * console.width + "[/dim]")
296
+ console.print()
297
+
298
+ # Collect files
299
+ console.print("[cyan]🔍 Scanning directory...[/cyan]")
300
+ console.print()
301
+
302
+ files = []
303
+ try:
304
+ with Progress(
305
+ SpinnerColumn(),
306
+ TextColumn("[progress.description]{task.description}"),
307
+ transient=True
308
+ ) as progress:
309
+ task = progress.add_task("Scanning files...", total=None)
310
+
311
+ def progress_callback(found, scanned):
312
+ progress.update(task, description=f"Found {found} files (scanned {scanned})")
313
+
314
+ files = self._collect_files(kb_path, progress_callback)
315
+
316
+ if not files:
317
+ console.print()
318
+ console.print(Panel(
319
+ "[yellow]⚠️ No files found[/yellow]\n\n"
320
+ "The directory appears to be empty or all files were excluded.",
321
+ border_style="yellow",
322
+ padding=(1, 2)
323
+ ))
324
+ return False
325
+
326
+ console.print(f"[green]✓[/green] Found [cyan]{len(files)}[/cyan] files")
327
+ console.print()
328
+
329
+ except Exception as e:
330
+ console.print()
331
+ console.print(Panel(
332
+ f"[red]❌ Error scanning directory[/red]\n\n{str(e)}",
333
+ border_style="red",
334
+ padding=(1, 2)
335
+ ))
336
+ return False
337
+
338
+ # Prepare upload data
339
+ kb_id = str(uuid.uuid4())
340
+ request_id = ''.join(str(uuid.uuid4()).split('-'))[:22]
341
+
342
+ upload_data = {
343
+ "id": kb_id,
344
+ "isAutoIndexed": False,
345
+ "name": kb_name,
346
+ "description": kb_description,
347
+ "source": "LOCAL",
348
+ "scope": "personal",
349
+ "syncConfig": {
350
+ "enabled": False,
351
+ "lastSynced": int(time.time() * 1000)
352
+ },
353
+ "status": "draft",
354
+ "progress": {
355
+ "status": "",
356
+ "message": "",
357
+ "progress": 0
358
+ },
359
+ "dateCreated": int(time.time() * 1000),
360
+ "dateSynced": None,
361
+ "dateUpdated": None,
362
+ "can_sync": False,
363
+ "can_upload": False,
364
+ "cloud_id": "",
365
+ "type": "codebase",
366
+ "metadata": {
367
+ "path": str(kb_path),
368
+ "files": files
369
+ },
370
+ "request_id": request_id
371
+ }
372
+
373
+ # Upload
374
+ console.print("[cyan]📤 Uploading to server...[/cyan]")
375
+ console.print()
376
+
377
+ try:
378
+ await self._upload_via_socket(upload_data, "codebase")
379
+ return True
380
+ except Exception as e:
381
+ console.print()
382
+ console.print(Panel(
383
+ f"[red]❌ Upload failed[/red]\n\n{str(e)}",
384
+ border_style="red",
385
+ padding=(1, 2)
386
+ ))
387
+ return False
388
+
389
+ async def _create_docs_kb(self) -> bool:
390
+ """Create a docs knowledge base from URLs"""
391
+ console.print()
392
+ console.print("[dim]═" * console.width + "[/dim]")
393
+ console.print()
394
+
395
+ # Step 1: Get URLs
396
+ console.print("[bold cyan]Step 2:[/bold cyan] Provide documentation URL(s)")
397
+ console.print("[dim]Enter one or more URLs (comma-separated)[/dim]")
398
+ console.print("[dim]Example: https://docs.example.com, https://guide.example.com[/dim]")
399
+ console.print()
400
+
401
+ urls_input = Prompt.ask("[cyan]Documentation URL(s)[/cyan]")
402
+
403
+ # Parse and validate URLs
404
+ urls = [url.strip() for url in urls_input.split(',')]
405
+ valid_urls = []
406
+ invalid_urls = []
407
+
408
+ for url in urls:
409
+ if self._validate_url(url):
410
+ valid_urls.append(url)
411
+ else:
412
+ invalid_urls.append(url)
413
+
414
+ if invalid_urls:
415
+ console.print()
416
+ console.print(Panel(
417
+ f"[yellow]⚠️ Invalid URL(s) found:[/yellow]\n\n" +
418
+ "\n".join([f"• {url}" for url in invalid_urls]) +
419
+ "\n\n[white]URLs must start with http:// or https://[/white]",
420
+ border_style="yellow",
421
+ padding=(1, 2)
422
+ ))
423
+
424
+ if not valid_urls:
425
+ console.print()
426
+ console.print(Panel(
427
+ "[red]❌ No valid URLs provided[/red]",
428
+ border_style="red",
429
+ padding=(1, 2)
430
+ ))
431
+ return False
432
+
433
+ console.print(f"[green]✓[/green] Valid URLs: [cyan]{len(valid_urls)}[/cyan]")
434
+ console.print()
435
+
436
+ # Step 2: Get KB name
437
+ console.print("[bold cyan]Step 3:[/bold cyan] Name your knowledge base")
438
+ console.print()
439
+
440
+ kb_name = Prompt.ask("[cyan]Knowledge base name[/cyan]")
441
+ console.print()
442
+
443
+ # Step 3: Get description
444
+ console.print("[bold cyan]Step 4:[/bold cyan] Add a description [dim](optional)[/dim]")
445
+ kb_description = Prompt.ask(
446
+ "[cyan]Description[/cyan]",
447
+ default=""
448
+ )
449
+ console.print()
450
+ console.print("[dim]─" * console.width + "[/dim]")
451
+ console.print()
452
+
453
+ # Show summary
454
+ summary = Table(
455
+ show_header=False,
456
+ border_style="cyan",
457
+ box=box.ROUNDED,
458
+ padding=(0, 1)
459
+ )
460
+ summary.add_column(style="cyan bold", width=20)
461
+ summary.add_column(style="white")
462
+
463
+ summary.add_row("Type:", "docs")
464
+ summary.add_row("URLs:", str(len(valid_urls)))
465
+ summary.add_row("Name:", kb_name)
466
+ summary.add_row("Description:", kb_description or "[dim](none)[/dim]")
467
+
468
+ console.print(Panel(
469
+ summary,
470
+ title="[bold cyan]📋 Summary[/bold cyan]",
471
+ border_style="cyan",
472
+ padding=(1, 2)
473
+ ))
474
+ console.print()
475
+
476
+ # Show URLs
477
+ if valid_urls:
478
+ console.print("[cyan]URLs to index:[/cyan]")
479
+ for i, url in enumerate(valid_urls, 1):
480
+ console.print(f" [dim]{i}.[/dim] {url}")
481
+ console.print()
482
+
483
+ # Confirm creation
484
+ if not Confirm.ask("[cyan]Create this knowledge base?[/cyan]", default=True):
485
+ console.print("[yellow]Cancelled.[/yellow]")
486
+ return False
487
+
488
+ console.print()
489
+ console.print("[dim]─" * console.width + "[/dim]")
490
+ console.print()
491
+
492
+ # Prepare upload data
493
+ kb_id = str(uuid.uuid4())
494
+ request_id = ''.join(str(uuid.uuid4()).split('-'))[:22]
495
+
496
+ upload_data = {
497
+ "id": kb_id,
498
+ "isAutoIndexed": False,
499
+ "name": kb_name,
500
+ "description": kb_description,
501
+ "source": "DOCS",
502
+ "scope": "personal",
503
+ "syncConfig": {
504
+ "enabled": False,
505
+ "lastSynced": int(time.time() * 1000)
506
+ },
507
+ "status": "draft",
508
+ "progress": {
509
+ "status": "",
510
+ "message": "",
511
+ "progress": 0
512
+ },
513
+ "dateCreated": int(time.time() * 1000),
514
+ "dateSynced": None,
515
+ "dateUpdated": None,
516
+ "can_sync": False,
517
+ "can_upload": False,
518
+ "cloud_id": "",
519
+ "type": "docs",
520
+ "metadata": {
521
+ "urls": valid_urls
522
+ },
523
+ "request_id": request_id
524
+ }
525
+
526
+ # Upload
527
+ console.print("[cyan]📤 Processing documentation...[/cyan]")
528
+ console.print()
529
+
530
+ try:
531
+ await self._upload_via_socket(upload_data, "docs")
532
+ return True
533
+ except Exception as e:
534
+ console.print()
535
+ console.print(Panel(
536
+ f"[red]❌ Processing failed[/red]\n\n{str(e)}",
537
+ border_style="red",
538
+ padding=(1, 2)
539
+ ))
540
+ return False
541
+
542
+ async def _upload_via_socket(self, upload_data: dict, kb_type: str):
543
+ """Upload knowledge base via SocketIO"""
544
+ sio = socketio.AsyncClient()
545
+ upload_complete = False
546
+ upload_error = None
547
+
548
+ @sio.on("upload:success")
549
+ def on_success(data):
550
+ nonlocal upload_complete
551
+ upload_complete = True
552
+ console.print()
553
+
554
+ if kb_type == "codebase":
555
+ file_count = len(upload_data['metadata']['files'])
556
+ detail_line = f"[white]Files:[/white] [cyan]{file_count}[/cyan]"
557
+ else:
558
+ url_count = len(upload_data['metadata']['urls'])
559
+ detail_line = f"[white]URLs:[/white] [cyan]{url_count}[/cyan]"
560
+
561
+ console.print(Panel(
562
+ "[green]✓ Knowledge base created successfully![/green]\n\n"
563
+ f"[white]Name:[/white] [cyan]{upload_data['name']}[/cyan]\n"
564
+ f"{detail_line}\n\n"
565
+ "[dim]Use /listkb to view all knowledge bases[/dim]",
566
+ border_style="green",
567
+ title="[bold green]Success[/bold green]",
568
+ padding=(1, 2)
569
+ ))
570
+
571
+ @sio.on("upload:error")
572
+ def on_error(data):
573
+ nonlocal upload_complete, upload_error
574
+ upload_complete = True
575
+ upload_error = data.get("message", "Unknown error")
576
+ console.print()
577
+ console.print(Panel(
578
+ f"[red]❌ Upload failed[/red]\n\n{upload_error}",
579
+ border_style="red",
580
+ padding=(1, 2)
581
+ ))
582
+
583
+ try:
584
+ # Connect to server
585
+ await sio.connect(self.socket_url)
586
+
587
+ # Send upload event
588
+ await sio.emit("upload", upload_data)
589
+
590
+ # Wait for completion with progress indicator
591
+ with Progress(
592
+ SpinnerColumn(),
593
+ TextColumn("[progress.description]{task.description}"),
594
+ transient=True
595
+ ) as progress:
596
+ task = progress.add_task(
597
+ f"Processing {kb_type}..." if kb_type == "docs" else "Uploading files...",
598
+ total=None
599
+ )
600
+
601
+ timeout = 6000 # 5 minutes
602
+ start_time = time.time()
603
+
604
+ while not upload_complete:
605
+ if time.time() - start_time > timeout:
606
+ raise TimeoutError("Upload timed out after 5 minutes")
607
+ await sio.sleep(0.5)
608
+
609
+ if upload_error:
610
+ raise Exception(upload_error)
611
+
612
+ finally:
613
+ await sio.disconnect()
614
+
615
+ async def delete_kb(self) -> bool:
616
+ """Interactive knowledge base deletion"""
617
+ console.print()
618
+ console.print(Panel(
619
+ "[red]🗑️ Delete Knowledge Base[/red]\n\n"
620
+ "[yellow]Warning: This action cannot be undone![/yellow]",
621
+ border_style="red",
622
+ padding=(1, 2),
623
+ box=box.ROUNDED
624
+ ))
625
+ console.print()
626
+
627
+ # Get available KBs
628
+ try:
629
+ kbs = self.client.list_kbs()
630
+
631
+ if not kbs:
632
+ console.print("[yellow]No knowledge bases found.[/yellow]")
633
+ return False
634
+
635
+ # Show available KBs
636
+ console.print("[cyan]Available knowledge bases:[/cyan]")
637
+ console.print()
638
+
639
+ table = Table(
640
+ show_header=True,
641
+ header_style="bold magenta",
642
+ border_style="blue",
643
+ box=box.ROUNDED
644
+ )
645
+ table.add_column("#", style="dim", width=4)
646
+ table.add_column("Name", style="cyan", width=25)
647
+ table.add_column("Type", style="yellow", width=12)
648
+ table.add_column("Files/Items", style="green", width=12)
649
+
650
+ for idx, kb in enumerate(kbs, 1):
651
+ kb_name = kb.get('name', '')
652
+ kb_type = kb.get('type', 'unknown')
653
+
654
+ # Get count based on type
655
+ metadata = kb.get('metadata', {})
656
+ if kb_type == "codebase":
657
+ count = len(metadata.get('files', [])) if 'files' in metadata else '-'
658
+ elif kb_type == "docs":
659
+ count = len(metadata.get('urls', [])) if 'urls' in metadata else '-'
660
+ else:
661
+ count = '-'
662
+
663
+ table.add_row(str(idx), kb_name, kb_type, str(count))
664
+
665
+ console.print(table)
666
+ console.print()
667
+
668
+ except Exception as e:
669
+ console.print(f"[red]Error listing knowledge bases: {e}[/red]")
670
+ return False
671
+
672
+ # Get KB to delete
673
+ kb_input = Prompt.ask(
674
+ "[cyan]Enter knowledge base name or number[/cyan]"
675
+ )
676
+
677
+ # Find KB
678
+ selected_kb = None
679
+
680
+ try:
681
+ idx = int(kb_input) - 1
682
+ if 0 <= idx < len(kbs):
683
+ selected_kb = kbs[idx]
684
+ except ValueError:
685
+ for kb in kbs:
686
+ if kb.get('name', '').lower() == kb_input.lower():
687
+ selected_kb = kb
688
+ break
689
+
690
+ if not selected_kb:
691
+ console.print()
692
+ console.print(f"[red]❌ Knowledge base not found: {kb_input}[/red]")
693
+ return False
694
+
695
+ kb_name = selected_kb.get('name')
696
+ kb_id = selected_kb.get('id')
697
+
698
+ console.print()
699
+ console.print(f"[yellow]⚠️ You are about to delete:[/yellow] [cyan]{kb_name}[/cyan]")
700
+ console.print()
701
+
702
+ # Confirm deletion
703
+ if not Confirm.ask(
704
+ f"[red]Are you sure you want to delete '{kb_name}'?[/red]",
705
+ default=False
706
+ ):
707
+ console.print("[yellow]Cancelled.[/yellow]")
708
+ return False
709
+
710
+ # Delete KB
711
+ console.print()
712
+ console.print("[cyan]🗑️ Deleting knowledge base...[/cyan]")
713
+
714
+ try:
715
+ async with httpx.AsyncClient(timeout=30.0) as client:
716
+ response = await client.post(
717
+ f"{self.api_url}/delete_kb",
718
+ json={"kbid": kb_id},
719
+ headers={"Content-Type": "application/json"}
720
+ )
721
+ response.raise_for_status()
722
+ result = response.json()
723
+
724
+ if result.get("status") == "success":
725
+ console.print()
726
+ console.print(Panel(
727
+ f"[green]✓ Successfully deleted '{kb_name}'[/green]",
728
+ border_style="green",
729
+ padding=(1, 2)
730
+ ))
731
+ return True
732
+ else:
733
+ error_msg = result.get("message", "Unknown error")
734
+ console.print()
735
+ console.print(Panel(
736
+ f"[red]❌ Deletion failed[/red]\n\n{error_msg}",
737
+ border_style="red",
738
+ padding=(1, 2)
739
+ ))
740
+ return False
741
+
742
+ except Exception as e:
743
+ console.print()
744
+ console.print(Panel(
745
+ f"[red]❌ Error deleting knowledge base[/red]\n\n{str(e)}",
746
+ border_style="red",
747
+ padding=(1, 2)
748
+ ))
749
+ return False