@realtimex/folio 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/.env.example +20 -0
  2. package/README.md +63 -0
  3. package/api/server.ts +130 -0
  4. package/api/src/config/index.ts +96 -0
  5. package/api/src/middleware/auth.ts +128 -0
  6. package/api/src/middleware/errorHandler.ts +88 -0
  7. package/api/src/middleware/index.ts +4 -0
  8. package/api/src/middleware/rateLimit.ts +71 -0
  9. package/api/src/middleware/validation.ts +58 -0
  10. package/api/src/routes/accounts.ts +142 -0
  11. package/api/src/routes/baseline-config.ts +124 -0
  12. package/api/src/routes/chat.ts +154 -0
  13. package/api/src/routes/health.ts +61 -0
  14. package/api/src/routes/index.ts +35 -0
  15. package/api/src/routes/ingestions.ts +275 -0
  16. package/api/src/routes/migrate.ts +112 -0
  17. package/api/src/routes/policies.ts +121 -0
  18. package/api/src/routes/processing.ts +90 -0
  19. package/api/src/routes/rules.ts +11 -0
  20. package/api/src/routes/sdk.ts +100 -0
  21. package/api/src/routes/settings.ts +80 -0
  22. package/api/src/routes/setup.ts +389 -0
  23. package/api/src/routes/stats.ts +81 -0
  24. package/api/src/routes/tts.ts +190 -0
  25. package/api/src/services/BaselineConfigService.ts +208 -0
  26. package/api/src/services/ChatService.ts +204 -0
  27. package/api/src/services/GoogleDriveService.ts +331 -0
  28. package/api/src/services/GoogleSheetsService.ts +1107 -0
  29. package/api/src/services/IngestionService.ts +1187 -0
  30. package/api/src/services/ModelCapabilityService.ts +248 -0
  31. package/api/src/services/PolicyEngine.ts +1625 -0
  32. package/api/src/services/PolicyLearningService.ts +527 -0
  33. package/api/src/services/PolicyLoader.ts +249 -0
  34. package/api/src/services/RAGService.ts +391 -0
  35. package/api/src/services/SDKService.ts +249 -0
  36. package/api/src/services/supabase.ts +113 -0
  37. package/api/src/utils/Actuator.ts +284 -0
  38. package/api/src/utils/actions/ActionHandler.ts +34 -0
  39. package/api/src/utils/actions/AppendToGSheetAction.ts +260 -0
  40. package/api/src/utils/actions/AutoRenameAction.ts +58 -0
  41. package/api/src/utils/actions/CopyAction.ts +120 -0
  42. package/api/src/utils/actions/CopyToGDriveAction.ts +64 -0
  43. package/api/src/utils/actions/LogCsvAction.ts +48 -0
  44. package/api/src/utils/actions/NotifyAction.ts +39 -0
  45. package/api/src/utils/actions/RenameAction.ts +57 -0
  46. package/api/src/utils/actions/WebhookAction.ts +58 -0
  47. package/api/src/utils/actions/utils.ts +293 -0
  48. package/api/src/utils/llmResponse.ts +61 -0
  49. package/api/src/utils/logger.ts +67 -0
  50. package/bin/folio-deploy.js +12 -0
  51. package/bin/folio-setup.js +45 -0
  52. package/bin/folio.js +65 -0
  53. package/dist/api/server.js +106 -0
  54. package/dist/api/src/config/index.js +81 -0
  55. package/dist/api/src/middleware/auth.js +93 -0
  56. package/dist/api/src/middleware/errorHandler.js +73 -0
  57. package/dist/api/src/middleware/index.js +4 -0
  58. package/dist/api/src/middleware/rateLimit.js +43 -0
  59. package/dist/api/src/middleware/validation.js +54 -0
  60. package/dist/api/src/routes/accounts.js +110 -0
  61. package/dist/api/src/routes/baseline-config.js +91 -0
  62. package/dist/api/src/routes/chat.js +114 -0
  63. package/dist/api/src/routes/health.js +52 -0
  64. package/dist/api/src/routes/index.js +31 -0
  65. package/dist/api/src/routes/ingestions.js +207 -0
  66. package/dist/api/src/routes/migrate.js +91 -0
  67. package/dist/api/src/routes/policies.js +86 -0
  68. package/dist/api/src/routes/processing.js +75 -0
  69. package/dist/api/src/routes/rules.js +8 -0
  70. package/dist/api/src/routes/sdk.js +80 -0
  71. package/dist/api/src/routes/settings.js +68 -0
  72. package/dist/api/src/routes/setup.js +315 -0
  73. package/dist/api/src/routes/stats.js +62 -0
  74. package/dist/api/src/routes/tts.js +178 -0
  75. package/dist/api/src/services/BaselineConfigService.js +168 -0
  76. package/dist/api/src/services/ChatService.js +166 -0
  77. package/dist/api/src/services/GoogleDriveService.js +280 -0
  78. package/dist/api/src/services/GoogleSheetsService.js +795 -0
  79. package/dist/api/src/services/IngestionService.js +990 -0
  80. package/dist/api/src/services/ModelCapabilityService.js +179 -0
  81. package/dist/api/src/services/PolicyEngine.js +1353 -0
  82. package/dist/api/src/services/PolicyLearningService.js +397 -0
  83. package/dist/api/src/services/PolicyLoader.js +159 -0
  84. package/dist/api/src/services/RAGService.js +295 -0
  85. package/dist/api/src/services/SDKService.js +212 -0
  86. package/dist/api/src/services/supabase.js +72 -0
  87. package/dist/api/src/utils/Actuator.js +225 -0
  88. package/dist/api/src/utils/actions/ActionHandler.js +1 -0
  89. package/dist/api/src/utils/actions/AppendToGSheetAction.js +191 -0
  90. package/dist/api/src/utils/actions/AutoRenameAction.js +49 -0
  91. package/dist/api/src/utils/actions/CopyAction.js +112 -0
  92. package/dist/api/src/utils/actions/CopyToGDriveAction.js +55 -0
  93. package/dist/api/src/utils/actions/LogCsvAction.js +42 -0
  94. package/dist/api/src/utils/actions/NotifyAction.js +32 -0
  95. package/dist/api/src/utils/actions/RenameAction.js +51 -0
  96. package/dist/api/src/utils/actions/WebhookAction.js +51 -0
  97. package/dist/api/src/utils/actions/utils.js +237 -0
  98. package/dist/api/src/utils/llmResponse.js +63 -0
  99. package/dist/api/src/utils/logger.js +51 -0
  100. package/dist/assets/index-DzN8-j-e.css +1 -0
  101. package/dist/assets/index-Uy-ai3Dh.js +113 -0
  102. package/dist/favicon.svg +31 -0
  103. package/dist/folio-logo.svg +46 -0
  104. package/dist/index.html +14 -0
  105. package/docs-dev/FPE-spec.md +196 -0
  106. package/docs-dev/folio-prd.md +47 -0
  107. package/docs-dev/foundation-checklist.md +30 -0
  108. package/docs-dev/hybrid-routing-architecture.md +205 -0
  109. package/docs-dev/ingestion-engine.md +69 -0
  110. package/docs-dev/port-from-email-automator.md +32 -0
  111. package/docs-dev/tech-spec.md +98 -0
  112. package/index.html +13 -0
  113. package/package.json +101 -0
  114. package/public/favicon.svg +31 -0
  115. package/public/folio-logo.svg +46 -0
  116. package/scripts/dev-task.mjs +51 -0
  117. package/scripts/get-latest-migration-timestamp.mjs +34 -0
  118. package/scripts/migrate.sh +91 -0
  119. package/supabase/.temp/cli-latest +1 -0
  120. package/supabase/.temp/gotrue-version +1 -0
  121. package/supabase/.temp/pooler-url +1 -0
  122. package/supabase/.temp/postgres-version +1 -0
  123. package/supabase/.temp/project-ref +1 -0
  124. package/supabase/.temp/rest-version +1 -0
  125. package/supabase/.temp/storage-migration +1 -0
  126. package/supabase/.temp/storage-version +1 -0
  127. package/supabase/config.toml +64 -0
  128. package/supabase/functions/_shared/auth.ts +35 -0
  129. package/supabase/functions/_shared/cors.ts +12 -0
  130. package/supabase/functions/_shared/supabaseAdmin.ts +17 -0
  131. package/supabase/functions/api-v1-settings/index.ts +66 -0
  132. package/supabase/functions/setup/index.ts +91 -0
  133. package/supabase/migrations/20260223000000_initial_foundation.sql +136 -0
  134. package/supabase/migrations/20260223000001_add_migration_rpc.sql +10 -0
  135. package/supabase/migrations/20260224000002_add_init_state_view.sql +20 -0
  136. package/supabase/migrations/20260224000003_port_user_creation_parity.sql +139 -0
  137. package/supabase/migrations/20260224000004_add_avatars_storage.sql +26 -0
  138. package/supabase/migrations/20260224000005_add_tts_and_embed_settings.sql +24 -0
  139. package/supabase/migrations/20260224000006_add_policies_table.sql +48 -0
  140. package/supabase/migrations/20260224000007_fix_migration_rpc.sql +9 -0
  141. package/supabase/migrations/20260224000008_add_ingestions_table.sql +42 -0
  142. package/supabase/migrations/20260225000000_setup_compatible_mode.sql +119 -0
  143. package/supabase/migrations/20260225000001_restore_ingestions.sql +49 -0
  144. package/supabase/migrations/20260225000002_add_ingestion_trace.sql +2 -0
  145. package/supabase/migrations/20260225000003_add_baseline_configs.sql +35 -0
  146. package/supabase/migrations/20260226000000_add_processing_events.sql +26 -0
  147. package/supabase/migrations/20260226000001_add_ingestion_file_hash.sql +10 -0
  148. package/supabase/migrations/20260226000002_add_dynamic_rag.sql +150 -0
  149. package/supabase/migrations/20260226000003_add_ingestion_summary.sql +4 -0
  150. package/supabase/migrations/20260226000004_add_ingestion_tags.sql +7 -0
  151. package/supabase/migrations/20260226000005_add_chat_tables.sql +60 -0
  152. package/supabase/migrations/20260227000000_harden_chat_messages_rls.sql +25 -0
  153. package/supabase/migrations/20260228000000_add_vision_model_capabilities.sql +8 -0
  154. package/supabase/migrations/20260228000001_add_policy_match_feedback.sql +51 -0
  155. package/supabase/migrations/29991231235959_test_migration.sql +0 -0
  156. package/supabase/templates/confirmation.html +76 -0
  157. package/supabase/templates/email-change.html +76 -0
  158. package/supabase/templates/invite.html +72 -0
  159. package/supabase/templates/magic-link.html +68 -0
  160. package/supabase/templates/recovery.html +82 -0
  161. package/tsconfig.api.json +16 -0
  162. package/tsconfig.json +25 -0
  163. package/vite.config.ts +146 -0
@@ -0,0 +1,142 @@
1
+ import { Router } from "express";
2
+ import axios from "axios";
3
+ import { asyncHandler } from "../middleware/errorHandler.js";
4
+ import { optionalAuth } from "../middleware/auth.js";
5
+
6
+ const router = Router();
7
+
8
+ router.use(optionalAuth);
9
+
10
+ // GET /api/accounts
11
+ router.get(
12
+ "/",
13
+ asyncHandler(async (req, res) => {
14
+ if (!req.user || !req.supabase) {
15
+ res.status(401).json({ error: "Authentication required" });
16
+ return;
17
+ }
18
+
19
+ const { data, error } = await req.supabase
20
+ .from("integrations")
21
+ .select("id, provider, is_enabled, created_at, updated_at")
22
+ .eq("user_id", req.user.id);
23
+
24
+ if (error) {
25
+ res.status(500).json({ error: error.message });
26
+ return;
27
+ }
28
+
29
+ // Map to expected frontend format
30
+ const accounts = data?.map(integration => ({
31
+ id: integration.id,
32
+ email_address: integration.provider, // Just for UI display
33
+ provider: integration.provider,
34
+ is_connected: integration.is_enabled,
35
+ sync_enabled: integration.is_enabled,
36
+ created_at: integration.created_at,
37
+ updated_at: integration.updated_at
38
+ })) || [];
39
+
40
+ res.json({ accounts });
41
+ })
42
+ );
43
+
44
+ // POST /api/accounts/google-drive/auth-url
45
+ router.post(
46
+ "/google-drive/auth-url",
47
+ asyncHandler(async (req, res) => {
48
+ const { clientId } = req.body;
49
+ if (!clientId) {
50
+ res.status(400).json({ error: "Missing clientId" });
51
+ return;
52
+ }
53
+
54
+ const redirectUri = "urn:ietf:wg:oauth:2.0:oob"; // Desktop/local app flow
55
+
56
+ // We request drive (full access) to allow downloading and uploading (moving) files
57
+ const params = new URLSearchParams({
58
+ client_id: clientId,
59
+ redirect_uri: redirectUri,
60
+ response_type: "code",
61
+ scope: "https://www.googleapis.com/auth/drive",
62
+ access_type: "offline",
63
+ prompt: "consent",
64
+ });
65
+
66
+ const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
67
+ res.json({ authUrl });
68
+ })
69
+ );
70
+
71
+ // POST /api/accounts/google-drive/connect
72
+ router.post(
73
+ "/google-drive/connect",
74
+ asyncHandler(async (req, res) => {
75
+ if (!req.supabase || !req.user) {
76
+ res.status(401).json({ error: "Authentication required" });
77
+ return;
78
+ }
79
+
80
+ const { authCode, clientId, clientSecret } = req.body;
81
+ if (!authCode || !clientId || !clientSecret) {
82
+ res.status(400).json({ error: "Missing authCode, clientId, or clientSecret" });
83
+ return;
84
+ }
85
+
86
+ try {
87
+ // Exchange code for tokens
88
+ const tokenResponse = await axios.post("https://oauth2.googleapis.com/token", null, {
89
+ params: {
90
+ client_id: clientId,
91
+ client_secret: clientSecret,
92
+ code: authCode,
93
+ grant_type: "authorization_code",
94
+ redirect_uri: "urn:ietf:wg:oauth:2.0:oob",
95
+ },
96
+ });
97
+
98
+ const { access_token, refresh_token, expires_in } = tokenResponse.data;
99
+
100
+ const credentials = {
101
+ access_token,
102
+ refresh_token,
103
+ expires_at: Date.now() + expires_in * 1000,
104
+ client_id: clientId,
105
+ client_secret: clientSecret
106
+ };
107
+
108
+ // Save to integrations
109
+ const { data: integration, error } = await req.supabase
110
+ .from("integrations")
111
+ .upsert(
112
+ {
113
+ user_id: req.user.id,
114
+ provider: "google_drive",
115
+ credentials,
116
+ is_enabled: true
117
+ },
118
+ { onConflict: "user_id,provider" }
119
+ )
120
+ .select()
121
+ .single();
122
+
123
+ if (error) {
124
+ throw new Error(`Database error: ${error.message}`);
125
+ }
126
+
127
+ res.json({ success: true, account: integration });
128
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
129
+ } catch (error: any) {
130
+ const errorMessage = error.response?.data?.error_description
131
+ || error.response?.data?.error
132
+ || error.message
133
+ || "Failed to connect Google Drive";
134
+
135
+ res.status(500).json({
136
+ error: errorMessage
137
+ });
138
+ }
139
+ })
140
+ );
141
+
142
+ export default router;
@@ -0,0 +1,124 @@
1
+ import { Router } from "express";
2
+ import { asyncHandler } from "../middleware/errorHandler.js";
3
+ import { optionalAuth } from "../middleware/auth.js";
4
+ import { BaselineConfigService, DEFAULT_BASELINE_FIELDS } from "../services/BaselineConfigService.js";
5
+ import { PolicyEngine } from "../services/PolicyEngine.js";
6
+
7
+ const router = Router();
8
+ router.use(optionalAuth);
9
+
10
+ // GET /api/baseline-config
11
+ // Returns the active config. If none exists, returns the built-in defaults
12
+ // with id: null so the UI can distinguish "never saved" from "saved and active".
13
+ router.get(
14
+ "/",
15
+ asyncHandler(async (req, res) => {
16
+ if (!req.supabase || !req.user) {
17
+ res.status(401).json({ success: false, error: "Authentication required" });
18
+ return;
19
+ }
20
+ const config = await BaselineConfigService.getActive(req.supabase, req.user.id);
21
+ res.json({
22
+ success: true,
23
+ config,
24
+ defaults: DEFAULT_BASELINE_FIELDS,
25
+ });
26
+ })
27
+ );
28
+
29
+ // GET /api/baseline-config/history
30
+ // Returns all saved versions for the user, newest first.
31
+ router.get(
32
+ "/history",
33
+ asyncHandler(async (req, res) => {
34
+ if (!req.supabase || !req.user) {
35
+ res.status(401).json({ success: false, error: "Authentication required" });
36
+ return;
37
+ }
38
+ const history = await BaselineConfigService.list(req.supabase, req.user.id);
39
+ res.json({ success: true, history });
40
+ })
41
+ );
42
+
43
+ // POST /api/baseline-config
44
+ // Save a new version. Body: { context?, fields[], activate? }
45
+ // Always creates a new row — never mutates an existing version.
46
+ router.post(
47
+ "/",
48
+ asyncHandler(async (req, res) => {
49
+ if (!req.supabase || !req.user) {
50
+ res.status(401).json({ success: false, error: "Authentication required" });
51
+ return;
52
+ }
53
+ const { context, fields, activate = true } = req.body;
54
+ if (!Array.isArray(fields) || fields.length === 0) {
55
+ res.status(400).json({ success: false, error: "fields array is required and must not be empty" });
56
+ return;
57
+ }
58
+ const config = await BaselineConfigService.save(
59
+ req.supabase,
60
+ req.user.id,
61
+ { context, fields },
62
+ activate
63
+ );
64
+ res.status(201).json({ success: true, config });
65
+ })
66
+ );
67
+
68
+ // POST /api/baseline-config/:id/activate
69
+ // Activate a previously saved version.
70
+ router.post(
71
+ "/:id/activate",
72
+ asyncHandler(async (req, res) => {
73
+ if (!req.supabase || !req.user) {
74
+ res.status(401).json({ success: false, error: "Authentication required" });
75
+ return;
76
+ }
77
+ const ok = await BaselineConfigService.activate(
78
+ req.supabase,
79
+ req.user.id,
80
+ req.params.id as string
81
+ );
82
+ if (!ok) {
83
+ res.status(404).json({ success: false, error: "Config version not found" });
84
+ return;
85
+ }
86
+ res.json({ success: true });
87
+ })
88
+ );
89
+
90
+ // POST /api/baseline-config/suggest
91
+ // Body: { description, provider?, model? }
92
+ // Returns a draft { context, fields[] } for the user to review before saving.
93
+ router.post(
94
+ "/suggest",
95
+ asyncHandler(async (req, res) => {
96
+ if (!req.supabase || !req.user) {
97
+ res.status(401).json({ success: false, error: "Authentication required" });
98
+ return;
99
+ }
100
+ const { description, provider, model } = req.body;
101
+ if (!description || typeof description !== "string") {
102
+ res.status(400).json({ success: false, error: "description is required" });
103
+ return;
104
+ }
105
+
106
+ // Pass current active fields so the LLM avoids duplicating them
107
+ const activeConfig = await BaselineConfigService.getActive(req.supabase, req.user.id);
108
+ const currentFields = activeConfig?.fields ?? DEFAULT_BASELINE_FIELDS;
109
+
110
+ const result = await PolicyEngine.suggestBaseline(description, currentFields, {
111
+ provider,
112
+ model,
113
+ userId: req.user.id,
114
+ supabase: req.supabase,
115
+ });
116
+ if (!result.suggestion) {
117
+ res.status(503).json({ success: false, error: result.error ?? "Suggestion failed. SDK may be unavailable." });
118
+ return;
119
+ }
120
+ res.json({ success: true, suggestion: result.suggestion });
121
+ })
122
+ );
123
+
124
+ export default router;
@@ -0,0 +1,154 @@
1
+ import { Router } from "express";
2
+ import { asyncHandler } from "../middleware/errorHandler.js";
3
+ import { optionalAuth } from "../middleware/auth.js";
4
+ import { ChatService } from "../services/ChatService.js";
5
+
6
+ const router = Router();
7
+
8
+ interface PostMessageBody {
9
+ sessionId?: string;
10
+ content?: string;
11
+ }
12
+
13
+ // All chat routes require authentication
14
+ router.use(optionalAuth);
15
+
16
+ // GET /api/chat/sessions
17
+ router.get(
18
+ "/sessions",
19
+ asyncHandler(async (req, res) => {
20
+ if (!req.supabase || !req.user) {
21
+ res.status(401).json({ success: false, error: "Authentication required" });
22
+ return;
23
+ }
24
+
25
+ const { data: sessions, error } = await req.supabase
26
+ .from("chat_sessions")
27
+ .select("*")
28
+ .eq("user_id", req.user.id)
29
+ .order("updated_at", { ascending: false });
30
+
31
+ if (error) {
32
+ res.status(500).json({ success: false, error: error.message });
33
+ return;
34
+ }
35
+
36
+ res.json({ success: true, sessions });
37
+ })
38
+ );
39
+
40
+ // POST /api/chat/sessions
41
+ router.post(
42
+ "/sessions",
43
+ asyncHandler(async (req, res) => {
44
+ if (!req.supabase || !req.user) {
45
+ res.status(401).json({ success: false, error: "Authentication required" });
46
+ return;
47
+ }
48
+
49
+ const { data: session, error } = await req.supabase
50
+ .from("chat_sessions")
51
+ .insert({ user_id: req.user.id, title: "New Conversation" })
52
+ .select("*")
53
+ .single();
54
+
55
+ if (error) {
56
+ res.status(500).json({ success: false, error: error.message });
57
+ return;
58
+ }
59
+
60
+ res.status(201).json({ success: true, session });
61
+ })
62
+ );
63
+
64
+ // GET /api/chat/sessions/:id/messages
65
+ router.get(
66
+ "/sessions/:id/messages",
67
+ asyncHandler(async (req, res) => {
68
+ if (!req.supabase || !req.user) {
69
+ res.status(401).json({ success: false, error: "Authentication required" });
70
+ return;
71
+ }
72
+
73
+ // Verify session belongs to user
74
+ const { data: sessionData } = await req.supabase
75
+ .from("chat_sessions")
76
+ .select("id")
77
+ .eq("id", req.params["id"] as string)
78
+ .eq("user_id", req.user.id)
79
+ .maybeSingle();
80
+
81
+ if (!sessionData) {
82
+ res.status(404).json({ success: false, error: "Session not found" });
83
+ return;
84
+ }
85
+
86
+ const { data: messages, error } = await req.supabase
87
+ .from("chat_messages")
88
+ .select("*")
89
+ .eq("session_id", req.params["id"] as string)
90
+ .eq("user_id", req.user.id)
91
+ .order("created_at", { ascending: true });
92
+
93
+ if (error) {
94
+ res.status(500).json({ success: false, error: error.message });
95
+ return;
96
+ }
97
+
98
+ res.json({ success: true, messages });
99
+ })
100
+ );
101
+
102
+ // POST /api/chat/message
103
+ router.post(
104
+ "/message",
105
+ asyncHandler(async (req, res) => {
106
+ if (!req.supabase || !req.user) {
107
+ res.status(401).json({ success: false, error: "Authentication required" });
108
+ return;
109
+ }
110
+
111
+ const { sessionId, content } = (req.body ?? {}) as PostMessageBody;
112
+ const normalizedSessionId = typeof sessionId === "string" ? sessionId : "";
113
+ const trimmedContent = typeof content === "string" ? content.trim() : "";
114
+ if (!normalizedSessionId || !trimmedContent) {
115
+ res.status(400).json({ success: false, error: "Missing sessionId or content" });
116
+ return;
117
+ }
118
+
119
+ // Verify session belongs to user
120
+ const { data: sessionData } = await req.supabase
121
+ .from("chat_sessions")
122
+ .select("id")
123
+ .eq("id", normalizedSessionId)
124
+ .eq("user_id", req.user.id)
125
+ .maybeSingle();
126
+
127
+ if (!sessionData) {
128
+ res.status(404).json({ success: false, error: "Session not found" });
129
+ return;
130
+ }
131
+
132
+ // Dynamically name session if it's the first message
133
+ const { count } = await req.supabase
134
+ .from("chat_messages")
135
+ .select("*", { count: 'exact', head: true })
136
+ .eq("session_id", normalizedSessionId)
137
+ .eq("user_id", req.user.id);
138
+
139
+ if (count === 0 && trimmedContent.length > 3) {
140
+ const title = trimmedContent.substring(0, 30) + (trimmedContent.length > 30 ? "..." : "");
141
+ await req.supabase.from("chat_sessions").update({ title }).eq("id", normalizedSessionId);
142
+ }
143
+
144
+ try {
145
+ const aiMessage = await ChatService.handleMessage(normalizedSessionId, req.user.id, trimmedContent, req.supabase);
146
+ res.json({ success: true, message: aiMessage });
147
+ } catch (error) {
148
+ const message = error instanceof Error ? error.message : "Failed to process message";
149
+ res.status(500).json({ success: false, error: message });
150
+ }
151
+ })
152
+ );
153
+
154
+ export default router;
@@ -0,0 +1,61 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import { createClient } from "@supabase/supabase-js";
5
+ import { Router } from "express";
6
+
7
+ import { config } from "../config/index.js";
8
+ import { SDKService } from "../services/SDKService.js";
9
+ import { getServerSupabase, getSupabaseConfigFromHeaders } from "../services/supabase.js";
10
+
11
+ const router = Router();
12
+
13
+ let version = "0.0.0";
14
+ try {
15
+ const pkgPath = join(config.packageRoot, "package.json");
16
+ version = JSON.parse(readFileSync(pkgPath, "utf8")).version;
17
+ } catch {
18
+ // fallback stays at default
19
+ }
20
+
21
+ router.get("/", async (req, res) => {
22
+ let supabase = getServerSupabase();
23
+
24
+ if (!supabase) {
25
+ const headerConfig = getSupabaseConfigFromHeaders(req.headers as Record<string, unknown>);
26
+ if (headerConfig) {
27
+ supabase = createClient(headerConfig.url, headerConfig.anonKey, {
28
+ auth: {
29
+ autoRefreshToken: false,
30
+ persistSession: false
31
+ }
32
+ });
33
+ }
34
+ }
35
+
36
+ let databaseStatus = "not_configured";
37
+
38
+ if (supabase) {
39
+ try {
40
+ const { error } = await supabase.from("user_settings").select("id").limit(1);
41
+ databaseStatus = error ? "error" : "connected";
42
+ } catch {
43
+ databaseStatus = "error";
44
+ }
45
+ }
46
+
47
+ const sdkAvailable = await SDKService.isAvailable();
48
+
49
+ res.json({
50
+ status: "healthy",
51
+ timestamp: new Date().toISOString(),
52
+ version,
53
+ environment: config.nodeEnv,
54
+ services: {
55
+ database: databaseStatus,
56
+ realtimeXSdk: sdkAvailable ? "available" : "unavailable"
57
+ }
58
+ });
59
+ });
60
+
61
+ export default router;
@@ -0,0 +1,35 @@
1
+ import { Router } from "express";
2
+
3
+ import healthRoutes from "./health.js";
4
+ import migrateRoutes from "./migrate.js";
5
+ import processingRoutes from "./processing.js";
6
+ import setupRoutes from "./setup.js";
7
+ import ttsRoutes from "./tts.js";
8
+ import sdkRoutes from "./sdk.js";
9
+ import policiesRoutes from "./policies.js";
10
+ import ingestionsRoutes from "./ingestions.js";
11
+ import baselineConfigRoutes from "./baseline-config.js";
12
+ import accountsRoutes from "./accounts.js";
13
+ import settingsRoutes from "./settings.js";
14
+ import rulesRoutes from "./rules.js";
15
+ import chatRoutes from "./chat.js";
16
+ import statsRoutes from "./stats.js";
17
+
18
+ const router = Router();
19
+
20
+ router.use("/health", healthRoutes);
21
+ router.use("/migrate", migrateRoutes);
22
+ router.use("/setup", setupRoutes);
23
+ router.use("/processing", processingRoutes);
24
+ router.use("/tts", ttsRoutes);
25
+ router.use("/sdk", sdkRoutes);
26
+ router.use("/policies", policiesRoutes);
27
+ router.use("/ingestions", ingestionsRoutes);
28
+ router.use("/baseline-config", baselineConfigRoutes);
29
+ router.use("/accounts", accountsRoutes);
30
+ router.use("/settings", settingsRoutes);
31
+ router.use("/rules", rulesRoutes);
32
+ router.use("/chat", chatRoutes);
33
+ router.use("/stats", statsRoutes);
34
+
35
+ export default router;