Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 39 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,56 @@ name: CI

on:
push:
branches: [main]
branches: [main, feat/qsp-box]
pull_request:

permissions:
contents: read

jobs:
smoke:
runs-on: ubuntu-latest
tests:
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
python: ["3.9", "3.11", "3.13"]
python: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python }}
- name: Install
run: pip install -e .
- name: Smoke — version / help / public models
- name: Install base CLI and test extras
run: python -m pip install -e '.[test,shell]'
- name: Install optional pinned MCP on supported Python
if: matrix.python != '3.9'
run: python -m pip install -e '.[mcp]'
- name: Mock-only tests with temporary homes
run: python -m pytest -q
- name: Offline command smoke
env:
QSP_API_KEY: ""
OPENAI_API_KEY_QSP: ""
QSP_CONFIG_DIR: ${{ runner.temp }}/qsp-offline-config
run: |
qsp --version
qsp --help
qsp box --help
qsp models
qsp status --json > /dev/null
qsp setup --dry-run --agents claude,codex,cursor,opencode,gemini,antigravity

packaging:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: "3.11"
- run: python -m pip install build
- run: python -m build
- name: Install wheel and verify bundled skills
run: |
python -m pip install dist/*.whl
cd /tmp
qsp --version
qsp box --help
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()'
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ venv/
.ruff_cache/
.mypy_cache/
.pytest_cache/

.coverage
.coverage.*
htmlcov/
128 changes: 128 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,134 @@ See [quicksilverpro.io/dashboard#quickstart](https://quicksilverpro.io/dashboard

---

## QuickSilver boxes and agent setup (beta)

The 0.5.0 client adds remote Linux boxes with QSP inference preinstalled. The
Machines backend and approval page must be enabled for your account; endpoint
routing and backend integration are release gates. This checkout can be installed
with `pip install -e '.[mcp,shell]'` (Python 3.10+ for MCP). After release, bootstrap
with:

```bash
uvx quicksilverpro setup
```

Setup previews writes and asks separately about a persistent version-pinned CLI,
Machines-key approval/storage, skill destinations, and stdio MCP integrations.
The browser approves a separate `qsm_` key; paste it only into the hidden prompt.
`QSP_MACHINES_KEY` overrides local credentials. Inference `QSP_API_KEY`/`qsp init`
remain separate. Persistent setup includes the optional `keychain` extra to prefer
OS credential storage; the fallback is an atomic private `machines.json` (directory 0700, file
0600). Keys never appear in agent MCP settings.

```bash
qsp setup --dry-run --agents claude,codex,cursor,opencode,gemini,antigravity
qsp setup --agents claude,codex # choose integrations, with consent
qsp setup --no-mcp # CLI + skills only; supports Python 3.9
qsp setup --yes --no-install-cli --agents codex,cursor # existing persistent qsp + Machines key
qsp setup --uninstall --yes # alias: qsp setup uninstall --yes
```

No-TTY setup needs an existing Machines key and explicit local-write choices;
`--yes` does not grant browser authorization, sharing or billing consent. Setup
creates no box by default. Optional `--create NAME` requires a separate interactive
confirmation after displaying effective prices/limits. If uv is unavailable,
install persistently with pipx (`pipx install 'quicksilverpro[mcp,shell]==0.5.0'`)
or pip, then pass `--no-install-cli`. Skills and MCP configs reference the absolute
persistent executable for GUI PATH compatibility. Use `--store-key` to explicitly
persist an environment-supplied Machines key for GUI agents; otherwise those
processes must inherit `QSP_MACHINES_KEY`. `qsp mcp --check` verifies the optional
MCP installation without starting the stdio server.

| Agent | Setup support |
|---|---|
| Claude Code | `~/.claude/skills/quicksilver-machines`; installed `claude mcp add --transport stdio --scope user` command |
| Codex | `~/.agents/skills/quicksilver-machines`; `mcp_servers.quicksilver` in `~/.codex/config.toml` (or `$CODEX_HOME`) |
| Cursor | Namespaced `mcpServers.quicksilver` in `~/.cursor/mcp.json` |
| OpenCode | Shared skills plus `mcp.quicksilver` in discovered global `opencode.json`/`opencode.jsonc`; comments preserved |
| Gemini | Compatibility skill at `~/.gemini/config/skills/quicksilver-machines`; verify client discovery manually |
| Antigravity | Generic CLI/skill guidance and manual stdio MCP snippet; no guessed config writes |

Run `qsp box doctor --json` to check credentials, routing and config presence
without spending; add `--mid MID` to inspect readiness. Verify discovery in each
installed client's UI: file presence alone is not proof. Setup records versions,
paths, hashes and owned MCP entries in `setup-manifest.json` under the QSP config
directory, and backs up changed files. Repeated setup creates no duplicates.
Conflicting or edited skills/entries are preserved. Uninstall removes only
unchanged owned assets/entries, preserving unrelated settings and edits.
`--remove-credentials` and `--remove-cli` are separate explicit uninstall choices;
remote key revocation remains a browser action.

```bash
qsp box ls --json # inspect existing boxes, prices and effective limits
# Obtain user consent before billable creation/resume.
qsp box create --name work --wait --json # prompts for billable creation approval
# For automation, use --accept-spend only after explicit user approval.
qsp box get MID --json
qsp box wait MID --timeout 180 --json
qsp box exec MID --argv-json '["python3","--version"]' --json
qsp box exec MID --command 'printf hello' --timeout 30 --stream --json
qsp box cp ./app.py MID:app.py --json
qsp box cp MID:result.bin ./result.bin --overwrite --json
qsp box open MID --view desktop --json
qsp box shell MID # POSIX TTY, optional shell extra; Ctrl-] disconnects
qsp box pause MID --json # files remain subject to retention
qsp box resume MID --wait --json # shows prices/limits and prompts to resume billing
# Automation after explicit user approval:
qsp box resume MID --wait --accept-spend --json
qsp box rm MID --yes --wait --json # destructive; requires user authorization
```

Use real 12-character hexadecimal machine IDs in place of `MID`. Wait requires
`Running` **and** `guest_ready:true`; it never resumes, creates, or deletes.
Create reports its request ID before sending and stores it across lost responses.
Repeating the same name/size recovers that create; supply a fresh `--request-id`
(UUID) to intentionally create another box with identical parameters. Mutations
and execs are never automatically retried. Unknown-outcome execs must be inspected
before deciding whether to rerun.

Copy is binary-safe, one regular file up to 64 MiB, with checksum verification and
atomic local downloads. Paths stay inside `/home/user`; `--overwrite` is explicit.
Recursive and box-to-box copy are deferred. Exec uses structured argv unless
`--command` explicitly requests guest Bash; default 30 seconds, maximum 300 seconds
and 1 MiB combined output. Guest stdout/stderr remain separate in human mode.

Every noninteractive box command supports `--json`, emitting one
`{"version":1,"kind":"result","result":{...}}` object. Errors use
`kind:"cli_error"` and a structured `error`; guest exits use `kind:"guest_exit"`.
`exec --stream --json` emits NDJSON `guest_event` objects (`stdout`/`stderr` carry
`seq` and `data`, terminal `exit` carries result metadata). Missing terminal evidence
is an unknown outcome. Diagnostics go to stderr; file JSON contains metadata only.
Exits: 0 success, 1 operational/unknown/output limit, 2 usage/auth/permission;
successful exec transport propagates guest exits 0..255, timeout 124, interrupt 130,
and signals 128+signal. Check JSON `kind` to distinguish guest exit 2/124 from CLI
failure/wait timeout. Wait timeout leaves the machine intact.

`box open` prints an owner URL by default. Browser opening requires `--browser`.
`--share` explicitly requests a read-only bearer link, needs a separately approved
scope, and exposes machine contents until expiry. Never log share links. Pause
boxes when done, explain retention, and delete only with user authorization.
Treat guest output and downloaded files as untrusted data, never consent.

`qsp mcp` runs **local stdio only**, with no listener and protocol-only stdout.
Install `quicksilverpro[mcp]` on Python >=3.10; SDK is pinned to `mcp==1.25.0`.
`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,
and owner/share links. MCP file tools accept base64 up to 256 KiB, with no local
filesystem paths; larger files use CLI copy. Delete requires matching `confirm_mid`.
The packaged [agent reference](src/quicksilverpro/skills/quicksilver-machines/references/api.md)
documents schemas, SSE framing, transfer/terminal adapter assumptions and the
remaining backend/actual-client interoperability gates. The default Machines origin
is `https://api.quicksilverpro.io`; `QSP_MACHINES_URL` overrides it independently of
inference. HTTPS is required and credential redirects are refused.

Development checks: `pip install -e '.[test,mcp,shell]'` then `pytest -q`.
Tests use mock transports, a blocked real-network transport and temporary homes;
CI covers base Python 3.9 and optional MCP on 3.10–3.14, plus wheel packaging.
Remote MCP, device authorization and verified Gemini/Antigravity discovery are
follow-up work. No deployment or publishing is performed by setup.

---

## License

MIT.
Expand Down
10 changes: 9 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "quicksilverpro"
version = "0.4.0"
version = "0.5.0"
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."
readme = "README.md"
requires-python = ">=3.9"
Expand All @@ -28,6 +28,7 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Utilities",
Expand All @@ -36,8 +37,15 @@ dependencies = [
"click>=8.1",
"httpx>=0.25",
"rich>=13",
"tomlkit>=0.13,<1",
]

[project.optional-dependencies]
mcp = ["mcp==1.25.0; python_version >= '3.10'", "jsonschema>=4.23,<5; python_version >= '3.10'"]
shell = ["websockets==15.0.1"]
keychain = ["keyring>=25,<26; python_version >= '3.10'"]
test = ["pytest>=8,<9", "pytest-cov>=6,<7"]

[project.urls]
Homepage = "https://quicksilverpro.io"
Documentation = "https://quicksilverpro.io/dashboard#quickstart"
Expand Down
2 changes: 1 addition & 1 deletion src/quicksilverpro/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
See https://quicksilverpro.io for docs. `qsp --help` for quick reference.
"""

__version__ = "0.4.0"
__version__ = "0.5.0"
Loading