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.
Files changed (53) hide show
  1. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/.claude/settings.local.json +2 -1
  2. sweatstack_cli-0.7.0/.claude/skills/sweatstack/SKILL.md +24 -0
  3. sweatstack_cli-0.7.0/.claude/skills/sweatstack/api.md +170 -0
  4. sweatstack_cli-0.7.0/.claude/skills/sweatstack/auth.md +136 -0
  5. sweatstack_cli-0.7.0/.claude/skills/sweatstack/cli.md +70 -0
  6. sweatstack_cli-0.7.0/.claude/skills/sweatstack/pyscript.md +33 -0
  7. sweatstack_cli-0.7.0/.claude/skills/sweatstack/webapp.md +138 -0
  8. sweatstack_cli-0.7.0/PKG-INFO +182 -0
  9. sweatstack_cli-0.7.0/README.md +152 -0
  10. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/pyproject.toml +3 -3
  11. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/__init__.py +1 -1
  12. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/commands/app.py +91 -5
  13. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/commands/page.py +43 -11
  14. sweatstack_cli-0.7.0/src/sweatstack_cli/project.py +111 -0
  15. sweatstack_cli-0.7.0/sweatstack.toml +3 -0
  16. sweatstack_cli-0.7.0/tests/test_project.py +110 -0
  17. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/uv.lock +2 -102
  18. sweatstack_cli-0.5.0/PKG-INFO +0 -132
  19. sweatstack_cli-0.5.0/README.md +0 -102
  20. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/.gitignore +0 -0
  21. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/.python-version +0 -0
  22. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/CREATE_PRIVATE_APP.md +0 -0
  23. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/DEVELOPMENT.md +0 -0
  24. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/Makefile +0 -0
  25. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/PLAN.md +0 -0
  26. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/api/__init__.py +0 -0
  27. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/api/client.py +0 -0
  28. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/auth/__init__.py +0 -0
  29. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/auth/callback_server.py +0 -0
  30. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/auth/jwt.py +0 -0
  31. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/auth/pkce.py +0 -0
  32. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/auth/tokens.py +0 -0
  33. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/commands/__init__.py +0 -0
  34. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/commands/auth.py +0 -0
  35. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/config.py +0 -0
  36. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/console.py +0 -0
  37. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/exceptions.py +0 -0
  38. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/main.py +0 -0
  39. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/py.typed +0 -0
  40. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/src/sweatstack_cli/version_check.py +0 -0
  41. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/tests/__init__.py +0 -0
  42. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/tests/conftest.py +0 -0
  43. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/tests/test_api/__init__.py +0 -0
  44. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/tests/test_api/test_client.py +0 -0
  45. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/tests/test_auth/__init__.py +0 -0
  46. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/tests/test_auth/test_callback_server.py +0 -0
  47. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/tests/test_auth/test_jwt.py +0 -0
  48. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/tests/test_auth/test_pkce.py +0 -0
  49. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/tests/test_auth/test_tokens.py +0 -0
  50. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/tests/test_commands/__init__.py +0 -0
  51. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/tests/test_commands/test_app.py +0 -0
  52. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/tests/test_commands/test_cli.py +0 -0
  53. {sweatstack_cli-0.5.0 → sweatstack_cli-0.7.0}/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