ci: update CI/CD configuration
This commit is contained in:
parent
a7679de295
commit
9db3967fe1
@ -1,9 +1,9 @@
|
||||
Last refresh: 2026-04-03T06:28:08Z (2026-04-02 23:28:08 PDT)
|
||||
Cascade conversations: 50 (298M)
|
||||
Memories: 121
|
||||
Last refresh: 2026-04-13T06:13:49Z (2026-04-12 23:13:49 PDT)
|
||||
Cascade conversations: 50 (421M)
|
||||
Memories: 130
|
||||
Implicit context: 20
|
||||
Code tracker dirs: 103
|
||||
File edit history: 4225 entries
|
||||
Workspace storage: 37 workspaces
|
||||
Code tracker dirs: 108
|
||||
File edit history: 4591 entries
|
||||
Workspace storage: 41 workspaces
|
||||
Repo docs: 7 files across 2 repos
|
||||
Repo workflows: 54 files across 12 repos
|
||||
Repo workflows: 55 files across 12 repos
|
||||
|
||||
@ -9,17 +9,17 @@
|
||||
|
||||
## 1. Project → Env File Map
|
||||
|
||||
| # | Project | Env File | Port |
|
||||
|---|---------|----------|------|
|
||||
| 1 | Desktop app (`src/`) | `.env` (root) | — |
|
||||
| 2 | Backend API (`backend/`) | `backend/.env` | 8000 |
|
||||
| 3 | Admin Dashboard (`admin-dashboard-web/`) | `admin-dashboard-web/.env.local` | 3001 |
|
||||
| 4 | User Dashboard (`user-dashboard-web/`) | `user-dashboard-web/.env.local` | 3002 |
|
||||
| 5 | Tracker Dashboard (`tracker-dashboard-web/`) | `tracker-dashboard-web/.env.local` | 3003 |
|
||||
| 6 | Billing Service (`services/billing-service/`) | `services/billing-service/.env` | 4002 |
|
||||
| 7 | Growth Service (`services/growth-service/`) | `services/growth-service/.env` | 4001 |
|
||||
| 8 | Platform Service (`services/platform-service/`) | `services/platform-service/.env` | 4003 |
|
||||
| 9 | Tracker Service (`services/tracker-service/`) | `services/tracker-service/.env` | 4004 |
|
||||
| # | Project | Env File | Port |
|
||||
| --- | ----------------------------------------------- | ---------------------------------- | ---- |
|
||||
| 1 | Desktop app (`src/`) | `.env` (root) | — |
|
||||
| 2 | Backend API (`backend/`) | `backend/.env` | 8000 |
|
||||
| 3 | Admin Dashboard (`admin-dashboard-web/`) | `admin-dashboard-web/.env.local` | 3001 |
|
||||
| 4 | User Dashboard (`user-dashboard-web/`) | `user-dashboard-web/.env.local` | 3002 |
|
||||
| 5 | Tracker Dashboard (`tracker-dashboard-web/`) | `tracker-dashboard-web/.env.local` | 3003 |
|
||||
| 6 | Billing Service (`services/billing-service/`) | `services/billing-service/.env` | 4002 |
|
||||
| 7 | Growth Service (`services/growth-service/`) | `services/growth-service/.env` | 4001 |
|
||||
| 8 | Platform Service (`services/platform-service/`) | `services/platform-service/.env` | 4003 |
|
||||
| 9 | Tracker Service (`services/tracker-service/`) | `services/tracker-service/.env` | 4004 |
|
||||
|
||||
---
|
||||
|
||||
@ -36,63 +36,63 @@ grep -rn 'MISSING_ENV_VALUE' --include='.env*' --include='*.env' . | grep -v nod
|
||||
|
||||
### 3.1 Root `.env` (Desktop App)
|
||||
|
||||
| Variable | Status | Action |
|
||||
|----------|--------|--------|
|
||||
| `APPLICATIONINSIGHTS_CONNECTION_STRING` | ❌ Empty | Get from Azure Portal → Application Insights → Overview |
|
||||
| `ANH_CONNECTION_STRING` | ⚠️ Has `YOUR_KEY_HERE` placeholder | Replace with real SharedAccessKey from Azure Portal → Notification Hubs |
|
||||
| Variable | Status | Action |
|
||||
| --------------------------------------- | ---------------------------------- | ----------------------------------------------------------------------- |
|
||||
| `APPLICATIONINSIGHTS_CONNECTION_STRING` | ❌ Empty | Get from Azure Portal → Application Insights → Overview |
|
||||
| `ANH_CONNECTION_STRING` | ⚠️ Has `YOUR_KEY_HERE` placeholder | Replace with real SharedAccessKey from Azure Portal → Notification Hubs |
|
||||
|
||||
### 3.2 `backend/.env`
|
||||
|
||||
| Variable | Status | Action |
|
||||
|----------|--------|--------|
|
||||
| `AZURE_EMAIL_CONNECTION_STRING` | ❌ Empty | Get from Azure Portal → Communication Services → Keys |
|
||||
| `SMTP_HOST` | ❌ Empty | Configure if using SMTP fallback instead of Azure Communication Services |
|
||||
| `SMTP_USER` | ❌ Empty | Configure if using SMTP fallback |
|
||||
| `SMTP_PASS` | ❌ Empty | Configure if using SMTP fallback |
|
||||
| Variable | Status | Action |
|
||||
| ------------------------------- | -------- | ------------------------------------------------------------------------ |
|
||||
| `AZURE_EMAIL_CONNECTION_STRING` | ❌ Empty | Get from Azure Portal → Communication Services → Keys |
|
||||
| `SMTP_HOST` | ❌ Empty | Configure if using SMTP fallback instead of Azure Communication Services |
|
||||
| `SMTP_USER` | ❌ Empty | Configure if using SMTP fallback |
|
||||
| `SMTP_PASS` | ❌ Empty | Configure if using SMTP fallback |
|
||||
|
||||
### 3.3 `admin-dashboard-web/.env.local`
|
||||
|
||||
| Variable | Status | Action |
|
||||
|----------|--------|--------|
|
||||
| `NEXT_PUBLIC_POSTHOG_KEY` | ❌ Empty | Get from PostHog → Project Settings (optional — analytics) |
|
||||
| Variable | Status | Action |
|
||||
| -------------------------- | -------- | ---------------------------------------------------------- |
|
||||
| `NEXT_PUBLIC_POSTHOG_KEY` | ❌ Empty | Get from PostHog → Project Settings (optional — analytics) |
|
||||
| `NEXT_PUBLIC_POSTHOG_HOST` | ❌ Empty | Get from PostHog → Project Settings (optional — analytics) |
|
||||
|
||||
### 3.4 `user-dashboard-web/.env.local`
|
||||
|
||||
| Variable | Status | Action |
|
||||
|----------|--------|--------|
|
||||
| Variable | Status | Action |
|
||||
| -------------------------- | -------- | ------------------------------------------------------------------- |
|
||||
| `ENTERPRISE_EMAIL_DOMAINS` | ❌ Empty | Set comma-separated list of domains that qualify for Enterprise SSO |
|
||||
| `MICROSOFT_CLIENT_ID` | ❌ Empty | Register app in Azure Portal → Entra ID → App registrations |
|
||||
| `MICROSOFT_CLIENT_SECRET` | ❌ Empty | Same as above |
|
||||
| `GOOGLE_CLIENT_ID` | ❌ Empty | Register app in Google Cloud Console → Credentials |
|
||||
| `GOOGLE_CLIENT_SECRET` | ❌ Empty | Same as above |
|
||||
| `NEXT_PUBLIC_POSTHOG_KEY` | ❌ Empty | PostHog analytics (optional) |
|
||||
| `NEXT_PUBLIC_POSTHOG_HOST` | ❌ Empty | PostHog analytics (optional) |
|
||||
| `MICROSOFT_CLIENT_ID` | ❌ Empty | Register app in Azure Portal → Entra ID → App registrations |
|
||||
| `MICROSOFT_CLIENT_SECRET` | ❌ Empty | Same as above |
|
||||
| `GOOGLE_CLIENT_ID` | ❌ Empty | Register app in Google Cloud Console → Credentials |
|
||||
| `GOOGLE_CLIENT_SECRET` | ❌ Empty | Same as above |
|
||||
| `NEXT_PUBLIC_POSTHOG_KEY` | ❌ Empty | PostHog analytics (optional) |
|
||||
| `NEXT_PUBLIC_POSTHOG_HOST` | ❌ Empty | PostHog analytics (optional) |
|
||||
|
||||
### 3.5 `tracker-dashboard-web/.env.local`
|
||||
|
||||
| Variable | Status | Action |
|
||||
|----------|--------|--------|
|
||||
| `NEXT_PUBLIC_POSTHOG_KEY` | ❌ Empty | PostHog analytics (optional) |
|
||||
| Variable | Status | Action |
|
||||
| -------------------------- | -------- | ---------------------------- |
|
||||
| `NEXT_PUBLIC_POSTHOG_KEY` | ❌ Empty | PostHog analytics (optional) |
|
||||
| `NEXT_PUBLIC_POSTHOG_HOST` | ❌ Empty | PostHog analytics (optional) |
|
||||
|
||||
### 3.6 `services/growth-service/.env`
|
||||
|
||||
| Variable | Status | Action |
|
||||
|----------|--------|--------|
|
||||
| Variable | Status | Action |
|
||||
| --------------------------------- | -------- | ------------------------------------------------------------ |
|
||||
| `WEBHOOK_INVITATION_REDEEMED_URL` | ❌ Empty | Set to backend or platform-service webhook callback endpoint |
|
||||
| `WEBHOOK_REFERRAL_STATUS_URL` | ❌ Empty | Set to backend or platform-service webhook callback endpoint |
|
||||
| `WEBHOOK_REFERRAL_STATUS_URL` | ❌ Empty | Set to backend or platform-service webhook callback endpoint |
|
||||
|
||||
### 3.7 `services/billing-service/.env`
|
||||
|
||||
| Variable | Status | Action |
|
||||
|----------|--------|--------|
|
||||
| Variable | Status | Action |
|
||||
| ------------------ | -------- | --------------------------------------------------------------- |
|
||||
| `PLAN_LIMITS_JSON` | ❌ Empty | Optional — set JSON with per-plan limits if overriding defaults |
|
||||
|
||||
### 3.8 `services/platform-service/.env`
|
||||
|
||||
| Variable | Status | Action |
|
||||
|----------|--------|--------|
|
||||
| Variable | Status | Action |
|
||||
| ------------------------ | -------- | ------------------------------------------------------------------------ |
|
||||
| `RATE_LIMIT_CONFIG_JSON` | ❌ Empty | Optional — set JSON with per-endpoint rate limits if overriding defaults |
|
||||
|
||||
### 3.9 `services/tracker-service/.env`
|
||||
@ -105,21 +105,21 @@ grep -rn 'MISSING_ENV_VALUE' --include='.env*' --include='*.env' . | grep -v nod
|
||||
|
||||
These were missing from `.env` files but had known values, so they were filled in:
|
||||
|
||||
| Project | Variable | Value Added |
|
||||
|---------|----------|-------------|
|
||||
| Root `.env` | `PLATFORM_SERVICE_URL` | `http://localhost:4003` |
|
||||
| Root `.env` | `LYSNR_API_URL` | `http://localhost:8000` |
|
||||
| Root `.env` | `LYSNR_ADMIN_URL` | `http://localhost:3001` |
|
||||
| Root `.env` | `LYSNR_DASHBOARD_URL` | `http://localhost:3002` |
|
||||
| `backend/.env` | `BILLING_SERVICE_URL` | `http://localhost:4002` |
|
||||
| `backend/.env` | `PLATFORM_SERVICE_URL` | `http://localhost:4003` |
|
||||
| `backend/.env` | `CORS_ORIGINS` | Expanded to include all dashboard ports |
|
||||
| `admin-dashboard-web/.env.local` | `STRIPE_PUBLISHABLE_KEY` | Test key (was missing) |
|
||||
| `admin-dashboard-web/.env.local` | `STRIPE_WEBHOOK_SECRET` | Test key (was missing) |
|
||||
| `admin-dashboard-web/.env.local` | `STRIPE_PRICE_PRO` | `price_1Szl2z...` |
|
||||
| `admin-dashboard-web/.env.local` | `STRIPE_PRICE_ENTERPRISE` | `price_1Szl3D...` |
|
||||
| `user-dashboard-web/.env.local` | `ENTERPRISE_EMAIL_DOMAINS` | Empty (needs config) |
|
||||
| `services/billing-service/.env` | `USAGE_WARN_THRESHOLD` | `80` |
|
||||
| Project | Variable | Value Added |
|
||||
| -------------------------------- | -------------------------- | --------------------------------------- |
|
||||
| Root `.env` | `PLATFORM_SERVICE_URL` | `http://localhost:4003` |
|
||||
| Root `.env` | `LYSNR_API_URL` | `http://localhost:8000` |
|
||||
| Root `.env` | `LYSNR_ADMIN_URL` | `http://localhost:3001` |
|
||||
| Root `.env` | `LYSNR_DASHBOARD_URL` | `http://localhost:3002` |
|
||||
| `backend/.env` | `BILLING_SERVICE_URL` | `http://localhost:4002` |
|
||||
| `backend/.env` | `PLATFORM_SERVICE_URL` | `http://localhost:4003` |
|
||||
| `backend/.env` | `CORS_ORIGINS` | Expanded to include all dashboard ports |
|
||||
| `admin-dashboard-web/.env.local` | `STRIPE_PUBLISHABLE_KEY` | Test key (was missing) |
|
||||
| `admin-dashboard-web/.env.local` | `STRIPE_WEBHOOK_SECRET` | Test key (was missing) |
|
||||
| `admin-dashboard-web/.env.local` | `STRIPE_PRICE_PRO` | `price_1Szl2z...` |
|
||||
| `admin-dashboard-web/.env.local` | `STRIPE_PRICE_ENTERPRISE` | `price_1Szl3D...` |
|
||||
| `user-dashboard-web/.env.local` | `ENTERPRISE_EMAIL_DOMAINS` | Empty (needs config) |
|
||||
| `services/billing-service/.env` | `USAGE_WARN_THRESHOLD` | `80` |
|
||||
|
||||
---
|
||||
|
||||
@ -134,24 +134,27 @@ These were missing from `.env` files but had known values, so they were filled i
|
||||
|
||||
These values **must be identical** across all services that use them:
|
||||
|
||||
| Secret | Used By |
|
||||
|--------|---------|
|
||||
| `JWT_SECRET` | All 4 Fastify services + all 3 dashboards + backend |
|
||||
| `COSMOS_ENDPOINT` | All 4 Fastify services + admin + user dashboards + backend + desktop |
|
||||
| `COSMOS_KEY` | Same as above |
|
||||
| `COSMOS_DATABASE` | Same as above (must be `lysnrai`) |
|
||||
| `STRIPE_SECRET_KEY` | billing-service, growth-service, admin-dashboard, user-dashboard |
|
||||
| `AZURE_BLOB_*` | platform-service, admin-dashboard, user-dashboard, desktop |
|
||||
| Secret | Used By |
|
||||
| ------------------- | -------------------------------------------------------------------- |
|
||||
| `JWT_SECRET` | All 4 Fastify services + all 3 dashboards + backend |
|
||||
| `COSMOS_ENDPOINT` | All 4 Fastify services + admin + user dashboards + backend + desktop |
|
||||
| `COSMOS_KEY` | Same as above |
|
||||
| `COSMOS_DATABASE` | Same as above (must be `lysnrai`) |
|
||||
| `STRIPE_SECRET_KEY` | billing-service, growth-service, admin-dashboard, user-dashboard |
|
||||
| `AZURE_BLOB_*` | platform-service, admin-dashboard, user-dashboard, desktop |
|
||||
|
||||
---
|
||||
|
||||
## 7. Production Deployment Notes
|
||||
|
||||
When deploying to **Vercel** (frontends) + **Railway** (backends):
|
||||
When deploying to the current stack:
|
||||
|
||||
- **Vercel** for public/front-end surfaces where applicable
|
||||
- **Azure VM / shared infra** for backend and internal service hosting
|
||||
|
||||
1. **JWT_SECRET** — rotate to a new 64-char hex. Must match across ALL services.
|
||||
2. **CORS_ORIGIN** — set on each Fastify service to restrict to Vercel domains (comma-separated).
|
||||
3. **Stripe webhook** — create a new webhook in Stripe Dashboard pointing to production URL; update `STRIPE_WEBHOOK_SECRET`.
|
||||
4. **SSO redirect URIs** — update `MICROSOFT_REDIRECT_URI` and `GOOGLE_REDIRECT_URI` to production Vercel domain.
|
||||
5. **NEXTAUTH_URL** — set to production Vercel domain for user-dashboard.
|
||||
6. **Service URLs** — replace all `localhost:*` with Railway public URLs.
|
||||
6. **Service URLs** — replace all `localhost:*` with the current production API and service URLs.
|
||||
|
||||
@ -15,20 +15,12 @@ Systematically verify consistency across all ByteLyst product repos. Catches dri
|
||||
```bash
|
||||
REPOS_DIR="/Users/sd9235/code/mygh"
|
||||
echo "=== packageManager in root package.json ==="
|
||||
for repo in \
|
||||
learning_ai_common_plat \
|
||||
learning_voice_ai_agent \
|
||||
learning_ai_clock \
|
||||
learning_ai_fastgap \
|
||||
learning_ai_jarvis_jr \
|
||||
learning_ai_peakpulse \
|
||||
learning_ai_notes \
|
||||
learning_ai_flowmonk \
|
||||
learning_ai_trails \
|
||||
learning_ai_local_memory_gpt; do
|
||||
while IFS= read -r repo; do
|
||||
[[ -z "$repo" || "$repo" =~ ^# ]] && continue
|
||||
[[ ! -f "$REPOS_DIR/$repo/package.json" ]] && continue
|
||||
printf "%-40s " "$repo:"
|
||||
grep '"packageManager"' "$REPOS_DIR/$repo/package.json" 2>/dev/null || echo "MISSING"
|
||||
done
|
||||
done < /Users/sd9235/code/mygh/learning_ai_common_plat/.windsurf/workflows/repos.txt
|
||||
```
|
||||
|
||||
Expect: all repos show `"packageManager": "pnpm@10.6.5"`. Fix any MISSING entries.
|
||||
@ -40,20 +32,12 @@ Expect: all repos show `"packageManager": "pnpm@10.6.5"`. Fix any MISSING entrie
|
||||
```bash
|
||||
REPOS_DIR="/Users/sd9235/code/mygh"
|
||||
echo "=== node_modules in .gitignore ==="
|
||||
for repo in \
|
||||
learning_ai_common_plat \
|
||||
learning_voice_ai_agent \
|
||||
learning_ai_clock \
|
||||
learning_ai_fastgap \
|
||||
learning_ai_jarvis_jr \
|
||||
learning_ai_peakpulse \
|
||||
learning_ai_notes \
|
||||
learning_ai_flowmonk \
|
||||
learning_ai_trails \
|
||||
learning_ai_local_memory_gpt; do
|
||||
while IFS= read -r repo; do
|
||||
[[ -z "$repo" || "$repo" =~ ^# ]] && continue
|
||||
[[ ! -f "$REPOS_DIR/$repo/.gitignore" ]] && continue
|
||||
printf "%-40s " "$repo:"
|
||||
grep -c 'node_modules' "$REPOS_DIR/$repo/.gitignore" 2>/dev/null || echo "MISSING"
|
||||
done
|
||||
done < /Users/sd9235/code/mygh/learning_ai_common_plat/.windsurf/workflows/repos.txt
|
||||
```
|
||||
|
||||
Expect: all repos have at least 1 match. Fix any with 0 or MISSING.
|
||||
@ -65,16 +49,8 @@ Expect: all repos have at least 1 match. Fix any with 0 or MISSING.
|
||||
```bash
|
||||
REPOS_DIR="/Users/sd9235/code/mygh"
|
||||
echo "=== .dockerignore health ==="
|
||||
for repo in \
|
||||
learning_voice_ai_agent \
|
||||
learning_ai_clock \
|
||||
learning_ai_fastgap \
|
||||
learning_ai_jarvis_jr \
|
||||
learning_ai_peakpulse \
|
||||
learning_ai_notes \
|
||||
learning_ai_flowmonk \
|
||||
learning_ai_trails \
|
||||
learning_ai_local_memory_gpt; do
|
||||
while IFS= read -r repo; do
|
||||
[[ -z "$repo" || "$repo" =~ ^# ]] && continue
|
||||
di="$REPOS_DIR/$repo/.dockerignore"
|
||||
if [ ! -f "$di" ]; then
|
||||
echo "$repo: MISSING .dockerignore"
|
||||
@ -83,10 +59,10 @@ for repo in \
|
||||
else
|
||||
echo "$repo: OK"
|
||||
fi
|
||||
done
|
||||
done < /Users/sd9235/code/mygh/learning_ai_common_plat/.windsurf/workflows/repos.txt
|
||||
```
|
||||
|
||||
Expect: all OK. Any BUG entries will break Docker builds.
|
||||
Expect: all OK (repos without .dockerignore are skipped). Any BUG entries will break Docker builds.
|
||||
|
||||
## 4. Check stale package-lock.json files
|
||||
|
||||
@ -95,19 +71,11 @@ Expect: all OK. Any BUG entries will break Docker builds.
|
||||
```bash
|
||||
REPOS_DIR="/Users/sd9235/code/mygh"
|
||||
echo "=== Stale package-lock.json ==="
|
||||
for repo in \
|
||||
learning_voice_ai_agent \
|
||||
learning_ai_clock \
|
||||
learning_ai_fastgap \
|
||||
learning_ai_jarvis_jr \
|
||||
learning_ai_peakpulse \
|
||||
learning_ai_notes \
|
||||
learning_ai_flowmonk \
|
||||
learning_ai_trails \
|
||||
learning_ai_local_memory_gpt; do
|
||||
while IFS= read -r repo; do
|
||||
[[ -z "$repo" || "$repo" =~ ^# ]] && continue
|
||||
found=$(find "$REPOS_DIR/$repo" -name "package-lock.json" -not -path "*/node_modules/*" 2>/dev/null)
|
||||
if [ -n "$found" ]; then echo "STALE: $found"; fi
|
||||
done
|
||||
done < /Users/sd9235/code/mygh/learning_ai_common_plat/.windsurf/workflows/repos.txt
|
||||
echo "(empty = all clean)"
|
||||
```
|
||||
|
||||
@ -120,17 +88,9 @@ Expect: no output. Remove any stale lockfiles found.
|
||||
```bash
|
||||
REPOS_DIR="/Users/sd9235/code/mygh"
|
||||
echo "=== Dockerfile base image + NODE_TLS ==="
|
||||
for repo in \
|
||||
learning_voice_ai_agent \
|
||||
learning_ai_clock \
|
||||
learning_ai_fastgap \
|
||||
learning_ai_jarvis_jr \
|
||||
learning_ai_peakpulse \
|
||||
learning_ai_notes \
|
||||
learning_ai_flowmonk \
|
||||
learning_ai_trails \
|
||||
learning_ai_local_memory_gpt; do
|
||||
for df in $(git -C "$REPOS_DIR/$repo" ls-files '*/Dockerfile' 2>/dev/null); do
|
||||
while IFS= read -r repo; do
|
||||
[[ -z "$repo" || "$repo" =~ ^# ]] && continue
|
||||
for df in $(git -C "$REPOS_DIR/$repo" ls-files '*/Dockerfile' 'Dockerfile' 2>/dev/null); do
|
||||
full="$REPOS_DIR/$repo/$df"
|
||||
base=$(grep -m1 '^FROM' "$full" | awk '{print $2}')
|
||||
tls=$(grep -c 'NODE_TLS_REJECT_UNAUTHORIZED' "$full" 2>/dev/null)
|
||||
@ -139,7 +99,7 @@ for repo in \
|
||||
[[ "$tls" == "0" && "$df" != *python* ]] && status="$status WARN:no-NODE_TLS"
|
||||
echo "$repo/$df: base=$base tls=$tls $status"
|
||||
done
|
||||
done
|
||||
done < /Users/sd9235/code/mygh/learning_ai_common_plat/.windsurf/workflows/repos.txt
|
||||
```
|
||||
|
||||
Expect: all use `node:22-slim`, all have `NODE_TLS` refs > 0. Fix any WARN entries.
|
||||
@ -151,15 +111,9 @@ Expect: all use `node:22-slim`, all have `NODE_TLS` refs > 0. Fix any WARN entri
|
||||
```bash
|
||||
REPOS_DIR="/Users/sd9235/code/mygh"
|
||||
echo "=== next.config.ts: transpilePackages + symlinks ==="
|
||||
for repo in \
|
||||
learning_voice_ai_agent \
|
||||
learning_ai_clock \
|
||||
learning_ai_fastgap \
|
||||
learning_ai_jarvis_jr \
|
||||
learning_ai_notes \
|
||||
learning_ai_trails \
|
||||
learning_ai_local_memory_gpt; do
|
||||
for cfg in $(find "$REPOS_DIR/$repo" -maxdepth 2 -name "next.config.ts" -not -path "*/node_modules/*" 2>/dev/null); do
|
||||
while IFS= read -r repo; do
|
||||
[[ -z "$repo" || "$repo" =~ ^# ]] && continue
|
||||
for cfg in $(find "$REPOS_DIR/$repo" -maxdepth 3 -name "next.config.ts" -not -path "*/node_modules/*" 2>/dev/null); do
|
||||
relpath="${cfg#$REPOS_DIR/}"
|
||||
tp=$(grep -c 'transpilePackages' "$cfg")
|
||||
sl=$(grep -c 'symlinks' "$cfg")
|
||||
@ -168,7 +122,7 @@ for repo in \
|
||||
[[ "$sl" == "0" ]] && status="$status MISSING:symlinks"
|
||||
echo "$relpath: transpile=$tp symlinks=$sl $status"
|
||||
done
|
||||
done
|
||||
done < /Users/sd9235/code/mygh/learning_ai_common_plat/.windsurf/workflows/repos.txt
|
||||
```
|
||||
|
||||
Expect: all show transpile>0 and symlinks>0. Fix any MISSING entries.
|
||||
@ -180,17 +134,10 @@ Expect: all show transpile>0 and symlinks>0. Fix any MISSING entries.
|
||||
```bash
|
||||
REPOS_DIR="/Users/sd9235/code/mygh"
|
||||
echo "=== pnpm-workspace.yaml includes common-plat ==="
|
||||
for repo in \
|
||||
learning_voice_ai_agent \
|
||||
learning_ai_clock \
|
||||
learning_ai_fastgap \
|
||||
learning_ai_jarvis_jr \
|
||||
learning_ai_peakpulse \
|
||||
learning_ai_notes \
|
||||
learning_ai_flowmonk \
|
||||
learning_ai_trails \
|
||||
learning_ai_local_memory_gpt; do
|
||||
while IFS= read -r repo; do
|
||||
[[ -z "$repo" || "$repo" =~ ^# ]] && continue
|
||||
ws="$REPOS_DIR/$repo/pnpm-workspace.yaml"
|
||||
[[ ! -f "$ws" ]] && continue
|
||||
if [ ! -f "$ws" ]; then
|
||||
echo "$repo: MISSING pnpm-workspace.yaml"
|
||||
elif grep -q 'common_plat' "$ws"; then
|
||||
@ -198,10 +145,10 @@ for repo in \
|
||||
else
|
||||
echo "$repo: MISSING common-plat in workspace"
|
||||
fi
|
||||
done
|
||||
done < /Users/sd9235/code/mygh/learning_ai_common_plat/.windsurf/workflows/repos.txt
|
||||
```
|
||||
|
||||
Expect: all OK. Fix any MISSING entries.
|
||||
Expect: all OK (repos without pnpm-workspace.yaml are skipped). Fix any MISSING entries.
|
||||
|
||||
## 8. Check docker-prep.sh uses shared prep-consumer
|
||||
|
||||
@ -210,16 +157,8 @@ Expect: all OK. Fix any MISSING entries.
|
||||
```bash
|
||||
REPOS_DIR="/Users/sd9235/code/mygh"
|
||||
echo "=== docker-prep.sh uses shared prep-consumer ==="
|
||||
for repo in \
|
||||
learning_voice_ai_agent \
|
||||
learning_ai_clock \
|
||||
learning_ai_fastgap \
|
||||
learning_ai_jarvis_jr \
|
||||
learning_ai_peakpulse \
|
||||
learning_ai_notes \
|
||||
learning_ai_flowmonk \
|
||||
learning_ai_trails \
|
||||
learning_ai_local_memory_gpt; do
|
||||
while IFS= read -r repo; do
|
||||
[[ -z "$repo" || "$repo" =~ ^# ]] && continue
|
||||
script="$REPOS_DIR/$repo/scripts/docker-prep.sh"
|
||||
if [ ! -f "$script" ]; then
|
||||
echo "$repo: NO docker-prep.sh"
|
||||
@ -228,10 +167,10 @@ for repo in \
|
||||
else
|
||||
echo "$repo: WARN — legacy docker-prep.sh"
|
||||
fi
|
||||
done
|
||||
done < /Users/sd9235/code/mygh/learning_ai_common_plat/.windsurf/workflows/repos.txt
|
||||
```
|
||||
|
||||
Expect: all OK. Legacy scripts should be replaced with the shared wrapper.
|
||||
Expect: all OK (repos without docker-prep.sh are skipped). Legacy scripts should be replaced with the shared wrapper.
|
||||
|
||||
## 9. Check verify scripts reference correct package filter names
|
||||
|
||||
@ -240,19 +179,12 @@ Expect: all OK. Legacy scripts should be replaced with the shared wrapper.
|
||||
```bash
|
||||
REPOS_DIR="/Users/sd9235/code/mygh"
|
||||
echo "=== Root verify scripts ==="
|
||||
for repo in \
|
||||
learning_voice_ai_agent \
|
||||
learning_ai_clock \
|
||||
learning_ai_fastgap \
|
||||
learning_ai_jarvis_jr \
|
||||
learning_ai_peakpulse \
|
||||
learning_ai_notes \
|
||||
learning_ai_flowmonk \
|
||||
learning_ai_trails \
|
||||
learning_ai_local_memory_gpt; do
|
||||
while IFS= read -r repo; do
|
||||
[[ -z "$repo" || "$repo" =~ ^# ]] && continue
|
||||
[[ ! -f "$REPOS_DIR/$repo/package.json" ]] && continue
|
||||
printf "%-40s " "$repo:"
|
||||
node -e "const p=require('$REPOS_DIR/$repo/package.json'); console.log(p.scripts?.verify || 'NONE')" 2>/dev/null
|
||||
done
|
||||
done < /Users/sd9235/code/mygh/learning_ai_common_plat/.windsurf/workflows/repos.txt
|
||||
```
|
||||
|
||||
Review output manually — ensure `--filter` names match actual package names in sub-packages.
|
||||
|
||||
@ -30,22 +30,14 @@ Each push triggers the `.gitea/workflows/ci.yml` workflow on the local runner.
|
||||
|
||||
```bash
|
||||
REPOS_DIR="/Users/sd9235/code/mygh"
|
||||
for repo in \
|
||||
learning_ai_common_plat \
|
||||
learning_voice_ai_agent \
|
||||
learning_multimodal_memory_agents \
|
||||
learning_ai_clock \
|
||||
learning_ai_fastgap \
|
||||
learning_ai_jarvis_jr \
|
||||
learning_ai_peakpulse \
|
||||
learning_ai_notes \
|
||||
learning_ai_flowmonk \
|
||||
learning_ai_trails \
|
||||
learning_ai_local_memory_gpt; do
|
||||
while IFS= read -r repo; do
|
||||
[[ -z "$repo" || "$repo" =~ ^# ]] && continue
|
||||
cd "$REPOS_DIR/$repo" 2>/dev/null || continue
|
||||
# Skip repos without a gitea remote
|
||||
git remote get-url gitea &>/dev/null || continue
|
||||
echo "=== $repo ==="
|
||||
cd "$REPOS_DIR/$repo"
|
||||
git push gitea main 2>&1 | tail -2
|
||||
done
|
||||
done < /Users/sd9235/code/mygh/learning_ai_common_plat/.windsurf/workflows/repos.txt
|
||||
```
|
||||
|
||||
## 4. Wait for jobs to process, then check results
|
||||
@ -54,20 +46,14 @@ Wait ~2 minutes per repo for the runner (capacity=1) to process the queue, then
|
||||
|
||||
```bash
|
||||
REPOS_DIR="/Users/sd9235/code/mygh"
|
||||
for repo in \
|
||||
learning_ai_common_plat \
|
||||
learning_ai_clock \
|
||||
learning_ai_trails \
|
||||
learning_ai_flowmonk \
|
||||
learning_ai_notes \
|
||||
learning_ai_fastgap \
|
||||
learning_ai_jarvis_jr \
|
||||
learning_ai_peakpulse \
|
||||
learning_ai_local_memory_gpt \
|
||||
learning_voice_ai_agent \
|
||||
learning_multimodal_memory_agents; do
|
||||
while IFS= read -r repo; do
|
||||
[[ -z "$repo" || "$repo" =~ ^# ]] && continue
|
||||
cd "$REPOS_DIR/$repo" 2>/dev/null || continue
|
||||
git remote get-url gitea &>/dev/null || continue
|
||||
# Use basename for Gitea API (oss/learning_ai_claw-cowork → learning_ai_claw-cowork)
|
||||
repo_name=$(basename "$repo")
|
||||
echo "=== $repo ==="
|
||||
curl -s -u "bytelyst:bytelyst123" "http://localhost:3300/api/v1/repos/bytelyst/$repo/actions/jobs" | python3 -c "
|
||||
curl -s -u "bytelyst:bytelyst123" "http://localhost:3300/api/v1/repos/bytelyst/$repo_name/actions/jobs" | python3 -c "
|
||||
import sys, json
|
||||
jobs = json.load(sys.stdin).get('jobs', [])
|
||||
if not jobs:
|
||||
@ -80,7 +66,7 @@ else:
|
||||
icon = '✅' if c == 'success' else '❌' if c == 'failure' else '⏳'
|
||||
print(f' {icon} {c:12} {j[\"name\"]}')
|
||||
" 2>/dev/null
|
||||
done
|
||||
done < /Users/sd9235/code/mygh/learning_ai_common_plat/.windsurf/workflows/repos.txt
|
||||
```
|
||||
|
||||
## 5. (Optional) View logs for a failing job
|
||||
|
||||
@ -0,0 +1,139 @@
|
||||
---
|
||||
description: Publish only outdated @bytelyst/* packages to the Gitea npm registry
|
||||
---
|
||||
|
||||
# Publish Outdated Packages to Gitea Registry
|
||||
|
||||
Detects which `@bytelyst/*` packages in `packages/` have local changes compared to what's published in the Gitea npm registry, and republishes only the outdated ones.
|
||||
|
||||
**Registry (auto-detected via `NETWORK` env var):**
|
||||
|
||||
- `NETWORK=corp` → `http://localhost:3300` (SSH tunnel to Azure VM)
|
||||
- `NETWORK=home` → `http://<azure-vm>:3300` (direct, from `~/.gitea_vm_host` or `gitea.bytelyst.com`)
|
||||
|
||||
**Auth:** `GITEA_NPM_TOKEN` env var (auto-loaded from `~/.gitea_npm_token` by `switch-network.sh`)
|
||||
**Token scope required:** `read:package` + `write:package`
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
The Gitea NPM token must have **both** `read:package` and `write:package` scopes.
|
||||
To create/update the token:
|
||||
|
||||
1. Open Gitea token settings:
|
||||
- **Corp network:** `http://localhost:3300/-/user/settings/applications`
|
||||
- **Home network:** `http://<azure-vm>:3300/-/user/settings/applications`
|
||||
2. Create a new token with scopes: `read:package`, `write:package`
|
||||
3. Save it: `echo "<token>" > ~/.gitea_npm_token`
|
||||
4. Reload shell: `source ~/.zshrc`
|
||||
|
||||
---
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Verify prerequisites
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
echo "Token: ${GITEA_NPM_TOKEN:0:5}..." && curl -s -o /dev/null -w "Gitea HTTP: %{http_code}\n" "http://${GITEA_NPM_HOST:-localhost}:3300/"
|
||||
```
|
||||
|
||||
### 2. Dry-run: detect outdated packages
|
||||
|
||||
Run the script in `--dry-run` mode first to see which packages need publishing without making any changes.
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && bash scripts/publish-outdated-gitea-packages.sh --dry-run
|
||||
```
|
||||
|
||||
Review the output:
|
||||
|
||||
- **UP-TO-DATE** — local content matches registry (no action needed)
|
||||
- **OUTDATED** — local content differs from registry (will bump patch version + publish)
|
||||
- **NOT FOUND** — version not in registry (will publish as-is)
|
||||
- **SKIP** — native SDKs or packages without dist/
|
||||
|
||||
### 3. Publish outdated packages
|
||||
|
||||
If the dry-run shows outdated packages, run the script without `--dry-run` to publish them.
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && bash scripts/publish-outdated-gitea-packages.sh
|
||||
```
|
||||
|
||||
The script will:
|
||||
|
||||
1. Build all packages (`pnpm build`)
|
||||
2. Compare each package's local content fingerprint against the registry's
|
||||
3. For outdated packages: auto-bump the patch version (e.g., 0.1.0 -> 0.1.1) and publish
|
||||
4. For not-found packages: publish as-is (current version)
|
||||
5. Print which `package.json` files were bumped so you can commit them
|
||||
|
||||
### 4. Commit version bumps
|
||||
|
||||
After publishing, the script reports which `package.json` files had their version bumped. Commit them:
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && git add packages/*/package.json && git commit -m "chore(packages): bump versions for Gitea registry publish"
|
||||
```
|
||||
|
||||
### 5. (Optional) Publish a single package
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && bash scripts/publish-outdated-gitea-packages.sh --filter @bytelyst/errors
|
||||
```
|
||||
|
||||
### 6. (Optional) Skip the build step
|
||||
|
||||
If you already ran `pnpm build`:
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && bash scripts/publish-outdated-gitea-packages.sh --skip-build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How detection works
|
||||
|
||||
For each package in `packages/`:
|
||||
|
||||
1. `pnpm pack` creates a local tarball
|
||||
2. Downloads the same version's tarball from the registry
|
||||
3. Extracts both and computes SHA-256 fingerprints of all file contents
|
||||
4. If fingerprints differ (or version not in registry) -> marked for publish
|
||||
|
||||
This is metadata-independent (ignores tar timestamps) and catches any source/dist change.
|
||||
|
||||
## Corp proxy handling
|
||||
|
||||
When `NETWORK=corp`, the script automatically:
|
||||
|
||||
- Routes to `http://localhost:3300` (SSH tunnel) instead of the Azure VM
|
||||
- Unsets all `NPM_CONFIG_*` and `*_proxy` env vars for the `npm publish` command
|
||||
|
||||
On both networks, the script:
|
||||
|
||||
- Strips `publishConfig.registry` from tarballs (avoids hardcoded external domain)
|
||||
- Runs `npm publish` from `/tmp` (avoids repo `.npmrc` scoped registry override)
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Fix |
|
||||
| ----------------------------- | ----------------------------------------------------------------------------------------------------- |
|
||||
| `GITEA_NPM_TOKEN is required` | Run `source ~/.zshrc` or `export GITEA_NPM_TOKEN=$(cat ~/.gitea_npm_token)` |
|
||||
| Gitea unreachable | Check if Gitea VM is running. Verify `GITEA_NPM_HOST` resolves |
|
||||
| E401 on publish | Token needs `write:package` scope. Regenerate at `http://localhost:3300/-/user/settings/applications` |
|
||||
| All packages show OUTDATED | Normal after first run. Subsequent runs will show UP-TO-DATE for unchanged packages |
|
||||
| Package has no dist/ | Run `pnpm build` first or don't use `--skip-build` |
|
||||
|
||||
## Script location
|
||||
|
||||
`scripts/publish-outdated-gitea-packages.sh` in `learning_ai_common_plat`
|
||||
|
||||
Run `bash scripts/publish-outdated-gitea-packages.sh --help` for usage.
|
||||
@ -0,0 +1,139 @@
|
||||
---
|
||||
description: Publish only outdated @bytelyst/* pnpm packages to the Gitea npm registry
|
||||
---
|
||||
|
||||
# Publish Outdated pnpm Packages to Gitea Registry
|
||||
|
||||
Detects which `@bytelyst/*` pnpm packages in `packages/` have local changes compared to what's published in the Gitea npm registry, and republishes only the outdated ones.
|
||||
|
||||
**Registry (auto-detected via `NETWORK` env var):**
|
||||
|
||||
- `NETWORK=corp` → `http://localhost:3300` (SSH tunnel to Azure VM)
|
||||
- `NETWORK=home` → `http://<azure-vm>:3300` (direct, from `~/.gitea_vm_host` or `gitea.bytelyst.com`)
|
||||
|
||||
**Auth:** `GITEA_NPM_TOKEN` env var (auto-loaded from `~/.gitea_npm_token` by `switch-network.sh`)
|
||||
**Token scope required:** `read:package` + `write:package`
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
The Gitea NPM token must have **both** `read:package` and `write:package` scopes.
|
||||
To create/update the token:
|
||||
|
||||
1. Open Gitea token settings:
|
||||
- **Corp network:** `http://localhost:3300/-/user/settings/applications`
|
||||
- **Home network:** `http://<azure-vm>:3300/-/user/settings/applications`
|
||||
2. Create a new token with scopes: `read:package`, `write:package`
|
||||
3. Save it: `echo "<token>" > ~/.gitea_npm_token`
|
||||
4. Reload shell: `source ~/.zshrc`
|
||||
|
||||
---
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Verify prerequisites
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
echo "Token: ${GITEA_NPM_TOKEN:0:5}..." && curl -s -o /dev/null -w "Gitea HTTP: %{http_code}\n" "http://${GITEA_NPM_HOST:-localhost}:3300/"
|
||||
```
|
||||
|
||||
### 2. Dry-run: detect outdated packages
|
||||
|
||||
Run the script in `--dry-run` mode first to see which packages need publishing without making any changes.
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && bash scripts/publish-outdated-gitea-packages.sh --dry-run
|
||||
```
|
||||
|
||||
Review the output:
|
||||
|
||||
- **UP-TO-DATE** — local content matches registry (no action needed)
|
||||
- **OUTDATED** — local content differs from registry (will bump patch version + publish)
|
||||
- **NOT FOUND** — version not in registry (will publish as-is)
|
||||
- **SKIP** — native SDKs or packages without dist/
|
||||
|
||||
### 3. Publish outdated packages
|
||||
|
||||
If the dry-run shows outdated packages, run the script without `--dry-run` to publish them.
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && bash scripts/publish-outdated-gitea-packages.sh
|
||||
```
|
||||
|
||||
The script will:
|
||||
|
||||
1. Build all packages (`pnpm build`)
|
||||
2. Compare each package's local content fingerprint against the registry's
|
||||
3. For outdated packages: auto-bump the patch version (e.g., 0.1.0 -> 0.1.1) and publish
|
||||
4. For not-found packages: publish as-is (current version)
|
||||
5. Print which `package.json` files were bumped so you can commit them
|
||||
|
||||
### 4. Commit version bumps
|
||||
|
||||
After publishing, the script reports which `package.json` files had their version bumped. Commit them:
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && git add packages/*/package.json && git commit -m "chore(packages): bump versions for Gitea registry publish"
|
||||
```
|
||||
|
||||
### 5. (Optional) Publish a single package
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && bash scripts/publish-outdated-gitea-packages.sh --filter @bytelyst/errors
|
||||
```
|
||||
|
||||
### 6. (Optional) Skip the build step
|
||||
|
||||
If you already ran `pnpm build`:
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && bash scripts/publish-outdated-gitea-packages.sh --skip-build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How detection works
|
||||
|
||||
For each package in `packages/`:
|
||||
|
||||
1. `pnpm pack` creates a local tarball
|
||||
2. Downloads the same version's tarball from the registry
|
||||
3. Extracts both and computes SHA-256 fingerprints of all file contents
|
||||
4. If fingerprints differ (or version not in registry) -> marked for publish
|
||||
|
||||
This is metadata-independent (ignores tar timestamps) and catches any source/dist change.
|
||||
|
||||
## Corp proxy handling
|
||||
|
||||
When `NETWORK=corp`, the script automatically:
|
||||
|
||||
- Routes to `http://localhost:3300` (SSH tunnel) instead of the Azure VM
|
||||
- Unsets all `NPM_CONFIG_*` and `*_proxy` env vars for the `npm publish` command
|
||||
|
||||
On both networks, the script:
|
||||
|
||||
- Strips `publishConfig.registry` from tarballs (avoids hardcoded external domain)
|
||||
- Runs `npm publish` from `/tmp` (avoids repo `.npmrc` scoped registry override)
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Fix |
|
||||
| ----------------------------- | ----------------------------------------------------------------------------------------------------- |
|
||||
| `GITEA_NPM_TOKEN is required` | Run `source ~/.zshrc` or `export GITEA_NPM_TOKEN=$(cat ~/.gitea_npm_token)` |
|
||||
| Gitea unreachable | Check if Gitea VM is running. Verify `GITEA_NPM_HOST` resolves |
|
||||
| E401 on publish | Token needs `write:package` scope. Regenerate at `http://localhost:3300/-/user/settings/applications` |
|
||||
| All packages show OUTDATED | Normal after first run. Subsequent runs will show UP-TO-DATE for unchanged packages |
|
||||
| Package has no dist/ | Run `pnpm build` first or don't use `--skip-build` |
|
||||
|
||||
## Script location
|
||||
|
||||
`scripts/publish-outdated-gitea-packages.sh` in `learning_ai_common_plat`
|
||||
|
||||
Run `bash scripts/publish-outdated-gitea-packages.sh --help` for usage.
|
||||
@ -30,3 +30,6 @@ learning_ai_productivity_web
|
||||
# --- OSS (subdirectory repos under oss/) ---
|
||||
oss/learning_ai_claw-code-oss
|
||||
oss/learning_ai_claw-cowork
|
||||
|
||||
# -- tooling --
|
||||
learning_ai_mac_tooling
|
||||
|
||||
@ -39,7 +39,7 @@ for entry in \
|
||||
"learning_ai_peakpulse:@peakpulse/backend" \
|
||||
"learning_voice_ai_agent:@lysnrai/backend" \
|
||||
"learning_ai_flowmonk:@flowmonk/backend" \
|
||||
"learning_ai_notes:@notelett/backend"; do
|
||||
"learning_ai_efforise:@efforise/backend"; do
|
||||
|
||||
repo="${entry%%:*}"
|
||||
filter="${entry##*:}"
|
||||
@ -83,7 +83,9 @@ for entry in \
|
||||
"learning_ai_clock:web" \
|
||||
"learning_ai_jarvis_jr:jarvisjr-web" \
|
||||
"learning_voice_ai_agent:user-dashboard-web" \
|
||||
"learning_ai_flowmonk:@flowmonk/web"; do
|
||||
"learning_ai_flowmonk:@flowmonk/web" \
|
||||
"learning_ai_efforise:client" \
|
||||
"learning_ai_local_llms:dashboard"; do
|
||||
|
||||
repo="${entry%%:*}"
|
||||
filter="${entry##*:}"
|
||||
|
||||
@ -53,14 +53,43 @@ function countMatches(text: string, patterns: RegExp[]): number {
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if messages contain image content parts (vision request).
|
||||
* Handles both string content and multipart content arrays.
|
||||
*/
|
||||
function hasImageContent(messages: { role: string; content: string | unknown[] }[]): boolean {
|
||||
for (const msg of messages) {
|
||||
if (Array.isArray(msg.content)) {
|
||||
for (const part of msg.content) {
|
||||
if (
|
||||
typeof part === 'object' &&
|
||||
part !== null &&
|
||||
'type' in part &&
|
||||
(part as { type: string }).type === 'image_url'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Classify a prompt into a category based on keyword matching.
|
||||
* No LLM needed — pure regex heuristics.
|
||||
* Detects vision (image) content and returns 'vision' category when present.
|
||||
*/
|
||||
export function classifyPrompt(
|
||||
messages: { role: string; content: string }[]
|
||||
messages: { role: string; content: string | unknown[] }[]
|
||||
): ClassificationResult {
|
||||
const fullText = messages.map(m => m.content).join('\n');
|
||||
// Check for vision content first — image inputs always classify as 'vision'
|
||||
if (hasImageContent(messages)) {
|
||||
const fullText = messages.map(m => (typeof m.content === 'string' ? m.content : '')).join('\n');
|
||||
return { category: 'vision', estimatedTokens: estimateTokens(fullText) + 1000 };
|
||||
}
|
||||
|
||||
const fullText = messages.map(m => (typeof m.content === 'string' ? m.content : '')).join('\n');
|
||||
const estimatedTokens = estimateTokens(fullText);
|
||||
|
||||
const scores: Record<PromptCategory, number> = {
|
||||
@ -69,6 +98,7 @@ export function classifyPrompt(
|
||||
reasoning: countMatches(fullText, REASONING_PATTERNS),
|
||||
creative: countMatches(fullText, CREATIVE_PATTERNS),
|
||||
general: 1, // baseline
|
||||
vision: 0,
|
||||
};
|
||||
|
||||
// Pick highest scoring category
|
||||
|
||||
@ -24,8 +24,9 @@ export const PAID_PROVIDERS: ProviderConfig[] = [
|
||||
id: 'gpt-4o',
|
||||
label: 'GPT-4o',
|
||||
contextWindow: 128_000,
|
||||
strengths: ['general', 'reasoning', 'code', 'creative'],
|
||||
strengths: ['general', 'reasoning', 'code', 'creative', 'vision'],
|
||||
speedTier: 2,
|
||||
supportsVision: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@ -18,6 +18,12 @@ export function createRoundRobinState(): Map<string, number> {
|
||||
function scoreModel(model: ModelConfig, category: PromptCategory): number {
|
||||
let score = 0;
|
||||
|
||||
// Vision requests require vision-capable models
|
||||
if (category === 'vision') {
|
||||
if (!model.supportsVision) return -1; // Exclude non-vision models
|
||||
score += 15; // Strong boost for vision capability
|
||||
}
|
||||
|
||||
// Direct strength match is the strongest signal
|
||||
if (model.strengths.includes(category)) {
|
||||
score += 10;
|
||||
@ -56,6 +62,7 @@ export function selectCandidates(
|
||||
if (!health.isHealthy(provider.name, model.id)) continue;
|
||||
|
||||
const score = scoreModel(model, category);
|
||||
if (score < 0) continue; // Skip incompatible models (e.g. non-vision for vision requests)
|
||||
candidates.push({ provider, model, score });
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,10 @@ export interface ModelConfig {
|
||||
strengths: PromptCategory[];
|
||||
/** Relative speed tier: 1 = fastest, 3 = slowest */
|
||||
speedTier: 1 | 2 | 3;
|
||||
/** Whether the model supports vision (image) inputs */
|
||||
supportsVision?: boolean;
|
||||
/** Whether the model supports text embedding generation */
|
||||
supportsEmbedding?: boolean;
|
||||
}
|
||||
|
||||
export interface ProviderConfig {
|
||||
@ -32,7 +36,7 @@ export interface ProviderConfig {
|
||||
|
||||
// ── Prompt Classification ──────────────────────────────────────
|
||||
|
||||
export type PromptCategory = 'code' | 'math' | 'reasoning' | 'creative' | 'general';
|
||||
export type PromptCategory = 'code' | 'math' | 'reasoning' | 'creative' | 'general' | 'vision';
|
||||
|
||||
export interface ClassificationResult {
|
||||
category: PromptCategory;
|
||||
|
||||
45
pnpm-lock.yaml
generated
45
pnpm-lock.yaml
generated
@ -194,7 +194,7 @@ importers:
|
||||
version: 9.39.2(jiti@2.6.1)
|
||||
eslint-config-next:
|
||||
specifier: 16.1.6
|
||||
version: 16.1.6(@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
version: 16.1.6(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
husky:
|
||||
specifier: ^9.0.0
|
||||
version: 9.1.7
|
||||
@ -291,7 +291,7 @@ importers:
|
||||
version: 9.39.2(jiti@2.6.1)
|
||||
eslint-config-next:
|
||||
specifier: 16.1.6
|
||||
version: 16.1.6(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
version: 16.1.6(@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
husky:
|
||||
specifier: ^9.0.0
|
||||
version: 9.1.7
|
||||
@ -19570,23 +19570,23 @@ snapshots:
|
||||
chai: 6.2.2
|
||||
tinyrainbow: 3.0.3
|
||||
|
||||
'@vitest/mocker@3.2.4(msw@2.12.10(@types/node@20.19.33)(typescript@5.9.3))(vite@6.4.1(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))':
|
||||
'@vitest/mocker@3.2.4(msw@2.12.10(@types/node@20.19.33)(typescript@5.9.3))(vite@7.3.1(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))':
|
||||
dependencies:
|
||||
'@vitest/spy': 3.2.4
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.21
|
||||
optionalDependencies:
|
||||
msw: 2.12.10(@types/node@20.19.33)(typescript@5.9.3)
|
||||
vite: 6.4.1(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
|
||||
vite: 7.3.1(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
|
||||
|
||||
'@vitest/mocker@3.2.4(msw@2.12.10(@types/node@22.19.11)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))':
|
||||
'@vitest/mocker@3.2.4(msw@2.12.10(@types/node@22.19.11)(typescript@5.9.3))(vite@7.3.1(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))':
|
||||
dependencies:
|
||||
'@vitest/spy': 3.2.4
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.21
|
||||
optionalDependencies:
|
||||
msw: 2.12.10(@types/node@22.19.11)(typescript@5.9.3)
|
||||
vite: 6.4.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
|
||||
vite: 7.3.1(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
|
||||
|
||||
'@vitest/mocker@4.0.18(msw@2.12.10(@types/node@20.19.33)(typescript@5.9.3))(vite@7.3.1(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))':
|
||||
dependencies:
|
||||
@ -20897,7 +20897,7 @@ snapshots:
|
||||
'@next/eslint-plugin-next': 16.1.6
|
||||
eslint: 9.39.2(jiti@2.6.1)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@2.6.1))
|
||||
@ -20920,7 +20920,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)):
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)):
|
||||
dependencies:
|
||||
'@nolyfill/is-core-module': 1.0.39
|
||||
debug: 4.4.3
|
||||
@ -20935,6 +20935,21 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)):
|
||||
dependencies:
|
||||
'@nolyfill/is-core-module': 1.0.39
|
||||
debug: 4.4.3
|
||||
eslint: 9.39.2(jiti@2.6.1)
|
||||
get-tsconfig: 4.13.6
|
||||
is-bun-module: 2.0.0
|
||||
stable-hash: 0.0.5
|
||||
tinyglobby: 0.2.15
|
||||
unrs-resolver: 1.11.1
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
@ -20946,14 +20961,14 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)):
|
||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
eslint: 9.39.2(jiti@2.6.1)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@ -20997,7 +21012,7 @@ snapshots:
|
||||
doctrine: 2.1.0
|
||||
eslint: 9.39.2(jiti@2.6.1)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.56.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.16.1
|
||||
is-glob: 4.0.3
|
||||
@ -25587,7 +25602,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/chai': 5.2.3
|
||||
'@vitest/expect': 3.2.4
|
||||
'@vitest/mocker': 3.2.4(msw@2.12.10(@types/node@20.19.33)(typescript@5.9.3))(vite@6.4.1(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))
|
||||
'@vitest/mocker': 3.2.4(msw@2.12.10(@types/node@20.19.33)(typescript@5.9.3))(vite@7.3.1(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))
|
||||
'@vitest/pretty-format': 3.2.4
|
||||
'@vitest/runner': 3.2.4
|
||||
'@vitest/snapshot': 3.2.4
|
||||
@ -25605,7 +25620,7 @@ snapshots:
|
||||
tinyglobby: 0.2.15
|
||||
tinypool: 1.1.1
|
||||
tinyrainbow: 2.0.0
|
||||
vite: 6.4.1(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
|
||||
vite: 7.3.1(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
|
||||
vite-node: 3.2.4(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
@ -25631,7 +25646,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/chai': 5.2.3
|
||||
'@vitest/expect': 3.2.4
|
||||
'@vitest/mocker': 3.2.4(msw@2.12.10(@types/node@22.19.11)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))
|
||||
'@vitest/mocker': 3.2.4(msw@2.12.10(@types/node@22.19.11)(typescript@5.9.3))(vite@7.3.1(@types/node@20.19.33)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))
|
||||
'@vitest/pretty-format': 3.2.4
|
||||
'@vitest/runner': 3.2.4
|
||||
'@vitest/snapshot': 3.2.4
|
||||
@ -25649,7 +25664,7 @@ snapshots:
|
||||
tinyglobby: 0.2.15
|
||||
tinypool: 1.1.1
|
||||
tinyrainbow: 2.0.0
|
||||
vite: 6.4.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
|
||||
vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
|
||||
vite-node: 3.2.4(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user