emdash-cli 0.1.46__py3-none-any.whl → 0.1.70__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 +78 -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 +523 -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 +278 -47
- 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 +980 -0
- emdash_cli/integrations/telegram/config.py +155 -0
- emdash_cli/integrations/telegram/formatter.py +392 -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.70.dist-info}/METADATA +2 -2
- emdash_cli-0.1.70.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.70.dist-info}/WHEEL +0 -0
- {emdash_cli-0.1.46.dist-info → emdash_cli-0.1.70.dist-info}/entry_points.txt +0 -0
|
@@ -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
|
|
|
@@ -8,7 +10,48 @@ from rich.console import Console
|
|
|
8
10
|
from rich.panel import Panel
|
|
9
11
|
from rich.markdown import Markdown
|
|
10
12
|
|
|
11
|
-
from .constants import AgentMode, SLASH_COMMANDS
|
|
13
|
+
from .constants import AgentMode, SLASH_COMMANDS, SLASH_SUBCOMMANDS
|
|
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()
|
|
@@ -129,6 +177,7 @@ def run_interactive(
|
|
|
129
177
|
from prompt_toolkit.completion import Completer, Completion
|
|
130
178
|
from prompt_toolkit.styles import Style
|
|
131
179
|
from prompt_toolkit.key_binding import KeyBindings
|
|
180
|
+
from prompt_toolkit.formatted_text import FormattedText
|
|
132
181
|
|
|
133
182
|
# Current mode
|
|
134
183
|
current_mode = AgentMode(options.get("mode", "code"))
|
|
@@ -141,18 +190,36 @@ def run_interactive(
|
|
|
141
190
|
# Pending todos to add when session starts
|
|
142
191
|
pending_todos: list[str] = []
|
|
143
192
|
|
|
144
|
-
# Style for prompt
|
|
193
|
+
# Style for prompt (emdash signature style)
|
|
194
|
+
# Toolbar info (will be set later, but need closure access)
|
|
195
|
+
toolbar_branch: str | None = None
|
|
196
|
+
toolbar_model: str = "unknown"
|
|
197
|
+
|
|
145
198
|
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
|
-
"
|
|
199
|
+
"prompt.mode.plan": f"{Colors.WARNING} bold",
|
|
200
|
+
"prompt.mode.code": f"{Colors.PRIMARY} bold",
|
|
201
|
+
"prompt.prefix": Colors.MUTED,
|
|
202
|
+
"prompt.cursor": f"{Colors.PRIMARY}",
|
|
203
|
+
"prompt.image": Colors.ACCENT,
|
|
204
|
+
"completion-menu": "bg:#1a1a2e #e8ecf0",
|
|
205
|
+
"completion-menu.completion": "bg:#1a1a2e #e8ecf0",
|
|
206
|
+
"completion-menu.completion.current": f"bg:#2a2a3e {Colors.SUCCESS} bold",
|
|
207
|
+
"completion-menu.meta.completion": f"bg:#1a1a2e {Colors.MUTED}",
|
|
208
|
+
"completion-menu.meta.completion.current": f"bg:#2a2a3e {Colors.SUBTLE}",
|
|
209
|
+
"command": f"{Colors.PRIMARY} bold",
|
|
210
|
+
# Styled completion text
|
|
211
|
+
"slash-cmd": f"{Colors.ACCENT} bold",
|
|
212
|
+
"sub-cmd": f"{Colors.SUCCESS}",
|
|
213
|
+
"file-ref": f"{Colors.WARNING}",
|
|
214
|
+
# Zen bottom toolbar styles
|
|
215
|
+
"bottom-toolbar": f"bg:#1a1a1a {Colors.DIM}",
|
|
216
|
+
"bottom-toolbar.brand": f"bg:#1a1a1a {Colors.PRIMARY}",
|
|
217
|
+
"bottom-toolbar.branch": f"bg:#1a1a1a {Colors.WARNING}",
|
|
218
|
+
"bottom-toolbar.model": f"bg:#1a1a1a {Colors.ACCENT}",
|
|
219
|
+
"bottom-toolbar.mode-code": f"bg:#1a1a1a {Colors.SUCCESS}",
|
|
220
|
+
"bottom-toolbar.mode-plan": f"bg:#1a1a1a {Colors.WARNING}",
|
|
221
|
+
"bottom-toolbar.session": f"bg:#1a1a1a {Colors.SUCCESS}",
|
|
222
|
+
"bottom-toolbar.no-session": f"bg:#1a1a1a {Colors.MUTED}",
|
|
156
223
|
})
|
|
157
224
|
|
|
158
225
|
class SlashCommandCompleter(Completer):
|
|
@@ -176,11 +243,14 @@ def run_interactive(
|
|
|
176
243
|
rel_path = match.relative_to(cwd)
|
|
177
244
|
except ValueError:
|
|
178
245
|
rel_path = match
|
|
179
|
-
# Replace from @ onwards
|
|
246
|
+
# Replace from @ onwards - styled display
|
|
247
|
+
styled_display = FormattedText([
|
|
248
|
+
("class:file-ref", f"@{rel_path}")
|
|
249
|
+
])
|
|
180
250
|
yield Completion(
|
|
181
251
|
f"@{rel_path}",
|
|
182
252
|
start_position=-(len(query) + 1), # +1 for @
|
|
183
|
-
display=
|
|
253
|
+
display=styled_display,
|
|
184
254
|
display_meta="file",
|
|
185
255
|
)
|
|
186
256
|
return
|
|
@@ -188,14 +258,45 @@ def run_interactive(
|
|
|
188
258
|
# Handle slash commands
|
|
189
259
|
if not text.startswith("/"):
|
|
190
260
|
return
|
|
261
|
+
|
|
262
|
+
# Check if we're completing a subcommand (e.g., "/telegram " or "/telegram c")
|
|
263
|
+
parts = text.split(maxsplit=1)
|
|
264
|
+
base_cmd = parts[0]
|
|
265
|
+
|
|
266
|
+
# If we have a space after base command, show subcommands
|
|
267
|
+
if len(parts) > 1 or text.endswith(" "):
|
|
268
|
+
subcommand_prefix = parts[1] if len(parts) > 1 else ""
|
|
269
|
+
if base_cmd in SLASH_SUBCOMMANDS:
|
|
270
|
+
subcommands = SLASH_SUBCOMMANDS[base_cmd]
|
|
271
|
+
for subcmd, description in subcommands.items():
|
|
272
|
+
if subcmd.startswith(subcommand_prefix):
|
|
273
|
+
# Styled display for subcommand
|
|
274
|
+
styled_display = FormattedText([
|
|
275
|
+
("class:sub-cmd", subcmd)
|
|
276
|
+
])
|
|
277
|
+
yield Completion(
|
|
278
|
+
subcmd,
|
|
279
|
+
start_position=-len(subcommand_prefix),
|
|
280
|
+
display=styled_display,
|
|
281
|
+
display_meta=description,
|
|
282
|
+
)
|
|
283
|
+
return
|
|
284
|
+
|
|
285
|
+
# Otherwise show main slash commands
|
|
191
286
|
for cmd, description in SLASH_COMMANDS.items():
|
|
192
287
|
# Extract base command (e.g., "/pr" from "/pr [url]")
|
|
193
|
-
|
|
194
|
-
if
|
|
288
|
+
cmd_base = cmd.split()[0]
|
|
289
|
+
if cmd_base.startswith(text):
|
|
290
|
+
# Add space suffix for commands with subcommands to trigger subcommand completion
|
|
291
|
+
completion_text = cmd_base + " " if cmd_base in SLASH_SUBCOMMANDS else cmd_base
|
|
292
|
+
# Styled display for slash command
|
|
293
|
+
styled_display = FormattedText([
|
|
294
|
+
("class:slash-cmd", cmd)
|
|
295
|
+
])
|
|
195
296
|
yield Completion(
|
|
196
|
-
|
|
297
|
+
completion_text,
|
|
197
298
|
start_position=-len(text),
|
|
198
|
-
display=
|
|
299
|
+
display=styled_display,
|
|
199
300
|
display_meta=description,
|
|
200
301
|
)
|
|
201
302
|
|
|
@@ -207,10 +308,17 @@ def run_interactive(
|
|
|
207
308
|
# Key bindings: Enter submits, Alt+Enter inserts newline
|
|
208
309
|
kb = KeyBindings()
|
|
209
310
|
|
|
210
|
-
@kb.add("enter"
|
|
311
|
+
@kb.add("enter")
|
|
211
312
|
def submit_on_enter(event):
|
|
212
|
-
"""Submit on Enter."""
|
|
213
|
-
event.current_buffer
|
|
313
|
+
"""Submit on Enter, or select completion if menu is open."""
|
|
314
|
+
buffer = event.current_buffer
|
|
315
|
+
# If completion menu is open, accept the selected completion
|
|
316
|
+
if buffer.complete_state and buffer.complete_state.current_completion:
|
|
317
|
+
completion = buffer.complete_state.current_completion
|
|
318
|
+
buffer.apply_completion(completion)
|
|
319
|
+
return
|
|
320
|
+
# Otherwise submit the input
|
|
321
|
+
buffer.validate_and_handle()
|
|
214
322
|
|
|
215
323
|
@kb.add("escape", "enter") # Alt+Enter (Escape then Enter)
|
|
216
324
|
@kb.add("c-j") # Ctrl+J as alternative for newline
|
|
@@ -229,6 +337,8 @@ def run_interactive(
|
|
|
229
337
|
if image_data:
|
|
230
338
|
base64_data, img_format = image_data
|
|
231
339
|
attached_images.append({"data": base64_data, "format": img_format})
|
|
340
|
+
# Show feedback that image was attached
|
|
341
|
+
console.print(f" [{Colors.SUCCESS}]✓ Image {len(attached_images)} attached[/{Colors.SUCCESS}]")
|
|
232
342
|
# Refresh prompt to show updated image list
|
|
233
343
|
event.app.invalidate()
|
|
234
344
|
return
|
|
@@ -275,39 +385,88 @@ def run_interactive(
|
|
|
275
385
|
except Exception:
|
|
276
386
|
pass
|
|
277
387
|
|
|
388
|
+
def get_bottom_toolbar():
|
|
389
|
+
"""Bottom status bar with zen aesthetic - em-dashes and warm colors."""
|
|
390
|
+
nonlocal current_mode, session_id, toolbar_branch, toolbar_model
|
|
391
|
+
|
|
392
|
+
# Zen symbols
|
|
393
|
+
em = "─"
|
|
394
|
+
dot = "∷"
|
|
395
|
+
|
|
396
|
+
# Build toolbar with zen aesthetic
|
|
397
|
+
parts = [
|
|
398
|
+
("class:bottom-toolbar", f" {em}{em} "),
|
|
399
|
+
("class:bottom-toolbar.brand", "◈ emdash"),
|
|
400
|
+
]
|
|
401
|
+
|
|
402
|
+
# Branch with stippled bullet
|
|
403
|
+
if toolbar_branch:
|
|
404
|
+
parts.append(("class:bottom-toolbar", f" {dot} "))
|
|
405
|
+
parts.append(("class:bottom-toolbar.branch", toolbar_branch))
|
|
406
|
+
|
|
407
|
+
# Model with stippled bullet
|
|
408
|
+
if toolbar_model and toolbar_model != "unknown":
|
|
409
|
+
parts.append(("class:bottom-toolbar", f" {dot} "))
|
|
410
|
+
parts.append(("class:bottom-toolbar.model", toolbar_model))
|
|
411
|
+
|
|
412
|
+
# Mode indicator
|
|
413
|
+
parts.append(("class:bottom-toolbar", f" {em}{em} "))
|
|
414
|
+
if current_mode == AgentMode.PLAN:
|
|
415
|
+
parts.append(("class:bottom-toolbar.mode-plan", "▹ plan"))
|
|
416
|
+
else:
|
|
417
|
+
parts.append(("class:bottom-toolbar.mode-code", "▸ code"))
|
|
418
|
+
|
|
419
|
+
# Session indicator
|
|
420
|
+
if session_id:
|
|
421
|
+
parts.append(("class:bottom-toolbar.session", " ●"))
|
|
422
|
+
else:
|
|
423
|
+
parts.append(("class:bottom-toolbar.no-session", " ○"))
|
|
424
|
+
|
|
425
|
+
parts.append(("class:bottom-toolbar", " "))
|
|
426
|
+
|
|
427
|
+
return parts
|
|
428
|
+
|
|
278
429
|
session = PromptSession(
|
|
279
430
|
history=history,
|
|
280
431
|
completer=SlashCommandCompleter(),
|
|
281
432
|
style=PROMPT_STYLE,
|
|
282
433
|
complete_while_typing=True,
|
|
283
434
|
multiline=True,
|
|
284
|
-
prompt_continuation="
|
|
435
|
+
prompt_continuation=" ",
|
|
285
436
|
key_bindings=kb,
|
|
437
|
+
bottom_toolbar=get_bottom_toolbar,
|
|
286
438
|
)
|
|
287
439
|
|
|
288
440
|
# Watch for image paths being pasted/dropped
|
|
289
441
|
session.default_buffer.on_text_changed += check_for_image_path
|
|
290
442
|
|
|
291
443
|
def get_prompt():
|
|
292
|
-
"""Get formatted prompt."""
|
|
293
|
-
nonlocal attached_images
|
|
444
|
+
"""Get formatted prompt with distinctive emdash styling."""
|
|
445
|
+
nonlocal attached_images, current_mode
|
|
294
446
|
parts = []
|
|
295
447
|
# Show attached images above prompt
|
|
296
448
|
if attached_images:
|
|
297
|
-
image_tags = " ".join(f"[Image
|
|
298
|
-
parts.append(("class:prompt.image", f"
|
|
299
|
-
|
|
449
|
+
image_tags = " ".join(f"[Image {i+1}]" for i in range(len(attached_images)))
|
|
450
|
+
parts.append(("class:prompt.image", f" {image_tags}\n"))
|
|
451
|
+
# Distinctive em-dash prompt with mode indicator
|
|
452
|
+
mode_class = "class:prompt.mode.plan" if current_mode == AgentMode.PLAN else "class:prompt.mode.code"
|
|
453
|
+
# Use em-dash as the signature prompt element
|
|
454
|
+
parts.append(("class:prompt.prefix", " "))
|
|
455
|
+
parts.append((mode_class, f"─── "))
|
|
456
|
+
parts.append(("class:prompt.cursor", "█ "))
|
|
300
457
|
return parts
|
|
301
458
|
|
|
302
459
|
def show_help():
|
|
303
|
-
"""Show available commands."""
|
|
460
|
+
"""Show available commands with zen styling."""
|
|
304
461
|
console.print()
|
|
305
|
-
console.print("[
|
|
462
|
+
console.print(f"[{Colors.MUTED}]{header('Commands', SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
306
463
|
console.print()
|
|
307
464
|
for cmd, desc in SLASH_COMMANDS.items():
|
|
308
|
-
console.print(f" [
|
|
465
|
+
console.print(f" [{Colors.PRIMARY}]{cmd:18}[/{Colors.PRIMARY}] [{Colors.DIM}]{desc}[/{Colors.DIM}]")
|
|
466
|
+
console.print()
|
|
467
|
+
console.print(f"[{Colors.MUTED}]{footer(SEPARATOR_WIDTH)}[/{Colors.MUTED}]")
|
|
309
468
|
console.print()
|
|
310
|
-
console.print("[
|
|
469
|
+
console.print(f" [{Colors.DIM}]Type your task or question to interact with the agent.[/{Colors.DIM}]")
|
|
311
470
|
console.print()
|
|
312
471
|
|
|
313
472
|
def handle_slash_command(cmd: str) -> bool:
|
|
@@ -322,33 +481,38 @@ def run_interactive(
|
|
|
322
481
|
return False
|
|
323
482
|
|
|
324
483
|
elif command == "/help":
|
|
325
|
-
|
|
484
|
+
if args:
|
|
485
|
+
# Show contextual help for specific command
|
|
486
|
+
show_command_help(args)
|
|
487
|
+
else:
|
|
488
|
+
show_help()
|
|
326
489
|
|
|
327
490
|
elif command == "/plan":
|
|
328
491
|
current_mode = AgentMode.PLAN
|
|
329
492
|
# Reset session so next chat creates a new session with plan mode
|
|
330
493
|
if session_id:
|
|
331
494
|
session_id = None
|
|
332
|
-
console.print("[
|
|
495
|
+
console.print(f" [{Colors.SUCCESS}]{STATUS_ACTIVE}[/{Colors.SUCCESS}] [{Colors.WARNING}]plan mode[/{Colors.WARNING}] [{Colors.DIM}](session reset)[/{Colors.DIM}]")
|
|
333
496
|
else:
|
|
334
|
-
console.print("[
|
|
497
|
+
console.print(f" [{Colors.SUCCESS}]{STATUS_ACTIVE}[/{Colors.SUCCESS}] [{Colors.WARNING}]plan mode[/{Colors.WARNING}]")
|
|
335
498
|
|
|
336
499
|
elif command == "/code":
|
|
337
500
|
current_mode = AgentMode.CODE
|
|
338
501
|
# Reset session so next chat creates a new session with code mode
|
|
339
502
|
if session_id:
|
|
340
503
|
session_id = None
|
|
341
|
-
console.print("[
|
|
504
|
+
console.print(f" [{Colors.SUCCESS}]{STATUS_ACTIVE}[/{Colors.SUCCESS}] [{Colors.SUCCESS}]code mode[/{Colors.SUCCESS}] [{Colors.DIM}](session reset)[/{Colors.DIM}]")
|
|
342
505
|
else:
|
|
343
|
-
console.print("[
|
|
506
|
+
console.print(f" [{Colors.SUCCESS}]{STATUS_ACTIVE}[/{Colors.SUCCESS}] [{Colors.SUCCESS}]code mode[/{Colors.SUCCESS}]")
|
|
344
507
|
|
|
345
508
|
elif command == "/mode":
|
|
346
|
-
|
|
509
|
+
mode_color = Colors.WARNING if current_mode == AgentMode.PLAN else Colors.SUCCESS
|
|
510
|
+
console.print(f" [{Colors.MUTED}]current mode:[/{Colors.MUTED}] [{mode_color}]{current_mode.value}[/{mode_color}]")
|
|
347
511
|
|
|
348
512
|
elif command == "/reset":
|
|
349
513
|
session_id = None
|
|
350
514
|
current_spec = None
|
|
351
|
-
console.print("[
|
|
515
|
+
console.print(f" [{Colors.DIM}]session reset[/{Colors.DIM}]")
|
|
352
516
|
|
|
353
517
|
elif command == "/spec":
|
|
354
518
|
if current_spec:
|
|
@@ -368,6 +532,9 @@ def run_interactive(
|
|
|
368
532
|
elif command == "/status":
|
|
369
533
|
handle_status(client)
|
|
370
534
|
|
|
535
|
+
elif command == "/diff":
|
|
536
|
+
handle_diff(args)
|
|
537
|
+
|
|
371
538
|
elif command == "/agents":
|
|
372
539
|
handle_agents(args, client, renderer, model, max_iterations, render_with_interrupt)
|
|
373
540
|
|
|
@@ -404,12 +571,33 @@ def run_interactive(
|
|
|
404
571
|
elif command == "/skills":
|
|
405
572
|
handle_skills(args, client, renderer, model, max_iterations, render_with_interrupt)
|
|
406
573
|
|
|
574
|
+
elif command == "/index":
|
|
575
|
+
handle_index(args, client)
|
|
576
|
+
|
|
407
577
|
elif command == "/context":
|
|
408
578
|
handle_context(renderer)
|
|
409
579
|
|
|
580
|
+
elif command == "/paste" or command == "/image":
|
|
581
|
+
# Attach image from clipboard
|
|
582
|
+
from ...clipboard import get_clipboard_image
|
|
583
|
+
image_data = get_clipboard_image()
|
|
584
|
+
if image_data:
|
|
585
|
+
base64_data, img_format = image_data
|
|
586
|
+
attached_images.append({"data": base64_data, "format": img_format})
|
|
587
|
+
console.print(f" [{Colors.SUCCESS}]✓ Image {len(attached_images)} attached[/{Colors.SUCCESS}]")
|
|
588
|
+
else:
|
|
589
|
+
console.print(f" [{Colors.WARNING}]No image in clipboard[/{Colors.WARNING}]")
|
|
590
|
+
console.print(f" [{Colors.DIM}]Copy an image first (Cmd+Shift+4 for screenshot)[/{Colors.DIM}]")
|
|
591
|
+
|
|
592
|
+
elif command == "/compact":
|
|
593
|
+
handle_compact(client, session_id)
|
|
594
|
+
|
|
410
595
|
elif command == "/mcp":
|
|
411
596
|
handle_mcp(args)
|
|
412
597
|
|
|
598
|
+
elif command == "/registry":
|
|
599
|
+
handle_registry(args)
|
|
600
|
+
|
|
413
601
|
elif command == "/auth":
|
|
414
602
|
handle_auth(args)
|
|
415
603
|
|
|
@@ -448,12 +636,31 @@ def run_interactive(
|
|
|
448
636
|
handle_setup(args, client, renderer, model)
|
|
449
637
|
return True
|
|
450
638
|
|
|
639
|
+
elif command == "/telegram":
|
|
640
|
+
handle_telegram(args)
|
|
641
|
+
return True
|
|
642
|
+
|
|
451
643
|
else:
|
|
452
644
|
console.print(f"[yellow]Unknown command: {command}[/yellow]")
|
|
453
645
|
console.print("[dim]Type /help for available commands[/dim]")
|
|
454
646
|
|
|
455
647
|
return True
|
|
456
648
|
|
|
649
|
+
# Check for first run and show onboarding
|
|
650
|
+
if is_first_run():
|
|
651
|
+
run_onboarding()
|
|
652
|
+
|
|
653
|
+
# Check for recent session to restore
|
|
654
|
+
recent_session = get_recent_session(client)
|
|
655
|
+
if recent_session:
|
|
656
|
+
choice, session_data = show_session_restore_prompt(recent_session)
|
|
657
|
+
if choice == "restore" and session_data:
|
|
658
|
+
session_id = session_data.get("name")
|
|
659
|
+
if session_data.get("mode"):
|
|
660
|
+
current_mode = AgentMode(session_data["mode"])
|
|
661
|
+
console.print(f" [{Colors.SUCCESS}]{STATUS_ACTIVE}[/{Colors.SUCCESS}] Session restored: {session_id}")
|
|
662
|
+
console.print()
|
|
663
|
+
|
|
457
664
|
# Show welcome message
|
|
458
665
|
from ... import __version__
|
|
459
666
|
|
|
@@ -472,20 +679,44 @@ def run_interactive(
|
|
|
472
679
|
except Exception:
|
|
473
680
|
pass
|
|
474
681
|
|
|
475
|
-
#
|
|
476
|
-
|
|
477
|
-
|
|
682
|
+
# Get current git branch
|
|
683
|
+
git_branch = None
|
|
684
|
+
try:
|
|
685
|
+
result = subprocess.run(
|
|
686
|
+
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
|
687
|
+
capture_output=True,
|
|
688
|
+
text=True,
|
|
689
|
+
timeout=5,
|
|
690
|
+
)
|
|
691
|
+
if result.returncode == 0:
|
|
692
|
+
git_branch = result.stdout.strip()
|
|
693
|
+
except Exception:
|
|
694
|
+
pass
|
|
695
|
+
|
|
478
696
|
# Get display model name
|
|
479
697
|
if model:
|
|
480
698
|
display_model = model
|
|
481
699
|
else:
|
|
482
700
|
from emdash_core.agent.providers.factory import DEFAULT_MODEL
|
|
483
701
|
display_model = DEFAULT_MODEL
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
702
|
+
|
|
703
|
+
# Shorten model name for display
|
|
704
|
+
if "/" in display_model:
|
|
705
|
+
display_model = display_model.split("/")[-1]
|
|
706
|
+
|
|
707
|
+
# Update toolbar variables for the bottom bar
|
|
708
|
+
toolbar_branch = git_branch
|
|
709
|
+
toolbar_model = display_model
|
|
710
|
+
|
|
711
|
+
# Welcome banner
|
|
712
|
+
show_welcome_banner(
|
|
713
|
+
version=__version__,
|
|
714
|
+
git_repo=git_repo,
|
|
715
|
+
git_branch=git_branch,
|
|
716
|
+
mode=current_mode.value,
|
|
717
|
+
model=display_model,
|
|
718
|
+
console=console,
|
|
719
|
+
)
|
|
489
720
|
|
|
490
721
|
while True:
|
|
491
722
|
try:
|
|
@@ -606,7 +837,7 @@ def run_interactive(
|
|
|
606
837
|
if choice == "approve":
|
|
607
838
|
current_mode = AgentMode.PLAN
|
|
608
839
|
console.print()
|
|
609
|
-
console.print("[
|
|
840
|
+
console.print(f" [{Colors.SUCCESS}]{STATUS_ACTIVE}[/{Colors.SUCCESS}] [{Colors.WARNING}]plan mode activated[/{Colors.WARNING}]")
|
|
610
841
|
console.print()
|
|
611
842
|
# Use the planmode approve endpoint
|
|
612
843
|
stream = client.planmode_approve_stream(session_id)
|