emdash-cli 0.1.43__tar.gz → 0.1.58__tar.gz
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.
Potentially problematic release.
This version of emdash-cli might be problematic. Click here for more details.
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/PKG-INFO +2 -2
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/client.py +12 -28
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/__init__.py +2 -2
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/agent/constants.py +10 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/agent/handlers/__init__.py +10 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/agent/handlers/agents.py +177 -49
- emdash_cli-0.1.58/emdash_cli/commands/agent/handlers/index.py +183 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/agent/handlers/misc.py +119 -0
- emdash_cli-0.1.58/emdash_cli/commands/agent/handlers/registry.py +72 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/agent/handlers/rules.py +48 -31
- emdash_cli-0.1.58/emdash_cli/commands/agent/handlers/setup.py +715 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/agent/handlers/skills.py +42 -4
- emdash_cli-0.1.58/emdash_cli/commands/agent/handlers/todos.py +119 -0
- emdash_cli-0.1.58/emdash_cli/commands/agent/handlers/verify.py +653 -0
- emdash_cli-0.1.58/emdash_cli/commands/agent/help.py +236 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/agent/interactive.py +223 -38
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/agent/menus.py +116 -84
- emdash_cli-0.1.58/emdash_cli/commands/agent/onboarding.py +619 -0
- emdash_cli-0.1.58/emdash_cli/commands/agent/session_restore.py +210 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/index.py +111 -13
- emdash_cli-0.1.58/emdash_cli/commands/registry.py +635 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/skills.py +72 -6
- emdash_cli-0.1.58/emdash_cli/design.py +328 -0
- emdash_cli-0.1.58/emdash_cli/diff_renderer.py +438 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/main.py +2 -2
- emdash_cli-0.1.58/emdash_cli/sse_renderer.py +1194 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/pyproject.toml +2 -2
- emdash_cli-0.1.43/emdash_cli/commands/agent/handlers/todos.py +0 -98
- emdash_cli-0.1.43/emdash_cli/commands/agent/handlers/verify.py +0 -282
- emdash_cli-0.1.43/emdash_cli/commands/swarm.py +0 -86
- emdash_cli-0.1.43/emdash_cli/sse_renderer.py +0 -794
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/__init__.py +0 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/clipboard.py +0 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/agent/__init__.py +0 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/agent/cli.py +0 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/agent/file_utils.py +0 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/agent/handlers/auth.py +0 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/agent/handlers/doctor.py +0 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/agent/handlers/hooks.py +0 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/agent/handlers/mcp.py +0 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/agent/handlers/sessions.py +0 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/agent.py +0 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/analyze.py +0 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/auth.py +0 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/db.py +0 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/embed.py +0 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/plan.py +0 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/projectmd.py +0 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/research.py +0 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/rules.py +0 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/search.py +0 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/server.py +0 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/spec.py +0 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/tasks.py +0 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/commands/team.py +0 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/keyboard.py +0 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/server_manager.py +0 -0
- {emdash_cli-0.1.43 → emdash_cli-0.1.58}/emdash_cli/session_store.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: emdash-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.58
|
|
4
4
|
Summary: EmDash CLI - Command-line interface for code intelligence
|
|
5
5
|
Author: Em Dash Team
|
|
6
6
|
Requires-Python: >=3.10,<4.0
|
|
@@ -11,7 +11,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.13
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.14
|
|
13
13
|
Requires-Dist: click (>=8.1.7,<9.0.0)
|
|
14
|
-
Requires-Dist: emdash-core (>=0.1.
|
|
14
|
+
Requires-Dist: emdash-core (>=0.1.58)
|
|
15
15
|
Requires-Dist: httpx (>=0.25.0)
|
|
16
16
|
Requires-Dist: prompt_toolkit (>=3.0.43,<4.0.0)
|
|
17
17
|
Requires-Dist: rich (>=13.7.0)
|
|
@@ -259,6 +259,18 @@ class EmdashClient:
|
|
|
259
259
|
"""
|
|
260
260
|
return self._client.get(f"{self.base_url}{path}")
|
|
261
261
|
|
|
262
|
+
def post(self, path: str, json: dict | None = None) -> "httpx.Response":
|
|
263
|
+
"""Make a POST request to the API.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
path: API path (e.g., "/api/agent/chat/123/compact")
|
|
267
|
+
json: Optional JSON body
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
HTTP response
|
|
271
|
+
"""
|
|
272
|
+
return self._client.post(f"{self.base_url}{path}", json=json)
|
|
273
|
+
|
|
262
274
|
def list_sessions(self) -> list[dict]:
|
|
263
275
|
"""List active agent sessions.
|
|
264
276
|
|
|
@@ -660,34 +672,6 @@ class EmdashClient:
|
|
|
660
672
|
response.raise_for_status()
|
|
661
673
|
return response.json()
|
|
662
674
|
|
|
663
|
-
# ==================== Swarm ====================
|
|
664
|
-
|
|
665
|
-
def swarm_run_stream(
|
|
666
|
-
self,
|
|
667
|
-
tasks: list[str],
|
|
668
|
-
model: Optional[str] = None,
|
|
669
|
-
auto_merge: bool = False,
|
|
670
|
-
) -> Iterator[str]:
|
|
671
|
-
"""Run multi-agent swarm with SSE streaming."""
|
|
672
|
-
payload = {"tasks": tasks, "auto_merge": auto_merge}
|
|
673
|
-
if model:
|
|
674
|
-
payload["model"] = model
|
|
675
|
-
|
|
676
|
-
with self._client.stream(
|
|
677
|
-
"POST",
|
|
678
|
-
f"{self.base_url}/api/swarm/run",
|
|
679
|
-
json=payload,
|
|
680
|
-
) as response:
|
|
681
|
-
response.raise_for_status()
|
|
682
|
-
for line in response.iter_lines():
|
|
683
|
-
yield line
|
|
684
|
-
|
|
685
|
-
def swarm_status(self) -> dict:
|
|
686
|
-
"""Get swarm execution status."""
|
|
687
|
-
response = self._client.get(f"{self.base_url}/api/swarm/status")
|
|
688
|
-
response.raise_for_status()
|
|
689
|
-
return response.json()
|
|
690
|
-
|
|
691
675
|
# ==================== Todos ====================
|
|
692
676
|
|
|
693
677
|
def get_todos(self, session_id: str) -> dict:
|
|
@@ -7,12 +7,12 @@ from .analyze import analyze
|
|
|
7
7
|
from .embed import embed
|
|
8
8
|
from .index import index
|
|
9
9
|
from .plan import plan
|
|
10
|
+
from .registry import registry
|
|
10
11
|
from .rules import rules
|
|
11
12
|
from .search import search
|
|
12
13
|
from .server import server
|
|
13
14
|
from .skills import skills
|
|
14
15
|
from .team import team
|
|
15
|
-
from .swarm import swarm
|
|
16
16
|
from .projectmd import projectmd
|
|
17
17
|
from .research import research
|
|
18
18
|
from .spec import spec
|
|
@@ -26,12 +26,12 @@ __all__ = [
|
|
|
26
26
|
"embed",
|
|
27
27
|
"index",
|
|
28
28
|
"plan",
|
|
29
|
+
"registry",
|
|
29
30
|
"rules",
|
|
30
31
|
"search",
|
|
31
32
|
"server",
|
|
32
33
|
"skills",
|
|
33
34
|
"team",
|
|
34
|
-
"swarm",
|
|
35
35
|
"projectmd",
|
|
36
36
|
"research",
|
|
37
37
|
"spec",
|
|
@@ -21,6 +21,7 @@ SLASH_COMMANDS = {
|
|
|
21
21
|
"/research [goal]": "Deep research on a topic",
|
|
22
22
|
# Status commands
|
|
23
23
|
"/status": "Show index and PROJECT.md status",
|
|
24
|
+
"/diff": "Show uncommitted changes in GitHub-style diff view",
|
|
24
25
|
"/agents": "Manage agents (interactive menu, or /agents [create|show|edit|delete] <name>)",
|
|
25
26
|
# Todo management
|
|
26
27
|
"/todos": "Show current agent todo list",
|
|
@@ -35,17 +36,26 @@ SLASH_COMMANDS = {
|
|
|
35
36
|
"/rules": "Manage rules (list, add, delete)",
|
|
36
37
|
# Skills
|
|
37
38
|
"/skills": "Manage skills (list, show, add, delete)",
|
|
39
|
+
# Index
|
|
40
|
+
"/index": "Manage codebase index (status, start, hook install/uninstall)",
|
|
38
41
|
# MCP
|
|
39
42
|
"/mcp": "Manage global MCP servers (list, edit)",
|
|
43
|
+
# Registry
|
|
44
|
+
"/registry": "Browse and install community skills, rules, agents, verifiers",
|
|
40
45
|
# Auth
|
|
41
46
|
"/auth": "GitHub authentication (login, logout, status)",
|
|
42
47
|
# Context
|
|
43
48
|
"/context": "Show current context frame (tokens, reranked items)",
|
|
49
|
+
"/compact": "Compact message history using LLM summarization",
|
|
50
|
+
# Image
|
|
51
|
+
"/paste": "Attach image from clipboard (or use Ctrl+V)",
|
|
44
52
|
# Diagnostics
|
|
45
53
|
"/doctor": "Check Python environment and diagnose issues",
|
|
46
54
|
# Verification
|
|
47
55
|
"/verify": "Run verification checks on current work",
|
|
48
56
|
"/verify-loop [task]": "Run task in loop until verifications pass",
|
|
57
|
+
# Setup wizard
|
|
58
|
+
"/setup": "Setup wizard for rules, agents, skills, and verifiers",
|
|
49
59
|
"/help": "Show available commands",
|
|
50
60
|
"/quit": "Exit the agent",
|
|
51
61
|
}
|
|
@@ -6,16 +6,21 @@ from .todos import handle_todos, handle_todo_add
|
|
|
6
6
|
from .hooks import handle_hooks
|
|
7
7
|
from .rules import handle_rules
|
|
8
8
|
from .skills import handle_skills
|
|
9
|
+
from .index import handle_index
|
|
9
10
|
from .mcp import handle_mcp
|
|
11
|
+
from .registry import handle_registry
|
|
10
12
|
from .auth import handle_auth
|
|
11
13
|
from .doctor import handle_doctor
|
|
12
14
|
from .verify import handle_verify, handle_verify_loop
|
|
15
|
+
from .setup import handle_setup
|
|
13
16
|
from .misc import (
|
|
14
17
|
handle_status,
|
|
15
18
|
handle_pr,
|
|
16
19
|
handle_projectmd,
|
|
17
20
|
handle_research,
|
|
18
21
|
handle_context,
|
|
22
|
+
handle_compact,
|
|
23
|
+
handle_diff,
|
|
19
24
|
)
|
|
20
25
|
|
|
21
26
|
__all__ = [
|
|
@@ -26,14 +31,19 @@ __all__ = [
|
|
|
26
31
|
"handle_hooks",
|
|
27
32
|
"handle_rules",
|
|
28
33
|
"handle_skills",
|
|
34
|
+
"handle_index",
|
|
29
35
|
"handle_mcp",
|
|
36
|
+
"handle_registry",
|
|
30
37
|
"handle_auth",
|
|
31
38
|
"handle_doctor",
|
|
32
39
|
"handle_verify",
|
|
33
40
|
"handle_verify_loop",
|
|
41
|
+
"handle_setup",
|
|
34
42
|
"handle_status",
|
|
35
43
|
"handle_pr",
|
|
36
44
|
"handle_projectmd",
|
|
37
45
|
"handle_research",
|
|
38
46
|
"handle_context",
|
|
47
|
+
"handle_compact",
|
|
48
|
+
"handle_diff",
|
|
39
49
|
]
|
|
@@ -8,6 +8,14 @@ from rich.console import Console
|
|
|
8
8
|
from rich.panel import Panel
|
|
9
9
|
|
|
10
10
|
from ..menus import show_agents_interactive_menu, prompt_agent_name, confirm_delete
|
|
11
|
+
from ....design import (
|
|
12
|
+
Colors,
|
|
13
|
+
header,
|
|
14
|
+
footer,
|
|
15
|
+
SEPARATOR_WIDTH,
|
|
16
|
+
STATUS_ACTIVE,
|
|
17
|
+
ARROW_PROMPT,
|
|
18
|
+
)
|
|
11
19
|
|
|
12
20
|
console = Console()
|
|
13
21
|
|
|
@@ -26,6 +34,9 @@ def create_agent(name: str) -> bool:
|
|
|
26
34
|
template = f'''---
|
|
27
35
|
description: Custom agent for specific tasks
|
|
28
36
|
tools: [grep, glob, read_file, semantic_search]
|
|
37
|
+
# rules: [typescript, security] # Optional: reference rules from .emdash/rules/
|
|
38
|
+
# skills: [code-review] # Optional: reference skills from .emdash/skills/
|
|
39
|
+
# verifiers: [eslint] # Optional: reference verifiers from .emdash/verifiers.json
|
|
29
40
|
---
|
|
30
41
|
|
|
31
42
|
# System Prompt
|
|
@@ -52,8 +63,10 @@ Describe what this agent should accomplish:
|
|
|
52
63
|
Describe how the agent should format its responses.
|
|
53
64
|
'''
|
|
54
65
|
agent_file.write_text(template)
|
|
55
|
-
console.print(
|
|
56
|
-
console.print(f"[
|
|
66
|
+
console.print()
|
|
67
|
+
console.print(f" [{Colors.SUCCESS}]{STATUS_ACTIVE}[/{Colors.SUCCESS}] [{Colors.TEXT}]created:[/{Colors.TEXT}] {name}")
|
|
68
|
+
console.print(f" [{Colors.DIM}]{agent_file}[/{Colors.DIM}]")
|
|
69
|
+
console.print()
|
|
57
70
|
return True
|
|
58
71
|
|
|
59
72
|
|
|
@@ -64,57 +77,67 @@ def show_agent_details(name: str) -> None:
|
|
|
64
77
|
builtin_agents = ["Explore", "Plan"]
|
|
65
78
|
|
|
66
79
|
console.print()
|
|
67
|
-
console.print("[
|
|
80
|
+
console.print(f"[{Colors.MUTED}]{header(name, SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
68
81
|
console.print()
|
|
82
|
+
|
|
69
83
|
if name in builtin_agents:
|
|
70
|
-
console.print(f"[
|
|
84
|
+
console.print(f" [{Colors.DIM}]type[/{Colors.DIM}] [{Colors.MUTED}]built-in[/{Colors.MUTED}]")
|
|
71
85
|
if name == "Explore":
|
|
72
|
-
console.print("[
|
|
73
|
-
console.print("[
|
|
86
|
+
console.print(f" [{Colors.DIM}]desc[/{Colors.DIM}] Fast codebase exploration (read-only)")
|
|
87
|
+
console.print(f" [{Colors.DIM}]tools[/{Colors.DIM}] glob, grep, read_file, list_files, semantic_search")
|
|
74
88
|
elif name == "Plan":
|
|
75
|
-
console.print("[
|
|
76
|
-
console.print("[
|
|
77
|
-
console.print(
|
|
89
|
+
console.print(f" [{Colors.DIM}]desc[/{Colors.DIM}] Design implementation plans")
|
|
90
|
+
console.print(f" [{Colors.DIM}]tools[/{Colors.DIM}] glob, grep, read_file, list_files, semantic_search")
|
|
91
|
+
console.print()
|
|
92
|
+
console.print(f" [{Colors.DIM}]Built-in agents cannot be edited or deleted.[/{Colors.DIM}]")
|
|
78
93
|
else:
|
|
79
94
|
agent = get_custom_agent(name, Path.cwd())
|
|
80
95
|
if agent:
|
|
81
|
-
console.print(f"[
|
|
96
|
+
console.print(f" [{Colors.DIM}]type[/{Colors.DIM}] [{Colors.PRIMARY}]custom[/{Colors.PRIMARY}]")
|
|
82
97
|
|
|
83
|
-
# Show description
|
|
84
98
|
if agent.description:
|
|
85
|
-
console.print(f"[
|
|
99
|
+
console.print(f" [{Colors.DIM}]desc[/{Colors.DIM}] {agent.description}")
|
|
86
100
|
|
|
87
|
-
# Show model
|
|
88
101
|
if agent.model:
|
|
89
|
-
console.print(f"[
|
|
102
|
+
console.print(f" [{Colors.DIM}]model[/{Colors.DIM}] {agent.model}")
|
|
90
103
|
|
|
91
|
-
# Show tools
|
|
92
104
|
if agent.tools:
|
|
93
|
-
console.print(f"[
|
|
105
|
+
console.print(f" [{Colors.DIM}]tools[/{Colors.DIM}] {', '.join(agent.tools)}")
|
|
94
106
|
|
|
95
|
-
# Show MCP servers
|
|
96
107
|
if agent.mcp_servers:
|
|
97
|
-
console.print(
|
|
108
|
+
console.print()
|
|
109
|
+
console.print(f" [{Colors.DIM}]mcp servers:[/{Colors.DIM}]")
|
|
98
110
|
for server in agent.mcp_servers:
|
|
99
|
-
status = "[
|
|
100
|
-
console.print(f"
|
|
101
|
-
console.print(f"
|
|
111
|
+
status = f"[{Colors.SUCCESS}]●[/{Colors.SUCCESS}]" if server.enabled else f"[{Colors.MUTED}]○[/{Colors.MUTED}]"
|
|
112
|
+
console.print(f" {status} [{Colors.PRIMARY}]{server.name}[/{Colors.PRIMARY}]")
|
|
113
|
+
console.print(f" [{Colors.DIM}]{server.command} {' '.join(server.args)}[/{Colors.DIM}]")
|
|
114
|
+
|
|
115
|
+
if agent.rules:
|
|
116
|
+
console.print(f" [{Colors.DIM}]rules[/{Colors.DIM}] {', '.join(agent.rules)}")
|
|
117
|
+
|
|
118
|
+
if agent.skills:
|
|
119
|
+
console.print(f" [{Colors.DIM}]skills[/{Colors.DIM}] {', '.join(agent.skills)}")
|
|
120
|
+
|
|
121
|
+
if agent.verifiers:
|
|
122
|
+
console.print(f" [{Colors.DIM}]verify[/{Colors.DIM}] {', '.join(agent.verifiers)}")
|
|
102
123
|
|
|
103
|
-
# Show file path
|
|
104
124
|
if agent.file_path:
|
|
105
|
-
console.print(
|
|
125
|
+
console.print()
|
|
126
|
+
console.print(f" [{Colors.DIM}]file[/{Colors.DIM}] {agent.file_path}")
|
|
106
127
|
|
|
107
|
-
# Show system prompt preview
|
|
108
128
|
if agent.system_prompt:
|
|
109
|
-
console.print(
|
|
110
|
-
|
|
111
|
-
|
|
129
|
+
console.print()
|
|
130
|
+
console.print(f" [{Colors.DIM}]prompt preview:[/{Colors.DIM}]")
|
|
131
|
+
preview = agent.system_prompt[:250]
|
|
132
|
+
if len(agent.system_prompt) > 250:
|
|
112
133
|
preview += "..."
|
|
113
|
-
|
|
134
|
+
for line in preview.split('\n')[:6]:
|
|
135
|
+
console.print(f" [{Colors.MUTED}]{line}[/{Colors.MUTED}]")
|
|
114
136
|
else:
|
|
115
|
-
console.print(f"[
|
|
137
|
+
console.print(f" [{Colors.WARNING}]Agent '{name}' not found[/{Colors.WARNING}]")
|
|
138
|
+
|
|
116
139
|
console.print()
|
|
117
|
-
console.print("[
|
|
140
|
+
console.print(f"[{Colors.MUTED}]{footer(SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
118
141
|
|
|
119
142
|
|
|
120
143
|
def delete_agent(name: str) -> bool:
|
|
@@ -177,19 +200,20 @@ def chat_edit_agent(name: str, client, renderer, model, max_iterations, render_w
|
|
|
177
200
|
agent_file = agents_dir / f"{name}.md"
|
|
178
201
|
|
|
179
202
|
if not agent_file.exists():
|
|
180
|
-
console.print(f"[
|
|
203
|
+
console.print(f" [{Colors.WARNING}]Agent file not found: {agent_file}[/{Colors.WARNING}]")
|
|
181
204
|
return
|
|
182
205
|
|
|
183
206
|
# Read current content
|
|
184
207
|
content = agent_file.read_text()
|
|
185
208
|
|
|
186
209
|
console.print()
|
|
187
|
-
console.print(f"[
|
|
188
|
-
console.print(
|
|
210
|
+
console.print(f"[{Colors.MUTED}]{header(f'Edit: {name}', SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
211
|
+
console.print()
|
|
212
|
+
console.print(f" [{Colors.DIM}]Describe changes. Type 'done' to finish.[/{Colors.DIM}]")
|
|
189
213
|
console.print()
|
|
190
214
|
|
|
191
215
|
chat_style = Style.from_dict({
|
|
192
|
-
"prompt": "
|
|
216
|
+
"prompt": f"{Colors.PRIMARY} bold",
|
|
193
217
|
})
|
|
194
218
|
|
|
195
219
|
ps = PromptSession(style=chat_style)
|
|
@@ -253,6 +277,119 @@ Please make the requested changes using the Edit tool."""
|
|
|
253
277
|
console.print(f"[red]Error: {e}[/red]")
|
|
254
278
|
|
|
255
279
|
|
|
280
|
+
def chat_create_agent(client, renderer, model, max_iterations, render_with_interrupt) -> str | None:
|
|
281
|
+
"""Start a chat session to create a new agent with AI assistance.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
The name of the created agent, or None if cancelled.
|
|
285
|
+
"""
|
|
286
|
+
from prompt_toolkit import PromptSession
|
|
287
|
+
from prompt_toolkit.styles import Style
|
|
288
|
+
|
|
289
|
+
agents_dir = Path.cwd() / ".emdash" / "agents"
|
|
290
|
+
|
|
291
|
+
console.print()
|
|
292
|
+
console.print(f"[{Colors.MUTED}]{header('Create Agent', SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
293
|
+
console.print()
|
|
294
|
+
console.print(f" [{Colors.DIM}]Describe your agent. AI will help design it.[/{Colors.DIM}]")
|
|
295
|
+
console.print(f" [{Colors.DIM}]Type 'done' to finish.[/{Colors.DIM}]")
|
|
296
|
+
console.print()
|
|
297
|
+
|
|
298
|
+
chat_style = Style.from_dict({
|
|
299
|
+
"prompt": f"{Colors.PRIMARY} bold",
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
ps = PromptSession(style=chat_style)
|
|
303
|
+
chat_session_id = None
|
|
304
|
+
first_message = True
|
|
305
|
+
|
|
306
|
+
# Ensure agents directory exists
|
|
307
|
+
agents_dir.mkdir(parents=True, exist_ok=True)
|
|
308
|
+
|
|
309
|
+
# Chat loop
|
|
310
|
+
while True:
|
|
311
|
+
try:
|
|
312
|
+
user_input = ps.prompt([("class:prompt", "› ")]).strip()
|
|
313
|
+
|
|
314
|
+
if not user_input:
|
|
315
|
+
continue
|
|
316
|
+
|
|
317
|
+
if user_input.lower() in ("done", "quit", "exit", "q"):
|
|
318
|
+
console.print("[dim]Finished[/dim]")
|
|
319
|
+
break
|
|
320
|
+
|
|
321
|
+
# First message includes context about agents
|
|
322
|
+
if first_message:
|
|
323
|
+
message_with_context = f"""I want to create a new custom agent for my project.
|
|
324
|
+
|
|
325
|
+
**Agents directory:** `{agents_dir}`
|
|
326
|
+
|
|
327
|
+
Agents are markdown files with YAML frontmatter that define specialized assistants with custom system prompts and tools.
|
|
328
|
+
|
|
329
|
+
**Agent file format:**
|
|
330
|
+
```markdown
|
|
331
|
+
---
|
|
332
|
+
description: Brief description of what this agent does
|
|
333
|
+
model: claude-sonnet # optional, defaults to main model
|
|
334
|
+
tools: [grep, glob, read_file, edit_file, bash] # tools this agent can use
|
|
335
|
+
mcp_servers: # optional, MCP servers for this agent
|
|
336
|
+
- name: server-name
|
|
337
|
+
command: npx
|
|
338
|
+
args: ["-y", "@modelcontextprotocol/server-name"]
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
# System Prompt
|
|
342
|
+
|
|
343
|
+
You are a specialized assistant for [purpose].
|
|
344
|
+
|
|
345
|
+
## Your Mission
|
|
346
|
+
[What this agent should accomplish]
|
|
347
|
+
|
|
348
|
+
## Approach
|
|
349
|
+
[How this agent should work]
|
|
350
|
+
|
|
351
|
+
## Output Format
|
|
352
|
+
[How the agent should format responses]
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
**Available tools:** grep, glob, read_file, edit_file, write_file, bash, semantic_search, list_files, etc.
|
|
356
|
+
|
|
357
|
+
**My request:** {user_input}
|
|
358
|
+
|
|
359
|
+
Please help me design and create an agent. Ask me questions about what I need, then use the Write tool to create the file at `{agents_dir}/<agent-name>.md`."""
|
|
360
|
+
stream = client.agent_chat_stream(
|
|
361
|
+
message=message_with_context,
|
|
362
|
+
model=model,
|
|
363
|
+
max_iterations=max_iterations,
|
|
364
|
+
options={"mode": "code"},
|
|
365
|
+
)
|
|
366
|
+
first_message = False
|
|
367
|
+
elif chat_session_id:
|
|
368
|
+
stream = client.agent_continue_stream(
|
|
369
|
+
chat_session_id, user_input
|
|
370
|
+
)
|
|
371
|
+
else:
|
|
372
|
+
stream = client.agent_chat_stream(
|
|
373
|
+
message=user_input,
|
|
374
|
+
model=model,
|
|
375
|
+
max_iterations=max_iterations,
|
|
376
|
+
options={"mode": "code"},
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
result = render_with_interrupt(renderer, stream)
|
|
380
|
+
if result and result.get("session_id"):
|
|
381
|
+
chat_session_id = result["session_id"]
|
|
382
|
+
|
|
383
|
+
except (KeyboardInterrupt, EOFError):
|
|
384
|
+
console.print()
|
|
385
|
+
console.print("[dim]Cancelled[/dim]")
|
|
386
|
+
break
|
|
387
|
+
except Exception as e:
|
|
388
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
389
|
+
|
|
390
|
+
return None
|
|
391
|
+
|
|
392
|
+
|
|
256
393
|
def handle_agents(args: str, client, renderer, model, max_iterations, render_with_interrupt) -> None:
|
|
257
394
|
"""Handle /agents command."""
|
|
258
395
|
from prompt_toolkit import PromptSession
|
|
@@ -287,7 +424,7 @@ def handle_agents(args: str, client, renderer, model, max_iterations, render_wit
|
|
|
287
424
|
is_custom = agent_name not in ("Explore", "Plan")
|
|
288
425
|
try:
|
|
289
426
|
if is_custom:
|
|
290
|
-
console.print("[
|
|
427
|
+
console.print("[cyan]'c'[/cyan] chat • [cyan]'e'[/cyan] edit • [red]'d'[/red] delete • [dim]Enter back[/dim]", end="")
|
|
291
428
|
else:
|
|
292
429
|
console.print("[dim]Press Enter to go back...[/dim]", end="")
|
|
293
430
|
ps = PromptSession()
|
|
@@ -296,24 +433,15 @@ def handle_agents(args: str, client, renderer, model, max_iterations, render_wit
|
|
|
296
433
|
chat_edit_agent(agent_name, client, renderer, model, max_iterations, render_with_interrupt)
|
|
297
434
|
elif is_custom and resp == 'e':
|
|
298
435
|
edit_agent(agent_name)
|
|
436
|
+
elif is_custom and resp == 'd':
|
|
437
|
+
if delete_agent(agent_name):
|
|
438
|
+
continue # Refresh menu after deletion
|
|
299
439
|
console.print() # Add spacing before menu reappears
|
|
300
440
|
except (KeyboardInterrupt, EOFError):
|
|
301
441
|
break
|
|
302
442
|
elif action == "create":
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
if create_agent(new_name):
|
|
306
|
-
# Ask if they want to edit it
|
|
307
|
-
console.print("[dim]Press 'e' to edit, or Enter to continue...[/dim]")
|
|
308
|
-
try:
|
|
309
|
-
ps = PromptSession()
|
|
310
|
-
resp = ps.prompt("> ")
|
|
311
|
-
if resp.strip().lower() == 'e':
|
|
312
|
-
edit_agent(new_name)
|
|
313
|
-
except (KeyboardInterrupt, EOFError):
|
|
314
|
-
pass
|
|
315
|
-
else:
|
|
316
|
-
console.print("[dim]Cancelled[/dim]")
|
|
443
|
+
# Use AI-assisted creation
|
|
444
|
+
chat_create_agent(client, renderer, model, max_iterations, render_with_interrupt)
|
|
317
445
|
elif action == "delete":
|
|
318
446
|
delete_agent(agent_name)
|
|
319
447
|
elif action == "edit":
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""Handler for /index command."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
console = Console()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def handle_index(args: str, client) -> None:
|
|
12
|
+
"""Handle /index command.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
args: Command arguments (status, start, hook install/uninstall)
|
|
16
|
+
client: EmdashClient instance
|
|
17
|
+
"""
|
|
18
|
+
# Parse subcommand
|
|
19
|
+
subparts = args.split(maxsplit=1) if args else []
|
|
20
|
+
subcommand = subparts[0].lower() if subparts else "status"
|
|
21
|
+
subargs = subparts[1].strip() if len(subparts) > 1 else ""
|
|
22
|
+
|
|
23
|
+
repo_path = os.getcwd()
|
|
24
|
+
|
|
25
|
+
if subcommand == "status":
|
|
26
|
+
_show_status(client, repo_path)
|
|
27
|
+
|
|
28
|
+
elif subcommand == "start":
|
|
29
|
+
_start_index(client, repo_path, subargs)
|
|
30
|
+
|
|
31
|
+
elif subcommand == "hook":
|
|
32
|
+
_handle_hook(repo_path, subargs)
|
|
33
|
+
|
|
34
|
+
else:
|
|
35
|
+
console.print(f"[yellow]Unknown subcommand: {subcommand}[/yellow]")
|
|
36
|
+
console.print("[dim]Usage: /index [status|start|hook][/dim]")
|
|
37
|
+
console.print("[dim] /index - Show index status[/dim]")
|
|
38
|
+
console.print("[dim] /index start - Start incremental indexing[/dim]")
|
|
39
|
+
console.print("[dim] /index start --full - Force full reindex[/dim]")
|
|
40
|
+
console.print("[dim] /index hook install - Install post-commit hook[/dim]")
|
|
41
|
+
console.print("[dim] /index hook uninstall - Remove post-commit hook[/dim]")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _show_status(client, repo_path: str) -> None:
|
|
45
|
+
"""Show index status."""
|
|
46
|
+
try:
|
|
47
|
+
status = client.index_status(repo_path)
|
|
48
|
+
|
|
49
|
+
console.print("\n[bold cyan]Index Status[/bold cyan]\n")
|
|
50
|
+
is_indexed = status.get("is_indexed", False)
|
|
51
|
+
console.print(f" Indexed: {'[green]Yes[/green]' if is_indexed else '[yellow]No[/yellow]'}")
|
|
52
|
+
|
|
53
|
+
if is_indexed:
|
|
54
|
+
console.print(f" Files: {status.get('file_count', 0)}")
|
|
55
|
+
console.print(f" Functions: {status.get('function_count', 0)}")
|
|
56
|
+
console.print(f" Classes: {status.get('class_count', 0)}")
|
|
57
|
+
console.print(f" Communities: {status.get('community_count', 0)}")
|
|
58
|
+
|
|
59
|
+
if status.get("last_indexed"):
|
|
60
|
+
console.print(f" Last indexed: {status.get('last_indexed')}")
|
|
61
|
+
if status.get("last_commit"):
|
|
62
|
+
console.print(f" Last commit: {status.get('last_commit')[:8]}")
|
|
63
|
+
|
|
64
|
+
# Check hook status
|
|
65
|
+
hooks_dir = Path(repo_path) / ".git" / "hooks"
|
|
66
|
+
hook_path = hooks_dir / "post-commit"
|
|
67
|
+
if hook_path.exists() and "emdash" in hook_path.read_text():
|
|
68
|
+
console.print(f" Auto-index: [green]Enabled[/green] (post-commit hook)")
|
|
69
|
+
else:
|
|
70
|
+
console.print(f" Auto-index: [dim]Disabled[/dim] (run /index hook install)")
|
|
71
|
+
|
|
72
|
+
console.print()
|
|
73
|
+
|
|
74
|
+
except Exception as e:
|
|
75
|
+
console.print(f"[red]Error getting status: {e}[/red]")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _start_index(client, repo_path: str, args: str) -> None:
|
|
79
|
+
"""Start indexing."""
|
|
80
|
+
import json
|
|
81
|
+
from rich.progress import Progress, BarColumn, TaskProgressColumn, TextColumn
|
|
82
|
+
|
|
83
|
+
# Parse options
|
|
84
|
+
full = "--full" in args
|
|
85
|
+
|
|
86
|
+
console.print(f"\n[bold cyan]Indexing[/bold cyan] {repo_path}\n")
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
with Progress(
|
|
90
|
+
TextColumn("[bold cyan]{task.description}[/bold cyan]"),
|
|
91
|
+
BarColumn(bar_width=40, complete_style="cyan", finished_style="green"),
|
|
92
|
+
TaskProgressColumn(),
|
|
93
|
+
console=console,
|
|
94
|
+
transient=True,
|
|
95
|
+
) as progress:
|
|
96
|
+
task = progress.add_task("Starting...", total=100)
|
|
97
|
+
|
|
98
|
+
for line in client.index_start_stream(repo_path, not full):
|
|
99
|
+
line = line.strip()
|
|
100
|
+
if line.startswith("event: "):
|
|
101
|
+
continue
|
|
102
|
+
if line.startswith("data: "):
|
|
103
|
+
try:
|
|
104
|
+
data = json.loads(line[6:])
|
|
105
|
+
step = data.get("step") or data.get("message", "")
|
|
106
|
+
percent = data.get("percent")
|
|
107
|
+
|
|
108
|
+
if step:
|
|
109
|
+
progress.update(task, description=step)
|
|
110
|
+
if percent is not None:
|
|
111
|
+
progress.update(task, completed=percent)
|
|
112
|
+
except json.JSONDecodeError:
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
progress.update(task, completed=100, description="Complete")
|
|
116
|
+
|
|
117
|
+
console.print("[bold green]Indexing complete![/bold green]\n")
|
|
118
|
+
|
|
119
|
+
except Exception as e:
|
|
120
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _handle_hook(repo_path: str, args: str) -> None:
|
|
124
|
+
"""Handle hook install/uninstall."""
|
|
125
|
+
action = args.lower() if args else ""
|
|
126
|
+
|
|
127
|
+
if action not in ("install", "uninstall"):
|
|
128
|
+
console.print("[yellow]Usage: /index hook [install|uninstall][/yellow]")
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
hooks_dir = Path(repo_path) / ".git" / "hooks"
|
|
132
|
+
hook_path = hooks_dir / "post-commit"
|
|
133
|
+
|
|
134
|
+
if not hooks_dir.exists():
|
|
135
|
+
console.print(f"[red]Error:[/red] Not a git repository: {repo_path}")
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
hook_content = """#!/bin/sh
|
|
139
|
+
# emdash post-commit hook - auto-reindex on commit
|
|
140
|
+
# Installed by: emdash index hook install
|
|
141
|
+
|
|
142
|
+
# Run indexing in background to not block the commit
|
|
143
|
+
emdash index start > /dev/null 2>&1 &
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
if action == "install":
|
|
147
|
+
if hook_path.exists():
|
|
148
|
+
existing = hook_path.read_text()
|
|
149
|
+
if "emdash" in existing:
|
|
150
|
+
console.print("[yellow]Hook already installed[/yellow]")
|
|
151
|
+
return
|
|
152
|
+
else:
|
|
153
|
+
console.print("[yellow]Appending to existing post-commit hook[/yellow]")
|
|
154
|
+
with open(hook_path, "a") as f:
|
|
155
|
+
f.write("\n# emdash auto-index\nemdash index start > /dev/null 2>&1 &\n")
|
|
156
|
+
else:
|
|
157
|
+
hook_path.write_text(hook_content)
|
|
158
|
+
|
|
159
|
+
hook_path.chmod(0o755)
|
|
160
|
+
console.print(f"[green]Post-commit hook installed[/green]")
|
|
161
|
+
console.print("[dim]Index will update automatically after each commit[/dim]")
|
|
162
|
+
|
|
163
|
+
elif action == "uninstall":
|
|
164
|
+
if not hook_path.exists():
|
|
165
|
+
console.print("[yellow]No post-commit hook found[/yellow]")
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
existing = hook_path.read_text()
|
|
169
|
+
if "emdash" not in existing:
|
|
170
|
+
console.print("[yellow]No emdash hook found in post-commit[/yellow]")
|
|
171
|
+
return
|
|
172
|
+
|
|
173
|
+
if existing.strip() == hook_content.strip():
|
|
174
|
+
hook_path.unlink()
|
|
175
|
+
console.print("[green]Post-commit hook removed[/green]")
|
|
176
|
+
else:
|
|
177
|
+
lines = existing.split("\n")
|
|
178
|
+
new_lines = [
|
|
179
|
+
line for line in lines
|
|
180
|
+
if "emdash" not in line and "auto-reindex" not in line
|
|
181
|
+
]
|
|
182
|
+
hook_path.write_text("\n".join(new_lines))
|
|
183
|
+
console.print("[green]Emdash hook lines removed from post-commit[/green]")
|