From 7706be0fe54143b8638401c0db7df1e35027faf1 Mon Sep 17 00:00:00 2001
From: 0xbeam
Date: Mon, 16 Mar 2026 11:32:40 +0530
Subject: [PATCH 01/27] =?UTF-8?q?feat:=20Gravity=20v2=20=E2=80=94=20live?=
=?UTF-8?q?=20Figma-connected=20DesignOps=20dashboard?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Express + SQLite backend with Figma API sync engine
- Session reconstruction from version history (30-min gap clustering)
- 6 tabs: Overview, Designers, Projects, Design System, Reviews, Activity
- Designer analytics, project intelligence, component health scoring
- Comment feed with response time metrics
- Cron-based auto-sync (every 5 min) + manual sync button
- Settings panel with Figma PAT/Team config and Release Log
- Last sync time KPI card on overview dashboard
- Vercel deployment support (serverless API + cron)
- Static data.json fallback mode when API unavailable
Co-Authored-By: Claude Opus 4.6
---
miniverse/gravity/.env.example | 9 +
miniverse/gravity/.gitignore | 5 +
miniverse/gravity/api/index.js | 218 ++
miniverse/gravity/data.json | 3241 ++++++++++++++++++++
miniverse/gravity/index.html | 2678 ++++++++++++++++
miniverse/gravity/package-lock.json | 1320 ++++++++
miniverse/gravity/package.json | 19 +
miniverse/gravity/server/db.js | 61 +
miniverse/gravity/server/figma-client.js | 112 +
miniverse/gravity/server/index.js | 424 +++
miniverse/gravity/server/schema.sql | 89 +
miniverse/gravity/server/session-engine.js | 101 +
miniverse/gravity/server/sync-engine.js | 176 ++
miniverse/gravity/vercel.json | 28 +
14 files changed, 8481 insertions(+)
create mode 100644 miniverse/gravity/.env.example
create mode 100644 miniverse/gravity/.gitignore
create mode 100644 miniverse/gravity/api/index.js
create mode 100644 miniverse/gravity/data.json
create mode 100644 miniverse/gravity/index.html
create mode 100644 miniverse/gravity/package-lock.json
create mode 100644 miniverse/gravity/package.json
create mode 100644 miniverse/gravity/server/db.js
create mode 100644 miniverse/gravity/server/figma-client.js
create mode 100644 miniverse/gravity/server/index.js
create mode 100644 miniverse/gravity/server/schema.sql
create mode 100644 miniverse/gravity/server/session-engine.js
create mode 100644 miniverse/gravity/server/sync-engine.js
create mode 100644 miniverse/gravity/vercel.json
diff --git a/miniverse/gravity/.env.example b/miniverse/gravity/.env.example
new file mode 100644
index 0000000..4a948b8
--- /dev/null
+++ b/miniverse/gravity/.env.example
@@ -0,0 +1,9 @@
+# Figma API Configuration
+FIGMA_PAT=your_figma_personal_access_token
+FIGMA_TEAM_ID=your_team_id_here
+
+# Server
+PORT=3847
+
+# Sync interval in minutes (default: 15)
+SYNC_INTERVAL=15
diff --git a/miniverse/gravity/.gitignore b/miniverse/gravity/.gitignore
new file mode 100644
index 0000000..7df938d
--- /dev/null
+++ b/miniverse/gravity/.gitignore
@@ -0,0 +1,5 @@
+node_modules/
+server/gravity.db
+server/gravity.db-shm
+server/gravity.db-wal
+.env
diff --git a/miniverse/gravity/api/index.js b/miniverse/gravity/api/index.js
new file mode 100644
index 0000000..793ee54
--- /dev/null
+++ b/miniverse/gravity/api/index.js
@@ -0,0 +1,218 @@
+import 'dotenv/config';
+import express from 'express';
+import cors from 'cors';
+import { join, dirname } from 'path';
+import { fileURLToPath } from 'url';
+import { getDb, getConfig, setConfig, getLastSync } from '../server/db.js';
+import { setToken, testConnection } from '../server/figma-client.js';
+import { fullSync, isSyncing } from '../server/sync-engine.js';
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const app = express();
+
+app.use(cors());
+app.use(express.json());
+
+// ── Status ──
+app.get('/api/status', (req, res) => {
+ const lastSync = getLastSync();
+ const teamId = getConfig('figma_team_id') || process.env.FIGMA_TEAM_ID || null;
+ const configured = !!(teamId && (getConfig('figma_pat') || process.env.FIGMA_PAT));
+ const db = getDb();
+ const fileCt = db.prepare('SELECT COUNT(*) as n FROM figma_files').get().n;
+ const versionCt = db.prepare('SELECT COUNT(*) as n FROM figma_versions').get().n;
+ const sessionCt = db.prepare('SELECT COUNT(*) as n FROM design_sessions').get().n;
+ const commentCt = db.prepare('SELECT COUNT(*) as n FROM figma_comments').get().n;
+ const componentCt = db.prepare('SELECT COUNT(*) as n FROM figma_components').get().n;
+ res.json({
+ configured,
+ syncing: isSyncing(),
+ lastSync: lastSync ? { completedAt: lastSync.completed_at, durationMs: lastSync.duration_ms, details: JSON.parse(lastSync.details || '{}') } : null,
+ counts: { files: fileCt, versions: versionCt, sessions: sessionCt, comments: commentCt, components: componentCt },
+ });
+});
+
+// ── Config ──
+app.get('/api/config', (req, res) => {
+ res.json({
+ figma_team_id: getConfig('figma_team_id') || process.env.FIGMA_TEAM_ID || '',
+ has_pat: !!(getConfig('figma_pat') || process.env.FIGMA_PAT),
+ sync_interval: getConfig('sync_interval') || process.env.SYNC_INTERVAL || '15',
+ });
+});
+
+app.post('/api/config', async (req, res) => {
+ const { figma_team_id, figma_pat, sync_interval } = req.body;
+ if (figma_team_id) setConfig('figma_team_id', figma_team_id);
+ if (figma_pat) { setConfig('figma_pat', figma_pat); setToken(figma_pat); }
+ if (sync_interval) setConfig('sync_interval', sync_interval);
+ const teamId = figma_team_id || getConfig('figma_team_id') || process.env.FIGMA_TEAM_ID;
+ if (teamId && (figma_pat || getConfig('figma_pat') || process.env.FIGMA_PAT)) {
+ const result = await testConnection(teamId);
+ return res.json({ saved: true, connection: result });
+ }
+ res.json({ saved: true });
+});
+
+// ── Sync ──
+app.post('/api/sync', async (req, res) => {
+ if (isSyncing()) return res.json({ status: 'already_running' });
+ fullSync().catch(err => console.error('[api] Sync error:', err));
+ res.json({ status: 'started' });
+});
+
+// ── Cron endpoint (Vercel Cron) ──
+app.get('/api/cron', async (req, res) => {
+ const authHeader = req.headers.authorization;
+ if (process.env.CRON_SECRET && authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
+ return res.status(401).json({ error: 'Unauthorized' });
+ }
+ if (isSyncing()) return res.json({ status: 'already_running' });
+ try {
+ await fullSync();
+ res.json({ status: 'completed' });
+ } catch (err) {
+ res.status(500).json({ status: 'error', message: err.message });
+ }
+});
+
+// ── Sessions ──
+app.get('/api/sessions', (req, res) => {
+ const { designer, from, to, project, limit = '200' } = req.query;
+ const db = getDb();
+ let sql = 'SELECT * FROM design_sessions WHERE 1=1';
+ const params = [];
+ if (designer) { sql += ' AND designer_name = ?'; params.push(designer); }
+ if (from) { sql += ' AND start_time >= ?'; params.push(from); }
+ if (to) { sql += ' AND end_time <= ?'; params.push(to); }
+ if (project) { sql += ' AND project_name LIKE ?'; params.push(`%${project}%`); }
+ sql += ' ORDER BY start_time DESC LIMIT ?';
+ params.push(parseInt(limit));
+ res.json(db.prepare(sql).all(...params));
+});
+
+app.get('/api/sessions/summary', (req, res) => {
+ const db = getDb();
+ const { period = 'week' } = req.query;
+ const cutoff = period === 'month'
+ ? new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString()
+ : new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
+ const byDesigner = db.prepare(`SELECT designer_name, SUM(duration_minutes) as total_minutes, COUNT(*) as session_count, AVG(duration_minutes) as avg_session_min, COUNT(DISTINCT file_key) as files_touched FROM design_sessions WHERE start_time >= ? GROUP BY designer_name ORDER BY total_minutes DESC`).all(cutoff);
+ const total = db.prepare(`SELECT SUM(duration_minutes) as total_minutes, COUNT(*) as session_count, COUNT(DISTINCT designer_name) as active_designers FROM design_sessions WHERE start_time >= ?`).get(cutoff);
+ const byDay = db.prepare(`SELECT DATE(start_time) as day, SUM(duration_minutes) as minutes, COUNT(*) as sessions FROM design_sessions WHERE start_time >= ? GROUP BY DATE(start_time) ORDER BY day`).all(cutoff);
+ res.json({ period, cutoff, total, byDesigner, byDay });
+});
+
+// ── Designers ──
+app.get('/api/designers', (req, res) => {
+ const db = getDb();
+ const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
+ const monthAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
+ const designers = db.prepare(`SELECT designer_name, designer_id, SUM(duration_minutes) as total_minutes, COUNT(*) as total_sessions, COUNT(DISTINCT file_key) as total_files, COUNT(DISTINCT project_name) as total_projects, MAX(end_time) as last_active, AVG(duration_minutes) as avg_session_min FROM design_sessions GROUP BY designer_name ORDER BY total_minutes DESC`).all();
+ for (const d of designers) {
+ const week = db.prepare(`SELECT SUM(duration_minutes) as week_min FROM design_sessions WHERE designer_name = ? AND start_time >= ?`).get(d.designer_name, weekAgo);
+ d.week_minutes = week?.week_min || 0;
+ const month = db.prepare(`SELECT SUM(duration_minutes) as month_min FROM design_sessions WHERE designer_name = ? AND start_time >= ?`).get(d.designer_name, monthAgo);
+ d.month_minutes = month?.month_min || 0;
+ const days = db.prepare(`SELECT COUNT(DISTINCT DATE(start_time)) as days FROM design_sessions WHERE designer_name = ? AND start_time >= ?`).get(d.designer_name, monthAgo);
+ d.active_days_month = days?.days || 0;
+ }
+ res.json(designers);
+});
+
+app.get('/api/designers/:name/pattern', (req, res) => {
+ const db = getDb();
+ const { name } = req.params;
+ const monthAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
+ const sessions = db.prepare(`SELECT start_time, duration_minutes FROM design_sessions WHERE designer_name = ? AND start_time >= ?`).all(name, monthAgo);
+ const grid = Array.from({ length: 7 }, () => Array(24).fill(0));
+ for (const s of sessions) { const d = new Date(s.start_time); grid[d.getDay()][d.getHours()] += s.duration_minutes; }
+ res.json({ designer: name, period: '30d', grid });
+});
+
+// ── Projects ──
+app.get('/api/projects', (req, res) => {
+ const db = getDb();
+ const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
+ const projects = db.prepare(`SELECT project_name, COUNT(*) as file_count, MAX(last_modified) as last_activity FROM figma_files WHERE project_name IS NOT NULL GROUP BY project_name ORDER BY last_activity DESC`).all();
+ for (const p of projects) {
+ const hours = db.prepare(`SELECT SUM(duration_minutes) as mins FROM design_sessions WHERE project_name = ? AND start_time >= ?`).get(p.project_name, weekAgo);
+ p.week_minutes = hours?.mins || 0;
+ const comments = db.prepare(`SELECT COUNT(*) as n FROM figma_comments c JOIN figma_files f ON c.file_key = f.file_key WHERE f.project_name = ? AND c.resolved_at IS NULL`).get(p.project_name);
+ p.unresolved_comments = comments?.n || 0;
+ }
+ res.json(projects);
+});
+
+// ── Comments ──
+app.get('/api/comments', (req, res) => {
+ const { unresolved, file, limit = '50' } = req.query;
+ const db = getDb();
+ let sql = `SELECT c.*, f.name as file_name, f.project_name FROM figma_comments c LEFT JOIN figma_files f ON c.file_key = f.file_key WHERE 1=1`;
+ const params = [];
+ if (unresolved === 'true') { sql += ' AND c.resolved_at IS NULL'; }
+ if (file) { sql += ' AND c.file_key = ?'; params.push(file); }
+ sql += ' ORDER BY c.created_at DESC LIMIT ?';
+ params.push(parseInt(limit));
+ res.json(db.prepare(sql).all(...params));
+});
+
+app.get('/api/comments/metrics', (req, res) => {
+ const db = getDb();
+ const monthAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
+ const total = db.prepare('SELECT COUNT(*) as n FROM figma_comments WHERE created_at >= ?').get(monthAgo).n;
+ const unresolved = db.prepare('SELECT COUNT(*) as n FROM figma_comments WHERE resolved_at IS NULL AND created_at >= ?').get(monthAgo).n;
+ const avgResolution = db.prepare(`SELECT AVG((julianday(resolved_at) - julianday(created_at)) * 24) as avg_hours FROM figma_comments WHERE resolved_at IS NOT NULL AND created_at >= ?`).get(monthAgo);
+ const byWeek = db.prepare(`SELECT strftime('%Y-W%W', created_at) as week, COUNT(*) as comments FROM figma_comments WHERE created_at >= ? GROUP BY week ORDER BY week`).all(monthAgo);
+ res.json({ total, unresolved, avgResolutionHours: Math.round(avgResolution?.avg_hours || 0), byWeek });
+});
+
+// ── Components ──
+app.get('/api/components', (req, res) => {
+ const db = getDb();
+ const components = db.prepare(`SELECT *, CAST((julianday('now') - julianday(COALESCE(updated_at, created_at))) AS INTEGER) as days_since_update FROM figma_components ORDER BY updated_at DESC`).all();
+ for (const c of components) {
+ const days = c.days_since_update || 999;
+ c.health = days <= 30 ? 'healthy' : days <= 90 ? 'aging' : 'stale';
+ c.health_score = Math.max(0, 100 - Math.floor(days * 0.8));
+ }
+ const total = components.length;
+ const healthy = components.filter(c => c.health === 'healthy').length;
+ const withDesc = components.filter(c => c.description).length;
+ res.json({
+ components,
+ summary: { total, healthy, aging: components.filter(c => c.health === 'aging').length, stale: components.filter(c => c.health === 'stale').length, descriptionCoverage: total ? Math.round((withDesc / total) * 100) : 0, avgHealthScore: total ? Math.round(components.reduce((s, c) => s + c.health_score, 0) / total) : 0 },
+ });
+});
+
+// ── Dashboard ──
+app.get('/api/dashboard', (req, res) => {
+ const db = getDb();
+ const files = db.prepare('SELECT COUNT(*) as n FROM figma_files').get().n;
+ const activeFiles = db.prepare(`SELECT COUNT(*) as n FROM figma_files WHERE last_modified >= datetime('now', '-90 days')`).get().n;
+ const designers = db.prepare('SELECT COUNT(DISTINCT designer_name) as n FROM design_sessions').get().n;
+ const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
+ const weekHours = db.prepare(`SELECT SUM(duration_minutes) as mins FROM design_sessions WHERE start_time >= ?`).get(weekAgo);
+ const activeToday = db.prepare(`SELECT COUNT(DISTINCT designer_name) as n FROM design_sessions WHERE DATE(start_time) = DATE('now')`).get().n;
+ const projects = db.prepare('SELECT COUNT(DISTINCT project_name) as n FROM figma_files WHERE project_name IS NOT NULL').get().n;
+ const timeline = db.prepare(`SELECT strftime('%Y-%m', start_time) as month, SUM(duration_minutes) as minutes, COUNT(*) as sessions FROM design_sessions GROUP BY month ORDER BY month`).all();
+ const recentSessions = db.prepare(`SELECT designer_name, file_name, project_name, start_time, duration_minutes, confidence FROM design_sessions ORDER BY start_time DESC LIMIT 50`).all();
+ res.json({
+ summary: { total_projects: projects, total_files: files, active_files: activeFiles, stale_files: files - activeFiles, total_designers: designers, design_hours_week: Math.round((weekHours?.mins || 0) / 60), active_designers_today: activeToday },
+ timeline,
+ recent_sessions: recentSessions,
+ });
+});
+
+// ── Webhook ──
+app.post('/api/webhook', (req, res) => {
+ const { event_type, file_key, timestamp } = req.body;
+ console.log(`[webhook] ${event_type} for ${file_key} at ${timestamp}`);
+ res.sendStatus(200);
+});
+
+// Initialize Figma token
+const pat = getConfig('figma_pat') || process.env.FIGMA_PAT;
+if (pat) setToken(pat);
+
+export default app;
diff --git a/miniverse/gravity/data.json b/miniverse/gravity/data.json
new file mode 100644
index 0000000..9c6a4f7
--- /dev/null
+++ b/miniverse/gravity/data.json
@@ -0,0 +1,3241 @@
+{
+ "summary": {
+ "total_projects": 176,
+ "total_files": 891,
+ "active_files": 484,
+ "stale_files": 407,
+ "empty_projects": 5,
+ "duplicate_names": {
+ "0xStardust": 2,
+ "Indus App Store": 2,
+ "Swish": 2
+ },
+ "total_designers": 31,
+ "generated_at": "2026-03-12T07:21:34"
+ },
+ "monthly_timeline": [
+ {
+ "month": "2022-10",
+ "files": 1
+ },
+ {
+ "month": "2022-11",
+ "files": 1
+ },
+ {
+ "month": "2022-12",
+ "files": 1
+ },
+ {
+ "month": "2023-01",
+ "files": 2
+ },
+ {
+ "month": "2023-02",
+ "files": 49
+ },
+ {
+ "month": "2023-03",
+ "files": 5
+ },
+ {
+ "month": "2023-04",
+ "files": 1
+ },
+ {
+ "month": "2023-05",
+ "files": 9
+ },
+ {
+ "month": "2023-06",
+ "files": 8
+ },
+ {
+ "month": "2023-07",
+ "files": 3
+ },
+ {
+ "month": "2023-08",
+ "files": 5
+ },
+ {
+ "month": "2023-09",
+ "files": 8
+ },
+ {
+ "month": "2023-10",
+ "files": 19
+ },
+ {
+ "month": "2023-11",
+ "files": 11
+ },
+ {
+ "month": "2023-12",
+ "files": 27
+ },
+ {
+ "month": "2024-01",
+ "files": 21
+ },
+ {
+ "month": "2024-02",
+ "files": 18
+ },
+ {
+ "month": "2024-03",
+ "files": 24
+ },
+ {
+ "month": "2024-04",
+ "files": 23
+ },
+ {
+ "month": "2024-05",
+ "files": 19
+ },
+ {
+ "month": "2024-06",
+ "files": 18
+ },
+ {
+ "month": "2024-07",
+ "files": 36
+ },
+ {
+ "month": "2024-08",
+ "files": 26
+ },
+ {
+ "month": "2024-09",
+ "files": 25
+ },
+ {
+ "month": "2024-10",
+ "files": 18
+ },
+ {
+ "month": "2024-11",
+ "files": 14
+ },
+ {
+ "month": "2024-12",
+ "files": 15
+ },
+ {
+ "month": "2025-01",
+ "files": 18
+ },
+ {
+ "month": "2025-02",
+ "files": 15
+ },
+ {
+ "month": "2025-03",
+ "files": 31
+ },
+ {
+ "month": "2025-04",
+ "files": 28
+ },
+ {
+ "month": "2025-05",
+ "files": 30
+ },
+ {
+ "month": "2025-06",
+ "files": 27
+ },
+ {
+ "month": "2025-07",
+ "files": 71
+ },
+ {
+ "month": "2025-08",
+ "files": 26
+ },
+ {
+ "month": "2025-09",
+ "files": 37
+ },
+ {
+ "month": "2025-10",
+ "files": 41
+ },
+ {
+ "month": "2025-11",
+ "files": 32
+ },
+ {
+ "month": "2025-12",
+ "files": 18
+ },
+ {
+ "month": "2026-01",
+ "files": 45
+ },
+ {
+ "month": "2026-02",
+ "files": 38
+ },
+ {
+ "month": "2026-03",
+ "files": 27
+ }
+ ],
+ "designers": [
+ {
+ "name": "Ashwin",
+ "total_edits": 109,
+ "files_touched": 14,
+ "projects_touched": 11,
+ "monthly": [
+ {
+ "month": "2024-09",
+ "edits": 5
+ },
+ {
+ "month": "2025-07",
+ "edits": 17
+ },
+ {
+ "month": "2025-08",
+ "edits": 6
+ },
+ {
+ "month": "2025-10",
+ "edits": 1
+ },
+ {
+ "month": "2025-11",
+ "edits": 2
+ },
+ {
+ "month": "2026-01",
+ "edits": 9
+ },
+ {
+ "month": "2026-02",
+ "edits": 41
+ },
+ {
+ "month": "2026-03",
+ "edits": 28
+ }
+ ]
+ },
+ {
+ "name": "Navaneeth Venu",
+ "total_edits": 85,
+ "files_touched": 14,
+ "projects_touched": 7,
+ "monthly": [
+ {
+ "month": "2024-02",
+ "edits": 7
+ },
+ {
+ "month": "2024-09",
+ "edits": 3
+ },
+ {
+ "month": "2025-06",
+ "edits": 1
+ },
+ {
+ "month": "2025-07",
+ "edits": 6
+ },
+ {
+ "month": "2025-09",
+ "edits": 8
+ },
+ {
+ "month": "2025-12",
+ "edits": 4
+ },
+ {
+ "month": "2026-02",
+ "edits": 26
+ },
+ {
+ "month": "2026-03",
+ "edits": 30
+ }
+ ]
+ },
+ {
+ "name": "Figma",
+ "total_edits": 66,
+ "files_touched": 24,
+ "projects_touched": 18,
+ "monthly": [
+ {
+ "month": "2024-05",
+ "edits": 1
+ },
+ {
+ "month": "2024-06",
+ "edits": 1
+ },
+ {
+ "month": "2024-07",
+ "edits": 2
+ },
+ {
+ "month": "2024-08",
+ "edits": 1
+ },
+ {
+ "month": "2024-10",
+ "edits": 1
+ },
+ {
+ "month": "2024-11",
+ "edits": 1
+ },
+ {
+ "month": "2024-12",
+ "edits": 1
+ },
+ {
+ "month": "2025-01",
+ "edits": 1
+ },
+ {
+ "month": "2025-03",
+ "edits": 1
+ },
+ {
+ "month": "2025-05",
+ "edits": 1
+ },
+ {
+ "month": "2025-06",
+ "edits": 2
+ },
+ {
+ "month": "2025-07",
+ "edits": 2
+ },
+ {
+ "month": "2025-08",
+ "edits": 2
+ },
+ {
+ "month": "2025-09",
+ "edits": 6
+ },
+ {
+ "month": "2025-10",
+ "edits": 9
+ },
+ {
+ "month": "2025-11",
+ "edits": 2
+ },
+ {
+ "month": "2026-02",
+ "edits": 17
+ },
+ {
+ "month": "2026-03",
+ "edits": 15
+ }
+ ]
+ },
+ {
+ "name": "Boris P",
+ "total_edits": 61,
+ "files_touched": 4,
+ "projects_touched": 3,
+ "monthly": [
+ {
+ "month": "2026-02",
+ "edits": 5
+ },
+ {
+ "month": "2026-03",
+ "edits": 56
+ }
+ ]
+ },
+ {
+ "name": "Gayatri",
+ "total_edits": 59,
+ "files_touched": 12,
+ "projects_touched": 8,
+ "monthly": [
+ {
+ "month": "2025-09",
+ "edits": 4
+ },
+ {
+ "month": "2026-01",
+ "edits": 4
+ },
+ {
+ "month": "2026-02",
+ "edits": 26
+ },
+ {
+ "month": "2026-03",
+ "edits": 25
+ }
+ ]
+ },
+ {
+ "name": "Urja",
+ "total_edits": 55,
+ "files_touched": 9,
+ "projects_touched": 5,
+ "monthly": [
+ {
+ "month": "2025-07",
+ "edits": 2
+ },
+ {
+ "month": "2025-10",
+ "edits": 8
+ },
+ {
+ "month": "2025-11",
+ "edits": 1
+ },
+ {
+ "month": "2025-12",
+ "edits": 2
+ },
+ {
+ "month": "2026-02",
+ "edits": 18
+ },
+ {
+ "month": "2026-03",
+ "edits": 24
+ }
+ ]
+ },
+ {
+ "name": "Hari",
+ "total_edits": 38,
+ "files_touched": 8,
+ "projects_touched": 5,
+ "monthly": [
+ {
+ "month": "2026-01",
+ "edits": 2
+ },
+ {
+ "month": "2026-02",
+ "edits": 15
+ },
+ {
+ "month": "2026-03",
+ "edits": 21
+ }
+ ]
+ },
+ {
+ "name": "Saaket",
+ "total_edits": 26,
+ "files_touched": 2,
+ "projects_touched": 2,
+ "monthly": [
+ {
+ "month": "2026-01",
+ "edits": 3
+ },
+ {
+ "month": "2026-02",
+ "edits": 10
+ },
+ {
+ "month": "2026-03",
+ "edits": 13
+ }
+ ]
+ },
+ {
+ "name": "Shubham Bhardwaj",
+ "total_edits": 25,
+ "files_touched": 10,
+ "projects_touched": 7,
+ "monthly": [
+ {
+ "month": "2024-09",
+ "edits": 3
+ },
+ {
+ "month": "2025-08",
+ "edits": 2
+ },
+ {
+ "month": "2025-10",
+ "edits": 1
+ },
+ {
+ "month": "2025-11",
+ "edits": 4
+ },
+ {
+ "month": "2025-12",
+ "edits": 4
+ },
+ {
+ "month": "2026-01",
+ "edits": 1
+ },
+ {
+ "month": "2026-02",
+ "edits": 5
+ },
+ {
+ "month": "2026-03",
+ "edits": 5
+ }
+ ]
+ },
+ {
+ "name": "Paul Finney",
+ "total_edits": 17,
+ "files_touched": 8,
+ "projects_touched": 6,
+ "monthly": [
+ {
+ "month": "2025-08",
+ "edits": 1
+ },
+ {
+ "month": "2025-11",
+ "edits": 1
+ },
+ {
+ "month": "2026-01",
+ "edits": 2
+ },
+ {
+ "month": "2026-02",
+ "edits": 6
+ },
+ {
+ "month": "2026-03",
+ "edits": 7
+ }
+ ]
+ },
+ {
+ "name": "Kunal",
+ "total_edits": 16,
+ "files_touched": 6,
+ "projects_touched": 6,
+ "monthly": [
+ {
+ "month": "2024-04",
+ "edits": 1
+ },
+ {
+ "month": "2024-09",
+ "edits": 5
+ },
+ {
+ "month": "2025-09",
+ "edits": 1
+ },
+ {
+ "month": "2025-10",
+ "edits": 2
+ },
+ {
+ "month": "2025-11",
+ "edits": 5
+ },
+ {
+ "month": "2025-12",
+ "edits": 2
+ }
+ ]
+ },
+ {
+ "name": "Neel",
+ "total_edits": 10,
+ "files_touched": 4,
+ "projects_touched": 4,
+ "monthly": [
+ {
+ "month": "2026-02",
+ "edits": 4
+ },
+ {
+ "month": "2026-03",
+ "edits": 6
+ }
+ ]
+ },
+ {
+ "name": "Achyut Saxena",
+ "total_edits": 9,
+ "files_touched": 3,
+ "projects_touched": 2,
+ "monthly": [
+ {
+ "month": "2026-02",
+ "edits": 6
+ },
+ {
+ "month": "2026-03",
+ "edits": 3
+ }
+ ]
+ },
+ {
+ "name": "Disha",
+ "total_edits": 8,
+ "files_touched": 3,
+ "projects_touched": 3,
+ "monthly": [
+ {
+ "month": "2025-10",
+ "edits": 6
+ },
+ {
+ "month": "2026-02",
+ "edits": 2
+ }
+ ]
+ },
+ {
+ "name": "Himanshu",
+ "total_edits": 7,
+ "files_touched": 1,
+ "projects_touched": 1,
+ "monthly": [
+ {
+ "month": "2026-03",
+ "edits": 7
+ }
+ ]
+ },
+ {
+ "name": "param",
+ "total_edits": 4,
+ "files_touched": 3,
+ "projects_touched": 3,
+ "monthly": [
+ {
+ "month": "2024-04",
+ "edits": 2
+ },
+ {
+ "month": "2025-10",
+ "edits": 1
+ },
+ {
+ "month": "2025-11",
+ "edits": 1
+ }
+ ]
+ },
+ {
+ "name": "Monika R",
+ "total_edits": 4,
+ "files_touched": 1,
+ "projects_touched": 1,
+ "monthly": [
+ {
+ "month": "2026-02",
+ "edits": 4
+ }
+ ]
+ },
+ {
+ "name": "Guest User",
+ "total_edits": 3,
+ "files_touched": 1,
+ "projects_touched": 1,
+ "monthly": [
+ {
+ "month": "2026-02",
+ "edits": 3
+ }
+ ]
+ },
+ {
+ "name": "farhan",
+ "total_edits": 3,
+ "files_touched": 2,
+ "projects_touched": 2,
+ "monthly": [
+ {
+ "month": "2025-12",
+ "edits": 3
+ }
+ ]
+ },
+ {
+ "name": "Sumit Yadav",
+ "total_edits": 2,
+ "files_touched": 1,
+ "projects_touched": 1,
+ "monthly": [
+ {
+ "month": "2026-03",
+ "edits": 2
+ }
+ ]
+ },
+ {
+ "name": "Aditi Mittal",
+ "total_edits": 2,
+ "files_touched": 2,
+ "projects_touched": 1,
+ "monthly": [
+ {
+ "month": "2026-03",
+ "edits": 2
+ }
+ ]
+ },
+ {
+ "name": "Armaan Dhanda",
+ "total_edits": 2,
+ "files_touched": 1,
+ "projects_touched": 1,
+ "monthly": [
+ {
+ "month": "2026-03",
+ "edits": 2
+ }
+ ]
+ },
+ {
+ "name": "Ayan Malik",
+ "total_edits": 1,
+ "files_touched": 1,
+ "projects_touched": 1,
+ "monthly": [
+ {
+ "month": "2026-03",
+ "edits": 1
+ }
+ ]
+ },
+ {
+ "name": "Prayul",
+ "total_edits": 1,
+ "files_touched": 1,
+ "projects_touched": 1,
+ "monthly": [
+ {
+ "month": "2024-08",
+ "edits": 1
+ }
+ ]
+ },
+ {
+ "name": "Anchal Chauhan",
+ "total_edits": 1,
+ "files_touched": 1,
+ "projects_touched": 1,
+ "monthly": [
+ {
+ "month": "2024-02",
+ "edits": 1
+ }
+ ]
+ },
+ {
+ "name": "Uday Somani",
+ "total_edits": 1,
+ "files_touched": 1,
+ "projects_touched": 1,
+ "monthly": [
+ {
+ "month": "2026-03",
+ "edits": 1
+ }
+ ]
+ },
+ {
+ "name": "Samyak Baid",
+ "total_edits": 1,
+ "files_touched": 1,
+ "projects_touched": 1,
+ "monthly": [
+ {
+ "month": "2026-02",
+ "edits": 1
+ }
+ ]
+ },
+ {
+ "name": "archana valecha",
+ "total_edits": 1,
+ "files_touched": 1,
+ "projects_touched": 1,
+ "monthly": [
+ {
+ "month": "2026-02",
+ "edits": 1
+ }
+ ]
+ },
+ {
+ "name": "Pankhudi",
+ "total_edits": 1,
+ "files_touched": 1,
+ "projects_touched": 1,
+ "monthly": [
+ {
+ "month": "2025-06",
+ "edits": 1
+ }
+ ]
+ },
+ {
+ "name": "Sumedha Uppal",
+ "total_edits": 1,
+ "files_touched": 1,
+ "projects_touched": 1,
+ "monthly": [
+ {
+ "month": "2026-02",
+ "edits": 1
+ }
+ ]
+ },
+ {
+ "name": "varun",
+ "total_edits": 1,
+ "files_touched": 1,
+ "projects_touched": 1,
+ "monthly": [
+ {
+ "month": "2026-02",
+ "edits": 1
+ }
+ ]
+ }
+ ],
+ "projects": [
+ {
+ "name": "Siren by Bureau ID",
+ "id": "410485498",
+ "file_count": 20,
+ "category": "Client",
+ "last_activity": "2026-03-11",
+ "oldest_file": "2025-07-05",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "Stylumia",
+ "id": "381613913",
+ "file_count": 16,
+ "category": "Client",
+ "last_activity": "2026-03-10",
+ "oldest_file": "2025-05-16",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "Bench",
+ "id": "374014905",
+ "file_count": 14,
+ "category": "Client",
+ "last_activity": "2026-03-10",
+ "oldest_file": "2025-04-25",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "Blockchain For Impact",
+ "id": "391182879",
+ "file_count": 12,
+ "category": "Client",
+ "last_activity": "2026-01-22",
+ "oldest_file": "2025-06-10",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "Profound",
+ "id": "424036654",
+ "file_count": 11,
+ "category": "Client",
+ "last_activity": "2026-01-16",
+ "oldest_file": "2025-11-17",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "Vanagon",
+ "id": "456673855",
+ "file_count": 8,
+ "category": "Client",
+ "last_activity": "2026-02-13",
+ "oldest_file": "2025-10-16",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "Qbeast",
+ "id": "359132810",
+ "file_count": 7,
+ "category": "Client",
+ "last_activity": "2026-02-26",
+ "oldest_file": "2025-04-28",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "Unconf '25",
+ "id": "431910119",
+ "file_count": 6,
+ "category": "Client",
+ "last_activity": "2026-01-13",
+ "oldest_file": "2025-08-20",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "M\u00f6bius Industries",
+ "id": "344515273",
+ "file_count": 5,
+ "category": "Client",
+ "last_activity": "2026-02-16",
+ "oldest_file": "2025-08-06",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "Sanctuary",
+ "id": "443438069",
+ "file_count": 5,
+ "category": "Client",
+ "last_activity": "2026-03-11",
+ "oldest_file": "2025-09-01",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "Sybilion",
+ "id": "428183761",
+ "file_count": 4,
+ "category": "Client",
+ "last_activity": "2026-01-16",
+ "oldest_file": "2025-10-28",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "LoopID",
+ "id": "508506010",
+ "file_count": 3,
+ "category": "Client",
+ "last_activity": "2026-02-23",
+ "oldest_file": "2025-12-22",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "Pika First Edition",
+ "id": "569927088",
+ "file_count": 2,
+ "category": "Client",
+ "last_activity": "2026-03-10",
+ "oldest_file": "2026-03-10",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "Sanctuary In Situ",
+ "id": "466668912",
+ "file_count": 2,
+ "category": "Client",
+ "last_activity": "2026-03-03",
+ "oldest_file": "2026-01-09",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "Sarvam AI",
+ "id": "537133618",
+ "file_count": 2,
+ "category": "Client",
+ "last_activity": "2026-02-26",
+ "oldest_file": "2026-02-26",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "Cascade",
+ "id": "555566632",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2026-02-18",
+ "oldest_file": "2026-02-18",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "Video Department Internal",
+ "id": "396076170",
+ "file_count": 1,
+ "category": "Marketing",
+ "last_activity": "2026-03-10",
+ "oldest_file": "2026-03-10",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "1st Edition",
+ "id": "567736611",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2026-03-06",
+ "oldest_file": "2026-03-06",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "Acre Robotics",
+ "id": "411035610",
+ "file_count": 2,
+ "category": "Client",
+ "last_activity": "2026-02-27",
+ "oldest_file": "2025-07-07",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "Anomaly",
+ "id": "530618362",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2026-03-03",
+ "oldest_file": "2026-03-03",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "Assurekit",
+ "id": "437770498",
+ "file_count": 14,
+ "category": "Client",
+ "last_activity": "2026-03-11",
+ "oldest_file": "2025-09-08",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "Attn co",
+ "id": "565041423",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2026-03-11",
+ "oldest_file": "2026-03-11",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "Deepflow",
+ "id": "416920455",
+ "file_count": 12,
+ "category": "Client",
+ "last_activity": "2026-02-19",
+ "oldest_file": "2025-07-17",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "Dental Agent",
+ "id": "412662820",
+ "file_count": 9,
+ "category": "Client",
+ "last_activity": "2026-01-30",
+ "oldest_file": "2025-07-15",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "Ewigbyte",
+ "id": "497193920",
+ "file_count": 4,
+ "category": "Client",
+ "last_activity": "2026-02-27",
+ "oldest_file": "2025-12-08",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "External Video Projects",
+ "id": "427746335",
+ "file_count": 10,
+ "category": "Marketing",
+ "last_activity": "2026-01-23",
+ "oldest_file": "2025-08-03",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "F-Log Ventures",
+ "id": "545642345",
+ "file_count": 7,
+ "category": "Client",
+ "last_activity": "2026-03-11",
+ "oldest_file": "2026-02-16",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "FanCraze",
+ "id": "325373800",
+ "file_count": 17,
+ "category": "Client",
+ "last_activity": "2026-02-23",
+ "oldest_file": "2025-01-31",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "Forj Capital",
+ "id": "405074388",
+ "file_count": 12,
+ "category": "Client",
+ "last_activity": "2026-02-16",
+ "oldest_file": "2025-06-24",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "Jupiter",
+ "id": "309844166",
+ "file_count": 26,
+ "category": "Client",
+ "last_activity": "2026-01-07",
+ "oldest_file": "2025-01-23",
+ "stale_files": 0,
+ "health_score": 100
+ },
+ {
+ "name": "Spacekayak Business",
+ "id": "241864269",
+ "file_count": 13,
+ "category": "Internal",
+ "last_activity": "2026-03-03",
+ "oldest_file": "2024-09-13",
+ "stale_files": 3,
+ "health_score": 91
+ },
+ {
+ "name": "Nucast",
+ "id": "184469815",
+ "file_count": 3,
+ "category": "Client",
+ "last_activity": "2026-02-23",
+ "oldest_file": "2024-06-20",
+ "stale_files": 1,
+ "health_score": 87
+ },
+ {
+ "name": "Kast Finance",
+ "id": "265268617",
+ "file_count": 6,
+ "category": "Client",
+ "last_activity": "2025-07-31",
+ "oldest_file": "2025-04-14",
+ "stale_files": 0,
+ "health_score": 85
+ },
+ {
+ "name": "Sentient",
+ "id": "354426095",
+ "file_count": 6,
+ "category": "Client",
+ "last_activity": "2025-12-17",
+ "oldest_file": "2025-03-19",
+ "stale_files": 0,
+ "health_score": 85
+ },
+ {
+ "name": "Social Posts",
+ "id": "466545942",
+ "file_count": 3,
+ "category": "Marketing",
+ "last_activity": "2025-10-13",
+ "oldest_file": "2025-10-08",
+ "stale_files": 0,
+ "health_score": 85
+ },
+ {
+ "name": "Azuki India",
+ "id": "319966884",
+ "file_count": 2,
+ "category": "Client",
+ "last_activity": "2025-09-30",
+ "oldest_file": "2025-05-15",
+ "stale_files": 0,
+ "health_score": 85
+ },
+ {
+ "name": "Bridgetown Research",
+ "id": "313208463",
+ "file_count": 2,
+ "category": "Client",
+ "last_activity": "2025-12-09",
+ "oldest_file": "2025-01-26",
+ "stale_files": 0,
+ "health_score": 85
+ },
+ {
+ "name": "Pengu India",
+ "id": "326370844",
+ "file_count": 2,
+ "category": "Client",
+ "last_activity": "2025-09-12",
+ "oldest_file": "2025-01-17",
+ "stale_files": 0,
+ "health_score": 85
+ },
+ {
+ "name": "Plum",
+ "id": "410609297",
+ "file_count": 2,
+ "category": "Client",
+ "last_activity": "2025-09-12",
+ "oldest_file": "2025-08-27",
+ "stale_files": 0,
+ "health_score": 85
+ },
+ {
+ "name": "Sanctum",
+ "id": "360683167",
+ "file_count": 2,
+ "category": "Client",
+ "last_activity": "2025-10-29",
+ "oldest_file": "2025-09-30",
+ "stale_files": 0,
+ "health_score": 85
+ },
+ {
+ "name": "SpaceKulture",
+ "id": "340142712",
+ "file_count": 2,
+ "category": "Merch",
+ "last_activity": "2025-10-22",
+ "oldest_file": "2025-02-26",
+ "stale_files": 0,
+ "health_score": 85
+ },
+ {
+ "name": "Blume",
+ "id": "394816103",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2025-11-11",
+ "oldest_file": "2025-11-11",
+ "stale_files": 0,
+ "health_score": 85
+ },
+ {
+ "name": "Cursor India",
+ "id": "505413297",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2025-12-05",
+ "oldest_file": "2025-12-05",
+ "stale_files": 0,
+ "health_score": 85
+ },
+ {
+ "name": "Kairos",
+ "id": "416932370",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2025-07-23",
+ "oldest_file": "2025-07-23",
+ "stale_files": 0,
+ "health_score": 85
+ },
+ {
+ "name": "attntion",
+ "id": "492320946",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2025-11-05",
+ "oldest_file": "2025-11-05",
+ "stale_files": 0,
+ "health_score": 85
+ },
+ {
+ "name": "Any2Any",
+ "id": "441098043",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2025-09-03",
+ "oldest_file": "2025-09-03",
+ "stale_files": 0,
+ "health_score": 85
+ },
+ {
+ "name": "Arctis AI",
+ "id": "452279999",
+ "file_count": 2,
+ "category": "Client",
+ "last_activity": "2025-09-17",
+ "oldest_file": "2025-09-17",
+ "stale_files": 0,
+ "health_score": 85
+ },
+ {
+ "name": "DeCharge",
+ "id": "244633962",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2025-11-09",
+ "oldest_file": "2025-11-09",
+ "stale_files": 0,
+ "health_score": 85
+ },
+ {
+ "name": "ExoMatter",
+ "id": "447919530",
+ "file_count": 2,
+ "category": "Client",
+ "last_activity": "2025-12-12",
+ "oldest_file": "2025-09-17",
+ "stale_files": 0,
+ "health_score": 85
+ },
+ {
+ "name": "Great Residential Hack",
+ "id": "402929365",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2025-08-01",
+ "oldest_file": "2025-08-01",
+ "stale_files": 0,
+ "health_score": 85
+ },
+ {
+ "name": "Handoff email",
+ "id": "420354536",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2025-10-30",
+ "oldest_file": "2025-10-30",
+ "stale_files": 0,
+ "health_score": 85
+ },
+ {
+ "name": "Holy Tech",
+ "id": "487720448",
+ "file_count": 3,
+ "category": "Client",
+ "last_activity": "2025-12-19",
+ "oldest_file": "2025-11-18",
+ "stale_files": 0,
+ "health_score": 85
+ },
+ {
+ "name": "Goatfish",
+ "id": "267594922",
+ "file_count": 5,
+ "category": "Client",
+ "last_activity": "2026-01-13",
+ "oldest_file": "2024-09-02",
+ "stale_files": 2,
+ "health_score": 84
+ },
+ {
+ "name": "Spacekayak's Website",
+ "id": "91235083",
+ "file_count": 20,
+ "category": "Internal",
+ "last_activity": "2026-03-11",
+ "oldest_file": "2023-06-08",
+ "stale_files": 10,
+ "health_score": 80
+ },
+ {
+ "name": "Superlend prev. PlendFi",
+ "id": "253576071",
+ "file_count": 10,
+ "category": "Client",
+ "last_activity": "2026-03-02",
+ "oldest_file": "2024-09-03",
+ "stale_files": 5,
+ "health_score": 80
+ },
+ {
+ "name": "GPUNet",
+ "id": "240777314",
+ "file_count": 8,
+ "category": "Client",
+ "last_activity": "2025-11-13",
+ "oldest_file": "2024-06-14",
+ "stale_files": 1,
+ "health_score": 80
+ },
+ {
+ "name": "Undeads",
+ "id": "258914797",
+ "file_count": 6,
+ "category": "Client",
+ "last_activity": "2025-11-19",
+ "oldest_file": "2024-09-10",
+ "stale_files": 1,
+ "health_score": 79
+ },
+ {
+ "name": "Arch0",
+ "id": "86752318",
+ "file_count": 6,
+ "category": "Client",
+ "last_activity": "2025-07-03",
+ "oldest_file": "2024-08-02",
+ "stale_files": 1,
+ "health_score": 79
+ },
+ {
+ "name": "Spacekayak Internal Design",
+ "id": "54603226",
+ "file_count": 80,
+ "category": "Internal",
+ "last_activity": "2026-03-11",
+ "oldest_file": "2022-11-17",
+ "stale_files": 24,
+ "health_score": 78
+ },
+ {
+ "name": "Cabbage",
+ "id": "303758234",
+ "file_count": 5,
+ "category": "Client",
+ "last_activity": "2025-07-31",
+ "oldest_file": "2024-12-04",
+ "stale_files": 1,
+ "health_score": 77
+ },
+ {
+ "name": "Superteam Cash Landing",
+ "id": "198973212",
+ "file_count": 6,
+ "category": "Client",
+ "last_activity": "2026-03-07",
+ "oldest_file": "2024-02-20",
+ "stale_files": 4,
+ "health_score": 74
+ },
+ {
+ "name": "Cosmo",
+ "id": "211006143",
+ "file_count": 6,
+ "category": "Client",
+ "last_activity": "2025-07-21",
+ "oldest_file": "2024-07-08",
+ "stale_files": 2,
+ "health_score": 72
+ },
+ {
+ "name": "Voosh",
+ "id": "240877832",
+ "file_count": 3,
+ "category": "Client",
+ "last_activity": "2025-06-30",
+ "oldest_file": "2024-12-04",
+ "stale_files": 1,
+ "health_score": 72
+ },
+ {
+ "name": "Composio",
+ "id": "321708882",
+ "file_count": 3,
+ "category": "Client",
+ "last_activity": "2025-05-02",
+ "oldest_file": "2025-01-07",
+ "stale_files": 0,
+ "health_score": 70
+ },
+ {
+ "name": "Audi India",
+ "id": "350428763",
+ "file_count": 2,
+ "category": "Client",
+ "last_activity": "2025-03-11",
+ "oldest_file": "2025-03-11",
+ "stale_files": 0,
+ "health_score": 70
+ },
+ {
+ "name": "Local Ferment Co.",
+ "id": "362317940",
+ "file_count": 2,
+ "category": "Client",
+ "last_activity": "2025-04-07",
+ "oldest_file": "2025-04-04",
+ "stale_files": 0,
+ "health_score": 70
+ },
+ {
+ "name": "PropVR",
+ "id": "347656006",
+ "file_count": 2,
+ "category": "Client",
+ "last_activity": "2025-05-19",
+ "oldest_file": "2025-03-12",
+ "stale_files": 0,
+ "health_score": 70
+ },
+ {
+ "name": "Wello",
+ "id": "375582444",
+ "file_count": 2,
+ "category": "Client",
+ "last_activity": "2025-05-22",
+ "oldest_file": "2025-05-06",
+ "stale_files": 0,
+ "health_score": 70
+ },
+ {
+ "name": "Blocklive",
+ "id": "348862316",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2025-03-24",
+ "oldest_file": "2025-03-24",
+ "stale_files": 0,
+ "health_score": 70
+ },
+ {
+ "name": "Misc",
+ "id": "379696010",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2025-05-27",
+ "oldest_file": "2025-05-27",
+ "stale_files": 0,
+ "health_score": 70
+ },
+ {
+ "name": "Paul Tuition",
+ "id": "347277770",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2025-03-04",
+ "oldest_file": "2025-03-04",
+ "stale_files": 0,
+ "health_score": 70
+ },
+ {
+ "name": "Shipyard",
+ "id": "254118809",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2025-05-15",
+ "oldest_file": "2025-05-15",
+ "stale_files": 0,
+ "health_score": 70
+ },
+ {
+ "name": "Solana Jam",
+ "id": "264106321",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2025-02-26",
+ "oldest_file": "2025-02-26",
+ "stale_files": 0,
+ "health_score": 70
+ },
+ {
+ "name": "StakeKit",
+ "id": "363220088",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2025-04-07",
+ "oldest_file": "2025-04-07",
+ "stale_files": 0,
+ "health_score": 70
+ },
+ {
+ "name": "SuperTeam Singapore",
+ "id": "379833277",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2025-05-07",
+ "oldest_file": "2025-05-07",
+ "stale_files": 0,
+ "health_score": 70
+ },
+ {
+ "name": "Utopia",
+ "id": "360190177",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2025-05-14",
+ "oldest_file": "2025-05-14",
+ "stale_files": 0,
+ "health_score": 70
+ },
+ {
+ "name": "Drip Haus & Rally Wallet",
+ "id": "282864749",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2025-03-07",
+ "oldest_file": "2025-03-07",
+ "stale_files": 0,
+ "health_score": 70
+ },
+ {
+ "name": "Emergent Labs",
+ "id": "380254528",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2025-05-22",
+ "oldest_file": "2025-05-22",
+ "stale_files": 0,
+ "health_score": 70
+ },
+ {
+ "name": "FUZE",
+ "id": "364858463",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2025-04-23",
+ "oldest_file": "2025-04-23",
+ "stale_files": 0,
+ "health_score": 70
+ },
+ {
+ "name": "Figma Practice Project",
+ "id": "372232329",
+ "file_count": 2,
+ "category": "Learning",
+ "last_activity": "2025-05-08",
+ "oldest_file": "2025-04-22",
+ "stale_files": 0,
+ "health_score": 70
+ },
+ {
+ "name": "Stackr",
+ "id": "199723297",
+ "file_count": 14,
+ "category": "Client",
+ "last_activity": "2026-02-23",
+ "oldest_file": "2024-02-09",
+ "stale_files": 11,
+ "health_score": 69
+ },
+ {
+ "name": "0xStardust",
+ "id": "79747312",
+ "file_count": 12,
+ "category": "Client",
+ "last_activity": "2026-02-02",
+ "oldest_file": "2023-02-16",
+ "stale_files": 10,
+ "health_score": 67
+ },
+ {
+ "name": "Project Case Studies & Social Media",
+ "id": "89837419",
+ "file_count": 8,
+ "category": "Marketing",
+ "last_activity": "2025-10-28",
+ "oldest_file": "2023-05-04",
+ "stale_files": 4,
+ "health_score": 65
+ },
+ {
+ "name": "ONDC Antler",
+ "id": "100796662",
+ "file_count": 2,
+ "category": "Client",
+ "last_activity": "2025-07-17",
+ "oldest_file": "2023-07-26",
+ "stale_files": 1,
+ "health_score": 65
+ },
+ {
+ "name": "Socket Surge",
+ "id": "92028500",
+ "file_count": 2,
+ "category": "Client",
+ "last_activity": "2025-07-23",
+ "oldest_file": "2024-01-16",
+ "stale_files": 1,
+ "health_score": 65
+ },
+ {
+ "name": "Swish",
+ "id": "263622329",
+ "file_count": 2,
+ "category": "Client",
+ "last_activity": "2025-07-17",
+ "oldest_file": "2024-09-12",
+ "stale_files": 1,
+ "health_score": 65
+ },
+ {
+ "name": "Honestly",
+ "id": "205410782",
+ "file_count": 4,
+ "category": "Client",
+ "last_activity": "2025-07-05",
+ "oldest_file": "2024-02-20",
+ "stale_files": 2,
+ "health_score": 65
+ },
+ {
+ "name": "Instadapp",
+ "id": "198051326",
+ "file_count": 4,
+ "category": "Client",
+ "last_activity": "2025-07-14",
+ "oldest_file": "2024-03-15",
+ "stale_files": 2,
+ "health_score": 65
+ },
+ {
+ "name": "House Of Models",
+ "id": "108933326",
+ "file_count": 5,
+ "category": "Client",
+ "last_activity": "2025-05-02",
+ "oldest_file": "2023-12-07",
+ "stale_files": 1,
+ "health_score": 62
+ },
+ {
+ "name": "Sending Network",
+ "id": "175163738",
+ "file_count": 9,
+ "category": "Client",
+ "last_activity": "2025-07-23",
+ "oldest_file": "2023-12-19",
+ "stale_files": 6,
+ "health_score": 59
+ },
+ {
+ "name": "UMA",
+ "id": "101198847",
+ "file_count": 3,
+ "category": "Client",
+ "last_activity": "2025-07-23",
+ "oldest_file": "2023-09-13",
+ "stale_files": 2,
+ "health_score": 59
+ },
+ {
+ "name": "AOH",
+ "id": "227391028",
+ "file_count": 3,
+ "category": "Client",
+ "last_activity": "2025-10-10",
+ "oldest_file": "2024-04-30",
+ "stale_files": 2,
+ "health_score": 59
+ },
+ {
+ "name": "FanTV",
+ "id": "225305867",
+ "file_count": 9,
+ "category": "Client",
+ "last_activity": "2025-11-12",
+ "oldest_file": "2024-04-22",
+ "stale_files": 6,
+ "health_score": 59
+ },
+ {
+ "name": "TRS 2.0",
+ "id": "75804960",
+ "file_count": 4,
+ "category": "Client",
+ "last_activity": "2025-06-03",
+ "oldest_file": "2023-02-16",
+ "stale_files": 3,
+ "health_score": 55
+ },
+ {
+ "name": "Hexon Global",
+ "id": "176704098",
+ "file_count": 4,
+ "category": "Client",
+ "last_activity": "2025-06-30",
+ "oldest_file": "2024-01-04",
+ "stale_files": 3,
+ "health_score": 55
+ },
+ {
+ "name": "Polygon Spacekayak",
+ "id": "106211311",
+ "file_count": 34,
+ "category": "Internal",
+ "last_activity": "2025-07-22",
+ "oldest_file": "2023-12-05",
+ "stale_files": 18,
+ "health_score": 54
+ },
+ {
+ "name": "Obvious Wallet",
+ "id": "61251026",
+ "file_count": 11,
+ "category": "Client",
+ "last_activity": "2025-06-20",
+ "oldest_file": "2023-02-16",
+ "stale_files": 9,
+ "health_score": 53
+ },
+ {
+ "name": "Silicon Halli",
+ "id": "285348523",
+ "file_count": 11,
+ "category": "Client",
+ "last_activity": "2025-11-12",
+ "oldest_file": "2024-10-23",
+ "stale_files": 9,
+ "health_score": 53
+ },
+ {
+ "name": "Itheum",
+ "id": "105852757",
+ "file_count": 10,
+ "category": "Client",
+ "last_activity": "2025-07-31",
+ "oldest_file": "2023-12-05",
+ "stale_files": 8,
+ "health_score": 53
+ },
+ {
+ "name": "Market",
+ "id": "53425086",
+ "file_count": 13,
+ "category": "Client",
+ "last_activity": "2025-07-31",
+ "oldest_file": "2022-12-16",
+ "stale_files": 11,
+ "health_score": 52
+ },
+ {
+ "name": "Pillow",
+ "id": "59920562",
+ "file_count": 13,
+ "category": "Client",
+ "last_activity": "2025-10-27",
+ "oldest_file": "2023-02-16",
+ "stale_files": 11,
+ "health_score": 52
+ },
+ {
+ "name": "Okto",
+ "id": "169696194",
+ "file_count": 6,
+ "category": "Client",
+ "last_activity": "2025-07-23",
+ "oldest_file": "2024-01-19",
+ "stale_files": 5,
+ "health_score": 52
+ },
+ {
+ "name": "SocialScan",
+ "id": "108332657",
+ "file_count": 6,
+ "category": "Marketing",
+ "last_activity": "2025-07-23",
+ "oldest_file": "2023-11-16",
+ "stale_files": 5,
+ "health_score": 52
+ },
+ {
+ "name": "Truffles",
+ "id": "76270018",
+ "file_count": 6,
+ "category": "Client",
+ "last_activity": "2025-07-23",
+ "oldest_file": "2023-02-16",
+ "stale_files": 5,
+ "health_score": 52
+ },
+ {
+ "name": "WeFi (prev. Paxo)",
+ "id": "85106080",
+ "file_count": 9,
+ "category": "Client",
+ "last_activity": "2025-07-14",
+ "oldest_file": "2023-06-27",
+ "stale_files": 8,
+ "health_score": 50
+ },
+ {
+ "name": "PayRam",
+ "id": "232417783",
+ "file_count": 8,
+ "category": "Client",
+ "last_activity": "2025-10-02",
+ "oldest_file": "2024-07-03",
+ "stale_files": 7,
+ "health_score": 50
+ },
+ {
+ "name": "Superteam",
+ "id": "255663129",
+ "file_count": 3,
+ "category": "Client",
+ "last_activity": "2025-05-12",
+ "oldest_file": "2024-10-29",
+ "stale_files": 2,
+ "health_score": 44
+ },
+ {
+ "name": "Shardeum",
+ "id": "219744759",
+ "file_count": 4,
+ "category": "Client",
+ "last_activity": "2025-02-12",
+ "oldest_file": "2024-07-24",
+ "stale_files": 3,
+ "health_score": 40
+ },
+ {
+ "name": "Wrexham Rebranding",
+ "id": "252750096",
+ "file_count": 4,
+ "category": "Client",
+ "last_activity": "2025-03-08",
+ "oldest_file": "2024-07-23",
+ "stale_files": 3,
+ "health_score": 40
+ },
+ {
+ "name": "Formsdock",
+ "id": "230934202",
+ "file_count": 4,
+ "category": "Client",
+ "last_activity": "2025-03-06",
+ "oldest_file": "2024-07-10",
+ "stale_files": 3,
+ "health_score": 40
+ },
+ {
+ "name": "Hexon x Spacekayak",
+ "id": "182906832",
+ "file_count": 8,
+ "category": "Internal",
+ "last_activity": "2025-03-18",
+ "oldest_file": "2024-01-17",
+ "stale_files": 6,
+ "health_score": 40
+ },
+ {
+ "name": "Spacekayak Instagram",
+ "id": "87650637",
+ "file_count": 7,
+ "category": "Internal",
+ "last_activity": "2025-04-16",
+ "oldest_file": "2023-09-27",
+ "stale_files": 6,
+ "health_score": 36
+ },
+ {
+ "name": "Spacekayak Community Initiatives",
+ "id": "197490594",
+ "file_count": 19,
+ "category": "Internal",
+ "last_activity": "2024-08-13",
+ "oldest_file": "2024-02-12",
+ "stale_files": 19,
+ "health_score": 30
+ },
+ {
+ "name": "Refactoring UI Masterclass",
+ "id": "71757611",
+ "file_count": 11,
+ "category": "Learning",
+ "last_activity": "2023-02-16",
+ "oldest_file": "2023-02-16",
+ "stale_files": 11,
+ "health_score": 30
+ },
+ {
+ "name": "Bridgy",
+ "id": "57572923",
+ "file_count": 10,
+ "category": "Client",
+ "last_activity": "2024-09-12",
+ "oldest_file": "2023-02-16",
+ "stale_files": 10,
+ "health_score": 30
+ },
+ {
+ "name": "Workshops & Templates",
+ "id": "114510339",
+ "file_count": 10,
+ "category": "Learning",
+ "last_activity": "2024-05-22",
+ "oldest_file": "2023-10-31",
+ "stale_files": 10,
+ "health_score": 30
+ },
+ {
+ "name": "Spacekayak Marketplace",
+ "id": "199787923",
+ "file_count": 5,
+ "category": "Internal",
+ "last_activity": "2024-08-15",
+ "oldest_file": "2024-02-15",
+ "stale_files": 5,
+ "health_score": 30
+ },
+ {
+ "name": "Team project",
+ "id": "45248663",
+ "file_count": 5,
+ "category": "Client",
+ "last_activity": "2023-07-21",
+ "oldest_file": "2023-02-16",
+ "stale_files": 5,
+ "health_score": 30
+ },
+ {
+ "name": "CoinDCX's Unfold'23",
+ "id": "91735674",
+ "file_count": 4,
+ "category": "Client",
+ "last_activity": "2024-07-11",
+ "oldest_file": "2023-05-12",
+ "stale_files": 4,
+ "health_score": 30
+ },
+ {
+ "name": "Komet",
+ "id": "79423292",
+ "file_count": 4,
+ "category": "Client",
+ "last_activity": "2024-02-22",
+ "oldest_file": "2023-02-16",
+ "stale_files": 4,
+ "health_score": 30
+ },
+ {
+ "name": "Superteam - Renaissance",
+ "id": "211040753",
+ "file_count": 4,
+ "category": "Client",
+ "last_activity": "2024-12-12",
+ "oldest_file": "2024-04-01",
+ "stale_files": 4,
+ "health_score": 30
+ },
+ {
+ "name": "Komet App",
+ "id": "92571363",
+ "file_count": 3,
+ "category": "Client",
+ "last_activity": "2024-08-08",
+ "oldest_file": "2023-05-15",
+ "stale_files": 3,
+ "health_score": 30
+ },
+ {
+ "name": "Solvent & Strikr",
+ "id": "59508689",
+ "file_count": 3,
+ "category": "Client",
+ "last_activity": "2023-09-08",
+ "oldest_file": "2023-02-16",
+ "stale_files": 3,
+ "health_score": 30
+ },
+ {
+ "name": "Spacekayak Framework Templates",
+ "id": "222265547",
+ "file_count": 3,
+ "category": "Internal",
+ "last_activity": "2024-07-09",
+ "oldest_file": "2024-04-09",
+ "stale_files": 3,
+ "health_score": 30
+ },
+ {
+ "name": "Bharatbox",
+ "id": "113298774",
+ "file_count": 2,
+ "category": "Client",
+ "last_activity": "2023-12-06",
+ "oldest_file": "2023-11-30",
+ "stale_files": 2,
+ "health_score": 30
+ },
+ {
+ "name": "BlockMesh",
+ "id": "247887720",
+ "file_count": 2,
+ "category": "Client",
+ "last_activity": "2024-07-08",
+ "oldest_file": "2024-07-02",
+ "stale_files": 2,
+ "health_score": 30
+ },
+ {
+ "name": "Llama",
+ "id": "58230073",
+ "file_count": 2,
+ "category": "Client",
+ "last_activity": "2024-11-22",
+ "oldest_file": "2022-10-19",
+ "stale_files": 2,
+ "health_score": 30
+ },
+ {
+ "name": "Travala",
+ "id": "282997323",
+ "file_count": 2,
+ "category": "Client",
+ "last_activity": "2024-10-09",
+ "oldest_file": "2024-09-30",
+ "stale_files": 2,
+ "health_score": 30
+ },
+ {
+ "name": "824 Events",
+ "id": "203548930",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2024-03-15",
+ "oldest_file": "2024-03-15",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "Avocado Video",
+ "id": "220269080",
+ "file_count": 1,
+ "category": "Marketing",
+ "last_activity": "2024-05-01",
+ "oldest_file": "2024-05-01",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "Bonomi",
+ "id": "229775045",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2024-09-06",
+ "oldest_file": "2024-09-06",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "Buidl.so",
+ "id": "176626228",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2023-12-13",
+ "oldest_file": "2023-12-13",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "C Negative",
+ "id": "54701076",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2024-06-10",
+ "oldest_file": "2024-06-10",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "Celio Labs",
+ "id": "169069836",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2024-03-25",
+ "oldest_file": "2024-03-25",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "Chapter 0",
+ "id": "291819377",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2024-10-23",
+ "oldest_file": "2024-10-23",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "Kayak Merch Breakpoint",
+ "id": "275338266",
+ "file_count": 1,
+ "category": "Merch",
+ "last_activity": "2024-09-13",
+ "oldest_file": "2024-09-13",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "Merch",
+ "id": "171139406",
+ "file_count": 1,
+ "category": "Merch",
+ "last_activity": "2024-12-17",
+ "oldest_file": "2024-12-17",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "Merch Box",
+ "id": "284390958",
+ "file_count": 1,
+ "category": "Merch",
+ "last_activity": "2024-10-21",
+ "oldest_file": "2024-10-21",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "Meth Labs",
+ "id": "172884743",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2023-12-06",
+ "oldest_file": "2023-12-06",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "NFA",
+ "id": "287167408",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2024-10-08",
+ "oldest_file": "2024-10-08",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "Polkadot After Party",
+ "id": "260805849",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2024-09-16",
+ "oldest_file": "2024-09-16",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "Socket",
+ "id": "92671746",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2024-06-11",
+ "oldest_file": "2024-06-11",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "Spacekayak Documentary",
+ "id": "286114429",
+ "file_count": 1,
+ "category": "Internal",
+ "last_activity": "2024-10-07",
+ "oldest_file": "2024-10-07",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "Spacekayak Marketing",
+ "id": "184258858",
+ "file_count": 1,
+ "category": "Internal",
+ "last_activity": "2024-01-16",
+ "oldest_file": "2024-01-16",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "Superteam Co-Hack",
+ "id": "270569640",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2024-09-01",
+ "oldest_file": "2024-09-01",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "Swish",
+ "id": "278716521",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2024-09-23",
+ "oldest_file": "2024-09-23",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "TPH Landing page",
+ "id": "71400532",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2023-12-06",
+ "oldest_file": "2023-12-06",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "Team Dynamics",
+ "id": "86638497",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2023-03-23",
+ "oldest_file": "2023-03-23",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "The Daily Optimist",
+ "id": "212152876",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2024-03-18",
+ "oldest_file": "2024-03-18",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "0xStardust",
+ "id": "79556698",
+ "file_count": 2,
+ "category": "Client",
+ "last_activity": "2024-08-08",
+ "oldest_file": "2024-01-11",
+ "stale_files": 2,
+ "health_score": 30
+ },
+ {
+ "name": "10K AI Presentations",
+ "id": "97014739",
+ "file_count": 3,
+ "category": "Client",
+ "last_activity": "2024-03-17",
+ "oldest_file": "2024-01-19",
+ "stale_files": 3,
+ "health_score": 30
+ },
+ {
+ "name": "824 Intro Video",
+ "id": "92862702",
+ "file_count": 1,
+ "category": "Marketing",
+ "last_activity": "2023-05-17",
+ "oldest_file": "2023-05-17",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "AI MAYHEM",
+ "id": "241649305",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2024-06-06",
+ "oldest_file": "2024-06-06",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "Accel",
+ "id": "72128338",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2023-02-16",
+ "oldest_file": "2023-02-16",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "Agg. Summit",
+ "id": "290606446",
+ "file_count": 5,
+ "category": "Client",
+ "last_activity": "2024-12-13",
+ "oldest_file": "2024-10-23",
+ "stale_files": 5,
+ "health_score": 30
+ },
+ {
+ "name": "ETHIndia",
+ "id": "60210654",
+ "file_count": 12,
+ "category": "Client",
+ "last_activity": "2024-09-29",
+ "oldest_file": "2023-02-16",
+ "stale_files": 12,
+ "health_score": 30
+ },
+ {
+ "name": "Flipkart Labs",
+ "id": "113856331",
+ "file_count": 2,
+ "category": "Client",
+ "last_activity": "2024-03-15",
+ "oldest_file": "2023-11-11",
+ "stale_files": 2,
+ "health_score": 30
+ },
+ {
+ "name": "Furrl",
+ "id": "250914303",
+ "file_count": 4,
+ "category": "Client",
+ "last_activity": "2024-10-23",
+ "oldest_file": "2024-07-17",
+ "stale_files": 4,
+ "health_score": 30
+ },
+ {
+ "name": "HeftyVerse",
+ "id": "58504330",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2023-02-16",
+ "oldest_file": "2023-02-16",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "Honestly.Club",
+ "id": "204418024",
+ "file_count": 2,
+ "category": "Client",
+ "last_activity": "2024-02-12",
+ "oldest_file": "2024-02-12",
+ "stale_files": 2,
+ "health_score": 30
+ },
+ {
+ "name": "Hyperdrive",
+ "id": "110127413",
+ "file_count": 8,
+ "category": "Client",
+ "last_activity": "2024-03-21",
+ "oldest_file": "2023-10-10",
+ "stale_files": 8,
+ "health_score": 30
+ },
+ {
+ "name": "Hyperdrive Workshops",
+ "id": "110880282",
+ "file_count": 13,
+ "category": "Learning",
+ "last_activity": "2023-11-08",
+ "oldest_file": "2023-10-08",
+ "stale_files": 13,
+ "health_score": 30
+ },
+ {
+ "name": "IHX",
+ "id": "104637371",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2024-06-24",
+ "oldest_file": "2024-06-24",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "InducedAI",
+ "id": "101198513",
+ "file_count": 4,
+ "category": "Client",
+ "last_activity": "2024-09-09",
+ "oldest_file": "2024-01-11",
+ "stale_files": 4,
+ "health_score": 30
+ },
+ {
+ "name": "Indus App Store",
+ "id": "230416261",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2024-05-07",
+ "oldest_file": "2024-05-07",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "Indus App Store",
+ "id": "230931745",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2024-10-07",
+ "oldest_file": "2024-10-07",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "Islands NFT",
+ "id": "83982254",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2024-01-11",
+ "oldest_file": "2024-01-11",
+ "stale_files": 1,
+ "health_score": 30
+ },
+ {
+ "name": "Juno Rewind",
+ "id": "75999069",
+ "file_count": 3,
+ "category": "Client",
+ "last_activity": "2024-11-24",
+ "oldest_file": "2023-02-16",
+ "stale_files": 3,
+ "health_score": 30
+ },
+ {
+ "name": "JupiterExchange",
+ "id": "218047369",
+ "file_count": 1,
+ "category": "Client",
+ "last_activity": "2024-03-29",
+ "oldest_file": "2024-03-29",
+ "stale_files": 1,
+ "health_score": 30
+ }
+ ],
+ "categories": [
+ {
+ "category": "Client",
+ "projects": 143,
+ "files": 629,
+ "stale": 263
+ },
+ {
+ "category": "Internal",
+ "projects": 11,
+ "files": 191,
+ "stale": 96
+ },
+ {
+ "category": "Learning",
+ "projects": 4,
+ "files": 36,
+ "stale": 34
+ },
+ {
+ "category": "Marketing",
+ "projects": 7,
+ "files": 30,
+ "stale": 11
+ },
+ {
+ "category": "Merch",
+ "projects": 4,
+ "files": 5,
+ "stale": 3
+ }
+ ],
+ "file_types": [
+ {
+ "type": "Other",
+ "count": 398
+ },
+ {
+ "type": "Brand",
+ "count": 130
+ },
+ {
+ "type": "Landing Page",
+ "count": 107
+ },
+ {
+ "type": "Presentation",
+ "count": 62
+ },
+ {
+ "type": "Video / Motion",
+ "count": 57
+ },
+ {
+ "type": "Design System",
+ "count": 41
+ },
+ {
+ "type": "App Design",
+ "count": 36
+ },
+ {
+ "type": "Moodboard",
+ "count": 32
+ },
+ {
+ "type": "Social / Marketing",
+ "count": 28
+ }
+ ],
+ "components": [
+ {
+ "file": "Website v4",
+ "file_key": "qqpc2Gr4h32B172mqb9CQv",
+ "component_count": 30,
+ "style_count": 21,
+ "components": [
+ {
+ "name": "State=Default",
+ "description": "",
+ "containing_frame": "Projects_Chip"
+ },
+ {
+ "name": "State=Open",
+ "description": "",
+ "containing_frame": "Menu"
+ },
+ {
+ "name": "option=lore",
+ "description": "",
+ "containing_frame": "gtmimages"
+ },
+ {
+ "name": "option=dspackage",
+ "description": "",
+ "containing_frame": "gtmimages"
+ },
+ {
+ "name": "option=experience",
+ "description": "",
+ "containing_frame": "gtmimages"
+ },
+ {
+ "name": "option=brand",
+ "description": "",
+ "containing_frame": "gtmimages"
+ },
+ {
+ "name": "option=nft",
+ "description": "",
+ "containing_frame": "gtmimages"
+ },
+ {
+ "name": "State=Hover",
+ "description": "",
+ "containing_frame": "About Card"
+ },
+ {
+ "name": "option=lore",
+ "description": "",
+ "containing_frame": "designimages"
+ },
+ {
+ "name": "option=nft",
+ "description": "",
+ "containing_frame": "designimages"
+ },
+ {
+ "name": "option=dspackage",
+ "description": "",
+ "containing_frame": "designimages"
+ },
+ {
+ "name": "option=experience",
+ "description": "",
+ "containing_frame": "designimages"
+ },
+ {
+ "name": "option=brand",
+ "description": "",
+ "containing_frame": "designimages"
+ },
+ {
+ "name": "option=lore",
+ "description": "",
+ "containing_frame": "communicationimages"
+ },
+ {
+ "name": "option=dspackage",
+ "description": "",
+ "containing_frame": "communicationimages"
+ },
+ {
+ "name": "option=experience",
+ "description": "",
+ "containing_frame": "communicationimages"
+ },
+ {
+ "name": "option=brand",
+ "description": "",
+ "containing_frame": "communicationimages"
+ },
+ {
+ "name": "option=nft",
+ "description": "",
+ "containing_frame": "communicationimages"
+ },
+ {
+ "name": "Property 1=Default",
+ "description": "",
+ "containing_frame": "Puzzle"
+ },
+ {
+ "name": "Property 1=Variant6",
+ "description": "",
+ "containing_frame": "Puzzle"
+ },
+ {
+ "name": "Property 1=Variant3",
+ "description": "",
+ "containing_frame": "Puzzle"
+ },
+ {
+ "name": "Property 1=Variant2",
+ "description": "",
+ "containing_frame": "Puzzle"
+ },
+ {
+ "name": "Property 1=Variant4",
+ "description": "",
+ "containing_frame": "Puzzle"
+ },
+ {
+ "name": "Property 1=Variant5",
+ "description": "",
+ "containing_frame": "Puzzle"
+ },
+ {
+ "name": "option=lore",
+ "description": "",
+ "containing_frame": "productimages"
+ },
+ {
+ "name": "option=dspackage",
+ "description": "",
+ "containing_frame": "productimages"
+ },
+ {
+ "name": "option=experience",
+ "description": "",
+ "containing_frame": "productimages"
+ },
+ {
+ "name": "option=brand",
+ "description": "",
+ "containing_frame": "productimages"
+ },
+ {
+ "name": "option=nft",
+ "description": "",
+ "containing_frame": "productimages"
+ },
+ {
+ "name": "Object=Palette, Selected=true",
+ "description": "",
+ "containing_frame": "Service Icons"
+ }
+ ],
+ "styles": [
+ {
+ "name": "New Styles/Heading/Heading4/H4 Bold",
+ "style_type": "TEXT",
+ "description": "H4"
+ },
+ {
+ "name": "New Styles/Heading/Heading5/H5 Italic",
+ "style_type": "TEXT",
+ "description": "H5"
+ },
+ {
+ "name": "Desktop Grid",
+ "style_type": "GRID",
+ "description": ""
+ },
+ {
+ "name": "New Styles/Heading/Heading1/H1 Italic",
+ "style_type": "TEXT",
+ "description": "H1"
+ },
+ {
+ "name": "New Styles/Heading/Heading2/H2",
+ "style_type": "TEXT",
+ "description": "H2"
+ },
+ {
+ "name": "New Styles/Heading/Heading2/H2 Italic",
+ "style_type": "TEXT",
+ "description": "H2"
+ },
+ {
+ "name": "New Styles/Heading/Heading1/H1",
+ "style_type": "TEXT",
+ "description": "H1"
+ },
+ {
+ "name": "New Styles/Heading/Heading3/H3",
+ "style_type": "TEXT",
+ "description": "H3"
+ },
+ {
+ "name": "New Styles/Heading/Heading4/H4",
+ "style_type": "TEXT",
+ "description": ""
+ },
+ {
+ "name": "New Styles/Heading/Heading4/H4 Italic",
+ "style_type": "TEXT",
+ "description": "H4"
+ },
+ {
+ "name": "New Styles/Heading/Heading3/H3 Italic",
+ "style_type": "TEXT",
+ "description": "H3"
+ },
+ {
+ "name": "New Styles/Heading/Body/Body Tiny",
+ "style_type": "TEXT",
+ "description": ""
+ },
+ {
+ "name": "New Styles/Heading/Body/Body Large",
+ "style_type": "TEXT",
+ "description": "BL"
+ },
+ {
+ "name": "New Styles/Heading/Body/Caption",
+ "style_type": "TEXT",
+ "description": ""
+ },
+ {
+ "name": "New Styles/Heading/Body/Body Small",
+ "style_type": "TEXT",
+ "description": "BS"
+ },
+ {
+ "name": "New Styles/Heading/Body/Body Bold",
+ "style_type": "TEXT",
+ "description": ""
+ },
+ {
+ "name": "New Styles/Heading/Body/Subheading-Mono",
+ "style_type": "TEXT",
+ "description": ""
+ },
+ {
+ "name": "New Styles/Heading/Body/Big CTA",
+ "style_type": "TEXT",
+ "description": ""
+ },
+ {
+ "name": "New Styles/Heading/Body/Body",
+ "style_type": "TEXT",
+ "description": "B"
+ },
+ {
+ "name": "New Styles/Heading/Body/Numbers",
+ "style_type": "TEXT",
+ "description": ""
+ },
+ {
+ "name": "New Styles/Heading/Heading5/H5",
+ "style_type": "TEXT",
+ "description": "H5"
+ }
+ ]
+ },
+ {
+ "file": "Siren App Design",
+ "file_key": "2u2VmKlLOKGnPExPqNhmly",
+ "component_count": 0,
+ "style_count": 0,
+ "components": [],
+ "styles": []
+ },
+ {
+ "file": "Bench Brand",
+ "file_key": "p7YGgAzHfB20IA7PDooWcR",
+ "component_count": 0,
+ "style_count": 0,
+ "components": [],
+ "styles": []
+ },
+ {
+ "file": "Sanctuary",
+ "file_key": "eo10ndA8lVQDiTZ96pDsGP",
+ "component_count": 0,
+ "style_count": 0,
+ "components": [],
+ "styles": []
+ },
+ {
+ "file": "Orbix Dev-Hand-Off",
+ "file_key": "MdbwVor4ARwXT5wWqouXpo",
+ "component_count": 0,
+ "style_count": 0,
+ "components": [],
+ "styles": []
+ },
+ {
+ "file": "Assurekit Brand",
+ "file_key": "BxtB6U7Kxb89T09qJDRfu1",
+ "component_count": 0,
+ "style_count": 0,
+ "components": [],
+ "styles": []
+ }
+ ],
+ "recent_feed": [
+ {
+ "user": "Boris P",
+ "file": "\ud83d\udcf1 Siren App Design",
+ "project": "Siren by Bureau ID",
+ "date": "2026-03-12",
+ "label": null
+ },
+ {
+ "user": "Navaneeth Venu",
+ "file": "\ud83d\udcf1 Siren App Design",
+ "project": "Siren by Bureau ID",
+ "date": "2026-03-12",
+ "label": null
+ },
+ {
+ "user": "Paul Finney",
+ "file": "Work Pages",
+ "project": "Spacekayak's Website",
+ "date": "2026-03-12",
+ "label": null
+ },
+ {
+ "user": "Sumit Yadav",
+ "file": "Design <> Dev",
+ "project": "Assurekit",
+ "date": "2026-03-12",
+ "label": null
+ },
+ {
+ "user": "Figma",
+ "file": "Design <> Dev",
+ "project": "Assurekit",
+ "date": "2026-03-12",
+ "label": null
+ },
+ {
+ "user": "Figma",
+ "file": "Design <> Dev",
+ "project": "Assurekit",
+ "date": "2026-03-12",
+ "label": null
+ },
+ {
+ "user": "Gayatri",
+ "file": "Attention Company",
+ "project": "Attn co",
+ "date": "2026-03-12",
+ "label": null
+ },
+ {
+ "user": "Navaneeth Venu",
+ "file": "F-LOG Brand Internal",
+ "project": "F-Log Ventures",
+ "date": "2026-03-12",
+ "label": null
+ },
+ {
+ "user": "Navaneeth Venu",
+ "file": "F-LOG Brand Internal",
+ "project": "F-Log Ventures",
+ "date": "2026-03-12",
+ "label": null
+ },
+ {
+ "user": "Navaneeth Venu",
+ "file": "F-LOG Brand Internal",
+ "project": "F-Log Ventures",
+ "date": "2026-03-12",
+ "label": null
+ },
+ {
+ "user": "Navaneeth Venu",
+ "file": "F-LOG Brand Internal",
+ "project": "F-Log Ventures",
+ "date": "2026-03-12",
+ "label": "Ready for dev"
+ },
+ {
+ "user": "Navaneeth Venu",
+ "file": "F-LOG Brand Internal",
+ "project": "F-Log Ventures",
+ "date": "2026-03-12",
+ "label": null
+ },
+ {
+ "user": "Navaneeth Venu",
+ "file": "F-LOG Brand Internal",
+ "project": "F-Log Ventures",
+ "date": "2026-03-12",
+ "label": null
+ },
+ {
+ "user": "Navaneeth Venu",
+ "file": "F-LOG Brand Internal",
+ "project": "F-Log Ventures",
+ "date": "2026-03-12",
+ "label": null
+ },
+ {
+ "user": "Boris P",
+ "file": "Orbix Dev-Hand-Off",
+ "project": "Stylumia",
+ "date": "2026-03-12",
+ "label": null
+ },
+ {
+ "user": "Shubham Bhardwaj",
+ "file": "Sanctuary",
+ "project": "Spacekayak Internal Design",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Shubham Bhardwaj",
+ "file": "Sanctuary",
+ "project": "Spacekayak Internal Design",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Himanshu",
+ "file": "\ud83d\udcf1 Siren App Design",
+ "project": "Siren by Bureau ID",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Himanshu",
+ "file": "\ud83d\udcf1 Siren App Design",
+ "project": "Siren by Bureau ID",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Himanshu",
+ "file": "\ud83d\udcf1 Siren App Design",
+ "project": "Siren by Bureau ID",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Boris P",
+ "file": "\ud83d\udcf1 Siren App Design",
+ "project": "Siren by Bureau ID",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Navaneeth Venu",
+ "file": "\ud83d\udcf1 Siren App Design",
+ "project": "Siren by Bureau ID",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Himanshu",
+ "file": "\ud83d\udcf1 Siren App Design",
+ "project": "Siren by Bureau ID",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Ayan Malik",
+ "file": "\ud83d\udcf1 Siren App Design",
+ "project": "Siren by Bureau ID",
+ "date": "2026-03-11",
+ "label": "Ready for dev"
+ },
+ {
+ "user": "Urja",
+ "file": "Work Pages",
+ "project": "Spacekayak's Website",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Urja",
+ "file": "Work Pages",
+ "project": "Spacekayak's Website",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Urja",
+ "file": "Work Pages",
+ "project": "Spacekayak's Website",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Urja",
+ "file": "Work Pages",
+ "project": "Spacekayak's Website",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Hari",
+ "file": "Work Pages",
+ "project": "Spacekayak's Website",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Navaneeth Venu",
+ "file": "Work Pages",
+ "project": "Spacekayak's Website",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Urja",
+ "file": "Work Pages",
+ "project": "Spacekayak's Website",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Urja",
+ "file": "Work Pages",
+ "project": "Spacekayak's Website",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Urja",
+ "file": "Work Pages",
+ "project": "Spacekayak's Website",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Urja",
+ "file": "Work Pages",
+ "project": "Spacekayak's Website",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Gayatri",
+ "file": "\ud83d\udcbb Website v4",
+ "project": "Spacekayak's Website",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Gayatri",
+ "file": "\ud83d\udcbb Website v4",
+ "project": "Spacekayak's Website",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Gayatri",
+ "file": "\ud83d\udcbb Website v4",
+ "project": "Spacekayak's Website",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Ashwin",
+ "file": "Sanctuary.Blr",
+ "project": "Sanctuary",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Ashwin",
+ "file": "Sanctuary.Blr",
+ "project": "Sanctuary",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Ashwin",
+ "file": "Sanctuary.Blr",
+ "project": "Sanctuary",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Ashwin",
+ "file": "Sanctuary.Blr",
+ "project": "Sanctuary",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Ashwin",
+ "file": "Sanctuary.Blr",
+ "project": "Sanctuary",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Boris P",
+ "file": "Assurekit Brand",
+ "project": "Assurekit",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Boris P",
+ "file": "Assurekit Brand",
+ "project": "Assurekit",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Boris P",
+ "file": "Assurekit Brand",
+ "project": "Assurekit",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Boris P",
+ "file": "Assurekit Brand",
+ "project": "Assurekit",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Achyut Saxena",
+ "file": "Assurekit Brand",
+ "project": "Assurekit",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Achyut Saxena",
+ "file": "Assurekit Brand",
+ "project": "Assurekit",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Boris P",
+ "file": "Design <> Dev",
+ "project": "Assurekit",
+ "date": "2026-03-11",
+ "label": null
+ },
+ {
+ "user": "Boris P",
+ "file": "Design <> Dev",
+ "project": "Assurekit",
+ "date": "2026-03-11",
+ "label": null
+ }
+ ]
+}
\ No newline at end of file
diff --git a/miniverse/gravity/index.html b/miniverse/gravity/index.html
new file mode 100644
index 0000000..933f33b
--- /dev/null
+++ b/miniverse/gravity/index.html
@@ -0,0 +1,2678 @@
+
+
+
+
+
+Gravity — Spacekayak
+
+
+
+
+
+
+
+
+
+
+
+
+ Overview
+ Designers
+ Projects
+ Design System
+ Reviews
+ Activity
+
+
+
+
+
+
Loading dashboard data…
+
+
+
+
+
+
+
+ Settings
+ ×
+
+
+
+ Configuration
+ Release Log
+
+
+
+
+
+
+
+ Sync Interval (minutes)
+
+
+
+
+ Cancel
+ Save & Test
+
+
+
+
+
+
+
+
v2.1.0
+
March 16, 2026
+
+ new Release log in settings panel
+ new Last sync time KPI on overview dashboard
+ new Sync timestamp tooltip with duration
+ improve Vercel deployment support
+
+
+
+
v2.0.0
+
March 15, 2026
+
+ new Live Figma API sync engine with cron scheduling
+ new Session reconstruction from version history
+ new Designer analytics with work pattern heatmaps
+ new Design system health scoring and component freshness
+ new Reviews tab — comment feed, response times, alerts
+ new Project intelligence with activity pulse
+ new SQLite persistence with WAL mode
+ new Settings panel with Figma configuration
+ new Real-time sync status indicator
+
+
+
+
v1.0.0
+
March 2026
+
+ new Initial static dashboard with Chart.js
+ new Overview, Designers, Projects, Assets, Activity tabs
+ new Imagine Fund editorial design aesthetic
+ new Static data.json fallback mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/miniverse/gravity/package-lock.json b/miniverse/gravity/package-lock.json
new file mode 100644
index 0000000..180dda7
--- /dev/null
+++ b/miniverse/gravity/package-lock.json
@@ -0,0 +1,1320 @@
+{
+ "name": "gravity",
+ "version": "2.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "gravity",
+ "version": "2.0.0",
+ "dependencies": {
+ "better-sqlite3": "^11.7.0",
+ "bottleneck": "^2.19.5",
+ "cors": "^2.8.5",
+ "dotenv": "^16.4.7",
+ "express": "^4.21.2",
+ "node-cron": "^3.0.3"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+ "license": "MIT"
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/better-sqlite3": {
+ "version": "11.10.0",
+ "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz",
+ "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "bindings": "^1.5.0",
+ "prebuild-install": "^7.1.1"
+ }
+ },
+ "node_modules/bindings": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
+ "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+ "license": "MIT",
+ "dependencies": {
+ "file-uri-to-path": "1.0.0"
+ }
+ },
+ "node_modules/bl": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
+ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer": "^5.5.0",
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.4.0"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.4",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
+ "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "~1.2.0",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "on-finished": "~2.4.1",
+ "qs": "~6.14.0",
+ "raw-body": "~2.5.3",
+ "type-is": "~1.6.18",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/bottleneck": {
+ "version": "2.19.5",
+ "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz",
+ "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==",
+ "license": "MIT"
+ },
+ "node_modules/buffer": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/chownr": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
+ "license": "ISC"
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
+ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
+ "license": "MIT"
+ },
+ "node_modules/cors": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/decompress-response": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
+ "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+ "license": "MIT",
+ "dependencies": {
+ "mimic-response": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/deep-extend": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "16.6.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
+ "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/end-of-stream": {
+ "version": "1.4.5",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
+ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
+ "license": "MIT",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/expand-template": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
+ "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
+ "license": "(MIT OR WTFPL)",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.22.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
+ "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "~1.20.3",
+ "content-disposition": "~0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "~0.7.1",
+ "cookie-signature": "~1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "~1.3.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.0",
+ "merge-descriptors": "1.0.3",
+ "methods": "~1.1.2",
+ "on-finished": "~2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "~0.1.12",
+ "proxy-addr": "~2.0.7",
+ "qs": "~6.14.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "~0.19.0",
+ "serve-static": "~1.16.2",
+ "setprototypeof": "1.2.0",
+ "statuses": "~2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/file-uri-to-path": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
+ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
+ "license": "MIT"
+ },
+ "node_modules/finalhandler": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
+ "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "~2.0.2",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fs-constants": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
+ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
+ "license": "MIT"
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/github-from-package": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
+ "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
+ "license": "MIT"
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "license": "ISC"
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-response": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
+ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/mkdirp-classic": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
+ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
+ "license": "MIT"
+ },
+ "node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/napi-build-utils": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
+ "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
+ "license": "MIT"
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/node-abi": {
+ "version": "3.88.0",
+ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.88.0.tgz",
+ "integrity": "sha512-At6b4UqIEVudaqPsXjmUO1r/N5BUr4yhDGs5PkBE8/oG5+TfLPhFechiskFsnT6Ql0VfUXbalUUCbfXxtj7K+w==",
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.3.5"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/node-cron": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz",
+ "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==",
+ "license": "ISC",
+ "dependencies": {
+ "uuid": "8.3.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
+ "license": "MIT"
+ },
+ "node_modules/prebuild-install": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
+ "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
+ "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",
+ "license": "MIT",
+ "dependencies": {
+ "detect-libc": "^2.0.0",
+ "expand-template": "^2.0.3",
+ "github-from-package": "0.0.0",
+ "minimist": "^1.2.3",
+ "mkdirp-classic": "^0.5.3",
+ "napi-build-utils": "^2.0.0",
+ "node-abi": "^3.3.0",
+ "pump": "^3.0.0",
+ "rc": "^1.2.7",
+ "simple-get": "^4.0.0",
+ "tar-fs": "^2.0.0",
+ "tunnel-agent": "^0.6.0"
+ },
+ "bin": {
+ "prebuild-install": "bin.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/pump": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
+ "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
+ "license": "MIT",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.14.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
+ "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
+ "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/rc": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+ "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
+ "dependencies": {
+ "deep-extend": "^0.6.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ },
+ "bin": {
+ "rc": "cli.js"
+ }
+ },
+ "node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/send": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
+ "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.1",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "~2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "~2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/serve-static": {
+ "version": "1.16.3",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
+ "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "~0.19.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/simple-concat": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
+ "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/simple-get": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
+ "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "decompress-response": "^6.0.0",
+ "once": "^1.3.1",
+ "simple-concat": "^1.0.0"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/tar-fs": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
+ "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "chownr": "^1.1.1",
+ "mkdirp-classic": "^0.5.2",
+ "pump": "^3.0.0",
+ "tar-stream": "^2.1.4"
+ }
+ },
+ "node_modules/tar-stream": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
+ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "bl": "^4.0.3",
+ "end-of-stream": "^1.4.1",
+ "fs-constants": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/tunnel-agent": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+ "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "license": "MIT",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "license": "MIT"
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ }
+ }
+}
diff --git a/miniverse/gravity/package.json b/miniverse/gravity/package.json
new file mode 100644
index 0000000..cdf7c24
--- /dev/null
+++ b/miniverse/gravity/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "gravity",
+ "version": "2.0.0",
+ "description": "Gravity — Spacekayak Figma Management Dashboard",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "start": "node server/index.js",
+ "dev": "node --watch server/index.js"
+ },
+ "dependencies": {
+ "better-sqlite3": "^11.7.0",
+ "bottleneck": "^2.19.5",
+ "cors": "^2.8.5",
+ "dotenv": "^16.4.7",
+ "express": "^4.21.2",
+ "node-cron": "^3.0.3"
+ }
+}
diff --git a/miniverse/gravity/server/db.js b/miniverse/gravity/server/db.js
new file mode 100644
index 0000000..f8889c8
--- /dev/null
+++ b/miniverse/gravity/server/db.js
@@ -0,0 +1,61 @@
+import Database from 'better-sqlite3';
+import { readFileSync } from 'fs';
+import { join, dirname } from 'path';
+import { fileURLToPath } from 'url';
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const isVercel = !!process.env.VERCEL;
+const DB_PATH = isVercel ? '/tmp/gravity.db' : join(__dirname, 'gravity.db');
+
+let db;
+
+export function getDb() {
+ if (!db) {
+ db = new Database(DB_PATH);
+ db.pragma('journal_mode = WAL');
+ db.pragma('foreign_keys = ON');
+
+ const schema = readFileSync(join(__dirname, 'schema.sql'), 'utf-8');
+ db.exec(schema);
+ }
+ return db;
+}
+
+// Config helpers
+export function getConfig(key) {
+ const row = getDb().prepare('SELECT value FROM config WHERE key = ?').get(key);
+ return row ? row.value : null;
+}
+
+export function setConfig(key, value) {
+ getDb().prepare(`
+ INSERT INTO config (key, value, updated_at) VALUES (?, ?, datetime('now'))
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
+ `).run(key, value);
+}
+
+// Sync log helpers
+export function logSync(eventType, status, details = null) {
+ const now = new Date().toISOString();
+ if (status === 'started') {
+ const info = getDb().prepare(
+ `INSERT INTO sync_log (event_type, status, started_at) VALUES (?, ?, ?)`
+ ).run(eventType, status, now);
+ return info.lastInsertRowid;
+ }
+ return null;
+}
+
+export function completeSync(id, status, details, startedAt) {
+ const now = new Date().toISOString();
+ const ms = new Date(now) - new Date(startedAt);
+ getDb().prepare(`
+ UPDATE sync_log SET status = ?, details = ?, completed_at = ?, duration_ms = ? WHERE id = ?
+ `).run(status, JSON.stringify(details), now, ms, id);
+}
+
+export function getLastSync() {
+ return getDb().prepare(
+ `SELECT * FROM sync_log WHERE status = 'completed' ORDER BY completed_at DESC LIMIT 1`
+ ).get();
+}
diff --git a/miniverse/gravity/server/figma-client.js b/miniverse/gravity/server/figma-client.js
new file mode 100644
index 0000000..005bed6
--- /dev/null
+++ b/miniverse/gravity/server/figma-client.js
@@ -0,0 +1,112 @@
+import Bottleneck from 'bottleneck';
+
+const BASE = 'https://api.figma.com/v1';
+
+// Rate limiter: max 30 requests per minute (conservative for most plans)
+const limiter = new Bottleneck({
+ reservoir: 30,
+ reservoirRefreshAmount: 30,
+ reservoirRefreshInterval: 60 * 1000,
+ maxConcurrent: 2,
+ minTime: 200,
+});
+
+let token = null;
+
+export function setToken(pat) {
+ token = pat;
+}
+
+async function request(path) {
+ if (!token) throw new Error('Figma PAT not configured');
+
+ const res = await limiter.schedule(() =>
+ fetch(`${BASE}${path}`, {
+ headers: { 'X-Figma-Token': token },
+ })
+ );
+
+ if (res.status === 429) {
+ const retry = parseInt(res.headers.get('retry-after') || '60', 10);
+ console.warn(`[figma] Rate limited. Retrying in ${retry}s...`);
+ await new Promise(r => setTimeout(r, retry * 1000));
+ return request(path);
+ }
+
+ if (!res.ok) {
+ const text = await res.text().catch(() => '');
+ throw new Error(`Figma API ${res.status}: ${path} — ${text.slice(0, 200)}`);
+ }
+
+ return res.json();
+}
+
+// Team projects
+export async function getTeamProjects(teamId) {
+ const data = await request(`/teams/${teamId}/projects`);
+ return data.projects || [];
+}
+
+// Project files
+export async function getProjectFiles(projectId) {
+ const data = await request(`/projects/${projectId}/files`);
+ return data.files || [];
+}
+
+// File versions (paginated)
+export async function getFileVersions(fileKey, maxPages = 4) {
+ const versions = [];
+ let url = `/files/${fileKey}/versions`;
+
+ for (let page = 0; page < maxPages; page++) {
+ const data = await request(url);
+ if (data.versions) versions.push(...data.versions);
+
+ if (data.pagination?.next_page) {
+ // Extract path from full URL
+ const nextUrl = new URL(data.pagination.next_page);
+ url = nextUrl.pathname + nextUrl.search;
+ } else {
+ break;
+ }
+ }
+
+ return versions;
+}
+
+// File comments
+export async function getFileComments(fileKey) {
+ const data = await request(`/files/${fileKey}/comments`);
+ return data.comments || [];
+}
+
+// Team components
+export async function getTeamComponents(teamId, maxPages = 4) {
+ const components = [];
+ let cursor = '';
+
+ for (let page = 0; page < maxPages; page++) {
+ const qs = cursor ? `?after=${cursor}` : '';
+ const data = await request(`/teams/${teamId}/components${qs}`);
+
+ if (data.meta?.components) components.push(...data.meta.components);
+
+ if (data.meta?.cursor?.after) {
+ cursor = data.meta.cursor.after;
+ } else {
+ break;
+ }
+ }
+
+ return components;
+}
+
+// Test connection
+export async function testConnection(teamId) {
+ try {
+ const projects = await getTeamProjects(teamId);
+ return { ok: true, projectCount: projects.length };
+ } catch (err) {
+ return { ok: false, error: err.message };
+ }
+}
diff --git a/miniverse/gravity/server/index.js b/miniverse/gravity/server/index.js
new file mode 100644
index 0000000..cf9ca9d
--- /dev/null
+++ b/miniverse/gravity/server/index.js
@@ -0,0 +1,424 @@
+import 'dotenv/config';
+import express from 'express';
+import cors from 'cors';
+import cron from 'node-cron';
+import { join, dirname } from 'path';
+import { fileURLToPath } from 'url';
+import { getDb, getConfig, setConfig, getLastSync } from './db.js';
+import { setToken, testConnection } from './figma-client.js';
+import { fullSync, isSyncing } from './sync-engine.js';
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const app = express();
+const PORT = process.env.PORT || 3847;
+
+app.use(cors());
+app.use(express.json());
+
+// Serve static files from parent directory (index.html, data.json)
+app.use(express.static(join(__dirname, '..')));
+
+// ──────────────────────────────────────────
+// API: Status
+// ──────────────────────────────────────────
+
+app.get('/api/status', (req, res) => {
+ const lastSync = getLastSync();
+ const teamId = getConfig('figma_team_id') || process.env.FIGMA_TEAM_ID || null;
+ const configured = !!(teamId && (getConfig('figma_pat') || process.env.FIGMA_PAT));
+
+ const db = getDb();
+ const fileCt = db.prepare('SELECT COUNT(*) as n FROM figma_files').get().n;
+ const versionCt = db.prepare('SELECT COUNT(*) as n FROM figma_versions').get().n;
+ const sessionCt = db.prepare('SELECT COUNT(*) as n FROM design_sessions').get().n;
+ const commentCt = db.prepare('SELECT COUNT(*) as n FROM figma_comments').get().n;
+ const componentCt = db.prepare('SELECT COUNT(*) as n FROM figma_components').get().n;
+
+ res.json({
+ configured,
+ syncing: isSyncing(),
+ lastSync: lastSync ? { completedAt: lastSync.completed_at, durationMs: lastSync.duration_ms, details: JSON.parse(lastSync.details || '{}') } : null,
+ counts: { files: fileCt, versions: versionCt, sessions: sessionCt, comments: commentCt, components: componentCt },
+ });
+});
+
+// ──────────────────────────────────────────
+// API: Config
+// ──────────────────────────────────────────
+
+app.get('/api/config', (req, res) => {
+ res.json({
+ figma_team_id: getConfig('figma_team_id') || process.env.FIGMA_TEAM_ID || '',
+ has_pat: !!(getConfig('figma_pat') || process.env.FIGMA_PAT),
+ sync_interval: getConfig('sync_interval') || process.env.SYNC_INTERVAL || '15',
+ });
+});
+
+app.post('/api/config', async (req, res) => {
+ const { figma_team_id, figma_pat, sync_interval } = req.body;
+
+ if (figma_team_id) setConfig('figma_team_id', figma_team_id);
+ if (figma_pat) {
+ setConfig('figma_pat', figma_pat);
+ setToken(figma_pat);
+ }
+ if (sync_interval) setConfig('sync_interval', sync_interval);
+
+ // Test connection if both are set
+ const teamId = figma_team_id || getConfig('figma_team_id') || process.env.FIGMA_TEAM_ID;
+ if (teamId && (figma_pat || getConfig('figma_pat') || process.env.FIGMA_PAT)) {
+ const result = await testConnection(teamId);
+ return res.json({ saved: true, connection: result });
+ }
+
+ res.json({ saved: true });
+});
+
+// ──────────────────────────────────────────
+// API: Sync
+// ──────────────────────────────────────────
+
+app.post('/api/sync', async (req, res) => {
+ if (isSyncing()) return res.json({ status: 'already_running' });
+
+ // Don't await — run in background
+ fullSync().catch(err => console.error('[api] Sync error:', err));
+ res.json({ status: 'started' });
+});
+
+// ──────────────────────────────────────────
+// API: Sessions (Time Intelligence)
+// ──────────────────────────────────────────
+
+app.get('/api/sessions', (req, res) => {
+ const { designer, from, to, project, limit = '200' } = req.query;
+ const db = getDb();
+
+ let sql = 'SELECT * FROM design_sessions WHERE 1=1';
+ const params = [];
+
+ if (designer) { sql += ' AND designer_name = ?'; params.push(designer); }
+ if (from) { sql += ' AND start_time >= ?'; params.push(from); }
+ if (to) { sql += ' AND end_time <= ?'; params.push(to); }
+ if (project) { sql += ' AND project_name LIKE ?'; params.push(`%${project}%`); }
+
+ sql += ' ORDER BY start_time DESC LIMIT ?';
+ params.push(parseInt(limit));
+
+ res.json(db.prepare(sql).all(...params));
+});
+
+app.get('/api/sessions/summary', (req, res) => {
+ const db = getDb();
+ const { period = 'week' } = req.query;
+
+ const cutoff = period === 'month'
+ ? new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString()
+ : new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
+
+ const byDesigner = db.prepare(`
+ SELECT designer_name, SUM(duration_minutes) as total_minutes, COUNT(*) as session_count,
+ AVG(duration_minutes) as avg_session_min, COUNT(DISTINCT file_key) as files_touched
+ FROM design_sessions
+ WHERE start_time >= ?
+ GROUP BY designer_name
+ ORDER BY total_minutes DESC
+ `).all(cutoff);
+
+ const total = db.prepare(`
+ SELECT SUM(duration_minutes) as total_minutes, COUNT(*) as session_count,
+ COUNT(DISTINCT designer_name) as active_designers
+ FROM design_sessions
+ WHERE start_time >= ?
+ `).get(cutoff);
+
+ const byDay = db.prepare(`
+ SELECT DATE(start_time) as day, SUM(duration_minutes) as minutes, COUNT(*) as sessions
+ FROM design_sessions
+ WHERE start_time >= ?
+ GROUP BY DATE(start_time)
+ ORDER BY day
+ `).all(cutoff);
+
+ res.json({ period, cutoff, total, byDesigner, byDay });
+});
+
+// ──────────────────────────────────────────
+// API: Designers
+// ──────────────────────────────────────────
+
+app.get('/api/designers', (req, res) => {
+ const db = getDb();
+ const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
+ const monthAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
+
+ const designers = db.prepare(`
+ SELECT
+ designer_name,
+ designer_id,
+ SUM(duration_minutes) as total_minutes,
+ COUNT(*) as total_sessions,
+ COUNT(DISTINCT file_key) as total_files,
+ COUNT(DISTINCT project_name) as total_projects,
+ MAX(end_time) as last_active,
+ AVG(duration_minutes) as avg_session_min
+ FROM design_sessions
+ GROUP BY designer_name
+ ORDER BY total_minutes DESC
+ `).all();
+
+ // Enrich with weekly hours
+ for (const d of designers) {
+ const week = db.prepare(`
+ SELECT SUM(duration_minutes) as week_min FROM design_sessions
+ WHERE designer_name = ? AND start_time >= ?
+ `).get(d.designer_name, weekAgo);
+ d.week_minutes = week?.week_min || 0;
+
+ const month = db.prepare(`
+ SELECT SUM(duration_minutes) as month_min FROM design_sessions
+ WHERE designer_name = ? AND start_time >= ?
+ `).get(d.designer_name, monthAgo);
+ d.month_minutes = month?.month_min || 0;
+
+ // Active days this month
+ const days = db.prepare(`
+ SELECT COUNT(DISTINCT DATE(start_time)) as days FROM design_sessions
+ WHERE designer_name = ? AND start_time >= ?
+ `).get(d.designer_name, monthAgo);
+ d.active_days_month = days?.days || 0;
+ }
+
+ res.json(designers);
+});
+
+app.get('/api/designers/:name/pattern', (req, res) => {
+ const db = getDb();
+ const { name } = req.params;
+ const monthAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
+
+ // 7x24 heatmap data: day-of-week x hour-of-day
+ const sessions = db.prepare(`
+ SELECT start_time, duration_minutes FROM design_sessions
+ WHERE designer_name = ? AND start_time >= ?
+ `).all(name, monthAgo);
+
+ const grid = Array.from({ length: 7 }, () => Array(24).fill(0));
+ for (const s of sessions) {
+ const d = new Date(s.start_time);
+ grid[d.getDay()][d.getHours()] += s.duration_minutes;
+ }
+
+ res.json({ designer: name, period: '30d', grid });
+});
+
+// ──────────────────────────────────────────
+// API: Projects (enhanced)
+// ──────────────────────────────────────────
+
+app.get('/api/projects', (req, res) => {
+ const db = getDb();
+ const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
+
+ const projects = db.prepare(`
+ SELECT project_name, COUNT(*) as file_count, MAX(last_modified) as last_activity
+ FROM figma_files
+ WHERE project_name IS NOT NULL
+ GROUP BY project_name
+ ORDER BY last_activity DESC
+ `).all();
+
+ for (const p of projects) {
+ const hours = db.prepare(`
+ SELECT SUM(duration_minutes) as mins FROM design_sessions
+ WHERE project_name = ? AND start_time >= ?
+ `).get(p.project_name, weekAgo);
+ p.week_minutes = hours?.mins || 0;
+
+ const comments = db.prepare(`
+ SELECT COUNT(*) as n FROM figma_comments c
+ JOIN figma_files f ON c.file_key = f.file_key
+ WHERE f.project_name = ? AND c.resolved_at IS NULL
+ `).get(p.project_name);
+ p.unresolved_comments = comments?.n || 0;
+ }
+
+ res.json(projects);
+});
+
+// ──────────────────────────────────────────
+// API: Comments (Collaboration)
+// ──────────────────────────────────────────
+
+app.get('/api/comments', (req, res) => {
+ const { unresolved, file, limit = '50' } = req.query;
+ const db = getDb();
+
+ let sql = `
+ SELECT c.*, f.name as file_name, f.project_name
+ FROM figma_comments c
+ LEFT JOIN figma_files f ON c.file_key = f.file_key
+ WHERE 1=1
+ `;
+ const params = [];
+
+ if (unresolved === 'true') { sql += ' AND c.resolved_at IS NULL'; }
+ if (file) { sql += ' AND c.file_key = ?'; params.push(file); }
+
+ sql += ' ORDER BY c.created_at DESC LIMIT ?';
+ params.push(parseInt(limit));
+
+ res.json(db.prepare(sql).all(...params));
+});
+
+app.get('/api/comments/metrics', (req, res) => {
+ const db = getDb();
+ const monthAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
+
+ const total = db.prepare('SELECT COUNT(*) as n FROM figma_comments WHERE created_at >= ?').get(monthAgo).n;
+ const unresolved = db.prepare('SELECT COUNT(*) as n FROM figma_comments WHERE resolved_at IS NULL AND created_at >= ?').get(monthAgo).n;
+
+ const avgResolution = db.prepare(`
+ SELECT AVG((julianday(resolved_at) - julianday(created_at)) * 24) as avg_hours
+ FROM figma_comments
+ WHERE resolved_at IS NOT NULL AND created_at >= ?
+ `).get(monthAgo);
+
+ const byWeek = db.prepare(`
+ SELECT strftime('%Y-W%W', created_at) as week, COUNT(*) as comments
+ FROM figma_comments
+ WHERE created_at >= ?
+ GROUP BY week ORDER BY week
+ `).all(monthAgo);
+
+ res.json({
+ total,
+ unresolved,
+ avgResolutionHours: Math.round(avgResolution?.avg_hours || 0),
+ byWeek,
+ });
+});
+
+// ──────────────────────────────────────────
+// API: Components (Design System Health)
+// ──────────────────────────────────────────
+
+app.get('/api/components', (req, res) => {
+ const db = getDb();
+ const components = db.prepare(`
+ SELECT *,
+ CAST((julianday('now') - julianday(COALESCE(updated_at, created_at))) AS INTEGER) as days_since_update
+ FROM figma_components
+ ORDER BY updated_at DESC
+ `).all();
+
+ for (const c of components) {
+ const days = c.days_since_update || 999;
+ c.health = days <= 30 ? 'healthy' : days <= 90 ? 'aging' : 'stale';
+ c.health_score = Math.max(0, 100 - Math.floor(days * 0.8));
+ }
+
+ const total = components.length;
+ const healthy = components.filter(c => c.health === 'healthy').length;
+ const withDesc = components.filter(c => c.description).length;
+
+ res.json({
+ components,
+ summary: {
+ total,
+ healthy,
+ aging: components.filter(c => c.health === 'aging').length,
+ stale: components.filter(c => c.health === 'stale').length,
+ descriptionCoverage: total ? Math.round((withDesc / total) * 100) : 0,
+ avgHealthScore: total ? Math.round(components.reduce((s, c) => s + c.health_score, 0) / total) : 0,
+ },
+ });
+});
+
+// ──────────────────────────────────────────
+// API: Figma data for frontend (replaces data.json)
+// ──────────────────────────────────────────
+
+app.get('/api/dashboard', (req, res) => {
+ const db = getDb();
+
+ const files = db.prepare('SELECT COUNT(*) as n FROM figma_files').get().n;
+ const activeFiles = db.prepare(`SELECT COUNT(*) as n FROM figma_files WHERE last_modified >= datetime('now', '-90 days')`).get().n;
+ const designers = db.prepare('SELECT COUNT(DISTINCT designer_name) as n FROM design_sessions').get().n;
+ const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
+ const weekHours = db.prepare(`SELECT SUM(duration_minutes) as mins FROM design_sessions WHERE start_time >= ?`).get(weekAgo);
+ const activeToday = db.prepare(`SELECT COUNT(DISTINCT designer_name) as n FROM design_sessions WHERE DATE(start_time) = DATE('now')`).get().n;
+
+ const projects = db.prepare('SELECT COUNT(DISTINCT project_name) as n FROM figma_files WHERE project_name IS NOT NULL').get().n;
+
+ // Monthly timeline from sessions
+ const timeline = db.prepare(`
+ SELECT strftime('%Y-%m', start_time) as month, SUM(duration_minutes) as minutes, COUNT(*) as sessions
+ FROM design_sessions
+ GROUP BY month ORDER BY month
+ `).all();
+
+ // Recent activity
+ const recentSessions = db.prepare(`
+ SELECT designer_name, file_name, project_name, start_time, duration_minutes, confidence
+ FROM design_sessions
+ ORDER BY start_time DESC LIMIT 50
+ `).all();
+
+ res.json({
+ summary: {
+ total_projects: projects,
+ total_files: files,
+ active_files: activeFiles,
+ stale_files: files - activeFiles,
+ total_designers: designers,
+ design_hours_week: Math.round((weekHours?.mins || 0) / 60),
+ active_designers_today: activeToday,
+ },
+ timeline,
+ recent_sessions: recentSessions,
+ });
+});
+
+// ──────────────────────────────────────────
+// Webhook endpoint
+// ──────────────────────────────────────────
+
+app.post('/api/webhook', (req, res) => {
+ const { event_type, file_key, timestamp } = req.body;
+ console.log(`[webhook] ${event_type} for ${file_key} at ${timestamp}`);
+
+ // Trigger targeted re-sync in background
+ if (event_type === 'FILE_UPDATE' || event_type === 'FILE_VERSION_UPDATE') {
+ // Could do a targeted file-only sync here
+ console.log(`[webhook] Would refresh file ${file_key}`);
+ }
+
+ res.sendStatus(200);
+});
+
+// ──────────────────────────────────────────
+// Initialize and start
+// ──────────────────────────────────────────
+
+function init() {
+ // Set Figma token from config or env
+ const pat = getConfig('figma_pat') || process.env.FIGMA_PAT;
+ if (pat) setToken(pat);
+
+ // Schedule periodic sync
+ const interval = parseInt(getConfig('sync_interval') || process.env.SYNC_INTERVAL || '15', 10);
+ cron.schedule(`*/${interval} * * * *`, () => {
+ console.log('[cron] Running scheduled sync');
+ fullSync().catch(err => console.error('[cron] Sync failed:', err));
+ });
+
+ console.log(`[gravity] Sync scheduled every ${interval} minutes`);
+}
+
+init();
+
+app.listen(PORT, () => {
+ console.log(`[gravity] Server running at http://localhost:${PORT}`);
+ console.log(`[gravity] Dashboard: http://localhost:${PORT}/index.html`);
+});
diff --git a/miniverse/gravity/server/schema.sql b/miniverse/gravity/server/schema.sql
new file mode 100644
index 0000000..c7e240b
--- /dev/null
+++ b/miniverse/gravity/server/schema.sql
@@ -0,0 +1,89 @@
+-- Gravity v2 Schema
+
+CREATE TABLE IF NOT EXISTS figma_files (
+ file_key TEXT PRIMARY KEY,
+ name TEXT NOT NULL,
+ project_name TEXT,
+ project_id TEXT,
+ last_modified TEXT NOT NULL,
+ thumbnail_url TEXT,
+ version_count INTEGER DEFAULT 0,
+ synced_at TEXT NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS figma_versions (
+ id TEXT NOT NULL,
+ file_key TEXT NOT NULL REFERENCES figma_files(file_key),
+ user_id TEXT NOT NULL,
+ user_name TEXT NOT NULL,
+ label TEXT,
+ description TEXT,
+ created_at TEXT NOT NULL,
+ synced_at TEXT NOT NULL,
+ PRIMARY KEY (file_key, id)
+);
+
+CREATE TABLE IF NOT EXISTS design_sessions (
+ id TEXT PRIMARY KEY,
+ designer_name TEXT NOT NULL,
+ designer_id TEXT,
+ file_key TEXT NOT NULL,
+ file_name TEXT,
+ project_name TEXT,
+ start_time TEXT NOT NULL,
+ end_time TEXT NOT NULL,
+ duration_minutes INTEGER NOT NULL,
+ version_count INTEGER NOT NULL,
+ confidence TEXT NOT NULL CHECK(confidence IN ('high','medium','low'))
+);
+
+CREATE TABLE IF NOT EXISTS figma_comments (
+ id TEXT PRIMARY KEY,
+ file_key TEXT NOT NULL,
+ user_name TEXT NOT NULL,
+ user_id TEXT NOT NULL,
+ message TEXT,
+ parent_id TEXT,
+ order_id TEXT,
+ created_at TEXT NOT NULL,
+ resolved_at TEXT,
+ synced_at TEXT NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS figma_components (
+ key TEXT PRIMARY KEY,
+ name TEXT NOT NULL,
+ description TEXT,
+ file_key TEXT NOT NULL,
+ containing_frame TEXT,
+ thumbnail_url TEXT,
+ created_at TEXT,
+ updated_at TEXT,
+ synced_at TEXT NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS sync_log (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ event_type TEXT NOT NULL,
+ status TEXT NOT NULL CHECK(status IN ('started','completed','failed')),
+ details TEXT,
+ started_at TEXT NOT NULL,
+ completed_at TEXT,
+ duration_ms INTEGER
+);
+
+CREATE TABLE IF NOT EXISTS config (
+ key TEXT PRIMARY KEY,
+ value TEXT NOT NULL,
+ updated_at TEXT NOT NULL
+);
+
+-- Indexes for common queries
+CREATE INDEX IF NOT EXISTS idx_versions_file ON figma_versions(file_key);
+CREATE INDEX IF NOT EXISTS idx_versions_user ON figma_versions(user_name);
+CREATE INDEX IF NOT EXISTS idx_versions_created ON figma_versions(created_at);
+CREATE INDEX IF NOT EXISTS idx_sessions_designer ON design_sessions(designer_name);
+CREATE INDEX IF NOT EXISTS idx_sessions_file ON design_sessions(file_key);
+CREATE INDEX IF NOT EXISTS idx_sessions_start ON design_sessions(start_time);
+CREATE INDEX IF NOT EXISTS idx_comments_file ON figma_comments(file_key);
+CREATE INDEX IF NOT EXISTS idx_comments_resolved ON figma_comments(resolved_at);
diff --git a/miniverse/gravity/server/session-engine.js b/miniverse/gravity/server/session-engine.js
new file mode 100644
index 0000000..c99577d
--- /dev/null
+++ b/miniverse/gravity/server/session-engine.js
@@ -0,0 +1,101 @@
+import { getDb } from './db.js';
+import { randomUUID } from 'crypto';
+
+const SESSION_GAP_MS = 30 * 60 * 1000; // 30 minutes
+const MIN_DURATION_MIN = 15; // minimum session estimate
+
+/**
+ * Reconstruct design sessions from version history.
+ * Groups consecutive versions by same user on same file within 30-min gaps.
+ */
+export function reconstructSessions(fileKey) {
+ const db = getDb();
+
+ // Get all versions for this file, ordered by time
+ const versions = db.prepare(`
+ SELECT id, file_key, user_id, user_name, created_at
+ FROM figma_versions
+ WHERE file_key = ?
+ ORDER BY created_at ASC
+ `).all(fileKey);
+
+ if (!versions.length) return 0;
+
+ // Get file name for session records
+ const file = db.prepare('SELECT name, project_name FROM figma_files WHERE file_key = ?').get(fileKey);
+ const fileName = file?.name || fileKey;
+ const projectName = file?.project_name || null;
+
+ // Delete old sessions for this file
+ db.prepare('DELETE FROM design_sessions WHERE file_key = ?').run(fileKey);
+
+ // Group into sessions
+ const sessions = [];
+ let current = null;
+
+ for (const v of versions) {
+ const ts = new Date(v.created_at).getTime();
+
+ if (!current || current.user_id !== v.user_id || ts - current.endMs > SESSION_GAP_MS) {
+ // Start new session
+ if (current) sessions.push(current);
+ current = {
+ user_id: v.user_id,
+ user_name: v.user_name,
+ startMs: ts,
+ endMs: ts,
+ count: 1,
+ };
+ } else {
+ // Extend current session
+ current.endMs = ts;
+ current.count++;
+ }
+ }
+ if (current) sessions.push(current);
+
+ // Insert sessions
+ const insert = db.prepare(`
+ INSERT INTO design_sessions (id, designer_name, designer_id, file_key, file_name, project_name,
+ start_time, end_time, duration_minutes, version_count, confidence)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ `);
+
+ const tx = db.transaction(() => {
+ for (const s of sessions) {
+ const durationMs = s.endMs - s.startMs;
+ const durationMin = Math.max(Math.round(durationMs / 60000), MIN_DURATION_MIN);
+ const confidence = s.count >= 5 ? 'high' : s.count >= 2 ? 'medium' : 'low';
+
+ insert.run(
+ randomUUID(),
+ s.user_name,
+ s.user_id,
+ fileKey,
+ fileName,
+ projectName,
+ new Date(s.startMs).toISOString(),
+ new Date(s.endMs).toISOString(),
+ durationMin,
+ s.count,
+ confidence
+ );
+ }
+ });
+
+ tx();
+ return sessions.length;
+}
+
+/**
+ * Reconstruct sessions for all synced files.
+ */
+export function reconstructAllSessions() {
+ const db = getDb();
+ const files = db.prepare('SELECT file_key FROM figma_files').all();
+ let total = 0;
+ for (const f of files) {
+ total += reconstructSessions(f.file_key);
+ }
+ return total;
+}
diff --git a/miniverse/gravity/server/sync-engine.js b/miniverse/gravity/server/sync-engine.js
new file mode 100644
index 0000000..9e45bad
--- /dev/null
+++ b/miniverse/gravity/server/sync-engine.js
@@ -0,0 +1,176 @@
+import { getDb, logSync, completeSync, getConfig, setConfig } from './db.js';
+import * as figma from './figma-client.js';
+import { reconstructAllSessions } from './session-engine.js';
+
+let syncing = false;
+
+export function isSyncing() { return syncing; }
+
+/**
+ * Full sync: teams → projects → files → versions → comments → components → sessions
+ */
+export async function fullSync() {
+ if (syncing) {
+ console.log('[sync] Already running, skipping');
+ return { skipped: true };
+ }
+
+ const teamId = getConfig('figma_team_id') || process.env.FIGMA_TEAM_ID;
+ if (!teamId) throw new Error('Figma team ID not configured');
+
+ syncing = true;
+ const startedAt = new Date().toISOString();
+ const syncId = logSync('full_sync', 'started');
+ const counts = { projects: 0, files: 0, versions: 0, comments: 0, components: 0, sessions: 0 };
+
+ try {
+ console.log('[sync] Starting full sync for team', teamId);
+ const db = getDb();
+
+ // 1. Get all projects
+ const projects = await figma.getTeamProjects(teamId);
+ counts.projects = projects.length;
+ console.log(`[sync] Found ${projects.length} projects`);
+
+ // 2. Get files for each project
+ const upsertFile = db.prepare(`
+ INSERT INTO figma_files (file_key, name, project_name, project_id, last_modified, thumbnail_url, synced_at)
+ VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
+ ON CONFLICT(file_key) DO UPDATE SET
+ name = excluded.name,
+ project_name = excluded.project_name,
+ last_modified = excluded.last_modified,
+ thumbnail_url = excluded.thumbnail_url,
+ synced_at = excluded.synced_at
+ `);
+
+ const allFiles = [];
+ for (const proj of projects) {
+ try {
+ const files = await figma.getProjectFiles(proj.id);
+ for (const f of files) {
+ upsertFile.run(f.key, f.name, proj.name, String(proj.id), f.last_modified, f.thumbnail_url || null);
+ allFiles.push({ key: f.key, lastModified: f.last_modified });
+ }
+ counts.files += files.length;
+ } catch (err) {
+ console.warn(`[sync] Failed to get files for project ${proj.name}:`, err.message);
+ }
+ }
+ console.log(`[sync] Found ${allFiles.length} files across ${projects.length} projects`);
+
+ // 3. Get versions for recently modified files (last 90 days)
+ const cutoff = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString();
+ const recentFiles = allFiles.filter(f => f.lastModified >= cutoff);
+ console.log(`[sync] Fetching versions for ${recentFiles.length} recently modified files`);
+
+ const upsertVersion = db.prepare(`
+ INSERT INTO figma_versions (id, file_key, user_id, user_name, label, description, created_at, synced_at)
+ VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))
+ ON CONFLICT(file_key, id) DO UPDATE SET
+ label = excluded.label,
+ synced_at = excluded.synced_at
+ `);
+
+ for (const f of recentFiles) {
+ try {
+ const versions = await figma.getFileVersions(f.key, 2); // 2 pages = ~50 versions
+ const tx = db.transaction(() => {
+ for (const v of versions) {
+ upsertVersion.run(
+ String(v.id), f.key,
+ v.user?.id || 'unknown', v.user?.handle || 'Unknown',
+ v.label || null, v.description || null,
+ v.created_at
+ );
+ }
+ });
+ tx();
+ counts.versions += versions.length;
+
+ // Update version count
+ db.prepare('UPDATE figma_files SET version_count = ? WHERE file_key = ?')
+ .run(versions.length, f.key);
+ } catch (err) {
+ console.warn(`[sync] Failed to get versions for ${f.key}:`, err.message);
+ }
+ }
+ console.log(`[sync] Synced ${counts.versions} versions`);
+
+ // 4. Get comments for recent files
+ const commentFiles = recentFiles.slice(0, 50); // Limit to avoid rate limits
+ const upsertComment = db.prepare(`
+ INSERT INTO figma_comments (id, file_key, user_name, user_id, message, parent_id, created_at, resolved_at, synced_at)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
+ ON CONFLICT(id) DO UPDATE SET
+ resolved_at = excluded.resolved_at,
+ synced_at = excluded.synced_at
+ `);
+
+ for (const f of commentFiles) {
+ try {
+ const comments = await figma.getFileComments(f.key);
+ const tx = db.transaction(() => {
+ for (const c of comments) {
+ upsertComment.run(
+ c.id, f.key,
+ c.user?.handle || 'Unknown', c.user?.id || 'unknown',
+ c.message || '', c.parent_id || null,
+ c.created_at, c.resolved_at || null
+ );
+ }
+ });
+ tx();
+ counts.comments += comments.length;
+ } catch (err) {
+ console.warn(`[sync] Failed to get comments for ${f.key}:`, err.message);
+ }
+ }
+ console.log(`[sync] Synced ${counts.comments} comments`);
+
+ // 5. Get team components
+ try {
+ const components = await figma.getTeamComponents(teamId);
+ const upsertComp = db.prepare(`
+ INSERT INTO figma_components (key, name, description, file_key, containing_frame, thumbnail_url, created_at, updated_at, synced_at)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
+ ON CONFLICT(key) DO UPDATE SET
+ name = excluded.name,
+ description = excluded.description,
+ thumbnail_url = excluded.thumbnail_url,
+ updated_at = excluded.updated_at,
+ synced_at = excluded.synced_at
+ `);
+ const tx = db.transaction(() => {
+ for (const c of components) {
+ upsertComp.run(
+ c.key, c.name, c.description || null,
+ c.file_key, c.containing_frame?.name || null,
+ c.thumbnail_url || null,
+ c.created_at || null, c.updated_at || null
+ );
+ }
+ });
+ tx();
+ counts.components = components.length;
+ console.log(`[sync] Synced ${components.length} components`);
+ } catch (err) {
+ console.warn('[sync] Failed to get components:', err.message);
+ }
+
+ // 6. Reconstruct sessions from version history
+ counts.sessions = reconstructAllSessions();
+ console.log(`[sync] Reconstructed ${counts.sessions} design sessions`);
+
+ setConfig('last_sync', new Date().toISOString());
+ completeSync(syncId, 'completed', counts, startedAt);
+ console.log('[sync] Full sync completed:', counts);
+ return counts;
+ } catch (err) {
+ completeSync(syncId, 'failed', { error: err.message }, startedAt);
+ console.error('[sync] Failed:', err);
+ throw err;
+ } finally {
+ syncing = false;
+ }
+}
diff --git a/miniverse/gravity/vercel.json b/miniverse/gravity/vercel.json
new file mode 100644
index 0000000..d072d26
--- /dev/null
+++ b/miniverse/gravity/vercel.json
@@ -0,0 +1,28 @@
+{
+ "version": 2,
+ "builds": [
+ {
+ "src": "api/index.js",
+ "use": "@vercel/node"
+ },
+ {
+ "src": "index.html",
+ "use": "@vercel/static"
+ },
+ {
+ "src": "data.json",
+ "use": "@vercel/static"
+ }
+ ],
+ "routes": [
+ { "src": "/api/(.*)", "dest": "/api/index.js" },
+ { "src": "/data.json", "dest": "/data.json" },
+ { "src": "/(.*)", "dest": "/index.html" }
+ ],
+ "crons": [
+ {
+ "path": "/api/cron",
+ "schedule": "*/5 * * * *"
+ }
+ ]
+}
From c156b79dd9380824d3184b1421f5d9b63a6f41ba Mon Sep 17 00:00:00 2001
From: 0xbeam
Date: Mon, 16 Mar 2026 11:35:25 +0530
Subject: [PATCH 02/27] fix: update Vercel cron to daily for Hobby plan
Co-Authored-By: Claude Opus 4.6
---
miniverse/gravity/vercel.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/miniverse/gravity/vercel.json b/miniverse/gravity/vercel.json
index d072d26..8ab9a07 100644
--- a/miniverse/gravity/vercel.json
+++ b/miniverse/gravity/vercel.json
@@ -22,7 +22,7 @@
"crons": [
{
"path": "/api/cron",
- "schedule": "*/5 * * * *"
+ "schedule": "0 8 * * *"
}
]
}
From d3810d8dc00830ad9179f9713eb351b3414db3e2 Mon Sep 17 00:00:00 2001
From: 0xbeam
Date: Mon, 16 Mar 2026 12:04:17 +0530
Subject: [PATCH 03/27] feat: add OAuth login, seed data for Vercel, release
log
- Google OAuth and Figma OAuth login with session management
- Login screen with provider buttons, user badge in header
- Auth middleware protects API routes (passthrough when no OAuth configured)
- Seed data system: exports DB snapshot, auto-loads on Vercel cold start
- Release log tab in settings modal (v2.1.0, v2.0.0, v1.0.0)
- Last sync time KPI card with exact timestamp tooltip
- Settings tabs: Configuration + Release Log
- cookie-parser dependency for session cookies
- Updated .env.example with OAuth configuration vars
Co-Authored-By: Claude Opus 4.6
---
miniverse/gravity/.env.example | 15 ++-
miniverse/gravity/.gitignore | 1 +
miniverse/gravity/api/index.js | 69 ++++++++++
miniverse/gravity/index.html | 198 +++++++++++++++++++++++++++-
miniverse/gravity/package-lock.json | 20 +++
miniverse/gravity/package.json | 1 +
miniverse/gravity/seed.json | 1 +
miniverse/gravity/server/auth.js | 116 ++++++++++++++++
miniverse/gravity/server/db.js | 43 +++++-
miniverse/gravity/server/index.js | 82 ++++++++++++
miniverse/gravity/server/schema.sql | 17 +++
11 files changed, 560 insertions(+), 3 deletions(-)
create mode 100644 miniverse/gravity/seed.json
create mode 100644 miniverse/gravity/server/auth.js
diff --git a/miniverse/gravity/.env.example b/miniverse/gravity/.env.example
index 4a948b8..ef5e0ac 100644
--- a/miniverse/gravity/.env.example
+++ b/miniverse/gravity/.env.example
@@ -6,4 +6,17 @@ FIGMA_TEAM_ID=your_team_id_here
PORT=3847
# Sync interval in minutes (default: 15)
-SYNC_INTERVAL=15
+SYNC_INTERVAL=5
+
+# OAuth — Login with Google (optional)
+# Create at: https://console.cloud.google.com/apis/credentials
+GOOGLE_CLIENT_ID=
+GOOGLE_CLIENT_SECRET=
+
+# OAuth — Login with Figma (optional)
+# Create at: https://www.figma.com/developers/apps
+FIGMA_CLIENT_ID=
+FIGMA_CLIENT_SECRET=
+
+# Vercel Cron Secret (optional)
+CRON_SECRET=
diff --git a/miniverse/gravity/.gitignore b/miniverse/gravity/.gitignore
index 7df938d..26ddc4e 100644
--- a/miniverse/gravity/.gitignore
+++ b/miniverse/gravity/.gitignore
@@ -3,3 +3,4 @@ server/gravity.db
server/gravity.db-shm
server/gravity.db-wal
.env
+.vercel
diff --git a/miniverse/gravity/api/index.js b/miniverse/gravity/api/index.js
index 793ee54..c7cdf93 100644
--- a/miniverse/gravity/api/index.js
+++ b/miniverse/gravity/api/index.js
@@ -1,17 +1,86 @@
import 'dotenv/config';
import express from 'express';
import cors from 'cors';
+import cookieParser from 'cookie-parser';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { getDb, getConfig, setConfig, getLastSync } from '../server/db.js';
import { setToken, testConnection } from '../server/figma-client.js';
import { fullSync, isSyncing } from '../server/sync-engine.js';
+import { requireAuth, createSession, getSession, deleteSession, googleCallback, figmaCallback } from '../server/auth.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
const app = express();
app.use(cors());
app.use(express.json());
+app.use(cookieParser());
+
+// ── Auth routes (public) ──
+app.get('/api/auth/status', (req, res) => {
+ const authEnabled = !!(process.env.GOOGLE_CLIENT_ID || process.env.FIGMA_CLIENT_ID);
+ const token = req.cookies?.gravity_session || req.headers['x-auth-token'];
+ const session = getSession(token);
+ res.json({
+ auth_enabled: authEnabled,
+ logged_in: !!session,
+ user: session ? { name: session.user_name, email: session.user_email, avatar: session.user_avatar, provider: session.provider } : null,
+ providers: { google: !!process.env.GOOGLE_CLIENT_ID, figma: !!process.env.FIGMA_CLIENT_ID },
+ });
+});
+
+app.get('/api/auth/google', (req, res) => {
+ if (!process.env.GOOGLE_CLIENT_ID) return res.status(400).json({ error: 'Google OAuth not configured' });
+ const base = `${req.protocol}://${req.get('host')}`;
+ const redirectUri = `${base}/api/auth/google/callback`;
+ const url = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${encodeURIComponent(process.env.GOOGLE_CLIENT_ID)}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=openid%20email%20profile&access_type=offline`;
+ res.redirect(url);
+});
+
+app.get('/api/auth/google/callback', async (req, res) => {
+ try {
+ const base = `${req.protocol}://${req.get('host')}`;
+ const user = await googleCallback(req.query.code, `${base}/api/auth/google/callback`);
+ const token = createSession('google', user);
+ res.cookie('gravity_session', token, { httpOnly: true, secure: true, sameSite: 'lax', maxAge: 30 * 24 * 60 * 60 * 1000 });
+ res.redirect('/');
+ } catch (e) {
+ res.redirect('/?auth_error=' + encodeURIComponent(e.message));
+ }
+});
+
+app.get('/api/auth/figma', (req, res) => {
+ if (!process.env.FIGMA_CLIENT_ID) return res.status(400).json({ error: 'Figma OAuth not configured' });
+ const base = `${req.protocol}://${req.get('host')}`;
+ const redirectUri = `${base}/api/auth/figma/callback`;
+ const url = `https://www.figma.com/oauth?client_id=${encodeURIComponent(process.env.FIGMA_CLIENT_ID)}&redirect_uri=${encodeURIComponent(redirectUri)}&scope=file_read&state=gravity&response_type=code`;
+ res.redirect(url);
+});
+
+app.get('/api/auth/figma/callback', async (req, res) => {
+ try {
+ const base = `${req.protocol}://${req.get('host')}`;
+ const user = await figmaCallback(req.query.code, `${base}/api/auth/figma/callback`);
+ const token = createSession('figma', user);
+ res.cookie('gravity_session', token, { httpOnly: true, secure: true, sameSite: 'lax', maxAge: 30 * 24 * 60 * 60 * 1000 });
+ res.redirect('/');
+ } catch (e) {
+ res.redirect('/?auth_error=' + encodeURIComponent(e.message));
+ }
+});
+
+app.post('/api/auth/logout', (req, res) => {
+ const token = req.cookies?.gravity_session;
+ if (token) deleteSession(token);
+ res.clearCookie('gravity_session');
+ res.json({ ok: true });
+});
+
+// ── Auth middleware for protected routes ──
+app.use('/api', (req, res, next) => {
+ if (req.path.startsWith('/auth/') || req.path === '/cron' || req.path === '/webhook') return next();
+ requireAuth(req, res, next);
+});
// ── Status ──
app.get('/api/status', (req, res) => {
diff --git a/miniverse/gravity/index.html b/miniverse/gravity/index.html
index 933f33b..1629160 100644
--- a/miniverse/gravity/index.html
+++ b/miniverse/gravity/index.html
@@ -913,6 +913,110 @@
/* ===== EXPAND CHART ===== */
.expand-chart-wrap { width: 100%; height: 180px; margin-top: 8px; }
+/* ===== LOGIN SCREEN ===== */
+.login-overlay {
+ display: none;
+ position: fixed;
+ inset: 0;
+ background: var(--bg);
+ z-index: 300;
+ align-items: center;
+ justify-content: center;
+}
+.login-overlay.active { display: flex; }
+.login-card {
+ text-align: center;
+ max-width: 380px;
+ width: 90%;
+}
+.login-logo {
+ font-family: var(--mono);
+ font-weight: 500;
+ font-size: 0.72rem;
+ letter-spacing: .1em;
+ text-transform: uppercase;
+ color: var(--text);
+ margin-bottom: 6px;
+}
+.login-logo .accent { color: var(--accent); }
+.login-title {
+ font-family: var(--serif);
+ font-size: 1.8rem;
+ font-weight: 300;
+ color: var(--text);
+ margin: 20px 0 8px;
+}
+.login-sub {
+ font-size: 0.78rem;
+ color: var(--text-muted);
+ margin-bottom: 32px;
+}
+.login-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ width: 100%;
+ padding: 12px 20px;
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ background: var(--bg);
+ color: var(--text);
+ font-family: var(--font);
+ font-size: 0.82rem;
+ font-weight: 400;
+ cursor: pointer;
+ transition: all var(--transition);
+ margin-bottom: 10px;
+ text-decoration: none;
+}
+.login-btn:hover { border-color: var(--accent); background: var(--accent-dim); }
+.login-btn svg { width: 18px; height: 18px; flex-shrink: 0; }
+.login-divider {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin: 16px 0;
+ color: var(--text-faint);
+ font-size: 0.62rem;
+ font-family: var(--mono);
+ letter-spacing: .04em;
+ text-transform: uppercase;
+}
+.login-divider::before, .login-divider::after {
+ content: '';
+ flex: 1;
+ height: 1px;
+ background: var(--border);
+}
+.login-error {
+ background: var(--red-dim);
+ color: var(--red);
+ font-size: 0.72rem;
+ padding: 8px 12px;
+ border-radius: var(--radius);
+ margin-bottom: 16px;
+}
+.user-badge {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 0.68rem;
+ font-family: var(--mono);
+ color: var(--text-muted);
+ cursor: pointer;
+ padding: 3px 8px;
+ border-radius: var(--radius);
+ transition: all var(--transition);
+}
+.user-badge:hover { background: var(--card); }
+.user-badge img {
+ width: 22px; height: 22px;
+ border-radius: 50%;
+ border: 1px solid var(--border);
+}
+.user-badge-name { max-width: 100px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+
/* ===== FOOTER ===== */
.footer {
text-align: center;
@@ -979,6 +1083,27 @@
+
+
+
+
Spacekayak Gravity
+
Design Operations
+
Sign in to access your team's Figma analytics dashboard
+
+
+
+
+
@@ -1231,6 +1360,7 @@
const API = {
async get(path) {
const res = await fetch(path);
+ if (res.status === 401) { window.location.reload(); throw new Error('Session expired'); }
if (!res.ok) throw new Error(`API ${res.status}`);
return res.json();
},
@@ -1240,6 +1370,7 @@
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
+ if (res.status === 401) { window.location.reload(); throw new Error('Session expired'); }
if (!res.ok) throw new Error(`API ${res.status}`);
return res.json();
},
@@ -2669,10 +2800,75 @@ No login providers configured. Set GOOGLE_CLIENT_ID or FIGMA_CLIENT_ID environment variables.';
+ }
+
+ // Check for auth error in URL
+ const params = new URLSearchParams(window.location.search);
+ if (params.get('auth_error')) {
+ document.getElementById('loginError').innerHTML = ` ${params.get('auth_error')}
`;
+ history.replaceState({}, '', '/');
+ }
+ } catch (e) {
+ // API unreachable — fall back to static mode (no auth)
+ loadData();
+ }
+}
+
+function showUserBadge(user) {
+ const badge = document.getElementById('userBadge');
+ badge.style.display = 'flex';
+ document.getElementById('userName').textContent = user.name;
+ if (user.avatar) {
+ document.getElementById('userAvatar').src = user.avatar;
+ } else {
+ document.getElementById('userAvatar').style.display = 'none';
+ }
+}
+
+async function toggleUserMenu() {
+ if (confirm(`Signed in as ${AUTH_USER?.name || 'User'}.\n\nSign out?`)) {
+ await fetch('/api/auth/logout', { method: 'POST' });
+ window.location.reload();
+ }
+}
+
// ============================================================
// INIT
// ============================================================
-loadData();
+checkAuth();