sweatstack-cli 0.12.0__tar.gz → 0.12.1__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 (63) hide show
  1. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/CHANGELOG.md +12 -0
  2. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/DEVELOPMENT.md +8 -6
  3. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/PKG-INFO +38 -9
  4. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/README.md +37 -8
  5. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/pyproject.toml +1 -1
  6. sweatstack_cli-0.12.1/src/sweatstack_cli/__init__.py +10 -0
  7. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/src/sweatstack_cli/commands/app.py +13 -12
  8. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/tests/test_commands/test_app.py +22 -4
  9. sweatstack_cli-0.12.1/tests/test_version.py +17 -0
  10. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/uv.lock +1 -1
  11. sweatstack_cli-0.12.0/src/sweatstack_cli/__init__.py +0 -3
  12. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/.claude/settings.local.json +0 -0
  13. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/.claude/skills/sweatstack/SKILL.md +0 -0
  14. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/.claude/skills/sweatstack/api.md +0 -0
  15. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/.claude/skills/sweatstack/auth.md +0 -0
  16. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/.claude/skills/sweatstack/cli.md +0 -0
  17. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/.claude/skills/sweatstack/pyscript.md +0 -0
  18. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/.claude/skills/sweatstack/webapp.md +0 -0
  19. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/.gitignore +0 -0
  20. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/.python-version +0 -0
  21. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/CREATE_PRIVATE_APP.md +0 -0
  22. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/Makefile +0 -0
  23. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/PLAN.md +0 -0
  24. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/plans/001-page-init-command.md +0 -0
  25. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/src/sweatstack_cli/api/__init__.py +0 -0
  26. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/src/sweatstack_cli/api/client.py +0 -0
  27. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/src/sweatstack_cli/auth/__init__.py +0 -0
  28. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/src/sweatstack_cli/auth/callback_server.py +0 -0
  29. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/src/sweatstack_cli/auth/jwt.py +0 -0
  30. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/src/sweatstack_cli/auth/pkce.py +0 -0
  31. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/src/sweatstack_cli/auth/tokens.py +0 -0
  32. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/src/sweatstack_cli/commands/__init__.py +0 -0
  33. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/src/sweatstack_cli/commands/auth.py +0 -0
  34. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/src/sweatstack_cli/commands/page.py +0 -0
  35. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/src/sweatstack_cli/config.py +0 -0
  36. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/src/sweatstack_cli/console.py +0 -0
  37. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/src/sweatstack_cli/exceptions.py +0 -0
  38. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/src/sweatstack_cli/main.py +0 -0
  39. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/src/sweatstack_cli/project.py +0 -0
  40. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/src/sweatstack_cli/py.typed +0 -0
  41. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/src/sweatstack_cli/services/__init__.py +0 -0
  42. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/src/sweatstack_cli/services/applications.py +0 -0
  43. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/src/sweatstack_cli/templates/page/index.html +0 -0
  44. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/src/sweatstack_cli/version_check.py +0 -0
  45. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/sweatstack.toml +0 -0
  46. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/tests/__init__.py +0 -0
  47. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/tests/conftest.py +0 -0
  48. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/tests/test_api/__init__.py +0 -0
  49. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/tests/test_api/test_client.py +0 -0
  50. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/tests/test_auth/__init__.py +0 -0
  51. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/tests/test_auth/test_callback_server.py +0 -0
  52. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/tests/test_auth/test_jwt.py +0 -0
  53. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/tests/test_auth/test_pkce.py +0 -0
  54. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/tests/test_auth/test_tokens.py +0 -0
  55. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/tests/test_commands/__init__.py +0 -0
  56. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/tests/test_commands/test_auth.py +0 -0
  57. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/tests/test_commands/test_cli.py +0 -0
  58. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/tests/test_commands/test_page_deploy.py +0 -0
  59. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/tests/test_commands/test_page_init.py +0 -0
  60. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/tests/test_project.py +0 -0
  61. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/tests/test_services/__init__.py +0 -0
  62. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/tests/test_services/test_applications.py +0 -0
  63. {sweatstack_cli-0.12.0 → sweatstack_cli-0.12.1}/tests/test_version_check.py +0 -0
@@ -4,6 +4,18 @@ All notable changes to this project are documented here. The format is based on
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
5
5
  adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.12.1] - 2026-10-13
8
+
9
+ ### Fixed
10
+
11
+ - 0.12.0 reported itself as 0.11.0: in `--version`, in `status` (which then
12
+ always offered an "update" to 0.12.0) and in the `User-Agent` header. The
13
+ version is now read from the installed package, so `pyproject.toml` is the
14
+ only place to bump it.
15
+ - `app create` checks for an existing `sweatstack.toml` before creating the
16
+ application, not after. Non-interactively, or when declining to overwrite, it
17
+ used to create the app and then stop, leaving an application nothing pointed to.
18
+
7
19
  ## [0.12.0] - 2026-10-13
8
20
 
9
21
  ### Fixed
@@ -25,7 +25,7 @@ pytest
25
25
 
26
26
  ```
27
27
  src/sweatstack_cli/
28
- ├── __init__.py # Package version
28
+ ├── __init__.py # Package version (read from metadata)
29
29
  ├── main.py # CLI entry point (Typer app)
30
30
  ├── config.py # Environment-based configuration
31
31
  ├── console.py # Rich console singleton
@@ -338,11 +338,13 @@ def my_command():
338
338
 
339
339
  ## Release Process
340
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/*`
341
+ 1. Bump `version` in `pyproject.toml`, then run `uv lock` to update `uv.lock`.
342
+ This is the only place the version lives: `__version__` reads it from the
343
+ installed package.
344
+ 2. Rename `## [Unreleased]` in `CHANGELOG.md` to `## [X.Y.Z] - YYYY-MM-DD`.
345
+ 3. Run all checks: `make check`
346
+ 4. Commit as `Release X.Y.Z`
347
+ 5. Build and publish: `make publish`
346
348
 
347
349
  ## Debugging
348
350
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sweatstack-cli
3
- Version: 0.12.0
3
+ Version: 0.12.1
4
4
  Summary: Command-line interface for SweatStack — the sports data platform for developers
5
5
  Project-URL: Homepage, https://sweatstack.no
6
6
  Project-URL: Documentation, https://docs.sweatstack.no
@@ -67,8 +67,11 @@ uv tool install --reinstall sweatstack-cli
67
67
  # Authenticate with SweatStack
68
68
  sweatstack login
69
69
 
70
- # Create an app and deploy
71
- sweatstack app create "My App" --page my-page --env
70
+ # Create an app with a page, and scaffold public/index.html
71
+ sweatstack page init "My App"
72
+
73
+ # Preview locally, then publish to https://my-app.pages.sweatstack.dev
74
+ sweatstack page serve
72
75
  sweatstack page deploy
73
76
  ```
74
77
 
@@ -110,7 +113,7 @@ sweatstack page deploy
110
113
 
111
114
  ### Project Configuration
112
115
 
113
- Both `app create` and `app link` generate a `sweatstack.toml` in the current directory. Other commands read defaults from this file.
116
+ `app create`, `app link` and `page init` generate a `sweatstack.toml` in the current directory. Other commands read defaults from this file.
114
117
 
115
118
  ```toml
116
119
  [app]
@@ -122,9 +125,33 @@ slug = "my-page"
122
125
  directory = "dist"
123
126
  ```
124
127
 
125
- ### Page Deployment
128
+ ### Pages
129
+
130
+ Static sites hosted at `https://<slug>.pages.sweatstack.dev`. Each application has at most one page.
131
+
132
+ | Command | Description |
133
+ |---------|-------------|
134
+ | `sweatstack page init [NAME]` | Create an app and its page, and scaffold `public/index.html` |
135
+ | `sweatstack page serve` | Preview the site locally |
136
+ | `sweatstack page deploy [SLUG]` | Publish the site, creating the page if needed |
137
+
138
+ #### `page init`
139
+
140
+ Creates a private application and its page, records both in `sweatstack.toml`, and writes a starter app to `public/index.html` that signs in with SweatStack and plots your latest activity. The slug is derived from the name. With an app already linked in `sweatstack.toml`, it reuses that app instead of creating one.
126
141
 
127
- Deploy static sites to SweatStack Pages.
142
+ | Option | Description |
143
+ |--------|-------------|
144
+ | `--slug TEXT` | Page slug (default: derived from the name) |
145
+ | `--dir PATH` | Directory to scaffold (default: `public`) |
146
+ | `--title TEXT` | Title shown on the page (default: the name) |
147
+ | `--client-id TEXT` | Reuse an existing application |
148
+ | `--force, -f` | Overwrite an existing `index.html` |
149
+
150
+ #### `page serve`
151
+
152
+ Serves the `[page] directory` from `sweatstack.toml` on the first free port from 8000. Extra arguments are passed to `python -m http.server`. Apps created by the CLI accept sign-in redirects to localhost on any port.
153
+
154
+ #### `page deploy`
128
155
 
129
156
  ```bash
130
157
  # Deploy using sweatstack.toml defaults (recommended)
@@ -134,9 +161,11 @@ sweatstack page deploy
134
161
  sweatstack page deploy my-page --dir ./dist
135
162
  ```
136
163
 
137
- If the page doesn't exist yet, `deploy` creates it. With an app linked in
138
- `sweatstack.toml`, it creates that app's page. Without one, it asks to create a
139
- new app with the page (and fails instead when not run interactively, e.g. in CI).
164
+ Every deploy replaces all files on the page. If the page doesn't exist yet, `deploy` creates it. With an app linked in `sweatstack.toml`, it creates that app's page. Without one, it asks to create a new app with the page, and fails instead when not run interactively (e.g. in CI).
165
+
166
+ New apps are private: only you can sign in. After deploying, `deploy` prints a link to make the app public.
167
+
168
+ **Limits:** 10 MB per file; 100 MB and about 1,000 files per deploy.
140
169
 
141
170
  ## License
142
171
 
@@ -37,8 +37,11 @@ uv tool install --reinstall sweatstack-cli
37
37
  # Authenticate with SweatStack
38
38
  sweatstack login
39
39
 
40
- # Create an app and deploy
41
- sweatstack app create "My App" --page my-page --env
40
+ # Create an app with a page, and scaffold public/index.html
41
+ sweatstack page init "My App"
42
+
43
+ # Preview locally, then publish to https://my-app.pages.sweatstack.dev
44
+ sweatstack page serve
42
45
  sweatstack page deploy
43
46
  ```
44
47
 
@@ -80,7 +83,7 @@ sweatstack page deploy
80
83
 
81
84
  ### Project Configuration
82
85
 
83
- Both `app create` and `app link` generate a `sweatstack.toml` in the current directory. Other commands read defaults from this file.
86
+ `app create`, `app link` and `page init` generate a `sweatstack.toml` in the current directory. Other commands read defaults from this file.
84
87
 
85
88
  ```toml
86
89
  [app]
@@ -92,9 +95,33 @@ slug = "my-page"
92
95
  directory = "dist"
93
96
  ```
94
97
 
95
- ### Page Deployment
98
+ ### Pages
99
+
100
+ Static sites hosted at `https://<slug>.pages.sweatstack.dev`. Each application has at most one page.
101
+
102
+ | Command | Description |
103
+ |---------|-------------|
104
+ | `sweatstack page init [NAME]` | Create an app and its page, and scaffold `public/index.html` |
105
+ | `sweatstack page serve` | Preview the site locally |
106
+ | `sweatstack page deploy [SLUG]` | Publish the site, creating the page if needed |
107
+
108
+ #### `page init`
109
+
110
+ Creates a private application and its page, records both in `sweatstack.toml`, and writes a starter app to `public/index.html` that signs in with SweatStack and plots your latest activity. The slug is derived from the name. With an app already linked in `sweatstack.toml`, it reuses that app instead of creating one.
96
111
 
97
- Deploy static sites to SweatStack Pages.
112
+ | Option | Description |
113
+ |--------|-------------|
114
+ | `--slug TEXT` | Page slug (default: derived from the name) |
115
+ | `--dir PATH` | Directory to scaffold (default: `public`) |
116
+ | `--title TEXT` | Title shown on the page (default: the name) |
117
+ | `--client-id TEXT` | Reuse an existing application |
118
+ | `--force, -f` | Overwrite an existing `index.html` |
119
+
120
+ #### `page serve`
121
+
122
+ Serves the `[page] directory` from `sweatstack.toml` on the first free port from 8000. Extra arguments are passed to `python -m http.server`. Apps created by the CLI accept sign-in redirects to localhost on any port.
123
+
124
+ #### `page deploy`
98
125
 
99
126
  ```bash
100
127
  # Deploy using sweatstack.toml defaults (recommended)
@@ -104,9 +131,11 @@ sweatstack page deploy
104
131
  sweatstack page deploy my-page --dir ./dist
105
132
  ```
106
133
 
107
- If the page doesn't exist yet, `deploy` creates it. With an app linked in
108
- `sweatstack.toml`, it creates that app's page. Without one, it asks to create a
109
- new app with the page (and fails instead when not run interactively, e.g. in CI).
134
+ Every deploy replaces all files on the page. If the page doesn't exist yet, `deploy` creates it. With an app linked in `sweatstack.toml`, it creates that app's page. Without one, it asks to create a new app with the page, and fails instead when not run interactively (e.g. in CI).
135
+
136
+ New apps are private: only you can sign in. After deploying, `deploy` prints a link to make the app public.
137
+
138
+ **Limits:** 10 MB per file; 100 MB and about 1,000 files per deploy.
110
139
 
111
140
  ## License
112
141
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sweatstack-cli"
3
- version = "0.12.0"
3
+ version = "0.12.1"
4
4
  description = "Command-line interface for SweatStack — the sports data platform for developers"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Aart Goossens", email = "aart@sweatstack.no" }]
@@ -0,0 +1,10 @@
1
+ """SweatStack CLI — Command-line interface for the SweatStack platform."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ # Single source of truth: the version in pyproject.toml, read from the installed
6
+ # package's metadata. A hardcoded copy here drifted once (0.12.0 shipped as 0.11.0).
7
+ try:
8
+ __version__ = version("sweatstack-cli")
9
+ except PackageNotFoundError: # Imported from a source tree that isn't installed.
10
+ __version__ = "unknown"
@@ -74,6 +74,18 @@ def create(
74
74
  raise typer.Exit(1)
75
75
  name = typer.prompt("App name")
76
76
 
77
+ # Protect an existing sweatstack.toml before creating anything, so declining
78
+ # (or a non-interactive refusal) never leaves an orphaned application behind.
79
+ if Path(CONFIG_FILENAME).exists():
80
+ if not interactive:
81
+ console.print(
82
+ f"[red]Error:[/red] {CONFIG_FILENAME} already exists. "
83
+ "Remove it first or run interactively."
84
+ )
85
+ raise typer.Exit(1)
86
+ console.print(f"[yellow]Warning:[/yellow] {CONFIG_FILENAME} already exists.")
87
+ typer.confirm("Overwrite?", abort=True)
88
+
77
89
  # Prompt for optional fields in interactive mode
78
90
  if interactive:
79
91
  if description is None:
@@ -110,18 +122,7 @@ def create(
110
122
  if env_path:
111
123
  _write_env_file(env_path, result)
112
124
 
113
- # Write sweatstack.toml (protect existing file)
114
- config_path = Path(CONFIG_FILENAME)
115
- if config_path.exists():
116
- if not interactive:
117
- console.print(
118
- f"[red]Error:[/red] {CONFIG_FILENAME} already exists. "
119
- "Remove it first or run interactively."
120
- )
121
- raise typer.Exit(1)
122
- console.print(f"[yellow]Warning:[/yellow] {CONFIG_FILENAME} already exists.")
123
- typer.confirm("Overwrite?", abort=True)
124
-
125
+ # Write sweatstack.toml (checked for overwrite before the app was created)
125
126
  # Default the deploy directory to public/ so the project is deploy-ready and
126
127
  # consistent with `page init` / `page serve` / `page deploy`.
127
128
  config_path = write_config(
@@ -189,9 +189,12 @@ class TestAppCreate:
189
189
  assert result.exit_code == 1
190
190
  assert "mutually exclusive" in result.stdout
191
191
 
192
- def test_create_not_authenticated(self) -> None:
192
+ def test_create_not_authenticated(self, tmp_path: Path) -> None:
193
193
  """Should error when not authenticated."""
194
- with patch("sweatstack_cli.commands.app.APIClient") as mock_client:
194
+ with (
195
+ patch("sweatstack_cli.commands.app.APIClient") as mock_client,
196
+ runner.isolated_filesystem(temp_dir=tmp_path),
197
+ ):
195
198
  mock_client.side_effect = AuthenticationError("Not authenticated")
196
199
 
197
200
  result = runner.invoke(app, ["app", "create", "Test App"])
@@ -200,15 +203,30 @@ class TestAppCreate:
200
203
  assert "Not authenticated" in result.stdout
201
204
  assert "sweatstack login" in result.stdout
202
205
 
203
- def test_create_api_error(self, mock_api: MagicMock) -> None:
206
+ def test_create_api_error(self, mock_api: MagicMock, tmp_path: Path) -> None:
204
207
  """Should handle API errors."""
205
208
  mock_api.post.side_effect = APIError("App name already exists")
206
209
 
207
- result = runner.invoke(app, ["app", "create", "Test App"])
210
+ with runner.isolated_filesystem(temp_dir=tmp_path):
211
+ result = runner.invoke(app, ["app", "create", "Test App"])
208
212
 
209
213
  assert result.exit_code == 3
210
214
  assert "App name already exists" in result.stdout
211
215
 
216
+ def test_create_existing_config_creates_nothing(
217
+ self, mock_api: MagicMock, tmp_path: Path
218
+ ) -> None:
219
+ """Should refuse before creating the app, never leaving an orphan behind."""
220
+ with runner.isolated_filesystem(temp_dir=tmp_path):
221
+ Path("sweatstack.toml").write_text('[app]\nclient_id = "existing"\n')
222
+
223
+ result = runner.invoke(app, ["app", "create", "Test App"])
224
+
225
+ assert result.exit_code == 1
226
+ assert "already exists" in result.stdout
227
+ mock_api.post.assert_not_called()
228
+ assert Path("sweatstack.toml").read_text() == '[app]\nclient_id = "existing"\n'
229
+
212
230
  def test_create_env_with_secret_no_warning_without_env(
213
231
  self, mock_api: MagicMock, tmp_path: Path
214
232
  ) -> None:
@@ -0,0 +1,17 @@
1
+ """The reported version is the one in pyproject.toml."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import tomllib
6
+ from pathlib import Path
7
+
8
+ from sweatstack_cli import __version__
9
+
10
+ PYPROJECT = Path(__file__).parent.parent / "pyproject.toml"
11
+
12
+
13
+ def test_version_matches_pyproject() -> None:
14
+ with PYPROJECT.open("rb") as f:
15
+ expected = tomllib.load(f)["project"]["version"]
16
+
17
+ assert __version__ == expected
@@ -463,7 +463,7 @@ wheels = [
463
463
 
464
464
  [[package]]
465
465
  name = "sweatstack-cli"
466
- version = "0.12.0"
466
+ version = "0.12.1"
467
467
  source = { editable = "." }
468
468
  dependencies = [
469
469
  { name = "httpx" },
@@ -1,3 +0,0 @@
1
- """SweatStack CLI — Command-line interface for the SweatStack platform."""
2
-
3
- __version__ = "0.11.0"
File without changes