sweatstack-cli 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.
- sweatstack_cli-0.1.0/.claude/settings.local.json +21 -0
- sweatstack_cli-0.1.0/.gitignore +10 -0
- sweatstack_cli-0.1.0/.python-version +1 -0
- sweatstack_cli-0.1.0/DEVELOPMENT.md +403 -0
- sweatstack_cli-0.1.0/Makefile +50 -0
- sweatstack_cli-0.1.0/PKG-INFO +132 -0
- sweatstack_cli-0.1.0/PLAN.md +1130 -0
- sweatstack_cli-0.1.0/README.md +102 -0
- sweatstack_cli-0.1.0/pyproject.toml +94 -0
- sweatstack_cli-0.1.0/src/sweatstack_cli/__init__.py +3 -0
- sweatstack_cli-0.1.0/src/sweatstack_cli/api/__init__.py +5 -0
- sweatstack_cli-0.1.0/src/sweatstack_cli/api/client.py +192 -0
- sweatstack_cli-0.1.0/src/sweatstack_cli/auth/__init__.py +216 -0
- sweatstack_cli-0.1.0/src/sweatstack_cli/auth/callback_server.py +216 -0
- sweatstack_cli-0.1.0/src/sweatstack_cli/auth/jwt.py +50 -0
- sweatstack_cli-0.1.0/src/sweatstack_cli/auth/pkce.py +50 -0
- sweatstack_cli-0.1.0/src/sweatstack_cli/auth/tokens.py +178 -0
- sweatstack_cli-0.1.0/src/sweatstack_cli/commands/__init__.py +1 -0
- sweatstack_cli-0.1.0/src/sweatstack_cli/commands/auth.py +127 -0
- sweatstack_cli-0.1.0/src/sweatstack_cli/commands/pages.py +130 -0
- sweatstack_cli-0.1.0/src/sweatstack_cli/config.py +32 -0
- sweatstack_cli-0.1.0/src/sweatstack_cli/console.py +6 -0
- sweatstack_cli-0.1.0/src/sweatstack_cli/exceptions.py +30 -0
- sweatstack_cli-0.1.0/src/sweatstack_cli/main.py +72 -0
- sweatstack_cli-0.1.0/src/sweatstack_cli/py.typed +0 -0
- sweatstack_cli-0.1.0/tests/__init__.py +1 -0
- sweatstack_cli-0.1.0/tests/conftest.py +69 -0
- sweatstack_cli-0.1.0/tests/test_api/__init__.py +1 -0
- sweatstack_cli-0.1.0/tests/test_api/test_client.py +195 -0
- sweatstack_cli-0.1.0/tests/test_auth/__init__.py +1 -0
- sweatstack_cli-0.1.0/tests/test_auth/test_callback_server.py +166 -0
- sweatstack_cli-0.1.0/tests/test_auth/test_jwt.py +92 -0
- sweatstack_cli-0.1.0/tests/test_auth/test_pkce.py +66 -0
- sweatstack_cli-0.1.0/tests/test_auth/test_tokens.py +173 -0
- sweatstack_cli-0.1.0/tests/test_commands/__init__.py +1 -0
- sweatstack_cli-0.1.0/tests/test_commands/test_cli.py +65 -0
- sweatstack_cli-0.1.0/uv.lock +453 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(uv pip install:*)",
|
|
5
|
+
"Bash(uv venv:*)",
|
|
6
|
+
"Bash(source .venv/bin/activate:*)",
|
|
7
|
+
"Bash(uv run pytest:*)",
|
|
8
|
+
"Bash(uv run ruff check:*)",
|
|
9
|
+
"Bash(uv run ruff format:*)",
|
|
10
|
+
"Bash(uv run mypy:*)",
|
|
11
|
+
"Bash(uv run sweatstack:*)",
|
|
12
|
+
"Bash(uv sync:*)",
|
|
13
|
+
"Bash(pytest:*)",
|
|
14
|
+
"Bash(ruff check:*)",
|
|
15
|
+
"WebFetch(domain:localhost)",
|
|
16
|
+
"Bash(curl:*)",
|
|
17
|
+
"Bash(python3:*)",
|
|
18
|
+
"Bash(make help:*)"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# SweatStack CLI — Development Guide
|
|
2
|
+
|
|
3
|
+
This document explains how to develop, maintain, and extend the SweatStack CLI.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Clone and install
|
|
9
|
+
git clone https://github.com/sweatstack/sweatstack-cli
|
|
10
|
+
cd sweatstack-cli
|
|
11
|
+
|
|
12
|
+
# Create virtual environment and install
|
|
13
|
+
uv venv
|
|
14
|
+
source .venv/bin/activate # or `.venv\Scripts\activate` on Windows
|
|
15
|
+
uv pip install -e ".[dev]"
|
|
16
|
+
|
|
17
|
+
# Run the CLI
|
|
18
|
+
sweatstack --help
|
|
19
|
+
|
|
20
|
+
# Run tests
|
|
21
|
+
pytest
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Project Structure
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
src/sweatstack_cli/
|
|
28
|
+
├── __init__.py # Package version
|
|
29
|
+
├── main.py # CLI entry point (Typer app)
|
|
30
|
+
├── config.py # Environment-based configuration
|
|
31
|
+
├── console.py # Rich console singleton
|
|
32
|
+
├── exceptions.py # Exception hierarchy with exit codes
|
|
33
|
+
├── auth/ # Authentication module
|
|
34
|
+
│ ├── __init__.py # Authenticator class (main API)
|
|
35
|
+
│ ├── pkce.py # PKCE challenge generation
|
|
36
|
+
│ ├── jwt.py # JWT payload decoding
|
|
37
|
+
│ ├── tokens.py # Token storage
|
|
38
|
+
│ └── callback_server.py # Local OAuth callback server
|
|
39
|
+
├── api/ # API client module
|
|
40
|
+
│ ├── __init__.py # Public exports
|
|
41
|
+
│ └── client.py # Authenticated HTTP client
|
|
42
|
+
└── commands/ # CLI command modules
|
|
43
|
+
├── __init__.py
|
|
44
|
+
├── auth.py # login, logout, whoami, status
|
|
45
|
+
└── pages.py # pages deploy, list, delete
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Architecture
|
|
49
|
+
|
|
50
|
+
### Design Principles
|
|
51
|
+
|
|
52
|
+
1. **Minimal dependencies** — Only essential packages: `typer`, `httpx`, `platformdirs`
|
|
53
|
+
2. **No magic** — Explicit configuration, clear error messages
|
|
54
|
+
3. **Testable** — Dependency injection, protocol-based abstractions
|
|
55
|
+
4. **Type safe** — Full type hints, strict mypy configuration
|
|
56
|
+
|
|
57
|
+
### Authentication Flow
|
|
58
|
+
|
|
59
|
+
The CLI uses OAuth2 PKCE (Proof Key for Code Exchange) for authentication:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
User runs `sweatstack login`
|
|
63
|
+
│
|
|
64
|
+
▼
|
|
65
|
+
Generate PKCE verifier + challenge
|
|
66
|
+
│
|
|
67
|
+
▼
|
|
68
|
+
Start local HTTP server on port 8400-8499
|
|
69
|
+
│
|
|
70
|
+
▼
|
|
71
|
+
Open browser to SweatStack OAuth page
|
|
72
|
+
│
|
|
73
|
+
▼
|
|
74
|
+
User authenticates in browser
|
|
75
|
+
│
|
|
76
|
+
▼
|
|
77
|
+
Browser redirects to localhost with auth code
|
|
78
|
+
│
|
|
79
|
+
▼
|
|
80
|
+
Exchange code for tokens (access + refresh)
|
|
81
|
+
│
|
|
82
|
+
▼
|
|
83
|
+
Store tokens in ~/.../SweatStack/tokens.json
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Token Storage
|
|
87
|
+
|
|
88
|
+
Tokens are stored in a platform-specific location using `platformdirs`:
|
|
89
|
+
|
|
90
|
+
| Platform | Path |
|
|
91
|
+
|----------|------|
|
|
92
|
+
| macOS | `~/Library/Application Support/SweatStack/SweatStack/tokens.json` |
|
|
93
|
+
| Linux | `~/.local/share/SweatStack/SweatStack/tokens.json` |
|
|
94
|
+
| Windows | `%APPDATA%\SweatStack\SweatStack\tokens.json` |
|
|
95
|
+
|
|
96
|
+
**Important:** This path is shared with the `sweatstack` Python library. Both projects must maintain compatibility with this format:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"access_token": "eyJ...",
|
|
101
|
+
"refresh_token": "eyJ..."
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Adding New Commands
|
|
106
|
+
|
|
107
|
+
### 1. Create a Command Module
|
|
108
|
+
|
|
109
|
+
Create a new file in `src/sweatstack_cli/commands/`:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
# src/sweatstack_cli/commands/activities.py
|
|
113
|
+
"""Activity commands."""
|
|
114
|
+
|
|
115
|
+
from __future__ import annotations
|
|
116
|
+
|
|
117
|
+
import typer
|
|
118
|
+
|
|
119
|
+
from sweatstack_cli.api import APIClient
|
|
120
|
+
from sweatstack_cli.console import console
|
|
121
|
+
from sweatstack_cli.exceptions import AuthenticationError
|
|
122
|
+
|
|
123
|
+
app = typer.Typer(
|
|
124
|
+
name="activities",
|
|
125
|
+
help="Manage activities.",
|
|
126
|
+
no_args_is_help=True,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@app.command(name="list")
|
|
131
|
+
def list_activities(
|
|
132
|
+
limit: int = typer.Option(10, "--limit", "-n", help="Number of activities"),
|
|
133
|
+
) -> None:
|
|
134
|
+
"""List recent activities."""
|
|
135
|
+
try:
|
|
136
|
+
client = APIClient()
|
|
137
|
+
activities = client.get("/api/v1/activities", params={"limit": limit})
|
|
138
|
+
|
|
139
|
+
for activity in activities["items"]:
|
|
140
|
+
console.print(f" {activity['id']}: {activity['name']}")
|
|
141
|
+
|
|
142
|
+
except AuthenticationError as e:
|
|
143
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
144
|
+
raise typer.Exit(2)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 2. Register in main.py
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
# src/sweatstack_cli/main.py
|
|
151
|
+
from sweatstack_cli.commands import activities
|
|
152
|
+
|
|
153
|
+
app.add_typer(activities.app, name="activities")
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### 3. Add Tests
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
# tests/test_commands/test_activities.py
|
|
160
|
+
from typer.testing import CliRunner
|
|
161
|
+
from sweatstack_cli.main import app
|
|
162
|
+
|
|
163
|
+
runner = CliRunner()
|
|
164
|
+
|
|
165
|
+
def test_activities_help():
|
|
166
|
+
result = runner.invoke(app, ["activities", "--help"])
|
|
167
|
+
assert result.exit_code == 0
|
|
168
|
+
assert "list" in result.stdout
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Extending the API Client
|
|
172
|
+
|
|
173
|
+
The `APIClient` class provides authenticated HTTP methods. To add new endpoints:
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
# src/sweatstack_cli/api/activities.py
|
|
177
|
+
from __future__ import annotations
|
|
178
|
+
|
|
179
|
+
from sweatstack_cli.api.client import APIClient
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class ActivitiesAPI:
|
|
183
|
+
"""Activities API wrapper."""
|
|
184
|
+
|
|
185
|
+
def __init__(self, client: APIClient | None = None) -> None:
|
|
186
|
+
self._client = client or APIClient()
|
|
187
|
+
|
|
188
|
+
def list(self, limit: int = 10) -> list[dict]:
|
|
189
|
+
"""List activities."""
|
|
190
|
+
response = self._client.get("/api/v1/activities", params={"limit": limit})
|
|
191
|
+
return response["items"]
|
|
192
|
+
|
|
193
|
+
def get(self, activity_id: str) -> dict:
|
|
194
|
+
"""Get a single activity."""
|
|
195
|
+
return self._client.get(f"/api/v1/activities/{activity_id}")
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Testing
|
|
199
|
+
|
|
200
|
+
### Running Tests
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
# All tests
|
|
204
|
+
pytest
|
|
205
|
+
|
|
206
|
+
# With coverage
|
|
207
|
+
pytest --cov=sweatstack_cli --cov-report=html
|
|
208
|
+
|
|
209
|
+
# Specific test file
|
|
210
|
+
pytest tests/test_auth/test_pkce.py
|
|
211
|
+
|
|
212
|
+
# Specific test
|
|
213
|
+
pytest tests/test_auth/test_pkce.py::TestGeneratePKCE::test_verifier_length_in_range
|
|
214
|
+
|
|
215
|
+
# Verbose output
|
|
216
|
+
pytest -v
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Test Fixtures
|
|
220
|
+
|
|
221
|
+
Common fixtures are defined in `tests/conftest.py`:
|
|
222
|
+
|
|
223
|
+
- `temp_token_storage` — FileTokenStorage with temp directory
|
|
224
|
+
- `sample_tokens` — Valid TokenPair with future expiry
|
|
225
|
+
- `expired_tokens` — TokenPair with past expiry
|
|
226
|
+
- `clean_env` — Removes SweatStack env vars for isolation
|
|
227
|
+
|
|
228
|
+
### Mocking HTTP Requests
|
|
229
|
+
|
|
230
|
+
Use `pytest-httpx` for HTTP mocking:
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
from pytest_httpx import HTTPXMock
|
|
234
|
+
|
|
235
|
+
def test_api_call(httpx_mock: HTTPXMock):
|
|
236
|
+
httpx_mock.add_response(
|
|
237
|
+
url="https://app.sweatstack.no/api/v1/test",
|
|
238
|
+
json={"data": "value"},
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
client = APIClient(authenticator=mock_auth)
|
|
242
|
+
result = client.get("/api/v1/test")
|
|
243
|
+
|
|
244
|
+
assert result["data"] == "value"
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Mocking Authentication
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
from unittest.mock import Mock
|
|
251
|
+
from sweatstack_cli.auth import Authenticator
|
|
252
|
+
from sweatstack_cli.auth.tokens import TokenPair
|
|
253
|
+
|
|
254
|
+
def test_with_mock_auth(sample_tokens: TokenPair):
|
|
255
|
+
auth = Mock(spec=Authenticator)
|
|
256
|
+
auth.get_valid_tokens.return_value = sample_tokens
|
|
257
|
+
|
|
258
|
+
client = APIClient(authenticator=auth)
|
|
259
|
+
# ... test API calls
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Code Quality
|
|
263
|
+
|
|
264
|
+
### Linting and Formatting
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
# Check linting
|
|
268
|
+
ruff check .
|
|
269
|
+
|
|
270
|
+
# Auto-fix issues
|
|
271
|
+
ruff check --fix .
|
|
272
|
+
|
|
273
|
+
# Format code
|
|
274
|
+
ruff format .
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Type Checking
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
mypy src/sweatstack_cli
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Pre-commit Checks
|
|
284
|
+
|
|
285
|
+
Consider adding these to CI or as pre-commit hooks:
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
ruff check .
|
|
289
|
+
ruff format --check .
|
|
290
|
+
mypy src/sweatstack_cli
|
|
291
|
+
pytest
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Environment Variables
|
|
295
|
+
|
|
296
|
+
| Variable | Purpose | Default |
|
|
297
|
+
|----------|---------|---------|
|
|
298
|
+
| `SWEATSTACK_URL` | API base URL | `https://app.sweatstack.no` |
|
|
299
|
+
| `SWEATSTACK_API_KEY` | Access token (CI/CD) | — |
|
|
300
|
+
| `SWEATSTACK_REFRESH_TOKEN` | Refresh token (CI/CD) | — |
|
|
301
|
+
|
|
302
|
+
For CI/CD pipelines, set `SWEATSTACK_API_KEY` and `SWEATSTACK_REFRESH_TOKEN` to bypass interactive login.
|
|
303
|
+
|
|
304
|
+
## Error Handling
|
|
305
|
+
|
|
306
|
+
### Exception Hierarchy
|
|
307
|
+
|
|
308
|
+
```
|
|
309
|
+
CLIError (exit code 1)
|
|
310
|
+
├── AuthenticationError (exit code 2)
|
|
311
|
+
├── APIError (exit code 3)
|
|
312
|
+
└── ValidationError (exit code 4)
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Adding New Exceptions
|
|
316
|
+
|
|
317
|
+
```python
|
|
318
|
+
# src/sweatstack_cli/exceptions.py
|
|
319
|
+
|
|
320
|
+
class RateLimitError(CLIError):
|
|
321
|
+
"""API rate limit exceeded."""
|
|
322
|
+
exit_code = 5
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Using Exceptions in Commands
|
|
326
|
+
|
|
327
|
+
```python
|
|
328
|
+
from sweatstack_cli.exceptions import AuthenticationError
|
|
329
|
+
|
|
330
|
+
def my_command():
|
|
331
|
+
try:
|
|
332
|
+
client = APIClient()
|
|
333
|
+
# ...
|
|
334
|
+
except AuthenticationError as e:
|
|
335
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
336
|
+
raise typer.Exit(2)
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## Release Process
|
|
340
|
+
|
|
341
|
+
1. Update version in `src/sweatstack_cli/__init__.py`
|
|
342
|
+
2. Update `pyproject.toml` version if needed
|
|
343
|
+
3. Run full test suite: `pytest`
|
|
344
|
+
4. Build: `uv build` or `python -m build`
|
|
345
|
+
5. Publish: `twine upload dist/*`
|
|
346
|
+
|
|
347
|
+
## Debugging
|
|
348
|
+
|
|
349
|
+
### Verbose Output
|
|
350
|
+
|
|
351
|
+
The CLI respects Rich's console settings. For debugging:
|
|
352
|
+
|
|
353
|
+
```python
|
|
354
|
+
from rich.console import Console
|
|
355
|
+
console = Console(force_terminal=True) # Force colors in CI
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Token Inspection
|
|
359
|
+
|
|
360
|
+
```bash
|
|
361
|
+
# Check token status
|
|
362
|
+
sweatstack status
|
|
363
|
+
|
|
364
|
+
# View raw token file (careful with secrets!)
|
|
365
|
+
cat ~/Library/Application\ Support/SweatStack/SweatStack/tokens.json | python -m json.tool
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Local API Testing
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
# Override API URL for local testing
|
|
372
|
+
SWEATSTACK_URL=http://localhost:8000 sweatstack login
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## Compatibility Notes
|
|
376
|
+
|
|
377
|
+
### Token Storage Compatibility
|
|
378
|
+
|
|
379
|
+
The CLI shares token storage with the `sweatstack` Python library. Changes to the token format must be coordinated:
|
|
380
|
+
|
|
381
|
+
1. Token file path: `platformdirs.user_data_dir("SweatStack", "SweatStack") / "tokens.json"`
|
|
382
|
+
2. JSON format: `{"access_token": "...", "refresh_token": "..."}`
|
|
383
|
+
3. File permissions: `0o600`
|
|
384
|
+
|
|
385
|
+
### Python Version
|
|
386
|
+
|
|
387
|
+
Requires Python 3.13+. Uses modern features:
|
|
388
|
+
- `type` statement (PEP 695)
|
|
389
|
+
- `X | Y` union syntax
|
|
390
|
+
- `@dataclass(slots=True)`
|
|
391
|
+
|
|
392
|
+
## Contributing
|
|
393
|
+
|
|
394
|
+
1. Fork the repository
|
|
395
|
+
2. Create a feature branch: `git checkout -b feature/my-feature`
|
|
396
|
+
3. Make changes with tests
|
|
397
|
+
4. Run checks: `ruff check . && mypy src && pytest`
|
|
398
|
+
5. Submit a pull request
|
|
399
|
+
|
|
400
|
+
## Getting Help
|
|
401
|
+
|
|
402
|
+
- GitHub Issues: [github.com/sweatstack/sweatstack-cli/issues](https://github.com/sweatstack/sweatstack-cli/issues)
|
|
403
|
+
- Documentation: [docs.sweatstack.no](https://docs.sweatstack.no)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
.PHONY: help install dev test lint format typecheck check clean build publish
|
|
2
|
+
|
|
3
|
+
help:
|
|
4
|
+
@echo "SweatStack CLI Development Commands"
|
|
5
|
+
@echo ""
|
|
6
|
+
@echo " make install Install the package"
|
|
7
|
+
@echo " make dev Install with development dependencies"
|
|
8
|
+
@echo " make test Run tests"
|
|
9
|
+
@echo " make lint Run linter"
|
|
10
|
+
@echo " make format Format code"
|
|
11
|
+
@echo " make typecheck Run type checker"
|
|
12
|
+
@echo " make check Run all checks (lint, format, typecheck, test)"
|
|
13
|
+
@echo " make clean Remove build artifacts"
|
|
14
|
+
@echo " make build Build distribution packages"
|
|
15
|
+
@echo " make publish Publish to PyPI"
|
|
16
|
+
|
|
17
|
+
install:
|
|
18
|
+
uv pip install -e .
|
|
19
|
+
|
|
20
|
+
dev:
|
|
21
|
+
uv pip install -e ".[dev]"
|
|
22
|
+
|
|
23
|
+
test:
|
|
24
|
+
uv run pytest
|
|
25
|
+
|
|
26
|
+
lint:
|
|
27
|
+
uv run ruff check .
|
|
28
|
+
|
|
29
|
+
format:
|
|
30
|
+
uv run ruff format .
|
|
31
|
+
|
|
32
|
+
typecheck:
|
|
33
|
+
uv run mypy src/sweatstack_cli
|
|
34
|
+
|
|
35
|
+
check: lint typecheck test
|
|
36
|
+
@echo "All checks passed!"
|
|
37
|
+
|
|
38
|
+
clean:
|
|
39
|
+
rm -rf dist/ build/ *.egg-info src/*.egg-info
|
|
40
|
+
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
|
|
41
|
+
find . -type d -name .pytest_cache -exec rm -rf {} + 2>/dev/null || true
|
|
42
|
+
find . -type d -name .mypy_cache -exec rm -rf {} + 2>/dev/null || true
|
|
43
|
+
find . -type d -name .ruff_cache -exec rm -rf {} + 2>/dev/null || true
|
|
44
|
+
|
|
45
|
+
build:
|
|
46
|
+
rm -rf dist
|
|
47
|
+
uv build
|
|
48
|
+
|
|
49
|
+
publish: build
|
|
50
|
+
uvx uv-publish
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sweatstack-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Command-line interface for SweatStack — the sports data platform for developers
|
|
5
|
+
Project-URL: Homepage, https://sweatstack.no
|
|
6
|
+
Project-URL: Documentation, https://docs.sweatstack.no
|
|
7
|
+
Project-URL: Repository, https://github.com/sweatstack/sweatstack-cli
|
|
8
|
+
Author-email: Aart Goossens <aart@goossens.me>
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: api,cli,fitness,sports,sweatstack
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.13
|
|
20
|
+
Requires-Dist: httpx>=0.28.0
|
|
21
|
+
Requires-Dist: platformdirs>=4.0
|
|
22
|
+
Requires-Dist: typer>=0.15.0
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: coverage>=7.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: mypy>=1.14; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest-httpx>=0.35; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: ruff>=0.9; extra == 'dev'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# SweatStack CLI
|
|
32
|
+
|
|
33
|
+
Command-line interface for [SweatStack](https://sweatstack.no) — the sports data platform for developers.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install sweatstack-cli
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Requires Python 3.13+.
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Authenticate with SweatStack
|
|
47
|
+
sweatstack login
|
|
48
|
+
|
|
49
|
+
# Check who you're logged in as
|
|
50
|
+
sweatstack whoami
|
|
51
|
+
|
|
52
|
+
# Deploy a static site
|
|
53
|
+
sweatstack pages deploy ./dist --prod
|
|
54
|
+
|
|
55
|
+
# View authentication status
|
|
56
|
+
sweatstack status
|
|
57
|
+
|
|
58
|
+
# Logout
|
|
59
|
+
sweatstack logout
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Commands
|
|
63
|
+
|
|
64
|
+
### Authentication
|
|
65
|
+
|
|
66
|
+
| Command | Description |
|
|
67
|
+
|---------|-------------|
|
|
68
|
+
| `sweatstack login` | Authenticate via browser |
|
|
69
|
+
| `sweatstack login --force` | Force re-authentication |
|
|
70
|
+
| `sweatstack logout` | Remove stored credentials |
|
|
71
|
+
| `sweatstack whoami` | Show current user |
|
|
72
|
+
| `sweatstack status` | Show token status and expiry |
|
|
73
|
+
|
|
74
|
+
### Pages
|
|
75
|
+
|
|
76
|
+
| Command | Description |
|
|
77
|
+
|---------|-------------|
|
|
78
|
+
| `sweatstack pages deploy <dir>` | Deploy static site |
|
|
79
|
+
| `sweatstack pages deploy --prod` | Deploy to production |
|
|
80
|
+
| `sweatstack pages list` | List all sites |
|
|
81
|
+
| `sweatstack pages delete <name>` | Delete a site |
|
|
82
|
+
|
|
83
|
+
## CI/CD Usage
|
|
84
|
+
|
|
85
|
+
For automated environments, use environment variables instead of interactive login:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
export SWEATSTACK_API_KEY="your-access-token"
|
|
89
|
+
export SWEATSTACK_REFRESH_TOKEN="your-refresh-token"
|
|
90
|
+
|
|
91
|
+
sweatstack pages deploy ./dist --prod
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Configuration
|
|
95
|
+
|
|
96
|
+
| Environment Variable | Description | Default |
|
|
97
|
+
|---------------------|-------------|---------|
|
|
98
|
+
| `SWEATSTACK_URL` | API base URL | `https://app.sweatstack.no` |
|
|
99
|
+
| `SWEATSTACK_API_KEY` | Access token | — |
|
|
100
|
+
| `SWEATSTACK_REFRESH_TOKEN` | Refresh token | — |
|
|
101
|
+
|
|
102
|
+
## Token Storage
|
|
103
|
+
|
|
104
|
+
Credentials are stored securely in your OS user data directory:
|
|
105
|
+
|
|
106
|
+
- **macOS**: `~/Library/Application Support/SweatStack/SweatStack/tokens.json`
|
|
107
|
+
- **Linux**: `~/.local/share/SweatStack/SweatStack/tokens.json`
|
|
108
|
+
- **Windows**: `%APPDATA%\SweatStack\SweatStack\tokens.json`
|
|
109
|
+
|
|
110
|
+
This location is shared with the [sweatstack Python library](https://github.com/sweatstack/sweatstack-python), so authenticating with either tool works for both.
|
|
111
|
+
|
|
112
|
+
## Development
|
|
113
|
+
|
|
114
|
+
See [DEVELOPMENT.md](DEVELOPMENT.md) for development setup and contribution guidelines.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# Install with dev dependencies
|
|
118
|
+
uv pip install -e ".[dev]"
|
|
119
|
+
|
|
120
|
+
# Run tests
|
|
121
|
+
pytest
|
|
122
|
+
|
|
123
|
+
# Lint and format
|
|
124
|
+
ruff check . && ruff format .
|
|
125
|
+
|
|
126
|
+
# Type check
|
|
127
|
+
mypy src/sweatstack_cli
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
MIT
|