ags-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.
Files changed (156) hide show
  1. ags_cli-0.1.0.dist-info/METADATA +332 -0
  2. ags_cli-0.1.0.dist-info/RECORD +156 -0
  3. ags_cli-0.1.0.dist-info/WHEEL +5 -0
  4. ags_cli-0.1.0.dist-info/entry_points.txt +2 -0
  5. ags_cli-0.1.0.dist-info/top_level.txt +2 -0
  6. cli/__init__.py +0 -0
  7. cli/__main__.py +4 -0
  8. cli/client.py +145 -0
  9. cli/commands/__init__.py +0 -0
  10. cli/commands/adapter.py +33 -0
  11. cli/commands/artifact.py +21 -0
  12. cli/commands/ask.py +212 -0
  13. cli/commands/chat.py +649 -0
  14. cli/commands/diff.py +49 -0
  15. cli/commands/doctor.py +93 -0
  16. cli/commands/gui.py +45 -0
  17. cli/commands/init.py +20 -0
  18. cli/commands/mcp.py +50 -0
  19. cli/commands/memory.py +65 -0
  20. cli/commands/model.py +73 -0
  21. cli/commands/policy.py +52 -0
  22. cli/commands/project.py +122 -0
  23. cli/commands/quota.py +107 -0
  24. cli/commands/run.py +84 -0
  25. cli/commands/serve.py +171 -0
  26. cli/commands/service.py +236 -0
  27. cli/commands/session.py +219 -0
  28. cli/commands/stats.py +96 -0
  29. cli/commands/status.py +36 -0
  30. cli/commands/task.py +37 -0
  31. cli/commands/usage.py +86 -0
  32. cli/local.py +84 -0
  33. cli/main.py +149 -0
  34. harness/__init__.py +4 -0
  35. harness/adapters/__init__.py +26 -0
  36. harness/adapters/antigravity.py +301 -0
  37. harness/adapters/claude_code.py +522 -0
  38. harness/adapters/local_openai.py +534 -0
  39. harness/adapters/mock.py +112 -0
  40. harness/adapters/probe.py +65 -0
  41. harness/adapters/registry.py +56 -0
  42. harness/adapters/warm_pool.py +255 -0
  43. harness/api/__init__.py +0 -0
  44. harness/api/router.py +38 -0
  45. harness/api/v1/__init__.py +0 -0
  46. harness/api/v1/adapters.py +105 -0
  47. harness/api/v1/approvals.py +173 -0
  48. harness/api/v1/artifacts.py +44 -0
  49. harness/api/v1/attachments.py +48 -0
  50. harness/api/v1/doctor.py +22 -0
  51. harness/api/v1/health.py +37 -0
  52. harness/api/v1/mcp.py +131 -0
  53. harness/api/v1/memory.py +80 -0
  54. harness/api/v1/policies.py +49 -0
  55. harness/api/v1/project.py +302 -0
  56. harness/api/v1/runs.py +98 -0
  57. harness/api/v1/sessions.py +248 -0
  58. harness/api/v1/stream.py +110 -0
  59. harness/api/v1/tasks.py +30 -0
  60. harness/api/v1/usage.py +58 -0
  61. harness/bootstrap.py +216 -0
  62. harness/db.py +164 -0
  63. harness/deps.py +58 -0
  64. harness/eventbus/__init__.py +20 -0
  65. harness/eventbus/inprocess_bus.py +85 -0
  66. harness/main.py +144 -0
  67. harness/mcp/__init__.py +22 -0
  68. harness/mcp/client.py +139 -0
  69. harness/mcp/config_gen.py +77 -0
  70. harness/mcp/registry.py +156 -0
  71. harness/mcp/tools.py +81 -0
  72. harness/memory/__init__.py +13 -0
  73. harness/memory/context_budget.py +116 -0
  74. harness/memory/embed.py +217 -0
  75. harness/memory/extract.py +74 -0
  76. harness/memory/ingest.py +106 -0
  77. harness/memory/namespaces.py +57 -0
  78. harness/memory/ranking.py +31 -0
  79. harness/memory/redact.py +37 -0
  80. harness/memory/service.py +320 -0
  81. harness/memory/sqlite_vec_backend.py +266 -0
  82. harness/memory/summarize.py +68 -0
  83. harness/models/__init__.py +35 -0
  84. harness/models/adapter_health.py +27 -0
  85. harness/models/approval.py +37 -0
  86. harness/models/artifact.py +35 -0
  87. harness/models/audit_log.py +33 -0
  88. harness/models/comparison.py +33 -0
  89. harness/models/enums.py +146 -0
  90. harness/models/mcp_server_config.py +49 -0
  91. harness/models/memory.py +76 -0
  92. harness/models/policy.py +29 -0
  93. harness/models/provider_config.py +35 -0
  94. harness/models/run.py +46 -0
  95. harness/models/run_event.py +35 -0
  96. harness/models/session.py +50 -0
  97. harness/models/task.py +30 -0
  98. harness/models/types.py +63 -0
  99. harness/models/workspace.py +27 -0
  100. harness/observability/__init__.py +4 -0
  101. harness/observability/audit.py +88 -0
  102. harness/observability/logging.py +47 -0
  103. harness/observability/metrics.py +43 -0
  104. harness/observability/tracing.py +38 -0
  105. harness/orchestrator/__init__.py +19 -0
  106. harness/orchestrator/engine.py +821 -0
  107. harness/orchestrator/handoff.py +33 -0
  108. harness/orchestrator/lifecycle.py +69 -0
  109. harness/orchestrator/native_resume.py +93 -0
  110. harness/orchestrator/policies.py +101 -0
  111. harness/orchestrator/reconcile.py +39 -0
  112. harness/orchestrator/run_graph.py +62 -0
  113. harness/orchestrator/snapshot.py +49 -0
  114. harness/schemas/__init__.py +1 -0
  115. harness/schemas/adapter.py +26 -0
  116. harness/schemas/approval.py +25 -0
  117. harness/schemas/artifact.py +17 -0
  118. harness/schemas/common.py +20 -0
  119. harness/schemas/memory.py +50 -0
  120. harness/schemas/policy.py +39 -0
  121. harness/schemas/run.py +116 -0
  122. harness/schemas/session.py +93 -0
  123. harness/schemas/task.py +24 -0
  124. harness/security/__init__.py +5 -0
  125. harness/security/approval_policy.py +107 -0
  126. harness/security/auth.py +106 -0
  127. harness/security/permissions.py +59 -0
  128. harness/security/risk.py +59 -0
  129. harness/security/secrets.py +49 -0
  130. harness/services/__init__.py +1 -0
  131. harness/services/adapters_health.py +212 -0
  132. harness/services/approvals.py +84 -0
  133. harness/services/artifacts.py +78 -0
  134. harness/services/attachments.py +228 -0
  135. harness/services/comparison.py +345 -0
  136. harness/services/doctor.py +156 -0
  137. harness/services/files.py +287 -0
  138. harness/services/mcp_servers.py +179 -0
  139. harness/services/project_git.py +101 -0
  140. harness/services/retention.py +97 -0
  141. harness/services/runs.py +155 -0
  142. harness/services/runs_diff.py +201 -0
  143. harness/services/sessions.py +242 -0
  144. harness/services/ssh.py +300 -0
  145. harness/services/stats.py +132 -0
  146. harness/services/tasks.py +41 -0
  147. harness/services/workspaces.py +184 -0
  148. harness/services/worktrees.py +439 -0
  149. harness/settings.py +186 -0
  150. harness/skills/__init__.py +11 -0
  151. harness/skills/manager.py +141 -0
  152. harness/tools/__init__.py +1 -0
  153. harness/tools/fs_tools.py +295 -0
  154. harness/workers/__init__.py +0 -0
  155. harness/workers/dispatch.py +26 -0
  156. harness/workers/inprocess.py +135 -0
@@ -0,0 +1,332 @@
1
+ Metadata-Version: 2.4
2
+ Name: ags-cli
3
+ Version: 0.1.0
4
+ Summary: AgentS: one unified interface over Claude Code, Google CLI (agy), and OpenAI-compatible local LLMs.
5
+ Author: Platform Engineering
6
+ License: Apache-2.0
7
+ Requires-Python: >=3.12
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: fastapi>=0.115
10
+ Requires-Dist: uvicorn[standard]>=0.32
11
+ Requires-Dist: pydantic>=2.9
12
+ Requires-Dist: pydantic-settings>=2.5
13
+ Requires-Dist: python-dotenv>=1.0
14
+ Requires-Dist: sqlalchemy[asyncio]>=2.0.35
15
+ Requires-Dist: aiosqlite>=0.20
16
+ Requires-Dist: httpx>=0.27
17
+ Requires-Dist: typer>=0.12
18
+ Requires-Dist: rich>=13.9
19
+ Requires-Dist: structlog>=24.4
20
+ Requires-Dist: prometheus-client>=0.21
21
+ Requires-Dist: opentelemetry-api>=1.27
22
+ Requires-Dist: opentelemetry-sdk>=1.27
23
+ Requires-Dist: opentelemetry-instrumentation-fastapi>=0.48b0
24
+ Requires-Dist: pyyaml>=6.0
25
+ Requires-Dist: mcp>=1.2
26
+ Requires-Dist: python-multipart>=0.0.12
27
+ Requires-Dist: bcrypt>=4.2
28
+ Requires-Dist: pyjwt>=2.9
29
+ Requires-Dist: ags-sdk>=0.1.0
30
+ Provides-Extra: embed
31
+ Requires-Dist: fastembed>=0.4; extra == "embed"
32
+ Provides-Extra: vec
33
+ Requires-Dist: sqlite-vec>=0.1.6; extra == "vec"
34
+ Provides-Extra: dev
35
+ Requires-Dist: pytest>=8.3; extra == "dev"
36
+ Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
37
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
38
+ Requires-Dist: anyio>=4.6; extra == "dev"
39
+ Requires-Dist: ruff>=0.7; extra == "dev"
40
+ Requires-Dist: mypy>=1.13; extra == "dev"
41
+ Requires-Dist: respx>=0.21; extra == "dev"
42
+
43
+ # AgentS
44
+
45
+ One agent over many AI coding agents. AgentS gives you a single interface
46
+ over **Claude Code**, **Antigravity** (Google's `agy`), and any
47
+ **OpenAI-compatible local LLM** (Ollama / LM Studio / vLLM / OpenRouter / …).
48
+ It keeps a shared, vendor-neutral **memory** and an append-only **run ledger**
49
+ in your own database, so the same task can run on one provider or several, be
50
+ compared and merged, and pick up where another agent left off — without sharing
51
+ vendor credentials.
52
+
53
+ # Install & Getting Started
54
+
55
+ AgentS ships as a single desktop application for **Windows**, **macOS**, and
56
+ **Linux**. This guide covers installing the app, satisfying its two
57
+ prerequisites (the **Antigravity** and **Claude Code** CLIs), fixing the most
58
+ common startup problem (agents not found on `PATH`), and taking your first
59
+ steps in the GUI.
60
+
61
+ > This guide covers the **GUI (desktop) application only**. The desktop app is
62
+ > self-contained: it bundles the AgentS engine and starts it for you in the
63
+ > background — there is nothing else to install or run from a terminal.
64
+
65
+ ---
66
+
67
+ ## 1. Prerequisites
68
+
69
+ AgentS is an orchestrator: it drives the **official Antigravity and Claude Code
70
+ CLIs** as subprocesses. It does **not** ship, replace, or embed them, and it
71
+ never reads, stores, or reuses your credentials — each agent uses its own login.
72
+
73
+ Before installing AgentS, both agent binaries must be **installed** *and*
74
+ **authenticated** on your machine:
75
+
76
+ | Agent | Binary | Install | Authenticate | Verify |
77
+ |---|---|---|---|---|
78
+ | **Claude Code** | `claude` | `npm i -g @anthropic-ai/claude-code` (or the official installer) | Run `claude` once and log in (or set `ANTHROPIC_API_KEY`) | `claude --version` |
79
+ | **Antigravity** | `agy` | Follow Google's official Antigravity install guide | Run `agy` once and sign in (or set `ANTIGRAVITY_API_KEY`) | `agy --version` |
80
+
81
+ Both checks must succeed in a terminal **before** you launch AgentS:
82
+
83
+ ```bash
84
+ claude --version # e.g. 1.x.x
85
+ agy --version # e.g. 0.x.x
86
+ ```
87
+
88
+ If either command prints a version but the agent later fails **inside AgentS**,
89
+ it is almost always a `PATH` problem — see [§4](#4-fixing-path-issues-agents-not-found)
90
+ below.
91
+
92
+ > You only need the agent(s) you intend to use. If you install just `claude`,
93
+ > the Claude Code agent works and Antigravity simply shows as unavailable (and
94
+ > vice-versa).
95
+
96
+ ---
97
+
98
+ ## 2. Download
99
+
100
+ Download the installer for your operating system from the
101
+ **[latest release](https://github.com/farookmfk/AgentS/releases/latest)**:
102
+
103
+ | OS | Download | Notes |
104
+ |---|---|---|
105
+ | **Windows** | `AgentS_<version>_x64-setup.exe` (or the `.msi`) | 64-bit Windows 10/11 |
106
+ | **macOS** | `AgentS_<version>_aarch64.dmg` | Apple Silicon (M-series) |
107
+ | **Linux** | `AgentS_<version>_amd64.AppImage` or `.deb` | AppImage runs anywhere; `.deb` for Debian/Ubuntu |
108
+
109
+ ---
110
+
111
+ ## 3. Install
112
+
113
+ ### Windows
114
+
115
+ 1. Run the downloaded `.exe` (or `.msi`) installer and follow the prompts.
116
+ 2. Launch **AgentS** from the Start menu.
117
+ 3. If SmartScreen shows a "Windows protected your PC" dialog, click
118
+ **More info → Run anyway** (the app is signed on official releases; this can
119
+ appear on first launch).
120
+
121
+ ### macOS
122
+
123
+ 1. Open the `.dmg` and drag **AgentS** into **Applications**.
124
+ 2. Launch it from **Applications** (or Spotlight).
125
+ 3. Official releases are signed and notarized, so Gatekeeper clears the app on
126
+ first launch (an online check — the first launch needs network). If a
127
+ download is still flagged, clear the quarantine attribute once:
128
+ ```bash
129
+ xattr -d com.apple.quarantine /Applications/AgentS.app
130
+ ```
131
+
132
+ ### Linux
133
+
134
+ **AppImage** (no install — runs anywhere):
135
+
136
+ ```bash
137
+ chmod +x AgentS_*_amd64.AppImage
138
+ ./AgentS_*_amd64.AppImage
139
+ ```
140
+
141
+ **Debian / Ubuntu (`.deb`):**
142
+
143
+ ```bash
144
+ sudo apt install ./AgentS_*_amd64.deb
145
+ # then launch "AgentS" from your app menu, or run:
146
+ agents
147
+ ```
148
+
149
+ On first launch AgentS creates its local state under `~/.ags/`
150
+ (`%USERPROFILE%\.ags\` on Windows) — a configuration file, a SQLite database,
151
+ and logs. Everything stays on your machine; there are no daemons or servers to
152
+ manage.
153
+
154
+ ---
155
+
156
+ ## 4. Fixing PATH issues (agents not found)
157
+
158
+ **The single most common problem.** Everything works in your terminal, but
159
+ inside AgentS the Claude Code or Antigravity agent shows as unavailable or its
160
+ runs fail with something like *"binary not on PATH"*.
161
+
162
+ **Why it happens:** a desktop app launched from the Start menu, the macOS Dock,
163
+ or an app menu does **not** inherit the `PATH` from your shell startup files
164
+ (`.bashrc`, `.zshrc`, `.profile`). So agents installed to a non-standard
165
+ location — npm's global prefix, `nvm`, Homebrew on Apple Silicon
166
+ (`/opt/homebrew/bin`), a custom `~/.local/bin`, etc. — are visible in your
167
+ terminal but invisible to AgentS.
168
+
169
+ You have two ways to fix it. **Either one** works — pick whichever you prefer.
170
+
171
+ ### Option A — Put the agents on a system-wide PATH
172
+
173
+ First, find where each agent actually lives:
174
+
175
+ ```bash
176
+ # macOS / Linux
177
+ which claude
178
+ which agy
179
+
180
+ # Windows (PowerShell)
181
+ where.exe claude
182
+ where.exe agy
183
+ ```
184
+
185
+ Then make that location visible to GUI apps:
186
+
187
+ - **macOS / Linux** — symlink (or move) each binary into a standard system
188
+ directory that GUI apps always see:
189
+ ```bash
190
+ sudo ln -sf "$(which claude)" /usr/local/bin/claude
191
+ sudo ln -sf "$(which agy)" /usr/local/bin/agy
192
+ ```
193
+ - **Windows** — add the folder that contains `claude.exe` / `agy.exe` (or the
194
+ npm global folder, e.g. `%APPDATA%\npm`) to the **System** `PATH`:
195
+ *Settings → System → About → Advanced system settings → Environment
196
+ Variables → System variables → Path → Edit → New*. Then **log out and back
197
+ in** so the desktop environment picks up the change.
198
+
199
+ ### Option B — Set the full path explicitly in `config.yaml`
200
+
201
+ If you'd rather not touch your system `PATH`, tell AgentS exactly where each
202
+ binary is. Edit the config file:
203
+
204
+ | OS | Config file |
205
+ |---|---|
206
+ | macOS / Linux | `~/.ags/config.yaml` |
207
+ | Windows | `%USERPROFILE%\.ags\config.yaml` |
208
+
209
+ Find the `providers:` section and set each agent's `params.bin` to the **full
210
+ absolute path** you got from `which` / `where.exe` above:
211
+
212
+ ```yaml
213
+ providers:
214
+ - name: claude
215
+ kind: claude_code
216
+ enabled: true
217
+ model: claude-opus-4-8
218
+ params:
219
+ bin: /usr/local/bin/claude # ← full path instead of just "claude"
220
+ permission_mode: plan
221
+ timeout_s: 600
222
+
223
+ - name: antigravity
224
+ kind: antigravity
225
+ enabled: true
226
+ model: gemini-1.5-pro
227
+ params:
228
+ bin: /opt/homebrew/bin/agy # ← full path instead of just "agy"
229
+ pty: true
230
+ timeout_s: 600
231
+ ```
232
+
233
+ **Windows path note:** use the full path including the extension, and prefer
234
+ forward slashes or a quoted string, e.g.
235
+ `bin: "C:/Users/you/AppData/Roaming/npm/claude.cmd"`. npm-installed CLIs on
236
+ Windows are usually `claude.cmd` / `agy.cmd`, not `.exe`.
237
+
238
+ ### After either fix
239
+
240
+ **Fully quit and reopen AgentS** so it restarts its engine and re-reads the
241
+ config. Then confirm the agents are healthy in the GUI:
242
+
243
+ - Open **Settings → Adapters** and click **Test** next to `claude` and
244
+ `antigravity`. A green result means the binary was found and responded.
245
+
246
+ If it still fails, check the diagnostic logs for the exact error:
247
+
248
+ - `~/.ags/sidecar.log` — did the engine start? (`%USERPROFILE%\.ags\sidecar.log`)
249
+ - `~/.ags/server.log` — runtime errors from the engine.
250
+
251
+ ---
252
+
253
+ ## 5. Getting started with the GUI
254
+
255
+ When AgentS opens you get a dashboard with a left sidebar:
256
+ **Dashboard**, **New session**, **Sessions**, and a **Settings** group
257
+ (**Project selection**, **Remote hosts**, **Adapters**, **MCP**, **Usage**,
258
+ **Approvals**).
259
+
260
+ ### 5.1 Add a project (local or remote)
261
+
262
+ Agents work inside a **project folder**. Set it under
263
+ **Settings → Project selection**:
264
+
265
+ - **Local** — keep the **Local** tab selected and browse to the folder on this
266
+ machine you want agents to read and edit. Click a folder to choose it.
267
+ - **Remote** — run agents against a folder on another machine over SSH:
268
+ 1. First save the machine under **Settings → Remote hosts**: give it a label
269
+ and an SSH target (e.g. `user@build-box`). AgentS uses your existing
270
+ `~/.ssh` config, keys, and agent. The agent CLI (`claude` / `agy`) must be
271
+ installed **on the remote host** too. Use **Test** to verify the
272
+ connection and that the CLI is present.
273
+ 2. Back on **Project selection**, switch to the **Remote** tab, pick the saved
274
+ host, and browse to the folder on that host.
275
+
276
+ Changes apply to the **next turn** — no restart needed. The current project
277
+ (and whether it's local or remote) is shown at the top of the page.
278
+
279
+ ### 5.2 Start a session — pick an agent and model
280
+
281
+ Click **New session** in the sidebar:
282
+
283
+ 1. **Agent** — choose **Antigravity** (`antigravity`) or **Claude Code**
284
+ (`claude`) from the dropdown. Only installed, healthy agents appear here.
285
+ 2. **Model (optional override)** — pick a specific model for the chosen agent
286
+ (e.g. `claude-opus-4-8` for Claude Code, `gemini-1.5-pro` for Antigravity), or
287
+ leave it to use the agent's default.
288
+ 3. **First message** — type your task (e.g. *"Audit the cache layer and propose
289
+ the safest fix"*). You can also drag-and-drop or paste files to attach them.
290
+ 4. **Mode** — **Plan** (read-only; the agent proposes but doesn't write) is the
291
+ default. Switch to **Edit** to let the agent modify files.
292
+ 5. **Private workspace** — leave checked to work on an isolated git branch and
293
+ merge back when you're happy; uncheck to edit the project folder directly.
294
+ 6. Click **Start session** (or press ⌘/Ctrl+Enter).
295
+
296
+ Responses stream live with Markdown rendering, and tool calls (file reads,
297
+ edits, commands) show compactly as the agent works. You can switch the active
298
+ agent mid-conversation from within the session.
299
+
300
+ ### 5.3 Add MCP servers (external tools)
301
+
302
+ MCP (Model Context Protocol) servers give agents extra tools — filesystems,
303
+ GitHub, databases, and more. Manage them under **Settings → MCP**:
304
+
305
+ 1. Click **Add server** and choose a transport:
306
+ - **stdio** — a local command, e.g. command `npx` with args
307
+ `-y @modelcontextprotocol/server-filesystem /path/to/project`.
308
+ - **http** — a remote endpoint URL, e.g.
309
+ `https://api.githubcopilot.com/mcp/`. Provide the token via an environment
310
+ variable reference (stored as a `0600` secret file, never inline).
311
+ 2. Optionally set a **tool allowlist** (empty = all tools) and mark the server
312
+ **read-only** so its tools are offered even in Plan mode.
313
+ 3. **Enable** the server, then click **Test** to connect and list the tools it
314
+ advertises. Tools appear to agents namespaced as `mcp__<server>__<tool>`.
315
+
316
+ Changes take effect immediately on the next run — no restart required.
317
+
318
+ ---
319
+
320
+ ## 6. Where things live
321
+
322
+ | Path | What it is |
323
+ |---|---|
324
+ | `~/.ags/config.yaml` | Providers (agents), models, MCP servers, memory settings |
325
+ | `~/.ags/harness.db` | Local SQLite database (sessions, run ledger, memory) |
326
+ | `~/.ags/sidecar.log` | Desktop-app ↔ engine startup diagnostics |
327
+ | `~/.ags/server.log` | Engine runtime log |
328
+
329
+ On Windows these live under `%USERPROFILE%\.ags\`.
330
+
331
+ To fully reset AgentS, quit the app and delete the `~/.ags` folder — it is
332
+ recreated with defaults on the next launch.
@@ -0,0 +1,156 @@
1
+ cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ cli/__main__.py,sha256=dPjp912dL0OMqmqWZyWm7H14z8nq0epiRI6Tr2IadeY,63
3
+ cli/client.py,sha256=lFOgyuVloEcIQfrrEnmyXUMhtf_LctrVEGqDtlo87iY,6418
4
+ cli/local.py,sha256=C1nw0kcfwTKFZOUCz-Ug-7wXdcfgTTgImgvmFRkkNk8,3048
5
+ cli/main.py,sha256=RJAgv0pdMcYqH9N79ZULloFFXyzypZVMGGsMiXaNTXY,4819
6
+ cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ cli/commands/adapter.py,sha256=ND5LiZTX-ZLs09t_5DOG0unXCeEAIdo8qX4sZ9eQjl8,1092
8
+ cli/commands/artifact.py,sha256=VylM1duKGJmLFPYBxwEC0g_URXrrjSRlxlAQnip4wxU,678
9
+ cli/commands/ask.py,sha256=q2VoLDsv_MQEnDnPRw-ogxspUG9dn3gJSPpYy70raoY,7898
10
+ cli/commands/chat.py,sha256=dcjvDjdDy7zNvpxIjcJGpQzajnAFOQ2O8OsIc0QMogw,31066
11
+ cli/commands/diff.py,sha256=NXGYmTcglm_iVuuprzXcYosIWxf_8ZmtzYxQ766bfXc,1568
12
+ cli/commands/doctor.py,sha256=QQxGf64s5etd0qysuQzvU2vVLogoTFT0rS_WqojmPG0,2776
13
+ cli/commands/gui.py,sha256=1ZUtQKzVmCSECij1qxqERr4rGtEX-9k0G983dylwHrA,1340
14
+ cli/commands/init.py,sha256=_RC-ObHw0pzRMvDOUeH_K4ZjTuCfFEqtCXSYKLm8HCs,648
15
+ cli/commands/mcp.py,sha256=-6LFZ4amFGgZ1xS88B00N98InWFkPoM8Tfxgnevkgtw,1700
16
+ cli/commands/memory.py,sha256=R_8JW_d4aHAZbxCZ1N9G_FBGsPjFgU9ZtWB29a-KgZo,2307
17
+ cli/commands/model.py,sha256=QtEGXKlJY0Qw3MsPkcmvcoJ7lO858snBIya5t36Si_I,2932
18
+ cli/commands/policy.py,sha256=3vRzfy5Gp9dKeJKSwra9FM5JAHgzaYssLHQi3RErRJk,1604
19
+ cli/commands/project.py,sha256=ki1MehVU3Nt0JhGgbzsGFUZPDbBn-eJ3hK71KTIrg5Q,4866
20
+ cli/commands/quota.py,sha256=z8nAsG1X5845g6qFfMMwvKdP-XwviYkKNKly_cL2N_A,4344
21
+ cli/commands/run.py,sha256=JHEFRC3IuEJZ5hBQlwnGqNx9eKcJxFwuiIGLEoadIK4,3165
22
+ cli/commands/serve.py,sha256=gmV89t3Iro87QyvibELjyM450X425LuNReojtkFxfTQ,8211
23
+ cli/commands/service.py,sha256=1anfl19V_PXKFdqLngdZZtpOJGQe4BlGBQdHVXDz5-g,7645
24
+ cli/commands/session.py,sha256=VvUI46PXNDITEYGsIJMveIB8HRhkxJGB3GN1WR7p8Go,9067
25
+ cli/commands/stats.py,sha256=JVe7JpYSmLvllGrj1Vvl6fIN-hPSHDfU0TT1CeXvvD4,3406
26
+ cli/commands/status.py,sha256=FchjFH5S_MfGW6OkwFAk0S2BTmtRABfVyY5SdySlvF0,1381
27
+ cli/commands/task.py,sha256=w6hebmqsmGA1KfhvPRwOpxaRx6UtZ_pMXytvri5vWgw,1109
28
+ cli/commands/usage.py,sha256=EAaDAvaIHPvYE6DZiu4mszhQR1rb8NJCKdm8tFQy7JU,2630
29
+ harness/__init__.py,sha256=lppaVKQ-IoXDncVdCB7EEX2n9aPjjPkTcJ8rkjE96xc,163
30
+ harness/bootstrap.py,sha256=LLNTXXKXAQ-LILUk0tGbkgxJZ3_xQzrpHoUizkbUZNY,9014
31
+ harness/db.py,sha256=KU_IXyG9lWpCAa1T_SWqnq5gPuREeReLh7BrZTn8jsk,6007
32
+ harness/deps.py,sha256=bYTmGfcgF0yfO6WsZkdIsFbZRCGMdiiAJvq53UY7USg,1860
33
+ harness/main.py,sha256=oodk4KDWfClQ0FldOZkrq5TRR7PpHoS_yLMWqx44q-U,5138
34
+ harness/settings.py,sha256=OV9GcHIepuaYM15WxW-6T4tFAVcrBCj8EbBu9uMBAjg,7356
35
+ harness/adapters/__init__.py,sha256=pFxdj9YlkBbAuisPo7LlhFNSMkJA8S54Qd1RZTw7gV4,492
36
+ harness/adapters/antigravity.py,sha256=M7KckHTwtnoyyoA4sX3kKnk_FVcIoJWgpe989bK1br8,14436
37
+ harness/adapters/claude_code.py,sha256=-e30Klf3CIWYYNY3UOk77LlFqQd_f-PE-WERJjGXvrw,24108
38
+ harness/adapters/local_openai.py,sha256=PeEruIQvx17JmPtr3WqbqKT64VRZIPXQBtb15DPZ-6Q,23929
39
+ harness/adapters/mock.py,sha256=6pcufTGbGWU-gfB08A67AzGAlVuMtRnwRgi7hEQvGXU,3616
40
+ harness/adapters/probe.py,sha256=XOgeouVF2hEaJ6J3BwMBWoZc8sumTC3nEPa0C5EaUxU,2156
41
+ harness/adapters/registry.py,sha256=myyAj6t2wvMY3ew5G7Aep-WHp85TafbCzEtMkW24-Sc,2197
42
+ harness/adapters/warm_pool.py,sha256=QsQASV0XPsVm2Ik5ktOJCjELYHBFCN9yaCa9o3x194g,10838
43
+ harness/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
+ harness/api/router.py,sha256=cUMgvrdorx6xJs-Pzl16cie4eiml2ufcCayqsVriJlk,957
45
+ harness/api/v1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
+ harness/api/v1/adapters.py,sha256=MAEWYceTtYSm-2e4eDM6B4EFgYXMCXUFNTGp0I-BCJg,3499
47
+ harness/api/v1/approvals.py,sha256=w_nyR1xxRy_XZHBSnR1IjkJCa0g9_WRHB6KnBSmZDIY,7665
48
+ harness/api/v1/artifacts.py,sha256=oaHrJunmQj9pkb4PnH5OkCzNzSjnkEQD-yQ6oN8INqQ,1782
49
+ harness/api/v1/attachments.py,sha256=1ASOedzwn3zOhYA-qE2JyMQ_iyiyYbq2nLvddhB-Kyo,1652
50
+ harness/api/v1/doctor.py,sha256=xcYDilo4CXI6Pcn-327zCYvqT4Wrq2xb8c0YCBE3seU,764
51
+ harness/api/v1/health.py,sha256=Z3DDivINA1-Wq3AgaAAvPDDsGkPALafMEkUegdcrdTU,1295
52
+ harness/api/v1/mcp.py,sha256=28SsCIzD21i1Wk8w1-4CfQIlypwo3Gxb-bdwGmlGJJI,4220
53
+ harness/api/v1/memory.py,sha256=nzn3MrdX2sdihAGJgy_DHXciRDXjVsCjr2FuWDs5wQI,2740
54
+ harness/api/v1/policies.py,sha256=jov7dqaL81aOMVLRrEbJsLWlInNhyN_rPMe1U3wUDsk,1992
55
+ harness/api/v1/project.py,sha256=zJeVOyo5BY-wKB-35FosEiOsryZaDzUSitbP1-b4opU,11127
56
+ harness/api/v1/runs.py,sha256=iNBt7tcxoBqXL01ubZyKg0086ndWNR3kD-uEN-uhq5w,3367
57
+ harness/api/v1/sessions.py,sha256=kCd0lqeizwubiDbE6Zz1Ue3DZGe1Vu7-Ri393Q9_rMQ,9727
58
+ harness/api/v1/stream.py,sha256=cxAe-DQx0lje4nk2d06aIzMxlvUmqKXNRLNF6jplrxI,3846
59
+ harness/api/v1/tasks.py,sha256=k_6183jcO-uNpDR3ZWe2fdwJUIhsi6UwkqoZZgi4w5g,1086
60
+ harness/api/v1/usage.py,sha256=_a6cm1iUD5NXDCutTamaYZKPMfoz1jgqSmP8pmJCCCk,2289
61
+ harness/eventbus/__init__.py,sha256=vGZZxrANu8TB2kZJ23S71A6EhNrNE7tPbeaX9EeTiwA,465
62
+ harness/eventbus/inprocess_bus.py,sha256=ym_GCTMagyHF--8yu8vJjnjeEee5ObqAzf0B9HyNLMY,3013
63
+ harness/mcp/__init__.py,sha256=nsZJ31jRmvwoULSd5HnHdNzhFqjKA4to6sGFM0p4xuM,746
64
+ harness/mcp/client.py,sha256=2WS7imUSYbNoCpni-U9-32Ow4uKgMb4LkpsPTFeGshE,5667
65
+ harness/mcp/config_gen.py,sha256=3vlcOYhVrhkqWTcaQ_w2OSXGOKxSkUkGsiwYXXPQgLE,2966
66
+ harness/mcp/registry.py,sha256=yHMfUOSYJsGe43mtGFMCzl9b_s1z-RMTPuBa2cCvsUo,6342
67
+ harness/mcp/tools.py,sha256=rMojkFxcFtLlp76_GKOBtwh_eSYcxRxVg5Mhcf7rxdE,2806
68
+ harness/memory/__init__.py,sha256=E75V_vRCajkSetcCBcXYyA9Fl-q_HJ4vFbfHTZwr02w,398
69
+ harness/memory/context_budget.py,sha256=wUmEH-lAjJdoB8-py6aRWIdGpG4oW6hu1cz4EAvSBdQ,4354
70
+ harness/memory/embed.py,sha256=2NMt1nvqnCh2F5tSKR55V9uqIPhrqeBoaoml_moWkY0,8297
71
+ harness/memory/extract.py,sha256=QDE5TIlE3b46_iDrFOTolYlryR2vtSmsUoezB4JjISk,2756
72
+ harness/memory/ingest.py,sha256=VQlxsqvk6Tio2bg8j-211BuRQKLoIRyb-meqCnbhmx8,3424
73
+ harness/memory/namespaces.py,sha256=yydks9VKBigfOfuqL6A3H5ieQylq3EAVuodZY1ipSho,1971
74
+ harness/memory/ranking.py,sha256=ek_1Go6fopvVrEVCdjFYm2-Dx70RH9odpeVXrsqtt-c,1047
75
+ harness/memory/redact.py,sha256=2tciIKRYtpP8CURPRAv62Q4jpMV5Fvi3DCt_SmUmsEI,1505
76
+ harness/memory/service.py,sha256=Ph17Mgf0BYC7tGwy86oahLvM_CpeRq2BWQ7lXeDGBR0,12893
77
+ harness/memory/sqlite_vec_backend.py,sha256=ftadqBOqkvbZcVTHN_h8iWMCMQQc5YgSMiO1x0WJJhE,10465
78
+ harness/memory/summarize.py,sha256=56HkdW5kFxtgutV-u8FVmHs5fQ93FZ28lqNxsUnwkgE,2381
79
+ harness/models/__init__.py,sha256=K1KDTuKrreYl52jrWqRR_qjNKEY2xeLTFexnJiHLNMM,1096
80
+ harness/models/adapter_health.py,sha256=NmtthKuIeQFnSl4kTPddNdA9lc66IDm0qBgWy7IhMBw,942
81
+ harness/models/approval.py,sha256=kpYEF4TnAzH1_b_u75tomHfpAbTSypcYORMcEOw8y2U,1476
82
+ harness/models/artifact.py,sha256=Y3SoK4gy6qEpfw55aqUbiVuTsPbjqAu5jQmn0Whteh0,1380
83
+ harness/models/audit_log.py,sha256=KqihxlbZ8i0U0Olpg1-j5DOIgR5JAB1-teWEAhZgPPI,1268
84
+ harness/models/comparison.py,sha256=olKQKL-VlIwVEqroe51H08SiAj6dSUv8hhFTb1lu8Dc,1218
85
+ harness/models/enums.py,sha256=slOOVj96JgJbZg68FyvTTn3xRw6K6hm9e1fs-oSrv6Q,3559
86
+ harness/models/mcp_server_config.py,sha256=hH8cOje_12QreusU_lE17mIwR9N0IJ27XfoiQ4oSSWQ,2429
87
+ harness/models/memory.py,sha256=l9QdR2GuvtVVt7R9j4-T-XML_pLKv_0f3g2kjhL39_Q,2759
88
+ harness/models/policy.py,sha256=361TzNd9NvDOEILHbrSd5PrtKodIOwSslLHuTOrh3VY,1058
89
+ harness/models/provider_config.py,sha256=xfrWhVPjkUg13RFSEOeHFWK4_rlLa9rlQrjBSB8386Y,1620
90
+ harness/models/run.py,sha256=f0XeDlD3S4AOZbudiQK7gmDFOJFSAcgeOqccDYPgDJE,2057
91
+ harness/models/run_event.py,sha256=HyAiWoG_O6DH44AsBG0KLcaFX1HFNoWb7dC95D7cvAk,1285
92
+ harness/models/session.py,sha256=mJcfQGrrrZy6QDOsXjSU1Ug_034y4pPPwuAE21w2nLc,2221
93
+ harness/models/task.py,sha256=gknnwYVviS-aB5VAda83GOMui8MEtgCPHjnzxFaLEDM,1047
94
+ harness/models/types.py,sha256=Tzk07kPbPMFyGWXmllQEw8fKRupoTWUALmecj8xehzg,1758
95
+ harness/models/workspace.py,sha256=Koe_eUJ-KSgt27cN97RTRPR2gRoLIYAGKcZVli6NCX8,946
96
+ harness/observability/__init__.py,sha256=JU3tP_e1z5bAGu2HaZlA5kRo1d7Y1byjd9Ktu3a8TGM,216
97
+ harness/observability/audit.py,sha256=9437e_uE00qI69jKWpLAR-XhkmCDTzgz-c0Xo1IFLJw,2506
98
+ harness/observability/logging.py,sha256=R-8Ts3Er9nUCgJrlqdINb96MpBPJ0IekyC7_d9XU2Fc,1337
99
+ harness/observability/metrics.py,sha256=nI04K9eHp909rwyLh6yfBnd0_kLfn_7CqOHu9ENmqko,1388
100
+ harness/observability/tracing.py,sha256=2QGOCQlOh6wZe__f2IssiG_mn7HHOHkJVUYOYnB3iI4,1175
101
+ harness/orchestrator/__init__.py,sha256=se64wEHwDMBRcw1UVdSaWxLawiKE5Jg6zv0hArt2zHs,440
102
+ harness/orchestrator/engine.py,sha256=SJp99IrTGFnPnaXYbeZf30YKG7A3MC0Bi25I-d0K4X0,37054
103
+ harness/orchestrator/handoff.py,sha256=wEYFPCHJEM2PWFHZJ0SCrewA5yJllbbmft5p0_dwvOw,1159
104
+ harness/orchestrator/lifecycle.py,sha256=ftNGSVq0OrIsAZ1JT61SOitBXaXJZBYcOypXSnZqkMk,2057
105
+ harness/orchestrator/native_resume.py,sha256=j6ytXOPSRHrlibBsc5MfLYwHhVl3G699KP_nuaFnGjM,3243
106
+ harness/orchestrator/policies.py,sha256=4BeKbtIqG04eDWAa0oV4eWcvNAgaZGVHCW2lOR4LKio,3269
107
+ harness/orchestrator/reconcile.py,sha256=ScF3gKWMH-1hEQToINc156mim4pbMa1j43usI7cc9fs,1519
108
+ harness/orchestrator/run_graph.py,sha256=woo2f_Zgqb7Ximc_Egm1u-Afum_kzVoVwJPJxAQk28Y,2173
109
+ harness/orchestrator/snapshot.py,sha256=WBaAex4G2pZi7SVaw8NkY5j-IVr9RjHM6MgZLi7v-ok,1715
110
+ harness/schemas/__init__.py,sha256=KQoa7duyjPTpEET4F6NKInF5x264Kv9qH36QbRe5igw,88
111
+ harness/schemas/adapter.py,sha256=8h3yARV_UIBouBZ0dUUK_yO1rQZcv62GrKb5ynKMi2E,523
112
+ harness/schemas/approval.py,sha256=YKIML6PTDbulG20OreYLS-4nKaRds0h3YL5dZlj3qYQ,599
113
+ harness/schemas/artifact.py,sha256=ABl1iQ2qgwRI9PAXvlNUng8znemnzUyU644QdFG_B5A,353
114
+ harness/schemas/common.py,sha256=X2WYRV7xPMj4qnSvv47eBTBpTUM3HVf3JV742YCBgfc,371
115
+ harness/schemas/memory.py,sha256=kDmTcjKcSu_fgM9-kyTcZpmchxotX9-p-WJKlEuhn00,1016
116
+ harness/schemas/policy.py,sha256=9wAQBjtuYupWDei2DtRxQs-T-UY5rN99FES0tDu2xzE,767
117
+ harness/schemas/run.py,sha256=oY3syF3F7UqVfz3Rqe4FWE1tTAwS0y0-iVMb8yhIhKc,3988
118
+ harness/schemas/session.py,sha256=-X0dI1WaerCRgEflY1mV0Y2jmMoHmsh0jBDRaHJeH58,2307
119
+ harness/schemas/task.py,sha256=Wrq1cFOU0QTPNVSaFeOcENHHUeegwYVkoB5aymiIb2c,522
120
+ harness/security/__init__.py,sha256=MAJlj1qcf2sbizh2fvOmkmVpJBySGO7Wb7N52U4rK4M,255
121
+ harness/security/approval_policy.py,sha256=l1lY7qjN16vEAnoAFS9IpuYi_64_4IXbpUuq425cL08,4100
122
+ harness/security/auth.py,sha256=spB64o0GLx6FMtT69E2hN4E1sTRgBQlvbIfj8slKNpw,3825
123
+ harness/security/permissions.py,sha256=HPwVvMe65m9IW5P0QzdqoOZ8toHZzpim8--pOUiUJX8,2169
124
+ harness/security/risk.py,sha256=4_ihRIt-fiYpixiQCSWGtLibDRPlFgyAtR0KMyub_vI,2058
125
+ harness/security/secrets.py,sha256=zH89znNizPU9zk0f44PY996naaw0TdPFTBVLNbUewqw,1638
126
+ harness/services/__init__.py,sha256=xEZgBBUOES34DrFkYdPLrwasLa9NX-W1lRxh6hu2iDI,80
127
+ harness/services/adapters_health.py,sha256=v7J_O8no-tO8wAnijuAsYprk3vgjQ-yOj7P4VbZMLGE,8424
128
+ harness/services/approvals.py,sha256=wbwE7OfRE8Hw7Nslc9qAmsMc9xuDbZGVwZBfyzvzZNU,2426
129
+ harness/services/artifacts.py,sha256=ud76kbktnZ5e2dIwvQHRaMheWPtx-MrlbzSAson4lBw,3307
130
+ harness/services/attachments.py,sha256=Fzw2nJ6GrADP7ami_qpopdzhA_pFiSVG17qAuvFMQxk,8332
131
+ harness/services/comparison.py,sha256=mvZT_WtKEi353ICLgkm1tDL1Ybodq55pohWj2OUaNpI,12110
132
+ harness/services/doctor.py,sha256=dwFHrjObB687sIXfjvkkJqs1OXdUYAZXUTOOScFYdMo,5751
133
+ harness/services/files.py,sha256=xA03L05_8ZTNzaKBVtPmpwm3iCnlTzQTkLz0XIXpIE8,12372
134
+ harness/services/mcp_servers.py,sha256=UY2L6p07CkaCT2cA2pM2bPGGW5WIaI69z9OF96OiMnQ,7353
135
+ harness/services/project_git.py,sha256=T332U4cygbtCeXZiC5CS0NYMtvlwEPQxc1DTNWMaNjg,4289
136
+ harness/services/retention.py,sha256=uUGZzwN62ADy5WykGxtFZ0Lkl6-ig2_U4d9kSMvXwrg,3148
137
+ harness/services/runs.py,sha256=157W99csIAXc_sxUhsotsc8hGLloXLuKgmBaebF_9yY,6918
138
+ harness/services/runs_diff.py,sha256=ul8pzMdi5KKoZjTxIeXwr_WzKcYbvwrknsK_fIpYpUc,7983
139
+ harness/services/sessions.py,sha256=iTrJZrk4e7VcgSL7L23EoOtVQ0QG6WL1t0Jd1IPOxc8,9021
140
+ harness/services/ssh.py,sha256=LAAgKxAv4v8zk2o4kS9PHGN4sLNbq80a-1cj-f8VsdM,11416
141
+ harness/services/stats.py,sha256=ItXDnu7HN3t_7Ck3EEBwojS9n-qYcYoTR1IP0SbItoo,4321
142
+ harness/services/tasks.py,sha256=xT_iHQvh50r40sJF2oWYiD5C4BRWG3QUYJL9kae2Kt4,1293
143
+ harness/services/workspaces.py,sha256=IbLAbC4Xl0v17uOIn80QM02UT5l5hQiEOyvPOCYpxks,8095
144
+ harness/services/worktrees.py,sha256=BIoPNgwKJ_PeGX4eMK-jdOSriYRxXVmOwcgxsjoYuF4,18051
145
+ harness/skills/__init__.py,sha256=IxxsbP29LrGFrTxPJOZkjm9Ybt17cOakuwTyVhzjNqw,303
146
+ harness/skills/manager.py,sha256=rKsW9RTrZRq2DOXfHPUJ6qRlcgje6tUUSNFp-DGVvS4,5101
147
+ harness/tools/__init__.py,sha256=bmPE6AMB1fDv70WRlGb9YKPcS-EOSh7vMWrJQKqyNYo,74
148
+ harness/tools/fs_tools.py,sha256=Hve5chc-Hvs644E7TKGFfl5e1dJLG-B0TEZ3VRQtgR4,12309
149
+ harness/workers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
150
+ harness/workers/dispatch.py,sha256=NaGM-aqwqEW8D88ANtZG-XKDwCmyycwSlm8Wf5Z6j-4,794
151
+ harness/workers/inprocess.py,sha256=Gjg4tjsSSH950qkBE5zoriG-BpgJJgU2txoDHxmc8PU,5834
152
+ ags_cli-0.1.0.dist-info/METADATA,sha256=-SQcIq-C5RokRzgDK-spYuY8Z7bLVFUWwiTi7Gd7h24,12681
153
+ ags_cli-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
154
+ ags_cli-0.1.0.dist-info/entry_points.txt,sha256=r19gV57ahT3XT9AxRtxYFwsCnDZHTL0YxtMR9xp_Gjw,38
155
+ ags_cli-0.1.0.dist-info/top_level.txt,sha256=o1Col3u-amx5Ry6wEbAkJzb39qDlYn38y_kGECKVvbg,12
156
+ ags_cli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ags = cli.main:main
@@ -0,0 +1,2 @@
1
+ cli
2
+ harness
cli/__init__.py ADDED
File without changes
cli/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from cli.main import app
2
+
3
+ if __name__ == "__main__":
4
+ app()
cli/client.py ADDED
@@ -0,0 +1,145 @@
1
+ """Thin synchronous HTTP client to the AgentS API used by all CLI commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import time
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ import httpx
12
+
13
+ # `ags serve` (via cli.local._write_api_url, on auto-start or explicit launch)
14
+ # records the live API URL here so `ags` works in any terminal without
15
+ # exporting env vars.
16
+ _API_URL_FILE = Path.home() / ".ags" / "api_url"
17
+
18
+ # Inline agent runs (agy reading a whole repo, a slow local LLM) can take many
19
+ # minutes, so the read timeout is generous; connect stays short so a down API
20
+ # fails fast with a clear message instead of hanging. Override with HARNESS_API_TIMEOUT.
21
+ _READ_TIMEOUT = float(os.environ.get("HARNESS_API_TIMEOUT", "1800")) # 30 min
22
+ _LONG = httpx.Timeout(connect=5.0, read=_READ_TIMEOUT, write=30.0, pool=5.0)
23
+ _SHORT = httpx.Timeout(connect=5.0, read=30.0, write=30.0, pool=5.0)
24
+
25
+
26
+ def _default_base_url() -> str:
27
+ if env := os.environ.get("HARNESS_API_BASE_URL"):
28
+ return env
29
+ # Find or auto-spawn the daemonless `ags serve` server.
30
+ try:
31
+ from cli.local import LOCAL_BASE_URL, ensure_local_server
32
+
33
+ if url := ensure_local_server():
34
+ return url
35
+ return LOCAL_BASE_URL
36
+ except Exception: # noqa: BLE001 - fall back to recorded URL / default below
37
+ pass
38
+ if _API_URL_FILE.exists() and (url := _API_URL_FILE.read_text().strip()):
39
+ return url
40
+ return "http://localhost:8000"
41
+
42
+
43
+ class APIError(RuntimeError):
44
+ """A user-facing API/transport failure — carries a clean message, no traceback.
45
+ Caught centrally in ``cli.main.main`` so commands never dump httpx internals."""
46
+
47
+
48
+ class HarnessClient:
49
+ def __init__(self, base_url: str | None = None, token: str | None = None) -> None:
50
+ self.base_url = (base_url or _default_base_url()).rstrip("/")
51
+ self.token = token or os.environ.get("HARNESS_API_TOKEN")
52
+
53
+ def _headers(self) -> dict[str, str]:
54
+ return {"Authorization": f"Bearer {self.token}"} if self.token else {}
55
+
56
+ def _url(self, path: str) -> str:
57
+ return f"{self.base_url}/api/v1{path}"
58
+
59
+ def _send(self, method: str, path: str, *, timeout, **kw: Any) -> Any:
60
+ try:
61
+ r = httpx.request(
62
+ method, self._url(path), headers=self._headers(), timeout=timeout, **kw
63
+ )
64
+ r.raise_for_status()
65
+ return r.json()
66
+ except httpx.ConnectError as exc:
67
+ raise APIError(
68
+ f"Can't reach the AgentS API at {self.base_url}.\n"
69
+ f"Is it running? Start it with: ags serve"
70
+ ) from exc
71
+ except httpx.ReadTimeout as exc:
72
+ raise APIError(
73
+ f"The run is taking longer than {int(_READ_TIMEOUT)}s on the client side. "
74
+ "It may still be completing on the server — check `ags session list` "
75
+ "in a moment, or raise the limit with HARNESS_API_TIMEOUT."
76
+ ) from exc
77
+ except httpx.HTTPStatusError as exc:
78
+ detail = ""
79
+ try:
80
+ detail = exc.response.json().get("detail", "")
81
+ except Exception: # noqa: BLE001
82
+ detail = exc.response.text[:300]
83
+ raise APIError(f"API error {exc.response.status_code}: {detail}") from exc
84
+ except httpx.HTTPError as exc:
85
+ raise APIError(f"Request failed: {exc}") from exc
86
+
87
+ def get(self, path: str, **params: Any) -> Any:
88
+ return self._send("GET", path, params=params, timeout=_SHORT)
89
+
90
+ def post(self, path: str, json: Any = None, **params: Any) -> Any:
91
+ # POSTs drive runs (inline can be slow) -> long read timeout.
92
+ return self._send("POST", path, json=json, params=params, timeout=_LONG)
93
+
94
+ def put(self, path: str, json: Any = None, **params: Any) -> Any:
95
+ return self._send("PUT", path, json=json, params=params, timeout=_SHORT)
96
+
97
+ def delete(self, path: str, **params: Any) -> Any:
98
+ return self._send("DELETE", path, params=params, timeout=_SHORT)
99
+
100
+ def _stream_once(self, run_id: str, *, after_seq: int, timeout_s: int):
101
+ """Open one SSE connection and yield raw data-frame strings; stop on event:end."""
102
+ sse_timeout = httpx.Timeout(connect=5.0, read=timeout_s + 30, write=30.0, pool=5.0)
103
+ with httpx.stream(
104
+ "GET", self._url(f"/stream/{run_id}"),
105
+ params={"timeout_s": timeout_s, "after_seq": after_seq},
106
+ headers=self._headers(),
107
+ timeout=sse_timeout,
108
+ ) as r:
109
+ # Buffer across raw byte chunks so we can detect the end-of-frame marker
110
+ # and the special "event: end" event type before yielding.
111
+ pending_event: list[str] = []
112
+ for line in r.iter_lines():
113
+ if line == "":
114
+ # Blank line = end of one SSE event block
115
+ event_lines = pending_event
116
+ pending_event = []
117
+ is_end = any(l == "event: end" for l in event_lines)
118
+ if is_end:
119
+ return
120
+ for l in event_lines:
121
+ if l.startswith("data:"):
122
+ yield l[len("data:"):].strip()
123
+ else:
124
+ pending_event.append(line)
125
+
126
+ def stream(self, run_id: str, timeout_s: int = 600):
127
+ """Yield SSE data frames for a run, reconnecting up to 3 times on transport errors.
128
+
129
+ Reconnect counter is 3 for the stream's lifetime (deliberate — a persistently
130
+ broken connection should surface as an error, not loop forever).
131
+ httpx.RemoteProtocolError is a TransportError subclass and is therefore covered.
132
+ """
133
+ last_seq, attempts = -1, 0 # -1 = nothing seen yet; seq=0 user event must not be skipped
134
+ while True:
135
+ try:
136
+ for frame in self._stream_once(run_id, after_seq=last_seq, timeout_s=timeout_s):
137
+ ev = json.loads(frame)
138
+ last_seq = max(last_seq, int(ev.get("seq", last_seq)))
139
+ yield frame
140
+ return # clean end (event: end received)
141
+ except httpx.TransportError:
142
+ attempts += 1
143
+ if attempts > 3:
144
+ raise APIError("stream dropped and could not reconnect")
145
+ time.sleep(0.5 * attempts) # linear backoff: 0.5 s × attempt
File without changes