sweatstack-cli 0.5.0__py3-none-any.whl → 0.7.0__py3-none-any.whl
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/__init__.py +1 -1
- sweatstack_cli/commands/app.py +91 -5
- sweatstack_cli/commands/page.py +43 -11
- sweatstack_cli/project.py +111 -0
- sweatstack_cli-0.7.0.dist-info/METADATA +182 -0
- {sweatstack_cli-0.5.0.dist-info → sweatstack_cli-0.7.0.dist-info}/RECORD +8 -7
- {sweatstack_cli-0.5.0.dist-info → sweatstack_cli-0.7.0.dist-info}/WHEEL +1 -1
- sweatstack_cli-0.5.0.dist-info/METADATA +0 -132
- {sweatstack_cli-0.5.0.dist-info → sweatstack_cli-0.7.0.dist-info}/entry_points.txt +0 -0
sweatstack_cli/__init__.py
CHANGED
sweatstack_cli/commands/app.py
CHANGED
|
@@ -4,12 +4,14 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import json as json_lib
|
|
6
6
|
from pathlib import Path
|
|
7
|
+
from typing import Any, cast
|
|
7
8
|
|
|
8
9
|
import typer
|
|
9
10
|
|
|
10
11
|
from sweatstack_cli.api import APIClient
|
|
11
12
|
from sweatstack_cli.console import console, stderr_console
|
|
12
13
|
from sweatstack_cli.exceptions import APIError, AuthenticationError
|
|
14
|
+
from sweatstack_cli.project import CONFIG_FILENAME, write_config
|
|
13
15
|
|
|
14
16
|
app = typer.Typer(name="app", help="Manage OAuth2 applications.")
|
|
15
17
|
|
|
@@ -17,6 +19,12 @@ app = typer.Typer(name="app", help="Manage OAuth2 applications.")
|
|
|
17
19
|
@app.command()
|
|
18
20
|
def create(
|
|
19
21
|
name: str = typer.Argument(..., help="Application name."),
|
|
22
|
+
description: str | None = typer.Option(
|
|
23
|
+
None,
|
|
24
|
+
"--description",
|
|
25
|
+
"-d",
|
|
26
|
+
help="Application description (max 500 chars).",
|
|
27
|
+
),
|
|
20
28
|
page: str | None = typer.Option(
|
|
21
29
|
None,
|
|
22
30
|
"--page",
|
|
@@ -52,6 +60,9 @@ def create(
|
|
|
52
60
|
|
|
53
61
|
body: dict[str, str | bool] = {"name": name}
|
|
54
62
|
|
|
63
|
+
if description:
|
|
64
|
+
body["description"] = description
|
|
65
|
+
|
|
55
66
|
if page:
|
|
56
67
|
body["page"] = page
|
|
57
68
|
|
|
@@ -75,13 +86,20 @@ def create(
|
|
|
75
86
|
if env_path:
|
|
76
87
|
_write_env_file(env_path, result)
|
|
77
88
|
|
|
89
|
+
# Write sweatstack.toml
|
|
90
|
+
config_path = write_config(
|
|
91
|
+
app_name=result["name"],
|
|
92
|
+
client_id=result["client_id"],
|
|
93
|
+
page_slug=page,
|
|
94
|
+
)
|
|
95
|
+
|
|
78
96
|
if json_output:
|
|
79
97
|
console.print(json_lib.dumps(result, indent=2))
|
|
80
98
|
else:
|
|
81
|
-
_print_app_created(result, env_path)
|
|
99
|
+
_print_app_created(result, env_path, config_path)
|
|
82
100
|
|
|
83
101
|
|
|
84
|
-
def _write_env_file(path: Path, result: dict) -> None:
|
|
102
|
+
def _write_env_file(path: Path, result: dict[str, str]) -> None:
|
|
85
103
|
"""Append credentials to an env file."""
|
|
86
104
|
lines_to_add: list[str] = []
|
|
87
105
|
existing_content = ""
|
|
@@ -116,7 +134,7 @@ def _write_env_file(path: Path, result: dict) -> None:
|
|
|
116
134
|
f.write("\n".join(lines_to_add) + "\n")
|
|
117
135
|
|
|
118
136
|
|
|
119
|
-
def _print_app_created(result: dict, env_path: Path | None) -> None:
|
|
137
|
+
def _print_app_created(result: dict[str, str], env_path: Path | None, config_path: Path) -> None:
|
|
120
138
|
"""Pretty-print the created application details."""
|
|
121
139
|
console.print(f"[green]✓[/green] Created application [bold]{result['name']}[/bold]")
|
|
122
140
|
console.print()
|
|
@@ -131,9 +149,77 @@ def _print_app_created(result: dict, env_path: Path | None) -> None:
|
|
|
131
149
|
for uri in result["redirect_uris"]:
|
|
132
150
|
console.print(f" - {uri}")
|
|
133
151
|
|
|
152
|
+
console.print()
|
|
153
|
+
console.print(f"[green]✓[/green] Config written to [bold]{config_path}[/bold]")
|
|
154
|
+
|
|
134
155
|
if env_path:
|
|
135
|
-
console.print()
|
|
136
156
|
console.print(f"[green]✓[/green] Credentials written to [bold]{env_path}[/bold]")
|
|
137
|
-
|
|
157
|
+
|
|
158
|
+
if "client_secret" in result and not env_path:
|
|
138
159
|
console.print()
|
|
139
160
|
console.print("[yellow]Save the secret now — it won't be shown again.[/yellow]")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@app.command()
|
|
164
|
+
def link(
|
|
165
|
+
force: bool = typer.Option(
|
|
166
|
+
False,
|
|
167
|
+
"--force",
|
|
168
|
+
"-f",
|
|
169
|
+
help="Overwrite existing sweatstack.toml.",
|
|
170
|
+
),
|
|
171
|
+
) -> None:
|
|
172
|
+
"""Link this directory to an existing application."""
|
|
173
|
+
config_path = Path(CONFIG_FILENAME)
|
|
174
|
+
|
|
175
|
+
# Check if config already exists
|
|
176
|
+
if config_path.exists() and not force:
|
|
177
|
+
console.print(f"[yellow]Already linked.[/yellow] {CONFIG_FILENAME} exists.")
|
|
178
|
+
console.print("Use [cyan]--force[/cyan] to overwrite.")
|
|
179
|
+
raise typer.Exit(1)
|
|
180
|
+
|
|
181
|
+
# Fetch user's apps
|
|
182
|
+
try:
|
|
183
|
+
client = APIClient()
|
|
184
|
+
except AuthenticationError as e:
|
|
185
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
186
|
+
console.print("[dim]Run 'sweatstack login' to authenticate.[/dim]")
|
|
187
|
+
raise typer.Exit(2)
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
# API returns a list, but client.get is typed as returning dict
|
|
191
|
+
apps = cast(list[dict[str, Any]], client.get("/api/v1/applications"))
|
|
192
|
+
except APIError as e:
|
|
193
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
194
|
+
raise typer.Exit(3)
|
|
195
|
+
|
|
196
|
+
if not apps:
|
|
197
|
+
console.print("[yellow]No applications found.[/yellow]")
|
|
198
|
+
console.print("Create one with [cyan]sweatstack app create <name>[/cyan]")
|
|
199
|
+
raise typer.Exit(1)
|
|
200
|
+
|
|
201
|
+
# Show apps and let user select
|
|
202
|
+
console.print("Link this directory to an existing application.\n")
|
|
203
|
+
console.print("Your applications:")
|
|
204
|
+
for i, app_info in enumerate(apps, 1):
|
|
205
|
+
page_info = f" [dim](page: {app_info['page_slug']})[/dim]" if app_info.get("page_slug") else ""
|
|
206
|
+
console.print(f" [cyan][{i}][/cyan] {app_info['name']}{page_info}")
|
|
207
|
+
|
|
208
|
+
console.print()
|
|
209
|
+
selection = typer.prompt("Select application", type=int)
|
|
210
|
+
|
|
211
|
+
if selection < 1 or selection > len(apps):
|
|
212
|
+
console.print("[red]Invalid selection.[/red]")
|
|
213
|
+
raise typer.Exit(1)
|
|
214
|
+
|
|
215
|
+
selected = apps[selection - 1]
|
|
216
|
+
|
|
217
|
+
# Write config
|
|
218
|
+
write_config(
|
|
219
|
+
app_name=selected["name"],
|
|
220
|
+
client_id=selected["id"],
|
|
221
|
+
page_slug=selected.get("page_slug"),
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
console.print()
|
|
225
|
+
console.print(f"[green]✓[/green] Linked to [bold]{selected['name']}[/bold]")
|
sweatstack_cli/commands/page.py
CHANGED
|
@@ -9,8 +9,10 @@ import typer
|
|
|
9
9
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
10
10
|
|
|
11
11
|
from sweatstack_cli.api import APIClient
|
|
12
|
+
from sweatstack_cli.config import get_settings
|
|
12
13
|
from sweatstack_cli.console import console
|
|
13
14
|
from sweatstack_cli.exceptions import APIError, AuthenticationError
|
|
15
|
+
from sweatstack_cli.project import CONFIG_FILENAME, ProjectConfig
|
|
14
16
|
|
|
15
17
|
# URL where users can create pages for their applications
|
|
16
18
|
SETTINGS_URL = "https://app.sweatstack.no/settings/api"
|
|
@@ -53,21 +55,40 @@ def _collect_files(directory: Path) -> list[tuple[str, tuple[str, bytes, str]]]:
|
|
|
53
55
|
|
|
54
56
|
@app.command()
|
|
55
57
|
def deploy(
|
|
56
|
-
slug: str = typer.Argument(
|
|
57
|
-
|
|
58
|
-
help="Page slug
|
|
58
|
+
slug: str | None = typer.Argument(
|
|
59
|
+
None,
|
|
60
|
+
help="Page slug. Reads from sweatstack.toml if not provided.",
|
|
59
61
|
),
|
|
60
|
-
directory: Path = typer.Argument(
|
|
61
|
-
|
|
62
|
-
help="Directory
|
|
63
|
-
exists=True,
|
|
64
|
-
file_okay=False,
|
|
65
|
-
dir_okay=True,
|
|
66
|
-
resolve_path=True,
|
|
62
|
+
directory: Path | None = typer.Argument(
|
|
63
|
+
None,
|
|
64
|
+
help="Directory to deploy. Reads from sweatstack.toml or defaults to current dir.",
|
|
67
65
|
),
|
|
68
66
|
) -> None:
|
|
69
67
|
"""Deploy a static site to SweatStack Pages."""
|
|
70
|
-
|
|
68
|
+
# Load config for defaults
|
|
69
|
+
config = ProjectConfig.load()
|
|
70
|
+
|
|
71
|
+
# Resolve slug
|
|
72
|
+
page_slug = slug or (config.page.slug if config else None)
|
|
73
|
+
if not page_slug:
|
|
74
|
+
console.print("[red]Error:[/red] No page slug provided.")
|
|
75
|
+
console.print(f"Specify a slug or add it to [bold]{CONFIG_FILENAME}[/bold]")
|
|
76
|
+
raise typer.Exit(1)
|
|
77
|
+
|
|
78
|
+
# Resolve directory
|
|
79
|
+
if directory is None:
|
|
80
|
+
dir_from_config = config.page.directory if config else None
|
|
81
|
+
directory = Path(dir_from_config) if dir_from_config else Path(".")
|
|
82
|
+
|
|
83
|
+
if not directory.exists():
|
|
84
|
+
console.print(f"[red]Error:[/red] Directory '{directory}' does not exist.")
|
|
85
|
+
raise typer.Exit(1)
|
|
86
|
+
|
|
87
|
+
if not directory.is_dir():
|
|
88
|
+
console.print(f"[red]Error:[/red] '{directory}' is not a directory.")
|
|
89
|
+
raise typer.Exit(1)
|
|
90
|
+
|
|
91
|
+
directory = directory.resolve()
|
|
71
92
|
|
|
72
93
|
# Collect files
|
|
73
94
|
files = _collect_files(directory)
|
|
@@ -126,3 +147,14 @@ def deploy(
|
|
|
126
147
|
console.print(f"[green]✓[/green] Deployed [bold]{page_slug}[/bold] successfully!")
|
|
127
148
|
console.print(f" Files: {file_count}")
|
|
128
149
|
console.print(f" URL: {result.get('url', 'N/A')}")
|
|
150
|
+
|
|
151
|
+
# Nudge to publish if app is private
|
|
152
|
+
application = result.get("application", {})
|
|
153
|
+
if application.get("is_private"):
|
|
154
|
+
app_id = application.get("id")
|
|
155
|
+
if app_id:
|
|
156
|
+
base_url = get_settings().api_url
|
|
157
|
+
console.print()
|
|
158
|
+
console.print("Your app is private. Ready to go public?")
|
|
159
|
+
publish_url = f"{base_url}/applications/{app_id}/publish"
|
|
160
|
+
console.print(f" {publish_url}")
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Project configuration via sweatstack.toml."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import tomllib
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
CONFIG_FILENAME = "sweatstack.toml"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class PageConfig:
|
|
14
|
+
"""Page configuration."""
|
|
15
|
+
|
|
16
|
+
slug: str | None = None
|
|
17
|
+
directory: str | None = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class AppConfig:
|
|
22
|
+
"""Application configuration."""
|
|
23
|
+
|
|
24
|
+
name: str | None = None
|
|
25
|
+
client_id: str | None = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class ProjectConfig:
|
|
30
|
+
"""Project configuration from sweatstack.toml."""
|
|
31
|
+
|
|
32
|
+
app: AppConfig
|
|
33
|
+
page: PageConfig
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def load(cls, path: Path | None = None) -> ProjectConfig | None:
|
|
37
|
+
"""
|
|
38
|
+
Load config from sweatstack.toml in current directory.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
path: Optional path to config file. Defaults to ./sweatstack.toml.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
ProjectConfig if file exists and is valid, None otherwise.
|
|
45
|
+
"""
|
|
46
|
+
config_path = path or Path(CONFIG_FILENAME)
|
|
47
|
+
|
|
48
|
+
if not config_path.exists():
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
data = tomllib.loads(config_path.read_text(encoding="utf-8"))
|
|
53
|
+
except tomllib.TOMLDecodeError:
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
app_data = data.get("app", {})
|
|
57
|
+
page_data = data.get("page", {})
|
|
58
|
+
|
|
59
|
+
return cls(
|
|
60
|
+
app=AppConfig(
|
|
61
|
+
name=app_data.get("name"),
|
|
62
|
+
client_id=app_data.get("client_id"),
|
|
63
|
+
),
|
|
64
|
+
page=PageConfig(
|
|
65
|
+
slug=page_data.get("slug"),
|
|
66
|
+
directory=page_data.get("directory"),
|
|
67
|
+
),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def write_config(
|
|
72
|
+
path: Path | None = None,
|
|
73
|
+
*,
|
|
74
|
+
app_name: str | None = None,
|
|
75
|
+
client_id: str | None = None,
|
|
76
|
+
page_slug: str | None = None,
|
|
77
|
+
page_directory: str | None = None,
|
|
78
|
+
) -> Path:
|
|
79
|
+
"""
|
|
80
|
+
Write sweatstack.toml file.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
path: Optional path to config file. Defaults to ./sweatstack.toml.
|
|
84
|
+
app_name: Application name.
|
|
85
|
+
client_id: OAuth2 client ID.
|
|
86
|
+
page_slug: Page slug.
|
|
87
|
+
page_directory: Page directory.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Path to the written config file.
|
|
91
|
+
"""
|
|
92
|
+
config_path = path or Path(CONFIG_FILENAME)
|
|
93
|
+
|
|
94
|
+
lines = ["[app]"]
|
|
95
|
+
if app_name:
|
|
96
|
+
lines.append(f'name = "{app_name}"')
|
|
97
|
+
if client_id:
|
|
98
|
+
lines.append(f'client_id = "{client_id}"')
|
|
99
|
+
|
|
100
|
+
if page_slug or page_directory:
|
|
101
|
+
lines.append("")
|
|
102
|
+
lines.append("[page]")
|
|
103
|
+
if page_slug:
|
|
104
|
+
lines.append(f'slug = "{page_slug}"')
|
|
105
|
+
if page_directory:
|
|
106
|
+
lines.append(f'directory = "{page_directory}"')
|
|
107
|
+
|
|
108
|
+
lines.append("") # Trailing newline
|
|
109
|
+
config_path.write_text("\n".join(lines), encoding="utf-8")
|
|
110
|
+
|
|
111
|
+
return config_path
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sweatstack-cli
|
|
3
|
+
Version: 0.7.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@sweatstack.no>
|
|
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.11
|
|
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.11+.
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Authenticate with SweatStack
|
|
47
|
+
sweatstack login
|
|
48
|
+
|
|
49
|
+
# Check authentication and version status
|
|
50
|
+
sweatstack status
|
|
51
|
+
|
|
52
|
+
# Deploy a static site
|
|
53
|
+
sweatstack page deploy my-page ./dist
|
|
54
|
+
|
|
55
|
+
# Create an OAuth2 application
|
|
56
|
+
sweatstack app create "My App" --page my-page --env
|
|
57
|
+
|
|
58
|
+
# Logout
|
|
59
|
+
sweatstack logout
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Commands
|
|
63
|
+
|
|
64
|
+
### Authentication
|
|
65
|
+
|
|
66
|
+
| Command | Description |
|
|
67
|
+
|---------|-------------|
|
|
68
|
+
| `sweatstack login` | Authenticate via browser (OAuth2 PKCE) |
|
|
69
|
+
| `sweatstack login --force` | Force re-authentication |
|
|
70
|
+
| `sweatstack logout` | Remove stored credentials |
|
|
71
|
+
| `sweatstack status` | Show auth status, user info, and version |
|
|
72
|
+
| `sweatstack --version` | Show version and check for updates |
|
|
73
|
+
|
|
74
|
+
### Page Deployment
|
|
75
|
+
|
|
76
|
+
Deploy static sites to SweatStack Pages.
|
|
77
|
+
|
|
78
|
+
| Command | Description |
|
|
79
|
+
|---------|-------------|
|
|
80
|
+
| `sweatstack page deploy [SLUG] [DIRECTORY]` | Deploy a static site |
|
|
81
|
+
|
|
82
|
+
- **SLUG** — Page identifier. Falls back to `sweatstack.toml` if omitted.
|
|
83
|
+
- **DIRECTORY** — Directory to deploy. Falls back to `sweatstack.toml`, then current directory.
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Deploy using CLI arguments
|
|
87
|
+
sweatstack page deploy my-page ./dist
|
|
88
|
+
|
|
89
|
+
# Deploy using sweatstack.toml defaults
|
|
90
|
+
sweatstack page deploy
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Application Management
|
|
94
|
+
|
|
95
|
+
Manage OAuth2 applications.
|
|
96
|
+
|
|
97
|
+
| Command | Description |
|
|
98
|
+
|---------|-------------|
|
|
99
|
+
| `sweatstack app create NAME` | Create a new private OAuth2 application |
|
|
100
|
+
| `sweatstack app link` | Link current directory to an existing application |
|
|
101
|
+
|
|
102
|
+
#### `app create` options
|
|
103
|
+
|
|
104
|
+
| Option | Description |
|
|
105
|
+
|--------|-------------|
|
|
106
|
+
| `--description, -d TEXT` | Application description (max 500 chars) |
|
|
107
|
+
| `--page, -p SLUG` | Associate with a SweatStack Page |
|
|
108
|
+
| `--secret, -s` | Generate a client secret |
|
|
109
|
+
| `--env` | Write credentials to `.env` |
|
|
110
|
+
| `--env-file PATH` | Write credentials to a specific file |
|
|
111
|
+
| `--json` | Output as JSON |
|
|
112
|
+
|
|
113
|
+
#### `app link` options
|
|
114
|
+
|
|
115
|
+
| Option | Description |
|
|
116
|
+
|--------|-------------|
|
|
117
|
+
| `--force, -f` | Overwrite existing `sweatstack.toml` |
|
|
118
|
+
|
|
119
|
+
## Project Configuration
|
|
120
|
+
|
|
121
|
+
Commands read defaults from a `sweatstack.toml` in the current directory. This file is created automatically by `app create` and `app link`.
|
|
122
|
+
|
|
123
|
+
```toml
|
|
124
|
+
[app]
|
|
125
|
+
name = "My App"
|
|
126
|
+
client_id = "abc123"
|
|
127
|
+
|
|
128
|
+
[page]
|
|
129
|
+
slug = "my-page"
|
|
130
|
+
directory = "dist"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## CI/CD Usage
|
|
134
|
+
|
|
135
|
+
For automated environments, set environment variables instead of interactive login:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
export SWEATSTACK_API_KEY="your-access-token"
|
|
139
|
+
export SWEATSTACK_REFRESH_TOKEN="your-refresh-token"
|
|
140
|
+
|
|
141
|
+
sweatstack page deploy my-page ./dist
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Environment Variables
|
|
145
|
+
|
|
146
|
+
| Variable | Description | Default |
|
|
147
|
+
|----------|-------------|---------|
|
|
148
|
+
| `SWEATSTACK_URL` | API base URL | `https://app.sweatstack.no` |
|
|
149
|
+
| `SWEATSTACK_API_KEY` | Access token (for CI/CD) | — |
|
|
150
|
+
| `SWEATSTACK_REFRESH_TOKEN` | Refresh token (for CI/CD) | — |
|
|
151
|
+
|
|
152
|
+
## Token Storage
|
|
153
|
+
|
|
154
|
+
Credentials are stored in your OS user data directory:
|
|
155
|
+
|
|
156
|
+
- **macOS**: `~/Library/Application Support/SweatStack/SweatStack/tokens.json`
|
|
157
|
+
- **Linux**: `~/.local/share/SweatStack/SweatStack/tokens.json`
|
|
158
|
+
- **Windows**: `%APPDATA%\SweatStack\SweatStack\tokens.json`
|
|
159
|
+
|
|
160
|
+
Token files are created with `0600` permissions (owner read/write only). This location is shared with the [sweatstack Python library](https://github.com/sweatstack/sweatstack-python), so authenticating with either tool works for both.
|
|
161
|
+
|
|
162
|
+
## Development
|
|
163
|
+
|
|
164
|
+
See [DEVELOPMENT.md](DEVELOPMENT.md) for development setup and contribution guidelines.
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
# Install with dev dependencies
|
|
168
|
+
uv pip install -e ".[dev]"
|
|
169
|
+
|
|
170
|
+
# Run tests
|
|
171
|
+
pytest
|
|
172
|
+
|
|
173
|
+
# Lint and format
|
|
174
|
+
ruff check . && ruff format .
|
|
175
|
+
|
|
176
|
+
# Type check
|
|
177
|
+
mypy src/sweatstack_cli
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
MIT
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
sweatstack_cli/__init__.py,sha256=
|
|
1
|
+
sweatstack_cli/__init__.py,sha256=n2zPulBh92qWjkryatKmyuF192Pfhslh0yCiCZdD-Co,100
|
|
2
2
|
sweatstack_cli/config.py,sha256=cD4OXnVAKVagYazyk9Cw-WWx62tJPqcjQt1cknYyymM,706
|
|
3
3
|
sweatstack_cli/console.py,sha256=69KWAM7ksnYRTONMYTXGXN4LWRAQhRxcLXO0LcWGLqo,300
|
|
4
4
|
sweatstack_cli/exceptions.py,sha256=qEUu8GydXGRKR4ndAdZ2wnkHekmFpBXDJiT5bXdl7Yk,613
|
|
5
5
|
sweatstack_cli/main.py,sha256=obcF1VGhwcE_pI1BwTJkY4J4BgIGac-y3yYGvY6ZOp8,2330
|
|
6
|
+
sweatstack_cli/project.py,sha256=mOwqzuc6D6J6IHf-5L9Vv_sfjpReS4vwzSjUSFi7qys,2716
|
|
6
7
|
sweatstack_cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
8
|
sweatstack_cli/version_check.py,sha256=yZ_qVe7tUBowUDAmMki-wyf5pSxlNDsoWKgTcSfesBA,2015
|
|
8
9
|
sweatstack_cli/api/__init__.py,sha256=l9s6BzF6Wx17K_ElRUX-8aAwwIIzqmGF1PQ3Mbc07mw,107
|
|
@@ -13,10 +14,10 @@ sweatstack_cli/auth/jwt.py,sha256=Ja6ccFyRKtX5MPjFdHNavVYf8KTz0MYT86StbnF-cwg,15
|
|
|
13
14
|
sweatstack_cli/auth/pkce.py,sha256=sO_xUeN1UIvXKX0yEIGgMTIIzvfCOCUwlyNAveEd16k,1542
|
|
14
15
|
sweatstack_cli/auth/tokens.py,sha256=kc8ytoO9PAyjLD6wUTIagVuP7_ia5ZjW5m_WjXIRIkA,4942
|
|
15
16
|
sweatstack_cli/commands/__init__.py,sha256=YCO8wYVvdGeoEI0p_oUm8ANLneuET4SHsKDjitMjAgY,35
|
|
16
|
-
sweatstack_cli/commands/app.py,sha256=
|
|
17
|
+
sweatstack_cli/commands/app.py,sha256=vTMR1Hs76LOJe8q1_FY1AcNPG7LOJdz8SCOfY3ZwayY,6945
|
|
17
18
|
sweatstack_cli/commands/auth.py,sha256=lmlN2RcZIHdbZG-EEUVFwOSn6vPN-Ijgh_5E-cFQVtU,3478
|
|
18
|
-
sweatstack_cli/commands/page.py,sha256=
|
|
19
|
-
sweatstack_cli-0.
|
|
20
|
-
sweatstack_cli-0.
|
|
21
|
-
sweatstack_cli-0.
|
|
22
|
-
sweatstack_cli-0.
|
|
19
|
+
sweatstack_cli/commands/page.py,sha256=tEWjzOmht_IKm47lbprJz-XDKibrx5nFlStuvBRST6A,5329
|
|
20
|
+
sweatstack_cli-0.7.0.dist-info/METADATA,sha256=e4RwtVRDIbDAH6uAPnLL4QcEXu_cvgs3yjMt-LxVqm0,5017
|
|
21
|
+
sweatstack_cli-0.7.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
22
|
+
sweatstack_cli-0.7.0.dist-info/entry_points.txt,sha256=rnH0EV1U9CQlmKQcJ7RmxGpNRTa9J-YdiIJZDYrAXb4,56
|
|
23
|
+
sweatstack_cli-0.7.0.dist-info/RECORD,,
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: sweatstack-cli
|
|
3
|
-
Version: 0.5.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.10
|
|
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
|
|
File without changes
|