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,438 @@
|
|
|
1
|
+
"""Diff rendering utilities for emdash CLI.
|
|
2
|
+
|
|
3
|
+
Provides GitHub-style diff display with line numbers and syntax highlighting.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
import subprocess
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.syntax import Syntax
|
|
13
|
+
from rich.text import Text
|
|
14
|
+
|
|
15
|
+
from .design import Colors, EM_DASH, STATUS_ACTIVE
|
|
16
|
+
|
|
17
|
+
# File extension to lexer mapping
|
|
18
|
+
EXTENSION_LEXERS = {
|
|
19
|
+
".py": "python",
|
|
20
|
+
".js": "javascript",
|
|
21
|
+
".jsx": "jsx",
|
|
22
|
+
".ts": "typescript",
|
|
23
|
+
".tsx": "tsx",
|
|
24
|
+
".json": "json",
|
|
25
|
+
".yaml": "yaml",
|
|
26
|
+
".yml": "yaml",
|
|
27
|
+
".md": "markdown",
|
|
28
|
+
".html": "html",
|
|
29
|
+
".css": "css",
|
|
30
|
+
".scss": "scss",
|
|
31
|
+
".sql": "sql",
|
|
32
|
+
".sh": "bash",
|
|
33
|
+
".bash": "bash",
|
|
34
|
+
".zsh": "zsh",
|
|
35
|
+
".rs": "rust",
|
|
36
|
+
".go": "go",
|
|
37
|
+
".java": "java",
|
|
38
|
+
".kt": "kotlin",
|
|
39
|
+
".swift": "swift",
|
|
40
|
+
".rb": "ruby",
|
|
41
|
+
".php": "php",
|
|
42
|
+
".c": "c",
|
|
43
|
+
".cpp": "cpp",
|
|
44
|
+
".h": "c",
|
|
45
|
+
".hpp": "cpp",
|
|
46
|
+
".toml": "toml",
|
|
47
|
+
".xml": "xml",
|
|
48
|
+
".dockerfile": "dockerfile",
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_lexer_for_file(filepath: str) -> str:
|
|
53
|
+
"""Get the syntax lexer for a file based on extension."""
|
|
54
|
+
ext = Path(filepath).suffix.lower()
|
|
55
|
+
|
|
56
|
+
# Special case for Dockerfile
|
|
57
|
+
if Path(filepath).name.lower() == "dockerfile":
|
|
58
|
+
return "dockerfile"
|
|
59
|
+
|
|
60
|
+
return EXTENSION_LEXERS.get(ext, "text")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def render_diff(
|
|
64
|
+
diff_output: str,
|
|
65
|
+
console: Optional[Console] = None,
|
|
66
|
+
compact: bool = False,
|
|
67
|
+
max_lines: int = 50,
|
|
68
|
+
) -> None:
|
|
69
|
+
"""Render git diff output with line numbers and syntax highlighting.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
diff_output: Raw git diff output
|
|
73
|
+
console: Rich console to render to
|
|
74
|
+
compact: If True, show compact view (fewer context lines)
|
|
75
|
+
max_lines: Maximum diff lines to show per file
|
|
76
|
+
"""
|
|
77
|
+
if console is None:
|
|
78
|
+
console = Console()
|
|
79
|
+
|
|
80
|
+
# Parse diff into files
|
|
81
|
+
files = _parse_diff(diff_output)
|
|
82
|
+
|
|
83
|
+
for filepath, hunks in files.items():
|
|
84
|
+
_render_file_diff(console, filepath, hunks, compact, max_lines)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def render_file_change(
|
|
88
|
+
console: Console,
|
|
89
|
+
filepath: str,
|
|
90
|
+
old_content: str = "",
|
|
91
|
+
new_content: str = "",
|
|
92
|
+
diff_lines: Optional[list] = None,
|
|
93
|
+
compact: bool = True,
|
|
94
|
+
) -> None:
|
|
95
|
+
"""Render a file change as inline diff (for agent edits).
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
console: Rich console to render to
|
|
99
|
+
filepath: Path to the file
|
|
100
|
+
old_content: Original file content
|
|
101
|
+
new_content: New file content
|
|
102
|
+
diff_lines: Pre-computed diff lines (optional)
|
|
103
|
+
compact: If True, show compact view
|
|
104
|
+
"""
|
|
105
|
+
# Try to get git diff if no diff_lines provided
|
|
106
|
+
if not diff_lines:
|
|
107
|
+
diff_lines = _get_git_diff_for_file(filepath)
|
|
108
|
+
|
|
109
|
+
# Count changes
|
|
110
|
+
additions = 0
|
|
111
|
+
deletions = 0
|
|
112
|
+
|
|
113
|
+
if diff_lines:
|
|
114
|
+
additions = sum(1 for l in diff_lines if l.startswith("+") and not l.startswith("+++"))
|
|
115
|
+
deletions = sum(1 for l in diff_lines if l.startswith("-") and not l.startswith("---"))
|
|
116
|
+
elif old_content and new_content:
|
|
117
|
+
old_lines = set(old_content.split("\n"))
|
|
118
|
+
new_lines = set(new_content.split("\n"))
|
|
119
|
+
additions = len(new_lines - old_lines)
|
|
120
|
+
deletions = len(old_lines - new_lines)
|
|
121
|
+
|
|
122
|
+
# Shorten path for display
|
|
123
|
+
display_path = filepath
|
|
124
|
+
if len(display_path) > 50:
|
|
125
|
+
display_path = "..." + display_path[-47:]
|
|
126
|
+
|
|
127
|
+
# Header line
|
|
128
|
+
header = Text()
|
|
129
|
+
header.append(f"{STATUS_ACTIVE} ", style=Colors.WARNING)
|
|
130
|
+
header.append("Update", style=f"{Colors.TEXT} bold")
|
|
131
|
+
header.append(f"({display_path})", style=Colors.MUTED)
|
|
132
|
+
console.print(header)
|
|
133
|
+
|
|
134
|
+
# Summary line
|
|
135
|
+
if additions or deletions:
|
|
136
|
+
summary = Text()
|
|
137
|
+
summary.append(" └ ", style=Colors.DIM)
|
|
138
|
+
if additions and deletions:
|
|
139
|
+
summary.append(f"Changed ", style=Colors.DIM)
|
|
140
|
+
summary.append(f"{additions + deletions}", style=Colors.TEXT)
|
|
141
|
+
summary.append(" lines", style=Colors.DIM)
|
|
142
|
+
elif additions:
|
|
143
|
+
summary.append("Added ", style=Colors.DIM)
|
|
144
|
+
summary.append(f"{additions}", style=Colors.SUCCESS)
|
|
145
|
+
summary.append(" lines", style=Colors.DIM)
|
|
146
|
+
else:
|
|
147
|
+
summary.append("Removed ", style=Colors.DIM)
|
|
148
|
+
summary.append(f"{deletions}", style=Colors.ERROR)
|
|
149
|
+
summary.append(" lines", style=Colors.DIM)
|
|
150
|
+
console.print(summary)
|
|
151
|
+
else:
|
|
152
|
+
# No diff available - just show modified
|
|
153
|
+
summary = Text()
|
|
154
|
+
summary.append(" └ ", style=Colors.DIM)
|
|
155
|
+
summary.append("modified", style=Colors.MUTED)
|
|
156
|
+
console.print(summary)
|
|
157
|
+
|
|
158
|
+
# Render diff lines with line numbers
|
|
159
|
+
if diff_lines:
|
|
160
|
+
_render_diff_lines_compact(console, filepath, diff_lines)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _get_git_diff_for_file(filepath: str) -> list:
|
|
164
|
+
"""Get git diff for a specific file.
|
|
165
|
+
|
|
166
|
+
Returns list of diff lines, or empty list if not available.
|
|
167
|
+
"""
|
|
168
|
+
try:
|
|
169
|
+
# Try unstaged changes first
|
|
170
|
+
result = subprocess.run(
|
|
171
|
+
["git", "diff", "--no-color", "--", filepath],
|
|
172
|
+
capture_output=True,
|
|
173
|
+
text=True,
|
|
174
|
+
timeout=5,
|
|
175
|
+
)
|
|
176
|
+
diff_output = result.stdout
|
|
177
|
+
|
|
178
|
+
# If no unstaged, try staged
|
|
179
|
+
if not diff_output:
|
|
180
|
+
result = subprocess.run(
|
|
181
|
+
["git", "diff", "--staged", "--no-color", "--", filepath],
|
|
182
|
+
capture_output=True,
|
|
183
|
+
text=True,
|
|
184
|
+
timeout=5,
|
|
185
|
+
)
|
|
186
|
+
diff_output = result.stdout
|
|
187
|
+
|
|
188
|
+
if diff_output:
|
|
189
|
+
# Parse and return just the diff lines (skip headers)
|
|
190
|
+
lines = []
|
|
191
|
+
in_hunk = False
|
|
192
|
+
for line in diff_output.split("\n"):
|
|
193
|
+
if line.startswith("@@"):
|
|
194
|
+
in_hunk = True
|
|
195
|
+
lines.append(line)
|
|
196
|
+
elif in_hunk:
|
|
197
|
+
lines.append(line)
|
|
198
|
+
return lines
|
|
199
|
+
|
|
200
|
+
except Exception:
|
|
201
|
+
pass
|
|
202
|
+
|
|
203
|
+
return []
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _parse_diff(diff_output: str) -> dict:
|
|
207
|
+
"""Parse git diff output into structured format.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Dict mapping filepath to list of hunks, where each hunk is
|
|
211
|
+
(old_start, old_count, new_start, new_count, lines)
|
|
212
|
+
"""
|
|
213
|
+
files = {}
|
|
214
|
+
current_file = None
|
|
215
|
+
current_hunks = []
|
|
216
|
+
current_hunk_lines = []
|
|
217
|
+
hunk_info = None
|
|
218
|
+
|
|
219
|
+
for line in diff_output.split("\n"):
|
|
220
|
+
if line.startswith("diff --git"):
|
|
221
|
+
# Save previous file
|
|
222
|
+
if current_file and current_hunks:
|
|
223
|
+
if current_hunk_lines and hunk_info:
|
|
224
|
+
current_hunks.append((hunk_info, current_hunk_lines))
|
|
225
|
+
files[current_file] = current_hunks
|
|
226
|
+
|
|
227
|
+
# Extract filename
|
|
228
|
+
parts = line.split(" b/")
|
|
229
|
+
current_file = parts[-1] if len(parts) > 1 else "unknown"
|
|
230
|
+
current_hunks = []
|
|
231
|
+
current_hunk_lines = []
|
|
232
|
+
hunk_info = None
|
|
233
|
+
|
|
234
|
+
elif line.startswith("@@"):
|
|
235
|
+
# New hunk - save previous
|
|
236
|
+
if current_hunk_lines and hunk_info:
|
|
237
|
+
current_hunks.append((hunk_info, current_hunk_lines))
|
|
238
|
+
|
|
239
|
+
# Parse hunk header: @@ -old_start,old_count +new_start,new_count @@
|
|
240
|
+
match = re.match(r"@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@", line)
|
|
241
|
+
if match:
|
|
242
|
+
old_start = int(match.group(1))
|
|
243
|
+
old_count = int(match.group(2)) if match.group(2) else 1
|
|
244
|
+
new_start = int(match.group(3))
|
|
245
|
+
new_count = int(match.group(4)) if match.group(4) else 1
|
|
246
|
+
hunk_info = (old_start, old_count, new_start, new_count)
|
|
247
|
+
else:
|
|
248
|
+
hunk_info = (1, 0, 1, 0)
|
|
249
|
+
|
|
250
|
+
current_hunk_lines = []
|
|
251
|
+
|
|
252
|
+
elif current_file and hunk_info is not None:
|
|
253
|
+
if not line.startswith("---") and not line.startswith("+++"):
|
|
254
|
+
current_hunk_lines.append(line)
|
|
255
|
+
|
|
256
|
+
# Don't forget last file/hunk
|
|
257
|
+
if current_file:
|
|
258
|
+
if current_hunk_lines and hunk_info:
|
|
259
|
+
current_hunks.append((hunk_info, current_hunk_lines))
|
|
260
|
+
if current_hunks:
|
|
261
|
+
files[current_file] = current_hunks
|
|
262
|
+
|
|
263
|
+
return files
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _render_file_diff(
|
|
267
|
+
console: Console,
|
|
268
|
+
filepath: str,
|
|
269
|
+
hunks: list,
|
|
270
|
+
compact: bool,
|
|
271
|
+
max_lines: int,
|
|
272
|
+
) -> None:
|
|
273
|
+
"""Render diff for a single file with line numbers."""
|
|
274
|
+
# Count changes
|
|
275
|
+
additions = 0
|
|
276
|
+
deletions = 0
|
|
277
|
+
for _, lines in hunks:
|
|
278
|
+
for line in lines:
|
|
279
|
+
if line.startswith("+"):
|
|
280
|
+
additions += 1
|
|
281
|
+
elif line.startswith("-"):
|
|
282
|
+
deletions += 1
|
|
283
|
+
|
|
284
|
+
# File header
|
|
285
|
+
header = Text()
|
|
286
|
+
header.append(f"{EM_DASH * 3} ", style=Colors.DIM)
|
|
287
|
+
header.append(filepath, style=f"{Colors.PRIMARY} bold")
|
|
288
|
+
header.append(f" {EM_DASH * max(1, 60 - len(filepath))}", style=Colors.DIM)
|
|
289
|
+
console.print()
|
|
290
|
+
console.print(header)
|
|
291
|
+
|
|
292
|
+
# Stats line
|
|
293
|
+
stats = Text()
|
|
294
|
+
if additions:
|
|
295
|
+
stats.append(f"+{additions}", style=Colors.SUCCESS)
|
|
296
|
+
if additions and deletions:
|
|
297
|
+
stats.append(" ")
|
|
298
|
+
if deletions:
|
|
299
|
+
stats.append(f"-{deletions}", style=Colors.ERROR)
|
|
300
|
+
if additions or deletions:
|
|
301
|
+
console.print(stats)
|
|
302
|
+
|
|
303
|
+
# Get lexer for syntax highlighting
|
|
304
|
+
lexer = get_lexer_for_file(filepath)
|
|
305
|
+
|
|
306
|
+
# Render hunks
|
|
307
|
+
total_lines = 0
|
|
308
|
+
for hunk_info, lines in hunks:
|
|
309
|
+
if total_lines >= max_lines:
|
|
310
|
+
remaining = sum(len(h[1]) for h in hunks) - total_lines
|
|
311
|
+
if remaining > 0:
|
|
312
|
+
console.print(f" [{Colors.DIM}]... {remaining} more lines[/{Colors.DIM}]")
|
|
313
|
+
break
|
|
314
|
+
|
|
315
|
+
old_line, _, new_line, _ = hunk_info
|
|
316
|
+
|
|
317
|
+
for line in lines:
|
|
318
|
+
if total_lines >= max_lines:
|
|
319
|
+
break
|
|
320
|
+
|
|
321
|
+
if line.startswith("+"):
|
|
322
|
+
# Addition
|
|
323
|
+
line_text = Text()
|
|
324
|
+
line_text.append(f"{new_line:4} ", style=Colors.DIM)
|
|
325
|
+
line_text.append("+ ", style=f"{Colors.SUCCESS} bold")
|
|
326
|
+
_append_highlighted(line_text, line[1:], lexer, Colors.SUCCESS)
|
|
327
|
+
console.print(line_text)
|
|
328
|
+
new_line += 1
|
|
329
|
+
total_lines += 1
|
|
330
|
+
|
|
331
|
+
elif line.startswith("-"):
|
|
332
|
+
# Deletion
|
|
333
|
+
line_text = Text()
|
|
334
|
+
line_text.append(f"{old_line:4} ", style=Colors.DIM)
|
|
335
|
+
line_text.append("- ", style=f"{Colors.ERROR} bold")
|
|
336
|
+
_append_highlighted(line_text, line[1:], lexer, Colors.ERROR)
|
|
337
|
+
console.print(line_text)
|
|
338
|
+
old_line += 1
|
|
339
|
+
total_lines += 1
|
|
340
|
+
|
|
341
|
+
else:
|
|
342
|
+
# Context line
|
|
343
|
+
if not compact or total_lines < 3:
|
|
344
|
+
line_text = Text()
|
|
345
|
+
line_text.append(f"{new_line:4} ", style=Colors.DIM)
|
|
346
|
+
line_text.append(" ", style=Colors.DIM)
|
|
347
|
+
line_text.append(line[1:] if line.startswith(" ") else line, style=Colors.DIM)
|
|
348
|
+
console.print(line_text)
|
|
349
|
+
total_lines += 1
|
|
350
|
+
|
|
351
|
+
old_line += 1
|
|
352
|
+
new_line += 1
|
|
353
|
+
|
|
354
|
+
console.print()
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def _render_diff_lines_compact(
|
|
358
|
+
console: Console,
|
|
359
|
+
filepath: str,
|
|
360
|
+
diff_lines: list,
|
|
361
|
+
max_lines: int = 8,
|
|
362
|
+
) -> None:
|
|
363
|
+
"""Render diff lines in compact format for inline display."""
|
|
364
|
+
lexer = get_lexer_for_file(filepath)
|
|
365
|
+
|
|
366
|
+
shown = 0
|
|
367
|
+
line_num = 1 # Approximate line number
|
|
368
|
+
|
|
369
|
+
for line in diff_lines:
|
|
370
|
+
if shown >= max_lines:
|
|
371
|
+
remaining = len(diff_lines) - shown
|
|
372
|
+
if remaining > 0:
|
|
373
|
+
console.print(f" [{Colors.DIM}]... {remaining} more lines[/{Colors.DIM}]")
|
|
374
|
+
break
|
|
375
|
+
|
|
376
|
+
if line.startswith("+") and not line.startswith("+++"):
|
|
377
|
+
line_text = Text()
|
|
378
|
+
line_text.append(f" {line_num:4} ", style=Colors.DIM)
|
|
379
|
+
line_text.append("+ ", style=f"{Colors.SUCCESS} bold")
|
|
380
|
+
_append_highlighted(line_text, line[1:], lexer, Colors.SUCCESS)
|
|
381
|
+
console.print(line_text)
|
|
382
|
+
shown += 1
|
|
383
|
+
line_num += 1
|
|
384
|
+
|
|
385
|
+
elif line.startswith("-") and not line.startswith("---"):
|
|
386
|
+
line_text = Text()
|
|
387
|
+
line_text.append(f" {line_num:4} ", style=Colors.DIM)
|
|
388
|
+
line_text.append("- ", style=f"{Colors.ERROR} bold")
|
|
389
|
+
_append_highlighted(line_text, line[1:], lexer, Colors.ERROR)
|
|
390
|
+
console.print(line_text)
|
|
391
|
+
shown += 1
|
|
392
|
+
# Don't increment line_num for deletions
|
|
393
|
+
|
|
394
|
+
elif not line.startswith("@@") and not line.startswith("---") and not line.startswith("+++"):
|
|
395
|
+
# Context line - show sparingly
|
|
396
|
+
if shown < 2:
|
|
397
|
+
line_text = Text()
|
|
398
|
+
line_text.append(f" {line_num:4} ", style=Colors.DIM)
|
|
399
|
+
line_text.append(" ", style=Colors.DIM)
|
|
400
|
+
content = line[1:] if line.startswith(" ") else line
|
|
401
|
+
line_text.append(content, style=Colors.DIM)
|
|
402
|
+
console.print(line_text)
|
|
403
|
+
shown += 1
|
|
404
|
+
line_num += 1
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def _append_highlighted(text: Text, content: str, lexer: str, base_style: str) -> None:
|
|
408
|
+
"""Append syntax-highlighted content to a Text object.
|
|
409
|
+
|
|
410
|
+
For simplicity, we apply the base style and let keywords stand out.
|
|
411
|
+
Full syntax highlighting would require more complex handling.
|
|
412
|
+
"""
|
|
413
|
+
# Simple keyword highlighting for common languages
|
|
414
|
+
if lexer in ("python", "javascript", "typescript", "java", "go", "rust"):
|
|
415
|
+
keywords = {
|
|
416
|
+
"python": ["def", "class", "import", "from", "if", "else", "elif", "try", "except", "finally", "for", "while", "return", "yield", "with", "as", "None", "True", "False", "and", "or", "not", "in", "is", "lambda", "async", "await"],
|
|
417
|
+
"javascript": ["function", "const", "let", "var", "if", "else", "for", "while", "return", "import", "export", "from", "class", "new", "this", "async", "await", "try", "catch", "finally", "null", "undefined", "true", "false"],
|
|
418
|
+
"typescript": ["function", "const", "let", "var", "if", "else", "for", "while", "return", "import", "export", "from", "class", "new", "this", "async", "await", "try", "catch", "finally", "null", "undefined", "true", "false", "interface", "type", "enum"],
|
|
419
|
+
"java": ["public", "private", "protected", "class", "interface", "extends", "implements", "if", "else", "for", "while", "return", "new", "this", "try", "catch", "finally", "null", "true", "false", "void", "static", "final"],
|
|
420
|
+
"go": ["func", "package", "import", "if", "else", "for", "return", "var", "const", "type", "struct", "interface", "nil", "true", "false", "go", "defer", "chan", "select", "case"],
|
|
421
|
+
"rust": ["fn", "let", "mut", "if", "else", "for", "while", "return", "use", "mod", "pub", "struct", "impl", "trait", "enum", "match", "Some", "None", "Ok", "Err", "self", "Self", "async", "await"],
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
kw_list = keywords.get(lexer, [])
|
|
425
|
+
if kw_list:
|
|
426
|
+
# Simple word-based highlighting
|
|
427
|
+
words = re.split(r'(\s+|\W)', content)
|
|
428
|
+
for word in words:
|
|
429
|
+
if word in kw_list:
|
|
430
|
+
text.append(word, style=f"{Colors.WARNING} bold")
|
|
431
|
+
elif word.startswith('"') or word.startswith("'") or word.startswith('`'):
|
|
432
|
+
text.append(word, style=Colors.SUCCESS)
|
|
433
|
+
else:
|
|
434
|
+
text.append(word, style=base_style)
|
|
435
|
+
return
|
|
436
|
+
|
|
437
|
+
# Default: just apply base style
|
|
438
|
+
text.append(content, style=base_style)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""EmDash CLI integrations."""
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Telegram integration for EmDash CLI.
|
|
2
|
+
|
|
3
|
+
This module provides integration with Telegram Bot API to receive
|
|
4
|
+
and respond to messages via Telegram while running the local EmDash agent.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .config import TelegramConfig, get_config, save_config
|
|
8
|
+
from .bot import TelegramBot
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"TelegramConfig",
|
|
12
|
+
"TelegramBot",
|
|
13
|
+
"get_config",
|
|
14
|
+
"save_config",
|
|
15
|
+
]
|