@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,32 @@
1
+ # Folio Port Map
2
+
3
+ This document maps what was intentionally inherited from `email-automator` into Folio before adding product features.
4
+
5
+ ## Inherited Foundations
6
+
7
+ - Local desktop runtime shape: React frontend + local Express backend
8
+ - Remote Supabase data plane with RLS-first schema and migrations
9
+ - Hybrid API client pattern: Edge Functions + Local API
10
+ - Dynamic Supabase config strategy: BYOK via UI/localStorage with env fallback
11
+ - Middleware stack: auth, validation, rate-limit, centralized error handling
12
+ - RealTimeX SDK bootstrap and provider detection service
13
+ - Migration/version check utility pattern
14
+ - CLI wrappers and migration script conventions
15
+
16
+ ## Adapted for Folio
17
+
18
+ - Default local API port changed to `3006`
19
+ - Domain models reduced to foundation tables (`user_settings`, `integrations`, `processing_jobs`, `system_logs`)
20
+ - Processing route is scaffold-only (`/api/processing/dispatch`), no business logic
21
+ - Edge function baseline includes only `api-v1-settings`
22
+
23
+ ## Explicitly Deferred
24
+
25
+ - Any Folio ingestion funnel implementation
26
+ - OCR/classification/extraction behavior
27
+ - Rule/routing business actions
28
+ - Calendar/ledger side effects
29
+
30
+ ## Why this sequence
31
+
32
+ This keeps architecture parity with the proven runtime and prevents rework. Feature development can now layer on top of stable app/server/database/runtime contracts.
@@ -0,0 +1,98 @@
1
+ # Technical Specification (Tech Spec)
2
+
3
+ **Project:** Folio Core Engine
4
+
5
+ ## 1. System Architecture
6
+
7
+ The system follows a **Pipeline Architecture**:
8
+ `Watcher` $\rightarrow$ `Preprocessor` $\rightarrow$ `Intelligence Engine` $\rightarrow$ `Actuator`
9
+
10
+ ### 1.1 Component Diagram
11
+ 1. **Watcher Service:** Python `watchdog` library monitoring local paths.
12
+ 2. **OCR/Vision Module:** Converts PDF/Images to text/layout data.
13
+ 3. **LLM Router:** The decision maker (OpenAI GPT-4o via API or Local Llama 3 via Ollama).
14
+ 4. **File System Manager:** Python `shutil` / `os` for moving/renaming.
15
+ 5. **Integration Manager:** APIs for Google Sheets, Notion, or CalDAV.
16
+
17
+ ## 2. Data Flow & Logic
18
+
19
+ ### Step 1: Ingestion & Pre-processing
20
+ * **Trigger:** New file detected in `/input`.
21
+ * **Action:**
22
+ * Generate SHA-256 hash (to prevent duplicate processing).
23
+ * Convert PDF to Image (for Vision models) using `pdf2image`.
24
+ * **Text Extraction:** Use `Tesseract` (Local) for basic text or send Image to Multimodal LLM.
25
+
26
+ ### Step 2: The "Reasoning Loop" (LLM Prompt Strategy)
27
+ * **Input:** Raw OCR text + Image.
28
+ * **Prompt Structure (System Prompt):**
29
+ > "You are Folio, a document assistant. Analyze the attached image.
30
+ > 1. Classify the document (Tax, Bill, Junk, Medical).
31
+ > 2. Extract the Document Date (YYYY-MM-DD).
32
+ > 3. Identify the Vendor/Sender Entity.
33
+ > 4. If Bill: Extract Total Amount.
34
+ > 5. If Tax: Extract Form Type (e.g., 1099-DIV).
35
+ > Return response STRICTLY as JSON."
36
+
37
+ ### Step 3: Validation & Routing
38
+ * **JSON Validation:** Python `Pydantic` validates the LLM output.
39
+ * *Check:* Is the date valid? Is the amount a float?
40
+ * **Confidence Check:** If critical fields are missing, move file to `/Needs_Review`.
41
+
42
+ ### Step 4: Execution (The "Actuator")
43
+ * **Renaming Logic:**
44
+ ```python
45
+ new_filename = f"{date}_{entity}_{doc_type}.pdf"
46
+ # Example: 2023-12-01_Kaiser_MedicalBill.pdf
47
+ ```
48
+ * **Filing Logic:**
49
+ * Map `Tax` $\rightarrow$ `/Server/Docs/Financial/Taxes/{Year}/`
50
+ * Map `Junk` $\rightarrow$ `/Server/Trash/`
51
+ * **Side Effects:**
52
+ * Update `ledger.csv` with `[Date, Entity, Type, Amount, FilePath]`.
53
+
54
+ ## 3. Data Schema (The "Manifest")
55
+
56
+ Every processed document generates a "Sidecar JSON" metadata file stored in a hidden `.folio` folder for searchability.
57
+
58
+ ```json
59
+ {
60
+ "id": "a1b2c3d4",
61
+ "original_name": "scan_001.pdf",
62
+ "processed_name": "2024-02-14_Chase_Statement.pdf",
63
+ "classification": "Financial_Statement",
64
+ "confidence_score": 0.98,
65
+ "metadata": {
66
+ "entity": "Chase Bank",
67
+ "account_last_4": "4490",
68
+ "statement_period": "Jan 2024",
69
+ "total_balance": 4500.00
70
+ },
71
+ "actions_taken": [
72
+ "Renamed",
73
+ "Moved to /Financial/Bank/Chase",
74
+ "Logged to SQLite DB"
75
+ ]
76
+ }
77
+ ```
78
+
79
+ ## 4. API & Integration Design
80
+
81
+ ### 4.1 Internal APIs (Python Classes)
82
+ * `DocumentProcessor.process(file_path)`
83
+ * `Classifier.predict(text_content)`
84
+ * `Extractor.get_entities(text_content, schema)`
85
+
86
+ ### 4.2 External Hooks
87
+ * **Google Sheets:** Use `gspread` library to append rows for tax tracking.
88
+ * **Notion:** Use Notion API to create a database entry for every "Legal" document found.
89
+
90
+ ## 5. Security & Privacy Implementation
91
+
92
+ To handle the "Sunnyvale Resident" requirement (SSN, Financials):
93
+
94
+ 1. **Tier 1 (Junk/Generic):** Can use Cloud LLM (OpenAI/Anthropic) for high accuracy.
95
+ 2. **Tier 2 (Sensitive - Tax/Medical):**
96
+ * **Option A:** Use **Local LLM** (Mistral-7B or Llama-3 running on local Mac Studio/PC). **Zero data leaves the house.**
97
+ * **Option B:** Redact PII (Regex for SSN patterns) before sending to Cloud API.
98
+
package/index.html ADDED
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
7
+ <title>Folio</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.tsx"></script>
12
+ </body>
13
+ </html>
package/package.json ADDED
@@ -0,0 +1,101 @@
1
+ {
2
+ "name": "@realtimex/folio",
3
+ "version": "0.1.2",
4
+ "type": "module",
5
+ "main": "dist/api/server.js",
6
+ "bin": {
7
+ "folio": "./bin/folio.js",
8
+ "folio-setup": "./bin/folio-setup.js",
9
+ "folio-deploy": "./bin/folio-deploy.js"
10
+ },
11
+ "files": [
12
+ "bin",
13
+ "api",
14
+ "dist",
15
+ "docs-dev",
16
+ "supabase",
17
+ "scripts",
18
+ "public",
19
+ ".env.example",
20
+ "tsconfig.json",
21
+ "tsconfig.api.json",
22
+ "vite.config.ts",
23
+ "index.html",
24
+ "README.md"
25
+ ],
26
+ "scripts": {
27
+ "dev": "vite",
28
+ "dev:api": "tsx watch api/server.ts",
29
+ "build": "npm run build:ui && npm run build:api",
30
+ "build:ui": "vite build",
31
+ "build:api": "tsc -p tsconfig.api.json",
32
+ "preview": "vite preview",
33
+ "serve": "node dist/api/server.js",
34
+ "start": "node dist/api/server.js",
35
+ "test": "vitest run",
36
+ "typecheck": "tsc --noEmit",
37
+ "lint": "eslint . --ext .ts,.tsx --max-warnings=0",
38
+ "migrate": "bash ./scripts/migrate.sh",
39
+ "task": "node scripts/dev-task.mjs"
40
+ },
41
+ "keywords": [
42
+ "folio",
43
+ "realtimex",
44
+ "desktop",
45
+ "supabase",
46
+ "local-app",
47
+ "document-automation"
48
+ ],
49
+ "license": "MIT",
50
+ "description": "Folio: local desktop app + remote Supabase + RealTimeX SDK backend runtime",
51
+ "engines": {
52
+ "node": ">=20.0.0"
53
+ },
54
+ "dependencies": {
55
+ "@radix-ui/react-alert-dialog": "^1.1.15",
56
+ "@radix-ui/react-dialog": "^1.1.15",
57
+ "@radix-ui/react-label": "^2.1.8",
58
+ "@radix-ui/react-slot": "^1.2.4",
59
+ "@realtimex/sdk": "^1.3.3",
60
+ "@supabase/supabase-js": "^2.90.1",
61
+ "@tailwindcss/vite": "^4.2.1",
62
+ "@types/multer": "^2.0.0",
63
+ "axios": "^1.13.4",
64
+ "class-variance-authority": "^0.7.1",
65
+ "clsx": "^2.1.1",
66
+ "cors": "^2.8.5",
67
+ "dotenv": "^17.2.3",
68
+ "express": "^5.2.1",
69
+ "framer-motion": "^12.34.3",
70
+ "js-yaml": "^4.1.1",
71
+ "lucide-react": "^0.575.0",
72
+ "multer": "^2.0.2",
73
+ "next-themes": "^0.4.6",
74
+ "pdf-parse": "^2.4.5",
75
+ "react": "^19.2.3",
76
+ "react-dom": "^19.2.3",
77
+ "tailwind-merge": "^3.5.0",
78
+ "tailwindcss": "^4.2.1",
79
+ "tsx": "^4.21.0",
80
+ "zod": "^3.25.76"
81
+ },
82
+ "devDependencies": {
83
+ "@eslint/js": "^9.39.3",
84
+ "@types/cors": "^2.8.19",
85
+ "@types/express": "^5.0.6",
86
+ "@types/js-yaml": "^4.0.9",
87
+ "@types/node": "^25.0.8",
88
+ "@types/react": "^19.2.8",
89
+ "@types/react-dom": "^19.2.3",
90
+ "@vitejs/plugin-react": "^5.1.2",
91
+ "eslint": "^9.39.3",
92
+ "eslint-plugin-react-hooks": "^7.0.1",
93
+ "eslint-plugin-react-refresh": "^0.4.26",
94
+ "globals": "^16.5.0",
95
+ "jsdom": "^26.1.0",
96
+ "typescript": "^5.9.3",
97
+ "typescript-eslint": "^8.56.1",
98
+ "vite": "^7.3.1",
99
+ "vitest": "^3.2.4"
100
+ }
101
+ }
@@ -0,0 +1,31 @@
1
+ <svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <!--
3
+ FAVICON STRATEGY:
4
+ 1. High Contrast: Uses the brand Indigo against transparent/white.
5
+ 2. Simplified Geometry: Removed shadows and subtle opacity layers.
6
+ 3. Scalable: This vector will look crisp on Retina displays.
7
+ -->
8
+
9
+ <defs>
10
+ <linearGradient id="faviconGradient" x1="0" y1="0" x2="64" y2="64" gradientUnits="userSpaceOnUse">
11
+ <stop offset="0%" stop-color="#6366F1" /> <!-- Indigo-500 -->
12
+ <stop offset="100%" stop-color="#4338CA" /> <!-- Indigo-700 -->
13
+ </linearGradient>
14
+ </defs>
15
+
16
+ <!-- The "Back" Page (Input) - Lighter/Translucent representation -->
17
+ <!-- Positioned to create the right stem of the abstract 'F' -->
18
+ <path d="M28 12 H 48 C 50.2 12 52 13.8 52 16 V 48 L 40 36 V 12 Z"
19
+ fill="url(#faviconGradient)"
20
+ fill-opacity="0.5" />
21
+
22
+ <!-- The "Front" Page (Output) - Solid representation -->
23
+ <!-- Positioned to create the main body of the 'F' -->
24
+ <path d="M12 12 H 36 C 38.2 12 40 13.8 40 16 V 52 C 40 54.2 38.2 56 36 56 H 12 V 12 Z"
25
+ fill="url(#faviconGradient)" />
26
+
27
+ <!-- The "Fold" (Action) - The distinctive dog-ear -->
28
+ <path d="M40 16 L 36 16 C 34.9 16 34 15.1 34 14 L 34 12"
29
+ fill="#FFFFFF"
30
+ fill-opacity="0.6"/>
31
+ </svg>
@@ -0,0 +1,46 @@
1
+ <svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <defs>
3
+ <!-- Main Gradient: Gives a subtle 'tech' feel and depth -->
4
+ <linearGradient id="folioGradient" x1="0" y1="0" x2="512" y2="512" gradientUnits="userSpaceOnUse">
5
+ <stop offset="0%" stop-color="#6366F1" /> <!-- Indigo-500 -->
6
+ <stop offset="100%" stop-color="#4338CA" /> <!-- Indigo-700 -->
7
+ </linearGradient>
8
+
9
+ <!-- Shadow for depth between the pages -->
10
+ <filter id="dropShadow" x="-20%" y="-20%" width="140%" height="140%">
11
+ <feGaussianBlur in="SourceAlpha" stdDeviation="8"/>
12
+ <feOffset dx="4" dy="8" result="offsetblur"/>
13
+ <feComponentTransfer>
14
+ <feFuncA type="linear" slope="0.3"/>
15
+ </feComponentTransfer>
16
+ <feMerge>
17
+ <feMergeNode/>
18
+ <feMergeNode in="SourceGraphic"/>
19
+ </feMerge>
20
+ </filter>
21
+ </defs>
22
+
23
+ <!-- BACKGROUND CONTAINER: Ensures visibility on both Dark and Light modes -->
24
+ <rect x="32" y="32" width="448" height="448" rx="112" fill="url(#folioGradient)" />
25
+
26
+ <!-- ICON ELEMENTS -->
27
+ <g filter="url(#dropShadow)">
28
+ <!-- The "Back" Page (The Foundation) -->
29
+ <!-- Representing the raw data/input -->
30
+ <path d="M180 140 H 330 C 352.09 140 370 157.91 370 180 V 370 C 370 370 370 370 370 370 H 260 L 180 290 V 140 Z"
31
+ fill="#FFFFFF"
32
+ fill-opacity="0.4" />
33
+
34
+ <!-- The "Front" Page (The Organization) -->
35
+ <!-- Representing the processed, clean output.
36
+ Notice how it creates an abstract 'F' shape with the back page. -->
37
+ <path d="M140 140 H 280 C 302.09 140 320 157.91 320 180 V 332 C 320 354.09 302.09 372 280 372 H 140 V 140 Z"
38
+ fill="#FFFFFF" />
39
+
40
+ <!-- The "Action" Accent (The Dog Ear / Fold) -->
41
+ <!-- Adds a tactile feel, implying this is a document being handled. -->
42
+ <path d="M320 180 L 280 180 C 268.95 180 260 171.05 260 160 L 260 140"
43
+ fill="#C7D2FE"
44
+ fill-opacity="0.5"/>
45
+ </g>
46
+ </svg>
@@ -0,0 +1,51 @@
1
+ import { execSync } from 'node:child_process';
2
+
3
+ // Helper to check for gh CLI
4
+ try {
5
+ execSync('gh --version', { stdio: 'ignore' });
6
+ } catch (e) {
7
+ console.error('Error: GitHub CLI (gh) is not installed. Please install it first: https://cli.github.com/');
8
+ process.exit(1);
9
+ }
10
+
11
+ // Helper to check for gh auth
12
+ try {
13
+ execSync('gh auth status', { stdio: 'ignore' });
14
+ } catch (e) {
15
+ console.error('Error: GitHub CLI is not authenticated. Run "gh auth login" first.');
16
+ process.exit(1);
17
+ }
18
+
19
+ const title = process.argv[2];
20
+ const body = process.argv.slice(3).join(' ');
21
+
22
+ if (!title) {
23
+ console.error('Usage: npm run task "Task title" "Task description (optional)"');
24
+ process.exit(1);
25
+ }
26
+
27
+ const label = 'agent-task';
28
+
29
+ // Create label if it doesn't exist
30
+ try {
31
+ execSync(`gh label list | grep -q "${label}"`, { stdio: 'ignore' });
32
+ } catch (e) {
33
+ console.log(`Creating label "${label}"...`);
34
+ try {
35
+ execSync(`gh label create ${label} --color "#5319e7" --description "Tasks for the Gemini agent to process"`, { stdio: 'inherit' });
36
+ } catch (labelError) {
37
+ // Label might have been created by another process or exist but grep failed
38
+ console.warn(`Note: Could not create label "${label}", it may already exist or there was a permission issue.`);
39
+ }
40
+ }
41
+
42
+ console.log(`Creating issue: ${title}...`);
43
+ try {
44
+ const result = execSync(`gh issue create --title "${title}" --body "${body || 'No description provided.'}" --label "${label}"`, { encoding: 'utf8' }).trim();
45
+ console.log(`
46
+ Successfully created issue: ${result}`);
47
+ console.log(`Agent is now ready to pick up: ${title}`);
48
+ } catch (e) {
49
+ console.error('Failed to create issue:', e.message);
50
+ process.exit(1);
51
+ }
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readdirSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+
10
+ const migrationsDir = join(__dirname, "..", "supabase", "migrations");
11
+
12
+ try {
13
+ const files = readdirSync(migrationsDir);
14
+ const timestamps = files
15
+ .filter((file) => file.endsWith(".sql"))
16
+ .filter((file) => !file.toLowerCase().includes("test")) // exclude test migrations
17
+ .map((file) => {
18
+ const match = file.match(/^(\d{14})_/);
19
+ return match ? match[1] : null;
20
+ })
21
+ .filter(Boolean)
22
+ .sort()
23
+ .reverse();
24
+
25
+ if (timestamps.length === 0) {
26
+ console.log("20240101000000");
27
+ process.exit(0);
28
+ }
29
+
30
+ console.log(timestamps[0]);
31
+ } catch (error) {
32
+ console.error("Error reading migrations:", error instanceof Error ? error.message : String(error));
33
+ process.exit(1);
34
+ }
@@ -0,0 +1,91 @@
1
+ #!/bin/bash
2
+
3
+ set -euo pipefail
4
+
5
+ echo "🚀 Starting Folio migration..."
6
+
7
+ SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
8
+ ROOT_DIR=$(cd "$SCRIPT_DIR/.." && pwd)
9
+ cd "$ROOT_DIR"
10
+
11
+ SUPABASE_CMD="supabase"
12
+ if [ -x "./node_modules/.bin/supabase" ]; then
13
+ SUPABASE_CMD="./node_modules/.bin/supabase"
14
+ elif ! command -v supabase &> /dev/null; then
15
+ SUPABASE_CMD="npx supabase@latest"
16
+ fi
17
+
18
+ if [ -z "${SUPABASE_PROJECT_ID:-}" ]; then
19
+ read -p "👉 Enter Supabase Project ID: " SUPABASE_PROJECT_ID
20
+ fi
21
+
22
+ if [ -z "${SUPABASE_PROJECT_ID:-}" ]; then
23
+ echo "❌ Error: SUPABASE_PROJECT_ID is required"
24
+ exit 1
25
+ fi
26
+
27
+ if [ -n "${SUPABASE_ACCESS_TOKEN:-}" ]; then
28
+ echo "🔑 Using provided Supabase Access Token"
29
+ export SUPABASE_ACCESS_TOKEN="$SUPABASE_ACCESS_TOKEN"
30
+ fi
31
+
32
+ echo "🔗 Linking project: $SUPABASE_PROJECT_ID"
33
+ $SUPABASE_CMD link --project-ref "$SUPABASE_PROJECT_ID" --yes
34
+
35
+ echo "📂 Pushing database migrations..."
36
+ max_retries="${DB_PUSH_MAX_RETRIES:-3}"
37
+ attempt=1
38
+ while true; do
39
+ set +e
40
+ DB_PUSH_OUTPUT=$($SUPABASE_CMD db push --include-all --yes 2>&1)
41
+ status=$?
42
+ set -e
43
+
44
+ echo "$DB_PUSH_OUTPUT"
45
+
46
+ if [ $status -eq 0 ]; then
47
+ break
48
+ fi
49
+
50
+ if echo "$DB_PUSH_OUTPUT" | grep -q "57P03\\|shutting down\\|Failed to create login role\\|connection reset"; then
51
+ if [ $attempt -lt $max_retries ]; then
52
+ wait_seconds=$((attempt * 10))
53
+ echo "⏳ Database appears busy/restarting. Retrying in ${wait_seconds}s (${attempt}/${max_retries})..."
54
+ sleep $wait_seconds
55
+ attempt=$((attempt + 1))
56
+ continue
57
+ fi
58
+ fi
59
+
60
+ echo "❌ Database push failed"
61
+ exit $status
62
+ done
63
+
64
+ echo "⚙️ Pushing Supabase project config..."
65
+ $SUPABASE_CMD config push --yes
66
+
67
+ if [ "${SKIP_FUNCTIONS:-0}" != "1" ]; then
68
+ echo "⚡ Deploying Edge Functions..."
69
+ if [ -d "supabase/functions" ]; then
70
+ for dir in supabase/functions/*/ ; do
71
+ func_name=$(basename "$dir")
72
+
73
+ if [ ! -d "$dir" ] || [[ "$func_name" =~ ^[._] ]] || [ "$func_name" == "_shared" ]; then
74
+ continue
75
+ fi
76
+
77
+ if [ -f "$dir/index.ts" ]; then
78
+ echo " Deploying $func_name"
79
+ $SUPABASE_CMD functions deploy "$func_name" --no-verify-jwt --use-api --yes
80
+ else
81
+ echo " ⏭️ Skipping $func_name (missing index.ts)"
82
+ fi
83
+ done
84
+ fi
85
+ else
86
+ echo "⏭️ SKIP_FUNCTIONS=1, skipping function deployment"
87
+ fi
88
+
89
+ echo ""
90
+ echo "✅ Folio migration completed"
91
+ echo ""
@@ -0,0 +1 @@
1
+ v2.75.0
@@ -0,0 +1 @@
1
+ v2.187.0
@@ -0,0 +1 @@
1
+ postgresql://postgres.hywjwqqgrhofmjddpswj@aws-1-us-west-1.pooler.supabase.com:5432/postgres
@@ -0,0 +1 @@
1
+ 17.6.1.063
@@ -0,0 +1 @@
1
+ hywjwqqgrhofmjddpswj
@@ -0,0 +1 @@
1
+ v14.1
@@ -0,0 +1 @@
1
+ fix-optimized-search-function
@@ -0,0 +1 @@
1
+ v1.37.7
@@ -0,0 +1,64 @@
1
+ [api]
2
+ enabled = true
3
+ port = 54321
4
+ schemas = ["public", "storage", "graphql_public"]
5
+ extra_search_path = ["public", "extensions"]
6
+ max_rows = 1000
7
+
8
+ [db]
9
+ port = 54322
10
+ major_version = 17
11
+
12
+ [studio]
13
+ enabled = true
14
+ port = 54323
15
+
16
+ [storage]
17
+ enabled = true
18
+ file_size_limit = "50MiB"
19
+
20
+ [auth]
21
+ enabled = true
22
+ site_url = "http://localhost:5176"
23
+ additional_redirect_urls = ["http://localhost:5173", "http://localhost:5176", "http://localhost:3006"]
24
+ jwt_expiry = 3600
25
+ enable_refresh_token_rotation = true
26
+ refresh_token_reuse_interval = 10
27
+ enable_signup = true
28
+ enable_anonymous_sign_ins = false
29
+ enable_manual_linking = false
30
+ minimum_password_length = 6
31
+ password_requirements = ""
32
+
33
+ [auth.email]
34
+ enable_signup = true
35
+ double_confirm_changes = true
36
+ enable_confirmations = false
37
+ secure_password_change = false
38
+ max_frequency = "1s"
39
+ otp_length = 6
40
+ otp_expiry = 3600
41
+
42
+ [auth.email.template.invite]
43
+ subject = "You have been invited to Folio"
44
+ content_path = "supabase/templates/invite.html"
45
+
46
+ [auth.email.template.confirmation]
47
+ subject = "Confirm your Folio account"
48
+ content_path = "supabase/templates/confirmation.html"
49
+
50
+ [auth.email.template.recovery]
51
+ subject = "Reset your Folio password"
52
+ content_path = "supabase/templates/recovery.html"
53
+
54
+ [auth.email.template.magic_link]
55
+ subject = "Login to Folio"
56
+ content_path = "supabase/templates/magic-link.html"
57
+
58
+ [auth.email.template.email_change]
59
+ subject = "Confirm your new email for Folio"
60
+ content_path = "supabase/templates/email-change.html"
61
+
62
+ [functions]
63
+ # Required runtime secret for some local processing paths:
64
+ # TOKEN_ENCRYPTION_KEY
@@ -0,0 +1,35 @@
1
+ import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
2
+
3
+ export async function authenticate(req: Request) {
4
+ const authHeader = req.headers.get("Authorization");
5
+ if (!authHeader?.startsWith("Bearer ")) {
6
+ throw new Error("Missing bearer token");
7
+ }
8
+
9
+ const token = authHeader.replace("Bearer ", "");
10
+ const supabaseUrl = Deno.env.get("SUPABASE_URL") || "";
11
+ const supabaseAnon = Deno.env.get("SUPABASE_ANON_KEY") || "";
12
+
13
+ if (!supabaseUrl || !supabaseAnon) {
14
+ throw new Error("Supabase env not configured");
15
+ }
16
+
17
+ const client = createClient(supabaseUrl, supabaseAnon, {
18
+ global: {
19
+ headers: {
20
+ Authorization: `Bearer ${token}`
21
+ }
22
+ }
23
+ });
24
+
25
+ const {
26
+ data: { user },
27
+ error
28
+ } = await client.auth.getUser(token);
29
+
30
+ if (error || !user) {
31
+ throw new Error(error?.message || "Invalid token");
32
+ }
33
+
34
+ return { user, client };
35
+ }
@@ -0,0 +1,12 @@
1
+ export const corsHeaders = {
2
+ "Access-Control-Allow-Origin": "*",
3
+ "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
4
+ "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS"
5
+ };
6
+
7
+ export function createErrorResponse(status: number, message: string) {
8
+ return new Response(JSON.stringify({ error: { message } }), {
9
+ status,
10
+ headers: { "Content-Type": "application/json", ...corsHeaders }
11
+ });
12
+ }
@@ -0,0 +1,17 @@
1
+ import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
2
+
3
+ export function getAdminClient() {
4
+ const supabaseUrl = Deno.env.get("SUPABASE_URL") || "";
5
+ const serviceRoleKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") || "";
6
+
7
+ if (!supabaseUrl || !serviceRoleKey) {
8
+ throw new Error("SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY missing");
9
+ }
10
+
11
+ return createClient(supabaseUrl, serviceRoleKey, {
12
+ auth: {
13
+ autoRefreshToken: false,
14
+ persistSession: false
15
+ }
16
+ });
17
+ }