sudosu 0.1.5__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.
- sudosu/__init__.py +3 -0
- sudosu/cli.py +561 -0
- sudosu/commands/__init__.py +15 -0
- sudosu/commands/agent.py +318 -0
- sudosu/commands/config.py +96 -0
- sudosu/commands/init.py +73 -0
- sudosu/commands/integrations.py +563 -0
- sudosu/commands/memory.py +170 -0
- sudosu/commands/onboarding.py +319 -0
- sudosu/commands/tasks.py +635 -0
- sudosu/core/__init__.py +238 -0
- sudosu/core/agent_loader.py +263 -0
- sudosu/core/connection.py +196 -0
- sudosu/core/default_agent.py +541 -0
- sudosu/core/prompt_refiner.py +0 -0
- sudosu/core/safety.py +75 -0
- sudosu/core/session.py +205 -0
- sudosu/tools/__init__.py +373 -0
- sudosu/ui/__init__.py +451 -0
- sudosu-0.1.5.dist-info/METADATA +172 -0
- sudosu-0.1.5.dist-info/RECORD +25 -0
- sudosu-0.1.5.dist-info/WHEEL +5 -0
- sudosu-0.1.5.dist-info/entry_points.txt +2 -0
- sudosu-0.1.5.dist-info/licenses/LICENSE +21 -0
- sudosu-0.1.5.dist-info/top_level.txt +1 -0
sudosu/ui/__init__.py
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
"""Console UI helpers for Sudosu."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from prompt_toolkit import PromptSession
|
|
6
|
+
from prompt_toolkit.formatted_text import HTML
|
|
7
|
+
from prompt_toolkit.history import FileHistory
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.live import Live
|
|
10
|
+
from rich.markdown import Markdown
|
|
11
|
+
from rich.markup import escape
|
|
12
|
+
from rich.panel import Panel
|
|
13
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
14
|
+
from rich.syntax import Syntax
|
|
15
|
+
from rich.table import Table
|
|
16
|
+
from rich.text import Text
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
console = Console()
|
|
20
|
+
|
|
21
|
+
# ══════════════════════════════════════════════════════════════════════════════
|
|
22
|
+
# COMMAND HISTORY - Enables up/down arrow navigation for previous commands
|
|
23
|
+
# ══════════════════════════════════════════════════════════════════════════════
|
|
24
|
+
|
|
25
|
+
def _get_history_file() -> Path:
|
|
26
|
+
"""Get the path to the command history file.
|
|
27
|
+
|
|
28
|
+
Uses XDG-compliant location: ~/.local/share/sudosu/command_history
|
|
29
|
+
This keeps app data separate from project-level .sudosu/ configs.
|
|
30
|
+
"""
|
|
31
|
+
# Use XDG-compliant location for app data
|
|
32
|
+
history_dir = Path.home() / ".local" / "share" / "sudosu"
|
|
33
|
+
history_dir.mkdir(parents=True, exist_ok=True)
|
|
34
|
+
return history_dir / "command_history"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# Global prompt session with persistent file history
|
|
38
|
+
_prompt_session: PromptSession | None = None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _get_prompt_session() -> PromptSession:
|
|
42
|
+
"""Get or create the global prompt session with history."""
|
|
43
|
+
global _prompt_session
|
|
44
|
+
if _prompt_session is None:
|
|
45
|
+
history_file = _get_history_file()
|
|
46
|
+
_prompt_session = PromptSession(history=FileHistory(str(history_file)))
|
|
47
|
+
return _prompt_session
|
|
48
|
+
|
|
49
|
+
# ══════════════════════════════════════════════════════════════════════════════
|
|
50
|
+
# SUDOSU BRAND COLORS
|
|
51
|
+
# ══════════════════════════════════════════════════════════════════════════════
|
|
52
|
+
COLOR_PRIMARY = "#FEEAC9" # Warm cream/peach - Logo, main headings
|
|
53
|
+
COLOR_SECONDARY = "#FFCDC9" # Light coral/pink - Section headings
|
|
54
|
+
COLOR_ACCENT = "#FD7979" # Coral red - Warnings, important highlights
|
|
55
|
+
COLOR_INTERACTIVE = "#BDE3C3" # Light blue - Commands, agent names, interactive elements
|
|
56
|
+
|
|
57
|
+
# Sudosu ASCII Art Logo
|
|
58
|
+
SUDOSU_LOGO = """
|
|
59
|
+
⣀⣀⣀⣀⣀⣀⣀⣀⣀
|
|
60
|
+
⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦
|
|
61
|
+
⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷
|
|
62
|
+
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
|
63
|
+
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
|
64
|
+
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
|
65
|
+
⣿⣿⣿⣿⡿⠋⠁⠈⠙⢿⡿⠋⠁⠈⠻⣿⣿⣿⣿
|
|
66
|
+
⣿⣿⣿⣿⡇⠀⣿⣿⠀⢸⡇⠀⣿⣿⠀⢸⣿⣿⣿
|
|
67
|
+
⣿⣿⣿⣿⣷⣄⣀⣀⣠⣾⣷⣄⣀⣀⣠⣾⣿⣿⣿
|
|
68
|
+
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
|
69
|
+
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
|
70
|
+
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
|
|
71
|
+
⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟
|
|
72
|
+
⠙⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠋
|
|
73
|
+
⠉⠉⠉⠉⠉⠉⠉⠉⠉
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
# Simpler fallback ASCII logo using basic characters
|
|
77
|
+
# Matches the Sudosu logo: filled body with hollow circular eyes and connected ears
|
|
78
|
+
SUDOSU_LOGO_SIMPLE = """
|
|
79
|
+
▄▄ ▄▄
|
|
80
|
+
██▀▀▀▀▀▀▀▀██
|
|
81
|
+
████ ██ ████
|
|
82
|
+
██████████████
|
|
83
|
+
▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_version() -> str:
|
|
88
|
+
"""Get Sudosu version."""
|
|
89
|
+
try:
|
|
90
|
+
from importlib.metadata import version
|
|
91
|
+
return version("sudosu")
|
|
92
|
+
except Exception:
|
|
93
|
+
return "0.1.0"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def print_welcome(username: str = "User"):
|
|
97
|
+
"""Print welcome message with ASCII art logo - Claude Code style."""
|
|
98
|
+
console.print()
|
|
99
|
+
|
|
100
|
+
# Create the welcome box similar to Claude Code
|
|
101
|
+
version = get_version()
|
|
102
|
+
|
|
103
|
+
# Create a table for the layout (left: welcome + logo, right: tips)
|
|
104
|
+
layout_table = Table.grid(padding=(0, 4))
|
|
105
|
+
layout_table.add_column(justify="center", width=35) # Left column for welcome + logo
|
|
106
|
+
layout_table.add_column(justify="left") # Right column for tips
|
|
107
|
+
|
|
108
|
+
# Build left side content
|
|
109
|
+
left_content = Text()
|
|
110
|
+
left_content.append(f"Welcome back {username}!\n\n", style="bold white")
|
|
111
|
+
left_content.append(SUDOSU_LOGO_SIMPLE, style=f"bold {COLOR_PRIMARY}") # Primary color for logo
|
|
112
|
+
left_content.append(f"\nv{version}", style="dim")
|
|
113
|
+
|
|
114
|
+
# Build right side content (tips and recent activity)
|
|
115
|
+
right_content = Text()
|
|
116
|
+
right_content.append("Tips for getting started\n", style=f"bold {COLOR_SECONDARY}") # Secondary for headings
|
|
117
|
+
right_content.append("Type a message to chat with your AI agent\n", style="white")
|
|
118
|
+
right_content.append("Use ", style="white")
|
|
119
|
+
right_content.append("@agent_name", style=f"{COLOR_INTERACTIVE}") # Interactive color for commands
|
|
120
|
+
right_content.append(" to switch agents\n", style="white")
|
|
121
|
+
right_content.append("Type ", style="white")
|
|
122
|
+
right_content.append("/help", style=f"{COLOR_INTERACTIVE}")
|
|
123
|
+
right_content.append(" for all commands\n\n", style="white")
|
|
124
|
+
right_content.append("Recent activity\n", style=f"bold {COLOR_SECONDARY}")
|
|
125
|
+
right_content.append("No recent activity", style="dim")
|
|
126
|
+
|
|
127
|
+
layout_table.add_row(left_content, right_content)
|
|
128
|
+
|
|
129
|
+
# Wrap in a panel with the title
|
|
130
|
+
panel = Panel(
|
|
131
|
+
layout_table,
|
|
132
|
+
title=f"[bold {COLOR_PRIMARY}]Sudosu v{version}[/bold {COLOR_PRIMARY}]",
|
|
133
|
+
title_align="left",
|
|
134
|
+
border_style=COLOR_PRIMARY,
|
|
135
|
+
padding=(1, 2),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
console.print(panel)
|
|
139
|
+
console.print()
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def print_help():
|
|
143
|
+
"""Print help message."""
|
|
144
|
+
table = Table(title="Sudosu Commands", border_style=COLOR_PRIMARY, title_style=f"bold {COLOR_PRIMARY}")
|
|
145
|
+
table.add_column("Command", style=COLOR_INTERACTIVE)
|
|
146
|
+
table.add_column("Description")
|
|
147
|
+
|
|
148
|
+
commands = [
|
|
149
|
+
("/help", "Show this help message"),
|
|
150
|
+
("/agent", "List available agents"),
|
|
151
|
+
("/agent create <name>", "Create a new agent"),
|
|
152
|
+
("/agent delete <name>", "Delete an agent"),
|
|
153
|
+
("/memory", "Show conversation memory info"),
|
|
154
|
+
("/memory clear", "Clear conversation (fresh start)"),
|
|
155
|
+
("/back", "Return to sudosu from an agent"),
|
|
156
|
+
("/profile", "View your profile"),
|
|
157
|
+
("/profile edit", "Update your profile"),
|
|
158
|
+
("/config", "Show current configuration"),
|
|
159
|
+
("/config set <key> <value>", "Set a configuration value"),
|
|
160
|
+
("/clear", "Clear the screen"),
|
|
161
|
+
("/quit", "Exit Sudosu"),
|
|
162
|
+
("", ""),
|
|
163
|
+
("-- Background Tasks --", ""),
|
|
164
|
+
("/tasks", "List your background tasks"),
|
|
165
|
+
("/tasks status <id>", "Get task status"),
|
|
166
|
+
("/tasks logs <id>", "View task execution logs"),
|
|
167
|
+
("/tasks cancel <id>", "Cancel a running task"),
|
|
168
|
+
("/tasks watch <id>", "Watch task progress live"),
|
|
169
|
+
("", ""),
|
|
170
|
+
("-- Integrations --", ""),
|
|
171
|
+
("/connect gmail", "Connect your Gmail account"),
|
|
172
|
+
("/disconnect gmail", "Disconnect Gmail"),
|
|
173
|
+
("/integrations", "Show connected integrations"),
|
|
174
|
+
("", ""),
|
|
175
|
+
("@<agent> <message>", "Switch to and message an agent"),
|
|
176
|
+
("<message>", "Continue with current agent"),
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
for cmd, desc in commands:
|
|
180
|
+
table.add_row(cmd, desc)
|
|
181
|
+
|
|
182
|
+
console.print(table)
|
|
183
|
+
console.print(f"\n[dim]Tip: After sudosu routes you to an agent,\n your follow-ups go to that agent automatically.[/dim]")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def print_agents(agents: list[dict]):
|
|
187
|
+
"""Print list of available agents in the current project."""
|
|
188
|
+
if not agents:
|
|
189
|
+
console.print(f"[{COLOR_ACCENT}]No agents found in this project.[/{COLOR_ACCENT}]")
|
|
190
|
+
console.print(f"[dim]Create one with [{COLOR_INTERACTIVE}]/agent create <name>[/{COLOR_INTERACTIVE}][/dim]")
|
|
191
|
+
console.print("[dim]Or type a message to chat with the default Sudosu assistant.[/dim]")
|
|
192
|
+
return
|
|
193
|
+
|
|
194
|
+
table = Table(title="Available Agents", border_style=COLOR_PRIMARY, title_style=f"bold {COLOR_PRIMARY}")
|
|
195
|
+
table.add_column("Name", style=COLOR_INTERACTIVE)
|
|
196
|
+
table.add_column("Description")
|
|
197
|
+
table.add_column("Model", style="dim")
|
|
198
|
+
|
|
199
|
+
for agent in agents:
|
|
200
|
+
table.add_row(
|
|
201
|
+
f"@{agent['name']}",
|
|
202
|
+
agent.get("description", "No description"),
|
|
203
|
+
agent.get("model", "gemini-2.5-pro"),
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
console.print(table)
|
|
207
|
+
console.print("\n[dim]Agents are stored in .sudosu/agents/ | Default prompt in .sudosu/AGENT.md[/dim]")
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def print_error(message: str):
|
|
211
|
+
"""Print error message."""
|
|
212
|
+
console.print(f"[bold {COLOR_ACCENT}]Error:[/bold {COLOR_ACCENT}] {escape(message)}", highlight=False)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def print_success(message: str):
|
|
216
|
+
"""Print success message."""
|
|
217
|
+
console.print(f"[bold {COLOR_INTERACTIVE}]✓[/bold {COLOR_INTERACTIVE}] {escape(message)}", highlight=False)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def print_warning(message: str):
|
|
221
|
+
"""Print warning message."""
|
|
222
|
+
console.print(f"[bold {COLOR_ACCENT}]⚠[/bold {COLOR_ACCENT}] {escape(message)}", highlight=False)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def print_info(message: str):
|
|
226
|
+
"""Print info message."""
|
|
227
|
+
console.print(f"[bold {COLOR_INTERACTIVE}]ℹ[/bold {COLOR_INTERACTIVE}] {escape(message)}", highlight=False)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def print_agent_thinking(agent_name: str):
|
|
231
|
+
"""Print agent thinking indicator.
|
|
232
|
+
|
|
233
|
+
For the default 'sudosu' agent, just shows 'thinking...'
|
|
234
|
+
For other agents, shows '@agent_name thinking...'
|
|
235
|
+
"""
|
|
236
|
+
if agent_name.lower() == "sudosu":
|
|
237
|
+
console.print(f"\n[bold {COLOR_PRIMARY}]thinking...[/bold {COLOR_PRIMARY}]\n")
|
|
238
|
+
else:
|
|
239
|
+
console.print(f"\n[bold {COLOR_PRIMARY}]@{agent_name}[/bold {COLOR_PRIMARY}] is thinking...\n")
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def print_routing_to_agent(agent_name: str):
|
|
243
|
+
"""Print routing transition message."""
|
|
244
|
+
console.print(f"\n[bold {COLOR_INTERACTIVE}]→ Routing to @{agent_name}...[/bold {COLOR_INTERACTIVE}]\n")
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def print_consultation_route(from_agent: str, to_agent: str, reason: str):
|
|
248
|
+
"""Print consultation routing message."""
|
|
249
|
+
console.print(f"\n[dim]💭 @{from_agent} consulted the orchestrator...[/dim]")
|
|
250
|
+
console.print(f"[bold {COLOR_INTERACTIVE}]→ Handing off to @{to_agent}[/bold {COLOR_INTERACTIVE}]")
|
|
251
|
+
console.print(f"[dim] Reason: {reason}[/dim]\n")
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def print_tool_execution(tool_name: str, args: dict):
|
|
255
|
+
"""Print tool execution info."""
|
|
256
|
+
if tool_name == "write_file":
|
|
257
|
+
path = args.get("path", "file")
|
|
258
|
+
console.print(f"[dim]📝 Writing to {path}...[/dim]")
|
|
259
|
+
elif tool_name == "read_file":
|
|
260
|
+
path = args.get("path", "file")
|
|
261
|
+
console.print(f"[dim]📖 Reading {path}...[/dim]")
|
|
262
|
+
elif tool_name == "list_directory":
|
|
263
|
+
path = args.get("path", ".")
|
|
264
|
+
console.print(f"[dim]📁 Listing {path}...[/dim]")
|
|
265
|
+
elif tool_name == "run_command":
|
|
266
|
+
cmd = args.get("command", "command")
|
|
267
|
+
console.print(f"[dim]⚡ Running: {cmd}[/dim]")
|
|
268
|
+
else:
|
|
269
|
+
console.print(f"[dim]🔧 Executing {tool_name}...[/dim]")
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def print_tool_result(tool_name: str, result: dict):
|
|
273
|
+
"""Print tool execution result."""
|
|
274
|
+
if result.get("success"):
|
|
275
|
+
if tool_name == "write_file":
|
|
276
|
+
console.print(f"[{COLOR_INTERACTIVE}]✓ File saved: {result.get('path', 'unknown')}[/{COLOR_INTERACTIVE}]")
|
|
277
|
+
elif tool_name == "read_file":
|
|
278
|
+
# Don't print content, it goes to the agent
|
|
279
|
+
pass
|
|
280
|
+
elif tool_name == "list_directory":
|
|
281
|
+
# Don't print listing, it goes to the agent
|
|
282
|
+
pass
|
|
283
|
+
elif "error" in result:
|
|
284
|
+
console.print(f"[{COLOR_ACCENT}]✗ {result['error']}[/{COLOR_ACCENT}]")
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def print_markdown(content: str):
|
|
288
|
+
"""Print markdown content."""
|
|
289
|
+
md = Markdown(content)
|
|
290
|
+
console.print(md)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def print_code(code: str, language: str = "python"):
|
|
294
|
+
"""Print syntax-highlighted code."""
|
|
295
|
+
syntax = Syntax(code, language, theme="monokai", line_numbers=True)
|
|
296
|
+
console.print(syntax)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def create_spinner(message: str = "Processing..."):
|
|
300
|
+
"""Create a spinner progress indicator."""
|
|
301
|
+
return Progress(
|
|
302
|
+
SpinnerColumn(),
|
|
303
|
+
TextColumn("[progress.description]{task.description}"),
|
|
304
|
+
console=console,
|
|
305
|
+
transient=True,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
class StreamPrinter:
|
|
310
|
+
"""Handles streaming text output with markdown rendering support.
|
|
311
|
+
|
|
312
|
+
Modes:
|
|
313
|
+
- render_markdown=True, show_streaming=False: Buffer all, render markdown at end (cleanest)
|
|
314
|
+
- render_markdown=True, show_streaming=True: Show raw stream then render markdown (redundant but shows activity)
|
|
315
|
+
- render_markdown=False: Print raw text as it streams (no formatting)
|
|
316
|
+
"""
|
|
317
|
+
|
|
318
|
+
def __init__(self, render_markdown: bool = True, show_streaming: bool = False):
|
|
319
|
+
self.buffer = ""
|
|
320
|
+
self.render_markdown = render_markdown
|
|
321
|
+
self.show_streaming = show_streaming
|
|
322
|
+
self._chunk_count = 0
|
|
323
|
+
|
|
324
|
+
def print_chunk(self, chunk: str):
|
|
325
|
+
"""Print a chunk of streaming text."""
|
|
326
|
+
self._chunk_count += 1
|
|
327
|
+
|
|
328
|
+
if self.render_markdown:
|
|
329
|
+
# Buffer for final markdown rendering
|
|
330
|
+
self.buffer += chunk
|
|
331
|
+
|
|
332
|
+
if self.show_streaming:
|
|
333
|
+
# Also show raw text as it streams (dimmed)
|
|
334
|
+
console.print(chunk, end="", style="dim")
|
|
335
|
+
else:
|
|
336
|
+
# Raw mode: print directly without markdown processing
|
|
337
|
+
console.print(chunk, end="")
|
|
338
|
+
|
|
339
|
+
def flush(self):
|
|
340
|
+
"""Flush buffer and render as markdown."""
|
|
341
|
+
if self.buffer:
|
|
342
|
+
if self.render_markdown:
|
|
343
|
+
if self.show_streaming:
|
|
344
|
+
# Add visual separator before formatted version
|
|
345
|
+
console.print("\n")
|
|
346
|
+
console.rule(style="dim blue")
|
|
347
|
+
console.print()
|
|
348
|
+
|
|
349
|
+
# Render the complete response as formatted markdown
|
|
350
|
+
md = Markdown(self.buffer.strip())
|
|
351
|
+
console.print(md)
|
|
352
|
+
else:
|
|
353
|
+
# Just ensure newline at end for raw mode
|
|
354
|
+
pass
|
|
355
|
+
self.buffer = ""
|
|
356
|
+
console.print() # Final newline
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
class LiveStreamPrinter:
|
|
360
|
+
"""Streams text with live-updating markdown rendering.
|
|
361
|
+
|
|
362
|
+
Uses Rich's Live display to progressively render markdown as chunks arrive.
|
|
363
|
+
Provides the best experience: see formatted output as it streams.
|
|
364
|
+
"""
|
|
365
|
+
|
|
366
|
+
def __init__(self):
|
|
367
|
+
self.buffer = ""
|
|
368
|
+
self._live: Live | None = None
|
|
369
|
+
|
|
370
|
+
def start(self):
|
|
371
|
+
"""Start live display."""
|
|
372
|
+
self._live = Live(
|
|
373
|
+
Markdown(""),
|
|
374
|
+
console=console,
|
|
375
|
+
refresh_per_second=10,
|
|
376
|
+
vertical_overflow="visible",
|
|
377
|
+
)
|
|
378
|
+
self._live.start()
|
|
379
|
+
|
|
380
|
+
def print_chunk(self, chunk: str):
|
|
381
|
+
"""Add chunk and update live markdown display."""
|
|
382
|
+
self.buffer += chunk
|
|
383
|
+
if self._live:
|
|
384
|
+
# Re-render markdown with updated content
|
|
385
|
+
self._live.update(Markdown(self.buffer))
|
|
386
|
+
|
|
387
|
+
def flush(self):
|
|
388
|
+
"""Stop live display and print final output."""
|
|
389
|
+
if self._live:
|
|
390
|
+
self._live.stop()
|
|
391
|
+
self._live = None
|
|
392
|
+
console.print() # Final newline
|
|
393
|
+
|
|
394
|
+
def __enter__(self):
|
|
395
|
+
self.start()
|
|
396
|
+
return self
|
|
397
|
+
|
|
398
|
+
def __exit__(self, *args):
|
|
399
|
+
self.flush()
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def get_user_input(prompt: str = "> ") -> str:
|
|
403
|
+
"""Get user input with styled prompt and command history.
|
|
404
|
+
|
|
405
|
+
Supports:
|
|
406
|
+
- Up/Down arrows to navigate command history
|
|
407
|
+
- History persisted to ~/.sudosu/command_history
|
|
408
|
+
- Standard readline-style editing (Ctrl+A, Ctrl+E, etc.)
|
|
409
|
+
|
|
410
|
+
Note: This is a sync wrapper. For async contexts, use get_user_input_async().
|
|
411
|
+
"""
|
|
412
|
+
session = _get_prompt_session()
|
|
413
|
+
# Use prompt_toolkit's HTML formatting for proper color support
|
|
414
|
+
# COLOR_PRIMARY is #FEEAC9 (warm cream)
|
|
415
|
+
styled_prompt = HTML(f'<style fg="#FEEAC9" bold="true">{prompt}</style>')
|
|
416
|
+
|
|
417
|
+
# Check if we're in an async context
|
|
418
|
+
import asyncio
|
|
419
|
+
try:
|
|
420
|
+
asyncio.get_running_loop()
|
|
421
|
+
# We're in an async context - use a thread to avoid event loop conflicts
|
|
422
|
+
import concurrent.futures
|
|
423
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
424
|
+
future = executor.submit(session.prompt, styled_prompt)
|
|
425
|
+
return future.result()
|
|
426
|
+
except RuntimeError:
|
|
427
|
+
# No running loop - safe to use sync version
|
|
428
|
+
return session.prompt(styled_prompt)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
async def get_user_input_async(prompt: str = "> ") -> str:
|
|
432
|
+
"""Async version of get_user_input with command history.
|
|
433
|
+
|
|
434
|
+
Use this in async contexts (like the main interactive loop).
|
|
435
|
+
"""
|
|
436
|
+
session = _get_prompt_session()
|
|
437
|
+
# Use prompt_toolkit's HTML formatting for proper color support
|
|
438
|
+
# COLOR_PRIMARY is #FEEAC9 (warm cream)
|
|
439
|
+
styled_prompt = HTML(f'<style fg="#FEEAC9" bold="true">{prompt}</style>')
|
|
440
|
+
return await session.prompt_async(styled_prompt)
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def get_user_confirmation(message: str) -> bool:
|
|
444
|
+
"""Get yes/no confirmation from user."""
|
|
445
|
+
response = console.input(f"{message} [y/N]: ").strip().lower()
|
|
446
|
+
return response in ("y", "yes")
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def clear_screen():
|
|
450
|
+
"""Clear the terminal screen."""
|
|
451
|
+
console.clear()
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sudosu
|
|
3
|
+
Version: 0.1.5
|
|
4
|
+
Summary: Your AI Coworker - that can actually get work done. Unlike chatbots that just talk, these teammates can read your files, write code, create documents, connect to all your tools (Gmail, Calendar, GitHub, Linear, Slack), and run commands - all while you stay in control
|
|
5
|
+
Author: Akash Munshi
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/csakash/sudosu-cli
|
|
8
|
+
Project-URL: Repository, https://github.com/csakash/sudosu-cli
|
|
9
|
+
Project-URL: Issues, https://github.com/csakash/sudosu-cli/issues
|
|
10
|
+
Keywords: ai,assistant,cli,automation,agent,coworker,productivity,llm
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Classifier: Topic :: Office/Business
|
|
20
|
+
Classifier: Topic :: Communications
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: typer>=0.9.0
|
|
25
|
+
Requires-Dist: rich>=13.0.0
|
|
26
|
+
Requires-Dist: websockets>=12.0
|
|
27
|
+
Requires-Dist: pyyaml>=6.0
|
|
28
|
+
Requires-Dist: python-frontmatter>=1.0.0
|
|
29
|
+
Requires-Dist: httpx>=0.25.0
|
|
30
|
+
Requires-Dist: prompt-toolkit>=3.0.0
|
|
31
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
35
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
36
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
# Sudosu 🚀
|
|
40
|
+
|
|
41
|
+
[](https://badge.fury.io/py/sudosu)
|
|
42
|
+
[](https://opensource.org/licenses/MIT)
|
|
43
|
+
[](https://www.python.org/downloads/)
|
|
44
|
+
[](https://pepy.tech/project/sudosu)
|
|
45
|
+
|
|
46
|
+
**Your AI Coworker - Right in Your Terminal**
|
|
47
|
+
|
|
48
|
+
Sudosu gives you AI coworkers that can actually get work done. Unlike chatbots that just talk, these teammates can read your files, write code, create documents, connect to all your tools (Gmail, Calendar, GitHub, Linear, Slack), and run commands - all while you stay in control.
|
|
49
|
+
|
|
50
|
+
**No more hopping between tools.** Your AI coworker does it all.
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install sudosu
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Quick Start
|
|
59
|
+
|
|
60
|
+
**Zero configuration required.** Just install and run:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Install
|
|
64
|
+
pip install sudosu
|
|
65
|
+
|
|
66
|
+
# Start using immediately - no setup needed!
|
|
67
|
+
sudosu
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
That's it! Sudosu works out of the box with our hosted backend.
|
|
71
|
+
|
|
72
|
+
### Example Tasks
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# Start interactive session
|
|
76
|
+
sudosu
|
|
77
|
+
|
|
78
|
+
# Then just ask:
|
|
79
|
+
> Summarize the unread emails in my inbox
|
|
80
|
+
> Create a Linear ticket for the bug we discussed
|
|
81
|
+
> Check my calendar for tomorrow and draft a prep email
|
|
82
|
+
> Go through #product-team slack and summarize yesterday's messages
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Connect Your Tools
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# Inside sudosu, connect integrations:
|
|
89
|
+
/connect gmail # Connect Gmail
|
|
90
|
+
/connect slack # Connect Slack
|
|
91
|
+
/connect linear # Connect Linear
|
|
92
|
+
/connect github # Connect GitHub
|
|
93
|
+
/connect notion # Connect Notion
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Features
|
|
97
|
+
|
|
98
|
+
- 🚀 **Zero Config**: Install and run - works immediately with hosted backend
|
|
99
|
+
- 🤖 **AI Coworkers**: Create specialized coworkers with specific personalities and capabilities
|
|
100
|
+
- 🔌 **Tool Integrations**: Connect to Gmail, Calendar, GitHub, Linear, Slack, Notion, and more
|
|
101
|
+
- 📝 **File Operations**: Coworkers can read and write files in your repository
|
|
102
|
+
- 🔄 **Real-time Streaming**: See responses as they're generated
|
|
103
|
+
- 🔒 **Local Execution**: File operations happen on your machine, keeping data secure
|
|
104
|
+
- ⚡ **Action-Oriented**: Your coworkers don't just answer questions — they take action
|
|
105
|
+
|
|
106
|
+
## Commands
|
|
107
|
+
|
|
108
|
+
| Command | Description |
|
|
109
|
+
|---------|-------------|
|
|
110
|
+
| `/help` | Show all available commands |
|
|
111
|
+
| `/connect <service>` | Connect an integration (gmail, slack, etc.) |
|
|
112
|
+
| `/disconnect <service>` | Disconnect an integration |
|
|
113
|
+
| `/integrations` | Show connected integrations |
|
|
114
|
+
| `/agent create <name>` | Create a new agent |
|
|
115
|
+
| `/agent list` | List available agents |
|
|
116
|
+
| `/clear` | Clear the screen |
|
|
117
|
+
| `/quit` | Exit sudosu |
|
|
118
|
+
|
|
119
|
+
## Configuration (Optional)
|
|
120
|
+
|
|
121
|
+
Sudosu works out of the box, but you can customize it:
|
|
122
|
+
|
|
123
|
+
### Environment Modes
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# Development mode (local backend)
|
|
127
|
+
export SUDOSU_MODE=dev
|
|
128
|
+
sudosu
|
|
129
|
+
|
|
130
|
+
# Production mode (default - uses hosted backend)
|
|
131
|
+
export SUDOSU_MODE=prod
|
|
132
|
+
sudosu
|
|
133
|
+
|
|
134
|
+
# Or switch within CLI
|
|
135
|
+
/config mode dev # Switch to development
|
|
136
|
+
/config mode prod # Switch to production
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Configuration Files
|
|
140
|
+
|
|
141
|
+
Sudosu stores minimal global config in `~/.sudosu/`:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
~/.sudosu/
|
|
145
|
+
└── config.yaml # API keys, mode settings, user ID
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Project-level configuration** is created automatically when you run `sudosu` in any folder:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
your-project/
|
|
152
|
+
└── .sudosu/
|
|
153
|
+
├── AGENT.md # Your customizable AI assistant prompt
|
|
154
|
+
├── agents/ # Custom agents created with /agent create
|
|
155
|
+
└── context.md # (optional) Project context for all agents
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Edit `.sudosu/AGENT.md` to customize how your AI assistant behaves in that project.
|
|
159
|
+
|
|
160
|
+
## Requirements
|
|
161
|
+
|
|
162
|
+
- Python 3.10+
|
|
163
|
+
- Internet connection (for hosted backend)
|
|
164
|
+
|
|
165
|
+
## Links
|
|
166
|
+
|
|
167
|
+
- **Website**: [trysudosu.com](https://trysudosu.com)
|
|
168
|
+
- **Issues**: [GitHub Issues](https://github.com/csakash/sudosu-cli/issues)
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
MIT
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
sudosu/__init__.py,sha256=jg4Ppqo_x4UYQ14sFr_H-vc8xSIdjA1UOBKV0fY_RWY,64
|
|
2
|
+
sudosu/cli.py,sha256=uF8LVHIaaQipRN-WZBnuGNZiG4F55oThBBNaQCm4iSY,19989
|
|
3
|
+
sudosu/commands/__init__.py,sha256=GNCZ4yx2q3NqosnBdtz4-3JvfH_sDaBhRe3rsZoz7UI,326
|
|
4
|
+
sudosu/commands/agent.py,sha256=ujD9iox56xX4VIlkKVDjNIIQk7Ph4oG3y4oFNQIHATQ,10603
|
|
5
|
+
sudosu/commands/config.py,sha256=cxmVG5X5Lh8eetKpjK0vK5kTmpMzR8t7AE-WZuGJSUc,3061
|
|
6
|
+
sudosu/commands/init.py,sha256=31s5VeM1ZbDKB6mKf_Lmgw-MdaDYYZWXtLHTiKtn0a4,2370
|
|
7
|
+
sudosu/commands/integrations.py,sha256=0BUItdpOSPfacfmyN_cNXkvCDORZIu45pRhOMOMulUw,18953
|
|
8
|
+
sudosu/commands/memory.py,sha256=pxX23l495myCR_6YuNXKvIJ-lTfe0XrSPtiJ30YjgFo,7240
|
|
9
|
+
sudosu/commands/onboarding.py,sha256=U8jLNNkgP-ZaMPkCk1yQgiEhk4gLVSzHoeiUpEQQndU,11040
|
|
10
|
+
sudosu/commands/tasks.py,sha256=UN3cpTtgwOiO-KZvYsBgn4G3RerO-soWZCSLcwraXq8,22620
|
|
11
|
+
sudosu/core/__init__.py,sha256=i9Il2BOL496bw1AHCpFBBKPWxnNhptbgYABcSltaXl8,7270
|
|
12
|
+
sudosu/core/agent_loader.py,sha256=mDesHWKspt_0r_zAZ8cAV5fGF6KF4xIQfwc5PLSmXLk,7769
|
|
13
|
+
sudosu/core/connection.py,sha256=KYCJJIgPEbCvEK9-4gxCtGR9764NL-Rx_5xhMu5r_BY,7172
|
|
14
|
+
sudosu/core/default_agent.py,sha256=FeYkxLiGfML_Sw-Mb2gEqepglXajzwnyyhz3gjwefYM,20684
|
|
15
|
+
sudosu/core/prompt_refiner.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
sudosu/core/safety.py,sha256=grJ4_3kPjmin0FVJlBsKIJ6ABEiKDeXQGDLsBgC9ZmI,2357
|
|
17
|
+
sudosu/core/session.py,sha256=HoxZDuoKDcLSt7O-hTtGhHibDxojlcsTeFDQ5OyUTEI,6870
|
|
18
|
+
sudosu/tools/__init__.py,sha256=1cpnkbOyjE8VWkUvFDUwGwLM-maubPcuipQisIORzdI,11536
|
|
19
|
+
sudosu/ui/__init__.py,sha256=HhX62SG_oZeLp9wNFN9MWE30UeRujAiGCyPuN9N7Q5E,17628
|
|
20
|
+
sudosu-0.1.5.dist-info/licenses/LICENSE,sha256=HI2QFjJE024vQ134p2J-LSneIOVfC6V_QHj4sBIuWE4,1069
|
|
21
|
+
sudosu-0.1.5.dist-info/METADATA,sha256=yFtD6JseNICJutBB7dMYMUubIb6x_kabXo96ewPiP5o,5560
|
|
22
|
+
sudosu-0.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
23
|
+
sudosu-0.1.5.dist-info/entry_points.txt,sha256=VPkFcpsv3fqmP2RXvQFleD0I2tnMgUVFu2P8iUR-JDw,42
|
|
24
|
+
sudosu-0.1.5.dist-info/top_level.txt,sha256=7MGNQzAxurlmEFHv0OHniQxH7xvYz-nKErWnCNPFmhs,7
|
|
25
|
+
sudosu-0.1.5.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Akash Munshi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sudosu
|