emdash-cli 0.1.46__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.
- emdash_cli/client.py +12 -28
- emdash_cli/commands/__init__.py +2 -2
- emdash_cli/commands/agent/constants.py +10 -0
- emdash_cli/commands/agent/handlers/__init__.py +10 -0
- emdash_cli/commands/agent/handlers/agents.py +67 -39
- emdash_cli/commands/agent/handlers/index.py +183 -0
- emdash_cli/commands/agent/handlers/misc.py +119 -0
- emdash_cli/commands/agent/handlers/registry.py +72 -0
- emdash_cli/commands/agent/handlers/rules.py +48 -31
- emdash_cli/commands/agent/handlers/sessions.py +1 -1
- emdash_cli/commands/agent/handlers/setup.py +187 -54
- emdash_cli/commands/agent/handlers/skills.py +42 -4
- emdash_cli/commands/agent/handlers/telegram.py +475 -0
- emdash_cli/commands/agent/handlers/todos.py +55 -34
- emdash_cli/commands/agent/handlers/verify.py +10 -5
- emdash_cli/commands/agent/help.py +236 -0
- emdash_cli/commands/agent/interactive.py +222 -37
- emdash_cli/commands/agent/menus.py +116 -84
- emdash_cli/commands/agent/onboarding.py +619 -0
- emdash_cli/commands/agent/session_restore.py +210 -0
- emdash_cli/commands/index.py +111 -13
- emdash_cli/commands/registry.py +635 -0
- emdash_cli/commands/skills.py +72 -6
- emdash_cli/design.py +328 -0
- emdash_cli/diff_renderer.py +438 -0
- emdash_cli/integrations/__init__.py +1 -0
- emdash_cli/integrations/telegram/__init__.py +15 -0
- emdash_cli/integrations/telegram/bot.py +402 -0
- emdash_cli/integrations/telegram/bridge.py +865 -0
- emdash_cli/integrations/telegram/config.py +155 -0
- emdash_cli/integrations/telegram/formatter.py +385 -0
- emdash_cli/main.py +52 -2
- emdash_cli/sse_renderer.py +632 -171
- {emdash_cli-0.1.46.dist-info → emdash_cli-0.1.67.dist-info}/METADATA +2 -2
- emdash_cli-0.1.67.dist-info/RECORD +63 -0
- emdash_cli/commands/swarm.py +0 -86
- emdash_cli-0.1.46.dist-info/RECORD +0 -49
- {emdash_cli-0.1.46.dist-info → emdash_cli-0.1.67.dist-info}/WHEEL +0 -0
- {emdash_cli-0.1.46.dist-info → emdash_cli-0.1.67.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"""Contextual help system for emdash CLI.
|
|
2
|
+
|
|
3
|
+
Provides detailed help for commands with zen styling.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
from ...design import (
|
|
9
|
+
Colors,
|
|
10
|
+
STATUS_ACTIVE,
|
|
11
|
+
DOT_BULLET,
|
|
12
|
+
ARROW_PROMPT,
|
|
13
|
+
header,
|
|
14
|
+
footer,
|
|
15
|
+
SEPARATOR_WIDTH,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
console = Console()
|
|
19
|
+
|
|
20
|
+
# Detailed help for each command
|
|
21
|
+
COMMAND_HELP = {
|
|
22
|
+
"/help": {
|
|
23
|
+
"description": "Show available commands and help",
|
|
24
|
+
"usage": ["/help", "/help <command>"],
|
|
25
|
+
"examples": ["/help", "/help agents"],
|
|
26
|
+
},
|
|
27
|
+
"/plan": {
|
|
28
|
+
"description": "Switch to plan mode for exploration and architecture",
|
|
29
|
+
"usage": ["/plan"],
|
|
30
|
+
"details": """Plan mode is read-only. The agent will explore your codebase,
|
|
31
|
+
analyze architecture, and create implementation plans without
|
|
32
|
+
making any changes.""",
|
|
33
|
+
"examples": ["/plan"],
|
|
34
|
+
},
|
|
35
|
+
"/code": {
|
|
36
|
+
"description": "Switch to code mode for implementation",
|
|
37
|
+
"usage": ["/code"],
|
|
38
|
+
"details": """Code mode allows the agent to make changes to your codebase.
|
|
39
|
+
Use this after approving a plan or for direct implementation tasks.""",
|
|
40
|
+
"examples": ["/code"],
|
|
41
|
+
},
|
|
42
|
+
"/agents": {
|
|
43
|
+
"description": "Manage custom agents for specialized tasks",
|
|
44
|
+
"usage": [
|
|
45
|
+
"/agents",
|
|
46
|
+
"/agents create <name>",
|
|
47
|
+
"/agents show <name>",
|
|
48
|
+
"/agents edit <name>",
|
|
49
|
+
"/agents delete <name>",
|
|
50
|
+
],
|
|
51
|
+
"details": """Custom agents extend emdash with specialized capabilities.
|
|
52
|
+
Each agent has its own prompt, tools, and behavior configuration.""",
|
|
53
|
+
"examples": [
|
|
54
|
+
"/agents",
|
|
55
|
+
"/agents create code-reviewer",
|
|
56
|
+
"/agents edit planner",
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
"/rules": {
|
|
60
|
+
"description": "Configure rules that guide agent behavior",
|
|
61
|
+
"usage": ["/rules", "/rules create", "/rules list"],
|
|
62
|
+
"details": """Rules define coding standards, preferences, and project
|
|
63
|
+
conventions. The agent follows these guidelines when working
|
|
64
|
+
on your codebase.""",
|
|
65
|
+
"examples": ["/rules", "/rules create"],
|
|
66
|
+
},
|
|
67
|
+
"/skills": {
|
|
68
|
+
"description": "Manage reusable skill templates",
|
|
69
|
+
"usage": ["/skills", "/skills create", "/skills list"],
|
|
70
|
+
"details": """Skills are reusable prompt templates that can be invoked
|
|
71
|
+
for common tasks like code review, testing, or documentation.""",
|
|
72
|
+
"examples": ["/skills", "/skills create"],
|
|
73
|
+
},
|
|
74
|
+
"/session": {
|
|
75
|
+
"description": "Manage conversation sessions",
|
|
76
|
+
"usage": ["/session", "/session save <name>", "/session load <name>"],
|
|
77
|
+
"details": """Sessions preserve conversation context. Save sessions to
|
|
78
|
+
continue work later or switch between different tasks.""",
|
|
79
|
+
"examples": [
|
|
80
|
+
"/session",
|
|
81
|
+
"/session save my-feature",
|
|
82
|
+
"/session load my-feature",
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
"/verify": {
|
|
86
|
+
"description": "Run verification checks on the codebase",
|
|
87
|
+
"usage": ["/verify", "/verify <check>"],
|
|
88
|
+
"details": """Runs configured verifiers (tests, linting, type checking)
|
|
89
|
+
to ensure code quality. Use /verify-loop for automatic fixing.""",
|
|
90
|
+
"examples": ["/verify", "/verify tests"],
|
|
91
|
+
},
|
|
92
|
+
"/verify-loop": {
|
|
93
|
+
"description": "Run verification loop with automatic fixing",
|
|
94
|
+
"usage": ["/verify-loop <task>"],
|
|
95
|
+
"details": """Runs verifiers repeatedly, letting the agent fix issues
|
|
96
|
+
until all checks pass or you stop the loop.""",
|
|
97
|
+
"examples": ["/verify-loop fix the failing tests"],
|
|
98
|
+
},
|
|
99
|
+
"/pr": {
|
|
100
|
+
"description": "Review or create pull requests",
|
|
101
|
+
"usage": ["/pr <url>", "/pr create"],
|
|
102
|
+
"details": """Review GitHub pull requests or create new ones.
|
|
103
|
+
Provides detailed analysis of changes and suggestions.""",
|
|
104
|
+
"examples": [
|
|
105
|
+
"/pr https://github.com/org/repo/pull/123",
|
|
106
|
+
"/pr create",
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
"/research": {
|
|
110
|
+
"description": "Research a topic using web search",
|
|
111
|
+
"usage": ["/research <query>"],
|
|
112
|
+
"details": """Searches the web for information and provides a summary.
|
|
113
|
+
Useful for finding documentation, examples, or solutions.""",
|
|
114
|
+
"examples": ["/research react hooks best practices"],
|
|
115
|
+
},
|
|
116
|
+
"/todos": {
|
|
117
|
+
"description": "View and manage task list",
|
|
118
|
+
"usage": ["/todos", "/todo-add <task>"],
|
|
119
|
+
"details": """Track tasks and progress. The agent can also add todos
|
|
120
|
+
during planning and implementation.""",
|
|
121
|
+
"examples": ["/todos", "/todo-add implement auth"],
|
|
122
|
+
},
|
|
123
|
+
"/context": {
|
|
124
|
+
"description": "View current context information",
|
|
125
|
+
"usage": ["/context"],
|
|
126
|
+
"details": """Shows token usage, context breakdown, and reranked items
|
|
127
|
+
in the current session.""",
|
|
128
|
+
"examples": ["/context"],
|
|
129
|
+
},
|
|
130
|
+
"/compact": {
|
|
131
|
+
"description": "Compact conversation context",
|
|
132
|
+
"usage": ["/compact"],
|
|
133
|
+
"details": """Summarizes the conversation to reduce context size while
|
|
134
|
+
preserving important information. Use when hitting limits.""",
|
|
135
|
+
"examples": ["/compact"],
|
|
136
|
+
},
|
|
137
|
+
"/status": {
|
|
138
|
+
"description": "Show current status and configuration",
|
|
139
|
+
"usage": ["/status"],
|
|
140
|
+
"details": """Displays current mode, model, session info, and
|
|
141
|
+
active configuration.""",
|
|
142
|
+
"examples": ["/status"],
|
|
143
|
+
},
|
|
144
|
+
"/doctor": {
|
|
145
|
+
"description": "Run diagnostic checks",
|
|
146
|
+
"usage": ["/doctor"],
|
|
147
|
+
"details": """Checks environment, dependencies, and configuration
|
|
148
|
+
for potential issues.""",
|
|
149
|
+
"examples": ["/doctor"],
|
|
150
|
+
},
|
|
151
|
+
"/auth": {
|
|
152
|
+
"description": "Manage authentication",
|
|
153
|
+
"usage": ["/auth login", "/auth logout", "/auth status"],
|
|
154
|
+
"details": """Connect or disconnect from GitHub. Authentication enables
|
|
155
|
+
PR reviews, issue management, and repository access.""",
|
|
156
|
+
"examples": ["/auth login", "/auth status"],
|
|
157
|
+
},
|
|
158
|
+
"/setup": {
|
|
159
|
+
"description": "Run interactive setup wizard",
|
|
160
|
+
"usage": ["/setup", "/setup rules", "/setup agents"],
|
|
161
|
+
"details": """AI-assisted setup for configuring rules, agents, skills,
|
|
162
|
+
and verifiers with templates and guidance.""",
|
|
163
|
+
"examples": ["/setup", "/setup rules"],
|
|
164
|
+
},
|
|
165
|
+
"/reset": {
|
|
166
|
+
"description": "Reset current session",
|
|
167
|
+
"usage": ["/reset"],
|
|
168
|
+
"details": """Clears the current session context. Use to start fresh
|
|
169
|
+
without closing the CLI.""",
|
|
170
|
+
"examples": ["/reset"],
|
|
171
|
+
},
|
|
172
|
+
"/quit": {
|
|
173
|
+
"description": "Exit emdash",
|
|
174
|
+
"usage": ["/quit", "/exit", "/q"],
|
|
175
|
+
"examples": ["/quit"],
|
|
176
|
+
},
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def show_command_help(command: str) -> None:
|
|
181
|
+
"""Show detailed help for a specific command."""
|
|
182
|
+
# Normalize command
|
|
183
|
+
if not command.startswith("/"):
|
|
184
|
+
command = "/" + command
|
|
185
|
+
|
|
186
|
+
help_info = COMMAND_HELP.get(command)
|
|
187
|
+
|
|
188
|
+
if not help_info:
|
|
189
|
+
console.print(f" [{Colors.ERROR}]Unknown command: {command}[/{Colors.ERROR}]")
|
|
190
|
+
console.print(f" [{Colors.DIM}]Type /help to see all commands[/{Colors.DIM}]")
|
|
191
|
+
return
|
|
192
|
+
|
|
193
|
+
console.print()
|
|
194
|
+
console.print(f"[{Colors.MUTED}]{header(command, SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
195
|
+
console.print()
|
|
196
|
+
console.print(f" {help_info['description']}")
|
|
197
|
+
console.print()
|
|
198
|
+
|
|
199
|
+
# Usage
|
|
200
|
+
console.print(f" [{Colors.PRIMARY}]Usage:[/{Colors.PRIMARY}]")
|
|
201
|
+
for usage in help_info.get("usage", []):
|
|
202
|
+
console.print(f" {usage}")
|
|
203
|
+
console.print()
|
|
204
|
+
|
|
205
|
+
# Details
|
|
206
|
+
if "details" in help_info:
|
|
207
|
+
console.print(f" [{Colors.DIM}]{help_info['details']}[/{Colors.DIM}]")
|
|
208
|
+
console.print()
|
|
209
|
+
|
|
210
|
+
# Examples
|
|
211
|
+
if "examples" in help_info:
|
|
212
|
+
console.print(f" [{Colors.PRIMARY}]Examples:[/{Colors.PRIMARY}]")
|
|
213
|
+
for example in help_info["examples"]:
|
|
214
|
+
console.print(f" [{Colors.SUCCESS}]{example}[/{Colors.SUCCESS}]")
|
|
215
|
+
console.print()
|
|
216
|
+
|
|
217
|
+
console.print(f"[{Colors.MUTED}]{footer(SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
218
|
+
console.print()
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def show_quick_tips() -> None:
|
|
222
|
+
"""Show quick tips for new users."""
|
|
223
|
+
console.print()
|
|
224
|
+
console.print(f"[{Colors.MUTED}]{header('Quick Tips', 35)}[/{Colors.MUTED}]")
|
|
225
|
+
console.print()
|
|
226
|
+
console.print(f" [{Colors.DIM}]Keyboard shortcuts:[/{Colors.DIM}]")
|
|
227
|
+
console.print(f" {DOT_BULLET} Ctrl+C to cancel")
|
|
228
|
+
console.print(f" {DOT_BULLET} Esc during execution to interrupt")
|
|
229
|
+
console.print(f" {DOT_BULLET} Alt+Enter for multiline input")
|
|
230
|
+
console.print()
|
|
231
|
+
console.print(f" [{Colors.DIM}]File references:[/{Colors.DIM}]")
|
|
232
|
+
console.print(f" {DOT_BULLET} Use @filename to include files")
|
|
233
|
+
console.print(f" {DOT_BULLET} Tab completion for @file paths")
|
|
234
|
+
console.print()
|
|
235
|
+
console.print(f"[{Colors.MUTED}]{footer(35)}[/{Colors.MUTED}]")
|
|
236
|
+
console.print()
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""Interactive REPL mode for the agent CLI."""
|
|
2
2
|
|
|
3
3
|
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
4
6
|
import threading
|
|
5
7
|
from pathlib import Path
|
|
6
8
|
|
|
@@ -9,6 +11,47 @@ from rich.panel import Panel
|
|
|
9
11
|
from rich.markdown import Markdown
|
|
10
12
|
|
|
11
13
|
from .constants import AgentMode, SLASH_COMMANDS
|
|
14
|
+
from .onboarding import is_first_run, run_onboarding
|
|
15
|
+
from .help import show_command_help
|
|
16
|
+
from .session_restore import get_recent_session, show_session_restore_prompt
|
|
17
|
+
from ...design import (
|
|
18
|
+
header, footer, Colors, STATUS_ACTIVE, DOT_BULLET,
|
|
19
|
+
ARROW_PROMPT, SEPARATOR_WIDTH,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def show_welcome_banner(
|
|
24
|
+
version: str,
|
|
25
|
+
git_repo: str | None,
|
|
26
|
+
git_branch: str | None,
|
|
27
|
+
mode: str,
|
|
28
|
+
model: str,
|
|
29
|
+
console: Console,
|
|
30
|
+
) -> None:
|
|
31
|
+
"""Display clean welcome banner with zen styling."""
|
|
32
|
+
console.print()
|
|
33
|
+
|
|
34
|
+
# Simple header
|
|
35
|
+
console.print(f"[{Colors.MUTED}]{header('emdash', SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
36
|
+
console.print(f" [{Colors.DIM}]v{version}[/{Colors.DIM}]")
|
|
37
|
+
console.print()
|
|
38
|
+
|
|
39
|
+
# Info section
|
|
40
|
+
if git_repo:
|
|
41
|
+
branch_display = f" [{Colors.WARNING}]{git_branch}[/{Colors.WARNING}]" if git_branch else ""
|
|
42
|
+
console.print(f" [{Colors.DIM}]repo[/{Colors.DIM}] [{Colors.SUCCESS}]{git_repo}[/{Colors.SUCCESS}]{branch_display}")
|
|
43
|
+
|
|
44
|
+
mode_color = Colors.WARNING if mode == "plan" else Colors.SUCCESS
|
|
45
|
+
console.print(f" [{Colors.DIM}]mode[/{Colors.DIM}] [{mode_color}]{mode}[/{mode_color}]")
|
|
46
|
+
console.print(f" [{Colors.DIM}]model[/{Colors.DIM}] [{Colors.MUTED}]{model}[/{Colors.MUTED}]")
|
|
47
|
+
console.print()
|
|
48
|
+
|
|
49
|
+
console.print(f"[{Colors.MUTED}]{footer(SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
50
|
+
console.print()
|
|
51
|
+
|
|
52
|
+
# Quick tips
|
|
53
|
+
console.print(f" [{Colors.DIM}]› /help commands › @file include files › Ctrl+C cancel[/{Colors.DIM}]")
|
|
54
|
+
console.print()
|
|
12
55
|
from .file_utils import expand_file_references, fuzzy_find_files
|
|
13
56
|
from .menus import (
|
|
14
57
|
get_clarification_response,
|
|
@@ -23,7 +66,9 @@ from .handlers import (
|
|
|
23
66
|
handle_hooks,
|
|
24
67
|
handle_rules,
|
|
25
68
|
handle_skills,
|
|
69
|
+
handle_index,
|
|
26
70
|
handle_mcp,
|
|
71
|
+
handle_registry,
|
|
27
72
|
handle_auth,
|
|
28
73
|
handle_doctor,
|
|
29
74
|
handle_verify,
|
|
@@ -34,6 +79,9 @@ from .handlers import (
|
|
|
34
79
|
handle_projectmd,
|
|
35
80
|
handle_research,
|
|
36
81
|
handle_context,
|
|
82
|
+
handle_compact,
|
|
83
|
+
handle_diff,
|
|
84
|
+
handle_telegram,
|
|
37
85
|
)
|
|
38
86
|
|
|
39
87
|
console = Console()
|
|
@@ -141,18 +189,32 @@ def run_interactive(
|
|
|
141
189
|
# Pending todos to add when session starts
|
|
142
190
|
pending_todos: list[str] = []
|
|
143
191
|
|
|
144
|
-
# Style for prompt
|
|
192
|
+
# Style for prompt (emdash signature style)
|
|
193
|
+
# Toolbar info (will be set later, but need closure access)
|
|
194
|
+
toolbar_branch: str | None = None
|
|
195
|
+
toolbar_model: str = "unknown"
|
|
196
|
+
|
|
145
197
|
PROMPT_STYLE = Style.from_dict({
|
|
146
|
-
"prompt.mode.plan": "
|
|
147
|
-
"prompt.mode.code": "
|
|
148
|
-
"prompt.prefix":
|
|
149
|
-
"prompt.
|
|
150
|
-
"
|
|
151
|
-
"completion-menu
|
|
152
|
-
"completion-menu.completion
|
|
153
|
-
"completion-menu.
|
|
154
|
-
"completion-menu.meta.completion
|
|
155
|
-
"
|
|
198
|
+
"prompt.mode.plan": f"{Colors.WARNING} bold",
|
|
199
|
+
"prompt.mode.code": f"{Colors.PRIMARY} bold",
|
|
200
|
+
"prompt.prefix": Colors.MUTED,
|
|
201
|
+
"prompt.cursor": f"{Colors.PRIMARY}",
|
|
202
|
+
"prompt.image": Colors.ACCENT,
|
|
203
|
+
"completion-menu": "bg:#1a1a2e #e8ecf0",
|
|
204
|
+
"completion-menu.completion": "bg:#1a1a2e #e8ecf0",
|
|
205
|
+
"completion-menu.completion.current": f"bg:#2a2a3e {Colors.SUCCESS} bold",
|
|
206
|
+
"completion-menu.meta.completion": f"bg:#1a1a2e {Colors.MUTED}",
|
|
207
|
+
"completion-menu.meta.completion.current": f"bg:#2a2a3e {Colors.SUBTLE}",
|
|
208
|
+
"command": f"{Colors.PRIMARY} bold",
|
|
209
|
+
# Zen bottom toolbar styles
|
|
210
|
+
"bottom-toolbar": f"bg:#1a1a1a {Colors.DIM}",
|
|
211
|
+
"bottom-toolbar.brand": f"bg:#1a1a1a {Colors.PRIMARY}",
|
|
212
|
+
"bottom-toolbar.branch": f"bg:#1a1a1a {Colors.WARNING}",
|
|
213
|
+
"bottom-toolbar.model": f"bg:#1a1a1a {Colors.ACCENT}",
|
|
214
|
+
"bottom-toolbar.mode-code": f"bg:#1a1a1a {Colors.SUCCESS}",
|
|
215
|
+
"bottom-toolbar.mode-plan": f"bg:#1a1a1a {Colors.WARNING}",
|
|
216
|
+
"bottom-toolbar.session": f"bg:#1a1a1a {Colors.SUCCESS}",
|
|
217
|
+
"bottom-toolbar.no-session": f"bg:#1a1a1a {Colors.MUTED}",
|
|
156
218
|
})
|
|
157
219
|
|
|
158
220
|
class SlashCommandCompleter(Completer):
|
|
@@ -229,6 +291,8 @@ def run_interactive(
|
|
|
229
291
|
if image_data:
|
|
230
292
|
base64_data, img_format = image_data
|
|
231
293
|
attached_images.append({"data": base64_data, "format": img_format})
|
|
294
|
+
# Show feedback that image was attached
|
|
295
|
+
console.print(f" [{Colors.SUCCESS}]✓ Image {len(attached_images)} attached[/{Colors.SUCCESS}]")
|
|
232
296
|
# Refresh prompt to show updated image list
|
|
233
297
|
event.app.invalidate()
|
|
234
298
|
return
|
|
@@ -275,39 +339,88 @@ def run_interactive(
|
|
|
275
339
|
except Exception:
|
|
276
340
|
pass
|
|
277
341
|
|
|
342
|
+
def get_bottom_toolbar():
|
|
343
|
+
"""Bottom status bar with zen aesthetic - em-dashes and warm colors."""
|
|
344
|
+
nonlocal current_mode, session_id, toolbar_branch, toolbar_model
|
|
345
|
+
|
|
346
|
+
# Zen symbols
|
|
347
|
+
em = "─"
|
|
348
|
+
dot = "∷"
|
|
349
|
+
|
|
350
|
+
# Build toolbar with zen aesthetic
|
|
351
|
+
parts = [
|
|
352
|
+
("class:bottom-toolbar", f" {em}{em} "),
|
|
353
|
+
("class:bottom-toolbar.brand", "◈ emdash"),
|
|
354
|
+
]
|
|
355
|
+
|
|
356
|
+
# Branch with stippled bullet
|
|
357
|
+
if toolbar_branch:
|
|
358
|
+
parts.append(("class:bottom-toolbar", f" {dot} "))
|
|
359
|
+
parts.append(("class:bottom-toolbar.branch", toolbar_branch))
|
|
360
|
+
|
|
361
|
+
# Model with stippled bullet
|
|
362
|
+
if toolbar_model and toolbar_model != "unknown":
|
|
363
|
+
parts.append(("class:bottom-toolbar", f" {dot} "))
|
|
364
|
+
parts.append(("class:bottom-toolbar.model", toolbar_model))
|
|
365
|
+
|
|
366
|
+
# Mode indicator
|
|
367
|
+
parts.append(("class:bottom-toolbar", f" {em}{em} "))
|
|
368
|
+
if current_mode == AgentMode.PLAN:
|
|
369
|
+
parts.append(("class:bottom-toolbar.mode-plan", "▹ plan"))
|
|
370
|
+
else:
|
|
371
|
+
parts.append(("class:bottom-toolbar.mode-code", "▸ code"))
|
|
372
|
+
|
|
373
|
+
# Session indicator
|
|
374
|
+
if session_id:
|
|
375
|
+
parts.append(("class:bottom-toolbar.session", " ●"))
|
|
376
|
+
else:
|
|
377
|
+
parts.append(("class:bottom-toolbar.no-session", " ○"))
|
|
378
|
+
|
|
379
|
+
parts.append(("class:bottom-toolbar", " "))
|
|
380
|
+
|
|
381
|
+
return parts
|
|
382
|
+
|
|
278
383
|
session = PromptSession(
|
|
279
384
|
history=history,
|
|
280
385
|
completer=SlashCommandCompleter(),
|
|
281
386
|
style=PROMPT_STYLE,
|
|
282
387
|
complete_while_typing=True,
|
|
283
388
|
multiline=True,
|
|
284
|
-
prompt_continuation="
|
|
389
|
+
prompt_continuation=" ",
|
|
285
390
|
key_bindings=kb,
|
|
391
|
+
bottom_toolbar=get_bottom_toolbar,
|
|
286
392
|
)
|
|
287
393
|
|
|
288
394
|
# Watch for image paths being pasted/dropped
|
|
289
395
|
session.default_buffer.on_text_changed += check_for_image_path
|
|
290
396
|
|
|
291
397
|
def get_prompt():
|
|
292
|
-
"""Get formatted prompt."""
|
|
293
|
-
nonlocal attached_images
|
|
398
|
+
"""Get formatted prompt with distinctive emdash styling."""
|
|
399
|
+
nonlocal attached_images, current_mode
|
|
294
400
|
parts = []
|
|
295
401
|
# Show attached images above prompt
|
|
296
402
|
if attached_images:
|
|
297
|
-
image_tags = " ".join(f"[Image
|
|
298
|
-
parts.append(("class:prompt.image", f"
|
|
299
|
-
|
|
403
|
+
image_tags = " ".join(f"[Image {i+1}]" for i in range(len(attached_images)))
|
|
404
|
+
parts.append(("class:prompt.image", f" {image_tags}\n"))
|
|
405
|
+
# Distinctive em-dash prompt with mode indicator
|
|
406
|
+
mode_class = "class:prompt.mode.plan" if current_mode == AgentMode.PLAN else "class:prompt.mode.code"
|
|
407
|
+
# Use em-dash as the signature prompt element
|
|
408
|
+
parts.append(("class:prompt.prefix", " "))
|
|
409
|
+
parts.append((mode_class, f"─── "))
|
|
410
|
+
parts.append(("class:prompt.cursor", "█ "))
|
|
300
411
|
return parts
|
|
301
412
|
|
|
302
413
|
def show_help():
|
|
303
|
-
"""Show available commands."""
|
|
414
|
+
"""Show available commands with zen styling."""
|
|
304
415
|
console.print()
|
|
305
|
-
console.print("[
|
|
416
|
+
console.print(f"[{Colors.MUTED}]{header('Commands', SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
306
417
|
console.print()
|
|
307
418
|
for cmd, desc in SLASH_COMMANDS.items():
|
|
308
|
-
console.print(f" [
|
|
419
|
+
console.print(f" [{Colors.PRIMARY}]{cmd:18}[/{Colors.PRIMARY}] [{Colors.DIM}]{desc}[/{Colors.DIM}]")
|
|
420
|
+
console.print()
|
|
421
|
+
console.print(f"[{Colors.MUTED}]{footer(SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
309
422
|
console.print()
|
|
310
|
-
console.print("[
|
|
423
|
+
console.print(f" [{Colors.DIM}]Type your task or question to interact with the agent.[/{Colors.DIM}]")
|
|
311
424
|
console.print()
|
|
312
425
|
|
|
313
426
|
def handle_slash_command(cmd: str) -> bool:
|
|
@@ -322,33 +435,38 @@ def run_interactive(
|
|
|
322
435
|
return False
|
|
323
436
|
|
|
324
437
|
elif command == "/help":
|
|
325
|
-
|
|
438
|
+
if args:
|
|
439
|
+
# Show contextual help for specific command
|
|
440
|
+
show_command_help(args)
|
|
441
|
+
else:
|
|
442
|
+
show_help()
|
|
326
443
|
|
|
327
444
|
elif command == "/plan":
|
|
328
445
|
current_mode = AgentMode.PLAN
|
|
329
446
|
# Reset session so next chat creates a new session with plan mode
|
|
330
447
|
if session_id:
|
|
331
448
|
session_id = None
|
|
332
|
-
console.print("[
|
|
449
|
+
console.print(f" [{Colors.SUCCESS}]{STATUS_ACTIVE}[/{Colors.SUCCESS}] [{Colors.WARNING}]plan mode[/{Colors.WARNING}] [{Colors.DIM}](session reset)[/{Colors.DIM}]")
|
|
333
450
|
else:
|
|
334
|
-
console.print("[
|
|
451
|
+
console.print(f" [{Colors.SUCCESS}]{STATUS_ACTIVE}[/{Colors.SUCCESS}] [{Colors.WARNING}]plan mode[/{Colors.WARNING}]")
|
|
335
452
|
|
|
336
453
|
elif command == "/code":
|
|
337
454
|
current_mode = AgentMode.CODE
|
|
338
455
|
# Reset session so next chat creates a new session with code mode
|
|
339
456
|
if session_id:
|
|
340
457
|
session_id = None
|
|
341
|
-
console.print("[
|
|
458
|
+
console.print(f" [{Colors.SUCCESS}]{STATUS_ACTIVE}[/{Colors.SUCCESS}] [{Colors.SUCCESS}]code mode[/{Colors.SUCCESS}] [{Colors.DIM}](session reset)[/{Colors.DIM}]")
|
|
342
459
|
else:
|
|
343
|
-
console.print("[
|
|
460
|
+
console.print(f" [{Colors.SUCCESS}]{STATUS_ACTIVE}[/{Colors.SUCCESS}] [{Colors.SUCCESS}]code mode[/{Colors.SUCCESS}]")
|
|
344
461
|
|
|
345
462
|
elif command == "/mode":
|
|
346
|
-
|
|
463
|
+
mode_color = Colors.WARNING if current_mode == AgentMode.PLAN else Colors.SUCCESS
|
|
464
|
+
console.print(f" [{Colors.MUTED}]current mode:[/{Colors.MUTED}] [{mode_color}]{current_mode.value}[/{mode_color}]")
|
|
347
465
|
|
|
348
466
|
elif command == "/reset":
|
|
349
467
|
session_id = None
|
|
350
468
|
current_spec = None
|
|
351
|
-
console.print("[
|
|
469
|
+
console.print(f" [{Colors.DIM}]session reset[/{Colors.DIM}]")
|
|
352
470
|
|
|
353
471
|
elif command == "/spec":
|
|
354
472
|
if current_spec:
|
|
@@ -368,6 +486,9 @@ def run_interactive(
|
|
|
368
486
|
elif command == "/status":
|
|
369
487
|
handle_status(client)
|
|
370
488
|
|
|
489
|
+
elif command == "/diff":
|
|
490
|
+
handle_diff(args)
|
|
491
|
+
|
|
371
492
|
elif command == "/agents":
|
|
372
493
|
handle_agents(args, client, renderer, model, max_iterations, render_with_interrupt)
|
|
373
494
|
|
|
@@ -404,12 +525,33 @@ def run_interactive(
|
|
|
404
525
|
elif command == "/skills":
|
|
405
526
|
handle_skills(args, client, renderer, model, max_iterations, render_with_interrupt)
|
|
406
527
|
|
|
528
|
+
elif command == "/index":
|
|
529
|
+
handle_index(args, client)
|
|
530
|
+
|
|
407
531
|
elif command == "/context":
|
|
408
532
|
handle_context(renderer)
|
|
409
533
|
|
|
534
|
+
elif command == "/paste" or command == "/image":
|
|
535
|
+
# Attach image from clipboard
|
|
536
|
+
from ...clipboard import get_clipboard_image
|
|
537
|
+
image_data = get_clipboard_image()
|
|
538
|
+
if image_data:
|
|
539
|
+
base64_data, img_format = image_data
|
|
540
|
+
attached_images.append({"data": base64_data, "format": img_format})
|
|
541
|
+
console.print(f" [{Colors.SUCCESS}]✓ Image {len(attached_images)} attached[/{Colors.SUCCESS}]")
|
|
542
|
+
else:
|
|
543
|
+
console.print(f" [{Colors.WARNING}]No image in clipboard[/{Colors.WARNING}]")
|
|
544
|
+
console.print(f" [{Colors.DIM}]Copy an image first (Cmd+Shift+4 for screenshot)[/{Colors.DIM}]")
|
|
545
|
+
|
|
546
|
+
elif command == "/compact":
|
|
547
|
+
handle_compact(client, session_id)
|
|
548
|
+
|
|
410
549
|
elif command == "/mcp":
|
|
411
550
|
handle_mcp(args)
|
|
412
551
|
|
|
552
|
+
elif command == "/registry":
|
|
553
|
+
handle_registry(args)
|
|
554
|
+
|
|
413
555
|
elif command == "/auth":
|
|
414
556
|
handle_auth(args)
|
|
415
557
|
|
|
@@ -448,12 +590,31 @@ def run_interactive(
|
|
|
448
590
|
handle_setup(args, client, renderer, model)
|
|
449
591
|
return True
|
|
450
592
|
|
|
593
|
+
elif command == "/telegram":
|
|
594
|
+
handle_telegram(args)
|
|
595
|
+
return True
|
|
596
|
+
|
|
451
597
|
else:
|
|
452
598
|
console.print(f"[yellow]Unknown command: {command}[/yellow]")
|
|
453
599
|
console.print("[dim]Type /help for available commands[/dim]")
|
|
454
600
|
|
|
455
601
|
return True
|
|
456
602
|
|
|
603
|
+
# Check for first run and show onboarding
|
|
604
|
+
if is_first_run():
|
|
605
|
+
run_onboarding()
|
|
606
|
+
|
|
607
|
+
# Check for recent session to restore
|
|
608
|
+
recent_session = get_recent_session(client)
|
|
609
|
+
if recent_session:
|
|
610
|
+
choice, session_data = show_session_restore_prompt(recent_session)
|
|
611
|
+
if choice == "restore" and session_data:
|
|
612
|
+
session_id = session_data.get("name")
|
|
613
|
+
if session_data.get("mode"):
|
|
614
|
+
current_mode = AgentMode(session_data["mode"])
|
|
615
|
+
console.print(f" [{Colors.SUCCESS}]{STATUS_ACTIVE}[/{Colors.SUCCESS}] Session restored: {session_id}")
|
|
616
|
+
console.print()
|
|
617
|
+
|
|
457
618
|
# Show welcome message
|
|
458
619
|
from ... import __version__
|
|
459
620
|
|
|
@@ -472,20 +633,44 @@ def run_interactive(
|
|
|
472
633
|
except Exception:
|
|
473
634
|
pass
|
|
474
635
|
|
|
475
|
-
#
|
|
476
|
-
|
|
477
|
-
|
|
636
|
+
# Get current git branch
|
|
637
|
+
git_branch = None
|
|
638
|
+
try:
|
|
639
|
+
result = subprocess.run(
|
|
640
|
+
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
|
641
|
+
capture_output=True,
|
|
642
|
+
text=True,
|
|
643
|
+
timeout=5,
|
|
644
|
+
)
|
|
645
|
+
if result.returncode == 0:
|
|
646
|
+
git_branch = result.stdout.strip()
|
|
647
|
+
except Exception:
|
|
648
|
+
pass
|
|
649
|
+
|
|
478
650
|
# Get display model name
|
|
479
651
|
if model:
|
|
480
652
|
display_model = model
|
|
481
653
|
else:
|
|
482
654
|
from emdash_core.agent.providers.factory import DEFAULT_MODEL
|
|
483
655
|
display_model = DEFAULT_MODEL
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
656
|
+
|
|
657
|
+
# Shorten model name for display
|
|
658
|
+
if "/" in display_model:
|
|
659
|
+
display_model = display_model.split("/")[-1]
|
|
660
|
+
|
|
661
|
+
# Update toolbar variables for the bottom bar
|
|
662
|
+
toolbar_branch = git_branch
|
|
663
|
+
toolbar_model = display_model
|
|
664
|
+
|
|
665
|
+
# Welcome banner
|
|
666
|
+
show_welcome_banner(
|
|
667
|
+
version=__version__,
|
|
668
|
+
git_repo=git_repo,
|
|
669
|
+
git_branch=git_branch,
|
|
670
|
+
mode=current_mode.value,
|
|
671
|
+
model=display_model,
|
|
672
|
+
console=console,
|
|
673
|
+
)
|
|
489
674
|
|
|
490
675
|
while True:
|
|
491
676
|
try:
|
|
@@ -606,7 +791,7 @@ def run_interactive(
|
|
|
606
791
|
if choice == "approve":
|
|
607
792
|
current_mode = AgentMode.PLAN
|
|
608
793
|
console.print()
|
|
609
|
-
console.print("[
|
|
794
|
+
console.print(f" [{Colors.SUCCESS}]{STATUS_ACTIVE}[/{Colors.SUCCESS}] [{Colors.WARNING}]plan mode activated[/{Colors.WARNING}]")
|
|
610
795
|
console.print()
|
|
611
796
|
# Use the planmode approve endpoint
|
|
612
797
|
stream = client.planmode_approve_stream(session_id)
|