sweatstack-cli 0.6.0__tar.gz → 0.7.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 (53) hide show
  1. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/.claude/settings.local.json +2 -1
  2. sweatstack_cli-0.7.1/.claude/skills/sweatstack/SKILL.md +24 -0
  3. sweatstack_cli-0.7.1/.claude/skills/sweatstack/api.md +170 -0
  4. sweatstack_cli-0.7.1/.claude/skills/sweatstack/auth.md +136 -0
  5. sweatstack_cli-0.7.1/.claude/skills/sweatstack/cli.md +70 -0
  6. sweatstack_cli-0.7.1/.claude/skills/sweatstack/pyscript.md +33 -0
  7. sweatstack_cli-0.7.1/.claude/skills/sweatstack/webapp.md +138 -0
  8. sweatstack_cli-0.7.1/PKG-INFO +139 -0
  9. sweatstack_cli-0.7.1/README.md +109 -0
  10. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/pyproject.toml +1 -1
  11. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/src/sweatstack_cli/__init__.py +1 -1
  12. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/src/sweatstack_cli/commands/app.py +9 -0
  13. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/src/sweatstack_cli/commands/page.py +12 -0
  14. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/uv.lock +1 -1
  15. sweatstack_cli-0.6.0/PKG-INFO +0 -132
  16. sweatstack_cli-0.6.0/README.md +0 -102
  17. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/.gitignore +0 -0
  18. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/.python-version +0 -0
  19. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/CREATE_PRIVATE_APP.md +0 -0
  20. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/DEVELOPMENT.md +0 -0
  21. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/Makefile +0 -0
  22. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/PLAN.md +0 -0
  23. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/src/sweatstack_cli/api/__init__.py +0 -0
  24. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/src/sweatstack_cli/api/client.py +0 -0
  25. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/src/sweatstack_cli/auth/__init__.py +0 -0
  26. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/src/sweatstack_cli/auth/callback_server.py +0 -0
  27. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/src/sweatstack_cli/auth/jwt.py +0 -0
  28. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/src/sweatstack_cli/auth/pkce.py +0 -0
  29. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/src/sweatstack_cli/auth/tokens.py +0 -0
  30. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/src/sweatstack_cli/commands/__init__.py +0 -0
  31. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/src/sweatstack_cli/commands/auth.py +0 -0
  32. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/src/sweatstack_cli/config.py +0 -0
  33. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/src/sweatstack_cli/console.py +0 -0
  34. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/src/sweatstack_cli/exceptions.py +0 -0
  35. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/src/sweatstack_cli/main.py +0 -0
  36. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/src/sweatstack_cli/project.py +0 -0
  37. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/src/sweatstack_cli/py.typed +0 -0
  38. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/src/sweatstack_cli/version_check.py +0 -0
  39. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/sweatstack.toml +0 -0
  40. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/tests/__init__.py +0 -0
  41. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/tests/conftest.py +0 -0
  42. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/tests/test_api/__init__.py +0 -0
  43. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/tests/test_api/test_client.py +0 -0
  44. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/tests/test_auth/__init__.py +0 -0
  45. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/tests/test_auth/test_callback_server.py +0 -0
  46. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/tests/test_auth/test_jwt.py +0 -0
  47. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/tests/test_auth/test_pkce.py +0 -0
  48. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/tests/test_auth/test_tokens.py +0 -0
  49. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/tests/test_commands/__init__.py +0 -0
  50. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/tests/test_commands/test_app.py +0 -0
  51. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/tests/test_commands/test_cli.py +0 -0
  52. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/tests/test_project.py +0 -0
  53. {sweatstack_cli-0.6.0 → sweatstack_cli-0.7.1}/tests/test_version_check.py +0 -0
@@ -18,7 +18,8 @@
18
18
  "Bash(make help:*)",
19
19
  "Bash(tree:*)",
20
20
  "Bash(env:*)",
21
- "Bash(uv run python:*)"
21
+ "Bash(uv run python:*)",
22
+ "Bash(git:*)"
22
23
  ]
23
24
  }
24
25
  }
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: sweatstack
3
+ description: Builds applications integrating with SweatStack sports data platform. Provides OAuth PKCE authentication, API endpoints, and patterns for single-file HTML web apps. Use when building fitness dashboards, athletic data tools, or apps using SweatStack.
4
+ ---
5
+
6
+ # SweatStack
7
+
8
+ Sports data platform providing auth, storage, and wearable integrations for developers.
9
+
10
+ **Base URL:** `https://app.sweatstack.no`
11
+
12
+ **Default scopes:** `openid profile data:read`
13
+
14
+ ## Reference
15
+
16
+ **Webapps:** Single-file HTML apps with vanilla JS. See [webapp.md](webapp.md)
17
+
18
+ **PyScript webapps:** Python data science in the browser (pandas, sklearn, scipy). Trade-offs and template. See [pyscript.md](pyscript.md)
19
+
20
+ **CLI:** App management and deployment. See [cli.md](cli.md)
21
+
22
+ **Auth:** OAuth2/PKCE authentication. See [auth.md](auth.md)
23
+
24
+ **API:** Endpoints and data formats. See [api.md](api.md)
@@ -0,0 +1,170 @@
1
+ # SweatStack API Reference
2
+
3
+ ## Contents
4
+
5
+ - [Endpoints](#endpoints)
6
+ - [Choosing the Right Endpoint](#choosing-the-right-endpoint)
7
+ - [Data Fetching Patterns](#data-fetching-patterns)
8
+ - [Parquet Responses](#parquet-responses)
9
+ - [Metrics Parameter](#metrics-parameter)
10
+ - [Available Metrics](#available-metrics)
11
+ - [GPS Data](#gps-data)
12
+ - [Sports](#sports)
13
+ - [Authentication](#authentication)
14
+
15
+ ---
16
+
17
+ **Base URL:** `https://app.sweatstack.no`
18
+
19
+ **OpenAPI spec:** https://app.sweatstack.no/openapi.json
20
+
21
+ **IMPORTANT: Before making any API call, fetch the OpenAPI spec to verify:**
22
+ - Valid enum values (e.g. sports use hierarchical format like `walking.hiking`, not `hiking`)
23
+ - Required vs optional parameters
24
+ - Response schemas — look up the specific schema (e.g., `ActivityRead`) before accessing fields
25
+
26
+ Never guess field names or enum values. The spec is the source of truth.
27
+
28
+ ## Common Gotchas
29
+
30
+ **Activity timestamps:** Use `start` (UTC) or `start_local` (user's local time). Not `start_time`, `started_at`, or `timestamp`.
31
+
32
+ **Key activity fields:** `id`, `start`, `start_local`, `sport`, `duration`, `name`, `description`
33
+
34
+ **Date parameters:** Use `YYYY-MM-DD` format. Most endpoints reject timestamps.
35
+
36
+ **Parameter naming:** Longitudinal endpoints use `sports` (plural), activity list uses `sport` (singular).
37
+
38
+ ## Endpoints
39
+
40
+ | Endpoint | Method | Purpose |
41
+ |----------|--------|---------|
42
+ | `/api/v1/activities/` | GET | List activities (filter: sport, tags, start, end) |
43
+ | `/api/v1/activities/{id}` | GET | Get activity details |
44
+ | `/api/v1/activities/{id}/data` | GET | Timeseries data (returns Parquet) |
45
+ | `/api/v1/activities/latest` | GET | Most recent activity |
46
+ | `/api/v1/activities/upload` | POST | Upload .fit files (max 10) |
47
+ | `/api/v1/profile/tags/` | GET | User-defined tags |
48
+ | `/api/v1/profile/sports/` | GET | Available sports hierarchy |
49
+ | `/api/v1/activities/longitudinal-data` | GET | Timeseries data across activities. Allows extensive filtering on sports and metrics. (returns Parquet) |
50
+ | `/api/v1/activities/longitudinal-mean-max` | GET | Historical mean-max (returns Parquet) |
51
+ | `/api/v1/oauth/userinfo` | GET | Current user info |
52
+
53
+ **Critical**: Never guess request paramaters or body schemas. Use the openapi.json when not sure.
54
+
55
+ ## Choosing the Right Endpoint
56
+
57
+ **Multi-activity analysis** (trends, distributions, comparisons): Use `/activities/longitudinal-data` — one request for all matching activities.
58
+
59
+ **Single activity** (detail view, route map): Use `/activities/{id}/data`.
60
+
61
+ ## Data Fetching Patterns
62
+
63
+ **Time window comparisons:** Fetch the larger window once, filter client-side. Avoid multiple requests for overlapping or adjacent ranges.
64
+
65
+ **Bulk activity data:** Need data from many activities? Use `/activities/longitudinal-data` with filters instead of looping through `/activities/{id}/data` — one request vs N.
66
+
67
+ **Per-activity stats:** Longitudinal endpoints return raw samples with `activity_id`. Use DuckDB to aggregate: `GROUP BY activity_id`.
68
+
69
+ ## Parquet Responses
70
+
71
+ Data endpoints return **Parquet files (snappy-compressed)**. Your Parquet library must support snappy codec.
72
+
73
+ **Browser apps:** Use DuckDB-WASM (recommended - full codec support, reliable CDN).
74
+
75
+ **Other environments:** Any Parquet library with snappy support.
76
+
77
+ ### DuckDB-WASM Setup (browser)
78
+
79
+ ```javascript
80
+ import * as duckdb from 'https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm@1.28.0/+esm';
81
+
82
+ const JSDELIVR_BUNDLES = duckdb.getJsDelivrBundles();
83
+ const bundle = await duckdb.selectBundle(JSDELIVR_BUNDLES);
84
+ const worker_url = URL.createObjectURL(
85
+ new Blob([`importScripts("${bundle.mainWorker}");`], { type: 'text/javascript' })
86
+ );
87
+ const worker = new Worker(worker_url);
88
+ const logger = new duckdb.ConsoleLogger();
89
+ const db = new duckdb.AsyncDuckDB(logger, worker);
90
+ await db.instantiate(bundle.mainModule, bundle.pthreadWorker);
91
+ URL.revokeObjectURL(worker_url);
92
+ ```
93
+
94
+ ### Parsing Parquet
95
+
96
+ ```javascript
97
+ async function parseParquet(arrayBuffer) {
98
+ const uint8 = new Uint8Array(arrayBuffer);
99
+ await db.registerFileBuffer('data.parquet', uint8);
100
+ const conn = await db.connect();
101
+ const result = await conn.query('SELECT * FROM "data.parquet"');
102
+
103
+ const data = {};
104
+ for (const field of result.schema.fields) {
105
+ data[field.name] = result.getChild(field.name).toArray();
106
+ }
107
+ await conn.close();
108
+ return data;
109
+ }
110
+
111
+ // Usage
112
+ const response = await fetch(`${API_BASE}/activities/${id}/data?metrics=altitude&metrics=distance`, {
113
+ headers: { 'Authorization': `Bearer ${token}` }
114
+ });
115
+ const data = await parseParquet(await response.arrayBuffer());
116
+ // data = { altitude: [...], distance: [...] }
117
+ ```
118
+
119
+ **Note:** DuckDB-WASM is ~10MB but reliable. Other browser libraries (parquet-wasm, hyparquet) have codec or CDN issues.
120
+
121
+ ## Metrics Parameter
122
+
123
+ Pass multiple times for multiple metrics:
124
+
125
+ ```
126
+ ?metrics=altitude&metrics=distance ✓
127
+ ?metrics=altitude,distance ✗
128
+ ```
129
+
130
+ ## Available Metrics
131
+
132
+ duration, power, speed, heart_rate, cadence, altitude, latitude, longitude, temperature, core_temperature, smo2, distance, lactate, RPE
133
+
134
+ ## GPS Data
135
+
136
+ Filter invalid coordinates:
137
+
138
+ ```javascript
139
+ function isValidGPS(lat, lon) {
140
+ if (!Number.isFinite(lat) || !Number.isFinite(lon)) return false;
141
+ if (lat < -90 || lat > 90) return false;
142
+ if (lon < -180 || lon > 180) return false;
143
+
144
+ const EPSILON = 1e-6;
145
+ if (Math.abs(lat) < EPSILON && Math.abs(lon) < EPSILON) return false;
146
+
147
+ return true;
148
+ }
149
+ ```
150
+
151
+ Catches NaN (from null values), Null Island (0, 0), and garbage floats.
152
+
153
+ ## Sports
154
+
155
+ **Formatting sport values for display:**
156
+
157
+ ```javascript
158
+ function formatSport(sport) {
159
+ const parts = sport.split(".");
160
+ const base = parts[0].replace(/_/g, " ");
161
+ if (parts.length === 1) return base;
162
+ const rest = parts.slice(1).join(" ").replace(/_/g, " ");
163
+ return `${base} (${rest})`;
164
+ }
165
+ // "cycling.mountain" → "cycling (mountain)"
166
+ ```
167
+
168
+ ## Authentication
169
+
170
+ All endpoints require `Authorization: Bearer TOKEN` header.
@@ -0,0 +1,136 @@
1
+ # OAuth2/OpenID Authentication
2
+
3
+ ## Contents
4
+
5
+ - [Endpoints](#endpoints)
6
+ - [Scopes](#scopes)
7
+ - [PKCE Flow](#pkce-flow-public-clients)
8
+ - [Authorization Code Flow](#authorization-code-flow-private-clients)
9
+ - [Token Exchange](#token-exchange)
10
+ - [Refresh Tokens](#refresh-tokens)
11
+ - [API Authentication](#api-authentication)
12
+
13
+ ---
14
+
15
+ ## Endpoints
16
+
17
+ | Endpoint | URL |
18
+ |----------|-----|
19
+ | Authorization | `https://app.sweatstack.no/oauth/authorize` |
20
+ | Token | `https://app.sweatstack.no/api/v1/oauth/token` |
21
+
22
+ ## Scopes
23
+
24
+ | Scope | Purpose |
25
+ |-------|---------|
26
+ | `openid` | OpenID Connect (currently unused by SweatStack) |
27
+ | `profile` | Access to profile information |
28
+ | `data:read` | Read access to user data |
29
+ | `data:write` | Write and delete access (no read) |
30
+ | `offline_access` | Refresh token for public clients |
31
+
32
+ **Default:** `openid profile data:read`
33
+
34
+ ## PKCE Flow (Public Clients)
35
+
36
+ For browser apps, mobile apps, and other public clients without a client secret.
37
+
38
+ ### 1. Generate PKCE pair
39
+
40
+ - Code verifier: random string (43-128 chars)
41
+ - Code challenge: base64url(SHA-256(verifier))
42
+
43
+ ### 2. Redirect to authorize
44
+
45
+ ```
46
+ https://app.sweatstack.no/oauth/authorize?
47
+ client_id=CLIENT_ID&
48
+ redirect_uri=REDIRECT_URI&
49
+ response_type=code&
50
+ scope=openid%20profile%20data:read&
51
+ code_challenge=CHALLENGE&
52
+ code_challenge_method=S256&
53
+ prompt=none
54
+ ```
55
+
56
+ **Critical:** Include `prompt=none` to avoid double consent screens.
57
+
58
+ ### 3. Exchange code for token
59
+
60
+ ```
61
+ POST https://app.sweatstack.no/api/v1/oauth/token
62
+ Content-Type: application/x-www-form-urlencoded
63
+
64
+ grant_type=authorization_code&
65
+ code=AUTH_CODE&
66
+ redirect_uri=REDIRECT_URI&
67
+ client_id=CLIENT_ID&
68
+ code_verifier=VERIFIER
69
+ ```
70
+
71
+ ## Authorization Code Flow (Private Clients)
72
+
73
+ For server-side apps with a client secret.
74
+
75
+ ### 1. Redirect to authorize
76
+
77
+ ```
78
+ https://app.sweatstack.no/oauth/authorize?
79
+ client_id=CLIENT_ID&
80
+ redirect_uri=REDIRECT_URI&
81
+ response_type=code&
82
+ scope=openid%20profile%20data:read&
83
+ prompt=none
84
+ ```
85
+
86
+ ### 2. Exchange code for token
87
+
88
+ ```
89
+ POST https://app.sweatstack.no/api/v1/oauth/token
90
+ Content-Type: application/x-www-form-urlencoded
91
+
92
+ grant_type=authorization_code&
93
+ code=AUTH_CODE&
94
+ redirect_uri=REDIRECT_URI&
95
+ client_id=CLIENT_ID&
96
+ client_secret=CLIENT_SECRET
97
+ ```
98
+
99
+ ## Token Exchange
100
+
101
+ **Response:**
102
+ ```json
103
+ {
104
+ "access_token": "...",
105
+ "token_type": "Bearer",
106
+ "expires_in": 3600,
107
+ "refresh_token": "...",
108
+ "scope": "openid profile data:read"
109
+ }
110
+ ```
111
+
112
+ - `refresh_token` included automatically for private clients
113
+ - Public clients must request `offline_access` scope
114
+
115
+ ## Refresh Tokens
116
+
117
+ Exchange a refresh token for a new access token:
118
+
119
+ ```
120
+ POST https://app.sweatstack.no/api/v1/oauth/token
121
+ Content-Type: application/x-www-form-urlencoded
122
+
123
+ grant_type=refresh_token&
124
+ refresh_token=REFRESH_TOKEN&
125
+ client_id=CLIENT_ID
126
+ ```
127
+
128
+ For private clients, include `client_secret`.
129
+
130
+ ## API Authentication
131
+
132
+ All API requests require the Authorization header:
133
+
134
+ ```
135
+ Authorization: Bearer ACCESS_TOKEN
136
+ ```
@@ -0,0 +1,70 @@
1
+ # SweatStack CLI
2
+
3
+ Command-line tool for authentication, app management, and static deployments.
4
+
5
+ ## Installation
6
+
7
+ **macOS/Linux:**
8
+ ```bash
9
+ curl -LsSf sweatstack.no/install-cli | sh
10
+ ```
11
+
12
+ **Windows:**
13
+ ```powershell
14
+ powershell -ExecutionPolicy ByPass -c "irm sweatstack.no/install-cli-windows | iex"
15
+ ```
16
+
17
+ Verify with `sweatstack --version`. Alternative: `uv pip install sweatstack-cli`
18
+
19
+ ## Authentication
20
+
21
+ ```bash
22
+ sweatstack login # Opens browser for OAuth2, stores credentials locally
23
+ sweatstack logout # Remove stored credentials
24
+ sweatstack status # Show auth status and token details
25
+ ```
26
+
27
+ Always `sweatstack status` to check if the user is authenticated.
28
+
29
+ ## Create Applications
30
+
31
+ ```bash
32
+ sweatstack app create "My App"
33
+ ```
34
+
35
+ **Options:**
36
+
37
+ | Flag | Purpose |
38
+ |------|---------|
39
+ | `--page myapp` | Associate with a SweatStack Page (auto-configures redirect URI) |
40
+ | `--secret` | Generate client secret for confidential clients |
41
+ | `--env` | Write credentials to `.env` file |
42
+ | `--env-file PATH` | Write credentials to specific file |
43
+ | `--json` | Output as JSON for scripting |
44
+
45
+ **Full example:**
46
+ ```bash
47
+ sweatstack app create "My Streamlit App" --page my-streamlit-app --secret --env-file .env.local
48
+ ```
49
+
50
+ Creates a private application. Public apps require the web dashboard. Page slugs can be rejected if unavailable.
51
+
52
+ ## Deploy Static Sites
53
+
54
+ ```bash
55
+ sweatstack page deploy myapp ./dist
56
+ ```
57
+
58
+ Uploads files to `https://myapp.sweatstack.pages.dev`.
59
+
60
+ **Constraints:**
61
+ - Pages must be pre-created via dashboard: https://app.sweatstack.no/settings/api
62
+ - 10MB limit per app
63
+
64
+ ## Help
65
+
66
+ ```bash
67
+ sweatstack --help
68
+ sweatstack app --help
69
+ sweatstack app create --help
70
+ ```
@@ -0,0 +1,33 @@
1
+ # PyScript Web Apps
2
+
3
+ Use when the user needs Python data science libraries (pandas, numpy, scikit-learn, scipy, matplotlib) in the browser. No backend — static deployment.
4
+
5
+ ## Trade-offs vs Vanilla JS Webapp
6
+
7
+ | | PyScript | Vanilla JS ([webapp.md](webapp.md)) |
8
+ |---|---|---|
9
+ | **Best for** | Data analysis, ML, statistical computing | Dashboards, visualizations, simple data display |
10
+ | **Libraries** | Full Python ecosystem via Pyodide | Browser JS only |
11
+ | **Cold start** | 10-20s (loading Pyodide + packages) | Instant |
12
+ | **Computation** | Client-side Python (slower than native) | Client-side JS (fast) |
13
+
14
+ Confirm with user before choosing PyScript — the cold start is significant.
15
+
16
+ ## Template
17
+
18
+ **Repo:** https://github.com/sweatstack/sweatstack-pyscript-template
19
+
20
+ Clone the template to start. The README covers setup, the ctx API, parameter patterns, examples, and deployment.
21
+
22
+ ## Workflow
23
+
24
+ 1. Clone the template repo
25
+ 2. Register an OAuth app with `sweatstack auth register`
26
+ 3. Set Client ID in `index.html`
27
+ 4. Write analysis logic in `main.py`
28
+ 5. Deploy with `sweatstack apps deploy`
29
+
30
+ ## Key Constraints
31
+
32
+ - **Only edit `main.py` and `index.html`.** Do not generate or modify `app.js`, `worker.py`, `runtime.py`, CSS, or `mini-coi.js` — these are framework files maintained by the template.
33
+ - The template includes 4 working examples (`examples/`) — use these as reference for different patterns (single activity, multi-parameter, ML pipelines).
@@ -0,0 +1,138 @@
1
+ # Building Single-File Web Apps
2
+
3
+ ## Contents
4
+
5
+ - [Tech Stack](#tech-stack)
6
+ - [Output Location](#output-location)
7
+ - [Quality Bar](#quality-bar)
8
+ - [Prerequisites](#prerequisites)
9
+ - [Authentication](#authentication)
10
+ - [HTML Structure](#html-structure)
11
+ - [Default Styling](#default-styling)
12
+ - [SweatStack Branding](#sweatstack-branding)
13
+ - [After Generation](#after-generation)
14
+ - [Reference Apps](#reference-apps)
15
+
16
+ ---
17
+
18
+ ## Tech Stack
19
+
20
+ - Single HTML file with inline `<style>` and `<script>`
21
+ - Vanilla JS only (no React, no build step)
22
+ - CDN dependencies when needed (e.g., html2canvas for sharing)
23
+ - localStorage for access token and user preferences
24
+ - DuckDB-WASM for Parquet parsing (supports full SQL for analytics)
25
+
26
+ ## Output Location
27
+
28
+ Place generated files in `public/index.html`. Creates a deployment-ready structure.
29
+
30
+ ## Quality Bar
31
+
32
+ UI can be minimal - focus on functionality. Auth and API error handling must be solid: handle 401s, show clear error states, never fail silently.
33
+
34
+ ## Prerequisites
35
+
36
+ Requires a **Client ID** from a SweatStack application.
37
+
38
+ **If user provides a client_id:** Use it directly, skip app creation.
39
+
40
+ **Otherwise, create an app with the SweatStack CLI:**
41
+ 1. Derive app name from the task (e.g., "heart rate analyzer" → "Heart Rate Analyzer")
42
+ 2. Generate page slug from app name: lowercase, spaces to hyphens (e.g., "heart-rate-analyzer")
43
+ 3. Create the app with the sweatstack cli
44
+ 4. If slug is rejected, prompt user for alternative
45
+
46
+ Prompt user for app name only if it cannot be reasonably inferred from the task.
47
+
48
+ ## Authentication
49
+
50
+ Browser apps use PKCE flow (public client, no client secret).
51
+
52
+ **Critical:** Always include `prompt=none` in auth redirects to avoid double consent screens.
53
+
54
+ **Token storage:** Use localStorage. Store `access_token` and expiry timestamp.
55
+
56
+ **On page load:** No token → show login. Expired token → silent re-auth with `prompt=none`.
57
+
58
+ **On 401:** Silent re-auth with `prompt=none`.
59
+
60
+ **Constants to define:**
61
+ ```javascript
62
+ const CLIENT_ID = '...';
63
+ const REDIRECT_URI = window.location.origin + window.location.pathname; // Works for localhost and production
64
+ const AUTH_URL = 'https://app.sweatstack.no/oauth/authorize';
65
+ const TOKEN_URL = 'https://app.sweatstack.no/api/v1/oauth/token';
66
+ const API_BASE = 'https://app.sweatstack.no/api/v1';
67
+ ```
68
+
69
+ ## HTML Structure
70
+
71
+ - Auth container (login button): shown when no token
72
+ - App container (main UI): shown after authentication
73
+
74
+ ## Default Styling
75
+
76
+ Use only when user doesn't specify styling preferences.
77
+
78
+ **Aesthetic direction:** Minimal and clean. Think scientific paper, not dashboard.
79
+
80
+ **Layout:**
81
+ - Centered container, `max-width: 48rem`
82
+ - Visual elements fill container width
83
+ - Full-width only for apps that are primarily visual (e.g., mapping app)
84
+
85
+ **Styling:**
86
+ - Clear heading hierarchy with generous whitespace
87
+ - Light background, dark text
88
+ - Everything flows vertically — no sidebars, no cards, no visual clutter
89
+
90
+ **Typography:** System fonts are acceptable for minimal aesthetic. If choosing a web font, make it intentional — one distinctive font, not generic defaults like Inter or Roboto.
91
+
92
+ **What to avoid:** Generic "AI slop" — purple gradients, card-heavy layouts, excessive shadows, decorative elements that add no meaning. Minimalism requires precision in spacing and typography, not decoration.
93
+
94
+ ## SweatStack Branding
95
+
96
+ **Login page content:**
97
+ - Brief explanation of what the app does
98
+ - Text explaining the app uses SweatStack to access sports data (e.g., "This app connects to SweatStack to access your workout data")
99
+
100
+ **Login button** (Norwegian flag red):
101
+
102
+ ```html
103
+ <button onclick="startAuth()" style="
104
+ background: #EF2B2D;
105
+ color: white;
106
+ border: none;
107
+ padding: 0.75rem 1.5rem;
108
+ font-size: 1rem;
109
+ border-radius: 4px;
110
+ cursor: pointer;
111
+ ">Connect with SweatStack</button>
112
+ ```
113
+
114
+ **Logout button:** Top right of main content area (not viewport). Clears sessionStorage and returns to login view.
115
+
116
+ **Footer** (always present):
117
+
118
+ ```html
119
+ <footer style="color: #666; font-size: 0.875rem; text-align: center; margin-top: 3rem;">
120
+ Powered by <a href="https://sweatstack.no" style="color: #666;">SweatStack</a> ·
121
+ Built with <a href="https://github.com/sweatStack/sweatstack-skills" style="color: #666;">Skills</a>
122
+ </footer>
123
+ ```
124
+
125
+ ## After Generation
126
+
127
+ **Preview locally:** Suggest user start a local file server in the `public/` directory.
128
+
129
+ **Next steps to mention:**
130
+ - **Deploy to the web:** `sweatstack page deploy <slug> ./public`
131
+ - **Make available to other users:** Add privacy policy and app description at `https://app.sweatstack.no/applications/{APP_ID}`
132
+
133
+ ## Reference Apps
134
+
135
+ Study these for patterns:
136
+
137
+ - **graph.sweatstack.no** - Activity contribution graph
138
+ - **zones.sweatstack.no** - Training zone calculator
@@ -0,0 +1,139 @@
1
+ Metadata-Version: 2.4
2
+ Name: sweatstack-cli
3
+ Version: 0.7.1
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
+ Requires Python 3.11+. We recommend [uv](https://docs.astral.sh/uv/) for installation.
38
+
39
+ ```bash
40
+ # Run directly without installing
41
+ uvx sweatstack-cli
42
+
43
+ # Or install as a tool
44
+ uv tool install sweatstack-cli
45
+
46
+ # Or with pip
47
+ pip install sweatstack-cli
48
+ ```
49
+
50
+ To upgrade:
51
+
52
+ ```bash
53
+ uv tool upgrade sweatstack-cli
54
+ ```
55
+
56
+ To reinstall (e.g. after a broken install or Python version change):
57
+
58
+ ```bash
59
+ uvx --reinstall sweatstack-cli
60
+ # or if installed as a tool
61
+ uv tool install --reinstall sweatstack-cli
62
+ ```
63
+
64
+ ## Quick Start
65
+
66
+ ```bash
67
+ # Authenticate with SweatStack
68
+ sweatstack login
69
+
70
+ # Create an app and deploy
71
+ sweatstack app create "My App" --page my-page --env
72
+ sweatstack page deploy
73
+ ```
74
+
75
+ ## Commands
76
+
77
+ ### Authentication
78
+
79
+ | Command | Description |
80
+ |---------|-------------|
81
+ | `sweatstack login` | Authenticate via browser (OAuth2 PKCE) |
82
+ | `sweatstack login --force` | Force re-authentication |
83
+ | `sweatstack logout` | Remove stored credentials |
84
+ | `sweatstack status` | Show auth status, user info, and version |
85
+ | `sweatstack --version` | Show version and check for updates |
86
+
87
+ ### Application Management
88
+
89
+ | Command | Description |
90
+ |---------|-------------|
91
+ | `sweatstack app create NAME` | Create a new private OAuth2 application |
92
+ | `sweatstack app link` | Link current directory to an existing application |
93
+
94
+ #### `app create` options
95
+
96
+ | Option | Description |
97
+ |--------|-------------|
98
+ | `--description, -d TEXT` | Application description (max 500 chars) |
99
+ | `--page, -p SLUG` | Associate with a SweatStack Page |
100
+ | `--secret, -s` | Generate a client secret |
101
+ | `--env` | Write credentials to `.env` |
102
+ | `--env-file PATH` | Write credentials to a specific file |
103
+ | `--json` | Output as JSON |
104
+
105
+ #### `app link` options
106
+
107
+ | Option | Description |
108
+ |--------|-------------|
109
+ | `--force, -f` | Overwrite existing `sweatstack.toml` |
110
+
111
+ ### Project Configuration
112
+
113
+ Both `app create` and `app link` generate a `sweatstack.toml` in the current directory. Other commands read defaults from this file.
114
+
115
+ ```toml
116
+ [app]
117
+ name = "My App"
118
+ client_id = "abc123"
119
+
120
+ [page]
121
+ slug = "my-page"
122
+ directory = "dist"
123
+ ```
124
+
125
+ ### Page Deployment
126
+
127
+ Deploy static sites to SweatStack Pages.
128
+
129
+ ```bash
130
+ # Deploy using sweatstack.toml defaults (recommended)
131
+ sweatstack page deploy
132
+
133
+ # Or specify slug and directory directly
134
+ sweatstack page deploy my-page ./dist
135
+ ```
136
+
137
+ ## License
138
+
139
+ MIT
@@ -0,0 +1,109 @@
1
+ # SweatStack CLI
2
+
3
+ Command-line interface for [SweatStack](https://sweatstack.no) — the sports data platform for developers.
4
+
5
+ ## Installation
6
+
7
+ Requires Python 3.11+. We recommend [uv](https://docs.astral.sh/uv/) for installation.
8
+
9
+ ```bash
10
+ # Run directly without installing
11
+ uvx sweatstack-cli
12
+
13
+ # Or install as a tool
14
+ uv tool install sweatstack-cli
15
+
16
+ # Or with pip
17
+ pip install sweatstack-cli
18
+ ```
19
+
20
+ To upgrade:
21
+
22
+ ```bash
23
+ uv tool upgrade sweatstack-cli
24
+ ```
25
+
26
+ To reinstall (e.g. after a broken install or Python version change):
27
+
28
+ ```bash
29
+ uvx --reinstall sweatstack-cli
30
+ # or if installed as a tool
31
+ uv tool install --reinstall sweatstack-cli
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ ```bash
37
+ # Authenticate with SweatStack
38
+ sweatstack login
39
+
40
+ # Create an app and deploy
41
+ sweatstack app create "My App" --page my-page --env
42
+ sweatstack page deploy
43
+ ```
44
+
45
+ ## Commands
46
+
47
+ ### Authentication
48
+
49
+ | Command | Description |
50
+ |---------|-------------|
51
+ | `sweatstack login` | Authenticate via browser (OAuth2 PKCE) |
52
+ | `sweatstack login --force` | Force re-authentication |
53
+ | `sweatstack logout` | Remove stored credentials |
54
+ | `sweatstack status` | Show auth status, user info, and version |
55
+ | `sweatstack --version` | Show version and check for updates |
56
+
57
+ ### Application Management
58
+
59
+ | Command | Description |
60
+ |---------|-------------|
61
+ | `sweatstack app create NAME` | Create a new private OAuth2 application |
62
+ | `sweatstack app link` | Link current directory to an existing application |
63
+
64
+ #### `app create` options
65
+
66
+ | Option | Description |
67
+ |--------|-------------|
68
+ | `--description, -d TEXT` | Application description (max 500 chars) |
69
+ | `--page, -p SLUG` | Associate with a SweatStack Page |
70
+ | `--secret, -s` | Generate a client secret |
71
+ | `--env` | Write credentials to `.env` |
72
+ | `--env-file PATH` | Write credentials to a specific file |
73
+ | `--json` | Output as JSON |
74
+
75
+ #### `app link` options
76
+
77
+ | Option | Description |
78
+ |--------|-------------|
79
+ | `--force, -f` | Overwrite existing `sweatstack.toml` |
80
+
81
+ ### Project Configuration
82
+
83
+ Both `app create` and `app link` generate a `sweatstack.toml` in the current directory. Other commands read defaults from this file.
84
+
85
+ ```toml
86
+ [app]
87
+ name = "My App"
88
+ client_id = "abc123"
89
+
90
+ [page]
91
+ slug = "my-page"
92
+ directory = "dist"
93
+ ```
94
+
95
+ ### Page Deployment
96
+
97
+ Deploy static sites to SweatStack Pages.
98
+
99
+ ```bash
100
+ # Deploy using sweatstack.toml defaults (recommended)
101
+ sweatstack page deploy
102
+
103
+ # Or specify slug and directory directly
104
+ sweatstack page deploy my-page ./dist
105
+ ```
106
+
107
+ ## License
108
+
109
+ MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sweatstack-cli"
3
- version = "0.6.0"
3
+ version = "0.7.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" }]
@@ -1,3 +1,3 @@
1
1
  """SweatStack CLI — Command-line interface for the SweatStack platform."""
2
2
 
3
- __version__ = "0.6.0"
3
+ __version__ = "0.7.1"
@@ -19,6 +19,12 @@ app = typer.Typer(name="app", help="Manage OAuth2 applications.")
19
19
  @app.command()
20
20
  def create(
21
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
+ ),
22
28
  page: str | None = typer.Option(
23
29
  None,
24
30
  "--page",
@@ -54,6 +60,9 @@ def create(
54
60
 
55
61
  body: dict[str, str | bool] = {"name": name}
56
62
 
63
+ if description:
64
+ body["description"] = description
65
+
57
66
  if page:
58
67
  body["page"] = page
59
68
 
@@ -9,6 +9,7 @@ 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
14
15
  from sweatstack_cli.project import CONFIG_FILENAME, ProjectConfig
@@ -146,3 +147,14 @@ def deploy(
146
147
  console.print(f"[green]✓[/green] Deployed [bold]{page_slug}[/bold] successfully!")
147
148
  console.print(f" Files: {file_count}")
148
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}")
@@ -463,7 +463,7 @@ wheels = [
463
463
 
464
464
  [[package]]
465
465
  name = "sweatstack-cli"
466
- version = "0.5.0"
466
+ version = "0.6.0"
467
467
  source = { editable = "." }
468
468
  dependencies = [
469
469
  { name = "httpx" },
@@ -1,132 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: sweatstack-cli
3
- Version: 0.6.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.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
@@ -1,102 +0,0 @@
1
- # SweatStack CLI
2
-
3
- Command-line interface for [SweatStack](https://sweatstack.no) — the sports data platform for developers.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- pip install sweatstack-cli
9
- ```
10
-
11
- Requires Python 3.13+.
12
-
13
- ## Quick Start
14
-
15
- ```bash
16
- # Authenticate with SweatStack
17
- sweatstack login
18
-
19
- # Check who you're logged in as
20
- sweatstack whoami
21
-
22
- # Deploy a static site
23
- sweatstack pages deploy ./dist --prod
24
-
25
- # View authentication status
26
- sweatstack status
27
-
28
- # Logout
29
- sweatstack logout
30
- ```
31
-
32
- ## Commands
33
-
34
- ### Authentication
35
-
36
- | Command | Description |
37
- |---------|-------------|
38
- | `sweatstack login` | Authenticate via browser |
39
- | `sweatstack login --force` | Force re-authentication |
40
- | `sweatstack logout` | Remove stored credentials |
41
- | `sweatstack whoami` | Show current user |
42
- | `sweatstack status` | Show token status and expiry |
43
-
44
- ### Pages
45
-
46
- | Command | Description |
47
- |---------|-------------|
48
- | `sweatstack pages deploy <dir>` | Deploy static site |
49
- | `sweatstack pages deploy --prod` | Deploy to production |
50
- | `sweatstack pages list` | List all sites |
51
- | `sweatstack pages delete <name>` | Delete a site |
52
-
53
- ## CI/CD Usage
54
-
55
- For automated environments, use environment variables instead of interactive login:
56
-
57
- ```bash
58
- export SWEATSTACK_API_KEY="your-access-token"
59
- export SWEATSTACK_REFRESH_TOKEN="your-refresh-token"
60
-
61
- sweatstack pages deploy ./dist --prod
62
- ```
63
-
64
- ## Configuration
65
-
66
- | Environment Variable | Description | Default |
67
- |---------------------|-------------|---------|
68
- | `SWEATSTACK_URL` | API base URL | `https://app.sweatstack.no` |
69
- | `SWEATSTACK_API_KEY` | Access token | — |
70
- | `SWEATSTACK_REFRESH_TOKEN` | Refresh token | — |
71
-
72
- ## Token Storage
73
-
74
- Credentials are stored securely in your OS user data directory:
75
-
76
- - **macOS**: `~/Library/Application Support/SweatStack/SweatStack/tokens.json`
77
- - **Linux**: `~/.local/share/SweatStack/SweatStack/tokens.json`
78
- - **Windows**: `%APPDATA%\SweatStack\SweatStack\tokens.json`
79
-
80
- This location is shared with the [sweatstack Python library](https://github.com/sweatstack/sweatstack-python), so authenticating with either tool works for both.
81
-
82
- ## Development
83
-
84
- See [DEVELOPMENT.md](DEVELOPMENT.md) for development setup and contribution guidelines.
85
-
86
- ```bash
87
- # Install with dev dependencies
88
- uv pip install -e ".[dev]"
89
-
90
- # Run tests
91
- pytest
92
-
93
- # Lint and format
94
- ruff check . && ruff format .
95
-
96
- # Type check
97
- mypy src/sweatstack_cli
98
- ```
99
-
100
- ## License
101
-
102
- MIT
File without changes
File without changes