From 30e1785fe2d74332b58cebfac83762bed68f9693 Mon Sep 17 00:00:00 2001 From: arkml <6592213+arkml@users.noreply.github.com> Date: Sat, 28 Mar 2026 00:41:46 +0530 Subject: [PATCH] Chrome extension (#453) * added chrome extension * prepare chrome extension for web store submission * retention 7 days * gate chrome service with a flag --- apps/x/apps/main/src/main.ts | 4 + apps/x/packages/core/package.json | 4 + .../src/knowledge/chrome-extension/README.md | 96 +++ .../chrome-extension/extension/background.js | 388 +++++++++++ .../chrome-extension/extension/content.js | 81 +++ .../chrome-extension/extension/icon.png | Bin 0 -> 22928 bytes .../extension/icons/icon128.png | Bin 0 -> 3684 bytes .../extension/icons/icon16.png | Bin 0 -> 912 bytes .../extension/icons/icon32.png | Bin 0 -> 1196 bytes .../extension/icons/icon48.png | Bin 0 -> 1638 bytes .../chrome-extension/extension/manifest.json | 40 ++ .../chrome-extension/extension/popup.html | 174 +++++ .../chrome-extension/extension/popup.js | 258 +++++++ .../chrome-extension/extension/styles.css | 279 ++++++++ .../chrome-extension/server/server.ts | 281 ++++++++ apps/x/pnpm-lock.yaml | 644 ++++-------------- 16 files changed, 1738 insertions(+), 511 deletions(-) create mode 100644 apps/x/packages/core/src/knowledge/chrome-extension/README.md create mode 100644 apps/x/packages/core/src/knowledge/chrome-extension/extension/background.js create mode 100644 apps/x/packages/core/src/knowledge/chrome-extension/extension/content.js create mode 100644 apps/x/packages/core/src/knowledge/chrome-extension/extension/icon.png create mode 100644 apps/x/packages/core/src/knowledge/chrome-extension/extension/icons/icon128.png create mode 100644 apps/x/packages/core/src/knowledge/chrome-extension/extension/icons/icon16.png create mode 100644 apps/x/packages/core/src/knowledge/chrome-extension/extension/icons/icon32.png create mode 100644 apps/x/packages/core/src/knowledge/chrome-extension/extension/icons/icon48.png create mode 100644 apps/x/packages/core/src/knowledge/chrome-extension/extension/manifest.json create mode 100644 apps/x/packages/core/src/knowledge/chrome-extension/extension/popup.html create mode 100644 apps/x/packages/core/src/knowledge/chrome-extension/extension/popup.js create mode 100644 apps/x/packages/core/src/knowledge/chrome-extension/extension/styles.css create mode 100644 apps/x/packages/core/src/knowledge/chrome-extension/server/server.ts diff --git a/apps/x/apps/main/src/main.ts b/apps/x/apps/main/src/main.ts index 3b8b70c8..d828f38d 100644 --- a/apps/x/apps/main/src/main.ts +++ b/apps/x/apps/main/src/main.ts @@ -25,6 +25,7 @@ import { init as initAgentNotes } from "@x/core/dist/knowledge/agent_notes.js"; import { initConfigs } from "@x/core/dist/config/initConfigs.js"; import started from "electron-squirrel-startup"; import { execSync } from "node:child_process"; +import { init as initChromeSync } from "@x/core/dist/knowledge/chrome-extension/server/server.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -234,6 +235,9 @@ app.whenReady().then(async () => { // start agent notes learning service initAgentNotes(); + // start chrome extension sync server + initChromeSync(); + app.on("activate", () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); diff --git a/apps/x/packages/core/package.json b/apps/x/packages/core/package.json index 53495637..72d6f079 100644 --- a/apps/x/packages/core/package.json +++ b/apps/x/packages/core/package.json @@ -24,7 +24,9 @@ "ai": "^5.0.133", "awilix": "^12.0.5", "chokidar": "^4.0.3", + "cors": "^2.8.6", "cron-parser": "^5.5.0", + "express": "^5.2.1", "glob": "^13.0.0", "google-auth-library": "^10.5.0", "isomorphic-git": "^1.29.0", @@ -41,6 +43,8 @@ "zod": "^4.2.1" }, "devDependencies": { + "@types/cors": "^2.8.19", + "@types/express": "^5.0.6", "@types/node": "^25.0.3", "@types/papaparse": "^5.5.2", "@types/pdf-parse": "^1.1.5" diff --git a/apps/x/packages/core/src/knowledge/chrome-extension/README.md b/apps/x/packages/core/src/knowledge/chrome-extension/README.md new file mode 100644 index 00000000..b7de8636 --- /dev/null +++ b/apps/x/packages/core/src/knowledge/chrome-extension/README.md @@ -0,0 +1,96 @@ +# Page Capture Chrome Extension + +A Chrome extension that captures web pages you visit and sends them to a local server for storage as markdown files. + +## Structure + +``` +/extension + manifest.json # Chrome extension manifest (v3) + background.js # Service worker that captures pages +/server + server.py # Flask server for storing captures + captured_pages/ # Directory where pages are saved +``` + +## Setup + +### 1. Install Server Dependencies + +```bash +cd server +pip install flask flask-cors +``` + +### 2. Start the Server + +```bash +cd server +python server.py +``` + +The server will run at `http://localhost:3001`. + +### 3. Install the Chrome Extension + +1. Open Chrome and navigate to `chrome://extensions/` +2. Enable "Developer mode" (toggle in top right) +3. Click "Load unpacked" +4. Select the `extension` folder + +## Usage + +Once both the server is running and the extension is installed, the extension will automatically capture pages as you browse: + +- Every page load (http/https URLs only) triggers a capture +- Content is hashed with SHA-256 to avoid duplicate captures +- Pages are saved as markdown files with frontmatter metadata + +## API Endpoints + +### POST /capture + +Receives captured page data. + +**Request body:** +```json +{ + "url": "https://example.com", + "content": "Page text content...", + "timestamp": 1706123456789, + "title": "Page Title" +} +``` + +**Response:** +```json +{"status": "captured", "filename": "1706123456789_example_com.md"} +``` + +### GET /status + +Returns the count of captured pages. + +**Response:** +```json +{"count": 42} +``` + +## File Format + +Captured pages are saved as markdown with YAML frontmatter: + +```markdown +--- +url: https://example.com/page +title: Page Title +captured_at: 2024-01-24T12:34:56 +--- + +Page content here... +``` + +## Debugging + +- **Extension logs**: Open `chrome://extensions/`, find "Page Capture", click "Service worker" to view console logs +- **Server logs**: Check the terminal where `server.py` is running diff --git a/apps/x/packages/core/src/knowledge/chrome-extension/extension/background.js b/apps/x/packages/core/src/knowledge/chrome-extension/extension/background.js new file mode 100644 index 00000000..235a292b --- /dev/null +++ b/apps/x/packages/core/src/knowledge/chrome-extension/extension/background.js @@ -0,0 +1,388 @@ +const SERVER_URL = 'http://localhost:3001'; +const contentHashMap = new Map(); + +let cachedConfig = null; +let serverReachable = true; + +// Default config +const DEFAULT_CONFIG = { + mode: 'ask', + whitelist: [], + blacklist: [], + enabled: true +}; + +// Config management +async function loadConfig() { + try { + const response = await fetch(`${SERVER_URL}/browse/config`); + if (response.ok) { + cachedConfig = await response.json(); + serverReachable = true; + } else { + throw new Error('Server returned error'); + } + } catch (error) { + console.log(`[Page Capture] Failed to load config: ${error.message}`); + serverReachable = false; + cachedConfig = cachedConfig || DEFAULT_CONFIG; + } + return cachedConfig; +} + +async function saveConfig(config) { + try { + const response = await fetch(`${SERVER_URL}/browse/config`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(config) + }); + if (response.ok) { + cachedConfig = config; + serverReachable = true; + return true; + } + } catch (error) { + console.log(`[Page Capture] Failed to save config: ${error.message}`); + serverReachable = false; + } + return false; +} + +function getConfig() { + return cachedConfig || DEFAULT_CONFIG; +} + +function extractDomain(url) { + try { + const parsed = new URL(url); + return parsed.hostname; + } catch { + return null; + } +} + +function isWhitelisted(domain) { + const config = getConfig(); + return config.whitelist.some(d => domain === d || domain.endsWith('.' + d)); +} + +function isBlacklisted(domain) { + const config = getConfig(); + return config.blacklist.some(d => domain === d || domain.endsWith('.' + d)); +} + +function getDomainStatus(domain) { + const config = getConfig(); + if (isBlacklisted(domain)) return 'blacklisted'; + if (config.mode === 'all') return 'capturing'; + if (isWhitelisted(domain)) return 'whitelisted'; + return 'unknown'; +} + +function shouldCapture(domain) { + const config = getConfig(); + if (!config.enabled) return false; + if (isBlacklisted(domain)) return false; + if (config.mode === 'all') return true; + return isWhitelisted(domain); +} + +// Badge management +async function setBadge(tabId, type) { + try { + if (type === 'needs-approval') { + await chrome.action.setBadgeText({ tabId, text: '?' }); + await chrome.action.setBadgeBackgroundColor({ tabId, color: '#F59E0B' }); + } else if (type === 'server-error') { + await chrome.action.setBadgeText({ tabId, text: '!' }); + await chrome.action.setBadgeBackgroundColor({ tabId, color: '#EF4444' }); + } else { + await chrome.action.setBadgeText({ tabId, text: '' }); + } + } catch (error) { + console.log(`[Page Capture] Failed to set badge: ${error.message}`); + } +} + +async function updateBadgeForTab(tabId, url) { + if (!serverReachable) { + await setBadge(tabId, 'server-error'); + return; + } + + const domain = extractDomain(url); + if (!domain) { + await setBadge(tabId, 'clear'); + return; + } + + const status = getDomainStatus(domain); + if (status === 'unknown') { + await setBadge(tabId, 'needs-approval'); + } else { + await setBadge(tabId, 'clear'); + } +} + +// Content hashing +async function hashContent(content) { + const encoder = new TextEncoder(); + const data = encoder.encode(content); + const hashBuffer = await crypto.subtle.digest('SHA-256', data); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); +} + +function isValidUrl(url) { + if (!url) return false; + try { + const parsed = new URL(url); + return parsed.protocol === 'http:' || parsed.protocol === 'https:'; + } catch { + return false; + } +} + +async function capturePageContent(tabId) { + try { + const results = await chrome.scripting.executeScript({ + target: { tabId }, + func: () => document.body.innerText + }); + return results[0]?.result || ''; + } catch (error) { + console.log(`[Page Capture] Failed to capture content: ${error.message}`); + return null; + } +} + +async function sendToServer(data) { + try { + const response = await fetch(`${SERVER_URL}/capture`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }); + serverReachable = response.ok; + return response.ok; + } catch (error) { + console.log(`[Page Capture] Failed to send to server: ${error.message}`); + serverReachable = false; + return false; + } +} + +async function captureTab(tabId, tab) { + const content = await capturePageContent(tabId); + if (content === null) return false; + + const hash = await hashContent(content); + const lastHash = contentHashMap.get(tab.url); + + if (lastHash === hash) { + console.log(`[Page Capture] Content unchanged for: ${tab.url}`); + return true; + } + + contentHashMap.set(tab.url, hash); + + const payload = { + url: tab.url, + content, + timestamp: Date.now(), + title: tab.title || 'Untitled' + }; + + const success = await sendToServer(payload); + if (success) { + console.log(`[Page Capture] Captured: ${tab.url}`); + } + return success; +} + +// Tab update listener +chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => { + if (changeInfo.status !== 'complete') return; + if (!isValidUrl(tab.url)) { + console.log(`[Page Capture] Skipping non-http URL: ${tab.url}`); + return; + } + + const domain = extractDomain(tab.url); + if (!domain) return; + + await updateBadgeForTab(tabId, tab.url); + + if (!shouldCapture(domain)) { + console.log(`[Page Capture] Skipping (not whitelisted): ${tab.url}`); + return; + } + + await captureTab(tabId, tab); +}); + +// Tab activated listener - update badge +chrome.tabs.onActivated.addListener(async (activeInfo) => { + try { + const tab = await chrome.tabs.get(activeInfo.tabId); + if (tab.url && isValidUrl(tab.url)) { + await updateBadgeForTab(activeInfo.tabId, tab.url); + } + } catch (error) { + console.log(`[Page Capture] Failed to update badge on tab switch: ${error.message}`); + } +}); + +// Handle scroll capture messages from content script +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.type === 'SCROLL_CAPTURE') { + const { url, content, timestamp, title, scrollY } = message; + const domain = extractDomain(url); + + if (!shouldCapture(domain)) { + console.log(`[Page Capture] Skipping scroll capture (not whitelisted): ${url}`); + return; + } + + console.log(`[Page Capture] Received scroll capture for: ${url}`); + + hashContent(content).then(async (hash) => { + const lastHash = contentHashMap.get(url); + if (lastHash === hash) { + console.log(`[Page Capture] Hash unchanged, skipping: ${url}`); + return; + } + + contentHashMap.set(url, hash); + + const payload = { url, content, timestamp, title }; + const success = await sendToServer(payload); + if (success) { + console.log(`[Page Capture] Scroll captured (y=${scrollY}): ${url}`); + } + }); + return; + } + + // Handle messages from popup + if (message.type === 'GET_CONFIG') { + loadConfig().then(config => { + sendResponse({ config, serverReachable }); + }); + return true; + } + + if (message.type === 'SAVE_CONFIG') { + saveConfig(message.config).then(success => { + sendResponse({ success }); + // Update badges on all tabs + chrome.tabs.query({}, tabs => { + tabs.forEach(tab => { + if (tab.url && isValidUrl(tab.url)) { + updateBadgeForTab(tab.id, tab.url); + } + }); + }); + }); + return true; + } + + if (message.type === 'GET_DOMAIN_STATUS') { + const domain = extractDomain(message.url); + const status = domain ? getDomainStatus(domain) : 'unknown'; + sendResponse({ status, domain, serverReachable }); + return true; + } + + if (message.type === 'APPROVE_DOMAIN') { + const config = getConfig(); + const domain = message.domain; + if (!config.whitelist.includes(domain)) { + config.whitelist.push(domain); + } + config.blacklist = config.blacklist.filter(d => d !== domain); + saveConfig(config).then(success => { + sendResponse({ success }); + chrome.tabs.query({}, tabs => { + tabs.forEach(tab => { + if (tab.url && isValidUrl(tab.url)) { + updateBadgeForTab(tab.id, tab.url); + } + }); + }); + }); + return true; + } + + if (message.type === 'REJECT_DOMAIN') { + const config = getConfig(); + const domain = message.domain; + if (!config.blacklist.includes(domain)) { + config.blacklist.push(domain); + } + config.whitelist = config.whitelist.filter(d => d !== domain); + saveConfig(config).then(success => { + sendResponse({ success }); + chrome.tabs.query({}, tabs => { + tabs.forEach(tab => { + if (tab.url && isValidUrl(tab.url)) { + updateBadgeForTab(tab.id, tab.url); + } + }); + }); + }); + return true; + } + + if (message.type === 'CAPTURE_ONCE') { + chrome.tabs.query({ active: true, currentWindow: true }, async tabs => { + if (tabs[0]) { + const success = await captureTab(tabs[0].id, tabs[0]); + sendResponse({ success }); + } else { + sendResponse({ success: false }); + } + }); + return true; + } + + if (message.type === 'REMOVE_FROM_WHITELIST') { + const config = getConfig(); + config.whitelist = config.whitelist.filter(d => d !== message.domain); + saveConfig(config).then(success => { + sendResponse({ success }); + chrome.tabs.query({}, tabs => { + tabs.forEach(tab => { + if (tab.url && isValidUrl(tab.url)) { + updateBadgeForTab(tab.id, tab.url); + } + }); + }); + }); + return true; + } + + if (message.type === 'REMOVE_FROM_BLACKLIST') { + const config = getConfig(); + config.blacklist = config.blacklist.filter(d => d !== message.domain); + saveConfig(config).then(success => { + sendResponse({ success }); + chrome.tabs.query({}, tabs => { + tabs.forEach(tab => { + if (tab.url && isValidUrl(tab.url)) { + updateBadgeForTab(tab.id, tab.url); + } + }); + }); + }); + return true; + } +}); + +// Load config on startup +loadConfig().then(() => { + console.log('[Page Capture] Config loaded'); +}); + +console.log('[Page Capture] Service worker started'); diff --git a/apps/x/packages/core/src/knowledge/chrome-extension/extension/content.js b/apps/x/packages/core/src/knowledge/chrome-extension/extension/content.js new file mode 100644 index 00000000..bae24963 --- /dev/null +++ b/apps/x/packages/core/src/knowledge/chrome-extension/extension/content.js @@ -0,0 +1,81 @@ +const DEBOUNCE_MS = 800; +const MIN_SCROLL_PIXELS = 500; +const MIN_CONTENT_CHANGE = 100; // characters + +let debounceTimer = null; +let lastCapturedContent = null; +let lastScrollTop = 0; +let scrollContainer = null; + +function getScrollTop() { + if (!scrollContainer || scrollContainer === window) { + return window.scrollY; + } + if (scrollContainer === document) { + return document.documentElement.scrollTop; + } + return scrollContainer.scrollTop || 0; +} + +function captureAndSend() { + const content = document.body.innerText; + + // Skip if content unchanged or minimal change + if (lastCapturedContent) { + const lengthDiff = Math.abs(content.length - lastCapturedContent.length); + if (content === lastCapturedContent || lengthDiff < MIN_CONTENT_CHANGE) { + return; + } + } + + lastCapturedContent = content; + lastScrollTop = getScrollTop(); + + chrome.runtime.sendMessage({ + type: 'SCROLL_CAPTURE', + url: window.location.href, + title: document.title, + content: content, + timestamp: Date.now(), + scrollY: lastScrollTop + }); +} + +function onScroll() { + const currentScrollTop = getScrollTop(); + const scrollDelta = Math.abs(currentScrollTop - lastScrollTop); + + if (scrollDelta < MIN_SCROLL_PIXELS) { + return; + } + + if (debounceTimer) { + clearTimeout(debounceTimer); + } + + debounceTimer = setTimeout(() => { + captureAndSend(); + }, DEBOUNCE_MS); +} + +function init() { + // Use document with capture to catch scroll events from any element + document.addEventListener('scroll', (e) => { + const target = e.target; + const scrollTop = target === document ? document.documentElement.scrollTop : target.scrollTop; + + // Update scroll container if we found the real one + if (scrollTop > 0 && scrollContainer !== target) { + scrollContainer = target; + } + + onScroll(); + }, { capture: true, passive: true }); +} + +// Wait for page to be ready, then init +if (document.readyState === 'complete') { + init(); +} else { + window.addEventListener('load', init); +} diff --git a/apps/x/packages/core/src/knowledge/chrome-extension/extension/icon.png b/apps/x/packages/core/src/knowledge/chrome-extension/extension/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e2fd6386b98cb11d2ba86fa6114c7e0f09bb65fb GIT binary patch literal 22928 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 literal 0 HcmV?d00001 diff --git a/apps/x/packages/core/src/knowledge/chrome-extension/extension/icons/icon128.png b/apps/x/packages/core/src/knowledge/chrome-extension/extension/icons/icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..c8cc16e3fec5febfaeb10bded77c6dfc8248aa3b GIT binary patch literal 3684 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 N($vtw7pPeU{|};&eTV=6 literal 0 HcmV?d00001 diff --git a/apps/x/packages/core/src/knowledge/chrome-extension/extension/icons/icon16.png b/apps/x/packages/core/src/knowledge/chrome-extension/extension/icons/icon16.png new file mode 100644 index 0000000000000000000000000000000000000000..482631a1a327df0e235c160e5b8d4d953374387a GIT binary patch literal 912 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|DpDgn(|mmy zw18|51{Ovs23C*~Ahu(ahO^xmH5iz|;!F$-?U@WLP&Ix)8pK=%BH%Rh0!FB6px6Q? zxN3m~%m_9}pD>G9ERf=?Nk^)#sNw%$0gl~X?bAC~(f|;Iy zo`I4bmx6+VO;JjkRgjAtR6CGotCUevQedU8UtV6WS8lAAUzDzIXlZGwZ(yWvWTXpJ zp<7&;SCUwvn^&w1F$89gOKNd)QD#9&W`3Rm$lS!F{L&IzB_)tWZ~%2@ZUNk)yke-Y zfd133NG#Ad)HBeBn+()w1N5Vnb5UwyNq$jCetwP}$Z45**~Q6;1*sqh+UTRJfLdvz z4{`^RyC9whivoRZ#|6|03qd=sO(&BSf#L1w>Eak-A-FVnZ&$N}z&&eSA>Aw2lzbg> zLTb9S&k8TuKDD~t##zAnqh!d3!XxM096x?|RyOUnz*#pY$saT3&VN5U^@eJTK&R58 z9}64A8~2`Y)U)2>74v-|$Iqn^;w>8!B_3GtWz0JE`ovwe=xMBhB98N(2V6JhIn1!` zbrIv2B*y#S6@7zW?BaX3HdE?C>EE3P^0qghetJmluJF&DI;X3RPE@ewwI6O&@pQ5| zuRP_zW$t*9qe{mbG_|^z5+x43EJ^a-lq6?qVMr+ zk7=ok`=o-36<(w&b1`JM9`?7p=g-v|<(4x8Jt&cYK(?qa|JK zw2`6jZlo#ZEMc-x%z(Q;9}!LEp=8OvTS{5GHAkFV^}=_{D;AF?^WNa%d+_?0g?k67_WTb6Mw<&;$SvqAimE literal 0 HcmV?d00001 diff --git a/apps/x/packages/core/src/knowledge/chrome-extension/extension/icons/icon32.png b/apps/x/packages/core/src/knowledge/chrome-extension/extension/icons/icon32.png new file mode 100644 index 0000000000000000000000000000000000000000..2b95aa629f311be7df1f1acd954560532de27cc3 GIT binary patch literal 1196 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 Jl)J}>{{cg&ldu2) literal 0 HcmV?d00001 diff --git a/apps/x/packages/core/src/knowledge/chrome-extension/extension/manifest.json b/apps/x/packages/core/src/knowledge/chrome-extension/extension/manifest.json new file mode 100644 index 00000000..f536adec --- /dev/null +++ b/apps/x/packages/core/src/knowledge/chrome-extension/extension/manifest.json @@ -0,0 +1,40 @@ +{ + "manifest_version": 3, + "name": "Rowboat Browser Capture", + "version": "1.1.1", + "description": "Allows users to save and capture web page content to their Rowboat workspace.", + "icons": { + "16": "icons/icon16.png", + "32": "icons/icon32.png", + "48": "icons/icon48.png", + "128": "icons/icon128.png" + }, + "permissions": [ + "tabs", + "scripting", + "activeTab" + ], + "host_permissions": [ + "http://*/*", + "https://*/*" + ], + "action": { + "default_popup": "popup.html", + "default_icon": { + "16": "icons/icon16.png", + "32": "icons/icon32.png", + "48": "icons/icon48.png", + "128": "icons/icon128.png" + } + }, + "background": { + "service_worker": "background.js" + }, + "content_scripts": [ + { + "matches": ["http://*/*", "https://*/*"], + "js": ["content.js"], + "run_at": "document_idle" + } + ] +} diff --git a/apps/x/packages/core/src/knowledge/chrome-extension/extension/popup.html b/apps/x/packages/core/src/knowledge/chrome-extension/extension/popup.html new file mode 100644 index 00000000..b8d55d71 --- /dev/null +++ b/apps/x/packages/core/src/knowledge/chrome-extension/extension/popup.html @@ -0,0 +1,174 @@ + + + + + + Rowboat + + + + +
+ - + + + - + +
+ + + + + + + +
+
Settings
+
+ + +
+
+ +
+ - +
+ + + + diff --git a/apps/x/packages/core/src/knowledge/chrome-extension/extension/popup.js b/apps/x/packages/core/src/knowledge/chrome-extension/extension/popup.js new file mode 100644 index 00000000..6a3fc0b1 --- /dev/null +++ b/apps/x/packages/core/src/knowledge/chrome-extension/extension/popup.js @@ -0,0 +1,258 @@ +const SERVER_URL = 'http://localhost:3001'; + + +let currentDomain = null; +let currentStatus = null; +let currentConfig = null; + +async function getCurrentTab() { + const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); + return tab; +} + +function extractDomain(url) { + try { + const parsed = new URL(url); + return parsed.hostname; + } catch { + return null; + } +} + +function updateStatusBadge(status, serverReachable) { + const badge = document.getElementById('statusBadge'); + const statusText = document.getElementById('statusText'); + + badge.classList.remove('capturing', 'not-capturing', 'awaiting', 'error'); + + if (!serverReachable) { + badge.classList.add('error'); + statusText.textContent = 'Error'; + return; + } + + switch (status) { + case 'whitelisted': + case 'capturing': + badge.classList.add('capturing'); + statusText.textContent = 'Indexing'; + break; + case 'blacklisted': + badge.classList.add('not-capturing'); + statusText.textContent = 'Not indexing'; + break; + case 'unknown': + badge.classList.add('awaiting'); + statusText.textContent = 'Awaiting'; + break; + default: + badge.classList.add('not-capturing'); + statusText.textContent = 'Unknown'; + } +} + +function showApprovalSection(show) { + document.getElementById('approvalSection').classList.toggle('hidden', !show); +} + +function showToggleSection(show, isCapturing) { + const section = document.getElementById('toggleSection'); + const label = document.getElementById('toggleLabel'); + const btn = document.getElementById('toggleBtn'); + + section.classList.toggle('hidden', !show); + + if (isCapturing) { + label.textContent = 'Capturing this site'; + btn.textContent = 'Stop'; + btn.onclick = () => removeDomain('whitelist'); + } else { + label.textContent = 'Not capturing this site'; + btn.textContent = 'Start'; + btn.onclick = () => removeDomain('blacklist'); + } +} + +function showError(show) { + document.getElementById('errorMessage').classList.toggle('hidden', !show); +} + +// Settings section +function getSelectedMode(config) { + return config.mode === 'all' ? 'work' : 'ask'; +} + +function initSettings(config) { + currentConfig = config; + const mode = getSelectedMode(config); + + const radio = document.querySelector(`input[name="captureMode"][value="${mode}"]`); + if (radio) radio.checked = true; +} + +async function saveSettingsFromUI() { + const selectedRadio = document.querySelector('input[name="captureMode"]:checked'); + const mode = selectedRadio ? selectedRadio.value : 'ask'; + + let config; + if (mode === 'work') { + config = { + mode: 'all', + whitelist: currentConfig ? currentConfig.whitelist : [], + blacklist: currentConfig ? currentConfig.blacklist : [], + enabled: true + }; + } else { + config = { + mode: 'ask', + whitelist: currentConfig ? currentConfig.whitelist : [], + blacklist: currentConfig ? currentConfig.blacklist : [], + enabled: true + }; + } + + try { + await chrome.runtime.sendMessage({ type: 'SAVE_CONFIG', config }); + currentConfig = config; + await loadStatus(); + } catch (error) { + console.error('Failed to save settings:', error); + } +} + +// Domain status +async function loadStatus() { + const tab = await getCurrentTab(); + if (!tab || !tab.url) { + document.getElementById('domainDisplay').textContent = 'No page'; + return; + } + + currentDomain = extractDomain(tab.url); + if (!currentDomain) { + document.getElementById('domainDisplay').textContent = 'Invalid URL'; + return; + } + + document.getElementById('domainDisplay').textContent = currentDomain; + + try { + const response = await chrome.runtime.sendMessage({ + type: 'GET_DOMAIN_STATUS', + url: tab.url + }); + + currentStatus = response.status; + const serverReachable = response.serverReachable; + + updateStatusBadge(currentStatus, serverReachable); + showError(!serverReachable); + + if (!serverReachable) { + showApprovalSection(false); + showToggleSection(false, false); + return; + } + + if (currentStatus === 'unknown') { + showApprovalSection(true); + showToggleSection(false, false); + } else if (currentStatus === 'whitelisted' || currentStatus === 'capturing') { + showApprovalSection(false); + showToggleSection(true, true); + } else if (currentStatus === 'blacklisted') { + showApprovalSection(false); + showToggleSection(true, false); + } else { + showApprovalSection(false); + showToggleSection(false, false); + } + } catch (error) { + console.error('Failed to get status:', error); + showError(true); + } +} + +async function loadStats() { + try { + const response = await fetch(`${SERVER_URL}/status`); + if (response.ok) { + const data = await response.json(); + document.getElementById('statsCount').textContent = `${data.count} pages indexed locally`; + } + } catch (error) { + console.log('Failed to load stats:', error); + } +} + +async function approveDomain() { + if (!currentDomain) return; + try { + await chrome.runtime.sendMessage({ type: 'APPROVE_DOMAIN', domain: currentDomain }); + // Reload config to reflect the new whitelist in settings + const resp = await chrome.runtime.sendMessage({ type: 'GET_CONFIG' }); + if (resp && resp.config) initSettings(resp.config); + await loadStatus(); + } catch (error) { + console.error('Failed to approve domain:', error); + } +} + +async function rejectDomain() { + if (!currentDomain) return; + try { + await chrome.runtime.sendMessage({ type: 'REJECT_DOMAIN', domain: currentDomain }); + await loadStatus(); + } catch (error) { + console.error('Failed to reject domain:', error); + } +} + +async function captureOnce() { + try { + const response = await chrome.runtime.sendMessage({ type: 'CAPTURE_ONCE' }); + if (response.success) { + window.close(); + } + } catch (error) { + console.error('Failed to capture:', error); + } +} + +async function removeDomain(list) { + if (!currentDomain) return; + try { + const messageType = list === 'whitelist' ? 'REMOVE_FROM_WHITELIST' : 'REMOVE_FROM_BLACKLIST'; + await chrome.runtime.sendMessage({ type: messageType, domain: currentDomain }); + // Reload config to reflect changes in settings + const resp = await chrome.runtime.sendMessage({ type: 'GET_CONFIG' }); + if (resp && resp.config) initSettings(resp.config); + await loadStatus(); + } catch (error) { + console.error('Failed to remove domain:', error); + } +} + +document.addEventListener('DOMContentLoaded', async () => { + // Load config and init settings + try { + const resp = await chrome.runtime.sendMessage({ type: 'GET_CONFIG' }); + if (resp && resp.config) { + initSettings(resp.config); + } + } catch (error) { + console.error('Failed to load config:', error); + } + + // Radio change listeners + document.querySelectorAll('input[name="captureMode"]').forEach(radio => { + radio.addEventListener('change', () => saveSettingsFromUI()); + }); + + loadStatus(); + loadStats(); + + document.getElementById('approveBtn').addEventListener('click', approveDomain); + document.getElementById('rejectBtn').addEventListener('click', rejectDomain); + document.getElementById('captureOnceBtn').addEventListener('click', captureOnce); +}); diff --git a/apps/x/packages/core/src/knowledge/chrome-extension/extension/styles.css b/apps/x/packages/core/src/knowledge/chrome-extension/extension/styles.css new file mode 100644 index 00000000..399c473b --- /dev/null +++ b/apps/x/packages/core/src/knowledge/chrome-extension/extension/styles.css @@ -0,0 +1,279 @@ +:root { + --bg-primary: #ffffff; + --bg-secondary: #f9fafb; + --bg-tertiary: #f3f4f6; + --text-primary: #111827; + --text-secondary: #6b7280; + --text-muted: #9ca3af; + --border-color: #e5e7eb; + --accent-color: #3b82f6; + --accent-hover: #2563eb; + --success-color: #10b981; + --warning-color: #f59e0b; + --error-color: #ef4444; + --shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 4px 6px rgba(0, 0, 0, 0.1); +} + +@media (prefers-color-scheme: dark) { + :root { + --bg-primary: #1f2937; + --bg-secondary: #111827; + --bg-tertiary: #374151; + --text-primary: #f9fafb; + --text-secondary: #d1d5db; + --text-muted: #9ca3af; + --border-color: #374151; + --accent-color: #60a5fa; + --accent-hover: #3b82f6; + --success-color: #34d399; + --warning-color: #fbbf24; + --error-color: #f87171; + --shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + --shadow-lg: 0 4px 6px rgba(0, 0, 0, 0.3); + } +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + font-size: 14px; + line-height: 1.5; + color: var(--text-primary); + background-color: var(--bg-primary); +} + +/* Buttons */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + font-size: 14px; + font-weight: 500; + border-radius: 6px; + border: none; + cursor: pointer; + transition: all 0.15s ease; +} + +.btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.btn-primary { + background-color: var(--accent-color); + color: white; +} + +.btn-primary:hover:not(:disabled) { + background-color: var(--accent-hover); +} + +.btn-secondary { + background-color: var(--bg-tertiary); + color: var(--text-primary); + border: 1px solid var(--border-color); +} + +.btn-secondary:hover:not(:disabled) { + background-color: var(--border-color); +} + +.btn-ghost { + background-color: transparent; + color: var(--text-secondary); +} + +.btn-ghost:hover:not(:disabled) { + background-color: var(--bg-tertiary); + color: var(--text-primary); +} + +.btn-sm { + padding: 4px 8px; + font-size: 12px; +} + +.btn-block { + width: 100%; +} + +/* Status badges */ +.status-badge { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 4px 10px; + border-radius: 9999px; + font-size: 12px; + font-weight: 500; +} + +.status-badge.capturing { + background-color: rgba(16, 185, 129, 0.1); + color: var(--success-color); +} + +.status-badge.not-capturing { + background-color: rgba(107, 114, 128, 0.1); + color: var(--text-secondary); +} + +.status-badge.awaiting { + background-color: rgba(245, 158, 11, 0.1); + color: var(--warning-color); +} + +.status-badge.error { + background-color: rgba(239, 68, 68, 0.1); + color: var(--error-color); +} + +.status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background-color: currentColor; +} + +/* Cards */ +.card { + background-color: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 8px; + padding: 16px; +} + +/* Form elements */ +.radio-group { + display: flex; + flex-direction: column; + gap: 12px; +} + +.radio-option { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 12px; + border: 1px solid var(--border-color); + border-radius: 8px; + cursor: pointer; + transition: all 0.15s ease; +} + +.radio-option:hover { + border-color: var(--accent-color); + background-color: var(--bg-secondary); +} + +.radio-option.selected { + border-color: var(--accent-color); + background-color: rgba(59, 130, 246, 0.05); +} + +.radio-option input[type="radio"] { + margin-top: 2px; + accent-color: var(--accent-color); +} + +.radio-option-content { + flex: 1; +} + +.radio-option-title { + font-weight: 500; + color: var(--text-primary); +} + +.radio-option-desc { + font-size: 13px; + color: var(--text-secondary); + margin-top: 2px; +} + +/* Toggle/Checkbox */ +.toggle-list { + display: flex; + flex-direction: column; + gap: 8px; + margin-top: 12px; + padding-left: 24px; +} + +.toggle-item { + display: flex; + align-items: center; + gap: 8px; +} + +.toggle-item input[type="checkbox"] { + accent-color: var(--accent-color); +} + +.toggle-item label { + font-size: 13px; + color: var(--text-secondary); + cursor: pointer; +} + +/* Divider */ +.divider { + height: 1px; + background-color: var(--border-color); + margin: 12px 0; +} + +/* Link */ +.link { + color: var(--accent-color); + text-decoration: none; + font-size: 13px; +} + +.link:hover { + text-decoration: underline; +} + +/* Text utilities */ +.text-sm { + font-size: 12px; +} + +.text-muted { + color: var(--text-muted); +} + +.text-secondary { + color: var(--text-secondary); +} + +.text-center { + text-align: center; +} + +/* Spacing utilities */ +.mt-1 { margin-top: 4px; } +.mt-2 { margin-top: 8px; } +.mt-3 { margin-top: 12px; } +.mt-4 { margin-top: 16px; } +.mb-1 { margin-bottom: 4px; } +.mb-2 { margin-bottom: 8px; } +.mb-3 { margin-bottom: 12px; } +.mb-4 { margin-bottom: 16px; } + +/* Flex utilities */ +.flex { display: flex; } +.flex-col { flex-direction: column; } +.items-center { align-items: center; } +.justify-between { justify-content: space-between; } +.gap-1 { gap: 4px; } +.gap-2 { gap: 8px; } +.gap-3 { gap: 12px; } diff --git a/apps/x/packages/core/src/knowledge/chrome-extension/server/server.ts b/apps/x/packages/core/src/knowledge/chrome-extension/server/server.ts new file mode 100644 index 00000000..0cb127b9 --- /dev/null +++ b/apps/x/packages/core/src/knowledge/chrome-extension/server/server.ts @@ -0,0 +1,281 @@ +import express from 'express'; +import cors from 'cors'; +import crypto from 'crypto'; +import fs from 'fs'; +import path from 'path'; +import { WorkDir } from '../../../config/config.js'; + +const app = express(); +app.use(cors()); +app.use(express.json({ limit: '10mb' })); + +const CAPTURED_PAGES_DIR = path.join(WorkDir, 'chrome_sync'); +const CONFIG_DIR = path.join(WorkDir, 'config'); +const CONFIG_FILE = path.join(CONFIG_DIR, 'chrome-plugin.json'); + +interface Config { + mode: 'all' | 'ask'; + whitelist: string[]; + blacklist: string[]; + enabled: boolean; +} + +const DEFAULT_CONFIG: Config = { + mode: 'ask', + whitelist: [], + blacklist: [], + enabled: true +}; + +const contentHashes = new Map(); + +function extractDomain(url: string): string { + try { + const parsed = new URL(url); + return parsed.host || 'unknown'; + } catch { + return 'unknown'; + } +} + +function pathToSlug(url: string): string { + try { + const parsed = new URL(url); + const p = parsed.pathname + (parsed.search || ''); + if (!p || p === '/') return 'index'; + let slug = p.replace(/[^a-zA-Z0-9]+/g, '_').replace(/^_|_$/g, ''); + return slug.substring(0, 80) || 'index'; + } catch { + return 'index'; + } +} + +function hashContent(content: string): string { + return crypto.createHash('sha256').update(content, 'utf-8').digest('hex'); +} + +function findExistingFile(domainDir: string, pathSlug: string): string | null { + if (!fs.existsSync(domainDir)) return null; + const files = fs.readdirSync(domainDir); + for (const filename of files) { + if (filename.endsWith(`_${pathSlug}.md`)) { + return path.join(domainDir, filename); + } + } + return null; +} + +// POST /capture +app.post('/capture', (req, res) => { + const data = req.body; + if (!data) { + return res.status(400).json({ error: 'No JSON data provided' }); + } + + const { url, content = '', timestamp, title = 'Untitled' } = data; + + if (!url || !timestamp) { + return res.status(400).json({ error: 'Missing required fields: url, timestamp' }); + } + + const domain = extractDomain(url); + const pathSlug = pathToSlug(url); + const contentHash = hashContent(content); + const cacheKey = `${domain}/${pathSlug}`; + + const dt = new Date(timestamp); + const year = dt.getFullYear(); + const month = String(dt.getMonth() + 1).padStart(2, '0'); + const day = String(dt.getDate()).padStart(2, '0'); + const dateStr = `${year}-${month}-${day}`; + const hours = String(dt.getHours()).padStart(2, '0'); + const minutes = String(dt.getMinutes()).padStart(2, '0'); + const seconds = String(dt.getSeconds()).padStart(2, '0'); + const timeStr = `${hours}-${minutes}`; + const timeDisplay = `${hours}:${minutes}:${seconds}`; + const tzOffset = -dt.getTimezoneOffset(); + const tzSign = tzOffset >= 0 ? '+' : '-'; + const tzHours = String(Math.floor(Math.abs(tzOffset) / 60)).padStart(2, '0'); + const tzMins = String(Math.abs(tzOffset) % 60).padStart(2, '0'); + const isoTimestamp = `${dateStr}T${hours}:${minutes}:${seconds}${tzSign}${tzHours}:${tzMins}`; + + // date/domain directory structure + const domainDir = path.join(CAPTURED_PAGES_DIR, dateStr, domain); + fs.mkdirSync(domainDir, { recursive: true }); + + const existingFile = findExistingFile(domainDir, pathSlug); + if (existingFile && contentHashes.get(cacheKey) === contentHash) { + return res.json({ status: 'skipped', reason: 'duplicate content' }); + } + + contentHashes.set(cacheKey, contentHash); + + // If file exists, append with scroll separator + if (existingFile) { + const scrollSeparator = `\n\n---\nšŸ“œ Scroll captured at ${timeDisplay}\n---\n\n`; + fs.appendFileSync(existingFile, scrollSeparator + content, 'utf-8'); + const rel = `${dateStr}/${domain}/${path.basename(existingFile)}`; + return res.json({ status: 'appended', filename: rel }); + } + + // New file - create with frontmatter + const filename = `${timeStr}_${pathSlug}.md`; + const filepath = path.join(domainDir, filename); + + const markdownContent = `--- +url: ${url} +title: ${title} +captured_at: ${isoTimestamp} +--- + +${content} +`; + + fs.writeFileSync(filepath, markdownContent, 'utf-8'); + return res.status(201).json({ status: 'captured', filename: `${dateStr}/${domain}/${filename}` }); +}); + +// GET /status +app.get('/status', (_req, res) => { + let count = 0; + const domains: Record = {}; + + if (!fs.existsSync(CAPTURED_PAGES_DIR)) { + return res.json({ count: 0, domains: [] }); + } + + for (const dateEntry of fs.readdirSync(CAPTURED_PAGES_DIR)) { + const datePath = path.join(CAPTURED_PAGES_DIR, dateEntry); + if (!fs.statSync(datePath).isDirectory()) continue; + + for (const domainEntry of fs.readdirSync(datePath)) { + const domainPath = path.join(datePath, domainEntry); + if (!fs.statSync(domainPath).isDirectory()) continue; + + const domainCount = fs.readdirSync(domainPath).filter(f => f.endsWith('.md')).length; + count += domainCount; + if (domainCount > 0) { + domains[domainEntry] = (domains[domainEntry] || 0) + domainCount; + } + } + } + + const domainList = Object.entries(domains) + .map(([domain, c]) => ({ domain, count: c })) + .sort((a, b) => b.count - a.count); + + return res.json({ count, domains: domainList }); +}); + +// Config helpers +function loadConfig(): Config { + if (fs.existsSync(CONFIG_FILE)) { + try { + const raw = fs.readFileSync(CONFIG_FILE, 'utf-8'); + return JSON.parse(raw); + } catch { + // fall through + } + } + return { ...DEFAULT_CONFIG }; +} + +function saveConfig(config: Config): void { + fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8'); +} + +function validateConfig(data: any): data is Config { + if (typeof data !== 'object' || data === null) return false; + if (data.mode !== 'all' && data.mode !== 'ask') return false; + if (!Array.isArray(data.whitelist)) return false; + if (!Array.isArray(data.blacklist)) return false; + if (typeof data.enabled !== 'boolean') return false; + return true; +} + +// GET /browse/config +app.get('/browse/config', (_req, res) => { + const config = loadConfig(); + return res.json(config); +}); + +// POST /browse/config +app.post('/browse/config', (req, res) => { + const data = req.body; + if (!data) { + return res.status(400).json({ error: 'No JSON data provided' }); + } + + if (!validateConfig(data)) { + return res.status(400).json({ error: 'Invalid config shape' }); + } + + saveConfig(data); + return res.json({ status: 'saved', config: data }); +}); + +const PORT = 3001; +const RETENTION_DAYS = 7; +const CLEANUP_INTERVAL_MS = 60 * 60 * 1000; // 1 hour + +function cleanUpOldFiles(): void { + if (!fs.existsSync(CAPTURED_PAGES_DIR)) return; + + const cutoff = new Date(); + cutoff.setDate(cutoff.getDate() - RETENTION_DAYS); + const cutoffStr = cutoff.toISOString().slice(0, 10); // YYYY-MM-DD + + for (const dateEntry of fs.readdirSync(CAPTURED_PAGES_DIR)) { + // only process date-formatted directories + if (!/^\d{4}-\d{2}-\d{2}$/.test(dateEntry)) continue; + if (dateEntry >= cutoffStr) continue; + + const datePath = path.join(CAPTURED_PAGES_DIR, dateEntry); + if (!fs.statSync(datePath).isDirectory()) continue; + + fs.rmSync(datePath, { recursive: true, force: true }); + console.log(`[ChromeSync] Cleaned up old captures: ${dateEntry}`); + } +} + +function isServerEnabled(): boolean { + if (!fs.existsSync(CONFIG_FILE)) return false; + try { + const raw = fs.readFileSync(CONFIG_FILE, 'utf-8'); + const config = JSON.parse(raw); + return config.serverEnabled === true; + } catch { + return false; + } +} + +function startServer(): void { + fs.mkdirSync(CAPTURED_PAGES_DIR, { recursive: true }); + + cleanUpOldFiles(); + setInterval(cleanUpOldFiles, CLEANUP_INTERVAL_MS); + + app.listen(PORT, 'localhost', () => { + console.log('[ChromeSync] Server starting.'); + console.log(` Captured pages: ${CAPTURED_PAGES_DIR}`); + console.log(` Config: ${CONFIG_FILE}`); + console.log(` Listening on http://localhost:${PORT}`); + }); +} + +export async function init(): Promise { + fs.mkdirSync(CONFIG_DIR, { recursive: true }); + + if (isServerEnabled()) { + startServer(); + return; + } + + console.log('[ChromeSync] Server disabled, watching config for changes...'); + fs.watch(CONFIG_DIR, (_, filename) => { + if (filename === 'chrome-plugin.json' && isServerEnabled()) { + console.log('[ChromeSync] serverEnabled set to true, starting server...'); + startServer(); + } + }); +} diff --git a/apps/x/pnpm-lock.yaml b/apps/x/pnpm-lock.yaml index c4e701a5..e59ce990 100644 --- a/apps/x/pnpm-lock.yaml +++ b/apps/x/pnpm-lock.yaml @@ -53,9 +53,6 @@ importers: electron-squirrel-startup: specifier: ^1.0.1 version: 1.0.1 - html-to-docx: - specifier: ^1.8.0 - version: 1.8.0(encoding@0.1.13) mammoth: specifier: ^1.11.0 version: 1.11.0 @@ -238,9 +235,6 @@ importers: react-dom: specifier: ^19.2.0 version: 19.2.3(react@19.2.3) - recharts: - specifier: ^3.8.0 - version: 3.8.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react-is@16.13.1)(react@19.2.3)(redux@5.0.1) sonner: specifier: ^2.0.7 version: 2.0.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -310,16 +304,16 @@ importers: dependencies: '@ai-sdk/anthropic': specifier: ^2.0.63 - version: 2.0.63(zod@4.2.1) + version: 2.0.70(zod@4.2.1) '@ai-sdk/google': specifier: ^2.0.53 - version: 2.0.53(zod@4.2.1) + version: 2.0.61(zod@4.2.1) '@ai-sdk/openai': specifier: ^2.0.91 - version: 2.0.91(zod@4.2.1) + version: 2.0.99(zod@4.2.1) '@ai-sdk/openai-compatible': specifier: ^1.0.33 - version: 1.0.33(zod@4.2.1) + version: 1.0.34(zod@4.2.1) '@ai-sdk/provider': specifier: ^2.0.1 version: 2.0.1 @@ -334,7 +328,7 @@ importers: version: 1.25.1(hono@4.11.3)(zod@4.2.1) '@openrouter/ai-sdk-provider': specifier: ^1.2.6 - version: 1.5.4(ai@5.0.133(zod@4.2.1))(zod@4.2.1) + version: 1.5.4(ai@5.0.151(zod@4.2.1))(zod@4.2.1) '@react-pdf/renderer': specifier: ^4.3.2 version: 4.3.2(react@19.2.3) @@ -346,16 +340,22 @@ importers: version: link:../shared ai: specifier: ^5.0.133 - version: 5.0.133(zod@4.2.1) + version: 5.0.151(zod@4.2.1) awilix: specifier: ^12.0.5 version: 12.0.5 chokidar: specifier: ^4.0.3 version: 4.0.3 + cors: + specifier: ^2.8.6 + version: 2.8.6 cron-parser: specifier: ^5.5.0 version: 5.5.0 + express: + specifier: ^5.2.1 + version: 5.2.1 glob: specifier: ^13.0.0 version: 13.0.0 @@ -399,6 +399,12 @@ importers: specifier: ^4.2.1 version: 4.2.1 devDependencies: + '@types/cors': + specifier: ^2.8.19 + version: 2.8.19 + '@types/express': + specifier: ^5.0.6 + version: 5.0.6 '@types/node': specifier: ^25.0.3 version: 25.0.3 @@ -417,8 +423,8 @@ importers: packages: - '@ai-sdk/anthropic@2.0.63': - resolution: {integrity: sha512-zXlUPCkumnvp8lWS9VFcen/MLF6CL/t1zAKDhpobYj9y/nmylQrKtRvn3RwH871Wd3dF3KYEUXd6M2c6dfCKOA==} + '@ai-sdk/anthropic@2.0.70': + resolution: {integrity: sha512-W3WjQlb0Ho+CVAQUvb8Rtk3hGS3Jlgy79ihY2H0yj2k4yU8XuxpQw0Oz+7JQsB47j+jlHhk7nUXtxhAeRg3S3Q==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -429,26 +435,26 @@ packages: peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/gateway@2.0.39': - resolution: {integrity: sha512-ULnefGmRHG0/tRrf+dtDwgQYAttGi/TR0FmASAzTs1dtpeZp4Xoh1VyWrX3Z1bM3WDs9RM3ZeSE77kQT/jbfjw==} + '@ai-sdk/gateway@2.0.56': + resolution: {integrity: sha512-omvb2Bwpgqg8PKqOpYdIaW+fdEIWcfm2B/j3dx37DxzOIt6fr57VVcfw7pu/EaACcY0O+wsg50iFCPGcsI2Cbg==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/google@2.0.53': - resolution: {integrity: sha512-ccCxr5mrd3AC2CjLq4e1ST7+UiN5T2Pdmgi0XdWM3QohmNBwUQ/RBG7BvL+cB/ex/j6y64tkMmpYz9zBw/SEFQ==} + '@ai-sdk/google@2.0.61': + resolution: {integrity: sha512-hIs7UvL8X5MBG3uxdciSotD4I27UcMa4/we9Qf98fM/RgMTwyk9zXcr7GM6k5yLBZ5S0QeZWkfqKwtdiDnUEEQ==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/openai-compatible@1.0.33': - resolution: {integrity: sha512-2KMcR2xAul3u5dGZD7gONgbIki3Hg7Ey+sFu7gsiJ4U2iRU0GDV3ccNq79dTuAEXPDFcOWCUpW8A8jXc0kxJxQ==} + '@ai-sdk/openai-compatible@1.0.34': + resolution: {integrity: sha512-AnGoxVNZ/E3EU4lW12rrufI6riqL2cEv4jk3OrjJ/i54XwR0CJU1V26jXAwxb+Pc+uZmYG++HM+gzXxPQZkMNQ==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/openai@2.0.91': - resolution: {integrity: sha512-lozfRHfSTHg5/UliQjTDcOtISYGbEpt4FS/6QM5PcLmhdT0HmROllaBmG7+JaK+uqFtDXZGgMIpz3bqB9nzqCQ==} + '@ai-sdk/openai@2.0.99': + resolution: {integrity: sha512-wwa1/DuO9XThaA+sAi0d3+xfkbEx9nRhZ1USV6kktndmEs8aQRR0DJK/Iec+mwNu06IhfDGd5vMscR1U1q155g==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -459,8 +465,8 @@ packages: peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/provider-utils@3.0.21': - resolution: {integrity: sha512-veuMwTLxsgh31Jjn0SnBABnM1f7ebHhRWcV2ZuY3hP3iJDCZ8VXBaYqcHXoOQDqUXTCas08sKQcHyWK+zl882Q==} + '@ai-sdk/provider-utils@3.0.22': + resolution: {integrity: sha512-fFT1KfUUKktfAFm5mClJhS1oux9tP2qgzmEZVl5UdwltQ1LO/s8hd7znVrgKzivwv1s1FIPza0s9OpJaNB/vHw==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -1572,46 +1578,6 @@ packages: '@octokit/types@6.41.0': resolution: {integrity: sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==} - '@oozcitak/dom@1.15.5': - resolution: {integrity: sha512-L6v3Mwb0TaYBYgeYlIeBaHnc+2ZEaDSbFiRm5KmqZQSoBlbPlf+l6aIH/sD5GUf2MYwULw00LT7+dOnEuAEC0A==} - engines: {node: '>=8.0'} - - '@oozcitak/dom@1.15.6': - resolution: {integrity: sha512-k4uEIa6DI3FCrFJMGq/05U/59WnS9DjME0kaPqBRCJAqBTkmopbYV1Xs4qFKbDJ/9wOg8W97p+1E0heng/LH7g==} - engines: {node: '>=8.0'} - - '@oozcitak/infra@1.0.3': - resolution: {integrity: sha512-9O2wxXGnRzy76O1XUxESxDGsXT5kzETJPvYbreO4mv6bqe1+YSuux2cZTagjJ/T4UfEwFJz5ixanOqB0QgYAag==} - engines: {node: '>=6.0'} - - '@oozcitak/infra@1.0.5': - resolution: {integrity: sha512-o+zZH7M6l5e3FaAWy3ojaPIVN5eusaYPrKm6MZQt0DKNdgXa2wDYExjpP0t/zx+GoQgQKzLu7cfD8rHCLt8JrQ==} - engines: {node: '>=6.0'} - - '@oozcitak/url@1.0.0': - resolution: {integrity: sha512-LGrMeSxeLzsdaitxq3ZmBRVOrlRRQIgNNci6L0VRnOKlJFuRIkNm4B+BObXPCJA6JT5bEJtrrwjn30jueHJYZQ==} - engines: {node: '>=8.0'} - - '@oozcitak/util@1.0.1': - resolution: {integrity: sha512-dFwFqcKrQnJ2SapOmRD1nQWEZUtbtIy9Y6TyJquzsalWNJsKIPxmTI0KG6Ypyl8j7v89L2wixH9fQDNrF78hKg==} - engines: {node: '>=6.0'} - - '@oozcitak/util@1.0.2': - resolution: {integrity: sha512-4n8B1cWlJleSOSba5gxsMcN4tO8KkkcvXhNWW+ADqvq9Xj+Lrl9uCa90GRpjekqQJyt84aUX015DG81LFpZYXA==} - engines: {node: '>=6.0'} - - '@oozcitak/util@8.0.0': - resolution: {integrity: sha512-+9Hq6yuoq/3TRV/n/xcpydGBq2qN2/DEDMqNTG7rm95K6ZE2/YY/sPyx62+1n8QsE9O26e5M1URlXsk+AnN9Jw==} - engines: {node: '>=6.0'} - - '@oozcitak/util@8.3.3': - resolution: {integrity: sha512-Ufpab7G5PfnEhQyy5kDg9C8ltWJjsVT1P/IYqacjstaqydG4Q21HAT2HUZQYBrC/a1ZLKCz87pfydlDvv8y97w==} - engines: {node: '>=6.0'} - - '@oozcitak/util@8.3.4': - resolution: {integrity: sha512-6gH/bLQJSJEg7OEpkH4wGQdA8KXHRbzL1YkGyUO12YNAgV3jxKy4K9kvfXj4+9T0OLug5k58cnPCKSSIKzp7pg==} - engines: {node: '>=8.0'} - '@openrouter/ai-sdk-provider@1.5.4': resolution: {integrity: sha512-xrSQPUIH8n9zuyYZR0XK7Ba0h2KsjJcMkxnwaYfmv13pKs3sDkjPzVPPhlhzqBGddHb5cFEwJ9VFuFeDcxCDSw==} engines: {node: '>=18'} @@ -2550,17 +2516,6 @@ packages: '@react-pdf/types@2.9.2': resolution: {integrity: sha512-dufvpKId9OajLLbgn9q7VLUmyo1Jf+iyGk2ZHmCL8nIDtL8N1Ejh9TH7+pXXrR0tdie1nmnEb5Bz9U7g4hI4/g==} - '@reduxjs/toolkit@2.11.2': - resolution: {integrity: sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==} - peerDependencies: - react: ^16.9.0 || ^17.0.0 || ^18 || ^19 - react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 - peerDependenciesMeta: - react: - optional: true - react-redux: - optional: true - '@remirror/core-constants@3.0.0': resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} @@ -2921,9 +2876,6 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@standard-schema/utils@0.3.0': - resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} - '@swc/helpers@0.5.18': resolution: {integrity: sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==} @@ -3221,9 +3173,18 @@ packages: '@types/babel__traverse@7.28.0': resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + '@types/cacheable-request@6.0.3': resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/cors@2.8.19': + resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} + '@types/d3-array@3.2.2': resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} @@ -3335,6 +3296,12 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/express-serve-static-core@5.1.1': + resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==} + + '@types/express@5.0.6': + resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} + '@types/fs-extra@9.0.13': resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} @@ -3347,6 +3314,9 @@ packages: '@types/http-cache-semantics@4.0.4': resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -3398,6 +3368,12 @@ packages: '@types/pdf-parse@1.1.5': resolution: {integrity: sha512-kBfrSXsloMnUJOKi25s3+hRmkycHfLK6A09eRGqF/N8BkQoPUmaCr+q8Cli5FnfohEz/rsv82zAiPz/LXtOGhA==} + '@types/qs@6.14.0': + resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: @@ -3409,6 +3385,12 @@ packages: '@types/responselike@1.0.3': resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + + '@types/serve-static@2.2.0': + resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} + '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -3617,8 +3599,8 @@ packages: peerDependencies: zod: ^3.25.76 || ^4.1.8 - ai@5.0.133: - resolution: {integrity: sha512-N6KnwSWKcXEWPnAri3anRuzRvcrvtDz1W1JG9CvMrQ0Xdp8Vu8ZToNW/eHt63CmrbmzTwVw/HaCtJuO+MYtS7A==} + ai@5.0.151: + resolution: {integrity: sha512-tsLIv+QN9wJ/xl/fnYgjRYoGSThdnOfU4d6+7QEUKX3EcwviWMEaL1gOE+zfdkcay/Tbc02ZBtTRHoulS6DYvQ==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -3793,9 +3775,6 @@ packages: brotli@1.3.3: resolution: {integrity: sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==} - browser-split@0.0.1: - resolution: {integrity: sha512-JhvgRb2ihQhsljNda3BI8/UcRHVzrVwo3Q+P8vDtSiyobXuFpuZ9mq+MbRGMnC22CjW3RrfXdg6j6ITX8M+7Ow==} - browserify-zlib@0.2.0: resolution: {integrity: sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==} @@ -3857,9 +3836,6 @@ packages: camel-case@4.1.2: resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} - camelize@1.0.1: - resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} - caniuse-lite@1.0.30001761: resolution: {integrity: sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==} @@ -4057,8 +4033,8 @@ packages: core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - cors@2.8.5: - resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} engines: {node: '>= 0.10'} cose-base@1.0.3: @@ -4287,9 +4263,6 @@ packages: supports-color: optional: true - decimal.js-light@2.5.1: - resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} - decode-named-character-reference@1.2.0: resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} @@ -4358,24 +4331,12 @@ packages: dir-compare@4.2.0: resolution: {integrity: sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==} - dom-serializer@0.2.2: - resolution: {integrity: sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==} - dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} - dom-walk@0.1.2: - resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==} - - domelementtype@1.3.1: - resolution: {integrity: sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==} - domelementtype@2.3.0: resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - domhandler@2.4.2: - resolution: {integrity: sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==} - domhandler@5.0.3: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} @@ -4383,9 +4344,6 @@ packages: dompurify@3.3.1: resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==} - domutils@1.7.0: - resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==} - domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} @@ -4470,16 +4428,6 @@ packages: resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} engines: {node: '>=10.13.0'} - ent@2.2.2: - resolution: {integrity: sha512-kKvD1tO6BM+oK9HzCPpUdRb4vKFQY/FPTFmurMvh6LlN68VMrdj77w8yp51/kDbpkFOS9J8w5W6zIzgM2H8/hw==} - engines: {node: '>= 0.4'} - - entities@1.1.2: - resolution: {integrity: sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==} - - entities@2.2.0: - resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} - entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -4498,9 +4446,6 @@ packages: error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - error@4.4.0: - resolution: {integrity: sha512-SNDKualLUtT4StGFP7xNfuFybL2f6iJujFtrWuvJqGbVQGaN+adE23veqzPz1hjUjTunLi2EnJ+0SJxtbJreKw==} - es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -4520,9 +4465,6 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} - es-toolkit@1.45.1: - resolution: {integrity: sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==} - es6-error@4.1.1: resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} @@ -4623,9 +4565,6 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} - ev-store@7.0.0: - resolution: {integrity: sha512-otazchNRnGzp2YarBJ+GXKVGvhxVATB1zmaStxJBYet0Dyq7A9VhH8IUEB/gRcL6Ch52lfpgPTRJ2m49epyMsQ==} - event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} @@ -4945,7 +4884,6 @@ packages: glob@10.5.0: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@13.0.0: @@ -4954,7 +4892,7 @@ packages: glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + deprecated: Glob versions prior to v9 are no longer supported glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} @@ -4969,9 +4907,6 @@ packages: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} engines: {node: '>=10'} - global@4.4.0: - resolution: {integrity: sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==} - globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -5115,24 +5050,12 @@ packages: hsl-to-rgb-for-reals@1.1.1: resolution: {integrity: sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==} - html-entities@2.6.0: - resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==} - - html-to-docx@1.8.0: - resolution: {integrity: sha512-IiMBWIqXM4+cEsW//RKoonWV7DlXAJBmmKI73XJSVWTIXjGUaxSr2ck1jqzVRZknpvO8xsFnVicldKVAWrBYBA==} - - html-to-vdom@0.7.0: - resolution: {integrity: sha512-k+d2qNkbx0JO00KezQsNcn6k2I/xSBP4yXYFLvXbcasTTDh+RDLUJS3puxqyNnpdyXWRHFGoKU7cRmby8/APcQ==} - html-url-attributes@3.0.1: resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} - htmlparser2@3.10.1: - resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==} - http-cache-semantics@4.2.0: resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} @@ -5190,23 +5113,9 @@ packages: engines: {node: '>=6.9.0'} hasBin: true - image-size@1.2.1: - resolution: {integrity: sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==} - engines: {node: '>=16.x'} - hasBin: true - - image-to-base64@2.2.0: - resolution: {integrity: sha512-Z+aMwm/91UOQqHhrz7Upre2ytKhWejZlWV/JxUTD1sT7GWWKFDJUEV5scVQKnkzSgPHFuQBUEWcanO+ma0PSVw==} - immediate@3.0.6: resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} - immer@10.2.0: - resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==} - - immer@11.1.4: - resolution: {integrity: sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==} - import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -5223,9 +5132,6 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} - individual@3.0.0: - resolution: {integrity: sha512-rUY5vtT748NMRbEMrTNiFfy29BgGZwGXUi2NFUVMWQrogSLzlJvQV9eeMWi+g1aVaQ53tpyLAQtd5x/JH0Nh1g==} - infer-owner@1.0.4: resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} @@ -5326,9 +5232,6 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-object@1.0.2: - resolution: {integrity: sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==} - is-plain-obj@4.1.0: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} @@ -5339,10 +5242,6 @@ packages: is-property@1.0.2: resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} - is-regex@1.2.1: - resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} - engines: {node: '>= 0.4'} - is-stream@1.1.0: resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} engines: {node: '>=0.10.0'} @@ -5937,9 +5836,6 @@ packages: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} - min-document@2.19.2: - resolution: {integrity: sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==} - minimatch@10.1.1: resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} engines: {node: 20 || >=22} @@ -6069,9 +5965,6 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - next-tick@0.2.2: - resolution: {integrity: sha512-f7h4svPtl+QidoBv4taKXUjJ70G2asaZ8G28nS0OkqaalX8dwwrtWtyxEDPK62AC00ur/+/E0pUwBwY5EPn15Q==} - nice-try@1.0.5: resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} @@ -6532,9 +6425,6 @@ packages: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} - punycode@1.4.1: - resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} - punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -6594,18 +6484,6 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - react-redux@9.2.0: - resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==} - peerDependencies: - '@types/react': ^18.2.25 || ^19 - react: ^18.0 || ^19 - redux: ^5.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - redux: - optional: true - react-refresh@0.18.0: resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} engines: {node: '>=0.10.0'} @@ -6671,26 +6549,10 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} - recharts@3.8.0: - resolution: {integrity: sha512-Z/m38DX3L73ExO4Tpc9/iZWHmHnlzWG4njQbxsF5aSjwqmHNDDIm0rdEBArkwsBvR8U6EirlEHiQNYWCVh9sGQ==} - engines: {node: '>=18'} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - rechoir@0.8.0: resolution: {integrity: sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==} engines: {node: '>= 10.13.0'} - redux-thunk@3.1.0: - resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} - peerDependencies: - redux: ^5.0.0 - - redux@5.0.1: - resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} - regex-recursion@6.0.2: resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} @@ -6763,9 +6625,6 @@ packages: resolution: {integrity: sha512-oTeemxwoMuxxTYxXUwjkrOPfngTQehlv0/HoYFNkB4uzsP1Un1A9nI8JQKGOFkxpqkC7qkMs0lUsGrvUlbLNUA==} engines: {node: '>=14', npm: '>=7'} - reselect@5.1.1: - resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} - resolve-alpn@1.2.1: resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} @@ -6854,10 +6713,6 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safe-regex-test@1.1.0: - resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} - engines: {node: '>= 0.4'} - safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -7053,9 +6908,6 @@ packages: peerDependencies: react: ^18.0.0 || ^19.0.0 - string-template@0.2.1: - resolution: {integrity: sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==} - string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -7141,7 +6993,7 @@ packages: tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} - deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me temp@0.9.4: resolution: {integrity: sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==} @@ -7174,9 +7026,6 @@ packages: tiny-inflate@1.0.3: resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} - tiny-invariant@1.3.3: - resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - tinyexec@1.0.2: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} @@ -7306,8 +7155,8 @@ packages: ufo@1.6.1: resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} - underscore@1.13.7: - resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==} + underscore@1.13.8: + resolution: {integrity: sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==} undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -7448,12 +7297,6 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - victory-vendor@37.3.6: - resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} - - virtual-dom@2.1.1: - resolution: {integrity: sha512-wb6Qc9Lbqug0kRqo/iuApfBpJJAq14Sk1faAnSmtqXiwahg7PVTvWMs9L02Z8nNIMqbwsxzBAA90bbtRLbw0zg==} - vite-compatible-readable-stream@3.6.1: resolution: {integrity: sha512-t20zYkrSf868+j/p31cRIGN28Phrjm3nRSLR2fyc2tiWi4cZGVdv68yNlwnIINTkMTmPoMiSlc0OadaO7DXZaQ==} engines: {node: '>= 6'} @@ -7603,21 +7446,11 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - x-is-array@0.1.0: - resolution: {integrity: sha512-goHPif61oNrr0jJgsXRfc8oqtYzvfiMJpTqwE7Z4y9uH+T3UozkGqQ4d2nX9mB9khvA8U2o/UbPOFjgC7hLWIA==} - - x-is-string@0.1.0: - resolution: {integrity: sha512-GojqklwG8gpzOVEVki5KudKNoq7MbbjYZCbyWzEz7tyPA7eleiE0+ePwOWQQRb5fm86rD3S8Tc0tSFf3AOv50w==} - xlsx@0.18.5: resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==} engines: {node: '>=0.8'} hasBin: true - xmlbuilder2@2.1.2: - resolution: {integrity: sha512-PI710tmtVlQ5VmwzbRTuhmVhKnj9pM8Si+iOZCV2g2SNo3gCrpzR2Ka9wNzZtqfD+mnP+xkrqoNy0sjKZqP4Dg==} - engines: {node: '>=8.0'} - xmlbuilder@10.1.1: resolution: {integrity: sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==} engines: {node: '>=4.0'} @@ -7694,10 +7527,10 @@ packages: snapshots: - '@ai-sdk/anthropic@2.0.63(zod@4.2.1)': + '@ai-sdk/anthropic@2.0.70(zod@4.2.1)': dependencies: '@ai-sdk/provider': 2.0.1 - '@ai-sdk/provider-utils': 3.0.21(zod@4.2.1) + '@ai-sdk/provider-utils': 3.0.22(zod@4.2.1) zod: 4.2.1 '@ai-sdk/gateway@2.0.24(zod@4.2.1)': @@ -7707,29 +7540,29 @@ snapshots: '@vercel/oidc': 3.0.5 zod: 4.2.1 - '@ai-sdk/gateway@2.0.39(zod@4.2.1)': + '@ai-sdk/gateway@2.0.56(zod@4.2.1)': dependencies: '@ai-sdk/provider': 2.0.1 - '@ai-sdk/provider-utils': 3.0.21(zod@4.2.1) + '@ai-sdk/provider-utils': 3.0.22(zod@4.2.1) '@vercel/oidc': 3.1.0 zod: 4.2.1 - '@ai-sdk/google@2.0.53(zod@4.2.1)': + '@ai-sdk/google@2.0.61(zod@4.2.1)': dependencies: '@ai-sdk/provider': 2.0.1 - '@ai-sdk/provider-utils': 3.0.21(zod@4.2.1) + '@ai-sdk/provider-utils': 3.0.22(zod@4.2.1) zod: 4.2.1 - '@ai-sdk/openai-compatible@1.0.33(zod@4.2.1)': + '@ai-sdk/openai-compatible@1.0.34(zod@4.2.1)': dependencies: '@ai-sdk/provider': 2.0.1 - '@ai-sdk/provider-utils': 3.0.21(zod@4.2.1) + '@ai-sdk/provider-utils': 3.0.22(zod@4.2.1) zod: 4.2.1 - '@ai-sdk/openai@2.0.91(zod@4.2.1)': + '@ai-sdk/openai@2.0.99(zod@4.2.1)': dependencies: '@ai-sdk/provider': 2.0.1 - '@ai-sdk/provider-utils': 3.0.21(zod@4.2.1) + '@ai-sdk/provider-utils': 3.0.22(zod@4.2.1) zod: 4.2.1 '@ai-sdk/provider-utils@3.0.20(zod@4.2.1)': @@ -7739,7 +7572,7 @@ snapshots: eventsource-parser: 3.0.6 zod: 4.2.1 - '@ai-sdk/provider-utils@3.0.21(zod@4.2.1)': + '@ai-sdk/provider-utils@3.0.22(zod@4.2.1)': dependencies: '@ai-sdk/provider': 2.0.1 '@standard-schema/spec': 1.1.0 @@ -9240,7 +9073,7 @@ snapshots: ajv: 8.17.1 ajv-formats: 3.0.1(ajv@8.17.1) content-type: 1.0.5 - cors: 2.8.5 + cors: 2.8.6 cross-spawn: 7.0.6 eventsource: 3.0.7 eventsource-parser: 3.0.6 @@ -9397,45 +9230,10 @@ snapshots: dependencies: '@octokit/openapi-types': 12.11.0 - '@oozcitak/dom@1.15.5': - dependencies: - '@oozcitak/infra': 1.0.5 - '@oozcitak/url': 1.0.0 - '@oozcitak/util': 8.0.0 - - '@oozcitak/dom@1.15.6': - dependencies: - '@oozcitak/infra': 1.0.5 - '@oozcitak/url': 1.0.0 - '@oozcitak/util': 8.3.4 - - '@oozcitak/infra@1.0.3': - dependencies: - '@oozcitak/util': 1.0.1 - - '@oozcitak/infra@1.0.5': - dependencies: - '@oozcitak/util': 8.0.0 - - '@oozcitak/url@1.0.0': - dependencies: - '@oozcitak/infra': 1.0.3 - '@oozcitak/util': 1.0.2 - - '@oozcitak/util@1.0.1': {} - - '@oozcitak/util@1.0.2': {} - - '@oozcitak/util@8.0.0': {} - - '@oozcitak/util@8.3.3': {} - - '@oozcitak/util@8.3.4': {} - - '@openrouter/ai-sdk-provider@1.5.4(ai@5.0.133(zod@4.2.1))(zod@4.2.1)': + '@openrouter/ai-sdk-provider@1.5.4(ai@5.0.151(zod@4.2.1))(zod@4.2.1)': dependencies: '@openrouter/sdk': 0.1.27 - ai: 5.0.133(zod@4.2.1) + ai: 5.0.151(zod@4.2.1) zod: 4.2.1 '@openrouter/sdk@0.1.27': @@ -10461,18 +10259,6 @@ snapshots: '@react-pdf/primitives': 4.1.1 '@react-pdf/stylesheet': 6.1.2 - '@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@19.2.7)(react@19.2.3)(redux@5.0.1))(react@19.2.3)': - dependencies: - '@standard-schema/spec': 1.1.0 - '@standard-schema/utils': 0.3.0 - immer: 11.1.4 - redux: 5.0.1 - redux-thunk: 3.1.0(redux@5.0.1) - reselect: 5.1.1 - optionalDependencies: - react: 19.2.3 - react-redux: 9.2.0(@types/react@19.2.7)(react@19.2.3)(redux@5.0.1) - '@remirror/core-constants@3.0.0': {} '@rolldown/pluginutils@1.0.0-beta.53': {} @@ -10918,8 +10704,6 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@standard-schema/utils@0.3.0': {} - '@swc/helpers@0.5.18': dependencies: tslib: 2.8.1 @@ -11232,6 +11016,11 @@ snapshots: dependencies: '@babel/types': 7.28.5 + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 25.0.3 + '@types/cacheable-request@6.0.3': dependencies: '@types/http-cache-semantics': 4.0.4 @@ -11239,6 +11028,14 @@ snapshots: '@types/node': 25.0.3 '@types/responselike': 1.0.3 + '@types/connect@3.4.38': + dependencies: + '@types/node': 25.0.3 + + '@types/cors@2.8.19': + dependencies: + '@types/node': 25.0.3 + '@types/d3-array@3.2.2': {} '@types/d3-axis@3.0.6': @@ -11378,6 +11175,19 @@ snapshots: '@types/estree@1.0.8': {} + '@types/express-serve-static-core@5.1.1': + dependencies: + '@types/node': 25.0.3 + '@types/qs': 6.14.0 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + + '@types/express@5.0.6': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.1.1 + '@types/serve-static': 2.2.0 + '@types/fs-extra@9.0.13': dependencies: '@types/node': 25.0.3 @@ -11391,6 +11201,8 @@ snapshots: '@types/http-cache-semantics@4.0.4': {} + '@types/http-errors@2.0.5': {} + '@types/json-schema@7.0.15': {} '@types/katex@0.16.7': {} @@ -11447,6 +11259,10 @@ snapshots: dependencies: '@types/node': 25.0.3 + '@types/qs@6.14.0': {} + + '@types/range-parser@1.2.7': {} + '@types/react-dom@19.2.3(@types/react@19.2.7)': dependencies: '@types/react': 19.2.7 @@ -11459,6 +11275,15 @@ snapshots: dependencies: '@types/node': 25.0.3 + '@types/send@1.2.1': + dependencies: + '@types/node': 25.0.3 + + '@types/serve-static@2.2.0': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 25.0.3 + '@types/trusted-types@2.0.7': optional: true @@ -11718,11 +11543,11 @@ snapshots: '@opentelemetry/api': 1.9.0 zod: 4.2.1 - ai@5.0.133(zod@4.2.1): + ai@5.0.151(zod@4.2.1): dependencies: - '@ai-sdk/gateway': 2.0.39(zod@4.2.1) + '@ai-sdk/gateway': 2.0.56(zod@4.2.1) '@ai-sdk/provider': 2.0.1 - '@ai-sdk/provider-utils': 3.0.21(zod@4.2.1) + '@ai-sdk/provider-utils': 3.0.22(zod@4.2.1) '@opentelemetry/api': 1.9.0 zod: 4.2.1 @@ -11904,8 +11729,6 @@ snapshots: dependencies: base64-js: 1.5.1 - browser-split@0.0.1: {} - browserify-zlib@0.2.0: dependencies: pako: 1.0.11 @@ -12000,8 +11823,6 @@ snapshots: pascal-case: 3.1.2 tslib: 2.8.1 - camelize@1.0.1: {} - caniuse-lite@1.0.30001761: {} ccount@2.0.1: {} @@ -12173,7 +11994,7 @@ snapshots: core-util-is@1.0.3: {} - cors@2.8.5: + cors@2.8.6: dependencies: object-assign: 4.1.1 vary: 1.1.2 @@ -12422,8 +12243,6 @@ snapshots: dependencies: ms: 2.1.3 - decimal.js-light@2.5.1: {} - decode-named-character-reference@1.2.0: dependencies: character-entities: 2.0.2 @@ -12487,27 +12306,14 @@ snapshots: minimatch: 3.1.2 p-limit: 3.1.0 - dom-serializer@0.2.2: - dependencies: - domelementtype: 2.3.0 - entities: 2.2.0 - dom-serializer@2.0.0: dependencies: domelementtype: 2.3.0 domhandler: 5.0.3 entities: 4.5.0 - dom-walk@0.1.2: {} - - domelementtype@1.3.1: {} - domelementtype@2.3.0: {} - domhandler@2.4.2: - dependencies: - domelementtype: 1.3.1 - domhandler@5.0.3: dependencies: domelementtype: 2.3.0 @@ -12516,11 +12322,6 @@ snapshots: optionalDependencies: '@types/trusted-types': 2.0.7 - domutils@1.7.0: - dependencies: - dom-serializer: 0.2.2 - domelementtype: 1.3.1 - domutils@3.2.2: dependencies: dom-serializer: 2.0.0 @@ -12536,7 +12337,7 @@ snapshots: duck@0.1.12: dependencies: - underscore: 1.13.7 + underscore: 1.13.8 dunder-proto@1.0.1: dependencies: @@ -12661,17 +12462,6 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.0 - ent@2.2.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - punycode: 1.4.1 - safe-regex-test: 1.1.0 - - entities@1.1.2: {} - - entities@2.2.0: {} - entities@4.5.0: {} entities@6.0.1: {} @@ -12684,12 +12474,6 @@ snapshots: dependencies: is-arrayish: 0.2.1 - error@4.4.0: - dependencies: - camelize: 1.0.1 - string-template: 0.2.1 - xtend: 4.0.2 - es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -12707,8 +12491,6 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 - es-toolkit@1.45.1: {} - es6-error@4.1.1: optional: true @@ -12873,10 +12655,6 @@ snapshots: etag@1.8.1: {} - ev-store@7.0.0: - dependencies: - individual: 3.0.0 - event-target-shim@5.0.1: {} eventemitter3@5.0.1: {} @@ -13320,11 +13098,6 @@ snapshots: dependencies: ini: 2.0.0 - global@4.4.0: - dependencies: - min-document: 2.19.2 - process: 0.11.10 - globals@14.0.0: {} globals@16.5.0: {} @@ -13571,44 +13344,10 @@ snapshots: hsl-to-rgb-for-reals@1.1.1: {} - html-entities@2.6.0: {} - - html-to-docx@1.8.0(encoding@0.1.13): - dependencies: - '@oozcitak/dom': 1.15.6 - '@oozcitak/util': 8.3.4 - color-name: 1.1.4 - html-entities: 2.6.0 - html-to-vdom: 0.7.0 - image-size: 1.2.1 - image-to-base64: 2.2.0(encoding@0.1.13) - jszip: 3.10.1 - lodash: 4.17.21 - mime-types: 2.1.35 - nanoid: 3.3.11 - virtual-dom: 2.1.1 - xmlbuilder2: 2.1.2 - transitivePeerDependencies: - - encoding - - html-to-vdom@0.7.0: - dependencies: - ent: 2.2.2 - htmlparser2: 3.10.1 - html-url-attributes@3.0.1: {} html-void-elements@3.0.0: {} - htmlparser2@3.10.1: - dependencies: - domelementtype: 1.3.1 - domhandler: 2.4.2 - domutils: 1.7.0 - entities: 1.1.2 - inherits: 2.0.4 - readable-stream: 3.6.2 - http-cache-semantics@4.2.0: {} http-errors@2.0.1: @@ -13673,22 +13412,8 @@ snapshots: image-size@0.7.5: optional: true - image-size@1.2.1: - dependencies: - queue: 6.0.2 - - image-to-base64@2.2.0(encoding@0.1.13): - dependencies: - node-fetch: 2.7.0(encoding@0.1.13) - transitivePeerDependencies: - - encoding - immediate@3.0.6: {} - immer@10.2.0: {} - - immer@11.1.4: {} - import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -13701,8 +13426,6 @@ snapshots: indent-string@4.0.0: {} - individual@3.0.0: {} - infer-owner@1.0.4: {} inflight@1.0.6: @@ -13777,8 +13500,6 @@ snapshots: is-number@7.0.0: {} - is-object@1.0.2: {} - is-plain-obj@4.1.0: {} is-promise@4.0.0: {} @@ -13786,13 +13507,6 @@ snapshots: is-property@1.0.2: optional: true - is-regex@1.2.1: - dependencies: - call-bound: 1.0.4 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - is-stream@1.1.0: {} is-stream@2.0.1: {} @@ -14078,7 +13792,7 @@ snapshots: dependencies: duck: 0.1.12 option: 0.2.4 - underscore: 1.13.7 + underscore: 1.13.8 lower-case@2.0.2: dependencies: @@ -14147,7 +13861,7 @@ snapshots: jszip: 3.10.1 lop: 0.4.2 path-is-absolute: 1.0.1 - underscore: 1.13.7 + underscore: 1.13.8 xmlbuilder: 10.1.1 map-age-cleaner@0.1.3: @@ -14638,10 +14352,6 @@ snapshots: mimic-response@3.1.0: {} - min-document@2.19.2: - dependencies: - dom-walk: 0.1.2 - minimatch@10.1.1: dependencies: '@isaacs/brace-expansion': 5.0.0 @@ -14757,8 +14467,6 @@ snapshots: neo-async@2.6.2: {} - next-tick@0.2.2: {} - nice-try@1.0.5: {} no-case@3.0.4: @@ -15247,8 +14955,6 @@ snapshots: punycode.js@2.3.1: {} - punycode@1.4.1: {} - punycode@2.3.1: {} pusher-js@8.4.0: @@ -15358,15 +15064,6 @@ snapshots: react-is@16.13.1: {} - react-redux@9.2.0(@types/react@19.2.7)(react@19.2.3)(redux@5.0.1): - dependencies: - '@types/use-sync-external-store': 0.0.6 - react: 19.2.3 - use-sync-external-store: 1.6.0(react@19.2.3) - optionalDependencies: - '@types/react': 19.2.7 - redux: 5.0.1 - react-refresh@0.18.0: {} react-remove-scroll-bar@2.3.8(@types/react@19.2.7)(react@19.2.3): @@ -15441,36 +15138,10 @@ snapshots: readdirp@4.1.2: {} - recharts@3.8.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react-is@16.13.1)(react@19.2.3)(redux@5.0.1): - dependencies: - '@reduxjs/toolkit': 2.11.2(react-redux@9.2.0(@types/react@19.2.7)(react@19.2.3)(redux@5.0.1))(react@19.2.3) - clsx: 2.1.1 - decimal.js-light: 2.5.1 - es-toolkit: 1.45.1 - eventemitter3: 5.0.1 - immer: 10.2.0 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - react-is: 16.13.1 - react-redux: 9.2.0(@types/react@19.2.7)(react@19.2.3)(redux@5.0.1) - reselect: 5.1.1 - tiny-invariant: 1.3.3 - use-sync-external-store: 1.6.0(react@19.2.3) - victory-vendor: 37.3.6 - transitivePeerDependencies: - - '@types/react' - - redux - rechoir@0.8.0: dependencies: resolve: 1.22.11 - redux-thunk@3.1.0(redux@5.0.1): - dependencies: - redux: 5.0.1 - - redux@5.0.1: {} - regex-recursion@6.0.2: dependencies: regex-utilities: 2.3.0 @@ -15577,8 +15248,6 @@ snapshots: dependencies: pe-library: 1.0.1 - reselect@5.1.1: {} - resolve-alpn@1.2.1: {} resolve-from@4.0.0: {} @@ -15697,12 +15366,6 @@ snapshots: safe-buffer@5.2.1: {} - safe-regex-test@1.1.0: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-regex: 1.2.1 - safer-buffer@2.1.2: {} scheduler@0.25.0-rc-603e6108-20241029: {} @@ -15953,8 +15616,6 @@ snapshots: - micromark-util-types - supports-color - string-template@0.2.1: {} - string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -16072,8 +15733,6 @@ snapshots: tiny-inflate@1.0.3: {} - tiny-invariant@1.3.3: {} - tinyexec@1.0.2: {} tinyglobby@0.2.15: @@ -16194,7 +15853,7 @@ snapshots: ufo@1.6.1: {} - underscore@1.13.7: {} + underscore@1.13.8: {} undici-types@6.21.0: {} @@ -16345,34 +16004,6 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - victory-vendor@37.3.6: - dependencies: - '@types/d3-array': 3.2.2 - '@types/d3-ease': 3.0.2 - '@types/d3-interpolate': 3.0.4 - '@types/d3-scale': 4.0.9 - '@types/d3-shape': 3.1.7 - '@types/d3-time': 3.0.4 - '@types/d3-timer': 3.0.2 - d3-array: 3.2.4 - d3-ease: 3.0.1 - d3-interpolate: 3.0.1 - d3-scale: 4.0.2 - d3-shape: 3.2.0 - d3-time: 3.1.0 - d3-timer: 3.0.1 - - virtual-dom@2.1.1: - dependencies: - browser-split: 0.0.1 - error: 4.4.0 - ev-store: 7.0.0 - global: 4.4.0 - is-object: 1.0.2 - next-tick: 0.2.2 - x-is-array: 0.1.0 - x-is-string: 0.1.0 - vite-compatible-readable-stream@3.6.1: dependencies: inherits: 2.0.4 @@ -16524,10 +16155,6 @@ snapshots: wrappy@1.0.2: {} - x-is-array@0.1.0: {} - - x-is-string@0.1.0: {} - xlsx@0.18.5: dependencies: adler-32: 1.3.1 @@ -16538,17 +16165,12 @@ snapshots: wmf: 1.0.2 word: 0.3.0 - xmlbuilder2@2.1.2: - dependencies: - '@oozcitak/dom': 1.15.5 - '@oozcitak/infra': 1.0.5 - '@oozcitak/util': 8.3.3 - xmlbuilder@10.1.1: {} xmlbuilder@15.1.1: {} - xtend@4.0.2: {} + xtend@4.0.2: + optional: true y18n@5.0.8: {}