klavex 0.1.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.
- klavex-0.1.0/.gitignore +17 -0
- klavex-0.1.0/.python-version +1 -0
- klavex-0.1.0/IMPLEMENTATION.md +343 -0
- klavex-0.1.0/PKG-INFO +73 -0
- klavex-0.1.0/README.md +42 -0
- klavex-0.1.0/pyproject.toml +76 -0
- klavex-0.1.0/src/klavex/__init__.py +1 -0
- klavex-0.1.0/src/klavex/__main__.py +4 -0
- klavex-0.1.0/src/klavex/api.py +166 -0
- klavex-0.1.0/src/klavex/auth.py +211 -0
- klavex-0.1.0/src/klavex/cli.py +98 -0
- klavex-0.1.0/src/klavex/commands/__init__.py +0 -0
- klavex-0.1.0/src/klavex/commands/envs.py +29 -0
- klavex-0.1.0/src/klavex/commands/login.py +35 -0
- klavex-0.1.0/src/klavex/commands/logout.py +35 -0
- klavex-0.1.0/src/klavex/commands/projects.py +29 -0
- klavex-0.1.0/src/klavex/commands/run.py +65 -0
- klavex-0.1.0/src/klavex/commands/status.py +24 -0
- klavex-0.1.0/src/klavex/commands/vars.py +36 -0
- klavex-0.1.0/src/klavex/commands/whoami.py +28 -0
- klavex-0.1.0/src/klavex/config.py +109 -0
- klavex-0.1.0/src/klavex/errors.py +61 -0
- klavex-0.1.0/src/klavex/inject.py +28 -0
- klavex-0.1.0/src/klavex/tokens.py +42 -0
- klavex-0.1.0/tests/__init__.py +0 -0
- klavex-0.1.0/tests/conftest.py +60 -0
- klavex-0.1.0/tests/test_api.py +212 -0
- klavex-0.1.0/tests/test_auth.py +167 -0
- klavex-0.1.0/tests/test_config.py +90 -0
- klavex-0.1.0/tests/test_inject.py +62 -0
- klavex-0.1.0/tests/test_tokens.py +36 -0
klavex-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.10
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# Klavex CLI — Implementation Plan
|
|
2
|
+
|
|
3
|
+
The third repo in the Klavex stack. Sibling to `klavex-frontend` (dashboard) and `klavex-backend` (FastAPI on Lambda).
|
|
4
|
+
|
|
5
|
+
## Goal
|
|
6
|
+
|
|
7
|
+
A Python package (`pip install klavex`) that:
|
|
8
|
+
|
|
9
|
+
1. Logs in through the browser — no passwords typed into a terminal.
|
|
10
|
+
2. Pulls environment variables from a Klavex project/environment the user has access to.
|
|
11
|
+
3. **Injects them into a child process at runtime.** Never writes a `.env` to disk.
|
|
12
|
+
|
|
13
|
+
This is the load-bearing claim of the product: "no plaintext secrets on disk." The CLI is what makes that claim true at the point of use. If the CLI ever writes secrets to a file by default, the entire pitch collapses.
|
|
14
|
+
|
|
15
|
+
The replaced workflow:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Before
|
|
19
|
+
cat > .env <<EOF
|
|
20
|
+
DATABASE_URL=postgres://...
|
|
21
|
+
STRIPE_KEY=sk_live_...
|
|
22
|
+
EOF
|
|
23
|
+
npm start
|
|
24
|
+
|
|
25
|
+
# After
|
|
26
|
+
klavex run -- npm start # vars exist only in npm's process env
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Naming
|
|
30
|
+
|
|
31
|
+
| Thing | Name |
|
|
32
|
+
| -------------------- | ----------------- |
|
|
33
|
+
| Repo directory | `klavex-cli` |
|
|
34
|
+
| Python package | `klavex` |
|
|
35
|
+
| PyPI distribution | `klavex` |
|
|
36
|
+
| Binary on `$PATH` | `klavex` (+ `kx` short alias) |
|
|
37
|
+
| Project pinning file | `.klavex` |
|
|
38
|
+
|
|
39
|
+
Matches the existing `klavex-*` repo convention while letting the PyPI/CLI surface the product name.
|
|
40
|
+
|
|
41
|
+
## Stack
|
|
42
|
+
|
|
43
|
+
| Layer | Choice | Why |
|
|
44
|
+
| ------------ | ------------------------------------- | ---------------------------------------------------------------------------- |
|
|
45
|
+
| Python | 3.10+ | Match macOS/Ubuntu LTS defaults; `match` statements + better typing |
|
|
46
|
+
| CLI framework| `typer` | Type-hinted args, auto-help, mature; `click` underneath if we ever need it |
|
|
47
|
+
| HTTP | `httpx` | Sync API today; async-ready if we add streaming reveals later |
|
|
48
|
+
| Token store | `keyring` | OS-native: macOS Keychain / Linux Secret Service / Windows Credential Mgr |
|
|
49
|
+
| Build | `hatchling` via `pyproject.toml` | PEP 621 standard, no `setup.py` |
|
|
50
|
+
| Lint/format | `ruff` | Replaces black + flake8 + isort |
|
|
51
|
+
| Tests | `pytest` + `respx` (httpx mocking) | Fast, no real network in unit tests |
|
|
52
|
+
| Packaging | `uv build` then `twine upload` | `uv` is faster than `python -m build`; output is the same wheel |
|
|
53
|
+
|
|
54
|
+
## Repo layout
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
klavex-cli/
|
|
58
|
+
├── pyproject.toml
|
|
59
|
+
├── README.md # User-facing install + usage
|
|
60
|
+
├── IMPLEMENTATION.md # This file
|
|
61
|
+
├── LICENSE
|
|
62
|
+
├── .gitignore
|
|
63
|
+
├── .python-version # 3.10
|
|
64
|
+
├── src/
|
|
65
|
+
│ └── klavex/
|
|
66
|
+
│ ├── __init__.py # __version__
|
|
67
|
+
│ ├── __main__.py # python -m klavex
|
|
68
|
+
│ ├── cli.py # typer app entry
|
|
69
|
+
│ ├── auth.py # browser-callback + device-code flows
|
|
70
|
+
│ ├── api.py # httpx-based backend client
|
|
71
|
+
│ ├── tokens.py # keyring read/write, refresh
|
|
72
|
+
│ ├── config.py # ~/.config/klavex/config.toml + .klavex
|
|
73
|
+
│ ├── inject.py # subprocess env injection (the core)
|
|
74
|
+
│ ├── errors.py # typed exceptions → exit codes
|
|
75
|
+
│ └── commands/
|
|
76
|
+
│ ├── __init__.py
|
|
77
|
+
│ ├── login.py
|
|
78
|
+
│ ├── logout.py
|
|
79
|
+
│ ├── whoami.py
|
|
80
|
+
│ ├── projects.py
|
|
81
|
+
│ ├── envs.py
|
|
82
|
+
│ ├── vars.py
|
|
83
|
+
│ ├── run.py # the headline command
|
|
84
|
+
│ ├── export.py # opt-in only, prints to stdout with a warning
|
|
85
|
+
│ ├── use.py # writes .klavex pin file
|
|
86
|
+
│ └── status.py
|
|
87
|
+
└── tests/
|
|
88
|
+
├── conftest.py
|
|
89
|
+
├── test_auth.py
|
|
90
|
+
├── test_inject.py
|
|
91
|
+
├── test_config.py
|
|
92
|
+
└── test_commands/
|
|
93
|
+
└── ...
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Commands
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
klavex login # browser login, stores token in keychain
|
|
100
|
+
klavex logout # delete token from keychain
|
|
101
|
+
klavex whoami # show current user + team
|
|
102
|
+
klavex status # show login state + active project pin
|
|
103
|
+
|
|
104
|
+
klavex projects # list projects in your team
|
|
105
|
+
klavex envs <project_id|slug> # list environments in a project
|
|
106
|
+
klavex vars -e <env_id> # list variable NAMES (no values, ever)
|
|
107
|
+
|
|
108
|
+
klavex run -e <env_id> -- <cmd...> # spawn cmd with vars in os.environ
|
|
109
|
+
klavex run -- npm start # uses .klavex pin
|
|
110
|
+
klavex export -e <env_id> # PRINT export statements (gated, see below)
|
|
111
|
+
|
|
112
|
+
klavex use -p <project> -e <env> # writes .klavex to cwd
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`run` is the headline. Everything else exists to support it.
|
|
116
|
+
|
|
117
|
+
### Why `run` and not `export`
|
|
118
|
+
|
|
119
|
+
`klavex export | source` is the obvious shortcut, but it leaves the secrets in the parent shell's environment forever — visible to every later command, every other process the shell spawns, every shell history mishap. We support `export` because power users will demand it, but it prints a one-line warning to stderr and is documented as a footgun. The default verb is `run`.
|
|
120
|
+
|
|
121
|
+
## Auth flow — browser callback (primary)
|
|
122
|
+
|
|
123
|
+
This is the same pattern `gh auth login --web` and `gcloud auth login` use. Faster UX than device-code when a browser is available.
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
┌───────────┐ ┌──────────┐ ┌─────────┐
|
|
127
|
+
│ CLI │ │ Browser │ │ API │
|
|
128
|
+
└─────┬─────┘ └────┬─────┘ └────┬────┘
|
|
129
|
+
│ │ │
|
|
130
|
+
1. Bind 127.0.0.1:<random_port> │ │
|
|
131
|
+
Generate state, code_verifier │ │
|
|
132
|
+
│ │ │
|
|
133
|
+
2. webbrowser.open( │
|
|
134
|
+
https://app.klavex.dev/cli/authorize │
|
|
135
|
+
?callback=http://127.0.0.1:PORT │
|
|
136
|
+
&state=...&code_challenge=...&device_name=hostname) │
|
|
137
|
+
│ ──────────────────────────► │ │
|
|
138
|
+
│ │ 3. User logs in (or is) │
|
|
139
|
+
│ │ already logged in. │
|
|
140
|
+
│ │ Frontend POSTs to: │
|
|
141
|
+
│ │ /cli/exchange │
|
|
142
|
+
│ │ ─────────────────────────► │
|
|
143
|
+
│ │ │
|
|
144
|
+
│ │ 4. Backend verifies the │
|
|
145
|
+
│ │ dashboard session, │
|
|
146
|
+
│ │ mints a CLI token, │
|
|
147
|
+
│ │ redirects to callback │
|
|
148
|
+
│ │ with ?code=...&state=. │
|
|
149
|
+
│ │ ◄───────────────────────── │
|
|
150
|
+
│ │ │
|
|
151
|
+
5. Local server receives /callback │
|
|
152
|
+
Verifies state, sends: │
|
|
153
|
+
POST /cli/token │
|
|
154
|
+
{ code, code_verifier } │
|
|
155
|
+
│ ──────────────────────────────────────────────────────► │
|
|
156
|
+
│ │
|
|
157
|
+
6. { cli_token, expires_at, user, team } │
|
|
158
|
+
│ ◄────────────────────────────────────────────────────── │
|
|
159
|
+
│ │
|
|
160
|
+
7. keyring.set_password("klavex", "default", cli_token) │
|
|
161
|
+
Browser tab shows "You can close this tab" │
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Key properties:
|
|
165
|
+
|
|
166
|
+
- **PKCE** (RFC 7636). The `code_verifier` never leaves the CLI; the browser only sees a `code_challenge`. So even if the redirect URL is logged somewhere, the auth code alone is useless.
|
|
167
|
+
- **State** parameter. Random per-flow. Defends against the browser opening a stale callback into a fresh CLI invocation.
|
|
168
|
+
- **Loopback only**, port chosen at runtime. No need to register a fixed port; matches OAuth 2.0 for native apps (RFC 8252 §7.3).
|
|
169
|
+
- The local HTTP server serves exactly **one route** (`/callback`), accepts exactly **one request**, then shuts down. Hard 60-second timeout.
|
|
170
|
+
- The CLI token is **distinct from a dashboard session token**. Backend stores it in a new `klavex-cli-tokens` table with `(user_id, device_name, created_at, last_used_at, revoked_at)`. Listing/revoking lives under Settings → CLI in the dashboard.
|
|
171
|
+
|
|
172
|
+
### Device-code fallback
|
|
173
|
+
|
|
174
|
+
For headless servers (CI, SSH-only boxes) where `webbrowser.open` can't do anything useful:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
klavex login --device
|
|
178
|
+
# Visit https://app.klavex.dev/cli/device and enter: ABCD-1234
|
|
179
|
+
# Waiting for confirmation...
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Mirrors GitHub's `gh auth login` device flow. Same backend `/cli/device-code` + `/cli/token` (poll) endpoints. Same final token in the keychain.
|
|
183
|
+
|
|
184
|
+
### Token storage
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
# tokens.py
|
|
188
|
+
import keyring
|
|
189
|
+
SERVICE = "klavex"
|
|
190
|
+
|
|
191
|
+
def store(token: str, profile: str = "default") -> None:
|
|
192
|
+
keyring.set_password(SERVICE, profile, token)
|
|
193
|
+
|
|
194
|
+
def load(profile: str = "default") -> str | None:
|
|
195
|
+
return keyring.get_password(SERVICE, profile)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
If `keyring` can't find a backend (rare — usually a stripped-down container), we **refuse to store** and fall back to memory-only auth (token re-issued every CLI invocation). We never silently write the token to a plaintext file. That's the whole product.
|
|
199
|
+
|
|
200
|
+
The cli token lives until either (a) the backend's expiry (e.g. 90 days idle), (b) the user revokes it from the dashboard, or (c) `klavex logout` clears it locally. Refresh is not needed for the CLI token specifically — it's long-lived but revocable.
|
|
201
|
+
|
|
202
|
+
## The injection mechanism (`run`)
|
|
203
|
+
|
|
204
|
+
This is twelve lines of code and the entire reason the product exists.
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
# inject.py
|
|
208
|
+
import os, subprocess, sys
|
|
209
|
+
|
|
210
|
+
def run(env_vars: dict[str, str], argv: list[str]) -> int:
|
|
211
|
+
if not argv:
|
|
212
|
+
raise UsageError("Nothing to run. Pass a command after --.")
|
|
213
|
+
# Child inherits parent env, then overrides with fetched secrets.
|
|
214
|
+
# Secrets are NEVER written to disk and NEVER appear in the parent shell.
|
|
215
|
+
child_env = {**os.environ, **env_vars}
|
|
216
|
+
proc = subprocess.run(argv, env=child_env)
|
|
217
|
+
return proc.returncode
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Notes:
|
|
221
|
+
|
|
222
|
+
- We `subprocess.run` (not `os.execvpe`) so that we can wrap the call later with telemetry/audit hooks if needed. Exit code is propagated.
|
|
223
|
+
- On POSIX, `argv[0]` is resolved via `$PATH` because we don't pass `shell=True`. So `klavex run -- npm start` works without invoking a shell. **Never use `shell=True`** — it'd let unescaped values in args become shell injection.
|
|
224
|
+
- We **don't** use `os.environ.update()`. The parent CLI process's env stays clean; secrets only exist in the child.
|
|
225
|
+
- We support `--no-inherit` for the paranoid case where you only want the secrets, not your shell's `PATH`/`HOME`/etc. Default is inherit because most apps need `PATH`.
|
|
226
|
+
|
|
227
|
+
### Project pinning (`.klavex`)
|
|
228
|
+
|
|
229
|
+
A small TOML file in the project root, like `.nvmrc` or `.python-version`:
|
|
230
|
+
|
|
231
|
+
```toml
|
|
232
|
+
# .klavex
|
|
233
|
+
project = "proj_a1b2c3d4e5f6"
|
|
234
|
+
default_env = "env_dev_xyz"
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
`klavex run -- npm start` walks up from cwd looking for `.klavex`, then uses the pinned project + default env. `-e` overrides. Committed to git.
|
|
238
|
+
|
|
239
|
+
`klavex use -p <project> -e <env>` writes this file.
|
|
240
|
+
|
|
241
|
+
## Backend client
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
# api.py
|
|
245
|
+
import httpx, os
|
|
246
|
+
from .tokens import load
|
|
247
|
+
|
|
248
|
+
def _client() -> httpx.Client:
|
|
249
|
+
base = os.environ.get("KLAVEX_API_URL",
|
|
250
|
+
"https://<api-gateway-id>.amazonaws.com/api")
|
|
251
|
+
token = load() or _fail_unauthenticated()
|
|
252
|
+
return httpx.Client(
|
|
253
|
+
base_url=base,
|
|
254
|
+
headers={"Authorization": f"Bearer {token}"},
|
|
255
|
+
timeout=httpx.Timeout(10.0, connect=5.0),
|
|
256
|
+
)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Endpoints used (alignment with backend):
|
|
260
|
+
|
|
261
|
+
| CLI command | HTTP call |
|
|
262
|
+
| -------------------- | --------------------------------------------- |
|
|
263
|
+
| `login` | `POST /cli/device-code`, `POST /cli/token` |
|
|
264
|
+
| `logout` | `DELETE /cli/tokens/me` |
|
|
265
|
+
| `whoami` | `GET /auth/me` |
|
|
266
|
+
| `projects` | `GET /projects` |
|
|
267
|
+
| `envs <p>` | `GET /projects/{p}/environments` |
|
|
268
|
+
| `vars -e <e>` | `GET /environments/{e}/variables` (names only — see below) |
|
|
269
|
+
| `run -e <e>` | `POST /environments/{e}/variables:reveal` |
|
|
270
|
+
|
|
271
|
+
**`POST :reveal` does not exist on the backend yet.** This is STEP-3 backlog item #1 in the frontend repo's `CLAUDE.md`. Today, `GET /environments/{id}/variables` returns plaintext, which the dashboard masks client-side. The CLI **must not ship** against that endpoint — the audit trail would be wrong (no `variable_revealed_cli` action) and there's no IP whitelist enforcement. We block the CLI's release on the backend exposing a real reveal endpoint. See "Backend prerequisites" below.
|
|
272
|
+
|
|
273
|
+
## Configuration
|
|
274
|
+
|
|
275
|
+
| Path | Contents |
|
|
276
|
+
| ------------------------------------------- | --------------------------------------- |
|
|
277
|
+
| OS keychain (`klavex` service) | CLI token |
|
|
278
|
+
| `~/.config/klavex/config.toml` | API URL override, default profile, telemetry opt-out |
|
|
279
|
+
| `<project>/.klavex` | Per-project pin (committed) |
|
|
280
|
+
| `$KLAVEX_API_URL` env var | Override for staging/local |
|
|
281
|
+
|
|
282
|
+
Nothing in `config.toml` is sensitive. The keychain is the only place a token lives.
|
|
283
|
+
|
|
284
|
+
## Errors and exit codes
|
|
285
|
+
|
|
286
|
+
```
|
|
287
|
+
0 success
|
|
288
|
+
1 generic error
|
|
289
|
+
2 usage error (bad args)
|
|
290
|
+
3 not authenticated
|
|
291
|
+
4 network / API unreachable
|
|
292
|
+
5 permission denied (logged in but no access)
|
|
293
|
+
6 project/env not found
|
|
294
|
+
130 interrupted (Ctrl-C, propagated)
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Typed exceptions in `errors.py` map to exit codes in `cli.py`. Each error renders a one-line human message + a `--debug` flag for the full traceback.
|
|
298
|
+
|
|
299
|
+
## Security checklist
|
|
300
|
+
|
|
301
|
+
- [ ] Secrets only ever exist in the child process's memory.
|
|
302
|
+
- [ ] Token in OS keychain only. No fallback to plaintext file.
|
|
303
|
+
- [ ] PKCE on the browser flow.
|
|
304
|
+
- [ ] State parameter validated on callback.
|
|
305
|
+
- [ ] Loopback redirect, ephemeral port, single request, 60s timeout.
|
|
306
|
+
- [ ] No `shell=True` in `subprocess` calls.
|
|
307
|
+
- [ ] `export` command warns to stderr that it's a footgun.
|
|
308
|
+
- [ ] CLI token is rate-limited and IP-whitelisted server-side (STEP-3 #2).
|
|
309
|
+
- [ ] Reveal calls write `variable_revealed_cli` audit rows.
|
|
310
|
+
- [ ] `--debug` does not log the token, the bearer header, or any var values.
|
|
311
|
+
- [ ] `httpx` TLS verification on by default; no `verify=False` flag.
|
|
312
|
+
- [ ] Variable values are never written to logs, telemetry, or crash reports.
|
|
313
|
+
|
|
314
|
+
## Backend prerequisites (blocks CLI v1)
|
|
315
|
+
|
|
316
|
+
These are all in the frontend's `RECONCILIATION.md` STEP-3 backlog already. Listing here so the CLI is honest about what it depends on:
|
|
317
|
+
|
|
318
|
+
1. **`/cli/device-code`, `/cli/token` endpoints + `klavex-cli-tokens` table.** STEP-3 #3.
|
|
319
|
+
2. **`/cli/exchange` endpoint** for the browser-callback flow (mints a one-shot code from a logged-in dashboard session).
|
|
320
|
+
3. **`POST /environments/{id}/variables:reveal`** that returns plaintext, runs IP whitelist enforcement, and writes a `variable_revealed_cli` audit row. STEP-3 #1.
|
|
321
|
+
4. **IP whitelist middleware** enforced on the reveal endpoint. STEP-3 #2.
|
|
322
|
+
5. **`/cli/authorize` page in the dashboard** (frontend) — the page the CLI redirects to. Shows "Klavex CLI on `<hostname>` wants to access your account." with Approve/Cancel.
|
|
323
|
+
6. **Settings → CLI Tokens tab** in the dashboard for listing/revoking. STEP-3 #3 again.
|
|
324
|
+
|
|
325
|
+
## Phasing
|
|
326
|
+
|
|
327
|
+
**v0.1 — auth only.** `login`, `logout`, `whoami`, `status`. No reveal. Lets us validate the browser flow and token storage end-to-end with minimal backend work.
|
|
328
|
+
|
|
329
|
+
**v0.2 — read paths.** `projects`, `envs`, `vars` (names only). Confirms the API client + auth middleware story.
|
|
330
|
+
|
|
331
|
+
**v0.3 — `run`.** Requires the reveal endpoint. The point of the whole project. Ship this and the CLI is useful.
|
|
332
|
+
|
|
333
|
+
**v0.4 — polish.** `export` (with the footgun warning), `use`, `.klavex` pinning, shell completion (`typer`'s built-in), better error messages.
|
|
334
|
+
|
|
335
|
+
**v0.5 — distribution.** PyPI publish. Homebrew tap (a small formula that does `pip install klavex` into an isolated venv and symlinks the binary). Docs site link.
|
|
336
|
+
|
|
337
|
+
## Open questions
|
|
338
|
+
|
|
339
|
+
1. **Refresh story.** CLI tokens are long-lived (90 days idle). If a token expires mid-`run`, we currently fail with exit 3 and ask the user to re-login. Acceptable? Or do we want silent re-login via a stored refresh token? Storing two tokens means two keychain entries.
|
|
340
|
+
2. **Multiple profiles.** `--profile work` for users who belong to multiple teams under different accounts. Easy to add to `keyring` (it's just a profile-keyed entry). Defer to v0.4.
|
|
341
|
+
3. **Caching.** Do we cache `projects`/`envs` listings? Probably no — they're cheap and freshness matters for permission changes. **Never cache reveal results.**
|
|
342
|
+
4. **Telemetry.** Anonymous CLI version + command name pings, opt-out via `config.toml` and `KLAVEX_NO_TELEMETRY=1`. Decide before v0.5.
|
|
343
|
+
5. **Shell integration.** A future `klavex shell -e dev` that spawns a subshell with vars set, like `aws-vault exec`. Strictly worse than `run` for security but users will ask for it. Defer.
|
klavex-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: klavex
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Klavex CLI — pull environment variables into a process without writing secrets to disk.
|
|
5
|
+
Author: Klavex
|
|
6
|
+
License: Proprietary
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Environment :: Console
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Operating System :: MacOS
|
|
11
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Requires-Dist: httpx<1.0,>=0.27
|
|
19
|
+
Requires-Dist: keyring<26,>=24
|
|
20
|
+
Requires-Dist: platformdirs<5,>=4
|
|
21
|
+
Requires-Dist: tomli-w<2.0,>=1.0
|
|
22
|
+
Requires-Dist: tomli>=2.0; python_version < '3.11'
|
|
23
|
+
Requires-Dist: typer<1.0,>=0.12
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest-cov>=5; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
28
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# klavex
|
|
33
|
+
|
|
34
|
+
CLI for [Klavex](https://klavex.dev). Pulls environment variables from your team's vault and injects them into a child process — secrets never touch disk.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install klavex
|
|
38
|
+
klavex login
|
|
39
|
+
klavex run -- npm start
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
See [IMPLEMENTATION.md](./IMPLEMENTATION.md) for the design.
|
|
43
|
+
|
|
44
|
+
## Commands
|
|
45
|
+
|
|
46
|
+
| Command | Status |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| `klavex login` | v0.1 |
|
|
49
|
+
| `klavex logout` | v0.1 |
|
|
50
|
+
| `klavex whoami` | v0.1 |
|
|
51
|
+
| `klavex status` | v0.1 |
|
|
52
|
+
| `klavex projects` | v0.2 |
|
|
53
|
+
| `klavex envs <project>` | v0.2 |
|
|
54
|
+
| `klavex vars -e <env>` | v0.2 |
|
|
55
|
+
| `klavex run -e <env> -- <cmd>` | v0.3 (blocked on backend reveal endpoint) |
|
|
56
|
+
| `klavex export -e <env>` | v0.4 |
|
|
57
|
+
| `klavex use -p <project> -e <env>` | v0.4 |
|
|
58
|
+
|
|
59
|
+
## Development
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install -e ".[dev]"
|
|
63
|
+
klavex --version
|
|
64
|
+
pytest
|
|
65
|
+
ruff check .
|
|
66
|
+
mypy
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Override the API URL for staging or local:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
export KLAVEX_API_URL=http://localhost:8000
|
|
73
|
+
```
|
klavex-0.1.0/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# klavex
|
|
2
|
+
|
|
3
|
+
CLI for [Klavex](https://klavex.dev). Pulls environment variables from your team's vault and injects them into a child process — secrets never touch disk.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install klavex
|
|
7
|
+
klavex login
|
|
8
|
+
klavex run -- npm start
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
See [IMPLEMENTATION.md](./IMPLEMENTATION.md) for the design.
|
|
12
|
+
|
|
13
|
+
## Commands
|
|
14
|
+
|
|
15
|
+
| Command | Status |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| `klavex login` | v0.1 |
|
|
18
|
+
| `klavex logout` | v0.1 |
|
|
19
|
+
| `klavex whoami` | v0.1 |
|
|
20
|
+
| `klavex status` | v0.1 |
|
|
21
|
+
| `klavex projects` | v0.2 |
|
|
22
|
+
| `klavex envs <project>` | v0.2 |
|
|
23
|
+
| `klavex vars -e <env>` | v0.2 |
|
|
24
|
+
| `klavex run -e <env> -- <cmd>` | v0.3 (blocked on backend reveal endpoint) |
|
|
25
|
+
| `klavex export -e <env>` | v0.4 |
|
|
26
|
+
| `klavex use -p <project> -e <env>` | v0.4 |
|
|
27
|
+
|
|
28
|
+
## Development
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install -e ".[dev]"
|
|
32
|
+
klavex --version
|
|
33
|
+
pytest
|
|
34
|
+
ruff check .
|
|
35
|
+
mypy
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Override the API URL for staging or local:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
export KLAVEX_API_URL=http://localhost:8000
|
|
42
|
+
```
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "klavex"
|
|
7
|
+
description = "Klavex CLI — pull environment variables into a process without writing secrets to disk."
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
license = { text = "Proprietary" }
|
|
11
|
+
authors = [{ name = "Klavex" }]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 3 - Alpha",
|
|
14
|
+
"Environment :: Console",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Operating System :: MacOS",
|
|
17
|
+
"Operating System :: POSIX :: Linux",
|
|
18
|
+
"Operating System :: Microsoft :: Windows",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
]
|
|
24
|
+
dynamic = ["version"]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"typer>=0.12,<1.0",
|
|
27
|
+
"httpx>=0.27,<1.0",
|
|
28
|
+
"keyring>=24,<26",
|
|
29
|
+
"platformdirs>=4,<5",
|
|
30
|
+
"tomli>=2.0; python_version < '3.11'",
|
|
31
|
+
"tomli-w>=1.0,<2.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.optional-dependencies]
|
|
35
|
+
dev = [
|
|
36
|
+
"pytest>=8",
|
|
37
|
+
"pytest-cov>=5",
|
|
38
|
+
"respx>=0.21",
|
|
39
|
+
"ruff>=0.6",
|
|
40
|
+
"mypy>=1.11",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.scripts]
|
|
44
|
+
klavex = "klavex.cli:main"
|
|
45
|
+
kx = "klavex.cli:main"
|
|
46
|
+
|
|
47
|
+
[tool.hatch.version]
|
|
48
|
+
path = "src/klavex/__init__.py"
|
|
49
|
+
|
|
50
|
+
[tool.hatch.build.targets.wheel]
|
|
51
|
+
packages = ["src/klavex"]
|
|
52
|
+
|
|
53
|
+
[tool.ruff]
|
|
54
|
+
line-length = 100
|
|
55
|
+
target-version = "py310"
|
|
56
|
+
src = ["src", "tests"]
|
|
57
|
+
|
|
58
|
+
[tool.ruff.lint]
|
|
59
|
+
select = [
|
|
60
|
+
"E", "F", "W", # pycodestyle + pyflakes
|
|
61
|
+
"I", # isort
|
|
62
|
+
"B", # bugbear
|
|
63
|
+
"UP", # pyupgrade
|
|
64
|
+
"SIM", # simplify
|
|
65
|
+
"RUF", # ruff-specific
|
|
66
|
+
]
|
|
67
|
+
ignore = ["E501"] # line length handled by formatter
|
|
68
|
+
|
|
69
|
+
[tool.mypy]
|
|
70
|
+
python_version = "3.10"
|
|
71
|
+
strict = true
|
|
72
|
+
files = ["src/klavex"]
|
|
73
|
+
|
|
74
|
+
[tool.pytest.ini_options]
|
|
75
|
+
testpaths = ["tests"]
|
|
76
|
+
addopts = "-q"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|