codegraph-cli 2.1.0__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 +204 -94
  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 +32 -8
  27. codegraph_cli/crew_chat.py +146 -13
  28. codegraph_cli/crew_tools.py +30 -2
  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.0.dist-info → codegraph_cli-2.1.2.dist-info}/METADATA +75 -21
  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.0.dist-info/RECORD +0 -43
  39. codegraph_cli-2.1.0.dist-info/entry_points.txt +0 -2
  40. {codegraph_cli-2.1.0.dist-info → codegraph_cli-2.1.2.dist-info}/WHEEL +0 -0
  41. {codegraph_cli-2.1.0.dist-info → codegraph_cli-2.1.2.dist-info}/licenses/LICENSE +0 -0
  42. {codegraph_cli-2.1.0.dist-info → codegraph_cli-2.1.2.dist-info}/top_level.txt +0 -0
codegraph_cli/cli_chat.py CHANGED
@@ -2,36 +2,27 @@
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
15
19
  from .chat_session import SessionManager
16
- from .crew_chat import CrewChatAgent
17
20
  from .llm import LocalLLM
18
21
  from .orchestrator import MCPOrchestrator
19
22
  from .rag import RAGRetriever
20
23
  from .storage import GraphStore, ProjectManager
21
24
 
22
-
23
- # ── Theme colors ──────────────────────────────────────────────
24
- C_RESET = "\033[0m"
25
- C_BOLD = "\033[1m"
26
- C_DIM = "\033[2m"
27
- C_CYAN = "\033[36m"
28
- C_GREEN = "\033[32m"
29
- C_YELLOW = "\033[33m"
30
- C_MAGENTA = "\033[35m"
31
- C_RED = "\033[31m"
32
- C_BLUE = "\033[34m"
33
- C_WHITE = "\033[97m"
34
- C_BG_DARK = "\033[48;5;235m"
25
+ console = Console()
35
26
 
36
27
 
37
28
  def _term_width() -> int:
@@ -39,45 +30,40 @@ def _term_width() -> int:
39
30
  return shutil.get_terminal_size((80, 24)).columns
40
31
 
41
32
 
42
- def _box(text: str, color: str = C_CYAN, width: int = 0) -> str:
43
- """Draw a box around text."""
44
- w = width or min(_term_width(), 70)
45
- inner = w - 4
46
- lines = text.split("\n")
47
- out = [f"{color}╭{'─' * (w - 2)}╮{C_RESET}"]
48
- for line in lines:
49
- padded = line.ljust(inner)[:inner]
50
- out.append(f"{color}│{C_RESET} {padded} {color}│{C_RESET}")
51
- out.append(f"{color}╰{'─' * (w - 2)}╯{C_RESET}")
52
- 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"
53
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]")
54
41
 
55
- def _divider(char: str = "─", color: str = C_DIM) -> str:
56
- w = min(_term_width(), 70)
57
- 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]")
58
55
 
59
-
60
- def _print_welcome(project_name: str, use_crew: bool, provider: str, model: str):
61
- """Print the modern welcome banner."""
62
- mode = "CrewAI Multi-Agent" if use_crew else "Chat"
63
-
64
- banner = (
65
- f" {C_BOLD}{C_CYAN}⚡ CodeGraph {mode}{C_RESET}\n"
66
- f" {C_DIM}Project: {C_WHITE}{project_name}{C_RESET}\n"
67
- f" {C_DIM}LLM: {C_WHITE}{provider}/{model}{C_RESET}"
68
- )
69
- print(f"\n{_box(f'⚡ CodeGraph {mode}', C_CYAN)}")
70
- print(f" {C_DIM}Project {C_RESET}{C_WHITE}{project_name}{C_RESET}")
71
- print(f" {C_DIM}LLM {C_RESET}{C_WHITE}{provider}/{model}{C_RESET}")
72
- print(f" {C_DIM}Type {C_YELLOW}/help{C_DIM} for commands, {C_YELLOW}/exit{C_DIM} to quit{C_RESET}")
73
- print(_divider())
74
- 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()
75
59
 
76
60
 
77
61
  def _print_help(use_crew: bool):
78
62
  """Print help with styled formatting."""
79
- print(f"\n {C_BOLD}{C_CYAN}📖 Commands{C_RESET}")
80
- 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")
81
67
  cmds = [
82
68
  ("/exit", "Exit chat session"),
83
69
  ("/clear", "Clear conversation history & start fresh"),
@@ -96,22 +82,23 @@ def _print_help(use_crew: bool):
96
82
  ("/preview", "Preview pending changes"),
97
83
  ])
98
84
  for cmd, desc in cmds:
99
- print(f" {C_YELLOW}{cmd:<22}{C_RESET}{C_DIM}{desc}{C_RESET}")
100
- print()
85
+ table.add_row(cmd, desc)
86
+ console.print(table)
87
+ console.print()
101
88
 
102
89
 
103
90
  def _print_response(text: str):
104
91
  """Print assistant response with styling."""
105
- print(f"\n {C_GREEN}●{C_RESET} {C_BOLD}Assistant{C_RESET}")
106
- # Indent each line for clean look
92
+ console.print()
93
+ console.print(" [green]●[/green] [bold]Assistant[/bold]")
107
94
  for line in text.split("\n"):
108
- print(f" {line}")
109
- print()
95
+ console.print(f" {line}", highlight=False)
96
+ console.print()
110
97
 
111
98
 
112
- def _print_status(emoji: str, msg: str, color: str = C_GREEN):
99
+ def _print_status(emoji: str, msg: str, style: str = "green"):
113
100
  """Print a status message."""
114
- print(f" {color}{emoji} {msg}{C_RESET}")
101
+ console.print(f" [{style}]{emoji} {msg}[/{style}]")
115
102
 
116
103
 
117
104
  def start_chat_repl(
@@ -122,13 +109,14 @@ def start_chat_repl(
122
109
  use_crew: bool = False,
123
110
  provider: str = "",
124
111
  model: str = "",
112
+ auto_context: dict | None = None,
125
113
  ):
126
114
  """Start interactive REPL for chat."""
127
115
  # Load or create session
128
116
  if session_id:
129
117
  session = session_manager.load_session(session_id)
130
118
  if not session:
131
- _print_status("🆕", "Session not found. Starting new session.", C_YELLOW)
119
+ _print_status("🆕", "Session not found. Starting new session.", "yellow")
132
120
  session = session_manager.create_session(project_name)
133
121
  else:
134
122
  _print_status("📂", f"Resumed session ({session.message_count} messages)")
@@ -142,17 +130,21 @@ def start_chat_repl(
142
130
  session = session_manager.create_session(project_name)
143
131
  _print_status("🆕", "Started new chat session")
144
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
+
145
137
  # Welcome
146
- _print_welcome(project_name, use_crew, provider, model)
138
+ _print_welcome(project_name, use_crew, provider, model, auto_context=auto_context)
147
139
 
148
140
  # REPL loop
149
141
  while True:
150
142
  try:
151
143
  # Prompt
152
144
  try:
153
- 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()
154
146
  except EOFError:
155
- print(f"\n {C_DIM}👋 Goodbye! Session saved.{C_RESET}\n")
147
+ console.print("\n [dim]👋 Goodbye! Session saved.[/dim]\n")
156
148
  break
157
149
 
158
150
  if not user_input:
@@ -164,22 +156,28 @@ def start_chat_repl(
164
156
 
165
157
  if cmd == "/exit":
166
158
  session_manager.save_session(session)
167
- print(f"\n {C_DIM}👋 Goodbye! Session saved.{C_RESET}\n")
159
+ console.print("\n [dim]👋 Goodbye! Session saved.[/dim]\n")
168
160
  break
169
161
 
170
162
  elif cmd == "/clear":
171
163
  session.clear_history()
172
164
  session.clear_proposals()
173
165
  session_manager.save_session(session)
174
- _print_status("🧹", "Conversation cleared. Fresh start!", C_GREEN)
175
- 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()
176
171
  continue
177
172
 
178
173
  elif cmd == "/new":
179
174
  session_manager.save_session(session)
180
175
  session = session_manager.create_session(project_name)
181
- _print_status("🆕", "New session started.", C_GREEN)
182
- 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()
183
181
  continue
184
182
 
185
183
  elif cmd == "/help":
@@ -192,57 +190,57 @@ def start_chat_repl(
192
190
  result = agent.apply_pending_proposal(session)
193
191
  _print_status("📋", result)
194
192
  else:
195
- _print_status("📋", "No pending proposals.", C_YELLOW)
193
+ _print_status("📋", "No pending proposals.", "yellow")
196
194
  continue
197
195
  elif cmd == "/preview":
198
196
  if session.pending_proposals:
199
197
  for i, prop in enumerate(session.pending_proposals):
200
- print(f"\n {C_BOLD}Proposal {i+1}:{C_RESET} {prop.description}")
198
+ console.print(f"\n [bold]Proposal {i+1}:[/bold] {prop.description}")
201
199
  for ch in prop.changes:
202
200
  icon = {"create": "🆕", "modify": "✏️", "delete": "🗑️"}.get(ch.change_type, "📄")
203
- print(f" {icon} {ch.file_path}")
201
+ console.print(f" {icon} {ch.file_path}")
204
202
  else:
205
- _print_status("📋", "No pending proposals.", C_YELLOW)
206
- print()
203
+ _print_status("📋", "No pending proposals.", "yellow")
204
+ console.print()
207
205
  continue
208
206
 
209
207
  elif use_crew:
210
208
  if cmd in ("/rollback", "/undo"):
211
209
  parts = user_input.split(maxsplit=1)
212
210
  if len(parts) < 2:
213
- _print_status("❓", "Usage: /rollback <file_path> [timestamp]", C_YELLOW)
211
+ _print_status("❓", "Usage: /rollback <file_path> [timestamp]", "yellow")
214
212
  continue
215
213
  args = parts[1].split()
216
214
  file_path = args[0]
217
215
  ts = args[1] if len(args) > 1 else None
218
216
  result = agent.rollback(file_path, ts)
219
217
  _print_status("⏪", result)
220
- print()
218
+ console.print()
221
219
  continue
222
220
 
223
221
  elif cmd == "/backups":
224
222
  backups = agent.list_all_backups()
225
223
  if not backups:
226
- _print_status("📦", "No backups found.", C_YELLOW)
224
+ _print_status("📦", "No backups found.", "yellow")
227
225
  else:
228
- print(f"\n {C_BOLD}{C_CYAN}📦 File Backups{C_RESET}")
229
- print(_divider("", C_DIM))
226
+ console.print("\n [bold cyan]📦 File Backups[/bold cyan]")
227
+ console.print(Rule(style="dim"))
230
228
  for b in backups:
231
229
  ts = b["timestamp"]
232
230
  fp = b["original_path"]
233
- print(f" {C_WHITE}{ts}{C_RESET} {C_DIM}{fp}{C_RESET}")
234
- print(f"\n {C_DIM}Use /rollback <file_path> to restore{C_RESET}")
235
- 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()
236
234
  continue
237
235
 
238
- _print_status("❓", f"Unknown command: {cmd}. Type /help", C_YELLOW)
236
+ _print_status("❓", f"Unknown command: {cmd}. Type /help", "yellow")
239
237
  continue
240
238
 
241
239
  # ── Process message ──────────────────────────────
242
240
  session.add_message("user", user_input, datetime.now().isoformat())
243
241
 
244
242
  # Show thinking indicator
245
- print(f"\n {C_DIM}⏳ Thinking...{C_RESET}", end="", flush=True)
243
+ console.print("\n [dim]⏳ Thinking...[/dim]", end="")
246
244
 
247
245
  if use_crew:
248
246
  response = agent.process_message(user_input)
@@ -250,7 +248,7 @@ def start_chat_repl(
250
248
  response = agent.process_message(user_input, session)
251
249
 
252
250
  # Clear thinking indicator
253
- print(f"\r{' ' * 30}\r", end="")
251
+ console.print(f"\r{' ' * 30}\r", end="")
254
252
 
255
253
  # Save & display
256
254
  session.add_message("assistant", response, datetime.now().isoformat())
@@ -260,16 +258,46 @@ def start_chat_repl(
260
258
 
261
259
  except KeyboardInterrupt:
262
260
  session_manager.save_session(session)
263
- print(f"\n\n {C_DIM}👋 Goodbye! Session saved.{C_RESET}\n")
261
+ console.print("\n\n [dim]👋 Goodbye! Session saved.[/dim]\n")
264
262
  break
265
263
  except Exception as e:
266
- print(f"\n {C_RED}❌ Error: {str(e)}{C_RESET}\n")
264
+ console.print(f"\n [red]❌ Error: {str(e)}[/red]\n")
267
265
 
268
266
 
269
267
  # ── Typer app ────────────────────────────────────────────────
270
268
  chat_app = typer.Typer(help="💬 Interactive chat with AI agents")
271
269
 
272
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
+
273
301
  @chat_app.command("start")
274
302
  def start_chat(
275
303
  session_id: Optional[str] = typer.Option(None, "--session", "-s", help="Resume specific session ID"),
@@ -280,7 +308,14 @@ def start_chat(
280
308
  use_crew: bool = typer.Option(False, "--crew", help="Use CrewAI multi-agent system"),
281
309
  new_session: bool = typer.Option(False, "--new", "-n", help="Force start a new session"),
282
310
  ):
283
- """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
+ """
284
319
  from .embeddings import get_embedder
285
320
  from .project_context import ProjectContext
286
321
 
@@ -288,8 +323,8 @@ def start_chat(
288
323
  project = pm.get_current_project()
289
324
 
290
325
  if not project:
291
- print(f"\n {C_RED}❌ No project loaded.{C_RESET}")
292
- 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")
293
328
  raise typer.Exit(1)
294
329
 
295
330
  # Initialize components
@@ -299,7 +334,13 @@ def start_chat(
299
334
  rag_retriever = RAGRetriever(context.store, embedding_model)
300
335
 
301
336
  if use_crew:
302
- print(f"\n {C_MAGENTA}🤖 Initializing CrewAI multi-agent system...{C_RESET}")
337
+ try:
338
+ from .crew_chat import CrewChatAgent
339
+ except ImportError:
340
+ console.print("\n [red]CrewAI is not installed.[/red]")
341
+ console.print(" [dim]Install with: pip install codegraph-cli\\[crew][/dim]\n")
342
+ raise typer.Exit(1)
343
+ console.print("\n [magenta]Initializing CrewAI multi-agent system...[/magenta]")
303
344
  agent = CrewChatAgent(context, llm, rag_retriever)
304
345
  else:
305
346
  orchestrator = MCPOrchestrator(
@@ -313,6 +354,9 @@ def start_chat(
313
354
 
314
355
  session_manager = SessionManager()
315
356
 
357
+ # Gather auto-context from the project for enriched chat experience
358
+ auto_context = _gather_auto_context(context)
359
+
316
360
  # Force new session if requested
317
361
  effective_session_id = None if new_session else session_id
318
362
 
@@ -322,6 +366,7 @@ def start_chat(
322
366
  use_crew=use_crew,
323
367
  provider=llm_provider,
324
368
  model=llm_model,
369
+ auto_context=auto_context,
325
370
  )
326
371
  finally:
327
372
  context.close()
@@ -331,16 +376,21 @@ def start_chat(
331
376
  def list_sessions(
332
377
  project: Optional[str] = typer.Option(None, "--project", "-p", help="Filter by project")
333
378
  ):
334
- """List all chat sessions."""
379
+ """📋 List all chat sessions.
380
+
381
+ Example:
382
+ cg chat list
383
+ cg chat list --project my-api
384
+ """
335
385
  session_manager = SessionManager()
336
386
  sessions = session_manager.list_sessions(project_name=project)
337
387
 
338
388
  if not sessions:
339
- print(f"\n {C_DIM}No chat sessions found.{C_RESET}\n")
389
+ console.print("\n [dim]No chat sessions found.[/dim]\n")
340
390
  return
341
391
 
342
- print(f"\n {C_BOLD}{C_CYAN}📋 Chat Sessions ({len(sessions)}){C_RESET}")
343
- print(_divider())
392
+ console.print(f"\n [bold cyan]📋 Chat Sessions ({len(sessions)})[/bold cyan]")
393
+ console.print(Rule(style="dim"))
344
394
 
345
395
  for i, sess in enumerate(sessions, 1):
346
396
  created = sess['created_at'][:16].replace("T", " ")
@@ -348,20 +398,80 @@ def list_sessions(
348
398
  proj = sess['project_name']
349
399
  sid = sess['id'][:8]
350
400
 
351
- 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]")
352
402
 
353
- print()
403
+ console.print()
354
404
 
355
405
 
356
406
  @chat_app.command("delete")
357
407
  def delete_session(
358
408
  session_id: str = typer.Argument(..., help="Session ID to delete")
359
409
  ):
360
- """Delete a chat session."""
410
+ """🗑️ Delete a chat session.
411
+
412
+ Example:
413
+ cg chat delete abc12345
414
+ """
361
415
  session_manager = SessionManager()
362
416
 
363
417
  if session_manager.delete_session(session_id):
364
418
  _print_status("✅", f"Deleted session {session_id[:8]}…")
365
419
  else:
366
- print(f" {C_RED}❌ Session not found{C_RESET}")
420
+ console.print(" [red]❌ Session not found[/red]")
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]")
367
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():