commit 852e01df397d18ed1264ba1c9d9d1979b752023b Author: Vadim Sobinin Date: Tue Feb 10 13:46:51 2026 +0300 feat: initial SleepGuard implementation Wake-on-demand proxy + agent system with SvelteKit dashboard. Monorepo: shared types, proxy (Hono + http-proxy), agent (monitors + locks), web (SvelteKit SPA). Co-Authored-By: Claude diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..1c9dfe3 --- /dev/null +++ b/.env.example @@ -0,0 +1,36 @@ +# === Proxy (n150) === +# Agent connection +AGENT_URL=http://192.168.1.50:48527 +AGENT_SECRET=change-me-to-a-strong-secret + +# UpSnap +UPSNAP_URL=http://localhost:8090 +UPSNAP_USERNAME=admin +UPSNAP_PASSWORD=admin +UPSNAP_DEVICE_ID=your-device-id + +# Proxy settings +PROXY_PORT=47391 +IDLE_TIMEOUT_MINUTES=15 +HEALTH_CHECK_INTERVAL_SECONDS=10 +WAKING_TIMEOUT_SECONDS=120 + +# Services to proxy (JSON array) +# Each: { "name": "Scriberr", "host": "scriberr.local", "target": "http://192.168.1.50:23636" } +SERVICES='[{"name":"Scriberr","host":"scriberr.local","target":"http://192.168.1.50:23636"}]' + +# === Agent (PC) === +AGENT_PORT=48527 +AGENT_SECRET=change-me-to-a-strong-secret + +# Thresholds +CPU_THRESHOLD_PERCENT=15 +GPU_THRESHOLD_PERCENT=10 +RAM_THRESHOLD_PERCENT=50 +DISK_IO_ACTIVE_THRESHOLD=true + +# Process watchlist (comma-separated) +WATCHED_PROCESSES=hashcat,ffmpeg,whisper,python3,ollama + +# Shutdown command +SHUTDOWN_COMMAND=systemctl poweroff diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2032dfe --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +node_modules/ +dist/ +build/ +.svelte-kit/ +.env +*.tsbuildinfo +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions +.pnp.* diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 0000000..3186f3f --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..fec5ecd --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,30 @@ +# SleepGuard + +Wake-on-demand система: proxy на n150 проксирует запросы к сервисам на PC, будит через UpSnap, agent на PC отслеживает нагрузку для умного выключения. + +## Структура + +Yarn workspaces монорепа: +- `packages/shared` — общие типы и утилиты +- `packages/proxy` — Hono сервер + reverse proxy (n150) +- `packages/proxy/web` — SvelteKit dashboard (adapter-static) +- `packages/agent` — Hono сервер мониторинга (PC) + +## Разработка + +```bash +yarn dev:agent # Agent на :3001 +yarn dev:proxy # Proxy на :3000 +yarn dev:web # SvelteKit dev server +``` + +## Деплой + +Через Dokploy — 2 приложения из одного git repo: +- sleepguard-proxy: `packages/proxy/Dockerfile`, build context `.` +- sleepguard-agent: `packages/agent/Dockerfile`, build context `.` + +## Стек + +- TypeScript, Hono, http-proxy, SvelteKit (adapter-static) +- Docker multi-stage builds diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..04e2fb7 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,31 @@ +services: + proxy: + build: + context: . + dockerfile: packages/proxy/Dockerfile + ports: + - "47391:47391" + env_file: + - .env + restart: unless-stopped + + agent: + build: + context: . + dockerfile: packages/agent/Dockerfile + ports: + - "48527:48527" + env_file: + - .env + # GPU passthrough for nvidia-smi + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + # Access to /proc/diskstats for disk I/O monitoring + volumes: + - /proc/diskstats:/proc/diskstats:ro + restart: unless-stopped diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..20ccf6d --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,51 @@ +# SleepGuard Architecture + +## Обзор + +Wake-on-demand система из двух компонентов: +- **Proxy** (n150, 192.168.50.199) — reverse proxy + state machine + dashboard +- **Agent** (PC, 192.168.1.50) — мониторинг нагрузки + lock manager + +## State Machine + +``` + REQUEST_RECEIVED + OFFLINE ─────────────────────► WAKING + ▲ │ + │ SHUTDOWN_COMPLETE │ HEALTH_CHECK_PASSED + │ ▼ + SHUTTING_DOWN ◄──── IDLE_CHECK ◄── ONLINE + AGENT_IDLE IDLE_TIMEOUT ▲ + │ │ + │ AGENT_BUSY / REQUEST + └────────┘ +``` + +- `OFFLINE` → запрос → wake через UpSnap → `WAKING` +- `WAKING` → health check passed → `ONLINE` +- `ONLINE` → idle timeout → `IDLE_CHECK` +- `IDLE_CHECK` → agent busy → `ONLINE`, agent idle → `SHUTTING_DOWN` +- `SHUTTING_DOWN` → agent offline → `OFFLINE` + +## Shutdown Policy (Agent) + +Три уровня проверки (все должны быть пройдены): +1. **Locks** — если есть активные локи, выключение блокируется +2. **Processes** — hashcat, ffmpeg, whisper, python3, ollama +3. **Metrics** — CPU >15%, GPU >10%, RAM >50%, Disk I/O active + +## Проксирование + +Proxy матчит сервисы по `Host` заголовку. Если PC online — http-proxy проксирует запрос. Если offline — показывает "Waking up" страницу с автополлом. + +## Деплой + +Два приложения в Dokploy из одного git repo: +- `sleepguard-proxy`: Dockerfile `packages/proxy/Dockerfile`, build context `.` +- `sleepguard-agent`: Dockerfile `packages/agent/Dockerfile`, build context `.` + +Agent деплоится на PC как remote server в Dokploy с NVIDIA GPU passthrough. + +## Env переменные + +См. `.env.example` в корне репозитория. diff --git a/package.json b/package.json new file mode 100644 index 0000000..d2359f8 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "sleepguard", + "private": true, + "workspaces": [ + "packages/*", + "packages/proxy/web" + ], + "scripts": { + "dev:agent": "yarn workspace @sleepguard/agent dev", + "dev:proxy": "yarn workspace @sleepguard/proxy dev", + "dev:web": "yarn workspace @sleepguard/web dev", + "build": "yarn workspaces foreach -At run build", + "build:shared": "yarn workspace @sleepguard/shared build", + "build:agent": "yarn workspace @sleepguard/agent build", + "build:proxy": "yarn workspace @sleepguard/proxy build", + "build:web": "yarn workspace @sleepguard/web build", + "typecheck": "yarn workspaces foreach -At run typecheck" + }, + "packageManager": "yarn@4.6.0" +} diff --git a/packages/agent/Dockerfile b/packages/agent/Dockerfile new file mode 100644 index 0000000..a2d5301 --- /dev/null +++ b/packages/agent/Dockerfile @@ -0,0 +1,42 @@ +FROM node:22-slim AS base +RUN corepack enable +RUN apt-get update && apt-get install -y --no-install-recommends \ + procps \ + && rm -rf /var/lib/apt/lists/* + +# --- Dependencies --- +FROM base AS deps +WORKDIR /app +COPY package.json yarn.lock .yarnrc.yml ./ +COPY packages/shared/package.json packages/shared/ +COPY packages/agent/package.json packages/agent/ +RUN yarn install --immutable + +# --- Build shared --- +FROM deps AS build-shared +WORKDIR /app +COPY packages/shared packages/shared +RUN yarn workspace @sleepguard/shared build + +# --- Build agent --- +FROM build-shared AS build-agent +WORKDIR /app +COPY packages/agent/tsconfig.json packages/agent/ +COPY packages/agent/src packages/agent/src +RUN yarn workspace @sleepguard/agent build + +# --- Production --- +FROM base AS production +WORKDIR /app + +COPY package.json yarn.lock .yarnrc.yml ./ +COPY packages/shared/package.json packages/shared/ +COPY packages/agent/package.json packages/agent/ +RUN yarn workspaces focus @sleepguard/agent --production + +COPY --from=build-shared /app/packages/shared/dist packages/shared/dist +COPY --from=build-agent /app/packages/agent/dist packages/agent/dist + +WORKDIR /app/packages/agent +EXPOSE 48527 +CMD ["node", "dist/index.js"] diff --git a/packages/agent/package.json b/packages/agent/package.json new file mode 100644 index 0000000..c63b45f --- /dev/null +++ b/packages/agent/package.json @@ -0,0 +1,22 @@ +{ + "name": "@sleepguard/agent", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc", + "start": "node dist/index.js", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@hono/node-server": "^1.14.0", + "@sleepguard/shared": "workspace:*", + "hono": "^4.7.0" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "tsx": "^4.19.0", + "typescript": "^5.7.0" + } +} diff --git a/packages/agent/src/config.ts b/packages/agent/src/config.ts new file mode 100644 index 0000000..8c3b21a --- /dev/null +++ b/packages/agent/src/config.ts @@ -0,0 +1,20 @@ +import { requireEnv, intEnv, optionalEnv } from '@sleepguard/shared'; + +export const config = { + port: intEnv('AGENT_PORT', 48527), + secret: requireEnv('AGENT_SECRET'), + + thresholds: { + cpuPercent: intEnv('CPU_THRESHOLD_PERCENT', 15), + gpuPercent: intEnv('GPU_THRESHOLD_PERCENT', 10), + ramPercent: intEnv('RAM_THRESHOLD_PERCENT', 50), + diskIoActive: true, + }, + + watchedProcesses: optionalEnv('WATCHED_PROCESSES', 'hashcat,ffmpeg,whisper,python3,ollama') + .split(',') + .map((p) => p.trim()) + .filter(Boolean), + + shutdownCommand: optionalEnv('SHUTDOWN_COMMAND', 'systemctl poweroff'), +} as const; diff --git a/packages/agent/src/index.ts b/packages/agent/src/index.ts new file mode 100644 index 0000000..e15cb9b --- /dev/null +++ b/packages/agent/src/index.ts @@ -0,0 +1,10 @@ +import { serve } from '@hono/node-server'; +import { app } from './server.js'; +import { startLockCleanup } from './services/lockManager.js'; +import { config } from './config.js'; + +startLockCleanup(); + +serve({ fetch: app.fetch, port: config.port }, (info) => { + console.log(`[SleepGuard Agent] Running on port ${info.port}`); +}); diff --git a/packages/agent/src/monitors/cpu.ts b/packages/agent/src/monitors/cpu.ts new file mode 100644 index 0000000..5668629 --- /dev/null +++ b/packages/agent/src/monitors/cpu.ts @@ -0,0 +1,37 @@ +import os from 'node:os'; +import type { CpuStatus } from '@sleepguard/shared'; + +let prevIdle = 0; +let prevTotal = 0; + +function measureCpuUsage(): number { + const cpus = os.cpus(); + let idle = 0; + let total = 0; + + for (const cpu of cpus) { + idle += cpu.times.idle; + total += cpu.times.user + cpu.times.nice + cpu.times.sys + cpu.times.idle + cpu.times.irq; + } + + const idleDelta = idle - prevIdle; + const totalDelta = total - prevTotal; + + prevIdle = idle; + prevTotal = total; + + if (totalDelta === 0) return 0; + return Math.round((1 - idleDelta / totalDelta) * 100 * 10) / 10; +} + +// Initialize baseline +measureCpuUsage(); + +export function getCpuStatus(): CpuStatus { + const loadAvg = os.loadavg() as [number, number, number]; + return { + usagePercent: measureCpuUsage(), + loadAvg, + cores: os.cpus().length, + }; +} diff --git a/packages/agent/src/monitors/disk.ts b/packages/agent/src/monitors/disk.ts new file mode 100644 index 0000000..8e3c4fa --- /dev/null +++ b/packages/agent/src/monitors/disk.ts @@ -0,0 +1,58 @@ +import { readFile } from 'node:fs/promises'; +import type { DiskIoStatus } from '@sleepguard/shared'; + +interface DiskStats { + readSectors: number; + writeSectors: number; + timestamp: number; +} + +let prevStats: DiskStats | null = null; + +const SECTOR_SIZE_KB = 0.5; // 512 bytes + +async function parseDiskStats(): Promise { + try { + const content = await readFile('/proc/diskstats', 'utf-8'); + let readSectors = 0; + let writeSectors = 0; + + for (const line of content.trim().split('\n')) { + const parts = line.trim().split(/\s+/); + if (parts.length < 14) continue; + const device = parts[2]; + // Only count whole disks (sda, nvme0n1), not partitions + if (/^(sd[a-z]|nvme\d+n\d+)$/.test(device)) { + readSectors += parseInt(parts[5], 10) || 0; + writeSectors += parseInt(parts[9], 10) || 0; + } + } + + return { readSectors, writeSectors, timestamp: Date.now() }; + } catch { + return { readSectors: 0, writeSectors: 0, timestamp: Date.now() }; + } +} + +export async function getDiskIoStatus(): Promise { + const current = await parseDiskStats(); + + if (!prevStats) { + prevStats = current; + return { active: false, readKBps: 0, writeKBps: 0 }; + } + + const elapsed = (current.timestamp - prevStats.timestamp) / 1000; + if (elapsed <= 0) { + return { active: false, readKBps: 0, writeKBps: 0 }; + } + + const readKBps = Math.round(((current.readSectors - prevStats.readSectors) * SECTOR_SIZE_KB) / elapsed); + const writeKBps = Math.round(((current.writeSectors - prevStats.writeSectors) * SECTOR_SIZE_KB) / elapsed); + + prevStats = current; + + const active = readKBps > 100 || writeKBps > 100; // >100 KB/s considered active + + return { active, readKBps, writeKBps }; +} diff --git a/packages/agent/src/monitors/gpu.ts b/packages/agent/src/monitors/gpu.ts new file mode 100644 index 0000000..d71e086 --- /dev/null +++ b/packages/agent/src/monitors/gpu.ts @@ -0,0 +1,37 @@ +import { execFile } from 'node:child_process'; +import { promisify } from 'node:util'; +import type { GpuStatus } from '@sleepguard/shared'; + +const execFileAsync = promisify(execFile); + +const EMPTY_GPU: GpuStatus = { + available: false, + usagePercent: 0, + memoryUsedMB: 0, + memoryTotalMB: 0, + temperature: 0, + name: 'N/A', +}; + +export async function getGpuStatus(): Promise { + try { + const { stdout } = await execFileAsync('nvidia-smi', [ + '--query-gpu=utilization.gpu,memory.used,memory.total,temperature.gpu,name', + '--format=csv,noheader,nounits', + ], { timeout: 5000 }); + + const parts = stdout.trim().split(',').map((s) => s.trim()); + if (parts.length < 5) return EMPTY_GPU; + + return { + available: true, + usagePercent: parseFloat(parts[0]) || 0, + memoryUsedMB: parseFloat(parts[1]) || 0, + memoryTotalMB: parseFloat(parts[2]) || 0, + temperature: parseFloat(parts[3]) || 0, + name: parts[4], + }; + } catch { + return EMPTY_GPU; + } +} diff --git a/packages/agent/src/monitors/memory.ts b/packages/agent/src/monitors/memory.ts new file mode 100644 index 0000000..53fcd0e --- /dev/null +++ b/packages/agent/src/monitors/memory.ts @@ -0,0 +1,11 @@ +import os from 'node:os'; +import type { MemoryStatus } from '@sleepguard/shared'; + +export function getMemoryStatus(): MemoryStatus { + const totalMB = Math.round(os.totalmem() / 1024 / 1024); + const freeMB = Math.round(os.freemem() / 1024 / 1024); + const usedMB = totalMB - freeMB; + const usedPercent = Math.round((usedMB / totalMB) * 100 * 10) / 10; + + return { usedPercent, usedMB, totalMB }; +} diff --git a/packages/agent/src/monitors/process.ts b/packages/agent/src/monitors/process.ts new file mode 100644 index 0000000..9904934 --- /dev/null +++ b/packages/agent/src/monitors/process.ts @@ -0,0 +1,29 @@ +import { execFile } from 'node:child_process'; +import { promisify } from 'node:util'; +import type { ProcessInfo } from '@sleepguard/shared'; + +const execFileAsync = promisify(execFile); + +async function findProcess(name: string): Promise { + try { + const { stdout } = await execFileAsync('pgrep', ['-x', name], { timeout: 3000 }); + return stdout + .trim() + .split('\n') + .filter(Boolean) + .map((pid) => parseInt(pid, 10)); + } catch { + // pgrep exits with 1 if no processes found + return []; + } +} + +export async function getProcessStatus(watchlist: string[]): Promise { + const results = await Promise.all( + watchlist.map(async (name) => { + const pids = await findProcess(name); + return { name, running: pids.length > 0, pids }; + }) + ); + return results; +} diff --git a/packages/agent/src/server.ts b/packages/agent/src/server.ts new file mode 100644 index 0000000..131cc01 --- /dev/null +++ b/packages/agent/src/server.ts @@ -0,0 +1,66 @@ +import { Hono } from 'hono'; +import { cors } from 'hono/cors'; +import type { CreateLockRequest } from '@sleepguard/shared'; +import { collectStatus } from './services/collector.js'; +import { evaluateShutdown } from './services/shutdownPolicy.js'; +import { createLock, deleteLock, getAllLocks } from './services/lockManager.js'; +import { config } from './config.js'; + +const app = new Hono(); + +// CORS +app.use('*', cors()); + +// Auth middleware for /api/* +app.use('/api/*', async (c, next) => { + const auth = c.req.header('Authorization'); + if (auth !== `Bearer ${config.secret}`) { + return c.json({ error: 'Unauthorized' }, 401); + } + await next(); +}); + +// Health check +app.get('/api/health', (c) => { + return c.json({ ok: true, timestamp: new Date().toISOString() }); +}); + +// Full status +app.get('/api/status', async (c) => { + const status = await collectStatus(); + return c.json(status); +}); + +// Can shutdown? +app.get('/api/can-shutdown', async (c) => { + const status = await collectStatus(); + const result = evaluateShutdown(status); + return c.json(result); +}); + +// List locks +app.get('/api/locks', (c) => { + return c.json(getAllLocks()); +}); + +// Create lock +app.post('/api/locks', async (c) => { + const body = await c.req.json(); + if (!body.name) { + return c.json({ error: 'name is required' }, 400); + } + const lock = createLock(body.name, body.ttlSeconds, body.reason); + return c.json(lock, 201); +}); + +// Delete lock +app.delete('/api/locks/:name', (c) => { + const name = c.req.param('name'); + const deleted = deleteLock(name); + if (!deleted) { + return c.json({ error: 'Lock not found' }, 404); + } + return c.json({ ok: true }); +}); + +export { app }; diff --git a/packages/agent/src/services/collector.ts b/packages/agent/src/services/collector.ts new file mode 100644 index 0000000..124e8c9 --- /dev/null +++ b/packages/agent/src/services/collector.ts @@ -0,0 +1,27 @@ +import os from 'node:os'; +import type { AgentStatus } from '@sleepguard/shared'; +import { getCpuStatus } from '../monitors/cpu.js'; +import { getGpuStatus } from '../monitors/gpu.js'; +import { getMemoryStatus } from '../monitors/memory.js'; +import { getDiskIoStatus } from '../monitors/disk.js'; +import { getProcessStatus } from '../monitors/process.js'; +import { getAllLocks } from './lockManager.js'; +import { config } from '../config.js'; + +export async function collectStatus(): Promise { + const [gpu, diskIo, processes] = await Promise.all([ + getGpuStatus(), + getDiskIoStatus(), + getProcessStatus(config.watchedProcesses), + ]); + + return { + cpu: getCpuStatus(), + gpu, + memory: getMemoryStatus(), + diskIo, + processes, + locks: getAllLocks(), + uptime: os.uptime(), + }; +} diff --git a/packages/agent/src/services/lockManager.ts b/packages/agent/src/services/lockManager.ts new file mode 100644 index 0000000..226bca9 --- /dev/null +++ b/packages/agent/src/services/lockManager.ts @@ -0,0 +1,56 @@ +import type { Lock } from '@sleepguard/shared'; + +const locks = new Map(); + +let cleanupInterval: ReturnType | null = null; + +function cleanExpired(): void { + const now = Date.now(); + for (const [name, lock] of locks) { + if (lock.expiresAt && new Date(lock.expiresAt).getTime() <= now) { + locks.delete(name); + } + } +} + +export function startLockCleanup(): void { + cleanupInterval = setInterval(cleanExpired, 10_000); +} + +export function stopLockCleanup(): void { + if (cleanupInterval) { + clearInterval(cleanupInterval); + cleanupInterval = null; + } +} + +export function createLock(name: string, ttlSeconds?: number, reason?: string): Lock { + const now = new Date(); + const lock: Lock = { + name, + reason, + createdAt: now.toISOString(), + expiresAt: ttlSeconds ? new Date(now.getTime() + ttlSeconds * 1000).toISOString() : undefined, + }; + locks.set(name, lock); + return lock; +} + +export function deleteLock(name: string): boolean { + return locks.delete(name); +} + +export function getLock(name: string): Lock | undefined { + cleanExpired(); + return locks.get(name); +} + +export function getAllLocks(): Lock[] { + cleanExpired(); + return Array.from(locks.values()); +} + +export function hasActiveLocks(): boolean { + cleanExpired(); + return locks.size > 0; +} diff --git a/packages/agent/src/services/shutdownPolicy.ts b/packages/agent/src/services/shutdownPolicy.ts new file mode 100644 index 0000000..4c886b0 --- /dev/null +++ b/packages/agent/src/services/shutdownPolicy.ts @@ -0,0 +1,41 @@ +import type { AgentStatus, CanShutdownResponse } from '@sleepguard/shared'; +import { config } from '../config.js'; + +export function evaluateShutdown(status: AgentStatus): CanShutdownResponse { + const reasons: string[] = []; + + // Level 1: Locks (absolute blocker) + if (status.locks.length > 0) { + const lockNames = status.locks.map((l) => l.name).join(', '); + reasons.push(`Active locks: ${lockNames}`); + } + + // Level 2: Process watchlist + const runningProcesses = status.processes.filter((p) => p.running); + if (runningProcesses.length > 0) { + const names = runningProcesses.map((p) => p.name).join(', '); + reasons.push(`Watched processes running: ${names}`); + } + + // Level 3: Metric thresholds + if (status.cpu.usagePercent > config.thresholds.cpuPercent) { + reasons.push(`CPU usage ${status.cpu.usagePercent}% > ${config.thresholds.cpuPercent}%`); + } + + if (status.gpu.available && status.gpu.usagePercent > config.thresholds.gpuPercent) { + reasons.push(`GPU usage ${status.gpu.usagePercent}% > ${config.thresholds.gpuPercent}%`); + } + + if (status.memory.usedPercent > config.thresholds.ramPercent) { + reasons.push(`RAM usage ${status.memory.usedPercent}% > ${config.thresholds.ramPercent}%`); + } + + if (config.thresholds.diskIoActive && status.diskIo.active) { + reasons.push(`Disk I/O active (R: ${status.diskIo.readKBps} KB/s, W: ${status.diskIo.writeKBps} KB/s)`); + } + + return { + canShutdown: reasons.length === 0, + reasons, + }; +} diff --git a/packages/agent/tsconfig.json b/packages/agent/tsconfig.json new file mode 100644 index 0000000..e2350de --- /dev/null +++ b/packages/agent/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"], + "references": [ + { "path": "../shared" } + ] +} diff --git a/packages/proxy/Dockerfile b/packages/proxy/Dockerfile new file mode 100644 index 0000000..daf93a0 --- /dev/null +++ b/packages/proxy/Dockerfile @@ -0,0 +1,48 @@ +FROM node:22-alpine AS base +RUN corepack enable + +# --- Dependencies --- +FROM base AS deps +WORKDIR /app +COPY package.json yarn.lock .yarnrc.yml ./ +COPY packages/shared/package.json packages/shared/ +COPY packages/proxy/package.json packages/proxy/ +COPY packages/proxy/web/package.json packages/proxy/web/ +RUN yarn install --immutable + +# --- Build shared --- +FROM deps AS build-shared +WORKDIR /app +COPY packages/shared packages/shared +RUN yarn workspace @sleepguard/shared build + +# --- Build web --- +FROM build-shared AS build-web +WORKDIR /app +COPY packages/proxy/web packages/proxy/web +RUN yarn workspace @sleepguard/web build + +# --- Build proxy --- +FROM build-shared AS build-proxy +WORKDIR /app +COPY packages/proxy/tsconfig.json packages/proxy/ +COPY packages/proxy/src packages/proxy/src +RUN yarn workspace @sleepguard/proxy build + +# --- Production --- +FROM base AS production +WORKDIR /app + +COPY package.json yarn.lock .yarnrc.yml ./ +COPY packages/shared/package.json packages/shared/ +COPY packages/proxy/package.json packages/proxy/ +COPY packages/proxy/web/package.json packages/proxy/web/ +RUN yarn workspaces focus @sleepguard/proxy --production + +COPY --from=build-shared /app/packages/shared/dist packages/shared/dist +COPY --from=build-proxy /app/packages/proxy/dist packages/proxy/dist +COPY --from=build-web /app/packages/proxy/web/build packages/proxy/public/dashboard + +WORKDIR /app/packages/proxy +EXPOSE 47391 +CMD ["node", "dist/index.js"] diff --git a/packages/proxy/package.json b/packages/proxy/package.json new file mode 100644 index 0000000..da3745b --- /dev/null +++ b/packages/proxy/package.json @@ -0,0 +1,26 @@ +{ + "name": "@sleepguard/proxy", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc", + "start": "node dist/index.js", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@hono/node-server": "^1.14.0", + "@sleepguard/shared": "workspace:*", + "hono": "^4.7.0", + "http-proxy": "^1.18.1", + "ws": "^8.18.0" + }, + "devDependencies": { + "@types/http-proxy": "^1.17.0", + "@types/node": "^22.0.0", + "@types/ws": "^8.5.0", + "tsx": "^4.19.0", + "typescript": "^5.7.0" + } +} diff --git a/packages/proxy/src/api/middleware.ts b/packages/proxy/src/api/middleware.ts new file mode 100644 index 0000000..302c420 --- /dev/null +++ b/packages/proxy/src/api/middleware.ts @@ -0,0 +1,7 @@ +import { cors } from 'hono/cors'; + +export const corsMiddleware = cors({ + origin: '*', + allowMethods: ['GET', 'POST', 'PATCH', 'DELETE', 'OPTIONS'], + allowHeaders: ['Content-Type', 'Authorization'], +}); diff --git a/packages/proxy/src/api/routes.ts b/packages/proxy/src/api/routes.ts new file mode 100644 index 0000000..9000877 --- /dev/null +++ b/packages/proxy/src/api/routes.ts @@ -0,0 +1,92 @@ +import { Hono } from 'hono'; +import type { ProxyStatus, ServiceHealth } from '@sleepguard/shared'; +import { + getState, + getLogs, + getLastAgentStatus, + manualWake, + manualShutdown, + refreshAgentStatus, +} from '../services/orchestrator.js'; +import { getLastActivity, getRemainingSeconds } from '../services/idleTimer.js'; +import { config } from '../config.js'; + +const api = new Hono(); + +api.get('/status', async (c) => { + const agentStatus = getLastAgentStatus(); + const services: ServiceHealth[] = config.services.map((s) => ({ + name: s.name, + host: s.host, + target: s.target, + healthy: getState() === 'ONLINE', + lastCheck: new Date().toISOString(), + })); + + const status: ProxyStatus = { + state: getState(), + services, + config: { + idleTimeoutMinutes: config.idleTimeoutMinutes, + healthCheckIntervalSeconds: config.healthCheckIntervalSeconds, + wakingTimeoutSeconds: config.wakingTimeoutSeconds, + }, + idleTimer: + getState() === 'ONLINE' + ? { + lastActivity: getLastActivity().toISOString(), + remainingSeconds: getRemainingSeconds(), + } + : null, + agent: agentStatus, + locks: agentStatus?.locks ?? [], + logs: getLogs().slice(-50), + }; + + return c.json(status); +}); + +api.post('/wake', async (c) => { + await manualWake(); + return c.json({ ok: true, state: getState() }); +}); + +api.post('/shutdown', async (c) => { + await manualShutdown(); + return c.json({ ok: true, state: getState() }); +}); + +api.get('/services', (c) => { + const services: ServiceHealth[] = config.services.map((s) => ({ + name: s.name, + host: s.host, + target: s.target, + healthy: getState() === 'ONLINE', + lastCheck: new Date().toISOString(), + })); + return c.json(services); +}); + +api.get('/logs', (c) => { + return c.json(getLogs().slice(-100)); +}); + +api.patch('/config', async (c) => { + // Runtime config updates could be added here + // For now, return current config + return c.json({ + idleTimeoutMinutes: config.idleTimeoutMinutes, + healthCheckIntervalSeconds: config.healthCheckIntervalSeconds, + wakingTimeoutSeconds: config.wakingTimeoutSeconds, + }); +}); + +api.get('/agent/status', async (c) => { + const status = await refreshAgentStatus(); + if (!status) { + return c.json({ error: 'Agent unreachable' }, 503); + } + return c.json(status); +}); + +export { api }; diff --git a/packages/proxy/src/config.ts b/packages/proxy/src/config.ts new file mode 100644 index 0000000..88d55de --- /dev/null +++ b/packages/proxy/src/config.ts @@ -0,0 +1,34 @@ +import { requireEnv, intEnv, optionalEnv, type ServiceConfig } from '@sleepguard/shared'; + +function parseServices(): ServiceConfig[] { + const raw = optionalEnv('SERVICES', '[]'); + try { + const parsed = JSON.parse(raw) as ServiceConfig[]; + return parsed; + } catch { + console.error('[Config] Failed to parse SERVICES env var'); + return []; + } +} + +export const config = { + port: intEnv('PROXY_PORT', 47391), + + agent: { + url: requireEnv('AGENT_URL'), + secret: requireEnv('AGENT_SECRET'), + }, + + upsnap: { + url: requireEnv('UPSNAP_URL'), + username: requireEnv('UPSNAP_USERNAME'), + password: requireEnv('UPSNAP_PASSWORD'), + deviceId: requireEnv('UPSNAP_DEVICE_ID'), + }, + + idleTimeoutMinutes: intEnv('IDLE_TIMEOUT_MINUTES', 15), + healthCheckIntervalSeconds: intEnv('HEALTH_CHECK_INTERVAL_SECONDS', 10), + wakingTimeoutSeconds: intEnv('WAKING_TIMEOUT_SECONDS', 120), + + services: parseServices(), +} as const; diff --git a/packages/proxy/src/index.ts b/packages/proxy/src/index.ts new file mode 100644 index 0000000..bb7df2d --- /dev/null +++ b/packages/proxy/src/index.ts @@ -0,0 +1,75 @@ +import { createServer } from 'node:http'; +import { createAdaptorServer } from '@hono/node-server'; +import { MachineState, type ServiceConfig } from '@sleepguard/shared'; +import { app } from './server/app.js'; +import { proxyRequest, proxyWebSocket } from './server/proxy.js'; +import { initOrchestrator, getState, onRequest } from './services/orchestrator.js'; +import { addClient, removeClient } from './server/ws.js'; +import { config } from './config.js'; +import { WebSocketServer } from 'ws'; + +function findServiceByHost(host: string | undefined): ServiceConfig | undefined { + if (!host) return undefined; + const hostname = host.split(':')[0]; + return config.services.find((s) => s.host === hostname); +} + +// Create the base HTTP server with Hono +const server = createServer(async (req, res) => { + const service = findServiceByHost(req.headers.host); + + // If it's a proxied service and machine is online, proxy directly + if (service) { + onRequest(); + const state = getState(); + + if (state === MachineState.ONLINE || state === MachineState.IDLE_CHECK) { + proxyRequest(req, res, service); + return; + } + } + + // Otherwise let Hono handle (API, dashboard, waking page) + const honoHandler = createAdaptorServer(app); + honoHandler.emit('request', req, res); +}); + +// Handle WebSocket upgrades +const wss = new WebSocketServer({ noServer: true }); + +server.on('upgrade', (req, socket, head) => { + const service = findServiceByHost(req.headers.host); + + if (service) { + onRequest(); + const state = getState(); + if (state === MachineState.ONLINE || state === MachineState.IDLE_CHECK) { + proxyWebSocket(req, socket, head, service); + return; + } + socket.destroy(); + return; + } + + // Dashboard WebSocket at /api/ws + if (req.url === '/api/ws') { + wss.handleUpgrade(req, socket, head, (ws) => { + addClient(ws); + ws.on('close', () => removeClient(ws)); + }); + return; + } + + socket.destroy(); +}); + +// Start +async function start(): Promise { + await initOrchestrator(); + server.listen(config.port, () => { + console.log(`[SleepGuard Proxy] Running on port ${config.port}`); + console.log(`[SleepGuard Proxy] Services: ${config.services.map((s) => s.name).join(', ') || 'none configured'}`); + }); +} + +start(); diff --git a/packages/proxy/src/server/app.ts b/packages/proxy/src/server/app.ts new file mode 100644 index 0000000..f07ada2 --- /dev/null +++ b/packages/proxy/src/server/app.ts @@ -0,0 +1,57 @@ +import { Hono } from 'hono'; +import { serveStatic } from '@hono/node-server/serve-static'; +import { MachineState, type ServiceConfig } from '@sleepguard/shared'; +import { corsMiddleware } from '../api/middleware.js'; +import { api } from '../api/routes.js'; +import { getState, onRequest } from '../services/orchestrator.js'; +import { getWakingPageHtml } from './wakingPage.js'; +import { config } from '../config.js'; + +const app = new Hono(); + +// CORS for API +app.use('/api/*', corsMiddleware); + +// API routes +app.route('/api', api); + +// Dashboard static files (in Docker: ./public/dashboard, in dev: ../web/build) +app.use('/dashboard/*', serveStatic({ root: './public' })); +app.get('/dashboard', serveStatic({ root: './public', path: '/dashboard/index.html' })); +// SPA fallback for dashboard subroutes +app.get('/dashboard/*', serveStatic({ root: './public', path: '/dashboard/index.html' })); + +// Service proxy — match by Host header +app.all('*', async (c) => { + const host = c.req.header('host')?.split(':')[0]; + if (!host) { + return c.text('Bad Request: no Host header', 400); + } + + const service = config.services.find((s) => s.host === host); + if (!service) { + // Not a proxied service — maybe the dashboard on the main host + return c.text('Not Found', 404); + } + + // Track activity + onRequest(); + + const state = getState(); + + if (state === MachineState.OFFLINE || state === MachineState.WAKING) { + return c.html(getWakingPageHtml(service.name)); + } + + if (state === MachineState.SHUTTING_DOWN) { + return c.html(getWakingPageHtml(service.name)); + } + + // ONLINE or IDLE_CHECK — proxy the request + // We'll handle actual proxying at the Node.js http level, not via Hono + // Mark this request for proxy pass-through + c.set('proxyTarget' as never, service as never); + return c.text('__PROXY__', 200); +}); + +export { app }; diff --git a/packages/proxy/src/server/proxy.ts b/packages/proxy/src/server/proxy.ts new file mode 100644 index 0000000..3271133 --- /dev/null +++ b/packages/proxy/src/server/proxy.ts @@ -0,0 +1,39 @@ +import httpProxy from 'http-proxy'; +import type { IncomingMessage, ServerResponse } from 'node:http'; +import type { ServiceConfig } from '@sleepguard/shared'; + +const proxyServer = httpProxy.createProxyServer({ + ws: true, + changeOrigin: true, + xfwd: true, +}); + +proxyServer.on('error', (err, _req, res) => { + console.error('[Proxy] Error:', err.message); + if (res && 'writeHead' in res) { + const serverRes = res as ServerResponse; + if (!serverRes.headersSent) { + serverRes.writeHead(502, { 'Content-Type': 'text/plain' }); + serverRes.end('Bad Gateway'); + } + } +}); + +export function proxyRequest( + req: IncomingMessage, + res: ServerResponse, + service: ServiceConfig +): void { + proxyServer.web(req, res, { target: service.target }); +} + +export function proxyWebSocket( + req: IncomingMessage, + socket: unknown, + head: Buffer, + service: ServiceConfig +): void { + proxyServer.ws(req, socket as import('node:net').Socket, head, { target: service.target }); +} + +export { proxyServer }; diff --git a/packages/proxy/src/server/wakingPage.ts b/packages/proxy/src/server/wakingPage.ts new file mode 100644 index 0000000..dc7dd64 --- /dev/null +++ b/packages/proxy/src/server/wakingPage.ts @@ -0,0 +1,68 @@ +export function getWakingPageHtml(serviceName: string): string { + return ` + + + + + Waking up — ${serviceName} + + + +
+
+

Waking up ${serviceName}...

+

The server is starting. This page will reload automatically.

+

Waiting for response...

+
+ + +`; +} diff --git a/packages/proxy/src/server/ws.ts b/packages/proxy/src/server/ws.ts new file mode 100644 index 0000000..a263b12 --- /dev/null +++ b/packages/proxy/src/server/ws.ts @@ -0,0 +1,33 @@ +import type { WebSocket } from 'ws'; +import type { WsMessage } from '@sleepguard/shared'; + +type WsClient = WebSocket; + +const clients = new Set(); + +export function addClient(ws: WsClient): void { + clients.add(ws); +} + +export function removeClient(ws: WsClient): void { + clients.delete(ws); +} + +export function broadcast(message: WsMessage): void { + const data = JSON.stringify(message); + for (const client of clients) { + try { + if (client.readyState === 1) { + client.send(data); + } else { + clients.delete(client); + } + } catch { + clients.delete(client); + } + } +} + +export function getClientCount(): number { + return clients.size; +} diff --git a/packages/proxy/src/services/healthCheck.ts b/packages/proxy/src/services/healthCheck.ts new file mode 100644 index 0000000..9d3ac8b --- /dev/null +++ b/packages/proxy/src/services/healthCheck.ts @@ -0,0 +1,32 @@ +import { config } from '../config.js'; + +export type HealthCallback = (healthy: boolean) => void; + +let intervalId: ReturnType | null = null; + +export async function checkHealth(): Promise { + try { + const res = await fetch(`${config.agent.url}/api/health`, { + headers: { Authorization: `Bearer ${config.agent.secret}` }, + signal: AbortSignal.timeout(5000), + }); + return res.ok; + } catch { + return false; + } +} + +export function startHealthCheck(callback: HealthCallback): void { + stopHealthCheck(); + intervalId = setInterval(async () => { + const healthy = await checkHealth(); + callback(healthy); + }, config.healthCheckIntervalSeconds * 1000); +} + +export function stopHealthCheck(): void { + if (intervalId) { + clearInterval(intervalId); + intervalId = null; + } +} diff --git a/packages/proxy/src/services/idleTimer.ts b/packages/proxy/src/services/idleTimer.ts new file mode 100644 index 0000000..2027818 --- /dev/null +++ b/packages/proxy/src/services/idleTimer.ts @@ -0,0 +1,42 @@ +export type IdleCallback = () => void; + +let lastActivity = Date.now(); +let timeoutId: ReturnType | null = null; +let idleTimeoutMs: number; +let onIdle: IdleCallback | null = null; + +export function initIdleTimer(timeoutMinutes: number, callback: IdleCallback): void { + idleTimeoutMs = timeoutMinutes * 60 * 1000; + onIdle = callback; + resetIdleTimer(); +} + +export function resetIdleTimer(): void { + lastActivity = Date.now(); + if (timeoutId) { + clearTimeout(timeoutId); + } + if (onIdle && idleTimeoutMs > 0) { + timeoutId = setTimeout(() => { + onIdle?.(); + }, idleTimeoutMs); + } +} + +export function stopIdleTimer(): void { + if (timeoutId) { + clearTimeout(timeoutId); + timeoutId = null; + } +} + +export function getLastActivity(): Date { + return new Date(lastActivity); +} + +export function getRemainingSeconds(): number { + if (!idleTimeoutMs) return 0; + const elapsed = Date.now() - lastActivity; + const remaining = Math.max(0, idleTimeoutMs - elapsed); + return Math.round(remaining / 1000); +} diff --git a/packages/proxy/src/services/orchestrator.ts b/packages/proxy/src/services/orchestrator.ts new file mode 100644 index 0000000..a4c2840 --- /dev/null +++ b/packages/proxy/src/services/orchestrator.ts @@ -0,0 +1,234 @@ +import { MachineState, type AgentStatus, type CanShutdownResponse, type LogEntry } from '@sleepguard/shared'; +import { wakeDevice } from './upsnap.js'; +import { checkHealth, startHealthCheck, stopHealthCheck } from './healthCheck.js'; +import { initIdleTimer, resetIdleTimer, stopIdleTimer } from './idleTimer.js'; +import { config } from '../config.js'; +import { broadcast } from '../server/ws.js'; + +let state: MachineState = MachineState.OFFLINE; +let wakingTimeout: ReturnType | null = null; +let lastAgentStatus: AgentStatus | null = null; +const logs: LogEntry[] = []; +let pendingWake = false; + +function log(level: LogEntry['level'], message: string, details?: string): void { + const entry: LogEntry = { + timestamp: new Date().toISOString(), + level, + message, + details, + }; + logs.push(entry); + if (logs.length > 200) logs.shift(); + broadcast({ type: 'log', data: entry, timestamp: entry.timestamp }); +} + +function setState(newState: MachineState): void { + const prev = state; + state = newState; + log('info', `State: ${prev} → ${newState}`); + broadcast({ type: 'state_change', data: { from: prev, to: newState }, timestamp: new Date().toISOString() }); +} + +async function fetchAgentStatus(): Promise { + try { + const res = await fetch(`${config.agent.url}/api/status`, { + headers: { Authorization: `Bearer ${config.agent.secret}` }, + signal: AbortSignal.timeout(5000), + }); + if (!res.ok) return null; + const data = (await res.json()) as AgentStatus; + lastAgentStatus = data; + return data; + } catch { + return null; + } +} + +async function fetchCanShutdown(): Promise { + try { + const res = await fetch(`${config.agent.url}/api/can-shutdown`, { + headers: { Authorization: `Bearer ${config.agent.secret}` }, + signal: AbortSignal.timeout(5000), + }); + if (!res.ok) return null; + return (await res.json()) as CanShutdownResponse; + } catch { + return null; + } +} + +async function doWake(): Promise { + setState(MachineState.WAKING); + try { + await wakeDevice(); + log('info', 'WoL packet sent via UpSnap'); + } catch (err) { + log('error', 'Failed to wake device', String(err)); + } + + // Start polling health + startHealthCheck((healthy) => { + if (healthy && state === MachineState.WAKING) { + onOnline(); + } + }); + + // Waking timeout + wakingTimeout = setTimeout(() => { + if (state === MachineState.WAKING) { + log('error', 'Waking timeout exceeded'); + setState(MachineState.OFFLINE); + stopHealthCheck(); + } + }, config.wakingTimeoutSeconds * 1000); +} + +function onOnline(): void { + if (wakingTimeout) { + clearTimeout(wakingTimeout); + wakingTimeout = null; + } + setState(MachineState.ONLINE); + initIdleTimer(config.idleTimeoutMinutes, onIdleTimeout); + + // Keep health check running to detect crashes + stopHealthCheck(); + startHealthCheck((healthy) => { + if (!healthy && (state === MachineState.ONLINE || state === MachineState.IDLE_CHECK)) { + log('warn', 'Agent became unreachable'); + setState(MachineState.OFFLINE); + stopIdleTimer(); + stopHealthCheck(); + if (pendingWake) { + pendingWake = false; + doWake(); + } + } + }); + + // Fetch initial status + fetchAgentStatus(); +} + +async function onIdleTimeout(): Promise { + if (state !== MachineState.ONLINE) return; + setState(MachineState.IDLE_CHECK); + + const result = await fetchCanShutdown(); + if (!result) { + log('warn', 'Could not reach agent for idle check, returning to ONLINE'); + setState(MachineState.ONLINE); + initIdleTimer(config.idleTimeoutMinutes, onIdleTimeout); + return; + } + + if (!result.canShutdown) { + log('info', 'Agent is busy, returning to ONLINE', result.reasons.join('; ')); + setState(MachineState.ONLINE); + initIdleTimer(config.idleTimeoutMinutes, onIdleTimeout); + return; + } + + // Proceed with shutdown + await doShutdown(); +} + +async function doShutdown(): Promise { + setState(MachineState.SHUTTING_DOWN); + stopIdleTimer(); + + try { + // Ask agent to shutdown the machine via UpSnap + // UpSnap uses its own shutdown mechanism (SSH/script) + const { shutdownDevice: upSnapShutdown } = await import('./upsnap.js'); + await upSnapShutdown(); + log('info', 'Shutdown command sent'); + } catch (err) { + log('error', 'Shutdown failed', String(err)); + } + + // Wait for agent to go offline + stopHealthCheck(); + startHealthCheck((healthy) => { + if (!healthy && state === MachineState.SHUTTING_DOWN) { + setState(MachineState.OFFLINE); + stopHealthCheck(); + if (pendingWake) { + pendingWake = false; + doWake(); + } + } + }); + + // Safety timeout — if machine doesn't go offline in 2 min, mark offline anyway + setTimeout(() => { + if (state === MachineState.SHUTTING_DOWN) { + log('warn', 'Shutdown timeout, marking as offline'); + setState(MachineState.OFFLINE); + stopHealthCheck(); + } + }, 120_000); +} + +// --- Public API --- + +export function getState(): MachineState { + return state; +} + +export function getLogs(): LogEntry[] { + return [...logs]; +} + +export function getLastAgentStatus(): AgentStatus | null { + return lastAgentStatus; +} + +export function onRequest(): void { + if (state === MachineState.ONLINE) { + resetIdleTimer(); + } else if (state === MachineState.IDLE_CHECK) { + // Cancel idle check, back to ONLINE + setState(MachineState.ONLINE); + resetIdleTimer(); + } else if (state === MachineState.OFFLINE) { + doWake(); + } else if (state === MachineState.SHUTTING_DOWN) { + pendingWake = true; + log('info', 'Request during shutdown, will wake after shutdown completes'); + } + // WAKING — do nothing, already waking +} + +export async function manualWake(): Promise { + if (state === MachineState.OFFLINE) { + await doWake(); + } else { + log('info', 'Manual wake ignored — not in OFFLINE state'); + } +} + +export async function manualShutdown(): Promise { + if (state === MachineState.ONLINE || state === MachineState.IDLE_CHECK) { + await doShutdown(); + } else { + log('info', 'Manual shutdown ignored — not in ONLINE/IDLE_CHECK state'); + } +} + +export async function refreshAgentStatus(): Promise { + return fetchAgentStatus(); +} + +// Initialize: check if PC is already online +export async function initOrchestrator(): Promise { + log('info', 'Orchestrator starting'); + const healthy = await checkHealth(); + if (healthy) { + log('info', 'Agent is already online'); + onOnline(); + } else { + log('info', 'Agent is offline'); + } +} diff --git a/packages/proxy/src/services/upsnap.ts b/packages/proxy/src/services/upsnap.ts new file mode 100644 index 0000000..476451a --- /dev/null +++ b/packages/proxy/src/services/upsnap.ts @@ -0,0 +1,84 @@ +import type { UpSnapAuthResponse } from '@sleepguard/shared'; +import { config } from '../config.js'; + +let token: string | null = null; + +async function authenticate(): Promise { + const res = await fetch( + `${config.upsnap.url}/api/collections/users/auth-with-password`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + identity: config.upsnap.username, + password: config.upsnap.password, + }), + } + ); + + if (!res.ok) { + throw new Error(`UpSnap auth failed: ${res.status} ${await res.text()}`); + } + + const data = (await res.json()) as UpSnapAuthResponse; + token = data.token; + return token; +} + +async function getToken(): Promise { + if (!token) { + return authenticate(); + } + return token; +} + +async function upSnapFetch(path: string, options: RequestInit = {}): Promise { + let authToken = await getToken(); + + const doFetch = (t: string) => + fetch(`${config.upsnap.url}${path}`, { + ...options, + headers: { + 'Content-Type': 'application/json', + Authorization: t, + ...options.headers, + }, + }); + + let res = await doFetch(authToken); + + // Token expired — re-auth once + if (res.status === 401 || res.status === 403) { + authToken = await authenticate(); + res = await doFetch(authToken); + } + + return res; +} + +export async function wakeDevice(): Promise { + const res = await upSnapFetch( + `/api/collections/devices/records/${config.upsnap.deviceId}`, + { method: 'PATCH', body: JSON.stringify({ status: 'on' }) } + ); + + // UpSnap may also have a dedicated wake endpoint — try both approaches + if (!res.ok) { + // Fallback: POST to wake endpoint + const res2 = await upSnapFetch(`/api/upsnap/wake/${config.upsnap.deviceId}`, { + method: 'GET', + }); + if (!res2.ok) { + throw new Error(`UpSnap wake failed: ${res2.status}`); + } + } +} + +export async function shutdownDevice(): Promise { + const res = await upSnapFetch(`/api/upsnap/shutdown/${config.upsnap.deviceId}`, { + method: 'GET', + }); + if (!res.ok) { + throw new Error(`UpSnap shutdown failed: ${res.status}`); + } +} diff --git a/packages/proxy/tsconfig.json b/packages/proxy/tsconfig.json new file mode 100644 index 0000000..e2350de --- /dev/null +++ b/packages/proxy/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"], + "references": [ + { "path": "../shared" } + ] +} diff --git a/packages/proxy/web/package.json b/packages/proxy/web/package.json new file mode 100644 index 0000000..fe74b09 --- /dev/null +++ b/packages/proxy/web/package.json @@ -0,0 +1,23 @@ +{ + "name": "@sleepguard/web", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "typecheck": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json" + }, + "dependencies": { + "@sleepguard/shared": "workspace:*" + }, + "devDependencies": { + "@sveltejs/adapter-static": "^3.0.0", + "@sveltejs/kit": "^2.15.0", + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "typescript": "^5.7.0", + "vite": "^6.0.0" + } +} diff --git a/packages/proxy/web/src/app.css b/packages/proxy/web/src/app.css new file mode 100644 index 0000000..51c5f2e --- /dev/null +++ b/packages/proxy/web/src/app.css @@ -0,0 +1,78 @@ +:root { + --bg: #0f172a; + --bg-card: #1e293b; + --bg-hover: #334155; + --text: #e2e8f0; + --text-muted: #94a3b8; + --text-dim: #64748b; + --accent: #3b82f6; + --accent-hover: #2563eb; + --green: #22c55e; + --yellow: #eab308; + --red: #ef4444; + --orange: #f97316; + --border: #334155; + --radius: 8px; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; + background: var(--bg); + color: var(--text); + line-height: 1.6; +} + +button { + cursor: pointer; + border: none; + border-radius: var(--radius); + padding: 0.5rem 1rem; + font-size: 0.875rem; + font-weight: 500; + transition: background-color 0.15s; +} + +button.primary { + background: var(--accent); + color: white; +} + +button.primary:hover { + background: var(--accent-hover); +} + +button.danger { + background: var(--red); + color: white; +} + +button.danger:hover { + background: #dc2626; +} + +button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.card { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 1.25rem; +} + +.card h2 { + font-size: 0.875rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--text-muted); + margin-bottom: 0.75rem; +} diff --git a/packages/proxy/web/src/app.html b/packages/proxy/web/src/app.html new file mode 100644 index 0000000..40b3466 --- /dev/null +++ b/packages/proxy/web/src/app.html @@ -0,0 +1,12 @@ + + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/packages/proxy/web/src/lib/api.ts b/packages/proxy/web/src/lib/api.ts new file mode 100644 index 0000000..7210f21 --- /dev/null +++ b/packages/proxy/web/src/lib/api.ts @@ -0,0 +1,34 @@ +import type { ProxyStatus, ServiceHealth, LogEntry } from '@sleepguard/shared'; + +const BASE = '/api'; + +async function request(path: string, options?: RequestInit): Promise { + const res = await fetch(`${BASE}${path}`, { + headers: { 'Content-Type': 'application/json' }, + ...options, + }); + if (!res.ok) { + throw new Error(`API error: ${res.status} ${res.statusText}`); + } + return res.json() as Promise; +} + +export function fetchStatus(): Promise { + return request('/status'); +} + +export function fetchServices(): Promise { + return request('/services'); +} + +export function fetchLogs(): Promise { + return request('/logs'); +} + +export function wake(): Promise<{ ok: boolean; state: string }> { + return request('/wake', { method: 'POST' }); +} + +export function shutdown(): Promise<{ ok: boolean; state: string }> { + return request('/shutdown', { method: 'POST' }); +} diff --git a/packages/proxy/web/src/lib/components/ActivityLog.svelte b/packages/proxy/web/src/lib/components/ActivityLog.svelte new file mode 100644 index 0000000..783a594 --- /dev/null +++ b/packages/proxy/web/src/lib/components/ActivityLog.svelte @@ -0,0 +1,85 @@ + + +
+

Activity Log

+
+ {#if logs.length === 0} +

No activity yet

+ {:else} + {#each logs.toReversed() as entry} +
+ {formatTime(entry.timestamp)} + {entry.level} + {entry.message} + {#if entry.details} + {entry.details} + {/if} +
+ {/each} + {/if} +
+
+ + diff --git a/packages/proxy/web/src/lib/components/IdleTimer.svelte b/packages/proxy/web/src/lib/components/IdleTimer.svelte new file mode 100644 index 0000000..27c8f8c --- /dev/null +++ b/packages/proxy/web/src/lib/components/IdleTimer.svelte @@ -0,0 +1,53 @@ + + +
+

Idle Timer

+
+ {formatTime(remainingSeconds)} + until idle check +
+

Last activity: {formatDate(lastActivity)}

+
+ + diff --git a/packages/proxy/web/src/lib/components/LocksList.svelte b/packages/proxy/web/src/lib/components/LocksList.svelte new file mode 100644 index 0000000..3b75322 --- /dev/null +++ b/packages/proxy/web/src/lib/components/LocksList.svelte @@ -0,0 +1,70 @@ + + +
+

Locks

+ {#if locks.length === 0} +

No active locks

+ {:else} +
    + {#each locks as lock} +
  • + {lock.name} + {#if lock.reason} + {lock.reason} + {/if} + {#if lock.expiresAt} + Expires: {formatDate(lock.expiresAt)} + {:else} + No expiry + {/if} +
  • + {/each} +
+ {/if} +
+ + diff --git a/packages/proxy/web/src/lib/components/ServiceList.svelte b/packages/proxy/web/src/lib/components/ServiceList.svelte new file mode 100644 index 0000000..0991708 --- /dev/null +++ b/packages/proxy/web/src/lib/components/ServiceList.svelte @@ -0,0 +1,71 @@ + + +
+

Services

+ {#if services.length === 0} +

No services configured

+ {:else} +
    + {#each services as service} +
  • + +
    + {service.name} + {service.host} +
    +
  • + {/each} +
+ {/if} +
+ + diff --git a/packages/proxy/web/src/lib/components/StateIndicator.svelte b/packages/proxy/web/src/lib/components/StateIndicator.svelte new file mode 100644 index 0000000..424ba18 --- /dev/null +++ b/packages/proxy/web/src/lib/components/StateIndicator.svelte @@ -0,0 +1,49 @@ + + +
+ + {config.label} +
+ + diff --git a/packages/proxy/web/src/lib/components/StatusCard.svelte b/packages/proxy/web/src/lib/components/StatusCard.svelte new file mode 100644 index 0000000..2f4985b --- /dev/null +++ b/packages/proxy/web/src/lib/components/StatusCard.svelte @@ -0,0 +1,104 @@ + + +
+

Machine Status

+ + +
+ + +
+ + {#if status.agent} +
+
+ CPU + {status.agent.cpu.usagePercent}% +
+
+ RAM + {status.agent.memory.usedPercent}% +
+ {#if status.agent.gpu.available} +
+ GPU + {status.agent.gpu.usagePercent}% +
+ {/if} +
+ Disk I/O + {status.agent.diskIo.active ? 'Active' : 'Idle'} +
+
+ {/if} +
+ + diff --git a/packages/proxy/web/src/lib/stores/machine.ts b/packages/proxy/web/src/lib/stores/machine.ts new file mode 100644 index 0000000..7959dcf --- /dev/null +++ b/packages/proxy/web/src/lib/stores/machine.ts @@ -0,0 +1,88 @@ +import { writable, derived } from 'svelte/store'; +import type { ProxyStatus, WsMessage } from '@sleepguard/shared'; +import { MachineState } from '@sleepguard/shared'; +import { fetchStatus } from '../api.js'; + +export const status = writable(null); +export const connected = writable(false); +export const error = writable(null); + +export const machineState = derived(status, ($s) => $s?.state ?? MachineState.OFFLINE); + +let ws: WebSocket | null = null; +let reconnectTimer: ReturnType | null = null; + +function getWsUrl(): string { + const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + return `${proto}//${window.location.host}/api/ws`; +} + +export function connectWs(): void { + if (ws) return; + + try { + ws = new WebSocket(getWsUrl()); + + ws.onopen = () => { + connected.set(true); + error.set(null); + // Fetch full status on connect + refreshStatus(); + }; + + ws.onmessage = (event) => { + try { + const msg = JSON.parse(event.data) as WsMessage; + handleMessage(msg); + } catch { + // ignore parse errors + } + }; + + ws.onclose = () => { + connected.set(false); + ws = null; + scheduleReconnect(); + }; + + ws.onerror = () => { + error.set('WebSocket connection failed'); + ws?.close(); + }; + } catch { + scheduleReconnect(); + } +} + +function scheduleReconnect(): void { + if (reconnectTimer) return; + reconnectTimer = setTimeout(() => { + reconnectTimer = null; + connectWs(); + }, 3000); +} + +function handleMessage(msg: WsMessage): void { + if (msg.type === 'state_change' || msg.type === 'status_update') { + refreshStatus(); + } +} + +export async function refreshStatus(): Promise { + try { + const s = await fetchStatus(); + status.set(s); + error.set(null); + } catch (e) { + error.set(e instanceof Error ? e.message : 'Failed to fetch status'); + } +} + +export function disconnectWs(): void { + if (reconnectTimer) { + clearTimeout(reconnectTimer); + reconnectTimer = null; + } + ws?.close(); + ws = null; +} diff --git a/packages/proxy/web/src/routes/+layout.svelte b/packages/proxy/web/src/routes/+layout.svelte new file mode 100644 index 0000000..9e9581e --- /dev/null +++ b/packages/proxy/web/src/routes/+layout.svelte @@ -0,0 +1,78 @@ + + +
+
+ +
+
+ {@render children()} +
+
+ + diff --git a/packages/proxy/web/src/routes/+page.svelte b/packages/proxy/web/src/routes/+page.svelte new file mode 100644 index 0000000..21a60eb --- /dev/null +++ b/packages/proxy/web/src/routes/+page.svelte @@ -0,0 +1,88 @@ + + + + SleepGuard Dashboard + + +
+ {#if $error} +
{$error}
+ {/if} + + {#if !$connected} +
Connecting to server...
+ {/if} + + {#if $status} +
+
+ + {#if $status.idleTimer} + + {/if} + +
+
+ + +
+
+ {:else} +
Loading...
+ {/if} +
+ + diff --git a/packages/proxy/web/src/routes/settings/+page.svelte b/packages/proxy/web/src/routes/settings/+page.svelte new file mode 100644 index 0000000..516555d --- /dev/null +++ b/packages/proxy/web/src/routes/settings/+page.svelte @@ -0,0 +1,157 @@ + + + + Settings — SleepGuard + + +
+

Settings

+ + {#if $status} +
+

Current Configuration

+
+
+ + {$status.config.idleTimeoutMinutes} minutes +
+
+ + {$status.config.healthCheckIntervalSeconds} seconds +
+
+ + {$status.config.wakingTimeoutSeconds} seconds +
+
+
+ + {#if $status.agent} +
+

Agent Details

+
+
+ + {$status.agent.cpu.cores} +
+
+ + {$status.agent.cpu.loadAvg.map(v => v.toFixed(2)).join(' / ')} +
+
+ + {Math.round($status.agent.memory.totalMB / 1024)} GB +
+ {#if $status.agent.gpu.available} +
+ + {$status.agent.gpu.name} +
+
+ + {$status.agent.gpu.memoryUsedMB} / {$status.agent.gpu.memoryTotalMB} MB +
+
+ + {$status.agent.gpu.temperature}°C +
+ {/if} +
+ + R: {$status.agent.diskIo.readKBps} KB/s | W: {$status.agent.diskIo.writeKBps} KB/s +
+
+ + {Math.round($status.agent.uptime / 3600)} hours +
+
+
+ +
+

Watched Processes

+
+ {#each $status.agent.processes as proc} +
+ + {proc.name} + {#if proc.running} + PIDs: {proc.pids.join(', ')} + {/if} +
+ {/each} +
+
+ {/if} + {:else} +

Loading configuration...

+ {/if} +
+ + diff --git a/packages/proxy/web/static/favicon.svg b/packages/proxy/web/static/favicon.svg new file mode 100644 index 0000000..dae3c3f --- /dev/null +++ b/packages/proxy/web/static/favicon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/proxy/web/svelte.config.js b/packages/proxy/web/svelte.config.js new file mode 100644 index 0000000..1fd4f97 --- /dev/null +++ b/packages/proxy/web/svelte.config.js @@ -0,0 +1,19 @@ +import adapter from '@sveltejs/adapter-static'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + preprocess: vitePreprocess(), + kit: { + adapter: adapter({ + pages: 'build', + assets: 'build', + fallback: 'index.html', + }), + paths: { + base: '/dashboard', + }, + }, +}; + +export default config; diff --git a/packages/proxy/web/tsconfig.json b/packages/proxy/web/tsconfig.json new file mode 100644 index 0000000..4344710 --- /dev/null +++ b/packages/proxy/web/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + } +} diff --git a/packages/proxy/web/vite.config.ts b/packages/proxy/web/vite.config.ts new file mode 100644 index 0000000..dbe5670 --- /dev/null +++ b/packages/proxy/web/vite.config.ts @@ -0,0 +1,11 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [sveltekit()], + server: { + proxy: { + '/api': 'http://localhost:3000', + }, + }, +}); diff --git a/packages/shared/package.json b/packages/shared/package.json new file mode 100644 index 0000000..1d2b38a --- /dev/null +++ b/packages/shared/package.json @@ -0,0 +1,23 @@ +{ + "name": "@sleepguard/shared", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "scripts": { + "build": "tsc", + "typecheck": "tsc --noEmit", + "dev": "tsc --watch" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "typescript": "^5.7.0" + } +} diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts new file mode 100644 index 0000000..010060e --- /dev/null +++ b/packages/shared/src/index.ts @@ -0,0 +1,5 @@ +export * from './types/common.js'; +export * from './types/agent.js'; +export * from './types/proxy.js'; +export * from './types/upsnap.js'; +export * from './utils/env.js'; diff --git a/packages/shared/src/types/agent.ts b/packages/shared/src/types/agent.ts new file mode 100644 index 0000000..f74ed63 --- /dev/null +++ b/packages/shared/src/types/agent.ts @@ -0,0 +1,60 @@ +export interface CpuStatus { + usagePercent: number; + loadAvg: [number, number, number]; + cores: number; +} + +export interface GpuStatus { + available: boolean; + usagePercent: number; + memoryUsedMB: number; + memoryTotalMB: number; + temperature: number; + name: string; +} + +export interface MemoryStatus { + usedPercent: number; + usedMB: number; + totalMB: number; +} + +export interface DiskIoStatus { + active: boolean; + readKBps: number; + writeKBps: number; +} + +export interface ProcessInfo { + name: string; + running: boolean; + pids: number[]; +} + +export interface Lock { + name: string; + reason?: string; + createdAt: string; + expiresAt?: string; +} + +export interface CreateLockRequest { + name: string; + ttlSeconds?: number; + reason?: string; +} + +export interface AgentStatus { + cpu: CpuStatus; + gpu: GpuStatus; + memory: MemoryStatus; + diskIo: DiskIoStatus; + processes: ProcessInfo[]; + locks: Lock[]; + uptime: number; +} + +export interface CanShutdownResponse { + canShutdown: boolean; + reasons: string[]; +} diff --git a/packages/shared/src/types/common.ts b/packages/shared/src/types/common.ts new file mode 100644 index 0000000..83988b5 --- /dev/null +++ b/packages/shared/src/types/common.ts @@ -0,0 +1,20 @@ +export enum MachineState { + OFFLINE = 'OFFLINE', + WAKING = 'WAKING', + ONLINE = 'ONLINE', + IDLE_CHECK = 'IDLE_CHECK', + SHUTTING_DOWN = 'SHUTTING_DOWN', +} + +export interface ServiceConfig { + name: string; + host: string; + target: string; +} + +export interface LogEntry { + timestamp: string; + level: 'info' | 'warn' | 'error'; + message: string; + details?: string; +} diff --git a/packages/shared/src/types/proxy.ts b/packages/shared/src/types/proxy.ts new file mode 100644 index 0000000..3e11bf7 --- /dev/null +++ b/packages/shared/src/types/proxy.ts @@ -0,0 +1,41 @@ +import type { MachineState, ServiceConfig, LogEntry } from './common.js'; +import type { AgentStatus, Lock } from './agent.js'; + +export interface ProxyConfig { + idleTimeoutMinutes: number; + healthCheckIntervalSeconds: number; + wakingTimeoutSeconds: number; +} + +export interface ServiceHealth { + name: string; + host: string; + target: string; + healthy: boolean; + lastCheck: string | null; +} + +export interface ProxyStatus { + state: MachineState; + services: ServiceHealth[]; + config: ProxyConfig; + idleTimer: { + lastActivity: string; + remainingSeconds: number; + } | null; + agent: AgentStatus | null; + locks: Lock[]; + logs: LogEntry[]; +} + +export type WsMessageType = + | 'state_change' + | 'status_update' + | 'service_health' + | 'log'; + +export interface WsMessage { + type: WsMessageType; + data: unknown; + timestamp: string; +} diff --git a/packages/shared/src/types/upsnap.ts b/packages/shared/src/types/upsnap.ts new file mode 100644 index 0000000..4fd12d5 --- /dev/null +++ b/packages/shared/src/types/upsnap.ts @@ -0,0 +1,18 @@ +export interface UpSnapAuthResponse { + token: string; + record: { + id: string; + email: string; + username: string; + }; +} + +export interface UpSnapDevice { + id: string; + name: string; + ip: string; + mac: string; + status: string; + collectionId: string; + collectionName: string; +} diff --git a/packages/shared/src/utils/env.ts b/packages/shared/src/utils/env.ts new file mode 100644 index 0000000..b653d22 --- /dev/null +++ b/packages/shared/src/utils/env.ts @@ -0,0 +1,27 @@ +export function requireEnv(name: string): string { + const value = process.env[name]; + if (!value) { + throw new Error(`Missing required environment variable: ${name}`); + } + return value; +} + +export function optionalEnv(name: string, defaultValue: string): string { + return process.env[name] ?? defaultValue; +} + +export function intEnv(name: string, defaultValue: number): number { + const raw = process.env[name]; + if (!raw) return defaultValue; + const parsed = parseInt(raw, 10); + if (isNaN(parsed)) { + throw new Error(`Environment variable ${name} must be a number, got: ${raw}`); + } + return parsed; +} + +export function boolEnv(name: string, defaultValue: boolean): boolean { + const raw = process.env[name]; + if (!raw) return defaultValue; + return raw === 'true' || raw === '1'; +} diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json new file mode 100644 index 0000000..d231bbc --- /dev/null +++ b/packages/shared/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "composite": true + }, + "include": ["src"] +} diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..6997030 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "dist", + "rootDir": "src" + } +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..24642f8 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,2074 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@esbuild/aix-ppc64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/aix-ppc64@npm:0.25.12" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/aix-ppc64@npm:0.27.3" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/android-arm64@npm:0.25.12" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/android-arm64@npm:0.27.3" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/android-arm@npm:0.25.12" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/android-arm@npm:0.27.3" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/android-x64@npm:0.25.12" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/android-x64@npm:0.27.3" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/darwin-arm64@npm:0.25.12" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/darwin-arm64@npm:0.27.3" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/darwin-x64@npm:0.25.12" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/darwin-x64@npm:0.27.3" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/freebsd-arm64@npm:0.25.12" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/freebsd-arm64@npm:0.27.3" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/freebsd-x64@npm:0.25.12" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/freebsd-x64@npm:0.27.3" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-arm64@npm:0.25.12" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-arm64@npm:0.27.3" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-arm@npm:0.25.12" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-arm@npm:0.27.3" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-ia32@npm:0.25.12" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-ia32@npm:0.27.3" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-loong64@npm:0.25.12" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-loong64@npm:0.27.3" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-mips64el@npm:0.25.12" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-mips64el@npm:0.27.3" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-ppc64@npm:0.25.12" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-ppc64@npm:0.27.3" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-riscv64@npm:0.25.12" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-riscv64@npm:0.27.3" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-s390x@npm:0.25.12" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-s390x@npm:0.27.3" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/linux-x64@npm:0.25.12" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-x64@npm:0.27.3" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/netbsd-arm64@npm:0.25.12" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/netbsd-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/netbsd-arm64@npm:0.27.3" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/netbsd-x64@npm:0.25.12" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/netbsd-x64@npm:0.27.3" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/openbsd-arm64@npm:0.25.12" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/openbsd-arm64@npm:0.27.3" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/openbsd-x64@npm:0.25.12" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/openbsd-x64@npm:0.27.3" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openharmony-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/openharmony-arm64@npm:0.25.12" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openharmony-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/openharmony-arm64@npm:0.27.3" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/sunos-x64@npm:0.25.12" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/sunos-x64@npm:0.27.3" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/win32-arm64@npm:0.25.12" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/win32-arm64@npm:0.27.3" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/win32-ia32@npm:0.25.12" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/win32-ia32@npm:0.27.3" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.25.12": + version: 0.25.12 + resolution: "@esbuild/win32-x64@npm:0.25.12" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/win32-x64@npm:0.27.3" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@hono/node-server@npm:^1.14.0": + version: 1.19.9 + resolution: "@hono/node-server@npm:1.19.9" + peerDependencies: + hono: ^4 + checksum: 10c0/de18c06b6b266dc45fe55fb82053bd1da8fe84939c49b6fbab4d2448b679d54ab5affbf8b15de9bead26f29b1755284d770aafb5ad14a8e4b3cfb4f79334554e + languageName: node + linkType: hard + +"@isaacs/balanced-match@npm:^4.0.1": + version: 4.0.1 + resolution: "@isaacs/balanced-match@npm:4.0.1" + checksum: 10c0/7da011805b259ec5c955f01cee903da72ad97c5e6f01ca96197267d3f33103d5b2f8a1af192140f3aa64526c593c8d098ae366c2b11f7f17645d12387c2fd420 + languageName: node + linkType: hard + +"@isaacs/brace-expansion@npm:^5.0.1": + version: 5.0.1 + resolution: "@isaacs/brace-expansion@npm:5.0.1" + dependencies: + "@isaacs/balanced-match": "npm:^4.0.1" + checksum: 10c0/e5d67c7bbf1f17b88132a35bc638af306d48acbb72810d48fa6e6edd8ab375854773108e8bf70f021f7ef6a8273455a6d1f0c3b5aa2aff06ce7894049ab77fb8 + languageName: node + linkType: hard + +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: "npm:^7.0.4" + checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.13 + resolution: "@jridgewell/gen-mapping@npm:0.3.13" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/9a7d65fb13bd9aec1fbab74cda08496839b7e2ceb31f5ab922b323e94d7c481ce0fc4fd7e12e2610915ed8af51178bdc61e168e92a8c8b8303b030b03489b13b + languageName: node + linkType: hard + +"@jridgewell/remapping@npm:^2.3.4": + version: 2.3.5 + resolution: "@jridgewell/remapping@npm:2.3.5" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/3de494219ffeb2c5c38711d0d7bb128097edf91893090a2dbc8ee0b55d092bb7347b1fd0f478486c5eab010e855c73927b1666f2107516d472d24a73017d1194 + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.15, @jridgewell/sourcemap-codec@npm:^1.5.0, @jridgewell/sourcemap-codec@npm:^1.5.5": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": + version: 0.3.31 + resolution: "@jridgewell/trace-mapping@npm:0.3.31" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/4b30ec8cd56c5fd9a661f088230af01e0c1a3888d11ffb6b47639700f71225be21d1f7e168048d6d4f9449207b978a235c07c8f15c07705685d16dc06280e9d9 + languageName: node + linkType: hard + +"@npmcli/agent@npm:^4.0.0": + version: 4.0.0 + resolution: "@npmcli/agent@npm:4.0.0" + dependencies: + agent-base: "npm:^7.1.0" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.1" + lru-cache: "npm:^11.2.1" + socks-proxy-agent: "npm:^8.0.3" + checksum: 10c0/f7b5ce0f3dd42c3f8c6546e8433573d8049f67ef11ec22aa4704bc41483122f68bf97752e06302c455ead667af5cb753e6a09bff06632bc465c1cfd4c4b75a53 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^5.0.0": + version: 5.0.0 + resolution: "@npmcli/fs@npm:5.0.0" + dependencies: + semver: "npm:^7.3.5" + checksum: 10c0/26e376d780f60ff16e874a0ac9bc3399186846baae0b6e1352286385ac134d900cc5dafaded77f38d77f86898fc923ae1cee9d7399f0275b1aa24878915d722b + languageName: node + linkType: hard + +"@polka/url@npm:^1.0.0-next.24": + version: 1.0.0-next.29 + resolution: "@polka/url@npm:1.0.0-next.29" + checksum: 10c0/0d58e081844095cb029d3c19a659bfefd09d5d51a2f791bc61eba7ea826f13d6ee204a8a448c2f5a855c17df07b37517373ff916dd05801063c0568ae9937684 + languageName: node + linkType: hard + +"@rollup/rollup-android-arm-eabi@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.57.1" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@rollup/rollup-android-arm64@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-android-arm64@npm:4.57.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-arm64@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-darwin-arm64@npm:4.57.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-x64@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-darwin-x64@npm:4.57.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-arm64@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.57.1" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-x64@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-freebsd-x64@npm:4.57.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-gnueabihf@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.57.1" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-musleabihf@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.57.1" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-gnu@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.57.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-musl@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.57.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-loong64-gnu@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.57.1" + conditions: os=linux & cpu=loong64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-loong64-musl@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-loong64-musl@npm:4.57.1" + conditions: os=linux & cpu=loong64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-gnu@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.57.1" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-musl@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-ppc64-musl@npm:4.57.1" + conditions: os=linux & cpu=ppc64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-gnu@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.57.1" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-musl@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.57.1" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-s390x-gnu@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.57.1" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-gnu@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.57.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-musl@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.57.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-openbsd-x64@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-openbsd-x64@npm:4.57.1" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-openharmony-arm64@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.57.1" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-arm64-msvc@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.57.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-ia32-msvc@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.57.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-gnu@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-win32-x64-gnu@npm:4.57.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-msvc@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.57.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@sleepguard/agent@workspace:packages/agent": + version: 0.0.0-use.local + resolution: "@sleepguard/agent@workspace:packages/agent" + dependencies: + "@hono/node-server": "npm:^1.14.0" + "@sleepguard/shared": "workspace:*" + "@types/node": "npm:^22.0.0" + hono: "npm:^4.7.0" + tsx: "npm:^4.19.0" + typescript: "npm:^5.7.0" + languageName: unknown + linkType: soft + +"@sleepguard/proxy@workspace:packages/proxy": + version: 0.0.0-use.local + resolution: "@sleepguard/proxy@workspace:packages/proxy" + dependencies: + "@hono/node-server": "npm:^1.14.0" + "@sleepguard/shared": "workspace:*" + "@types/http-proxy": "npm:^1.17.0" + "@types/node": "npm:^22.0.0" + "@types/ws": "npm:^8.5.0" + hono: "npm:^4.7.0" + http-proxy: "npm:^1.18.1" + tsx: "npm:^4.19.0" + typescript: "npm:^5.7.0" + ws: "npm:^8.18.0" + languageName: unknown + linkType: soft + +"@sleepguard/shared@workspace:*, @sleepguard/shared@workspace:packages/shared": + version: 0.0.0-use.local + resolution: "@sleepguard/shared@workspace:packages/shared" + dependencies: + "@types/node": "npm:^22.0.0" + typescript: "npm:^5.7.0" + languageName: unknown + linkType: soft + +"@sleepguard/web@workspace:packages/proxy/web": + version: 0.0.0-use.local + resolution: "@sleepguard/web@workspace:packages/proxy/web" + dependencies: + "@sleepguard/shared": "workspace:*" + "@sveltejs/adapter-static": "npm:^3.0.0" + "@sveltejs/kit": "npm:^2.15.0" + "@sveltejs/vite-plugin-svelte": "npm:^5.0.0" + svelte: "npm:^5.0.0" + svelte-check: "npm:^4.0.0" + typescript: "npm:^5.7.0" + vite: "npm:^6.0.0" + languageName: unknown + linkType: soft + +"@standard-schema/spec@npm:^1.0.0": + version: 1.1.0 + resolution: "@standard-schema/spec@npm:1.1.0" + checksum: 10c0/d90f55acde4b2deb983529c87e8025fa693de1a5e8b49ecc6eb84d1fd96328add0e03d7d551442156c7432fd78165b2c26ff561b970a9a881f046abb78d6a526 + languageName: node + linkType: hard + +"@sveltejs/acorn-typescript@npm:^1.0.5": + version: 1.0.8 + resolution: "@sveltejs/acorn-typescript@npm:1.0.8" + peerDependencies: + acorn: ^8.9.0 + checksum: 10c0/3de68af48db0b9cbc82872b218cd9134f494ba7716872e8c11bfdbb156b11dba2205541a627eed943733e4a4e8bbb261fe898bf7659105e1a4c641033fe3d4fe + languageName: node + linkType: hard + +"@sveltejs/adapter-static@npm:^3.0.0": + version: 3.0.10 + resolution: "@sveltejs/adapter-static@npm:3.0.10" + peerDependencies: + "@sveltejs/kit": ^2.0.0 + checksum: 10c0/14a8c05e1ef2bd07fe0fbc709f13f13cdd17b8f7940ccae61f6aa1b2c1a32e8067c2a36b3a80b10558a52fe2c8b07f581b848af2fe123c41b79a5fc4ec8d3140 + languageName: node + linkType: hard + +"@sveltejs/kit@npm:^2.15.0": + version: 2.50.2 + resolution: "@sveltejs/kit@npm:2.50.2" + dependencies: + "@standard-schema/spec": "npm:^1.0.0" + "@sveltejs/acorn-typescript": "npm:^1.0.5" + "@types/cookie": "npm:^0.6.0" + acorn: "npm:^8.14.1" + cookie: "npm:^0.6.0" + devalue: "npm:^5.6.2" + esm-env: "npm:^1.2.2" + kleur: "npm:^4.1.5" + magic-string: "npm:^0.30.5" + mrmime: "npm:^2.0.0" + sade: "npm:^1.8.1" + set-cookie-parser: "npm:^3.0.0" + sirv: "npm:^3.0.0" + peerDependencies: + "@opentelemetry/api": ^1.0.0 + "@sveltejs/vite-plugin-svelte": ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 + svelte: ^4.0.0 || ^5.0.0-next.0 + typescript: ^5.3.3 + vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 + peerDependenciesMeta: + "@opentelemetry/api": + optional: true + typescript: + optional: true + bin: + svelte-kit: svelte-kit.js + checksum: 10c0/a5e6c7781ae180bc35c5cdac8f17cc5795a3c50bd6b1ef422db11dafc0c52e95f3ab6f8fe2d08a90141d3541d60f4067deaff362d6da0022243263eee32d9205 + languageName: node + linkType: hard + +"@sveltejs/vite-plugin-svelte-inspector@npm:^4.0.1": + version: 4.0.1 + resolution: "@sveltejs/vite-plugin-svelte-inspector@npm:4.0.1" + dependencies: + debug: "npm:^4.3.7" + peerDependencies: + "@sveltejs/vite-plugin-svelte": ^5.0.0 + svelte: ^5.0.0 + vite: ^6.0.0 + checksum: 10c0/03cc334346fcf291d3d0cba435bf752d15df91c0ac55dccdb5bf1372ae5a85fcb25f0f63ca3d1f7a25ebaa9e711fd1178d8a1fd6f495d57ac2eb327b855c0aea + languageName: node + linkType: hard + +"@sveltejs/vite-plugin-svelte@npm:^5.0.0": + version: 5.1.1 + resolution: "@sveltejs/vite-plugin-svelte@npm:5.1.1" + dependencies: + "@sveltejs/vite-plugin-svelte-inspector": "npm:^4.0.1" + debug: "npm:^4.4.1" + deepmerge: "npm:^4.3.1" + kleur: "npm:^4.1.5" + magic-string: "npm:^0.30.17" + vitefu: "npm:^1.0.6" + peerDependencies: + svelte: ^5.0.0 + vite: ^6.0.0 + checksum: 10c0/a8ca872c07180ac0676d54243ba0bd47690ad07933abe5c02732bdd286b31a27370e1ed67f07b4de012aea0b7c8d12d67a3c48d0567b18af67dc5adebea6859a + languageName: node + linkType: hard + +"@types/cookie@npm:^0.6.0": + version: 0.6.0 + resolution: "@types/cookie@npm:0.6.0" + checksum: 10c0/5b326bd0188120fb32c0be086b141b1481fec9941b76ad537f9110e10d61ee2636beac145463319c71e4be67a17e85b81ca9e13ceb6e3bb63b93d16824d6c149 + languageName: node + linkType: hard + +"@types/estree@npm:1.0.8, @types/estree@npm:^1.0.5, @types/estree@npm:^1.0.6": + version: 1.0.8 + resolution: "@types/estree@npm:1.0.8" + checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5 + languageName: node + linkType: hard + +"@types/http-proxy@npm:^1.17.0": + version: 1.17.17 + resolution: "@types/http-proxy@npm:1.17.17" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/547e322a5eecf0b50d08f6a46bd89c8c8663d67dbdcd472da5daf968b03e63a82f6b3650443378abe6c10a46475dac52015f30e8c74ba2ea5820dd4e9cdef2d4 + languageName: node + linkType: hard + +"@types/node@npm:*": + version: 25.2.2 + resolution: "@types/node@npm:25.2.2" + dependencies: + undici-types: "npm:~7.16.0" + checksum: 10c0/45aa45b00df0aac4712c2d6e934a6ed21ac54e0284dd726df1c7620b8c7d36a4fb601b9f8fe1d2951298d1ee7618cf8275688e329c295eb36e8b8fa827a8e334 + languageName: node + linkType: hard + +"@types/node@npm:^22.0.0": + version: 22.19.10 + resolution: "@types/node@npm:22.19.10" + dependencies: + undici-types: "npm:~6.21.0" + checksum: 10c0/fb1484f68c259abb73b018e6a521dba2ffe7846128d3aacc7fba5672f354ddb364cb4c81b5a0e686cf58ea0067df4d68d4159f668c864238a0c3fe7fee603341 + languageName: node + linkType: hard + +"@types/ws@npm:^8.5.0": + version: 8.18.1 + resolution: "@types/ws@npm:8.18.1" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/61aff1129143fcc4312f083bc9e9e168aa3026b7dd6e70796276dcfb2c8211c4292603f9c4864fae702f2ed86e4abd4d38aa421831c2fd7f856c931a481afbab + languageName: node + linkType: hard + +"abbrev@npm:^4.0.0": + version: 4.0.0 + resolution: "abbrev@npm:4.0.0" + checksum: 10c0/b4cc16935235e80702fc90192e349e32f8ef0ed151ef506aa78c81a7c455ec18375c4125414b99f84b2e055199d66383e787675f0bcd87da7a4dbd59f9eac1d5 + languageName: node + linkType: hard + +"acorn@npm:^8.12.1, acorn@npm:^8.14.1": + version: 8.15.0 + resolution: "acorn@npm:8.15.0" + bin: + acorn: bin/acorn + checksum: 10c0/dec73ff59b7d6628a01eebaece7f2bdb8bb62b9b5926dcad0f8931f2b8b79c2be21f6c68ac095592adb5adb15831a3635d9343e6a91d028bbe85d564875ec3ec + languageName: node + linkType: hard + +"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": + version: 7.1.4 + resolution: "agent-base@npm:7.1.4" + checksum: 10c0/c2c9ab7599692d594b6a161559ada307b7a624fa4c7b03e3afdb5a5e31cd0e53269115b620fcab024c5ac6a6f37fa5eb2e004f076ad30f5f7e6b8b671f7b35fe + languageName: node + linkType: hard + +"aria-query@npm:^5.3.1": + version: 5.3.2 + resolution: "aria-query@npm:5.3.2" + checksum: 10c0/003c7e3e2cff5540bf7a7893775fc614de82b0c5dde8ae823d47b7a28a9d4da1f7ed85f340bdb93d5649caa927755f0e31ecc7ab63edfdfc00c8ef07e505e03e + languageName: node + linkType: hard + +"axobject-query@npm:^4.1.0": + version: 4.1.0 + resolution: "axobject-query@npm:4.1.0" + checksum: 10c0/c470e4f95008f232eadd755b018cb55f16c03ccf39c027b941cd8820ac6b68707ce5d7368a46756db4256fbc91bb4ead368f84f7fb034b2b7932f082f6dc0775 + languageName: node + linkType: hard + +"cacache@npm:^20.0.1": + version: 20.0.3 + resolution: "cacache@npm:20.0.3" + dependencies: + "@npmcli/fs": "npm:^5.0.0" + fs-minipass: "npm:^3.0.0" + glob: "npm:^13.0.0" + lru-cache: "npm:^11.1.0" + minipass: "npm:^7.0.3" + minipass-collect: "npm:^2.0.1" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + p-map: "npm:^7.0.2" + ssri: "npm:^13.0.0" + unique-filename: "npm:^5.0.0" + checksum: 10c0/c7da1ca694d20e8f8aedabd21dc11518f809a7d2b59aa76a1fc655db5a9e62379e465c157ddd2afe34b19230808882288effa6911b2de26a088a6d5645123462 + languageName: node + linkType: hard + +"chokidar@npm:^4.0.1": + version: 4.0.3 + resolution: "chokidar@npm:4.0.3" + dependencies: + readdirp: "npm:^4.0.1" + checksum: 10c0/a58b9df05bb452f7d105d9e7229ac82fa873741c0c40ddcc7bb82f8a909fbe3f7814c9ebe9bc9a2bef9b737c0ec6e2d699d179048ef06ad3ec46315df0ebe6ad + languageName: node + linkType: hard + +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 + languageName: node + linkType: hard + +"clsx@npm:^2.1.1": + version: 2.1.1 + resolution: "clsx@npm:2.1.1" + checksum: 10c0/c4c8eb865f8c82baab07e71bfa8897c73454881c4f99d6bc81585aecd7c441746c1399d08363dc096c550cceaf97bd4ce1e8854e1771e9998d9f94c4fe075839 + languageName: node + linkType: hard + +"cookie@npm:^0.6.0": + version: 0.6.0 + resolution: "cookie@npm:0.6.0" + checksum: 10c0/f2318b31af7a31b4ddb4a678d024514df5e705f9be5909a192d7f116cfb6d45cbacf96a473fa733faa95050e7cff26e7832bb3ef94751592f1387b71c8956686 + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.3.4, debug@npm:^4.3.7, debug@npm:^4.4.1": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/d79136ec6c83ecbefd0f6a5593da6a9c91ec4d7ddc4b54c883d6e71ec9accb5f67a1a5e96d00a328196b5b5c86d365e98d8a3a70856aaf16b4e7b1985e67f5a6 + languageName: node + linkType: hard + +"deepmerge@npm:^4.3.1": + version: 4.3.1 + resolution: "deepmerge@npm:4.3.1" + checksum: 10c0/e53481aaf1aa2c4082b5342be6b6d8ad9dfe387bc92ce197a66dea08bd4265904a087e75e464f14d1347cf2ac8afe1e4c16b266e0561cc5df29382d3c5f80044 + languageName: node + linkType: hard + +"devalue@npm:^5.6.2": + version: 5.6.2 + resolution: "devalue@npm:5.6.2" + checksum: 10c0/654f257ec525a2d3f35c941bfbb361148bc65ced060710969fbaa1c45abf1c9d7c4fcb77310bf8d2fb73c34cf60bad10710e7bf5b15643bbc082518ea04cb00b + languageName: node + linkType: hard + +"encoding@npm:^0.1.13": + version: 0.1.13 + resolution: "encoding@npm:0.1.13" + dependencies: + iconv-lite: "npm:^0.6.2" + checksum: 10c0/36d938712ff00fe1f4bac88b43bcffb5930c1efa57bbcdca9d67e1d9d6c57cfb1200fb01efe0f3109b2ce99b231f90779532814a81370a1bd3274a0f58585039 + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 + languageName: node + linkType: hard + +"err-code@npm:^2.0.2": + version: 2.0.3 + resolution: "err-code@npm:2.0.3" + checksum: 10c0/b642f7b4dd4a376e954947550a3065a9ece6733ab8e51ad80db727aaae0817c2e99b02a97a3d6cecc648a97848305e728289cf312d09af395403a90c9d4d8a66 + languageName: node + linkType: hard + +"esbuild@npm:^0.25.0": + version: 0.25.12 + resolution: "esbuild@npm:0.25.12" + dependencies: + "@esbuild/aix-ppc64": "npm:0.25.12" + "@esbuild/android-arm": "npm:0.25.12" + "@esbuild/android-arm64": "npm:0.25.12" + "@esbuild/android-x64": "npm:0.25.12" + "@esbuild/darwin-arm64": "npm:0.25.12" + "@esbuild/darwin-x64": "npm:0.25.12" + "@esbuild/freebsd-arm64": "npm:0.25.12" + "@esbuild/freebsd-x64": "npm:0.25.12" + "@esbuild/linux-arm": "npm:0.25.12" + "@esbuild/linux-arm64": "npm:0.25.12" + "@esbuild/linux-ia32": "npm:0.25.12" + "@esbuild/linux-loong64": "npm:0.25.12" + "@esbuild/linux-mips64el": "npm:0.25.12" + "@esbuild/linux-ppc64": "npm:0.25.12" + "@esbuild/linux-riscv64": "npm:0.25.12" + "@esbuild/linux-s390x": "npm:0.25.12" + "@esbuild/linux-x64": "npm:0.25.12" + "@esbuild/netbsd-arm64": "npm:0.25.12" + "@esbuild/netbsd-x64": "npm:0.25.12" + "@esbuild/openbsd-arm64": "npm:0.25.12" + "@esbuild/openbsd-x64": "npm:0.25.12" + "@esbuild/openharmony-arm64": "npm:0.25.12" + "@esbuild/sunos-x64": "npm:0.25.12" + "@esbuild/win32-arm64": "npm:0.25.12" + "@esbuild/win32-ia32": "npm:0.25.12" + "@esbuild/win32-x64": "npm:0.25.12" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/c205357531423220a9de8e1e6c6514242bc9b1666e762cd67ccdf8fdfdc3f1d0bd76f8d9383958b97ad4c953efdb7b6e8c1f9ca5951cd2b7c5235e8755b34a6b + languageName: node + linkType: hard + +"esbuild@npm:~0.27.0": + version: 0.27.3 + resolution: "esbuild@npm:0.27.3" + dependencies: + "@esbuild/aix-ppc64": "npm:0.27.3" + "@esbuild/android-arm": "npm:0.27.3" + "@esbuild/android-arm64": "npm:0.27.3" + "@esbuild/android-x64": "npm:0.27.3" + "@esbuild/darwin-arm64": "npm:0.27.3" + "@esbuild/darwin-x64": "npm:0.27.3" + "@esbuild/freebsd-arm64": "npm:0.27.3" + "@esbuild/freebsd-x64": "npm:0.27.3" + "@esbuild/linux-arm": "npm:0.27.3" + "@esbuild/linux-arm64": "npm:0.27.3" + "@esbuild/linux-ia32": "npm:0.27.3" + "@esbuild/linux-loong64": "npm:0.27.3" + "@esbuild/linux-mips64el": "npm:0.27.3" + "@esbuild/linux-ppc64": "npm:0.27.3" + "@esbuild/linux-riscv64": "npm:0.27.3" + "@esbuild/linux-s390x": "npm:0.27.3" + "@esbuild/linux-x64": "npm:0.27.3" + "@esbuild/netbsd-arm64": "npm:0.27.3" + "@esbuild/netbsd-x64": "npm:0.27.3" + "@esbuild/openbsd-arm64": "npm:0.27.3" + "@esbuild/openbsd-x64": "npm:0.27.3" + "@esbuild/openharmony-arm64": "npm:0.27.3" + "@esbuild/sunos-x64": "npm:0.27.3" + "@esbuild/win32-arm64": "npm:0.27.3" + "@esbuild/win32-ia32": "npm:0.27.3" + "@esbuild/win32-x64": "npm:0.27.3" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/fdc3f87a3f08b3ef98362f37377136c389a0d180fda4b8d073b26ba930cf245521db0a368f119cc7624bc619248fff1439f5811f062d853576f8ffa3df8ee5f1 + languageName: node + linkType: hard + +"esm-env@npm:^1.2.1, esm-env@npm:^1.2.2": + version: 1.2.2 + resolution: "esm-env@npm:1.2.2" + checksum: 10c0/3d25c973f2fd69c25ffff29c964399cea573fe10795ecc1d26f6f957ce0483d3254e1cceddb34bf3296a0d7b0f1d53a28992f064ba509dfe6366751e752c4166 + languageName: node + linkType: hard + +"esrap@npm:^2.2.2": + version: 2.2.3 + resolution: "esrap@npm:2.2.3" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.4.15" + checksum: 10c0/6eda59f9968e52b9b150dec0cb8acf2f42fef62b16918cefb64d25e5aeaa969cf44ca2a6260b14994cb7e5ac2e78ab8170158339901ab0e913c52f780891ab95 + languageName: node + linkType: hard + +"eventemitter3@npm:^4.0.0": + version: 4.0.7 + resolution: "eventemitter3@npm:4.0.7" + checksum: 10c0/5f6d97cbcbac47be798e6355e3a7639a84ee1f7d9b199a07017f1d2f1e2fe236004d14fa5dfaeba661f94ea57805385e326236a6debbc7145c8877fbc0297c6b + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.3 + resolution: "exponential-backoff@npm:3.1.3" + checksum: 10c0/77e3ae682b7b1f4972f563c6dbcd2b0d54ac679e62d5d32f3e5085feba20483cf28bd505543f520e287a56d4d55a28d7874299941faf637e779a1aa5994d1267 + languageName: node + linkType: hard + +"fdir@npm:^6.2.0, fdir@npm:^6.4.4, fdir@npm:^6.5.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f + languageName: node + linkType: hard + +"follow-redirects@npm:^1.0.0": + version: 1.15.11 + resolution: "follow-redirects@npm:1.15.11" + peerDependenciesMeta: + debug: + optional: true + checksum: 10c0/d301f430542520a54058d4aeeb453233c564aaccac835d29d15e050beb33f339ad67d9bddbce01739c5dc46a6716dbe3d9d0d5134b1ca203effa11a7ef092343 + languageName: node + linkType: hard + +"fs-minipass@npm:^3.0.0": + version: 3.0.3 + resolution: "fs-minipass@npm:3.0.3" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94 + languageName: node + linkType: hard + +"fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + +"get-tsconfig@npm:^4.7.5": + version: 4.13.6 + resolution: "get-tsconfig@npm:4.13.6" + dependencies: + resolve-pkg-maps: "npm:^1.0.0" + checksum: 10c0/bab6937302f542f97217cbe7cbbdfa7e85a56a377bc7a73e69224c1f0b7c9ae8365918e55752ae8648265903f506c1705f63c0de1d4bab1ec2830fef3e539a1a + languageName: node + linkType: hard + +"glob@npm:^13.0.0": + version: 13.0.1 + resolution: "glob@npm:13.0.1" + dependencies: + minimatch: "npm:^10.1.2" + minipass: "npm:^7.1.2" + path-scurry: "npm:^2.0.0" + checksum: 10c0/af7b863dec8dff74f61d7d6e53104e1f6bbdd482157a196cade8ed857481e876ec35181b38a059b2a7b93ea3b08248f4ff0792fef6dc91814fd5097a716f48e4 + languageName: node + linkType: hard + +"graceful-fs@npm:^4.2.6": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 + languageName: node + linkType: hard + +"hono@npm:^4.7.0": + version: 4.11.9 + resolution: "hono@npm:4.11.9" + checksum: 10c0/fe396a8e1a61755adb7afe8ce8e0935a6423a5680ec62f4fd3577c5c5a9236dec390dc28fef6b9742e067b5d9c53ddb3aa511b3359bf87dcc2105dae751f1242 + languageName: node + linkType: hard + +"http-cache-semantics@npm:^4.1.1": + version: 4.2.0 + resolution: "http-cache-semantics@npm:4.2.0" + checksum: 10c0/45b66a945cf13ec2d1f29432277201313babf4a01d9e52f44b31ca923434083afeca03f18417f599c9ab3d0e7b618ceb21257542338b57c54b710463b4a53e37 + languageName: node + linkType: hard + +"http-proxy-agent@npm:^7.0.0": + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" + dependencies: + agent-base: "npm:^7.1.0" + debug: "npm:^4.3.4" + checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 + languageName: node + linkType: hard + +"http-proxy@npm:^1.18.1": + version: 1.18.1 + resolution: "http-proxy@npm:1.18.1" + dependencies: + eventemitter3: "npm:^4.0.0" + follow-redirects: "npm:^1.0.0" + requires-port: "npm:^1.0.0" + checksum: 10c0/148dfa700a03fb421e383aaaf88ac1d94521dfc34072f6c59770528c65250983c2e4ec996f2f03aa9f3fe46cd1270a593126068319311e3e8d9e610a37533e94 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^7.0.1": + version: 7.0.6 + resolution: "https-proxy-agent@npm:7.0.6" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:4" + checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac + languageName: node + linkType: hard + +"iconv-lite@npm:^0.6.2": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 + languageName: node + linkType: hard + +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 + languageName: node + linkType: hard + +"ip-address@npm:^10.0.1": + version: 10.1.0 + resolution: "ip-address@npm:10.1.0" + checksum: 10c0/0103516cfa93f6433b3bd7333fa876eb21263912329bfa47010af5e16934eeeff86f3d2ae700a3744a137839ddfad62b900c7a445607884a49b5d1e32a3d7566 + languageName: node + linkType: hard + +"is-reference@npm:^3.0.3": + version: 3.0.3 + resolution: "is-reference@npm:3.0.3" + dependencies: + "@types/estree": "npm:^1.0.6" + checksum: 10c0/35edd284cfb4cd9e9f08973f20e276ec517eaca31f5f049598e97dbb2d05544973dde212dac30fddee5b420930bff365e2e67dcd1293d0866c6720377382e3e5 + languageName: node + linkType: hard + +"isexe@npm:^3.1.1": + version: 3.1.5 + resolution: "isexe@npm:3.1.5" + checksum: 10c0/8be2973a09f2f804ea1f34bfccfd5ea219ef48083bdb12107fe5bcf96b3e36b85084409e1b09ddaf2fae8927fdd9f6d70d90baadb78caa1ca7c530935706c8a4 + languageName: node + linkType: hard + +"kleur@npm:^4.1.5": + version: 4.1.5 + resolution: "kleur@npm:4.1.5" + checksum: 10c0/e9de6cb49657b6fa70ba2d1448fd3d691a5c4370d8f7bbf1c2f64c24d461270f2117e1b0afe8cb3114f13bbd8e51de158c2a224953960331904e636a5e4c0f2a + languageName: node + linkType: hard + +"locate-character@npm:^3.0.0": + version: 3.0.0 + resolution: "locate-character@npm:3.0.0" + checksum: 10c0/9da917622395002eb1336fca8cbef1c19904e3dc0b3b8078abe8ff390106d947a86feccecd0346f0e0e19fa017623fb4ccb65263d72a76dfa36e20cc18766b6c + languageName: node + linkType: hard + +"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1": + version: 11.2.5 + resolution: "lru-cache@npm:11.2.5" + checksum: 10c0/cc98958d25dddf1c8a8cbdc49588bd3b24450e8dfa78f32168fd188a20d4a0331c7406d0f3250c86a46619ee288056fd7a1195e8df56dc8a9592397f4fbd8e1d + languageName: node + linkType: hard + +"magic-string@npm:^0.30.11, magic-string@npm:^0.30.17, magic-string@npm:^0.30.5": + version: 0.30.21 + resolution: "magic-string@npm:0.30.21" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.5" + checksum: 10c0/299378e38f9a270069fc62358522ddfb44e94244baa0d6a8980ab2a9b2490a1d03b236b447eee309e17eb3bddfa482c61259d47960eb018a904f0ded52780c4a + languageName: node + linkType: hard + +"make-fetch-happen@npm:^15.0.0": + version: 15.0.3 + resolution: "make-fetch-happen@npm:15.0.3" + dependencies: + "@npmcli/agent": "npm:^4.0.0" + cacache: "npm:^20.0.1" + http-cache-semantics: "npm:^4.1.1" + minipass: "npm:^7.0.2" + minipass-fetch: "npm:^5.0.0" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + negotiator: "npm:^1.0.0" + proc-log: "npm:^6.0.0" + promise-retry: "npm:^2.0.1" + ssri: "npm:^13.0.0" + checksum: 10c0/525f74915660be60b616bcbd267c4a5b59481b073ba125e45c9c3a041bb1a47a2bd0ae79d028eb6f5f95bf9851a4158423f5068539c3093621abb64027e8e461 + languageName: node + linkType: hard + +"minimatch@npm:^10.1.2": + version: 10.1.2 + resolution: "minimatch@npm:10.1.2" + dependencies: + "@isaacs/brace-expansion": "npm:^5.0.1" + checksum: 10c0/0cccef3622201703de6ecf9d772c0be1d5513dcc038ed9feb866c20cf798243e678ac35605dac3f1a054650c28037486713fe9e9a34b184b9097959114daf086 + languageName: node + linkType: hard + +"minipass-collect@npm:^2.0.1": + version: 2.0.1 + resolution: "minipass-collect@npm:2.0.1" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e + languageName: node + linkType: hard + +"minipass-fetch@npm:^5.0.0": + version: 5.0.1 + resolution: "minipass-fetch@npm:5.0.1" + dependencies: + encoding: "npm:^0.1.13" + minipass: "npm:^7.0.3" + minipass-sized: "npm:^2.0.0" + minizlib: "npm:^3.0.1" + dependenciesMeta: + encoding: + optional: true + checksum: 10c0/50bcf48c9841ebb25e29a2817468595219c72cfffc7c175a1d7327843c8bef9b72cb01778f46df7eca695dfe47ab98e6167af4cb026ddd80f660842919a5193c + languageName: node + linkType: hard + +"minipass-flush@npm:^1.0.5": + version: 1.0.5 + resolution: "minipass-flush@npm:1.0.5" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd + languageName: node + linkType: hard + +"minipass-pipeline@npm:^1.2.4": + version: 1.2.4 + resolution: "minipass-pipeline@npm:1.2.4" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2 + languageName: node + linkType: hard + +"minipass-sized@npm:^2.0.0": + version: 2.0.0 + resolution: "minipass-sized@npm:2.0.0" + dependencies: + minipass: "npm:^7.1.2" + checksum: 10c0/f9201696a6f6d68610d04c9c83e3d2e5cb9c026aae1c8cbf7e17f386105cb79c1bb088dbc21bf0b1eb4f3fb5df384fd1e7aa3bf1f33868c416ae8c8a92679db8 + languageName: node + linkType: hard + +"minipass@npm:^3.0.0": + version: 3.3.6 + resolution: "minipass@npm:3.3.6" + dependencies: + yallist: "npm:^4.0.0" + checksum: 10c0/a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c + languageName: node + linkType: hard + +"minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": + version: 7.1.2 + resolution: "minipass@npm:7.1.2" + checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 + languageName: node + linkType: hard + +"minizlib@npm:^3.0.1, minizlib@npm:^3.1.0": + version: 3.1.0 + resolution: "minizlib@npm:3.1.0" + dependencies: + minipass: "npm:^7.1.2" + checksum: 10c0/5aad75ab0090b8266069c9aabe582c021ae53eb33c6c691054a13a45db3b4f91a7fb1bd79151e6b4e9e9a86727b522527c0a06ec7d45206b745d54cd3097bcec + languageName: node + linkType: hard + +"mri@npm:^1.1.0": + version: 1.2.0 + resolution: "mri@npm:1.2.0" + checksum: 10c0/a3d32379c2554cf7351db6237ddc18dc9e54e4214953f3da105b97dc3babe0deb3ffe99cf409b38ea47cc29f9430561ba6b53b24ab8f9ce97a4b50409e4a50e7 + languageName: node + linkType: hard + +"mrmime@npm:^2.0.0": + version: 2.0.1 + resolution: "mrmime@npm:2.0.1" + checksum: 10c0/af05afd95af202fdd620422f976ad67dc18e6ee29beb03dd1ce950ea6ef664de378e44197246df4c7cdd73d47f2e7143a6e26e473084b9e4aa2095c0ad1e1761 + languageName: node + linkType: hard + +"ms@npm:^2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 + languageName: node + linkType: hard + +"nanoid@npm:^3.3.11": + version: 3.3.11 + resolution: "nanoid@npm:3.3.11" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/40e7f70b3d15f725ca072dfc4f74e81fcf1fbb02e491cf58ac0c79093adc9b0a73b152bcde57df4b79cd097e13023d7504acb38404a4da7bc1cd8e887b82fe0b + languageName: node + linkType: hard + +"negotiator@npm:^1.0.0": + version: 1.0.0 + resolution: "negotiator@npm:1.0.0" + checksum: 10c0/4c559dd52669ea48e1914f9d634227c561221dd54734070791f999c52ed0ff36e437b2e07d5c1f6e32909fc625fe46491c16e4a8f0572567d4dd15c3a4fda04b + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 12.2.0 + resolution: "node-gyp@npm:12.2.0" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + graceful-fs: "npm:^4.2.6" + make-fetch-happen: "npm:^15.0.0" + nopt: "npm:^9.0.0" + proc-log: "npm:^6.0.0" + semver: "npm:^7.3.5" + tar: "npm:^7.5.4" + tinyglobby: "npm:^0.2.12" + which: "npm:^6.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/3ed046746a5a7d90950cd8b0547332b06598443f31fe213ef4332a7174c7b7d259e1704835feda79b87d3f02e59d7791842aac60642ede4396ab25fdf0f8f759 + languageName: node + linkType: hard + +"nopt@npm:^9.0.0": + version: 9.0.0 + resolution: "nopt@npm:9.0.0" + dependencies: + abbrev: "npm:^4.0.0" + bin: + nopt: bin/nopt.js + checksum: 10c0/1822eb6f9b020ef6f7a7516d7b64a8036e09666ea55ac40416c36e4b2b343122c3cff0e2f085675f53de1d2db99a2a89a60ccea1d120bcd6a5347bf6ceb4a7fd + languageName: node + linkType: hard + +"p-map@npm:^7.0.2": + version: 7.0.4 + resolution: "p-map@npm:7.0.4" + checksum: 10c0/a5030935d3cb2919d7e89454d1ce82141e6f9955413658b8c9403cfe379283770ed3048146b44cde168aa9e8c716505f196d5689db0ae3ce9a71521a2fef3abd + languageName: node + linkType: hard + +"path-scurry@npm:^2.0.0": + version: 2.0.1 + resolution: "path-scurry@npm:2.0.1" + dependencies: + lru-cache: "npm:^11.0.0" + minipass: "npm:^7.1.2" + checksum: 10c0/2a16ed0e81fbc43513e245aa5763354e25e787dab0d539581a6c3f0f967461a159ed6236b2559de23aa5b88e7dc32b469b6c47568833dd142a4b24b4f5cd2620 + languageName: node + linkType: hard + +"picocolors@npm:^1.0.0, picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 + languageName: node + linkType: hard + +"picomatch@npm:^4.0.2, picomatch@npm:^4.0.3": + version: 4.0.3 + resolution: "picomatch@npm:4.0.3" + checksum: 10c0/9582c951e95eebee5434f59e426cddd228a7b97a0161a375aed4be244bd3fe8e3a31b846808ea14ef2c8a2527a6eeab7b3946a67d5979e81694654f939473ae2 + languageName: node + linkType: hard + +"postcss@npm:^8.5.3": + version: 8.5.6 + resolution: "postcss@npm:8.5.6" + dependencies: + nanoid: "npm:^3.3.11" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/5127cc7c91ed7a133a1b7318012d8bfa112da9ef092dddf369ae699a1f10ebbd89b1b9f25f3228795b84585c72aabd5ced5fc11f2ba467eedf7b081a66fad024 + languageName: node + linkType: hard + +"proc-log@npm:^6.0.0": + version: 6.1.0 + resolution: "proc-log@npm:6.1.0" + checksum: 10c0/4f178d4062733ead9d71a9b1ab24ebcecdfe2250916a5b1555f04fe2eda972a0ec76fbaa8df1ad9c02707add6749219d118a4fc46dc56bdfe4dde4b47d80bb82 + languageName: node + linkType: hard + +"promise-retry@npm:^2.0.1": + version: 2.0.1 + resolution: "promise-retry@npm:2.0.1" + dependencies: + err-code: "npm:^2.0.2" + retry: "npm:^0.12.0" + checksum: 10c0/9c7045a1a2928094b5b9b15336dcd2a7b1c052f674550df63cc3f36cd44028e5080448175b6f6ca32b642de81150f5e7b1a98b728f15cb069f2dd60ac2616b96 + languageName: node + linkType: hard + +"readdirp@npm:^4.0.1": + version: 4.1.2 + resolution: "readdirp@npm:4.1.2" + checksum: 10c0/60a14f7619dec48c9c850255cd523e2717001b0e179dc7037cfa0895da7b9e9ab07532d324bfb118d73a710887d1e35f79c495fa91582784493e085d18c72c62 + languageName: node + linkType: hard + +"requires-port@npm:^1.0.0": + version: 1.0.0 + resolution: "requires-port@npm:1.0.0" + checksum: 10c0/b2bfdd09db16c082c4326e573a82c0771daaf7b53b9ce8ad60ea46aa6e30aaf475fe9b164800b89f93b748d2c234d8abff945d2551ba47bf5698e04cd7713267 + languageName: node + linkType: hard + +"resolve-pkg-maps@npm:^1.0.0": + version: 1.0.0 + resolution: "resolve-pkg-maps@npm:1.0.0" + checksum: 10c0/fb8f7bbe2ca281a73b7ef423a1cbc786fb244bd7a95cbe5c3fba25b27d327150beca8ba02f622baea65919a57e061eb5005204daa5f93ed590d9b77463a567ab + languageName: node + linkType: hard + +"retry@npm:^0.12.0": + version: 0.12.0 + resolution: "retry@npm:0.12.0" + checksum: 10c0/59933e8501727ba13ad73ef4a04d5280b3717fd650408460c987392efe9d7be2040778ed8ebe933c5cbd63da3dcc37919c141ef8af0a54a6e4fca5a2af177bfe + languageName: node + linkType: hard + +"rollup@npm:^4.34.9": + version: 4.57.1 + resolution: "rollup@npm:4.57.1" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.57.1" + "@rollup/rollup-android-arm64": "npm:4.57.1" + "@rollup/rollup-darwin-arm64": "npm:4.57.1" + "@rollup/rollup-darwin-x64": "npm:4.57.1" + "@rollup/rollup-freebsd-arm64": "npm:4.57.1" + "@rollup/rollup-freebsd-x64": "npm:4.57.1" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.57.1" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.57.1" + "@rollup/rollup-linux-arm64-gnu": "npm:4.57.1" + "@rollup/rollup-linux-arm64-musl": "npm:4.57.1" + "@rollup/rollup-linux-loong64-gnu": "npm:4.57.1" + "@rollup/rollup-linux-loong64-musl": "npm:4.57.1" + "@rollup/rollup-linux-ppc64-gnu": "npm:4.57.1" + "@rollup/rollup-linux-ppc64-musl": "npm:4.57.1" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.57.1" + "@rollup/rollup-linux-riscv64-musl": "npm:4.57.1" + "@rollup/rollup-linux-s390x-gnu": "npm:4.57.1" + "@rollup/rollup-linux-x64-gnu": "npm:4.57.1" + "@rollup/rollup-linux-x64-musl": "npm:4.57.1" + "@rollup/rollup-openbsd-x64": "npm:4.57.1" + "@rollup/rollup-openharmony-arm64": "npm:4.57.1" + "@rollup/rollup-win32-arm64-msvc": "npm:4.57.1" + "@rollup/rollup-win32-ia32-msvc": "npm:4.57.1" + "@rollup/rollup-win32-x64-gnu": "npm:4.57.1" + "@rollup/rollup-win32-x64-msvc": "npm:4.57.1" + "@types/estree": "npm:1.0.8" + fsevents: "npm:~2.3.2" + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-freebsd-arm64": + optional: true + "@rollup/rollup-freebsd-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-loong64-gnu": + optional: true + "@rollup/rollup-linux-loong64-musl": + optional: true + "@rollup/rollup-linux-ppc64-gnu": + optional: true + "@rollup/rollup-linux-ppc64-musl": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-riscv64-musl": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-openbsd-x64": + optional: true + "@rollup/rollup-openharmony-arm64": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-gnu": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 10c0/a90aaf1166fc495920e44e52dced0b12283aaceb0924abd6f863102128dd428bbcbf85970f792c06bc63d2a2168e7f073b73e05f6f8d76fdae17b7ac6cacba06 + languageName: node + linkType: hard + +"sade@npm:^1.7.4, sade@npm:^1.8.1": + version: 1.8.1 + resolution: "sade@npm:1.8.1" + dependencies: + mri: "npm:^1.1.0" + checksum: 10c0/da8a3a5d667ad5ce3bf6d4f054bbb9f711103e5df21003c5a5c1a8a77ce12b640ed4017dd423b13c2307ea7e645adee7c2ae3afe8051b9db16a6f6d3da3f90b1 + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 + languageName: node + linkType: hard + +"semver@npm:^7.3.5": + version: 7.7.4 + resolution: "semver@npm:7.7.4" + bin: + semver: bin/semver.js + checksum: 10c0/5215ad0234e2845d4ea5bb9d836d42b03499546ddafb12075566899fc617f68794bb6f146076b6881d755de17d6c6cc73372555879ec7dce2c2feee947866ad2 + languageName: node + linkType: hard + +"set-cookie-parser@npm:^3.0.0": + version: 3.0.1 + resolution: "set-cookie-parser@npm:3.0.1" + checksum: 10c0/29bd085ab3167fd0b3ee25b20575c1be845bf1c051b816c83ea99aada02c6a1bf16458e3c2825abf702bd09725bbb4868730737596b7a2d48f712551cf733d1d + languageName: node + linkType: hard + +"sirv@npm:^3.0.0": + version: 3.0.2 + resolution: "sirv@npm:3.0.2" + dependencies: + "@polka/url": "npm:^1.0.0-next.24" + mrmime: "npm:^2.0.0" + totalist: "npm:^3.0.0" + checksum: 10c0/5930e4397afdb14fbae13751c3be983af4bda5c9aadec832607dc2af15a7162f7d518c71b30e83ae3644b9a24cea041543cc969e5fe2b80af6ce8ea3174b2d04 + languageName: node + linkType: hard + +"sleepguard@workspace:.": + version: 0.0.0-use.local + resolution: "sleepguard@workspace:." + languageName: unknown + linkType: soft + +"smart-buffer@npm:^4.2.0": + version: 4.2.0 + resolution: "smart-buffer@npm:4.2.0" + checksum: 10c0/a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539 + languageName: node + linkType: hard + +"socks-proxy-agent@npm:^8.0.3": + version: 8.0.5 + resolution: "socks-proxy-agent@npm:8.0.5" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:^4.3.4" + socks: "npm:^2.8.3" + checksum: 10c0/5d2c6cecba6821389aabf18728325730504bf9bb1d9e342e7987a5d13badd7a98838cc9a55b8ed3cb866ad37cc23e1086f09c4d72d93105ce9dfe76330e9d2a6 + languageName: node + linkType: hard + +"socks@npm:^2.8.3": + version: 2.8.7 + resolution: "socks@npm:2.8.7" + dependencies: + ip-address: "npm:^10.0.1" + smart-buffer: "npm:^4.2.0" + checksum: 10c0/2805a43a1c4bcf9ebf6e018268d87b32b32b06fbbc1f9282573583acc155860dc361500f89c73bfbb157caa1b4ac78059eac0ef15d1811eb0ca75e0bdadbc9d2 + languageName: node + linkType: hard + +"source-map-js@npm:^1.2.1": + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf + languageName: node + linkType: hard + +"ssri@npm:^13.0.0": + version: 13.0.1 + resolution: "ssri@npm:13.0.1" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/cf6408a18676c57ff2ed06b8a20dc64bb3e748e5c7e095332e6aecaa2b8422b1e94a739a8453bf65156a8a47afe23757ba4ab52d3ea3b62322dc40875763e17a + languageName: node + linkType: hard + +"svelte-check@npm:^4.0.0": + version: 4.3.6 + resolution: "svelte-check@npm:4.3.6" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.25" + chokidar: "npm:^4.0.1" + fdir: "npm:^6.2.0" + picocolors: "npm:^1.0.0" + sade: "npm:^1.7.4" + peerDependencies: + svelte: ^4.0.0 || ^5.0.0-next.0 + typescript: ">=5.0.0" + bin: + svelte-check: bin/svelte-check + checksum: 10c0/0b2a70efaedb9bae01ab9a4b6086a1445f24aec3132002bcd69b43e40a8baf259c8f3eac8b83ac681d7cfbf13804351f860959026335ad59a02ae4455cf599ce + languageName: node + linkType: hard + +"svelte@npm:^5.0.0": + version: 5.50.1 + resolution: "svelte@npm:5.50.1" + dependencies: + "@jridgewell/remapping": "npm:^2.3.4" + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + "@sveltejs/acorn-typescript": "npm:^1.0.5" + "@types/estree": "npm:^1.0.5" + acorn: "npm:^8.12.1" + aria-query: "npm:^5.3.1" + axobject-query: "npm:^4.1.0" + clsx: "npm:^2.1.1" + devalue: "npm:^5.6.2" + esm-env: "npm:^1.2.1" + esrap: "npm:^2.2.2" + is-reference: "npm:^3.0.3" + locate-character: "npm:^3.0.0" + magic-string: "npm:^0.30.11" + zimmerframe: "npm:^1.1.2" + checksum: 10c0/9eee4b3ef3ba921090c4f0ab9eb66d9a85e58458875f342403c3ef516085e0fc02daf763295fb6133898832cd106fc2e7bd74ecb8a33ac399a5a568a77854244 + languageName: node + linkType: hard + +"tar@npm:^7.5.4": + version: 7.5.7 + resolution: "tar@npm:7.5.7" + dependencies: + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.1.0" + yallist: "npm:^5.0.0" + checksum: 10c0/51f261afc437e1112c3e7919478d6176ea83f7f7727864d8c2cce10f0b03a631d1911644a567348c3063c45abdae39718ba97abb073d22aa3538b9a53ae1e31c + languageName: node + linkType: hard + +"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.13": + version: 0.2.15 + resolution: "tinyglobby@npm:0.2.15" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.3" + checksum: 10c0/869c31490d0d88eedb8305d178d4c75e7463e820df5a9b9d388291daf93e8b1eb5de1dad1c1e139767e4269fe75f3b10d5009b2cc14db96ff98986920a186844 + languageName: node + linkType: hard + +"totalist@npm:^3.0.0": + version: 3.0.1 + resolution: "totalist@npm:3.0.1" + checksum: 10c0/4bb1fadb69c3edbef91c73ebef9d25b33bbf69afe1e37ce544d5f7d13854cda15e47132f3e0dc4cafe300ddb8578c77c50a65004d8b6e97e77934a69aa924863 + languageName: node + linkType: hard + +"tsx@npm:^4.19.0": + version: 4.21.0 + resolution: "tsx@npm:4.21.0" + dependencies: + esbuild: "npm:~0.27.0" + fsevents: "npm:~2.3.3" + get-tsconfig: "npm:^4.7.5" + dependenciesMeta: + fsevents: + optional: true + bin: + tsx: dist/cli.mjs + checksum: 10c0/f5072923cd8459a1f9a26df87823a2ab5754641739d69df2a20b415f61814322b751fa6be85db7c6ec73cf68ba8fac2fd1cfc76bdb0aa86ded984d84d5d2126b + languageName: node + linkType: hard + +"typescript@npm:^5.7.0": + version: 5.9.3 + resolution: "typescript@npm:5.9.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/6bd7552ce39f97e711db5aa048f6f9995b53f1c52f7d8667c1abdc1700c68a76a308f579cd309ce6b53646deb4e9a1be7c813a93baaf0a28ccd536a30270e1c5 + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^5.7.0#optional!builtin": + version: 5.9.3 + resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/ad09fdf7a756814dce65bc60c1657b40d44451346858eea230e10f2e95a289d9183b6e32e5c11e95acc0ccc214b4f36289dcad4bf1886b0adb84d711d336a430 + languageName: node + linkType: hard + +"undici-types@npm:~6.21.0": + version: 6.21.0 + resolution: "undici-types@npm:6.21.0" + checksum: 10c0/c01ed51829b10aa72fc3ce64b747f8e74ae9b60eafa19a7b46ef624403508a54c526ffab06a14a26b3120d055e1104d7abe7c9017e83ced038ea5cf52f8d5e04 + languageName: node + linkType: hard + +"undici-types@npm:~7.16.0": + version: 7.16.0 + resolution: "undici-types@npm:7.16.0" + checksum: 10c0/3033e2f2b5c9f1504bdc5934646cb54e37ecaca0f9249c983f7b1fc2e87c6d18399ebb05dc7fd5419e02b2e915f734d872a65da2e3eeed1813951c427d33cc9a + languageName: node + linkType: hard + +"unique-filename@npm:^5.0.0": + version: 5.0.0 + resolution: "unique-filename@npm:5.0.0" + dependencies: + unique-slug: "npm:^6.0.0" + checksum: 10c0/afb897e9cf4c2fb622ea716f7c2bb462001928fc5f437972213afdf1cc32101a230c0f1e9d96fc91ee5185eca0f2feb34127145874975f347be52eb91d6ccc2c + languageName: node + linkType: hard + +"unique-slug@npm:^6.0.0": + version: 6.0.0 + resolution: "unique-slug@npm:6.0.0" + dependencies: + imurmurhash: "npm:^0.1.4" + checksum: 10c0/da7ade4cb04eb33ad0499861f82fe95ce9c7c878b7139dc54d140ecfb6a6541c18a5c8dac16188b8b379fe62c0c1f1b710814baac910cde5f4fec06212126c6a + languageName: node + linkType: hard + +"vite@npm:^6.0.0": + version: 6.4.1 + resolution: "vite@npm:6.4.1" + dependencies: + esbuild: "npm:^0.25.0" + fdir: "npm:^6.4.4" + fsevents: "npm:~2.3.3" + picomatch: "npm:^4.0.2" + postcss: "npm:^8.5.3" + rollup: "npm:^4.34.9" + tinyglobby: "npm:^0.2.13" + peerDependencies: + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: ">=1.21.0" + less: "*" + lightningcss: ^1.21.0 + sass: "*" + sass-embedded: "*" + stylus: "*" + sugarss: "*" + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/77bb4c5b10f2a185e7859cc9a81c789021bc18009b02900347d1583b453b58e4b19ff07a5e5a5b522b68fc88728460bb45a63b104d969e8c6a6152aea3b849f7 + languageName: node + linkType: hard + +"vitefu@npm:^1.0.6": + version: 1.1.1 + resolution: "vitefu@npm:1.1.1" + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 + peerDependenciesMeta: + vite: + optional: true + checksum: 10c0/7e0d0dd6fb73bd80cb56a3f47ccc44159e0330c3e94b2951648079b35711226f9088dbe616d910b931740b92259230b874fbe351108b49f5c11b629b641292a5 + languageName: node + linkType: hard + +"which@npm:^6.0.0": + version: 6.0.0 + resolution: "which@npm:6.0.0" + dependencies: + isexe: "npm:^3.1.1" + bin: + node-which: bin/which.js + checksum: 10c0/fe9d6463fe44a76232bb6e3b3181922c87510a5b250a98f1e43a69c99c079b3f42ddeca7e03d3e5f2241bf2d334f5a7657cfa868b97c109f3870625842f4cc15 + languageName: node + linkType: hard + +"ws@npm:^8.18.0": + version: 8.19.0 + resolution: "ws@npm:8.19.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/4741d9b9bc3f9c791880882414f96e36b8b254e34d4b503279d6400d9a4b87a033834856dbdd94ee4b637944df17ea8afc4bce0ff4a1560d2166be8855da5b04 + languageName: node + linkType: hard + +"yallist@npm:^4.0.0": + version: 4.0.0 + resolution: "yallist@npm:4.0.0" + checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a + languageName: node + linkType: hard + +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 + languageName: node + linkType: hard + +"zimmerframe@npm:^1.1.2": + version: 1.1.4 + resolution: "zimmerframe@npm:1.1.4" + checksum: 10c0/9470cbf22cefae975ab413c7158a119d082b354ddcf0da48a842f2f42246fa15943cd9b92c047de39db38015e3b866e32f383bc217e8e4f4192945c7d425536b + languageName: node + linkType: hard