tsugite-cli 0.3.3__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.
- tsugite/__init__.py +6 -0
- tsugite/agent_composition.py +163 -0
- tsugite/agent_inheritance.py +479 -0
- tsugite/agent_preparation.py +236 -0
- tsugite/agent_runner/__init__.py +45 -0
- tsugite/agent_runner/helpers.py +106 -0
- tsugite/agent_runner/history_integration.py +248 -0
- tsugite/agent_runner/metrics.py +100 -0
- tsugite/agent_runner/runner.py +1879 -0
- tsugite/agent_runner/validation.py +70 -0
- tsugite/agent_utils.py +167 -0
- tsugite/attachments/__init__.py +65 -0
- tsugite/attachments/auto_context.py +199 -0
- tsugite/attachments/base.py +34 -0
- tsugite/attachments/file.py +51 -0
- tsugite/attachments/inline.py +31 -0
- tsugite/attachments/storage.py +178 -0
- tsugite/attachments/url.py +59 -0
- tsugite/attachments/youtube.py +101 -0
- tsugite/benchmark/__init__.py +62 -0
- tsugite/benchmark/config.py +183 -0
- tsugite/benchmark/core.py +292 -0
- tsugite/benchmark/discovery.py +377 -0
- tsugite/benchmark/evaluators.py +671 -0
- tsugite/benchmark/execution.py +657 -0
- tsugite/benchmark/metrics.py +204 -0
- tsugite/benchmark/reports.py +420 -0
- tsugite/benchmark/utils.py +288 -0
- tsugite/builtin_agents/chat-assistant.md +53 -0
- tsugite/builtin_agents/default.md +140 -0
- tsugite/builtin_agents.py +5 -0
- tsugite/cache.py +195 -0
- tsugite/cli/__init__.py +1042 -0
- tsugite/cli/agents.py +148 -0
- tsugite/cli/attachments.py +193 -0
- tsugite/cli/benchmark.py +663 -0
- tsugite/cli/cache.py +113 -0
- tsugite/cli/config.py +272 -0
- tsugite/cli/helpers.py +534 -0
- tsugite/cli/history.py +193 -0
- tsugite/cli/init.py +387 -0
- tsugite/cli/mcp.py +193 -0
- tsugite/cli/tools.py +419 -0
- tsugite/config.py +204 -0
- tsugite/console.py +48 -0
- tsugite/constants.py +21 -0
- tsugite/core/__init__.py +19 -0
- tsugite/core/agent.py +774 -0
- tsugite/core/executor.py +300 -0
- tsugite/core/memory.py +67 -0
- tsugite/core/tools.py +271 -0
- tsugite/docker_cli.py +270 -0
- tsugite/events/__init__.py +55 -0
- tsugite/events/base.py +46 -0
- tsugite/events/bus.py +62 -0
- tsugite/events/events.py +224 -0
- tsugite/exceptions.py +40 -0
- tsugite/history/__init__.py +29 -0
- tsugite/history/index.py +210 -0
- tsugite/history/models.py +106 -0
- tsugite/history/storage.py +157 -0
- tsugite/mcp_client.py +219 -0
- tsugite/mcp_config.py +174 -0
- tsugite/md_agents.py +751 -0
- tsugite/models.py +257 -0
- tsugite/renderer.py +151 -0
- tsugite/shell_tool_config.py +265 -0
- tsugite/templates/assistant.md +14 -0
- tsugite/tools/__init__.py +265 -0
- tsugite/tools/agents.py +312 -0
- tsugite/tools/edit_strategies.py +393 -0
- tsugite/tools/fs.py +329 -0
- tsugite/tools/http.py +239 -0
- tsugite/tools/interactive.py +430 -0
- tsugite/tools/shell.py +129 -0
- tsugite/tools/shell_tools.py +214 -0
- tsugite/tools/tasks.py +339 -0
- tsugite/tsugite.py +7 -0
- tsugite/ui/__init__.py +46 -0
- tsugite/ui/base.py +638 -0
- tsugite/ui/chat.py +265 -0
- tsugite/ui/chat.tcss +92 -0
- tsugite/ui/chat_history.py +286 -0
- tsugite/ui/helpers.py +102 -0
- tsugite/ui/jsonl.py +125 -0
- tsugite/ui/live_template.py +529 -0
- tsugite/ui/plain.py +419 -0
- tsugite/ui/textual_chat.py +642 -0
- tsugite/ui/textual_handler.py +225 -0
- tsugite/ui/widgets/__init__.py +6 -0
- tsugite/ui/widgets/base_scroll_log.py +27 -0
- tsugite/ui/widgets/message_list.py +121 -0
- tsugite/ui/widgets/thought_log.py +80 -0
- tsugite/ui_context.py +90 -0
- tsugite/utils.py +367 -0
- tsugite/xdg.py +104 -0
- tsugite_cli-0.3.3.dist-info/METADATA +325 -0
- tsugite_cli-0.3.3.dist-info/RECORD +101 -0
- tsugite_cli-0.3.3.dist-info/WHEEL +4 -0
- tsugite_cli-0.3.3.dist-info/entry_points.txt +5 -0
- tsugite_cli-0.3.3.dist-info/licenses/LICENSE +235 -0
tsugite/cli/agents.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Agent management CLI commands."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
console = Console()
|
|
9
|
+
|
|
10
|
+
agents_app = typer.Typer(help="Manage agents and agent inheritance")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@agents_app.command("list")
|
|
14
|
+
def agents_list(
|
|
15
|
+
global_only: bool = typer.Option(False, "--global", help="List only global agents"),
|
|
16
|
+
local_only: bool = typer.Option(False, "--local", help="List only local agents"),
|
|
17
|
+
):
|
|
18
|
+
"""List available agents."""
|
|
19
|
+
from tsugite.agent_inheritance import get_global_agents_paths
|
|
20
|
+
from tsugite.agent_utils import list_local_agents
|
|
21
|
+
|
|
22
|
+
# Show global agents
|
|
23
|
+
if global_only or not local_only:
|
|
24
|
+
console.print("[cyan]Global Agents:[/cyan]\n")
|
|
25
|
+
|
|
26
|
+
found_any = False
|
|
27
|
+
for global_path in get_global_agents_paths():
|
|
28
|
+
if not global_path.exists():
|
|
29
|
+
continue
|
|
30
|
+
|
|
31
|
+
agent_files = sorted(global_path.glob("*.md"))
|
|
32
|
+
if agent_files:
|
|
33
|
+
found_any = True
|
|
34
|
+
console.print(f"[dim]{global_path}[/dim]")
|
|
35
|
+
for agent_file in agent_files:
|
|
36
|
+
console.print(f" • {agent_file.stem}")
|
|
37
|
+
console.print()
|
|
38
|
+
|
|
39
|
+
if not found_any:
|
|
40
|
+
console.print("[yellow]No global agents found[/yellow]")
|
|
41
|
+
console.print("\nGlobal agent locations:")
|
|
42
|
+
for path in get_global_agents_paths():
|
|
43
|
+
console.print(f" {path}")
|
|
44
|
+
console.print()
|
|
45
|
+
|
|
46
|
+
# Show local agents
|
|
47
|
+
if local_only or not global_only:
|
|
48
|
+
console.print("[cyan]Local Agents:[/cyan]\n")
|
|
49
|
+
|
|
50
|
+
local_agents = list_local_agents()
|
|
51
|
+
|
|
52
|
+
if not local_agents:
|
|
53
|
+
console.print("[yellow]No local agents found[/yellow]")
|
|
54
|
+
console.print("\nLocal agent locations:")
|
|
55
|
+
console.print(" • Current directory (*.md)")
|
|
56
|
+
console.print(" • .tsugite/")
|
|
57
|
+
console.print(" • ./agents/")
|
|
58
|
+
else:
|
|
59
|
+
for location, agent_files in local_agents.items():
|
|
60
|
+
console.print(f"[dim]{location}[/dim]")
|
|
61
|
+
for agent_file in agent_files:
|
|
62
|
+
console.print(f" • {agent_file.stem}")
|
|
63
|
+
console.print()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@agents_app.command("show")
|
|
67
|
+
def agents_show(
|
|
68
|
+
agent_path: str = typer.Argument(help="Agent name or path to agent file"),
|
|
69
|
+
show_inheritance: bool = typer.Option(False, "--inheritance", help="Show inheritance chain"),
|
|
70
|
+
):
|
|
71
|
+
"""Show agent information.
|
|
72
|
+
|
|
73
|
+
Can be either a file path (e.g., 'examples/my_agent.md') or
|
|
74
|
+
an agent name to search globally (e.g., 'default', 'builtin-default').
|
|
75
|
+
"""
|
|
76
|
+
from tsugite.agent_inheritance import find_agent_file
|
|
77
|
+
from tsugite.md_agents import parse_agent_file
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
agent_file = Path(agent_path)
|
|
81
|
+
|
|
82
|
+
# If path doesn't exist, try to find it as an agent name
|
|
83
|
+
if not agent_file.exists():
|
|
84
|
+
found_path = find_agent_file(agent_path, Path.cwd())
|
|
85
|
+
if found_path:
|
|
86
|
+
agent_file = found_path
|
|
87
|
+
console.print(f"[dim]Found: {agent_file}[/dim]\n")
|
|
88
|
+
else:
|
|
89
|
+
console.print(f"[red]Agent not found: {agent_path}[/red]")
|
|
90
|
+
console.print("\nSearched in:")
|
|
91
|
+
console.print(" • Package agents")
|
|
92
|
+
console.print(" • Current directory")
|
|
93
|
+
console.print(" • .tsugite/")
|
|
94
|
+
console.print(" • ./agents/")
|
|
95
|
+
console.print(" • Global locations (use 'agents list --global' to see)")
|
|
96
|
+
raise typer.Exit(1)
|
|
97
|
+
|
|
98
|
+
agent = parse_agent_file(agent_file)
|
|
99
|
+
config = agent.config
|
|
100
|
+
|
|
101
|
+
console.print(f"[cyan]Agent:[/cyan] [bold]{config.name}[/bold]\n")
|
|
102
|
+
|
|
103
|
+
if config.description:
|
|
104
|
+
console.print(f"[bold]Description:[/bold] {config.description}\n")
|
|
105
|
+
|
|
106
|
+
if config.model:
|
|
107
|
+
console.print(f"[bold]Model:[/bold] {config.model}")
|
|
108
|
+
else:
|
|
109
|
+
console.print("[bold]Model:[/bold] [dim](uses default)[/dim]")
|
|
110
|
+
|
|
111
|
+
console.print(f"[bold]Max Steps:[/bold] {config.max_turns}")
|
|
112
|
+
|
|
113
|
+
if config.tools:
|
|
114
|
+
console.print(f"\n[bold]Tools ({len(config.tools)}):[/bold]")
|
|
115
|
+
for tool in config.tools:
|
|
116
|
+
console.print(f" • {tool}")
|
|
117
|
+
|
|
118
|
+
if config.extends:
|
|
119
|
+
console.print(f"\n[bold]Extends:[/bold] {config.extends}")
|
|
120
|
+
|
|
121
|
+
if show_inheritance:
|
|
122
|
+
from tsugite.agent_utils import build_inheritance_chain
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
chain = build_inheritance_chain(agent_file)
|
|
126
|
+
console.print("\n[bold cyan]Inheritance Chain:[/bold cyan]")
|
|
127
|
+
|
|
128
|
+
if len(chain) == 1:
|
|
129
|
+
console.print(" [dim](no inheritance)[/dim]")
|
|
130
|
+
else:
|
|
131
|
+
for i, (agent_name, agent_path) in enumerate(chain):
|
|
132
|
+
is_current = i == len(chain) - 1
|
|
133
|
+
prefix = " └─" if is_current else " ├─"
|
|
134
|
+
|
|
135
|
+
if is_current:
|
|
136
|
+
console.print(f"{prefix} [bold]{agent_name}[/bold] [dim](current)[/dim]")
|
|
137
|
+
else:
|
|
138
|
+
console.print(f"{prefix} {agent_name} [dim]({agent_path.name})[/dim]")
|
|
139
|
+
if i < len(chain) - 1:
|
|
140
|
+
console.print(" │")
|
|
141
|
+
except Exception as e:
|
|
142
|
+
console.print(f"\n[yellow]Could not build inheritance chain: {e}[/yellow]")
|
|
143
|
+
|
|
144
|
+
console.print()
|
|
145
|
+
|
|
146
|
+
except Exception as e:
|
|
147
|
+
console.print(f"[red]Failed to load agent: {e}[/red]")
|
|
148
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""Attachment management CLI commands."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
console = Console()
|
|
9
|
+
|
|
10
|
+
attachments_app = typer.Typer(help="Manage reusable text attachments")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@attachments_app.command("add")
|
|
14
|
+
def attachments_add(
|
|
15
|
+
alias: str = typer.Argument(help="Unique name for the attachment"),
|
|
16
|
+
source: str = typer.Argument(help="File path, URL, or '-' for stdin"),
|
|
17
|
+
):
|
|
18
|
+
"""Add or update an attachment.
|
|
19
|
+
|
|
20
|
+
For stdin input ('-'), content is stored inline in attachments.json.
|
|
21
|
+
For files and URLs, only the reference is stored (content fetched on demand).
|
|
22
|
+
"""
|
|
23
|
+
from tsugite.attachments import add_attachment
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
if source == "-":
|
|
27
|
+
# Read from stdin - store inline
|
|
28
|
+
import sys
|
|
29
|
+
|
|
30
|
+
content = sys.stdin.read()
|
|
31
|
+
add_attachment(alias, source="inline", content=content)
|
|
32
|
+
|
|
33
|
+
console.print(f"[green]✓ Inline attachment '{alias}' saved[/green]")
|
|
34
|
+
console.print(" Type: Inline text")
|
|
35
|
+
console.print(f" Size: {len(content)} characters")
|
|
36
|
+
else:
|
|
37
|
+
# File or URL reference - validate but don't fetch
|
|
38
|
+
if source.startswith("http://") or source.startswith("https://"):
|
|
39
|
+
# URL reference
|
|
40
|
+
add_attachment(alias, source=source)
|
|
41
|
+
console.print(f"[green]✓ URL attachment '{alias}' saved[/green]")
|
|
42
|
+
console.print(f" Source: {source}")
|
|
43
|
+
console.print(" Type: URL (fetched on demand)")
|
|
44
|
+
else:
|
|
45
|
+
# File reference - validate it exists
|
|
46
|
+
file_path = Path(source).expanduser()
|
|
47
|
+
if not file_path.exists():
|
|
48
|
+
console.print(f"[red]File not found: {source}[/red]")
|
|
49
|
+
raise typer.Exit(1)
|
|
50
|
+
|
|
51
|
+
# Store absolute path for reliability
|
|
52
|
+
absolute_path = str(file_path.resolve())
|
|
53
|
+
add_attachment(alias, source=absolute_path)
|
|
54
|
+
|
|
55
|
+
console.print(f"[green]✓ File attachment '{alias}' saved[/green]")
|
|
56
|
+
console.print(f" Source: {absolute_path}")
|
|
57
|
+
console.print(" Type: File (read on demand)")
|
|
58
|
+
|
|
59
|
+
except Exception as e:
|
|
60
|
+
console.print(f"[red]Failed to add attachment: {e}[/red]")
|
|
61
|
+
raise typer.Exit(1)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@attachments_app.command("list")
|
|
65
|
+
def attachments_list():
|
|
66
|
+
"""List all attachments."""
|
|
67
|
+
from rich.table import Table
|
|
68
|
+
|
|
69
|
+
from tsugite.attachments import list_attachments
|
|
70
|
+
|
|
71
|
+
attachments = list_attachments()
|
|
72
|
+
|
|
73
|
+
if not attachments:
|
|
74
|
+
console.print("[yellow]No attachments found[/yellow]")
|
|
75
|
+
console.print("\nAdd an attachment with: [cyan]tsugite attachments add NAME SOURCE[/cyan]")
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
table = Table(title=f"Attachments ({len(attachments)} total)")
|
|
79
|
+
table.add_column("Alias", style="cyan")
|
|
80
|
+
table.add_column("Source", style="dim")
|
|
81
|
+
table.add_column("Size", justify="right")
|
|
82
|
+
table.add_column("Updated", style="dim")
|
|
83
|
+
|
|
84
|
+
for alias, data in sorted(attachments.items()):
|
|
85
|
+
size = len(data.get("content", ""))
|
|
86
|
+
updated = data.get("updated", "unknown")[:10] # Just date part
|
|
87
|
+
table.add_row(alias, data.get("source", "unknown"), f"{size:,}", updated)
|
|
88
|
+
|
|
89
|
+
console.print(table)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@attachments_app.command("show")
|
|
93
|
+
def attachments_show(
|
|
94
|
+
alias: str = typer.Argument(help="Attachment alias to show"),
|
|
95
|
+
content: bool = typer.Option(False, "--content", help="Show full content"),
|
|
96
|
+
):
|
|
97
|
+
"""Show details of an attachment."""
|
|
98
|
+
from rich.panel import Panel
|
|
99
|
+
|
|
100
|
+
from tsugite.attachments import get_attachment
|
|
101
|
+
from tsugite.cache import get_cache_key, list_cache
|
|
102
|
+
|
|
103
|
+
result = get_attachment(alias)
|
|
104
|
+
|
|
105
|
+
if result is None:
|
|
106
|
+
console.print(f"[red]Attachment '{alias}' not found[/red]")
|
|
107
|
+
raise typer.Exit(1)
|
|
108
|
+
|
|
109
|
+
source, stored_content = result
|
|
110
|
+
|
|
111
|
+
# Determine attachment type
|
|
112
|
+
is_inline = source.lower() in ("inline", "text")
|
|
113
|
+
|
|
114
|
+
# Build panel content
|
|
115
|
+
panel_content = f"[cyan]Alias:[/cyan] {alias}\n"
|
|
116
|
+
panel_content += f"[cyan]Type:[/cyan] {'Inline' if is_inline else 'Reference'}\n"
|
|
117
|
+
panel_content += f"[cyan]Source:[/cyan] {source}\n"
|
|
118
|
+
|
|
119
|
+
# Check cache status for references
|
|
120
|
+
if not is_inline:
|
|
121
|
+
cache_entries = list_cache()
|
|
122
|
+
cache_key = get_cache_key(source)
|
|
123
|
+
if cache_key in cache_entries:
|
|
124
|
+
cache_info = cache_entries[cache_key]
|
|
125
|
+
panel_content += f"[cyan]Cached:[/cyan] Yes (size: {cache_info['size']:,} bytes)\n"
|
|
126
|
+
panel_content += f"[cyan]Cached at:[/cyan] {cache_info['cached_at']}\n"
|
|
127
|
+
else:
|
|
128
|
+
panel_content += "[cyan]Cached:[/cyan] No\n"
|
|
129
|
+
|
|
130
|
+
# Show content or preview
|
|
131
|
+
if is_inline and stored_content:
|
|
132
|
+
panel_content += f"[cyan]Size:[/cyan] {len(stored_content):,} characters\n"
|
|
133
|
+
if content:
|
|
134
|
+
panel_content += f"\n[cyan]Content:[/cyan]\n{stored_content}"
|
|
135
|
+
else:
|
|
136
|
+
preview = stored_content[:200]
|
|
137
|
+
if len(stored_content) > 200:
|
|
138
|
+
preview += "..."
|
|
139
|
+
panel_content += f"\n[cyan]Preview:[/cyan]\n{preview}"
|
|
140
|
+
panel_content += "\n\n[dim]Use --content to show full content[/dim]"
|
|
141
|
+
elif not is_inline and content:
|
|
142
|
+
# For references, fetch and show content if requested
|
|
143
|
+
from tsugite.utils import resolve_attachments
|
|
144
|
+
|
|
145
|
+
resolved = resolve_attachments([alias])
|
|
146
|
+
if resolved:
|
|
147
|
+
_, resolved_content = resolved[0]
|
|
148
|
+
panel_content += f"[cyan]Size:[/cyan] {len(resolved_content):,} characters\n"
|
|
149
|
+
panel_content += f"\n[cyan]Content:[/cyan]\n{resolved_content}"
|
|
150
|
+
elif not is_inline:
|
|
151
|
+
panel_content += "\n[dim]Use --content to fetch and show full content[/dim]"
|
|
152
|
+
|
|
153
|
+
console.print(Panel(panel_content, title=f"Attachment: {alias}", border_style="blue"))
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@attachments_app.command("remove")
|
|
157
|
+
def attachments_remove(
|
|
158
|
+
alias: str = typer.Argument(help="Attachment alias to remove"),
|
|
159
|
+
):
|
|
160
|
+
"""Remove an attachment."""
|
|
161
|
+
from tsugite.attachments import remove_attachment
|
|
162
|
+
|
|
163
|
+
if remove_attachment(alias):
|
|
164
|
+
console.print(f"[green]✓ Attachment '{alias}' removed[/green]")
|
|
165
|
+
else:
|
|
166
|
+
console.print(f"[yellow]Attachment '{alias}' not found[/yellow]")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@attachments_app.command("search")
|
|
170
|
+
def attachments_search(
|
|
171
|
+
query: str = typer.Argument(help="Search term"),
|
|
172
|
+
):
|
|
173
|
+
"""Search attachments by alias or source."""
|
|
174
|
+
from rich.table import Table
|
|
175
|
+
|
|
176
|
+
from tsugite.attachments import search_attachments
|
|
177
|
+
|
|
178
|
+
results = search_attachments(query)
|
|
179
|
+
|
|
180
|
+
if not results:
|
|
181
|
+
console.print(f"[yellow]No attachments found matching '{query}'[/yellow]")
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
table = Table(title=f"Search Results for '{query}' ({len(results)} found)")
|
|
185
|
+
table.add_column("Alias", style="cyan")
|
|
186
|
+
table.add_column("Source", style="dim")
|
|
187
|
+
table.add_column("Size", justify="right")
|
|
188
|
+
|
|
189
|
+
for alias, data in sorted(results.items()):
|
|
190
|
+
size = len(data.get("content", ""))
|
|
191
|
+
table.add_row(alias, data.get("source", "unknown"), f"{size:,}")
|
|
192
|
+
|
|
193
|
+
console.print(table)
|