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,183 @@
1
+ """Handler for /mcp command."""
2
+
3
+ import json
4
+ import os
5
+ import subprocess
6
+ from pathlib import Path
7
+
8
+ from rich.console import Console
9
+
10
+ console = Console()
11
+
12
+
13
+ def handle_mcp(args: str) -> None:
14
+ """Handle /mcp command.
15
+
16
+ Args:
17
+ args: Command arguments
18
+ """
19
+ from emdash_core.agent.mcp.manager import get_mcp_manager
20
+ from emdash_core.agent.mcp.config import get_default_mcp_config_path
21
+
22
+ manager = get_mcp_manager(config_path=get_default_mcp_config_path(Path.cwd()))
23
+
24
+ # Parse subcommand
25
+ subparts = args.split(maxsplit=1) if args else []
26
+ subcommand = subparts[0].lower() if subparts else ""
27
+
28
+ def _show_mcp_interactive():
29
+ """Show interactive MCP server list with toggle."""
30
+ from prompt_toolkit import Application
31
+ from prompt_toolkit.key_binding import KeyBindings
32
+ from prompt_toolkit.layout import Layout, Window, FormattedTextControl
33
+ from prompt_toolkit.styles import Style
34
+
35
+ servers = manager.list_servers()
36
+ if not servers:
37
+ console.print("\n[dim]No global MCP servers configured.[/dim]")
38
+ console.print(f"[dim]Edit {manager.config_path} to add servers[/dim]\n")
39
+ return None
40
+
41
+ selected_index = [0]
42
+ server_names = [s["name"] for s in servers]
43
+
44
+ kb = KeyBindings()
45
+
46
+ @kb.add("up")
47
+ @kb.add("k")
48
+ def move_up(event):
49
+ selected_index[0] = (selected_index[0] - 1) % len(servers)
50
+
51
+ @kb.add("down")
52
+ @kb.add("j")
53
+ def move_down(event):
54
+ selected_index[0] = (selected_index[0] + 1) % len(servers)
55
+
56
+ @kb.add("enter")
57
+ @kb.add("space")
58
+ def toggle_server(event):
59
+ # Toggle the selected server's enabled status
60
+ server_name = server_names[selected_index[0]]
61
+ config_path = manager.config_path
62
+
63
+ # Read current config
64
+ if config_path.exists():
65
+ with open(config_path) as f:
66
+ config = json.load(f)
67
+ else:
68
+ config = {"mcpServers": {}}
69
+
70
+ # Toggle enabled status
71
+ if server_name in config.get("mcpServers", {}):
72
+ current = config["mcpServers"][server_name].get("enabled", True)
73
+ config["mcpServers"][server_name]["enabled"] = not current
74
+
75
+ # Save config
76
+ with open(config_path, "w") as f:
77
+ json.dump(config, f, indent=2)
78
+
79
+ # Reload manager
80
+ manager.reload_config()
81
+
82
+ # Update local servers list
83
+ servers[:] = manager.list_servers()
84
+
85
+ @kb.add("e")
86
+ def edit_config(event):
87
+ event.app.exit(result="edit")
88
+
89
+ @kb.add("q")
90
+ @kb.add("escape")
91
+ def quit_menu(event):
92
+ event.app.exit()
93
+
94
+ def get_formatted_content():
95
+ lines = []
96
+ lines.append(("class:title", "Global MCP Servers\n"))
97
+ lines.append(("class:dim", f"Config: {manager.config_path}\n\n"))
98
+
99
+ for i, server in enumerate(servers):
100
+ if server["enabled"]:
101
+ if server["running"]:
102
+ status = "running"
103
+ status_style = "class:running"
104
+ else:
105
+ status = "enabled"
106
+ status_style = "class:enabled"
107
+ else:
108
+ status = "disabled"
109
+ status_style = "class:disabled"
110
+
111
+ if i == selected_index[0]:
112
+ lines.append(("class:selected", f" > {server['name']}"))
113
+ else:
114
+ lines.append(("class:normal", f" {server['name']}"))
115
+
116
+ lines.append((status_style, f" ({status})\n"))
117
+
118
+ lines.append(("class:hint", "\n↑/↓ navigate • Enter toggle • e edit • q quit"))
119
+ return lines
120
+
121
+ style = Style.from_dict({
122
+ "title": "#00cc66 bold",
123
+ "dim": "#888888",
124
+ "selected": "#00cc66 bold",
125
+ "normal": "#cccccc",
126
+ "running": "#00cc66",
127
+ "enabled": "#cccc00",
128
+ "disabled": "#888888",
129
+ "hint": "#888888 italic",
130
+ })
131
+
132
+ layout = Layout(Window(FormattedTextControl(get_formatted_content)))
133
+ app = Application(layout=layout, key_bindings=kb, style=style, full_screen=False)
134
+
135
+ result = app.run()
136
+ if result == "edit":
137
+ return "edit"
138
+ return None
139
+
140
+ if subcommand == "" or subcommand == "list":
141
+ # Show interactive menu (default)
142
+ result = _show_mcp_interactive()
143
+ if result == "edit":
144
+ subcommand = "edit"
145
+ else:
146
+ # Don't continue to edit
147
+ subcommand = "done"
148
+
149
+ if subcommand == "edit":
150
+ # Open MCP config in editor
151
+ config_path = manager.config_path
152
+
153
+ # Create default config if it doesn't exist
154
+ if not config_path.exists():
155
+ config_path.parent.mkdir(parents=True, exist_ok=True)
156
+ config_path.write_text('{\n "mcpServers": {}\n}\n')
157
+ console.print(f"[dim]Created {config_path}[/dim]")
158
+
159
+ editor = os.environ.get("EDITOR", "")
160
+ if not editor:
161
+ for ed in ["code", "vim", "nano", "vi"]:
162
+ try:
163
+ subprocess.run(["which", ed], capture_output=True, check=True)
164
+ editor = ed
165
+ break
166
+ except (subprocess.CalledProcessError, FileNotFoundError):
167
+ continue
168
+
169
+ if editor:
170
+ console.print(f"[dim]Opening {config_path} in {editor}...[/dim]")
171
+ try:
172
+ subprocess.run([editor, str(config_path)])
173
+ manager.reload_config()
174
+ console.print("[dim]Config reloaded[/dim]")
175
+ except Exception as e:
176
+ console.print(f"[red]Failed to open editor: {e}[/red]")
177
+ else:
178
+ console.print(f"[yellow]No editor found. Edit manually:[/yellow]")
179
+ console.print(f" {config_path}")
180
+
181
+ elif subcommand != "done":
182
+ console.print(f"[yellow]Unknown subcommand: {subcommand}[/yellow]")
183
+ console.print("[dim]Usage: /mcp [list|edit][/dim]")
@@ -0,0 +1,319 @@
1
+ """Handlers for miscellaneous slash commands."""
2
+
3
+ import json
4
+ import subprocess
5
+ from datetime import datetime
6
+ from pathlib import Path
7
+
8
+ from rich.console import Console
9
+ from rich.panel import Panel
10
+ from rich.syntax import Syntax
11
+ from rich.text import Text
12
+
13
+ from emdash_cli.design import Colors, EM_DASH
14
+ from emdash_cli.diff_renderer import render_diff
15
+
16
+ console = Console()
17
+
18
+
19
+ def handle_status(client) -> None:
20
+ """Handle /status command.
21
+
22
+ Args:
23
+ client: EmdashClient instance
24
+ """
25
+ console.print("\n[bold cyan]Status[/bold cyan]\n")
26
+
27
+ # Index status
28
+ console.print("[bold]Index Status[/bold]")
29
+ try:
30
+ status = client.index_status(str(Path.cwd()))
31
+ is_indexed = status.get("is_indexed", False)
32
+ console.print(f" Indexed: {'[green]Yes[/green]' if is_indexed else '[yellow]No[/yellow]'}")
33
+
34
+ if is_indexed:
35
+ console.print(f" Files: {status.get('file_count', 0)}")
36
+ console.print(f" Functions: {status.get('function_count', 0)}")
37
+ console.print(f" Classes: {status.get('class_count', 0)}")
38
+ console.print(f" Communities: {status.get('community_count', 0)}")
39
+ if status.get("last_indexed"):
40
+ console.print(f" Last indexed: {status.get('last_indexed')}")
41
+ if status.get("last_commit"):
42
+ console.print(f" Last commit: {status.get('last_commit')}")
43
+ except Exception as e:
44
+ console.print(f" [red]Error fetching index status: {e}[/red]")
45
+
46
+ console.print()
47
+
48
+ # PROJECT.md status
49
+ console.print("[bold]PROJECT.md Status[/bold]")
50
+ projectmd_path = Path.cwd() / "PROJECT.md"
51
+ if projectmd_path.exists():
52
+ stat = projectmd_path.stat()
53
+ modified_time = datetime.fromtimestamp(stat.st_mtime)
54
+ size_kb = stat.st_size / 1024
55
+ console.print(f" Exists: [green]Yes[/green]")
56
+ console.print(f" Path: {projectmd_path}")
57
+ console.print(f" Size: {size_kb:.1f} KB")
58
+ console.print(f" Last modified: {modified_time.strftime('%Y-%m-%d %H:%M:%S')}")
59
+ else:
60
+ console.print(f" Exists: [yellow]No[/yellow]")
61
+ console.print("[dim] Run /projectmd to generate it[/dim]")
62
+
63
+ console.print()
64
+
65
+
66
+ def handle_pr(args: str, run_slash_command_task, client, renderer, model, max_iterations) -> None:
67
+ """Handle /pr command.
68
+
69
+ Args:
70
+ args: PR URL or number
71
+ run_slash_command_task: Function to run slash command tasks
72
+ client: EmdashClient instance
73
+ renderer: SSERenderer instance
74
+ model: Current model
75
+ max_iterations: Max iterations
76
+ """
77
+ if not args:
78
+ console.print("[yellow]Usage: /pr <pr-url-or-number>[/yellow]")
79
+ console.print("[dim]Example: /pr 123 or /pr https://github.com/org/repo/pull/123[/dim]")
80
+ else:
81
+ console.print(f"[cyan]Reviewing PR: {args}[/cyan]")
82
+ run_slash_command_task(
83
+ client, renderer, model, max_iterations,
84
+ f"Review this pull request and provide feedback: {args}",
85
+ {"mode": "code"}
86
+ )
87
+
88
+
89
+ def handle_projectmd(run_slash_command_task, client, renderer, model, max_iterations) -> None:
90
+ """Handle /projectmd command.
91
+
92
+ Args:
93
+ run_slash_command_task: Function to run slash command tasks
94
+ client: EmdashClient instance
95
+ renderer: SSERenderer instance
96
+ model: Current model
97
+ max_iterations: Max iterations
98
+ """
99
+ console.print("[cyan]Generating PROJECT.md...[/cyan]")
100
+ run_slash_command_task(
101
+ client, renderer, model, max_iterations,
102
+ "Analyze this codebase and generate a comprehensive PROJECT.md file that describes the architecture, main components, how to get started, and key design decisions.",
103
+ {"mode": "code"}
104
+ )
105
+
106
+
107
+ def handle_research(args: str, run_slash_command_task, client, renderer, model) -> None:
108
+ """Handle /research command.
109
+
110
+ Args:
111
+ args: Research goal
112
+ run_slash_command_task: Function to run slash command tasks
113
+ client: EmdashClient instance
114
+ renderer: SSERenderer instance
115
+ model: Current model
116
+ """
117
+ if not args:
118
+ console.print("[yellow]Usage: /research <goal>[/yellow]")
119
+ console.print("[dim]Example: /research How does authentication work in this codebase?[/dim]")
120
+ else:
121
+ console.print(f"[cyan]Researching: {args}[/cyan]")
122
+ run_slash_command_task(
123
+ client, renderer, model, 50, # More iterations for research
124
+ f"Conduct deep research on: {args}\n\nExplore the codebase thoroughly, analyze relevant code, and provide a comprehensive answer with references to specific files and functions.",
125
+ {"mode": "plan"} # Use plan mode for research
126
+ )
127
+
128
+
129
+ def handle_context(renderer) -> None:
130
+ """Handle /context command.
131
+
132
+ Args:
133
+ renderer: SSERenderer instance with _last_context_frame attribute
134
+ """
135
+ context_data = getattr(renderer, '_last_context_frame', None)
136
+ if not context_data:
137
+ console.print("\n[dim]No context frame available yet. Run a query first.[/dim]\n")
138
+ else:
139
+ adding = context_data.get("adding") or {}
140
+ reading = context_data.get("reading") or {}
141
+
142
+ # Get stats
143
+ step_count = adding.get("step_count", 0)
144
+ entities_found = adding.get("entities_found", 0)
145
+ context_tokens = adding.get("context_tokens", 0)
146
+ context_breakdown = adding.get("context_breakdown", {})
147
+
148
+ console.print()
149
+ console.print("[bold cyan]Context Frame[/bold cyan]")
150
+ console.print()
151
+
152
+ # Show total context
153
+ if context_tokens > 0:
154
+ console.print(f"[bold]Total:[/bold] {context_tokens:,} tokens")
155
+
156
+ # Show breakdown
157
+ if context_breakdown:
158
+ console.print(f"\n[bold]Breakdown:[/bold]")
159
+ for key, tokens in context_breakdown.items():
160
+ if tokens > 0:
161
+ console.print(f" {key}: {tokens:,}")
162
+
163
+ # Show stats
164
+ if step_count > 0 or entities_found > 0:
165
+ console.print(f"\n[bold]Stats:[/bold]")
166
+ if step_count > 0:
167
+ console.print(f" Steps: {step_count}")
168
+ if entities_found > 0:
169
+ console.print(f" Entities: {entities_found}")
170
+
171
+ # Show reranking query
172
+ query = reading.get("query")
173
+ if query:
174
+ console.print(f"\n[bold]Reranking Query:[/bold]")
175
+ console.print(f" [yellow]{query}[/yellow]")
176
+
177
+ # Show reranked items
178
+ items = reading.get("items", [])
179
+ if items:
180
+ console.print(f"\n[bold]Reranked Items ({len(items)}):[/bold]")
181
+ for i, item in enumerate(items, 1):
182
+ name = item.get("name", "?")
183
+ item_type = item.get("type", "?")
184
+ score = item.get("score")
185
+ file_path = item.get("file", "")
186
+ description = item.get("description", "")
187
+ touch_count = item.get("touch_count", 0)
188
+ neighbors = item.get("neighbors", [])
189
+
190
+ score_str = f"[cyan]{score:.3f}[/cyan]" if score is not None else "[dim]n/a[/dim]"
191
+ touch_str = f"[magenta]×{touch_count}[/magenta]" if touch_count > 1 else ""
192
+
193
+ console.print(f"\n [bold white]{i}.[/bold white] [dim]{item_type}[/dim] [bold]{name}[/bold]")
194
+ console.print(f" Score: {score_str} {touch_str}")
195
+ if file_path:
196
+ console.print(f" File: [dim]{file_path}[/dim]")
197
+ if description:
198
+ desc_preview = description[:100] + "..." if len(description) > 100 else description
199
+ console.print(f" Desc: [dim]{desc_preview}[/dim]")
200
+ if neighbors:
201
+ console.print(f" Neighbors: [dim]{', '.join(neighbors)}[/dim]")
202
+ else:
203
+ debug_info = reading.get("debug", "")
204
+ if debug_info:
205
+ console.print(f"\n[dim]No reranked items: {debug_info}[/dim]")
206
+ else:
207
+ console.print(f"\n[dim]No reranked items yet. Items appear after exploration (file reads, searches).[/dim]")
208
+
209
+ # Show full context frame as JSON
210
+ console.print(f"\n[bold]Full Context Frame:[/bold]")
211
+ context_json = json.dumps(context_data, indent=2, default=str)
212
+ syntax = Syntax(context_json, "json", theme="monokai", line_numbers=False)
213
+ console.print(syntax)
214
+ console.print()
215
+
216
+
217
+ def handle_compact(client, session_id: str | None) -> None:
218
+ """Handle /compact command.
219
+
220
+ Manually triggers message history compaction using LLM summarization.
221
+
222
+ Args:
223
+ client: EmdashClient instance
224
+ session_id: Current session ID (if any)
225
+ """
226
+ if not session_id:
227
+ console.print("\n[yellow]No active session. Start a conversation first.[/yellow]\n")
228
+ return
229
+
230
+ console.print("\n[bold cyan]Compacting message history...[/bold cyan]\n")
231
+
232
+ try:
233
+ response = client.post(f"/api/agent/chat/{session_id}/compact")
234
+
235
+ if response.status_code == 404:
236
+ console.print("[yellow]Session not found.[/yellow]\n")
237
+ return
238
+
239
+ if response.status_code != 200:
240
+ console.print(f"[red]Error: {response.text}[/red]\n")
241
+ return
242
+
243
+ data = response.json()
244
+
245
+ if not data.get("compacted"):
246
+ reason = data.get("reason", "Unknown reason")
247
+ console.print(f"[yellow]Could not compact: {reason}[/yellow]\n")
248
+ return
249
+
250
+ # Show stats
251
+ original_msgs = data.get("original_message_count", 0)
252
+ new_msgs = data.get("new_message_count", 0)
253
+ original_tokens = data.get("original_tokens", 0)
254
+ new_tokens = data.get("new_tokens", 0)
255
+ reduction = data.get("reduction_percent", 0)
256
+
257
+ console.print("[green]✓ Compaction complete![/green]\n")
258
+ console.print(f"[bold]Messages:[/bold] {original_msgs} → {new_msgs}")
259
+ console.print(f"[bold]Tokens:[/bold] {original_tokens:,} → {new_tokens:,} ([green]-{reduction}%[/green])")
260
+
261
+ # Show the summary
262
+ summary = data.get("summary")
263
+ if summary:
264
+ console.print(f"\n[bold]Summary:[/bold]")
265
+ console.print(f"[dim]{'─' * 60}[/dim]")
266
+ console.print(summary)
267
+ console.print(f"[dim]{'─' * 60}[/dim]")
268
+
269
+ console.print()
270
+
271
+ except Exception as e:
272
+ console.print(f"[red]Error during compaction: {e}[/red]\n")
273
+
274
+
275
+ def handle_diff(args: str = "") -> None:
276
+ """Handle /diff command - show uncommitted changes in GitHub-style diff view.
277
+
278
+ Args:
279
+ args: Optional file path to show diff for specific file
280
+ """
281
+ try:
282
+ # Build git diff command
283
+ cmd = ["git", "diff", "--no-color"]
284
+ if args:
285
+ cmd.append(args)
286
+
287
+ # Also include staged changes
288
+ result_unstaged = subprocess.run(
289
+ cmd, capture_output=True, text=True, cwd=Path.cwd()
290
+ )
291
+
292
+ cmd_staged = ["git", "diff", "--staged", "--no-color"]
293
+ if args:
294
+ cmd_staged.append(args)
295
+
296
+ result_staged = subprocess.run(
297
+ cmd_staged, capture_output=True, text=True, cwd=Path.cwd()
298
+ )
299
+
300
+ # Combine diffs
301
+ diff_output = ""
302
+ if result_staged.stdout:
303
+ diff_output += result_staged.stdout
304
+ if result_unstaged.stdout:
305
+ if diff_output:
306
+ diff_output += "\n"
307
+ diff_output += result_unstaged.stdout
308
+
309
+ if not diff_output:
310
+ console.print(f"\n[{Colors.MUTED}]No uncommitted changes.[/{Colors.MUTED}]\n")
311
+ return
312
+
313
+ # Render diff with line numbers and syntax highlighting
314
+ render_diff(diff_output, console)
315
+
316
+ except FileNotFoundError:
317
+ console.print(f"\n[{Colors.ERROR}]Git not found. Make sure git is installed.[/{Colors.ERROR}]\n")
318
+ except Exception as e:
319
+ console.print(f"\n[{Colors.ERROR}]Error running git diff: {e}[/{Colors.ERROR}]\n")
@@ -0,0 +1,72 @@
1
+ """Handler for /registry command."""
2
+
3
+ from rich.console import Console
4
+
5
+ console = Console()
6
+
7
+
8
+ def handle_registry(args: str) -> None:
9
+ """Handle /registry command.
10
+
11
+ Args:
12
+ args: Command arguments (list, show, install, search)
13
+ """
14
+ from emdash_cli.commands.registry import (
15
+ _show_registry_wizard,
16
+ _fetch_registry,
17
+ registry_list,
18
+ registry_show,
19
+ registry_install,
20
+ registry_search,
21
+ )
22
+
23
+ # Parse subcommand
24
+ subparts = args.split(maxsplit=1) if args else []
25
+ subcommand = subparts[0].lower() if subparts else ""
26
+ subargs = subparts[1] if len(subparts) > 1 else ""
27
+
28
+ if subcommand == "" or subcommand == "wizard":
29
+ # Show interactive wizard (default)
30
+ _show_registry_wizard()
31
+
32
+ elif subcommand == "list":
33
+ # List components
34
+ component_type = subargs if subargs else None
35
+ if component_type and component_type not in ["skills", "rules", "agents", "verifiers"]:
36
+ console.print(f"[yellow]Unknown type: {component_type}[/yellow]")
37
+ console.print("[dim]Types: skills, rules, agents, verifiers[/dim]")
38
+ return
39
+ # Invoke click command
40
+ registry_list.callback(component_type)
41
+
42
+ elif subcommand == "show":
43
+ if not subargs:
44
+ console.print("[yellow]Usage: /registry show type:name[/yellow]")
45
+ console.print("[dim]Example: /registry show skill:frontend-design[/dim]")
46
+ return
47
+ registry_show.callback(subargs)
48
+
49
+ elif subcommand == "install":
50
+ if not subargs:
51
+ console.print("[yellow]Usage: /registry install type:name [type:name ...][/yellow]")
52
+ console.print("[dim]Example: /registry install skill:frontend-design rule:typescript[/dim]")
53
+ return
54
+ component_ids = tuple(subargs.split())
55
+ registry_install.callback(component_ids)
56
+
57
+ elif subcommand == "search":
58
+ if not subargs:
59
+ console.print("[yellow]Usage: /registry search query[/yellow]")
60
+ console.print("[dim]Example: /registry search frontend[/dim]")
61
+ return
62
+ # Simple search without tag filtering from slash command
63
+ registry_search.callback(subargs, ())
64
+
65
+ else:
66
+ console.print(f"[yellow]Unknown subcommand: {subcommand}[/yellow]")
67
+ console.print("[dim]Usage: /registry [list|show|install|search][/dim]")
68
+ console.print("[dim] /registry - Interactive wizard[/dim]")
69
+ console.print("[dim] /registry list - List all components[/dim]")
70
+ console.print("[dim] /registry show x:y - Show component details[/dim]")
71
+ console.print("[dim] /registry install x:y - Install components[/dim]")
72
+ console.print("[dim] /registry search q - Search registry[/dim]")