railguey 0.2.3__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 (42) hide show
  1. railguey-0.2.3/.github/workflows/publish.yml +30 -0
  2. railguey-0.2.3/.github/workflows/test.yml +28 -0
  3. railguey-0.2.3/.gitignore +13 -0
  4. railguey-0.2.3/CHANGELOG.md +18 -0
  5. railguey-0.2.3/LICENSE +21 -0
  6. railguey-0.2.3/PKG-INFO +314 -0
  7. railguey-0.2.3/PYPI-PLAN.md +115 -0
  8. railguey-0.2.3/README.md +282 -0
  9. railguey-0.2.3/WHY-NOT-RAILWAY-APP.md +72 -0
  10. railguey-0.2.3/WHY-RAILGUEY.md +210 -0
  11. railguey-0.2.3/docs/railguey-solution.png +0 -0
  12. railguey-0.2.3/docs/railway-github-app-problem.png +0 -0
  13. railguey-0.2.3/examples/deploy-multi-service.yml +31 -0
  14. railguey-0.2.3/examples/deploy-with-tests.yml +47 -0
  15. railguey-0.2.3/examples/deploy.yml +34 -0
  16. railguey-0.2.3/logo.png +0 -0
  17. railguey-0.2.3/pyproject.toml +55 -0
  18. railguey-0.2.3/railguey/__init__.py +9 -0
  19. railguey-0.2.3/railguey/cli.py +200 -0
  20. railguey-0.2.3/railguey/lib/__init__.py +19 -0
  21. railguey-0.2.3/railguey/lib/accounts.py +166 -0
  22. railguey-0.2.3/railguey/lib/cli_backend.py +54 -0
  23. railguey-0.2.3/railguey/lib/doctor.py +995 -0
  24. railguey-0.2.3/railguey/lib/graphql.py +103 -0
  25. railguey-0.2.3/railguey/lib/orchestrate.py +590 -0
  26. railguey-0.2.3/railguey/lib/token.py +30 -0
  27. railguey-0.2.3/railguey/lib/tools.py +1266 -0
  28. railguey-0.2.3/railguey/lib/totp.py +201 -0
  29. railguey-0.2.3/railguey/mcp.py +642 -0
  30. railguey-0.2.3/registry/service-registry.yaml +276 -0
  31. railguey-0.2.3/ship/README.md +127 -0
  32. railguey-0.2.3/tests/__init__.py +0 -0
  33. railguey-0.2.3/tests/conftest.py +40 -0
  34. railguey-0.2.3/tests/helpers.py +69 -0
  35. railguey-0.2.3/tests/test_cli.py +120 -0
  36. railguey-0.2.3/tests/test_doctor.py +359 -0
  37. railguey-0.2.3/tests/test_graphql.py +108 -0
  38. railguey-0.2.3/tests/test_integration.py +410 -0
  39. railguey-0.2.3/tests/test_orchestrate.py +1405 -0
  40. railguey-0.2.3/tests/test_runner.py +96 -0
  41. railguey-0.2.3/tests/test_token.py +79 -0
  42. railguey-0.2.3/tests/test_tools.py +877 -0
@@ -0,0 +1,30 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ environment: pypi
11
+ permissions:
12
+ id-token: write
13
+ contents: read
14
+
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Set up Python
19
+ uses: actions/setup-python@v5
20
+ with:
21
+ python-version: "3.12"
22
+
23
+ - name: Install build tools
24
+ run: pip install build
25
+
26
+ - name: Build package
27
+ run: python -m build
28
+
29
+ - name: Publish to PyPI
30
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,28 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Python ${{ matrix.python-version }}
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ - name: Install dependencies
25
+ run: pip install -e ".[dev]"
26
+
27
+ - name: Run tests
28
+ run: pytest tests/ --ignore=tests/test_integration.py -v
@@ -0,0 +1,13 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .eggs/
8
+ .venv/
9
+ venv/
10
+ *.env
11
+ *.env.local
12
+ .pytest_cache/
13
+ .mcp.json
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0 — First PyPI release
4
+
5
+ - **Package restructure**: proper Python package (`railguey/` directory) instead of bare `server.py`
6
+ - **Entry point**: `pip install railguey` then run `railguey` or use `uvx railguey`
7
+ - **CI**: GitHub Actions test matrix (Python 3.10–3.13) and PyPI publish on tag
8
+ - **Wheel fix**: `packages = ["railguey"]` — tests and docs no longer leak into the wheel
9
+
10
+ No tool changes. All 17 tools work exactly as before.
11
+
12
+ ## 0.1.0 — Initial development
13
+
14
+ - 17 MCP tools: 10 CLI-backed, 5 GraphQL-backed, 1 coaching, 1 audit
15
+ - Dual backend: Railway CLI + Backboard GraphQL API
16
+ - Project-scoped token discovery from `.env.local`
17
+ - `railguey_doctor` workspace audit (4-point check)
18
+ - 39 unit tests + 38 integration tests
railguey-0.2.3/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eidos AGI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,314 @@
1
+ Metadata-Version: 2.4
2
+ Name: railguey
3
+ Version: 0.2.3
4
+ Summary: Project-scoped Railway MCP server — reads RAILWAY_TOKEN from .env.local, no login needed
5
+ Project-URL: Homepage, https://github.com/eidos-agi/railguey
6
+ Project-URL: Repository, https://github.com/eidos-agi/railguey
7
+ Project-URL: Issues, https://github.com/eidos-agi/railguey/issues
8
+ Project-URL: Changelog, https://github.com/eidos-agi/railguey/blob/main/CHANGELOG.md
9
+ Author-email: Daniel Shanklin <daniel@eidosagi.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: deploy,devops,mcp,model-context-protocol,railway
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Build Tools
21
+ Classifier: Topic :: System :: Systems Administration
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: click>=8.0.0
24
+ Requires-Dist: httpx>=0.27.0
25
+ Requires-Dist: mcp>=1.0.0
26
+ Requires-Dist: pyyaml>=6.0
27
+ Requires-Dist: qrcode[pil]>=7.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
30
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
31
+ Description-Content-Type: text/markdown
32
+
33
+ <p align="center">
34
+ <img src="logo.png" alt="railguey" width="500">
35
+ </p>
36
+
37
+ <p align="center">
38
+ Project-scoped Railway MCP server.<br>
39
+ Reads <code>RAILWAY_TOKEN</code> from each project's <code>.env.local</code> — no <code>railway login</code> needed.
40
+ </p>
41
+
42
+ <p align="center">
43
+ <a href="https://pypi.org/project/railguey/"><img src="https://img.shields.io/pypi/v/railguey" alt="PyPI"></a>
44
+ <a href="https://github.com/eidos-agi/railguey/actions/workflows/test.yml"><img src="https://github.com/eidos-agi/railguey/actions/workflows/test.yml/badge.svg" alt="Tests"></a>
45
+ <a href="https://pypi.org/project/railguey/"><img src="https://img.shields.io/pypi/pyversions/railguey" alt="Python"></a>
46
+ <a href="LICENSE"><img src="https://img.shields.io/github/license/eidos-agi/railguey" alt="License"></a>
47
+ </p>
48
+
49
+ ---
50
+
51
+ **railguey is for teams and businesses that need reliable Railway deployments.** It is not the simplest way to deploy — Railway's built-in GitHub app is simpler. But railguey is more reliable, because it draws a cleaner engineering boundary.
52
+
53
+ ## Why not just use Railway's GitHub App?
54
+
55
+ Railway's GitHub App is fast to set up: connect your repo, push to main, and your service deploys. For prototyping, that speed is genuinely great. But speed of setup and quality of engineering are different things.
56
+
57
+ The GitHub App bundles five responsibilities into one opaque chain: watch for code changes, authenticate to GitHub, receive a webhook, clone the repo, build and deploy. When the chain works, it feels like magic. When it doesn't — and it has broken [four times in four months](WHY-RAILGUEY.md#the-incident-timeline) — there is no observability, no retry, and no notification. Your push goes in. Nothing comes out. You find out when a customer does.
58
+
59
+ <p align="center">
60
+ <img src="docs/railway-github-app-problem.png" alt="Railway GitHub App: the fragile chain" width="600">
61
+ </p>
62
+
63
+ Every decision diamond in this diagram is a place where the chain can silently break. Missed webhooks, lost build triggers, GitHub App auth failures — all produce the same result: **nothing happens, and nobody tells you**.
64
+
65
+ This isn't a bug. It's an architectural choice. Railway chose to own the entire pipeline from push to deploy, which means every failure in GitHub's webhook delivery becomes Railway's problem — and yours.
66
+
67
+ ## How railguey fixes this
68
+
69
+ railguey separates concerns. GitHub Actions watches your repo (GitHub watching GitHub — the thing it was built for). railguey handles the deploy via Railway's API using project-scoped tokens. Railway builds and runs your service (the thing *it* was built for). Each system does one job.
70
+
71
+ <p align="center">
72
+ <img src="docs/railguey-solution.png" alt="railguey: token-based deploy pipeline" width="700">
73
+ </p>
74
+
75
+ If CI fails, GitHub tells you. If the deploy fails, the CLI returns an error. If the service is unhealthy, `railguey doctor` catches it. Every step is observable, retryable, and owned by the system best suited to do it.
76
+
77
+ **Fast delivery and good engineering aren't opposites** — but Railway's GitHub App trades the second for the first. railguey gives you both.
78
+ | Doc | What it covers |
79
+ |-----|---------------|
80
+ | **[WHY-NOT-RAILWAY-APP.md](WHY-NOT-RAILWAY-APP.md)** | The architectural argument — why coupling CI/CD triggering with deployment is a design flaw, not just a bug |
81
+ | **[WHY-RAILGUEY.md](WHY-RAILGUEY.md)** | The evidence — four incidents, community reports, and what the project-token pattern does differently |
82
+ | **[WHY-RAILGUEY.md#case-study](WHY-RAILGUEY.md#case-study-ghost-repo-links-block-env-var-operations-march-2026)** | Real-world case study — ghost GitHub repo links silently blocked env var operations across 5 services |
83
+
84
+ ## When to use railguey
85
+
86
+ - You manage Railway services from AI agents (Claude Code, Cursor, etc.) and want them to deploy, rollback, and read logs without `railway login`
87
+ - You run multiple Railway projects and want one auth pattern across local dev, CI/CD, and AI tooling
88
+ - Deploy reliability matters — production services, client projects, anything where a silently missed deploy costs you
89
+ - You're already using GitHub Actions and want Railway deploys in the same pipeline as your tests
90
+
91
+ ## When NOT to use railguey
92
+
93
+ - **Quick demos and hobby projects.** Railway's GitHub app is genuinely convenient for push-and-forget deploys. If you're prototyping and don't care about deploy reliability, the built-in integration is fine.
94
+ - **You don't use an MCP client or CLI.** railguey includes both an MCP server and a CLI. But if you only deploy once in a while from the terminal, the Railway CLI with a project token is enough.
95
+ - **You're happy with the dashboard.** If you deploy once a week and check status manually, railguey adds complexity you don't need.
96
+
97
+ ## Known limitations
98
+
99
+ - **All tools depend on Railway's Backboard GraphQL API**, which isn't officially documented. The schema could change without notice.
100
+ - **No Railway CLI required.** All 17 tools use pure GraphQL with project-scoped tokens. The CLI backend module still exists for backward compatibility but is no longer used by any tool.
101
+ - **One token per project.** Project-scoped tokens can't query across projects. If you manage 10 projects, you need 10 `.env.local` files in 10 workspaces. This is by design (isolation), but it's more setup than a user-level login.
102
+
103
+ ## Install
104
+
105
+ Requires Python 3.10+. No Railway CLI needed.
106
+
107
+ ```bash
108
+ pip install railguey
109
+ ```
110
+
111
+ Or run without installing:
112
+
113
+ ```bash
114
+ uvx railguey --help
115
+ ```
116
+
117
+ <details>
118
+ <summary>Install from source</summary>
119
+
120
+ ```bash
121
+ git clone https://github.com/eidos-agi/railguey.git
122
+ cd railguey
123
+ pip install -e .
124
+ ```
125
+
126
+ </details>
127
+
128
+ ## CLI usage
129
+
130
+ `pip install railguey` gives you the `railguey` command with all 17 tools as subcommands:
131
+
132
+ ```bash
133
+ railguey status ~/repos/my-app
134
+ railguey logs ~/repos/my-app cerebro --lines 50
135
+ railguey deploy ~/repos/my-app web
136
+ railguey deployments ~/repos/my-app cerebro --limit 5
137
+ railguey doctor ~/repos/my-app
138
+ railguey variables ~/repos/my-app web
139
+ railguey service-info ~/repos/my-app cerebro
140
+ ```
141
+
142
+ Every command takes a `workspace` path — the directory containing `.env.local` with `RAILWAY_TOKEN`.
143
+
144
+ ## Configure Claude Code (MCP)
145
+
146
+ The recommended setup uses the Claude Code CLI to register railguey as a user-level MCP server:
147
+
148
+ ```bash
149
+ pip install railguey
150
+ claude mcp add --scope user railguey -- railguey serve
151
+ ```
152
+
153
+ This makes railguey available in every Claude Code session, across all projects. The `--scope user` flag writes to `~/.claude.json` so it persists globally.
154
+
155
+ <details>
156
+ <summary>Manual config (alternative)</summary>
157
+
158
+ Add to `~/.claude.json` under `mcpServers`:
159
+
160
+ ```json
161
+ {
162
+ "mcpServers": {
163
+ "railguey": {
164
+ "command": "railguey",
165
+ "args": ["serve"]
166
+ }
167
+ }
168
+ }
169
+ ```
170
+
171
+ </details>
172
+
173
+ <details>
174
+ <summary>From source (development)</summary>
175
+
176
+ ```bash
177
+ git clone https://github.com/eidos-agi/railguey.git
178
+ cd railguey
179
+ pip install -e .
180
+ claude mcp add --scope user railguey -- railguey serve
181
+ ```
182
+
183
+ Or manually in `~/.claude.json`:
184
+
185
+ ```json
186
+ {
187
+ "mcpServers": {
188
+ "railguey": {
189
+ "command": "python3",
190
+ "args": ["-m", "railguey.mcp"]
191
+ }
192
+ }
193
+ }
194
+ ```
195
+
196
+ </details>
197
+
198
+ ## Tools
199
+
200
+ 17 tools, all pure GraphQL. No Railway CLI required — just a project-scoped token.
201
+
202
+ | Tool | What it does |
203
+ |------|-------------|
204
+ | `railguey_status` | Project overview — all services, deploy status, domains |
205
+ | `railguey_services` | List services with IDs |
206
+ | `railguey_logs` | Fetch recent deploy or build logs (with optional filter) |
207
+ | `railguey_deploy` | Trigger a deploy from linked source |
208
+ | `railguey_redeploy` | Redeploy latest deployment (rebuilds from source) |
209
+ | `railguey_restart` | Restart latest deployment (no rebuild, fast) |
210
+ | `railguey_variables` | List env vars for a service |
211
+ | `railguey_variable_set` | Set an env var (triggers redeploy) |
212
+ | `railguey_domain` | Generate a railway.app domain or add a custom domain |
213
+ | `railguey_environment_create` | Create a new environment (staging, preview, etc.) |
214
+ | `railguey_deployments` | Deployment history with IDs, statuses, timestamps, rollback eligibility |
215
+ | `railguey_rollback` | Roll back to a specific deployment |
216
+ | `railguey_service_info` | Full service config — build/start commands, healthcheck, region, replicas |
217
+ | `railguey_http_logs` | HTTP request logs — status codes, latency, paths |
218
+ | `railguey_deployment_logs` | Logs for a specific deployment by ID (deploy or build, with filter) |
219
+ | `railguey_unlink_repo` | Disconnect a service from GitHub repo linking |
220
+
221
+ ### Coaching tools
222
+
223
+ | Tool | What it does |
224
+ |------|-------------|
225
+ | `railguey_doctor` | Audit a workspace for deployment best practices (4-point check) |
226
+
227
+ `railguey_doctor` checks:
228
+ 1. `RAILWAY_TOKEN` exists in `.env.local`
229
+ 2. `.env.local` is in `.gitignore`
230
+ 3. GitHub Actions deploy workflow exists with token-based CI/CD
231
+ 4. No services linked to GitHub repos
232
+
233
+ Every tool requires a `workspace` parameter — the absolute path to a project directory that has a `.env.local` (or `.env`) containing `RAILWAY_TOKEN`.
234
+
235
+ ## Example
236
+
237
+ **CLI:**
238
+ ```bash
239
+ railguey logs ~/repos/my-app web --lines 50
240
+ ```
241
+
242
+ **MCP (via AI agent):**
243
+ ```python
244
+ railguey_logs(workspace="/Users/you/repos/my-app", service="web", lines=50)
245
+ ```
246
+
247
+ **Python library:**
248
+ ```python
249
+ from railguey.lib import tools
250
+ result = await tools.logs("/Users/you/repos/my-app", "web", lines=50)
251
+ ```
252
+
253
+ All three read `/Users/you/repos/my-app/.env.local`, extract the token, and run:
254
+
255
+ ```bash
256
+ RAILWAY_TOKEN=<token> railway logs --service web --lines 50
257
+ ```
258
+
259
+ ## The project-token pattern
260
+
261
+ Railway lets you create [project-scoped tokens](https://docs.railway.com/guides/cli#project-tokens) — API keys that authenticate to a single project without any user login. These tokens work the same way everywhere:
262
+
263
+ | Context | How the token is used |
264
+ |---------|----------------------|
265
+ | **Local dev** | `.env.local` — `railway logs`, `railway up`, etc. |
266
+ | **AI agents (railguey)** | Read from `.env.local` at the workspace path |
267
+ | **GitHub Actions CI/CD** | Repository secret → `RAILWAY_TOKEN` env var |
268
+ | **Any CI system** | Same — export the token, run `railway up` |
269
+
270
+ One mechanism. No OAuth. No repo linking. No webhook fragility.
271
+
272
+ <details>
273
+ <summary>GitHub Actions deploy workflow (copy-paste)</summary>
274
+
275
+ Add `RAILWAY_TOKEN` as a [repository secret](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions), then:
276
+
277
+ ```yaml
278
+ # .github/workflows/deploy.yml
279
+ name: Deploy to Railway
280
+
281
+ on:
282
+ push:
283
+ branches: [main]
284
+
285
+ jobs:
286
+ deploy:
287
+ runs-on: ubuntu-latest
288
+ steps:
289
+ - uses: actions/checkout@v4
290
+
291
+ - name: Install Railway CLI
292
+ run: curl -fsSL https://railway.com/install.sh | sh
293
+
294
+ - name: Deploy
295
+ env:
296
+ RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
297
+ run: railway up --service ${{ vars.RAILWAY_SERVICE }} --detach
298
+ ```
299
+
300
+ Set `RAILWAY_SERVICE` as a [repository variable](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables). More examples in [`examples/`](examples/).
301
+
302
+ </details>
303
+
304
+ ## Token discovery
305
+
306
+ 1. Looks for `RAILWAY_TOKEN=` in `{workspace}/.env.local`
307
+ 2. Falls back to `{workspace}/.env`
308
+ 3. Raises a clear error if not found
309
+
310
+ Supports bare values, single-quoted, and double-quoted values.
311
+
312
+ ## License
313
+
314
+ MIT
@@ -0,0 +1,115 @@
1
+ # Plan: Publish railguey to PyPI
2
+
3
+ ## Prerequisites
4
+
5
+ ### 1. PyPI account
6
+ - Register at https://pypi.org/account/register/ (use `daniel@eidosagi.com`)
7
+ - Enable 2FA (required for new projects since 2024)
8
+ - Create an API token: Account Settings → API tokens → "Add API token"
9
+ - Scope: "Entire account" for first publish, then lock to project after
10
+
11
+ ### 2. TestPyPI account (optional but recommended)
12
+ - Register at https://test.pypi.org/account/register/
13
+ - Same 2FA + API token setup
14
+ - Dry-run publishes here first
15
+
16
+ ## Steps
17
+
18
+ ### Step 1: Verify package metadata
19
+ ```bash
20
+ pip install check-wheel-contents twine
21
+ python -m build
22
+ check-wheel-contents dist/railguey-0.2.0-py3-none-any.whl
23
+ twine check dist/*
24
+ ```
25
+
26
+ Confirm:
27
+ - `pyproject.toml` has all required fields (name, version, description, license, authors, urls)
28
+ - README.md renders correctly (twine check validates this)
29
+ - No test files or secrets in the wheel
30
+
31
+ ### Step 2: Claim the name on TestPyPI
32
+ ```bash
33
+ twine upload --repository testpypi dist/*
34
+ ```
35
+
36
+ Then verify:
37
+ ```bash
38
+ pip install --index-url https://test.pypi.org/simple/ railguey
39
+ railguey --version
40
+ railguey --help
41
+ railguey serve --help
42
+ ```
43
+
44
+ ### Step 3: Publish to PyPI
45
+ ```bash
46
+ twine upload dist/*
47
+ ```
48
+
49
+ Verify:
50
+ ```bash
51
+ pip install railguey
52
+ railguey --version
53
+ uvx railguey --help
54
+ uvx railguey serve
55
+ ```
56
+
57
+ ### Step 4: Lock API token to project
58
+ - Go to PyPI → Account Settings → API tokens
59
+ - Delete the "Entire account" token
60
+ - Create a new token scoped to the `railguey` project only
61
+ - Store in GitHub repo secret as `PYPI_API_TOKEN`
62
+
63
+ ### Step 5: Automate future releases (GitHub Actions)
64
+ Create `.github/workflows/publish.yml`:
65
+ ```yaml
66
+ name: Publish to PyPI
67
+
68
+ on:
69
+ release:
70
+ types: [published]
71
+
72
+ jobs:
73
+ publish:
74
+ runs-on: ubuntu-latest
75
+ permissions:
76
+ id-token: write # trusted publishing
77
+ steps:
78
+ - uses: actions/checkout@v4
79
+ - uses: actions/setup-python@v5
80
+ with:
81
+ python-version: "3.12"
82
+ - run: pip install build
83
+ - run: python -m build
84
+ - uses: pypa/gh-action-pypi-publish@release/v1
85
+ ```
86
+
87
+ Even better: use PyPI's [Trusted Publishers](https://docs.pypi.org/trusted-publishers/) instead of API tokens. Link the GitHub repo to the PyPI project — no secrets to manage.
88
+
89
+ ### Step 6: GitHub release workflow
90
+ 1. Bump version in `pyproject.toml` and `railguey/__init__.py`
91
+ 2. Update `CHANGELOG.md`
92
+ 3. Commit: `git commit -m "release: v0.2.0"`
93
+ 4. Tag: `git tag v0.2.0`
94
+ 5. Push: `git push origin main --tags`
95
+ 6. Create GitHub release from the tag → triggers publish workflow
96
+
97
+ ## Checklist
98
+
99
+ - [ ] PyPI account created with 2FA
100
+ - [ ] `twine check` passes
101
+ - [ ] TestPyPI upload works
102
+ - [ ] `pip install railguey` from TestPyPI works
103
+ - [ ] PyPI upload works
104
+ - [ ] `uvx railguey --help` works (proves it's installable without pre-install)
105
+ - [ ] `uvx railguey serve` starts MCP server
106
+ - [ ] API token scoped to project only
107
+ - [ ] GitHub Actions publish workflow added
108
+ - [ ] Trusted Publishers configured (replaces API token)
109
+
110
+ ## Version strategy
111
+
112
+ - `0.2.0` — current (lib extraction + CLI)
113
+ - `0.2.x` — patch releases (bug fixes)
114
+ - `0.3.0` — next feature release
115
+ - `1.0.0` — when API is stable and battle-tested across multiple teams