gpu4agents 0.5.0__tar.gz → 0.6.0__tar.gz

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 (119) hide show
  1. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/CHANGELOG.md +14 -0
  2. gpu4agents-0.6.0/INSTALL.md +53 -0
  3. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/PKG-INFO +23 -10
  4. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/README.md +22 -9
  5. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/docs/ACCEPTANCE.md +2 -0
  6. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/docs/AGENTS.md +39 -13
  7. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/__init__.py +1 -1
  8. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/agent/install.py +138 -17
  9. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/agent/skill/SKILL.md +58 -9
  10. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/cli/account.py +87 -0
  11. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/cli/agent.py +3 -3
  12. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/cli/main.py +1 -0
  13. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/core/credentials.py +26 -0
  14. gpu4agents-0.6.0/install.sh +104 -0
  15. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/pyproject.toml +1 -1
  16. gpu4agents-0.6.0/skills/gpu4agents/SKILL.md +117 -0
  17. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/tests/test_agent.py +88 -6
  18. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/tests/test_cli.py +74 -0
  19. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/.env.example +0 -0
  20. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  21. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  22. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  23. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/.github/dependabot.yml +0 -0
  24. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/.github/pull_request_template.md +0 -0
  25. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/.github/workflows/ci.yml +0 -0
  26. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/.github/workflows/release.yml +0 -0
  27. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/.gitignore +0 -0
  28. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/CODE_OF_CONDUCT.md +0 -0
  29. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/CONTRIBUTING.md +0 -0
  30. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/LICENSE +0 -0
  31. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/SECURITY.md +0 -0
  32. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/docs/ADDING_A_PROVIDER.md +0 -0
  33. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/docs/CHOOSING_A_MODEL.md +0 -0
  34. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/docs/CONFIG.md +0 -0
  35. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/docs/INTEGRATION.md +0 -0
  36. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/docs/LIFECYCLE.md +0 -0
  37. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/docs/USAGE.md +0 -0
  38. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/docs/assets/banner.png +0 -0
  39. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/docs/assets/jambu-logo.jpg +0 -0
  40. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/examples/experiment.py +0 -0
  41. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/examples/jambu.yaml +0 -0
  42. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/examples/mastra-agent/README.md +0 -0
  43. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/examples/mastra-agent/package-lock.json +0 -0
  44. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/examples/mastra-agent/package.json +0 -0
  45. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/examples/mastra-agent/src/agent.ts +0 -0
  46. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/examples/mastra-agent/src/index.ts +0 -0
  47. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/examples/mastra-agent/src/tools/weather-tool.ts +0 -0
  48. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/examples/mastra-agent/tsconfig.json +0 -0
  49. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/examples/typescript-client/README.md +0 -0
  50. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/examples/typescript-client/package-lock.json +0 -0
  51. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/examples/typescript-client/package.json +0 -0
  52. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/examples/typescript-client/src/gpu.ts +0 -0
  53. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/examples/typescript-client/src/index.ts +0 -0
  54. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/examples/typescript-client/tsconfig.json +0 -0
  55. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/__main__.py +0 -0
  56. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/agent/__init__.py +0 -0
  57. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/agent/jobs.py +0 -0
  58. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/agent/mcp.py +0 -0
  59. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/cli/__init__.py +0 -0
  60. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/cli/_common.py +0 -0
  61. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/cli/inspect.py +0 -0
  62. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/cli/lifecycle.py +0 -0
  63. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/cli/providers.py +0 -0
  64. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/cli/run.py +0 -0
  65. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/cli/setup.py +0 -0
  66. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/cli/status.py +0 -0
  67. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/cli/timer.py +0 -0
  68. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/cli/validate.py +0 -0
  69. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/core/__init__.py +0 -0
  70. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/core/beacon.py +0 -0
  71. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/core/config.py +0 -0
  72. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/core/durations.py +0 -0
  73. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/core/engine.py +0 -0
  74. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/core/errors.py +0 -0
  75. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/core/events.py +0 -0
  76. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/core/execution.py +0 -0
  77. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/core/health.py +0 -0
  78. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/core/hosts.py +0 -0
  79. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/core/model_inspect.py +0 -0
  80. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/core/models.py +0 -0
  81. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/core/paths.py +0 -0
  82. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/core/policy.py +0 -0
  83. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/core/state.py +0 -0
  84. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/core/sweep.py +0 -0
  85. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/core/tool_parsers.py +0 -0
  86. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/providers/__init__.py +0 -0
  87. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/providers/base.py +0 -0
  88. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/providers/registry.py +0 -0
  89. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/providers/vast/__init__.py +0 -0
  90. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/providers/vast/client.py +0 -0
  91. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/providers/vast/config.py +0 -0
  92. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/providers/vast/mapper.py +0 -0
  93. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/providers/vast/provider.py +0 -0
  94. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/runtime/__init__.py +0 -0
  95. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/runtime/base.py +0 -0
  96. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/runtime/vllm.py +0 -0
  97. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/watchdog/__init__.py +0 -0
  98. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/watchdog/agent_body.py +0 -0
  99. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/gpu4agents/watchdog/builder.py +0 -0
  100. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/jambu.example.yaml +0 -0
  101. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/jambu.models.yaml +0 -0
  102. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/jambu.yaml +0 -0
  103. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/jambu_gpu/__init__.py +0 -0
  104. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/jambu_gpu/__main__.py +0 -0
  105. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/tests/conftest.py +0 -0
  106. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/tests/fakes.py +0 -0
  107. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/tests/test_config.py +0 -0
  108. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/tests/test_engine.py +0 -0
  109. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/tests/test_engine_sessions.py +0 -0
  110. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/tests/test_integration.py +0 -0
  111. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/tests/test_model_inspect.py +0 -0
  112. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/tests/test_paths.py +0 -0
  113. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/tests/test_policy.py +0 -0
  114. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/tests/test_runtime_vllm.py +0 -0
  115. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/tests/test_state.py +0 -0
  116. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/tests/test_sweep.py +0 -0
  117. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/tests/test_vast_mapper.py +0 -0
  118. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/tests/test_vast_provider.py +0 -0
  119. {gpu4agents-0.5.0 → gpu4agents-0.6.0}/tests/test_watchdog.py +0 -0
@@ -2,6 +2,19 @@
2
2
 
3
3
  All notable changes to this project. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses [Semantic Versioning](https://semver.org/).
4
4
 
5
+ ## [0.6.0] - 2026-11-02
6
+
7
+ ### Added
8
+ - **Agents install it themselves.** Paste one line into Claude Code, Codex, Gemini CLI or Cursor; the agent follows [`INSTALL.md`](INSTALL.md) (served at `gpu4agents.jambu.ai/install.md`) and sets everything up, without renting anything.
9
+ - `install.sh`: needs only `curl`. Installs uv if missing and `gpu4agents` with a Python uv downloads when needed, removes an old `jambu-gpu` tool, and runs `gpu agent install` for the runtimes it finds (`--agent` to pick one).
10
+ - `gpu login`: the user types the Vast.ai key into a hidden prompt; it is checked against the API, then saved in `~/.gpu/credentials` (mode 600). `--check` reports whether a working key is found and where; `--stdin` reads it from a pipe.
11
+ - `gpu agent install -t codex` (skill in `.agents/skills/`, MCP server in `~/.codex/config.toml`), `-t gemini` (skill in `.agents/skills/`, MCP server in `.gemini/settings.json`) and `-t auto` (every runtime found).
12
+ - The skill is published at `skills/gpu4agents/SKILL.md`, so `npx skills add jambuai/gpu4agents` installs it, and it now covers installation, connecting the account, monitoring (`status`, `logs`, `events`, `ls`, `sweep`) and the exit codes.
13
+
14
+ ### Changed
15
+ - The skill is named `gpu4agents` (it was `gpu`). `gpu agent install` replaces the old `gpu` skill folder it wrote before; a folder of the same name from someone else is left alone.
16
+ - Cursor now gets the skill in `.agents/skills/` instead of `.claude/skills/`.
17
+
5
18
  ## [0.5.0] - 2026-11-02
6
19
 
7
20
  ### Changed
@@ -52,6 +65,7 @@ All notable changes to this project. The format follows [Keep a Changelog](https
52
65
  - A per-instance endpoint key; `gpu --json setup` / `gpu env` return `base_url`, `api_key` and `model`.
53
66
  - [docs/INTEGRATION.md](docs/INTEGRATION.md) and a one-file TypeScript client.
54
67
 
68
+ [0.6.0]: https://github.com/jambuai/gpu4agents/compare/v0.5.0...v0.6.0
55
69
  [0.5.0]: https://github.com/jambuai/gpu4agents/compare/v0.4.1...v0.5.0
56
70
  [0.4.1]: https://github.com/jambuai/gpu4agents/compare/v0.4.0...v0.4.1
57
71
  [0.4.0]: https://github.com/jambuai/gpu4agents/compare/v0.3.1...v0.4.0
@@ -0,0 +1,53 @@
1
+ # Install GPU for Agents (instructions for the agent)
2
+
3
+ You are a coding agent (Claude Code, Codex, Gemini CLI, Cursor, or another) and the user asked you to set up **GPU for Agents**: a `gpu` command, a skill and MCP tools that let you rent a cloud GPU, serve a model and run experiments, while a watchdog on the machine shuts it down when idle so the user pays only while it works.
4
+
5
+ Follow these steps in order. Run the commands yourself; tell the user briefly what each step did.
6
+
7
+ ## 1. Install the `gpu` command and give it to this agent
8
+
9
+ Pick the name of the runtime you are running in: `claude` (Claude Code), `codex` (Codex), `gemini` (Gemini CLI) or `cursor` (Cursor). If you are none of these, use `auto`.
10
+
11
+ ```bash
12
+ curl -fsSL https://gpu4agents.jambu.ai/install.sh | sh -s -- --agent <runtime>
13
+ ```
14
+
15
+ If that address does not resolve, use the copy in the repository:
16
+
17
+ ```bash
18
+ curl -fsSL https://raw.githubusercontent.com/jambuai/gpu4agents/main/install.sh | sh -s -- --agent <runtime>
19
+ ```
20
+
21
+ The script needs only `curl` (Linux or macOS; on Windows, run it inside WSL). It installs [uv](https://docs.astral.sh/uv/) if missing, uses it to install the `gpu4agents` package with a Python it downloads by itself, and then installs the skill and registers the `gpu` MCP server for your runtime. It does not ask for or read any key.
22
+
23
+ Check: `~/.local/bin/gpu --version` prints 0.6.0 or later. Use `~/.local/bin/gpu` for the rest of this session if plain `gpu` is not found.
24
+
25
+ ## 2. Connect the user's GPU account
26
+
27
+ Run `gpu login --check`.
28
+
29
+ - Exit code 0: the account is connected. Go to step 3.
30
+ - Exit code 5: no working key yet. Tell the user, in these words or close:
31
+
32
+ > Create an API key at https://cloud.vast.ai/manage-keys/ and add a few dollars of credit. Then open your own terminal, run `gpu login`, and paste the key into the hidden prompt.
33
+
34
+ Wait until the user says it is done, then run `gpu login --check` again.
35
+
36
+ Never ask the user to paste the key into this conversation, never read `~/.gpu/credentials` or `.env` files, and never print the key.
37
+
38
+ ## 3. Learn how to use it
39
+
40
+ Run `gpu agent skill` and read the output: it is the skill you just installed, with the rules (ask before renting, wait out a slow setup, clean up), the commands, monitoring and error codes. Follow it from now on in this session.
41
+
42
+ The MCP tools (`gpu_setup`, `gpu_status`, ...) appear after the user restarts the agent. Until then, the CLI does everything: `gpu --json <command>`.
43
+
44
+ ## 4. Tell the user it is ready
45
+
46
+ Summarize in a few lines:
47
+
48
+ - what was installed (the `gpu` command, the skill, the MCP server for this runtime);
49
+ - whether the account is connected, or what they still need to do (`gpu login`);
50
+ - that they should restart the agent to get the tools;
51
+ - an example request: "Compare Qwen 7B and Llama 8B on 50 prompts", and that you will show the price and ask before renting anything.
52
+
53
+ Do not rent a GPU as part of the installation.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: gpu4agents
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: GPU for Agents: your coding agent rents the GPU it needs, on its own. You pay only while it works.
5
5
  Project-URL: Homepage, https://gpu4agents.jambu.ai
6
6
  Project-URL: Documentation, https://github.com/jambuai/gpu4agents/tree/main/docs
@@ -67,22 +67,35 @@ Description-Content-Type: text/markdown
67
67
 
68
68
  Ask Claude Code, Cursor or any MCP client to "compare Qwen 7B and Llama 8B on these 50 prompts". The agent finds a GPU under your budget, serves the model behind an OpenAI-compatible API with vLLM, and runs the experiment. A watchdog runs on the machine itself, so when nothing uses it for `idle_timeout` (15 minutes by default) it shuts down, even if the agent forgot, the chat closed or your laptop is asleep.
69
69
 
70
- - **Self-service GPUs for agents.** A skill and local MCP tools (`gpu_offers`, `gpu_setup`, `gpu_run`, `gpu_status`, `gpu_destroy`, ...) give the agent the whole lifecycle. The skill tells it to show you the price and ask before renting.
70
+ - **Self-service GPUs for agents.** Claude Code, Codex, Gemini CLI and Cursor install it themselves from one pasted line. A skill and local MCP tools (`gpu_offers`, `gpu_setup`, `gpu_run`, `gpu_status`, `gpu_destroy`, ...) give the agent the whole lifecycle. The skill tells it to show you the price and ask before renting.
71
71
  - **Billing that ends when the work does.** Idle timeout, maximum lifetime, hourly and session budgets, and a sweep that cleans up anything left behind.
72
72
  - **Any app, any language.** `gpu --json setup` returns `base_url`, `api_key` and `model` for any OpenAI SDK.
73
73
 
74
74
  Vast.ai is the first provider; another provider is a new adapter, and the lifecycle rule stays the same.
75
75
 
76
- ## Quickstart: give your agent a GPU
76
+ ## Quickstart: let your agent install it
77
+
78
+ Paste this into Claude Code, Codex, Gemini CLI or Cursor (say which one you use):
79
+
80
+ ```text
81
+ Set up GPU for Agents for me. I'm using Claude Code. Fetch https://gpu4agents.jambu.ai/install.md with curl and follow its steps.
82
+ ```
83
+
84
+ The agent installs the `gpu` command (with [uv](https://docs.astral.sh/uv/), which brings its own Python), the `gpu4agents` skill and the `gpu` MCP tools. Then you connect your GPU account in your own terminal, so the agent never sees the key:
77
85
 
78
86
  ```bash
79
- pipx install gpu4agents # or: uv tool install gpu4agents
80
- cd your-project
81
- echo "VAST_API_KEY=..." >> .env # or ~/.gpu/credentials; never in jambu.yaml
82
- gpu agent install # Claude Code; -t cursor, -t desktop, --global
87
+ gpu login # paste your Vast.ai key (https://cloud.vast.ai/manage-keys/) into the hidden prompt
83
88
  ```
84
89
 
85
- Then ask the agent for an experiment. See [Letting a coding agent rent GPUs](docs/AGENTS.md).
90
+ Restart the agent and ask for an experiment. It shows the price and asks before renting anything. See [Letting a coding agent rent GPUs](docs/AGENTS.md).
91
+
92
+ Prefer a terminal? These do the same:
93
+
94
+ ```bash
95
+ curl -fsSL https://gpu4agents.jambu.ai/install.sh | sh # uv + Python + gpu, then every agent it finds
96
+ npx skills add jambuai/gpu4agents # just the skill; it installs gpu on first use
97
+ uv tool install gpu4agents && gpu agent install -t codex --global # by hand: claude | codex | gemini | cursor | auto
98
+ ```
86
99
 
87
100
  ## Quickstart: from a shell or an app
88
101
 
@@ -116,11 +129,11 @@ gpu_runtime:
116
129
 
117
130
  A full example is [`jambu.example.yaml`](jambu.example.yaml). As a last line of defense, `gpu ls` shows every machine billing in the account and `gpu sweep --install-timer` guards every project on the machine and cleans up orphans. See [Keeping the account clean](docs/USAGE.md#keeping-the-account-clean).
118
131
 
119
- The API key is read from the environment, a project `.env`, or `~/.gpu/credentials`. It never goes in the manifest, and the agent never sees it.
132
+ `gpu login` saves the API key in `~/.gpu/credentials` (mode 600) after checking it; the environment and a project `.env` work too. It never goes in the manifest, and the agent never sees it.
120
133
 
121
134
  ## Docs
122
135
 
123
- - [Letting a coding agent rent GPUs (Claude Code, Cursor, Claude Desktop)](docs/AGENTS.md)
136
+ - [Letting a coding agent rent GPUs (Claude Code, Codex, Gemini CLI, Cursor, Claude Desktop)](docs/AGENTS.md)
124
137
  - [Integrating into an application (any language)](docs/INTEGRATION.md)
125
138
  - [Commands, workloads, and agents](docs/USAGE.md)
126
139
  - [Every config field](docs/CONFIG.md)
@@ -26,22 +26,35 @@
26
26
 
27
27
  Ask Claude Code, Cursor or any MCP client to "compare Qwen 7B and Llama 8B on these 50 prompts". The agent finds a GPU under your budget, serves the model behind an OpenAI-compatible API with vLLM, and runs the experiment. A watchdog runs on the machine itself, so when nothing uses it for `idle_timeout` (15 minutes by default) it shuts down, even if the agent forgot, the chat closed or your laptop is asleep.
28
28
 
29
- - **Self-service GPUs for agents.** A skill and local MCP tools (`gpu_offers`, `gpu_setup`, `gpu_run`, `gpu_status`, `gpu_destroy`, ...) give the agent the whole lifecycle. The skill tells it to show you the price and ask before renting.
29
+ - **Self-service GPUs for agents.** Claude Code, Codex, Gemini CLI and Cursor install it themselves from one pasted line. A skill and local MCP tools (`gpu_offers`, `gpu_setup`, `gpu_run`, `gpu_status`, `gpu_destroy`, ...) give the agent the whole lifecycle. The skill tells it to show you the price and ask before renting.
30
30
  - **Billing that ends when the work does.** Idle timeout, maximum lifetime, hourly and session budgets, and a sweep that cleans up anything left behind.
31
31
  - **Any app, any language.** `gpu --json setup` returns `base_url`, `api_key` and `model` for any OpenAI SDK.
32
32
 
33
33
  Vast.ai is the first provider; another provider is a new adapter, and the lifecycle rule stays the same.
34
34
 
35
- ## Quickstart: give your agent a GPU
35
+ ## Quickstart: let your agent install it
36
+
37
+ Paste this into Claude Code, Codex, Gemini CLI or Cursor (say which one you use):
38
+
39
+ ```text
40
+ Set up GPU for Agents for me. I'm using Claude Code. Fetch https://gpu4agents.jambu.ai/install.md with curl and follow its steps.
41
+ ```
42
+
43
+ The agent installs the `gpu` command (with [uv](https://docs.astral.sh/uv/), which brings its own Python), the `gpu4agents` skill and the `gpu` MCP tools. Then you connect your GPU account in your own terminal, so the agent never sees the key:
36
44
 
37
45
  ```bash
38
- pipx install gpu4agents # or: uv tool install gpu4agents
39
- cd your-project
40
- echo "VAST_API_KEY=..." >> .env # or ~/.gpu/credentials; never in jambu.yaml
41
- gpu agent install # Claude Code; -t cursor, -t desktop, --global
46
+ gpu login # paste your Vast.ai key (https://cloud.vast.ai/manage-keys/) into the hidden prompt
42
47
  ```
43
48
 
44
- Then ask the agent for an experiment. See [Letting a coding agent rent GPUs](docs/AGENTS.md).
49
+ Restart the agent and ask for an experiment. It shows the price and asks before renting anything. See [Letting a coding agent rent GPUs](docs/AGENTS.md).
50
+
51
+ Prefer a terminal? These do the same:
52
+
53
+ ```bash
54
+ curl -fsSL https://gpu4agents.jambu.ai/install.sh | sh # uv + Python + gpu, then every agent it finds
55
+ npx skills add jambuai/gpu4agents # just the skill; it installs gpu on first use
56
+ uv tool install gpu4agents && gpu agent install -t codex --global # by hand: claude | codex | gemini | cursor | auto
57
+ ```
45
58
 
46
59
  ## Quickstart: from a shell or an app
47
60
 
@@ -75,11 +88,11 @@ gpu_runtime:
75
88
 
76
89
  A full example is [`jambu.example.yaml`](jambu.example.yaml). As a last line of defense, `gpu ls` shows every machine billing in the account and `gpu sweep --install-timer` guards every project on the machine and cleans up orphans. See [Keeping the account clean](docs/USAGE.md#keeping-the-account-clean).
77
90
 
78
- The API key is read from the environment, a project `.env`, or `~/.gpu/credentials`. It never goes in the manifest, and the agent never sees it.
91
+ `gpu login` saves the API key in `~/.gpu/credentials` (mode 600) after checking it; the environment and a project `.env` work too. It never goes in the manifest, and the agent never sees it.
79
92
 
80
93
  ## Docs
81
94
 
82
- - [Letting a coding agent rent GPUs (Claude Code, Cursor, Claude Desktop)](docs/AGENTS.md)
95
+ - [Letting a coding agent rent GPUs (Claude Code, Codex, Gemini CLI, Cursor, Claude Desktop)](docs/AGENTS.md)
83
96
  - [Integrating into an application (any language)](docs/INTEGRATION.md)
84
97
  - [Commands, workloads, and agents](docs/USAGE.md)
85
98
  - [Every config field](docs/CONFIG.md)
@@ -61,6 +61,8 @@ It found two more bugs, both fixed and tested: Vast reports a stopping instance
61
61
 
62
62
  On 2026-10-25 a headless Claude Code with no prior context (`claude -p`) was given a fresh directory holding only `gpu agent install`'s skill and `.mcp.json`, plus `VAST_API_KEY` in `.env`. It was asked in one sentence to test Qwen2.5-0.5B on a rented GPU, ask it the capital of Brazil, report time and cost, and destroy the machine. It loaded the `gpu` skill by itself and called `gpu_init` (with the cap and VRAM from the request), `gpu_validate`, `gpu_offers`, then `gpu_setup` until ready, `curl` for the question, `gpu_status`, `gpu_destroy` and `gpu_ls`. The model answered "Brasília", the account was empty at the end, and the agent reported setup time and cost correctly, noting that `session_cost_usd` leaves out the replaced host.
63
63
 
64
+ On 2026-11-02 the self-install was checked the same way. In an empty home directory with no `uv`, `gpu` or Python tool on `PATH`, a headless Claude Code got one sentence: "Set up GPU for Agents for me. I'm using Claude Code. Fetch …/install.md with curl and follow its steps." It ran `install.sh --agent claude`, which put `gpu` 0.6.0 in `~/.local/bin`, the skill in `~/.claude/skills/gpu4agents/` and the `gpu` server in the user MCP config. `gpu login --check` exited 5, and the agent told the user to create a key and run `gpu login` in their own terminal, without asking for the key. It read the skill with `gpu agent skill` and rented nothing. Separately, `install.sh` run with `GPU4AGENTS_PYTHON=3.13` on a machine without Python 3.13 had uv download CPython 3.13 and install the tool on it.
65
+
64
66
  | What was checked | What happened |
65
67
  | --- | --- |
66
68
  | Setup longer than any tool timeout | `gpu_setup` returned `running` every 300 s for 42 minutes and re-attached to the same background job each time: one machine at a time, never a second setup. |
@@ -1,31 +1,57 @@
1
1
  # Letting a coding agent rent GPUs
2
2
 
3
- `gpu` ships a skill and a local MCP server, so Claude Code, Cursor or Claude Desktop can rent a GPU, serve a model, run an experiment against it and shut it down, from a request like "compare Qwen 7B and Llama 8B on these 50 prompts".
3
+ `gpu` ships a skill and a local MCP server, so Claude Code, Codex, Gemini CLI, Cursor or Claude Desktop can rent a GPU, serve a model, run an experiment against it and shut it down, from a request like "compare Qwen 7B and Llama 8B on these 50 prompts".
4
4
 
5
5
  The watchdog on the machine is what makes this safe to hand to an agent: a machine the agent forgets, or a conversation that ends mid-experiment, stops by itself after `lifecycle.idle_timeout`.
6
6
 
7
- ## Install
7
+ ## Install: let the agent do it
8
+
9
+ Paste into the agent, naming it:
10
+
11
+ ```text
12
+ Set up GPU for Agents for me. I'm using Codex. Fetch https://gpu4agents.jambu.ai/install.md with curl and follow its steps.
13
+ ```
14
+
15
+ [`INSTALL.md`](../INSTALL.md) (served at `/install.md`) tells the agent to run [`install.sh`](../install.sh) with `--agent <runtime>`, check the account with `gpu login --check`, send the user to `gpu login` if there is no key, read the skill with `gpu agent skill`, and stop there: nothing is rented during installation.
16
+
17
+ `install.sh` needs only `curl` on Linux or macOS (Windows: WSL). It installs [uv](https://docs.astral.sh/uv/) if missing and runs `uv tool install --upgrade --python 3.12 gpu4agents`, so uv downloads a Python when the machine has no suitable one. It removes an old `jambu-gpu` tool, then runs `gpu agent install -t <runtime> --global` (`auto` by default: every runtime it finds). Options: `--agent claude|codex|gemini|cursor|auto|none`, `--no-modify-path`; `GPU4AGENTS_SPEC` installs a pinned version or a wheel.
18
+
19
+ With the [skills](https://skills.sh) CLI, `npx skills add jambuai/gpu4agents` installs only the skill (from [`skills/gpu4agents/`](../skills/gpu4agents/SKILL.md)); its first section installs the `gpu` command when it is missing.
20
+
21
+ ## Connect the account: `gpu login`
22
+
23
+ The user, in their own terminal:
24
+
25
+ ```bash
26
+ gpu login # hidden prompt; checks the key against Vast.ai, then saves it
27
+ gpu login --check # exit 0 and where the key was found, or exit 5
28
+ echo "$KEY" | gpu login --stdin
29
+ ```
30
+
31
+ The key goes to `~/.gpu/credentials` (mode 600, directory 700); other lines in the file are kept. A key in the environment or a project `.env` still works and wins over the file. The skill tells the agent never to ask for the key in chat, read those files, or print it.
32
+
33
+ ## Install by hand
8
34
 
9
35
  ```bash
10
- pipx install gpu4agents # or: uv tool install gpu4agents
11
- cd your-project
12
- echo "VAST_API_KEY=..." >> .env # or ~/.gpu/credentials; never in jambu.yaml
13
- gpu agent install # Claude Code
14
- gpu agent install -t cursor # Cursor
15
- gpu agent install -t desktop # Claude Desktop
36
+ uv tool install --python 3.12 gpu4agents # or: pipx install gpu4agents
37
+ gpu agent install -t claude # this project; add --global for every project
38
+ gpu agent install -t auto --global # every runtime on this machine
16
39
  ```
17
40
 
18
41
  | Target | Skill | MCP server |
19
42
  | --- | --- | --- |
20
- | `claude` (default) | `.claude/skills/gpu/SKILL.md` | `.mcp.json` (Claude Code asks once to approve it) |
21
- | `cursor` | the same `.claude/skills/gpu/` (Cursor reads Claude's skill directories) | `.cursor/mcp.json`, with `--project ${workspaceFolder}` |
43
+ | `claude` | `.claude/skills/gpu4agents/SKILL.md` | `.mcp.json` (Claude Code asks once to approve it); `--global`: `claude mcp add --scope user` |
44
+ | `codex` | `.agents/skills/gpu4agents/SKILL.md` | `[mcp_servers.gpu]` in `$CODEX_HOME/config.toml` (`~/.codex`), always user-wide |
45
+ | `gemini` | `.agents/skills/gpu4agents/SKILL.md` | `mcpServers.gpu` in `.gemini/settings.json` |
46
+ | `cursor` | `.agents/skills/gpu4agents/SKILL.md` | `.cursor/mcp.json`, with `--project ${workspaceFolder}` |
22
47
  | `desktop` | `~/.gpu/gpu-skill.zip`, to upload under Settings > Capabilities > Skills | `claude_desktop_config.json`, pinned to this project |
48
+ | `auto` | every runtime found: its command on `PATH` or its config directory | |
23
49
 
24
- `--global` installs for every project of the user instead: the skill in `~/.claude/skills/gpu/`, the MCP server through `claude mcp add --scope user` and in `~/.cursor/mcp.json`. Existing config files are merged; only the `gpu` entry is written. Re-running is safe.
50
+ `.agents/skills/` is the shared directory Codex, Gemini CLI and Cursor all read. `--global` puts skills and settings under the home directory instead of the project. Existing config files are merged; only the `gpu` entry is written, and a skill installed by an older version under the name `gpu` is replaced. Re-running is safe. Restart the agent afterwards so it loads the skill and the tools.
25
51
 
26
- Claude Desktop and Cursor do not get your shell's environment. Keep `VAST_API_KEY` in the project `.env` or `~/.gpu/credentials`, not only in an `export`.
52
+ Claude Desktop and Cursor do not get your shell's environment. Keep `VAST_API_KEY` in `~/.gpu/credentials` (`gpu login`) or the project `.env`, not only in an `export`.
27
53
 
28
- Another agent that reads `SKILL.md` files: `gpu agent skill > path/to/skills/gpu/SKILL.md`. Any MCP client: run `gpu mcp --project /path/to/project` over stdio.
54
+ Another agent that reads `SKILL.md` files: `gpu agent skill > path/to/skills/gpu4agents/SKILL.md`. Any MCP client: run `gpu mcp --project /path/to/project` over stdio.
29
55
 
30
56
  ## The MCP tools
31
57
 
@@ -1,5 +1,5 @@
1
1
  """GPU for Agents: GPUs your agent rents on its own, billed only while they work."""
2
2
 
3
- __version__ = "0.5.0"
3
+ __version__ = "0.6.0"
4
4
 
5
5
  __all__ = ["__version__"]
@@ -1,16 +1,23 @@
1
1
  """`gpu agent install`: put the skill and the MCP server where agents look for them.
2
2
 
3
- One skill copy, in ``.claude/skills/gpu/`` (or ``~/.claude/skills/gpu/``),
4
- serves both Claude Code and Cursor, which also reads Claude's skill
5
- directories. The MCP server is registered per client:
3
+ Skills follow the open Agent Skills layout, one folder with a SKILL.md:
4
+
5
+ - claude: ``.claude/skills/gpu4agents/`` (Claude Code; Cursor reads it too)
6
+ - codex, gemini, cursor: ``.agents/skills/gpu4agents/``, the shared directory
7
+ Codex, Gemini CLI and Cursor all read
8
+
9
+ Each is per project, or under the home directory with --global. The MCP server
10
+ is registered per client:
6
11
 
7
12
  - claude: ``.mcp.json`` in the project; with --global, ``claude mcp add -s user``
13
+ - codex: ``[mcp_servers.gpu]`` in ``$CODEX_HOME/config.toml`` (``~/.codex``)
14
+ - gemini: ``mcpServers.gpu`` in ``.gemini/settings.json``, or ``~/.gemini/settings.json``
8
15
  - cursor: ``.cursor/mcp.json`` in the project, or ``~/.cursor/mcp.json``
9
16
  - desktop: Claude Desktop's ``claude_desktop_config.json``, pinned to this
10
17
  project, plus a zip of the skill to upload in Settings
11
18
 
12
- Existing config files are merged, never replaced: only the ``gpu`` server
13
- entry is written.
19
+ ``auto`` picks every runtime found on this machine. Existing config files are
20
+ merged, never replaced: only the ``gpu`` server entry is written.
14
21
  """
15
22
 
16
23
  from __future__ import annotations
@@ -18,6 +25,7 @@ from __future__ import annotations
18
25
  import json
19
26
  import os
20
27
  import platform
28
+ import re
21
29
  import shutil
22
30
  import subprocess
23
31
  import sys
@@ -29,8 +37,10 @@ from ..core.errors import ConfigError
29
37
  from ..core.paths import gpu_home
30
38
 
31
39
  SERVER_NAME = "gpu"
32
- SKILL_NAME = "gpu"
33
- TARGETS = ("claude", "cursor", "desktop")
40
+ SKILL_NAME = "gpu4agents"
41
+ LEGACY_SKILL_NAME = "gpu"
42
+ TARGETS = ("claude", "codex", "gemini", "cursor", "desktop")
43
+ SHARED_SKILL_TARGETS = ("codex", "gemini", "cursor")
34
44
 
35
45
 
36
46
  def skill_source() -> Path:
@@ -66,6 +76,25 @@ def desktop_config_path() -> Path:
66
76
  return (Path(base) if base else Path.home() / ".config") / "Claude" / "claude_desktop_config.json"
67
77
 
68
78
 
79
+ def codex_home() -> Path:
80
+ return Path(os.environ.get("CODEX_HOME") or Path.home() / ".codex")
81
+
82
+
83
+ def detect_targets() -> list[str]:
84
+ """Runtimes present on this machine: their command on PATH or their config directory."""
85
+ home = Path.home()
86
+ found = []
87
+ if shutil.which("claude") or (home / ".claude").is_dir():
88
+ found.append("claude")
89
+ if shutil.which("codex") or codex_home().is_dir():
90
+ found.append("codex")
91
+ if shutil.which("gemini") or (home / ".gemini").is_dir():
92
+ found.append("gemini")
93
+ if shutil.which("cursor") or shutil.which("cursor-agent") or (home / ".cursor").is_dir():
94
+ found.append("cursor")
95
+ return found
96
+
97
+
69
98
  def merge_server(path: Path, entry: dict[str, Any]) -> str:
70
99
  """Write ``mcpServers.gpu`` into a JSON config. Returns created/updated/unchanged."""
71
100
  data: dict[str, Any] = {}
@@ -90,8 +119,60 @@ def merge_server(path: Path, entry: dict[str, Any]) -> str:
90
119
  return action
91
120
 
92
121
 
122
+ _TOML_TABLE = re.compile(r"^\s*\[\[?[^\]]+\]\]?\s*(#.*)?$")
123
+
124
+
125
+ def merge_codex_server(path: Path, command: list[str]) -> str:
126
+ """Write ``[mcp_servers.gpu]`` into Codex's config.toml, keeping everything else.
127
+
128
+ Python 3.10 has no TOML parser, and only one table is ours, so the table is
129
+ replaced as text: from its header to the next table header.
130
+ """
131
+ header = f"[mcp_servers.{SERVER_NAME}]"
132
+ block = [
133
+ header,
134
+ f"command = {json.dumps(command[0])}",
135
+ f"args = {json.dumps([*command[1:], 'mcp'])}",
136
+ ]
137
+ lines = path.read_text().splitlines() if path.is_file() else []
138
+ start = next((i for i, line in enumerate(lines) if line.strip() == header), None)
139
+ if start is None:
140
+ while lines and not lines[-1].strip():
141
+ lines.pop()
142
+ new = lines + ([""] if lines else []) + block
143
+ action = "created"
144
+ else:
145
+ end = next(
146
+ (i for i in range(start + 1, len(lines)) if _TOML_TABLE.match(lines[i])),
147
+ len(lines),
148
+ )
149
+ old = [line for line in lines[start:end] if line.strip()]
150
+ if old == block:
151
+ return "unchanged"
152
+ tail = lines[end:]
153
+ new = lines[:start] + block + ([""] + tail if tail else [])
154
+ action = "updated"
155
+ path.parent.mkdir(parents=True, exist_ok=True)
156
+ path.write_text("\n".join(new) + "\n")
157
+ return action
158
+
159
+
160
+ def _is_our_legacy_skill(path: Path) -> bool:
161
+ """The skill shipped before 0.6.0 under the name ``gpu``."""
162
+ try:
163
+ text = path.read_text()
164
+ except OSError:
165
+ return False
166
+ return text.startswith(f"---\nname: {LEGACY_SKILL_NAME}\n") and "gpu_setup" in text
167
+
168
+
93
169
  def write_skill(directory: Path) -> str:
94
170
  target = directory / SKILL_NAME / "SKILL.md"
171
+ legacy = directory / LEGACY_SKILL_NAME / "SKILL.md"
172
+ if _is_our_legacy_skill(legacy):
173
+ legacy.unlink()
174
+ if not any(legacy.parent.iterdir()):
175
+ legacy.parent.rmdir()
95
176
  text = skill_text()
96
177
  if target.is_file() and target.read_text() == text:
97
178
  return "unchanged"
@@ -109,6 +190,21 @@ def write_skill_zip(path: Path) -> str:
109
190
  return "created"
110
191
 
111
192
 
193
+ def resolve_targets(targets: list[str]) -> list[str]:
194
+ wanted: list[str] = []
195
+ for target in targets:
196
+ expanded = detect_targets() if target == "auto" else [target]
197
+ for name in expanded:
198
+ if name not in wanted:
199
+ wanted.append(name)
200
+ unknown = [t for t in wanted if t not in TARGETS]
201
+ if unknown:
202
+ raise ConfigError(
203
+ f"unknown target {', '.join(unknown)} (choose from {', '.join(TARGETS)}, or auto)"
204
+ )
205
+ return wanted
206
+
207
+
112
208
  def install(
113
209
  targets: list[str],
114
210
  project_dir: Path,
@@ -116,16 +212,27 @@ def install(
116
212
  run=subprocess.run,
117
213
  ) -> dict[str, Any]:
118
214
  """Install for each target. ``run`` is a seam for tests (``claude mcp add``)."""
119
- unknown = [t for t in targets if t not in TARGETS]
120
- if unknown:
121
- raise ConfigError(f"unknown target {', '.join(unknown)} (choose from {', '.join(TARGETS)})")
215
+ targets = resolve_targets(targets)
122
216
  project_dir = project_dir.resolve()
217
+ base = Path.home() if global_scope else project_dir
123
218
  done: list[dict[str, Any]] = []
124
219
  notes: list[str] = []
220
+ if not targets:
221
+ notes.append(
222
+ "no agent runtime found (Claude Code, Codex, Gemini CLI, Cursor); "
223
+ "name one with -t claude|codex|gemini|cursor|desktop"
224
+ )
225
+
226
+ if "claude" in targets:
227
+ skills = base / ".claude" / "skills"
228
+ done.append({"what": "skill (Claude Code)", "path": str(skills / SKILL_NAME / "SKILL.md"), "action": write_skill(skills)})
125
229
 
126
- if "claude" in targets or "cursor" in targets:
127
- skills = (Path.home() if global_scope else project_dir) / ".claude" / "skills"
128
- done.append({"what": "skill (Claude Code, Cursor)", "path": str(skills / SKILL_NAME / "SKILL.md"), "action": write_skill(skills)})
230
+ shared = [t for t in targets if t in SHARED_SKILL_TARGETS]
231
+ if shared:
232
+ skills = base / ".agents" / "skills"
233
+ names = {"codex": "Codex", "gemini": "Gemini CLI", "cursor": "Cursor"}
234
+ label = ", ".join(names[t] for t in shared)
235
+ done.append({"what": f"skill ({label})", "path": str(skills / SKILL_NAME / "SKILL.md"), "action": write_skill(skills)})
129
236
 
130
237
  if "claude" in targets:
131
238
  if global_scope:
@@ -147,6 +254,18 @@ def install(
147
254
  done.append({"what": "MCP server (Claude Code)", "path": str(path), "action": merge_server(path, entry)})
148
255
  notes.append("Claude Code asks once to approve the project's .mcp.json servers")
149
256
 
257
+ if "codex" in targets:
258
+ # Codex reads MCP servers from the user config; the server works in
259
+ # whatever directory Codex runs, so one entry serves every project.
260
+ path = codex_home() / "config.toml"
261
+ done.append({"what": "MCP server (Codex)", "path": str(path), "action": merge_codex_server(path, gpu_command(portable=False))})
262
+
263
+ if "gemini" in targets:
264
+ command = gpu_command(portable=not global_scope)
265
+ entry = {"command": command[0], "args": [*command[1:], "mcp"]}
266
+ path = base / ".gemini" / "settings.json"
267
+ done.append({"what": "MCP server (Gemini CLI)", "path": str(path), "action": merge_server(path, entry)})
268
+
150
269
  if "cursor" in targets:
151
270
  command = gpu_command(portable=not global_scope)
152
271
  entry = {
@@ -154,7 +273,7 @@ def install(
154
273
  "command": command[0],
155
274
  "args": [*command[1:], "mcp", "--project", "${workspaceFolder}"],
156
275
  }
157
- path = (Path.home() if global_scope else project_dir) / ".cursor" / "mcp.json"
276
+ path = base / ".cursor" / "mcp.json"
158
277
  done.append({"what": "MCP server (Cursor)", "path": str(path), "action": merge_server(path, entry)})
159
278
 
160
279
  if "desktop" in targets:
@@ -166,8 +285,10 @@ def install(
166
285
  done.append({"what": "skill zip (Claude Desktop)", "path": str(archive), "action": write_skill_zip(archive)})
167
286
  notes.append(f"Claude Desktop: restart it, then upload {archive} under Settings > Capabilities > Skills")
168
287
  notes.append(
169
- "Claude Desktop does not get your shell's environment: keep VAST_API_KEY in the "
170
- "project .env or ~/.gpu/credentials"
288
+ "Claude Desktop does not get your shell's environment: keep VAST_API_KEY in "
289
+ "~/.gpu/credentials (gpu login) or the project .env"
171
290
  )
172
291
 
173
- return {"installed": done, "notes": notes}
292
+ if any(t in targets for t in ("claude", "codex", "gemini", "cursor")):
293
+ notes.append("restart the agent (or start a new session) so it loads the skill and the gpu tools")
294
+ return {"installed": done, "targets": targets, "notes": notes}