agent-scout-cli 0.1.0__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.
- agent_scout_cli/__init__.py +3 -0
- agent_scout_cli/commands/__init__.py +0 -0
- agent_scout_cli/commands/runner.py +256 -0
- agent_scout_cli/display/__init__.py +0 -0
- agent_scout_cli/display/components.py +181 -0
- agent_scout_cli/main.py +445 -0
- agent_scout_cli-0.1.0.dist-info/METADATA +29 -0
- agent_scout_cli-0.1.0.dist-info/RECORD +10 -0
- agent_scout_cli-0.1.0.dist-info/WHEEL +4 -0
- agent_scout_cli-0.1.0.dist-info/entry_points.txt +2 -0
|
File without changes
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""Agent runner — wire up and execute tasks from the CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
|
|
7
|
+
from agent_scout.agent import Agent
|
|
8
|
+
from agent_scout.config.settings import Settings
|
|
9
|
+
from agent_scout.db.models import AutonomyLevel, Task
|
|
10
|
+
from agent_scout.events import Event, EventBus, EventType
|
|
11
|
+
from agent_scout.llm.budget import BudgetConfig, BudgetEngine
|
|
12
|
+
from agent_scout.llm.estimator import CostEstimator
|
|
13
|
+
from agent_scout.llm.provider import (
|
|
14
|
+
AgentLLMProvider,
|
|
15
|
+
CustomEndpoint,
|
|
16
|
+
LLMClient,
|
|
17
|
+
ProviderConfig,
|
|
18
|
+
)
|
|
19
|
+
from agent_scout.llm.router import ModelRouter, RouterConfig
|
|
20
|
+
from agent_scout.plugins.registry import PluginRegistry
|
|
21
|
+
from agent_scout.plugins.web_search import WebSearchPlugin
|
|
22
|
+
from rich.console import Console
|
|
23
|
+
|
|
24
|
+
from agent_scout_cli.display.components import (
|
|
25
|
+
action_panel,
|
|
26
|
+
cost_display,
|
|
27
|
+
error_panel,
|
|
28
|
+
finish_panel,
|
|
29
|
+
observation_panel,
|
|
30
|
+
thought_panel,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _build_provider_config(settings: Settings) -> ProviderConfig:
|
|
35
|
+
"""Build ProviderConfig from Settings."""
|
|
36
|
+
endpoints = [
|
|
37
|
+
CustomEndpoint(
|
|
38
|
+
name=ep.name,
|
|
39
|
+
base_url=ep.base_url,
|
|
40
|
+
api_key=ep.api_key,
|
|
41
|
+
model_name=ep.model_name,
|
|
42
|
+
)
|
|
43
|
+
for ep in settings.custom_endpoints
|
|
44
|
+
]
|
|
45
|
+
return ProviderConfig(
|
|
46
|
+
default_model=settings.llm.default_model,
|
|
47
|
+
temperature=settings.llm.temperature,
|
|
48
|
+
max_tokens=settings.llm.max_tokens,
|
|
49
|
+
custom_endpoints=endpoints,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _build_router(settings: Settings) -> ModelRouter:
|
|
54
|
+
"""Build ModelRouter from Settings."""
|
|
55
|
+
config = RouterConfig(
|
|
56
|
+
fast_model=settings.llm.fast_model,
|
|
57
|
+
balanced_model=settings.llm.default_model,
|
|
58
|
+
best_model=settings.llm.best_model,
|
|
59
|
+
enabled=settings.llm.enable_routing,
|
|
60
|
+
)
|
|
61
|
+
return ModelRouter(config)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _build_budget(settings: Settings, bus: EventBus) -> BudgetEngine:
|
|
65
|
+
"""Build BudgetEngine from Settings."""
|
|
66
|
+
config = BudgetConfig(
|
|
67
|
+
task_limit=settings.budget.task_limit,
|
|
68
|
+
daily_limit=settings.budget.daily_limit,
|
|
69
|
+
monthly_limit=settings.budget.monthly_limit,
|
|
70
|
+
enforce=settings.budget.enforce,
|
|
71
|
+
)
|
|
72
|
+
return BudgetEngine(config=config, event_bus=bus)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _build_registry(settings: Settings) -> PluginRegistry:
|
|
76
|
+
"""Build and populate the plugin registry."""
|
|
77
|
+
from agent_scout.plugins.base import PluginConfig
|
|
78
|
+
|
|
79
|
+
registry = PluginRegistry()
|
|
80
|
+
|
|
81
|
+
if "web_search" in settings.plugins:
|
|
82
|
+
ws_config = PluginConfig(
|
|
83
|
+
settings={
|
|
84
|
+
"tavily_api_key": settings.search.tavily_api_key,
|
|
85
|
+
"serpapi_api_key": settings.search.serpapi_api_key,
|
|
86
|
+
"brave_api_key": settings.search.brave_api_key,
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
registry.register(WebSearchPlugin(config=ws_config))
|
|
90
|
+
|
|
91
|
+
return registry
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class CLIEventHandler:
|
|
95
|
+
"""Display agent events in the terminal."""
|
|
96
|
+
|
|
97
|
+
def __init__(self, console: Console, verbose: bool = False) -> None:
|
|
98
|
+
self._console = console
|
|
99
|
+
self._verbose = verbose
|
|
100
|
+
self._step_count = 0
|
|
101
|
+
self._total_input = 0
|
|
102
|
+
self._total_output = 0
|
|
103
|
+
self._total_cost = 0.0
|
|
104
|
+
|
|
105
|
+
async def handle(self, event: Event) -> None:
|
|
106
|
+
"""Route events to display methods."""
|
|
107
|
+
handlers = {
|
|
108
|
+
EventType.THOUGHT: self._on_thought,
|
|
109
|
+
EventType.ACTION: self._on_action,
|
|
110
|
+
EventType.OBSERVATION: self._on_observation,
|
|
111
|
+
EventType.TOOL_CALL_START: self._on_tool_start,
|
|
112
|
+
EventType.TOOL_CALL_END: self._on_tool_end,
|
|
113
|
+
EventType.TOOL_ERROR: self._on_tool_error,
|
|
114
|
+
EventType.COST_UPDATE: self._on_cost,
|
|
115
|
+
EventType.BUDGET_WARNING: self._on_budget_warning,
|
|
116
|
+
EventType.BUDGET_EXCEEDED: self._on_budget_exceeded,
|
|
117
|
+
EventType.TASK_COMPLETED: self._on_completed,
|
|
118
|
+
EventType.TASK_FAILED: self._on_failed,
|
|
119
|
+
EventType.ERROR: self._on_error,
|
|
120
|
+
}
|
|
121
|
+
handler = handlers.get(event.event_type)
|
|
122
|
+
if handler:
|
|
123
|
+
handler(event)
|
|
124
|
+
|
|
125
|
+
def _on_thought(self, event: Event) -> None:
|
|
126
|
+
self._step_count += 1
|
|
127
|
+
self._console.print(thought_panel(event.data.get("thought", ""), self._step_count))
|
|
128
|
+
|
|
129
|
+
def _on_action(self, event: Event) -> None:
|
|
130
|
+
tool = event.data.get("tool_name", "")
|
|
131
|
+
if tool:
|
|
132
|
+
self._console.print(action_panel(tool, event.data.get("tool_input")))
|
|
133
|
+
|
|
134
|
+
def _on_observation(self, event: Event) -> None:
|
|
135
|
+
obs = event.data.get("observation", "")
|
|
136
|
+
if obs and self._verbose:
|
|
137
|
+
self._console.print(observation_panel(obs))
|
|
138
|
+
elif obs:
|
|
139
|
+
short = obs[:200] + "..." if len(obs) > 200 else obs
|
|
140
|
+
self._console.print(f" [dim]{short}[/dim]")
|
|
141
|
+
|
|
142
|
+
def _on_tool_start(self, event: Event) -> None:
|
|
143
|
+
tool = event.data.get("tool_name", "")
|
|
144
|
+
self._console.print(f" [dim]Running {tool}...[/dim]")
|
|
145
|
+
|
|
146
|
+
def _on_tool_end(self, event: Event) -> None:
|
|
147
|
+
pass # observation handles the output
|
|
148
|
+
|
|
149
|
+
def _on_tool_error(self, event: Event) -> None:
|
|
150
|
+
err = event.data.get("error", "")
|
|
151
|
+
self._console.print(f" [red]Tool error: {err}[/red]")
|
|
152
|
+
|
|
153
|
+
def _on_cost(self, event: Event) -> None:
|
|
154
|
+
self._total_input += event.data.get("input_tokens", 0)
|
|
155
|
+
self._total_output += event.data.get("output_tokens", 0)
|
|
156
|
+
self._total_cost += event.data.get("cost_usd", 0.0)
|
|
157
|
+
|
|
158
|
+
def _on_budget_warning(self, event: Event) -> None:
|
|
159
|
+
pct = event.data.get("percentage", 0)
|
|
160
|
+
limit_type = event.data.get("limit_type", "")
|
|
161
|
+
self._console.print(f" [yellow]Budget warning: {limit_type} at {pct}%[/yellow]")
|
|
162
|
+
|
|
163
|
+
def _on_budget_exceeded(self, event: Event) -> None:
|
|
164
|
+
limit_type = event.data.get("limit_type", "")
|
|
165
|
+
self._console.print(f" [bold red]Budget exceeded: {limit_type}[/bold red]")
|
|
166
|
+
|
|
167
|
+
def _on_completed(self, event: Event) -> None:
|
|
168
|
+
result = event.data.get("result", {})
|
|
169
|
+
message = result.get("message", "") if isinstance(result, dict) else str(result)
|
|
170
|
+
if message:
|
|
171
|
+
self._console.print(finish_panel(message))
|
|
172
|
+
self._print_summary()
|
|
173
|
+
|
|
174
|
+
def _on_failed(self, event: Event) -> None:
|
|
175
|
+
err = event.data.get("error", "Task failed")
|
|
176
|
+
self._console.print(error_panel(err))
|
|
177
|
+
self._print_summary()
|
|
178
|
+
|
|
179
|
+
def _on_error(self, event: Event) -> None:
|
|
180
|
+
err = event.data.get("error", "")
|
|
181
|
+
self._console.print(f" [red]{err}[/red]")
|
|
182
|
+
|
|
183
|
+
def _print_summary(self) -> None:
|
|
184
|
+
if self._total_input > 0 or self._total_output > 0:
|
|
185
|
+
self._console.print(
|
|
186
|
+
cost_display(
|
|
187
|
+
self._total_input,
|
|
188
|
+
self._total_output,
|
|
189
|
+
self._total_cost,
|
|
190
|
+
)
|
|
191
|
+
)
|
|
192
|
+
self._console.print(f"[dim]Completed in {self._step_count} steps[/dim]")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
async def run_task(
|
|
196
|
+
task_description: str,
|
|
197
|
+
settings: Settings,
|
|
198
|
+
console: Console,
|
|
199
|
+
model_override: str | None = None,
|
|
200
|
+
budget_override: float | None = None,
|
|
201
|
+
verbose: bool = False,
|
|
202
|
+
) -> None:
|
|
203
|
+
"""Build the full agent stack and execute a task."""
|
|
204
|
+
bus = EventBus()
|
|
205
|
+
|
|
206
|
+
# Event display
|
|
207
|
+
handler = CLIEventHandler(console, verbose=verbose)
|
|
208
|
+
bus.subscribe(handler.handle)
|
|
209
|
+
|
|
210
|
+
# LLM stack
|
|
211
|
+
provider_config = _build_provider_config(settings)
|
|
212
|
+
client = LLMClient(config=provider_config, event_bus=bus)
|
|
213
|
+
router = _build_router(settings)
|
|
214
|
+
estimator = CostEstimator()
|
|
215
|
+
budget = _build_budget(settings, bus)
|
|
216
|
+
|
|
217
|
+
if budget_override is not None:
|
|
218
|
+
budget.config.task_limit = budget_override
|
|
219
|
+
|
|
220
|
+
# Route to best model for this task
|
|
221
|
+
model = router.route(task_description, override_model=model_override)
|
|
222
|
+
llm_provider = AgentLLMProvider(client, model=model)
|
|
223
|
+
|
|
224
|
+
# Plugin registry
|
|
225
|
+
registry = _build_registry(settings)
|
|
226
|
+
await registry.load_all()
|
|
227
|
+
|
|
228
|
+
# Build autonomy level
|
|
229
|
+
autonomy_map = {
|
|
230
|
+
"full": AutonomyLevel.FULL,
|
|
231
|
+
"semi": AutonomyLevel.SEMI,
|
|
232
|
+
"step": AutonomyLevel.STEP_BY_STEP,
|
|
233
|
+
}
|
|
234
|
+
autonomy = autonomy_map.get(settings.autonomy, AutonomyLevel.FULL)
|
|
235
|
+
|
|
236
|
+
# Pre-execution cost estimate
|
|
237
|
+
estimate = estimator.estimate(model=model, input_tokens=500, expected_output_tokens=1000)
|
|
238
|
+
console.print(f"[dim]Model: {model} | Estimated cost: {estimate.formatted}[/dim]")
|
|
239
|
+
|
|
240
|
+
# Build and run agent
|
|
241
|
+
agent = Agent(
|
|
242
|
+
llm=llm_provider,
|
|
243
|
+
tools=registry,
|
|
244
|
+
event_bus=bus,
|
|
245
|
+
autonomy=autonomy,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
task = Task(
|
|
249
|
+
id=uuid.uuid4().hex[:12],
|
|
250
|
+
description=task_description,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
await agent.run_task(task)
|
|
254
|
+
|
|
255
|
+
# Cleanup
|
|
256
|
+
await registry.unload_all()
|
|
File without changes
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Rich display components for the AgentScout CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from rich.panel import Panel
|
|
6
|
+
from rich.table import Table
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def thought_panel(thought: str, step_number: int) -> Panel:
|
|
10
|
+
"""Display an agent thought."""
|
|
11
|
+
return Panel(
|
|
12
|
+
thought,
|
|
13
|
+
title=f"[bold cyan]Thought #{step_number}[/bold cyan]",
|
|
14
|
+
border_style="cyan",
|
|
15
|
+
padding=(0, 1),
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def action_panel(tool_name: str, tool_input: dict | None = None) -> Panel:
|
|
20
|
+
"""Display an agent action (tool call)."""
|
|
21
|
+
content = f"[bold]{tool_name}[/bold]"
|
|
22
|
+
if tool_input:
|
|
23
|
+
args = ", ".join(f"{k}={v!r}" for k, v in tool_input.items())
|
|
24
|
+
content += f"\n[dim]{args}[/dim]"
|
|
25
|
+
return Panel(
|
|
26
|
+
content,
|
|
27
|
+
title="[bold yellow]Action[/bold yellow]",
|
|
28
|
+
border_style="yellow",
|
|
29
|
+
padding=(0, 1),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def observation_panel(observation: str, max_length: int = 500) -> Panel:
|
|
34
|
+
"""Display an observation (tool result)."""
|
|
35
|
+
display = observation[:max_length]
|
|
36
|
+
if len(observation) > max_length:
|
|
37
|
+
display += f"\n[dim]... ({len(observation) - max_length} chars truncated)[/dim]"
|
|
38
|
+
return Panel(
|
|
39
|
+
display,
|
|
40
|
+
title="[bold green]Observation[/bold green]",
|
|
41
|
+
border_style="green",
|
|
42
|
+
padding=(0, 1),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def finish_panel(message: str) -> Panel:
|
|
47
|
+
"""Display the final result."""
|
|
48
|
+
return Panel(
|
|
49
|
+
message,
|
|
50
|
+
title="[bold green]Result[/bold green]",
|
|
51
|
+
border_style="bold green",
|
|
52
|
+
padding=(1, 2),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def error_panel(error: str) -> Panel:
|
|
57
|
+
"""Display an error."""
|
|
58
|
+
return Panel(
|
|
59
|
+
f"[red]{error}[/red]",
|
|
60
|
+
title="[bold red]Error[/bold red]",
|
|
61
|
+
border_style="red",
|
|
62
|
+
padding=(0, 1),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def cost_display(
|
|
67
|
+
input_tokens: int,
|
|
68
|
+
output_tokens: int,
|
|
69
|
+
cost_usd: float,
|
|
70
|
+
budget_remaining: float | None = None,
|
|
71
|
+
) -> Panel:
|
|
72
|
+
"""Display cost information."""
|
|
73
|
+
lines = [
|
|
74
|
+
f"Tokens: [cyan]{input_tokens:,}[/cyan] in / [cyan]{output_tokens:,}[/cyan] out",
|
|
75
|
+
f"Cost: [bold]${cost_usd:.4f}[/bold]",
|
|
76
|
+
]
|
|
77
|
+
if budget_remaining is not None:
|
|
78
|
+
color = "green" if budget_remaining > 0.5 else "yellow" if budget_remaining > 0.1 else "red"
|
|
79
|
+
lines.append(f"Budget remaining: [{color}]${budget_remaining:.2f}[/{color}]")
|
|
80
|
+
return Panel(
|
|
81
|
+
"\n".join(lines),
|
|
82
|
+
title="[bold]Cost[/bold]",
|
|
83
|
+
border_style="dim",
|
|
84
|
+
padding=(0, 1),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def comparison_table(
|
|
89
|
+
title: str,
|
|
90
|
+
columns: list[str],
|
|
91
|
+
rows: list[list[str]],
|
|
92
|
+
highlight_col: int | None = None,
|
|
93
|
+
highlight_row: int | None = 0,
|
|
94
|
+
) -> Table:
|
|
95
|
+
"""Build a comparison table with optional row highlighting."""
|
|
96
|
+
table = Table(title=title, show_lines=True)
|
|
97
|
+
|
|
98
|
+
for i, col in enumerate(columns):
|
|
99
|
+
style = "bold cyan" if i == highlight_col else ""
|
|
100
|
+
table.add_column(col, style=style)
|
|
101
|
+
|
|
102
|
+
for i, row in enumerate(rows):
|
|
103
|
+
style = "bold green" if i == highlight_row else ""
|
|
104
|
+
table.add_row(*row, style=style)
|
|
105
|
+
|
|
106
|
+
return table
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def task_progress(
|
|
110
|
+
step_count: int,
|
|
111
|
+
max_steps: int,
|
|
112
|
+
current_action: str = "",
|
|
113
|
+
) -> Panel:
|
|
114
|
+
"""Display task progress."""
|
|
115
|
+
pct = min(100, int((step_count / max_steps) * 100)) if max_steps > 0 else 0
|
|
116
|
+
bar_filled = pct // 5
|
|
117
|
+
bar_empty = 20 - bar_filled
|
|
118
|
+
bar = f"[green]{'=' * bar_filled}[/green][dim]{'.' * bar_empty}[/dim]"
|
|
119
|
+
|
|
120
|
+
lines = [
|
|
121
|
+
f"Step {step_count}/{max_steps} [{bar}] {pct}%",
|
|
122
|
+
]
|
|
123
|
+
if current_action:
|
|
124
|
+
lines.append(f"[dim]{current_action}[/dim]")
|
|
125
|
+
|
|
126
|
+
return Panel(
|
|
127
|
+
"\n".join(lines),
|
|
128
|
+
title="[bold]Progress[/bold]",
|
|
129
|
+
border_style="blue",
|
|
130
|
+
padding=(0, 1),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def sparkline(values: list[float], width: int = 20) -> str:
|
|
135
|
+
"""Generate a simple text-based sparkline from values."""
|
|
136
|
+
if not values:
|
|
137
|
+
return ""
|
|
138
|
+
|
|
139
|
+
blocks = " ▁▂▃▄▅▆▇█"
|
|
140
|
+
mn, mx = min(values), max(values)
|
|
141
|
+
rng = mx - mn if mx != mn else 1
|
|
142
|
+
|
|
143
|
+
# Sample values to fit width
|
|
144
|
+
if len(values) > width:
|
|
145
|
+
step = len(values) / width
|
|
146
|
+
sampled = [values[int(i * step)] for i in range(width)]
|
|
147
|
+
else:
|
|
148
|
+
sampled = values
|
|
149
|
+
|
|
150
|
+
chars = []
|
|
151
|
+
for v in sampled:
|
|
152
|
+
idx = int(((v - mn) / rng) * (len(blocks) - 1))
|
|
153
|
+
chars.append(blocks[idx])
|
|
154
|
+
return "".join(chars)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def price_trend_display(
|
|
158
|
+
item_name: str,
|
|
159
|
+
prices: list[float],
|
|
160
|
+
currency: str = "USD",
|
|
161
|
+
) -> Panel:
|
|
162
|
+
"""Display a price trend with sparkline."""
|
|
163
|
+
if not prices:
|
|
164
|
+
return Panel(f"No price data for '{item_name}'", border_style="dim")
|
|
165
|
+
|
|
166
|
+
chart = sparkline(prices)
|
|
167
|
+
current = prices[-1]
|
|
168
|
+
mn = min(prices)
|
|
169
|
+
mx = max(prices)
|
|
170
|
+
|
|
171
|
+
lines = [
|
|
172
|
+
f"[bold]{item_name}[/bold]",
|
|
173
|
+
f"Current: {currency} {current:.2f}",
|
|
174
|
+
f"Range: {currency} {mn:.2f} - {currency} {mx:.2f}",
|
|
175
|
+
f"Trend: {chart}",
|
|
176
|
+
]
|
|
177
|
+
return Panel(
|
|
178
|
+
"\n".join(lines),
|
|
179
|
+
border_style="cyan",
|
|
180
|
+
padding=(0, 1),
|
|
181
|
+
)
|
agent_scout_cli/main.py
ADDED
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
"""AgentScout CLI entry point."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from agent_scout import __version__ as core_version
|
|
10
|
+
from agent_scout.config.settings import (
|
|
11
|
+
DEFAULT_CONFIG_FILE,
|
|
12
|
+
CustomEndpointSettings,
|
|
13
|
+
Settings,
|
|
14
|
+
load_settings,
|
|
15
|
+
save_settings,
|
|
16
|
+
)
|
|
17
|
+
from rich.console import Console
|
|
18
|
+
from rich.panel import Panel
|
|
19
|
+
from rich.prompt import Confirm, Prompt
|
|
20
|
+
from rich.table import Table
|
|
21
|
+
|
|
22
|
+
from . import __version__ as cli_version
|
|
23
|
+
|
|
24
|
+
app = typer.Typer(
|
|
25
|
+
name="agent-scout",
|
|
26
|
+
help="AgentScout — Your personal AI assistant agent that scouts the web for you.",
|
|
27
|
+
no_args_is_help=True,
|
|
28
|
+
)
|
|
29
|
+
console = Console()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _load_settings() -> Settings:
|
|
33
|
+
"""Load settings with error display."""
|
|
34
|
+
return load_settings()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ---- run ----
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@app.command()
|
|
41
|
+
def run(
|
|
42
|
+
task: str = typer.Argument(
|
|
43
|
+
help="Natural language description of what you want the agent to do",
|
|
44
|
+
),
|
|
45
|
+
model: str | None = typer.Option(
|
|
46
|
+
None, "--model", "-m", help="LLM model to use (overrides routing)"
|
|
47
|
+
),
|
|
48
|
+
budget: float | None = typer.Option(
|
|
49
|
+
None, "--budget", "-b", help="Max budget for this task (USD)"
|
|
50
|
+
),
|
|
51
|
+
autonomy: str = typer.Option(None, "--autonomy", "-a", help="Autonomy level: full, semi, step"),
|
|
52
|
+
verbose: bool = typer.Option(
|
|
53
|
+
False, "--verbose", "-v", help="Show full observations and debug info"
|
|
54
|
+
),
|
|
55
|
+
):
|
|
56
|
+
"""Run a scouting task."""
|
|
57
|
+
settings = _load_settings()
|
|
58
|
+
|
|
59
|
+
if autonomy:
|
|
60
|
+
settings.autonomy = autonomy
|
|
61
|
+
if verbose:
|
|
62
|
+
settings.verbose = True
|
|
63
|
+
|
|
64
|
+
console.print(
|
|
65
|
+
Panel(
|
|
66
|
+
f"[bold]Task:[/bold] {task}",
|
|
67
|
+
title="[bold green]AgentScout[/bold green]",
|
|
68
|
+
subtitle="Starting...",
|
|
69
|
+
border_style="green",
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
from agent_scout_cli.commands.runner import run_task
|
|
74
|
+
|
|
75
|
+
asyncio.run(
|
|
76
|
+
run_task(
|
|
77
|
+
task_description=task,
|
|
78
|
+
settings=settings,
|
|
79
|
+
console=console,
|
|
80
|
+
model_override=model,
|
|
81
|
+
budget_override=budget,
|
|
82
|
+
verbose=verbose,
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ---- setup ----
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@app.command()
|
|
91
|
+
def setup(
|
|
92
|
+
reconfigure: bool = typer.Option(False, "--reconfigure", help="Reconfigure existing settings"),
|
|
93
|
+
):
|
|
94
|
+
"""Run the interactive setup wizard."""
|
|
95
|
+
settings = _load_settings() if reconfigure else Settings()
|
|
96
|
+
|
|
97
|
+
console.print(
|
|
98
|
+
Panel(
|
|
99
|
+
"[bold]Welcome to AgentScout![/bold]\n\n"
|
|
100
|
+
"This wizard will help you configure your AI assistant.\n"
|
|
101
|
+
"You can re-run this anytime with: [cyan]agent-scout setup --reconfigure[/cyan]",
|
|
102
|
+
title="[bold green]Setup Wizard[/bold green]",
|
|
103
|
+
border_style="green",
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Step 1: LLM model
|
|
108
|
+
console.print("\n[bold]Step 1: LLM Configuration[/bold]")
|
|
109
|
+
settings.llm.default_model = Prompt.ask(
|
|
110
|
+
"Default model",
|
|
111
|
+
default=settings.llm.default_model,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Step 2: API keys (optional)
|
|
115
|
+
console.print("\n[bold]Step 2: Search API Keys[/bold] [dim](optional, Enter to skip)[/dim]")
|
|
116
|
+
settings.search.tavily_api_key = Prompt.ask(
|
|
117
|
+
"Tavily API key", default=settings.search.tavily_api_key or "", show_default=False
|
|
118
|
+
)
|
|
119
|
+
settings.search.serpapi_api_key = Prompt.ask(
|
|
120
|
+
"SerpAPI key", default=settings.search.serpapi_api_key or "", show_default=False
|
|
121
|
+
)
|
|
122
|
+
settings.search.brave_api_key = Prompt.ask(
|
|
123
|
+
"Brave Search key", default=settings.search.brave_api_key or "", show_default=False
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Step 3: Custom endpoints
|
|
127
|
+
console.print("\n[bold]Step 3: Custom LLM Endpoints[/bold]")
|
|
128
|
+
while Confirm.ask("Add a custom LLM endpoint?", default=False):
|
|
129
|
+
ep = CustomEndpointSettings(
|
|
130
|
+
name=Prompt.ask("Endpoint name (e.g., 'myapi')"),
|
|
131
|
+
base_url=Prompt.ask("Base URL"),
|
|
132
|
+
api_key=Prompt.ask("API key"),
|
|
133
|
+
model_name=Prompt.ask("Model name"),
|
|
134
|
+
)
|
|
135
|
+
settings.custom_endpoints.append(ep)
|
|
136
|
+
|
|
137
|
+
# Step 4: Budget
|
|
138
|
+
console.print("\n[bold]Step 4: Budget Limits[/bold]")
|
|
139
|
+
settings.budget.task_limit = float(
|
|
140
|
+
Prompt.ask("Max per-task spend (USD)", default=str(settings.budget.task_limit))
|
|
141
|
+
)
|
|
142
|
+
settings.budget.daily_limit = float(
|
|
143
|
+
Prompt.ask("Max daily spend (USD)", default=str(settings.budget.daily_limit))
|
|
144
|
+
)
|
|
145
|
+
settings.budget.monthly_limit = float(
|
|
146
|
+
Prompt.ask("Max monthly spend (USD)", default=str(settings.budget.monthly_limit))
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Step 5: Personality
|
|
150
|
+
console.print("\n[bold]Step 5: Personality[/bold]")
|
|
151
|
+
settings.personality = Prompt.ask(
|
|
152
|
+
"Agent personality",
|
|
153
|
+
choices=["helpful", "concise", "detailed", "casual"],
|
|
154
|
+
default=settings.personality,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Save
|
|
158
|
+
save_settings(settings)
|
|
159
|
+
console.print(
|
|
160
|
+
Panel(
|
|
161
|
+
f"Configuration saved to [cyan]{DEFAULT_CONFIG_FILE}[/cyan]\n\n"
|
|
162
|
+
"You're all set! Run a task with:\n"
|
|
163
|
+
'[bold]agent-scout run "Find me the best deal on..."[/bold]',
|
|
164
|
+
title="[bold green]Setup Complete[/bold green]",
|
|
165
|
+
border_style="green",
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# ---- config ----
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@app.command()
|
|
174
|
+
def config(
|
|
175
|
+
action: str = typer.Argument("show", help="Action: show, set, add-endpoint"),
|
|
176
|
+
key: str | None = typer.Argument(None, help="Config key (for 'set')"),
|
|
177
|
+
value: str | None = typer.Argument(None, help="Config value (for 'set')"),
|
|
178
|
+
):
|
|
179
|
+
"""View or modify configuration."""
|
|
180
|
+
settings = _load_settings()
|
|
181
|
+
|
|
182
|
+
if action == "show":
|
|
183
|
+
_config_show(settings)
|
|
184
|
+
elif action == "set":
|
|
185
|
+
if not key or value is None:
|
|
186
|
+
console.print("[red]Usage: agent-scout config set <key> <value>[/red]")
|
|
187
|
+
raise typer.Exit(1)
|
|
188
|
+
_config_set(settings, key, value)
|
|
189
|
+
elif action == "add-endpoint":
|
|
190
|
+
_config_add_endpoint(settings)
|
|
191
|
+
else:
|
|
192
|
+
console.print(f"[red]Unknown action: {action}[/red]")
|
|
193
|
+
raise typer.Exit(1)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _config_show(settings: Settings) -> None:
|
|
197
|
+
"""Display current configuration."""
|
|
198
|
+
table = Table(title="AgentScout Configuration")
|
|
199
|
+
table.add_column("Setting", style="cyan")
|
|
200
|
+
table.add_column("Value", style="white")
|
|
201
|
+
|
|
202
|
+
table.add_row("Default model", settings.llm.default_model)
|
|
203
|
+
table.add_row("Fast model", settings.llm.fast_model)
|
|
204
|
+
table.add_row("Best model", settings.llm.best_model)
|
|
205
|
+
table.add_row("Temperature", str(settings.llm.temperature))
|
|
206
|
+
table.add_row("Max tokens", str(settings.llm.max_tokens))
|
|
207
|
+
table.add_row("Smart routing", str(settings.llm.enable_routing))
|
|
208
|
+
table.add_row("---", "---")
|
|
209
|
+
table.add_row("Task budget", f"${settings.budget.task_limit}")
|
|
210
|
+
table.add_row("Daily budget", f"${settings.budget.daily_limit}")
|
|
211
|
+
table.add_row("Monthly budget", f"${settings.budget.monthly_limit}")
|
|
212
|
+
table.add_row("Enforce budgets", str(settings.budget.enforce))
|
|
213
|
+
table.add_row("---", "---")
|
|
214
|
+
table.add_row("Personality", settings.personality)
|
|
215
|
+
table.add_row("Autonomy", settings.autonomy)
|
|
216
|
+
table.add_row("Plugins", ", ".join(settings.plugins))
|
|
217
|
+
table.add_row("---", "---")
|
|
218
|
+
table.add_row("Tavily key", "***" if settings.search.tavily_api_key else "(not set)")
|
|
219
|
+
table.add_row("SerpAPI key", "***" if settings.search.serpapi_api_key else "(not set)")
|
|
220
|
+
table.add_row("Brave key", "***" if settings.search.brave_api_key else "(not set)")
|
|
221
|
+
|
|
222
|
+
if settings.custom_endpoints:
|
|
223
|
+
table.add_row("---", "---")
|
|
224
|
+
for ep in settings.custom_endpoints:
|
|
225
|
+
table.add_row(f"Endpoint: {ep.name}", f"{ep.base_url} ({ep.model_name})")
|
|
226
|
+
|
|
227
|
+
console.print(table)
|
|
228
|
+
console.print(f"\n[dim]Config file: {DEFAULT_CONFIG_FILE}[/dim]")
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _config_set(settings: Settings, key: str, value: str) -> None:
|
|
232
|
+
"""Set a single config value."""
|
|
233
|
+
parts = key.split(".")
|
|
234
|
+
try:
|
|
235
|
+
if len(parts) == 2:
|
|
236
|
+
section, field = parts
|
|
237
|
+
obj = getattr(settings, section, None)
|
|
238
|
+
if obj is None:
|
|
239
|
+
console.print(f"[red]Unknown section: {section}[/red]")
|
|
240
|
+
raise typer.Exit(1)
|
|
241
|
+
if not hasattr(obj, field):
|
|
242
|
+
console.print(f"[red]Unknown field: {section}.{field}[/red]")
|
|
243
|
+
raise typer.Exit(1)
|
|
244
|
+
current = getattr(obj, field)
|
|
245
|
+
# Type coercion
|
|
246
|
+
if isinstance(current, bool):
|
|
247
|
+
setattr(obj, field, value.lower() in ("true", "1", "yes"))
|
|
248
|
+
elif isinstance(current, float):
|
|
249
|
+
setattr(obj, field, float(value))
|
|
250
|
+
elif isinstance(current, int):
|
|
251
|
+
setattr(obj, field, int(value))
|
|
252
|
+
else:
|
|
253
|
+
setattr(obj, field, value)
|
|
254
|
+
elif len(parts) == 1:
|
|
255
|
+
if not hasattr(settings, key):
|
|
256
|
+
console.print(f"[red]Unknown key: {key}[/red]")
|
|
257
|
+
raise typer.Exit(1)
|
|
258
|
+
setattr(settings, key, value)
|
|
259
|
+
else:
|
|
260
|
+
console.print(f"[red]Invalid key format: {key}[/red]")
|
|
261
|
+
raise typer.Exit(1)
|
|
262
|
+
|
|
263
|
+
save_settings(settings)
|
|
264
|
+
console.print(f"[green]Set {key} = {value}[/green]")
|
|
265
|
+
|
|
266
|
+
except (ValueError, TypeError) as e:
|
|
267
|
+
console.print(f"[red]Invalid value: {e}[/red]")
|
|
268
|
+
raise typer.Exit(1) from e
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _config_add_endpoint(settings: Settings) -> None:
|
|
272
|
+
"""Interactively add a custom endpoint."""
|
|
273
|
+
ep = CustomEndpointSettings(
|
|
274
|
+
name=Prompt.ask("Endpoint name"),
|
|
275
|
+
base_url=Prompt.ask("Base URL"),
|
|
276
|
+
api_key=Prompt.ask("API key"),
|
|
277
|
+
model_name=Prompt.ask("Model name"),
|
|
278
|
+
)
|
|
279
|
+
settings.custom_endpoints.append(ep)
|
|
280
|
+
save_settings(settings)
|
|
281
|
+
console.print(f"[green]Added endpoint '{ep.name}'[/green]")
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
# ---- history ----
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@app.command()
|
|
288
|
+
def history(
|
|
289
|
+
task_id: str | None = typer.Argument(None, help="Task ID to view details"),
|
|
290
|
+
search: str | None = typer.Option(None, "--search", "-s", help="Search task descriptions"),
|
|
291
|
+
export_format: str | None = typer.Option(
|
|
292
|
+
None, "--export", "-e", help="Export format: json, markdown, csv"
|
|
293
|
+
),
|
|
294
|
+
):
|
|
295
|
+
"""View task history."""
|
|
296
|
+
console.print("[bold]Task History[/bold]")
|
|
297
|
+
|
|
298
|
+
from agent_scout.config.settings import DEFAULT_CONFIG_DIR
|
|
299
|
+
from agent_scout.db.database import Database
|
|
300
|
+
|
|
301
|
+
db_path = Path(DEFAULT_CONFIG_DIR) / "agent_scout.db"
|
|
302
|
+
|
|
303
|
+
if not db_path.exists():
|
|
304
|
+
console.print("[dim]No task history yet. Run a task first![/dim]")
|
|
305
|
+
return
|
|
306
|
+
|
|
307
|
+
async def _show_history() -> None:
|
|
308
|
+
db = Database(str(db_path))
|
|
309
|
+
await db.connect()
|
|
310
|
+
try:
|
|
311
|
+
tasks = await db.list_tasks()
|
|
312
|
+
|
|
313
|
+
if search:
|
|
314
|
+
tasks = [t for t in tasks if search.lower() in t.description.lower()]
|
|
315
|
+
|
|
316
|
+
if task_id:
|
|
317
|
+
task = await db.get_task(task_id)
|
|
318
|
+
if task:
|
|
319
|
+
_display_task_detail(task)
|
|
320
|
+
else:
|
|
321
|
+
console.print(f"[red]Task '{task_id}' not found[/red]")
|
|
322
|
+
return
|
|
323
|
+
|
|
324
|
+
if not tasks:
|
|
325
|
+
console.print("[dim]No tasks found.[/dim]")
|
|
326
|
+
return
|
|
327
|
+
|
|
328
|
+
table = Table(title="Task History")
|
|
329
|
+
table.add_column("ID", style="cyan", max_width=12)
|
|
330
|
+
table.add_column("Description", max_width=50)
|
|
331
|
+
table.add_column("Status", style="bold")
|
|
332
|
+
table.add_column("Created")
|
|
333
|
+
|
|
334
|
+
for t in tasks[:50]:
|
|
335
|
+
status_color = {
|
|
336
|
+
"completed": "green",
|
|
337
|
+
"failed": "red",
|
|
338
|
+
"executing": "yellow",
|
|
339
|
+
}.get(t.status.value, "white")
|
|
340
|
+
|
|
341
|
+
import time
|
|
342
|
+
|
|
343
|
+
created = time.strftime("%Y-%m-%d %H:%M", time.localtime(t.created_at))
|
|
344
|
+
table.add_row(
|
|
345
|
+
t.id[:12],
|
|
346
|
+
t.description[:50],
|
|
347
|
+
f"[{status_color}]{t.status.value}[/{status_color}]",
|
|
348
|
+
created,
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
console.print(table)
|
|
352
|
+
finally:
|
|
353
|
+
await db.disconnect()
|
|
354
|
+
|
|
355
|
+
asyncio.run(_show_history())
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def _display_task_detail(task) -> None: # noqa: ANN001
|
|
359
|
+
"""Display detailed task information."""
|
|
360
|
+
import time
|
|
361
|
+
|
|
362
|
+
created = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(task.created_at))
|
|
363
|
+
updated = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(task.updated_at))
|
|
364
|
+
|
|
365
|
+
console.print(
|
|
366
|
+
Panel(
|
|
367
|
+
f"[bold]ID:[/bold] {task.id}\n"
|
|
368
|
+
f"[bold]Description:[/bold] {task.description}\n"
|
|
369
|
+
f"[bold]Status:[/bold] {task.status.value}\n"
|
|
370
|
+
f"[bold]Created:[/bold] {created}\n"
|
|
371
|
+
f"[bold]Updated:[/bold] {updated}\n"
|
|
372
|
+
f"[bold]Result:[/bold] {task.result or '(none)'}\n"
|
|
373
|
+
f"[bold]Error:[/bold] {task.error or '(none)'}",
|
|
374
|
+
title="[bold]Task Detail[/bold]",
|
|
375
|
+
)
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
# ---- schedule ----
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
@app.command()
|
|
383
|
+
def schedule(
|
|
384
|
+
action: str = typer.Argument("list", help="Action: list, add, remove"),
|
|
385
|
+
schedule_id: str | None = typer.Argument(None, help="Schedule ID (for remove)"),
|
|
386
|
+
task_desc: str | None = typer.Option(None, "--task", "-t", help="Task description"),
|
|
387
|
+
cron: str | None = typer.Option(None, "--cron", "-c", help="Cron expression"),
|
|
388
|
+
):
|
|
389
|
+
"""Manage scheduled tasks."""
|
|
390
|
+
if action == "list":
|
|
391
|
+
console.print("[bold]Scheduled Tasks[/bold]")
|
|
392
|
+
console.print("[dim]No scheduled tasks. Add one with:[/dim]")
|
|
393
|
+
console.print(
|
|
394
|
+
'[dim]agent-scout schedule add --task "Check prices" --cron "0 8 * * *"[/dim]'
|
|
395
|
+
)
|
|
396
|
+
elif action == "add":
|
|
397
|
+
if not task_desc or not cron:
|
|
398
|
+
console.print("[red]Usage: agent-scout schedule add --task '...' --cron '...'[/red]")
|
|
399
|
+
raise typer.Exit(1)
|
|
400
|
+
console.print(f"[green]Scheduled: '{task_desc}' with cron '{cron}'[/green]")
|
|
401
|
+
console.print("[yellow]Note: Scheduling persistence coming in Phase 7[/yellow]")
|
|
402
|
+
elif action == "remove":
|
|
403
|
+
if not schedule_id:
|
|
404
|
+
console.print("[red]Usage: agent-scout schedule remove <schedule-id>[/red]")
|
|
405
|
+
raise typer.Exit(1)
|
|
406
|
+
console.print(f"[green]Removed schedule '{schedule_id}'[/green]")
|
|
407
|
+
else:
|
|
408
|
+
console.print(f"[red]Unknown action: {action}[/red]")
|
|
409
|
+
raise typer.Exit(1)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
# ---- status ----
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
@app.command()
|
|
416
|
+
def status():
|
|
417
|
+
"""Show agent status and cost summary."""
|
|
418
|
+
settings = _load_settings()
|
|
419
|
+
|
|
420
|
+
console.print(
|
|
421
|
+
Panel(
|
|
422
|
+
f"[bold]Model:[/bold] {settings.llm.default_model}\n"
|
|
423
|
+
f"[bold]Routing:[/bold] {'enabled' if settings.llm.enable_routing else 'disabled'}\n"
|
|
424
|
+
f"[bold]Autonomy:[/bold] {settings.autonomy}\n"
|
|
425
|
+
f"[bold]Budget:[/bold] ${settings.budget.task_limit}/task, "
|
|
426
|
+
f"${settings.budget.daily_limit}/day, "
|
|
427
|
+
f"${settings.budget.monthly_limit}/month\n"
|
|
428
|
+
f"[bold]Plugins:[/bold] {', '.join(settings.plugins)}",
|
|
429
|
+
title="[bold green]AgentScout Status[/bold green]",
|
|
430
|
+
border_style="green",
|
|
431
|
+
)
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
# ---- version ----
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
@app.command()
|
|
439
|
+
def version():
|
|
440
|
+
"""Show version information."""
|
|
441
|
+
console.print(f"[bold]AgentScout[/bold] CLI v{cli_version} | Core v{core_version}")
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
if __name__ == "__main__":
|
|
445
|
+
app()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-scout-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI interface for AgentScout — your personal AI assistant agent
|
|
5
|
+
Author: AgentScout Contributors
|
|
6
|
+
License-Expression: AGPL-3.0-or-later
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Requires-Dist: agent-scout-core
|
|
9
|
+
Requires-Dist: rich>=13
|
|
10
|
+
Requires-Dist: typer>=0.24
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
13
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# agent-scout-cli
|
|
17
|
+
|
|
18
|
+
CLI interface for AgentScout — your personal AI assistant agent.
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
agent-scout run "Find me the best flight from NYC to London on Feb 28"
|
|
24
|
+
agent-scout setup
|
|
25
|
+
agent-scout config show
|
|
26
|
+
agent-scout history
|
|
27
|
+
agent-scout schedule list
|
|
28
|
+
agent-scout web
|
|
29
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
agent_scout_cli/__init__.py,sha256=2rlDEZnw95ylM3JIK-INxpQJvyNjH-oFyYq0_uI7urs,87
|
|
2
|
+
agent_scout_cli/main.py,sha256=ZY5vWRlwIKDa1wTeu1ZekXC08uGgCFDeWc3xYGoQlD8,15063
|
|
3
|
+
agent_scout_cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
agent_scout_cli/commands/runner.py,sha256=qGLk8pLNy5WId4PnnwD6NG8PbaiJEcSLXnk4YtGGPQQ,8613
|
|
5
|
+
agent_scout_cli/display/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
agent_scout_cli/display/components.py,sha256=_AHH8Q5kmTu_pQvxdTbsRcrecRgSwCIq1fmAOoXL6I4,4939
|
|
7
|
+
agent_scout_cli-0.1.0.dist-info/METADATA,sha256=nALbGjGIwX85_PydvOOD79aaCeprFtfhcnPThVenN9k,738
|
|
8
|
+
agent_scout_cli-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
9
|
+
agent_scout_cli-0.1.0.dist-info/entry_points.txt,sha256=pij6m7OIa1ibq9o2Tnq1frz43k6Z5_cdWMp6uc6d1zo,57
|
|
10
|
+
agent_scout_cli-0.1.0.dist-info/RECORD,,
|