@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.
- package/.env.example +20 -0
- package/README.md +63 -0
- package/api/server.ts +130 -0
- package/api/src/config/index.ts +96 -0
- package/api/src/middleware/auth.ts +128 -0
- package/api/src/middleware/errorHandler.ts +88 -0
- package/api/src/middleware/index.ts +4 -0
- package/api/src/middleware/rateLimit.ts +71 -0
- package/api/src/middleware/validation.ts +58 -0
- package/api/src/routes/accounts.ts +142 -0
- package/api/src/routes/baseline-config.ts +124 -0
- package/api/src/routes/chat.ts +154 -0
- package/api/src/routes/health.ts +61 -0
- package/api/src/routes/index.ts +35 -0
- package/api/src/routes/ingestions.ts +275 -0
- package/api/src/routes/migrate.ts +112 -0
- package/api/src/routes/policies.ts +121 -0
- package/api/src/routes/processing.ts +90 -0
- package/api/src/routes/rules.ts +11 -0
- package/api/src/routes/sdk.ts +100 -0
- package/api/src/routes/settings.ts +80 -0
- package/api/src/routes/setup.ts +389 -0
- package/api/src/routes/stats.ts +81 -0
- package/api/src/routes/tts.ts +190 -0
- package/api/src/services/BaselineConfigService.ts +208 -0
- package/api/src/services/ChatService.ts +204 -0
- package/api/src/services/GoogleDriveService.ts +331 -0
- package/api/src/services/GoogleSheetsService.ts +1107 -0
- package/api/src/services/IngestionService.ts +1187 -0
- package/api/src/services/ModelCapabilityService.ts +248 -0
- package/api/src/services/PolicyEngine.ts +1625 -0
- package/api/src/services/PolicyLearningService.ts +527 -0
- package/api/src/services/PolicyLoader.ts +249 -0
- package/api/src/services/RAGService.ts +391 -0
- package/api/src/services/SDKService.ts +249 -0
- package/api/src/services/supabase.ts +113 -0
- package/api/src/utils/Actuator.ts +284 -0
- package/api/src/utils/actions/ActionHandler.ts +34 -0
- package/api/src/utils/actions/AppendToGSheetAction.ts +260 -0
- package/api/src/utils/actions/AutoRenameAction.ts +58 -0
- package/api/src/utils/actions/CopyAction.ts +120 -0
- package/api/src/utils/actions/CopyToGDriveAction.ts +64 -0
- package/api/src/utils/actions/LogCsvAction.ts +48 -0
- package/api/src/utils/actions/NotifyAction.ts +39 -0
- package/api/src/utils/actions/RenameAction.ts +57 -0
- package/api/src/utils/actions/WebhookAction.ts +58 -0
- package/api/src/utils/actions/utils.ts +293 -0
- package/api/src/utils/llmResponse.ts +61 -0
- package/api/src/utils/logger.ts +67 -0
- package/bin/folio-deploy.js +12 -0
- package/bin/folio-setup.js +45 -0
- package/bin/folio.js +65 -0
- package/dist/api/server.js +106 -0
- package/dist/api/src/config/index.js +81 -0
- package/dist/api/src/middleware/auth.js +93 -0
- package/dist/api/src/middleware/errorHandler.js +73 -0
- package/dist/api/src/middleware/index.js +4 -0
- package/dist/api/src/middleware/rateLimit.js +43 -0
- package/dist/api/src/middleware/validation.js +54 -0
- package/dist/api/src/routes/accounts.js +110 -0
- package/dist/api/src/routes/baseline-config.js +91 -0
- package/dist/api/src/routes/chat.js +114 -0
- package/dist/api/src/routes/health.js +52 -0
- package/dist/api/src/routes/index.js +31 -0
- package/dist/api/src/routes/ingestions.js +207 -0
- package/dist/api/src/routes/migrate.js +91 -0
- package/dist/api/src/routes/policies.js +86 -0
- package/dist/api/src/routes/processing.js +75 -0
- package/dist/api/src/routes/rules.js +8 -0
- package/dist/api/src/routes/sdk.js +80 -0
- package/dist/api/src/routes/settings.js +68 -0
- package/dist/api/src/routes/setup.js +315 -0
- package/dist/api/src/routes/stats.js +62 -0
- package/dist/api/src/routes/tts.js +178 -0
- package/dist/api/src/services/BaselineConfigService.js +168 -0
- package/dist/api/src/services/ChatService.js +166 -0
- package/dist/api/src/services/GoogleDriveService.js +280 -0
- package/dist/api/src/services/GoogleSheetsService.js +795 -0
- package/dist/api/src/services/IngestionService.js +990 -0
- package/dist/api/src/services/ModelCapabilityService.js +179 -0
- package/dist/api/src/services/PolicyEngine.js +1353 -0
- package/dist/api/src/services/PolicyLearningService.js +397 -0
- package/dist/api/src/services/PolicyLoader.js +159 -0
- package/dist/api/src/services/RAGService.js +295 -0
- package/dist/api/src/services/SDKService.js +212 -0
- package/dist/api/src/services/supabase.js +72 -0
- package/dist/api/src/utils/Actuator.js +225 -0
- package/dist/api/src/utils/actions/ActionHandler.js +1 -0
- package/dist/api/src/utils/actions/AppendToGSheetAction.js +191 -0
- package/dist/api/src/utils/actions/AutoRenameAction.js +49 -0
- package/dist/api/src/utils/actions/CopyAction.js +112 -0
- package/dist/api/src/utils/actions/CopyToGDriveAction.js +55 -0
- package/dist/api/src/utils/actions/LogCsvAction.js +42 -0
- package/dist/api/src/utils/actions/NotifyAction.js +32 -0
- package/dist/api/src/utils/actions/RenameAction.js +51 -0
- package/dist/api/src/utils/actions/WebhookAction.js +51 -0
- package/dist/api/src/utils/actions/utils.js +237 -0
- package/dist/api/src/utils/llmResponse.js +63 -0
- package/dist/api/src/utils/logger.js +51 -0
- package/dist/assets/index-DzN8-j-e.css +1 -0
- package/dist/assets/index-Uy-ai3Dh.js +113 -0
- package/dist/favicon.svg +31 -0
- package/dist/folio-logo.svg +46 -0
- package/dist/index.html +14 -0
- package/docs-dev/FPE-spec.md +196 -0
- package/docs-dev/folio-prd.md +47 -0
- package/docs-dev/foundation-checklist.md +30 -0
- package/docs-dev/hybrid-routing-architecture.md +205 -0
- package/docs-dev/ingestion-engine.md +69 -0
- package/docs-dev/port-from-email-automator.md +32 -0
- package/docs-dev/tech-spec.md +98 -0
- package/index.html +13 -0
- package/package.json +101 -0
- package/public/favicon.svg +31 -0
- package/public/folio-logo.svg +46 -0
- package/scripts/dev-task.mjs +51 -0
- package/scripts/get-latest-migration-timestamp.mjs +34 -0
- package/scripts/migrate.sh +91 -0
- package/supabase/.temp/cli-latest +1 -0
- package/supabase/.temp/gotrue-version +1 -0
- package/supabase/.temp/pooler-url +1 -0
- package/supabase/.temp/postgres-version +1 -0
- package/supabase/.temp/project-ref +1 -0
- package/supabase/.temp/rest-version +1 -0
- package/supabase/.temp/storage-migration +1 -0
- package/supabase/.temp/storage-version +1 -0
- package/supabase/config.toml +64 -0
- package/supabase/functions/_shared/auth.ts +35 -0
- package/supabase/functions/_shared/cors.ts +12 -0
- package/supabase/functions/_shared/supabaseAdmin.ts +17 -0
- package/supabase/functions/api-v1-settings/index.ts +66 -0
- package/supabase/functions/setup/index.ts +91 -0
- package/supabase/migrations/20260223000000_initial_foundation.sql +136 -0
- package/supabase/migrations/20260223000001_add_migration_rpc.sql +10 -0
- package/supabase/migrations/20260224000002_add_init_state_view.sql +20 -0
- package/supabase/migrations/20260224000003_port_user_creation_parity.sql +139 -0
- package/supabase/migrations/20260224000004_add_avatars_storage.sql +26 -0
- package/supabase/migrations/20260224000005_add_tts_and_embed_settings.sql +24 -0
- package/supabase/migrations/20260224000006_add_policies_table.sql +48 -0
- package/supabase/migrations/20260224000007_fix_migration_rpc.sql +9 -0
- package/supabase/migrations/20260224000008_add_ingestions_table.sql +42 -0
- package/supabase/migrations/20260225000000_setup_compatible_mode.sql +119 -0
- package/supabase/migrations/20260225000001_restore_ingestions.sql +49 -0
- package/supabase/migrations/20260225000002_add_ingestion_trace.sql +2 -0
- package/supabase/migrations/20260225000003_add_baseline_configs.sql +35 -0
- package/supabase/migrations/20260226000000_add_processing_events.sql +26 -0
- package/supabase/migrations/20260226000001_add_ingestion_file_hash.sql +10 -0
- package/supabase/migrations/20260226000002_add_dynamic_rag.sql +150 -0
- package/supabase/migrations/20260226000003_add_ingestion_summary.sql +4 -0
- package/supabase/migrations/20260226000004_add_ingestion_tags.sql +7 -0
- package/supabase/migrations/20260226000005_add_chat_tables.sql +60 -0
- package/supabase/migrations/20260227000000_harden_chat_messages_rls.sql +25 -0
- package/supabase/migrations/20260228000000_add_vision_model_capabilities.sql +8 -0
- package/supabase/migrations/20260228000001_add_policy_match_feedback.sql +51 -0
- package/supabase/migrations/29991231235959_test_migration.sql +0 -0
- package/supabase/templates/confirmation.html +76 -0
- package/supabase/templates/email-change.html +76 -0
- package/supabase/templates/invite.html +72 -0
- package/supabase/templates/magic-link.html +68 -0
- package/supabase/templates/recovery.html +82 -0
- package/tsconfig.api.json +16 -0
- package/tsconfig.json +25 -0
- 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
|
+
}
|