From 276c7b9a7be07efb965575d7403ecddac59bb53e Mon Sep 17 00:00:00 2001 From: Ross Douglas Date: Fri, 27 Feb 2026 13:16:59 +0200 Subject: [PATCH 1/3] feat: Janee config CRUD from dashboard - Add @true-and-useful/janee as local dependency for direct config access - Rewrite janee-config.ts to use Janee library (loadYAMLConfig/saveYAMLConfig) with proper secret masking, replacing manual YAML parsing - Add CRUD API routes: POST/PUT/DELETE for services, capabilities, and capability agent access, each followed by SIGHUP to reload Janee - Add dashboard API mutation functions (addJaneeService, deleteJaneeService, etc.) - Make JaneeSection fully interactive: inline forms for adding services and capabilities, hover delete buttons with confirmation, editable agent access chips with add/remove - Fix duplicate "restart" key in package.json Made-with: Cursor --- dashboard/src/api.ts | 43 + dashboard/src/components/JaneeSection.tsx | 320 ++++- package.json | 2 +- pnpm-lock.yaml | 1430 ++++++++++++++++++++- src/host/index.ts | 121 +- src/host/janee-config.ts | 178 ++- src/host/janee.ts | 7 + 7 files changed, 1994 insertions(+), 107 deletions(-) diff --git a/dashboard/src/api.ts b/dashboard/src/api.ts index 392c546..11761b3 100644 --- a/dashboard/src/api.ts +++ b/dashboard/src/api.ts @@ -105,3 +105,46 @@ export async function fetchJaneeConfig(): Promise { + const res = await fetch(url, { + method, + headers: { 'Content-Type': 'application/json' }, + body: body ? JSON.stringify(body) : undefined, + }); + if (!res.ok) { + const err = await res.json().catch(() => ({ error: 'Unknown error' })); + throw new Error(err.error || 'Request failed'); + } + return res.json(); +} + +export function addJaneeService(name: string, baseUrl: string, authType: string) { + return janeeMutate('/api/janee/services', 'POST', { name, baseUrl, authType }); +} + +export function updateJaneeService(name: string, patch: { baseUrl?: string; authType?: string }) { + return janeeMutate(`/api/janee/services/${encodeURIComponent(name)}`, 'PUT', patch); +} + +export function deleteJaneeService(name: string) { + return janeeMutate(`/api/janee/services/${encodeURIComponent(name)}`, 'DELETE'); +} + +export function addJaneeCapability(name: string, config: { service: string; ttl?: string; mode?: string; allowedAgents?: string[] }) { + return janeeMutate('/api/janee/capabilities', 'POST', { name, ...config }); +} + +export function updateJaneeCapability(name: string, patch: Record) { + return janeeMutate(`/api/janee/capabilities/${encodeURIComponent(name)}`, 'PUT', patch); +} + +export function deleteJaneeCapability(name: string) { + return janeeMutate(`/api/janee/capabilities/${encodeURIComponent(name)}`, 'DELETE'); +} + +export function updateCapabilityAgents(capName: string, agents: string[]) { + return janeeMutate(`/api/janee/capabilities/${encodeURIComponent(capName)}/agents`, 'PUT', { agents }); +} diff --git a/dashboard/src/components/JaneeSection.tsx b/dashboard/src/components/JaneeSection.tsx index 3fbac44..1d55f5a 100644 --- a/dashboard/src/components/JaneeSection.tsx +++ b/dashboard/src/components/JaneeSection.tsx @@ -1,46 +1,235 @@ -import { useState, useEffect } from 'react'; +import { + useCallback, + useEffect, + useState, +} from 'react'; + import * as api from '@/api'; -import type { JaneeConfigView, MaskedService, MaskedCapability, AgentAccess } from '@/types'; +import type { + AgentAccess, + JaneeConfigView, + MaskedCapability, + MaskedService, +} from '@/types'; const tabs = ['Services', 'Agents'] as const; type Tab = typeof tabs[number]; -function CapRow({ cap, agents }: { cap: MaskedCapability; agents: string[] }) { +const AUTH_TYPES = ['bearer', 'headers', 'hmac-mexc', 'hmac-bybit', 'hmac-okx', 'service-account', 'github-app'] as const; + +// ── Inline forms ── + +function AddServiceForm({ onSubmit, onCancel }: { + onSubmit: (name: string, baseUrl: string, authType: string) => void; + onCancel: () => void; +}) { + const [name, setName] = useState(''); + const [baseUrl, setBaseUrl] = useState(''); + const [authType, setAuthType] = useState('bearer'); + const [busy, setBusy] = useState(false); + + const submit = () => { + if (!name.trim() || !baseUrl.trim()) return; + setBusy(true); + onSubmit(name.trim(), baseUrl.trim(), authType); + }; + + return ( +
+
New service
+
+ setName(e.target.value)} + className="flex-1 text-[12.5px] px-2.5 py-1.5 rounded border border-border-light bg-white focus:outline-none focus:border-text-primary/40 font-mono" + onKeyDown={e => e.key === 'Enter' && submit()} + /> + setBaseUrl(e.target.value)} + className="flex-2 text-[12.5px] px-2.5 py-1.5 rounded border border-border-light bg-white focus:outline-none focus:border-text-primary/40 font-mono" + onKeyDown={e => e.key === 'Enter' && submit()} + /> + +
+
+ + +
+
+ ); +} + +function AddCapabilityForm({ serviceName, onSubmit, onCancel }: { + serviceName: string; + onSubmit: (name: string, config: { service: string; ttl?: string; mode?: string }) => void; + onCancel: () => void; +}) { + const [name, setName] = useState(''); + const [ttl, setTtl] = useState('1h'); + const [mode, setMode] = useState('proxy'); + const [busy, setBusy] = useState(false); + + const submit = () => { + if (!name.trim()) return; + setBusy(true); + onSubmit(name.trim(), { service: serviceName, ttl, mode }); + }; + + return ( +
+
+ setName(e.target.value)} + className="flex-1 text-[12px] px-2 py-1 rounded border border-border-light bg-white focus:outline-none focus:border-text-primary/40 font-mono" + onKeyDown={e => e.key === 'Enter' && submit()} + /> + setTtl(e.target.value)} + className="w-16 text-[12px] px-2 py-1 rounded border border-border-light bg-white focus:outline-none font-mono" + /> + + + +
+
+ ); +} + +function AgentChips({ agents, capName, onUpdate }: { + agents: string[]; + capName: string; + onUpdate: (capName: string, agents: string[]) => void; +}) { + const [adding, setAdding] = useState(false); + const [value, setValue] = useState(''); + + const remove = (agent: string) => { + onUpdate(capName, agents.filter(a => a !== agent)); + }; + + const add = () => { + const v = value.trim(); + if (!v || agents.includes(v)) return; + onUpdate(capName, [...agents, v]); + setValue(''); + setAdding(false); + }; + + return ( +
+ access + {agents.map(a => ( + + {a} + + + ))} + {adding ? ( + setValue(e.target.value)} + onKeyDown={e => { if (e.key === 'Enter') add(); if (e.key === 'Escape') setAdding(false); }} + onBlur={() => { if (!value.trim()) setAdding(false); }} + placeholder="agent-id" + className="text-[10px] font-mono w-24 px-1.5 py-0.5 rounded border border-border-light bg-white focus:outline-none focus:border-text-primary/40" + /> + ) : ( + + )} +
+ ); +} + +// ── Display components ── + +function CapRow({ cap, agents, onDelete, onUpdateAgents }: { + cap: MaskedCapability; + agents: string[]; + onDelete: (name: string) => void; + onUpdateAgents: (capName: string, agents: string[]) => void; +}) { return (
{cap.name} {cap.mode} + {cap.ttl && {cap.ttl}}
+
- {agents.length > 0 && ( -
- access - {agents.map(a => ( - {a} - ))} -
- )} +
); } -function ServiceCard({ svc, caps, agentsByCap }: { +function ServiceCard({ svc, caps, agentsByCap, onDeleteService, onDeleteCap, onAddCap, onUpdateAgents }: { svc: MaskedService; caps: MaskedCapability[]; agentsByCap: Map; + onDeleteService: (name: string) => void; + onDeleteCap: (name: string) => void; + onAddCap: (name: string, config: { service: string; ttl?: string; mode?: string }) => void; + onUpdateAgents: (capName: string, agents: string[]) => void; }) { + const [addingCap, setAddingCap] = useState(false); + return (
-
+
{svc.name} {svc.baseUrl}
- - {svc.authType} - +
+ + {svc.authType} + + +
{caps.length > 0 ? ( caps.map(cap => ( @@ -48,30 +237,38 @@ function ServiceCard({ svc, caps, agentsByCap }: { key={cap.name} cap={cap} agents={agentsByCap.get(cap.name) || cap.allowedAgents || []} + onDelete={onDeleteCap} + onUpdateAgents={onUpdateAgents} /> )) ) : (
No capabilities
)} + {addingCap ? ( + { onAddCap(name, config); setAddingCap(false); }} + onCancel={() => setAddingCap(false)} + /> + ) : ( + + )}
); } -function ServicesTab({ services, capabilities, agents }: { +function ServicesTab({ services, capabilities, agents, onMutate }: { services: MaskedService[]; capabilities: MaskedCapability[]; agents: AgentAccess[]; + onMutate: (fn: () => Promise) => void; }) { - if (!services.length && !capabilities.length) { - return ( -
-

No services configured.

-

- Run janee add to get started. -

-
- ); - } + const [addingService, setAddingService] = useState(false); const capsByService = new Map(); for (const cap of capabilities) { @@ -90,14 +287,52 @@ function ServicesTab({ services, capabilities, agents }: { const unbound = capsByService.get('') || []; + const handleAddService = (name: string, baseUrl: string, authType: string) => { + onMutate(() => api.addJaneeService(name, baseUrl, authType)); + setAddingService(false); + }; + + const handleDeleteService = (name: string) => { + if (!confirm(`Delete service "${name}" and all its capabilities?`)) return; + onMutate(() => api.deleteJaneeService(name)); + }; + + const handleDeleteCap = (name: string) => { + if (!confirm(`Delete capability "${name}"?`)) return; + onMutate(() => api.deleteJaneeCapability(name)); + }; + + const handleAddCap = (name: string, config: { service: string; ttl?: string; mode?: string }) => { + onMutate(() => api.addJaneeCapability(name, config)); + }; + + const handleUpdateAgents = (capName: string, newAgents: string[]) => { + onMutate(() => api.updateCapabilityAgents(capName, newAgents)); + }; + return (
+ {addingService ? ( + setAddingService(false)} /> + ) : ( + + )} + {services.map(svc => ( ))} {unbound.length > 0 && ( @@ -110,10 +345,18 @@ function ServicesTab({ services, capabilities, agents }: { key={cap.name} cap={cap} agents={agentsByCap.get(cap.name) || cap.allowedAgents || []} + onDelete={handleDeleteCap} + onUpdateAgents={handleUpdateAgents} /> ))}
)} + + {!services.length && !capabilities.length && !addingService && ( +
+

No services configured.

+
+ )}
); } @@ -123,6 +366,7 @@ function AgentsTab({ agents }: { agents: AgentAccess[] }) { return (

No agent access rules defined.

+

Grant access via capability settings in the Services tab.

); } @@ -146,6 +390,7 @@ export function JaneeSection() { const [tab, setTab] = useState('Services'); const [config, setConfig] = useState(null); const [error, setError] = useState(null); + const [mutating, setMutating] = useState(false); useEffect(() => { api.fetchJaneeConfig() @@ -153,7 +398,16 @@ export function JaneeSection() { .catch(() => setError('Could not load Janee config. Is Janee running?')); }, []); - if (error) { + const onMutate = useCallback((fn: () => Promise) => { + setMutating(true); + setError(null); + fn() + .then(setConfig) + .catch(e => setError(e.message)) + .finally(() => setMutating(false)); + }, []); + + if (error && !config) { return (

Janee

@@ -172,12 +426,16 @@ export function JaneeSection() { } return ( -
+

Janee

- Read-only view of ~/.janee/config.yaml + Services, capabilities & agent access for ~/.janee/config.yaml

+ {error && ( +
{error}
+ )} +
{tabs.map(t => (
); diff --git a/package.json b/package.json index f00541d..792d9d3 100644 --- a/package.json +++ b/package.json @@ -25,12 +25,12 @@ "destroy": "tsx src/cli/index.ts destroy", "fork": "tsx src/cli/index.ts fork", "build:dashboard": "cd dashboard && pnpm build", - "restart": "./scripts/restart.sh", "restart": "./scripts/restart.sh" }, "dependencies": { "@ai-sdk/anthropic": "^3.0.44", "@anthropic-ai/sdk": "^0.74.0", + "@true-and-useful/janee": "file:../../janee", "ai": "^6.0.86", "tsx": "^4.21.0", "zod": "^4.3.6" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a4f11bc..2de2ef1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,13 +9,16 @@ importers: .: dependencies: '@ai-sdk/anthropic': - specifier: ^3.0.46 + specifier: ^3.0.44 version: 3.0.46(zod@4.3.6) '@anthropic-ai/sdk': specifier: ^0.74.0 version: 0.74.0(zod@4.3.6) + '@true-and-useful/janee': + specifier: file:../../janee + version: file:../../janee(@types/node@22.19.11)(zod@4.3.6) ai: - specifier: ^6.0.97 + specifier: ^6.0.86 version: 6.0.97(zod@4.3.6) tsx: specifier: ^4.21.0 @@ -39,6 +42,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + dompurify: + specifier: ^3.3.1 + version: 3.3.1 html2canvas: specifier: ^1.4.1 version: 1.4.1 @@ -833,6 +839,12 @@ packages: '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + '@hono/node-server@1.19.9': + resolution: {integrity: sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + '@img/colour@1.0.0': resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} engines: {node: '>=18'} @@ -1075,6 +1087,140 @@ packages: cpu: [x64] os: [win32] + '@inquirer/ansi@2.0.3': + resolution: {integrity: sha512-g44zhR3NIKVs0zUesa4iMzExmZpLUdTLRMCStqX3GE5NT6VkPcxQGJ+uC8tDgBUC/vB1rUhUd55cOf++4NZcmw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + + '@inquirer/checkbox@5.1.0': + resolution: {integrity: sha512-/HjF1LN0a1h4/OFsbGKHNDtWICFU/dqXCdym719HFTyJo9IG7Otr+ziGWc9S0iQuohRZllh+WprSgd5UW5Fw0g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/confirm@6.0.8': + resolution: {integrity: sha512-Di6dgmiZ9xCSUxWUReWTqDtbhXCuG2MQm2xmgSAIruzQzBqNf49b8E07/vbCYY506kDe8BiwJbegXweG8M1klw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@11.1.5': + resolution: {integrity: sha512-QQPAX+lka8GyLcZ7u7Nb1h6q72iZ/oy0blilC3IB2nSt1Qqxp7akt94Jqhi/DzARuN3Eo9QwJRvtl4tmVe4T5A==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/editor@5.0.8': + resolution: {integrity: sha512-sLcpbb9B3XqUEGrj1N66KwhDhEckzZ4nI/W6SvLXyBX8Wic3LDLENlWRvkOGpCPoserabe+MxQkpiMoI8irvyA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/expand@5.0.8': + resolution: {integrity: sha512-QieW3F1prNw3j+hxO7/NKkG1pk3oz7pOB6+5Upwu3OIwADfPX0oZVppsqlL+Vl/uBHHDSOBY0BirLctLnXwGGg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/external-editor@2.0.3': + resolution: {integrity: sha512-LgyI7Agbda74/cL5MvA88iDpvdXI2KuMBCGRkbCl2Dg1vzHeOgs+s0SDcXV7b+WZJrv2+ERpWSM65Fpi9VfY3w==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@2.0.3': + resolution: {integrity: sha512-y09iGt3JKoOCBQ3w4YrSJdokcD8ciSlMIWsD+auPu+OZpfxLuyz+gICAQ6GCBOmJJt4KEQGHuZSVff2jiNOy7g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + + '@inquirer/input@5.0.8': + resolution: {integrity: sha512-p0IJslw0AmedLEkOU+yrEX3Aj2RTpQq7ZOf8nc1DIhjzaxRWrrgeuE5Kyh39fVRgtcACaMXx/9WNo8+GjgBOfw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/number@4.0.8': + resolution: {integrity: sha512-uGLiQah9A0F9UIvJBX52m0CnqtLaym0WpT9V4YZrjZ+YRDKZdwwoEPz06N6w8ChE2lrnsdyhY9sL+Y690Kh9gQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/password@5.0.8': + resolution: {integrity: sha512-zt1sF4lYLdvPqvmvHdmjOzuUUjuCQ897pdUCO8RbXMUDKXJTTyOQgtn23le+jwcb+MpHl3VAFvzIdxRAf6aPlA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/prompts@8.3.0': + resolution: {integrity: sha512-JAj66kjdH/F1+B7LCigjARbwstt3SNUOSzMdjpsvwJmzunK88gJeXmcm95L9nw1KynvFVuY4SzXh/3Y0lvtgSg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/rawlist@5.2.4': + resolution: {integrity: sha512-fTuJ5Cq9W286isLxwj6GGyfTjx1Zdk4qppVEPexFuA6yioCCXS4V1zfKroQqw7QdbDPN73xs2DiIAlo55+kBqg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/search@4.1.4': + resolution: {integrity: sha512-9yPTxq7LPmYjrGn3DRuaPuPbmC6u3fiWcsE9ggfLcdgO/ICHYgxq7mEy1yJ39brVvgXhtOtvDVjDh9slJxE4LQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/select@5.1.0': + resolution: {integrity: sha512-OyYbKnchS1u+zRe14LpYrN8S0wH1vD0p2yKISvSsJdH2TpI87fh4eZdWnpdbrGauCRWDph3NwxRmM4Pcm/hx1Q==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@4.0.3': + resolution: {integrity: sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -1094,6 +1240,16 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@modelcontextprotocol/sdk@1.27.1': + resolution: {integrity: sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + '@opentelemetry/api@1.9.0': resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} @@ -2063,6 +2219,11 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 || ^7 + '@true-and-useful/janee@file:../../janee': + resolution: {directory: ../../janee, type: directory} + engines: {node: '>=18.0.0'} + hasBin: true + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -2084,6 +2245,9 @@ packages: '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/jsonwebtoken@9.0.10': + resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} + '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} @@ -2104,6 +2268,9 @@ packages: '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} @@ -2120,6 +2287,14 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + acorn-walk@8.3.2: resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} engines: {node: '>=0.4.0'} @@ -2140,6 +2315,17 @@ packages: peerDependencies: zod: ^3.25.76 || ^4.1.8 + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} @@ -2170,6 +2356,9 @@ packages: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} + array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + array-iterate@2.0.1: resolution: {integrity: sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==} @@ -2200,6 +2389,14 @@ packages: blake3-wasm@2.1.5: resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} + body-parser@1.20.4: + resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -2212,6 +2409,21 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + camelcase@8.0.0: resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} engines: {node: '>=16'} @@ -2235,6 +2447,9 @@ packages: character-entities@2.0.2: resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + chardet@2.1.1: + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + chokidar@5.0.0: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} @@ -2250,6 +2465,10 @@ packages: resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} engines: {node: '>=10'} + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -2278,16 +2497,47 @@ packages: common-ancestor-path@1.0.1: resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-disposition@1.0.1: + resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} cookie-es@1.2.2: resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} + cookie-signature@1.0.7: + resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + cookie@1.1.1: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + crossws@0.3.5: resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} @@ -2321,6 +2571,14 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -2336,6 +2594,10 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -2343,6 +2605,10 @@ packages: destr@2.0.5: resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -2377,6 +2643,9 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} + dompurify@3.3.1: + resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==} + domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} @@ -2384,6 +2653,16 @@ packages: resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==} engines: {node: '>=4'} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + electron-to-chromium@1.5.302: resolution: {integrity: sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==} @@ -2393,6 +2672,10 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + enhanced-resolve@5.19.0: resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==} engines: {node: '>=10.13.0'} @@ -2408,9 +2691,21 @@ packages: error-stack-parser-es@1.0.5: resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + esbuild@0.25.12: resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} @@ -2430,6 +2725,9 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@5.0.0: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} @@ -2440,6 +2738,10 @@ packages: estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + eventemitter3@5.0.4: resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} @@ -2447,13 +2749,46 @@ packages: resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} engines: {node: '>=18.0.0'} + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + exit-hook@2.2.1: resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} engines: {node: '>=6'} + express-rate-limit@8.2.1: + resolution: {integrity: sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@4.22.1: + resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} + engines: {node: '>= 0.10.0'} + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fast-wrap-ansi@0.2.0: + resolution: {integrity: sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -2463,6 +2798,14 @@ packages: picomatch: optional: true + finalhandler@1.3.2: + resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} + engines: {node: '>= 0.8'} + + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + flattie@1.1.1: resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==} engines: {node: '>=8'} @@ -2474,11 +2817,26 @@ packages: resolution: {integrity: sha512-piJxbLnkD9Xcyi7dWJRnqszEURixe7CrF/efBfbffe2DPyabmuIuqraruY8cXTs19QoM8VJzx47BDRVNXETM7Q==} engines: {node: '>=20'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -2487,10 +2845,18 @@ packages: resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} engines: {node: '>=18'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + get-nonce@1.0.1: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + get-tsconfig@4.13.6: resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} @@ -2500,12 +2866,24 @@ packages: glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} h3@1.15.5: resolution: {integrity: sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + hast-util-from-html@2.0.3: resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} @@ -2536,6 +2914,10 @@ packages: hastscript@9.0.1: resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + hono@4.12.3: + resolution: {integrity: sha512-SFsVSjp8sj5UumXOOFlkZOG6XS9SJDKw0TbwFeV+AJ8xlST8kxK5Z/5EYa111UY8732lK2S/xB653ceuaoGwpg==} + engines: {node: '>=16.9.0'} + html-escaper@3.0.3: resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} @@ -2549,9 +2931,32 @@ packages: http-cache-semantics@4.2.0: resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + import-meta-resolve@4.2.0: resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ip-address@10.0.1: + resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + iron-webcrypto@1.2.1: resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} @@ -2576,14 +2981,23 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-wsl@3.1.1: resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} engines: {node: '>=16'} + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + jiti@2.6.1: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + jose@6.1.3: + resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2600,6 +3014,12 @@ packages: resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==} engines: {node: '>=16'} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + json-schema@0.4.0: resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} @@ -2608,6 +3028,16 @@ packages: engines: {node: '>=6'} hasBin: true + jsonwebtoken@9.0.3: + resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} + engines: {node: '>=12', npm: '>=6'} + + jwa@2.0.1: + resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} + + jws@4.0.1: + resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} + kleur@3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} @@ -2686,6 +3116,27 @@ packages: resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} engines: {node: '>= 12.0.0'} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -2715,6 +3166,10 @@ packages: engines: {node: '>= 20'} hasBin: true + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + mdast-util-definitions@6.0.0: resolution: {integrity: sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==} @@ -2760,6 +3215,25 @@ packages: mdn-data@2.12.2: resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -2844,6 +3318,27 @@ packages: micromark@4.0.2: resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + mime@3.0.0: resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} engines: {node: '>=10.0.0'} @@ -2863,14 +3358,29 @@ packages: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + mute-stream@3.0.0: + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} + engines: {node: ^20.17.0 || >=22.9.0} + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + neotraverse@0.6.18: resolution: {integrity: sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==} engines: {node: '>= 10'} @@ -2894,12 +3404,27 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + ofetch@1.5.1: resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + oniguruma-parser@0.12.1: resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} @@ -2927,9 +3452,23 @@ packages: parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-to-regexp@0.1.12: + resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} + path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + path-to-regexp@8.3.0: + resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -2947,6 +3486,10 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + postcss-selector-parser@6.0.10: resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} engines: {node: '>=4'} @@ -2966,6 +3509,18 @@ packages: property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + qs@6.14.2: + resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==} + engines: {node: '>=0.6'} + + qs@6.15.0: + resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} + engines: {node: '>=0.6'} + radix-ui@1.4.3: resolution: {integrity: sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==} peerDependencies: @@ -2982,6 +3537,18 @@ packages: radix3@1.1.2: resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@2.5.3: + resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} + engines: {node: '>= 0.8'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + react-dom@19.2.4: resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} peerDependencies: @@ -3066,6 +3633,10 @@ packages: remark-stringify@11.0.0: resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -3086,6 +3657,16 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + sax@1.4.4: resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==} engines: {node: '>=11.0.0'} @@ -3102,6 +3683,25 @@ packages: engines: {node: '>=10'} hasBin: true + send@0.19.2: + resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} + engines: {node: '>= 0.8.0'} + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@1.16.3: + resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} + engines: {node: '>= 0.8.0'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + sharp@0.33.5: resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -3110,9 +3710,37 @@ packages: resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + shiki@3.22.0: resolution: {integrity: sha512-LBnhsoYEe0Eou4e1VgJACes+O6S6QC0w71fCSp5Oya79inkwkm15gQ1UF6VtQ8j/taMDh79hAB49WUk8ALQW3g==} + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + simple-swizzle@0.2.4: resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} @@ -3130,6 +3758,10 @@ packages: space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + stoppable@1.1.0: resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==} engines: {node: '>=4', npm: '>=6'} @@ -3186,6 +3818,10 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -3220,6 +3856,14 @@ packages: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -3281,6 +3925,10 @@ packages: unist-util-visit@5.1.0: resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + unstorage@1.17.4: resolution: {integrity: sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw==} peerDependencies: @@ -3377,9 +4025,17 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + utrie@1.0.2: resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==} + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + vfile-location@5.0.3: resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} @@ -3484,6 +4140,11 @@ packages: resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==} engines: {node: '>=4'} + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + widest-line@5.0.0: resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} engines: {node: '>=18'} @@ -3522,6 +4183,9 @@ packages: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} engines: {node: '>=18'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.18.0: resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} engines: {node: '>=10.0.0'} @@ -4132,6 +4796,10 @@ snapshots: '@floating-ui/utils@0.2.10': {} + '@hono/node-server@1.19.9(hono@4.12.3)': + dependencies: + hono: 4.12.3 + '@img/colour@1.0.0': {} '@img/sharp-darwin-arm64@0.33.5': @@ -4291,17 +4959,136 @@ snapshots: '@img/sharp-win32-arm64@0.34.5': optional: true - '@img/sharp-win32-ia32@0.33.5': - optional: true + '@img/sharp-win32-ia32@0.33.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.33.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@inquirer/ansi@2.0.3': {} + + '@inquirer/checkbox@5.1.0(@types/node@22.19.11)': + dependencies: + '@inquirer/ansi': 2.0.3 + '@inquirer/core': 11.1.5(@types/node@22.19.11) + '@inquirer/figures': 2.0.3 + '@inquirer/type': 4.0.3(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 + + '@inquirer/confirm@6.0.8(@types/node@22.19.11)': + dependencies: + '@inquirer/core': 11.1.5(@types/node@22.19.11) + '@inquirer/type': 4.0.3(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 + + '@inquirer/core@11.1.5(@types/node@22.19.11)': + dependencies: + '@inquirer/ansi': 2.0.3 + '@inquirer/figures': 2.0.3 + '@inquirer/type': 4.0.3(@types/node@22.19.11) + cli-width: 4.1.0 + fast-wrap-ansi: 0.2.0 + mute-stream: 3.0.0 + signal-exit: 4.1.0 + optionalDependencies: + '@types/node': 22.19.11 + + '@inquirer/editor@5.0.8(@types/node@22.19.11)': + dependencies: + '@inquirer/core': 11.1.5(@types/node@22.19.11) + '@inquirer/external-editor': 2.0.3(@types/node@22.19.11) + '@inquirer/type': 4.0.3(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 + + '@inquirer/expand@5.0.8(@types/node@22.19.11)': + dependencies: + '@inquirer/core': 11.1.5(@types/node@22.19.11) + '@inquirer/type': 4.0.3(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 + + '@inquirer/external-editor@2.0.3(@types/node@22.19.11)': + dependencies: + chardet: 2.1.1 + iconv-lite: 0.7.2 + optionalDependencies: + '@types/node': 22.19.11 + + '@inquirer/figures@2.0.3': {} + + '@inquirer/input@5.0.8(@types/node@22.19.11)': + dependencies: + '@inquirer/core': 11.1.5(@types/node@22.19.11) + '@inquirer/type': 4.0.3(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 + + '@inquirer/number@4.0.8(@types/node@22.19.11)': + dependencies: + '@inquirer/core': 11.1.5(@types/node@22.19.11) + '@inquirer/type': 4.0.3(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 + + '@inquirer/password@5.0.8(@types/node@22.19.11)': + dependencies: + '@inquirer/ansi': 2.0.3 + '@inquirer/core': 11.1.5(@types/node@22.19.11) + '@inquirer/type': 4.0.3(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 + + '@inquirer/prompts@8.3.0(@types/node@22.19.11)': + dependencies: + '@inquirer/checkbox': 5.1.0(@types/node@22.19.11) + '@inquirer/confirm': 6.0.8(@types/node@22.19.11) + '@inquirer/editor': 5.0.8(@types/node@22.19.11) + '@inquirer/expand': 5.0.8(@types/node@22.19.11) + '@inquirer/input': 5.0.8(@types/node@22.19.11) + '@inquirer/number': 4.0.8(@types/node@22.19.11) + '@inquirer/password': 5.0.8(@types/node@22.19.11) + '@inquirer/rawlist': 5.2.4(@types/node@22.19.11) + '@inquirer/search': 4.1.4(@types/node@22.19.11) + '@inquirer/select': 5.1.0(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 + + '@inquirer/rawlist@5.2.4(@types/node@22.19.11)': + dependencies: + '@inquirer/core': 11.1.5(@types/node@22.19.11) + '@inquirer/type': 4.0.3(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 - '@img/sharp-win32-ia32@0.34.5': - optional: true + '@inquirer/search@4.1.4(@types/node@22.19.11)': + dependencies: + '@inquirer/core': 11.1.5(@types/node@22.19.11) + '@inquirer/figures': 2.0.3 + '@inquirer/type': 4.0.3(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 - '@img/sharp-win32-x64@0.33.5': - optional: true + '@inquirer/select@5.1.0(@types/node@22.19.11)': + dependencies: + '@inquirer/ansi': 2.0.3 + '@inquirer/core': 11.1.5(@types/node@22.19.11) + '@inquirer/figures': 2.0.3 + '@inquirer/type': 4.0.3(@types/node@22.19.11) + optionalDependencies: + '@types/node': 22.19.11 - '@img/sharp-win32-x64@0.34.5': - optional: true + '@inquirer/type@4.0.3(@types/node@22.19.11)': + optionalDependencies: + '@types/node': 22.19.11 '@jridgewell/gen-mapping@0.3.13': dependencies: @@ -4327,6 +5114,28 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@modelcontextprotocol/sdk@1.27.1(zod@4.3.6)': + dependencies: + '@hono/node-server': 1.19.9(hono@4.12.3) + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.6 + express: 5.2.1 + express-rate-limit: 8.2.1(express@5.2.1) + hono: 4.12.3 + jose: 6.1.3 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.3.6 + zod-to-json-schema: 3.25.1(zod@4.3.6) + transitivePeerDependencies: + - supports-color + '@opentelemetry/api@1.9.0': {} '@oslojs/encoding@1.1.0': {} @@ -5294,6 +6103,21 @@ snapshots: tailwindcss: 4.1.18 vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0) + '@true-and-useful/janee@file:../../janee(@types/node@22.19.11)(zod@4.3.6)': + dependencies: + '@inquirer/prompts': 8.3.0(@types/node@22.19.11) + '@modelcontextprotocol/sdk': 1.27.1(zod@4.3.6) + '@types/jsonwebtoken': 9.0.10 + commander: 11.1.0 + express: 4.22.1 + js-yaml: 4.1.1 + jsonwebtoken: 9.0.3 + transitivePeerDependencies: + - '@cfworker/json-schema' + - '@types/node' + - supports-color + - zod + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.29.0 @@ -5325,6 +6149,11 @@ snapshots: dependencies: '@types/unist': 3.0.3 + '@types/jsonwebtoken@9.0.10': + dependencies: + '@types/ms': 2.1.0 + '@types/node': 22.19.11 + '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 @@ -5347,6 +6176,9 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/trusted-types@2.0.7': + optional: true + '@types/unist@3.0.3': {} '@ungap/structured-clone@1.3.0': {} @@ -5365,6 +6197,16 @@ snapshots: transitivePeerDependencies: - supports-color + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + acorn-walk@8.3.2: {} acorn@8.14.0: {} @@ -5379,6 +6221,17 @@ snapshots: '@opentelemetry/api': 1.9.0 zod: 4.3.6 + ajv-formats@3.0.1(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + + ajv@8.18.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + ansi-align@3.0.1: dependencies: string-width: 4.2.3 @@ -5402,6 +6255,8 @@ snapshots: aria-query@5.3.2: {} + array-flatten@1.1.1: {} + array-iterate@2.0.1: {} astro@5.17.2(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.57.1)(tsx@4.21.0)(typescript@5.9.3): @@ -5518,6 +6373,37 @@ snapshots: blake3-wasm@2.1.5: {} + body-parser@1.20.4: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.14.2 + raw-body: 2.5.3 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.15.0 + raw-body: 3.0.2 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + boolbase@1.0.0: {} boxen@8.0.1: @@ -5539,6 +6425,20 @@ snapshots: node-releases: 2.0.27 update-browserslist-db: 1.2.3(browserslist@4.28.1) + buffer-equal-constant-time@1.0.1: {} + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + camelcase@8.0.0: {} caniuse-lite@1.0.30001770: {} @@ -5553,6 +6453,8 @@ snapshots: character-entities@2.0.2: {} + chardet@2.1.1: {} + chokidar@5.0.0: dependencies: readdirp: 5.0.0 @@ -5565,6 +6467,8 @@ snapshots: cli-boxes@3.0.0: {} + cli-width@4.1.0: {} + clsx@2.1.1: {} color-convert@2.0.1: @@ -5589,12 +6493,37 @@ snapshots: common-ancestor-path@1.0.1: {} + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-disposition@1.0.1: {} + + content-type@1.0.5: {} + convert-source-map@2.0.0: {} cookie-es@1.2.2: {} + cookie-signature@1.0.7: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + cookie@1.1.1: {} + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + crossws@0.3.5: dependencies: uncrypto: 0.1.3 @@ -5631,6 +6560,10 @@ snapshots: csstype@3.2.3: {} + debug@2.6.9: + dependencies: + ms: 2.0.0 + debug@4.4.3: dependencies: ms: 2.1.3 @@ -5641,10 +6574,14 @@ snapshots: defu@6.1.4: {} + depd@2.0.0: {} + dequal@2.0.3: {} destr@2.0.5: {} + destroy@1.2.0: {} + detect-libc@2.1.2: {} detect-node-es@1.1.0: {} @@ -5675,6 +6612,10 @@ snapshots: dependencies: domelementtype: 2.3.0 + dompurify@3.3.1: + optionalDependencies: + '@types/trusted-types': 2.0.7 + domutils@3.2.2: dependencies: dom-serializer: 2.0.0 @@ -5683,12 +6624,26 @@ snapshots: dset@3.1.4: {} + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + + ee-first@1.1.1: {} + electron-to-chromium@1.5.302: {} emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} + encodeurl@2.0.0: {} + enhanced-resolve@5.19.0: dependencies: graceful-fs: 4.2.11 @@ -5700,8 +6655,16 @@ snapshots: error-stack-parser-es@1.0.5: {} + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + es-module-lexer@1.7.0: {} + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + esbuild@0.25.12: optionalDependencies: '@esbuild/aix-ppc64': 0.25.12 @@ -5790,6 +6753,8 @@ snapshots: escalade@3.2.0: {} + escape-html@1.0.3: {} + escape-string-regexp@5.0.0: {} estree-walker@2.0.2: {} @@ -5798,18 +6763,135 @@ snapshots: dependencies: '@types/estree': 1.0.8 + etag@1.8.1: {} + eventemitter3@5.0.4: {} eventsource-parser@3.0.6: {} + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.0.6 + exit-hook@2.2.1: {} + express-rate-limit@8.2.1(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.0.1 + + express@4.22.1: + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.4 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.0.7 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.3.2 + fresh: 0.5.2 + http-errors: 2.0.1 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.12 + proxy-addr: 2.0.7 + qs: 6.14.2 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.2 + serve-static: 1.16.3 + setprototypeof: 1.2.0 + statuses: 2.0.2 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.0.1 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.0 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + extend@3.0.2: {} + fast-deep-equal@3.1.3: {} + + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + + fast-uri@3.1.0: {} + + fast-wrap-ansi@0.2.0: + dependencies: + fast-string-width: 3.0.2 + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 + finalhandler@1.3.2: + dependencies: + debug: 2.6.9 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + flattie@1.1.1: {} fontace@0.4.1: @@ -5820,15 +6902,41 @@ snapshots: dependencies: tiny-inflate: 1.0.3 + forwarded@0.2.0: {} + + fresh@0.5.2: {} + + fresh@2.0.0: {} + fsevents@2.3.3: optional: true + function-bind@1.1.2: {} + gensync@1.0.0-beta.2: {} get-east-asian-width@1.4.0: {} + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + get-nonce@1.0.1: {} + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + get-tsconfig@4.13.6: dependencies: resolve-pkg-maps: 1.0.0 @@ -5837,6 +6945,8 @@ snapshots: glob-to-regexp@0.4.1: {} + gopd@1.2.0: {} + graceful-fs@4.2.11: {} h3@1.15.5: @@ -5851,6 +6961,12 @@ snapshots: ufo: 1.6.3 uncrypto: 0.1.3 + has-symbols@1.1.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + hast-util-from-html@2.0.3: dependencies: '@types/hast': 3.0.4 @@ -5938,6 +7054,8 @@ snapshots: property-information: 7.1.0 space-separated-tokens: 2.0.2 + hono@4.12.3: {} + html-escaper@3.0.3: {} html-void-elements@3.0.0: {} @@ -5949,8 +7067,30 @@ snapshots: http-cache-semantics@4.2.0: {} + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + import-meta-resolve@4.2.0: {} + inherits@2.0.4: {} + + ip-address@10.0.1: {} + + ipaddr.js@1.9.1: {} + iron-webcrypto@1.2.1: {} is-arrayish@0.3.4: {} @@ -5965,12 +7105,18 @@ snapshots: is-plain-obj@4.1.0: {} + is-promise@4.0.0: {} + is-wsl@3.1.1: dependencies: is-inside-container: 1.0.0 + isexe@2.0.0: {} + jiti@2.6.1: {} + jose@6.1.3: {} + js-tokens@4.0.0: {} js-yaml@4.1.1: @@ -5984,10 +7130,38 @@ snapshots: '@babel/runtime': 7.28.6 ts-algebra: 2.0.0 + json-schema-traverse@1.0.0: {} + + json-schema-typed@8.0.2: {} + json-schema@0.4.0: {} json5@2.2.3: {} + jsonwebtoken@9.0.3: + dependencies: + jws: 4.0.1 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.7.4 + + jwa@2.0.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@4.0.1: + dependencies: + jwa: 2.0.1 + safe-buffer: 5.2.1 + kleur@3.0.3: {} kleur@4.1.5: {} @@ -6041,6 +7215,20 @@ snapshots: lightningcss-win32-arm64-msvc: 1.30.2 lightningcss-win32-x64-msvc: 1.30.2 + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + + lodash.once@4.1.1: {} + longest-streak@3.1.0: {} lru-cache@11.2.6: {} @@ -6067,6 +7255,8 @@ snapshots: marked@17.0.3: {} + math-intrinsics@1.1.0: {} + mdast-util-definitions@6.0.0: dependencies: '@types/mdast': 4.0.4 @@ -6191,6 +7381,16 @@ snapshots: mdn-data@2.12.2: {} + media-typer@0.3.0: {} + + media-typer@1.1.0: {} + + merge-descriptors@1.0.3: {} + + merge-descriptors@2.0.0: {} + + methods@1.1.2: {} + micromark-core-commonmark@2.0.3: dependencies: decode-named-character-reference: 1.3.0 @@ -6382,6 +7582,20 @@ snapshots: transitivePeerDependencies: - supports-color + mime-db@1.52.0: {} + + mime-db@1.54.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mime@1.6.0: {} + mime@3.0.0: {} miniflare@4.20251118.1: @@ -6416,10 +7630,18 @@ snapshots: mrmime@2.0.1: {} + ms@2.0.0: {} + ms@2.1.3: {} + mute-stream@3.0.0: {} + nanoid@3.3.11: {} + negotiator@0.6.3: {} + + negotiator@1.0.0: {} + neotraverse@0.6.18: {} nlcst-to-string@4.0.0: @@ -6438,6 +7660,10 @@ snapshots: dependencies: boolbase: 1.0.0 + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + ofetch@1.5.1: dependencies: destr: 2.0.5 @@ -6446,6 +7672,14 @@ snapshots: ohash@2.0.11: {} + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + oniguruma-parser@0.12.1: {} oniguruma-to-es@4.3.4: @@ -6480,8 +7714,16 @@ snapshots: dependencies: entities: 6.0.1 + parseurl@1.3.3: {} + + path-key@3.1.1: {} + + path-to-regexp@0.1.12: {} + path-to-regexp@6.3.0: {} + path-to-regexp@8.3.0: {} + pathe@2.0.3: {} piccolore@0.1.3: {} @@ -6492,6 +7734,8 @@ snapshots: picomatch@4.0.3: {} + pkce-challenge@5.0.1: {} + postcss-selector-parser@6.0.10: dependencies: cssesc: 3.0.0 @@ -6512,6 +7756,19 @@ snapshots: property-information@7.1.0: {} + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + qs@6.14.2: + dependencies: + side-channel: 1.1.0 + + qs@6.15.0: + dependencies: + side-channel: 1.1.0 + radix-ui@1.4.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: '@radix-ui/primitive': 1.1.3 @@ -6577,6 +7834,22 @@ snapshots: radix3@1.1.2: {} + range-parser@1.2.1: {} + + raw-body@2.5.3: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + react-dom@19.2.4(react@19.2.4): dependencies: react: 19.2.4 @@ -6691,6 +7964,8 @@ snapshots: mdast-util-to-markdown: 2.1.2 unified: 11.0.5 + require-from-string@2.0.2: {} + resolve-pkg-maps@1.0.0: {} retext-latin@4.0.0: @@ -6749,6 +8024,20 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.57.1 fsevents: 2.3.3 + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.3.0 + transitivePeerDependencies: + - supports-color + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + sax@1.4.4: {} scheduler@0.27.0: {} @@ -6757,6 +8046,60 @@ snapshots: semver@7.7.4: {} + send@0.19.2: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.1 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@1.16.3: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + sharp@0.33.5: dependencies: color: 4.2.3 @@ -6814,6 +8157,12 @@ snapshots: '@img/sharp-win32-ia32': 0.34.5 '@img/sharp-win32-x64': 0.34.5 + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + shiki@3.22.0: dependencies: '@shikijs/core': 3.22.0 @@ -6825,6 +8174,36 @@ snapshots: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@4.1.0: {} + simple-swizzle@0.2.4: dependencies: is-arrayish: 0.3.4 @@ -6837,6 +8216,8 @@ snapshots: space-separated-tokens@2.0.2: {} + statuses@2.0.2: {} + stoppable@1.1.0: {} string-width@4.2.3: @@ -6895,6 +8276,8 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + toidentifier@1.0.1: {} + trim-lines@3.0.1: {} trough@2.2.0: {} @@ -6918,6 +8301,17 @@ snapshots: type-fest@4.41.0: {} + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 + typescript@5.9.3: {} ufo@1.6.3: {} @@ -6994,6 +8388,8 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 + unpipe@1.0.0: {} + unstorage@1.17.4: dependencies: anymatch: 3.1.3 @@ -7032,10 +8428,14 @@ snapshots: util-deprecate@1.0.2: {} + utils-merge@1.0.1: {} + utrie@1.0.2: dependencies: base64-arraybuffer: 1.0.2 + vary@1.1.2: {} + vfile-location@5.0.3: dependencies: '@types/unist': 3.0.3 @@ -7089,6 +8489,10 @@ snapshots: which-pm-runs@1.1.0: {} + which@2.0.2: + dependencies: + isexe: 2.0.0 + widest-line@5.0.0: dependencies: string-width: 7.2.0 @@ -7149,6 +8553,8 @@ snapshots: string-width: 7.2.0 strip-ansi: 7.1.2 + wrappy@1.0.2: {} + ws@8.18.0: {} xxhash-wasm@1.1.0: {} @@ -7182,6 +8588,10 @@ snapshots: dependencies: zod: 3.25.76 + zod-to-json-schema@3.25.1(zod@4.3.6): + dependencies: + zod: 4.3.6 + zod-to-ts@1.2.0(typescript@5.9.3)(zod@3.25.76): dependencies: typescript: 5.9.3 diff --git a/src/host/index.ts b/src/host/index.ts index 1d94a3f..662e204 100644 --- a/src/host/index.ts +++ b/src/host/index.ts @@ -28,8 +28,17 @@ import { import { CostTracker, initPricing } from './costs.js'; import { EventStore } from './events.js'; import { getStatus, onStatusChange, startHealthLoop, stopHealthLoop } from './health.js'; -import { startJanee, stopJanee } from './janee.js'; -import { readJaneeConfig } from "./janee-config.js"; +import { startJanee, stopJanee, reloadJaneeConfig } from './janee.js'; +import { + readJaneeConfig, + addService as janeeAddService, + updateService as janeeUpdateService, + deleteService as janeeDeleteService, + addCapability as janeeAddCapability, + updateCapability as janeeUpdateCapability, + deleteCapability as janeeDeleteCapability, + updateCapabilityAgents as janeeUpdateCapabilityAgents, +} from './janee-config.js'; import { Narrator } from './narrator.js'; import type { BudgetCheckResult } from './proxy.js'; import { handleLLMProxy } from './proxy.js'; @@ -651,13 +660,117 @@ export class Orchestrator { return; } - if (p === "/api/janee/config" && req.method === "GET") { + if (p === '/api/janee/config' && req.method === 'GET') { const config = readJaneeConfig(); - res.writeHead(200, { "Content-Type": "application/json" }); + res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(config)); return; } + // -- Janee config mutations -- + + if (p === '/api/janee/services' && req.method === 'POST') { + try { + const body = JSON.parse(await readBody(req)); + const result = janeeAddService(body.name, body.baseUrl, body.auth || { type: body.authType || 'bearer' }); + reloadJaneeConfig(); + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify(result)); + } catch (err: any) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: err.message })); + } + return; + } + + if (p.startsWith('/api/janee/services/') && req.method === 'PUT') { + try { + const name = decodeURIComponent(p.split('/')[4]); + const body = JSON.parse(await readBody(req)); + const result = janeeUpdateService(name, body); + reloadJaneeConfig(); + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify(result)); + } catch (err: any) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: err.message })); + } + return; + } + + if (p.startsWith('/api/janee/services/') && req.method === 'DELETE') { + try { + const name = decodeURIComponent(p.split('/')[4]); + const result = janeeDeleteService(name); + reloadJaneeConfig(); + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify(result)); + } catch (err: any) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: err.message })); + } + return; + } + + if (p === '/api/janee/capabilities' && req.method === 'POST') { + try { + const body = JSON.parse(await readBody(req)); + const { name, ...capConfig } = body; + const result = janeeAddCapability(name, capConfig); + reloadJaneeConfig(); + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify(result)); + } catch (err: any) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: err.message })); + } + return; + } + + if (p.match(/^\/api\/janee\/capabilities\/[^/]+$/) && req.method === 'PUT') { + try { + const name = decodeURIComponent(p.split('/')[4]); + const body = JSON.parse(await readBody(req)); + const result = janeeUpdateCapability(name, body); + reloadJaneeConfig(); + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify(result)); + } catch (err: any) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: err.message })); + } + return; + } + + if (p.match(/^\/api\/janee\/capabilities\/[^/]+$/) && req.method === 'DELETE') { + try { + const name = decodeURIComponent(p.split('/')[4]); + const result = janeeDeleteCapability(name); + reloadJaneeConfig(); + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify(result)); + } catch (err: any) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: err.message })); + } + return; + } + + if (p.match(/^\/api\/janee\/capabilities\/[^/]+\/agents$/) && req.method === 'PUT') { + try { + const capName = decodeURIComponent(p.split('/')[4]); + const body = JSON.parse(await readBody(req)); + const result = janeeUpdateCapabilityAgents(capName, body.agents || []); + reloadJaneeConfig(); + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify(result)); + } catch (err: any) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: err.message })); + } + return; + } + if (p === '/api/status' && req.method === 'GET') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(getStatus())); diff --git a/src/host/janee-config.ts b/src/host/janee-config.ts index caa6f11..bf3192c 100644 --- a/src/host/janee-config.ts +++ b/src/host/janee-config.ts @@ -1,21 +1,29 @@ /** - * Read-only access to Janee config.yaml for the dashboard. - * Masks all secrets/keys before returning. + * Janee config access layer for the dashboard. + * Uses Janee's own library for read/write, masks secrets before returning. */ -import fs from 'node:fs'; -import path from 'node:path'; -import jsYaml from 'js-yaml'; +import { + loadYAMLConfig, + saveYAMLConfig, + hasYAMLConfig, + addServiceYAML, + addCapabilityYAML, + type JaneeYAMLConfig, + type ServiceConfig, + type CapabilityConfig, + type AuthConfig, +} from '@true-and-useful/janee'; -const JANEE_HOME = process.env.JANEE_HOME || path.join(process.env.HOME || '/root', '.janee'); +// ── Public view types (sent to dashboard, secrets stripped) ── -interface MaskedService { +export interface MaskedService { name: string; baseUrl: string; authType: string; ownership?: { type: string; agentId?: string; accessPolicy?: string }; } -interface MaskedCapability { +export interface MaskedCapability { name: string; service: string; mode: string; @@ -28,7 +36,7 @@ interface MaskedCapability { timeout?: number; } -interface AgentAccess { +export interface AgentAccess { agentId: string; capabilities: string[]; } @@ -41,60 +49,35 @@ export interface JaneeConfigView { agents: AgentAccess[]; } -export function readJaneeConfig(): JaneeConfigView { - const configPath = path.join(JANEE_HOME, 'config.yaml'); - try { - const raw = fs.readFileSync(configPath, 'utf-8'); - const parsed = jsYaml.load(raw); - return parseConfig(parsed); - } catch { - return { available: false, services: [], capabilities: [], agents: [] }; - } -} +const EMPTY: JaneeConfigView = { available: false, services: [], capabilities: [], agents: [] }; -function parseConfig(config: any): JaneeConfigView { - if (!config || typeof config !== 'object') { - return { available: false, services: [], capabilities: [], agents: [] }; - } +// ── Read (mask secrets) ── - const services: MaskedService[] = []; - if (config.services && typeof config.services === 'object') { - for (const [name, svc] of Object.entries(config.services as Record)) { - services.push({ - name, - baseUrl: svc.baseUrl || '', - authType: svc.auth?.type || 'unknown', - ownership: svc.ownership ? { - type: svc.ownership.type || 'cli', - agentId: svc.ownership.agentId, - accessPolicy: svc.ownership.accessPolicy, - } : undefined, - }); - } - } +function maskConfig(config: JaneeYAMLConfig): JaneeConfigView { + const services: MaskedService[] = Object.entries(config.services).map(([name, svc]) => ({ + name, + baseUrl: svc.baseUrl, + authType: svc.auth.type, + ownership: svc.ownership ? { + type: svc.ownership.accessPolicy || 'all-agents', + agentId: svc.ownership.createdBy, + accessPolicy: svc.ownership.accessPolicy, + } : undefined, + })); - const capabilities: MaskedCapability[] = []; - if (config.capabilities && typeof config.capabilities === 'object') { - for (const [name, cap] of Object.entries(config.capabilities as Record)) { - capabilities.push({ - name, - service: cap.service || '', - mode: cap.mode || 'proxy', - ttl: cap.ttl || '', - requiresReason: !!cap.requiresReason, - rules: cap.rules ? { - allow: cap.rules.allow || [], - deny: cap.rules.deny || [], - } : undefined, - allowedAgents: cap.allowedAgents, - allowCommands: cap.allowCommands, - workDir: cap.workDir, - timeout: cap.timeout, - }); - } - } + const capabilities: MaskedCapability[] = Object.entries(config.capabilities).map(([name, cap]) => ({ + name, + service: cap.service, + mode: cap.mode || 'proxy', + ttl: cap.ttl, + requiresReason: !!cap.requiresReason, + rules: cap.rules ? { allow: cap.rules.allow || [], deny: cap.rules.deny || [] } : undefined, + allowedAgents: cap.allowedAgents, + allowCommands: cap.allowCommands, + workDir: cap.workDir, + timeout: cap.timeout, + })); - // Derive agent access from capabilities const agentMap = new Map(); for (const cap of capabilities) { if (cap.allowedAgents) { @@ -112,8 +95,8 @@ function parseConfig(config: any): JaneeConfigView { return { available: true, server: config.server ? { - port: config.server.port || 3100, - host: config.server.host || 'localhost', + port: config.server.port, + host: config.server.host, defaultAccess: config.server.defaultAccess, } : undefined, services, @@ -121,3 +104,76 @@ function parseConfig(config: any): JaneeConfigView { agents, }; } + +export function readJaneeConfig(): JaneeConfigView { + try { + if (!hasYAMLConfig()) return EMPTY; + return maskConfig(loadYAMLConfig()); + } catch { + return EMPTY; + } +} + +// ── Mutations (all return fresh masked view) ── + +function loadConfig(): JaneeYAMLConfig { + return loadYAMLConfig(); +} + +export function addService(name: string, baseUrl: string, auth: AuthConfig): JaneeConfigView { + addServiceYAML(name, baseUrl, auth); + return readJaneeConfig(); +} + +export function updateService(name: string, patch: { baseUrl?: string; authType?: AuthConfig['type'] }): JaneeConfigView { + const config = loadConfig(); + const svc = config.services[name]; + if (!svc) throw new Error(`Service "${name}" not found`); + if (patch.baseUrl !== undefined) svc.baseUrl = patch.baseUrl; + if (patch.authType !== undefined) svc.auth.type = patch.authType; + saveYAMLConfig(config); + return readJaneeConfig(); +} + +export function deleteService(name: string): JaneeConfigView { + const config = loadConfig(); + if (!config.services[name]) throw new Error(`Service "${name}" not found`); + delete config.services[name]; + // Remove capabilities that referenced this service + for (const [capName, cap] of Object.entries(config.capabilities)) { + if (cap.service === name) delete config.capabilities[capName]; + } + saveYAMLConfig(config); + return readJaneeConfig(); +} + +export function addCapability(name: string, capConfig: CapabilityConfig): JaneeConfigView { + addCapabilityYAML(name, capConfig); + return readJaneeConfig(); +} + +export function updateCapability(name: string, patch: Partial): JaneeConfigView { + const config = loadConfig(); + const cap = config.capabilities[name]; + if (!cap) throw new Error(`Capability "${name}" not found`); + Object.assign(cap, patch); + saveYAMLConfig(config); + return readJaneeConfig(); +} + +export function deleteCapability(name: string): JaneeConfigView { + const config = loadConfig(); + if (!config.capabilities[name]) throw new Error(`Capability "${name}" not found`); + delete config.capabilities[name]; + saveYAMLConfig(config); + return readJaneeConfig(); +} + +export function updateCapabilityAgents(capName: string, agents: string[]): JaneeConfigView { + const config = loadConfig(); + const cap = config.capabilities[capName]; + if (!cap) throw new Error(`Capability "${capName}" not found`); + cap.allowedAgents = agents.length > 0 ? agents : undefined; + saveYAMLConfig(config); + return readJaneeConfig(); +} diff --git a/src/host/janee.ts b/src/host/janee.ts index bfb2118..036657a 100644 --- a/src/host/janee.ts +++ b/src/host/janee.ts @@ -188,3 +188,10 @@ export function stopJanee(): void { export function isJaneeAvailable(): boolean { return janeeAvailable; } + +/** Send SIGHUP to the running Janee process to reload config from disk. */ +export function reloadJaneeConfig(): boolean { + if (!janeeProcess?.pid) return false; + janeeProcess.kill('SIGHUP'); + return true; +} From 6d09c4e46bd85ed5f02dd36a04932a460427a12f Mon Sep 17 00:00:00 2001 From: Ross Douglas Date: Fri, 27 Feb 2026 13:48:21 +0200 Subject: [PATCH 2/3] Refine Janee settings UI: permission-aware access states, creature dropdown - Permission state is now the primary element on each capability row (right-aligned, same line as name/mode) - Shows correct state based on server.defaultAccess: "No access" (amber) when restricted, "All creatures" (green) when open - Agent picker uses dropdown of known creatures instead of free-text input - Creature chips show short names (alpha not creature:alpha) - Remove duplicate "restart" key from package.json Made-with: Cursor --- dashboard/src/components/JaneeSection.tsx | 321 ++++++++++++---------- 1 file changed, 183 insertions(+), 138 deletions(-) diff --git a/dashboard/src/components/JaneeSection.tsx b/dashboard/src/components/JaneeSection.tsx index 1d55f5a..c7dd1a3 100644 --- a/dashboard/src/components/JaneeSection.tsx +++ b/dashboard/src/components/JaneeSection.tsx @@ -17,6 +17,79 @@ type Tab = typeof tabs[number]; const AUTH_TYPES = ['bearer', 'headers', 'hmac-mexc', 'hmac-bybit', 'hmac-okx', 'service-account', 'github-app'] as const; +// ── Permission state ── + +function PermissionState({ agents, capName, onUpdate, knownAgents, defaultAccess }: { + agents: string[]; + capName: string; + onUpdate: (capName: string, agents: string[]) => void; + knownAgents: string[]; + defaultAccess?: string; +}) { + const [picking, setPicking] = useState(false); + const hasAgents = agents.length > 0; + const available = knownAgents.filter(a => !agents.includes(a)); + + const addAgent = (agentId: string) => { + if (!agentId || agents.includes(agentId)) return; + onUpdate(capName, [...agents, agentId]); + setPicking(false); + }; + + const removeAgent = (agent: string) => { + onUpdate(capName, agents.filter(a => a !== agent)); + }; + + const picker = available.length > 0 && ( + picking ? ( + + ) : ( + + ) + ); + + if (!hasAgents) { + const isRestricted = defaultAccess === 'restricted'; + return ( +
+ + {isRestricted ? 'No access' : 'All creatures'} + + {picker} +
+ ); + } + + return ( +
+ {agents.map(a => ( + + {a.replace('creature:', '')} + + + ))} + {picker} +
+ ); +} + // ── Inline forms ── function AddServiceForm({ onSubmit, onCancel }: { @@ -35,36 +108,36 @@ function AddServiceForm({ onSubmit, onCancel }: { }; return ( -
-
New service
-
+
+
New service
+
setName(e.target.value)} - className="flex-1 text-[12.5px] px-2.5 py-1.5 rounded border border-border-light bg-white focus:outline-none focus:border-text-primary/40 font-mono" + className="flex-1 text-[13.5px] px-3 py-2 rounded-md border border-border-light bg-bg focus:outline-none focus:border-text-primary/30 font-mono" onKeyDown={e => e.key === 'Enter' && submit()} /> setBaseUrl(e.target.value)} - className="flex-2 text-[12.5px] px-2.5 py-1.5 rounded border border-border-light bg-white focus:outline-none focus:border-text-primary/40 font-mono" + className="flex-2 text-[13.5px] px-3 py-2 rounded-md border border-border-light bg-bg focus:outline-none focus:border-text-primary/30 font-mono" onKeyDown={e => e.key === 'Enter' && submit()} />
-
- - +
@@ -88,121 +161,69 @@ function AddCapabilityForm({ serviceName, onSubmit, onCancel }: { }; return ( -
-
+
+
setName(e.target.value)} - className="flex-1 text-[12px] px-2 py-1 rounded border border-border-light bg-white focus:outline-none focus:border-text-primary/40 font-mono" + className="flex-1 text-[13px] px-2.5 py-1.5 rounded-md border border-border-light bg-white focus:outline-none focus:border-text-primary/30 font-mono" onKeyDown={e => e.key === 'Enter' && submit()} /> setTtl(e.target.value)} - className="w-16 text-[12px] px-2 py-1 rounded border border-border-light bg-white focus:outline-none font-mono" + className="w-16 text-[13px] px-2.5 py-1.5 rounded-md border border-border-light bg-white focus:outline-none font-mono text-center" /> - - +
); } -function AgentChips({ agents, capName, onUpdate }: { - agents: string[]; - capName: string; - onUpdate: (capName: string, agents: string[]) => void; -}) { - const [adding, setAdding] = useState(false); - const [value, setValue] = useState(''); - - const remove = (agent: string) => { - onUpdate(capName, agents.filter(a => a !== agent)); - }; - - const add = () => { - const v = value.trim(); - if (!v || agents.includes(v)) return; - onUpdate(capName, [...agents, v]); - setValue(''); - setAdding(false); - }; - - return ( -
- access - {agents.map(a => ( - - {a} - - - ))} - {adding ? ( - setValue(e.target.value)} - onKeyDown={e => { if (e.key === 'Enter') add(); if (e.key === 'Escape') setAdding(false); }} - onBlur={() => { if (!value.trim()) setAdding(false); }} - placeholder="agent-id" - className="text-[10px] font-mono w-24 px-1.5 py-0.5 rounded border border-border-light bg-white focus:outline-none focus:border-text-primary/40" - /> - ) : ( - - )} -
- ); -} - // ── Display components ── -function CapRow({ cap, agents, onDelete, onUpdateAgents }: { +function CapRow({ cap, agents, onDelete, onUpdateAgents, knownAgents, defaultAccess }: { cap: MaskedCapability; agents: string[]; onDelete: (name: string) => void; onUpdateAgents: (capName: string, agents: string[]) => void; + knownAgents: string[]; + defaultAccess?: string; }) { return ( -
-
-
- {cap.name} - {cap.mode} - {cap.ttl && {cap.ttl}} -
+
+
+ {cap.name} + {cap.mode} + {cap.ttl && {cap.ttl}} +
+
+
-
); } -function ServiceCard({ svc, caps, agentsByCap, onDeleteService, onDeleteCap, onAddCap, onUpdateAgents }: { +function ServiceCard({ svc, caps, agentsByCap, onDeleteService, onDeleteCap, onAddCap, onUpdateAgents, knownAgents, defaultAccess }: { svc: MaskedService; caps: MaskedCapability[]; agentsByCap: Map; @@ -210,40 +231,46 @@ function ServiceCard({ svc, caps, agentsByCap, onDeleteService, onDeleteCap, onA onDeleteCap: (name: string) => void; onAddCap: (name: string, config: { service: string; ttl?: string; mode?: string }) => void; onUpdateAgents: (capName: string, agents: string[]) => void; + knownAgents: string[]; + defaultAccess?: string; }) { const [addingCap, setAddingCap] = useState(false); return (
-
-
- {svc.name} - {svc.baseUrl} +
+
+ {svc.name} + {svc.baseUrl}
-
- +
+ {svc.authType}
- {caps.length > 0 ? ( - caps.map(cap => ( - - )) - ) : ( -
No capabilities
+ + {caps.map(cap => ( + + ))} + + {!caps.length && !addingCap && ( +
No capabilities defined
)} + {addingCap ? ( setAddingCap(true)} - className="w-full px-4 py-2 text-[10.5px] text-text-faint hover:text-text-primary hover:bg-[#faf9f6] transition-colors border-t border-border-light/60 text-left" + className="w-full px-5 py-2.5 text-[12.5px] text-text-faint hover:text-text-secondary hover:bg-bg/40 transition-colors border-t border-border-light/50 text-left" > + Add capability @@ -262,11 +289,15 @@ function ServiceCard({ svc, caps, agentsByCap, onDeleteService, onDeleteCap, onA ); } -function ServicesTab({ services, capabilities, agents, onMutate }: { +// ── Tabs ── + +function ServicesTab({ services, capabilities, agents, onMutate, knownAgents, defaultAccess }: { services: MaskedService[]; capabilities: MaskedCapability[]; agents: AgentAccess[]; onMutate: (fn: () => Promise) => void; + knownAgents: string[]; + defaultAccess?: string; }) { const [addingService, setAddingService] = useState(false); @@ -311,18 +342,7 @@ function ServicesTab({ services, capabilities, agents, onMutate }: { }; return ( -
- {addingService ? ( - setAddingService(false)} /> - ) : ( - - )} - +
{services.map(svc => ( ))} + {unbound.length > 0 && (
-
- Unbound capabilities +
+ Unbound capabilities
{unbound.map(cap => ( ))}
)} + {addingService ? ( + setAddingService(false)} /> + ) : ( + + )} + {!services.length && !capabilities.length && !addingService && ( -
-

No services configured.

+
+

No services configured yet.

+

Add a service to start managing API access.

)}
@@ -364,20 +401,22 @@ function ServicesTab({ services, capabilities, agents, onMutate }: { function AgentsTab({ agents }: { agents: AgentAccess[] }) { if (!agents.length) { return ( -
-

No agent access rules defined.

-

Grant access via capability settings in the Services tab.

+
+

No agent access rules yet.

+

+ Restrict a capability to specific creatures in the Services tab. +

); } return ( -
+
{agents.map(a => ( -
-
{a.agentId}
-
+
+
{a.agentId}
+
{a.capabilities.map(s => ( - {s} + {s} ))}
@@ -386,16 +425,22 @@ function AgentsTab({ agents }: { agents: AgentAccess[] }) { ); } +// ── Main ── + export function JaneeSection() { const [tab, setTab] = useState('Services'); const [config, setConfig] = useState(null); const [error, setError] = useState(null); const [mutating, setMutating] = useState(false); + const [knownAgents, setKnownAgents] = useState([]); useEffect(() => { api.fetchJaneeConfig() .then(setConfig) .catch(() => setError('Could not load Janee config. Is Janee running?')); + api.fetchCreatures() + .then(creatures => setKnownAgents(creatures.map(c => `creature:${c.name}`))) + .catch(() => {}); }, []); const onMutate = useCallback((fn: () => Promise) => { @@ -426,39 +471,39 @@ export function JaneeSection() { } return ( -
+

Janee

-

- Services, capabilities & agent access for ~/.janee/config.yaml +

+ Manage services, capabilities, and which creatures can use them.

{error && ( -
{error}
+
{error}
)} -
+
{tabs.map(t => ( ))}
- {tab === 'Services' && } + {tab === 'Services' && } {tab === 'Agents' && }
); From 39ba1bab12f49aa504a7f8ae79ecd68963b2a871 Mon Sep 17 00:00:00 2001 From: Ross Douglas Date: Fri, 27 Feb 2026 13:56:14 +0200 Subject: [PATCH 3/3] Address PR review: input validation, route matching, field whitelisting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add input validation on service/capability creation (name, baseUrl required) - Use regex route matching consistently for all service routes (was startsWith) - Use p.slice() for path param extraction instead of fragile split()[4] - Whitelist known CapabilityConfig fields in updateCapability (was Object.assign) - Return reloaded: boolean in mutation responses (SIGHUP result) - Extract shared janeeReply/janeeFail helpers to reduce route boilerplate - Add comment noting single-user load→mutate→save is intentionally non-atomic Made-with: Cursor --- src/host/index.ts | 104 +++++++++++++++------------------------ src/host/janee-config.ts | 6 ++- 2 files changed, 45 insertions(+), 65 deletions(-) diff --git a/src/host/index.ts b/src/host/index.ts index 662e204..773dcf9 100644 --- a/src/host/index.ts +++ b/src/host/index.ts @@ -668,106 +668,82 @@ export class Orchestrator { } // -- Janee config mutations -- + // Note: no mutex on load→mutate→save; fine for single-user dashboard. + + const janeeReply = (result: any) => { + const reloaded = reloadJaneeConfig(); + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ ...result, reloaded })); + }; + const janeeFail = (err: any, code = 400) => { + res.writeHead(code, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: err.message })); + }; if (p === '/api/janee/services' && req.method === 'POST') { try { const body = JSON.parse(await readBody(req)); - const result = janeeAddService(body.name, body.baseUrl, body.auth || { type: body.authType || 'bearer' }); - reloadJaneeConfig(); - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify(result)); - } catch (err: any) { - res.writeHead(400, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ error: err.message })); - } + const name = (body.name || '').trim(); + const baseUrl = (body.baseUrl || '').trim(); + if (!name) throw new Error('name is required'); + if (!baseUrl) throw new Error('baseUrl is required'); + janeeReply(janeeAddService(name, baseUrl, body.auth || { type: body.authType || 'bearer' })); + } catch (err: any) { janeeFail(err); } return; } - if (p.startsWith('/api/janee/services/') && req.method === 'PUT') { + if (p.match(/^\/api\/janee\/services\/[^/]+$/) && req.method === 'PUT') { try { - const name = decodeURIComponent(p.split('/')[4]); + const name = decodeURIComponent(p.slice('/api/janee/services/'.length)); const body = JSON.parse(await readBody(req)); - const result = janeeUpdateService(name, body); - reloadJaneeConfig(); - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify(result)); - } catch (err: any) { - res.writeHead(400, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ error: err.message })); - } + janeeReply(janeeUpdateService(name, body)); + } catch (err: any) { janeeFail(err); } return; } - if (p.startsWith('/api/janee/services/') && req.method === 'DELETE') { + if (p.match(/^\/api\/janee\/services\/[^/]+$/) && req.method === 'DELETE') { try { - const name = decodeURIComponent(p.split('/')[4]); - const result = janeeDeleteService(name); - reloadJaneeConfig(); - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify(result)); - } catch (err: any) { - res.writeHead(400, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ error: err.message })); - } + const name = decodeURIComponent(p.slice('/api/janee/services/'.length)); + janeeReply(janeeDeleteService(name)); + } catch (err: any) { janeeFail(err); } return; } if (p === '/api/janee/capabilities' && req.method === 'POST') { try { const body = JSON.parse(await readBody(req)); - const { name, ...capConfig } = body; - const result = janeeAddCapability(name, capConfig); - reloadJaneeConfig(); - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify(result)); - } catch (err: any) { - res.writeHead(400, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ error: err.message })); - } + const name = (body.name || '').trim(); + if (!name) throw new Error('name is required'); + if (!body.service) throw new Error('service is required'); + const { name: _, ...capConfig } = body; + janeeReply(janeeAddCapability(name, capConfig)); + } catch (err: any) { janeeFail(err); } return; } if (p.match(/^\/api\/janee\/capabilities\/[^/]+$/) && req.method === 'PUT') { try { - const name = decodeURIComponent(p.split('/')[4]); + const name = decodeURIComponent(p.slice('/api/janee/capabilities/'.length)); const body = JSON.parse(await readBody(req)); - const result = janeeUpdateCapability(name, body); - reloadJaneeConfig(); - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify(result)); - } catch (err: any) { - res.writeHead(400, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ error: err.message })); - } + janeeReply(janeeUpdateCapability(name, body)); + } catch (err: any) { janeeFail(err); } return; } if (p.match(/^\/api\/janee\/capabilities\/[^/]+$/) && req.method === 'DELETE') { try { - const name = decodeURIComponent(p.split('/')[4]); - const result = janeeDeleteCapability(name); - reloadJaneeConfig(); - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify(result)); - } catch (err: any) { - res.writeHead(400, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ error: err.message })); - } + const name = decodeURIComponent(p.slice('/api/janee/capabilities/'.length)); + janeeReply(janeeDeleteCapability(name)); + } catch (err: any) { janeeFail(err); } return; } if (p.match(/^\/api\/janee\/capabilities\/[^/]+\/agents$/) && req.method === 'PUT') { try { - const capName = decodeURIComponent(p.split('/')[4]); + const capName = decodeURIComponent(p.split('/api/janee/capabilities/')[1].split('/agents')[0]); const body = JSON.parse(await readBody(req)); - const result = janeeUpdateCapabilityAgents(capName, body.agents || []); - reloadJaneeConfig(); - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify(result)); - } catch (err: any) { - res.writeHead(400, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ error: err.message })); - } + janeeReply(janeeUpdateCapabilityAgents(capName, body.agents || [])); + } catch (err: any) { janeeFail(err); } return; } diff --git a/src/host/janee-config.ts b/src/host/janee-config.ts index bf3192c..dc19499 100644 --- a/src/host/janee-config.ts +++ b/src/host/janee-config.ts @@ -152,11 +152,15 @@ export function addCapability(name: string, capConfig: CapabilityConfig): JaneeC return readJaneeConfig(); } +const CAP_FIELDS = ['service', 'ttl', 'mode', 'requiresReason', 'rules', 'allowedAgents', 'allowCommands', 'workDir', 'timeout'] as const; + export function updateCapability(name: string, patch: Partial): JaneeConfigView { const config = loadConfig(); const cap = config.capabilities[name]; if (!cap) throw new Error(`Capability "${name}" not found`); - Object.assign(cap, patch); + for (const key of CAP_FIELDS) { + if (key in patch) (cap as any)[key] = (patch as any)[key]; + } saveYAMLConfig(config); return readJaneeConfig(); }