diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/.last-refresh.log b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/.last-refresh.log index 9d62108d..2cd6c6fb 100644 --- a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/.last-refresh.log +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/.last-refresh.log @@ -1,9 +1,9 @@ -Last refresh: 2026-03-21T06:00:10Z (2026-03-20 23:00:10 PDT) -Cascade conversations: 50 (411M) -Memories: 91 +Last refresh: 2026-03-21T21:44:43Z (2026-03-21 14:44:44 PDT) +Cascade conversations: 50 (387M) +Memories: 95 Implicit context: 20 -Code tracker dirs: 132 -File edit history: 3365 entries -Workspace storage: 35 workspaces +Code tracker dirs: 89 +File edit history: 3457 entries +Workspace storage: 36 workspaces Repo docs: 7 files across 2 repos Repo workflows: 43 files across 10 repos diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_clock/README.md b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_clock/README.md index f8fe496c..dc0ad025 100644 --- a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_clock/README.md +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_clock/README.md @@ -5,7 +5,6 @@ Product-specific workflows for ChronoMind AI-powered contextual clock. ## Repo-management Workflows Cross-repo workflows are maintained centrally in `learning_ai_common_plat/.windsurf/workflows/`: - - `/repo_sync-repos` — Pull latest from all repos - `/repo_commit-workspace` — Commit changes across repos - `/repo_backup-main-branch` — Backup main branches diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_clock/refresh-chat-history.md b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_clock/refresh-chat-history.md index 8da42c43..b52ca018 100644 --- a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_clock/refresh-chat-history.md +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_clock/refresh-chat-history.md @@ -9,16 +9,16 @@ Auto-discovers new repos, updates symlinks, and re-copies docs + workflows. ## Covered Repos (All 8 workspaces) -| Repo | Product | Workflows | Docs | -| ----------------------------------- | ---------------- | --------- | ---- | -| `learning_voice_ai_agent` | LysnrAI | ✅ | ✅ | -| `learning_multimodal_memory_agents` | MindLyst | ✅ | ✅ | -| `learning_ai_clock` | ChronoMind | ✅ | — | -| `learning_ai_peakpulse` | PeakPulse | ✅ | — | -| `learning_ai_fastgap` | NomGap | ✅ | — | -| `learning_ai_jarvis_jr` | JarvisJr | ✅ | — | -| `learning_ai_common_plat` | Common Platform | ✅ | — | -| `learning_agent_monitoring_fx` | Agent Monitoring | ✅ | — | +| Repo | Product | Workflows | Docs | +|------|---------|-----------|------| +| `learning_voice_ai_agent` | LysnrAI | ✅ | ✅ | +| `learning_multimodal_memory_agents` | MindLyst | ✅ | ✅ | +| `learning_ai_clock` | ChronoMind | ✅ | — | +| `learning_ai_peakpulse` | PeakPulse | ✅ | — | +| `learning_ai_fastgap` | NomGap | ✅ | — | +| `learning_ai_jarvis_jr` | JarvisJr | ✅ | — | +| `learning_ai_common_plat` | Common Platform | ✅ | — | +| `learning_agent_monitoring_fx` | Agent Monitoring | ✅ | — | ## Steps diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_common_plat/repo_update-agent-docs.md b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_common_plat/repo_update-agent-docs.md index 43fddf90..51bbe0f5 100644 --- a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_common_plat/repo_update-agent-docs.md +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_common_plat/repo_update-agent-docs.md @@ -8,16 +8,16 @@ Regenerates all 8 AI agent configuration files across all repos in the workspace ## Files Generated Per Repo -| File | Tool | -| --------------------------------- | ----------------------------------------------- | -| `AGENTS.md` | Universal (OpenAI Codex, Claude, Copilot, etc.) | -| `CLAUDE.md` | Claude Code | -| `.cursorrules` | Cursor AI | -| `.github/copilot-instructions.md` | GitHub Copilot | -| `.windsurfrules` | Windsurf / Cascade | -| `.clinerules` | Cline / Roo Code | -| `.aider.conf.yml` | Aider | -| `.editorconfig` | All editors | +| File | Tool | +|------|------| +| `AGENTS.md` | Universal (OpenAI Codex, Claude, Copilot, etc.) | +| `CLAUDE.md` | Claude Code | +| `.cursorrules` | Cursor AI | +| `.github/copilot-instructions.md` | GitHub Copilot | +| `.windsurfrules` | Windsurf / Cascade | +| `.clinerules` | Cline / Roo Code | +| `.aider.conf.yml` | Aider | +| `.editorconfig` | All editors | ## Steps diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_fastgap/README.md b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_fastgap/README.md index 4c7b30c5..0df5b681 100644 --- a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_fastgap/README.md +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_fastgap/README.md @@ -5,7 +5,6 @@ Product-specific workflows for NomGap fasting visualization app. ## Repo-management Workflows Cross-repo workflows are maintained centrally in `learning_ai_common_plat/.windsurf/workflows/`: - - `/repo_sync-repos` — Pull latest from all repos - `/repo_commit-workspace` — Commit changes across repos - `/repo_backup-main-branch` — Backup main branches diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_fastgap/refresh-chat-history.md b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_fastgap/refresh-chat-history.md index 8da42c43..b52ca018 100644 --- a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_fastgap/refresh-chat-history.md +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_fastgap/refresh-chat-history.md @@ -9,16 +9,16 @@ Auto-discovers new repos, updates symlinks, and re-copies docs + workflows. ## Covered Repos (All 8 workspaces) -| Repo | Product | Workflows | Docs | -| ----------------------------------- | ---------------- | --------- | ---- | -| `learning_voice_ai_agent` | LysnrAI | ✅ | ✅ | -| `learning_multimodal_memory_agents` | MindLyst | ✅ | ✅ | -| `learning_ai_clock` | ChronoMind | ✅ | — | -| `learning_ai_peakpulse` | PeakPulse | ✅ | — | -| `learning_ai_fastgap` | NomGap | ✅ | — | -| `learning_ai_jarvis_jr` | JarvisJr | ✅ | — | -| `learning_ai_common_plat` | Common Platform | ✅ | — | -| `learning_agent_monitoring_fx` | Agent Monitoring | ✅ | — | +| Repo | Product | Workflows | Docs | +|------|---------|-----------|------| +| `learning_voice_ai_agent` | LysnrAI | ✅ | ✅ | +| `learning_multimodal_memory_agents` | MindLyst | ✅ | ✅ | +| `learning_ai_clock` | ChronoMind | ✅ | — | +| `learning_ai_peakpulse` | PeakPulse | ✅ | — | +| `learning_ai_fastgap` | NomGap | ✅ | — | +| `learning_ai_jarvis_jr` | JarvisJr | ✅ | — | +| `learning_ai_common_plat` | Common Platform | ✅ | — | +| `learning_agent_monitoring_fx` | Agent Monitoring | ✅ | — | ## Steps diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_flowmonk/README.md b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_flowmonk/README.md index 43d35651..89ed7a05 100644 --- a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_flowmonk/README.md +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_flowmonk/README.md @@ -5,7 +5,6 @@ Product-specific workflows for FlowMonk, the agent-first planning and execution ## Repo-management Workflows Cross-repo workflows are maintained centrally in `learning_ai_common_plat/.windsurf/workflows/`: - - `/repo_sync-repos` — Pull latest from all repos - `/repo_commit-workspace` — Commit changes across repos - `/repo_backup-main-branch` — Backup main branches diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_jarvis_jr/README.md b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_jarvis_jr/README.md index 67b9d771..c534fb93 100644 --- a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_jarvis_jr/README.md +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_jarvis_jr/README.md @@ -5,7 +5,6 @@ Product-specific workflows for JarvisJr voice coaching app. ## Repo-management Workflows Cross-repo workflows are maintained centrally in `learning_ai_common_plat/.windsurf/workflows/`: - - `/repo_sync-repos` — Pull latest from all repos - `/repo_commit-workspace` — Commit changes across repos - `/repo_backup-main-branch` — Backup main branches diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_jarvis_jr/refresh-chat-history.md b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_jarvis_jr/refresh-chat-history.md index 8da42c43..b52ca018 100644 --- a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_jarvis_jr/refresh-chat-history.md +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_jarvis_jr/refresh-chat-history.md @@ -9,16 +9,16 @@ Auto-discovers new repos, updates symlinks, and re-copies docs + workflows. ## Covered Repos (All 8 workspaces) -| Repo | Product | Workflows | Docs | -| ----------------------------------- | ---------------- | --------- | ---- | -| `learning_voice_ai_agent` | LysnrAI | ✅ | ✅ | -| `learning_multimodal_memory_agents` | MindLyst | ✅ | ✅ | -| `learning_ai_clock` | ChronoMind | ✅ | — | -| `learning_ai_peakpulse` | PeakPulse | ✅ | — | -| `learning_ai_fastgap` | NomGap | ✅ | — | -| `learning_ai_jarvis_jr` | JarvisJr | ✅ | — | -| `learning_ai_common_plat` | Common Platform | ✅ | — | -| `learning_agent_monitoring_fx` | Agent Monitoring | ✅ | — | +| Repo | Product | Workflows | Docs | +|------|---------|-----------|------| +| `learning_voice_ai_agent` | LysnrAI | ✅ | ✅ | +| `learning_multimodal_memory_agents` | MindLyst | ✅ | ✅ | +| `learning_ai_clock` | ChronoMind | ✅ | — | +| `learning_ai_peakpulse` | PeakPulse | ✅ | — | +| `learning_ai_fastgap` | NomGap | ✅ | — | +| `learning_ai_jarvis_jr` | JarvisJr | ✅ | — | +| `learning_ai_common_plat` | Common Platform | ✅ | — | +| `learning_agent_monitoring_fx` | Agent Monitoring | ✅ | — | ## Steps diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_mac_tooling/network-audit.md b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_mac_tooling/network-audit.md index b5ac463d..fa537dc4 100644 --- a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_mac_tooling/network-audit.md +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_mac_tooling/network-audit.md @@ -9,17 +9,14 @@ The primary interface for running audits is the **Sensor Audit Dashboard**. ## Steps 1. **Ensure the dashboard is running** - ```bash /Users/sd9235/code/mygh/learning_ai_mac_tooling/audit-ctl.sh start ``` 2. **Open the dashboard** - ```bash /Users/sd9235/code/mygh/learning_ai_mac_tooling/audit-ctl.sh open ``` - Navigate to **Run Audit** in the sidebar. 3. **Choose a mode in the dashboard UI**: diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/README.md b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/README.md index c13d9e87..8b6d560c 100644 --- a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/README.md +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/README.md @@ -5,7 +5,6 @@ Product-specific workflows for PeakPulse sensor-driven adventure tracker. ## Repo-management Workflows Cross-repo workflows are maintained centrally in `learning_ai_common_plat/.windsurf/workflows/`: - - `/repo_sync-repos` — Pull latest from all repos - `/repo_commit-workspace` — Commit changes across repos - `/repo_backup-main-branch` — Backup main branches diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/production-readiness.md b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/production-readiness.md index 33f8ab9b..5148dc9a 100644 --- a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/production-readiness.md +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/production-readiness.md @@ -19,12 +19,12 @@ cd /Users/sd9235/code/mygh/learning_ai_peakpulse/ios && xcodegen generate 3. Build all iOS targets: ```bash -cd /Users/sd9235/code/mygh/learning_ai_peakpulse/ios && xcodebuild -project PeakPulse.xcodeproj -scheme PeakPulse -destination 'platform=iOS Simulator,name=iPhone 16' -quiet build +cd /Users/sd9235/code/mygh/learning_ai_peakpulse/ios && xcodebuild -project PeakPulse.xcodeproj -scheme PeakPulse -destination 'platform=iOS Simulator,name=iPhone 17' -quiet build ``` 4. Run unit tests: ```bash -cd /Users/sd9235/code/mygh/learning_ai_peakpulse/ios && xcodebuild -project PeakPulse.xcodeproj -scheme PeakPulse -destination 'platform=iOS Simulator,name=iPhone 16' -quiet test +cd /Users/sd9235/code/mygh/learning_ai_peakpulse/ios && xcodebuild -project PeakPulse.xcodeproj -scheme PeakPulse -destination 'platform=iOS Simulator,name=iPhone 17' -quiet test ``` 5. Verify no print() statements in production code: @@ -51,9 +51,9 @@ cat /Users/sd9235/code/mygh/learning_ai_peakpulse/ios/PeakPulse/PeakPulse.entitl cat /Users/sd9235/code/mygh/learning_ai_peakpulse/.env.example ``` -9. Run platform-service peak-sessions tests: +9. Run backend tests: ```bash -cd /Users/sd9235/code/mygh/learning_ai_common_plat && npx vitest run services/platform-service/src/modules/peak-sessions/peak-sessions.test.ts +cd /Users/sd9235/code/mygh/learning_ai_peakpulse/backend && npm test ``` 10. Verify CI workflow exists and is valid: diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/refresh-chat-history.md b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/refresh-chat-history.md index 8da42c43..b52ca018 100644 --- a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/refresh-chat-history.md +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_ai_peakpulse/refresh-chat-history.md @@ -9,16 +9,16 @@ Auto-discovers new repos, updates symlinks, and re-copies docs + workflows. ## Covered Repos (All 8 workspaces) -| Repo | Product | Workflows | Docs | -| ----------------------------------- | ---------------- | --------- | ---- | -| `learning_voice_ai_agent` | LysnrAI | ✅ | ✅ | -| `learning_multimodal_memory_agents` | MindLyst | ✅ | ✅ | -| `learning_ai_clock` | ChronoMind | ✅ | — | -| `learning_ai_peakpulse` | PeakPulse | ✅ | — | -| `learning_ai_fastgap` | NomGap | ✅ | — | -| `learning_ai_jarvis_jr` | JarvisJr | ✅ | — | -| `learning_ai_common_plat` | Common Platform | ✅ | — | -| `learning_agent_monitoring_fx` | Agent Monitoring | ✅ | — | +| Repo | Product | Workflows | Docs | +|------|---------|-----------|------| +| `learning_voice_ai_agent` | LysnrAI | ✅ | ✅ | +| `learning_multimodal_memory_agents` | MindLyst | ✅ | ✅ | +| `learning_ai_clock` | ChronoMind | ✅ | — | +| `learning_ai_peakpulse` | PeakPulse | ✅ | — | +| `learning_ai_fastgap` | NomGap | ✅ | — | +| `learning_ai_jarvis_jr` | JarvisJr | ✅ | — | +| `learning_ai_common_plat` | Common Platform | ✅ | — | +| `learning_agent_monitoring_fx` | Agent Monitoring | ✅ | — | ## Steps diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_multimodal_memory_agents/README.md b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_multimodal_memory_agents/README.md index 314945c6..d6213a68 100644 --- a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_multimodal_memory_agents/README.md +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_multimodal_memory_agents/README.md @@ -5,7 +5,6 @@ Product-specific workflows for MindLyst role-based life OS. ## Repo-management Workflows Cross-repo workflows are maintained centrally in `learning_ai_common_plat/.windsurf/workflows/`: - - `/repo_sync-repos` — Pull latest from all repos - `/repo_commit-workspace` — Commit changes across repos - `/repo_backup-main-branch` — Backup main branches diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_multimodal_memory_agents/refresh-chat-history.md b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_multimodal_memory_agents/refresh-chat-history.md index 8da42c43..b52ca018 100644 --- a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_multimodal_memory_agents/refresh-chat-history.md +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_multimodal_memory_agents/refresh-chat-history.md @@ -9,16 +9,16 @@ Auto-discovers new repos, updates symlinks, and re-copies docs + workflows. ## Covered Repos (All 8 workspaces) -| Repo | Product | Workflows | Docs | -| ----------------------------------- | ---------------- | --------- | ---- | -| `learning_voice_ai_agent` | LysnrAI | ✅ | ✅ | -| `learning_multimodal_memory_agents` | MindLyst | ✅ | ✅ | -| `learning_ai_clock` | ChronoMind | ✅ | — | -| `learning_ai_peakpulse` | PeakPulse | ✅ | — | -| `learning_ai_fastgap` | NomGap | ✅ | — | -| `learning_ai_jarvis_jr` | JarvisJr | ✅ | — | -| `learning_ai_common_plat` | Common Platform | ✅ | — | -| `learning_agent_monitoring_fx` | Agent Monitoring | ✅ | — | +| Repo | Product | Workflows | Docs | +|------|---------|-----------|------| +| `learning_voice_ai_agent` | LysnrAI | ✅ | ✅ | +| `learning_multimodal_memory_agents` | MindLyst | ✅ | ✅ | +| `learning_ai_clock` | ChronoMind | ✅ | — | +| `learning_ai_peakpulse` | PeakPulse | ✅ | — | +| `learning_ai_fastgap` | NomGap | ✅ | — | +| `learning_ai_jarvis_jr` | JarvisJr | ✅ | — | +| `learning_ai_common_plat` | Common Platform | ✅ | — | +| `learning_agent_monitoring_fx` | Agent Monitoring | ✅ | — | ## Steps diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_multimodal_memory_agents/release-testflight.md b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_multimodal_memory_agents/release-testflight.md index 24ca6094..265f53ca 100644 --- a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_multimodal_memory_agents/release-testflight.md +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_multimodal_memory_agents/release-testflight.md @@ -49,7 +49,6 @@ See `iosApp/README_SETUP.md` for more details. ### 4. Verify Gradle/KMP builds // turbo - ```bash cd mindlyst-native && ./gradlew :shared:compileKotlinIosSimulatorArm64 ``` @@ -67,7 +66,6 @@ cd mindlyst-native && ./gradlew :shared:compileKotlinIosArm64 ``` For simulator testing: - ```bash cd mindlyst-native && ./gradlew :shared:compileKotlinIosSimulatorArm64 ``` @@ -75,7 +73,6 @@ cd mindlyst-native && ./gradlew :shared:compileKotlinIosSimulatorArm64 ### 2. Embed the shared framework in Xcode If not already linked: - ```bash cd mindlyst-native && ./gradlew :shared:embedAndSignAppleFrameworkForXcode ``` @@ -90,7 +87,6 @@ Or manually: open Xcode → Target → General → Build. ### 4. Clean build folder // turbo - ```bash xcodebuild clean -project mindlyst-native/MindLyst.xcodeproj -scheme MindLyst -configuration Release 2>&1 | tail -3 ``` @@ -144,24 +140,24 @@ The `app-store-connect` export method auto-uploads the IPA. ### Key Paths -| Path | Purpose | -| ------------------------------------------- | ---------------------------------------- | -| `mindlyst-native/iosApp/` | Swift UI source files | -| `mindlyst-native/shared/` | KMP shared module (business logic) | -| `mindlyst-native/shared/build.gradle.kts` | KMP build config (iOS targets) | -| `mindlyst-native/gradle/libs.versions.toml` | Version catalog | -| `mindlyst-native/MindLyst.xcodeproj/` | Xcode project (must be created manually) | -| `scripts/MindLystExportOptions.plist` | Export options for TestFlight upload | +| Path | Purpose | +|------|---------| +| `mindlyst-native/iosApp/` | Swift UI source files | +| `mindlyst-native/shared/` | KMP shared module (business logic) | +| `mindlyst-native/shared/build.gradle.kts` | KMP build config (iOS targets) | +| `mindlyst-native/gradle/libs.versions.toml` | Version catalog | +| `mindlyst-native/MindLyst.xcodeproj/` | Xcode project (must be created manually) | +| `scripts/MindLystExportOptions.plist` | Export options for TestFlight upload | ### Build Identity -| Field | Value | -| ------------- | ----------------------- | -| Team ID | `748N7QPX7J` | -| Bundle ID | `com.mindlyst.MindLyst` | -| Signing | Automatic | -| KMP Framework | `shared` (static) | -| Min iOS | 16.0 | +| Field | Value | +|-------|-------| +| Team ID | `748N7QPX7J` | +| Bundle ID | `com.mindlyst.MindLyst` | +| Signing | Automatic | +| KMP Framework | `shared` (static) | +| Min iOS | 16.0 | ### KMP Architecture @@ -177,12 +173,12 @@ All business logic lives in `shared/src/commonMain/`. iOS code in `iosApp/` is a ### Troubleshooting -| Problem | Fix | -| ----------------------------- | ------------------------------------------------------------------------ | -| "No signing certificate" | Xcode → Settings → Accounts → Manage Certificates → + Apple Distribution | -| "Provisioning profile" error | Xcode → Target → Signing → Enable "Automatically manage signing" | -| "Build number already exists" | Increment build number in step 3 | -| KMP build fails | Check Java 17: `java -version`. Install: `brew install openjdk@17` | -| "shared.framework not found" | Run `./gradlew :shared:embedAndSignAppleFrameworkForXcode` | -| Gradle SSL proxy error | Build on home network (corporate proxy blocks Gradle repos) | -| Processing >30 min | Check [Apple system status](https://developer.apple.com/system-status/) | +| Problem | Fix | +|---------|-----| +| "No signing certificate" | Xcode → Settings → Accounts → Manage Certificates → + Apple Distribution | +| "Provisioning profile" error | Xcode → Target → Signing → Enable "Automatically manage signing" | +| "Build number already exists" | Increment build number in step 3 | +| KMP build fails | Check Java 17: `java -version`. Install: `brew install openjdk@17` | +| "shared.framework not found" | Run `./gradlew :shared:embedAndSignAppleFrameworkForXcode` | +| Gradle SSL proxy error | Build on home network (corporate proxy blocks Gradle repos) | +| Processing >30 min | Check [Apple system status](https://developer.apple.com/system-status/) | diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_voice_ai_agent/README.md b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_voice_ai_agent/README.md index c6a2510f..c04ed392 100644 --- a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_voice_ai_agent/README.md +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_voice_ai_agent/README.md @@ -5,7 +5,6 @@ Product-specific workflows for LysnrAI voice-to-text dictation platform. ## Repo-management Workflows Cross-repo workflows are maintained centrally in `learning_ai_common_plat/.windsurf/workflows/`: - - `/repo_sync-repos` — Pull latest from all repos - `/repo_commit-workspace` — Commit changes across repos - `/repo_backup-main-branch` — Backup main branches diff --git a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_voice_ai_agent/refresh-chat-history.md b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_voice_ai_agent/refresh-chat-history.md index 8da42c43..b52ca018 100644 --- a/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_voice_ai_agent/refresh-chat-history.md +++ b/__LOCAL_LLMs/AI_IDE_CHAT_HISTORY/WINDSURF/repo_workflows/learning_voice_ai_agent/refresh-chat-history.md @@ -9,16 +9,16 @@ Auto-discovers new repos, updates symlinks, and re-copies docs + workflows. ## Covered Repos (All 8 workspaces) -| Repo | Product | Workflows | Docs | -| ----------------------------------- | ---------------- | --------- | ---- | -| `learning_voice_ai_agent` | LysnrAI | ✅ | ✅ | -| `learning_multimodal_memory_agents` | MindLyst | ✅ | ✅ | -| `learning_ai_clock` | ChronoMind | ✅ | — | -| `learning_ai_peakpulse` | PeakPulse | ✅ | — | -| `learning_ai_fastgap` | NomGap | ✅ | — | -| `learning_ai_jarvis_jr` | JarvisJr | ✅ | — | -| `learning_ai_common_plat` | Common Platform | ✅ | — | -| `learning_agent_monitoring_fx` | Agent Monitoring | ✅ | — | +| Repo | Product | Workflows | Docs | +|------|---------|-----------|------| +| `learning_voice_ai_agent` | LysnrAI | ✅ | ✅ | +| `learning_multimodal_memory_agents` | MindLyst | ✅ | ✅ | +| `learning_ai_clock` | ChronoMind | ✅ | — | +| `learning_ai_peakpulse` | PeakPulse | ✅ | — | +| `learning_ai_fastgap` | NomGap | ✅ | — | +| `learning_ai_jarvis_jr` | JarvisJr | ✅ | — | +| `learning_ai_common_plat` | Common Platform | ✅ | — | +| `learning_agent_monitoring_fx` | Agent Monitoring | ✅ | — | ## Steps diff --git a/dashboards/admin-web/src/app/(dashboard)/agent-evals/page.tsx b/dashboards/admin-web/src/app/(dashboard)/agent-evals/page.tsx index 1f177992..22d472e0 100644 --- a/dashboards/admin-web/src/app/(dashboard)/agent-evals/page.tsx +++ b/dashboards/admin-web/src/app/(dashboard)/agent-evals/page.tsx @@ -1,5 +1,7 @@ 'use client'; +import { createProxyFetch } from '@/lib/proxy-fetch'; + import { useState, useEffect, useCallback } from 'react'; import { FlaskConical, @@ -67,13 +69,7 @@ function formatDate(iso: string) { }); } -async function apiFetch(path: string, opts?: RequestInit) { - const res = await fetch(`/api/agent-evals/${path}`, { - headers: { 'Content-Type': 'application/json' }, - ...opts, - }); - return res.json(); -} +const apiFetch = createProxyFetch('/api/agent-evals'); export default function AgentEvalsPage() { const [suites, setSuites] = useState([]); diff --git a/dashboards/admin-web/src/app/(dashboard)/ai-budgets/page.tsx b/dashboards/admin-web/src/app/(dashboard)/ai-budgets/page.tsx index 8e4d9364..957c553d 100644 --- a/dashboards/admin-web/src/app/(dashboard)/ai-budgets/page.tsx +++ b/dashboards/admin-web/src/app/(dashboard)/ai-budgets/page.tsx @@ -1,5 +1,7 @@ 'use client'; +import { createProxyFetch } from '@/lib/proxy-fetch'; + import { useState, useEffect, useCallback } from 'react'; import { Coins, Plus, MoreHorizontal, AlertTriangle, TrendingUp, Trash2 } from 'lucide-react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; @@ -73,13 +75,7 @@ function formatDate(iso: string) { }); } -async function apiFetch(path: string, opts?: RequestInit) { - const res = await fetch(`/api/ai-budgets/${path}`, { - headers: { 'Content-Type': 'application/json' }, - ...opts, - }); - return res.json(); -} +const apiFetch = createProxyFetch('/api/ai-budgets'); export default function AIBudgetsPage() { const [policies, setPolicies] = useState([]); diff --git a/dashboards/admin-web/src/app/(dashboard)/delivery/page.tsx b/dashboards/admin-web/src/app/(dashboard)/delivery/page.tsx index f09e7c8c..cbb71d9d 100644 --- a/dashboards/admin-web/src/app/(dashboard)/delivery/page.tsx +++ b/dashboards/admin-web/src/app/(dashboard)/delivery/page.tsx @@ -1,5 +1,7 @@ 'use client'; +import { createProxyFetch } from '@/lib/proxy-fetch'; + import { useState, useEffect, useCallback } from 'react'; import { Mail, RotateCcw } from 'lucide-react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; @@ -49,13 +51,7 @@ function formatDate(iso: string) { }); } -async function apiFetch(path: string, opts?: RequestInit) { - const res = await fetch(`/api/delivery/${path}`, { - headers: { 'Content-Type': 'application/json' }, - ...opts, - }); - return res.json(); -} +const apiFetch = createProxyFetch('/api/delivery'); export default function DeliveryPage() { const [entries, setEntries] = useState([]); diff --git a/dashboards/admin-web/src/app/(dashboard)/event-subscriptions/page.tsx b/dashboards/admin-web/src/app/(dashboard)/event-subscriptions/page.tsx index 5bfdcbe3..4d3c0c09 100644 --- a/dashboards/admin-web/src/app/(dashboard)/event-subscriptions/page.tsx +++ b/dashboards/admin-web/src/app/(dashboard)/event-subscriptions/page.tsx @@ -1,5 +1,7 @@ 'use client'; +import { createProxyFetch } from '@/lib/proxy-fetch'; + import { useState, useEffect, useCallback } from 'react'; import { Radio, Plus, MoreHorizontal, Trash2 } from 'lucide-react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; @@ -47,13 +49,7 @@ function formatDate(iso: string) { }); } -async function apiFetch(path: string, opts?: RequestInit) { - const res = await fetch(`/api/event-subscriptions/${path}`, { - headers: { 'Content-Type': 'application/json' }, - ...opts, - }); - return res.json(); -} +const apiFetch = createProxyFetch('/api/event-subscriptions'); export default function EventSubscriptionsPage() { const [subs, setSubs] = useState([]); @@ -65,7 +61,7 @@ export default function EventSubscriptionsPage() { const loadData = useCallback(async () => { setLoading(true); - const data = await apiFetch('list'); + const data = await apiFetch(''); setSubs( Array.isArray(data?.subscriptions) ? data.subscriptions : Array.isArray(data) ? data : [] ); @@ -78,7 +74,7 @@ export default function EventSubscriptionsPage() { async function handleCreate() { setCreating(true); - await apiFetch('create', { + await apiFetch('', { method: 'POST', body: JSON.stringify({ eventType: newEvent, callbackUrl: newUrl }), }); diff --git a/dashboards/admin-web/src/app/(dashboard)/ip-rules/page.tsx b/dashboards/admin-web/src/app/(dashboard)/ip-rules/page.tsx index 1269e631..461bf6b8 100644 --- a/dashboards/admin-web/src/app/(dashboard)/ip-rules/page.tsx +++ b/dashboards/admin-web/src/app/(dashboard)/ip-rules/page.tsx @@ -1,5 +1,7 @@ 'use client'; +import { createProxyFetch } from '@/lib/proxy-fetch'; + import { useState, useEffect, useCallback } from 'react'; import { ShieldBan, Plus, Trash2 } from 'lucide-react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; @@ -48,13 +50,7 @@ function formatDate(iso: string) { }); } -async function apiFetch(path: string, opts?: RequestInit) { - const res = await fetch(`/api/ip-rules/${path}`, { - headers: { 'Content-Type': 'application/json' }, - ...opts, - }); - return res.json(); -} +const apiFetch = createProxyFetch('/api/ip-rules'); export default function IpRulesPage() { const [rules, setRules] = useState([]); @@ -67,7 +63,7 @@ export default function IpRulesPage() { const loadData = useCallback(async () => { setLoading(true); - const data = await apiFetch('list'); + const data = await apiFetch(''); setRules(Array.isArray(data?.rules) ? data.rules : Array.isArray(data) ? data : []); setLoading(false); }, []); @@ -78,7 +74,7 @@ export default function IpRulesPage() { async function handleCreate() { setCreating(true); - await apiFetch('create', { + await apiFetch('', { method: 'POST', body: JSON.stringify({ cidr: newCidr, type: newType, reason: newReason || undefined }), }); diff --git a/dashboards/admin-web/src/app/(dashboard)/jobs/page.tsx b/dashboards/admin-web/src/app/(dashboard)/jobs/page.tsx index 0d3c260f..2c745a18 100644 --- a/dashboards/admin-web/src/app/(dashboard)/jobs/page.tsx +++ b/dashboards/admin-web/src/app/(dashboard)/jobs/page.tsx @@ -19,6 +19,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; +import { createProxyFetch } from '@/lib/proxy-fetch'; interface Job { id: string; @@ -58,21 +59,8 @@ function formatDate(iso: string) { }); } -async function jobsFetch(path: string, opts?: RequestInit) { - const res = await fetch(`/api/jobs/${path}`, { - headers: { 'Content-Type': 'application/json' }, - ...opts, - }); - return res.json(); -} - -async function runsFetch(path: string, opts?: RequestInit) { - const res = await fetch(`/api/runs/${path}`, { - headers: { 'Content-Type': 'application/json' }, - ...opts, - }); - return res.json(); -} +const jobsFetch = createProxyFetch('/api/jobs'); +const runsFetch = createProxyFetch('/api/runs'); export default function JobsPage() { const [jobs, setJobs] = useState([]); diff --git a/dashboards/admin-web/src/app/(dashboard)/knowledge/page.tsx b/dashboards/admin-web/src/app/(dashboard)/knowledge/page.tsx index c14c23f1..398ac94f 100644 --- a/dashboards/admin-web/src/app/(dashboard)/knowledge/page.tsx +++ b/dashboards/admin-web/src/app/(dashboard)/knowledge/page.tsx @@ -1,5 +1,7 @@ 'use client'; +import { createProxyFetch } from '@/lib/proxy-fetch'; + import { useState, useEffect, useCallback } from 'react'; import { BookOpen, Plus, MoreHorizontal, Search, FileText, Trash2, Upload } from 'lucide-react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; @@ -49,13 +51,7 @@ function formatDate(iso: string) { }); } -async function apiFetch(path: string, opts?: RequestInit) { - const res = await fetch(`/api/knowledge/${path}`, { - headers: { 'Content-Type': 'application/json' }, - ...opts, - }); - return res.json(); -} +const apiFetch = createProxyFetch('/api/knowledge'); export default function KnowledgePage() { const [bases, setBases] = useState([]); diff --git a/dashboards/admin-web/src/app/(dashboard)/maintenance/page.tsx b/dashboards/admin-web/src/app/(dashboard)/maintenance/page.tsx index e9e2f10c..dd51e0ae 100644 --- a/dashboards/admin-web/src/app/(dashboard)/maintenance/page.tsx +++ b/dashboards/admin-web/src/app/(dashboard)/maintenance/page.tsx @@ -14,6 +14,7 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; +import { createProxyFetch } from '@/lib/proxy-fetch'; interface MaintenanceStatus { mode: string; @@ -47,13 +48,7 @@ const modeConfig: Record({ mode: 'off' }); diff --git a/dashboards/admin-web/src/app/(dashboard)/marketplace/page.tsx b/dashboards/admin-web/src/app/(dashboard)/marketplace/page.tsx index 37a13b6f..e063e89d 100644 --- a/dashboards/admin-web/src/app/(dashboard)/marketplace/page.tsx +++ b/dashboards/admin-web/src/app/(dashboard)/marketplace/page.tsx @@ -1,5 +1,7 @@ 'use client'; +import { createProxyFetch } from '@/lib/proxy-fetch'; + import { useState, useEffect, useCallback } from 'react'; import { Store, MoreHorizontal, Search, ThumbsUp, ThumbsDown } from 'lucide-react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; @@ -49,13 +51,7 @@ function formatDate(iso: string) { }); } -async function apiFetch(path: string, opts?: RequestInit) { - const res = await fetch(`/api/marketplace/${path}`, { - headers: { 'Content-Type': 'application/json' }, - ...opts, - }); - return res.json(); -} +const apiFetch = createProxyFetch('/api/marketplace'); export default function MarketplacePage() { const [listings, setListings] = useState([]); diff --git a/dashboards/admin-web/src/app/(dashboard)/organizations/page.tsx b/dashboards/admin-web/src/app/(dashboard)/organizations/page.tsx index c9c65b2d..7c17d5ba 100644 --- a/dashboards/admin-web/src/app/(dashboard)/organizations/page.tsx +++ b/dashboards/admin-web/src/app/(dashboard)/organizations/page.tsx @@ -1,5 +1,7 @@ 'use client'; +import { createProxyFetch } from '@/lib/proxy-fetch'; + import { useState, useEffect, useCallback } from 'react'; import { Building2, Plus, MoreHorizontal, Users, FolderOpen, Search, Trash2 } from 'lucide-react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; @@ -71,13 +73,7 @@ function formatDate(iso: string) { }); } -async function apiFetch(path: string, opts?: RequestInit) { - const res = await fetch(`/api/orgs/${path}`, { - headers: { 'Content-Type': 'application/json' }, - ...opts, - }); - return res.json(); -} +const apiFetch = createProxyFetch('/api/orgs'); export default function OrganizationsPage() { const [orgs, setOrgs] = useState([]); @@ -98,7 +94,7 @@ export default function OrganizationsPage() { const loadOrgs = useCallback(async () => { setLoading(true); const qs = search ? `?search=${encodeURIComponent(search)}` : ''; - const data = await apiFetch(`list${qs}`); + const data = await apiFetch(qs); if (data?.organizations) { setOrgs(data.organizations); setTotal(data.total ?? data.organizations.length); @@ -115,7 +111,7 @@ export default function OrganizationsPage() { async function handleCreate() { setCreating(true); - await apiFetch('create', { + await apiFetch('', { method: 'POST', body: JSON.stringify({ name: newName, slug: newSlug || undefined }), }); diff --git a/dashboards/admin-web/src/app/(dashboard)/reviews/page.tsx b/dashboards/admin-web/src/app/(dashboard)/reviews/page.tsx index cc3d3e62..b468244b 100644 --- a/dashboards/admin-web/src/app/(dashboard)/reviews/page.tsx +++ b/dashboards/admin-web/src/app/(dashboard)/reviews/page.tsx @@ -2,6 +2,7 @@ import { useState, useEffect, useCallback } from 'react'; import { Star, ThumbsUp, ThumbsDown, MoreHorizontal, Flag } from 'lucide-react'; +import { createProxyFetch } from '@/lib/proxy-fetch'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; @@ -48,13 +49,7 @@ function formatDate(iso: string) { }); } -async function apiFetch(path: string, opts?: RequestInit) { - const res = await fetch(`/api/reviews/${path}`, { - headers: { 'Content-Type': 'application/json' }, - ...opts, - }); - return res.json(); -} +const apiFetch = createProxyFetch('/api/reviews'); export default function ReviewsPage() { const [reviews, setReviews] = useState([]); diff --git a/dashboards/admin-web/src/app/(dashboard)/sessions-admin/page.tsx b/dashboards/admin-web/src/app/(dashboard)/sessions-admin/page.tsx index 319747cb..55a72c34 100644 --- a/dashboards/admin-web/src/app/(dashboard)/sessions-admin/page.tsx +++ b/dashboards/admin-web/src/app/(dashboard)/sessions-admin/page.tsx @@ -1,5 +1,7 @@ 'use client'; +import { createProxyFetch } from '@/lib/proxy-fetch'; + import { useState, useEffect, useCallback } from 'react'; import { MonitorSmartphone, Search, Globe } from 'lucide-react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; @@ -35,13 +37,7 @@ function formatDate(iso: string) { }); } -async function apiFetch(path: string, opts?: RequestInit) { - const res = await fetch(`/api/sessions/${path}`, { - headers: { 'Content-Type': 'application/json' }, - ...opts, - }); - return res.json(); -} +const apiFetch = createProxyFetch('/api/sessions'); export default function SessionsAdminPage() { const [sessions, setSessions] = useState([]); @@ -51,7 +47,7 @@ export default function SessionsAdminPage() { const loadData = useCallback(async () => { setLoading(true); const qs = search ? `?search=${encodeURIComponent(search)}` : ''; - const data = await apiFetch(`list${qs}`); + const data = await apiFetch(qs); setSessions(Array.isArray(data?.sessions) ? data.sessions : Array.isArray(data) ? data : []); setLoading(false); }, [search]); @@ -62,7 +58,7 @@ export default function SessionsAdminPage() { async function handleRevoke(id: string) { if (!confirm('Revoke this session?')) return; - await apiFetch(`${id}/revoke`, { method: 'POST' }); + await apiFetch(`${id}`, { method: 'DELETE' }); loadData(); } diff --git a/dashboards/admin-web/src/app/(dashboard)/support/page.tsx b/dashboards/admin-web/src/app/(dashboard)/support/page.tsx index fa38b734..bf22ec29 100644 --- a/dashboards/admin-web/src/app/(dashboard)/support/page.tsx +++ b/dashboards/admin-web/src/app/(dashboard)/support/page.tsx @@ -1,5 +1,7 @@ 'use client'; +import { createProxyFetch } from '@/lib/proxy-fetch'; + import { useState, useEffect, useCallback } from 'react'; import { LifeBuoy, @@ -83,13 +85,7 @@ function formatDate(iso: string) { }); } -async function apiFetch(path: string, opts?: RequestInit) { - const res = await fetch(`/api/support/${path}`, { - headers: { 'Content-Type': 'application/json' }, - ...opts, - }); - return res.json(); -} +const apiFetch = createProxyFetch('/api/support'); export default function SupportCasesPage() { const [cases, setCases] = useState([]); diff --git a/dashboards/admin-web/src/app/(dashboard)/waitlist/page.tsx b/dashboards/admin-web/src/app/(dashboard)/waitlist/page.tsx index a6226a52..5a831a7c 100644 --- a/dashboards/admin-web/src/app/(dashboard)/waitlist/page.tsx +++ b/dashboards/admin-web/src/app/(dashboard)/waitlist/page.tsx @@ -1,5 +1,7 @@ 'use client'; +import { createProxyFetch } from '@/lib/proxy-fetch'; + import { useState, useEffect, useCallback } from 'react'; import { ListOrdered, @@ -65,13 +67,7 @@ function formatDate(iso: string) { }); } -async function apiFetch(path: string, opts?: RequestInit) { - const res = await fetch(`/api/waitlist/${path}`, { - headers: { 'Content-Type': 'application/json' }, - ...opts, - }); - return res.json(); -} +const apiFetch = createProxyFetch('/api/waitlist'); export default function WaitlistPage() { const [entries, setEntries] = useState([]); diff --git a/dashboards/admin-web/src/app/(dashboard)/webhooks/page.tsx b/dashboards/admin-web/src/app/(dashboard)/webhooks/page.tsx index 4544634b..bc3774c6 100644 --- a/dashboards/admin-web/src/app/(dashboard)/webhooks/page.tsx +++ b/dashboards/admin-web/src/app/(dashboard)/webhooks/page.tsx @@ -1,5 +1,7 @@ 'use client'; +import { createProxyFetch } from '@/lib/proxy-fetch'; + import { useState, useEffect, useCallback } from 'react'; import { Webhook, Plus, MoreHorizontal, RotateCcw, Trash2 } from 'lucide-react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; @@ -57,13 +59,7 @@ function formatDate(iso: string) { }); } -async function apiFetch(path: string, opts?: RequestInit) { - const res = await fetch(`/api/webhooks/${path}`, { - headers: { 'Content-Type': 'application/json' }, - ...opts, - }); - return res.json(); -} +const apiFetch = createProxyFetch('/api/webhooks'); export default function WebhooksPage() { const [subs, setSubs] = useState([]); diff --git a/dashboards/admin-web/src/app/api/actiontrail/route.ts b/dashboards/admin-web/src/app/api/actiontrail/route.ts index f7efbbd0..7833d461 100644 --- a/dashboards/admin-web/src/app/api/actiontrail/route.ts +++ b/dashboards/admin-web/src/app/api/actiontrail/route.ts @@ -1,11 +1,11 @@ import { NextRequest, NextResponse } from 'next/server'; import { logError } from '@/lib/logger'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import * as actiontrailClient from '@/lib/actiontrail-client'; export async function GET(req: NextRequest) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } diff --git a/dashboards/admin-web/src/app/api/agent-evals/[...path]/route.ts b/dashboards/admin-web/src/app/api/agent-evals/[...path]/route.ts index 2eb3260c..9ce5c6e4 100644 --- a/dashboards/admin-web/src/app/api/agent-evals/[...path]/route.ts +++ b/dashboards/admin-web/src/app/api/agent-evals/[...path]/route.ts @@ -3,14 +3,14 @@ */ import { NextRequest, NextResponse } from 'next/server'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import { logError } from '@/lib/logger'; const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003'; async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); const { path } = await params; const targetPath = `/api/agent-evals/${path.join('/')}`; diff --git a/dashboards/admin-web/src/app/api/ai-budgets/[...path]/route.ts b/dashboards/admin-web/src/app/api/ai-budgets/[...path]/route.ts index 13608b8f..da809110 100644 --- a/dashboards/admin-web/src/app/api/ai-budgets/[...path]/route.ts +++ b/dashboards/admin-web/src/app/api/ai-budgets/[...path]/route.ts @@ -5,14 +5,14 @@ */ import { NextRequest, NextResponse } from 'next/server'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import { logError } from '@/lib/logger'; const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003'; async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } diff --git a/dashboards/admin-web/src/app/api/analytics/retention/route.ts b/dashboards/admin-web/src/app/api/analytics/retention/route.ts index 16ee2d2a..81d3bd9c 100644 --- a/dashboards/admin-web/src/app/api/analytics/retention/route.ts +++ b/dashboards/admin-web/src/app/api/analytics/retention/route.ts @@ -8,7 +8,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { logError } from '@/lib/logger'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import { getCollection } from '@/lib/datastore'; import { getRequestProductId } from '@/lib/product-config'; interface CohortRow { @@ -37,7 +37,7 @@ function getMondayOfWeek(date: Date): string { } export async function GET(req: NextRequest) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } diff --git a/dashboards/admin-web/src/app/api/analytics/revenue/route.ts b/dashboards/admin-web/src/app/api/analytics/revenue/route.ts index e4ac4a46..42b1be6e 100644 --- a/dashboards/admin-web/src/app/api/analytics/revenue/route.ts +++ b/dashboards/admin-web/src/app/api/analytics/revenue/route.ts @@ -6,7 +6,7 @@ */ import { NextRequest, NextResponse } from 'next/server'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import { getCollection } from '@/lib/datastore'; import { getRequestProductId } from '@/lib/product-config'; @@ -32,7 +32,7 @@ interface RevenueResponse { export async function GET(req: NextRequest) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } diff --git a/dashboards/admin-web/src/app/api/audit/route.ts b/dashboards/admin-web/src/app/api/audit/route.ts index c45ed0d1..c0b28f8c 100644 --- a/dashboards/admin-web/src/app/api/audit/route.ts +++ b/dashboards/admin-web/src/app/api/audit/route.ts @@ -1,10 +1,10 @@ import { NextRequest, NextResponse } from 'next/server'; import { logError } from '@/lib/logger'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import * as platformClient from '@/lib/platform-client'; export async function GET(req: NextRequest) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } diff --git a/dashboards/admin-web/src/app/api/delivery/[...path]/route.ts b/dashboards/admin-web/src/app/api/delivery/[...path]/route.ts index acaac1be..355b4cf5 100644 --- a/dashboards/admin-web/src/app/api/delivery/[...path]/route.ts +++ b/dashboards/admin-web/src/app/api/delivery/[...path]/route.ts @@ -3,14 +3,14 @@ */ import { NextRequest, NextResponse } from 'next/server'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import { logError } from '@/lib/logger'; const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003'; async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); const { path } = await params; const targetPath = `/api/delivery/${path.join('/')}`; diff --git a/dashboards/admin-web/src/app/api/event-subscriptions/[...path]/route.ts b/dashboards/admin-web/src/app/api/event-subscriptions/[...path]/route.ts index 41369a25..addebd8d 100644 --- a/dashboards/admin-web/src/app/api/event-subscriptions/[...path]/route.ts +++ b/dashboards/admin-web/src/app/api/event-subscriptions/[...path]/route.ts @@ -3,14 +3,14 @@ */ import { NextRequest, NextResponse } from 'next/server'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import { logError } from '@/lib/logger'; const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003'; async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); const { path } = await params; const targetPath = `/api/event-subscriptions/${path.join('/')}`; @@ -38,6 +38,9 @@ export async function GET(req: NextRequest, ctx: { params: Promise<{ path: strin export async function POST(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) { return proxy(req, ctx); } +export async function PATCH(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) { + return proxy(req, ctx); +} export async function DELETE(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) { return proxy(req, ctx); } diff --git a/dashboards/admin-web/src/app/api/event-subscriptions/route.ts b/dashboards/admin-web/src/app/api/event-subscriptions/route.ts new file mode 100644 index 00000000..55240f0a --- /dev/null +++ b/dashboards/admin-web/src/app/api/event-subscriptions/route.ts @@ -0,0 +1,39 @@ +/** + * Event Subscriptions base route — handles GET /api/event-subscriptions and POST /api/event-subscriptions. + * The [...path] catch-all only matches sub-paths like /api/event-subscriptions/:id. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; +import { logError } from '@/lib/logger'; + +const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003'; + +async function proxyBase(req: NextRequest) { + try { + const caller = await getCurrentUserFromRequest(req); + if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + const qs = new URL(req.url).search; + const headers: Record = { + 'Content-Type': 'application/json', + 'x-request-id': req.headers.get('x-request-id') || crypto.randomUUID(), + 'x-user-id': caller.id, + 'x-product-id': req.headers.get('x-product-id') || process.env.PRODUCT_ID || 'lysnrai', + }; + const fetchOptions: RequestInit = { method: req.method, headers }; + if (req.method !== 'GET' && req.method !== 'HEAD') fetchOptions.body = await req.text(); + const res = await fetch(`${PLATFORM_URL}/api/event-subscriptions${qs}`, fetchOptions); + const data = await res.json().catch(() => null); + return NextResponse.json(data ?? { error: res.statusText }, { status: res.status }); + } catch (error) { + logError('Event subscriptions base proxy error', error); + return NextResponse.json({ error: 'Service unavailable' }, { status: 502 }); + } +} + +export async function GET(req: NextRequest) { + return proxyBase(req); +} +export async function POST(req: NextRequest) { + return proxyBase(req); +} diff --git a/dashboards/admin-web/src/app/api/exports/[...path]/route.ts b/dashboards/admin-web/src/app/api/exports/[...path]/route.ts index f43aae20..d3300fee 100644 --- a/dashboards/admin-web/src/app/api/exports/[...path]/route.ts +++ b/dashboards/admin-web/src/app/api/exports/[...path]/route.ts @@ -3,14 +3,14 @@ */ import { NextRequest, NextResponse } from 'next/server'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import { logError } from '@/lib/logger'; const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003'; async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); const { path } = await params; const targetPath = `/api/exports/${path.join('/')}`; diff --git a/dashboards/admin-web/src/app/api/extraction/[...path]/route.ts b/dashboards/admin-web/src/app/api/extraction/[...path]/route.ts index b6b2c127..b913a96f 100644 --- a/dashboards/admin-web/src/app/api/extraction/[...path]/route.ts +++ b/dashboards/admin-web/src/app/api/extraction/[...path]/route.ts @@ -5,18 +5,17 @@ */ import { NextRequest, NextResponse } from 'next/server'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import { logError } from '@/lib/logger'; -const EXTRACTION_SERVICE_URL = - process.env.EXTRACTION_SERVICE_URL || 'http://localhost:4005'; +const EXTRACTION_SERVICE_URL = process.env.EXTRACTION_SERVICE_URL || 'http://localhost:4005'; async function proxyToExtraction( req: NextRequest, - { params }: { params: Promise<{ path: string[] }> }, + { params }: { params: Promise<{ path: string[] }> } ) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } @@ -50,37 +49,22 @@ async function proxyToExtraction( }); } catch (error) { logError('Extraction proxy error', error); - return NextResponse.json( - { error: 'Extraction service unavailable' }, - { status: 502 }, - ); + return NextResponse.json({ error: 'Extraction service unavailable' }, { status: 502 }); } } -export async function GET( - req: NextRequest, - context: { params: Promise<{ path: string[] }> }, -) { +export async function GET(req: NextRequest, context: { params: Promise<{ path: string[] }> }) { return proxyToExtraction(req, context); } -export async function POST( - req: NextRequest, - context: { params: Promise<{ path: string[] }> }, -) { +export async function POST(req: NextRequest, context: { params: Promise<{ path: string[] }> }) { return proxyToExtraction(req, context); } -export async function PUT( - req: NextRequest, - context: { params: Promise<{ path: string[] }> }, -) { +export async function PUT(req: NextRequest, context: { params: Promise<{ path: string[] }> }) { return proxyToExtraction(req, context); } -export async function DELETE( - req: NextRequest, - context: { params: Promise<{ path: string[] }> }, -) { +export async function DELETE(req: NextRequest, context: { params: Promise<{ path: string[] }> }) { return proxyToExtraction(req, context); } diff --git a/dashboards/admin-web/src/app/api/invitations/[id]/route.ts b/dashboards/admin-web/src/app/api/invitations/[id]/route.ts index 0a19a6a2..131f0908 100644 --- a/dashboards/admin-web/src/app/api/invitations/[id]/route.ts +++ b/dashboards/admin-web/src/app/api/invitations/[id]/route.ts @@ -5,11 +5,11 @@ import { NextRequest, NextResponse } from 'next/server'; import { logError } from '@/lib/logger'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import * as growthClient from '@/lib/growth-client'; export async function PATCH(req: NextRequest, { params }: { params: Promise<{ id: string }> }) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller || !['super_admin', 'admin'].includes(caller.role)) { return NextResponse.json({ error: 'Forbidden' }, { status: 403 }); } @@ -40,7 +40,7 @@ export async function PATCH(req: NextRequest, { params }: { params: Promise<{ id } export async function DELETE(req: NextRequest, { params }: { params: Promise<{ id: string }> }) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller || caller.role !== 'super_admin') { return NextResponse.json({ error: 'Forbidden' }, { status: 403 }); } diff --git a/dashboards/admin-web/src/app/api/invitations/bulk/route.ts b/dashboards/admin-web/src/app/api/invitations/bulk/route.ts index dbfcacbb..709a87ec 100644 --- a/dashboards/admin-web/src/app/api/invitations/bulk/route.ts +++ b/dashboards/admin-web/src/app/api/invitations/bulk/route.ts @@ -5,11 +5,11 @@ import { NextRequest, NextResponse } from 'next/server'; import { logError } from '@/lib/logger'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import * as growthClient from '@/lib/growth-client'; export async function POST(req: NextRequest) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller || !['super_admin', 'admin'].includes(caller.role)) { return NextResponse.json({ error: 'Forbidden' }, { status: 403 }); } diff --git a/dashboards/admin-web/src/app/api/invitations/route.ts b/dashboards/admin-web/src/app/api/invitations/route.ts index 7fe25abb..c00e8277 100644 --- a/dashboards/admin-web/src/app/api/invitations/route.ts +++ b/dashboards/admin-web/src/app/api/invitations/route.ts @@ -5,12 +5,12 @@ import { NextRequest, NextResponse } from 'next/server'; import { logError } from '@/lib/logger'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import * as growthClient from '@/lib/growth-client'; import crypto from 'crypto'; export async function GET(req: NextRequest) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } @@ -31,7 +31,7 @@ export async function GET(req: NextRequest) { } export async function POST(req: NextRequest) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller || !['super_admin', 'admin'].includes(caller.role)) { return NextResponse.json({ error: 'Forbidden' }, { status: 403 }); } diff --git a/dashboards/admin-web/src/app/api/ip-rules/[...path]/route.ts b/dashboards/admin-web/src/app/api/ip-rules/[...path]/route.ts index 80a89976..026700d7 100644 --- a/dashboards/admin-web/src/app/api/ip-rules/[...path]/route.ts +++ b/dashboards/admin-web/src/app/api/ip-rules/[...path]/route.ts @@ -3,14 +3,14 @@ */ import { NextRequest, NextResponse } from 'next/server'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import { logError } from '@/lib/logger'; const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003'; async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); const { path } = await params; const targetPath = `/api/ratelimit/ip-rules/${path.join('/')}`; diff --git a/dashboards/admin-web/src/app/api/ip-rules/route.ts b/dashboards/admin-web/src/app/api/ip-rules/route.ts new file mode 100644 index 00000000..0eb2218e --- /dev/null +++ b/dashboards/admin-web/src/app/api/ip-rules/route.ts @@ -0,0 +1,39 @@ +/** + * IP Rules base route — handles GET /api/ip-rules and POST /api/ip-rules. + * Maps to platform-service GET /api/ratelimit/ip-rules and POST /api/ratelimit/ip-rules. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; +import { logError } from '@/lib/logger'; + +const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003'; + +async function proxyBase(req: NextRequest) { + try { + const caller = await getCurrentUserFromRequest(req); + if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + const qs = new URL(req.url).search; + const headers: Record = { + 'Content-Type': 'application/json', + 'x-request-id': req.headers.get('x-request-id') || crypto.randomUUID(), + 'x-user-id': caller.id, + 'x-product-id': req.headers.get('x-product-id') || process.env.PRODUCT_ID || 'lysnrai', + }; + const fetchOptions: RequestInit = { method: req.method, headers }; + if (req.method !== 'GET' && req.method !== 'HEAD') fetchOptions.body = await req.text(); + const res = await fetch(`${PLATFORM_URL}/api/ratelimit/ip-rules${qs}`, fetchOptions); + const data = await res.json().catch(() => null); + return NextResponse.json(data ?? { error: res.statusText }, { status: res.status }); + } catch (error) { + logError('IP rules base proxy error', error); + return NextResponse.json({ error: 'Service unavailable' }, { status: 502 }); + } +} + +export async function GET(req: NextRequest) { + return proxyBase(req); +} +export async function POST(req: NextRequest) { + return proxyBase(req); +} diff --git a/dashboards/admin-web/src/app/api/jobs/[...path]/route.ts b/dashboards/admin-web/src/app/api/jobs/[...path]/route.ts index 625fac01..b5893994 100644 --- a/dashboards/admin-web/src/app/api/jobs/[...path]/route.ts +++ b/dashboards/admin-web/src/app/api/jobs/[...path]/route.ts @@ -3,14 +3,14 @@ */ import { NextRequest, NextResponse } from 'next/server'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import { logError } from '@/lib/logger'; const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003'; async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); const { path } = await params; const targetPath = `/api/jobs/${path.join('/')}`; diff --git a/dashboards/admin-web/src/app/api/knowledge/[...path]/route.ts b/dashboards/admin-web/src/app/api/knowledge/[...path]/route.ts index cddc5a6b..5b98b96e 100644 --- a/dashboards/admin-web/src/app/api/knowledge/[...path]/route.ts +++ b/dashboards/admin-web/src/app/api/knowledge/[...path]/route.ts @@ -3,14 +3,14 @@ */ import { NextRequest, NextResponse } from 'next/server'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import { logError } from '@/lib/logger'; const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003'; async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); const { path } = await params; const targetPath = `/api/knowledge/${path.join('/')}`; diff --git a/dashboards/admin-web/src/app/api/maintenance/[...path]/route.ts b/dashboards/admin-web/src/app/api/maintenance/[...path]/route.ts index 4f9d8a89..7050c93f 100644 --- a/dashboards/admin-web/src/app/api/maintenance/[...path]/route.ts +++ b/dashboards/admin-web/src/app/api/maintenance/[...path]/route.ts @@ -3,14 +3,14 @@ */ import { NextRequest, NextResponse } from 'next/server'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import { logError } from '@/lib/logger'; const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003'; async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); const { path } = await params; const targetPath = `/api/settings/maintenance/${path.join('/')}`; diff --git a/dashboards/admin-web/src/app/api/marketplace/[...path]/route.ts b/dashboards/admin-web/src/app/api/marketplace/[...path]/route.ts index 290e263c..5430ee86 100644 --- a/dashboards/admin-web/src/app/api/marketplace/[...path]/route.ts +++ b/dashboards/admin-web/src/app/api/marketplace/[...path]/route.ts @@ -3,14 +3,14 @@ */ import { NextRequest, NextResponse } from 'next/server'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import { logError } from '@/lib/logger'; const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003'; async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); const { path } = await params; const targetPath = `/api/marketplace/${path.join('/')}`; diff --git a/dashboards/admin-web/src/app/api/orgs/[...path]/route.ts b/dashboards/admin-web/src/app/api/orgs/[...path]/route.ts index 8834ab8b..74cce491 100644 --- a/dashboards/admin-web/src/app/api/orgs/[...path]/route.ts +++ b/dashboards/admin-web/src/app/api/orgs/[...path]/route.ts @@ -5,14 +5,14 @@ */ import { NextRequest, NextResponse } from 'next/server'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import { logError } from '@/lib/logger'; const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003'; async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } diff --git a/dashboards/admin-web/src/app/api/orgs/route.ts b/dashboards/admin-web/src/app/api/orgs/route.ts new file mode 100644 index 00000000..086777a2 --- /dev/null +++ b/dashboards/admin-web/src/app/api/orgs/route.ts @@ -0,0 +1,39 @@ +/** + * Orgs base route — handles GET /api/orgs and POST /api/orgs. + * Maps to platform-service GET /api/orgs and POST /api/orgs. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; +import { logError } from '@/lib/logger'; + +const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003'; + +async function proxyBase(req: NextRequest) { + try { + const caller = await getCurrentUserFromRequest(req); + if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + const qs = new URL(req.url).search; + const headers: Record = { + 'Content-Type': 'application/json', + 'x-request-id': req.headers.get('x-request-id') || crypto.randomUUID(), + 'x-user-id': caller.id, + 'x-product-id': req.headers.get('x-product-id') || process.env.PRODUCT_ID || 'lysnrai', + }; + const fetchOptions: RequestInit = { method: req.method, headers }; + if (req.method !== 'GET' && req.method !== 'HEAD') fetchOptions.body = await req.text(); + const res = await fetch(`${PLATFORM_URL}/api/orgs${qs}`, fetchOptions); + const data = await res.json().catch(() => null); + return NextResponse.json(data ?? { error: res.statusText }, { status: res.status }); + } catch (error) { + logError('Orgs base proxy error', error); + return NextResponse.json({ error: 'Service unavailable' }, { status: 502 }); + } +} + +export async function GET(req: NextRequest) { + return proxyBase(req); +} +export async function POST(req: NextRequest) { + return proxyBase(req); +} diff --git a/dashboards/admin-web/src/app/api/promos/[id]/route.ts b/dashboards/admin-web/src/app/api/promos/[id]/route.ts index ca9937da..35747457 100644 --- a/dashboards/admin-web/src/app/api/promos/[id]/route.ts +++ b/dashboards/admin-web/src/app/api/promos/[id]/route.ts @@ -6,7 +6,7 @@ */ import { NextRequest, NextResponse } from 'next/server'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import { deactivatePromo } from '@/lib/growth-client'; import { logError } from '@/lib/logger'; @@ -14,7 +14,7 @@ type RouteContext = { params: Promise<{ id: string }> }; export async function DELETE(req: NextRequest, ctx: RouteContext) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller || !['super_admin', 'admin'].includes(caller.role)) { return NextResponse.json({ error: 'Forbidden' }, { status: 403 }); } @@ -29,7 +29,7 @@ export async function DELETE(req: NextRequest, ctx: RouteContext) { export async function PATCH(req: NextRequest, ctx: RouteContext) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller || !['super_admin', 'admin'].includes(caller.role)) { return NextResponse.json({ error: 'Forbidden' }, { status: 403 }); } diff --git a/dashboards/admin-web/src/app/api/promos/route.ts b/dashboards/admin-web/src/app/api/promos/route.ts index db582040..60e2d7d7 100644 --- a/dashboards/admin-web/src/app/api/promos/route.ts +++ b/dashboards/admin-web/src/app/api/promos/route.ts @@ -8,11 +8,11 @@ import { NextRequest, NextResponse } from 'next/server'; import { logError } from '@/lib/logger'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import * as growthClient from '@/lib/growth-client'; export async function GET(req: NextRequest) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } @@ -27,7 +27,7 @@ export async function GET(req: NextRequest) { } export async function POST(req: NextRequest) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller || !['super_admin', 'admin'].includes(caller.role)) { return NextResponse.json({ error: 'Forbidden' }, { status: 403 }); } diff --git a/dashboards/admin-web/src/app/api/referrals/route.ts b/dashboards/admin-web/src/app/api/referrals/route.ts index bf2a5701..ff49ebcb 100644 --- a/dashboards/admin-web/src/app/api/referrals/route.ts +++ b/dashboards/admin-web/src/app/api/referrals/route.ts @@ -4,11 +4,11 @@ import { NextRequest, NextResponse } from 'next/server'; import { logError } from '@/lib/logger'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import * as growthClient from '@/lib/growth-client'; export async function GET(req: NextRequest) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } diff --git a/dashboards/admin-web/src/app/api/reviews/[...path]/route.ts b/dashboards/admin-web/src/app/api/reviews/[...path]/route.ts index 232c29c4..e8fca353 100644 --- a/dashboards/admin-web/src/app/api/reviews/[...path]/route.ts +++ b/dashboards/admin-web/src/app/api/reviews/[...path]/route.ts @@ -3,14 +3,14 @@ */ import { NextRequest, NextResponse } from 'next/server'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import { logError } from '@/lib/logger'; const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003'; async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); const { path } = await params; const targetPath = `/api/reviews/${path.join('/')}`; diff --git a/dashboards/admin-web/src/app/api/runs/[...path]/route.ts b/dashboards/admin-web/src/app/api/runs/[...path]/route.ts index 572eba90..9478c83e 100644 --- a/dashboards/admin-web/src/app/api/runs/[...path]/route.ts +++ b/dashboards/admin-web/src/app/api/runs/[...path]/route.ts @@ -3,14 +3,14 @@ */ import { NextRequest, NextResponse } from 'next/server'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import { logError } from '@/lib/logger'; const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003'; async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); const { path } = await params; const targetPath = `/api/runs/${path.join('/')}`; @@ -38,3 +38,6 @@ export async function GET(req: NextRequest, ctx: { params: Promise<{ path: strin export async function POST(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) { return proxy(req, ctx); } +export async function PATCH(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) { + return proxy(req, ctx); +} diff --git a/dashboards/admin-web/src/app/api/runs/route.ts b/dashboards/admin-web/src/app/api/runs/route.ts new file mode 100644 index 00000000..5da46554 --- /dev/null +++ b/dashboards/admin-web/src/app/api/runs/route.ts @@ -0,0 +1,39 @@ +/** + * Runs base route — handles GET /api/runs and POST /api/runs. + * Maps to platform-service GET /api/runs and POST /api/runs. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; +import { logError } from '@/lib/logger'; + +const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003'; + +async function proxyBase(req: NextRequest) { + try { + const caller = await getCurrentUserFromRequest(req); + if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + const qs = new URL(req.url).search; + const headers: Record = { + 'Content-Type': 'application/json', + 'x-request-id': req.headers.get('x-request-id') || crypto.randomUUID(), + 'x-user-id': caller.id, + 'x-product-id': req.headers.get('x-product-id') || process.env.PRODUCT_ID || 'lysnrai', + }; + const fetchOptions: RequestInit = { method: req.method, headers }; + if (req.method !== 'GET' && req.method !== 'HEAD') fetchOptions.body = await req.text(); + const res = await fetch(`${PLATFORM_URL}/api/runs${qs}`, fetchOptions); + const data = await res.json().catch(() => null); + return NextResponse.json(data ?? { error: res.statusText }, { status: res.status }); + } catch (error) { + logError('Runs base proxy error', error); + return NextResponse.json({ error: 'Service unavailable' }, { status: 502 }); + } +} + +export async function GET(req: NextRequest) { + return proxyBase(req); +} +export async function POST(req: NextRequest) { + return proxyBase(req); +} diff --git a/dashboards/admin-web/src/app/api/sessions/[...path]/route.ts b/dashboards/admin-web/src/app/api/sessions/[...path]/route.ts index 8898ff51..44d22383 100644 --- a/dashboards/admin-web/src/app/api/sessions/[...path]/route.ts +++ b/dashboards/admin-web/src/app/api/sessions/[...path]/route.ts @@ -3,14 +3,14 @@ */ import { NextRequest, NextResponse } from 'next/server'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import { logError } from '@/lib/logger'; const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003'; async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); const { path } = await params; const targetPath = `/api/sessions/${path.join('/')}`; diff --git a/dashboards/admin-web/src/app/api/sessions/route.ts b/dashboards/admin-web/src/app/api/sessions/route.ts new file mode 100644 index 00000000..bd25e1d9 --- /dev/null +++ b/dashboards/admin-web/src/app/api/sessions/route.ts @@ -0,0 +1,39 @@ +/** + * Sessions base route — handles GET /api/sessions and DELETE /api/sessions. + * Maps to platform-service GET /api/sessions and DELETE /api/sessions. + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; +import { logError } from '@/lib/logger'; + +const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003'; + +async function proxyBase(req: NextRequest) { + try { + const caller = await getCurrentUserFromRequest(req); + if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + const qs = new URL(req.url).search; + const headers: Record = { + 'Content-Type': 'application/json', + 'x-request-id': req.headers.get('x-request-id') || crypto.randomUUID(), + 'x-user-id': caller.id, + 'x-product-id': req.headers.get('x-product-id') || process.env.PRODUCT_ID || 'lysnrai', + }; + const fetchOptions: RequestInit = { method: req.method, headers }; + if (req.method !== 'GET' && req.method !== 'HEAD') fetchOptions.body = await req.text(); + const res = await fetch(`${PLATFORM_URL}/api/sessions${qs}`, fetchOptions); + const data = await res.json().catch(() => null); + return NextResponse.json(data ?? { error: res.statusText }, { status: res.status }); + } catch (error) { + logError('Sessions base proxy error', error); + return NextResponse.json({ error: 'Service unavailable' }, { status: 502 }); + } +} + +export async function GET(req: NextRequest) { + return proxyBase(req); +} +export async function DELETE(req: NextRequest) { + return proxyBase(req); +} diff --git a/dashboards/admin-web/src/app/api/support/[...path]/route.ts b/dashboards/admin-web/src/app/api/support/[...path]/route.ts index 2f3643ee..b8157326 100644 --- a/dashboards/admin-web/src/app/api/support/[...path]/route.ts +++ b/dashboards/admin-web/src/app/api/support/[...path]/route.ts @@ -5,14 +5,14 @@ */ import { NextRequest, NextResponse } from 'next/server'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import { logError } from '@/lib/logger'; const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003'; async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } diff --git a/dashboards/admin-web/src/app/api/usage/route.ts b/dashboards/admin-web/src/app/api/usage/route.ts index ebbd4e49..c51b28f1 100644 --- a/dashboards/admin-web/src/app/api/usage/route.ts +++ b/dashboards/admin-web/src/app/api/usage/route.ts @@ -1,10 +1,10 @@ import { NextRequest, NextResponse } from 'next/server'; import { logError } from '@/lib/logger'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import * as billingClient from '@/lib/billing-client'; export async function GET(req: NextRequest) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } diff --git a/dashboards/admin-web/src/app/api/waitlist/[...path]/route.ts b/dashboards/admin-web/src/app/api/waitlist/[...path]/route.ts index d611c37b..d6fc179a 100644 --- a/dashboards/admin-web/src/app/api/waitlist/[...path]/route.ts +++ b/dashboards/admin-web/src/app/api/waitlist/[...path]/route.ts @@ -5,14 +5,14 @@ */ import { NextRequest, NextResponse } from 'next/server'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import { logError } from '@/lib/logger'; const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003'; async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } diff --git a/dashboards/admin-web/src/app/api/webhooks/[...path]/route.ts b/dashboards/admin-web/src/app/api/webhooks/[...path]/route.ts index dd387fbe..a0b1eb8c 100644 --- a/dashboards/admin-web/src/app/api/webhooks/[...path]/route.ts +++ b/dashboards/admin-web/src/app/api/webhooks/[...path]/route.ts @@ -3,14 +3,14 @@ */ import { NextRequest, NextResponse } from 'next/server'; -import { getCurrentUser } from '@/lib/auth-server'; +import { getCurrentUserFromRequest } from '@/lib/auth-server'; import { logError } from '@/lib/logger'; const PLATFORM_URL = process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003'; async function proxy(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) { try { - const caller = await getCurrentUser(req.headers.get('authorization')); + const caller = await getCurrentUserFromRequest(req); if (!caller) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); const { path } = await params; const targetPath = `/api/webhooks/${path.join('/')}`; diff --git a/dashboards/admin-web/src/lib/auth-server.ts b/dashboards/admin-web/src/lib/auth-server.ts index d0dbcefb..ea7694be 100644 --- a/dashboards/admin-web/src/lib/auth-server.ts +++ b/dashboards/admin-web/src/lib/auth-server.ts @@ -42,6 +42,25 @@ export async function getCurrentUser(authHeader: string | null): Promise { + const cookieHeader = request.headers.get('cookie') || ''; + const tokenMatch = cookieHeader.match(/(?:^|;\s*)token=([^;]+)/); + const authHeader = request.headers.get('authorization'); + + if (tokenMatch) { + return getCurrentUser(`Bearer ${tokenMatch[1]}`); + } + if (authHeader) { + return getCurrentUser(authHeader); + } + return null; +} + /** * Require an admin user from the request. Checks cookies first, then Authorization header. * Throws Error("Unauthorized") if not authenticated or not admin/super_admin role. diff --git a/dashboards/admin-web/src/lib/proxy-fetch.ts b/dashboards/admin-web/src/lib/proxy-fetch.ts new file mode 100644 index 00000000..4db04073 --- /dev/null +++ b/dashboards/admin-web/src/lib/proxy-fetch.ts @@ -0,0 +1,37 @@ +/** + * Shared fetch helper for admin-web pages that call proxy API routes. + * Automatically injects Authorization + x-product-id headers from localStorage. + */ + +function getAuthHeaders(): Record { + if (typeof window === 'undefined') return {}; + const headers: Record = {}; + const token = localStorage.getItem('admin_access_token'); + if (token) headers['Authorization'] = `Bearer ${token}`; + const productId = localStorage.getItem('admin_selected_product'); + if (productId) headers['x-product-id'] = productId; + return headers; +} + +/** + * Creates a scoped fetch helper for a given proxy base path. + * + * Usage: + * const apiFetch = createProxyFetch('/api/jobs'); + * const data = await apiFetch('list'); // GET /api/jobs/list + * await apiFetch('123', { method: 'PUT', body: ... }); + */ +export function createProxyFetch(basePath: string) { + return async function proxyFetch(path: string, opts?: RequestInit) { + const url = !path || path.startsWith('?') ? `${basePath}${path}` : `${basePath}/${path}`; + const res = await fetch(url, { + ...opts, + headers: { + 'Content-Type': 'application/json', + ...getAuthHeaders(), + ...opts?.headers, + }, + }); + return res.json(); + }; +}