sweatstack-cli 0.5.0__tar.gz → 0.7.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.5.0 → sweatstack_cli-0.7.0}/.claude/settings.local.json +2 -1
- sweatstack_cli-0.7.0/.claude/skills/sweatstack/SKILL.md +24 -0
- sweatstack_cli-0.7.0/.claude/skills/sweatstack/api.md +170 -0
- sweatstack_cli-0.7.0/.claude/skills/sweatstack/auth.md +136 -0
- sweatstack_cli-0.7.0/.claude/skills/sweatstack/cli.md +70 -0
- sweatstack_cli-0.7.0/.claude/skills/sweatstack/pyscript.md +33 -0
- sweatstack_cli-0.7.0/.claude/skills/sweatstack/webapp.md +138 -0
- sweatstack_cli-0.7.0/PKG-INFO +182 -0
- sweatstack_cli-0.7.0/README.md +152 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/pyproject.toml +3 -3
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/__init__.py +1 -1
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/commands/app.py +91 -5
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/commands/page.py +43 -11
- sweatstack_cli-0.7.0/src/sweatstack_cli/project.py +111 -0
- sweatstack_cli-0.7.0/sweatstack.toml +3 -0
- sweatstack_cli-0.7.0/tests/test_project.py +110 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/uv.lock +2 -102
- sweatstack_cli-0.5.0/PKG-INFO +0 -132
- sweatstack_cli-0.5.0/README.md +0 -102
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/.gitignore +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/.python-version +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/CREATE_PRIVATE_APP.md +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/DEVELOPMENT.md +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/Makefile +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/PLAN.md +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/api/__init__.py +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/api/client.py +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/auth/__init__.py +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/auth/callback_server.py +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/auth/jwt.py +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/auth/pkce.py +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/auth/tokens.py +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/commands/__init__.py +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/commands/auth.py +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/config.py +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/console.py +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/exceptions.py +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/main.py +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/py.typed +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/version_check.py +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/tests/__init__.py +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/tests/conftest.py +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/tests/test_api/__init__.py +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/tests/test_api/test_client.py +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/tests/test_auth/__init__.py +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/tests/test_auth/test_callback_server.py +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/tests/test_auth/test_jwt.py +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/tests/test_auth/test_pkce.py +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/tests/test_auth/test_tokens.py +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/tests/test_commands/__init__.py +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/tests/test_commands/test_app.py +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/tests/test_commands/test_cli.py +0 -0
- {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/tests/test_version_check.py +0 -0
|
@@ -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
|