emdash-cli 0.1.35__py3-none-any.whl → 0.1.67__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 (50) hide show
  1. emdash_cli/client.py +41 -22
  2. emdash_cli/clipboard.py +30 -61
  3. emdash_cli/commands/__init__.py +2 -2
  4. emdash_cli/commands/agent/__init__.py +14 -0
  5. emdash_cli/commands/agent/cli.py +100 -0
  6. emdash_cli/commands/agent/constants.py +63 -0
  7. emdash_cli/commands/agent/file_utils.py +178 -0
  8. emdash_cli/commands/agent/handlers/__init__.py +51 -0
  9. emdash_cli/commands/agent/handlers/agents.py +449 -0
  10. emdash_cli/commands/agent/handlers/auth.py +69 -0
  11. emdash_cli/commands/agent/handlers/doctor.py +319 -0
  12. emdash_cli/commands/agent/handlers/hooks.py +121 -0
  13. emdash_cli/commands/agent/handlers/index.py +183 -0
  14. emdash_cli/commands/agent/handlers/mcp.py +183 -0
  15. emdash_cli/commands/agent/handlers/misc.py +319 -0
  16. emdash_cli/commands/agent/handlers/registry.py +72 -0
  17. emdash_cli/commands/agent/handlers/rules.py +411 -0
  18. emdash_cli/commands/agent/handlers/sessions.py +168 -0
  19. emdash_cli/commands/agent/handlers/setup.py +715 -0
  20. emdash_cli/commands/agent/handlers/skills.py +478 -0
  21. emdash_cli/commands/agent/handlers/telegram.py +475 -0
  22. emdash_cli/commands/agent/handlers/todos.py +119 -0
  23. emdash_cli/commands/agent/handlers/verify.py +653 -0
  24. emdash_cli/commands/agent/help.py +236 -0
  25. emdash_cli/commands/agent/interactive.py +842 -0
  26. emdash_cli/commands/agent/menus.py +760 -0
  27. emdash_cli/commands/agent/onboarding.py +619 -0
  28. emdash_cli/commands/agent/session_restore.py +210 -0
  29. emdash_cli/commands/agent.py +7 -1321
  30. emdash_cli/commands/index.py +111 -13
  31. emdash_cli/commands/registry.py +635 -0
  32. emdash_cli/commands/server.py +99 -40
  33. emdash_cli/commands/skills.py +72 -6
  34. emdash_cli/design.py +328 -0
  35. emdash_cli/diff_renderer.py +438 -0
  36. emdash_cli/integrations/__init__.py +1 -0
  37. emdash_cli/integrations/telegram/__init__.py +15 -0
  38. emdash_cli/integrations/telegram/bot.py +402 -0
  39. emdash_cli/integrations/telegram/bridge.py +865 -0
  40. emdash_cli/integrations/telegram/config.py +155 -0
  41. emdash_cli/integrations/telegram/formatter.py +385 -0
  42. emdash_cli/main.py +52 -2
  43. emdash_cli/server_manager.py +70 -10
  44. emdash_cli/sse_renderer.py +659 -167
  45. {emdash_cli-0.1.35.dist-info → emdash_cli-0.1.67.dist-info}/METADATA +2 -4
  46. emdash_cli-0.1.67.dist-info/RECORD +63 -0
  47. emdash_cli/commands/swarm.py +0 -86
  48. emdash_cli-0.1.35.dist-info/RECORD +0 -30
  49. {emdash_cli-0.1.35.dist-info → emdash_cli-0.1.67.dist-info}/WHEEL +0 -0
  50. {emdash_cli-0.1.35.dist-info → emdash_cli-0.1.67.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,411 @@
1
+ """Handler for /rules command."""
2
+
3
+ from pathlib import Path
4
+
5
+ from rich.console import Console
6
+ from rich.panel import Panel
7
+
8
+ from ....design import (
9
+ Colors,
10
+ header,
11
+ footer,
12
+ SEPARATOR_WIDTH,
13
+ STATUS_ACTIVE,
14
+ ARROW_PROMPT,
15
+ )
16
+
17
+ console = Console()
18
+
19
+
20
+ def get_rules_dir() -> Path:
21
+ """Get the rules directory path."""
22
+ return Path.cwd() / ".emdash" / "rules"
23
+
24
+
25
+ def list_rules() -> list[dict]:
26
+ """List all rules files.
27
+
28
+ Returns:
29
+ List of dicts with name, file_path, and preview
30
+ """
31
+ rules_dir = get_rules_dir()
32
+ rules = []
33
+
34
+ if not rules_dir.exists():
35
+ return rules
36
+
37
+ for md_file in sorted(rules_dir.glob("*.md")):
38
+ try:
39
+ content = md_file.read_text().strip()
40
+ # Get first non-empty line as preview
41
+ lines = [l for l in content.split("\n") if l.strip()]
42
+ preview = lines[0][:60] + "..." if lines and len(lines[0]) > 60 else (lines[0] if lines else "")
43
+ # Remove markdown heading prefix
44
+ if preview.startswith("#"):
45
+ preview = preview.lstrip("#").strip()
46
+
47
+ rules.append({
48
+ "name": md_file.stem,
49
+ "file_path": str(md_file),
50
+ "preview": preview,
51
+ })
52
+ except Exception:
53
+ rules.append({
54
+ "name": md_file.stem,
55
+ "file_path": str(md_file),
56
+ "preview": "(error reading file)",
57
+ })
58
+
59
+ return rules
60
+
61
+
62
+ def show_rules_interactive_menu() -> tuple[str, str]:
63
+ """Show interactive rules menu.
64
+
65
+ Returns:
66
+ Tuple of (action, rule_name) where action is one of:
67
+ - 'view': View rule details
68
+ - 'create': Create new rule
69
+ - 'delete': Delete rule
70
+ - 'cancel': User cancelled
71
+ """
72
+ from prompt_toolkit import Application
73
+ from prompt_toolkit.key_binding import KeyBindings
74
+ from prompt_toolkit.layout import Layout, HSplit, Window, FormattedTextControl
75
+ from prompt_toolkit.styles import Style
76
+
77
+ rules = list_rules()
78
+
79
+ # Build menu items: (name, preview, is_action)
80
+ menu_items = []
81
+
82
+ for rule in rules:
83
+ menu_items.append((rule["name"], rule["preview"], False))
84
+
85
+ # Add action items at the bottom
86
+ menu_items.append(("+ Create New Rule", "Create a new rule with AI assistance", True))
87
+
88
+ if not menu_items:
89
+ menu_items.append(("+ Create New Rule", "Create a new rule with AI assistance", True))
90
+
91
+ selected_index = [0]
92
+ result = [("cancel", "")]
93
+
94
+ kb = KeyBindings()
95
+
96
+ @kb.add("up")
97
+ @kb.add("k")
98
+ def move_up(event):
99
+ selected_index[0] = (selected_index[0] - 1) % len(menu_items)
100
+
101
+ @kb.add("down")
102
+ @kb.add("j")
103
+ def move_down(event):
104
+ selected_index[0] = (selected_index[0] + 1) % len(menu_items)
105
+
106
+ @kb.add("enter")
107
+ def select(event):
108
+ item = menu_items[selected_index[0]]
109
+ name, preview, is_action = item
110
+ if is_action:
111
+ if "Create" in name:
112
+ result[0] = ("create", "")
113
+ else:
114
+ result[0] = ("view", name)
115
+ event.app.exit()
116
+
117
+ @kb.add("d")
118
+ def delete_rule(event):
119
+ item = menu_items[selected_index[0]]
120
+ name, preview, is_action = item
121
+ if not is_action:
122
+ result[0] = ("delete", name)
123
+ event.app.exit()
124
+
125
+ @kb.add("n")
126
+ def new_rule(event):
127
+ result[0] = ("create", "")
128
+ event.app.exit()
129
+
130
+ @kb.add("c-c")
131
+ @kb.add("escape")
132
+ @kb.add("q")
133
+ def cancel(event):
134
+ result[0] = ("cancel", "")
135
+ event.app.exit()
136
+
137
+ def get_formatted_menu():
138
+ lines = [("class:title", f"─── Rules {'─' * 35}\n\n")]
139
+
140
+ if not rules:
141
+ lines.append(("class:dim", " No rules defined yet.\n\n"))
142
+
143
+ for i, (name, preview, is_action) in enumerate(menu_items):
144
+ is_selected = i == selected_index[0]
145
+ prefix = "▸ " if is_selected else " "
146
+
147
+ if is_action:
148
+ if is_selected:
149
+ lines.append(("class:action-selected", f" {prefix}{name}\n"))
150
+ else:
151
+ lines.append(("class:action", f" {prefix}{name}\n"))
152
+ else:
153
+ if is_selected:
154
+ lines.append(("class:rule-selected", f" {prefix}{name}"))
155
+ lines.append(("class:preview-selected", f" {preview}\n"))
156
+ else:
157
+ lines.append(("class:rule", f" {prefix}{name}"))
158
+ lines.append(("class:preview", f" {preview}\n"))
159
+
160
+ lines.append(("class:hint", f"\n{'─' * 45}\n ↑↓ navigate Enter view n new d delete q quit"))
161
+ return lines
162
+
163
+ style = Style.from_dict({
164
+ "title": f"{Colors.MUTED}",
165
+ "dim": f"{Colors.DIM}",
166
+ "rule": f"{Colors.PRIMARY}",
167
+ "rule-selected": f"{Colors.SUCCESS} bold",
168
+ "action": f"{Colors.WARNING}",
169
+ "action-selected": f"{Colors.WARNING} bold",
170
+ "preview": f"{Colors.DIM}",
171
+ "preview-selected": f"{Colors.SUCCESS}",
172
+ "hint": f"{Colors.DIM}",
173
+ })
174
+
175
+ height = len(menu_items) + 5 # items + title + hint + padding
176
+
177
+ layout = Layout(
178
+ HSplit([
179
+ Window(
180
+ FormattedTextControl(get_formatted_menu),
181
+ height=height,
182
+ ),
183
+ ])
184
+ )
185
+
186
+ app = Application(
187
+ layout=layout,
188
+ key_bindings=kb,
189
+ style=style,
190
+ full_screen=False,
191
+ )
192
+
193
+ console.print()
194
+
195
+ try:
196
+ app.run()
197
+ except (KeyboardInterrupt, EOFError):
198
+ result[0] = ("cancel", "")
199
+
200
+ # Clear menu visually with separator
201
+ console.print()
202
+
203
+ return result[0]
204
+
205
+
206
+ def show_rule_details(name: str) -> None:
207
+ """Show detailed view of a rule."""
208
+ rules_dir = get_rules_dir()
209
+ rule_file = rules_dir / f"{name}.md"
210
+
211
+ console.print()
212
+ console.print(f"[{Colors.MUTED}]{header(name, SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
213
+ console.print()
214
+
215
+ if rule_file.exists():
216
+ try:
217
+ content = rule_file.read_text()
218
+ console.print(f" [{Colors.DIM}]file[/{Colors.DIM}] {rule_file}")
219
+ console.print()
220
+
221
+ # Show content with indentation
222
+ for line in content.split('\n'):
223
+ if line.startswith('#'):
224
+ console.print(f" [{Colors.PRIMARY}]{line}[/{Colors.PRIMARY}]")
225
+ else:
226
+ console.print(f" [{Colors.MUTED}]{line}[/{Colors.MUTED}]")
227
+
228
+ except Exception as e:
229
+ console.print(f" [{Colors.ERROR}]Error reading rule: {e}[/{Colors.ERROR}]")
230
+ else:
231
+ console.print(f" [{Colors.WARNING}]Rule '{name}' not found[/{Colors.WARNING}]")
232
+
233
+ console.print()
234
+ console.print(f"[{Colors.MUTED}]{footer(SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
235
+
236
+
237
+ def confirm_delete(rule_name: str) -> bool:
238
+ """Confirm rule deletion."""
239
+ from prompt_toolkit import PromptSession
240
+
241
+ console.print()
242
+ console.print(f"[yellow]Delete rule '{rule_name}'?[/yellow]")
243
+ console.print("[dim]This will remove the rule file. Type 'yes' to confirm.[/dim]")
244
+
245
+ try:
246
+ session = PromptSession()
247
+ response = session.prompt("Confirm > ").strip().lower()
248
+ return response in ("yes", "y")
249
+ except (KeyboardInterrupt, EOFError):
250
+ return False
251
+
252
+
253
+ def delete_rule(name: str) -> bool:
254
+ """Delete a rule file."""
255
+ rules_dir = get_rules_dir()
256
+ rule_file = rules_dir / f"{name}.md"
257
+
258
+ if not rule_file.exists():
259
+ console.print(f"[yellow]Rule file not found: {rule_file}[/yellow]")
260
+ return False
261
+
262
+ if confirm_delete(name):
263
+ rule_file.unlink()
264
+ console.print(f"[green]Deleted rule: {name}[/green]")
265
+ return True
266
+ else:
267
+ console.print("[dim]Cancelled[/dim]")
268
+ return False
269
+
270
+
271
+ def chat_create_rule(client, renderer, model, max_iterations, render_with_interrupt) -> None:
272
+ """Start a chat session to create a new rule with AI assistance."""
273
+ from prompt_toolkit import PromptSession
274
+ from prompt_toolkit.styles import Style
275
+
276
+ rules_dir = get_rules_dir()
277
+
278
+ console.print()
279
+ console.print(f"[{Colors.MUTED}]{header('Create Rule', SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
280
+ console.print()
281
+ console.print(f" [{Colors.DIM}]Describe your rule. AI will help write it.[/{Colors.DIM}]")
282
+ console.print(f" [{Colors.DIM}]Type 'done' to finish.[/{Colors.DIM}]")
283
+ console.print()
284
+
285
+ chat_style = Style.from_dict({
286
+ "prompt": f"{Colors.PRIMARY} bold",
287
+ })
288
+
289
+ ps = PromptSession(style=chat_style)
290
+ chat_session_id = None
291
+ first_message = True
292
+
293
+ # Ensure rules directory exists
294
+ rules_dir.mkdir(parents=True, exist_ok=True)
295
+
296
+ # Chat loop
297
+ while True:
298
+ try:
299
+ user_input = ps.prompt([("class:prompt", "› ")]).strip()
300
+
301
+ if not user_input:
302
+ continue
303
+
304
+ if user_input.lower() in ("done", "quit", "exit", "q"):
305
+ console.print("[dim]Finished[/dim]")
306
+ break
307
+
308
+ # First message includes context about rules
309
+ if first_message:
310
+ message_with_context = f"""I want to create a new rule file for my project.
311
+
312
+ **Rules directory:** `{rules_dir}`
313
+
314
+ Rules are markdown files that define guidelines for the AI agent. They are stored in `.emdash/rules/` and get injected into the agent's system prompt.
315
+
316
+ Example rule file:
317
+ ```markdown
318
+ # Code Style Guidelines
319
+
320
+ - Use meaningful variable names
321
+ - Keep functions small and focused
322
+ - Add comments for complex logic
323
+ ```
324
+
325
+ **My request:** {user_input}
326
+
327
+ Please help me create a rule file. Ask me questions if needed to understand what rules I want, then use the Write tool to create the file at `{rules_dir}/<rule-name>.md`."""
328
+ stream = client.agent_chat_stream(
329
+ message=message_with_context,
330
+ model=model,
331
+ max_iterations=max_iterations,
332
+ options={"mode": "code"},
333
+ )
334
+ first_message = False
335
+ elif chat_session_id:
336
+ stream = client.agent_continue_stream(
337
+ chat_session_id, user_input
338
+ )
339
+ else:
340
+ stream = client.agent_chat_stream(
341
+ message=user_input,
342
+ model=model,
343
+ max_iterations=max_iterations,
344
+ options={"mode": "code"},
345
+ )
346
+
347
+ result = render_with_interrupt(renderer, stream)
348
+ if result and result.get("session_id"):
349
+ chat_session_id = result["session_id"]
350
+
351
+ except (KeyboardInterrupt, EOFError):
352
+ console.print()
353
+ console.print("[dim]Cancelled[/dim]")
354
+ break
355
+ except Exception as e:
356
+ console.print(f"[red]Error: {e}[/red]")
357
+
358
+
359
+ def handle_rules(args: str, client, renderer, model, max_iterations, render_with_interrupt) -> None:
360
+ """Handle /rules command."""
361
+ from prompt_toolkit import PromptSession
362
+
363
+ # Handle subcommands
364
+ if args:
365
+ subparts = args.split(maxsplit=1)
366
+ subcommand = subparts[0].lower()
367
+ subargs = subparts[1] if len(subparts) > 1 else ""
368
+
369
+ if subcommand == "list":
370
+ rules = list_rules()
371
+ if rules:
372
+ console.print("\n[bold cyan]Rules[/bold cyan]\n")
373
+ for rule in rules:
374
+ console.print(f" [cyan]{rule['name']}[/cyan] - {rule['preview']}")
375
+ console.print()
376
+ else:
377
+ console.print("\n[dim]No rules defined yet.[/dim]")
378
+ console.print(f"[dim]Rules directory: {get_rules_dir()}[/dim]\n")
379
+ elif subcommand == "show" and subargs:
380
+ show_rule_details(subargs.strip())
381
+ elif subcommand == "delete" and subargs:
382
+ delete_rule(subargs.strip())
383
+ elif subcommand == "add" or subcommand == "create" or subcommand == "new":
384
+ chat_create_rule(client, renderer, model, max_iterations, render_with_interrupt)
385
+ else:
386
+ console.print("[yellow]Usage: /rules [list|show|add|delete] [name][/yellow]")
387
+ console.print("[dim]Or just /rules for interactive menu[/dim]")
388
+ else:
389
+ # Interactive menu
390
+ while True:
391
+ action, rule_name = show_rules_interactive_menu()
392
+
393
+ if action == "cancel":
394
+ break
395
+ elif action == "view":
396
+ show_rule_details(rule_name)
397
+ # After viewing, show options
398
+ try:
399
+ console.print("[red]'d'[/red] delete • [dim]Enter back[/dim]", end="")
400
+ ps = PromptSession()
401
+ resp = ps.prompt(" ").strip().lower()
402
+ if resp == 'd':
403
+ delete_rule(rule_name)
404
+ console.print()
405
+ except (KeyboardInterrupt, EOFError):
406
+ break
407
+ elif action == "create":
408
+ chat_create_rule(client, renderer, model, max_iterations, render_with_interrupt)
409
+ # Refresh menu after creating
410
+ elif action == "delete":
411
+ delete_rule(rule_name)
@@ -0,0 +1,168 @@
1
+ """Handler for /session command."""
2
+
3
+ from pathlib import Path
4
+
5
+ from rich.console import Console
6
+
7
+ from ..menus import show_sessions_interactive_menu, confirm_session_delete
8
+ from ..constants import AgentMode
9
+
10
+ console = Console()
11
+
12
+
13
+ def handle_session(
14
+ args: str,
15
+ client,
16
+ model: str | None,
17
+ session_id_ref: list,
18
+ current_spec_ref: list,
19
+ current_mode_ref: list,
20
+ loaded_messages_ref: list,
21
+ ) -> None:
22
+ """Handle /session command.
23
+
24
+ Args:
25
+ args: Command arguments
26
+ client: EmdashClient instance
27
+ model: Current model
28
+ session_id_ref: Reference to session_id (list wrapper for mutation)
29
+ current_spec_ref: Reference to current_spec (list wrapper for mutation)
30
+ current_mode_ref: Reference to current_mode (list wrapper for mutation)
31
+ loaded_messages_ref: Reference to loaded_messages (list wrapper for mutation)
32
+ """
33
+ from ....session_store import SessionStore
34
+
35
+ store = SessionStore(Path.cwd())
36
+
37
+ # Parse subcommand
38
+ subparts = args.split(maxsplit=1) if args else []
39
+ subcommand = subparts[0].lower() if subparts else ""
40
+ subargs = subparts[1].strip() if len(subparts) > 1 else ""
41
+
42
+ def _load_session(name: str) -> bool:
43
+ """Load a session by name. Returns True if successful."""
44
+ session_data = store.load_session(name)
45
+ if session_data:
46
+ session_id_ref[0] = None
47
+ current_spec_ref[0] = session_data.spec
48
+ if session_data.mode == "plan":
49
+ current_mode_ref[0] = AgentMode.PLAN
50
+ else:
51
+ current_mode_ref[0] = AgentMode.CODE
52
+ loaded_messages_ref[0] = session_data.messages
53
+ store.set_active_session(name)
54
+ console.print(f"[green]Loaded session '{name}'[/green]")
55
+ console.print(f"[dim]{len(session_data.messages)} messages restored, mode: {current_mode_ref[0].value}[/dim]")
56
+ if current_spec_ref[0]:
57
+ console.print("[dim]Spec restored[/dim]")
58
+ return True
59
+ else:
60
+ console.print(f"[yellow]Session '{name}' not found[/yellow]")
61
+ return False
62
+
63
+ def _delete_session(name: str) -> bool:
64
+ """Delete a session by name with confirmation."""
65
+ if confirm_session_delete(name):
66
+ success, msg = store.delete_session(name)
67
+ if success:
68
+ console.print(f"[green]{msg}[/green]")
69
+ return True
70
+ else:
71
+ console.print(f"[yellow]{msg}[/yellow]")
72
+ else:
73
+ console.print("[dim]Cancelled[/dim]")
74
+ return False
75
+
76
+ if subcommand == "" or subcommand == "list":
77
+ # Interactive menu (or list if no sessions)
78
+ sessions = store.list_sessions()
79
+ if sessions:
80
+ if subcommand == "list":
81
+ # Just list, don't show interactive menu
82
+ console.print("\n[bold cyan]Saved Sessions[/bold cyan]\n")
83
+ for s in sessions:
84
+ mode_color = "green" if s.mode == "code" else "yellow"
85
+ active_marker = " [bold green]*[/bold green]" if store.get_active_session() == s.name else ""
86
+ console.print(f" [cyan]{s.name}[/cyan]{active_marker} [{mode_color}]{s.mode}[/{mode_color}]")
87
+ console.print(f" [dim]{s.message_count} messages | {s.updated_at[:10]}[/dim]")
88
+ if s.summary:
89
+ summary = s.summary[:60] + "..." if len(s.summary) > 60 else s.summary
90
+ console.print(f" [dim]{summary}[/dim]")
91
+ console.print()
92
+ else:
93
+ # Interactive menu
94
+ while True:
95
+ action, session_name = show_sessions_interactive_menu(
96
+ sessions, store.get_active_session()
97
+ )
98
+ if action == "cancel":
99
+ break
100
+ elif action == "load":
101
+ _load_session(session_name)
102
+ break
103
+ elif action == "delete":
104
+ if _delete_session(session_name):
105
+ # Refresh sessions list
106
+ sessions = store.list_sessions()
107
+ if not sessions:
108
+ console.print("\n[dim]No more sessions.[/dim]\n")
109
+ break
110
+ # Continue showing menu
111
+ else:
112
+ console.print("\n[dim]No saved sessions.[/dim]")
113
+ console.print("[dim]Save with: /session save <name>[/dim]\n")
114
+
115
+ elif subcommand == "save":
116
+ if not subargs:
117
+ console.print("[yellow]Usage: /session save <name>[/yellow]")
118
+ console.print("[dim]Example: /session save auth-feature[/dim]")
119
+ else:
120
+ # Get current messages from the API session
121
+ if session_id_ref[0]:
122
+ try:
123
+ export_resp = client.get(f"/api/agent/chat/{session_id_ref[0]}/export")
124
+ if export_resp.status_code == 200:
125
+ data = export_resp.json()
126
+ messages = data.get("messages", [])
127
+ else:
128
+ messages = []
129
+ except Exception:
130
+ messages = []
131
+ else:
132
+ messages = []
133
+
134
+ success, msg = store.save_session(
135
+ name=subargs,
136
+ messages=messages,
137
+ mode=current_mode_ref[0].value,
138
+ spec=current_spec_ref[0],
139
+ model=model,
140
+ )
141
+ if success:
142
+ store.set_active_session(subargs)
143
+ console.print(f"[green]{msg}[/green]")
144
+ else:
145
+ console.print(f"[yellow]{msg}[/yellow]")
146
+
147
+ elif subcommand == "load":
148
+ if not subargs:
149
+ console.print("[yellow]Usage: /session load <name>[/yellow]")
150
+ else:
151
+ _load_session(subargs)
152
+
153
+ elif subcommand == "delete":
154
+ if not subargs:
155
+ console.print("[yellow]Usage: /session delete <name>[/yellow]")
156
+ else:
157
+ _delete_session(subargs)
158
+
159
+ elif subcommand == "clear":
160
+ session_id_ref[0] = None
161
+ current_spec_ref[0] = None
162
+ loaded_messages_ref[0] = []
163
+ store.set_active_session(None)
164
+ console.print("[green]Session cleared[/green]")
165
+
166
+ else:
167
+ console.print(f"[yellow]Unknown subcommand: {subcommand}[/yellow]")
168
+ console.print("[dim]Usage: /session [list|save|load|delete|clear] [name][/dim]")