codegraph-cli 2.1.1__py3-none-any.whl → 2.1.2__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.
Files changed (42) hide show
  1. codegraph_cli/__init__.py +1 -1
  2. codegraph_cli/agents.py +59 -3
  3. codegraph_cli/chat_agent.py +58 -11
  4. codegraph_cli/cli.py +569 -54
  5. codegraph_cli/cli_chat.py +200 -95
  6. codegraph_cli/cli_diagnose.py +13 -2
  7. codegraph_cli/cli_docs.py +207 -0
  8. codegraph_cli/cli_explore.py +1053 -0
  9. codegraph_cli/cli_export.py +941 -0
  10. codegraph_cli/cli_groups.py +33 -0
  11. codegraph_cli/cli_health.py +316 -0
  12. codegraph_cli/cli_history.py +213 -0
  13. codegraph_cli/cli_onboard.py +380 -0
  14. codegraph_cli/cli_quickstart.py +256 -0
  15. codegraph_cli/cli_refactor.py +17 -3
  16. codegraph_cli/cli_setup.py +12 -12
  17. codegraph_cli/cli_suggestions.py +90 -0
  18. codegraph_cli/cli_test.py +17 -3
  19. codegraph_cli/cli_tui.py +210 -0
  20. codegraph_cli/cli_v2.py +24 -4
  21. codegraph_cli/cli_watch.py +158 -0
  22. codegraph_cli/cli_workflows.py +255 -0
  23. codegraph_cli/codegen_agent.py +15 -1
  24. codegraph_cli/config.py +18 -5
  25. codegraph_cli/context_manager.py +117 -15
  26. codegraph_cli/crew_agents.py +26 -7
  27. codegraph_cli/crew_chat.py +141 -12
  28. codegraph_cli/crew_tools.py +21 -1
  29. codegraph_cli/embeddings.py +95 -5
  30. codegraph_cli/llm.py +42 -55
  31. codegraph_cli/project_context.py +64 -1
  32. codegraph_cli/rag.py +282 -19
  33. codegraph_cli/storage.py +310 -14
  34. codegraph_cli/vector_store.py +110 -8
  35. {codegraph_cli-2.1.1.dist-info → codegraph_cli-2.1.2.dist-info}/METADATA +35 -24
  36. codegraph_cli-2.1.2.dist-info/RECORD +55 -0
  37. codegraph_cli-2.1.2.dist-info/entry_points.txt +2 -0
  38. codegraph_cli-2.1.1.dist-info/RECORD +0 -43
  39. codegraph_cli-2.1.1.dist-info/entry_points.txt +0 -2
  40. {codegraph_cli-2.1.1.dist-info → codegraph_cli-2.1.2.dist-info}/WHEEL +0 -0
  41. {codegraph_cli-2.1.1.dist-info → codegraph_cli-2.1.2.dist-info}/licenses/LICENSE +0 -0
  42. {codegraph_cli-2.1.1.dist-info → codegraph_cli-2.1.2.dist-info}/top_level.txt +0 -0
codegraph_cli/cli_chat.py CHANGED
@@ -2,13 +2,17 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import os
6
5
  import shutil
7
6
  from datetime import datetime
8
7
  from pathlib import Path
9
8
  from typing import Optional
10
9
 
11
10
  import typer
11
+ from rich.console import Console
12
+ from rich.panel import Panel
13
+ from rich.rule import Rule
14
+ from rich.table import Table
15
+ from rich.text import Text
12
16
 
13
17
  from . import config
14
18
  from .chat_agent import ChatAgent
@@ -18,19 +22,7 @@ from .orchestrator import MCPOrchestrator
18
22
  from .rag import RAGRetriever
19
23
  from .storage import GraphStore, ProjectManager
20
24
 
21
-
22
- # ── Theme colors ──────────────────────────────────────────────
23
- C_RESET = "\033[0m"
24
- C_BOLD = "\033[1m"
25
- C_DIM = "\033[2m"
26
- C_CYAN = "\033[36m"
27
- C_GREEN = "\033[32m"
28
- C_YELLOW = "\033[33m"
29
- C_MAGENTA = "\033[35m"
30
- C_RED = "\033[31m"
31
- C_BLUE = "\033[34m"
32
- C_WHITE = "\033[97m"
33
- C_BG_DARK = "\033[48;5;235m"
25
+ console = Console()
34
26
 
35
27
 
36
28
  def _term_width() -> int:
@@ -38,45 +30,40 @@ def _term_width() -> int:
38
30
  return shutil.get_terminal_size((80, 24)).columns
39
31
 
40
32
 
41
- def _box(text: str, color: str = C_CYAN, width: int = 0) -> str:
42
- """Draw a box around text."""
43
- w = width or min(_term_width(), 70)
44
- inner = w - 4
45
- lines = text.split("\n")
46
- out = [f"{color}╭{'─' * (w - 2)}╮{C_RESET}"]
47
- for line in lines:
48
- padded = line.ljust(inner)[:inner]
49
- out.append(f"{color}│{C_RESET} {padded} {color}│{C_RESET}")
50
- out.append(f"{color}╰{'─' * (w - 2)}╯{C_RESET}")
51
- return "\n".join(out)
33
+ def _print_welcome(project_name: str, use_crew: bool, provider: str, model: str, auto_context: dict | None = None):
34
+ """Print the modern welcome banner."""
35
+ mode = "CrewAI Multi-Agent" if use_crew else "Chat"
52
36
 
37
+ console.print()
38
+ console.print(Panel(f"⚡ CodeGraph {mode}", style="bold cyan", width=min(_term_width(), 70)))
39
+ console.print(f" [dim]Project[/dim] [white]{project_name}[/white]")
40
+ console.print(f" [dim]LLM[/dim] [white]{provider}/{model}[/white]")
53
41
 
54
- def _divider(char: str = "─", color: str = C_DIM) -> str:
55
- w = min(_term_width(), 70)
56
- return f"{color}{char * w}{C_RESET}"
42
+ # Show auto-context summary
43
+ if auto_context:
44
+ summary = auto_context.get("summary", {})
45
+ indexed_files = summary.get("indexed_files", 0)
46
+ total_nodes = summary.get("total_nodes", 0)
47
+ node_types = summary.get("node_types", {})
48
+ if indexed_files or total_nodes:
49
+ funcs = node_types.get("function", 0)
50
+ classes = node_types.get("class", 0)
51
+ console.print(f" [dim]Indexed[/dim] [white]{indexed_files} files • {funcs} functions • {classes} classes[/white]")
52
+ recent = auto_context.get("recent_files", [])
53
+ if recent:
54
+ console.print(f" [dim]Recent[/dim] [white]{', '.join(recent[:3])}[/white]")
57
55
 
58
-
59
- def _print_welcome(project_name: str, use_crew: bool, provider: str, model: str):
60
- """Print the modern welcome banner."""
61
- mode = "CrewAI Multi-Agent" if use_crew else "Chat"
62
-
63
- banner = (
64
- f" {C_BOLD}{C_CYAN}⚡ CodeGraph {mode}{C_RESET}\n"
65
- f" {C_DIM}Project: {C_WHITE}{project_name}{C_RESET}\n"
66
- f" {C_DIM}LLM: {C_WHITE}{provider}/{model}{C_RESET}"
67
- )
68
- print(f"\n{_box(f'⚡ CodeGraph {mode}', C_CYAN)}")
69
- print(f" {C_DIM}Project {C_RESET}{C_WHITE}{project_name}{C_RESET}")
70
- print(f" {C_DIM}LLM {C_RESET}{C_WHITE}{provider}/{model}{C_RESET}")
71
- print(f" {C_DIM}Type {C_YELLOW}/help{C_DIM} for commands, {C_YELLOW}/exit{C_DIM} to quit{C_RESET}")
72
- print(_divider())
73
- print()
56
+ console.print(" [dim]Type [yellow]/help[/yellow] for commands, [yellow]/exit[/yellow] to quit[/dim]")
57
+ console.print(Rule(style="dim"))
58
+ console.print()
74
59
 
75
60
 
76
61
  def _print_help(use_crew: bool):
77
62
  """Print help with styled formatting."""
78
- print(f"\n {C_BOLD}{C_CYAN}📖 Commands{C_RESET}")
79
- print(_divider("", C_DIM))
63
+ console.print()
64
+ table = Table(show_header=False, box=None, padding=(0, 2), title="📖 Commands", title_style="bold cyan")
65
+ table.add_column(style="yellow", min_width=22)
66
+ table.add_column(style="dim")
80
67
  cmds = [
81
68
  ("/exit", "Exit chat session"),
82
69
  ("/clear", "Clear conversation history & start fresh"),
@@ -95,22 +82,23 @@ def _print_help(use_crew: bool):
95
82
  ("/preview", "Preview pending changes"),
96
83
  ])
97
84
  for cmd, desc in cmds:
98
- print(f" {C_YELLOW}{cmd:<22}{C_RESET}{C_DIM}{desc}{C_RESET}")
99
- print()
85
+ table.add_row(cmd, desc)
86
+ console.print(table)
87
+ console.print()
100
88
 
101
89
 
102
90
  def _print_response(text: str):
103
91
  """Print assistant response with styling."""
104
- print(f"\n {C_GREEN}●{C_RESET} {C_BOLD}Assistant{C_RESET}")
105
- # Indent each line for clean look
92
+ console.print()
93
+ console.print(" [green]●[/green] [bold]Assistant[/bold]")
106
94
  for line in text.split("\n"):
107
- print(f" {line}")
108
- print()
95
+ console.print(f" {line}", highlight=False)
96
+ console.print()
109
97
 
110
98
 
111
- def _print_status(emoji: str, msg: str, color: str = C_GREEN):
99
+ def _print_status(emoji: str, msg: str, style: str = "green"):
112
100
  """Print a status message."""
113
- print(f" {color}{emoji} {msg}{C_RESET}")
101
+ console.print(f" [{style}]{emoji} {msg}[/{style}]")
114
102
 
115
103
 
116
104
  def start_chat_repl(
@@ -121,13 +109,14 @@ def start_chat_repl(
121
109
  use_crew: bool = False,
122
110
  provider: str = "",
123
111
  model: str = "",
112
+ auto_context: dict | None = None,
124
113
  ):
125
114
  """Start interactive REPL for chat."""
126
115
  # Load or create session
127
116
  if session_id:
128
117
  session = session_manager.load_session(session_id)
129
118
  if not session:
130
- _print_status("🆕", "Session not found. Starting new session.", C_YELLOW)
119
+ _print_status("🆕", "Session not found. Starting new session.", "yellow")
131
120
  session = session_manager.create_session(project_name)
132
121
  else:
133
122
  _print_status("📂", f"Resumed session ({session.message_count} messages)")
@@ -141,17 +130,21 @@ def start_chat_repl(
141
130
  session = session_manager.create_session(project_name)
142
131
  _print_status("🆕", "Started new chat session")
143
132
 
133
+ # Hydrate agent with session history (enables crew continuity across restarts)
134
+ if hasattr(agent, 'load_session_history') and session.messages:
135
+ agent.load_session_history(session)
136
+
144
137
  # Welcome
145
- _print_welcome(project_name, use_crew, provider, model)
138
+ _print_welcome(project_name, use_crew, provider, model, auto_context=auto_context)
146
139
 
147
140
  # REPL loop
148
141
  while True:
149
142
  try:
150
143
  # Prompt
151
144
  try:
152
- user_input = input(f" {C_BLUE}{C_RESET} {C_BOLD}You ›{C_RESET} ").strip()
145
+ user_input = console.input(" [blue][/blue] [bold]You ›[/bold] ").strip()
153
146
  except EOFError:
154
- print(f"\n {C_DIM}👋 Goodbye! Session saved.{C_RESET}\n")
147
+ console.print("\n [dim]👋 Goodbye! Session saved.[/dim]\n")
155
148
  break
156
149
 
157
150
  if not user_input:
@@ -163,22 +156,28 @@ def start_chat_repl(
163
156
 
164
157
  if cmd == "/exit":
165
158
  session_manager.save_session(session)
166
- print(f"\n {C_DIM}👋 Goodbye! Session saved.{C_RESET}\n")
159
+ console.print("\n [dim]👋 Goodbye! Session saved.[/dim]\n")
167
160
  break
168
161
 
169
162
  elif cmd == "/clear":
170
163
  session.clear_history()
171
164
  session.clear_proposals()
172
165
  session_manager.save_session(session)
173
- _print_status("🧹", "Conversation cleared. Fresh start!", C_GREEN)
174
- print()
166
+ # Clear agent-side memory (crew mode)
167
+ if hasattr(agent, 'clear_history'):
168
+ agent.clear_history()
169
+ _print_status("🧹", "Conversation cleared. Fresh start!")
170
+ console.print()
175
171
  continue
176
172
 
177
173
  elif cmd == "/new":
178
174
  session_manager.save_session(session)
179
175
  session = session_manager.create_session(project_name)
180
- _print_status("🆕", "New session started.", C_GREEN)
181
- print()
176
+ # Clear agent-side memory (crew mode)
177
+ if hasattr(agent, 'clear_history'):
178
+ agent.clear_history()
179
+ _print_status("🆕", "New session started.")
180
+ console.print()
182
181
  continue
183
182
 
184
183
  elif cmd == "/help":
@@ -191,57 +190,57 @@ def start_chat_repl(
191
190
  result = agent.apply_pending_proposal(session)
192
191
  _print_status("📋", result)
193
192
  else:
194
- _print_status("📋", "No pending proposals.", C_YELLOW)
193
+ _print_status("📋", "No pending proposals.", "yellow")
195
194
  continue
196
195
  elif cmd == "/preview":
197
196
  if session.pending_proposals:
198
197
  for i, prop in enumerate(session.pending_proposals):
199
- print(f"\n {C_BOLD}Proposal {i+1}:{C_RESET} {prop.description}")
198
+ console.print(f"\n [bold]Proposal {i+1}:[/bold] {prop.description}")
200
199
  for ch in prop.changes:
201
200
  icon = {"create": "🆕", "modify": "✏️", "delete": "🗑️"}.get(ch.change_type, "📄")
202
- print(f" {icon} {ch.file_path}")
201
+ console.print(f" {icon} {ch.file_path}")
203
202
  else:
204
- _print_status("📋", "No pending proposals.", C_YELLOW)
205
- print()
203
+ _print_status("📋", "No pending proposals.", "yellow")
204
+ console.print()
206
205
  continue
207
206
 
208
207
  elif use_crew:
209
208
  if cmd in ("/rollback", "/undo"):
210
209
  parts = user_input.split(maxsplit=1)
211
210
  if len(parts) < 2:
212
- _print_status("❓", "Usage: /rollback <file_path> [timestamp]", C_YELLOW)
211
+ _print_status("❓", "Usage: /rollback <file_path> [timestamp]", "yellow")
213
212
  continue
214
213
  args = parts[1].split()
215
214
  file_path = args[0]
216
215
  ts = args[1] if len(args) > 1 else None
217
216
  result = agent.rollback(file_path, ts)
218
217
  _print_status("⏪", result)
219
- print()
218
+ console.print()
220
219
  continue
221
220
 
222
221
  elif cmd == "/backups":
223
222
  backups = agent.list_all_backups()
224
223
  if not backups:
225
- _print_status("📦", "No backups found.", C_YELLOW)
224
+ _print_status("📦", "No backups found.", "yellow")
226
225
  else:
227
- print(f"\n {C_BOLD}{C_CYAN}📦 File Backups{C_RESET}")
228
- print(_divider("", C_DIM))
226
+ console.print("\n [bold cyan]📦 File Backups[/bold cyan]")
227
+ console.print(Rule(style="dim"))
229
228
  for b in backups:
230
229
  ts = b["timestamp"]
231
230
  fp = b["original_path"]
232
- print(f" {C_WHITE}{ts}{C_RESET} {C_DIM}{fp}{C_RESET}")
233
- print(f"\n {C_DIM}Use /rollback <file_path> to restore{C_RESET}")
234
- print()
231
+ console.print(f" [white]{ts}[/white] [dim]{fp}[/dim]")
232
+ console.print("\n [dim]Use /rollback <file_path> to restore[/dim]")
233
+ console.print()
235
234
  continue
236
235
 
237
- _print_status("❓", f"Unknown command: {cmd}. Type /help", C_YELLOW)
236
+ _print_status("❓", f"Unknown command: {cmd}. Type /help", "yellow")
238
237
  continue
239
238
 
240
239
  # ── Process message ──────────────────────────────
241
240
  session.add_message("user", user_input, datetime.now().isoformat())
242
241
 
243
242
  # Show thinking indicator
244
- print(f"\n {C_DIM}⏳ Thinking...{C_RESET}", end="", flush=True)
243
+ console.print("\n [dim]⏳ Thinking...[/dim]", end="")
245
244
 
246
245
  if use_crew:
247
246
  response = agent.process_message(user_input)
@@ -249,7 +248,7 @@ def start_chat_repl(
249
248
  response = agent.process_message(user_input, session)
250
249
 
251
250
  # Clear thinking indicator
252
- print(f"\r{' ' * 30}\r", end="")
251
+ console.print(f"\r{' ' * 30}\r", end="")
253
252
 
254
253
  # Save & display
255
254
  session.add_message("assistant", response, datetime.now().isoformat())
@@ -259,16 +258,46 @@ def start_chat_repl(
259
258
 
260
259
  except KeyboardInterrupt:
261
260
  session_manager.save_session(session)
262
- print(f"\n\n {C_DIM}👋 Goodbye! Session saved.{C_RESET}\n")
261
+ console.print("\n\n [dim]👋 Goodbye! Session saved.[/dim]\n")
263
262
  break
264
263
  except Exception as e:
265
- print(f"\n {C_RED}❌ Error: {str(e)}{C_RESET}\n")
264
+ console.print(f"\n [red]❌ Error: {str(e)}[/red]\n")
266
265
 
267
266
 
268
267
  # ── Typer app ────────────────────────────────────────────────
269
268
  chat_app = typer.Typer(help="💬 Interactive chat with AI agents")
270
269
 
271
270
 
271
+ def _gather_auto_context(project_context) -> dict:
272
+ """Gather automatic context from the project for chat enrichment.
273
+
274
+ Collects project summary (file count, symbol count, node types)
275
+ and recently modified files so the chat session starts with
276
+ useful awareness of the codebase.
277
+ """
278
+ auto_ctx: dict = {}
279
+
280
+ try:
281
+ summary = project_context.get_project_summary()
282
+ auto_ctx["summary"] = summary
283
+ except Exception:
284
+ auto_ctx["summary"] = {}
285
+
286
+ # Recently modified source files
287
+ try:
288
+ if project_context.has_source_access:
289
+ items = project_context.list_directory(".")
290
+ files = [f for f in items if f["type"] == "file"]
291
+ files.sort(key=lambda f: f.get("modified", ""), reverse=True)
292
+ auto_ctx["recent_files"] = [f["name"] for f in files[:5]]
293
+ else:
294
+ auto_ctx["recent_files"] = []
295
+ except Exception:
296
+ auto_ctx["recent_files"] = []
297
+
298
+ return auto_ctx
299
+
300
+
272
301
  @chat_app.command("start")
273
302
  def start_chat(
274
303
  session_id: Optional[str] = typer.Option(None, "--session", "-s", help="Resume specific session ID"),
@@ -279,7 +308,14 @@ def start_chat(
279
308
  use_crew: bool = typer.Option(False, "--crew", help="Use CrewAI multi-agent system"),
280
309
  new_session: bool = typer.Option(False, "--new", "-n", help="Force start a new session"),
281
310
  ):
282
- """Start interactive chat session."""
311
+ """💬 Start interactive chat session.
312
+
313
+ Example:
314
+ cg chat start
315
+ cg chat start --crew
316
+ cg chat start --new
317
+ cg chat start --session abc123
318
+ """
283
319
  from .embeddings import get_embedder
284
320
  from .project_context import ProjectContext
285
321
 
@@ -287,8 +323,8 @@ def start_chat(
287
323
  project = pm.get_current_project()
288
324
 
289
325
  if not project:
290
- print(f"\n {C_RED}❌ No project loaded.{C_RESET}")
291
- print(f" {C_DIM}Use: cg load-project <name> or cg index <path>{C_RESET}\n")
326
+ console.print("\n [red]❌ No project loaded.[/red]")
327
+ console.print(" [dim]Use: cg load-project <name> or cg index <path>[/dim]\n")
292
328
  raise typer.Exit(1)
293
329
 
294
330
  # Initialize components
@@ -301,10 +337,10 @@ def start_chat(
301
337
  try:
302
338
  from .crew_chat import CrewChatAgent
303
339
  except ImportError:
304
- print(f"\n {C_RED}CrewAI is not installed.{C_RESET}")
305
- print(f" {C_DIM}Install with: pip install codegraph-cli[crew]{C_RESET}\n")
340
+ console.print("\n [red]CrewAI is not installed.[/red]")
341
+ console.print(" [dim]Install with: pip install codegraph-cli\\[crew][/dim]\n")
306
342
  raise typer.Exit(1)
307
- print(f"\n {C_MAGENTA}Initializing CrewAI multi-agent system...{C_RESET}")
343
+ console.print("\n [magenta]Initializing CrewAI multi-agent system...[/magenta]")
308
344
  agent = CrewChatAgent(context, llm, rag_retriever)
309
345
  else:
310
346
  orchestrator = MCPOrchestrator(
@@ -318,6 +354,9 @@ def start_chat(
318
354
 
319
355
  session_manager = SessionManager()
320
356
 
357
+ # Gather auto-context from the project for enriched chat experience
358
+ auto_context = _gather_auto_context(context)
359
+
321
360
  # Force new session if requested
322
361
  effective_session_id = None if new_session else session_id
323
362
 
@@ -327,6 +366,7 @@ def start_chat(
327
366
  use_crew=use_crew,
328
367
  provider=llm_provider,
329
368
  model=llm_model,
369
+ auto_context=auto_context,
330
370
  )
331
371
  finally:
332
372
  context.close()
@@ -336,16 +376,21 @@ def start_chat(
336
376
  def list_sessions(
337
377
  project: Optional[str] = typer.Option(None, "--project", "-p", help="Filter by project")
338
378
  ):
339
- """List all chat sessions."""
379
+ """📋 List all chat sessions.
380
+
381
+ Example:
382
+ cg chat list
383
+ cg chat list --project my-api
384
+ """
340
385
  session_manager = SessionManager()
341
386
  sessions = session_manager.list_sessions(project_name=project)
342
387
 
343
388
  if not sessions:
344
- print(f"\n {C_DIM}No chat sessions found.{C_RESET}\n")
389
+ console.print("\n [dim]No chat sessions found.[/dim]\n")
345
390
  return
346
391
 
347
- print(f"\n {C_BOLD}{C_CYAN}📋 Chat Sessions ({len(sessions)}){C_RESET}")
348
- print(_divider())
392
+ console.print(f"\n [bold cyan]📋 Chat Sessions ({len(sessions)})[/bold cyan]")
393
+ console.print(Rule(style="dim"))
349
394
 
350
395
  for i, sess in enumerate(sessions, 1):
351
396
  created = sess['created_at'][:16].replace("T", " ")
@@ -353,20 +398,80 @@ def list_sessions(
353
398
  proj = sess['project_name']
354
399
  sid = sess['id'][:8]
355
400
 
356
- print(f" {C_WHITE}{i}.{C_RESET} {C_BOLD}{proj}{C_RESET} {C_DIM}({msgs} msgs, {created}){C_RESET} {C_DIM}id:{sid}…{C_RESET}")
401
+ console.print(f" [white]{i}.[/white] [bold]{proj}[/bold] [dim]({msgs} msgs, {created})[/dim] [dim]id:{sid}…[/dim]")
357
402
 
358
- print()
403
+ console.print()
359
404
 
360
405
 
361
406
  @chat_app.command("delete")
362
407
  def delete_session(
363
408
  session_id: str = typer.Argument(..., help="Session ID to delete")
364
409
  ):
365
- """Delete a chat session."""
410
+ """🗑️ Delete a chat session.
411
+
412
+ Example:
413
+ cg chat delete abc12345
414
+ """
366
415
  session_manager = SessionManager()
367
416
 
368
417
  if session_manager.delete_session(session_id):
369
418
  _print_status("✅", f"Deleted session {session_id[:8]}…")
370
419
  else:
371
- print(f" {C_RED}❌ Session not found{C_RESET}")
420
+ console.print(" [red]❌ Session not found[/red]")
372
421
  raise typer.Exit(1)
422
+
423
+
424
+ @chat_app.command("export")
425
+ def export_session(
426
+ session_id: str = typer.Argument(..., help="Session ID to export"),
427
+ fmt: str = typer.Option("markdown", "--format", "-f", help="Export format: markdown, json"),
428
+ output: Optional[str] = typer.Option(None, "--output", "-o", help="Output file path"),
429
+ ):
430
+ """📤 Export chat session to a file.
431
+
432
+ Example:
433
+ cg chat export abc12345
434
+ cg chat export abc12345 --format json
435
+ cg chat export abc12345 --output conversation.md
436
+ """
437
+ import json as _json
438
+
439
+ session_manager = SessionManager()
440
+ session = session_manager.load_session(session_id)
441
+
442
+ if not session:
443
+ console.print(f" [red]❌ Session '{session_id}' not found[/red]")
444
+ raise typer.Exit(1)
445
+
446
+ if fmt == "markdown":
447
+ lines = [
448
+ f"# Chat Session: {session.project_name}",
449
+ f"\nSession ID: {session.id}",
450
+ f"Messages: {session.message_count}\n",
451
+ "---\n",
452
+ ]
453
+ for msg in session.messages:
454
+ role = "**You**" if msg["role"] == "user" else "**CodeGraph**"
455
+ lines.append(f"### {role}\n")
456
+ lines.append(f"{msg['content']}\n")
457
+ lines.append("\n---\n")
458
+ content = "\n".join(lines)
459
+ ext = ".md"
460
+
461
+ elif fmt == "json":
462
+ data = {
463
+ "session_id": session.id,
464
+ "project_name": session.project_name,
465
+ "message_count": session.message_count,
466
+ "messages": session.messages,
467
+ }
468
+ content = _json.dumps(data, indent=2)
469
+ ext = ".json"
470
+
471
+ else:
472
+ console.print(f" [red]❌ Unknown format: {fmt}[/red]")
473
+ raise typer.Exit(1)
474
+
475
+ filename = output or f"{session_id[:12]}_export{ext}"
476
+ Path(filename).write_text(content)
477
+ _print_status("✅", f"Exported to {filename}")
@@ -19,7 +19,12 @@ diagnose_app = typer.Typer(help="Detect and fix code errors")
19
19
  def check_errors(
20
20
  path: str = typer.Argument(".", help="Path to check (default: current directory)"),
21
21
  ):
22
- """Scan project for syntax errors."""
22
+ """🔍 Scan project for syntax errors.
23
+
24
+ Example:
25
+ cg v2 diagnose check
26
+ cg v2 diagnose check ./src
27
+ """
23
28
  project_path = Path(path).resolve()
24
29
 
25
30
  if not project_path.exists():
@@ -49,7 +54,13 @@ def fix_errors(
49
54
  preview_only: bool = typer.Option(False, "--preview", "-p", help="Preview fixes without applying"),
50
55
  auto_apply: bool = typer.Option(False, "--auto-apply", "-y", help="Apply fixes without confirmation"),
51
56
  ):
52
- """Automatically fix common syntax errors."""
57
+ """🔧 Automatically fix common syntax errors.
58
+
59
+ Example:
60
+ cg v2 diagnose fix
61
+ cg v2 diagnose fix ./src --preview
62
+ cg v2 diagnose fix ./src --auto-apply
63
+ """
53
64
  project_path = Path(path).resolve()
54
65
 
55
66
  if not project_path.exists():