@lightupai/polaris 0.0.57 → 0.0.59

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.
package/Makefile CHANGED
@@ -1,4 +1,4 @@
1
- .PHONY: dev dev-up dev-down api web daemon bridge test clean prod
1
+ .PHONY: dev dev-up dev-down api web daemon bridge test perf seo css css-watch clean prod
2
2
 
3
3
  # Load .env if it exists
4
4
  ifneq (,$(wildcard .env))
@@ -6,8 +6,15 @@ ifneq (,$(wildcard .env))
6
6
  export
7
7
  endif
8
8
 
9
+ # Build purged Tailwind CSS
10
+ css:
11
+ @npx bun x tailwindcss -i src/web/styles/input.css -o src/web/styles/output.css --minify
12
+
13
+ css-watch:
14
+ @npx bun x tailwindcss -i src/web/styles/input.css -o src/web/styles/output.css --watch
15
+
9
16
  # Start everything for local development
10
- dev: dev-up api web daemon bridge
17
+ dev: dev-up css api web daemon bridge
11
18
 
12
19
  # Postgres
13
20
  dev-up:
@@ -62,6 +69,18 @@ prod:
62
69
  test:
63
70
  npx bun test
64
71
 
72
+ # Lighthouse performance audit against production and local
73
+ perf:
74
+ @prod_failed=0; \
75
+ npx bun run scripts/perf-audit.ts https://app.withpolaris.ai || prod_failed=1; \
76
+ npx bun run scripts/perf-audit.ts local || exit 1; \
77
+ if [ "$$prod_failed" = "1" ]; then exit 1; fi
78
+
79
+ # DataForSEO on-page SEO audit against production
80
+ SEO_URL ?= https://app.withpolaris.ai
81
+ seo:
82
+ @npx bun run scripts/seo-audit.ts $(SEO_URL)
83
+
65
84
  # Stop all background processes, tunnels, and Postgres
66
85
  clean:
67
86
  @lsof -ti :4321 | xargs kill -9 2>/dev/null || true
package/README.md CHANGED
@@ -202,6 +202,28 @@ tests/ Test suite (bun test)
202
202
  - [ ] Update available indicator — daemon periodically checks npm for newer version, caches the result. Status line shows "update available" when stale. `polaris update` command installs the latest version and rewrites skill/hooks.
203
203
  - [ ] Slack channel name collision — if a channel name was previously deleted, Slack reserves it. Bridge should handle `name_taken` by trying a prefix/suffix (e.g., `p-project-name`)
204
204
 
205
+ ## Testing
206
+
207
+ ```sh
208
+ # Unit tests (uses polaris_test database)
209
+ make test
210
+
211
+ # Lighthouse performance audit against production
212
+ # Runs mobile + desktop, checks budgets (score >= 90, FCP <= 1.8s, LCP <= 2.5s)
213
+ # Saves results to docs/audits/perf-audit-YYYY-MM-DD.json
214
+ make perf
215
+
216
+ # DataForSEO on-page SEO audit against production
217
+ # Checks meta tags, headings, social tags, content rate, technical SEO
218
+ # Saves results to docs/audits/seo-audit-YYYY-MM-DD.json
219
+ # Requires DataForSEO API credentials (see scripts/seo-audit.ts)
220
+ make seo
221
+ ```
222
+
223
+ All three targets exit non-zero on failure. `make perf` and `make seo` run against the live production site (`app.withpolaris.ai`) by default. Override with `make perf PERF_URL=http://localhost:3000` or `make seo SEO_URL=http://localhost:3000`.
224
+
225
+ Audit results are saved as JSON in `docs/audits/` for historical tracking.
226
+
205
227
  ## Development
206
228
 
207
229
  Services run as background processes. Logs go to `/tmp/polaris-*.log`. The Makefile's `clean` target kills all processes and stops Postgres.
package/bun.lock CHANGED
@@ -15,13 +15,25 @@
15
15
  "zod": "^3.23.0",
16
16
  },
17
17
  "devDependencies": {
18
+ "@tailwindcss/cli": "^4.3.1",
18
19
  "@types/bun": "latest",
20
+ "tailwindcss": "^4.3.1",
19
21
  },
20
22
  },
21
23
  },
22
24
  "packages": {
23
25
  "@hono/node-server": ["@hono/node-server@1.19.14", "", { "peerDependencies": { "hono": "^4" } }, "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw=="],
24
26
 
27
+ "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
28
+
29
+ "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
30
+
31
+ "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
32
+
33
+ "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
34
+
35
+ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
36
+
25
37
  "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="],
26
38
 
27
39
  "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
@@ -34,6 +46,34 @@
34
46
 
35
47
  "@oslojs/jwt": ["@oslojs/jwt@0.2.0", "", { "dependencies": { "@oslojs/encoding": "0.4.1" } }, "sha512-bLE7BtHrURedCn4Mco3ma9L4Y1GR2SMBuIvjWr7rmQ4/W/4Jy70TIAgZ+0nIlk0xHz1vNP8x8DCns45Sb2XRbg=="],
36
48
 
49
+ "@parcel/watcher": ["@parcel/watcher@2.5.1", "", { "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", "micromatch": "^4.0.5", "node-addon-api": "^7.0.0" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.1", "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-darwin-x64": "2.5.1", "@parcel/watcher-freebsd-x64": "2.5.1", "@parcel/watcher-linux-arm-glibc": "2.5.1", "@parcel/watcher-linux-arm-musl": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", "@parcel/watcher-linux-arm64-musl": "2.5.1", "@parcel/watcher-linux-x64-glibc": "2.5.1", "@parcel/watcher-linux-x64-musl": "2.5.1", "@parcel/watcher-win32-arm64": "2.5.1", "@parcel/watcher-win32-ia32": "2.5.1", "@parcel/watcher-win32-x64": "2.5.1" } }, "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg=="],
50
+
51
+ "@parcel/watcher-android-arm64": ["@parcel/watcher-android-arm64@2.5.1", "", { "os": "android", "cpu": "arm64" }, "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA=="],
52
+
53
+ "@parcel/watcher-darwin-arm64": ["@parcel/watcher-darwin-arm64@2.5.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw=="],
54
+
55
+ "@parcel/watcher-darwin-x64": ["@parcel/watcher-darwin-x64@2.5.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg=="],
56
+
57
+ "@parcel/watcher-freebsd-x64": ["@parcel/watcher-freebsd-x64@2.5.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ=="],
58
+
59
+ "@parcel/watcher-linux-arm-glibc": ["@parcel/watcher-linux-arm-glibc@2.5.1", "", { "os": "linux", "cpu": "arm" }, "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA=="],
60
+
61
+ "@parcel/watcher-linux-arm-musl": ["@parcel/watcher-linux-arm-musl@2.5.1", "", { "os": "linux", "cpu": "arm" }, "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q=="],
62
+
63
+ "@parcel/watcher-linux-arm64-glibc": ["@parcel/watcher-linux-arm64-glibc@2.5.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w=="],
64
+
65
+ "@parcel/watcher-linux-arm64-musl": ["@parcel/watcher-linux-arm64-musl@2.5.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg=="],
66
+
67
+ "@parcel/watcher-linux-x64-glibc": ["@parcel/watcher-linux-x64-glibc@2.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A=="],
68
+
69
+ "@parcel/watcher-linux-x64-musl": ["@parcel/watcher-linux-x64-musl@2.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg=="],
70
+
71
+ "@parcel/watcher-win32-arm64": ["@parcel/watcher-win32-arm64@2.5.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw=="],
72
+
73
+ "@parcel/watcher-win32-ia32": ["@parcel/watcher-win32-ia32@2.5.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ=="],
74
+
75
+ "@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.1", "", { "os": "win32", "cpu": "x64" }, "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA=="],
76
+
37
77
  "@slack/logger": ["@slack/logger@4.0.1", "", { "dependencies": { "@types/node": ">=18" } }, "sha512-6cmdPrV/RYfd2U0mDGiMK8S7OJqpCTm7enMLRR3edccsPX8j7zXTLnaEF4fhxxJJTAIOil6+qZrnUPTuaLvwrQ=="],
38
78
 
39
79
  "@slack/socket-mode": ["@slack/socket-mode@2.0.7", "", { "dependencies": { "@slack/logger": "^4.0.1", "@slack/web-api": "^7.15.0", "@types/node": ">=18", "@types/ws": "^8", "eventemitter3": "^5", "ws": "^8" } }, "sha512-qYy07je71WnEHgRwmw12DlAnZLi5HXmdlI2WUzUK2LH/rYXQpP6uEg462S5CwfE8FoCKUdIigHtYnOOfzZH1lQ=="],
@@ -42,6 +82,36 @@
42
82
 
43
83
  "@slack/web-api": ["@slack/web-api@7.16.0", "", { "dependencies": { "@slack/logger": "^4.0.1", "@slack/types": "^2.21.0", "@types/node": ">=18", "@types/retry": "0.12.0", "axios": "^1.16.0", "eventemitter3": "^5.0.1", "form-data": "^4.0.4", "is-electron": "2.2.2", "is-stream": "^2", "p-queue": "^6", "p-retry": "^4", "retry": "^0.13.1" } }, "sha512-68SAV77uuGKuhyyaRytX8UijVnqSLsTSKslGXw17cjQYXn+jtNl7gbaEjHgC5x2rhCuFdahBrEC2VCLppbzReg=="],
44
84
 
85
+ "@tailwindcss/cli": ["@tailwindcss/cli@4.3.1", "", { "dependencies": { "@parcel/watcher": "2.5.1", "@tailwindcss/node": "4.3.1", "@tailwindcss/oxide": "4.3.1", "enhanced-resolve": "5.21.6", "mri": "^1.2.0", "picocolors": "^1.1.1", "tailwindcss": "4.3.1" }, "bin": { "tailwindcss": "dist/index.mjs" } }, "sha512-ZWPy20rF+TBfTImxDMG3Wr75Y3RpaPlo9lc+oJbInlMyjT+XPkTVKVIL5RZ7JirXuIahcfHoLNFRmDorKi+JQQ=="],
86
+
87
+ "@tailwindcss/node": ["@tailwindcss/node@4.3.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "5.21.6", "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.3.1" } }, "sha512-6NDaqRoAMSXD1mr/RXu0HBvNE9a2n5tHPsxu9XHLws8o4Twes5rBM2205SUUiJ9goAtadrN6xTGX0UDEwp/N4A=="],
88
+
89
+ "@tailwindcss/oxide": ["@tailwindcss/oxide@4.3.1", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.3.1", "@tailwindcss/oxide-darwin-arm64": "4.3.1", "@tailwindcss/oxide-darwin-x64": "4.3.1", "@tailwindcss/oxide-freebsd-x64": "4.3.1", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.1", "@tailwindcss/oxide-linux-arm64-gnu": "4.3.1", "@tailwindcss/oxide-linux-arm64-musl": "4.3.1", "@tailwindcss/oxide-linux-x64-gnu": "4.3.1", "@tailwindcss/oxide-linux-x64-musl": "4.3.1", "@tailwindcss/oxide-wasm32-wasi": "4.3.1", "@tailwindcss/oxide-win32-arm64-msvc": "4.3.1", "@tailwindcss/oxide-win32-x64-msvc": "4.3.1" } }, "sha512-yVPyo8RNkabVr3O2EhHEE0Rewu7YKzc1DhIqfL46LKveFrmu9XbDazNOJY7/GRuvw1h6u3utWnR29H/p5JPlgA=="],
90
+
91
+ "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.3.1", "", { "os": "android", "cpu": "arm64" }, "sha512-SVlyf61g374l5cHyg8x9kf5xmLcOaxvOTsbsqDnSsDJaKOEFZ7GCvi84VAVGpxojYOs1+3K6M0UjXfqPU8vmOQ=="],
92
+
93
+ "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.3.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-hVnWLwv+e/l7c4WKyVtHVrIPvYdqWHjRB3MDIqARynzFtnQg85kmQEFCbV9Ja0VVx4xXTIiDWY60Y7iz/iNoDA=="],
94
+
95
+ "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.3.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Cf7abu0WVgbhU7ANgPUnSAvm7nCvMweusHb8FnaHlLfv/Caq4GYaEZg7ZImzzmjx4lIAfuS8q+eLIS7A7IzxIg=="],
96
+
97
+ "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.3.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-ZZqzX2Y+GXtXXfqSfpJhDm60OoZfvLHLCgm+J7NVqgHHJjG/m9ugZI77RwTsVd4fnBJuCFP6Ae6kTJb71UdS8g=="],
98
+
99
+ "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.3.1", "", { "os": "linux", "cpu": "arm" }, "sha512-/Ah/xik0LaMYfv9DZ0S/t4pBlBNYOcqtRwusjgovHkvT8ixueWCLyJjsaF5kQIckjb4IT8Q6K6p/iPmZMixYgg=="],
100
+
101
+ "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.3.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-gqdFoVJlw444GvpnheZLHmvTzSxI/cOUUh2KSNejQjTcYkW062SVD+En0rUgD+QV91bz1XGIGtt1HJd48xUGbQ=="],
102
+
103
+ "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.3.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Bwv9KwOvE0VKa86xPFif9b9c3Y1NxOV1P0gLti/IYaWEsQYZXDlxfGEtA8mdDZ7SG3wyNXAWYT5SIn3giL57oA=="],
104
+
105
+ "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.3.1", "", { "os": "linux", "cpu": "x64" }, "sha512-Ymi8O8T15HYQdOUWUtTI6ldN0neHP85FC+Qz32xTcZ7iJXtem/x8ITev0o1e9e5rkqj4lONZfTRLvkmin1+tKg=="],
106
+
107
+ "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.3.1", "", { "os": "linux", "cpu": "x64" }, "sha512-M+P/91qJ6uILLw4k2G93GMDRAXj61SMvFQYt39AqvUqYgExXpLL5aepfns7sj4HiAQeolirQF9E0lzRvdf4zPQ=="],
108
+
109
+ "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.3.1", "", { "dependencies": { "@emnapi/core": "^1.10.0", "@emnapi/runtime": "^1.10.0", "@emnapi/wasi-threads": "^1.2.1", "@napi-rs/wasm-runtime": "^1.1.4", "@tybys/wasm-util": "^0.10.2", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-zsM8uOeqvVGHsAXsJxsT28ttosFahLJKCLOTUBqRAtKnVgGSRitds9T432QiT8b77Yga7JIBkulIRRlJPtYhRA=="],
110
+
111
+ "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.3.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-aiNvSq9BsVk8V513lDKlrCFAgf8qBMPZTpgEhInL+NwQqs97mYmupVMrPrgBBSL8Pv/0zXu9MrMF9rMun1ZeNg=="],
112
+
113
+ "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.3.1", "", { "os": "win32", "cpu": "x64" }, "sha512-xDEyu1rg290472FEGaKHnzyDyh5QH+AlWvsU5hMoMtPpzmKlRI0jaYKCgSHDYtaQWZOYbMaduSyCwFwY4n1HmA=="],
114
+
45
115
  "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
46
116
 
47
117
  "@types/node": ["@types/node@25.9.1", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg=="],
@@ -66,6 +136,8 @@
66
136
 
67
137
  "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
68
138
 
139
+ "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
140
+
69
141
  "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
70
142
 
71
143
  "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
@@ -94,12 +166,16 @@
94
166
 
95
167
  "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
96
168
 
169
+ "detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
170
+
97
171
  "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
98
172
 
99
173
  "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
100
174
 
101
175
  "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
102
176
 
177
+ "enhanced-resolve": ["enhanced-resolve@5.21.6", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ=="],
178
+
103
179
  "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
104
180
 
105
181
  "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
@@ -126,6 +202,8 @@
126
202
 
127
203
  "fast-uri": ["fast-uri@3.1.2", "", {}, "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ=="],
128
204
 
205
+ "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
206
+
129
207
  "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
130
208
 
131
209
  "follow-redirects": ["follow-redirects@1.16.0", "", {}, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="],
@@ -144,6 +222,8 @@
144
222
 
145
223
  "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
146
224
 
225
+ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
226
+
147
227
  "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
148
228
 
149
229
  "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
@@ -166,32 +246,72 @@
166
246
 
167
247
  "is-electron": ["is-electron@2.2.2", "", {}, "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg=="],
168
248
 
249
+ "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
250
+
251
+ "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
252
+
253
+ "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
254
+
169
255
  "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
170
256
 
171
257
  "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
172
258
 
173
259
  "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
174
260
 
261
+ "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
262
+
175
263
  "jose": ["jose@6.2.3", "", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="],
176
264
 
177
265
  "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
178
266
 
179
267
  "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="],
180
268
 
269
+ "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
270
+
271
+ "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
272
+
273
+ "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
274
+
275
+ "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
276
+
277
+ "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
278
+
279
+ "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
280
+
281
+ "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
282
+
283
+ "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
284
+
285
+ "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
286
+
287
+ "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
288
+
289
+ "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
290
+
291
+ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
292
+
293
+ "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
294
+
181
295
  "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
182
296
 
183
297
  "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
184
298
 
185
299
  "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
186
300
 
301
+ "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
302
+
187
303
  "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
188
304
 
189
305
  "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
190
306
 
307
+ "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
308
+
191
309
  "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
192
310
 
193
311
  "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
194
312
 
313
+ "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
314
+
195
315
  "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
196
316
 
197
317
  "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
@@ -214,6 +334,10 @@
214
334
 
215
335
  "path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="],
216
336
 
337
+ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
338
+
339
+ "picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
340
+
217
341
  "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="],
218
342
 
219
343
  "postgres": ["postgres@3.4.9", "", {}, "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw=="],
@@ -254,8 +378,16 @@
254
378
 
255
379
  "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
256
380
 
381
+ "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
382
+
257
383
  "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
258
384
 
385
+ "tailwindcss": ["tailwindcss@4.3.1", "", {}, "sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q=="],
386
+
387
+ "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="],
388
+
389
+ "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
390
+
259
391
  "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
260
392
 
261
393
  "type-is": ["type-is@2.1.0", "", { "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA=="],
@@ -278,8 +410,22 @@
278
410
 
279
411
  "@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="],
280
412
 
413
+ "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="],
414
+
415
+ "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="],
416
+
417
+ "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="],
418
+
419
+ "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.5", "", { "dependencies": { "@tybys/wasm-util": "^0.10.2" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q=="],
420
+
421
+ "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="],
422
+
423
+ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
424
+
281
425
  "form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
282
426
 
427
+ "lightningcss/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
428
+
283
429
  "p-queue/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
284
430
 
285
431
  "type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
package/docker/Dockerfile CHANGED
@@ -9,5 +9,8 @@ RUN bun install --frozen-lockfile
9
9
  COPY src/ ./src/
10
10
  COPY docker/bridge-entrypoint.sh ./docker/
11
11
 
12
+ # Build Tailwind CSS
13
+ RUN bunx tailwindcss -i src/web/styles/input.css -o src/web/styles/output.css --minify
14
+
12
15
  # Default: API server
13
16
  CMD ["bun", "run", "src/service/server.ts"]
@@ -0,0 +1,22 @@
1
+ {
2
+ "url": "http://localhost:3000",
3
+ "timestamp": "2026-06-18T18:13:42.438Z",
4
+ "mobile": {
5
+ "score": 100,
6
+ "fcp": 1052.0306,
7
+ "lcp": 1277.0306,
8
+ "tbt": 0,
9
+ "cls": 0,
10
+ "si": 1052.0306,
11
+ "weight": 77990
12
+ },
13
+ "desktop": {
14
+ "score": 100,
15
+ "fcp": 281.5984,
16
+ "lcp": 321.5984,
17
+ "tbt": 0,
18
+ "cls": 0,
19
+ "si": 281.5984,
20
+ "weight": 77990
21
+ }
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "url": "https://app.withpolaris.ai",
3
+ "timestamp": "2026-06-18T17:52:26.077Z",
4
+ "mobile": {
5
+ "score": 84,
6
+ "fcp": 3411.0824999999995,
7
+ "lcp": 3411.0824999999995,
8
+ "tbt": 0,
9
+ "cls": 0,
10
+ "si": 3411.0824999999995,
11
+ "weight": 179565
12
+ },
13
+ "desktop": {
14
+ "score": 91,
15
+ "fcp": 1405.5195,
16
+ "lcp": 1405.5195,
17
+ "tbt": 0,
18
+ "cls": 0,
19
+ "si": 1405.5195,
20
+ "weight": 179543
21
+ }
22
+ }
@@ -0,0 +1,189 @@
1
+ {
2
+ "resource_type": "html",
3
+ "status_code": 200,
4
+ "location": null,
5
+ "url": "https://app.withpolaris.ai/",
6
+ "meta": {
7
+ "title": "Polaris — It's like Gong for Claude Code sessions",
8
+ "charset": 65001,
9
+ "follow": true,
10
+ "generator": null,
11
+ "htags": {
12
+ "h1": [
13
+ "Meet Polaris.\nIt's like Gong for Claude Code sessions."
14
+ ],
15
+ "h2": [
16
+ "One channel, every session",
17
+ "What you can do with Polaris",
18
+ "Why Polaris",
19
+ "The vision",
20
+ "Pricing",
21
+ "How it works",
22
+ "Ready to try it?"
23
+ ],
24
+ "h3": [
25
+ "Start streaming your session",
26
+ "Pull a teammate into your session",
27
+ "Catch up on a teammate's session",
28
+ "Redirect an agent from Slack",
29
+ "Attach session context to a PR",
30
+ "Search past sessions",
31
+ "AI sessions are invisible",
32
+ "Knowledge evaporates",
33
+ "Feedback comes too late",
34
+ "Multiple agents, zero shared context",
35
+ "Learning on the Shop Floor",
36
+ "Free",
37
+ "Pro",
38
+ "Team",
39
+ "Enterprise",
40
+ "Connect",
41
+ "Install",
42
+ "Collaborate"
43
+ ]
44
+ },
45
+ "description": "Capture every AI coding session. Stream prompts, responses, and tool calls to Slack in real time. Collaborate across agents. Nothing is lost.",
46
+ "favicon": null,
47
+ "meta_keywords": null,
48
+ "canonical": "https://app.withpolaris.ai/",
49
+ "internal_links_count": 7,
50
+ "external_links_count": 2,
51
+ "inbound_links_count": 0,
52
+ "images_count": 0,
53
+ "images_size": 0,
54
+ "scripts_count": 1,
55
+ "scripts_size": 0,
56
+ "stylesheets_count": 0,
57
+ "stylesheets_size": 0,
58
+ "title_length": 49,
59
+ "description_length": 141,
60
+ "render_blocking_scripts_count": 1,
61
+ "render_blocking_stylesheets_count": 0,
62
+ "cumulative_layout_shift": 0,
63
+ "meta_title": null,
64
+ "content": {
65
+ "plain_text_size": 5565,
66
+ "plain_text_rate": 0.0824627695043343,
67
+ "plain_text_word_count": 909,
68
+ "automated_readability_index": 8.19262715768782,
69
+ "coleman_liau_readability_index": 11.131434977578476,
70
+ "dale_chall_readability_index": 8.925914282204067,
71
+ "flesch_kincaid_readability_index": 55.14824037103017,
72
+ "smog_readability_index": 11.235764001479073,
73
+ "description_to_content_consistency": 1,
74
+ "title_to_content_consistency": 1,
75
+ "meta_keywords_to_content_consistency": null
76
+ },
77
+ "deprecated_tags": null,
78
+ "duplicate_meta_tags": null,
79
+ "spell": null,
80
+ "social_media_tags": {
81
+ "og:type": "website",
82
+ "og:title": "Polaris — It's like Gong for Claude Code sessions",
83
+ "og:description": "Capture every AI coding session. Stream prompts, responses, and tool calls to Slack in real time. Collaborate across agents. Nothing is lost.",
84
+ "og:image": "https://app.withpolaris.ai/og-image.png",
85
+ "og:url": "https://app.withpolaris.ai",
86
+ "twitter:card": "summary_large_image",
87
+ "twitter:title": "Polaris — It's like Gong for Claude Code sessions",
88
+ "twitter:description": "Capture every AI coding session. Stream prompts, responses, and tool calls to Slack in real time. Collaborate across agents. Nothing is lost.",
89
+ "twitter:image": "https://app.withpolaris.ai/og-image.png"
90
+ }
91
+ },
92
+ "page_timing": {
93
+ "time_to_interactive": 966,
94
+ "dom_complete": 966,
95
+ "largest_contentful_paint": 0,
96
+ "first_input_delay": 0,
97
+ "connection_time": 28,
98
+ "time_to_secure_connection": 16,
99
+ "request_sent_time": 0,
100
+ "waiting_time": 18,
101
+ "download_time": 9,
102
+ "duration_time": 967,
103
+ "fetch_start": 0,
104
+ "fetch_end": 967
105
+ },
106
+ "onpage_score": 100,
107
+ "total_dom_size": 67534,
108
+ "custom_js_response": null,
109
+ "custom_js_client_exception": null,
110
+ "resource_errors": {
111
+ "errors": null,
112
+ "warnings": null
113
+ },
114
+ "broken_resources": false,
115
+ "broken_links": false,
116
+ "duplicate_title": false,
117
+ "duplicate_description": false,
118
+ "duplicate_content": false,
119
+ "click_depth": 0,
120
+ "size": 67534,
121
+ "encoded_size": 51810,
122
+ "total_transfer_size": 51810,
123
+ "fetch_time": "2026-06-18 17:46:26 +00:00",
124
+ "cache_control": {
125
+ "cachable": false,
126
+ "ttl": 0
127
+ },
128
+ "checks": {
129
+ "no_content_encoding": true,
130
+ "high_loading_time": false,
131
+ "from_sitemap": false,
132
+ "is_redirect": false,
133
+ "is_4xx_code": false,
134
+ "is_5xx_code": false,
135
+ "is_broken": false,
136
+ "is_www": false,
137
+ "is_https": true,
138
+ "is_http": false,
139
+ "high_waiting_time": false,
140
+ "has_micromarkup": false,
141
+ "has_micromarkup_errors": false,
142
+ "no_doctype": false,
143
+ "has_html_doctype": true,
144
+ "canonical": true,
145
+ "no_encoding_meta_tag": false,
146
+ "no_h1_tag": false,
147
+ "https_to_http_links": false,
148
+ "size_greater_than_3mb": false,
149
+ "meta_charset_consistency": true,
150
+ "has_meta_refresh_redirect": false,
151
+ "has_render_blocking_resources": true,
152
+ "low_content_rate": true,
153
+ "high_content_rate": false,
154
+ "low_character_count": false,
155
+ "high_character_count": false,
156
+ "small_page_size": false,
157
+ "large_page_size": false,
158
+ "low_readability_rate": false,
159
+ "irrelevant_description": false,
160
+ "irrelevant_title": false,
161
+ "irrelevant_meta_keywords": false,
162
+ "title_too_long": false,
163
+ "has_meta_title": false,
164
+ "title_too_short": false,
165
+ "deprecated_html_tags": false,
166
+ "duplicate_meta_tags": false,
167
+ "duplicate_title_tag": false,
168
+ "no_image_alt": false,
169
+ "no_image_title": false,
170
+ "no_description": false,
171
+ "no_title": false,
172
+ "no_favicon": true,
173
+ "seo_friendly_url": true,
174
+ "flash": false,
175
+ "frame": false,
176
+ "lorem_ipsum": false,
177
+ "seo_friendly_url_characters_check": true,
178
+ "seo_friendly_url_dynamic_check": true,
179
+ "seo_friendly_url_keywords_check": true,
180
+ "seo_friendly_url_relative_length_check": true
181
+ },
182
+ "content_encoding": null,
183
+ "media_type": "text/html",
184
+ "server": null,
185
+ "is_resource": false,
186
+ "url_length": 27,
187
+ "relative_url_length": 1,
188
+ "last_modified": null
189
+ }
@@ -0,0 +1,42 @@
1
+ # Lighthouse Baseline — 2026-06-18
2
+
3
+ Captured before self-hosting Tailwind CSS (currently loaded via CDN).
4
+
5
+ ## Mobile Performance
6
+
7
+ | Metric | Value | Score |
8
+ |--------------------------|-------|--------------|
9
+ | **Performance** | — | **85/100** |
10
+ | First Contentful Paint | 3.3s | 0.39 (poor) |
11
+ | Largest Contentful Paint | 3.3s | 0.69 (needs work) |
12
+ | Total Blocking Time | 0ms | 1.0 (perfect) |
13
+ | Cumulative Layout Shift | 0 | 1.0 (perfect) |
14
+ | Speed Index | 3.6s | 0.87 (good) |
15
+
16
+ ## Desktop Performance
17
+
18
+ | Metric | Value | Score |
19
+ |--------------------------|-------|--------------|
20
+ | **Performance** | — | **90/100** |
21
+ | First Contentful Paint | 1.4s | 0.61 (needs work) |
22
+ | Largest Contentful Paint | 1.4s | 0.83 (good) |
23
+ | Total Blocking Time | 0ms | 1.0 (perfect) |
24
+ | Cumulative Layout Shift | 0 | 1.0 (perfect) |
25
+ | Speed Index | 1.4s | 0.86 (good) |
26
+
27
+ ## Page Weight Breakdown
28
+
29
+ | Resource | Size | % of Total |
30
+ |-------------------------------|-----------|------------|
31
+ | `cdn.tailwindcss.com/3.4.17` | 127 KB | 72% |
32
+ | HTML document | 52 KB | 28% |
33
+ | **Total** | **175 KB**| 100% |
34
+
35
+ Total network requests: 5
36
+
37
+ ## Key Finding
38
+
39
+ The Tailwind CDN script (127 KB) is the single largest resource and the
40
+ direct cause of the 3.3s FCP — the browser cannot paint until the script
41
+ downloads and executes. Switching to purged, self-hosted CSS should reduce
42
+ this to ~10 KB of static CSS.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightupai/polaris",
3
- "version": "0.0.57",
3
+ "version": "0.0.59",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "polaris": "bin/polaris",
@@ -20,6 +20,8 @@
20
20
  "zod": "^3.23.0"
21
21
  },
22
22
  "devDependencies": {
23
- "@types/bun": "latest"
23
+ "@tailwindcss/cli": "^4.3.1",
24
+ "@types/bun": "latest",
25
+ "tailwindcss": "^4.3.1"
24
26
  }
25
27
  }
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Lighthouse performance audit.
3
+ *
4
+ * Usage:
5
+ * bun run scripts/perf-audit.ts [url]
6
+ *
7
+ * If url is http://localhost:*, builds CSS, starts the local web server,
8
+ * runs the audit, then stops the server.
9
+ *
10
+ * Runs mobile + desktop Lighthouse audits, prints a formatted report,
11
+ * checks performance budgets, and saves raw JSON to docs/audits/.
12
+ */
13
+
14
+ import { mkdirSync, writeFileSync } from "fs";
15
+ import { execSync } from "child_process";
16
+ import { join } from "path";
17
+
18
+ let url = process.argv[2] ?? "https://app.withpolaris.ai";
19
+ const isLocal = url === "local";
20
+ let localServerPid: number | undefined;
21
+
22
+ // Find a free port by binding to :0 and releasing
23
+ async function freePort(): Promise<number> {
24
+ const server = Bun.serve({ port: 0, fetch: () => new Response() });
25
+ const port = server.port;
26
+ server.stop(true);
27
+ return port;
28
+ }
29
+
30
+ if (isLocal) {
31
+ // Build CSS and start local web server on an ephemeral port
32
+ console.log("");
33
+ console.log(" Building CSS and starting local server ...");
34
+ execSync("npx bun x tailwindcss -i src/web/styles/input.css -o src/web/styles/output.css --minify", { stdio: "pipe" });
35
+
36
+ const port = await freePort();
37
+ url = `http://localhost:${port}`;
38
+
39
+ const proc = Bun.spawn(["bun", "run", "src/web/serve.ts"], {
40
+ env: { ...process.env, WEB_PORT: String(port), DATABASE_URL: process.env.DATABASE_URL ?? "postgres://polaris:polaris@localhost:5432/polaris" },
41
+ stdout: "pipe",
42
+ stderr: "pipe",
43
+ });
44
+ localServerPid = proc.pid;
45
+
46
+ // Wait for server to be ready
47
+ for (let i = 0; i < 30; i++) {
48
+ try {
49
+ await fetch(url);
50
+ break;
51
+ } catch {
52
+ await new Promise((r) => setTimeout(r, 1000));
53
+ }
54
+ }
55
+ }
56
+
57
+ console.log("");
58
+ console.log(` Running Lighthouse against ${url} ...`);
59
+
60
+ // --- Run Lighthouse ---
61
+
62
+ const tmpMobile = "/tmp/lighthouse-mobile.json";
63
+ const tmpDesktop = "/tmp/lighthouse-desktop.json";
64
+
65
+ execSync(
66
+ `npx --yes lighthouse ${url} --only-categories=performance --output=json --output-path=${tmpMobile} --chrome-flags="--headless --no-sandbox" 2>/dev/null`,
67
+ { stdio: "pipe" },
68
+ );
69
+
70
+ execSync(
71
+ `npx lighthouse ${url} --only-categories=performance --preset=desktop --output=json --output-path=${tmpDesktop} --chrome-flags="--headless --no-sandbox" 2>/dev/null`,
72
+ { stdio: "pipe" },
73
+ );
74
+
75
+ const m = JSON.parse(require("fs").readFileSync(tmpMobile, "utf-8"));
76
+ const d = JSON.parse(require("fs").readFileSync(tmpDesktop, "utf-8"));
77
+
78
+ // --- Extract metrics ---
79
+
80
+ function metric(report: any, key: string) {
81
+ return report.audits[key];
82
+ }
83
+
84
+ const metrics = [
85
+ { key: "first-contentful-paint", label: "First Contentful Paint", unit: "s", divisor: 1000 },
86
+ { key: "largest-contentful-paint", label: "Largest Contentful Paint", unit: "s", divisor: 1000 },
87
+ { key: "total-blocking-time", label: "Total Blocking Time", unit: "ms", divisor: 1 },
88
+ { key: "cumulative-layout-shift", label: "Cumulative Layout Shift", unit: "", divisor: 1 },
89
+ { key: "speed-index", label: "Speed Index", unit: "s", divisor: 1000 },
90
+ { key: "total-byte-weight", label: "Page weight", unit: "KB", divisor: 1024 },
91
+ ];
92
+
93
+ const ms = m.categories.performance.score * 100;
94
+ const ds = d.categories.performance.score * 100;
95
+
96
+ // --- Save raw JSON ---
97
+
98
+ const now = new Date();
99
+ const stamp = now.toISOString().slice(0, 10);
100
+ const auditDir = join(import.meta.dir, "../docs/audits");
101
+ mkdirSync(auditDir, { recursive: true });
102
+
103
+ const summary = {
104
+ url,
105
+ timestamp: now.toISOString(),
106
+ mobile: {
107
+ score: ms,
108
+ fcp: metric(m, "first-contentful-paint").numericValue,
109
+ lcp: metric(m, "largest-contentful-paint").numericValue,
110
+ tbt: metric(m, "total-blocking-time").numericValue,
111
+ cls: metric(m, "cumulative-layout-shift").numericValue,
112
+ si: metric(m, "speed-index").numericValue,
113
+ weight: metric(m, "total-byte-weight").numericValue,
114
+ },
115
+ desktop: {
116
+ score: ds,
117
+ fcp: metric(d, "first-contentful-paint").numericValue,
118
+ lcp: metric(d, "largest-contentful-paint").numericValue,
119
+ tbt: metric(d, "total-blocking-time").numericValue,
120
+ cls: metric(d, "cumulative-layout-shift").numericValue,
121
+ si: metric(d, "speed-index").numericValue,
122
+ weight: metric(d, "total-byte-weight").numericValue,
123
+ },
124
+ };
125
+
126
+ const suffix = isLocal ? "-local" : "";
127
+ const jsonPath = join(auditDir, `perf-audit-${stamp}${suffix}.json`);
128
+ writeFileSync(jsonPath, JSON.stringify(summary, null, 2) + "\n");
129
+
130
+ // --- Report ---
131
+
132
+ console.log("");
133
+ console.log(" Lighthouse Performance Audit");
134
+ console.log("");
135
+ console.log(" Metric Mobile Desktop Budget");
136
+ console.log(" ────────────────────────────────────────────────────────");
137
+
138
+ function fmt(val: number, unit: string, divisor: number): string {
139
+ if (unit === "KB") return Math.round(val / divisor) + " KB";
140
+ if (unit === "ms") return Math.round(val / divisor) + "ms";
141
+ if (unit === "") return (val / divisor).toFixed(3);
142
+ return (val / divisor).toFixed(1) + unit;
143
+ }
144
+
145
+ console.log(
146
+ " Performance score "
147
+ + String(ms).padStart(6) + " "
148
+ + String(ds).padStart(6) + " >= 90"
149
+ );
150
+
151
+ for (const { key, label, unit, divisor } of metrics) {
152
+ const mv = metric(m, key).numericValue;
153
+ const dv = metric(d, key).numericValue;
154
+ const mStr = fmt(mv, unit, divisor).padStart(unit === "KB" ? 6 : 5);
155
+ const dStr = fmt(dv, unit, divisor).padStart(unit === "KB" ? 6 : 5);
156
+
157
+ let budget = "";
158
+ if (key === "first-contentful-paint") budget = "<= 1.8s";
159
+ if (key === "largest-contentful-paint") budget = "<= 2.5s";
160
+ if (key === "total-blocking-time") budget = "<= 200ms";
161
+ if (key === "cumulative-layout-shift") budget = "<= 0.100";
162
+ if (key === "speed-index") budget = "<= 3.4s";
163
+
164
+ const padLabel = (label + " ").padEnd(26);
165
+ console.log(` ${padLabel}${mStr} ${dStr} ${budget}`);
166
+ }
167
+
168
+ // --- Budget checks ---
169
+
170
+ type CheckResult = { label: string; pass: boolean; detail: string };
171
+ const results: CheckResult[] = [];
172
+
173
+ function check(label: string, pass: boolean, detail: string) {
174
+ results.push({ label, pass, detail });
175
+ }
176
+
177
+ check("Mobile score", ms >= 90, `${ms} (want >= 90)`);
178
+ check("Desktop score", ds >= 90, `${ds} (want >= 90)`);
179
+ check("Mobile FCP", summary.mobile.fcp <= 1800, `${(summary.mobile.fcp / 1000).toFixed(1)}s (want <= 1.8s)`);
180
+ check("Desktop FCP", summary.desktop.fcp <= 1800, `${(summary.desktop.fcp / 1000).toFixed(1)}s (want <= 1.8s)`);
181
+ check("Mobile LCP", summary.mobile.lcp <= 2500, `${(summary.mobile.lcp / 1000).toFixed(1)}s (want <= 2.5s)`);
182
+ check("Desktop LCP", summary.desktop.lcp <= 2500, `${(summary.desktop.lcp / 1000).toFixed(1)}s (want <= 2.5s)`);
183
+ check("Mobile TBT", summary.mobile.tbt <= 200, `${Math.round(summary.mobile.tbt)}ms (want <= 200ms)`);
184
+ check("Desktop TBT", summary.desktop.tbt <= 200, `${Math.round(summary.desktop.tbt)}ms (want <= 200ms)`);
185
+ check("Mobile CLS", summary.mobile.cls <= 0.1, `${summary.mobile.cls.toFixed(3)} (want <= 0.100)`);
186
+ check("Desktop CLS", summary.desktop.cls <= 0.1, `${summary.desktop.cls.toFixed(3)} (want <= 0.100)`);
187
+
188
+ const passCount = results.filter((r) => r.pass).length;
189
+ const failCount = results.filter((r) => !r.pass).length;
190
+
191
+ console.log("");
192
+ console.log(" Check Result Detail");
193
+ console.log(" ─────────────────────────────────────────────────────────");
194
+
195
+ for (const r of results) {
196
+ const icon = r.pass ? "PASS" : "FAIL";
197
+ console.log(` ${r.label.padEnd(24)} ${icon.padEnd(6)} ${r.detail}`);
198
+ }
199
+
200
+ console.log("");
201
+ console.log(` ${passCount} passed, ${failCount} failed`);
202
+ console.log(` Report saved to docs/audits/perf-audit-${stamp}${suffix}.json`);
203
+ console.log("");
204
+
205
+ // Cleanup
206
+ try { require("fs").unlinkSync(tmpMobile); } catch {}
207
+ try { require("fs").unlinkSync(tmpDesktop); } catch {}
208
+ if (localServerPid) {
209
+ try { process.kill(localServerPid); } catch {}
210
+ }
211
+
212
+ if (failCount > 0) process.exit(1);
@@ -0,0 +1,154 @@
1
+ /**
2
+ * DataForSEO on-page SEO audit.
3
+ *
4
+ * Usage:
5
+ * DATAFORSEO_AUTH=<base64> bun run scripts/seo-audit.ts [url]
6
+ *
7
+ * Reads credentials from DATAFORSEO_AUTH env var, or falls back to
8
+ * ~/workspace/mbhome/keys/lightup-dataforseo-api-key-manub.
9
+ *
10
+ * Prints a formatted report and saves raw JSON to docs/audits/.
11
+ */
12
+
13
+ import { mkdirSync, readFileSync, writeFileSync } from "fs";
14
+ import { homedir } from "os";
15
+ import { join } from "path";
16
+
17
+ const url = process.argv[2] ?? "https://app.withpolaris.ai";
18
+
19
+ // --- Credentials ---
20
+
21
+ function getAuth(): string {
22
+ if (process.env.DATAFORSEO_AUTH) return process.env.DATAFORSEO_AUTH;
23
+ const keyFile = join(homedir(), "workspace/mbhome/keys/lightup-dataforseo-api-key-manub");
24
+ try {
25
+ const content = readFileSync(keyFile, "utf-8");
26
+ const match = content.match(/api password base64:\s*(.+)/);
27
+ if (match) return match[1].trim();
28
+ } catch {}
29
+ console.error("Error: Set DATAFORSEO_AUTH or ensure key file exists at ~/workspace/mbhome/keys/lightup-dataforseo-api-key-manub");
30
+ process.exit(1);
31
+ }
32
+
33
+ const auth = getAuth();
34
+
35
+ // --- API call ---
36
+
37
+ const res = await fetch("https://api.dataforseo.com/v3/on_page/instant_pages", {
38
+ method: "POST",
39
+ headers: {
40
+ Authorization: `Basic ${auth}`,
41
+ "Content-Type": "application/json",
42
+ },
43
+ body: JSON.stringify([{ url, enable_javascript: true }]),
44
+ });
45
+
46
+ if (!res.ok) {
47
+ console.error(`API error: ${res.status} ${res.statusText}`);
48
+ process.exit(1);
49
+ }
50
+
51
+ const data = await res.json();
52
+ const task = data.tasks?.[0];
53
+
54
+ if (!task || task.status_code !== 20000) {
55
+ console.error("API returned an error:", task?.status_message ?? "unknown");
56
+ process.exit(1);
57
+ }
58
+
59
+ const page = task.result?.[0]?.items?.[0];
60
+ if (!page) {
61
+ console.error("No page data returned");
62
+ process.exit(1);
63
+ }
64
+
65
+ // --- Save raw JSON ---
66
+
67
+ const now = new Date();
68
+ const stamp = now.toISOString().slice(0, 10);
69
+ const auditDir = join(import.meta.dir, "../docs/audits");
70
+ mkdirSync(auditDir, { recursive: true });
71
+ const jsonPath = join(auditDir, `seo-audit-${stamp}.json`);
72
+ writeFileSync(jsonPath, JSON.stringify(page, null, 2) + "\n");
73
+
74
+ // --- Report ---
75
+
76
+ const meta = page.meta ?? {};
77
+ const checks = page.checks ?? {};
78
+ const timing = page.page_timing ?? {};
79
+ const content = meta.content ?? {};
80
+ const social = meta.social_media_tags ?? {};
81
+ const htags = meta.htags ?? {};
82
+
83
+ type CheckResult = { label: string; pass: boolean; detail: string };
84
+ const results: CheckResult[] = [];
85
+
86
+ function check(label: string, pass: boolean, detail: string) {
87
+ results.push({ label, pass, detail });
88
+ }
89
+
90
+ // Meta
91
+ check("Title", !!meta.title && meta.title_length >= 30 && meta.title_length <= 60,
92
+ meta.title ? `${meta.title_length} chars` : "missing");
93
+ check("Description", !!meta.description && meta.description_length >= 70 && meta.description_length <= 160,
94
+ meta.description ? `${meta.description_length} chars` : "missing");
95
+ check("Canonical", !!meta.canonical, meta.canonical ?? "missing");
96
+ check("HTTPS", !!checks.is_https, checks.is_https ? "yes" : "no");
97
+ check("Doctype", !!checks.has_html_doctype, checks.has_html_doctype ? "yes" : "no");
98
+ check("Charset", !!checks.meta_charset_consistency, checks.meta_charset_consistency ? "consistent" : "inconsistent");
99
+
100
+ // Headings
101
+ const h1Count = htags.h1?.length ?? 0;
102
+ check("Single H1", h1Count === 1, `${h1Count} h1 tag(s)`);
103
+ check("Heading hierarchy", (htags.h2?.length ?? 0) > 0, `h1:${h1Count} h2:${htags.h2?.length ?? 0} h3:${htags.h3?.length ?? 0}`);
104
+
105
+ // Social
106
+ check("OG tags", !!social["og:title"] && !!social["og:description"] && !!social["og:image"],
107
+ social["og:title"] ? "title + desc + image" : "incomplete");
108
+ check("Twitter card", !!social["twitter:card"],
109
+ social["twitter:card"] ?? "missing");
110
+
111
+ // Content
112
+ check("Content rate", !checks.low_content_rate,
113
+ `${(content.plain_text_rate * 100).toFixed(1)}% (want >= 10%)`);
114
+ check("Title/content match", content.title_to_content_consistency >= 0.8,
115
+ content.title_to_content_consistency?.toFixed(2) ?? "n/a");
116
+ check("Desc/content match", content.description_to_content_consistency >= 0.8,
117
+ content.description_to_content_consistency?.toFixed(2) ?? "n/a");
118
+
119
+ // Technical
120
+ check("Favicon", !checks.no_favicon, checks.no_favicon ? "missing" : "present");
121
+ check("No render-blocking", !checks.has_render_blocking_resources,
122
+ checks.has_render_blocking_resources ? `${meta.render_blocking_scripts_count} script(s)` : "clean");
123
+ check("Content encoding", !checks.no_content_encoding,
124
+ checks.no_content_encoding ? "no gzip/brotli" : "enabled");
125
+ check("SEO-friendly URL", !!checks.seo_friendly_url, checks.seo_friendly_url ? "yes" : "no");
126
+ check("Image alt text", !checks.no_image_alt, checks.no_image_alt ? "missing on some images" : "present");
127
+ check("No broken links", !checks.is_broken, checks.is_broken ? "broken" : "ok");
128
+ check("Page size", !checks.large_page_size, checks.large_page_size ? ">3MB" : `${Math.round(page.encoded_size / 1024)} KB`);
129
+
130
+ // Print report
131
+ console.log("");
132
+ console.log(` DataForSEO On-Page Audit — ${url}`);
133
+ console.log(` On-Page Score: ${page.onpage_score}/100`);
134
+ console.log(` Page size: ${Math.round(page.encoded_size / 1024)} KB (${Math.round(page.total_dom_size / 1024)} KB DOM)`);
135
+ console.log(` Words: ${content.plain_text_word_count} Readability: ${content.flesch_kincaid_readability_index?.toFixed(0)}/100 Flesch-Kincaid`);
136
+ console.log("");
137
+ console.log(" Check Result Detail");
138
+ console.log(" ─────────────────────────────────────────────────────────");
139
+
140
+ const passCount = results.filter((r) => r.pass).length;
141
+ const failCount = results.filter((r) => !r.pass).length;
142
+
143
+ for (const r of results) {
144
+ const icon = r.pass ? "PASS" : "FAIL";
145
+ console.log(` ${r.label.padEnd(24)} ${icon.padEnd(6)} ${r.detail}`);
146
+ }
147
+
148
+ console.log("");
149
+ console.log(` ${passCount} passed, ${failCount} failed`);
150
+ console.log(` Report saved to docs/audits/seo-audit-${stamp}.json`);
151
+ console.log("");
152
+
153
+ // Exit non-zero if any checks failed
154
+ if (failCount > 0) process.exit(1);
package/src/web/app.ts CHANGED
@@ -140,6 +140,15 @@ export function createApp(sql: Sql) {
140
140
  // Start hourly signup rollup
141
141
  startSignupRollup(sql);
142
142
 
143
+ // --- Static assets ---
144
+
145
+ app.get("/styles.css", async (c) => {
146
+ const file = Bun.file(new URL("./styles/output.css", import.meta.url).pathname);
147
+ return new Response(await file.arrayBuffer(), {
148
+ headers: { "Content-Type": "text/css", "Cache-Control": "public, max-age=31536000, immutable" },
149
+ });
150
+ });
151
+
143
152
  // --- SEO ---
144
153
 
145
154
  app.get("/og-image.png", async (c) => {
package/src/web/layout.ts CHANGED
@@ -37,18 +37,7 @@ export function layout(body: string, title = "Polaris", seo?: SeoOpts): Response
37
37
  <meta name="twitter:title" content="${pageTitle}">
38
38
  <meta name="twitter:description" content="${description}">
39
39
  <meta name="twitter:image" content="${ogImage}">
40
- <script src="https://cdn.tailwindcss.com"></script>
41
- <script>
42
- tailwind.config = {
43
- theme: {
44
- extend: {
45
- colors: {
46
- polaris: { 50: '#f0f4ff', 100: '#dbe4ff', 200: '#bac8ff', 300: '#91a7ff', 400: '#748ffc', 500: '#5c7cfa', 600: '#4c6ef5', 700: '#4263eb', 800: '#3b5bdb', 900: '#364fc7' }
47
- }
48
- }
49
- }
50
- }
51
- </script>
40
+ <link rel="stylesheet" href="/styles.css">
52
41
  </head>
53
42
  <body class="bg-gray-50 text-gray-900 antialiased"><div class="overflow-x-hidden max-w-[100vw]">${body}</div>
54
43
  <script>
@@ -0,0 +1,16 @@
1
+ @import "tailwindcss";
2
+
3
+ @source "../*.ts";
4
+
5
+ @theme {
6
+ --color-polaris-50: #f0f4ff;
7
+ --color-polaris-100: #dbe4ff;
8
+ --color-polaris-200: #bac8ff;
9
+ --color-polaris-300: #91a7ff;
10
+ --color-polaris-400: #748ffc;
11
+ --color-polaris-500: #5c7cfa;
12
+ --color-polaris-600: #4c6ef5;
13
+ --color-polaris-700: #4263eb;
14
+ --color-polaris-800: #3b5bdb;
15
+ --color-polaris-900: #364fc7;
16
+ }