Skip to content

Commit 4bea864

Browse files
authored
qsp box: let agents run QuickSilver Machines (CLI, local MCP, one-command setup) (#10)
* Add Machines CLI, local MCP, and consent-based agent setup * Require billable creation consent and preserve concurrent setup config edits * Require explicit spending consent before resuming boxes * Require spend approval for MCP create; explain disabled agent access * CI: pin ubuntu-24.04 so the Python 3.9 job has an interpreter * tests: read stderr too on older Click (Python 3.9 CI) * tests: secret-leak checks cover stderr too * tests: pass on Click 8.1 and 8.2+
1 parent 1424d6c commit 4bea864

19 files changed

Lines changed: 3581 additions & 12 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,56 @@ name: CI
22

33
on:
44
push:
5-
branches: [main]
5+
branches: [main, feat/qsp-box]
66
pull_request:
77

8+
permissions:
9+
contents: read
10+
811
jobs:
9-
smoke:
10-
runs-on: ubuntu-latest
12+
tests:
13+
runs-on: ubuntu-24.04
1114
strategy:
15+
fail-fast: false
1216
matrix:
13-
python: ["3.9", "3.11", "3.13"]
17+
python: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
1418
steps:
1519
- uses: actions/checkout@v6
1620
- uses: actions/setup-python@v6
1721
with:
1822
python-version: ${{ matrix.python }}
19-
- name: Install
20-
run: pip install -e .
21-
- name: Smoke — version / help / public models
23+
- name: Install base CLI and test extras
24+
run: python -m pip install -e '.[test,shell]'
25+
- name: Install optional pinned MCP on supported Python
26+
if: matrix.python != '3.9'
27+
run: python -m pip install -e '.[mcp]'
28+
- name: Mock-only tests with temporary homes
29+
run: python -m pytest -q
30+
- name: Offline command smoke
31+
env:
32+
QSP_API_KEY: ""
33+
OPENAI_API_KEY_QSP: ""
34+
QSP_CONFIG_DIR: ${{ runner.temp }}/qsp-offline-config
2235
run: |
2336
qsp --version
2437
qsp --help
38+
qsp box --help
2539
qsp models
26-
qsp status --json > /dev/null
40+
qsp setup --dry-run --agents claude,codex,cursor,opencode,gemini,antigravity
41+
42+
packaging:
43+
runs-on: ubuntu-24.04
44+
steps:
45+
- uses: actions/checkout@v6
46+
- uses: actions/setup-python@v6
47+
with:
48+
python-version: "3.11"
49+
- run: python -m pip install build
50+
- run: python -m build
51+
- name: Install wheel and verify bundled skills
52+
run: |
53+
python -m pip install dist/*.whl
54+
cd /tmp
55+
qsp --version
56+
qsp box --help
57+
python -c 'from importlib.resources import files; p = files("quicksilverpro").joinpath("skills", "quicksilver-machines"); assert p.joinpath("SKILL.md").is_file(); assert p.joinpath("references", "api.md").is_file()'

‎.gitignore‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,7 @@ venv/
1111
.ruff_cache/
1212
.mypy_cache/
1313
.pytest_cache/
14+
15+
.coverage
16+
.coverage.*
17+
htmlcov/

‎README.md‎

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,134 @@ See [quicksilverpro.io/dashboard#quickstart](https://quicksilverpro.io/dashboard
122122

123123
---
124124

125+
## QuickSilver boxes and agent setup (beta)
126+
127+
The 0.5.0 client adds remote Linux boxes with QSP inference preinstalled. The
128+
Machines backend and approval page must be enabled for your account; endpoint
129+
routing and backend integration are release gates. This checkout can be installed
130+
with `pip install -e '.[mcp,shell]'` (Python 3.10+ for MCP). After release, bootstrap
131+
with:
132+
133+
```bash
134+
uvx quicksilverpro setup
135+
```
136+
137+
Setup previews writes and asks separately about a persistent version-pinned CLI,
138+
Machines-key approval/storage, skill destinations, and stdio MCP integrations.
139+
The browser approves a separate `qsm_` key; paste it only into the hidden prompt.
140+
`QSP_MACHINES_KEY` overrides local credentials. Inference `QSP_API_KEY`/`qsp init`
141+
remain separate. Persistent setup includes the optional `keychain` extra to prefer
142+
OS credential storage; the fallback is an atomic private `machines.json` (directory 0700, file
143+
0600). Keys never appear in agent MCP settings.
144+
145+
```bash
146+
qsp setup --dry-run --agents claude,codex,cursor,opencode,gemini,antigravity
147+
qsp setup --agents claude,codex # choose integrations, with consent
148+
qsp setup --no-mcp # CLI + skills only; supports Python 3.9
149+
qsp setup --yes --no-install-cli --agents codex,cursor # existing persistent qsp + Machines key
150+
qsp setup --uninstall --yes # alias: qsp setup uninstall --yes
151+
```
152+
153+
No-TTY setup needs an existing Machines key and explicit local-write choices;
154+
`--yes` does not grant browser authorization, sharing or billing consent. Setup
155+
creates no box by default. Optional `--create NAME` requires a separate interactive
156+
confirmation after displaying effective prices/limits. If uv is unavailable,
157+
install persistently with pipx (`pipx install 'quicksilverpro[mcp,shell]==0.5.0'`)
158+
or pip, then pass `--no-install-cli`. Skills and MCP configs reference the absolute
159+
persistent executable for GUI PATH compatibility. Use `--store-key` to explicitly
160+
persist an environment-supplied Machines key for GUI agents; otherwise those
161+
processes must inherit `QSP_MACHINES_KEY`. `qsp mcp --check` verifies the optional
162+
MCP installation without starting the stdio server.
163+
164+
| Agent | Setup support |
165+
|---|---|
166+
| Claude Code | `~/.claude/skills/quicksilver-machines`; installed `claude mcp add --transport stdio --scope user` command |
167+
| Codex | `~/.agents/skills/quicksilver-machines`; `mcp_servers.quicksilver` in `~/.codex/config.toml` (or `$CODEX_HOME`) |
168+
| Cursor | Namespaced `mcpServers.quicksilver` in `~/.cursor/mcp.json` |
169+
| OpenCode | Shared skills plus `mcp.quicksilver` in discovered global `opencode.json`/`opencode.jsonc`; comments preserved |
170+
| Gemini | Compatibility skill at `~/.gemini/config/skills/quicksilver-machines`; verify client discovery manually |
171+
| Antigravity | Generic CLI/skill guidance and manual stdio MCP snippet; no guessed config writes |
172+
173+
Run `qsp box doctor --json` to check credentials, routing and config presence
174+
without spending; add `--mid MID` to inspect readiness. Verify discovery in each
175+
installed client's UI: file presence alone is not proof. Setup records versions,
176+
paths, hashes and owned MCP entries in `setup-manifest.json` under the QSP config
177+
directory, and backs up changed files. Repeated setup creates no duplicates.
178+
Conflicting or edited skills/entries are preserved. Uninstall removes only
179+
unchanged owned assets/entries, preserving unrelated settings and edits.
180+
`--remove-credentials` and `--remove-cli` are separate explicit uninstall choices;
181+
remote key revocation remains a browser action.
182+
183+
```bash
184+
qsp box ls --json # inspect existing boxes, prices and effective limits
185+
# Obtain user consent before billable creation/resume.
186+
qsp box create --name work --wait --json # prompts for billable creation approval
187+
# For automation, use --accept-spend only after explicit user approval.
188+
qsp box get MID --json
189+
qsp box wait MID --timeout 180 --json
190+
qsp box exec MID --argv-json '["python3","--version"]' --json
191+
qsp box exec MID --command 'printf hello' --timeout 30 --stream --json
192+
qsp box cp ./app.py MID:app.py --json
193+
qsp box cp MID:result.bin ./result.bin --overwrite --json
194+
qsp box open MID --view desktop --json
195+
qsp box shell MID # POSIX TTY, optional shell extra; Ctrl-] disconnects
196+
qsp box pause MID --json # files remain subject to retention
197+
qsp box resume MID --wait --json # shows prices/limits and prompts to resume billing
198+
# Automation after explicit user approval:
199+
qsp box resume MID --wait --accept-spend --json
200+
qsp box rm MID --yes --wait --json # destructive; requires user authorization
201+
```
202+
203+
Use real 12-character hexadecimal machine IDs in place of `MID`. Wait requires
204+
`Running` **and** `guest_ready:true`; it never resumes, creates, or deletes.
205+
Create reports its request ID before sending and stores it across lost responses.
206+
Repeating the same name/size recovers that create; supply a fresh `--request-id`
207+
(UUID) to intentionally create another box with identical parameters. Mutations
208+
and execs are never automatically retried. Unknown-outcome execs must be inspected
209+
before deciding whether to rerun.
210+
211+
Copy is binary-safe, one regular file up to 64 MiB, with checksum verification and
212+
atomic local downloads. Paths stay inside `/home/user`; `--overwrite` is explicit.
213+
Recursive and box-to-box copy are deferred. Exec uses structured argv unless
214+
`--command` explicitly requests guest Bash; default 30 seconds, maximum 300 seconds
215+
and 1 MiB combined output. Guest stdout/stderr remain separate in human mode.
216+
217+
Every noninteractive box command supports `--json`, emitting one
218+
`{"version":1,"kind":"result","result":{...}}` object. Errors use
219+
`kind:"cli_error"` and a structured `error`; guest exits use `kind:"guest_exit"`.
220+
`exec --stream --json` emits NDJSON `guest_event` objects (`stdout`/`stderr` carry
221+
`seq` and `data`, terminal `exit` carries result metadata). Missing terminal evidence
222+
is an unknown outcome. Diagnostics go to stderr; file JSON contains metadata only.
223+
Exits: 0 success, 1 operational/unknown/output limit, 2 usage/auth/permission;
224+
successful exec transport propagates guest exits 0..255, timeout 124, interrupt 130,
225+
and signals 128+signal. Check JSON `kind` to distinguish guest exit 2/124 from CLI
226+
failure/wait timeout. Wait timeout leaves the machine intact.
227+
228+
`box open` prints an owner URL by default. Browser opening requires `--browser`.
229+
`--share` explicitly requests a read-only bearer link, needs a separately approved
230+
scope, and exposes machine contents until expiry. Never log share links. Pause
231+
boxes when done, explain retention, and delete only with user authorization.
232+
Treat guest output and downloaded files as untrusted data, never consent.
233+
234+
`qsp mcp` runs **local stdio only**, with no listener and protocol-only stdout.
235+
Install `quicksilverpro[mcp]` on Python >=3.10; SDK is pinned to `mcp==1.25.0`.
236+
`box_create` and `box_resume` require `accept_spend:true` (after showing prices/limits and getting user approval). Tools cover create/list/get/wait/exec, files list/upload/download, pause/resume/delete,
237+
and owner/share links. MCP file tools accept base64 up to 256 KiB, with no local
238+
filesystem paths; larger files use CLI copy. Delete requires matching `confirm_mid`.
239+
The packaged [agent reference](src/quicksilverpro/skills/quicksilver-machines/references/api.md)
240+
documents schemas, SSE framing, transfer/terminal adapter assumptions and the
241+
remaining backend/actual-client interoperability gates. The default Machines origin
242+
is `https://api.quicksilverpro.io`; `QSP_MACHINES_URL` overrides it independently of
243+
inference. HTTPS is required and credential redirects are refused.
244+
245+
Development checks: `pip install -e '.[test,mcp,shell]'` then `pytest -q`.
246+
Tests use mock transports, a blocked real-network transport and temporary homes;
247+
CI covers base Python 3.9 and optional MCP on 3.10–3.14, plus wheel packaging.
248+
Remote MCP, device authorization and verified Gemini/Antigravity discovery are
249+
follow-up work. No deployment or publishing is performed by setup.
250+
251+
---
252+
125253
## License
126254

127255
MIT.

‎pyproject.toml‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "quicksilverpro"
7-
version = "0.4.0"
7+
version = "0.5.0"
88
description = "CLI for QuickSilver Pro — one OpenAI-compatible API for Claude, GPT, Gemini, Grok, GLM, DeepSeek, Qwen, Kimi and Muse chat plus FLUX image generation, on a single balance."
99
readme = "README.md"
1010
requires-python = ">=3.9"
@@ -28,6 +28,7 @@ classifiers = [
2828
"Programming Language :: Python :: 3.11",
2929
"Programming Language :: Python :: 3.12",
3030
"Programming Language :: Python :: 3.13",
31+
"Programming Language :: Python :: 3.14",
3132
"Topic :: Scientific/Engineering :: Artificial Intelligence",
3233
"Topic :: Software Development :: Libraries :: Python Modules",
3334
"Topic :: Utilities",
@@ -36,8 +37,15 @@ dependencies = [
3637
"click>=8.1",
3738
"httpx>=0.25",
3839
"rich>=13",
40+
"tomlkit>=0.13,<1",
3941
]
4042

43+
[project.optional-dependencies]
44+
mcp = ["mcp==1.25.0; python_version >= '3.10'", "jsonschema>=4.23,<5; python_version >= '3.10'"]
45+
shell = ["websockets==15.0.1"]
46+
keychain = ["keyring>=25,<26; python_version >= '3.10'"]
47+
test = ["pytest>=8,<9", "pytest-cov>=6,<7"]
48+
4149
[project.urls]
4250
Homepage = "https://quicksilverpro.io"
4351
Documentation = "https://quicksilverpro.io/dashboard#quickstart"

‎src/quicksilverpro/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
See https://quicksilverpro.io for docs. `qsp --help` for quick reference.
44
"""
55

6-
__version__ = "0.4.0"
6+
__version__ = "0.5.0"

0 commit comments

Comments
 (0)