agencode 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.
agencli/tui/trace.py ADDED
@@ -0,0 +1,334 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from textual.widgets import DataTable
6
+
7
+ from agencli.agents.runtime import StreamEvent
8
+
9
+
10
+ TRACE_ROW_LIMIT = 40
11
+
12
+
13
+ @dataclass(slots=True)
14
+ class TraceRow:
15
+ step: int
16
+ kind: str
17
+ label: str
18
+ status: str
19
+ detail: str
20
+
21
+
22
+ @dataclass(slots=True)
23
+ class OrchestrationRow:
24
+ name: str
25
+ role: str
26
+ tools: str
27
+ status: str
28
+ detail: str
29
+
30
+
31
+ class TraceLedger:
32
+ def __init__(self, max_rows: int = TRACE_ROW_LIMIT) -> None:
33
+ self.max_rows = max_rows
34
+ self.rows: list[TraceRow] = []
35
+ self._next_step = 1
36
+ self._active_tools: dict[str, TraceRow] = {}
37
+ self._active_subagents: dict[str, TraceRow] = {}
38
+
39
+ def reset(self) -> None:
40
+ self.rows.clear()
41
+ self._active_tools.clear()
42
+ self._active_subagents.clear()
43
+ self._next_step = 1
44
+
45
+ def begin_run(self, agent_name: str, thread_id: str) -> None:
46
+ self.reset()
47
+ self.add_status(label=agent_name, status="thread", detail=thread_id, kind="session")
48
+
49
+ def add_status(self, *, label: str, status: str, detail: str, kind: str = "state") -> None:
50
+ self._append_row(kind=kind, label=label, status=status, detail=detail)
51
+
52
+ def record(self, event: StreamEvent) -> None:
53
+ if event.kind == "assistant":
54
+ return
55
+ if event.kind == "tool":
56
+ self._record_tool_event(event)
57
+ return
58
+ if event.kind == "subagent":
59
+ self._record_subagent_event(event)
60
+ return
61
+ self._append_row(
62
+ kind=event.kind,
63
+ label=event.label,
64
+ status=event.phase or "update",
65
+ detail=event.text or "(ok)",
66
+ )
67
+
68
+ def _record_tool_event(self, event: StreamEvent) -> None:
69
+ tool_key = event.trace_id or event.label
70
+ if event.phase == "start":
71
+ row = self._append_row(
72
+ kind="tool",
73
+ label=event.label,
74
+ status="running",
75
+ detail=event.text or "invoked",
76
+ )
77
+ self._active_tools[tool_key] = row
78
+ return
79
+
80
+ existing = self._active_tools.pop(tool_key, None)
81
+ if existing is None:
82
+ self._append_row(
83
+ kind="tool",
84
+ label=event.label,
85
+ status="done",
86
+ detail=event.text or "(ok)",
87
+ )
88
+ return
89
+
90
+ existing.status = "done"
91
+ existing.detail = _merge_detail(existing.detail, event.text or "(ok)")
92
+
93
+ def _record_subagent_event(self, event: StreamEvent) -> None:
94
+ subagent_key = event.trace_id or event.label
95
+ if event.phase == "start":
96
+ row = self._append_row(
97
+ kind="subagent",
98
+ label=event.label,
99
+ status="running",
100
+ detail=event.text or "delegated",
101
+ )
102
+ self._active_subagents[subagent_key] = row
103
+ return
104
+
105
+ existing = self._active_subagents.pop(subagent_key, None)
106
+ if existing is None:
107
+ self._append_row(
108
+ kind="subagent",
109
+ label=event.label,
110
+ status="done",
111
+ detail=event.text or "(ok)",
112
+ )
113
+ return
114
+
115
+ existing.status = "done"
116
+ existing.detail = _merge_detail(existing.detail, event.text or "(ok)")
117
+
118
+ def _append_row(self, *, kind: str, label: str, status: str, detail: str) -> TraceRow:
119
+ row = TraceRow(
120
+ step=self._next_step,
121
+ kind=kind,
122
+ label=label,
123
+ status=status,
124
+ detail=detail,
125
+ )
126
+ self._next_step += 1
127
+ self.rows.append(row)
128
+ self._trim_rows()
129
+ return row
130
+
131
+ def _trim_rows(self) -> None:
132
+ while len(self.rows) > self.max_rows:
133
+ dropped = self.rows.pop(0)
134
+ stale_keys = [key for key, value in self._active_tools.items() if value is dropped]
135
+ for key in stale_keys:
136
+ self._active_tools.pop(key, None)
137
+ stale_subagents = [key for key, value in self._active_subagents.items() if value is dropped]
138
+ for key in stale_subagents:
139
+ self._active_subagents.pop(key, None)
140
+
141
+
142
+ class OrchestrationLedger:
143
+ def __init__(self) -> None:
144
+ self.rows: list[OrchestrationRow] = []
145
+ self._rows_by_name: dict[str, OrchestrationRow] = {}
146
+ self._active_runs: dict[str, OrchestrationRow] = {}
147
+
148
+ def configure(self, subagents: list[dict[str, str]]) -> None:
149
+ self.rows.clear()
150
+ self._rows_by_name.clear()
151
+ self._active_runs.clear()
152
+ if not subagents:
153
+ self.rows.append(
154
+ OrchestrationRow(
155
+ name="-",
156
+ role="No configured subagents",
157
+ tools="-",
158
+ status="idle",
159
+ detail="This agent currently runs without delegated subagent roles.",
160
+ )
161
+ )
162
+ return
163
+ for subagent in subagents:
164
+ row = OrchestrationRow(
165
+ name=subagent["name"],
166
+ role=subagent["role"],
167
+ tools=subagent["tools"],
168
+ status="idle",
169
+ detail=subagent.get("detail", "Ready"),
170
+ )
171
+ self.rows.append(row)
172
+ self._rows_by_name[row.name] = row
173
+
174
+ def record(self, event: StreamEvent) -> None:
175
+ if event.kind != "subagent":
176
+ return
177
+ run_key = event.trace_id or event.label
178
+ if event.phase == "start":
179
+ row = self._rows_by_name.get(event.label)
180
+ if row is None:
181
+ if self.rows and self.rows[0].name == "-":
182
+ self.rows.clear()
183
+ row = OrchestrationRow(
184
+ name=event.label,
185
+ role="Delegated task",
186
+ tools="dynamic",
187
+ status="idle",
188
+ detail="Discovered from runtime activity.",
189
+ )
190
+ self.rows.append(row)
191
+ self._rows_by_name[row.name] = row
192
+ row.status = "running"
193
+ row.detail = event.text or "delegated"
194
+ if event.trace_id:
195
+ self._active_runs[run_key] = row
196
+ return
197
+
198
+ row = self._active_runs.pop(run_key, None)
199
+ if row is None:
200
+ row = self._rows_by_name.get(event.label)
201
+ if row is None:
202
+ row = OrchestrationRow(
203
+ name=event.label,
204
+ role="Delegated task",
205
+ tools="dynamic",
206
+ status="done",
207
+ detail=event.text or "(ok)",
208
+ )
209
+ self.rows.append(row)
210
+ self._rows_by_name[row.name] = row
211
+ return
212
+ row.status = "done"
213
+ row.detail = _merge_detail(row.detail, event.text or "(ok)")
214
+
215
+ def record_status(self, name: str, status: str, detail: str) -> None:
216
+ row = self._rows_by_name.get(name)
217
+ if row is None:
218
+ return
219
+ row.status = status
220
+ row.detail = detail
221
+
222
+
223
+ class TraceTable(DataTable[str]):
224
+ def __init__(self, *args, **kwargs) -> None:
225
+ super().__init__(*args, **kwargs)
226
+ self.ledger = TraceLedger()
227
+ self.cursor_type = "none"
228
+ self.zebra_stripes = True
229
+ self._configured = False
230
+
231
+ def on_mount(self) -> None:
232
+ self._ensure_columns()
233
+
234
+ def begin_run(self, agent_name: str, thread_id: str) -> None:
235
+ self.ledger.begin_run(agent_name, thread_id)
236
+ self._render_rows()
237
+
238
+ def reset(self) -> None:
239
+ self.ledger.reset()
240
+ self._render_rows()
241
+
242
+ def record_event(self, event: StreamEvent) -> None:
243
+ self.ledger.record(event)
244
+ self._render_rows()
245
+
246
+ def record_status(self, *, label: str, status: str, detail: str, kind: str = "state") -> None:
247
+ self.ledger.add_status(label=label, status=status, detail=detail, kind=kind)
248
+ self._render_rows()
249
+
250
+ def _ensure_columns(self) -> None:
251
+ if self._configured:
252
+ return
253
+ self.show_row_labels = False
254
+ self.add_column("Step", key="step", width=6)
255
+ self.add_column("Kind", key="kind", width=10)
256
+ self.add_column("Target", key="label", width=18)
257
+ self.add_column("Status", key="status", width=10)
258
+ self.add_column("Detail", key="detail")
259
+ self._configured = True
260
+
261
+ def _render_rows(self) -> None:
262
+ self._ensure_columns()
263
+ self.clear(columns=False)
264
+ for row in self.ledger.rows:
265
+ self.add_row(
266
+ str(row.step),
267
+ row.kind,
268
+ row.label,
269
+ row.status,
270
+ row.detail,
271
+ key=f"trace-{row.step}",
272
+ )
273
+
274
+
275
+ class OrchestrationTable(DataTable[str]):
276
+ def __init__(self, *args, **kwargs) -> None:
277
+ super().__init__(*args, **kwargs)
278
+ self.ledger = OrchestrationLedger()
279
+ self.cursor_type = "none"
280
+ self.zebra_stripes = True
281
+ self._configured = False
282
+
283
+ def on_mount(self) -> None:
284
+ self._ensure_columns()
285
+
286
+ def configure_subagents(self, subagents: list[dict[str, str]]) -> None:
287
+ self.ledger.configure(subagents)
288
+ self._render_rows()
289
+
290
+ def reset(self) -> None:
291
+ self.ledger.configure([])
292
+ self._render_rows()
293
+
294
+ def record_event(self, event: StreamEvent) -> None:
295
+ self.ledger.record(event)
296
+ self._render_rows()
297
+
298
+ def record_status(self, name: str, status: str, detail: str) -> None:
299
+ self.ledger.record_status(name, status, detail)
300
+ self._render_rows()
301
+
302
+ def _ensure_columns(self) -> None:
303
+ if self._configured:
304
+ return
305
+ self.show_row_labels = False
306
+ self.add_column("Subagent", key="name", width=20)
307
+ self.add_column("Role", key="role", width=30)
308
+ self.add_column("Tools", key="tools", width=18)
309
+ self.add_column("Status", key="status", width=10)
310
+ self.add_column("Detail", key="detail")
311
+ self._configured = True
312
+
313
+ def _render_rows(self) -> None:
314
+ self._ensure_columns()
315
+ self.clear(columns=False)
316
+ for index, row in enumerate(self.ledger.rows, start=1):
317
+ self.add_row(
318
+ row.name,
319
+ row.role,
320
+ row.tools,
321
+ row.status,
322
+ row.detail,
323
+ key=f"orchestration-{index}",
324
+ )
325
+
326
+
327
+ def _merge_detail(current: str, new: str) -> str:
328
+ normalized_current = current.strip()
329
+ normalized_new = new.strip()
330
+ if not normalized_current:
331
+ return normalized_new
332
+ if not normalized_new or normalized_current == normalized_new:
333
+ return normalized_current
334
+ return f"{normalized_current} -> {normalized_new}"
agencli/tui/voice.py ADDED
@@ -0,0 +1,77 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import base64
5
+ import sys
6
+ from textwrap import dedent
7
+
8
+
9
+ VOICE_TIMEOUT_SECONDS = 15
10
+
11
+
12
+ def _powershell_executable() -> str:
13
+ return "powershell.exe" if sys.platform == "win32" else "pwsh"
14
+
15
+
16
+ def _encode_powershell(script: str) -> str:
17
+ return base64.b64encode(script.encode("utf-16-le")).decode("ascii")
18
+
19
+
20
+ def build_voice_capture_script(timeout_seconds: int = VOICE_TIMEOUT_SECONDS) -> str:
21
+ timeout_seconds = max(3, int(timeout_seconds))
22
+ return dedent(
23
+ f"""
24
+ $ErrorActionPreference = 'Stop'
25
+ [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
26
+ Add-Type -AssemblyName System.Speech
27
+
28
+ $culture = [System.Globalization.CultureInfo]::InstalledUICulture
29
+ try {{
30
+ $engine = New-Object System.Speech.Recognition.SpeechRecognitionEngine($culture)
31
+ }} catch {{
32
+ $engine = New-Object System.Speech.Recognition.SpeechRecognitionEngine('en-US')
33
+ }}
34
+
35
+ $engine.LoadGrammar((New-Object System.Speech.Recognition.DictationGrammar))
36
+ $engine.SetInputToDefaultAudioDevice()
37
+ $result = $engine.Recognize([TimeSpan]::FromSeconds({timeout_seconds}))
38
+
39
+ if ($null -eq $result -or [string]::IsNullOrWhiteSpace($result.Text)) {{
40
+ exit 0
41
+ }}
42
+
43
+ Write-Output $result.Text
44
+ """
45
+ ).strip()
46
+
47
+
48
+ async def transcribe_once(timeout_seconds: int = VOICE_TIMEOUT_SECONDS) -> str:
49
+ script = build_voice_capture_script(timeout_seconds=timeout_seconds)
50
+ encoded = _encode_powershell(script)
51
+ process = await asyncio.create_subprocess_exec(
52
+ _powershell_executable(),
53
+ "-NoLogo",
54
+ "-NoProfile",
55
+ "-NonInteractive",
56
+ "-EncodedCommand",
57
+ encoded,
58
+ stdout=asyncio.subprocess.PIPE,
59
+ stderr=asyncio.subprocess.PIPE,
60
+ )
61
+ try:
62
+ stdout, stderr = await process.communicate()
63
+ except asyncio.CancelledError:
64
+ if process.returncode is None:
65
+ process.terminate()
66
+ try:
67
+ await asyncio.wait_for(process.wait(), timeout=1.0)
68
+ except asyncio.TimeoutError:
69
+ process.kill()
70
+ await process.wait()
71
+ raise
72
+
73
+ if process.returncode not in (0, None):
74
+ error_text = (stderr or b"").decode("utf-8", errors="replace").strip()
75
+ raise RuntimeError(error_text or "Voice capture failed.")
76
+
77
+ return (stdout or b"").decode("utf-8", errors="replace").strip()
@@ -0,0 +1,44 @@
1
+ Metadata-Version: 2.4
2
+ Name: agencode
3
+ Version: 0.1.0
4
+ Summary: AgenCLI: a multi-agent terminal workspace built in Python.
5
+ Requires-Python: >=3.12
6
+ Requires-Dist: deepagents
7
+ Requires-Dist: httpx>=0.28.1
8
+ Requires-Dist: keyring>=25.6.0
9
+ Requires-Dist: langchain
10
+ Requires-Dist: langchain-mcp-adapters
11
+ Requires-Dist: langchain-openai
12
+ Requires-Dist: langgraph
13
+ Requires-Dist: langgraph-checkpoint-sqlite
14
+ Requires-Dist: rich>=14.1.0
15
+ Requires-Dist: textual>=6.2.1
16
+ Requires-Dist: tomli-w>=1.2.0
17
+ Requires-Dist: typer>=0.19.2
18
+ Description-Content-Type: text/markdown
19
+
20
+ # AgenCLI
21
+
22
+ AgenCLI is the Python reboot of the original agent CLI concept: a multi-agent terminal workspace with a Textual UI and a DeepAgents/LangGraph runtime.
23
+
24
+ This repo is now scaffolded as a proper `uv` project with:
25
+
26
+ - a package entrypoint at `agencode`
27
+ - `agencode tui` uses the current directory as the workspace by default
28
+ - a Typer CLI
29
+ - a minimal Textual app
30
+ - config and session directory bootstrapping
31
+ - basic agent registry and runtime stubs for the later DeepAgents integration
32
+ - CLI support for OpenAI-compatible `base_url`, `api_key`, `model`, and `model_kind`
33
+
34
+ ## Run
35
+
36
+ ```bash
37
+ uv sync
38
+ uv run agencode doctor
39
+ uv run agencode config-openai --base-url https://api.deepseek.com --model deepseek-chat --model-kind chat
40
+ uv run agencode tui
41
+
42
+ # Open a specific workspace explicitly
43
+ uv run agencode tui --workspace /path/to/project
44
+ ```
@@ -0,0 +1,39 @@
1
+ agencli/__init__.py,sha256=aIM0xbT8C85KKpWTJs7xEIEVhvfpLw0UCVqAm1Yyp1M,73
2
+ agencli/__main__.py,sha256=5zufvIUIZc3u_eknnGzLgjcf9_K1AKpfOnz_js_qlm8,100
3
+ agencli/cli.py,sha256=jAqFC40LmBbxXwb_WxrSJ3xhUITJTuprQeOus80E2oc,22733
4
+ agencli/agents/__init__.py,sha256=592-nFGEczCezV9HOuOHFy6v6xb3-F_e6HyNDaITLtU,51
5
+ agencli/agents/editor.py,sha256=2rYdef0Akvty5dxziujBbzNizedp2PMh_b6SWRPVEqE,3531
6
+ agencli/agents/factory.py,sha256=N__E4pHcjV9Qr4xH3Ot1unogRCgsTnccRSqiOcAJhZs,12404
7
+ agencli/agents/management_tools.py,sha256=IY-RwiWrm8pBFrUY01TCM6CrEmgsqWdclY4LuT9xw4k,10911
8
+ agencli/agents/registry.py,sha256=TsudkVwch6p5PcBURy7u7YBk3Svxie5DXCmbq86NyPc,1875
9
+ agencli/agents/runtime.py,sha256=uGSjxG-F6GMHfDHT-LfkgDe0H9VTTLzKyMk1eUpcbLI,8672
10
+ agencli/agents/supervisor.py,sha256=uhS3o_mdpePPe-tSfLYXdU_6N2cZSqdlw_LHr7vjUuw,4105
11
+ agencli/agents/prebuilt/__init__.py,sha256=shljfx-NVNf1JTVzdzyL-9hFrHrck8aquRSSwND5Tu8,34
12
+ agencli/agents/prebuilt/catalog.py,sha256=RMPKS0ZbKJxPwUC5gqtuieuDayi6aaxQgm-FTzVY4PA,3323
13
+ agencli/core/__init__.py,sha256=FbCxjjAZmG--aZIGwWuHZ3bBXlgZnhK_2Aalkzw6AAk,43
14
+ agencli/core/config.py,sha256=FCQD0R9yc2xHvBpp6YzLWaDGV8Qq1ALq3oXp5JAN6tY,6468
15
+ agencli/core/keystore.py,sha256=Rutx-vT-Z2V0yGZbSoxl9CjATes6TqkTcupOJzzKCv8,329
16
+ agencli/core/logger.py,sha256=tYLBtMn_ejZoHng2vzqZ1YtFFc4zvEWrQkiL4Ku5CNs,445
17
+ agencli/core/paths.py,sha256=QFxRBMFFLtohyPkdorhKjfA4w67Ukz4PRlX066sXrro,1044
18
+ agencli/core/session.py,sha256=fcycfQL9IT6UPT1GLfGrEaCGuLIC_WiaYjZOoCFdD38,16608
19
+ agencli/mcp/__init__.py,sha256=AdBHnJvMClCg8rLH5aby2v9W4ykVr5hKAadxpHfAY9o,44
20
+ agencli/mcp/client.py,sha256=mEqJfH7EGpbH8KYrYOd9MyDDFFXpS8PZwX5GtiDlGkQ,1186
21
+ agencli/mcp/config.py,sha256=SdT27hM_PJxaJ1oXh5T1k0KuRF3j6Tr-Ah78bGAjwiI,3252
22
+ agencli/providers/__init__.py,sha256=fPCpETqrBU6cejso3j_BKhH9_yYvPe3VfvPy1rRzCZk,32
23
+ agencli/providers/model.py,sha256=qlXB6HEOFEVPavoQsNNe_Q1oJq0dOjzlk6AbyneF9DY,6425
24
+ agencli/skills/__init__.py,sha256=Zfw_2JkkJo09jduwTh4y4SRRUsIU-LmkwNLkGVl9l_Y,952
25
+ agencli/skills/cli_backend.py,sha256=ApY7c2OypCMgDshmM66taW1X3xAopj6mQpy7WQOD8Jg,16166
26
+ agencli/skills/loader.py,sha256=BRhDNcPz93fFktyCe7pocmsqqWfpJBMhcN0QK6I5Cvc,2475
27
+ agencli/skills/manager.py,sha256=637GY1N4W6B2qOUm1TZW1XPgMZeYut2SSKn0z44TCtw,5701
28
+ agencli/tools/__init__.py,sha256=mVLTc6KR5PZ931StQuAf8ESGOnakszxFpYRSWgKlnv8,59
29
+ agencli/tools/mcp.py,sha256=jidkNX3IafHr60VXqzqrM_yDzbQwtSFpmPc65YJIaUI,3943
30
+ agencli/tui/__init__.py,sha256=tjeCtfeMyy9I0mVxRRbpi83cYVJ-p65lwJOGDyVWcMg,30
31
+ agencli/tui/app.py,sha256=IBHjBOYlqNoXjpRvqVzj0Ft3rX9BrWsMfEim5eBHAfs,181478
32
+ agencli/tui/commands.py,sha256=7rmbwkRSv1MFmW9iu5zHK849vv1RU9QQVkKMaBOlVk4,4485
33
+ agencli/tui/screens.py,sha256=RzBO0a4OcgszlQ2rY1T8qpV3uV79A4rVYkpHOwXChDo,35291
34
+ agencli/tui/trace.py,sha256=U3o1bMIMg_983SKv9kgirW6ybWezLu19l7k00v_aCKo,10806
35
+ agencli/tui/voice.py,sha256=I9wqQLw-og8dcK__lGd9Mc-wADIuIJ0tSSu1Sqb59Fg,2485
36
+ agencode-0.1.0.dist-info/METADATA,sha256=hOmP6T0VIsFMOgsuVq2afALphJ9jlFyIQEgmVNjh2Ys,1396
37
+ agencode-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
38
+ agencode-0.1.0.dist-info/entry_points.txt,sha256=WgOWlIZAEhNazwvFucLq_R3jlo9dFx4yQBBnJP7GLRo,83
39
+ agencode-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ agencli = agencli.__main__:main
3
+ agencode = agencli.__main__:main