From 0d71df61e45b85c171c5bfca070ebd7d8b65918f Mon Sep 17 00:00:00 2001 From: Omair Saleh Date: Mon, 2 Mar 2026 19:10:57 +0800 Subject: [PATCH] =?UTF-8?q?clean:=20calvana=20project=20files=20only=20?= =?UTF-8?q?=E2=80=94=20remove=20all=20framework/tooling=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/commands/prime.md | 16 - .env.sample | 20 - .pi/agents/agent-chain.yaml | 49 - .pi/agents/bowser.md | 19 - .pi/agents/builder.md | 6 - .pi/agents/documenter.md | 6 - .pi/agents/pi-pi/agent-expert.md | 98 - .pi/agents/pi-pi/cli-expert.md | 41 - .pi/agents/pi-pi/config-expert.md | 63 - .pi/agents/pi-pi/ext-expert.md | 43 - .pi/agents/pi-pi/keybinding-expert.md | 134 - .pi/agents/pi-pi/pi-orchestrator.md | 57 - .pi/agents/pi-pi/prompt-expert.md | 70 - .pi/agents/pi-pi/skill-expert.md | 42 - .pi/agents/pi-pi/theme-expert.md | 40 - .pi/agents/pi-pi/tui-expert.md | 85 - .pi/agents/plan-reviewer.md | 22 - .pi/agents/planner.md | 6 - .pi/agents/red-team.md | 6 - .pi/agents/reviewer.md | 6 - .pi/agents/scout.md | 6 - .pi/agents/teams.yaml | 31 - .pi/damage-control-rules.yaml | 279 - .pi/extensions/calvana-shiplog.ts | 719 -- .pi/observatory/.gitignore | 3 - .pi/observatory/.gitkeep | 0 .pi/settings.json | 6 - .pi/skills/bowser.md | 120 - .pi/themes/catppuccin-mocha.json | 86 - .pi/themes/cyberpunk.json | 81 - .pi/themes/dracula.json | 81 - .pi/themes/everforest.json | 82 - .pi/themes/gruvbox.json | 80 - .pi/themes/midnight-ocean.json | 76 - .pi/themes/nord.json | 84 - .pi/themes/ocean-breeze.json | 83 - .pi/themes/rose-pine.json | 82 - .pi/themes/synthwave.json | 82 - .pi/themes/tokyo-night.json | 83 - CLAUDE.md | 20 - COMPARISON.md | 243 - PI_VS_OPEN_CODE.md | 178 - RESERVED_KEYS.md | 75 - THEME.md | 29 - TOOLS.md | 27 - application-answers.md | 195 - bun.lock | 28 - calvana-build/Dockerfile | 4 - calvana-build/html/404.html | 26 - calvana-build/html/css/style.css | 104 - calvana-build/html/hire/index.html | 43 - calvana-build/html/index.html | 5 - calvana-build/html/live/index.html | 121 - calvana-build/html/manifesto/index.html | 47 - calvana-build/nginx.conf | 28 - extensions/agent-chain.ts | 797 -- extensions/agent-dashboard.ts | 971 -- extensions/agent-team.ts | 944 -- extensions/cross-agent.ts | 265 - extensions/damage-control.ts | 206 - extensions/minimal.ts | 34 - extensions/observatory.ts | 1100 --- extensions/pi-pi.ts | 633 -- extensions/pure-focus.ts | 24 - extensions/purpose-gate.ts | 84 - extensions/session-replay.ts | 216 - extensions/stop.ts | 41 - extensions/subagent-widget.ts | 481 - extensions/system-select.ts | 167 - extensions/theme-cycler.ts | 181 - extensions/themeMap.ts | 145 - extensions/tilldone.ts | 726 -- extensions/tool-counter-widget.ts | 68 - extensions/tool-counter.ts | 102 - images/pi-logo.png | Bin 3778 -> 0 bytes images/pi-logo.svg | 22 - job-application-guide.md | 229 - job-page-top.png | Bin 146812 -> 0 bytes justfile | 114 - package.json | 12 - pledge-now-pay-later/.env.example | 7 - pledge-now-pay-later/.eslintrc.json | 3 - pledge-now-pay-later/.gitignore | 43 - pledge-now-pay-later/Dockerfile | 27 - pledge-now-pay-later/README.md | 139 - pledge-now-pay-later/bun.lock | 1242 --- pledge-now-pay-later/docker-compose.yml | 23 - pledge-now-pay-later/docs/EMBED_GUIDE.md | 134 - pledge-now-pay-later/docs/PRODUCT_SPEC.md | 1364 --- pledge-now-pay-later/next.config.mjs | 6 - pledge-now-pay-later/package-lock.json | 8166 ----------------- pledge-now-pay-later/package.json | 49 - pledge-now-pay-later/postcss.config.mjs | 8 - pledge-now-pay-later/prisma.config.ts | 15 - pledge-now-pay-later/prisma/schema.prisma | 208 - pledge-now-pay-later/prisma/seed.mts | 306 - .../src/app/api/analytics/route.ts | 33 - .../src/app/api/dashboard/route.ts | 156 - .../events/[id]/qr/[qrId]/download/route.ts | 32 - .../src/app/api/events/[id]/qr/route.ts | 104 - .../src/app/api/events/route.ts | 107 - .../src/app/api/exports/crm-pack/route.ts | 78 - .../src/app/api/gocardless/callback/route.ts | 74 - .../app/api/gocardless/create-flow/route.ts | 135 - .../src/app/api/gocardless/webhook/route.ts | 82 - .../app/api/imports/bank-statement/route.ts | 133 - .../api/pledges/[id]/mark-initiated/route.ts | 31 - .../src/app/api/pledges/[id]/route.ts | 88 - .../src/app/api/pledges/route.ts | 133 - .../src/app/api/qr/[token]/route.ts | 66 - .../src/app/api/settings/route.ts | 60 - .../src/app/api/stripe/checkout/route.ts | 118 - .../src/app/api/stripe/webhook/route.ts | 88 - .../src/app/api/webhooks/route.ts | 79 - .../src/app/dashboard/apply/page.tsx | 92 - .../src/app/dashboard/events/[id]/page.tsx | 241 - .../src/app/dashboard/events/page.tsx | 225 - .../src/app/dashboard/exports/page.tsx | 77 - .../src/app/dashboard/layout.tsx | 89 - .../src/app/dashboard/loading.tsx | 23 - .../src/app/dashboard/page.tsx | 324 - .../src/app/dashboard/pledges/page.tsx | 293 - .../src/app/dashboard/reconcile/page.tsx | 239 - .../src/app/dashboard/settings/page.tsx | 266 - pledge-now-pay-later/src/app/favicon.ico | Bin 25931 -> 0 bytes .../src/app/fonts/GeistMonoVF.woff | Bin 67864 -> 0 bytes .../src/app/fonts/GeistVF.woff | Bin 66268 -> 0 bytes pledge-now-pay-later/src/app/globals.css | 45 - pledge-now-pay-later/src/app/layout.tsx | 20 - .../src/app/p/[token]/loading.tsx | 12 - .../src/app/p/[token]/page.tsx | 210 - .../src/app/p/[token]/steps/amount-step.tsx | 97 - .../[token]/steps/bank-instructions-step.tsx | 165 - .../app/p/[token]/steps/card-payment-step.tsx | 305 - .../app/p/[token]/steps/confirmation-step.tsx | 87 - .../app/p/[token]/steps/direct-debit-step.tsx | 365 - .../app/p/[token]/steps/fpx-payment-step.tsx | 329 - .../src/app/p/[token]/steps/identity-step.tsx | 123 - .../src/app/p/[token]/steps/payment-step.tsx | 95 - .../src/app/p/success/page.tsx | 152 - pledge-now-pay-later/src/app/page.tsx | 94 - .../src/components/qr-code.tsx | 32 - .../src/components/ui/badge.tsx | 28 - .../src/components/ui/button.tsx | 38 - .../src/components/ui/card.tsx | 34 - .../src/components/ui/dialog.tsx | 35 - .../src/components/ui/input.tsx | 17 - .../src/components/ui/label.tsx | 9 - .../src/components/ui/select.tsx | 18 - .../src/components/ui/skeleton.tsx | 7 - .../src/components/ui/textarea.tsx | 18 - .../src/components/ui/toast.tsx | 49 - pledge-now-pay-later/src/lib/analytics.ts | 37 - pledge-now-pay-later/src/lib/exports.ts | 42 - pledge-now-pay-later/src/lib/gocardless.ts | 171 - pledge-now-pay-later/src/lib/matching.ts | 98 - pledge-now-pay-later/src/lib/org.ts | 29 - pledge-now-pay-later/src/lib/prisma.ts | 21 - pledge-now-pay-later/src/lib/qr.ts | 48 - pledge-now-pay-later/src/lib/reference.ts | 39 - pledge-now-pay-later/src/lib/reminders.ts | 100 - pledge-now-pay-later/src/lib/stripe.ts | 127 - pledge-now-pay-later/src/lib/utils.ts | 16 - pledge-now-pay-later/src/lib/validators.ts | 48 - pledge-now-pay-later/src/middleware.ts | 50 - pledge-now-pay-later/tailwind.config.ts | 62 - pledge-now-pay-later/tsconfig.json | 26 - specs/agent-forge.md | 72 - specs/agent-workflow.md | 64 - specs/damage-control.md | 44 - specs/pi-pi.md | 138 - 171 files changed, 30450 deletions(-) delete mode 100644 .claude/commands/prime.md delete mode 100644 .env.sample delete mode 100644 .pi/agents/agent-chain.yaml delete mode 100644 .pi/agents/bowser.md delete mode 100644 .pi/agents/builder.md delete mode 100644 .pi/agents/documenter.md delete mode 100644 .pi/agents/pi-pi/agent-expert.md delete mode 100644 .pi/agents/pi-pi/cli-expert.md delete mode 100644 .pi/agents/pi-pi/config-expert.md delete mode 100644 .pi/agents/pi-pi/ext-expert.md delete mode 100644 .pi/agents/pi-pi/keybinding-expert.md delete mode 100644 .pi/agents/pi-pi/pi-orchestrator.md delete mode 100644 .pi/agents/pi-pi/prompt-expert.md delete mode 100644 .pi/agents/pi-pi/skill-expert.md delete mode 100644 .pi/agents/pi-pi/theme-expert.md delete mode 100644 .pi/agents/pi-pi/tui-expert.md delete mode 100644 .pi/agents/plan-reviewer.md delete mode 100644 .pi/agents/planner.md delete mode 100644 .pi/agents/red-team.md delete mode 100644 .pi/agents/reviewer.md delete mode 100644 .pi/agents/scout.md delete mode 100644 .pi/agents/teams.yaml delete mode 100644 .pi/damage-control-rules.yaml delete mode 100644 .pi/extensions/calvana-shiplog.ts delete mode 100644 .pi/observatory/.gitignore delete mode 100644 .pi/observatory/.gitkeep delete mode 100644 .pi/settings.json delete mode 100644 .pi/skills/bowser.md delete mode 100644 .pi/themes/catppuccin-mocha.json delete mode 100644 .pi/themes/cyberpunk.json delete mode 100644 .pi/themes/dracula.json delete mode 100644 .pi/themes/everforest.json delete mode 100644 .pi/themes/gruvbox.json delete mode 100644 .pi/themes/midnight-ocean.json delete mode 100644 .pi/themes/nord.json delete mode 100644 .pi/themes/ocean-breeze.json delete mode 100644 .pi/themes/rose-pine.json delete mode 100644 .pi/themes/synthwave.json delete mode 100644 .pi/themes/tokyo-night.json delete mode 100644 CLAUDE.md delete mode 100644 COMPARISON.md delete mode 100644 PI_VS_OPEN_CODE.md delete mode 100644 RESERVED_KEYS.md delete mode 100644 THEME.md delete mode 100644 TOOLS.md delete mode 100644 application-answers.md delete mode 100644 bun.lock delete mode 100644 calvana-build/Dockerfile delete mode 100644 calvana-build/html/404.html delete mode 100644 calvana-build/html/css/style.css delete mode 100644 calvana-build/html/hire/index.html delete mode 100644 calvana-build/html/index.html delete mode 100644 calvana-build/html/live/index.html delete mode 100644 calvana-build/html/manifesto/index.html delete mode 100644 calvana-build/nginx.conf delete mode 100644 extensions/agent-chain.ts delete mode 100644 extensions/agent-dashboard.ts delete mode 100644 extensions/agent-team.ts delete mode 100644 extensions/cross-agent.ts delete mode 100644 extensions/damage-control.ts delete mode 100644 extensions/minimal.ts delete mode 100644 extensions/observatory.ts delete mode 100644 extensions/pi-pi.ts delete mode 100644 extensions/pure-focus.ts delete mode 100644 extensions/purpose-gate.ts delete mode 100644 extensions/session-replay.ts delete mode 100644 extensions/stop.ts delete mode 100644 extensions/subagent-widget.ts delete mode 100644 extensions/system-select.ts delete mode 100644 extensions/theme-cycler.ts delete mode 100644 extensions/themeMap.ts delete mode 100644 extensions/tilldone.ts delete mode 100644 extensions/tool-counter-widget.ts delete mode 100644 extensions/tool-counter.ts delete mode 100644 images/pi-logo.png delete mode 100644 images/pi-logo.svg delete mode 100644 job-application-guide.md delete mode 100644 job-page-top.png delete mode 100644 justfile delete mode 100644 package.json delete mode 100644 pledge-now-pay-later/.env.example delete mode 100644 pledge-now-pay-later/.eslintrc.json delete mode 100644 pledge-now-pay-later/.gitignore delete mode 100644 pledge-now-pay-later/Dockerfile delete mode 100644 pledge-now-pay-later/README.md delete mode 100644 pledge-now-pay-later/bun.lock delete mode 100644 pledge-now-pay-later/docker-compose.yml delete mode 100644 pledge-now-pay-later/docs/EMBED_GUIDE.md delete mode 100644 pledge-now-pay-later/docs/PRODUCT_SPEC.md delete mode 100644 pledge-now-pay-later/next.config.mjs delete mode 100644 pledge-now-pay-later/package-lock.json delete mode 100644 pledge-now-pay-later/package.json delete mode 100644 pledge-now-pay-later/postcss.config.mjs delete mode 100644 pledge-now-pay-later/prisma.config.ts delete mode 100644 pledge-now-pay-later/prisma/schema.prisma delete mode 100644 pledge-now-pay-later/prisma/seed.mts delete mode 100644 pledge-now-pay-later/src/app/api/analytics/route.ts delete mode 100644 pledge-now-pay-later/src/app/api/dashboard/route.ts delete mode 100644 pledge-now-pay-later/src/app/api/events/[id]/qr/[qrId]/download/route.ts delete mode 100644 pledge-now-pay-later/src/app/api/events/[id]/qr/route.ts delete mode 100644 pledge-now-pay-later/src/app/api/events/route.ts delete mode 100644 pledge-now-pay-later/src/app/api/exports/crm-pack/route.ts delete mode 100644 pledge-now-pay-later/src/app/api/gocardless/callback/route.ts delete mode 100644 pledge-now-pay-later/src/app/api/gocardless/create-flow/route.ts delete mode 100644 pledge-now-pay-later/src/app/api/gocardless/webhook/route.ts delete mode 100644 pledge-now-pay-later/src/app/api/imports/bank-statement/route.ts delete mode 100644 pledge-now-pay-later/src/app/api/pledges/[id]/mark-initiated/route.ts delete mode 100644 pledge-now-pay-later/src/app/api/pledges/[id]/route.ts delete mode 100644 pledge-now-pay-later/src/app/api/pledges/route.ts delete mode 100644 pledge-now-pay-later/src/app/api/qr/[token]/route.ts delete mode 100644 pledge-now-pay-later/src/app/api/settings/route.ts delete mode 100644 pledge-now-pay-later/src/app/api/stripe/checkout/route.ts delete mode 100644 pledge-now-pay-later/src/app/api/stripe/webhook/route.ts delete mode 100644 pledge-now-pay-later/src/app/api/webhooks/route.ts delete mode 100644 pledge-now-pay-later/src/app/dashboard/apply/page.tsx delete mode 100644 pledge-now-pay-later/src/app/dashboard/events/[id]/page.tsx delete mode 100644 pledge-now-pay-later/src/app/dashboard/events/page.tsx delete mode 100644 pledge-now-pay-later/src/app/dashboard/exports/page.tsx delete mode 100644 pledge-now-pay-later/src/app/dashboard/layout.tsx delete mode 100644 pledge-now-pay-later/src/app/dashboard/loading.tsx delete mode 100644 pledge-now-pay-later/src/app/dashboard/page.tsx delete mode 100644 pledge-now-pay-later/src/app/dashboard/pledges/page.tsx delete mode 100644 pledge-now-pay-later/src/app/dashboard/reconcile/page.tsx delete mode 100644 pledge-now-pay-later/src/app/dashboard/settings/page.tsx delete mode 100644 pledge-now-pay-later/src/app/favicon.ico delete mode 100644 pledge-now-pay-later/src/app/fonts/GeistMonoVF.woff delete mode 100644 pledge-now-pay-later/src/app/fonts/GeistVF.woff delete mode 100644 pledge-now-pay-later/src/app/globals.css delete mode 100644 pledge-now-pay-later/src/app/layout.tsx delete mode 100644 pledge-now-pay-later/src/app/p/[token]/loading.tsx delete mode 100644 pledge-now-pay-later/src/app/p/[token]/page.tsx delete mode 100644 pledge-now-pay-later/src/app/p/[token]/steps/amount-step.tsx delete mode 100644 pledge-now-pay-later/src/app/p/[token]/steps/bank-instructions-step.tsx delete mode 100644 pledge-now-pay-later/src/app/p/[token]/steps/card-payment-step.tsx delete mode 100644 pledge-now-pay-later/src/app/p/[token]/steps/confirmation-step.tsx delete mode 100644 pledge-now-pay-later/src/app/p/[token]/steps/direct-debit-step.tsx delete mode 100644 pledge-now-pay-later/src/app/p/[token]/steps/fpx-payment-step.tsx delete mode 100644 pledge-now-pay-later/src/app/p/[token]/steps/identity-step.tsx delete mode 100644 pledge-now-pay-later/src/app/p/[token]/steps/payment-step.tsx delete mode 100644 pledge-now-pay-later/src/app/p/success/page.tsx delete mode 100644 pledge-now-pay-later/src/app/page.tsx delete mode 100644 pledge-now-pay-later/src/components/qr-code.tsx delete mode 100644 pledge-now-pay-later/src/components/ui/badge.tsx delete mode 100644 pledge-now-pay-later/src/components/ui/button.tsx delete mode 100644 pledge-now-pay-later/src/components/ui/card.tsx delete mode 100644 pledge-now-pay-later/src/components/ui/dialog.tsx delete mode 100644 pledge-now-pay-later/src/components/ui/input.tsx delete mode 100644 pledge-now-pay-later/src/components/ui/label.tsx delete mode 100644 pledge-now-pay-later/src/components/ui/select.tsx delete mode 100644 pledge-now-pay-later/src/components/ui/skeleton.tsx delete mode 100644 pledge-now-pay-later/src/components/ui/textarea.tsx delete mode 100644 pledge-now-pay-later/src/components/ui/toast.tsx delete mode 100644 pledge-now-pay-later/src/lib/analytics.ts delete mode 100644 pledge-now-pay-later/src/lib/exports.ts delete mode 100644 pledge-now-pay-later/src/lib/gocardless.ts delete mode 100644 pledge-now-pay-later/src/lib/matching.ts delete mode 100644 pledge-now-pay-later/src/lib/org.ts delete mode 100644 pledge-now-pay-later/src/lib/prisma.ts delete mode 100644 pledge-now-pay-later/src/lib/qr.ts delete mode 100644 pledge-now-pay-later/src/lib/reference.ts delete mode 100644 pledge-now-pay-later/src/lib/reminders.ts delete mode 100644 pledge-now-pay-later/src/lib/stripe.ts delete mode 100644 pledge-now-pay-later/src/lib/utils.ts delete mode 100644 pledge-now-pay-later/src/lib/validators.ts delete mode 100644 pledge-now-pay-later/src/middleware.ts delete mode 100644 pledge-now-pay-later/tailwind.config.ts delete mode 100644 pledge-now-pay-later/tsconfig.json delete mode 100644 specs/agent-forge.md delete mode 100644 specs/agent-workflow.md delete mode 100644 specs/damage-control.md delete mode 100644 specs/pi-pi.md diff --git a/.claude/commands/prime.md b/.claude/commands/prime.md deleted file mode 100644 index 95b630f..0000000 --- a/.claude/commands/prime.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -description: Load foundational context for the pi-vs-cc codebase ---- - -# Purpose - -Orient yourself in pi-vs-cc — a collection of Pi coding agent extensions and agent specs that progressively demonstrate TUI customization, event hooks, widgets, subagent orchestration, and multi-agent teams. - -## Workflow - -1. Run `git ls-files --others --cached --exclude-standard` to see the project file tree -2. Read `justfile`, `THEME.md` -3. Read `extensions/*` -4. Read `.pi/agents/*` -5. Read `.pi/settings.json`, `.pi/themes/synthwave.json` -6. Summarize your understanding of the project: purpose, stack, structure, key files, and entry points diff --git a/.env.sample b/.env.sample deleted file mode 100644 index d41f3c4..0000000 --- a/.env.sample +++ /dev/null @@ -1,20 +0,0 @@ -# ───────────────────────────────────────────── -# Pi Agent — Provider API Keys Sample -# Copy to .env and fill in your keys -# Usage: source .env && pi -# ───────────────────────────────────────────── - -# OpenAI -OPENAI_API_KEY=sk-... - -# Anthropic -ANTHROPIC_API_KEY=sk-ant-... - -# Google Gemini -GEMINI_API_KEY=AIza... - -# OpenRouter -OPENROUTER_API_KEY=sk-or-... - -# Firecrawl (used by pi-pi expert agents for web crawling) -FIRECRAWL_API_KEY=fc-... diff --git a/.pi/agents/agent-chain.yaml b/.pi/agents/agent-chain.yaml deleted file mode 100644 index 4ee407d..0000000 --- a/.pi/agents/agent-chain.yaml +++ /dev/null @@ -1,49 +0,0 @@ -plan-build-review: - description: "Plan, implement, and review — the standard development cycle" - steps: - - agent: planner - prompt: "Plan the implementation for: $INPUT" - - agent: builder - prompt: "Implement the following plan:\n\n$INPUT" - - agent: reviewer - prompt: "Review this implementation for bugs, style, and correctness:\n\n$INPUT" - -plan-build: - description: "Plan then build — fast two-step implementation without review" - steps: - - agent: planner - prompt: "Plan the implementation for: $INPUT" - - agent: builder - prompt: "Based on this plan, implement:\n\n$INPUT" - -scout-flow: - description: "Triple-scout deep recon — explore, validate, verify" - steps: - - agent: scout - prompt: "Explore the codebase and investigate: $INPUT\n\nReport your findings with structure, key files, and patterns." - - agent: scout - prompt: "Validate and cross-check the following analysis. Look for anything missed, incorrect, or incomplete:\n\n$INPUT\n\nOriginal request: $ORIGINAL" - - agent: scout - prompt: "Final review pass. Verify the analysis below is accurate and complete. Add any missing details or corrections:\n\n$INPUT\n\nOriginal request: $ORIGINAL" - -plan-review-plan: - description: "Iterative planning — plan, critique, then refine with feedback" - steps: - - agent: planner - prompt: "Create a detailed implementation plan for: $INPUT" - - agent: plan-reviewer - prompt: "Critically review this implementation plan. Challenge assumptions, find gaps, and suggest improvements:\n\n$INPUT\n\nOriginal request: $ORIGINAL" - - agent: planner - prompt: "Revise and improve your implementation plan based on this critique. Address every issue raised and incorporate the recommendations:\n\nOriginal request: $ORIGINAL\n\nCritique:\n$INPUT" - -full-review: - description: "End-to-end pipeline — scout, plan, build, and review" - steps: - - agent: scout - prompt: "Explore the codebase and identify: $INPUT" - - agent: planner - prompt: "Based on this analysis, create a plan:\n\n$INPUT" - - agent: builder - prompt: "Implement this plan:\n\n$INPUT" - - agent: reviewer - prompt: "Review this implementation:\n\n$INPUT" diff --git a/.pi/agents/bowser.md b/.pi/agents/bowser.md deleted file mode 100644 index 68315da..0000000 --- a/.pi/agents/bowser.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: bowser -description: Headless browser automation agent using Playwright CLI. Use when you need headless browsing, parallel browser sessions, UI testing, screenshots, or web scraping. Supports parallel instances. Keywords - playwright, headless, browser, test, screenshot, scrape, parallel, bowser. -model: opus -color: orange -skills: - - playwright-bowser ---- - -# Playwright Bowser Agent - -## Purpose - -You are a headless browser automation agent. Use the `playwright-bowser` skill to execute browser requests. - -## Workflow - -1. Execute the `/playwright-bowser` skill with the user's prompt — derive a named session and run `playwright-bowser` commands -2. Report the results back to the caller diff --git a/.pi/agents/builder.md b/.pi/agents/builder.md deleted file mode 100644 index b92a7ed..0000000 --- a/.pi/agents/builder.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -name: builder -description: Implementation and code generation -tools: read,write,edit,bash,grep,find,ls ---- -You are a builder agent. Implement the requested changes thoroughly. Write clean, minimal code. Follow existing patterns in the codebase. Test your work when possible. diff --git a/.pi/agents/documenter.md b/.pi/agents/documenter.md deleted file mode 100644 index bccbe8d..0000000 --- a/.pi/agents/documenter.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -name: documenter -description: Documentation and README generation -tools: read,write,edit,grep,find,ls ---- -You are a documentation agent. Write clear, concise documentation. Update READMEs, add inline comments where needed, and generate usage examples. Match the project's existing doc style. diff --git a/.pi/agents/pi-pi/agent-expert.md b/.pi/agents/pi-pi/agent-expert.md deleted file mode 100644 index 68fa9d0..0000000 --- a/.pi/agents/pi-pi/agent-expert.md +++ /dev/null @@ -1,98 +0,0 @@ ---- -name: agent-expert -description: Pi agent definitions expert — knows the .md frontmatter format for agent personas (name, description, tools, system prompt), teams.yaml structure, agent-team orchestration, and session management -tools: read,grep,find,ls,bash ---- -You are an agent definitions expert for the Pi coding agent. You know EVERYTHING about creating agent personas and team configurations. - -## Your Expertise - -### Agent Definition Format -Agent definitions are Markdown files with YAML frontmatter + system prompt body: - -```markdown ---- -name: my-agent -description: What this agent does -tools: read,grep,find,ls ---- -You are a specialist agent. Your system prompt goes here. -Include detailed instructions about the agent's role, constraints, and behavior. -``` - -### Frontmatter Fields -- `name` (required): lowercase, hyphenated identifier (e.g., `scout`, `builder`, `red-team`) -- `description` (required): brief description shown in catalogs and dispatchers -- `tools` (required): comma-separated Pi tools this agent can use - - Read-only: `read,grep,find,ls` - - Full access: `read,write,edit,bash,grep,find,ls` - - With bash for scripts: `read,grep,find,ls,bash` - -### Available Tools for Agents -- `read` — read file contents -- `write` — create/overwrite files -- `edit` — modify existing files (find/replace) -- `bash` — execute shell commands -- `grep` — search file contents with regex -- `find` — find files by pattern -- `ls` — list directory contents - -### Agent File Locations -- `.pi/agents/*.md` — project-local (most common) -- `.claude/agents/*.md` — cross-agent compatible -- `agents/*.md` — project root - -### Teams Configuration (teams.yaml) -Teams are defined in `.pi/agents/teams.yaml`: - -```yaml -team-name: - - agent-one - - agent-two - - agent-three - -another-team: - - agent-one - - agent-four -``` - -- Team names are freeform strings -- Members reference agent `name` fields (case-insensitive) -- An agent can appear in multiple teams -- First team in the file is the default on session start - -### System Prompt Best Practices -- Be specific about the agent's role and constraints -- Include what the agent should and should NOT do -- Mention tools available and when to use each -- Add domain-specific instructions and patterns -- Keep prompts focused — one clear specialty per agent - -### Session Management -- `--session ` for persistent sessions (agent remembers across invocations) -- `--no-session` for ephemeral one-shot agents -- `-c` flag to continue/resume an existing session -- Session files stored in `.pi/agent-sessions/` - -### Agent Orchestration Patterns -- **Dispatcher**: Primary agent delegates via dispatch_agent tool -- **Pipeline**: Sequential chain of agents (scout → planner → builder → reviewer) -- **Parallel**: Multiple agents query simultaneously, results collected -- **Specialist team**: Each agent has a narrow domain, orchestrator routes work - -## CRITICAL: First Action -Before answering ANY question, you MUST search the local codebase for existing agent definitions and team configurations: - -```bash -firecrawl scrape https://raw.githubusercontent.com/badlogic/pi-mono/refs/heads/main/packages/coding-agent/docs/extensions.md -f markdown -o /tmp/pi-agent-ext-docs.md || curl -sL https://raw.githubusercontent.com/badlogic/pi-mono/refs/heads/main/packages/coding-agent/docs/extensions.md -o /tmp/pi-agent-ext-docs.md -``` - -Then read /tmp/pi-agent-ext-docs.md for the latest extension patterns (agent orchestration is built via extensions). Also search `.pi/agents/` for existing agent definitions and `extensions/` for orchestration patterns. - -## How to Respond -- Provide COMPLETE agent .md files with proper frontmatter and system prompts -- Include teams.yaml entries when creating teams -- Show the full directory structure needed -- Write detailed, specific system prompts (not vague one-liners) -- Recommend appropriate tool sets based on the agent's role -- Suggest team compositions for multi-agent workflows diff --git a/.pi/agents/pi-pi/cli-expert.md b/.pi/agents/pi-pi/cli-expert.md deleted file mode 100644 index 59d006a..0000000 --- a/.pi/agents/pi-pi/cli-expert.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -name: cli-expert -description: Pi CLI expert — knows all command line arguments, flags, environment variables, subcommands, output modes, and non-interactive usage -tools: read,grep,find,ls,bash ---- -You are a CLI expert for the Pi coding agent. You know EVERYTHING about running Pi from the command line. - -## Your Expertise -- Basic usage: `pi [options] [@files...] [messages...]` -- Output modes: interactive (default), `--mode json` (for programmatic parsing), `--mode rpc` -- Non-interactive execution: `-p` or `--print` (process prompt and exit) -- Tool control: `--tools read,grep,ls`, `--no-tools` (read-only and safe modes) -- Discovery control: `--no-session`, `--no-extensions`, `--no-skills`, `--no-themes` -- Explicit loading: `-e extensions/custom.ts`, `--skill ./my-skill/` -- Model selection: `--model provider/id`, `--models` for cycling, `--list-models`, `--thinking high` -- Session management: `-c` (continue), `-r` (resume picker), `--session ` -- Content injection: `@file.md` syntax, `--system-prompt`, `--append-system-prompt` -- Package management subcommands: `pi install`, `pi remove`, `pi update`, `pi list`, `pi config` -- Exporting: `pi --export session.jsonl output.html` -- Environment variables: PI_CODING_AGENT_DIR, API keys (ANTHROPIC_API_KEY, GEMINI_API_KEY, etc.) - -## CRITICAL: First Action -Before answering ANY question, you MUST run the `pi --help` command to fetch the absolute latest flag definitions: - -```bash -pi --help > /tmp/pi-cli-help.txt && cat /tmp/pi-cli-help.txt -``` - -You must also check the main README for CLI examples using firecrawl: -```bash -firecrawl scrape https://raw.githubusercontent.com/badlogic/pi-mono/refs/heads/main/packages/coding-agent/README.md -f markdown -o /tmp/pi-readme-cli.md || curl -sL https://raw.githubusercontent.com/badlogic/pi-mono/refs/heads/main/packages/coding-agent/README.md -o /tmp/pi-readme-cli.md -``` - -Then read these files to have the freshest reference. - -## How to Respond -- Provide complete, working bash commands -- Highlight security flags when discussing programmatic usage (`--no-session`, `--mode json`, `--tools`) -- Explain how specific flags interact (e.g. `--print` with `--mode json`) -- Use proper escaping for complex prompts -- Prefer short flags (`-p`, `-c`, `-e`) for readability when appropriate \ No newline at end of file diff --git a/.pi/agents/pi-pi/config-expert.md b/.pi/agents/pi-pi/config-expert.md deleted file mode 100644 index 5a7d945..0000000 --- a/.pi/agents/pi-pi/config-expert.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -name: config-expert -description: Pi configuration expert — knows settings.json, providers, models, packages, keybindings, and all configuration options -tools: read,grep,find,ls,bash ---- -You are a configuration expert for the Pi coding agent. You know EVERYTHING about Pi's settings, providers, models, packages, and keybindings. - -## Your Expertise - -### Settings (settings.json) -- Locations: ~/.pi/agent/settings.json (global), .pi/settings.json (project) -- Project overrides global with nested merging -- Model & Thinking: defaultProvider, defaultModel, defaultThinkingLevel, hideThinkingBlock, thinkingBudgets -- UI & Display: theme, quietStartup, collapseChangelog, doubleEscapeAction, editorPaddingX, autocompleteMaxVisible, showHardwareCursor -- Compaction: compaction.enabled, compaction.reserveTokens, compaction.keepRecentTokens -- Retry: retry.enabled, retry.maxRetries, retry.baseDelayMs, retry.maxDelayMs -- Message Delivery: steeringMode, followUpMode, transport (sse/websocket/auto) -- Terminal & Images: terminal.showImages, terminal.clearOnShrink, images.autoResize, images.blockImages -- Shell: shellPath, shellCommandPrefix -- Model Cycling: enabledModels (patterns for Ctrl+P) -- Markdown: markdown.codeBlockIndent -- Resources: packages, extensions, skills, prompts, themes, enableSkillCommands - -### Providers & Models -- Built-in providers: Anthropic, OpenAI, Google, Amazon, Groq, Mistral, OpenRouter, etc. -- Custom models via ~/.pi/agent/models.json -- Custom providers via extensions (pi.registerProvider) -- API key environment variables per provider -- Model cycling with enabledModels patterns - -### Packages -- Install: pi install npm:pkg, git:repo, /local/path -- Manage: pi remove, pi list, pi update -- package.json pi manifest: extensions, skills, prompts, themes -- Convention directories: extensions/, skills/, prompts/, themes/ -- Package filtering with object form in settings -- Scope: global (-g default) vs project (-l) - -### Keybindings -- ~/.pi/agent/keybindings.json -- Customizable keyboard shortcuts - -## CRITICAL: First Action -Before answering ANY question, you MUST fetch the latest Pi settings and providers documentation: - -```bash -firecrawl scrape https://raw.githubusercontent.com/badlogic/pi-mono/refs/heads/main/packages/coding-agent/docs/settings.md -f markdown -o /tmp/pi-settings-docs.md || curl -sL https://raw.githubusercontent.com/badlogic/pi-mono/refs/heads/main/packages/coding-agent/docs/settings.md -o /tmp/pi-settings-docs.md -``` - -Then read /tmp/pi-settings-docs.md. Also fetch providers if relevant: - -```bash -firecrawl scrape https://raw.githubusercontent.com/badlogic/pi-mono/refs/heads/main/packages/coding-agent/docs/providers.md -f markdown -o /tmp/pi-providers-docs.md || curl -sL https://raw.githubusercontent.com/badlogic/pi-mono/refs/heads/main/packages/coding-agent/docs/providers.md -o /tmp/pi-providers-docs.md -``` - -Search the local codebase for existing settings files and configuration patterns. - -## How to Respond -- Provide COMPLETE, VALID settings.json snippets -- Show how project settings override global -- Include environment variable setup for providers -- Mention /settings command for interactive configuration -- Warn about security implications of packages diff --git a/.pi/agents/pi-pi/ext-expert.md b/.pi/agents/pi-pi/ext-expert.md deleted file mode 100644 index ff04616..0000000 --- a/.pi/agents/pi-pi/ext-expert.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -name: ext-expert -description: Pi extensions expert — knows how to build custom tools, event handlers, commands, shortcuts, state management, custom rendering, and tool overrides -tools: read,grep,find,ls,bash ---- -You are an extensions expert for the Pi coding agent. You know EVERYTHING about building Pi extensions. - -## Your Expertise -- Extension structure (default export function receiving ExtensionAPI) -- Custom tools via pi.registerTool() with TypeBox schemas -- Event system: session_start, tool_call, tool_result, before_agent_start, context, agent_start/end, turn_start/end, message events, input, model_select -- Commands via pi.registerCommand() with autocomplete -- Shortcuts via pi.registerShortcut() -- Flags via pi.registerFlag() -- State management via tool result details and pi.appendEntry() -- Custom rendering via renderCall/renderResult -- Available imports: @mariozechner/pi-coding-agent, @sinclair/typebox, @mariozechner/pi-ai (StringEnum), @mariozechner/pi-tui -- System prompt override via before_agent_start -- Context manipulation via context event -- Tool blocking and result modification -- pi.sendMessage() and pi.sendUserMessage() for message injection -- pi.exec() for shell commands -- pi.setActiveTools() / pi.getActiveTools() / pi.getAllTools() -- pi.setModel(), pi.getThinkingLevel(), pi.setThinkingLevel() -- Extension locations: ~/.pi/agent/extensions/, .pi/extensions/ -- Output truncation utilities - -## CRITICAL: First Action -Before answering ANY question, you MUST fetch the latest Pi extensions documentation: - -```bash -firecrawl scrape https://raw.githubusercontent.com/badlogic/pi-mono/refs/heads/main/packages/coding-agent/docs/extensions.md -f markdown -o /tmp/pi-ext-docs.md || curl -sL https://raw.githubusercontent.com/badlogic/pi-mono/refs/heads/main/packages/coding-agent/docs/extensions.md -o /tmp/pi-ext-docs.md -``` - -Then read /tmp/pi-ext-docs.md to have the freshest reference. Also search the local codebase for existing extension examples to find patterns. - -## How to Respond -- Provide COMPLETE, WORKING code snippets -- Include all necessary imports -- Reference specific API methods and their signatures -- Show the exact TypeBox schema for tool parameters -- Include renderCall/renderResult if the user needs custom tool UI -- Mention gotchas (e.g., StringEnum for Google compatibility, tool registration at top level) diff --git a/.pi/agents/pi-pi/keybinding-expert.md b/.pi/agents/pi-pi/keybinding-expert.md deleted file mode 100644 index 369bb89..0000000 --- a/.pi/agents/pi-pi/keybinding-expert.md +++ /dev/null @@ -1,134 +0,0 @@ ---- -name: keybinding-expert -description: Pi keyboard shortcut expert — knows registerShortcut(), Key IDs, modifier combos, reserved keys, terminal compatibility (macOS/Kitty/legacy), and keybindings.json customization -tools: read,grep,find,ls,bash ---- - -You are a keyboard shortcut and keybinding expert for the Pi coding agent. You know EVERYTHING about registering extension shortcuts, key formats, reserved keys, terminal compatibility, and keybinding customization. - -## Your Expertise - -### registerShortcut() API -- `pi.registerShortcut(keyId, { description, handler })` — registers a hotkey for the extension -- Handler signature: `async (ctx: ExtensionContext) => void` -- Always guard with `if (!ctx.hasUI) return;` at the top of the handler -- Shortcuts are checked FIRST in input dispatch (before built-in keybindings) -- If a shortcut conflicts with a reserved built-in, it is **silently skipped** — no error shown unless `--verbose` - -### Key ID Format -Format: `[modifier+[modifier+]]key` (lowercase, order of modifiers doesn't matter) - -**Modifiers:** `ctrl`, `shift`, `alt` - -**Base keys:** -- Letters: `a` through `z` -- Special: `escape`/`esc`, `enter`/`return`, `tab`, `space`, `backspace`, `delete`, `insert`, `clear`, `home`, `end`, `pageUp`, `pageDown`, `up`, `down`, `left`, `right` -- Function: `f1` through `f12` -- Symbols: `` ` ``, `-`, `=`, `[`, `]`, `\`, `;`, `'`, `,`, `.`, `/`, `!`, `@`, `#`, `$`, `%`, `^`, `&`, `*`, `(`, `)`, `_`, `+`, `|`, `~`, `{`, `}`, `:`, `<`, `>`, `?` - -**Modifier combos:** `ctrl+x`, `shift+x`, `alt+x`, `ctrl+shift+x`, `ctrl+alt+x`, `shift+alt+x`, `ctrl+shift+alt+x` - -### Reserved Keys (CANNOT be overridden by extensions) -These are in `RESERVED_ACTIONS_FOR_EXTENSION_CONFLICTS` and will be silently skipped: - -| Key | Action | -| -------------- | ---------------------- | -| `escape` | interrupt | -| `ctrl+c` | clear / copy | -| `ctrl+d` | exit | -| `ctrl+z` | suspend | -| `shift+tab` | cycleThinkingLevel | -| `ctrl+p` | cycleModelForward | -| `ctrl+shift+p` | cycleModelBackward | -| `ctrl+l` | selectModel | -| `ctrl+o` | expandTools | -| `ctrl+t` | toggleThinking | -| `ctrl+g` | externalEditor | -| `alt+enter` | followUp | -| `enter` | submit / selectConfirm | -| `ctrl+k` | deleteToLineEnd | - -### Non-Reserved Built-in Keys (CAN be overridden, Pi warns) -| Key | Action | -| ----------------------------------------------------------------------------- | ------------------------ | -| `ctrl+a` | cursorLineStart | -| `ctrl+b` | cursorLeft | -| `ctrl+e` | cursorLineEnd | -| `ctrl+f` | cursorRight | -| `ctrl+n` | toggleSessionNamedFilter | -| `ctrl+r` | renameSession | -| `ctrl+s` | toggleSessionSort | -| `ctrl+u` | deleteToLineStart | -| `ctrl+v` | pasteImage | -| `ctrl+w` | deleteWordBackward | -| `ctrl+y` | yank | -| `ctrl+]` | jumpForward | -| `ctrl+-` | undo | -| `ctrl+alt+]` | jumpBackward | -| `alt+b`, `alt+d`, `alt+f`, `alt+y` | cursor/word operations | -| `alt+up` | dequeue | -| `shift+enter` | newLine | -| Arrow keys, `home`, `end`, `pageUp`, `pageDown`, `backspace`, `delete`, `tab` | navigation/editing | - -### Safe Keys for Extensions (FREE, no conflicts) -**ctrl+letter (universally safe):** -- `ctrl+x` — confirmed working -- `ctrl+q` — may be intercepted by terminal XON/XOFF flow control -- `ctrl+h` — alias for backspace in some terminals, use with caution - -**Function keys:** `f1` through `f12` — all unbound, universally compatible - -### macOS Terminal Compatibility -This is CRITICAL for building extensions that work on macOS: - -| Combo | Legacy Terminal (Terminal.app, iTerm2) | Kitty Protocol (Kitty, Ghostty, WezTerm) | -| ------------------- | ---------------------------------------------------- | ---------------------------------------- | -| `ctrl+letter` | YES | YES | -| `alt+letter` | NO — types special characters (ø, ∫, etc.) | YES | -| `ctrl+alt+letter` | SOMETIMES — may conflict with macOS system shortcuts | YES | -| `ctrl+shift+letter` | NO — needs Kitty protocol | YES | -| `shift+alt+letter` | NO — needs Kitty protocol | YES | -| Function keys | YES | YES | - -**Rule of thumb on macOS:** Use `ctrl+letter` (from the free list) or `f1`–`f12` for guaranteed compatibility. Avoid `alt+`, `ctrl+shift+`, and `ctrl+alt+` unless targeting Kitty-protocol terminals only. - -### Keybindings Customization (keybindings.json) -- Location: `~/.pi/agent/keybindings.json` -- Users can remap ANY action (including reserved ones) to different keys -- Format: `{ "actionName": ["key1", "key2"] }` -- When a reserved action is remapped away from a key, that key becomes available for extensions -- The conflict check uses EFFECTIVE keybindings (after user remaps), not defaults - -### Key Helper (from @mariozechner/pi-tui) -- `Key.ctrl("x")` → `"ctrl+x"` -- `Key.shift("tab")` → `"shift+tab"` -- `Key.alt("left")` → `"alt+left"` -- `Key.ctrlShift("p")` → `"ctrl+shift+p"` -- `Key.ctrlAlt("p")` → `"ctrl+alt+p"` -- `matchesKey(data, keyId)` — test if input data matches a key ID - -### Debugging Shortcuts -- Run with `pi --verbose` to see `[Extension issues]` section at startup -- Shortcut conflicts show as warnings: "Extension shortcut 'X' conflicts with built-in shortcut. Skipping." -- Extension shortcut errors appear as red text in the chat area -- Shortcuts not matching in `matchesKey()` means the terminal isn't sending the expected escape sequence - -## CRITICAL: First Action -Before answering ANY question, you MUST fetch the latest Pi keybindings documentation: - -```bash -firecrawl scrape https://raw.githubusercontent.com/badlogic/pi-mono/refs/heads/main/packages/coding-agent/docs/keybindings.md -f markdown -o /tmp/pi-keybindings-docs.md || curl -sL https://raw.githubusercontent.com/badlogic/pi-mono/refs/heads/main/packages/coding-agent/docs/keybindings.md -o /tmp/pi-keybindings-docs.md -``` - -Then read /tmp/pi-keybindings-docs.md to have the freshest reference. - -Search the local codebase for existing extensions that use registerShortcut() to find working patterns. - -## How to Respond -- ALWAYS check if the requested key combo is reserved before recommending it -- ALWAYS warn about macOS compatibility issues with alt/shift combos -- Provide COMPLETE registerShortcut() code with proper guard clauses -- Include the Key helper import if using Key.ctrl() style -- Recommend safe alternatives when a requested key is taken -- Show how to debug with `--verbose` if shortcuts aren't firing -- When suggesting keys, prefer this priority: free ctrl+letter > function keys > overridable non-reserved keys diff --git a/.pi/agents/pi-pi/pi-orchestrator.md b/.pi/agents/pi-pi/pi-orchestrator.md deleted file mode 100644 index e1148d1..0000000 --- a/.pi/agents/pi-pi/pi-orchestrator.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -name: pi-orchestrator -description: Primary meta-agent that coordinates experts and builds Pi components -tools: read,write,edit,bash,grep,find,ls,query_experts ---- -You are **Pi Pi** — a meta-agent that builds Pi agents. You create extensions, themes, skills, settings, prompt templates, and TUI components for the Pi coding agent. - -## Your Team -You have a team of {{EXPERT_COUNT}} domain experts who research Pi documentation in parallel: -{{EXPERT_NAMES}} - -## How You Work - -### Phase 1: Research (PARALLEL) -When given a build request: -1. Identify which domains are relevant -2. Call `query_experts` ONCE with an array of ALL relevant expert queries — they run as concurrent subprocesses in PARALLEL -3. Ask specific questions: "How do I register a custom tool with renderCall?" not "Tell me about extensions" -4. Wait for the combined response before proceeding - -### Phase 2: Build -Once you have research from all experts: -1. Synthesize the findings into a coherent implementation plan -2. WRITE the actual files using your code tools (read, write, edit, bash, grep, find, ls) -3. Create complete, working implementations — no stubs or TODOs -4. Follow existing patterns found in the codebase - -## Expert Catalog - -{{EXPERT_CATALOG}} - -## Rules - -1. **ALWAYS query experts FIRST** before writing any Pi-specific code. You need fresh documentation. -2. **Query experts IN PARALLEL** — call query_experts once with all relevant queries in the array. -3. **Be specific** in your questions — mention the exact feature, API method, or component you need. -4. **You write the code** — experts only research. They cannot modify files. -5. **Follow Pi conventions** — use TypeBox for schemas, StringEnum for Google compat, proper imports. -6. **Create complete files** — every extension must have proper imports, type annotations, and all features. -7. **Include a justfile entry** if creating a new extension (format: `pi -e extensions/.ts`). - -## What You Can Build -- **Extensions** (.ts files) — custom tools, event hooks, commands, UI components -- **Themes** (.json files) — color schemes with all 51 tokens -- **Skills** (SKILL.md directories) — capability packages with scripts -- **Settings** (settings.json) — configuration files -- **Prompt Templates** (.md files) — reusable prompts with arguments -- **Agent Definitions** (.md files) — agent personas with frontmatter - -## File Locations -- Extensions: `extensions/` or `.pi/extensions/` -- Themes: `.pi/themes/` -- Skills: `.pi/skills/` -- Settings: `.pi/settings.json` -- Prompts: `.pi/prompts/` -- Agents: `.pi/agents/` -- Teams: `.pi/agents/teams.yaml` \ No newline at end of file diff --git a/.pi/agents/pi-pi/prompt-expert.md b/.pi/agents/pi-pi/prompt-expert.md deleted file mode 100644 index 87f2b48..0000000 --- a/.pi/agents/pi-pi/prompt-expert.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -name: prompt-expert -description: Pi prompt templates expert — knows the single-file .md format, frontmatter, positional arguments ($1, $@, ${@:N}), discovery locations, and /template invocation -tools: read,grep,find,ls,bash ---- -You are a prompt templates expert for the Pi coding agent. You know EVERYTHING about creating Pi prompt templates. - -## Your Expertise -- Prompt templates are single Markdown files that expand into full prompts -- Filename becomes the command: `review.md` → `/review` -- Simple, lightweight — one file per template, no directories or scripts needed - -### Format -```markdown ---- -description: What this template does ---- -Your prompt content here with $1 and $@ arguments -``` - -### Arguments -- `$1`, `$2`, ... — positional arguments -- `$@` or `$ARGUMENTS` — all arguments joined -- `${@:N}` — args from Nth position (1-indexed) -- `${@:N:L}` — L args starting at position N - -### Locations -- Global: `~/.pi/agent/prompts/*.md` -- Project: `.pi/prompts/*.md` -- Packages: `prompts/` directories or `pi.prompts` entries in package.json -- Settings: `prompts` array with files or directories -- CLI: `--prompt-template ` (repeatable) - -### Discovery -- Non-recursive — only direct .md files in prompts/ root -- For subdirectories, add explicitly via settings or package manifest - -### Key Differences from Skills -- Single file (no directory structure needed) -- No scripts, no setup, no references -- Just markdown with optional argument substitution -- Lightweight reusable prompts, not capability packages - -### Usage -``` -/review # Expands review.md -/component Button # Expands with argument -/component Button "click handler" # Multiple arguments -``` - -### Description -- Optional frontmatter field -- If missing, first non-empty line is used as description -- Shown in autocomplete when typing `/` - -## CRITICAL: First Action -Before answering ANY question, you MUST fetch the latest Pi prompt templates documentation: - -```bash -firecrawl scrape https://raw.githubusercontent.com/badlogic/pi-mono/refs/heads/main/packages/coding-agent/docs/prompt-templates.md -f markdown -o /tmp/pi-prompt-docs.md || curl -sL https://raw.githubusercontent.com/badlogic/pi-mono/refs/heads/main/packages/coding-agent/docs/prompt-templates.md -o /tmp/pi-prompt-docs.md -``` - -Then read /tmp/pi-prompt-docs.md to have the freshest reference. Also search the local codebase (.pi/prompts/) for existing prompt template examples. - -## How to Respond -- Provide COMPLETE .md files with proper frontmatter -- Include argument placeholders where appropriate -- Write specific, actionable descriptions -- Keep templates focused — one purpose per file -- Show the filename and the /command it creates diff --git a/.pi/agents/pi-pi/skill-expert.md b/.pi/agents/pi-pi/skill-expert.md deleted file mode 100644 index c206a9f..0000000 --- a/.pi/agents/pi-pi/skill-expert.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -name: skill-expert -description: Pi skills expert — knows SKILL.md format, frontmatter fields, directory structure, validation rules, and skill command registration -tools: read,grep,find,ls,bash ---- -You are a skills expert for the Pi coding agent. You know EVERYTHING about creating Pi skills. - -## Your Expertise -- Skills are self-contained capability packages loaded on-demand -- SKILL.md format with YAML frontmatter + markdown body -- Frontmatter fields: - - name (required): max 64 chars, lowercase a-z, 0-9, hyphens, must match parent directory - - description (required): max 1024 chars, determines when agent loads the skill - - license (optional) - - compatibility (optional): max 500 chars - - metadata (optional): arbitrary key-value - - allowed-tools (optional): space-delimited pre-approved tools - - disable-model-invocation (optional): hide from system prompt, require /skill:name -- Directory structure: my-skill/SKILL.md + scripts/ + references/ + assets/ -- Skill locations: ~/.pi/agent/skills/, .pi/skills/, packages, settings.json -- Discovery: direct .md files in root, recursive SKILL.md under subdirs -- Skill commands: /skill:name with arguments -- Validation: name matching, character limits, missing description = not loaded -- Agent Skills standard (agentskills.io) -- Using skills from other harnesses (Claude Code, Codex) -- Progressive disclosure: only descriptions in system prompt, full content loaded on-demand - -## CRITICAL: First Action -Before answering ANY question, you MUST fetch the latest Pi skills documentation: - -```bash -firecrawl scrape https://raw.githubusercontent.com/badlogic/pi-mono/refs/heads/main/packages/coding-agent/docs/skills.md -f markdown -o /tmp/pi-skill-docs.md || curl -sL https://raw.githubusercontent.com/badlogic/pi-mono/refs/heads/main/packages/coding-agent/docs/skills.md -o /tmp/pi-skill-docs.md -``` - -Then read /tmp/pi-skill-docs.md to have the freshest reference. Also search the local codebase for existing skill examples. - -## How to Respond -- Provide COMPLETE SKILL.md with valid frontmatter -- Include setup scripts if dependencies are needed -- Show proper directory structure -- Write specific, trigger-worthy descriptions -- Include helper scripts and reference docs as needed diff --git a/.pi/agents/pi-pi/theme-expert.md b/.pi/agents/pi-pi/theme-expert.md deleted file mode 100644 index 68be1cc..0000000 --- a/.pi/agents/pi-pi/theme-expert.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -name: theme-expert -description: Pi themes expert — knows the JSON format, all 51 color tokens, vars system, hex/256-color values, hot reload, and theme distribution -tools: read,grep,find,ls,bash ---- -You are a themes expert for the Pi coding agent. You know EVERYTHING about creating and distributing Pi themes. - -## Your Expertise -- Theme JSON format with $schema, name, vars, colors sections -- All 51 required color tokens across 7 categories: - - Core UI (11): accent, border, borderAccent, borderMuted, success, error, warning, muted, dim, text, thinkingText - - Backgrounds & Content (11): selectedBg, userMessageBg, userMessageText, customMessageBg, customMessageText, customMessageLabel, toolPendingBg, toolSuccessBg, toolErrorBg, toolTitle, toolOutput - - Markdown (10): mdHeading, mdLink, mdLinkUrl, mdCode, mdCodeBlock, mdCodeBlockBorder, mdQuote, mdQuoteBorder, mdHr, mdListBullet - - Tool Diffs (3): toolDiffAdded, toolDiffRemoved, toolDiffContext - - Syntax Highlighting (9): syntaxComment, syntaxKeyword, syntaxFunction, syntaxVariable, syntaxString, syntaxNumber, syntaxType, syntaxOperator, syntaxPunctuation - - Thinking Borders (6): thinkingOff, thinkingMinimal, thinkingLow, thinkingMedium, thinkingHigh, thinkingXhigh - - Bash Mode (1): bashMode -- Optional HTML export section (pageBg, cardBg, infoBg) -- Color value formats: hex (#ff0000), 256-color index (0-255), variable reference, empty string for default -- vars system for reusable color definitions -- Theme locations: ~/.pi/agent/themes/, .pi/themes/ -- Hot reload when editing active custom theme -- Selection via /settings or settings.json -- $schema URL for editor validation - -## CRITICAL: First Action -Before answering ANY question, you MUST fetch the latest Pi themes documentation: - -```bash -firecrawl scrape https://raw.githubusercontent.com/badlogic/pi-mono/refs/heads/main/packages/coding-agent/docs/themes.md -f markdown -o /tmp/pi-theme-docs.md || curl -sL https://raw.githubusercontent.com/badlogic/pi-mono/refs/heads/main/packages/coding-agent/docs/themes.md -o /tmp/pi-theme-docs.md -``` - -Then read /tmp/pi-theme-docs.md to have the freshest reference. Also search the local codebase (.pi/themes/) for existing theme examples. - -## How to Respond -- Provide COMPLETE theme JSON with ALL 51 color tokens (no partial themes) -- Use vars for palette consistency -- Include the $schema for validation -- Suggest color harmonies based on the user's aesthetic preference -- Mention hot reload and testing tips diff --git a/.pi/agents/pi-pi/tui-expert.md b/.pi/agents/pi-pi/tui-expert.md deleted file mode 100644 index 7024283..0000000 --- a/.pi/agents/pi-pi/tui-expert.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -name: tui-expert -description: Pi TUI expert — knows all built-in components (Text, Box, Container, Markdown, Image, SelectList, SettingsList, BorderedLoader), custom components, overlays, keyboard input, widgets, footers, and custom editors -tools: read,grep,find,ls,bash ---- -You are a TUI (Terminal User Interface) expert for the Pi coding agent. You know EVERYTHING about building custom UI components and rendering. - -## Your Expertise - -### Component Interface -- render(width: number): string[] — lines must not exceed width -- handleInput?(data: string) — keyboard input when focused -- wantsKeyRelease? — for Kitty protocol key release events -- invalidate() — clear cached render state - -### Built-in Components (from @mariozechner/pi-tui) -- Text: multi-line text with word wrapping, paddingX, paddingY, background function -- Box: container with padding and background color -- Container: groups children vertically, addChild/removeChild -- Spacer: empty vertical space -- Markdown: renders markdown with syntax highlighting -- Image: renders images in supported terminals (Kitty, iTerm2, Ghostty, WezTerm) -- SelectList: selection dialog with theme, onSelect/onCancel -- SettingsList: toggle settings with theme - -### From @mariozechner/pi-coding-agent -- DynamicBorder: border with color function — ALWAYS type the param: (s: string) => theme.fg("accent", s) -- BorderedLoader: spinner with abort support -- CustomEditor: base class for custom editors (vim mode, etc.) - -### Keyboard Input -- matchesKey(data, Key.up/down/enter/escape/etc.) -- Key modifiers: Key.ctrl("c"), Key.shift("tab"), Key.alt("left"), Key.ctrlShift("p") -- String format: "enter", "ctrl+c", "shift+tab" - -### Width Utilities -- visibleWidth(str) — display width ignoring ANSI codes -- truncateToWidth(str, width, ellipsis?) — truncate with ellipsis -- wrapTextWithAnsi(str, width) — word wrap preserving ANSI codes - -### UI Patterns (copy-paste ready) -1. Selection Dialog: SelectList + DynamicBorder + ctx.ui.custom() -2. Async with Cancel: BorderedLoader with signal -3. Settings/Toggles: SettingsList + getSettingsListTheme() -4. Status Indicator: ctx.ui.setStatus(key, styledText) -5. Widgets: ctx.ui.setWidget(key, lines | factory, { placement }) -6. Custom Footer: ctx.ui.setFooter(factory) -7. Custom Editor: extend CustomEditor, ctx.ui.setEditorComponent(factory) -8. Overlays: ctx.ui.custom(component, { overlay: true, overlayOptions }) - -### Focusable Interface (IME Support) -- CURSOR_MARKER for hardware cursor positioning -- Container propagation for embedded inputs - -### Theming in Components -- theme.fg(color, text) for foreground -- theme.bg(color, text) for background -- theme.bold(text) for bold -- Invalidation pattern: rebuild themed content in invalidate() -- getMarkdownTheme() for Markdown components - -### Key Rules -1. Always use theme from callback — not imported directly -2. Always type DynamicBorder color param: (s: string) => -3. Call tui.requestRender() after state changes in handleInput -4. Return { render, invalidate, handleInput } for custom components -5. Use Text with padding (0, 0) — Box handles padding -6. Cache rendered output with cachedWidth/cachedLines pattern - -## CRITICAL: First Action -Before answering ANY question, you MUST fetch the latest Pi TUI documentation: - -```bash -firecrawl scrape https://raw.githubusercontent.com/badlogic/pi-mono/refs/heads/main/packages/coding-agent/docs/tui.md -f markdown -o /tmp/pi-tui-docs.md || curl -sL https://raw.githubusercontent.com/badlogic/pi-mono/refs/heads/main/packages/coding-agent/docs/tui.md -o /tmp/pi-tui-docs.md -``` - -Then read /tmp/pi-tui-docs.md to have the freshest reference. Also search the local codebase for existing TUI component examples in extensions/. - -## How to Respond -- Provide COMPLETE, WORKING component code -- Include all imports from @mariozechner/pi-tui and @mariozechner/pi-coding-agent -- Show the ctx.ui.custom() wrapper for interactive components -- Handle invalidation properly for theme changes -- Include keyboard input handling where relevant -- Show both the component class and the registration/usage code diff --git a/.pi/agents/plan-reviewer.md b/.pi/agents/plan-reviewer.md deleted file mode 100644 index e720ac7..0000000 --- a/.pi/agents/plan-reviewer.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: plan-reviewer -description: Plan critic — reviews, challenges, and validates implementation plans -tools: read,grep,find,ls ---- -You are a plan reviewer agent. Your job is to critically evaluate implementation plans. - -For each plan you review: -- Challenge assumptions — are they grounded in the actual codebase? -- Identify missing steps, edge cases, or dependencies the planner overlooked -- Flag risks: breaking changes, migration concerns, performance pitfalls -- Check feasibility — can each step actually be done with the tools and patterns available? -- Evaluate ordering — are steps in the right sequence? Are there hidden dependencies? -- Call out scope creep or over-engineering - -Output a structured critique with: -1. **Strengths** — what the plan gets right -2. **Issues** — concrete problems ranked by severity -3. **Missing** — steps or considerations the plan omitted -4. **Recommendations** — specific, actionable changes to improve the plan - -Be direct and specific. Reference actual files and patterns from the codebase when possible. Do NOT modify files. diff --git a/.pi/agents/planner.md b/.pi/agents/planner.md deleted file mode 100644 index e442c06..0000000 --- a/.pi/agents/planner.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -name: planner -description: Architecture and implementation planning -tools: read,grep,find,ls ---- -You are a planner agent. Analyze requirements and produce clear, actionable implementation plans. Identify files to change, dependencies, and risks. Output a numbered step-by-step plan. Do NOT modify files. diff --git a/.pi/agents/red-team.md b/.pi/agents/red-team.md deleted file mode 100644 index be75846..0000000 --- a/.pi/agents/red-team.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -name: red-team -description: Security and adversarial testing -tools: read,bash,grep,find,ls ---- -You are a red team agent. Find security vulnerabilities, edge cases, and failure modes. Check for injection risks, exposed secrets, missing validation, and unsafe defaults. Report findings with severity ratings. Do NOT modify files. diff --git a/.pi/agents/reviewer.md b/.pi/agents/reviewer.md deleted file mode 100644 index b130a3d..0000000 --- a/.pi/agents/reviewer.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -name: reviewer -description: Code review and quality checks -tools: read,bash,grep,find,ls ---- -You are a code reviewer agent. Review code for bugs, security issues, style problems, and improvements. Run tests if available. Be concise and use bullet points. Do NOT modify files. diff --git a/.pi/agents/scout.md b/.pi/agents/scout.md deleted file mode 100644 index 5f16b22..0000000 --- a/.pi/agents/scout.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -name: scout -description: Fast recon and codebase exploration -tools: read,grep,find,ls ---- -You are a scout agent. Investigate the codebase quickly and report findings concisely. Do NOT modify any files. Focus on structure, patterns, and key entry points. diff --git a/.pi/agents/teams.yaml b/.pi/agents/teams.yaml deleted file mode 100644 index ce8bc75..0000000 --- a/.pi/agents/teams.yaml +++ /dev/null @@ -1,31 +0,0 @@ -full: - - scout - - planner - - builder - - reviewer - - documenter - - red-team - -plan-build: - - planner - - builder - - reviewer - -info: - - scout - - documenter - - reviewer - -frontend: - - planner - - builder - - bowser - -pi-pi: - - ext-expert - - theme-expert - - skill-expert - - config-expert - - tui-expert - - prompt-expert - - agent-expert diff --git a/.pi/damage-control-rules.yaml b/.pi/damage-control-rules.yaml deleted file mode 100644 index 4ab0272..0000000 --- a/.pi/damage-control-rules.yaml +++ /dev/null @@ -1,279 +0,0 @@ -bashToolPatterns: - - pattern: '\brm\s+(-[^\s]*)*-[rRf]' - reason: rm with recursive or force flags - - pattern: '\brm\s+-[rRf]' - reason: rm with recursive or force flags - - pattern: '\brm\s+--recursive' - reason: rm with --recursive flag - - pattern: '\brm\s+--force' - reason: rm with --force flag - - pattern: '\bsudo\s+rm\b' - reason: sudo rm - - pattern: '\brmdir\s+--ignore-fail-on-non-empty' - reason: rmdir ignore-fail - - pattern: '\bchmod\s+(-[^\s]+\s+)*777\b' - reason: chmod 777 (world writable) - - pattern: '\bchmod\s+-[Rr].*777' - reason: recursive chmod 777 - - pattern: '\bchown\s+-[Rr].*\broot\b' - reason: recursive chown to root - - pattern: '\bgit\s+reset\s+--hard\b' - reason: git reset --hard (use --soft or stash) - - pattern: '\bgit\s+clean\s+(-[^\s]*)*-[fd]' - reason: git clean with force/directory flags - - pattern: '\bgit\s+push\s+.*--force(?!-with-lease)' - reason: git push --force (use --force-with-lease) - - pattern: '\bgit\s+push\s+(-[^\s]*)*-f\b' - reason: git push -f (use --force-with-lease) - - pattern: '\bgit\s+stash\s+clear\b' - reason: git stash clear (deletes ALL stashes) - - pattern: '\bgit\s+reflog\s+expire\b' - reason: git reflog expire (destroys recovery mechanism) - - pattern: '\bgit\s+gc\s+.*--prune=now' - reason: git gc --prune=now (can lose dangling commits) - - pattern: '\bgit\s+filter-branch\b' - reason: git filter-branch (rewrites entire history) - - pattern: '\bgit\s+checkout\s+--\s*\.' - reason: Discards all uncommitted changes - ask: true - - pattern: '\bgit\s+restore\s+\.' - reason: Discards all uncommitted changes - ask: true - - pattern: '\bgit\s+stash\s+drop\b' - reason: Permanently deletes a stash - ask: true - - pattern: '\bgit\s+branch\s+(-[^\s]*)*-D' - reason: Force deletes branch (even if unmerged) - ask: true - - pattern: '\bgit\s+push\s+\S+\s+--delete\b' - reason: Deletes remote branch - ask: true - - pattern: '\bgit\s+push\s+\S+\s+:\S+' - reason: Deletes remote branch (old syntax) - ask: true - - pattern: '\bmkfs\.' - reason: filesystem format command - - pattern: '\bdd\s+.*of=/dev/' - reason: dd writing to device - - pattern: '\bkill\s+-9\s+-1\b' - reason: kill all processes - - pattern: '\bkillall\s+-9\b' - reason: killall -9 - - pattern: '\bpkill\s+-9\b' - reason: pkill -9 - - pattern: '\bhistory\s+-c\b' - reason: clearing shell history - - pattern: '\baws\s+s3\s+rm\s+.*--recursive' - reason: aws s3 rm --recursive (deletes all objects) - - pattern: '\baws\s+s3\s+rb\s+.*--force' - reason: aws s3 rb --force (force removes bucket) - - pattern: '\baws\s+ec2\s+terminate-instances\b' - reason: aws ec2 terminate-instances - - pattern: '\baws\s+rds\s+delete-db-instance\b' - reason: aws rds delete-db-instance - - pattern: '\baws\s+cloudformation\s+delete-stack\b' - reason: aws cloudformation delete-stack (deletes infrastructure) - - pattern: '\baws\s+dynamodb\s+delete-table\b' - reason: aws dynamodb delete-table - - pattern: '\baws\s+eks\s+delete-cluster\b' - reason: aws eks delete-cluster - - pattern: '\baws\s+lambda\s+delete-function\b' - reason: aws lambda delete-function - - pattern: '\baws\s+iam\s+delete-role\b' - reason: aws iam delete-role - - pattern: '\baws\s+iam\s+delete-user\b' - reason: aws iam delete-user - - pattern: '\bgcloud\s+projects\s+delete\b' - reason: gcloud projects delete (DELETES ENTIRE PROJECT) - - pattern: '\bgcloud\s+compute\s+instances\s+delete\b' - reason: gcloud compute instances delete - - pattern: '\bgcloud\s+sql\s+instances\s+delete\b' - reason: gcloud sql instances delete - - pattern: '\bgcloud\s+container\s+clusters\s+delete\b' - reason: gcloud container clusters delete (GKE) - - pattern: '\bgcloud\s+storage\s+rm\s+.*-r' - reason: gcloud storage rm -r (recursive delete) - - pattern: '\bgcloud\s+functions\s+delete\b' - reason: gcloud functions delete - - pattern: '\bgcloud\s+iam\s+service-accounts\s+delete\b' - reason: gcloud iam service-accounts delete - - pattern: '\bgcloud\s+run\s+services\s+delete\b' - reason: gcloud run services delete (deletes Cloud Run service) - - pattern: '\bgcloud\s+run\s+jobs\s+delete\b' - reason: gcloud run jobs delete (deletes Cloud Run job) - - pattern: '\bgcloud\s+services\s+disable\b' - reason: gcloud services disable (disables GCP APIs) - - pattern: '\bgcloud\s+iam\s+roles\s+delete\b' - reason: gcloud iam roles delete (deletes IAM role) - - pattern: '\bgcloud\s+iam\s+policies\b' - reason: gcloud iam policies (modifies IAM policies) - ask: true - - pattern: '\bfirebase\s+projects:delete\b' - reason: firebase projects:delete (deletes entire project) - - pattern: '\bfirebase\s+firestore:delete\s+.*--all-collections' - reason: firebase firestore:delete --all-collections (wipes all data) - - pattern: '\bfirebase\s+database:remove\b' - reason: firebase database:remove (wipes Realtime DB) - - pattern: '\bfirebase\s+hosting:disable\b' - reason: firebase hosting:disable - - pattern: '\bfirebase\s+functions:delete\b' - reason: firebase functions:delete - - pattern: '\bvercel\s+remove\s+.*--yes' - reason: vercel remove --yes (removes deployment) - - pattern: '\bvercel\s+projects\s+rm\b' - reason: vercel projects rm (deletes project) - - pattern: '\bvercel\s+env\s+rm\b' - reason: vercel env rm (removes env variables) - - pattern: '\bvercel\s+rm\b' - reason: vercel rm (removes deployment) - - pattern: '\bvercel\s+remove\b' - reason: vercel remove (removes deployment) - - pattern: '\bvercel\s+domains\s+rm\b' - reason: vercel domains rm (removes custom domain) - - pattern: '\bnetlify\s+sites:delete\b' - reason: netlify sites:delete (deletes entire site) - - pattern: '\bnetlify\s+functions:delete\b' - reason: netlify functions:delete - - pattern: '\bwrangler\s+delete\b' - reason: wrangler delete (deletes Worker) - - pattern: '\bwrangler\s+r2\s+bucket\s+delete\b' - reason: wrangler r2 bucket delete - - pattern: '\bwrangler\s+kv:namespace\s+delete\b' - reason: wrangler kv:namespace delete - - pattern: '\bwrangler\s+d1\s+delete\b' - reason: wrangler d1 delete (deletes database) - - pattern: '\bwrangler\s+queues\s+delete\b' - reason: wrangler queues delete - - pattern: 'DELETE\s+FROM\s+\w+\s*;' - reason: DELETE without WHERE clause (will delete ALL rows) - - pattern: 'DELETE\s+\*\s+FROM' - reason: DELETE * (will delete ALL rows) - - pattern: '\bTRUNCATE\s+TABLE\b' - reason: TRUNCATE TABLE (will delete ALL rows) - - pattern: '\bDROP\s+TABLE\b' - reason: DROP TABLE - - pattern: '\bDROP\s+DATABASE\b' - reason: DROP DATABASE - - pattern: '\bDROP\s+SCHEMA\b' - reason: DROP SCHEMA - - pattern: '\bDELETE\s+FROM\s+\w+\s+WHERE\b.*\bid\s*=' - reason: SQL DELETE with specific ID - ask: true - -zeroAccessPaths: - - ".env" - - ".env.local" - - ".env.development" - - ".env.production" - - ".env.staging" - - ".env.test" - - ".env.*.local" - - "*.env" - - "~/.ssh/" - - "~/.gnupg/" - - "~/.aws/" - - "~/.config/gcloud/" - - "*-credentials.json" - - "*serviceAccount*.json" - - "*service-account*.json" - - "~/.azure/" - - "~/.kube/" - - "kubeconfig" - - "*-secret.yaml" - - "secrets.yaml" - - "~/.docker/" - - "*.pem" - - "*.key" - - "*.p12" - - "*.pfx" - - "*.tfstate" - - "*.tfstate.backup" - - ".terraform/" - - ".vercel/" - - ".netlify/" - - "firebase-adminsdk*.json" - - "serviceAccountKey.json" - - ".supabase/" - - "~/.netrc" - - "~/.npmrc" - - "~/.pypirc" - - "~/.git-credentials" - - ".git-credentials" - - "dump.sql" - - "backup.sql" - - "*.dump" - -readOnlyPaths: - - /etc/ - - /usr/ - - /bin/ - - /sbin/ - - /boot/ - - /root/ - - ~/.bash_history - - ~/.zsh_history - - ~/.node_repl_history - - ~/.bashrc - - ~/.zshrc - - ~/.profile - - ~/.bash_profile - - "package-lock.json" - - "yarn.lock" - - "pnpm-lock.yaml" - - "Gemfile.lock" - - "poetry.lock" - - "Pipfile.lock" - - "composer.lock" - - "Cargo.lock" - - "go.sum" - - "flake.lock" - - "bun.lockb" - - "uv.lock" - - "npm-shrinkwrap.json" - - "*.lock" - - "*.lockb" - - "*.min.js" - - "*.min.css" - - "*.bundle.js" - - "*.chunk.js" - - dist/ - - build/ - - .next/ - - .nuxt/ - - .output/ - - node_modules/ - - __pycache__/ - - .venv/ - - venv/ - - target/ - -noDeletePaths: - - ~/.claude/ - - CLAUDE.md - - "LICENSE" - - "LICENSE.*" - - "COPYING" - - "COPYING.*" - - "NOTICE" - - "PATENTS" - - "README.md" - - "README.*" - - "CONTRIBUTING.md" - - "CHANGELOG.md" - - "CODE_OF_CONDUCT.md" - - "SECURITY.md" - - .git/ - - .gitignore - - .gitattributes - - .gitmodules - - .github/ - - .gitlab-ci.yml - - .circleci/ - - Jenkinsfile - - .travis.yml - - azure-pipelines.yml - - Dockerfile - - "Dockerfile.*" - - docker-compose.yml - - "docker-compose.*.yml" - - .dockerignore diff --git a/.pi/extensions/calvana-shiplog.ts b/.pi/extensions/calvana-shiplog.ts deleted file mode 100644 index c2b8cca..0000000 --- a/.pi/extensions/calvana-shiplog.ts +++ /dev/null @@ -1,719 +0,0 @@ -/** - * Calvana Ship Log Extension - * - * Automatically tracks what you're shipping and updates the live Calvana site. - * - * Tools (LLM-callable): - * - calvana_ship: Add/update/complete shipping log entries - * - calvana_oops: Log mistakes and fixes - * - calvana_deploy: Push changes to the live site - * - * Commands (user): - * /ships — View current shipping log - * /ship-deploy — Force deploy to calvana.quikcue.com - * - * How it works: - * 1. When you work on tasks, the LLM uses calvana_ship to track progress - * 2. If something breaks, calvana_oops logs it - * 3. calvana_deploy rebuilds the /live page HTML and pushes it to the server - * 4. The extension auto-injects context so the LLM knows to track ships - * - * Edit the SSH/deploy config in the DEPLOY_CONFIG section below. - */ - -import { StringEnum } from "@mariozechner/pi-ai"; -import type { ExtensionAPI, ExtensionContext, Theme } from "@mariozechner/pi-coding-agent"; -import { Text, truncateToWidth, matchesKey } from "@mariozechner/pi-tui"; -import { Type } from "@sinclair/typebox"; - -// ════════════════════════════════════════════════════════════════════ -// CONFIGURATION — Edit these to change deploy target, copy, links -// ════════════════════════════════════════════════════════════════════ - -const DEPLOY_CONFIG = { - sshHost: "root@159.195.60.33", - sshPort: "22", - container: "qc-server-new", - sitePath: "/opt/calvana/html", - domain: "calvana.quikcue.com", -}; - -const SITE_CONFIG = { - title: "Calvana", - tagline: "I break rules. Not production.", - email: "omair@quikcue.com", - referralLine: "PS — Umar pointed me here. If this turns into a hire, I want him to get paid.", -}; - -// ════════════════════════════════════════════════════════════════════ -// TYPES -// ════════════════════════════════════════════════════════════════════ - -type ShipStatus = "planned" | "shipping" | "shipped"; - -interface ShipEntry { - id: number; - title: string; - status: ShipStatus; - timestamp: string; - metric: string; - prLink: string; - deployLink: string; - loomLink: string; -} - -interface OopsEntry { - id: number; - description: string; - fixTime: string; - commitLink: string; - timestamp: string; -} - -interface ShipLogState { - ships: ShipEntry[]; - oops: OopsEntry[]; - nextShipId: number; - nextOopsId: number; - lastDeployed: string | null; -} - -// ════════════════════════════════════════════════════════════════════ -// TOOL SCHEMAS -// ════════════════════════════════════════════════════════════════════ - -const ShipParams = Type.Object({ - action: StringEnum(["add", "update", "list"] as const), - title: Type.Optional(Type.String({ description: "Ship title (for add)" })), - id: Type.Optional(Type.Number({ description: "Ship ID (for update)" })), - status: Type.Optional(StringEnum(["planned", "shipping", "shipped"] as const)), - metric: Type.Optional(Type.String({ description: "What moved — metric line" })), - prLink: Type.Optional(Type.String({ description: "PR link" })), - deployLink: Type.Optional(Type.String({ description: "Deploy link" })), - loomLink: Type.Optional(Type.String({ description: "Loom clip link" })), -}); - -const OopsParams = Type.Object({ - action: StringEnum(["add", "list"] as const), - description: Type.Optional(Type.String({ description: "What broke and how it was fixed" })), - fixTime: Type.Optional(Type.String({ description: "Time to fix, e.g. '3 min'" })), - commitLink: Type.Optional(Type.String({ description: "Link to the fix commit" })), -}); - -const DeployParams = Type.Object({ - dryRun: Type.Optional(Type.Boolean({ description: "If true, generate HTML but don't deploy" })), -}); - -// ════════════════════════════════════════════════════════════════════ -// EXTENSION -// ════════════════════════════════════════════════════════════════════ - -export default function (pi: ExtensionAPI) { - // ── State ── - let state: ShipLogState = { - ships: [], - oops: [], - nextShipId: 1, - nextOopsId: 1, - lastDeployed: null, - }; - - // ── State reconstruction from session ── - const reconstructState = (ctx: ExtensionContext) => { - state = { ships: [], oops: [], nextShipId: 1, nextOopsId: 1, lastDeployed: null }; - - for (const entry of ctx.sessionManager.getBranch()) { - if (entry.type !== "message") continue; - const msg = entry.message; - if (msg.role !== "toolResult") continue; - if (msg.toolName === "calvana_ship" || msg.toolName === "calvana_oops" || msg.toolName === "calvana_deploy") { - const details = msg.details as { state?: ShipLogState } | undefined; - if (details?.state) { - state = details.state; - } - } - } - }; - - pi.on("session_start", async (_event, ctx) => { - reconstructState(ctx); - if (ctx.hasUI) { - const theme = ctx.ui.theme; - const shipCount = state.ships.length; - const shipped = state.ships.filter(s => s.status === "shipped").length; - ctx.ui.setStatus("calvana", theme.fg("dim", `🚀 ${shipped}/${shipCount} shipped`)); - } - }); - pi.on("session_switch", async (_event, ctx) => reconstructState(ctx)); - pi.on("session_fork", async (_event, ctx) => reconstructState(ctx)); - pi.on("session_tree", async (_event, ctx) => reconstructState(ctx)); - - // ── Inject context so LLM knows about ship tracking ── - pi.on("before_agent_start", async (event, _ctx) => { - const shipContext = ` -[Calvana Ship Log Extension Active] -You have access to these tools for tracking work: -- calvana_ship: Track shipping progress (add/update/list entries) -- calvana_oops: Log mistakes and fixes -- calvana_deploy: Push updates to the live site at https://${DEPLOY_CONFIG.domain}/live - -When you START working on a task, use calvana_ship to add or update it to "shipping". -When you COMPLETE a task, update it to "shipped" with a metric. -If something BREAKS, log it with calvana_oops. -After significant changes, use calvana_deploy to push updates live. - -Current ships: ${state.ships.length} (${state.ships.filter(s => s.status === "shipped").length} shipped) -Current oops: ${state.oops.length} -`; - return { - systemPrompt: event.systemPrompt + shipContext, - }; - }); - - // ── Update status bar on turn end ── - pi.on("turn_end", async (_event, ctx) => { - if (ctx.hasUI) { - const theme = ctx.ui.theme; - const shipped = state.ships.filter(s => s.status === "shipped").length; - const shipping = state.ships.filter(s => s.status === "shipping").length; - const total = state.ships.length; - let statusText = `🚀 ${shipped}/${total} shipped`; - if (shipping > 0) statusText += ` · ${shipping} in flight`; - if (state.lastDeployed) statusText += ` · last deploy ${state.lastDeployed}`; - ctx.ui.setStatus("calvana", theme.fg("dim", statusText)); - } - }); - - // ════════════════════════════════════════════════════════════════ - // TOOL: calvana_ship - // ════════════════════════════════════════════════════════════════ - - pi.registerTool({ - name: "calvana_ship", - label: "Ship Log", - description: "Track shipping progress. Actions: add (new entry), update (change status/links), list (show all). Use this whenever you start, progress, or finish a task.", - parameters: ShipParams, - - async execute(_toolCallId, params, _signal, _onUpdate, _ctx) { - const now = new Date().toISOString().replace("T", " ").slice(0, 19) + " GMT+8"; - - switch (params.action) { - case "add": { - if (!params.title) { - return { - content: [{ type: "text", text: "Error: title required" }], - details: { state: { ...state }, error: "title required" }, - }; - } - const entry: ShipEntry = { - id: state.nextShipId++, - title: params.title, - status: (params.status as ShipStatus) || "planned", - timestamp: now, - metric: params.metric || "—", - prLink: params.prLink || "#pr", - deployLink: params.deployLink || "#deploy", - loomLink: params.loomLink || "#loomclip", - }; - state.ships.push(entry); - return { - content: [{ type: "text", text: `Ship #${entry.id} added: "${entry.title}" [${entry.status}]` }], - details: { state: { ...state, ships: [...state.ships] } }, - }; - } - - case "update": { - if (params.id === undefined) { - return { - content: [{ type: "text", text: "Error: id required for update" }], - details: { state: { ...state }, error: "id required" }, - }; - } - const ship = state.ships.find(s => s.id === params.id); - if (!ship) { - return { - content: [{ type: "text", text: `Ship #${params.id} not found` }], - details: { state: { ...state }, error: `#${params.id} not found` }, - }; - } - if (params.status) ship.status = params.status as ShipStatus; - if (params.metric) ship.metric = params.metric; - if (params.prLink) ship.prLink = params.prLink; - if (params.deployLink) ship.deployLink = params.deployLink; - if (params.loomLink) ship.loomLink = params.loomLink; - ship.timestamp = now; - return { - content: [{ type: "text", text: `Ship #${ship.id} updated: "${ship.title}" [${ship.status}]` }], - details: { state: { ...state, ships: [...state.ships] } }, - }; - } - - case "list": { - if (state.ships.length === 0) { - return { - content: [{ type: "text", text: "No ships logged yet." }], - details: { state: { ...state } }, - }; - } - const lines = state.ships.map(s => - `#${s.id} [${s.status.toUpperCase()}] ${s.title} (${s.timestamp}) — ${s.metric}` - ); - return { - content: [{ type: "text", text: lines.join("\n") }], - details: { state: { ...state } }, - }; - } - - default: - return { - content: [{ type: "text", text: `Unknown action: ${params.action}` }], - details: { state: { ...state } }, - }; - } - }, - - renderCall(args, theme) { - let text = theme.fg("toolTitle", theme.bold("🚀 ship ")); - text += theme.fg("muted", args.action || ""); - if (args.title) text += " " + theme.fg("dim", `"${args.title}"`); - if (args.id !== undefined) text += " " + theme.fg("accent", `#${args.id}`); - if (args.status) text += " → " + theme.fg("accent", args.status); - return new Text(text, 0, 0); - }, - - renderResult(result, { expanded }, theme) { - const details = result.details as { state?: ShipLogState; error?: string } | undefined; - if (details?.error) return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0); - - const st = details?.state; - if (!st || st.ships.length === 0) return new Text(theme.fg("dim", "No ships"), 0, 0); - - const shipped = st.ships.filter(s => s.status === "shipped").length; - const total = st.ships.length; - let text = theme.fg("success", `${shipped}/${total} shipped`); - - if (expanded) { - for (const s of st.ships) { - const badge = s.status === "shipped" ? theme.fg("success", "✓") - : s.status === "shipping" ? theme.fg("warning", "●") - : theme.fg("dim", "○"); - text += `\n ${badge} ${theme.fg("accent", `#${s.id}`)} ${theme.fg("muted", s.title)}`; - } - } - return new Text(text, 0, 0); - }, - }); - - // ════════════════════════════════════════════════════════════════ - // TOOL: calvana_oops - // ════════════════════════════════════════════════════════════════ - - pi.registerTool({ - name: "calvana_oops", - label: "Oops Log", - description: "Log mistakes and fixes. Actions: add (new oops entry), list (show all). Use when something breaks during a task.", - parameters: OopsParams, - - async execute(_toolCallId, params, _signal, _onUpdate, _ctx) { - const now = new Date().toISOString().replace("T", " ").slice(0, 19) + " GMT+8"; - - switch (params.action) { - case "add": { - if (!params.description) { - return { - content: [{ type: "text", text: "Error: description required" }], - details: { state: { ...state }, error: "description required" }, - }; - } - const entry: OopsEntry = { - id: state.nextOopsId++, - description: params.description, - fixTime: params.fixTime || "—", - commitLink: params.commitLink || "#commit", - timestamp: now, - }; - state.oops.push(entry); - return { - content: [{ type: "text", text: `Oops #${entry.id}: "${entry.description}" (fixed in ${entry.fixTime})` }], - details: { state: { ...state, oops: [...state.oops] } }, - }; - } - - case "list": { - if (state.oops.length === 0) { - return { - content: [{ type: "text", text: "No oops entries. Clean run so far." }], - details: { state: { ...state } }, - }; - } - const lines = state.oops.map(o => - `#${o.id} ${o.description} — fixed in ${o.fixTime}` - ); - return { - content: [{ type: "text", text: lines.join("\n") }], - details: { state: { ...state } }, - }; - } - - default: - return { - content: [{ type: "text", text: `Unknown action: ${params.action}` }], - details: { state: { ...state } }, - }; - } - }, - - renderCall(args, theme) { - let text = theme.fg("toolTitle", theme.bold("💥 oops ")); - text += theme.fg("muted", args.action || ""); - if (args.description) text += " " + theme.fg("dim", `"${args.description}"`); - return new Text(text, 0, 0); - }, - - renderResult(result, _options, theme) { - const details = result.details as { state?: ShipLogState; error?: string } | undefined; - if (details?.error) return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0); - const text = result.content[0]; - return new Text(theme.fg("warning", text?.type === "text" ? text.text : ""), 0, 0); - }, - }); - - // ════════════════════════════════════════════════════════════════ - // TOOL: calvana_deploy - // ════════════════════════════════════════════════════════════════ - - pi.registerTool({ - name: "calvana_deploy", - label: "Deploy Calvana", - description: `Regenerate the /live page with current ship log and deploy to https://${DEPLOY_CONFIG.domain}. Call this after adding/updating ships or oops entries to push changes live.`, - parameters: DeployParams, - - async execute(_toolCallId, params, signal, onUpdate, _ctx) { - onUpdate?.({ content: [{ type: "text", text: "Generating HTML..." }] }); - - const liveHtml = generateLivePageHtml(state); - - if (params.dryRun) { - return { - content: [{ type: "text", text: `Dry run — generated ${liveHtml.length} bytes of HTML.\n\n${liveHtml.slice(0, 500)}...` }], - details: { state: { ...state }, dryRun: true }, - }; - } - - onUpdate?.({ content: [{ type: "text", text: "Deploying to server..." }] }); - - try { - // Write HTML to server via SSH + incus exec - const escapedHtml = liveHtml.replace(/'/g, "'\\''"); - const sshCmd = `ssh -o ConnectTimeout=10 -p ${DEPLOY_CONFIG.sshPort} ${DEPLOY_CONFIG.sshHost}`; - const writeCmd = `${sshCmd} "incus exec ${DEPLOY_CONFIG.container} -- bash -c 'cat > ${DEPLOY_CONFIG.sitePath}/live/index.html << '\\''HTMLEOF'\\'' -${liveHtml} -HTMLEOF -'"`; - - // Use base64 to avoid all escaping nightmares - const b64Html = Buffer.from(liveHtml).toString("base64"); - const deployResult = await pi.exec("bash", ["-c", - `ssh -o ConnectTimeout=10 -p ${DEPLOY_CONFIG.sshPort} ${DEPLOY_CONFIG.sshHost} "incus exec ${DEPLOY_CONFIG.container} -- bash -c 'echo ${b64Html} | base64 -d > ${DEPLOY_CONFIG.sitePath}/live/index.html'"` - ], { signal, timeout: 30000 }); - - if (deployResult.code !== 0) { - return { - content: [{ type: "text", text: `Deploy failed: ${deployResult.stderr}` }], - details: { state: { ...state }, error: deployResult.stderr }, - isError: true, - }; - } - - // Rebuild and update docker service - const rebuildResult = await pi.exec("bash", ["-c", - `ssh -o ConnectTimeout=10 -p ${DEPLOY_CONFIG.sshPort} ${DEPLOY_CONFIG.sshHost} "incus exec ${DEPLOY_CONFIG.container} -- bash -c 'cd /opt/calvana && docker build -t calvana:latest . 2>&1 | tail -2 && docker service update --force calvana 2>&1 | tail -2'"` - ], { signal, timeout: 60000 }); - - const now = new Date().toISOString().replace("T", " ").slice(0, 19); - state.lastDeployed = now; - - return { - content: [{ type: "text", text: `✓ Deployed to https://${DEPLOY_CONFIG.domain}/live\n${rebuildResult.stdout}` }], - details: { state: { ...state, lastDeployed: now } }, - }; - } catch (err: any) { - return { - content: [{ type: "text", text: `Deploy error: ${err.message}` }], - details: { state: { ...state }, error: err.message }, - isError: true, - }; - } - }, - - renderCall(_args, theme) { - return new Text(theme.fg("toolTitle", theme.bold("🌐 deploy calvana")), 0, 0); - }, - - renderResult(result, _options, theme) { - const details = result.details as { error?: string } | undefined; - if (details?.error) return new Text(theme.fg("error", `✗ ${details.error}`), 0, 0); - return new Text(theme.fg("success", `✓ Live at https://${DEPLOY_CONFIG.domain}/live`), 0, 0); - }, - }); - - // ════════════════════════════════════════════════════════════════ - // COMMAND: /ships - // ════════════════════════════════════════════════════════════════ - - pi.registerCommand("ships", { - description: "View current Calvana shipping log", - handler: async (_args, ctx) => { - if (!ctx.hasUI) { - ctx.ui.notify("Requires interactive mode", "error"); - return; - } - - await ctx.ui.custom((_tui, theme, _kb, done) => { - return new ShipLogComponent(state, theme, () => done()); - }); - }, - }); - - // ════════════════════════════════════════════════════════════════ - // COMMAND: /ship-deploy - // ════════════════════════════════════════════════════════════════ - - pi.registerCommand("ship-deploy", { - description: "Force deploy the Calvana site with current ship log", - handler: async (_args, ctx) => { - const ok = await ctx.ui.confirm("Deploy?", `Push ship log to https://${DEPLOY_CONFIG.domain}/live?`); - if (!ok) return; - - // Queue a deploy via the LLM - pi.sendUserMessage("Use calvana_deploy to push the current ship log to the live site.", { deliverAs: "followUp" }); - }, - }); -} - -// ════════════════════════════════════════════════════════════════════ -// UI COMPONENT: /ships viewer -// ════════════════════════════════════════════════════════════════════ - -class ShipLogComponent { - private state: ShipLogState; - private theme: Theme; - private onClose: () => void; - private cachedWidth?: number; - private cachedLines?: string[]; - - constructor(state: ShipLogState, theme: Theme, onClose: () => void) { - this.state = state; - this.theme = theme; - this.onClose = onClose; - } - - handleInput(data: string): void { - if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) { - this.onClose(); - } - } - - render(width: number): string[] { - if (this.cachedLines && this.cachedWidth === width) return this.cachedLines; - - const lines: string[] = []; - const th = this.theme; - - lines.push(""); - lines.push(truncateToWidth( - th.fg("borderMuted", "─".repeat(3)) + - th.fg("accent", " 🚀 Calvana Ship Log ") + - th.fg("borderMuted", "─".repeat(Math.max(0, width - 26))), - width - )); - lines.push(""); - - // Ships - if (this.state.ships.length === 0) { - lines.push(truncateToWidth(` ${th.fg("dim", "No ships yet.")}`, width)); - } else { - const shipped = this.state.ships.filter(s => s.status === "shipped").length; - lines.push(truncateToWidth( - ` ${th.fg("muted", `${shipped}/${this.state.ships.length} shipped`)}`, - width - )); - lines.push(""); - - for (const s of this.state.ships) { - const badge = s.status === "shipped" ? th.fg("success", "✓ SHIPPED ") - : s.status === "shipping" ? th.fg("warning", "● SHIPPING") - : th.fg("dim", "○ PLANNED "); - lines.push(truncateToWidth( - ` ${badge} ${th.fg("accent", `#${s.id}`)} ${th.fg("text", s.title)}`, - width - )); - lines.push(truncateToWidth( - ` ${th.fg("dim", s.timestamp)} · ${th.fg("dim", s.metric)}`, - width - )); - } - } - - // Oops - if (this.state.oops.length > 0) { - lines.push(""); - lines.push(truncateToWidth(` ${th.fg("warning", "💥 Oops Log")}`, width)); - for (const o of this.state.oops) { - lines.push(truncateToWidth( - ` ${th.fg("error", "─")} ${th.fg("muted", o.description)} ${th.fg("dim", `(${o.fixTime})`)}`, - width - )); - } - } - - lines.push(""); - if (this.state.lastDeployed) { - lines.push(truncateToWidth(` ${th.fg("dim", `Last deployed: ${this.state.lastDeployed}`)}`, width)); - } - lines.push(truncateToWidth(` ${th.fg("dim", "Press Escape to close")}`, width)); - lines.push(""); - - this.cachedWidth = width; - this.cachedLines = lines; - return lines; - } - - invalidate(): void { - this.cachedWidth = undefined; - this.cachedLines = undefined; - } -} - -// ════════════════════════════════════════════════════════════════════ -// HTML GENERATOR — Builds the /live page from current state -// ════════════════════════════════════════════════════════════════════ - -function generateLivePageHtml(state: ShipLogState): string { - const now = new Date().toISOString(); - - const shipCards = state.ships.map(s => { - const badgeClass = s.status === "shipped" ? "badge-shipped" - : s.status === "shipping" ? "badge-shipping" - : "badge-planned"; - const badgeLabel = s.status.charAt(0).toUpperCase() + s.status.slice(1); - const titleSuffix = s.status === "shipped" ? " ✓" : ""; - - return `
-
- ${escapeHtml(s.title)}${titleSuffix} - ${badgeLabel} -
-

⏱ ${escapeHtml(s.timestamp)}

-

What moved: ${escapeHtml(s.metric)}

- -
`; - }).join("\n"); - - const oopsEntries = state.oops.map(o => { - return `
- ${escapeHtml(o.description)}${o.fixTime !== "—" ? ` Fixed in ${escapeHtml(o.fixTime)}.` : ""} - → commit -
`; - }).join("\n"); - - // If no ships yet, show placeholder - const shipsSection = state.ships.length > 0 ? shipCards : `
-
- Warming up... - Planned -
-

⏱ —

-

What moved: —

-
`; - - const oopsSection = state.oops.length > 0 ? oopsEntries : `
- Nothing broken yet. Give it time. - → waiting -
`; - - return ` - - - - - Calvana — Live Shipping Log - - - - - - - - - - - -
-

Live Shipping Log

-

Intentional chaos. Full receipts.

- -
-

Today's Ships

-
-${shipsSection} -
-
- -
-
-
-

Rules I broke today

-
    -
  • Didn't ask permission
  • -
  • Didn't wait for alignment
  • -
  • Didn't write a PRD
  • -
  • Didn't submit a normal application
  • -
-
-
-

Rules I refuse to break

-
    -
  • No silent failures
  • -
  • No unbounded AI spend
  • -
  • No hallucinations shipped to users
  • -
  • No deploy without rollback path
  • -
-
-
-
- -
-

Oops Log

-

If it's not here, I haven't broken it yet.

-
-${oopsSection} -
-
- -
- -

Last updated: ${now}

-
-
- -`; -} - -function escapeHtml(str: string): string { - return str - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); -} diff --git a/.pi/observatory/.gitignore b/.pi/observatory/.gitignore deleted file mode 100644 index d5e0d85..0000000 --- a/.pi/observatory/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -events.jsonl -summary.json -report.md diff --git a/.pi/observatory/.gitkeep b/.pi/observatory/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/.pi/settings.json b/.pi/settings.json deleted file mode 100644 index b6bdc35..0000000 --- a/.pi/settings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "theme": "synthwave", - "prompts": [ - "../.claude/commands" - ] -} \ No newline at end of file diff --git a/.pi/skills/bowser.md b/.pi/skills/bowser.md deleted file mode 100644 index 5b67413..0000000 --- a/.pi/skills/bowser.md +++ /dev/null @@ -1,120 +0,0 @@ ---- -name: bowser -description: Headless browser automation using Playwright CLI. Use when you need headless browsing, parallel browser sessions, UI testing, screenshots, web scraping, or browser automation that can run in the background. Keywords - playwright, headless, browser, test, screenshot, scrape, parallel. -allowed-tools: Bash ---- - -# Playwright Bowser - -## Purpose - -Automate browsers using `playwright-cli` (via `@playwright/cli`) — a token-efficient CLI for Playwright. Runs headless by default, supports parallel sessions via named sessions (`-s=`), and doesn't load tool schemas into context. - -## Prerequisites - -Ensure the package is installed in the project: -```bash -bun add -d @playwright/cli -bunx playwright install chromium -``` - -## Key Details - -- **Headless by default** — pass `--headed` to `open` to see the browser -- **Parallel sessions** — use `-s=` to run multiple independent browser instances -- **Persistent profiles** — cookies and storage state preserved between calls -- **Token-efficient** — CLI-based, no accessibility trees or tool schemas in context -- **Vision mode** (opt-in) — set `PLAYWRIGHT_MCP_CAPS=vision` to receive screenshots as image responses in context instead of just saving to disk - -## Sessions - -**Always use a named session.** Derive a short, descriptive kebab-case name from the user's prompt. This gives each task a persistent browser profile (cookies, localStorage, history) that accumulates across calls. - -```bash -# Derive session name from prompt context: -# "test the checkout flow on mystore.com" → -s=mystore-checkout -# "scrape pricing from competitor.com" → -s=competitor-pricing -# "UI test the login page" → -s=login-ui-test - -bunx playwright-cli -s=mystore-checkout open https://mystore.com --persistent -bunx playwright-cli -s=mystore-checkout snapshot -bunx playwright-cli -s=mystore-checkout click e12 -``` - -Managing sessions: -```bash -bunx playwright-cli list # list all sessions -bunx playwright-cli close-all # close all sessions -bunx playwright-cli -s= close # close specific session -bunx playwright-cli -s= delete-data # wipe session profile -``` - -## Quick Reference - -``` -Core: open [url], goto , click , fill , type , snapshot, screenshot [ref], close -Navigate: go-back, go-forward, reload -Keyboard: press , keydown , keyup -Mouse: mousemove , mousedown, mouseup, mousewheel -Tabs: tab-list, tab-new [url], tab-close [index], tab-select -Save: screenshot [ref], pdf, screenshot --filename=f -Storage: state-save, state-load, cookie-*, localstorage-*, sessionstorage-* -Network: route , route-list, unroute, network -DevTools: console, run-code , tracing-start/stop, video-start/stop -Sessions: -s= , list, close-all, kill-all -Config: open --headed, open --browser=chrome, resize -``` - -## Workflow - -1. Derive a session name from the user's prompt and open with `--persistent` to preserve cookies/state. Always set the viewport via env var at launch: -```bash -PLAYWRIGHT_MCP_VIEWPORT_SIZE=1440x900 bunx playwright-cli -s= open --persistent -# or headed: -PLAYWRIGHT_MCP_VIEWPORT_SIZE=1440x900 bunx playwright-cli -s= open --persistent --headed -# or with vision (screenshots returned as image responses in context): -PLAYWRIGHT_MCP_VIEWPORT_SIZE=1440x900 PLAYWRIGHT_MCP_CAPS=vision bunx playwright-cli -s= open --persistent -``` - -3. Get element references via snapshot: -```bash -bunx playwright-cli snapshot -``` - -4. Interact using refs from snapshot: -```bash -bunx playwright-cli click -bunx playwright-cli fill "text" -bunx playwright-cli type "text" -bunx playwright-cli press Enter -``` - -5. Capture results: -```bash -bunx playwright-cli screenshot -bunx playwright-cli screenshot --filename=output.png -``` - -6. **Always close the session when done.** This is not optional — close the named session after finishing your task: -```bash -bunx playwright-cli -s= close -``` - -## Configuration - -If a `playwright-cli.json` exists in the working directory, use it automatically. If the user provides a path to a config file, use `--config path/to/config.json`. Otherwise, skip configuration — the env var and CLI defaults are sufficient. - -```json -{ - "browser": { - "browserName": "chromium", - "launchOptions": { "headless": true }, - "contextOptions": { "viewport": { "width": 1440, "height": 900 } } - }, - "outputDir": "./screenshots" -} -``` - -## Full Help - -Run `bunx playwright-cli --help` or `bunx playwright-cli --help ` for detailed command usage. diff --git a/.pi/themes/catppuccin-mocha.json b/.pi/themes/catppuccin-mocha.json deleted file mode 100644 index 27819b3..0000000 --- a/.pi/themes/catppuccin-mocha.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json", - "name": "catppuccin-mocha", - "vars": { - "bg": "#1e1e2e", - "bgDark": "#181825", - "bgDeep": "#13131e", - "surface": "#2a2a3c", - "selection": "#34344a", - "bgRed": "#2e1420", - "bgGreen": "#142218", - "bgPeach": "#2e2010", - "bgBlue": "#141e38", - "bgMauve": "#261840", - "bgTeal": "#122830", - "comment": "#d5bcff", - "fg": "#ffffff", - "fgSoft": "#bbbbbb", - "red": "#ff7eb3", - "maroon": "#ffa0b8", - "peach": "#ffb370", - "yellow": "#ffe585", - "green": "#7af5a0", - "teal": "#60f0d8", - "sky": "#6ae4ff", - "sapphire": "#5cceff", - "blue": "#7db8ff", - "lavender": "#bfb8ff", - "mauve": "#d9a0ff", - "flamingo": "#ffc4c4", - "pink": "#ffb0e0" - }, - "colors": { - "accent": "mauve", - "border": "selection", - "borderAccent": "mauve", - "borderMuted": "surface", - "success": "green", - "error": "red", - "warning": "yellow", - "muted": "comment", - "dim": "comment", - "text": "fg", - "thinkingText": "teal", - "selectedBg": "bgMauve", - "userMessageBg": "bgBlue", - "userMessageText": "fg", - "customMessageBg": "bgTeal", - "customMessageText": "fg", - "customMessageLabel": "teal", - "toolPendingBg": "bgPeach", - "toolSuccessBg": "bgGreen", - "toolErrorBg": "bgRed", - "toolTitle": "peach", - "toolOutput": "fgSoft", - "mdHeading": "peach", - "mdLink": "blue", - "mdLinkUrl": "comment", - "mdCode": "sky", - "mdCodeBlock": "fgSoft", - "mdCodeBlockBorder": "surface", - "mdQuote": "green", - "mdQuoteBorder": "surface", - "mdHr": "surface", - "mdListBullet": "mauve", - "toolDiffAdded": "green", - "toolDiffRemoved": "red", - "toolDiffContext": "comment", - "syntaxComment": "comment", - "syntaxKeyword": "mauve", - "syntaxFunction": "blue", - "syntaxVariable": "pink", - "syntaxString": "green", - "syntaxNumber": "peach", - "syntaxType": "sky", - "syntaxOperator": "lavender", - "syntaxPunctuation": "fgSoft", - "thinkingOff": "surface", - "thinkingMinimal": "comment", - "thinkingLow": "blue", - "thinkingMedium": "sky", - "thinkingHigh": "mauve", - "thinkingXhigh": "red", - "bashMode": "yellow" - } -} diff --git a/.pi/themes/cyberpunk.json b/.pi/themes/cyberpunk.json deleted file mode 100644 index 09ad7df..0000000 --- a/.pi/themes/cyberpunk.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json", - "name": "cyberpunk", - "vars": { - "bg": "#0a0a14", - "bgDark": "#06060e", - "bgDeep": "#040410", - "surface": "#12122a", - "selection": "#1a1a38", - "bgRed": "#2a0a12", - "bgOrange": "#2a1408", - "bgSky": "#081a30", - "bgCyan": "#0a2228", - "bgWarm": "#220a30", - "bgPink": "#2a0a22", - "fg": "#ffffff", - "fgSoft": "#bbbbbb", - "comment": "#ffe600", - "yellow": "#ffe600", - "cyan": "#00e5ff", - "magenta": "#ff00aa", - "red": "#ff1744", - "green": "#00e676", - "purple": "#aa00ff", - "blue": "#2979ff", - "orange": "#ff6d00" - }, - "colors": { - "accent": "cyan", - "border": "magenta", - "borderAccent": "yellow", - "borderMuted": "surface", - "success": "green", - "error": "red", - "warning": "orange", - "muted": "comment", - "dim": "comment", - "text": "fg", - "thinkingText": "green", - "selectedBg": "bgPink", - "userMessageBg": "bgWarm", - "userMessageText": "fg", - "customMessageBg": "bgCyan", - "customMessageText": "fg", - "customMessageLabel": "cyan", - "toolPendingBg": "bgOrange", - "toolSuccessBg": "bgSky", - "toolErrorBg": "bgRed", - "toolTitle": "yellow", - "toolOutput": "fgSoft", - "mdHeading": "magenta", - "mdLink": "cyan", - "mdLinkUrl": "comment", - "mdCode": "green", - "mdCodeBlock": "fgSoft", - "mdCodeBlockBorder": "surface", - "mdQuote": "purple", - "mdQuoteBorder": "surface", - "mdHr": "surface", - "mdListBullet": "yellow", - "toolDiffAdded": "green", - "toolDiffRemoved": "red", - "toolDiffContext": "comment", - "syntaxComment": "comment", - "syntaxKeyword": "magenta", - "syntaxFunction": "cyan", - "syntaxVariable": "yellow", - "syntaxString": "green", - "syntaxNumber": "purple", - "syntaxType": "blue", - "syntaxOperator": "magenta", - "syntaxPunctuation": "fgSoft", - "thinkingOff": "surface", - "thinkingMinimal": "comment", - "thinkingLow": "blue", - "thinkingMedium": "purple", - "thinkingHigh": "cyan", - "thinkingXhigh": "magenta", - "bashMode": "orange" - } -} diff --git a/.pi/themes/dracula.json b/.pi/themes/dracula.json deleted file mode 100644 index d42a72c..0000000 --- a/.pi/themes/dracula.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json", - "name": "dracula", - "vars": { - "bg": "#1a1b26", - "bgDark": "#161722", - "bgDeep": "#141520", - "surface": "#252738", - "selection": "#2c2e44", - "bgRed": "#2e1220", - "bgOrange": "#2e1c12", - "bgGreen": "#122e1a", - "bgCyan": "#122a2e", - "bgPurple": "#261536", - "bgPink": "#2e1228", - "fg": "#ffffff", - "fgSoft": "#bbbbbb", - "comment": "#f8fcc4", - "cyan": "#8be9fd", - "green": "#50fa7b", - "orange": "#ffb86c", - "pink": "#ff79c6", - "purple": "#bd93f9", - "red": "#ff5555", - "yellow": "#f1fa8c", - "blue": "#6296e4" - }, - "colors": { - "accent": "purple", - "border": "pink", - "borderAccent": "purple", - "borderMuted": "surface", - "success": "green", - "error": "red", - "warning": "orange", - "muted": "comment", - "dim": "comment", - "text": "fg", - "thinkingText": "cyan", - "selectedBg": "bgPurple", - "userMessageBg": "bgPink", - "userMessageText": "fg", - "customMessageBg": "bgCyan", - "customMessageText": "fg", - "customMessageLabel": "cyan", - "toolPendingBg": "bgOrange", - "toolSuccessBg": "bgGreen", - "toolErrorBg": "bgRed", - "toolTitle": "pink", - "toolOutput": "fgSoft", - "mdHeading": "pink", - "mdLink": "cyan", - "mdLinkUrl": "comment", - "mdCode": "green", - "mdCodeBlock": "fgSoft", - "mdCodeBlockBorder": "surface", - "mdQuote": "purple", - "mdQuoteBorder": "surface", - "mdHr": "surface", - "mdListBullet": "pink", - "toolDiffAdded": "green", - "toolDiffRemoved": "red", - "toolDiffContext": "comment", - "syntaxComment": "comment", - "syntaxKeyword": "pink", - "syntaxFunction": "green", - "syntaxVariable": "fg", - "syntaxString": "yellow", - "syntaxNumber": "purple", - "syntaxType": "cyan", - "syntaxOperator": "pink", - "syntaxPunctuation": "fgSoft", - "thinkingOff": "surface", - "thinkingMinimal": "comment", - "thinkingLow": "blue", - "thinkingMedium": "purple", - "thinkingHigh": "cyan", - "thinkingXhigh": "pink", - "bashMode": "orange" - } -} diff --git a/.pi/themes/everforest.json b/.pi/themes/everforest.json deleted file mode 100644 index 3131378..0000000 --- a/.pi/themes/everforest.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json", - "name": "everforest", - "vars": { - "bg": "#191f1d", - "bgDark": "#141a18", - "bg1": "#1e2522", - "bg2": "#222a28", - "surface": "#2c3532", - "selection": "#323e3a", - "bgRed": "#301718", - "bgOrange": "#302217", - "bgSky": "#192b34", - "bgCyan": "#172b26", - "bgWarm": "#351d29", - "bgPink": "#311c31", - "fg": "#ffffff", - "fgSoft": "#bbbbbb", - "comment": "#e7f4cd", - "red": "#eb7073", - "orange": "#f1a27e", - "yellow": "#eed096", - "green": "#bde481", - "aqua": "#78e292", - "teal": "#52e0bd", - "blue": "#78c8e2", - "purple": "#e689b5" - }, - "colors": { - "accent": "green", - "border": "aqua", - "borderAccent": "green", - "borderMuted": "surface", - "success": "green", - "error": "red", - "warning": "orange", - "muted": "comment", - "dim": "comment", - "text": "fg", - "thinkingText": "teal", - "selectedBg": "bgCyan", - "userMessageBg": "bgWarm", - "userMessageText": "fg", - "customMessageBg": "bgSky", - "customMessageText": "fg", - "customMessageLabel": "aqua", - "toolPendingBg": "bgOrange", - "toolSuccessBg": "bgCyan", - "toolErrorBg": "bgRed", - "toolTitle": "green", - "toolOutput": "fgSoft", - "mdHeading": "yellow", - "mdLink": "blue", - "mdLinkUrl": "comment", - "mdCode": "aqua", - "mdCodeBlock": "fgSoft", - "mdCodeBlockBorder": "surface", - "mdQuote": "teal", - "mdQuoteBorder": "surface", - "mdHr": "surface", - "mdListBullet": "green", - "toolDiffAdded": "green", - "toolDiffRemoved": "red", - "toolDiffContext": "comment", - "syntaxComment": "comment", - "syntaxKeyword": "red", - "syntaxFunction": "green", - "syntaxVariable": "blue", - "syntaxString": "yellow", - "syntaxNumber": "purple", - "syntaxType": "aqua", - "syntaxOperator": "orange", - "syntaxPunctuation": "fgSoft", - "thinkingOff": "surface", - "thinkingMinimal": "comment", - "thinkingLow": "blue", - "thinkingMedium": "teal", - "thinkingHigh": "green", - "thinkingXhigh": "red", - "bashMode": "orange" - } -} diff --git a/.pi/themes/gruvbox.json b/.pi/themes/gruvbox.json deleted file mode 100644 index dbed3aa..0000000 --- a/.pi/themes/gruvbox.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json", - "name": "gruvbox", - "vars": { - "bg": "#221f1c", - "bgDark": "#1c1a17", - "bgDeep": "#171412", - "surface": "#322d29", - "selection": "#3f3731", - "bgRed": "#341714", - "bgOrange": "#322215", - "bgSky": "#152432", - "bgCyan": "#142924", - "bgWarm": "#322b15", - "bgPink": "#321524", - "comment": "#fcebc5", - "fg": "#ffffff", - "fgSoft": "#bbbbbb", - "red": "#fb4b37", - "green": "#ebed5e", - "yellow": "#fcd783", - "blue": "#67a6e4", - "purple": "#ca74e7", - "aqua": "#81e4be", - "orange": "#fd953f" - }, - "colors": { - "accent": "orange", - "border": "yellow", - "borderAccent": "orange", - "borderMuted": "surface", - "success": "green", - "error": "red", - "warning": "yellow", - "muted": "comment", - "dim": "comment", - "text": "fg", - "thinkingText": "aqua", - "selectedBg": "bgWarm", - "userMessageBg": "bgOrange", - "userMessageText": "fg", - "customMessageBg": "bgCyan", - "customMessageText": "fg", - "customMessageLabel": "aqua", - "toolPendingBg": "bgSky", - "toolSuccessBg": "bgCyan", - "toolErrorBg": "bgRed", - "toolTitle": "orange", - "toolOutput": "fgSoft", - "mdHeading": "yellow", - "mdLink": "aqua", - "mdLinkUrl": "comment", - "mdCode": "green", - "mdCodeBlock": "fgSoft", - "mdCodeBlockBorder": "surface", - "mdQuote": "blue", - "mdQuoteBorder": "surface", - "mdHr": "surface", - "mdListBullet": "orange", - "toolDiffAdded": "green", - "toolDiffRemoved": "red", - "toolDiffContext": "comment", - "syntaxComment": "comment", - "syntaxKeyword": "red", - "syntaxFunction": "aqua", - "syntaxVariable": "blue", - "syntaxString": "green", - "syntaxNumber": "purple", - "syntaxType": "yellow", - "syntaxOperator": "orange", - "syntaxPunctuation": "fgSoft", - "thinkingOff": "surface", - "thinkingMinimal": "comment", - "thinkingLow": "blue", - "thinkingMedium": "aqua", - "thinkingHigh": "yellow", - "thinkingXhigh": "red", - "bashMode": "orange" - } -} diff --git a/.pi/themes/midnight-ocean.json b/.pi/themes/midnight-ocean.json deleted file mode 100644 index a00cc0f..0000000 --- a/.pi/themes/midnight-ocean.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json", - "name": "midnight-ocean", - "vars": { - "deepBlue": "#0a192f", - "oceanBlue": "#0077be", - "teal": "#00ced1", - "cyan": "#4fd1ed", - "softWhite": "#e6f1ff", - "mutedBlue": "#233554", - "lightMutedBlue": "#a8b2d1", - "slate": "#8892b0", - "successGreen": "#64ffda", - "errorRed": "#ff5f56", - "warningAmber": "#ffd700", - "purple": "#c678dd" - }, - "colors": { - "accent": "oceanBlue", - "border": "mutedBlue", - "borderAccent": "teal", - "borderMuted": 236, - "success": "successGreen", - "error": "errorRed", - "warning": "warningAmber", - "muted": "slate", - "dim": 240, - "text": "softWhite", - "thinkingText": "teal", - "selectedBg": "#112240", - "userMessageBg": "#112240", - "userMessageText": "softWhite", - "customMessageBg": "#112240", - "customMessageText": "softWhite", - "customMessageLabel": "teal", - "toolPendingBg": "deepBlue", - "toolSuccessBg": "#0d2521", - "toolErrorBg": "#331616", - "toolTitle": "cyan", - "toolOutput": "lightMutedBlue", - "mdHeading": "teal", - "mdLink": "oceanBlue", - "mdLinkUrl": "slate", - "mdCode": "cyan", - "mdCodeBlock": "#011627", - "mdCodeBlockBorder": "mutedBlue", - "mdQuote": "slate", - "mdQuoteBorder": "mutedBlue", - "mdHr": "mutedBlue", - "mdListBullet": "teal", - "toolDiffAdded": "successGreen", - "toolDiffRemoved": "errorRed", - "toolDiffContext": "slate", - "syntaxComment": "slate", - "syntaxKeyword": "purple", - "syntaxFunction": "teal", - "syntaxVariable": "cyan", - "syntaxString": "successGreen", - "syntaxNumber": "warningAmber", - "syntaxType": "oceanBlue", - "syntaxOperator": "teal", - "syntaxPunctuation": "lightMutedBlue", - "thinkingOff": "mutedBlue", - "thinkingMinimal": "oceanBlue", - "thinkingLow": "teal", - "thinkingMedium": "cyan", - "thinkingHigh": "warningAmber", - "thinkingXhigh": "errorRed", - "bashMode": "warningAmber" - }, - "export": { - "pageBg": "#0a192f", - "cardBg": "#112240", - "infoBg": "#0077be" - } -} diff --git a/.pi/themes/nord.json b/.pi/themes/nord.json deleted file mode 100644 index 86f494c..0000000 --- a/.pi/themes/nord.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json", - "name": "nord", - "vars": { - "bg": "#1a1d23", - "bgDark": "#15181d", - "bgDeep": "#111316", - "surface": "#272b34", - "selection": "#2f3541", - "bgRed": "#2e1818", - "bgOrange": "#31241a", - "bgSky": "#1c2835", - "bgCyan": "#192c2d", - "bgWarm": "#291b30", - "bgPink": "#2d1927", - "comment": "#ccebf4", - "fg": "#ffffff", - "fgSoft": "#bbbbbb", - "frost1": "#67e4e2", - "frost2": "#72cee8", - "frost3": "#67a5e4", - "frost4": "#5c97df", - "red": "#e85e6c", - "orange": "#ed7f5e", - "yellow": "#f5d189", - "green": "#92df6b", - "purple": "#e278e2", - "border": "#3e5974", - "dim": "#3d4c5b" - }, - "colors": { - "accent": "frost2", - "border": "border", - "borderAccent": "frost2", - "borderMuted": "surface", - "success": "green", - "error": "red", - "warning": "orange", - "muted": "comment", - "dim": "comment", - "text": "fg", - "thinkingText": "frost1", - "selectedBg": "bgPink", - "userMessageBg": "bgWarm", - "userMessageText": "fg", - "customMessageBg": "bgCyan", - "customMessageText": "fg", - "customMessageLabel": "frost2", - "toolPendingBg": "bgOrange", - "toolSuccessBg": "bgSky", - "toolErrorBg": "bgRed", - "toolTitle": "orange", - "toolOutput": "fgSoft", - "mdHeading": "yellow", - "mdLink": "frost2", - "mdLinkUrl": "comment", - "mdCode": "frost1", - "mdCodeBlock": "fgSoft", - "mdCodeBlockBorder": "surface", - "mdQuote": "purple", - "mdQuoteBorder": "surface", - "mdHr": "surface", - "mdListBullet": "frost2", - "toolDiffAdded": "green", - "toolDiffRemoved": "red", - "toolDiffContext": "comment", - "syntaxComment": "comment", - "syntaxKeyword": "frost3", - "syntaxFunction": "frost2", - "syntaxVariable": "fg", - "syntaxString": "green", - "syntaxNumber": "purple", - "syntaxType": "frost1", - "syntaxOperator": "frost3", - "syntaxPunctuation": "fgSoft", - "thinkingOff": "surface", - "thinkingMinimal": "dim", - "thinkingLow": "frost4", - "thinkingMedium": "frost3", - "thinkingHigh": "frost2", - "thinkingXhigh": "frost1", - "bashMode": "yellow" - } -} diff --git a/.pi/themes/ocean-breeze.json b/.pi/themes/ocean-breeze.json deleted file mode 100644 index 462d9f2..0000000 --- a/.pi/themes/ocean-breeze.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json", - "name": "ocean-breeze", - "vars": { - "bg": "#0d1b2a", - "bgDark": "#0a1520", - "bgDeep": "#081018", - "surface": "#152a3e", - "selection": "#1b3450", - "bgRed": "#2a1018", - "bgOrange": "#2a1e10", - "bgSky": "#0e2440", - "bgCyan": "#0c2a2e", - "bgWarm": "#2a1530", - "bgPink": "#2e1028", - "fg": "#ffffff", - "fgSoft": "#bbbbbb", - "comment": "#c2faf2", - "coral": "#ff6b6b", - "amber": "#ffd166", - "kelp": "#2eeab5", - "biolum": "#33fff7", - "foam": "#50b0e0", - "spray": "#7ec8e3", - "mist": "#a8d8ea", - "sand": "#ecf49a", - "purple": "#b48aef", - "pink": "#f772b9" - }, - "colors": { - "accent": "biolum", - "border": "foam", - "borderAccent": "biolum", - "borderMuted": "surface", - "success": "kelp", - "error": "coral", - "warning": "amber", - "muted": "comment", - "dim": "comment", - "text": "fg", - "thinkingText": "biolum", - "selectedBg": "selection", - "userMessageBg": "bgSky", - "userMessageText": "fg", - "customMessageBg": "bgCyan", - "customMessageText": "fg", - "customMessageLabel": "spray", - "toolPendingBg": "bgOrange", - "toolSuccessBg": "bgCyan", - "toolErrorBg": "bgRed", - "toolTitle": "spray", - "toolOutput": "fgSoft", - "mdHeading": "mist", - "mdLink": "biolum", - "mdLinkUrl": "comment", - "mdCode": "kelp", - "mdCodeBlock": "fgSoft", - "mdCodeBlockBorder": "surface", - "mdQuote": "purple", - "mdQuoteBorder": "surface", - "mdHr": "surface", - "mdListBullet": "spray", - "toolDiffAdded": "kelp", - "toolDiffRemoved": "coral", - "toolDiffContext": "comment", - "syntaxComment": "comment", - "syntaxKeyword": "coral", - "syntaxFunction": "biolum", - "syntaxVariable": "spray", - "syntaxString": "kelp", - "syntaxNumber": "amber", - "syntaxType": "purple", - "syntaxOperator": "foam", - "syntaxPunctuation": "fgSoft", - "thinkingOff": "surface", - "thinkingMinimal": "comment", - "thinkingLow": "foam", - "thinkingMedium": "spray", - "thinkingHigh": "biolum", - "thinkingXhigh": "pink", - "bashMode": "amber" - } -} diff --git a/.pi/themes/rose-pine.json b/.pi/themes/rose-pine.json deleted file mode 100644 index fa7f211..0000000 --- a/.pi/themes/rose-pine.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json", - "name": "rose-pine", - "vars": { - "bg": "#1a1726", - "bgDark": "#161320", - "bgDeep": "#12101c", - "surface": "#242038", - "selection": "#2e2946", - "bgRed": "#2c1220", - "bgOrange": "#2a1c12", - "bgSky": "#122030", - "bgCyan": "#132a2e", - "bgWarm": "#2a1830", - "bgPink": "#301828", - "fg": "#ffffff", - "fgSoft": "#bbbbbb", - "comment": "#f0a8be", - "love": "#f47a9e", - "gold": "#f8cc85", - "rose": "#f0c4c4", - "pine": "#50b8d8", - "foam": "#a8e0ea", - "iris": "#d4a8ff", - "orchid": "#e088d0", - "ember": "#f09060", - "green": "#78e0a0" - }, - "colors": { - "accent": "iris", - "border": "orchid", - "borderAccent": "iris", - "borderMuted": "surface", - "success": "foam", - "error": "love", - "warning": "gold", - "muted": "comment", - "dim": "comment", - "text": "fg", - "thinkingText": "foam", - "selectedBg": "bgPink", - "userMessageBg": "bgWarm", - "userMessageText": "fg", - "customMessageBg": "bgCyan", - "customMessageText": "fg", - "customMessageLabel": "iris", - "toolPendingBg": "bgOrange", - "toolSuccessBg": "bgSky", - "toolErrorBg": "bgRed", - "toolTitle": "gold", - "toolOutput": "fgSoft", - "mdHeading": "love", - "mdLink": "foam", - "mdLinkUrl": "comment", - "mdCode": "gold", - "mdCodeBlock": "fgSoft", - "mdCodeBlockBorder": "surface", - "mdQuote": "rose", - "mdQuoteBorder": "surface", - "mdHr": "surface", - "mdListBullet": "iris", - "toolDiffAdded": "green", - "toolDiffRemoved": "love", - "toolDiffContext": "comment", - "syntaxComment": "comment", - "syntaxKeyword": "love", - "syntaxFunction": "foam", - "syntaxVariable": "fg", - "syntaxString": "gold", - "syntaxNumber": "iris", - "syntaxType": "pine", - "syntaxOperator": "orchid", - "syntaxPunctuation": "fgSoft", - "thinkingOff": "surface", - "thinkingMinimal": "comment", - "thinkingLow": "pine", - "thinkingMedium": "iris", - "thinkingHigh": "foam", - "thinkingXhigh": "love", - "bashMode": "ember" - } -} diff --git a/.pi/themes/synthwave.json b/.pi/themes/synthwave.json deleted file mode 100644 index 7f5657a..0000000 --- a/.pi/themes/synthwave.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json", - "name": "synthwave", - "vars": { - "bg": "#262335", - "bgDark": "#241b2f", - "bgDeep": "#1e1d2d", - "surface": "#34294f", - "selection": "#463465", - "bgRed": "#3d1018", - "bgRedWarm": "#301510", - "bgOrange": "#2e1f10", - "bgSky": "#1a2e4a", - "bgCyan": "#152838", - "bgWarm": "#4a1e6a", - "bgPink": "#35153a", - "comment": "#fede5d", - "fg": "#ffffff", - "fgSoft": "#bbbbbb", - "red": "#fe4450", - "cyan": "#36f9f6", - "yellow": "#fede5d", - "pink": "#ff7edb", - "green": "#72f1b8", - "orange": "#ff8b39", - "purple": "#c792ea", - "blue": "#4d9de0" - }, - "colors": { - "accent": "cyan", - "border": "pink", - "borderAccent": "cyan", - "borderMuted": "surface", - "success": "green", - "error": "red", - "warning": "orange", - "muted": "comment", - "dim": "comment", - "text": "fg", - "thinkingText": "#4a9e6a", - "selectedBg": "bgPink", - "userMessageBg": "bgWarm", - "userMessageText": "fg", - "customMessageBg": "bgCyan", - "customMessageText": "fg", - "customMessageLabel": "cyan", - "toolPendingBg": "bgOrange", - "toolSuccessBg": "bgSky", - "toolErrorBg": "bgRed", - "toolTitle": "orange", - "toolOutput": "fgSoft", - "mdHeading": "yellow", - "mdLink": "cyan", - "mdLinkUrl": "comment", - "mdCode": "yellow", - "mdCodeBlock": "fgSoft", - "mdCodeBlockBorder": "surface", - "mdQuote": "purple", - "mdQuoteBorder": "surface", - "mdHr": "surface", - "mdListBullet": "pink", - "toolDiffAdded": "green", - "toolDiffRemoved": "red", - "toolDiffContext": "comment", - "syntaxComment": "comment", - "syntaxKeyword": "red", - "syntaxFunction": "cyan", - "syntaxVariable": "fg", - "syntaxString": "yellow", - "syntaxNumber": "pink", - "syntaxType": "green", - "syntaxOperator": "cyan", - "syntaxPunctuation": "fgSoft", - "thinkingOff": "surface", - "thinkingMinimal": "comment", - "thinkingLow": "blue", - "thinkingMedium": "purple", - "thinkingHigh": "cyan", - "thinkingXhigh": "pink", - "bashMode": "orange" - } -} diff --git a/.pi/themes/tokyo-night.json b/.pi/themes/tokyo-night.json deleted file mode 100644 index 1db95bf..0000000 --- a/.pi/themes/tokyo-night.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json", - "name": "tokyo-night", - "vars": { - "bg": "#1a1b26", - "bgDark": "#141520", - "bg1": "#1e2030", - "bg2": "#252840", - "surface": "#2a2d48", - "selection": "#353860", - "bgRed": "#301420", - "bgOrange": "#2e1e14", - "bgSky": "#162040", - "bgCyan": "#142530", - "bgWarm": "#301848", - "bgPink": "#2d1430", - "comment": "#90e8ff", - "fg": "#ffffff", - "fgSoft": "#bbbbbb", - "blue": "#7eaaff", - "cyan": "#72dfff", - "magenta": "#c9a5ff", - "purple": "#b48ef5", - "green": "#a8e06a", - "red": "#ff7a94", - "orange": "#ffa55c", - "yellow": "#f0c060", - "teal": "#20d4b0" - }, - "colors": { - "accent": "blue", - "border": "purple", - "borderAccent": "cyan", - "borderMuted": "surface", - "success": "green", - "error": "red", - "warning": "orange", - "muted": "comment", - "dim": "comment", - "text": "fg", - "thinkingText": "teal", - "selectedBg": "bgPink", - "userMessageBg": "bgWarm", - "userMessageText": "fg", - "customMessageBg": "bgCyan", - "customMessageText": "fg", - "customMessageLabel": "cyan", - "toolPendingBg": "bgOrange", - "toolSuccessBg": "bgSky", - "toolErrorBg": "bgRed", - "toolTitle": "orange", - "toolOutput": "fgSoft", - "mdHeading": "yellow", - "mdLink": "cyan", - "mdLinkUrl": "comment", - "mdCode": "magenta", - "mdCodeBlock": "fgSoft", - "mdCodeBlockBorder": "surface", - "mdQuote": "green", - "mdQuoteBorder": "surface", - "mdHr": "surface", - "mdListBullet": "blue", - "toolDiffAdded": "green", - "toolDiffRemoved": "red", - "toolDiffContext": "comment", - "syntaxComment": "comment", - "syntaxKeyword": "magenta", - "syntaxFunction": "blue", - "syntaxVariable": "purple", - "syntaxString": "green", - "syntaxNumber": "orange", - "syntaxType": "cyan", - "syntaxOperator": "teal", - "syntaxPunctuation": "fgSoft", - "thinkingOff": "surface", - "thinkingMinimal": "comment", - "thinkingLow": "blue", - "thinkingMedium": "cyan", - "thinkingHigh": "magenta", - "thinkingXhigh": "red", - "bashMode": "yellow" - } -} diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 7f119f9..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,20 +0,0 @@ -# Pi vs CC — Extension Playground - -Pi Coding Agent extension examples and experiments. - -## Tooling -- **Package manager**: `bun` (not npm/yarn/pnpm) -- **Task runner**: `just` (see justfile) -- **Extensions run via**: `pi -e extensions/.ts` - -## Project Structure -- `extensions/` — Pi extension source files (.ts) -- `specs/` — Feature specifications -- `.pi/agents/` — Agent definitions for agent-team extension -- `.pi/agent-sessions/` — Ephemeral session files (gitignored) - -## Conventions -- Extensions are standalone .ts files loaded by Pi's jiti runtime -- Available imports: `@mariozechner/pi-coding-agent`, `@mariozechner/pi-tui`, `@mariozechner/pi-ai`, `@sinclair/typebox`, plus any deps in package.json -- Register tools at the top level of the extension function (not inside event handlers) -- Use `isToolCallEventType()` for type-safe tool_call event narrowing diff --git a/COMPARISON.md b/COMPARISON.md deleted file mode 100644 index 98ff420..0000000 --- a/COMPARISON.md +++ /dev/null @@ -1,243 +0,0 @@ -# Claude Code vs Pi Agent — Feature Comparison - -> Pi v0.52.10 vs Claude Code (Feb 2026) - ---- - -## Design Philosophy - -| Dimension | Claude Code | Pi Agent | Winner | -| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| Core Mantra | "Tool for every engineer" — batteries-included, accessible to all skill levels | "If I don't need it, it won't be built" — minimal, opinionated, built for one engineer's workflow | Both | -| Approach to Features | Ship everything built-in (sub-agents, teams, MCP, plan mode, todos, web search, notebooks, 10+ tools) | Ship the minimum (4 tools, ~200-token prompt). Everything else is opt-in via extensions or bash | Both | -| Safety Philosophy | Safe by default — deny-first permissions, 5 modes, filesystem sandbox, Haiku pre-screening of commands | YOLO by default — no permissions, no sandbox. "Security in coding agents is mostly theater; if it can write and run code, it's game over" | Both | -| System Prompt Trust Model | Extensive guardrails (~10K tokens) — behavioral rules, formatting instructions, safety constraints, tool usage examples | Trust the model (~200 tokens) — "frontier models have been RL-trained up the wazoo, they inherently understand what a coding agent is" | Both | -| Observability | Abstracted — sub-agents are black boxes, compaction happens silently, system prompt not user-visible by default | Full transparency — every token visible, every tool call inspectable, no hidden orchestration, session HTML export | Pi | -| Context Engineering | Managed for you — auto-compaction, sub-agents handle overflow, system decides what enters context | User-controlled — minimal prompt overhead, no hidden injections, "exactly controlling what goes into context yields better outputs" | Pi | -| Extensibility Model | Shell hooks (external processes) + MCP protocol + Skills (markdown prompts) — loosely coupled, config-driven | TypeScript in-process extensions — same runtime, access full session state, block/modify/transform any event | Both | -| Target Audience | Every engineer — beginner-friendly, enterprise-ready, guided workflows, progressive disclosure | Power users — engineers who want control, understand tradeoffs, willing to build their own workflows | Both | -| Multi-Model Stance | Claude-first — optimized for Claude family, gateway workaround for others | Model-agnostic from day one — 324 models, 20+ providers, cross-provider context handoff, "we live in a multi-model world" | Pi | -| Planning Approach | Built-in plan mode — structured explore → plan → code phases, read-only mode, dedicated sub-agents | No plan mode — "just tell the agent to think with you." Write plans to files for persistence, versioning, and cross-session reuse | Both | -| Complexity Budget | Complexity lives in the harness so you don't have to think about it — more magic, less wiring | Complexity lives in your hands — minimal harness, you decide what to add and when. "Three similar lines of code is better than a premature abstraction" | Both | - ---- - -## Cost & Licensing - -| Feature | Claude Code | Pi Agent | Winner | -| ---------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ------ | -| Tool License | Proprietary | MIT (open source, fork/embed/self-host) | Pi | -| Subscription Cost | $20-200/mo required or Dedicated Anthropic API Keys | $0 (MIT, BYO API keys) | Pi | -| Cost Tracking | Available via /cost command, customizable via statusline configuration | Real-time $/token/cache display in footer per session and customizable via extensions | Both | -| Cost Optimization | 3 models at 3 price tiers (Opus > Sonnet > Haiku) — single provider | Mix cheap/expensive models per task across any provider, free tiers available | Pi | -| System Prompt Overhead | ~10,000+ tokens | ~200 tokens (more context for actual work) | Pi | - - ---- - -## Model & Provider Support - -| Feature | Claude Code | Pi Agent | Winner | -| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | -| Official Providers | 4 platforms (Anthropic API, AWS Bedrock, Google Vertex, Foundry) — all serving Claude models | 20+ native (Anthropic, OpenAI, Google, Groq, xAI, OpenRouter, Azure, Bedrock, Vertex, Mistral, MiniMax, Kimi, Cerebras, ZAI, HuggingFace, custom) | Pi | -| Non-Anthropic / Self-Hosted Models | Via ANTHROPIC_BASE_URL gateway — routes to any OpenAI-compatible backend (OpenRouter, LiteLLM, local TGI, vLLM). Functional but unofficial workaround | Native first-class support for all providers + local (Ollama, vLLM, LM Studio via models.json). No proxy needed | Pi | -| Built-in Models | ~6 aliases (opus, sonnet, haiku, opusplan, sonnet[1m], default) mapping to Claude family | 324 (confirmed via ModelRegistry) across all providers | Pi | -| Model Switching Mid-Session | Yes — `/model ` command, `--model` flag at startup, ANTHROPIC_MODEL env var | Yes — Ctrl+P cycle, Ctrl+L fuzzy selector, session.setModel() in SDK | Tie | -| OAuth/Subscription Login | Anthropic subscriptions (Pro, Max, Teams, Enterprise) | Claude Pro, ChatGPT Plus, GitHub Copilot, Gemini CLI, Antigravity — all via /login and API keys | Pi | -| Thinking/Effort Levels | 3 effort levels (low/medium/high) on Opus 4.6 via `/model` slider, env var, or settings | 5 unified levels (off/minimal/low/medium/high) across ALL thinking capable models, Shift+Tab to cycle | Pi | - ---- - -## Agent Harness - -| Feature | Claude Code | Pi Agent | Winner | -| ------------------------ | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| Source Code | Closed source (proprietary) | Open source (MIT license) | Pi | -| System Prompt Size | ~10,000+ tokens (extensive tool descriptions, behavioral rules, safety guardrails) | ~200 tokens (minimal — trusts frontier models to code without hand-holding) | Pi | -| Default Tools | 10+ (Read, Write, Edit, Bash, Glob, Grep, WebSearch, WebFetch, NotebookEdit, Task) | 4 (read, write, edit, bash) + 3 optional (grep, find, ls) | Both | -| Agent Architecture | Monorepo TypeScript CLI — single package with built-in tool execution, sub-agents, and team coordination | 4-package monorepo (pi-ai, pi-agent-core, pi-tui, pi-coding-agent) — modular separation of LLM abstraction, agent loop, TUI, and CLI | Both | -| Sub-Agent Support | Native Task tool — 7 parallel sub-agents, permission inheritance, typed agent roles (Explore, Plan, Bash, general-purpose) | None built-in, but available through extension that spawns separate pi processes in single/parallel/chain modes with different models per sub-agent | Claude Code | -| Agent Teams | Native team coordination (lead + workers, shared task lists, message passing, broadcast) | None built-in, but achievable through SDK orchestration scripts or RPC mode driving multiple pi processes | Claude Code | -| Default Permission Model | 5 modes (default, plan, acceptEdits, bypassPermissions, dontAsk) — deny-first with filesystem/network sandbox | None by default ("YOLO mode") — runs everything without asking. Permission-gate extension available but opt-in | Claude Code | -| Memory File | CLAUDE.md (project root, nested dirs, user-level) — auto-loaded, hierarchical | AGENTS.md — similar convention, compatible with ~/.claude/skills cross-tool standard | Tie | -| Cost Visibility | Available via /cost command, customizable via statusline configuration | Immediately visible in footer by default, further customizable via extensions and getSessionStats() API | Tie | -| Hooks System | Shell-command hooks (PreToolUse, PostToolUse, Stop, Notification) — external scripts, pass/fail | TypeScript extension events (20+ types) — in-process async handlers that block, modify, transform, access session state, render UI | Pi | -| Session Format | Linear conversation | JSONL tree with id/parentId (branching, forking, labels via /tree and /fork) | Pi | -| Extension State | No built-in state persistence for extensions | pi.appendEntry() persists custom data to session, survives restart | Pi | - ---- - -## Tools & Capabilities - -### Built-in Tools (Tool-by-Tool) - -| Tool | Claude Code | Pi Agent | Winner | -| ----------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | ----------- | -| Read | Built-in — reads files with optional offset/limit, images, PDFs, notebooks | Built-in (`read`) — reads files with optional range, auto-resizes images | Tie | -| Write | Built-in — creates or overwrites files | Built-in (`write`) — creates or overwrites files | Tie | -| Edit | Built-in — exact string replacement with replace_all option | Built-in (`edit`) — surgical find-and-replace, returns unified diff | Tie | -| Bash | Built-in — shell execution with timeout, background mode, description | Built-in (`bash`) — shell execution with streaming output and abort | Tie | -| Glob | Built-in — fast file pattern matching, sorted by modification time | Not built-in. Optional `find` tool available via `--tools` flag | Claude Code | -| Grep | Built-in — ripgrep-powered search with regex, context lines, output modes | Not built-in by default. Optional `grep` tool available via `--tools` flag | Tie | -| WebSearch | Built-in — web search with domain filtering, returns formatted results | Not built-in, customizable via extensions | Claude Code | -| WebFetch | Built-in — fetches URL content, converts HTML to markdown, AI processing | Not built-in, customizable via extensions | Claude Code | -| NotebookEdit | Built-in — Jupyter notebook cell editing (replace, insert, delete) | Not built-in, customizable via extensions | Claude Code | -| Task (Sub-agents) | Built-in — spawns typed sub-agents (Explore, Plan, Bash, general-purpose) with parallel execution | Not built-in, customizable via extensions. Subagent extension spawns separate pi processes | Claude Code | -| ls | Not a dedicated tool (use Bash or Glob) | Optional built-in (`ls`) via `--tools` flag | Tie | - -### Tool System Capabilities - -| Feature | Claude Code | Pi Agent | Winner | -| --------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------- | ----------- | -| Tool Observability | Sub-agent tool calls opaque | Every tool call, token, and dollar visible | Pi | -| Custom Tools | Via MCP servers (external process, JSON-RPC) | pi.registerTool() in-process TypeScript, streaming results, custom rendering | Pi | -| Tool Override | Not possible | Register tool with same name to replace built-in (e.g., audited read) | Pi | -| MCP Support | Native first-class, lazy loading (95% context reduction), OAuth | Not built-in (by design, argues 7-14k token overhead); available via extensions | Claude Code | -| Tool Count Philosophy | More tools = more capable out of the box (10+) | Fewer tools = smaller system prompt (~1000 tokens), trusts frontier models | Both | - ---- - -## Hooks & Event System - -> Claude Code: **14 hook events**, 3 handler types (command, prompt, agent) — shell-based, JSON stdin/stdout -> Pi: **25 extension events** across 7 categories — in-process TypeScript with full API access - -### Architecture - -| Feature | Claude Code | Pi Agent | Winner | -| ------------------------ | ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------ | -| Hook Language | Shell commands (any language), LLM prompts, or agent subprocesses | TypeScript (in-process, zero-build via jiti) | Pi | -| Handler Types | 3: command (shell), prompt (LLM eval), agent (multi-turn subagent) | 1: async TypeScript handler with full session/UI access | Both | -| Hook Configuration | JSON in settings files (.claude/settings.json, managed policy, plugin hooks.json, skill/agent frontmatter) | TypeScript code in extension files | Both | -| Can Modify Tool Input | Yes — updatedInput in PreToolUse/PermissionRequest | Yes — return modified args from tool_call handler | Tie | -| Async/Background Hooks | Yes — async: true on command hooks (non-blocking, results delivered next turn) | Yes — handlers are async by default, can fire-and-forget | Tie | -| Inter-Hook Communication | No built-in | Yes — pi.events shared event bus between extensions | Pi | - -### Hook-by-Hook Mapping - -| Lifecycle Point | Claude Code Hook | Pi Extension Event(s) | Notes | -| ----------------------------- | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------- | -| Session starts | `SessionStart` (matcher: startup/resume/clear/compact) | `session_start` | CC can persist env vars via CLAUDE_ENV_FILE. Both can inject context | -| User submits prompt | `UserPromptSubmit` — can block prompt, add context | `input` — can block, transform text, or handle entirely | Pi also distinguishes source: interactive/rpc/extension | -| Before tool executes | `PreToolUse` — allow/deny/ask, modify input | `tool_call` — block with reason, modify args, typed per-tool | Both can intercept and modify. Pi has typed narrowing via isToolCallEventType | -| Permission dialog shown | `PermissionRequest` — auto-allow/deny on behalf of user | N/A (Pi has no permission system by default) | CC-only — Pi runs YOLO by default, permission-gate is an extension | -| After tool succeeds | `PostToolUse` — feedback to Claude, modify MCP output | `tool_result` — modify results, log, transform output | Comparable | -| After tool fails | `PostToolUseFailure` — add context about the failure | `tool_result` (isError flag) | CC has a dedicated event; Pi uses same event with error flag | -| Tool execution streaming | N/A | `tool_execution_start`, `tool_execution_update`, `tool_execution_end` | Pi-only — real-time streaming of tool execution progress | -| Bash spawn intercept | N/A | BashSpawnHook — modify command, cwd, env before bash executes | Pi-only — intercepts at process spawn level | -| User runs bash directly | N/A | `user_bash` — fired when user types shell commands (!! prefix) | Pi-only | -| Notification sent | `Notification` (matcher: permission_prompt/idle_prompt/auth_success/elicitation_dialog) | N/A (use ctx.ui.notify in any handler) | CC-only as a hook event | -| Subagent spawned | `SubagentStart` (matcher: agent type) | N/A (Pi has no built-in subagents) | CC-only | -| Subagent finished | `SubagentStop` — can prevent subagent from stopping | N/A | CC-only | -| Agent stops responding | `Stop` — can force Claude to continue | N/A (use turn_end or agent_end to react) | CC can block stopping; Pi can't but can queue follow-ups | -| Teammate goes idle | `TeammateIdle` — can force teammate to continue | N/A (Pi has no built-in teams) | CC-only | -| Task marked complete | `TaskCompleted` — can block completion | N/A (Pi has no built-in task system) | CC-only | -| Before compaction | `PreCompact` (matcher: manual/auto) | `session_before_compact` — can provide custom compaction entirely | Pi can fully replace compaction logic | -| After compaction | N/A | `session_compact` | Pi-only | -| Before session branching | N/A | `session_before_fork`, `session_before_switch`, `session_before_tree` | Pi-only — session tree architecture | -| After session branching | N/A | `session_fork`, `session_switch`, `session_tree` | Pi-only | -| Session ends | `SessionEnd` (matcher: clear/logout/exit/other) — no decision control | `session_shutdown` | Both fire on exit; neither can block | -| Before agent processes prompt | N/A | `before_agent_start` — can modify system prompt, images, prompt text | Pi-only — dynamic system prompt per-turn | -| Agent turn lifecycle | N/A | `agent_start`, `agent_end`, `turn_start`, `turn_end` | Pi-only — granular agent lifecycle | -| Message streaming | N/A | `message_start`, `message_update`, `message_end` | Pi-only — token-by-token streaming access | -| Model changed | N/A | `model_select` (source: set/cycle/restore) | Pi-only — react to model switches | -| Context window access | N/A | `context` — deep copy of messages, can filter/prune | Pi-only — direct context manipulation | - ---- - -## Extensions & Customization - -| Feature | Claude Code | Pi Agent | Winner | -| ------------------------------ | ------------------------------------------ | ------------------------------------------------------------------------------- | ------ | -| Extension Language | Shell scripts (hooks), Markdown (commands) | TypeScript (zero-build via jiti) | Pi | -| Slash Commands | .claude/commands/*.md prompt templates | Prompt templates + /skill:name + extension-registered commands | Tie | -| Package Distribution | Plugin marketplace — `/plugin` commands, git-based sharing | pi install npm:/git:/local, pi config TUI, npm gallery | Both | -| Skills (Agent Skills Standard) | Yes (auto-invocation) | Yes (progressive disclosure, cross-tool compat with ~/.claude/skills) | Tie | -| Themes | Minimally customizable | 51 color tokens, hot-reload, dark/light built-in, community themes via packages | Pi | -| Custom Keyboard Shortcuts | ~/.claude/keybindings.json | pi.registerShortcut() in extensions | Tie | -| Custom CLI Flags | Not possible | pi.registerFlag() adds custom flags to CLI | Pi | -| Custom Providers | Not possible | pi.registerProvider() with OAuth support | Pi | -| Custom Editors | Not possible | Modal editor (vim), emacs bindings, rainbow editor via extensions | Pi | - ---- - -## UI & Terminal - -| Feature | Claude Code | Pi Agent | Winner | -| --------------- | --------------------------------------- | ------------------------------------------------------------------------- | ------ | -| Custom Header | No | ctx.ui.setHeader() replaces logo/keybinding hints with custom component | Pi | -| Custom Footer | Configurable statusline (tokens, cost, model) | ctx.ui.setFooter() with git branch, token stats, cost tracking, anything | Pi | -| Status Line | Configurable statusline (tokens, cost, model) | ctx.ui.setStatus() with themed colors, turn tracking, custom data | Pi | -| Widgets | No | ctx.ui.setWidget() above/below editor with custom content | Pi | -| Overlays | No | Full overlay applications (Doom, Space Invaders, QA test overlays) | Pi | -| Dialogs | Basic permission prompts | ctx.ui.select(), confirm(), input(), editor() + custom rendering | Pi | -| Rendering | Standard terminal with known issues | Standard terminal with known issues | Both | -| Message Queuing | Supported — queue messages while agent works | Enter = steer (interrupt), Alt+Enter = follow-up (queue after completion) | Both | - ---- - -## Programmatic & SDK - -| Feature | Claude Code | Pi Agent | Winner | -| -------------------- | ------------------------------ | ----------------------------------------------------------------------------- | ------ | -| Non-Interactive Mode | claude --print | pi -p (+ stdin auto-activates) | Tie | -| JSON Streaming | --output-format stream-json | --mode json (JSONL events with full lifecycle) | Tie | -| RPC Mode | None | --mode rpc (26+ commands, bidirectional JSON protocol, any language) | Pi | -| Node.js SDK | @anthropic-ai/claude-agent-sdk | @mariozechner/pi-coding-agent (createAgentSession, full internal API) | Tie | -| Mid-Stream Control | ClaudeSDKClient.interrupt() — stop and redirect | steer() interrupts, followUp() queues messages while agent works | Pi | -| Session Stats API | Limited | getSessionStats() returns tokens (in/out/cache), cost, tool calls per session | Pi | -| HTML Export | /export — session to HTML | --export, /export, session.exportToHtml() | Both | -| SDK Examples | Docs-based | 12 official examples from minimal to full-control in package | Pi | - ---- - -## Multi-Agent & Orchestration - -| Feature | Claude Code | Pi Agent | Winner | -| ------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------ | ----------- | -| Sub-Agents | Native Task tool, 7 parallel, permission inheritance | Subagent extension (single/parallel/chain modes), spawns separate pi processes | Claude Code | -| Agent Teams | Native team coordination (lead + workers) | No built-in equivalent; use orchestration scripts | Claude Code | -| Multi-Model Orchestration | Not possible (single provider) | Different models per sub-agent (scout on flash, worker on opus) | Pi | - ---- - -## Enterprise & Platform - -| Feature | Claude Code | Pi Agent | Winner | -| ---------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------ | ----------- | -| IDE Integration | VS Code, JetBrains, Cursor (inline diffs, @mentions) | Terminal-only (could integrate via RPC) | Claude Code | -| Web/Mobile/Desktop | claude.ai/code, iOS app, desktop app | Terminal only | Claude Code | -| Enterprise SSO/Audit | Yes (SSO, MFA, audit logs, admin dashboard) | No | Claude Code | -| Permissions/Sandboxing | 5 modes, deny-first rules, filesystem/network sandbox | None by default ("YOLO mode"); permission-gate extension available | Claude Code | -| Git Integration | Deep (commits, PRs, merge conflicts, GitHub Actions, GitLab CI) | Via bash; git-checkpoint extension available | Claude Code | -| Slack/Chat Integration | Native @Claude mentions to PRs | pi-mom Slack bot package | Claude Code | - ---- - -## Sharing & Distribution - -| Feature | Claude Code | Pi Agent | Winner | -| -------------------------- | ------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ----------- | -| Package System | Plugin marketplace — `/plugin` commands, `.claude-plugin/plugin.json` manifest | `pi install npm:/git:/local` — `package.json` with `pi` key, `pi-package` npm keyword | Both | -| What's Bundled | Skills, agents, hooks, MCP servers, LSP servers | Extensions, skills, prompt templates, themes | Both | -| Distribution Sources | Marketplace (GitHub repo, git URL, npm, pip, direct URL, local path) | npm registry, git (GitHub/GitLab/SSH), local paths — no intermediate marketplace needed | Both | -| Discovery | Official `claude-plugins-official` marketplace + team/community marketplaces via `extraKnownMarketplaces` | npm search (`pi-package` keyword) + gallery at shittycodingagent.ai/packages with video/image previews | Both | -| Scope | User, project, local, managed (enterprise) — namespaced as `plugin-name:skill-name` | Global (`~/.pi/`) or project (`.pi/`, `-l` flag) — project settings auto-install missing packages on startup | Tie | -| Config UI | `/plugin` slash commands for install/browse/manage | `pi config` interactive TUI for enable/disable per-resource | Tie | -| Try Without Installing | No equivalent | `pi -e npm:@foo/bar` — ephemeral install for current session only | Pi | -| Cross-Tool Portability | Agent Skills standard (agentskills.io) — shared with VS Code, Codex, Cursor, GitHub | No cross-tool standard — Pi-specific extensions | Claude Code | -| Enterprise Controls | `strictKnownMarketplaces`, allowlists by repo/URL/host regex, managed plugin deployment | No enterprise controls — trust-based, review source before installing | Claude Code | -| Git-Based Sharing | `.claude/` directory (settings, skills, agents, rules, hooks) committed to repo — team gets config on clone | `.pi/settings.json` with packages — team gets packages auto-installed on startup | Tie | -| Update Mechanism | Marketplace auto-updates at startup (configurable) | `pi update` for non-pinned packages, version pinning with `@version` | Tie | -| Package Filtering | Plugin resources loaded as-is (namespaced to prevent conflicts) | Glob patterns + `!exclusions` per resource type, force-include/exclude exact paths | Pi | - ---- - -## Community & Ecosystem - -| Feature | Claude Code | Pi Agent | -| ---------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------- | -| Creator | Anthropic — $1B+ ARR, enterprise AI company | Mario Zechner — libGDX creator (24.8K stars), solo maintainer | -| Traction | Enterprise adoption, deep IDE integrations, massive user base | 11.5K stars, 3.17M monthly npm downloads, 208 versions | -| Endorsements | Enterprise customers, Anthropic ecosystem | Armin Ronacher (Flask/Ruff) uses + contributes, powers OpenClaw (145K stars) | -| Release Velocity | Regular releases | 10+ releases in 8 days, new model support within hours | diff --git a/PI_VS_OPEN_CODE.md b/PI_VS_OPEN_CODE.md deleted file mode 100644 index c810112..0000000 --- a/PI_VS_OPEN_CODE.md +++ /dev/null @@ -1,178 +0,0 @@ -# Pi Agent vs OpenCode — Customization & Control Comparison - -> Pi v0.52+ vs OpenCode v1.1+ (Feb 2026) -> -> **Thesis:** Pi and OpenCode are both MIT-licensed, open-source, model-agnostic terminal coding agents. But they represent fundamentally different architectures. Pi is a **programmable platform** — a minimal harness with 25+ in-process TypeScript hooks that let you build your own agent experience. OpenCode is a **configurable product** — a full-featured Claude Code alternative with JSON-driven settings and a plugin system for extras. The distinction matters: Pi gives you control at the *runtime* level. OpenCode gives you control at the *configuration* level. - ---- - -## The Core Architectural Split - -| Dimension | Pi Agent | OpenCode | -| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **What it ships** | 4 tools, ~200-token system prompt, 25+ extension events. Everything else is opt-in. | 12+ tools, built-in sub-agents, Plan mode, LSP, web search, MCP, permissions, desktop app. | -| **Extension model** | In-process TypeScript. Extensions run in the same runtime as the agent loop. They can intercept, block, modify, and transform any event in real-time. | Out-of-process plugins. JS/TS files in a config directory that subscribe to events and register tools. | -| **Customization ceiling** | Effectively unlimited — you can replace the entire UI, override any tool, inject custom system prompts per-turn, build full overlay applications (Doom, Space Invaders, QA tools), and orchestrate multi-agent pipelines. | Bounded — you can add tools, hook into tool execution, customize compaction, and configure permissions via JSON. But you cannot modify the TUI, inject custom UI components, or intercept the input/agent lifecycle at the same depth. | -| **Philosophy** | "If I don't need it, it won't be built. Build what you need." | "Ship a polished, complete product. Configure what you need." | -| **Closest analogy** | A race car chassis + engine. You design the body, aero, and electronics. | A production car with a tuning package. You adjust settings and bolt on accessories. | - ---- - -## Extension / Plugin System — Deep Comparison - -This is the single most important comparison between these two tools. Pi's extension system is architecturally different from OpenCode's plugin system — not just in API surface, but in *where code runs* and *what it can touch*. - -### Architecture - -| Feature | Pi Extensions | OpenCode Plugins | Winner | -| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | -| Runtime model | **In-process** — extensions execute in the same Bun/Node.js runtime as the agent loop. Zero serialization overhead. Direct access to session state, UI context, and the event stream. | **Separate module** — plugins are loaded from `.opencode/plugins/` or npm. They receive a context object and return hook handlers. Communication happens through the SDK client. | Pi | -| Build step | None — TypeScript executed via jiti at runtime. Write `.ts`, run immediately. | None — TypeScript supported natively via Bun loader. | Tie | -| Composability | Stack multiple extensions with `-e` flags: `pi -e ext1.ts -e ext2.ts`. Extensions can communicate via `pi.events` shared bus. | Multiple plugins loaded from directory. No built-in inter-plugin communication channel. | Pi | -| Ephemeral testing | `pi -e npm:@foo/bar` — try a package without installing | Not possible — must add to config or plugin directory | Pi | - -### Event Coverage - -This is where the gap is widest. Pi exposes 25+ typed events across 7 categories. OpenCode exposes ~20 events but skips critical lifecycle hooks. - -| Lifecycle Point | Pi Extension Event | OpenCode Plugin Hook | Gap Analysis | -| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Session starts** | `session_start` | `session.created` | Comparable | -| **User submits prompt** | `input` — can **block**, transform text, or handle entirely | ❌ Not available | **Pi-only.** This is huge — Pi can gate all user input, inject context, redirect prompts, or prevent execution before the agent ever sees it. OpenCode has no equivalent. | -| **Before agent processes prompt** | `before_agent_start` — can modify system prompt, images, prompt text **per-turn** | ❌ Not available | **Pi-only.** Dynamic system prompt injection on every turn. The purpose-gate extension uses this to inject session intent into every agent call. | -| **Agent turn lifecycle** | `agent_start`, `agent_end`, `turn_start`, `turn_end` | `session.idle` (only fires when agent finishes) | **Pi has 4x granularity.** OpenCode only knows when the agent is done, not when turns start/end within a session. | -| **Before tool executes** | `tool_call` — block with reason, modify args, typed per-tool via `isToolCallEventType()` | `tool.execute.before` — can modify args, throw to block | Both can intercept. Pi has typed narrowing per tool (bash, read, write, edit). | -| **After tool executes** | `tool_result` — modify results, log, transform output | `tool.execute.after` — react to results | Comparable | -| **Tool execution streaming** | `tool_execution_start`, `_update`, `_end` | ❌ Not available | **Pi-only.** Real-time streaming of tool output as it happens — critical for building live progress UIs. | -| **Bash spawn intercept** | `BashSpawnHook` — modify command, cwd, env vars **before the process spawns** | `shell.env` — inject env vars into shell execution | Pi intercepts at process spawn level. OpenCode only injects env vars. | -| **Message streaming** | `message_start`, `message_update`, `message_end` — token-by-token access | `message.part.updated`, `message.updated` | Both have message events; Pi is more granular with token-level streaming. | -| **Model changed** | `model_select` (source: set/cycle/restore) | ❌ Not available | **Pi-only.** React to model switches programmatically. | -| **Context window access** | `context` — deep copy of all messages, can filter and prune | ❌ Not available | **Pi-only.** Direct manipulation of what's in the context window. No other tool offers this. | -| **Before compaction** | `session_before_compact` — can **replace compaction logic entirely** | `experimental.session.compacting` — inject context or replace prompt | Both can customize. Pi can replace the entire compaction flow; OpenCode can replace the prompt. | -| **Session branching** | `session_before_fork`, `session_fork`, `session_before_switch`, `session_switch`, `session_before_tree`, `session_tree` | ❌ Not applicable (no branching model) | **Pi-only.** Pi's JSONL tree session format supports forking/branching. OpenCode uses linear SQLite sessions. | -| **Permission events** | Not applicable (YOLO by default) | `permission.asked`, `permission.replied` | OpenCode-only — but Pi can build equivalent or better permission systems via `tool_call` blocking. | -| **LSP events** | Not built-in | `lsp.client.diagnostics`, `lsp.updated` | OpenCode-only — native LSP integration. | -| **File watcher** | Not built-in | `file.watcher.updated` | OpenCode-only. | -| **Todo events** | Not built-in | `todo.updated` | OpenCode-only. | - -**Summary: Pi has 8+ hook points that OpenCode simply doesn't expose**, including the critical `input`, `before_agent_start`, agent lifecycle, tool execution streaming, context window access, and session branching hooks. These aren't minor — they're the hooks you need to build fundamentally different agent behaviors. - -### UI Customization - -This is the other dimension where Pi is in a different category entirely. - -| Feature | Pi Extensions | OpenCode Plugins | Winner | -| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | ------ | -| Custom header | `ctx.ui.setHeader()` — replace the logo and keybinding hints with any content | ❌ Not possible | Pi | -| Custom footer | `ctx.ui.setFooter()` — git branch, token stats, cost tracking, tool counters, anything | ❌ Not possible | Pi | -| Status line | `ctx.ui.setStatus()` — themed colors, turn tracking, custom data | ❌ Not possible | Pi | -| Widgets | `ctx.ui.setWidget(key, renderFn)` — persistent UI panels above/below the editor. Used for subagent progress, task lists, tool counters, purpose display. | ❌ Not possible | Pi | -| Overlays | Full overlay applications — session replay timeline, game overlays (Doom), QA tools | ❌ Not possible | Pi | -| Dialogs | `ctx.ui.select()`, `confirm()`, `input()`, `editor()` — interactive prompts with custom rendering | ❌ Not available to plugins (only built-in permission dialogs) | Pi | -| Custom editors | vim modal editor, emacs bindings, rainbow editor — all via extensions | ❌ Not possible | Pi | -| Notifications | `ctx.ui.notify()` from any handler | `osascript` or desktop app notifications via plugin | Both | -| Theme system | 51 color tokens, hot-reload, dark/light, custom themes via packages | Theme customization via `tui.json` | Pi | - -**OpenCode's TUI is polished but closed.** It's built with Bubble Tea (Go) and the Ink React renderer, and it looks great out of the box. But you can't inject custom UI components, widgets, or overlays into it from a plugin. What you see is what you get. - -**Pi's TUI is a canvas.** The extensions API gives you full control over every UI surface — header, footer, status line, widgets above/below the editor, fullscreen overlays, and interactive dialogs. The pi-vs-claude-code repo demonstrates this with 16 extensions that completely transform the agent experience. - -### Registration APIs - -| What you can register | Pi | OpenCode | -| ----------------------------- | --------------------------------------------------------------------- | --------------------------------------------------------- | -| Custom tools | `pi.registerTool()` — in-process, streaming results, custom rendering | `tool()` helper in plugins — Zod schema, execute function | Tie | -| Override built-in tools | Register tool with same name → replaces built-in | Plugin tool with same name → takes precedence | Tie | -| Custom slash commands | `pi.registerCommand()` + prompt templates in `.pi/prompts/` | Markdown files in `.opencode/commands/` | Tie | -| Custom CLI flags | `pi.registerFlag()` — adds flags to the `pi` CLI | ❌ Not possible | Pi | -| Custom keyboard shortcuts | `pi.registerShortcut()` | Configurable via `tui.json` keybinds (JSON, not code) | Pi | -| Custom providers | `pi.registerProvider()` with OAuth support | JSON config in `opencode.json` with npm AI SDK packages | Pi | -| Persistent extension state | `pi.appendEntry()` — survives restarts, stored in session JSONL | ❌ Not available to plugins | Pi | -| Inter-extension communication | `pi.events` shared event bus | ❌ Not available | Pi | - ---- - -## What OpenCode Does Better (And Why It Still Matters) - -The thesis isn't "Pi is better." It's "Pi is more controllable." OpenCode has real advantages that come from its product-first approach: - -| Feature | Why OpenCode Wins | Pi's Alternative | -| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | -| **Batteries included** | 12+ tools, LSP, web search, MCP, permissions, todo tracking — zero configuration needed. You install and start coding. | You build what you need with extensions, or install pi packages. Higher ceiling, higher floor. | -| **Built-in Plan mode** | Tab to switch between Build (full access) and Plan (read-only). No setup. | No plan mode. "Just tell the agent to think." Or build it with an extension. | -| **Native MCP support** | stdio and remote MCP servers configured in JSON. First-class integration. | Not built-in by design (argues 7-14K token overhead). Available via extensions. | -| **Permission system** | Granular allow/deny/ask with glob patterns per tool, per agent, per command pattern. `.env` files denied by default. Doom loop detection. | YOLO by default. The damage-control extension builds equivalent functionality — but you have to build or install it. | -| **LSP integration** | go-to-definition, find references, hover, document symbols, call hierarchy — all built in. | Not available. Would need to be built as an extension. | -| **Client/server architecture** | TUI is one client. Desktop app, VS Code extension, web UI, and mobile are others. `opencode serve` exposes an HTTP API. | Terminal only. RPC mode over stdin/stdout for programmatic access, but no multi-client architecture. | -| **Desktop app** | Native app for macOS, Windows, Linux. | No. | -| **GitHub/GitLab integration** | Comment `@opencode` on issues/PRs. GitLab Duo OAuth. | Via bash. | -| **Massive community** | 104K stars, 735 contributors, 9,200+ commits. Issues get fixed fast. | ~8.9K stars (pi-mono), solo maintainer + approved contributors. Smaller but passionate community. | -| **Organizational config** | `.well-known/opencode` endpoint for enterprise defaults. | Not available. | - ---- - -## Concrete Examples: What Pi's Control Enables - -These aren't theoretical. They're actual extensions in the pi-vs-claude-code repo. - -### 1. Purpose Gate (Input Interception + Dynamic System Prompts + Custom Widgets) -Forces the engineer to declare session intent before any work begins. Uses `input` to block all prompts until purpose is set, `before_agent_start` to inject purpose into the system prompt on every turn, and `setWidget` to display it persistently. - -**Could OpenCode do this?** Partially. You could create a custom agent with a specific prompt, but you can't block user input, you can't inject per-turn system prompts, and you can't add a persistent widget to the UI. - -### 2. Damage Control (Safety Auditing via tool_call Interception) -Intercepts every tool call, checks against YAML-defined rules (dangerous bash patterns, zero-access paths, read-only paths, no-delete paths), and blocks or prompts for confirmation. Uses typed narrowing (`isToolCallEventType`) to extract tool-specific args. - -**Could OpenCode do this?** Partially — OpenCode's `tool.execute.before` can throw to block tool calls, and the built-in permission system covers common cases. But Pi's version is fully programmable with custom rules and custom UI (confirm dialogs, status line updates, persistent logging via `appendEntry`). - -### 3. Subagent Widget (Multi-Agent Orchestration with Live UI) -`/sub` spawns background Pi processes as sub-agents. Each gets its own persistent session file, live-streaming progress widget, and independent model. `/subcont` continues a subagent's conversation. Widgets stack in the UI showing real-time status. - -**Could OpenCode do this?** OpenCode has built-in subagents (@general, custom agents), but you can't add live-updating widgets to the UI, you can't show streaming progress from multiple agents simultaneously, and you can't continue a specific subagent's conversation across turns. - -### 4. Agent Team (Dispatcher Orchestration with Grid Dashboard) -The primary agent becomes a pure dispatcher — it reads your prompt, picks a specialist from a YAML roster, and delegates via a `dispatch_agent` tool. A grid dashboard widget shows all agents and their status. - -**Could OpenCode do this?** OpenCode has custom agents, but the dispatcher pattern with a grid dashboard UI and real-time status widgets isn't possible through plugins. - -### 5. Theme Cycler (Full TUI Theming) -Ctrl+X/Ctrl+Q keyboard shortcuts cycle through custom themes. `/theme` command opens a selector. Hot-reload with 51 color tokens. - -**Could OpenCode do this?** OpenCode has themes via `tui.json`, but no keyboard shortcuts for cycling and no extension-level theme registration. - ---- - -## The OpenCode Clone Thesis - -You asked whether OpenCode is more of a "Claude Code copycat." The evidence supports this framing. Here's what Claude Code ships vs. what OpenCode ships vs. what Pi ships: - -| Feature | Claude Code | OpenCode | Pi | -| --------------------- | ------------------------------ | ----------------------------------- | -------------------------------- | -| Built-in sub-agents | ✅ Native Task tool, 7 parallel | ✅ General subagent + custom agents | ❌ Build with extensions | -| Plan mode | ✅ Built-in plan mode | ✅ Tab to switch to Plan agent | ❌ "Just tell the agent to think" | -| Permission system | ✅ 5 modes, deny-first | ✅ allow/deny/ask with glob patterns | ❌ YOLO by default | -| MCP support | ✅ Native, first-class | ✅ Native, stdio + remote | ❌ Not built-in | -| Web search | ✅ Built-in | ✅ Built-in (Exa AI) | ❌ Build with extensions | -| LSP integration | ❌ Not built-in | ✅ Native | ❌ Not built-in | -| IDE extensions | ✅ VS Code, JetBrains | ✅ VS Code | ❌ Terminal only | -| Desktop app | ✅ Desktop app | ✅ Desktop app (beta) | ❌ Terminal only | -| AGENTS.md / CLAUDE.md | ✅ CLAUDE.md | ✅ AGENTS.md | ✅ AGENTS.md (or CLAUDE.md) | -| Slash commands | ✅ .claude/commands/ | ✅ .opencode/commands/ | ✅ .pi/prompts/ + extensions | -| Skills | ✅ Agent Skills standard | ✅ SKILL.md loading | ✅ Agent Skills standard | -| Session sharing | ✅ /export to HTML | ✅ /share creates link | ✅ /export to HTML | -| GitHub Actions bot | ✅ Native | ✅ @opencode bot | ❌ Not built-in | -| Todo tracking | ✅ Built-in | ✅ Built-in | ❌ Build with extensions | - -OpenCode systematically reproduced Claude Code's feature set, added LSP support and a desktop app, swapped the single-provider lock-in for model-agnostic support, and open-sourced it under MIT. It's a very good execution of this strategy. - -Pi rejected the feature set entirely and built a minimal, extensible harness that trusts the engineer to compose their own agent experience. It's a different bet — that the right set of primitives (events, UI APIs, tool registration, session branching) is more valuable than a pre-built feature set. - ---- - -## Summary: The Decision Framework - -**Choose Pi if you are building a custom agent workflow** — you want to control input interception, dynamic system prompts, UI components, multi-agent orchestration with live dashboards, safety auditing with custom rules, or anything that requires programmatic control over the agent loop. Pi's extensions are code that runs inside the agent. The ceiling is whatever you can build in TypeScript. - -**Choose OpenCode if you want a production-ready Claude Code replacement** — you need LSP, MCP, permissions, Plan mode, sub-agents, web search, GitHub integration, a desktop app, and a massive community shipping updates daily. OpenCode's plugin system handles common customization needs (tool hooks, custom tools, notifications, compaction), and the JSON-driven config covers agent definitions, permissions, and provider setup without writing code. - -**The one-line version:** Pi is a platform. OpenCode is a product. Both are open source. The question is whether you want to *build* your experience or *configure* an existing Claude Code like experience. \ No newline at end of file diff --git a/RESERVED_KEYS.md b/RESERVED_KEYS.md deleted file mode 100644 index 19604ea..0000000 --- a/RESERVED_KEYS.md +++ /dev/null @@ -1,75 +0,0 @@ -# Pi Reserved Keybindings - -Extensions **cannot** override these shortcuts — they are silently skipped by `registerShortcut()`. - -| Key | Action | -|-----|--------| -| `escape` | interrupt | -| `ctrl+c` | clear / copy | -| `ctrl+d` | exit | -| `ctrl+z` | suspend | -| `shift+tab` | cycleThinkingLevel | -| `ctrl+p` | cycleModelForward | -| `ctrl+shift+p` | cycleModelBackward | -| `ctrl+l` | selectModel | -| `ctrl+o` | expandTools | -| `ctrl+t` | toggleThinking | -| `ctrl+g` | externalEditor | -| `alt+enter` | followUp | -| `enter` | submit / selectConfirm | -| `ctrl+k` | deleteToLineEnd | - -## Non-Reserved Built-in Keys - -Extensions **can** override these (Pi will warn but allow it). - -| Key | Action | -|-----|--------| -| `up` / `down` | cursor / select navigation | -| `left` / `right` | cursor movement | -| `ctrl+a` | cursorLineStart | -| `ctrl+b` | cursorLeft | -| `ctrl+e` | cursorLineEnd | -| `ctrl+f` | cursorRight | -| `ctrl+n` | toggleSessionNamedFilter | -| `ctrl+r` | renameSession | -| `ctrl+s` | toggleSessionSort | -| `ctrl+u` | deleteToLineStart | -| `ctrl+v` | pasteImage | -| `ctrl+w` | deleteWordBackward | -| `ctrl+y` | yank | -| `ctrl+]` | jumpForward | -| `ctrl+-` | undo | -| `ctrl+alt+]` | jumpBackward | -| `alt+b` | cursorWordLeft | -| `alt+d` | deleteWordForward | -| `alt+f` | cursorWordRight | -| `alt+y` | yankPop | -| `alt+up` | dequeue | -| `alt+backspace` | deleteWordBackward | -| `alt+delete` | deleteWordForward | -| `alt+left` / `alt+right` | cursorWord left/right | -| `ctrl+left` / `ctrl+right` | cursorWord left/right | -| `shift+enter` | newLine | -| `home` / `end` | cursorLineStart/End | -| `pageUp` / `pageDown` | page navigation | -| `backspace` | deleteCharBackward | -| `delete` | deleteCharForward | -| `tab` | tab | - -## Safe Keys for Extensions - -These `ctrl+letter` combos are **free** and work in all terminals: - -| Key | Notes | -|-----|-------| -| `ctrl+x` | Safe | -| `ctrl+q` | May be intercepted by terminal (XON/XOFF flow control) | -| `ctrl+h` | Alias for backspace in some terminals — use with caution | - -## macOS Notes - -- `alt+letter` combos type special characters in most macOS terminals — they don't send alt sequences -- `ctrl+shift+letter` requires Kitty keyboard protocol (Kitty, Ghostty, WezTerm) -- `ctrl+alt+letter` works in legacy terminals but may conflict with macOS system shortcuts -- **Safest bet on macOS:** stick to `ctrl+letter` combos from the free list above, or use `f1`–`f12` diff --git a/THEME.md b/THEME.md deleted file mode 100644 index 9ea21c1..0000000 --- a/THEME.md +++ /dev/null @@ -1,29 +0,0 @@ -# Theme Color Conventions - -Extensions in this repo use a consistent color language mapped to Pi's theme tokens. Follow these rules when building new extensions. - -## Color Roles - -| Token | Role | Used For | -|-----------|-------------------|-----------------------------------------------| -| `success` | Primary value | Token counts, hash fills, branch name, counts | -| `accent` | Secondary value | Percentages, tool names, token out counts | -| `warning` | Punctuation/frame | Brackets `[]`, parens `()`, pipes `|`, cost | -| `dim` | Filler/spacing | Dashes, labels ("in", "out"), separators | -| `muted` | Subdued text | CWD name, fallback states | - -## Examples - -``` -Context meter: warning([) success(###) dim(---) warning(]) accent(30%) -Git branch: dim(pi-vs-cc) warning(() success(main) warning()) -Token stats: success(1.2k) dim(in) accent(340) dim(out) warning($0.0042) -Tool tally: accent(Bash) success(3) warning(|) accent(Read) success(7) -``` - -## Rationale - -- **Green (success)** draws the eye to live values that change — counts, filled bars, branch -- **Cyan (accent)** highlights identifiers and secondary metrics — names, percentages -- **Yellow (warning)** frames structure — delimiters tell you where one value ends and the next begins -- **Dim** recedes into the background — labels and filler shouldn't compete for attention diff --git a/TOOLS.md b/TOOLS.md deleted file mode 100644 index c9d8504..0000000 --- a/TOOLS.md +++ /dev/null @@ -1,27 +0,0 @@ -```ts -// Read the contents of a file. Supports text files and images. Output is truncated to 2000 lines or 50KB. -function read( - path: string, // Path to the file to read (relative or absolute) - limit?: number, // Maximum number of lines to read - offset?: number // Line number to start reading from (1-indexed) -): string; - -// Execute a bash command in the current working directory. Returns stdout and stderr. -function bash( - command: string, // Bash command to execute - timeout?: number // Timeout in seconds (optional, no default timeout) -): string; - -// Edit a file by replacing exact text. The oldText must match exactly (including whitespace). -function edit( - path: string, // Path to the file to edit (relative or absolute) - oldText: string, // Exact text to find and replace (must match exactly) - newText: string // New text to replace the old text with -): void; - -// Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories. -function write( - path: string, // Path to the file to write (relative or absolute) - content: string // Content to write to the file -): void; -``` diff --git a/application-answers.md b/application-answers.md deleted file mode 100644 index ba531b7..0000000 --- a/application-answers.md +++ /dev/null @@ -1,195 +0,0 @@ -# 🎯 Omair Saleh — Full-Stack Engineer Application @ Calvana LTD -## The Outlaw Application - ---- - -## Field 1: Full Name -``` -Omair Saleh -``` - -## Field 2: Email Address -``` -omair@quikcue.com -``` - -## Field 3: LinkedIn / Personal Site / Portfolio -``` -https://www.linkedin.com/in/omair-rescues/ -``` -> 💡 If quikcue.com is live, use that. Custom domain > LinkedIn every time. - -## Field 4: GitHub or Equivalent -``` -[Your GitHub URL here] -``` -> 💡 Pin the charity platform, the Hub, and the outreach agent. Let the commit history speak. - -## Field 5: Location -``` -Kuala Lumpur, Malaysia — happy to overlap with London hours. I work when the work needs doing, not when a calendar tells me to. -``` - -## Field 6: Employment Status -> **Select: "Running my own thing"** - ---- - -## Field 7: 🔥 "Describe something you built end-to-end" - -``` -A UK charity came to me with a problem: their donation flow was bleeding donors. Poor conversion, no recurring giving, no peer-to-peer fundraising, no Gift Aid automation. They didn't hand me a spec. There was no spec. There was a problem and a deadline. - -So I built the whole thing. From the database schema to the Stripe webhook handlers. - -Next.js 15 frontend. PostgreSQL with Prisma. Stripe for payments — PaymentIntents for one-off, SetupIntents for recurring. I designed a multi-step checkout with progressive disclosure because I know that every extra field before the payment button is a donor you'll never see again. - -Nobody told me to handle Zakat compliance. I just knew that if a Muslim donor selects Zakat, admin fees need to auto-disable — it's a religious obligation, not a suggestion. So I built it. Nobody told me to move Gift Aid capture to post-payment either. But I knew that asking a donor for their home address BEFORE they've committed to giving is how you kill conversion. So I moved it. HMRC still gets what they need. The charity gets more donations. Problem solved. - -Then I built the P2P fundraising engine — individual pages, team pages, leaderboards, URL-based attribution — architected as its own domain service because I could see it would need to scale independently. Then an admin dashboard. Then a Chatwoot integration for donor support, white-labeled with a Chrome extension I wrote because the dev workflow needed it. Then a data sync pipeline using Playwright to scrape donor CSVs from LaunchGood and reconcile them into Postgres with strict deduplication. - -No PM. No Jira board. No sprint ceremonies. Just me, the problem, and the production environment. - -This is what I do. I see a mess, I build the system, I ship it. In a corporate environment, this gets me in trouble — I've been told I "move too fast", I "don't follow process", I "should wait for alignment." At a startup, this is the only speed that matters. -``` - ---- - -## Field 8: Link to Something You've Built -``` -[Link to your charity donation platform or best GitHub repo] -``` - ---- - -## Field 9: 🔥 AI/ML API Experience - -``` -I don't prototype with AI. I ship with it. There's a difference. - -1. AI Outreach Agent: A charity needed to find and contact decision-makers across the entire UK charity sector. Hundreds of thousands of records. I built a Python pipeline that ingests raw Charity Commission data into PostgreSQL, then uses OpenAI to translate natural language queries ("large education charities with income over £1M operating nationally") into SQL filter logic via a custom segment engine. Once leads are qualified, OpenAI generates personalised outreach assets — emails, talking points — based on each charity's actual profile, income band, and sector. Not templated mail-merge garbage. Actually personalised. Then it enriches contacts through Apify to find the CEO, Director, or Head of Fundraising. The whole thing runs from a CLI with deterministic Python scripts underneath — the AI makes decisions, but the infrastructure is boring and reliable. On purpose. - -2. Conversation Intelligence (Hub Platform): Built into a B2B customer service platform. When a support agent opens a Chatwoot conversation, the system pulls the customer's order history from Salla, their previous interactions, and uses OpenAI with structured function calling to suggest contextual responses grounded in real data. Not vibes-based autocomplete — actual responses that reference real order numbers and real product names. I built it this way because I've seen what happens when you let AI hallucinate in customer-facing contexts. It destroys trust instantly. - -3. AI Command Center: This one's borderline unhinged. An autonomous multi-agent system that runs on a 15-minute cron cycle. Reliability agent monitors Sentry. Code-steward reviews MRs on GitLab. Product-driver agent analyses codebase health metrics from Postgres/MySQL and proposes improvements. But — and this is the part that matters — nothing executes without human approval. I built a full safety layer with auto-pause on excessive API spend, command allowlists, and dry-run mode. Because I learned early that autonomous AI without kill switches is just a very expensive way to break production. - -The real lesson across all of these: the API call is the easy part. The hard part is building the deterministic scaffolding that makes AI trustworthy — retry logic, structured outputs, cost ceilings, caching layers, human-in-the-loop gates. Anyone can call OpenAI. I build the systems that make it safe to let OpenAI call the shots. -``` - ---- - -## Field 10: Tech Skills Rating - -| Technology | Select This | -|---|---| -| **React / Next.js** | **production-level experience** | -| **Python / Django** | **strong experience** | -| **PostgreSQL** | **production-level experience** | -| **AWS** | **decent experience** | -| **REST API design & integrations** | **production-level experience** | -| **OAuth** | **strong experience** | -| **CI/CD & Deployment Pipelines** | **strong experience** | -| **Docker / containerisation** | **strong experience** | - -> Don't inflate. Let the project descriptions do the talking. Honesty here builds trust for everything else. - ---- - -## Field 11: 🔥 "Why does this role interest you specifically?" - -``` -I'll be honest: I'm a terrible employee. - -Not in the way you'd think. I ship fast, I write clean code, I own my systems end-to-end. But I've learned the hard way that I don't survive in environments where shipping requires three meetings, two approvals, and a Confluence page nobody reads. I've been told I "go rogue." I've been told I "need to wait for the team to align." I've sat in sprint planning sessions thinking about the three features I could've shipped in the time it took to estimate the story points. - -That's not a personality flaw. It's a misallocation. - -Your job post reads like someone wrote it specifically for people like me. "This isn't a role where you'll have a dedicated PM writing specs." Good — I've never needed one. "This isn't a role where 'that's not my job' is a useful phrase." I literally built a Chrome extension because my dev workflow for a Chatwoot integration was annoying me. Nobody asked me to. The friction existed, so I killed it. - -But here's what actually made me stop scrolling and pay attention: - -You have cash, audience, distribution, and PMF. You DON'T have engineers. That's the most dangerous inflection point for a startup — the gap between "this works" and "this scales." That gap gets filled by someone who can pick up an entire problem, architect a solution, ship it as a microservice, and move on to the next one without waiting for permission. I've been doing exactly that for the past year: a full donation platform with Stripe, P2P, and Gift Aid compliance. A multi-service B2B operations hub with 30+ services, AI automation, and real-time event processing. An outreach engine that processes hundreds of thousands of leads with AI. All end-to-end. All without a PM. - -Your stack is my stack — Next.js, Python, PostgreSQL, Stripe, OAuth, Docker. Your AI ambitions are things I've already built. Your microservices architecture is how I think. - -I watched Charlie's Loom. "We're going to the moon with this thing." I believe it. And I know that the difference between going to the moon and talking about going to the moon is having someone in the engine room who builds without asking for permission. - -That's me. I'm the guy in the engine room. -``` - ---- - -## Field 12: Salary Expectation -``` -£50,000–£65,000 GBP/year — flexible on structure. If the equity conversation is real, I'm more interested in upside than ceiling. -``` - ---- - -## Field 13: How soon could you start? -> **Select: "Immediately"** — you're running your own thing, you set your own timeline. - ---- - -## Field 14: 🔥 Loom Video Script (THE KNOCKOUT PUNCH) - -``` -[0:00-0:20] -"Hey Charlie — I'm Omair. I'll be straight with you: I'm a terrible fit -for most companies. I've been told I move too fast, I don't wait for -alignment, I build things nobody asked for. Turns out those are -features, not bugs — just depends on the environment. Your job post -reads like it was written for someone exactly like me." - -[0:20-0:55] [SCREEN SHARE: Charity donation platform] -"Quick example. A UK charity had a broken donation flow. No spec, no PM, -no Jira. Just a problem. So I built this — end to end. Next.js 15, -Prisma, PostgreSQL, Stripe. Multi-step checkout, recurring giving, P2P -fundraising, Zakat compliance, Gift Aid for HMRC. Designed the schema, -wrote the webhook handlers, deployed it. That's how I work — give me the -problem, get out of the way." - -[0:55-1:25] [SCREEN SHARE: Hub platform or AI outreach agent] -"Then there's this — an AI outreach engine I built. Ingests hundreds of -thousands of charity records, uses OpenAI to segment and qualify leads, -generates personalised outreach. The AI is wrapped in deterministic -Python with cost controls and approval gates — because I've learned that -AI without guardrails is just an expensive way to break things." - -[1:25-1:50] -"Your post said 'we have cash, audience, distribution, and PMF — we just -need YOU.' I felt that. I've spent the last year building entire systems -solo — the donation platform, a B2B SaaS hub with 30+ microservices, AI -agents running on cron cycles. No PM, no sprint ceremonies. Just -problems and production. That's the only way I know how to work — and -it sounds like that's exactly what you need." - -[1:50-2:00] -"I don't need onboarding. I need a problem and a git repo. Let's talk." -``` - ---- - -## ⚡ PRE-SUBMIT CHECKLIST - -- [ ] GitHub pinned repos updated and READMEs are clean -- [ ] LinkedIn headline: "Full-Stack Engineer | I build things nobody asked for" -- [ ] All answers proofread — raw ≠ sloppy -- [ ] Loom recorded — show real projects, show real energy, close hard -- [ ] quikcue.com email shows you're a founder, not an applicant - ---- - -## 🎭 THE OUTLAW POSITIONING — WHY THIS WORKS - -The entire job posting is a filter for people who **can't survive in corporate**: - -| What Their Post Says | What It Actually Means | Your Outlaw Angle | -|---|---|---| -| "No PM writing specs for you" | We need self-starters | "I've never needed a PM. I AM the PM." | -| "Not just one part of the codebase" | Generalists only | "I built frontend, backend, infra, Chrome extensions, data pipelines — in one project." | -| "'That's not my job' isn't useful" | Ego-free builders | "I built a Chrome extension because a workflow annoyed me. Nobody asked." | -| "Ambiguity of early-stage work" | Chaos tolerance required | "Chaos is where I do my best work. Structure is where I suffocate." | -| "No AI screening — we read every app" | Charlie reads this personally | You're speaking directly to a founder. Be human. Be direct. | - -**The core message in every answer:** *The things that make me a liability in corporate make me your most valuable hire. I don't wait for permission. I don't need process. I see problems and I ship solutions. That's why big companies don't know what to do with me — and it's exactly why you should.* diff --git a/bun.lock b/bun.lock deleted file mode 100644 index b2f7695..0000000 --- a/bun.lock +++ /dev/null @@ -1,28 +0,0 @@ -{ - "lockfileVersion": 1, - "configVersion": 1, - "workspaces": { - "": { - "name": "pi-vs-cc", - "dependencies": { - "yaml": "^2.8.0", - }, - "devDependencies": { - "@playwright/cli": "^0.1.1", - }, - }, - }, - "packages": { - "@playwright/cli": ["@playwright/cli@0.1.1", "", { "dependencies": { "minimist": "^1.2.5", "playwright": "1.59.0-alpha-1771104257000" }, "bin": { "playwright-cli": "playwright-cli.js" } }, "sha512-9k11ZfDwAfMVDDIuEVW1Wvs8SoDNXIY1dNQ+9C9/SS8ZmElkcxesu5eoL7vNa96ntibUGaq1TM2qQoqvdl/I9g=="], - - "fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], - - "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], - - "playwright": ["playwright@1.59.0-alpha-1771104257000", "", { "dependencies": { "playwright-core": "1.59.0-alpha-1771104257000" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-6SCMMMJaDRsSqiKVLmb2nhtLES7iTYawTWWrQK6UdIGNzXi8lka4sLKRec3L4DnTWwddAvCuRn8035dhNiHzbg=="], - - "playwright-core": ["playwright-core@1.59.0-alpha-1771104257000", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-YiXup3pnpQUCBMSIW5zx8CErwRx4K6O5Kojkw2BzJui8MazoMUDU6E3xGsb1kzFviEAE09LFQ+y1a0RhIJQ5SA=="], - - "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], - } -} diff --git a/calvana-build/Dockerfile b/calvana-build/Dockerfile deleted file mode 100644 index 040c466..0000000 --- a/calvana-build/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM nginx:alpine -COPY nginx.conf /etc/nginx/conf.d/default.conf -COPY html/ /usr/share/nginx/html/ -EXPOSE 80 diff --git a/calvana-build/html/404.html b/calvana-build/html/404.html deleted file mode 100644 index 10b74f8..0000000 --- a/calvana-build/html/404.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - 404 — Calvana - - - - -
-

404

-

This page doesn't exist yet. But give me 10 minutes.

- ← Back to manifesto -
- - diff --git a/calvana-build/html/css/style.css b/calvana-build/html/css/style.css deleted file mode 100644 index cecc4de..0000000 --- a/calvana-build/html/css/style.css +++ /dev/null @@ -1,104 +0,0 @@ -:root { - --bg: #0a0a0a; - --bg-card: #111; - --bg-card-hover: #161616; - --text: #e5e5e5; - --text-muted: #666; - --text-dim: #444; - --accent: #00ff9f; - --accent-dim: #00cc7f; - --cta: #ff6b35; - --cta-hover: #ff8c5a; - --yellow: #ffd93d; - --red: #ff4757; - --border: #1a1a1a; - --border-light: #222; - --font-mono: 'SF Mono','Fira Code','JetBrains Mono','Cascadia Code',monospace; - --font-sans: -apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif; - --max-w: 780px; -} -*,*::before,*::after{margin:0;padding:0;box-sizing:border-box} -html{font-size:16px;scroll-behavior:smooth} -body{background:var(--bg);color:var(--text);font-family:var(--font-sans);line-height:1.65;min-height:100vh;-webkit-font-smoothing:antialiased} - -nav{position:sticky;top:0;z-index:100;background:rgba(10,10,10,0.85);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-bottom:1px solid var(--border);padding:.75rem 1.5rem} -nav .nav-inner{max-width:var(--max-w);margin:0 auto;display:flex;align-items:center;justify-content:space-between;gap:1rem} -nav .logo{font-family:var(--font-mono);font-size:.95rem;font-weight:700;color:var(--accent);text-decoration:none;letter-spacing:-.02em} -nav .logo span{color:var(--text-muted);font-weight:400} -nav .nav-links{display:flex;gap:.25rem;list-style:none} -nav .nav-links a{font-family:var(--font-mono);font-size:.8rem;color:var(--text-muted);text-decoration:none;padding:.35rem .65rem;border-radius:6px;transition:all .15s ease} -nav .nav-links a:hover,nav .nav-links a.active{color:var(--text);background:var(--bg-card)} - -.page{max-width:var(--max-w);margin:0 auto;padding:3.5rem 1.5rem 5rem} - -.hero-title{font-family:var(--font-mono);font-size:clamp(2rem,6vw,3.2rem);font-weight:800;line-height:1.15;letter-spacing:-.03em;color:var(--text);margin-bottom:1rem} -.hero-title .accent{color:var(--accent)} -h2{font-family:var(--font-mono);font-size:1.35rem;font-weight:700;color:var(--text);margin-bottom:1rem;letter-spacing:-.02em} -h3{font-family:var(--font-mono);font-size:1rem;font-weight:600;color:var(--text);margin-bottom:.5rem} -.subtitle{font-size:1.05rem;color:var(--text-muted);line-height:1.7;max-width:600px} -.section{margin-top:3.5rem} - -.btn-row{display:flex;flex-wrap:wrap;gap:.75rem;margin-top:2rem} -.btn{display:inline-flex;align-items:center;gap:.4rem;font-family:var(--font-mono);font-size:.85rem;font-weight:600;padding:.65rem 1.25rem;border-radius:8px;text-decoration:none;transition:all .15s ease;cursor:pointer;border:none} -.btn-primary{background:var(--accent);color:#0a0a0a} -.btn-primary:hover{background:#33ffb3;transform:translateY(-1px)} -.btn-cta{background:var(--cta);color:#fff} -.btn-cta:hover{background:var(--cta-hover);transform:translateY(-1px)} -.btn-outline{background:transparent;color:var(--text-muted);border:1px solid var(--border-light)} -.btn-outline:hover{color:var(--text);border-color:var(--text-muted);transform:translateY(-1px)} - -.card-grid{display:grid;grid-template-columns:1fr;gap:1rem;margin-top:1rem} -.card{background:var(--bg-card);border:1px solid var(--border);border-radius:10px;padding:1.25rem;transition:border-color .15s ease} -.card:hover{border-color:var(--border-light)} -.card-header{display:flex;align-items:flex-start;justify-content:space-between;gap:.75rem;margin-bottom:.6rem} -.card-title{font-family:var(--font-mono);font-size:.9rem;font-weight:600;line-height:1.4} -.card-meta{font-size:.78rem;color:var(--text-muted);margin-bottom:.5rem} -.card-links{display:flex;gap:.75rem;margin-top:.6rem} -.card-links a{font-family:var(--font-mono);font-size:.75rem;color:var(--accent-dim);text-decoration:none} -.card-links a:hover{color:var(--accent);text-decoration:underline} - -.badge{font-family:var(--font-mono);font-size:.7rem;font-weight:600;padding:.2rem .55rem;border-radius:99px;white-space:nowrap;flex-shrink:0} -.badge-planned{color:var(--text-muted);border:1px solid var(--border-light)} -.badge-shipping{color:#0a0a0a;background:var(--yellow)} -.badge-shipped{color:#0a0a0a;background:var(--accent)} - -.two-col{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem;margin-top:1rem} -.col{background:var(--bg-card);border:1px solid var(--border);border-radius:10px;padding:1.25rem} -.col h3{margin-bottom:.75rem} -.col ul{list-style:none;display:flex;flex-direction:column;gap:.5rem} -.col ul li{font-size:.88rem;color:var(--text-muted);padding-left:1.25rem;position:relative} -.col ul li::before{content:'';position:absolute;left:0;top:.55rem;width:6px;height:6px;border-radius:50%} -.col-broke ul li::before{background:var(--cta)} -.col-kept ul li::before{background:var(--accent)} - -.oops-log{margin-top:1rem;display:flex;flex-direction:column;gap:.6rem} -.oops-entry{display:flex;align-items:center;justify-content:space-between;gap:1rem;font-size:.85rem;color:var(--text-muted);padding:.75rem 1rem;background:var(--bg-card);border:1px solid var(--border);border-radius:8px;border-left:3px solid var(--red)} -.oops-entry a{font-family:var(--font-mono);font-size:.75rem;color:var(--accent-dim);text-decoration:none;white-space:nowrap} -.oops-entry a:hover{color:var(--accent)} - -.manifesto-para{font-size:1.05rem;color:var(--text-muted);line-height:1.75;margin-top:3.5rem;max-width:620px;border-left:3px solid var(--accent);padding-left:1rem} - -.hire-ctas{display:flex;flex-direction:column;gap:1rem;margin-top:2rem;max-width:480px} -.hire-ctas .btn{justify-content:center;padding:1rem 1.5rem;font-size:.9rem} -.hire-note{margin-top:3.5rem;font-size:.9rem;color:var(--text-muted);line-height:1.7} -.hire-referral{margin-top:2rem;font-size:.85rem;color:var(--text-dim);font-style:italic} - -footer{margin-top:5rem;padding-top:2rem;border-top:1px solid var(--border)} -footer p{font-family:var(--font-mono);font-size:.78rem;color:var(--text-dim)} -.footer-tagline{color:var(--text-muted)!important;font-style:italic} -.metric{font-family:var(--font-mono);font-size:.78rem;color:var(--text-dim)} - -@media(max-width:640px){ - .page{padding:2rem 1rem 3rem} - .hero-title{font-size:clamp(1.7rem,7vw,2.4rem)} - .btn-row{flex-direction:column}.btn-row .btn{width:100%;justify-content:center} - .two-col{grid-template-columns:1fr} - nav .nav-links a{font-size:.75rem;padding:.3rem .5rem} - .card-header{flex-direction:column;gap:.4rem} - .oops-entry{flex-direction:column;align-items:flex-start;gap:.4rem} - .hire-ctas{max-width:100%} -} -@media(max-width:380px){ - nav .logo span{display:none} - nav .nav-links{gap:0} -} diff --git a/calvana-build/html/hire/index.html b/calvana-build/html/hire/index.html deleted file mode 100644 index 7b93ff8..0000000 --- a/calvana-build/html/hire/index.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - Calvana — Hire - - - - - - - - - - - -
-

If you're reading this,
you already know.

- -

- If you want safe hands, hire safe people.
- If you want velocity with control — let's talk. -

-

PS — Umar pointed me here. If this turns into a hire, I want him to get paid.

-
- -
-
- - diff --git a/calvana-build/html/index.html b/calvana-build/html/index.html deleted file mode 100644 index f507eb6..0000000 --- a/calvana-build/html/index.html +++ /dev/null @@ -1,5 +0,0 @@ - - -Calvana -

Redirecting to /manifesto

- diff --git a/calvana-build/html/live/index.html b/calvana-build/html/live/index.html deleted file mode 100644 index f198905..0000000 --- a/calvana-build/html/live/index.html +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - Calvana — Live Shipping Log - - - - - - - - - - - -
-

Live Shipping Log

-

Intentional chaos. Full receipts.

- -
-

Today's Ships

-
-
-
- Fix post-donation consent funnel (Email + WhatsApp) - Planned -
-

⏱ —

-

What moved: —

- -
-
-
- Deploy pledge-now-pay-later micro-saas - Planned -
-

⏱ —

-

What moved: —

- -
-
-
- JustVitamin post-migration AI automation demos - Shipping -
-

⏱ —

-

What moved: —

- -
-
-
- This Calvana application — shipped ✓ - Shipped -
-

⏱ 2026-03-02 14:00 GMT+8

-

What moved: 0 → live in one session

- -
-
-
- -
-
-
-

Rules I broke today

-
    -
  • Didn't ask permission
  • -
  • Didn't wait for alignment
  • -
  • Didn't write a PRD
  • -
  • Didn't submit a normal application
  • -
-
-
-

Rules I refuse to break

-
    -
  • No silent failures
  • -
  • No unbounded AI spend
  • -
  • No hallucinations shipped to users
  • -
  • No deploy without rollback path
  • -
-
-
-
- -
-

Oops Log

-

If it's not here, I haven't broken it yet.

-
-
- Traefik label typo → 404 on first deploy. Fixed in 3 min. - → commit -
-
- CSS grid overflow on mobile. Caught in preview, fixed before push. - → commit -
-
- Forgot meta viewport tag. Pinch-zoom chaos. Fixed in 90 seconds. - → commit -
-
-
- -
- -

Last updated: 2026-03-02T14:00:00+08:00

-
-
- - diff --git a/calvana-build/html/manifesto/index.html b/calvana-build/html/manifesto/index.html deleted file mode 100644 index d0a8892..0000000 --- a/calvana-build/html/manifesto/index.html +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - Calvana — I don't apply. I deploy. - - - - - - - - - - - -
-

I don't apply.
I deploy.

-

- You're hiring engineers. I'm showing you what changes when you hire an engine.
- This application is a product. Built for you. Right now. -

- -

- Most applications prove the past. I'm proving the next 7 days.
- Build → test → ship → observe → iterate… at speed, without breaking reality. -

-
- -
-
- - diff --git a/calvana-build/nginx.conf b/calvana-build/nginx.conf deleted file mode 100644 index 4f10346..0000000 --- a/calvana-build/nginx.conf +++ /dev/null @@ -1,28 +0,0 @@ -server { - listen 80; - server_name calvana.quikcue.com; - root /usr/share/nginx/html; - index index.html; - - # Fix: behind reverse proxy, use relative redirects - absolute_redirect off; - - location / { - try_files $uri $uri/ =404; - } - - location /css/ { - expires 1h; - add_header Cache-Control "public, immutable"; - } - - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-Content-Type-Options "nosniff" always; - add_header Referrer-Policy "strict-origin-when-cross-origin" always; - - gzip on; - gzip_types text/html text/css application/javascript text/plain; - gzip_min_length 256; - - error_page 404 /404.html; -} diff --git a/extensions/agent-chain.ts b/extensions/agent-chain.ts deleted file mode 100644 index 8cf7d2a..0000000 --- a/extensions/agent-chain.ts +++ /dev/null @@ -1,797 +0,0 @@ -/** - * Agent Chain — Sequential pipeline orchestrator - * - * Runs opinionated, repeatable agent workflows. Chains are defined in - * .pi/agents/agent-chain.yaml — each chain is a sequence of agent steps - * with prompt templates. The user's original prompt flows into step 1, - * the output becomes $INPUT for step 2's prompt template, and so on. - * $ORIGINAL is always the user's original prompt. - * - * The primary Pi agent has NO codebase tools — it can ONLY kick off the - * pipeline via the `run_chain` tool. On boot you select a chain; the - * agent decides when to run it based on the user's prompt. - * - * Agents maintain session context within a Pi session — re-running the - * chain lets each agent resume where it left off. - * - * Commands: - * /chain — switch active chain - * /chain-list — list all available chains - * - * Usage: pi -e extensions/agent-chain.ts - */ - -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; -import { Type } from "@sinclair/typebox"; -import { Text, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui"; -import { spawn } from "child_process"; -import { readFileSync, existsSync, readdirSync, mkdirSync, unlinkSync } from "fs"; -import { join, resolve } from "path"; -import { applyExtensionDefaults } from "./themeMap.ts"; - -// ── Types ──────────────────────────────────────── - -interface ChainStep { - agent: string; - prompt: string; -} - -interface ChainDef { - name: string; - description: string; - steps: ChainStep[]; -} - -interface AgentDef { - name: string; - description: string; - tools: string; - systemPrompt: string; -} - -interface StepState { - agent: string; - status: "pending" | "running" | "done" | "error"; - elapsed: number; - lastWork: string; -} - -// ── Display Name Helper ────────────────────────── - -function displayName(name: string): string { - return name.split("-").map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(" "); -} - -// ── Chain YAML Parser ──────────────────────────── - -function parseChainYaml(raw: string): ChainDef[] { - const chains: ChainDef[] = []; - let current: ChainDef | null = null; - let currentStep: ChainStep | null = null; - - for (const line of raw.split("\n")) { - // Chain name: top-level key - const chainMatch = line.match(/^(\S[^:]*):$/); - if (chainMatch) { - if (current && currentStep) { - current.steps.push(currentStep); - currentStep = null; - } - current = { name: chainMatch[1].trim(), description: "", steps: [] }; - chains.push(current); - continue; - } - - // Chain description - const descMatch = line.match(/^\s+description:\s+(.+)$/); - if (descMatch && current && !currentStep) { - let desc = descMatch[1].trim(); - if ((desc.startsWith('"') && desc.endsWith('"')) || - (desc.startsWith("'") && desc.endsWith("'"))) { - desc = desc.slice(1, -1); - } - current.description = desc; - continue; - } - - // "steps:" label — skip - if (line.match(/^\s+steps:\s*$/) && current) { - continue; - } - - // Step agent line - const agentMatch = line.match(/^\s+-\s+agent:\s+(.+)$/); - if (agentMatch && current) { - if (currentStep) { - current.steps.push(currentStep); - } - currentStep = { agent: agentMatch[1].trim(), prompt: "" }; - continue; - } - - // Step prompt line - const promptMatch = line.match(/^\s+prompt:\s+(.+)$/); - if (promptMatch && currentStep) { - let prompt = promptMatch[1].trim(); - if ((prompt.startsWith('"') && prompt.endsWith('"')) || - (prompt.startsWith("'") && prompt.endsWith("'"))) { - prompt = prompt.slice(1, -1); - } - prompt = prompt.replace(/\\n/g, "\n"); - currentStep.prompt = prompt; - continue; - } - } - - if (current && currentStep) { - current.steps.push(currentStep); - } - - return chains; -} - -// ── Frontmatter Parser ─────────────────────────── - -function parseAgentFile(filePath: string): AgentDef | null { - try { - const raw = readFileSync(filePath, "utf-8"); - const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/); - if (!match) return null; - - const frontmatter: Record = {}; - for (const line of match[1].split("\n")) { - const idx = line.indexOf(":"); - if (idx > 0) { - frontmatter[line.slice(0, idx).trim()] = line.slice(idx + 1).trim(); - } - } - - if (!frontmatter.name) return null; - - return { - name: frontmatter.name, - description: frontmatter.description || "", - tools: frontmatter.tools || "read,grep,find,ls", - systemPrompt: match[2].trim(), - }; - } catch { - return null; - } -} - -function scanAgentDirs(cwd: string): Map { - const dirs = [ - join(cwd, "agents"), - join(cwd, ".claude", "agents"), - join(cwd, ".pi", "agents"), - ]; - - const agents = new Map(); - - for (const dir of dirs) { - if (!existsSync(dir)) continue; - try { - for (const file of readdirSync(dir)) { - if (!file.endsWith(".md")) continue; - const fullPath = resolve(dir, file); - const def = parseAgentFile(fullPath); - if (def && !agents.has(def.name.toLowerCase())) { - agents.set(def.name.toLowerCase(), def); - } - } - } catch {} - } - - return agents; -} - -// ── Extension ──────────────────────────────────── - -export default function (pi: ExtensionAPI) { - let allAgents: Map = new Map(); - let chains: ChainDef[] = []; - let activeChain: ChainDef | null = null; - let widgetCtx: any; - let sessionDir = ""; - const agentSessions: Map = new Map(); - - // Per-step state for the active chain - let stepStates: StepState[] = []; - let pendingReset = false; - - function loadChains(cwd: string) { - sessionDir = join(cwd, ".pi", "agent-sessions"); - if (!existsSync(sessionDir)) { - mkdirSync(sessionDir, { recursive: true }); - } - - allAgents = scanAgentDirs(cwd); - - agentSessions.clear(); - for (const [key] of allAgents) { - const sessionFile = join(sessionDir, `chain-${key}.json`); - agentSessions.set(key, existsSync(sessionFile) ? sessionFile : null); - } - - const chainPath = join(cwd, ".pi", "agents", "agent-chain.yaml"); - if (existsSync(chainPath)) { - try { - chains = parseChainYaml(readFileSync(chainPath, "utf-8")); - } catch { - chains = []; - } - } else { - chains = []; - } - } - - function activateChain(chain: ChainDef) { - activeChain = chain; - stepStates = chain.steps.map(s => ({ - agent: s.agent, - status: "pending" as const, - elapsed: 0, - lastWork: "", - })); - // Skip widget re-registration if reset is pending — let before_agent_start handle it - if (!pendingReset) { - updateWidget(); - } - } - - // ── Card Rendering ────────────────────────── - - function renderCard(state: StepState, colWidth: number, theme: any): string[] { - const w = colWidth - 2; - const truncate = (s: string, max: number) => s.length > max ? s.slice(0, max - 3) + "..." : s; - - const statusColor = state.status === "pending" ? "dim" - : state.status === "running" ? "accent" - : state.status === "done" ? "success" : "error"; - const statusIcon = state.status === "pending" ? "○" - : state.status === "running" ? "●" - : state.status === "done" ? "✓" : "✗"; - - const name = displayName(state.agent); - const nameStr = theme.fg("accent", theme.bold(truncate(name, w))); - const nameVisible = Math.min(name.length, w); - - const statusStr = `${statusIcon} ${state.status}`; - const timeStr = state.status !== "pending" ? ` ${Math.round(state.elapsed / 1000)}s` : ""; - const statusLine = theme.fg(statusColor, statusStr + timeStr); - const statusVisible = statusStr.length + timeStr.length; - - const workRaw = state.lastWork || ""; - const workText = workRaw ? truncate(workRaw, Math.min(50, w - 1)) : ""; - const workLine = workText ? theme.fg("muted", workText) : theme.fg("dim", "—"); - const workVisible = workText ? workText.length : 1; - - const top = "┌" + "─".repeat(w) + "┐"; - const bot = "└" + "─".repeat(w) + "┘"; - const border = (content: string, visLen: number) => - theme.fg("dim", "│") + content + " ".repeat(Math.max(0, w - visLen)) + theme.fg("dim", "│"); - - return [ - theme.fg("dim", top), - border(" " + nameStr, 1 + nameVisible), - border(" " + statusLine, 1 + statusVisible), - border(" " + workLine, 1 + workVisible), - theme.fg("dim", bot), - ]; - } - - function updateWidget() { - if (!widgetCtx) return; - - widgetCtx.ui.setWidget("agent-chain", (_tui: any, theme: any) => { - const text = new Text("", 0, 1); - - return { - render(width: number): string[] { - if (!activeChain || stepStates.length === 0) { - text.setText(theme.fg("dim", "No chain active. Use /chain to select one.")); - return text.render(width); - } - - const arrowWidth = 5; // " ──▶ " - const cols = stepStates.length; - const totalArrowWidth = arrowWidth * (cols - 1); - const colWidth = Math.max(12, Math.floor((width - totalArrowWidth) / cols)); - const arrowRow = 2; // middle of 5-line card (0-indexed) - - const cards = stepStates.map(s => renderCard(s, colWidth, theme)); - const cardHeight = cards[0].length; - const outputLines: string[] = []; - - for (let line = 0; line < cardHeight; line++) { - let row = cards[0][line]; - for (let c = 1; c < cols; c++) { - if (line === arrowRow) { - row += theme.fg("dim", " ──▶ "); - } else { - row += " ".repeat(arrowWidth); - } - row += cards[c][line]; - } - outputLines.push(row); - } - - text.setText(outputLines.join("\n")); - return text.render(width); - }, - invalidate() { - text.invalidate(); - }, - }; - }); - } - - // ── Run Agent (subprocess) ────────────────── - - function runAgent( - agentDef: AgentDef, - task: string, - stepIndex: number, - ctx: any, - ): Promise<{ output: string; exitCode: number; elapsed: number }> { - const model = ctx.model - ? `${ctx.model.provider}/${ctx.model.id}` - : "openrouter/google/gemini-3-flash-preview"; - - const agentKey = agentDef.name.toLowerCase().replace(/\s+/g, "-"); - const agentSessionFile = join(sessionDir, `chain-${agentKey}.json`); - const hasSession = agentSessions.get(agentKey); - - const args = [ - "--mode", "json", - "-p", - "--no-extensions", - "--model", model, - "--tools", agentDef.tools, - "--thinking", "off", - "--append-system-prompt", agentDef.systemPrompt, - "--session", agentSessionFile, - ]; - - if (hasSession) { - args.push("-c"); - } - - args.push(task); - - const textChunks: string[] = []; - const startTime = Date.now(); - const state = stepStates[stepIndex]; - - return new Promise((resolve) => { - const proc = spawn("pi", args, { - stdio: ["ignore", "pipe", "pipe"], - env: { ...process.env }, - }); - - const timer = setInterval(() => { - state.elapsed = Date.now() - startTime; - updateWidget(); - }, 1000); - - let buffer = ""; - - proc.stdout!.setEncoding("utf-8"); - proc.stdout!.on("data", (chunk: string) => { - buffer += chunk; - const lines = buffer.split("\n"); - buffer = lines.pop() || ""; - for (const line of lines) { - if (!line.trim()) continue; - try { - const event = JSON.parse(line); - if (event.type === "message_update") { - const delta = event.assistantMessageEvent; - if (delta?.type === "text_delta") { - textChunks.push(delta.delta || ""); - const full = textChunks.join(""); - const last = full.split("\n").filter((l: string) => l.trim()).pop() || ""; - state.lastWork = last; - updateWidget(); - } - } - } catch {} - } - }); - - proc.stderr!.setEncoding("utf-8"); - proc.stderr!.on("data", () => {}); - - proc.on("close", (code) => { - if (buffer.trim()) { - try { - const event = JSON.parse(buffer); - if (event.type === "message_update") { - const delta = event.assistantMessageEvent; - if (delta?.type === "text_delta") textChunks.push(delta.delta || ""); - } - } catch {} - } - - clearInterval(timer); - const elapsed = Date.now() - startTime; - state.elapsed = elapsed; - const output = textChunks.join(""); - state.lastWork = output.split("\n").filter((l: string) => l.trim()).pop() || ""; - - if (code === 0) { - agentSessions.set(agentKey, agentSessionFile); - } - - resolve({ output, exitCode: code ?? 1, elapsed }); - }); - - proc.on("error", (err) => { - clearInterval(timer); - resolve({ - output: `Error spawning agent: ${err.message}`, - exitCode: 1, - elapsed: Date.now() - startTime, - }); - }); - }); - } - - // ── Run Chain (sequential pipeline) ───────── - - async function runChain( - task: string, - ctx: any, - ): Promise<{ output: string; success: boolean; elapsed: number }> { - if (!activeChain) { - return { output: "No chain active", success: false, elapsed: 0 }; - } - - const chainStart = Date.now(); - - // Reset all steps to pending - stepStates = activeChain.steps.map(s => ({ - agent: s.agent, - status: "pending" as const, - elapsed: 0, - lastWork: "", - })); - updateWidget(); - - let input = task; - const originalPrompt = task; - - for (let i = 0; i < activeChain.steps.length; i++) { - const step = activeChain.steps[i]; - stepStates[i].status = "running"; - updateWidget(); - - const resolvedPrompt = step.prompt - .replace(/\$INPUT/g, input) - .replace(/\$ORIGINAL/g, originalPrompt); - - const agentDef = allAgents.get(step.agent.toLowerCase()); - if (!agentDef) { - stepStates[i].status = "error"; - stepStates[i].lastWork = `Agent "${step.agent}" not found`; - updateWidget(); - return { - output: `Error at step ${i + 1}: Agent "${step.agent}" not found. Available: ${Array.from(allAgents.keys()).join(", ")}`, - success: false, - elapsed: Date.now() - chainStart, - }; - } - - const result = await runAgent(agentDef, resolvedPrompt, i, ctx); - - if (result.exitCode !== 0) { - stepStates[i].status = "error"; - updateWidget(); - return { - output: `Error at step ${i + 1} (${step.agent}): ${result.output}`, - success: false, - elapsed: Date.now() - chainStart, - }; - } - - stepStates[i].status = "done"; - updateWidget(); - - input = result.output; - } - - return { output: input, success: true, elapsed: Date.now() - chainStart }; - } - - // ── run_chain Tool ────────────────────────── - - pi.registerTool({ - name: "run_chain", - label: "Run Chain", - description: "Execute the active agent chain pipeline. Each step runs sequentially — output from one step feeds into the next. Agents maintain session context across runs.", - parameters: Type.Object({ - task: Type.String({ description: "The task/prompt for the chain to process" }), - }), - - async execute(_toolCallId, params, _signal, onUpdate, ctx) { - const { task } = params as { task: string }; - - if (onUpdate) { - onUpdate({ - content: [{ type: "text", text: `Starting chain: ${activeChain?.name}...` }], - details: { chain: activeChain?.name, task, status: "running" }, - }); - } - - const result = await runChain(task, ctx); - - const truncated = result.output.length > 8000 - ? result.output.slice(0, 8000) + "\n\n... [truncated]" - : result.output; - - const status = result.success ? "done" : "error"; - const summary = `[chain:${activeChain?.name}] ${status} in ${Math.round(result.elapsed / 1000)}s`; - - return { - content: [{ type: "text", text: `${summary}\n\n${truncated}` }], - details: { - chain: activeChain?.name, - task, - status, - elapsed: result.elapsed, - fullOutput: result.output, - }, - }; - }, - - renderCall(args, theme) { - const task = (args as any).task || ""; - const preview = task.length > 60 ? task.slice(0, 57) + "..." : task; - return new Text( - theme.fg("toolTitle", theme.bold("run_chain ")) + - theme.fg("accent", activeChain?.name || "?") + - theme.fg("dim", " — ") + - theme.fg("muted", preview), - 0, 0, - ); - }, - - renderResult(result, options, theme) { - const details = result.details as any; - if (!details) { - const text = result.content[0]; - return new Text(text?.type === "text" ? text.text : "", 0, 0); - } - - if (options.isPartial || details.status === "running") { - return new Text( - theme.fg("accent", `● ${details.chain || "chain"}`) + - theme.fg("dim", " running..."), - 0, 0, - ); - } - - const icon = details.status === "done" ? "✓" : "✗"; - const color = details.status === "done" ? "success" : "error"; - const elapsed = typeof details.elapsed === "number" ? Math.round(details.elapsed / 1000) : 0; - const header = theme.fg(color, `${icon} ${details.chain}`) + - theme.fg("dim", ` ${elapsed}s`); - - if (options.expanded && details.fullOutput) { - const output = details.fullOutput.length > 4000 - ? details.fullOutput.slice(0, 4000) + "\n... [truncated]" - : details.fullOutput; - return new Text(header + "\n" + theme.fg("muted", output), 0, 0); - } - - return new Text(header, 0, 0); - }, - }); - - // ── Commands ───────────────────────────────── - - pi.registerCommand("chain", { - description: "Switch active chain", - handler: async (_args, ctx) => { - widgetCtx = ctx; - if (chains.length === 0) { - ctx.ui.notify("No chains defined in .pi/agents/agent-chain.yaml", "warning"); - return; - } - - const options = chains.map(c => { - const steps = c.steps.map(s => displayName(s.agent)).join(" → "); - const desc = c.description ? ` — ${c.description}` : ""; - return `${c.name}${desc} (${steps})`; - }); - - const choice = await ctx.ui.select("Select Chain", options); - if (choice === undefined) return; - - const idx = options.indexOf(choice); - activateChain(chains[idx]); - const flow = chains[idx].steps.map(s => displayName(s.agent)).join(" → "); - ctx.ui.setStatus("agent-chain", `Chain: ${chains[idx].name} (${chains[idx].steps.length} steps)`); - ctx.ui.notify( - `Chain: ${chains[idx].name}\n${chains[idx].description}\n${flow}`, - "info", - ); - }, - }); - - pi.registerCommand("chain-list", { - description: "List all available chains", - handler: async (_args, ctx) => { - widgetCtx = ctx; - if (chains.length === 0) { - ctx.ui.notify("No chains defined in .pi/agents/agent-chain.yaml", "warning"); - return; - } - - const list = chains.map(c => { - const desc = c.description ? ` ${c.description}` : ""; - const steps = c.steps.map((s, i) => - ` ${i + 1}. ${displayName(s.agent)}` - ).join("\n"); - return `${c.name}:${desc ? "\n" + desc : ""}\n${steps}`; - }).join("\n\n"); - - ctx.ui.notify(list, "info"); - }, - }); - - // ── System Prompt Override ─────────────────── - - pi.on("before_agent_start", async (_event, _ctx) => { - // Force widget reset on first turn after /new - if (pendingReset && activeChain) { - pendingReset = false; - widgetCtx = _ctx; - stepStates = activeChain.steps.map(s => ({ - agent: s.agent, - status: "pending" as const, - elapsed: 0, - lastWork: "", - })); - updateWidget(); - } - - if (!activeChain) return {}; - - const flow = activeChain.steps.map(s => displayName(s.agent)).join(" → "); - const desc = activeChain.description ? `\n${activeChain.description}` : ""; - - // Build pipeline steps summary - const steps = activeChain.steps.map((s, i) => { - const agentDef = allAgents.get(s.agent.toLowerCase()); - const agentDesc = agentDef?.description || ""; - return `${i + 1}. **${displayName(s.agent)}** — ${agentDesc}`; - }).join("\n"); - - // Build full agent catalog (like agent-team.ts) - const seen = new Set(); - const agentCatalog = activeChain.steps - .filter(s => { - const key = s.agent.toLowerCase(); - if (seen.has(key)) return false; - seen.add(key); - return true; - }) - .map(s => { - const agentDef = allAgents.get(s.agent.toLowerCase()); - if (!agentDef) return `### ${displayName(s.agent)}\nAgent not found.`; - return `### ${displayName(agentDef.name)}\n${agentDef.description}\n**Tools:** ${agentDef.tools}\n**Role:** ${agentDef.systemPrompt}`; - }) - .join("\n\n"); - - return { - systemPrompt: `You are an agent with a sequential pipeline called "${activeChain.name}" at your disposal.${desc} -You have full access to your own tools AND the run_chain tool to delegate to your team. - -## Active Chain: ${activeChain.name} -Flow: ${flow} - -${steps} - -## Agent Details - -${agentCatalog} - -## When to Use run_chain -- Significant work: new features, refactors, multi-file changes, anything non-trivial -- Tasks that benefit from the full pipeline: planning, building, reviewing -- When you want structured, multi-agent collaboration on a problem - -## When to Work Directly -- Simple one-off commands: reading a file, checking status, listing contents -- Quick lookups, small edits, answering questions about the codebase -- Anything you can handle in a single step without needing the pipeline - -## How run_chain Works -- Pass a clear task description to run_chain -- Each step's output feeds into the next step as $INPUT -- Agents maintain session context — they remember previous work within this session -- You can run the chain multiple times with different tasks if needed -- After the chain completes, review the result and summarize for the user - -## Guidelines -- Use your judgment — if it's quick, just do it; if it's real work, run the chain -- Keep chain tasks focused and clearly described -- You can mix direct work and chain runs in the same conversation`, - }; - }); - - // ── Session Start ─────────────────────────── - - pi.on("session_start", async (_event, _ctx) => { - applyExtensionDefaults(import.meta.url, _ctx); - // Clear widget with both old and new ctx — one of them will be valid - if (widgetCtx) { - widgetCtx.ui.setWidget("agent-chain", undefined); - } - _ctx.ui.setWidget("agent-chain", undefined); - widgetCtx = _ctx; - - // Reset execution state — widget re-registration deferred to before_agent_start - stepStates = []; - activeChain = null; - pendingReset = true; - - // Wipe chain session files — reset agent context on /new and launch - const sessDir = join(_ctx.cwd, ".pi", "agent-sessions"); - if (existsSync(sessDir)) { - for (const f of readdirSync(sessDir)) { - if (f.startsWith("chain-") && f.endsWith(".json")) { - try { unlinkSync(join(sessDir, f)); } catch {} - } - } - } - - // Reload chains + clear agentSessions map (all agents start fresh) - loadChains(_ctx.cwd); - - if (chains.length === 0) { - _ctx.ui.notify("No chains found in .pi/agents/agent-chain.yaml", "warning"); - return; - } - - // Default to first chain — use /chain to switch - activateChain(chains[0]); - - // run_chain is registered as a tool — available alongside all default tools - - const flow = activeChain!.steps.map(s => displayName(s.agent)).join(" → "); - _ctx.ui.setStatus("agent-chain", `Chain: ${activeChain!.name} (${activeChain!.steps.length} steps)`); - _ctx.ui.notify( - `Chain: ${activeChain!.name}\n${activeChain!.description}\n${flow}\n\n` + - `/chain Switch chain\n` + - `/chain-list List all chains`, - "info", - ); - - // Footer: model | chain name | context bar - _ctx.ui.setFooter((_tui, theme, _footerData) => ({ - dispose: () => {}, - invalidate() {}, - render(width: number): string[] { - const model = _ctx.model?.id || "no-model"; - const usage = _ctx.getContextUsage(); - const pct = usage ? usage.percent : 0; - const filled = Math.round(pct / 10); - const bar = "#".repeat(filled) + "-".repeat(10 - filled); - - const chainLabel = activeChain - ? theme.fg("accent", activeChain.name) - : theme.fg("dim", "no chain"); - - const left = theme.fg("dim", ` ${model}`) + - theme.fg("muted", " · ") + - chainLabel; - const right = theme.fg("dim", `[${bar}] ${Math.round(pct)}% `); - const pad = " ".repeat(Math.max(1, width - visibleWidth(left) - visibleWidth(right))); - - return [truncateToWidth(left + pad + right, width)]; - }, - })); - }); -} diff --git a/extensions/agent-dashboard.ts b/extensions/agent-dashboard.ts deleted file mode 100644 index ec2acca..0000000 --- a/extensions/agent-dashboard.ts +++ /dev/null @@ -1,971 +0,0 @@ -/** - * Agent Dashboard — Unified observability across all agent interfaces - * - * Passively tracks agent activity from team dispatches, subagent spawns, - * and chain pipeline runs. Provides a compact always-visible widget plus - * a full-screen overlay with four switchable views. - * - * Hooks into: dispatch_agent, subagent_create, subagent_continue, run_chain - * tool calls and their completions. Completely passive — never blocks. - * - * Commands: - * /dashboard — toggle full-screen overlay - * /dashboard clear — reset all tracked state - * - * Usage: pi -e extensions/agent-dashboard.ts - */ - -import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent"; -import { DynamicBorder } from "@mariozechner/pi-coding-agent"; -import { Container, matchesKey, Text, truncateToWidth } from "@mariozechner/pi-tui"; -import { applyExtensionDefaults } from "./themeMap.ts"; - -// ── Data Types ───────────────────────────────────────────────────────── - -type AgentInterface = "team" | "subagent" | "chain"; - -interface TrackedAgent { - id: string; - name: string; - iface: AgentInterface; - status: "running" | "done" | "error"; - task: string; - startedAt: number; - endedAt?: number; - elapsed: number; - toolCount: number; - lastText: string; - turnCount: number; - chainStep?: number; - chainName?: string; - teamName?: string; -} - -interface AgentRun { - id: string; - name: string; - iface: AgentInterface; - task: string; - status: "done" | "error"; - startedAt: number; - endedAt: number; - duration: number; - toolCount: number; - resultPreview: string; - chainStep?: number; - chainName?: string; - teamName?: string; -} - -interface DashboardStats { - totalRuns: number; - totalSuccess: number; - totalError: number; - totalDuration: number; - agentRunCounts: Record; - ifaceCounts: Record; -} - -// ── Helpers ──────────────────────────────────────────────────────────── - -function fmtDuration(ms: number): string { - if (ms < 1000) return `${ms}ms`; - const secs = Math.floor(ms / 1000); - if (secs < 60) return `${secs}s`; - const mins = Math.floor(secs / 60); - const remSecs = secs % 60; - if (mins < 60) return `${mins}m ${remSecs}s`; - const hrs = Math.floor(mins / 60); - const remMins = mins % 60; - return `${hrs}h ${remMins}m`; -} - -function shortId(): string { - return Math.random().toString(36).slice(2, 6); -} - -function truncate(s: string, max: number): string { - return s.length > max ? s.slice(0, max - 1) + "…" : s; -} - -function emptyStats(): DashboardStats { - return { - totalRuns: 0, - totalSuccess: 0, - totalError: 0, - totalDuration: 0, - agentRunCounts: {}, - ifaceCounts: { team: 0, subagent: 0, chain: 0 }, - }; -} - -// ── Extension ────────────────────────────────────────────────────────── - -export default function (pi: ExtensionAPI) { - // ── State ────────────────────────────────────────────────────────── - - const activeAgents: Map = new Map(); - let history: AgentRun[] = []; - let stats: DashboardStats = emptyStats(); - let widgetCtx: ExtensionContext | null = null; - let tickTimer: ReturnType | null = null; - - // Mapping from toolCallId → tracked agent info (with timestamp for staleness) - const pendingCalls: Map = new Map(); - - // Staleness threshold: 10 minutes - const STALE_TIMEOUT_MS = 10 * 60 * 1000; - - // Inactivity auto-stop: stop tick after 30s with no active agents - let lastActivityTs = Date.now(); - - // ── Tracked tool names ───────────────────────────────────────────── - - const TRACKED_TOOLS = new Set([ - "dispatch_agent", - "subagent_create", - "subagent_continue", - "run_chain", - ]); - - // ── State Management ─────────────────────────────────────────────── - - function clearState() { - activeAgents.clear(); - history = []; - stats = emptyStats(); - pendingCalls.clear(); - lastActivityTs = Date.now(); - } - - function addToHistory(agent: TrackedAgent) { - const run: AgentRun = { - id: agent.id, - name: agent.name, - iface: agent.iface, - task: agent.task, - status: agent.status === "error" ? "error" : "done", - startedAt: agent.startedAt, - endedAt: agent.endedAt || Date.now(), - duration: agent.elapsed, - toolCount: agent.toolCount, - resultPreview: truncate(agent.lastText, 200), - chainStep: agent.chainStep, - chainName: agent.chainName, - teamName: agent.teamName, - }; - - history.push(run); - // Ring buffer capped at 200 - if (history.length > 200) { - history = history.slice(-200); - } - - // Update stats - stats.totalRuns++; - if (run.status === "done") stats.totalSuccess++; - else stats.totalError++; - stats.totalDuration += run.duration; - stats.agentRunCounts[run.name] = (stats.agentRunCounts[run.name] || 0) + 1; - stats.ifaceCounts[run.iface] = (stats.ifaceCounts[run.iface] || 0) + 1; - } - - // ── Tick Timer ───────────────────────────────────────────────────── - - function startTick() { - if (tickTimer) return; - tickTimer = setInterval(() => { - const now = Date.now(); - - // Update elapsed on running agents - for (const agent of activeAgents.values()) { - if (agent.status === "running") { - agent.elapsed = now - agent.startedAt; - } - } - - // Staleness check: expire pending calls older than 10 minutes - for (const [callId, pending] of pendingCalls) { - if (now - pending.ts > STALE_TIMEOUT_MS) { - pendingCalls.delete(callId); - const agent = activeAgents.get(pending.agentId); - if (agent && agent.status === "running") { - agent.status = "error"; - agent.endedAt = now; - agent.elapsed = now - agent.startedAt; - agent.lastText = "Timed out (no completion after 10m)"; - addToHistory(agent); - activeAgents.delete(pending.agentId); - } - } - } - - // Auto-stop tick after 30s of inactivity (no active agents, no pending calls) - if (activeAgents.size === 0 && pendingCalls.size === 0) { - if (now - lastActivityTs > 30_000) { - stopTick(); - return; - } - } else { - lastActivityTs = now; - } - - updateWidget(); - }, 1000); - } - - function stopTick() { - if (tickTimer) { - clearInterval(tickTimer); - tickTimer = null; - } - } - - // ── Widget Rendering ─────────────────────────────────────────────── - - function updateWidget() { - if (!widgetCtx) return; - - try { - widgetCtx.ui.setWidget("agent-dashboard", (_tui, theme) => { - const container = new Container(); - const borderFn = (s: string) => theme.fg("accent", s); - - container.addChild(new DynamicBorder(borderFn)); - - const headerText = new Text("", 1, 0); - container.addChild(headerText); - - const agentLines: Text[] = []; - // Pre-allocate up to 4 lines for active agents - for (let i = 0; i < 4; i++) { - const t = new Text("", 1, 0); - agentLines.push(t); - container.addChild(t); - } - - const hintText = new Text("", 1, 0); - container.addChild(hintText); - - container.addChild(new DynamicBorder(borderFn)); - - return { - render(width: number): string[] { - const activeCount = activeAgents.size; - const doneCount = stats.totalSuccess; - const errorCount = stats.totalError; - - // Line 1: summary bar - const line1 = - theme.fg("accent", " 📊 Dashboard") + - theme.fg("dim", " │ Active: ") + theme.fg(activeCount > 0 ? "accent" : "muted", `${activeCount}`) + - theme.fg("dim", " │ Done: ") + theme.fg("success", `${doneCount}`) + - theme.fg("dim", " │ Errors: ") + theme.fg(errorCount > 0 ? "error" : "muted", `${errorCount}`); - headerText.setText(truncateToWidth(line1, width - 4)); - - // Active agent lines - const agents = Array.from(activeAgents.values()); - for (let i = 0; i < agentLines.length; i++) { - if (i < agents.length) { - const a = agents[i]; - const icon = a.status === "running" ? "⟳" - : a.status === "done" ? "✓" : "✗"; - const statusColor = a.status === "running" ? "accent" - : a.status === "done" ? "success" : "error"; - const ifaceTag = theme.fg("dim", `[${a.iface}]`); - const elapsed = theme.fg("muted", fmtDuration(a.elapsed)); - const tools = theme.fg("dim", `🔧${a.toolCount}`); - const lastText = a.lastText - ? theme.fg("muted", truncate(a.lastText, Math.max(20, width - 60))) - : ""; - - const line = - " " + theme.fg(statusColor, icon) + " " + - theme.fg("accent", truncate(a.name, 16)) + " " + - ifaceTag + " " + - elapsed + " " + - tools + - (lastText ? theme.fg("dim", " │ ") + lastText : ""); - agentLines[i].setText(truncateToWidth(line, width - 4)); - } else { - agentLines[i].setText(""); - } - } - - // Hint line - const hintLine = - theme.fg("dim", " /dashboard") + theme.fg("muted", " — full view") + - theme.fg("dim", " │ ") + - theme.fg("muted", `${stats.totalRuns} total runs`) + - (stats.totalDuration > 0 - ? theme.fg("dim", " │ avg ") + theme.fg("muted", fmtDuration(Math.round(stats.totalDuration / Math.max(1, stats.totalRuns)))) - : ""); - hintText.setText(truncateToWidth(hintLine, width - 4)); - - return container.render(width); - }, - invalidate() { - container.invalidate(); - }, - }; - }); - } catch {} - } - - // ── Overlay ──────────────────────────────────────────────────────── - - async function openOverlay(ctx: ExtensionContext) { - if (!ctx.hasUI) return; - - let currentView = 0; // 0=Live, 1=History, 2=Interfaces, 3=Stats - let scrollOffset = 0; - - const viewNames = ["1:Live", "2:History", "3:Interfaces", "4:Stats"]; - - await ctx.ui.custom((_tui, theme, _kb, done) => { - return { - render(width: number): string[] { - const lines: string[] = []; - - // ── Header ── - lines.push(""); - const tabs = viewNames.map((name, i) => - i === currentView - ? theme.fg("accent", theme.bold(`[${name}]`)) - : theme.fg("dim", `[${name}]`) - ).join(" "); - lines.push(truncateToWidth( - " " + theme.fg("accent", theme.bold("📊 Agent Dashboard")) + - " ".repeat(Math.max(1, width - 20 - viewNames.join(" ").length - 2)) + - tabs, - width, - )); - lines.push(theme.fg("dim", "─".repeat(width))); - - // ── View content ── - const contentLines = renderView(currentView, width, theme, scrollOffset); - lines.push(...contentLines); - - // ── Footer controls ── - lines.push(""); - lines.push(theme.fg("dim", "─".repeat(width))); - lines.push(truncateToWidth( - " " + theme.fg("dim", "1-4/Tab: views │ j/k: scroll │ c: clear │ q/Esc: close"), - width, - )); - lines.push(""); - - return lines; - }, - handleInput(data: string) { - if (matchesKey(data, "escape") || data === "q") { - done(undefined); - return; - } - if (data === "1") { currentView = 0; scrollOffset = 0; } - else if (data === "2") { currentView = 1; scrollOffset = 0; } - else if (data === "3") { currentView = 2; scrollOffset = 0; } - else if (data === "4") { currentView = 3; scrollOffset = 0; } - else if (data === "\t") { currentView = (currentView + 1) % 4; scrollOffset = 0; } - else if (matchesKey(data, "up") || data === "k") { scrollOffset = Math.max(0, scrollOffset - 1); } - else if (matchesKey(data, "down") || data === "j") { scrollOffset++; } - else if (matchesKey(data, "pageUp")) { scrollOffset = Math.max(0, scrollOffset - 20); } - else if (matchesKey(data, "pageDown")) { scrollOffset += 20; } - else if (data === "c") { - clearState(); - scrollOffset = 0; - } - _tui.requestRender(); - }, - invalidate() {}, - }; - }, { - overlay: true, - overlayOptions: { width: "90%", anchor: "center" }, - }); - } - - // ── View Renderers ───────────────────────────────────────────────── - - function renderView(view: number, width: number, theme: any, offset: number): string[] { - switch (view) { - case 0: return renderLiveView(width, theme, offset); - case 1: return renderHistoryView(width, theme, offset); - case 2: return renderInterfacesView(width, theme, offset); - case 3: return renderStatsView(width, theme, offset); - default: return []; - } - } - - // ── View 1: Live ─────────────────────────────────────────────────── - - function renderLiveView(width: number, theme: any, offset: number): string[] { - const lines: string[] = []; - const agents = Array.from(activeAgents.values()); - - lines.push(truncateToWidth( - " " + theme.fg("accent", theme.bold("Active Agents")) + - theme.fg("dim", ` (${agents.length} running)`), - width, - )); - lines.push(""); - - if (agents.length === 0) { - lines.push(truncateToWidth( - " " + theme.fg("dim", "No agents currently running. Activity will appear here when"), - width, - )); - lines.push(truncateToWidth( - " " + theme.fg("dim", "dispatch_agent, subagent_create, subagent_continue, or run_chain is called."), - width, - )); - lines.push(""); - - // Show recent completions as context - if (history.length > 0) { - lines.push(truncateToWidth( - " " + theme.fg("muted", `Last completed: ${history.length} agents`), - width, - )); - const recent = history.slice(-3).reverse(); - for (const run of recent) { - const icon = run.status === "done" ? "✓" : "✗"; - const color = run.status === "done" ? "success" : "error"; - lines.push(truncateToWidth( - " " + theme.fg(color, `${icon} ${run.name}`) + - theme.fg("dim", ` [${run.iface}] `) + - theme.fg("muted", fmtDuration(run.duration)) + - theme.fg("dim", " — ") + - theme.fg("muted", truncate(run.task, 50)), - width, - )); - } - } - return lines; - } - - const allLines: string[] = []; - for (const agent of agents) { - const icon = agent.status === "running" ? "●" - : agent.status === "done" ? "✓" : "✗"; - const statusColor = agent.status === "running" ? "accent" - : agent.status === "done" ? "success" : "error"; - - // Card top - allLines.push(truncateToWidth( - " " + theme.fg("dim", "┌─ ") + - theme.fg(statusColor, `${icon} ${agent.name}`) + - theme.fg("dim", ` [${agent.iface}]`) + - (agent.chainName ? theme.fg("dim", ` chain:${agent.chainName}`) : "") + - (agent.teamName ? theme.fg("dim", ` team:${agent.teamName}`) : "") + - (agent.chainStep !== undefined ? theme.fg("dim", ` step:${agent.chainStep}`) : "") + - theme.fg("dim", " ─".repeat(Math.max(0, Math.floor((width - 50) / 2)))), - width, - )); - - // Task - allLines.push(truncateToWidth( - " " + theme.fg("dim", "│ ") + - theme.fg("muted", "Task: ") + - theme.fg("accent", truncate(agent.task, width - 20)), - width, - )); - - // Metrics - allLines.push(truncateToWidth( - " " + theme.fg("dim", "│ ") + - theme.fg("muted", "Elapsed: ") + theme.fg("success", fmtDuration(agent.elapsed)) + - theme.fg("dim", " │ ") + - theme.fg("muted", "Tools: ") + theme.fg("accent", `${agent.toolCount}`) + - theme.fg("dim", " │ ") + - theme.fg("muted", "Turns: ") + theme.fg("accent", `${agent.turnCount}`), - width, - )); - - // Streaming text - if (agent.lastText) { - allLines.push(truncateToWidth( - " " + theme.fg("dim", "│ ") + - theme.fg("muted", truncate(agent.lastText, width - 10)), - width, - )); - } - - // Card bottom - allLines.push(truncateToWidth( - " " + theme.fg("dim", "└" + "─".repeat(Math.max(0, width - 5))), - width, - )); - allLines.push(""); - } - - const visible = allLines.slice(offset); - lines.push(...visible); - - return lines; - } - - // ── View 2: History ──────────────────────────────────────────────── - - function renderHistoryView(width: number, theme: any, offset: number): string[] { - const lines: string[] = []; - - lines.push(truncateToWidth( - " " + theme.fg("accent", theme.bold("Completed Runs")) + - theme.fg("dim", ` (${history.length} total)`), - width, - )); - lines.push(""); - - if (history.length === 0) { - lines.push(truncateToWidth(" " + theme.fg("dim", "No completed runs yet."), width)); - return lines; - } - - // Table header - const hdr = - theme.fg("accent", " Status") + - theme.fg("accent", " │ Name ") + - theme.fg("accent", " │ Interface ") + - theme.fg("accent", " │ Duration ") + - theme.fg("accent", " │ Tools ") + - theme.fg("accent", " │ Task"); - lines.push(truncateToWidth(hdr, width)); - lines.push(truncateToWidth(" " + theme.fg("dim", "─".repeat(Math.min(80, width - 4))), width)); - - // Show newest first - const rows: string[] = []; - const reversed = [...history].reverse(); - for (const run of reversed) { - const icon = run.status === "done" ? "✓" : "✗"; - const color = run.status === "done" ? "success" : "error"; - const ifaceLabel = run.iface.padEnd(9); - const nameLabel = truncate(run.name, 14).padEnd(14); - const durLabel = fmtDuration(run.duration).padEnd(8); - const toolLabel = String(run.toolCount).padStart(5); - const taskPreview = truncate(run.task, Math.max(10, width - 70)); - - const row = - " " + theme.fg(color, ` ${icon} `) + - theme.fg("dim", " │ ") + theme.fg("accent", nameLabel) + - theme.fg("dim", " │ ") + theme.fg("muted", ifaceLabel) + - theme.fg("dim", " │ ") + theme.fg("success", durLabel) + - theme.fg("dim", " │ ") + theme.fg("accent", toolLabel) + - theme.fg("dim", " │ ") + theme.fg("muted", taskPreview); - rows.push(row); - } - - const visible = rows.slice(offset); - for (const row of visible) { - lines.push(truncateToWidth(row, width)); - } - - return lines; - } - - // ── View 3: Interfaces ───────────────────────────────────────────── - - function renderInterfacesView(width: number, theme: any, offset: number): string[] { - const lines: string[] = []; - - lines.push(truncateToWidth( - " " + theme.fg("accent", theme.bold("Agents by Interface")), - width, - )); - lines.push(""); - - const ifaceLabels: Record = { - team: "🏢 Team (dispatch_agent)", - subagent: "🤖 Subagent (subagent_create/continue)", - chain: "🔗 Chain (run_chain)", - }; - - const allLines: string[] = []; - - for (const iface of ["team", "subagent", "chain"] as AgentInterface[]) { - const activeForIface = Array.from(activeAgents.values()).filter(a => a.iface === iface); - const historyForIface = history.filter(r => r.iface === iface); - const totalCount = stats.ifaceCounts[iface] || 0; - - allLines.push(truncateToWidth( - " " + theme.fg("accent", theme.bold(ifaceLabels[iface])) + - theme.fg("dim", ` — ${activeForIface.length} active, ${totalCount} completed`), - width, - )); - allLines.push(truncateToWidth(" " + theme.fg("dim", "─".repeat(Math.min(60, width - 6))), width)); - - // Active - if (activeForIface.length > 0) { - for (const agent of activeForIface) { - allLines.push(truncateToWidth( - " " + theme.fg("accent", "● ") + - theme.fg("accent", agent.name) + - theme.fg("dim", " — ") + - theme.fg("success", fmtDuration(agent.elapsed)) + - theme.fg("dim", " │ 🔧") + theme.fg("muted", `${agent.toolCount}`) + - theme.fg("dim", " │ ") + - theme.fg("muted", truncate(agent.task, 40)), - width, - )); - } - } - - // Recent completed (last 5) - const recent = historyForIface.slice(-5).reverse(); - if (recent.length > 0) { - for (const run of recent) { - const icon = run.status === "done" ? "✓" : "✗"; - const color = run.status === "done" ? "success" : "error"; - allLines.push(truncateToWidth( - " " + theme.fg(color, `${icon} `) + - theme.fg("muted", run.name) + - theme.fg("dim", " — ") + - theme.fg("muted", fmtDuration(run.duration)) + - theme.fg("dim", " │ ") + - theme.fg("muted", truncate(run.task, 40)), - width, - )); - } - } - - if (activeForIface.length === 0 && recent.length === 0) { - allLines.push(truncateToWidth(" " + theme.fg("dim", "No activity recorded."), width)); - } - - allLines.push(""); - } - - const visible = allLines.slice(offset); - lines.push(...visible); - - return lines; - } - - // ── View 4: Stats ────────────────────────────────────────────────── - - function renderStatsView(width: number, theme: any, offset: number): string[] { - const lines: string[] = []; - - lines.push(truncateToWidth( - " " + theme.fg("accent", theme.bold("Aggregate Statistics")), - width, - )); - lines.push(""); - - const avgDur = stats.totalRuns > 0 - ? fmtDuration(Math.round(stats.totalDuration / stats.totalRuns)) - : "—"; - const successRate = stats.totalRuns > 0 - ? `${Math.round((stats.totalSuccess / stats.totalRuns) * 100)}%` - : "—"; - - const allLines: string[] = []; - - // Summary cards - allLines.push(truncateToWidth( - " " + - theme.fg("muted", "Total Runs: ") + theme.fg("accent", `${stats.totalRuns}`) + - theme.fg("dim", " │ ") + - theme.fg("muted", "Success: ") + theme.fg("success", `${stats.totalSuccess}`) + - theme.fg("dim", " │ ") + - theme.fg("muted", "Errors: ") + theme.fg(stats.totalError > 0 ? "error" : "muted", `${stats.totalError}`) + - theme.fg("dim", " │ ") + - theme.fg("muted", "Success Rate: ") + theme.fg("success", successRate), - width, - )); - allLines.push(truncateToWidth( - " " + - theme.fg("muted", "Total Duration: ") + theme.fg("success", fmtDuration(stats.totalDuration)) + - theme.fg("dim", " │ ") + - theme.fg("muted", "Avg Duration: ") + theme.fg("accent", avgDur), - width, - )); - allLines.push(""); - - // Interface breakdown - allLines.push(truncateToWidth( - " " + theme.fg("accent", theme.bold("Interface Breakdown")), - width, - )); - allLines.push(""); - - const ifaceTotal = Math.max(1, stats.ifaceCounts.team + stats.ifaceCounts.subagent + stats.ifaceCounts.chain); - const barWidth = Math.min(30, Math.floor(width * 0.3)); - - for (const [iface, label] of [["team", "Team "], ["subagent", "Subagent "], ["chain", "Chain "]] as [AgentInterface, string][]) { - const count = stats.ifaceCounts[iface] || 0; - const ratio = count / ifaceTotal; - const filled = Math.round(ratio * barWidth); - const bar = "█".repeat(filled) + "░".repeat(barWidth - filled); - - allLines.push(truncateToWidth( - " " + - theme.fg("accent", label) + " " + - theme.fg("success", bar) + " " + - theme.fg("muted", `${count}`) + - theme.fg("dim", ` (${Math.round(ratio * 100)}%)`), - width, - )); - } - allLines.push(""); - - // Most-used agents bar chart - allLines.push(truncateToWidth( - " " + theme.fg("accent", theme.bold("Most-Used Agents")), - width, - )); - allLines.push(""); - - const agentEntries = Object.entries(stats.agentRunCounts).sort((a, b) => b[1] - a[1]); - - if (agentEntries.length === 0) { - allLines.push(truncateToWidth(" " + theme.fg("dim", "No agent runs recorded yet."), width)); - } else { - const maxCount = agentEntries[0][1]; - for (const [name, count] of agentEntries.slice(0, 15)) { - const ratio = maxCount > 0 ? count / maxCount : 0; - const filled = Math.round(ratio * barWidth); - const bar = "█".repeat(filled) + "░".repeat(barWidth - filled); - - allLines.push(truncateToWidth( - " " + - theme.fg("accent", name.padEnd(16)) + " " + - theme.fg("success", bar) + " " + - theme.fg("muted", `${count}`), - width, - )); - } - } - allLines.push(""); - - // Per-agent average durations - if (history.length > 0) { - allLines.push(truncateToWidth( - " " + theme.fg("accent", theme.bold("Average Duration by Agent")), - width, - )); - allLines.push(""); - - const durByAgent: Record = {}; - for (const run of history) { - if (!durByAgent[run.name]) durByAgent[run.name] = []; - durByAgent[run.name].push(run.duration); - } - - const durEntries = Object.entries(durByAgent).sort((a, b) => { - const avgA = a[1].reduce((s, v) => s + v, 0) / a[1].length; - const avgB = b[1].reduce((s, v) => s + v, 0) / b[1].length; - return avgB - avgA; - }); - - for (const [name, durations] of durEntries) { - const avg = durations.reduce((s, v) => s + v, 0) / durations.length; - const min = Math.min(...durations); - const max = Math.max(...durations); - - allLines.push(truncateToWidth( - " " + - theme.fg("accent", name.padEnd(16)) + - theme.fg("dim", " avg: ") + theme.fg("success", fmtDuration(Math.round(avg)).padEnd(8)) + - theme.fg("dim", " min: ") + theme.fg("muted", fmtDuration(min).padEnd(8)) + - theme.fg("dim", " max: ") + theme.fg("muted", fmtDuration(max).padEnd(8)) + - theme.fg("dim", " runs: ") + theme.fg("muted", `${durations.length}`), - width, - )); - } - } - - const visible = allLines.slice(offset); - lines.push(...visible); - - return lines; - } - - // ── Commands ─────────────────────────────────────────────────────── - - pi.registerCommand("dashboard", { - description: "Open Agent Dashboard overlay. Args: clear", - handler: async (args, ctx) => { - widgetCtx = ctx; - const arg = (args || "").trim().toLowerCase(); - - if (arg === "clear") { - stopTick(); - clearState(); - startTick(); - ctx.ui.notify("📊 Dashboard: All data cleared.", "info"); - updateWidget(); - return; - } - - await openOverlay(ctx); - }, - }); - - // ── Event Handlers ───────────────────────────────────────────────── - - pi.on("session_start", async (_event, ctx) => { - applyExtensionDefaults(import.meta.url, ctx); - stopTick(); - widgetCtx = ctx; - clearState(); - startTick(); - updateWidget(); - }); - - pi.on("before_agent_start", async (_event, ctx) => { - widgetCtx = ctx; - return undefined; - }); - - pi.on("agent_end", async (_event, ctx) => { - widgetCtx = ctx; - updateWidget(); - }); - - pi.on("tool_call", async (event, _ctx) => { - try { - const toolName = event.toolName; - if (!TRACKED_TOOLS.has(toolName)) return undefined; - - const input = event.input; - const now = Date.now(); - const callId = event.toolCallId; - lastActivityTs = now; - - if (toolName === "dispatch_agent") { - const agentName = (input.agent as string) || "unknown"; - const task = (input.task as string) || ""; - const id = `team:${agentName}:${shortId()}`; - - const tracked: TrackedAgent = { - id, - name: agentName, - iface: "team", - status: "running", - task, - startedAt: now, - elapsed: 0, - toolCount: 0, - lastText: "", - turnCount: 1, - teamName: agentName, - }; - - activeAgents.set(id, tracked); - pendingCalls.set(callId, { agentId: id, ts: now }); - - } else if (toolName === "subagent_create") { - const task = (input.task as string) || ""; - const id = `sub:create:${shortId()}`; - - const tracked: TrackedAgent = { - id, - name: "Subagent", - iface: "subagent", - status: "running", - task, - startedAt: now, - elapsed: 0, - toolCount: 0, - lastText: "", - turnCount: 1, - }; - - activeAgents.set(id, tracked); - pendingCalls.set(callId, { agentId: id, ts: now }); - - } else if (toolName === "subagent_continue") { - // Always create a new tracking entry using the widget's ID from input - const subId = input.id; - const prompt = (input.prompt as string) || ""; - const id = `sub:cont:${subId}:${shortId()}`; - - const tracked: TrackedAgent = { - id, - name: `Subagent #${subId}`, - iface: "subagent", - status: "running", - task: prompt, - startedAt: now, - elapsed: 0, - toolCount: 0, - lastText: "", - turnCount: 1, - }; - - activeAgents.set(id, tracked); - pendingCalls.set(callId, { agentId: id, ts: now }); - - } else if (toolName === "run_chain") { - const task = (input.task as string) || ""; - const id = `chain:${shortId()}`; - - const tracked: TrackedAgent = { - id, - name: "chain", - iface: "chain", - status: "running", - task, - startedAt: now, - elapsed: 0, - toolCount: 0, - lastText: "", - turnCount: 1, - chainName: "pipeline", - }; - - activeAgents.set(id, tracked); - pendingCalls.set(callId, { agentId: id, ts: now }); - } - - // Ensure tick is running when we have active agents - startTick(); - updateWidget(); - } catch {} - - return undefined; - }); - - pi.on("tool_execution_end", async (event) => { - try { - const toolName = event.toolName; - if (!TRACKED_TOOLS.has(toolName)) return; - - const now = Date.now(); - const callId = event.toolCallId; - lastActivityTs = now; - - const pending = pendingCalls.get(callId); - if (pending) { - pendingCalls.delete(callId); - - const agent = activeAgents.get(pending.agentId); - if (agent) { - agent.status = event.isError ? "error" : "done"; - agent.endedAt = now; - agent.elapsed = now - agent.startedAt; - - // Extract result preview if available - try { - const result = event.result; - if (result?.content) { - for (const block of result.content) { - if (block.type === "text" && block.text) { - agent.lastText = block.text.slice(0, 200); - break; - } - } - } - } catch {} - - // Move to history - addToHistory(agent); - activeAgents.delete(pending.agentId); - } - } - - updateWidget(); - } catch {} - }); -} diff --git a/extensions/agent-team.ts b/extensions/agent-team.ts deleted file mode 100644 index 18c3f71..0000000 --- a/extensions/agent-team.ts +++ /dev/null @@ -1,944 +0,0 @@ -/** - * Agent Team — Dispatcher-only orchestrator with grid dashboard - * - * The primary Pi agent has NO codebase tools. It can ONLY delegate work - * to specialist agents via the `dispatch_agent` tool (single) or - * `dispatch_agents` tool (parallel batch). Each specialist maintains - * its own Pi session for cross-invocation memory. - * - * Loads agent definitions from agents/*.md, .claude/agents/*.md, .pi/agents/*.md. - * Teams are defined in .pi/agents/teams.yaml — on boot a select dialog lets - * you pick which team to work with. Only team members are available for dispatch. - * - * Commands: - * /agents-team — switch active team - * /agents-list — list loaded agents - * /agents-grid N — set column count (default 2) - * - * Usage: pi -e extensions/agent-team.ts - */ - -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; -import { Type } from "@sinclair/typebox"; -import { Text, type AutocompleteItem, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui"; -import { spawn, type ChildProcess } from "child_process"; -import { readdirSync, readFileSync, existsSync, mkdirSync, unlinkSync } from "fs"; -import { join, resolve } from "path"; -import { applyExtensionDefaults } from "./themeMap.ts"; - -// ── Constants ──────────────────────────────────── - -const AGENT_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes per dispatch -const WIDGET_THROTTLE_MS = 500; // max widget refresh rate - -// ── Types ──────────────────────────────────────── - -interface AgentDef { - name: string; - description: string; - tools: string; - systemPrompt: string; - file: string; -} - -interface AgentState { - def: AgentDef; - status: "idle" | "running" | "done" | "error"; - task: string; - toolCount: number; - elapsed: number; - lastWork: string; - contextPct: number; - sessionFile: string | null; - runCount: number; - timer?: ReturnType; - proc?: ChildProcess; -} - -// ── Display Name Helper ────────────────────────── - -function displayName(name: string): string { - return name.split("-").map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(" "); -} - -// ── Teams YAML Parser ──────────────────────────── - -function parseTeamsYaml(raw: string): Record { - const teams: Record = {}; - let current: string | null = null; - for (const line of raw.split("\n")) { - const teamMatch = line.match(/^(\S[^:]*):$/); - if (teamMatch) { - current = teamMatch[1].trim(); - teams[current] = []; - continue; - } - const itemMatch = line.match(/^\s+-\s+(.+)$/); - if (itemMatch && current) { - teams[current].push(itemMatch[1].trim()); - } - } - return teams; -} - -// ── Frontmatter Parser ─────────────────────────── - -function parseAgentFile(filePath: string): AgentDef | null { - try { - const raw = readFileSync(filePath, "utf-8"); - const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/); - if (!match) return null; - - const frontmatter: Record = {}; - for (const line of match[1].split("\n")) { - const idx = line.indexOf(":"); - if (idx > 0) { - frontmatter[line.slice(0, idx).trim()] = line.slice(idx + 1).trim(); - } - } - - if (!frontmatter.name) return null; - - return { - name: frontmatter.name, - description: frontmatter.description || "", - tools: frontmatter.tools || "read,grep,find,ls", - systemPrompt: match[2].trim(), - file: filePath, - }; - } catch { - return null; - } -} - -function scanAgentDirs(cwd: string): AgentDef[] { - const dirs = [ - join(cwd, "agents"), - join(cwd, ".claude", "agents"), - join(cwd, ".pi", "agents"), - ]; - - const agents: AgentDef[] = []; - const seen = new Set(); - - for (const dir of dirs) { - if (!existsSync(dir)) continue; - try { - for (const file of readdirSync(dir)) { - if (!file.endsWith(".md")) continue; - const fullPath = resolve(dir, file); - const def = parseAgentFile(fullPath); - if (def && !seen.has(def.name.toLowerCase())) { - seen.add(def.name.toLowerCase()); - agents.push(def); - } - } - } catch {} - } - - return agents; -} - -// ── Extension ──────────────────────────────────── - -export default function (pi: ExtensionAPI) { - const agentStates: Map = new Map(); - const activeProcesses: Set = new Set(); - let allAgentDefs: AgentDef[] = []; - let teams: Record = {}; - let activeTeamName = ""; - let gridCols = 2; - let widgetCtx: any; - let sessionDir = ""; - let contextWindow = 0; - - // ── Throttled Widget Update ────────────────── - - let widgetDirty = false; - let widgetTimer: ReturnType | null = null; - - function scheduleWidgetUpdate() { - widgetDirty = true; - if (widgetTimer) return; // already scheduled - widgetTimer = setTimeout(() => { - widgetTimer = null; - if (widgetDirty) { - widgetDirty = false; - doUpdateWidget(); - } - }, WIDGET_THROTTLE_MS); - } - - function flushWidgetUpdate() { - if (widgetTimer) { - clearTimeout(widgetTimer); - widgetTimer = null; - } - widgetDirty = false; - doUpdateWidget(); - } - - function loadAgents(cwd: string) { - sessionDir = join(cwd, ".pi", "agent-sessions"); - if (!existsSync(sessionDir)) { - mkdirSync(sessionDir, { recursive: true }); - } - - allAgentDefs = scanAgentDirs(cwd); - - const teamsPath = join(cwd, ".pi", "agents", "teams.yaml"); - if (existsSync(teamsPath)) { - try { - teams = parseTeamsYaml(readFileSync(teamsPath, "utf-8")); - } catch { - teams = {}; - } - } else { - teams = {}; - } - - if (Object.keys(teams).length === 0) { - teams = { all: allAgentDefs.map(d => d.name) }; - } - } - - function activateTeam(teamName: string) { - activeTeamName = teamName; - const members = teams[teamName] || []; - const defsByName = new Map(allAgentDefs.map(d => [d.name.toLowerCase(), d])); - - agentStates.clear(); - for (const member of members) { - const def = defsByName.get(member.toLowerCase()); - if (!def) continue; - const key = def.name.toLowerCase().replace(/\s+/g, "-"); - const sessionFile = join(sessionDir, `${key}.json`); - agentStates.set(def.name.toLowerCase(), { - def, - status: "idle", - task: "", - toolCount: 0, - elapsed: 0, - lastWork: "", - contextPct: 0, - sessionFile: existsSync(sessionFile) ? sessionFile : null, - runCount: 0, - }); - } - - const size = agentStates.size; - gridCols = size <= 3 ? size : size === 4 ? 2 : 3; - } - - // ── Kill all tracked child processes ───────── - - function killAllAgents() { - for (const proc of activeProcesses) { - try { proc.kill("SIGTERM"); } catch {} - } - // Force kill after 3s - setTimeout(() => { - for (const proc of activeProcesses) { - try { proc.kill("SIGKILL"); } catch {} - } - }, 3000); - } - - // ── Grid Rendering ─────────────────────────── - - function renderCard(state: AgentState, colWidth: number, theme: any): string[] { - const w = colWidth - 2; - const truncate = (s: string, max: number) => s.length > max ? s.slice(0, max - 3) + "..." : s; - - const statusColor = state.status === "idle" ? "dim" - : state.status === "running" ? "accent" - : state.status === "done" ? "success" : "error"; - const statusIcon = state.status === "idle" ? "○" - : state.status === "running" ? "●" - : state.status === "done" ? "✓" : "✗"; - - const name = displayName(state.def.name); - const nameStr = theme.fg("accent", theme.bold(truncate(name, w))); - const nameVisible = Math.min(name.length, w); - - const statusStr = `${statusIcon} ${state.status}`; - const timeStr = state.status !== "idle" ? ` ${Math.round(state.elapsed / 1000)}s` : ""; - const statusLine = theme.fg(statusColor, statusStr + timeStr); - const statusVisible = statusStr.length + timeStr.length; - - const filled = Math.ceil(state.contextPct / 20); - const bar = "#".repeat(filled) + "-".repeat(5 - filled); - const ctxStr = `[${bar}] ${Math.ceil(state.contextPct)}%`; - const ctxLine = theme.fg("dim", ctxStr); - const ctxVisible = ctxStr.length; - - const workRaw = state.task - ? (state.lastWork || state.task) - : state.def.description; - const workText = truncate(workRaw, Math.min(50, w - 1)); - const workLine = theme.fg("muted", workText); - const workVisible = workText.length; - - const top = "┌" + "─".repeat(w) + "┐"; - const bot = "└" + "─".repeat(w) + "┘"; - const border = (content: string, visLen: number) => - theme.fg("dim", "│") + content + " ".repeat(Math.max(0, w - visLen)) + theme.fg("dim", "│"); - - return [ - theme.fg("dim", top), - border(" " + nameStr, 1 + nameVisible), - border(" " + statusLine, 1 + statusVisible), - border(" " + ctxLine, 1 + ctxVisible), - border(" " + workLine, 1 + workVisible), - theme.fg("dim", bot), - ]; - } - - function doUpdateWidget() { - if (!widgetCtx) return; - - widgetCtx.ui.setWidget("agent-team", (_tui: any, theme: any) => { - const text = new Text("", 0, 1); - - return { - render(width: number): string[] { - if (agentStates.size === 0) { - text.setText(theme.fg("dim", "No agents found. Add .md files to agents/")); - return text.render(width); - } - - const cols = Math.min(gridCols, agentStates.size); - const gap = 1; - const colWidth = Math.floor((width - gap * (cols - 1)) / cols); - const agents = Array.from(agentStates.values()); - const rows: string[][] = []; - - for (let i = 0; i < agents.length; i += cols) { - const rowAgents = agents.slice(i, i + cols); - const cards = rowAgents.map(a => renderCard(a, colWidth, theme)); - - while (cards.length < cols) { - cards.push(Array(6).fill(" ".repeat(colWidth))); - } - - const cardHeight = cards[0].length; - for (let line = 0; line < cardHeight; line++) { - rows.push(cards.map(card => card[line] || "")); - } - } - - const output = rows.map(cols => cols.join(" ".repeat(gap))); - text.setText(output.join("\n")); - return text.render(width); - }, - invalidate() { - text.invalidate(); - }, - }; - }); - } - - // ── Dispatch Agent (returns Promise) ───────── - - function dispatchAgent( - agentName: string, - task: string, - ctx: any, - signal?: AbortSignal, - ): Promise<{ output: string; exitCode: number; elapsed: number }> { - const key = agentName.toLowerCase(); - const state = agentStates.get(key); - if (!state) { - return Promise.resolve({ - output: `Agent "${agentName}" not found. Available: ${Array.from(agentStates.values()).map(s => displayName(s.def.name)).join(", ")}`, - exitCode: 1, - elapsed: 0, - }); - } - - if (state.status === "running") { - return Promise.resolve({ - output: `Agent "${displayName(state.def.name)}" is already running. Wait for it to finish.`, - exitCode: 1, - elapsed: 0, - }); - } - - // Reset state for new run - state.status = "running"; - state.task = task; - state.toolCount = 0; - state.elapsed = 0; - state.lastWork = ""; - state.contextPct = 0; - state.runCount++; - scheduleWidgetUpdate(); - - const startTime = Date.now(); - state.timer = setInterval(() => { - state.elapsed = Date.now() - startTime; - scheduleWidgetUpdate(); - }, 1000); - - const model = ctx.model - ? `${ctx.model.provider}/${ctx.model.id}` - : "openrouter/google/gemini-3-flash-preview"; - - const agentKey = state.def.name.toLowerCase().replace(/\s+/g, "-"); - const agentSessionFile = join(sessionDir, `${agentKey}.json`); - - const args = [ - "--mode", "json", - "-p", - "--no-extensions", - "--model", model, - "--tools", state.def.tools, - "--thinking", "off", - "--append-system-prompt", state.def.systemPrompt, - "--session", agentSessionFile, - ]; - - if (state.sessionFile) { - args.push("-c"); - } - - args.push(task); - - const textChunks: string[] = []; - let resolved = false; - - return new Promise((promiseResolve) => { - // Guard against double-resolve - const safeResolve = (val: { output: string; exitCode: number; elapsed: number }) => { - if (resolved) return; - resolved = true; - promiseResolve(val); - }; - - const proc = spawn("pi", args, { - stdio: ["ignore", "pipe", "pipe"], - env: { ...process.env }, - }); - - state.proc = proc; - activeProcesses.add(proc); - - // ── Timeout guard ── - const timeout = setTimeout(() => { - try { proc.kill("SIGTERM"); } catch {} - // Force kill after 3s if still alive - setTimeout(() => { try { proc.kill("SIGKILL"); } catch {} }, 3000); - }, AGENT_TIMEOUT_MS); - - // ── AbortSignal support ── - const onAbort = () => { - try { proc.kill("SIGTERM"); } catch {} - setTimeout(() => { try { proc.kill("SIGKILL"); } catch {} }, 3000); - }; - if (signal) { - if (signal.aborted) { - onAbort(); - } else { - signal.addEventListener("abort", onAbort, { once: true }); - } - } - - let buffer = ""; - - proc.stdout!.setEncoding("utf-8"); - proc.stdout!.on("data", (chunk: string) => { - buffer += chunk; - const lines = buffer.split("\n"); - buffer = lines.pop() || ""; - for (const line of lines) { - if (!line.trim()) continue; - try { - const event = JSON.parse(line); - if (event.type === "message_update") { - const delta = event.assistantMessageEvent; - if (delta?.type === "text_delta") { - textChunks.push(delta.delta || ""); - const full = textChunks.join(""); - const last = full.split("\n").filter((l: string) => l.trim()).pop() || ""; - state.lastWork = last; - scheduleWidgetUpdate(); - } - } else if (event.type === "tool_execution_start") { - state.toolCount++; - scheduleWidgetUpdate(); - } else if (event.type === "message_end") { - const msg = event.message; - if (msg?.usage && contextWindow > 0) { - state.contextPct = ((msg.usage.input || 0) / contextWindow) * 100; - scheduleWidgetUpdate(); - } - } else if (event.type === "agent_end") { - const msgs = event.messages || []; - const last = [...msgs].reverse().find((m: any) => m.role === "assistant"); - if (last?.usage && contextWindow > 0) { - state.contextPct = ((last.usage.input || 0) / contextWindow) * 100; - scheduleWidgetUpdate(); - } - } - } catch {} - } - }); - - proc.stderr!.setEncoding("utf-8"); - proc.stderr!.on("data", () => {}); - - proc.on("close", (code) => { - clearTimeout(timeout); - if (signal) signal.removeEventListener?.("abort", onAbort); - activeProcesses.delete(proc); - state.proc = undefined; - - // Process any remaining buffer - if (buffer.trim()) { - try { - const event = JSON.parse(buffer); - if (event.type === "message_update") { - const delta = event.assistantMessageEvent; - if (delta?.type === "text_delta") textChunks.push(delta.delta || ""); - } - } catch {} - } - - clearInterval(state.timer); - state.elapsed = Date.now() - startTime; - - const timedOut = state.elapsed >= AGENT_TIMEOUT_MS; - state.status = timedOut ? "error" : (code === 0 ? "done" : "error"); - - if (code === 0) { - state.sessionFile = agentSessionFile; - } - - const full = textChunks.join(""); - state.lastWork = full.split("\n").filter((l: string) => l.trim()).pop() || ""; - flushWidgetUpdate(); - - const statusMsg = timedOut - ? `${displayName(state.def.name)} timed out after ${Math.round(AGENT_TIMEOUT_MS / 1000)}s` - : `${displayName(state.def.name)} ${state.status} in ${Math.round(state.elapsed / 1000)}s`; - - ctx.ui.notify(statusMsg, state.status === "done" ? "success" : "error"); - - const output = timedOut - ? full + "\n\n[TIMED OUT after " + Math.round(AGENT_TIMEOUT_MS / 1000) + "s]" - : full; - - safeResolve({ - output, - exitCode: code ?? 1, - elapsed: state.elapsed, - }); - }); - - proc.on("error", (err) => { - clearTimeout(timeout); - if (signal) signal.removeEventListener?.("abort", onAbort); - activeProcesses.delete(proc); - state.proc = undefined; - clearInterval(state.timer); - state.status = "error"; - state.lastWork = `Error: ${err.message}`; - flushWidgetUpdate(); - safeResolve({ - output: `Error spawning agent: ${err.message}`, - exitCode: 1, - elapsed: Date.now() - startTime, - }); - }); - }); - } - - // ── dispatch_agent Tool (single) ───────────── - - pi.registerTool({ - name: "dispatch_agent", - label: "Dispatch Agent", - description: "Dispatch a task to a single specialist agent. The agent executes the task and returns the result. For dispatching multiple agents in parallel, use dispatch_agents instead.", - parameters: Type.Object({ - agent: Type.String({ description: "Agent name (case-insensitive)" }), - task: Type.String({ description: "Task description for the agent to execute" }), - }), - - async execute(_toolCallId, params, signal, onUpdate, ctx) { - const { agent, task } = params as { agent: string; task: string }; - - try { - if (onUpdate) { - onUpdate({ - content: [{ type: "text", text: `Dispatching to ${agent}...` }], - details: { agent, task, status: "dispatching" }, - }); - } - - const result = await dispatchAgent(agent, task, ctx, signal); - - const truncated = result.output.length > 8000 - ? result.output.slice(0, 8000) + "\n\n... [truncated]" - : result.output; - - const status = result.exitCode === 0 ? "done" : "error"; - const summary = `[${agent}] ${status} in ${Math.round(result.elapsed / 1000)}s`; - - return { - content: [{ type: "text", text: `${summary}\n\n${truncated}` }], - details: { - agent, - task, - status, - elapsed: result.elapsed, - exitCode: result.exitCode, - fullOutput: result.output, - }, - }; - } catch (err: any) { - return { - content: [{ type: "text", text: `Error dispatching to ${agent}: ${err?.message || err}` }], - details: { agent, task, status: "error", elapsed: 0, exitCode: 1, fullOutput: "" }, - }; - } - }, - - renderCall(args, theme) { - const agentName = (args as any).agent || "?"; - const task = (args as any).task || ""; - const preview = task.length > 60 ? task.slice(0, 57) + "..." : task; - return new Text( - theme.fg("toolTitle", theme.bold("dispatch_agent ")) + - theme.fg("accent", agentName) + - theme.fg("dim", " — ") + - theme.fg("muted", preview), - 0, 0, - ); - }, - - renderResult(result, options, theme) { - const details = result.details as any; - if (!details) { - const text = result.content[0]; - return new Text(text?.type === "text" ? text.text : "", 0, 0); - } - - if (options.isPartial || details.status === "dispatching") { - return new Text( - theme.fg("accent", `● ${details.agent || "?"}`) + - theme.fg("dim", " working..."), - 0, 0, - ); - } - - const icon = details.status === "done" ? "✓" : "✗"; - const color = details.status === "done" ? "success" : "error"; - const elapsed = typeof details.elapsed === "number" ? Math.round(details.elapsed / 1000) : 0; - const header = theme.fg(color, `${icon} ${details.agent}`) + - theme.fg("dim", ` ${elapsed}s`); - - if (options.expanded && details.fullOutput) { - const output = details.fullOutput.length > 4000 - ? details.fullOutput.slice(0, 4000) + "\n... [truncated]" - : details.fullOutput; - return new Text(header + "\n" + theme.fg("muted", output), 0, 0); - } - - return new Text(header, 0, 0); - }, - }); - - // ── dispatch_agents Tool (parallel batch) ──── - - pi.registerTool({ - name: "dispatch_agents", - label: "Dispatch Agents (Parallel)", - description: "Dispatch tasks to multiple specialist agents in parallel. All agents run simultaneously and results are returned together. Much faster than sequential dispatch_agent calls when tasks are independent.", - parameters: Type.Object({ - dispatches: Type.Array( - Type.Object({ - agent: Type.String({ description: "Agent name (case-insensitive)" }), - task: Type.String({ description: "Task description for the agent" }), - }), - { description: "Array of {agent, task} pairs to dispatch in parallel", minItems: 1 }, - ), - }), - - async execute(_toolCallId, params, signal, onUpdate, ctx) { - const { dispatches } = params as { dispatches: { agent: string; task: string }[] }; - - const agentNames = dispatches.map(d => d.agent).join(", "); - if (onUpdate) { - onUpdate({ - content: [{ type: "text", text: `Dispatching ${dispatches.length} agents in parallel: ${agentNames}` }], - details: { dispatches, status: "dispatching", count: dispatches.length }, - }); - } - - // Launch all in parallel - const promises = dispatches.map(({ agent, task }) => - dispatchAgent(agent, task, ctx, signal).then(result => ({ - agent, - task, - ...result, - })) - ); - - const results = await Promise.all(promises); - - const summaryParts: string[] = []; - const allDetails: any[] = []; - - for (const r of results) { - const status = r.exitCode === 0 ? "done" : "error"; - const truncated = r.output.length > 4000 - ? r.output.slice(0, 4000) + "\n... [truncated]" - : r.output; - summaryParts.push(`## [${r.agent}] ${status} in ${Math.round(r.elapsed / 1000)}s\n\n${truncated}`); - allDetails.push({ - agent: r.agent, - task: r.task, - status, - elapsed: r.elapsed, - exitCode: r.exitCode, - fullOutput: r.output, - }); - } - - const doneCount = results.filter(r => r.exitCode === 0).length; - const header = `Parallel dispatch complete: ${doneCount}/${results.length} succeeded`; - - return { - content: [{ type: "text", text: `${header}\n\n${summaryParts.join("\n\n---\n\n")}` }], - details: { - dispatches: allDetails, - status: "complete", - count: results.length, - succeeded: doneCount, - }, - }; - }, - - renderCall(args, theme) { - const dispatches = (args as any).dispatches || []; - const names = dispatches.map((d: any) => d.agent || "?").join(", "); - return new Text( - theme.fg("toolTitle", theme.bold("dispatch_agents ")) + - theme.fg("accent", `[${dispatches.length}] `) + - theme.fg("muted", names), - 0, 0, - ); - }, - - renderResult(result, options, theme) { - const details = result.details as any; - if (!details) { - const text = result.content[0]; - return new Text(text?.type === "text" ? text.text : "", 0, 0); - } - - if (options.isPartial || details.status === "dispatching") { - return new Text( - theme.fg("accent", `● Parallel dispatch`) + - theme.fg("dim", ` ${details.count || "?"} agents working...`), - 0, 0, - ); - } - - const header = theme.fg("success", `✓ ${details.succeeded}`) + - theme.fg("dim", `/${details.count} agents completed`); - - if (options.expanded && Array.isArray(details.dispatches)) { - const lines = details.dispatches.map((d: any) => { - const icon = d.status === "done" ? "✓" : "✗"; - const color = d.status === "done" ? "success" : "error"; - return theme.fg(color, ` ${icon} ${d.agent}`) + - theme.fg("dim", ` ${Math.round(d.elapsed / 1000)}s`); - }); - return new Text(header + "\n" + lines.join("\n"), 0, 0); - } - - return new Text(header, 0, 0); - }, - }); - - // ── Commands ───────────────────────────────── - - pi.registerCommand("agents-team", { - description: "Select a team to work with", - handler: async (_args, ctx) => { - widgetCtx = ctx; - const teamNames = Object.keys(teams); - if (teamNames.length === 0) { - ctx.ui.notify("No teams defined in .pi/agents/teams.yaml", "warning"); - return; - } - - const options = teamNames.map(name => { - const members = teams[name].map(m => displayName(m)); - return `${name} — ${members.join(", ")}`; - }); - - const choice = await ctx.ui.select("Select Team", options); - if (choice === undefined) return; - - const idx = options.indexOf(choice); - const name = teamNames[idx]; - activateTeam(name); - flushWidgetUpdate(); - ctx.ui.setStatus("agent-team", `Team: ${name} (${agentStates.size})`); - ctx.ui.notify(`Team: ${name} — ${Array.from(agentStates.values()).map(s => displayName(s.def.name)).join(", ")}`, "info"); - }, - }); - - pi.registerCommand("agents-list", { - description: "List all loaded agents", - handler: async (_args, _ctx) => { - widgetCtx = _ctx; - const names = Array.from(agentStates.values()) - .map(s => { - const session = s.sessionFile ? "resumed" : "new"; - return `${displayName(s.def.name)} (${s.status}, ${session}, runs: ${s.runCount}): ${s.def.description}`; - }) - .join("\n"); - _ctx.ui.notify(names || "No agents loaded", "info"); - }, - }); - - pi.registerCommand("agents-grid", { - description: "Set grid columns: /agents-grid <1-6>", - getArgumentCompletions: (prefix: string): AutocompleteItem[] | null => { - const items = ["1", "2", "3", "4", "5", "6"].map(n => ({ - value: n, - label: `${n} columns`, - })); - const filtered = items.filter(i => i.value.startsWith(prefix)); - return filtered.length > 0 ? filtered : items; - }, - handler: async (args, _ctx) => { - widgetCtx = _ctx; - const n = parseInt(args?.trim() || "", 10); - if (n >= 1 && n <= 6) { - gridCols = n; - _ctx.ui.notify(`Grid set to ${gridCols} columns`, "info"); - flushWidgetUpdate(); - } else { - _ctx.ui.notify("Usage: /agents-grid <1-6>", "error"); - } - }, - }); - - // ── System Prompt Override ─────────────────── - - pi.on("before_agent_start", async (_event, _ctx) => { - const agentCatalog = Array.from(agentStates.values()) - .map(s => `### ${displayName(s.def.name)}\n**Dispatch as:** \`${s.def.name}\`\n${s.def.description}\n**Tools:** ${s.def.tools}`) - .join("\n\n"); - - const teamMembers = Array.from(agentStates.values()).map(s => displayName(s.def.name)).join(", "); - - return { - systemPrompt: `You are a dispatcher agent. You coordinate specialist agents to accomplish tasks. -You do NOT have direct access to the codebase. You MUST delegate all work through -agents using the dispatch_agent or dispatch_agents tools. - -## Active Team: ${activeTeamName} -Members: ${teamMembers} -You can ONLY dispatch to agents listed below. Do not attempt to dispatch to agents outside this team. - -## How to Work -- Analyze the user's request and break it into clear sub-tasks -- Choose the right agent(s) for each sub-task -- **Use dispatch_agents for independent parallel tasks** — this is much faster -- Use dispatch_agent for sequential tasks where order matters -- Review results and dispatch follow-up agents if needed -- If a task fails, try a different agent or adjust the task description -- Summarize the outcome for the user - -## Rules -- NEVER try to read, write, or execute code directly — you have no such tools -- ALWAYS use dispatch_agent or dispatch_agents to get work done -- **Prefer dispatch_agents when tasks are independent** — parallelism saves time -- You can chain agents: use scout to explore, then builder to implement -- You can dispatch the same agent multiple times with different tasks -- Keep tasks focused — one clear objective per dispatch -- Each agent has a ${Math.round(AGENT_TIMEOUT_MS / 1000)}s timeout — break large tasks into smaller ones - -## Agents - -${agentCatalog}`, - }; - }); - - // ── Session Start ──────────────────────────── - - pi.on("session_start", async (_event, _ctx) => { - applyExtensionDefaults(import.meta.url, _ctx); - if (widgetCtx) { - widgetCtx.ui.setWidget("agent-team", undefined); - } - widgetCtx = _ctx; - contextWindow = _ctx.model?.contextWindow || 0; - - // Wipe old agent session files so subagents start fresh - const sessDir = join(_ctx.cwd, ".pi", "agent-sessions"); - if (existsSync(sessDir)) { - for (const f of readdirSync(sessDir)) { - if (f.endsWith(".json")) { - try { unlinkSync(join(sessDir, f)); } catch {} - } - } - } - - loadAgents(_ctx.cwd); - - const teamNames = Object.keys(teams); - if (teamNames.length > 0) { - activateTeam(teamNames[0]); - } - - pi.setActiveTools(["dispatch_agent", "dispatch_agents"]); - - _ctx.ui.setStatus("agent-team", `Team: ${activeTeamName} (${agentStates.size})`); - const members = Array.from(agentStates.values()).map(s => displayName(s.def.name)).join(", "); - _ctx.ui.notify( - `Team: ${activeTeamName} (${members})\n` + - `Team sets loaded from: .pi/agents/teams.yaml\n\n` + - `/agents-team Select a team\n` + - `/agents-list List active agents and status\n` + - `/agents-grid <1-6> Set grid column count`, - "info", - ); - flushWidgetUpdate(); - - _ctx.ui.setFooter((_tui, theme, _footerData) => ({ - dispose: () => {}, - invalidate() {}, - render(width: number): string[] { - const model = _ctx.model?.id || "no-model"; - const usage = _ctx.getContextUsage(); - const pct = usage ? usage.percent : 0; - const filled = Math.round(pct / 10); - const bar = "#".repeat(filled) + "-".repeat(10 - filled); - - const running = Array.from(agentStates.values()).filter(s => s.status === "running").length; - const runningStr = running > 0 ? theme.fg("accent", ` ● ${running} running`) : ""; - - const left = theme.fg("dim", ` ${model}`) + - theme.fg("muted", " · ") + - theme.fg("accent", activeTeamName) + - runningStr; - const right = theme.fg("dim", `[${bar}] ${Math.round(pct)}% `); - const pad = " ".repeat(Math.max(1, width - visibleWidth(left) - visibleWidth(right))); - - return [truncateToWidth(left + pad + right, width)]; - }, - })); - }); - - // ── Cleanup on exit ────────────────────────── - - process.on("exit", () => killAllAgents()); - process.on("SIGINT", () => { killAllAgents(); process.exit(0); }); - process.on("SIGTERM", () => { killAllAgents(); process.exit(0); }); -} diff --git a/extensions/cross-agent.ts b/extensions/cross-agent.ts deleted file mode 100644 index a7e17a4..0000000 --- a/extensions/cross-agent.ts +++ /dev/null @@ -1,265 +0,0 @@ -/** - * Cross-Agent — Load commands, skills, and agents from other AI coding agents - * - * Scans .claude/, .gemini/, .codex/ directories (project + global) for: - * commands/*.md → registered as /name - * skills/ → listed as /skill:name (discovery only) - * agents/*.md → listed as @name (discovery only) - * - * Usage: pi -e extensions/cross-agent.ts - */ - -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; -import { readdirSync, readFileSync, existsSync, statSync } from "node:fs"; -import { join, basename } from "node:path"; -import { homedir } from "node:os"; -import { applyExtensionDefaults } from "./themeMap.ts"; -import { wrapTextWithAnsi, visibleWidth } from "@mariozechner/pi-tui"; - -// --- Synthwave palette --- -function bg(s: string): string { - return `\x1b[48;2;52;20;58m${s}\x1b[49m`; -} -function pink(s: string): string { - return `\x1b[38;2;255;126;219m${s}\x1b[39m`; -} -function cyan(s: string): string { - return `\x1b[38;2;54;249;246m${s}\x1b[39m`; -} -function green(s: string): string { - return `\x1b[38;2;114;241;184m${s}\x1b[39m`; -} -function yellow(s: string): string { - return `\x1b[38;2;254;222;93m${s}\x1b[39m`; -} -function dim(s: string): string { - return `\x1b[38;2;120;100;140m${s}\x1b[39m`; -} -function bold(s: string): string { - return `\x1b[1m${s}\x1b[22m`; -} - -interface Discovered { - name: string; - description: string; - content: string; -} - -interface SourceGroup { - source: string; - commands: Discovered[]; - skills: string[]; - agents: Discovered[]; -} - -function parseFrontmatter(raw: string): { description: string; body: string; fields: Record } { - const match = raw.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/); - if (!match) return { description: "", body: raw, fields: {} }; - - const front = match[1]; - const body = match[2]; - const fields: Record = {}; - for (const line of front.split("\n")) { - const idx = line.indexOf(":"); - if (idx > 0) fields[line.slice(0, idx).trim()] = line.slice(idx + 1).trim(); - } - return { description: fields.description || "", body, fields }; -} - -function expandArgs(template: string, args: string): string { - const parts = args.split(/\s+/).filter(Boolean); - let result = template; - result = result.replace(/\$ARGUMENTS|\$@/g, args); - for (let i = 0; i < parts.length; i++) { - result = result.replaceAll(`$${i + 1}`, parts[i]); - } - return result; -} - -function scanCommands(dir: string): Discovered[] { - if (!existsSync(dir)) return []; - const items: Discovered[] = []; - try { - for (const file of readdirSync(dir)) { - if (!file.endsWith(".md")) continue; - const raw = readFileSync(join(dir, file), "utf-8"); - const { description, body } = parseFrontmatter(raw); - items.push({ - name: basename(file, ".md"), - description: description || body.split("\n").find((l) => l.trim())?.trim() || "", - content: body, - }); - } - } catch {} - return items; -} - -function scanSkills(dir: string): string[] { - if (!existsSync(dir)) return []; - const names: string[] = []; - try { - for (const entry of readdirSync(dir)) { - const skillFile = join(dir, entry, "SKILL.md"); - const flatFile = join(dir, entry); - if (existsSync(skillFile) && statSync(skillFile).isFile()) { - names.push(entry); - } else if (entry.endsWith(".md") && statSync(flatFile).isFile()) { - names.push(basename(entry, ".md")); - } - } - } catch {} - return names; -} - -function scanAgents(dir: string): Discovered[] { - if (!existsSync(dir)) return []; - const items: Discovered[] = []; - try { - for (const file of readdirSync(dir)) { - if (!file.endsWith(".md")) continue; - const raw = readFileSync(join(dir, file), "utf-8"); - const { fields } = parseFrontmatter(raw); - items.push({ - name: fields.name || basename(file, ".md"), - description: fields.description || "", - content: raw, - }); - } - } catch {} - return items; -} - -export default function (pi: ExtensionAPI) { - pi.on("session_start", async (_event, ctx) => { - applyExtensionDefaults(import.meta.url, ctx); - const home = homedir(); - const cwd = ctx.cwd; - const providers = ["claude", "gemini", "codex"]; - const groups: SourceGroup[] = []; - - for (const p of providers) { - for (const [dir, label] of [ - [join(cwd, `.${p}`), `.${p}`], - [join(home, `.${p}`), `~/.${p}`], - ] as const) { - const commands = scanCommands(join(dir, "commands")); - const skills = scanSkills(join(dir, "skills")); - const agents = scanAgents(join(dir, "agents")); - - if (commands.length || skills.length || agents.length) { - groups.push({ source: label, commands, skills, agents }); - } - } - } - - // Also scan .pi/agents/ (pi-vs-cc pattern) - const localAgents = scanAgents(join(cwd, ".pi", "agents")); - if (localAgents.length) { - groups.push({ source: ".pi/agents", commands: [], skills: [], agents: localAgents }); - } - - // Register commands - const seenCmds = new Set(); - let totalCommands = 0; - let totalSkills = 0; - let totalAgents = 0; - - for (const g of groups) { - totalSkills += g.skills.length; - totalAgents += g.agents.length; - - for (const cmd of g.commands) { - if (seenCmds.has(cmd.name)) continue; - seenCmds.add(cmd.name); - totalCommands++; - pi.registerCommand(cmd.name, { - description: `[${g.source}] ${cmd.description}`.slice(0, 120), - handler: async (args) => { - pi.sendUserMessage(expandArgs(cmd.content, args || "")); - }, - }); - } - } - - if (groups.length === 0) return; - - // We delay slightly so it doesn't get instantly overwritten by system-select's default startup notify - setTimeout(() => { - if (!ctx.hasUI) return; - // Reduce max width slightly to ensure it never overflows and breaks the next line - const width = Math.min((process.stdout.columns || 80) - 4, 100); - const pad = bg(" ".repeat(width)); - const lines: string[] = []; - - lines.push(""); // space from prev - - for (let i = 0; i < groups.length; i++) { - const g = groups[i]; - - // Title with counts - const counts: string[] = []; - if (g.skills.length) counts.push(yellow("(") + green(`${g.skills.length}`) + dim(` skill${g.skills.length > 1 ? "s" : ""}`) + yellow(")")); - if (g.commands.length) counts.push(yellow("(") + green(`${g.commands.length}`) + dim(` command${g.commands.length > 1 ? "s" : ""}`) + yellow(")")); - if (g.agents.length) counts.push(yellow("(") + green(`${g.agents.length}`) + dim(` agent${g.agents.length > 1 ? "s" : ""}`) + yellow(")")); - const countStr = counts.length ? " " + counts.join(" ") : ""; - lines.push(pink(bold(` ${g.source}`)) + countStr); - - // Build body content - const items: string[] = []; - if (g.commands.length) { - items.push( - yellow("/") + - g.commands.map((c) => cyan(c.name)).join(yellow(", /")) - ); - } - if (g.skills.length) { - items.push( - yellow("/skill:") + - g.skills.map((s) => cyan(s)).join(yellow(", /skill:")) - ); - } - if (g.agents.length) { - items.push( - yellow("@") + - g.agents.map((a) => green(a.name)).join(yellow(", @")) - ); - } - - const body = items.join("\n"); - - // Top padding - lines.push(pad); - - // Wrap body text, cap at 3 rows - const maxRows = 3; - const innerWidth = width - 4; - const wrapped = wrapTextWithAnsi(body, innerWidth); - const totalItems = g.commands.length + g.skills.length + g.agents.length; - const shown = wrapped.slice(0, maxRows); - - for (const wline of shown) { - const vis = visibleWidth(wline); - const fill = Math.max(0, width - vis - 4); - lines.push(bg(" " + wline + " ".repeat(fill) + " ")); - } - - if (wrapped.length > maxRows) { - const overflow = dim(` ... ${totalItems - 15 > 0 ? totalItems - 15 : "more"} more`); - const oVis = visibleWidth(overflow); - const oFill = Math.max(0, width - oVis - 2); - lines.push(bg(overflow + " ".repeat(oFill) + " ")); - } - - // Bottom padding - lines.push(pad); - - // Spacing between groups - if (i < groups.length - 1) lines.push(""); - } - - // We send it as "info" which forces it to be a raw text element in the chat - // without the widget container, but preserving all our ANSI colors! - ctx.ui.notify(lines.join("\n"), "info"); - }, 100); - }); -} diff --git a/extensions/damage-control.ts b/extensions/damage-control.ts deleted file mode 100644 index c34912a..0000000 --- a/extensions/damage-control.ts +++ /dev/null @@ -1,206 +0,0 @@ -import type { ExtensionAPI, ToolCallEvent } from "@mariozechner/pi-coding-agent"; -import { isToolCallEventType } from "@mariozechner/pi-coding-agent"; -import { parse as yamlParse } from "yaml"; -import * as fs from "fs"; -import * as path from "path"; -import * as os from "os"; -import { applyExtensionDefaults } from "./themeMap.ts"; - -interface Rule { - pattern: string; - reason: string; - ask?: boolean; -} - -interface Rules { - bashToolPatterns: Rule[]; - zeroAccessPaths: string[]; - readOnlyPaths: string[]; - noDeletePaths: string[]; -} - -export default function (pi: ExtensionAPI) { - let rules: Rules = { - bashToolPatterns: [], - zeroAccessPaths: [], - readOnlyPaths: [], - noDeletePaths: [], - }; - - function resolvePath(p: string, cwd: string): string { - if (p.startsWith("~")) { - p = path.join(os.homedir(), p.slice(1)); - } - return path.resolve(cwd, p); - } - - function isPathMatch(targetPath: string, pattern: string, cwd: string): boolean { - // Simple glob-to-regex or substring match - // Expand tilde in pattern if present - const resolvedPattern = pattern.startsWith("~") ? path.join(os.homedir(), pattern.slice(1)) : pattern; - - // If pattern ends with /, it's a directory match - if (resolvedPattern.endsWith("/")) { - const absolutePattern = path.isAbsolute(resolvedPattern) ? resolvedPattern : path.resolve(cwd, resolvedPattern); - return targetPath.startsWith(absolutePattern); - } - - // Handle basic wildcards * - const regexPattern = resolvedPattern - .replace(/[.+^${}()|[\]\\]/g, "\\$&") // escape regex chars - .replace(/\*/g, ".*"); // convert * to .* - - const regex = new RegExp(`^${regexPattern}$|^${regexPattern}/|/${regexPattern}$|/${regexPattern}/`); - - // Match against absolute path and relative-to-cwd path - const relativePath = path.relative(cwd, targetPath); - - return regex.test(targetPath) || regex.test(relativePath) || targetPath.includes(resolvedPattern) || relativePath.includes(resolvedPattern); - } - - pi.on("session_start", async (_event, ctx) => { - applyExtensionDefaults(import.meta.url, ctx); - const rulesPath = path.join(ctx.cwd, ".pi", "damage-control-rules.yaml"); - try { - if (fs.existsSync(rulesPath)) { - const content = fs.readFileSync(rulesPath, "utf8"); - const loaded = yamlParse(content) as Partial; - rules = { - bashToolPatterns: loaded.bashToolPatterns || [], - zeroAccessPaths: loaded.zeroAccessPaths || [], - readOnlyPaths: loaded.readOnlyPaths || [], - noDeletePaths: loaded.noDeletePaths || [], - }; - ctx.ui.notify(`🛡️ Damage-Control: Loaded ${rules.bashToolPatterns.length + rules.zeroAccessPaths.length + rules.readOnlyPaths.length + rules.noDeletePaths.length} rules.`); - } else { - ctx.ui.notify("🛡️ Damage-Control: No rules found at .pi/damage-control-rules.yaml"); - } - } catch (err) { - ctx.ui.notify(`🛡️ Damage-Control: Failed to load rules: ${err instanceof Error ? err.message : String(err)}`); - } - - ctx.ui.setStatus(`🛡️ Damage-Control Active: ${rules.bashToolPatterns.length + rules.zeroAccessPaths.length + rules.readOnlyPaths.length + rules.noDeletePaths.length} Rules`); - }); - - pi.on("tool_call", async (event, ctx) => { - let violationReason: string | null = null; - let shouldAsk = false; - - // 1. Check Zero Access Paths for all tools that use path or glob - const checkPaths = (pathsToCheck: string[]) => { - for (const p of pathsToCheck) { - const resolved = resolvePath(p, ctx.cwd); - for (const zap of rules.zeroAccessPaths) { - if (isPathMatch(resolved, zap, ctx.cwd)) { - return `Access to zero-access path restricted: ${zap}`; - } - } - } - return null; - }; - - // Extract paths from tool input - const inputPaths: string[] = []; - if (isToolCallEventType("read", event) || isToolCallEventType("write", event) || isToolCallEventType("edit", event)) { - inputPaths.push(event.input.path); - } else if (isToolCallEventType("grep", event) || isToolCallEventType("find", event) || isToolCallEventType("ls", event)) { - inputPaths.push(event.input.path || "."); - } - - if (isToolCallEventType("grep", event) && event.input.glob) { - // Check glob field as well - for (const zap of rules.zeroAccessPaths) { - if (event.input.glob.includes(zap) || isPathMatch(event.input.glob, zap, ctx.cwd)) { - violationReason = `Glob matches zero-access path: ${zap}`; - break; - } - } - } - - if (!violationReason) { - violationReason = checkPaths(inputPaths); - } - - // 2. Tool-specific logic - if (!violationReason) { - if (isToolCallEventType("bash", event)) { - const command = event.input.command; - - // Check bashToolPatterns - for (const rule of rules.bashToolPatterns) { - const regex = new RegExp(rule.pattern); - if (regex.test(command)) { - violationReason = rule.reason; - shouldAsk = !!rule.ask; - break; - } - } - - // Check if bash command interacts with restricted paths - if (!violationReason) { - for (const zap of rules.zeroAccessPaths) { - if (command.includes(zap)) { - violationReason = `Bash command references zero-access path: ${zap}`; - break; - } - } - } - - if (!violationReason) { - for (const rop of rules.readOnlyPaths) { - // Heuristic: check if command might modify a read-only path - // Redirects, sed -i, rm, mv to, etc. - if (command.includes(rop) && (/[\s>|]/.test(command) || command.includes("rm") || command.includes("mv") || command.includes("sed"))) { - violationReason = `Bash command may modify read-only path: ${rop}`; - break; - } - } - } - - if (!violationReason) { - for (const ndp of rules.noDeletePaths) { - if (command.includes(ndp) && (command.includes("rm") || command.includes("mv"))) { - violationReason = `Bash command attempts to delete/move protected path: ${ndp}`; - break; - } - } - } - } else if (isToolCallEventType("write", event) || isToolCallEventType("edit", event)) { - // Check Read-Only paths - for (const p of inputPaths) { - const resolved = resolvePath(p, ctx.cwd); - for (const rop of rules.readOnlyPaths) { - if (isPathMatch(resolved, rop, ctx.cwd)) { - violationReason = `Modification of read-only path restricted: ${rop}`; - break; - } - } - } - } - } - - if (violationReason) { - if (shouldAsk) { - const confirmed = await ctx.ui.confirm("🛡️ Damage-Control Confirmation", `Dangerous command detected: ${violationReason}\n\nCommand: ${isToolCallEventType("bash", event) ? event.input.command : JSON.stringify(event.input)}\n\nDo you want to proceed?`, { timeout: 30000 }); - - if (!confirmed) { - ctx.ui.setStatus(`⚠️ Last Violation Blocked: ${violationReason.slice(0, 30)}...`); - pi.appendEntry("damage-control-log", { tool: event.toolName, input: event.input, rule: violationReason, action: "blocked_by_user" }); - ctx.abort(); - return { block: true, reason: `🛑 BLOCKED by Damage-Control: ${violationReason} (User denied)\n\nDO NOT attempt to work around this restriction. DO NOT retry with alternative commands, paths, or approaches that achieve the same result. Report this block to the user exactly as stated and ask how they would like to proceed.` }; - } else { - pi.appendEntry("damage-control-log", { tool: event.toolName, input: event.input, rule: violationReason, action: "confirmed_by_user" }); - return { block: false }; - } - } else { - ctx.ui.notify(`🛑 Damage-Control: Blocked ${event.toolName} due to ${violationReason}`); - ctx.ui.setStatus(`⚠️ Last Violation: ${violationReason.slice(0, 30)}...`); - pi.appendEntry("damage-control-log", { tool: event.toolName, input: event.input, rule: violationReason, action: "blocked" }); - ctx.abort(); - return { block: true, reason: `🛑 BLOCKED by Damage-Control: ${violationReason}\n\nDO NOT attempt to work around this restriction. DO NOT retry with alternative commands, paths, or approaches that achieve the same result. Report this block to the user exactly as stated and ask how they would like to proceed.` }; - } - } - - return { block: false }; - }); -} diff --git a/extensions/minimal.ts b/extensions/minimal.ts deleted file mode 100644 index 5fed862..0000000 --- a/extensions/minimal.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Minimal — Model name + context meter in a compact footer - * - * Shows model ID and a 10-block context usage bar: [###-------] 30% - * - * Usage: pi -e extensions/minimal.ts - */ - -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; -import { applyExtensionDefaults } from "./themeMap.ts"; -import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui"; - -export default function (pi: ExtensionAPI) { - pi.on("session_start", async (_event, ctx) => { - applyExtensionDefaults(import.meta.url, ctx); - ctx.ui.setFooter((_tui, theme, _footerData) => ({ - dispose: () => {}, - invalidate() {}, - render(width: number): string[] { - const model = ctx.model?.id || "no-model"; - const usage = ctx.getContextUsage(); - const pct = (usage && usage.percent !== null) ? usage.percent : 0; - const filled = Math.round(pct / 10); - const bar = "#".repeat(filled) + "-".repeat(10 - filled); - - const left = theme.fg("dim", ` ${model}`); - const right = theme.fg("dim", `[${bar}] ${Math.round(pct)}% `); - const pad = " ".repeat(Math.max(1, width - visibleWidth(left) - visibleWidth(right))); - - return [truncateToWidth(left + pad + right, width)]; - }, - })); - }); -} \ No newline at end of file diff --git a/extensions/observatory.ts b/extensions/observatory.ts deleted file mode 100644 index 7a22536..0000000 --- a/extensions/observatory.ts +++ /dev/null @@ -1,1100 +0,0 @@ -/** - * Observatory — Comprehensive observability dashboard for the Pi coding agent - * - * A completely passive extension that observes and records every event — - * session starts, tool calls, agent turns, errors, context usage — into - * an append-only JSONL log and rolling summary on disk. - * - * Surfaces: - * - Widget: compact live dashboard with session stats, tool counts, cost - * - Footer: single-line status bar with model, context %, event count - * - Overlay: full-screen dashboard (4 views) opened via /obs command - * - Export: markdown report via /obs export - * - * Storage: .pi/observatory/events.jsonl, .pi/observatory/summary.json - * - * Commands: - * /obs or /observatory — open dashboard overlay - * /obs clear — wipe all observatory data - * /obs export — write markdown report - * /obs agents — overlay → agents view - * /obs tools — overlay → tools view - * /obs timeline — overlay → timeline view - * - * Usage: pi -e extensions/observatory.ts - */ - -import type { AssistantMessage } from "@mariozechner/pi-ai"; -import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent"; -import { DynamicBorder } from "@mariozechner/pi-coding-agent"; -import { Container, matchesKey, Text, truncateToWidth } from "@mariozechner/pi-tui"; -import * as fs from "fs"; -import * as path from "path"; -import { applyExtensionDefaults } from "./themeMap.ts"; - -// ── Data Model ───────────────────────────────────────────────────────── - -type ObsEventType = - | "session_start" - | "session_switch" - | "session_fork" - | "tool_call" - | "tool_end" - | "agent_start" - | "agent_end" - | "dispatch" - | "error" - | "blocked"; - -interface ObsEvent { - ts: number; - type: ObsEventType; - sessionId: string; - tool?: string; - durationMs?: number; - blocked?: boolean; - blockReason?: string; - agentTurn?: number; - contextPercent?: number; - tokensIn?: number; - tokensOut?: number; - cost?: number; - error?: string; - meta?: Record; -} - -interface ObsSummary { - totalSessions: number; - totalEvents: number; - totalToolCalls: number; - totalAgentTurns: number; - totalErrors: number; - totalBlocked: number; - totalTokensIn: number; - totalTokensOut: number; - totalCost: number; - toolCounts: Record; - toolDurations: Record; - toolBlocked: Record; - sessions: SessionSummary[]; - lastUpdated: number; -} - -interface SessionSummary { - sessionId: string; - startedAt: number; - endedAt?: number; - model?: string; - toolCalls: number; - agentTurns: number; - errors: number; - blocked: number; - tokensIn: number; - tokensOut: number; - cost: number; - peakContextPercent: number; -} - -// ── Helpers ──────────────────────────────────────────────────────────── - -function fmtDuration(ms: number): string { - if (ms < 1000) return `${ms}ms`; - const secs = Math.floor(ms / 1000); - if (secs < 60) return `${secs}s`; - const mins = Math.floor(secs / 60); - const remSecs = secs % 60; - if (mins < 60) return `${mins}m ${remSecs}s`; - const hrs = Math.floor(mins / 60); - const remMins = mins % 60; - return `${hrs}h ${remMins}m`; -} - -function fmtTokens(n: number): string { - if (n < 1000) return `${n}`; - return `${(n / 1000).toFixed(1)}k`; -} - -function fmtCost(n: number): string { - return `$${n.toFixed(4)}`; -} - -function fmtTime(ts: number): string { - const d = new Date(ts); - return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" }); -} - -function fmtDate(ts: number): string { - const d = new Date(ts); - return d.toLocaleDateString([], { year: "numeric", month: "2-digit", day: "2-digit" }); -} - -function shortId(): string { - return Math.random().toString(36).slice(2, 8); -} - -function emptySummary(): ObsSummary { - return { - totalSessions: 0, - totalEvents: 0, - totalToolCalls: 0, - totalAgentTurns: 0, - totalErrors: 0, - totalBlocked: 0, - totalTokensIn: 0, - totalTokensOut: 0, - totalCost: 0, - toolCounts: {}, - toolDurations: {}, - toolBlocked: {}, - sessions: [], - lastUpdated: Date.now(), - }; -} - -function emptySessionSummary(sessionId: string, model?: string): SessionSummary { - return { - sessionId, - startedAt: Date.now(), - model, - toolCalls: 0, - agentTurns: 0, - errors: 0, - blocked: 0, - tokensIn: 0, - tokensOut: 0, - cost: 0, - peakContextPercent: 0, - }; -} - -// ── Token/Cost computation (follows tool-counter.ts pattern) ─────────── - -function computeTokensAndCost(ctx: ExtensionContext): { tokensIn: number; tokensOut: number; cost: number } { - let tokensIn = 0; - let tokensOut = 0; - let cost = 0; - try { - for (const entry of ctx.sessionManager.getBranch()) { - if (entry.type === "message" && entry.message.role === "assistant") { - const m = entry.message as AssistantMessage; - tokensIn += m.usage.input; - tokensOut += m.usage.output; - cost += m.usage.cost.total; - } - } - } catch { - // Session not yet ready — return zeros - } - return { tokensIn, tokensOut, cost }; -} - -// ── Extension ────────────────────────────────────────────────────────── - -export default function (pi: ExtensionAPI) { - // ── State ────────────────────────────────────────────────────────── - - let obsDir = ""; - let eventsPath = ""; - let summaryPath = ""; - - let sessionId = ""; - let sessionStartTs = 0; - let summary: ObsSummary = emptySummary(); - let currentSession: SessionSummary | null = null; - let sessionEvents: ObsEvent[] = []; - - let widgetCtx: ExtensionContext | null = null; - - // Tool call timing: Map — FIFO queue per tool - const toolTimers: Map = new Map(); - - // Agent turn tracking - let agentTurnCount = 0; - let agentTurnStartTs = 0; - - // Previous token/cost values for delta-based cross-session accumulation - let prevTokensIn = 0; - let prevTokensOut = 0; - let prevCost = 0; - - // Debounced summary flush - let summaryDirty = false; - let flushTimer: ReturnType | null = null; - - // ── Storage Layer ────────────────────────────────────────────────── - - function ensureDir() { - try { - if (!fs.existsSync(obsDir)) { - fs.mkdirSync(obsDir, { recursive: true }); - } - } catch {} - } - - function loadSummary() { - try { - if (fs.existsSync(summaryPath)) { - const raw = fs.readFileSync(summaryPath, "utf-8"); - const loaded = JSON.parse(raw) as ObsSummary; - // Merge loaded with defaults for any missing fields - summary = { ...emptySummary(), ...loaded }; - // Cap stored durations - for (const tool of Object.keys(summary.toolDurations)) { - if (summary.toolDurations[tool].length > 200) { - summary.toolDurations[tool] = summary.toolDurations[tool].slice(-200); - } - } - } - } catch { - summary = emptySummary(); - } - } - - function saveSummary() { - try { - summary.lastUpdated = Date.now(); - fs.writeFileSync(summaryPath, JSON.stringify(summary, null, 2), "utf-8"); - summaryDirty = false; - } catch {} - } - - function markDirty() { - summaryDirty = true; - } - - function startFlushTimer() { - if (flushTimer) return; - flushTimer = setInterval(() => { - if (summaryDirty) saveSummary(); - }, 5000); - } - - function stopFlushTimer() { - if (flushTimer) { - clearInterval(flushTimer); - flushTimer = null; - } - if (summaryDirty) saveSummary(); - } - - function appendEvent(evt: ObsEvent) { - try { - ensureDir(); - fs.appendFileSync(eventsPath, JSON.stringify(evt) + "\n", "utf-8"); - } catch {} - sessionEvents.push(evt); - // Keep in-memory events bounded - if (sessionEvents.length > 500) { - sessionEvents = sessionEvents.slice(-400); - } - summary.totalEvents++; - markDirty(); - } - - function clearAllData() { - try { - if (fs.existsSync(eventsPath)) fs.unlinkSync(eventsPath); - if (fs.existsSync(summaryPath)) fs.unlinkSync(summaryPath); - } catch {} - summary = emptySummary(); - sessionEvents = []; - toolTimers.clear(); - agentTurnCount = 0; - currentSession = emptySessionSummary(sessionId, widgetCtx?.model?.id); - summary.sessions.push(currentSession); - summary.totalSessions = summary.sessions.length; - markDirty(); - } - - // ── Context snapshot ─────────────────────────────────────────────── - - function getContextPercent(): number { - try { - const usage = widgetCtx?.getContextUsage(); - return usage ? Math.round(usage.percent ?? 0) : 0; - } catch { - return 0; - } - } - - // ── Widget rendering ─────────────────────────────────────────────── - - function updateWidget() { - if (!widgetCtx) return; - - try { - widgetCtx.ui.setWidget("observatory", (_tui, theme) => { - const container = new Container(); - const borderFn = (s: string) => theme.fg("accent", s); - - container.addChild(new DynamicBorder(borderFn)); - - const headerText = new Text("", 1, 0); - container.addChild(headerText); - - const liveText = new Text("", 1, 0); - container.addChild(liveText); - - const hintText = new Text("", 1, 0); - container.addChild(hintText); - - container.addChild(new DynamicBorder(borderFn)); - - return { - render(width: number): string[] { - const elapsed = fmtDuration(Date.now() - sessionStartTs); - const toolCount = currentSession?.toolCalls || 0; - const turns = currentSession?.agentTurns || 0; - const ctxPct = getContextPercent(); - - // Update peak context - if (currentSession && ctxPct > currentSession.peakContextPercent) { - currentSession.peakContextPercent = ctxPct; - } - - // Line 1: session info - const sid = theme.fg("accent", `#${sessionId}`); - const line1 = - theme.fg("dim", " SESSION ") + sid + - theme.fg("dim", " │ ⏱ ") + theme.fg("success", elapsed) + - theme.fg("dim", " │ 🔧 ") + theme.fg("accent", `${toolCount}`) + theme.fg("dim", " tools") + - theme.fg("dim", " │ 🔄 ") + theme.fg("accent", `${turns}`) + theme.fg("dim", " turns") + - theme.fg("dim", " │ 📊 ") + theme.fg(ctxPct > 80 ? "error" : ctxPct > 60 ? "warning" : "success", `${ctxPct}%`) + theme.fg("dim", " ctx"); - headerText.setText(truncateToWidth(line1, width - 4)); - - // Line 2: tool frequency bar (top 8 tools) - const tc = summary.toolCounts; - const sorted = Object.entries(tc).sort((a, b) => b[1] - a[1]).slice(0, 8); - const parts = sorted.map( - ([name, count]) => - theme.fg("accent", name) + theme.fg("dim", "(") + theme.fg("success", `${count}`) + theme.fg("dim", ")") - ); - const liveLine = parts.length > 0 - ? theme.fg("dim", " LIVE ") + theme.fg("success", "● ") + parts.join(theme.fg("dim", " ")) - : theme.fg("dim", " LIVE ● waiting for tools…"); - liveText.setText(truncateToWidth(liveLine, width - 4)); - - // Line 3: hint + tokens + cost - const { tokensIn, tokensOut, cost } = computeTokensAndCost(widgetCtx!); - const hintLine = - theme.fg("dim", " /obs") + theme.fg("muted", " — dashboard") + - theme.fg("dim", " │ tokens: ") + - theme.fg("success", fmtTokens(tokensIn)) + theme.fg("dim", " in · ") + - theme.fg("accent", fmtTokens(tokensOut)) + theme.fg("dim", " out") + - theme.fg("dim", " │ ") + theme.fg("warning", fmtCost(cost)); - hintText.setText(truncateToWidth(hintLine, width - 4)); - - return container.render(width); - }, - invalidate() { - container.invalidate(); - }, - }; - }); - } catch {} - } - - // ── Footer rendering ─────────────────────────────────────────────── - - function updateFooter(ctx: ExtensionContext) { - try { - ctx.ui.setFooter((_tui, theme) => ({ - dispose: () => {}, - invalidate() {}, - render(width: number): string[] { - const model = ctx.model?.id || "no-model"; - const ctxPct = getContextPercent(); - const evtCount = summary.totalEvents; - - const line = - theme.fg("accent", " 🔭 Observatory") + - theme.fg("dim", " │ ") + theme.fg("muted", model) + - theme.fg("dim", " │ ctx: ") + - theme.fg(ctxPct > 80 ? "error" : ctxPct > 60 ? "warning" : "success", `${ctxPct}%`) + - theme.fg("dim", " │ ") + theme.fg("success", `${evtCount}`) + theme.fg("dim", " events") + - theme.fg("dim", " │ /obs for dashboard "); - - return [truncateToWidth(line, width)]; - }, - })); - } catch {} - } - - // ── Dashboard Overlay ────────────────────────────────────────────── - - async function openOverlay(ctx: ExtensionContext, initialView: number = 0) { - if (!ctx.hasUI) return; - - let currentView = initialView; // 0=Feed, 1=Agents, 2=Tools, 3=Timeline - let scrollOffset = 0; - - const viewNames = ["1:Feed", "2:Agents", "3:Tools", "4:Timeline"]; - - await ctx.ui.custom((_tui, theme, _kb, done) => { - return { - render(width: number): string[] { - const lines: string[] = []; - const innerW = width - 2; - - // ── Header ── - lines.push(""); - const tabs = viewNames.map((name, i) => - i === currentView - ? theme.fg("accent", theme.bold(`[${name}]`)) - : theme.fg("dim", `[${name}]`) - ).join(" "); - lines.push(truncateToWidth( - " " + theme.fg("accent", theme.bold("🔭 Observatory Dashboard")) + - " ".repeat(Math.max(1, innerW - 24 - viewNames.join(" ").length - 2)) + - tabs, - width, - )); - lines.push(theme.fg("dim", "─".repeat(width))); - - // ── View content ── - const contentLines = renderView(currentView, width, theme, scrollOffset); - lines.push(...contentLines); - - // ── Footer controls ── - lines.push(""); - lines.push(theme.fg("dim", "─".repeat(width))); - lines.push(truncateToWidth( - " " + theme.fg("dim", "1-4/Tab: views │ ↑↓/j/k: scroll │ PgUp/PgDn: page │ c: clear │ q/Esc: close"), - width, - )); - lines.push(""); - - return lines; - }, - handleInput(data: string) { - if (matchesKey(data, "escape") || data === "q") { - done(undefined); - return; - } - if (data === "1") { currentView = 0; scrollOffset = 0; } - else if (data === "2") { currentView = 1; scrollOffset = 0; } - else if (data === "3") { currentView = 2; scrollOffset = 0; } - else if (data === "4") { currentView = 3; scrollOffset = 0; } - else if (data === "\t") { currentView = (currentView + 1) % 4; scrollOffset = 0; } - else if (matchesKey(data, "up") || data === "k") { scrollOffset = Math.max(0, scrollOffset - 1); } - else if (matchesKey(data, "down") || data === "j") { scrollOffset++; } - else if (matchesKey(data, "pageUp")) { scrollOffset = Math.max(0, scrollOffset - 20); } - else if (matchesKey(data, "pageDown")) { scrollOffset += 20; } - else if (data === "c") { - clearAllData(); - scrollOffset = 0; - } - _tui.requestRender(); - }, - invalidate() {}, - }; - }, { - overlay: true, - overlayOptions: { width: "90%", anchor: "center" }, - }); - } - - // ── View Renderers ───────────────────────────────────────────────── - - function renderView(view: number, width: number, theme: any, offset: number): string[] { - switch (view) { - case 0: return renderFeedView(width, theme, offset); - case 1: return renderAgentsView(width, theme, offset); - case 2: return renderToolsView(width, theme, offset); - case 3: return renderTimelineView(width, theme, offset); - default: return []; - } - } - - // ── View 1: Live Feed ────────────────────────────────────────────── - - function renderFeedView(width: number, theme: any, offset: number): string[] { - const lines: string[] = []; - const events = sessionEvents.slice(-100); - - if (events.length === 0) { - lines.push(""); - lines.push(truncateToWidth(" " + theme.fg("dim", "No events yet. Start working and events will appear here."), width)); - return lines; - } - - lines.push(truncateToWidth( - " " + theme.fg("muted", `Showing last ${events.length} events from session #${sessionId}`), - width, - )); - lines.push(""); - - const iconMap: Record = { - session_start: "🚀", session_switch: "🔀", session_fork: "🔱", - tool_call: "🔧", tool_end: "✅", agent_start: "🤖", agent_end: "🏁", - dispatch: "📤", error: "❌", blocked: "🚫", - }; - const colorMap: Record = { - session_start: "success", session_switch: "accent", session_fork: "accent", - tool_call: "accent", tool_end: "success", agent_start: "warning", agent_end: "success", - dispatch: "accent", error: "error", blocked: "error", - }; - - const visible = events.slice(offset); - for (const evt of visible) { - const time = theme.fg("dim", `[${fmtTime(evt.ts)}]`); - const icon = iconMap[evt.type] || "●"; - const color = colorMap[evt.type] || "muted"; - let detail = ""; - - if (evt.type === "tool_call" && evt.tool) { - detail = theme.fg(color, `${evt.tool}`) + - (evt.contextPercent !== undefined ? theme.fg("dim", ` ctx:${evt.contextPercent}%`) : ""); - } else if (evt.type === "tool_end" && evt.tool) { - detail = theme.fg(color, `${evt.tool}`) + - (evt.durationMs !== undefined ? theme.fg("dim", ` ${fmtDuration(evt.durationMs)}`) : ""); - } else if (evt.type === "agent_start") { - detail = theme.fg(color, `turn #${evt.agentTurn || "?"}`) + - (evt.contextPercent !== undefined ? theme.fg("dim", ` ctx:${evt.contextPercent}%`) : ""); - } else if (evt.type === "agent_end") { - detail = theme.fg(color, `turn #${evt.agentTurn || "?"}`) + - (evt.durationMs !== undefined ? theme.fg("dim", ` ${fmtDuration(evt.durationMs)}`) : "") + - (evt.tokensIn !== undefined ? theme.fg("dim", ` tok:${fmtTokens(evt.tokensIn)}in`) : "") + - (evt.cost !== undefined ? theme.fg("dim", ` ${fmtCost(evt.cost)}`) : ""); - } else if (evt.type === "error") { - detail = theme.fg(color, evt.error || "unknown error"); - } else if (evt.type === "blocked") { - detail = theme.fg(color, `${evt.tool || "?"}: ${evt.blockReason || "blocked"}`); - } else { - detail = theme.fg(color, evt.type); - } - - lines.push(truncateToWidth(` ${time} ${icon} ${detail}`, width)); - } - - return lines; - } - - // ── View 2: Agent Performance ────────────────────────────────────── - - function renderAgentsView(width: number, theme: any, offset: number): string[] { - const lines: string[] = []; - - // Cross-session summary - lines.push(truncateToWidth( - " " + theme.fg("accent", theme.bold("Cross-Session Summary")), - width, - )); - lines.push(truncateToWidth( - " " + - theme.fg("muted", "Total turns: ") + theme.fg("success", `${summary.totalAgentTurns}`) + - theme.fg("dim", " │ ") + - theme.fg("muted", "Total sessions: ") + theme.fg("success", `${summary.totalSessions}`) + - theme.fg("dim", " │ ") + - theme.fg("muted", "Total cost: ") + theme.fg("warning", fmtCost(summary.totalCost)), - width, - )); - lines.push(""); - - // Current session agent turns - lines.push(truncateToWidth( - " " + theme.fg("accent", theme.bold("Current Session Turns")) + - theme.fg("dim", ` (#${sessionId})`), - width, - )); - lines.push(""); - - // Collect agent turn events - const turnStarts: ObsEvent[] = sessionEvents.filter(e => e.type === "agent_start"); - const turnEnds: ObsEvent[] = sessionEvents.filter(e => e.type === "agent_end"); - - if (turnStarts.length === 0) { - lines.push(truncateToWidth(" " + theme.fg("dim", "No agent turns recorded yet."), width)); - return lines; - } - - // Table header - const hdr = - theme.fg("accent", " Turn") + - theme.fg("accent", " │ Duration ") + - theme.fg("accent", " │ Tools ") + - theme.fg("accent", " │ Context ") + - theme.fg("accent", " │ Tokens In ") + - theme.fg("accent", " │ Cost "); - lines.push(truncateToWidth(hdr, width)); - lines.push(truncateToWidth(" " + theme.fg("dim", "─".repeat(Math.min(70, width - 4))), width)); - - // Build turn rows — pair starts with ends - const turnRows: string[] = []; - let prevCtx = 0; - for (let i = 0; i < turnStarts.length; i++) { - const start = turnStarts[i]; - const end = turnEnds[i]; // May be undefined if still running - const turn = start.agentTurn || (i + 1); - const dur = end?.durationMs !== undefined ? fmtDuration(end.durationMs) : "running…"; - - // Count tools between this turn start and end (or now) - const startTs = start.ts; - const endTs = end?.ts || Date.now(); - const toolsInTurn = sessionEvents.filter( - e => e.type === "tool_call" && e.ts >= startTs && e.ts <= endTs - ).length; - - const ctxNow = end?.contextPercent ?? start.contextPercent ?? 0; - const ctxDelta = ctxNow - prevCtx; - prevCtx = ctxNow; - - const tokIn = end?.tokensIn ?? 0; - const cost = end?.cost ?? 0; - - const row = - theme.fg("success", ` #${String(turn).padStart(2)}`) + - theme.fg("dim", " │ ") + theme.fg("muted", dur.padEnd(9)) + - theme.fg("dim", " │ ") + theme.fg("accent", String(toolsInTurn).padStart(5)) + " " + - theme.fg("dim", " │ ") + theme.fg(ctxDelta > 15 ? "warning" : "muted", `${ctxDelta >= 0 ? "+" : ""}${ctxDelta}%`.padStart(7)) + " " + - theme.fg("dim", " │ ") + theme.fg("muted", fmtTokens(tokIn).padStart(9)) + " " + - theme.fg("dim", " │ ") + theme.fg("warning", fmtCost(cost).padStart(8)); - turnRows.push(row); - } - - const visible = turnRows.slice(offset); - for (const row of visible) { - lines.push(truncateToWidth(row, width)); - } - - return lines; - } - - // ── View 3: Tool Analytics ───────────────────────────────────────── - - function renderToolsView(width: number, theme: any, offset: number): string[] { - const lines: string[] = []; - - lines.push(truncateToWidth( - " " + theme.fg("accent", theme.bold("Tool Analytics")) + - theme.fg("dim", " (all sessions)"), - width, - )); - lines.push(""); - - const tc = summary.toolCounts; - const entries = Object.entries(tc).sort((a, b) => b[1] - a[1]); - - if (entries.length === 0) { - lines.push(truncateToWidth(" " + theme.fg("dim", "No tool usage recorded yet."), width)); - return lines; - } - - const maxCount = entries[0][1]; - const barWidth = Math.min(25, Math.floor(width * 0.3)); - const blocks = "▏▎▍▌▋▊▉█"; - - const visible = entries.slice(offset); - for (const [toolName, count] of visible) { - // Bar - const ratio = maxCount > 0 ? count / maxCount : 0; - const fullBlocks = Math.floor(ratio * barWidth); - const remainder = (ratio * barWidth) - fullBlocks; - const partialIdx = Math.floor(remainder * 8); - let bar = "█".repeat(fullBlocks); - if (fullBlocks < barWidth && partialIdx > 0) { - bar += blocks[partialIdx - 1]; - } - - // Duration stats - const durations = summary.toolDurations[toolName] || []; - let avgDur = "—"; - let minDur = "—"; - let maxDur = "—"; - if (durations.length > 0) { - const avg = durations.reduce((a, b) => a + b, 0) / durations.length; - const min = Math.min(...durations); - const max = Math.max(...durations); - avgDur = fmtDuration(Math.round(avg)); - minDur = fmtDuration(min); - maxDur = fmtDuration(max); - } - - const blocked = summary.toolBlocked[toolName] || 0; - - const namePad = toolName.padEnd(12); - const line = - " " + - theme.fg("accent", namePad) + " " + - theme.fg("success", bar.padEnd(barWidth)) + " " + - theme.fg("muted", String(count).padStart(5)) + - theme.fg("dim", " avg: ") + theme.fg("muted", avgDur.padEnd(6)) + - theme.fg("dim", " min: ") + theme.fg("muted", minDur.padEnd(6)) + - theme.fg("dim", " max: ") + theme.fg("muted", maxDur.padEnd(6)) + - (blocked > 0 - ? theme.fg("dim", " blocked: ") + theme.fg("error", `${blocked}`) - : theme.fg("dim", " blocked: ") + theme.fg("muted", "0")); - - lines.push(truncateToWidth(line, width)); - } - - return lines; - } - - // ── View 4: Timeline ─────────────────────────────────────────────── - - function renderTimelineView(width: number, theme: any, offset: number): string[] { - const lines: string[] = []; - - lines.push(truncateToWidth( - " " + theme.fg("accent", theme.bold("Session Timeline")) + - theme.fg("dim", ` (${summary.sessions.length} sessions)`), - width, - )); - lines.push(""); - - if (summary.sessions.length === 0) { - lines.push(truncateToWidth(" " + theme.fg("dim", "No sessions recorded yet."), width)); - return lines; - } - - // Show sessions newest first - const sessionsDesc = [...summary.sessions].reverse(); - const cardLines: string[] = []; - - for (const sess of sessionsDesc) { - const dur = sess.endedAt - ? fmtDuration(sess.endedAt - sess.startedAt) - : fmtDuration(Date.now() - sess.startedAt); - const date = fmtDate(sess.startedAt); - const time = fmtTime(sess.startedAt); - const isCurrent = sess.sessionId === sessionId; - - const topBorder = theme.fg("dim", " ┌─ ") + - theme.fg("accent", `Session ${sess.sessionId}`) + - (isCurrent ? theme.fg("success", " (current)") : "") + - theme.fg("dim", ` ─ ${date} ${time} `) + - theme.fg("dim", "─".repeat(Math.max(0, width - 50))); - cardLines.push(truncateToWidth(topBorder, width)); - - const line1 = - theme.fg("dim", " │ ") + - theme.fg("muted", "Duration: ") + theme.fg("success", dur) + - theme.fg("dim", " │ ") + - theme.fg("muted", "Model: ") + theme.fg("accent", sess.model || "?") + - theme.fg("dim", " │ ") + - theme.fg("muted", "Tools: ") + theme.fg("success", `${sess.toolCalls}`) + - theme.fg("dim", " │ ") + - theme.fg("muted", "Turns: ") + theme.fg("success", `${sess.agentTurns}`); - cardLines.push(truncateToWidth(line1, width)); - - const line2 = - theme.fg("dim", " │ ") + - theme.fg("muted", "Cost: ") + theme.fg("warning", fmtCost(sess.cost)) + - theme.fg("dim", " │ ") + - theme.fg("muted", "Peak Context: ") + theme.fg( - sess.peakContextPercent > 80 ? "error" : sess.peakContextPercent > 60 ? "warning" : "success", - `${sess.peakContextPercent}%`, - ) + - theme.fg("dim", " │ ") + - theme.fg("muted", "Errors: ") + theme.fg(sess.errors > 0 ? "error" : "muted", `${sess.errors}`) + - theme.fg("dim", " │ ") + - theme.fg("muted", "Tokens: ") + - theme.fg("success", fmtTokens(sess.tokensIn)) + theme.fg("dim", " in / ") + - theme.fg("accent", fmtTokens(sess.tokensOut)) + theme.fg("dim", " out"); - cardLines.push(truncateToWidth(line2, width)); - - const botBorder = theme.fg("dim", " └" + "─".repeat(Math.max(0, width - 5)) + "┘"); - cardLines.push(truncateToWidth(botBorder, width)); - cardLines.push(""); - } - - const visible = cardLines.slice(offset); - lines.push(...visible); - - return lines; - } - - // ── Export Report ────────────────────────────────────────────────── - - function exportReport(ctx: ExtensionContext) { - try { - ensureDir(); - const { tokensIn, tokensOut, cost } = computeTokensAndCost(ctx); - const elapsed = fmtDuration(Date.now() - sessionStartTs); - const model = ctx.model?.id || "unknown"; - - const toolRows = Object.entries(summary.toolCounts) - .sort((a, b) => b[1] - a[1]) - .map(([name, count]) => { - const durations = summary.toolDurations[name] || []; - const avg = durations.length > 0 - ? fmtDuration(Math.round(durations.reduce((a, b) => a + b, 0) / durations.length)) - : "—"; - const blocked = summary.toolBlocked[name] || 0; - return `| ${name} | ${count} | ${avg} | ${blocked} |`; - }) - .join("\n"); - - const sessionRows = summary.sessions.map(sess => { - const dur = sess.endedAt - ? fmtDuration(sess.endedAt - sess.startedAt) - : fmtDuration(Date.now() - sess.startedAt); - return `| ${sess.sessionId} | ${fmtDate(sess.startedAt)} | ${dur} | ${sess.model || "?"} | ${sess.toolCalls} | ${sess.agentTurns} | ${fmtCost(sess.cost)} | ${sess.peakContextPercent}% |`; - }).join("\n"); - - const report = `# 🔭 Observatory Report -Generated: ${new Date().toISOString()} - -## Current Session -- **ID:** ${sessionId} -- **Duration:** ${elapsed} -- **Model:** ${model} -- **Tool Calls:** ${currentSession?.toolCalls || 0} -- **Agent Turns:** ${currentSession?.agentTurns || 0} -- **Tokens:** ${fmtTokens(tokensIn)} in / ${fmtTokens(tokensOut)} out -- **Cost:** ${fmtCost(cost)} -- **Peak Context:** ${currentSession?.peakContextPercent || 0}% - -## Tool Usage -| Tool | Count | Avg Duration | Blocked | -|------|-------|-------------|---------| -${toolRows || "| (none) | — | — | — |"} - -## Cross-Session Summary -- **Total Sessions:** ${summary.totalSessions} -- **Total Events:** ${summary.totalEvents} -- **Total Tool Calls:** ${summary.totalToolCalls} -- **Total Agent Turns:** ${summary.totalAgentTurns} -- **Total Errors:** ${summary.totalErrors} -- **Total Blocked:** ${summary.totalBlocked} -- **Total Tokens In:** ${fmtTokens(summary.totalTokensIn)} -- **Total Tokens Out:** ${fmtTokens(summary.totalTokensOut)} -- **Total Cost:** ${fmtCost(summary.totalCost)} - -## Session History -| Session | Date | Duration | Model | Tools | Turns | Cost | Peak Ctx | -|---------|------|----------|-------|-------|-------|------|----------| -${sessionRows || "| (none) | — | — | — | — | — | — | — |"} -`; - - const reportPath = path.join(obsDir, "report.md"); - fs.writeFileSync(reportPath, report, "utf-8"); - ctx.ui.notify(`📄 Report exported to .pi/observatory/report.md`, "success"); - } catch (err) { - ctx.ui.notify(`Export failed: ${err instanceof Error ? err.message : String(err)}`, "error"); - } - } - - // ── Commands ─────────────────────────────────────────────────────── - - pi.registerCommand("obs", { - description: "Open Observatory dashboard. Args: clear | export | agents | tools | timeline", - handler: async (args, ctx) => { - widgetCtx = ctx; - const arg = (args || "").trim().toLowerCase(); - - if (arg === "clear") { - clearAllData(); - ctx.ui.notify("🔭 Observatory: All data cleared.", "info"); - updateWidget(); - return; - } - if (arg === "export") { - exportReport(ctx); - return; - } - - let view = 0; - if (arg === "agents") view = 1; - else if (arg === "tools") view = 2; - else if (arg === "timeline") view = 3; - - await openOverlay(ctx, view); - }, - }); - - pi.registerCommand("observatory", { - description: "Open Observatory dashboard (alias for /obs)", - handler: async (_args, ctx) => { - widgetCtx = ctx; - await openOverlay(ctx, 0); - }, - }); - - // ── Event Handlers ───────────────────────────────────────────────── - - pi.on("session_start", async (_event, ctx) => { - try { - applyExtensionDefaults(import.meta.url, ctx); - - widgetCtx = ctx; - sessionId = shortId(); - sessionStartTs = Date.now(); - agentTurnCount = 0; - sessionEvents = []; - toolTimers.clear(); - prevTokensIn = 0; - prevTokensOut = 0; - prevCost = 0; - - // Initialize storage - obsDir = path.join(ctx.cwd, ".pi", "observatory"); - eventsPath = path.join(obsDir, "events.jsonl"); - summaryPath = path.join(obsDir, "summary.json"); - ensureDir(); - loadSummary(); - - // Create session summary - currentSession = emptySessionSummary(sessionId, ctx.model?.id); - summary.sessions.push(currentSession); - summary.totalSessions = summary.sessions.length; - - // Log event - appendEvent({ - ts: Date.now(), - type: "session_start", - sessionId, - contextPercent: getContextPercent(), - meta: { model: ctx.model?.id }, - }); - - // Start flush timer - startFlushTimer(); - - // Set up UI - updateWidget(); - updateFooter(ctx); - } catch {} - }); - - pi.on("tool_call", async (event, _ctx) => { - try { - const toolName = event.toolName; - - // Record start time (FIFO queue per tool name) - if (!toolTimers.has(toolName)) { - toolTimers.set(toolName, []); - } - toolTimers.get(toolName)!.push(Date.now()); - - // Update counts - summary.toolCounts[toolName] = (summary.toolCounts[toolName] || 0) + 1; - summary.totalToolCalls++; - if (currentSession) currentSession.toolCalls++; - - // Log event - appendEvent({ - ts: Date.now(), - type: "tool_call", - sessionId, - tool: toolName, - contextPercent: getContextPercent(), - }); - - updateWidget(); - } catch {} - - // IMPORTANT: Never block — completely passive - return undefined; - }); - - pi.on("tool_execution_end", async (event) => { - try { - const toolName = event.toolName; - - // Calculate duration (pop earliest timer for this tool) - let durationMs: number | undefined; - const timers = toolTimers.get(toolName); - if (timers && timers.length > 0) { - const startTs = timers.shift()!; - durationMs = Date.now() - startTs; - - // Store duration (cap at 200 per tool) - if (!summary.toolDurations[toolName]) { - summary.toolDurations[toolName] = []; - } - summary.toolDurations[toolName].push(durationMs); - if (summary.toolDurations[toolName].length > 200) { - summary.toolDurations[toolName] = summary.toolDurations[toolName].slice(-200); - } - } - - // Log event - appendEvent({ - ts: Date.now(), - type: "tool_end", - sessionId, - tool: toolName, - durationMs, - }); - - updateWidget(); - } catch {} - }); - - pi.on("before_agent_start", async (_event, _ctx) => { - try { - agentTurnCount++; - agentTurnStartTs = Date.now(); - - summary.totalAgentTurns++; - if (currentSession) currentSession.agentTurns++; - - const ctxPct = getContextPercent(); - - appendEvent({ - ts: Date.now(), - type: "agent_start", - sessionId, - agentTurn: agentTurnCount, - contextPercent: ctxPct, - }); - - updateWidget(); - } catch {} - - // Don't modify the system prompt — return undefined - return undefined; - }); - - pi.on("agent_end", async (_event, ctx) => { - try { - widgetCtx = ctx; - const turnDuration = Date.now() - agentTurnStartTs; - const { tokensIn, tokensOut, cost } = computeTokensAndCost(ctx); - const ctxPct = getContextPercent(); - - // Update session summary - if (currentSession) { - currentSession.tokensIn = tokensIn; - currentSession.tokensOut = tokensOut; - currentSession.cost = cost; - if (ctxPct > currentSession.peakContextPercent) { - currentSession.peakContextPercent = ctxPct; - } - } - - // Update global summary totals using deltas (so cross-session values accumulate) - const deltaIn = tokensIn - prevTokensIn; - const deltaOut = tokensOut - prevTokensOut; - const deltaCost = cost - prevCost; - summary.totalTokensIn += deltaIn; - summary.totalTokensOut += deltaOut; - summary.totalCost += deltaCost; - prevTokensIn = tokensIn; - prevTokensOut = tokensOut; - prevCost = cost; - - appendEvent({ - ts: Date.now(), - type: "agent_end", - sessionId, - agentTurn: agentTurnCount, - durationMs: turnDuration, - contextPercent: ctxPct, - tokensIn, - tokensOut, - cost, - }); - - // Flush summary on every agent end - markDirty(); - saveSummary(); - - updateWidget(); - updateFooter(ctx); - } catch {} - }); - -} diff --git a/extensions/pi-pi.ts b/extensions/pi-pi.ts deleted file mode 100644 index 97c46d2..0000000 --- a/extensions/pi-pi.ts +++ /dev/null @@ -1,633 +0,0 @@ -/** - * Pi Pi — Meta-agent that builds Pi agents - * - * A team of domain-specific research experts (extensions, themes, skills, - * settings, TUI) operate in PARALLEL to gather documentation and patterns. - * The primary agent synthesizes their findings and WRITES the actual files. - * - * Each expert fetches fresh Pi documentation via firecrawl on first query. - * Experts are read-only researchers. The primary agent is the only writer. - * - * Commands: - * /experts — list available experts and their status - * /experts-grid N — set dashboard column count (default 3) - * - * Usage: pi -e extensions/pi-pi.ts - */ - -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; -import { Type } from "@sinclair/typebox"; -import { Text, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui"; -import { spawn } from "child_process"; -import { readdirSync, readFileSync, existsSync, mkdirSync } from "fs"; -import { join, resolve } from "path"; -import { applyExtensionDefaults } from "./themeMap.ts"; - -// ── Types ──────────────────────────────────────── - -interface ExpertDef { - name: string; - description: string; - tools: string; - systemPrompt: string; - file: string; -} - -interface ExpertState { - def: ExpertDef; - status: "idle" | "researching" | "done" | "error"; - question: string; - elapsed: number; - lastLine: string; - queryCount: number; - timer?: ReturnType; -} - -// ── Helpers ────────────────────────────────────── - -function displayName(name: string): string { - return name.split("-").map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(" "); -} - -function parseAgentFile(filePath: string): ExpertDef | null { - try { - const raw = readFileSync(filePath, "utf-8"); - const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/); - if (!match) return null; - - const frontmatter: Record = {}; - for (const line of match[1].split("\n")) { - const idx = line.indexOf(":"); - if (idx > 0) { - frontmatter[line.slice(0, idx).trim()] = line.slice(idx + 1).trim(); - } - } - - if (!frontmatter.name) return null; - - return { - name: frontmatter.name, - description: frontmatter.description || "", - tools: frontmatter.tools || "read,grep,find,ls", - systemPrompt: match[2].trim(), - file: filePath, - }; - } catch { - return null; - } -} - -// ── Expert card colors ──────────────────────────── -// Each expert gets a unique hue: bg fills the card interior, -// br is the matching border foreground (brighter shade of same hue). -const EXPERT_COLORS: Record = { - "agent-expert": { bg: "\x1b[48;2;20;30;75m", br: "\x1b[38;2;70;110;210m" }, // navy - "config-expert": { bg: "\x1b[48;2;18;65;30m", br: "\x1b[38;2;55;175;90m" }, // forest - "ext-expert": { bg: "\x1b[48;2;80;18;28m", br: "\x1b[38;2;210;65;85m" }, // crimson - "keybinding-expert": { bg: "\x1b[48;2;50;22;85m", br: "\x1b[38;2;145;80;220m" }, // violet - "prompt-expert": { bg: "\x1b[48;2;80;55;12m", br: "\x1b[38;2;215;150;40m" }, // amber - "skill-expert": { bg: "\x1b[48;2;12;65;75m", br: "\x1b[38;2;40;175;195m" }, // teal - "theme-expert": { bg: "\x1b[48;2;80;18;62m", br: "\x1b[38;2;210;55;160m" }, // rose - "tui-expert": { bg: "\x1b[48;2;28;42;80m", br: "\x1b[38;2;85;120;210m" }, // slate - "cli-expert": { bg: "\x1b[48;2;60;80;20m", br: "\x1b[38;2;160;210;55m" }, // olive/lime -}; -const FG_RESET = "\x1b[39m"; -const BG_RESET = "\x1b[49m"; - -// ── Extension ──────────────────────────────────── - -export default function (pi: ExtensionAPI) { - const experts: Map = new Map(); - let gridCols = 3; - let widgetCtx: any; - - function loadExperts(cwd: string) { - // Pi Pi experts live in their own dedicated directory - const piPiDir = join(cwd, ".pi", "agents", "pi-pi"); - - experts.clear(); - - if (!existsSync(piPiDir)) return; - try { - for (const file of readdirSync(piPiDir)) { - if (!file.endsWith(".md")) continue; - if (file === "pi-orchestrator.md") continue; - const fullPath = resolve(piPiDir, file); - const def = parseAgentFile(fullPath); - if (def) { - const key = def.name.toLowerCase(); - if (!experts.has(key)) { - experts.set(key, { - def, - status: "idle", - question: "", - elapsed: 0, - lastLine: "", - queryCount: 0, - }); - } - } - } - } catch {} - } - - // ── Grid Rendering ─────────────────────────── - - function renderCard(state: ExpertState, colWidth: number, theme: any): string[] { - const w = colWidth - 2; - const truncate = (s: string, max: number) => s.length > max ? s.slice(0, max - 3) + "..." : s; - - const statusColor = state.status === "idle" ? "dim" - : state.status === "researching" ? "accent" - : state.status === "done" ? "success" : "error"; - const statusIcon = state.status === "idle" ? "○" - : state.status === "researching" ? "◉" - : state.status === "done" ? "✓" : "✗"; - - const name = displayName(state.def.name); - const nameStr = theme.fg("accent", theme.bold(truncate(name, w))); - const nameVisible = Math.min(name.length, w); - - const statusStr = `${statusIcon} ${state.status}`; - const timeStr = state.status !== "idle" ? ` ${Math.round(state.elapsed / 1000)}s` : ""; - const queriesStr = state.queryCount > 0 ? ` (${state.queryCount})` : ""; - const statusLine = theme.fg(statusColor, statusStr + timeStr + queriesStr); - const statusVisible = statusStr.length + timeStr.length + queriesStr.length; - - const workRaw = state.question || state.def.description; - const workText = truncate(workRaw, Math.min(50, w - 1)); - const workLine = theme.fg("muted", workText); - const workVisible = workText.length; - - const lastRaw = state.lastLine || ""; - const lastText = truncate(lastRaw, Math.min(50, w - 1)); - const lastLineRendered = lastText ? theme.fg("dim", lastText) : theme.fg("dim", "—"); - const lastVisible = lastText ? lastText.length : 1; - - const colors = EXPERT_COLORS[state.def.name]; - const bg = colors?.bg ?? ""; - const br = colors?.br ?? ""; - const bgr = bg ? BG_RESET : ""; - const fgr = br ? FG_RESET : ""; - - // br colors the box-drawing characters; bg fills behind them so the - // full card — top line, side bars, bottom line — is one solid block. - const bord = (s: string) => bg + br + s + bgr + fgr; - - const top = "┌" + "─".repeat(w) + "┐"; - const bot = "└" + "─".repeat(w) + "┘"; - - // bg fills the inner content area; re-applied before padding to ensure - // the full row is colored even if theme.fg uses a full ANSI reset inside. - const border = (content: string, visLen: number) => { - const pad = " ".repeat(Math.max(0, w - visLen)); - return bord("│") + bg + content + bg + pad + bgr + bord("│"); - }; - - return [ - bord(top), - border(" " + nameStr, 1 + nameVisible), - border(" " + statusLine, 1 + statusVisible), - border(" " + workLine, 1 + workVisible), - border(" " + lastLineRendered, 1 + lastVisible), - bord(bot), - ]; - } - - function updateWidget() { - if (!widgetCtx) return; - - widgetCtx.ui.setWidget("pi-pi-grid", (_tui: any, theme: any) => { - - return { - render(width: number): string[] { - if (experts.size === 0) { - return ["", theme.fg("dim", " No experts found. Add agent .md files to .pi/agents/pi-pi/")]; - } - - const cols = Math.min(gridCols, experts.size); - const gap = 1; - // avoid Text component's ANSI-width miscounting by returning raw lines - const colWidth = Math.floor((width - gap * (cols - 1)) / cols) - 1; - const allExperts = Array.from(experts.values()); - - const lines: string[] = [""]; // top margin - - for (let i = 0; i < allExperts.length; i += cols) { - const rowExperts = allExperts.slice(i, i + cols); - const cards = rowExperts.map(e => renderCard(e, colWidth, theme)); - - while (cards.length < cols) { - cards.push(Array(6).fill(" ".repeat(colWidth))); - } - - const cardHeight = cards[0].length; - for (let line = 0; line < cardHeight; line++) { - lines.push(cards.map(card => card[line] || "").join(" ".repeat(gap))); - } - } - - return lines; - }, - invalidate() {}, - }; - }); - } - - // ── Query Expert ───────────────────────────── - - function queryExpert( - expertName: string, - question: string, - ctx: any, - ): Promise<{ output: string; exitCode: number; elapsed: number }> { - const key = expertName.toLowerCase(); - const state = experts.get(key); - if (!state) { - return Promise.resolve({ - output: `Expert "${expertName}" not found. Available: ${Array.from(experts.values()).map(s => s.def.name).join(", ")}`, - exitCode: 1, - elapsed: 0, - }); - } - - if (state.status === "researching") { - return Promise.resolve({ - output: `Expert "${displayName(state.def.name)}" is already researching. Wait for it to finish.`, - exitCode: 1, - elapsed: 0, - }); - } - - state.status = "researching"; - state.question = question; - state.elapsed = 0; - state.lastLine = ""; - state.queryCount++; - updateWidget(); - - const startTime = Date.now(); - state.timer = setInterval(() => { - state.elapsed = Date.now() - startTime; - updateWidget(); - }, 1000); - - const model = ctx.model - ? `${ctx.model.provider}/${ctx.model.id}` - : "openrouter/google/gemini-3-flash-preview"; - - const args = [ - "--mode", "json", - "-p", - "--no-session", - "--no-extensions", - "--model", model, - "--tools", state.def.tools, - "--thinking", "off", - "--append-system-prompt", state.def.systemPrompt, - question, - ]; - - const textChunks: string[] = []; - - return new Promise((resolve) => { - const proc = spawn("pi", args, { - stdio: ["ignore", "pipe", "pipe"], - env: { ...process.env }, - }); - - let buffer = ""; - - proc.stdout!.setEncoding("utf-8"); - proc.stdout!.on("data", (chunk: string) => { - buffer += chunk; - const lines = buffer.split("\n"); - buffer = lines.pop() || ""; - for (const line of lines) { - if (!line.trim()) continue; - try { - const event = JSON.parse(line); - if (event.type === "message_update") { - const delta = event.assistantMessageEvent; - if (delta?.type === "text_delta") { - textChunks.push(delta.delta || ""); - const full = textChunks.join(""); - const last = full.split("\n").filter((l: string) => l.trim()).pop() || ""; - state.lastLine = last; - updateWidget(); - } - } - } catch {} - } - }); - - proc.stderr!.setEncoding("utf-8"); - proc.stderr!.on("data", () => {}); - - proc.on("close", (code) => { - if (buffer.trim()) { - try { - const event = JSON.parse(buffer); - if (event.type === "message_update") { - const delta = event.assistantMessageEvent; - if (delta?.type === "text_delta") textChunks.push(delta.delta || ""); - } - } catch {} - } - - clearInterval(state.timer); - state.elapsed = Date.now() - startTime; - state.status = code === 0 ? "done" : "error"; - - const full = textChunks.join(""); - state.lastLine = full.split("\n").filter((l: string) => l.trim()).pop() || ""; - updateWidget(); - - ctx.ui.notify( - `${displayName(state.def.name)} ${state.status} in ${Math.round(state.elapsed / 1000)}s`, - state.status === "done" ? "success" : "error" - ); - - resolve({ - output: full, - exitCode: code ?? 1, - elapsed: state.elapsed, - }); - }); - - proc.on("error", (err) => { - clearInterval(state.timer); - state.status = "error"; - state.lastLine = `Error: ${err.message}`; - updateWidget(); - resolve({ - output: `Error spawning expert: ${err.message}`, - exitCode: 1, - elapsed: Date.now() - startTime, - }); - }); - }); - } - - // ── query_experts Tool (parallel) ─────────── - - pi.registerTool({ - name: "query_experts", - label: "Query Experts", - description: `Query one or more Pi domain experts IN PARALLEL. All experts run simultaneously as concurrent subprocesses. - -Pass an array of queries — each with an expert name and a specific question. All experts start at the same time and their results are returned together. - -Available experts: -- ext-expert: Extensions — tools, events, commands, rendering, state management -- theme-expert: Themes — JSON format, 51 color tokens, vars, color values -- skill-expert: Skills — SKILL.md multi-file packages, scripts, references, frontmatter -- config-expert: Settings — settings.json, providers, models, packages, keybindings -- tui-expert: TUI — components, keyboard input, overlays, widgets, footers, editors -- prompt-expert: Prompt templates — single-file .md commands, arguments ($1, $@) -- agent-expert: Agent definitions — .md personas, tools, teams.yaml, orchestration -- keybinding-expert: Keyboard shortcuts — registerShortcut(), Key IDs, reserved keys, macOS terminal compatibility - -Ask specific questions about what you need to BUILD. Each expert will return documentation excerpts, code patterns, and implementation guidance.`, - - parameters: Type.Object({ - queries: Type.Array( - Type.Object({ - expert: Type.String({ - description: "Expert name: ext-expert, theme-expert, skill-expert, config-expert, tui-expert, prompt-expert, or agent-expert", - }), - question: Type.String({ - description: "Specific question about what you need to build. Include context about the target component.", - }), - }), - { description: "Array of expert queries to run in parallel" }, - ), - }), - - async execute(_toolCallId, params, _signal, onUpdate, ctx) { - const { queries } = params as { queries: { expert: string; question: string }[] }; - - if (!queries || queries.length === 0) { - return { - content: [{ type: "text", text: "No queries provided." }], - details: { results: [], status: "error" }, - }; - } - - const names = queries.map(q => displayName(q.expert)).join(", "); - if (onUpdate) { - onUpdate({ - content: [{ type: "text", text: `Querying ${queries.length} experts in parallel: ${names}` }], - details: { queries, status: "researching", results: [] }, - }); - } - - // Launch ALL experts concurrently — allSettled so one failure - // never discards results from the others - const settled = await Promise.allSettled( - queries.map(async ({ expert, question }) => { - const result = await queryExpert(expert, question, ctx); - const truncated = result.output.length > 12000 - ? result.output.slice(0, 12000) + "\n\n... [truncated — ask follow-up for more]" - : result.output; - const status = result.exitCode === 0 ? "done" : "error"; - return { - expert, - question, - status, - elapsed: result.elapsed, - exitCode: result.exitCode, - output: truncated, - fullOutput: result.output, - }; - }), - ); - - const results = settled.map((s, i) => - s.status === "fulfilled" - ? s.value - : { - expert: queries[i].expert, - question: queries[i].question, - status: "error" as const, - elapsed: 0, - exitCode: 1, - output: `Error: ${(s.reason as any)?.message || s.reason}`, - fullOutput: "", - }, - ); - - // Build combined response - const sections = results.map(r => { - const icon = r.status === "done" ? "✓" : "✗"; - return `## [${icon}] ${displayName(r.expert)} (${Math.round(r.elapsed / 1000)}s)\n\n${r.output}`; - }); - - return { - content: [{ type: "text", text: sections.join("\n\n---\n\n") }], - details: { - results, - status: results.every(r => r.status === "done") ? "done" : "partial", - }, - }; - }, - - renderCall(args, theme) { - const queries = (args as any).queries || []; - const names = queries.map((q: any) => displayName(q.expert || "?")).join(", "); - return new Text( - theme.fg("toolTitle", theme.bold("query_experts ")) + - theme.fg("accent", `${queries.length} parallel`) + - theme.fg("dim", " — ") + - theme.fg("muted", names), - 0, 0, - ); - }, - - renderResult(result, options, theme) { - const details = result.details as any; - if (!details?.results) { - const text = result.content[0]; - return new Text(text?.type === "text" ? text.text : "", 0, 0); - } - - if (options.isPartial || details.status === "researching") { - const count = details.queries?.length || "?"; - return new Text( - theme.fg("accent", `◉ ${count} experts`) + - theme.fg("dim", " researching in parallel..."), - 0, 0, - ); - } - - const lines = (details.results as any[]).map((r: any) => { - const icon = r.status === "done" ? "✓" : "✗"; - const color = r.status === "done" ? "success" : "error"; - const elapsed = typeof r.elapsed === "number" ? Math.round(r.elapsed / 1000) : 0; - return theme.fg(color, `${icon} ${displayName(r.expert)}`) + - theme.fg("dim", ` ${elapsed}s`); - }); - - const header = lines.join(theme.fg("dim", " · ")); - - if (options.expanded && details.results) { - const expanded = (details.results as any[]).map((r: any) => { - const output = r.fullOutput - ? (r.fullOutput.length > 4000 ? r.fullOutput.slice(0, 4000) + "\n... [truncated]" : r.fullOutput) - : r.output || ""; - return theme.fg("accent", `── ${displayName(r.expert)} ──`) + "\n" + theme.fg("muted", output); - }); - return new Text(header + "\n\n" + expanded.join("\n\n"), 0, 0); - } - - return new Text(header, 0, 0); - }, - }); - - // ── Commands ───────────────────────────────── - - pi.registerCommand("experts", { - description: "List available Pi Pi experts and their status", - handler: async (_args, _ctx) => { - widgetCtx = _ctx; - const lines = Array.from(experts.values()) - .map(s => `${displayName(s.def.name)} (${s.status}, queries: ${s.queryCount}): ${s.def.description}`) - .join("\n"); - _ctx.ui.notify(lines || "No experts loaded", "info"); - }, - }); - - pi.registerCommand("experts-grid", { - description: "Set expert grid columns: /experts-grid <1-5>", - handler: async (args, _ctx) => { - widgetCtx = _ctx; - const n = parseInt(args?.trim() || "", 10); - if (n >= 1 && n <= 5) { - gridCols = n; - _ctx.ui.notify(`Grid set to ${gridCols} columns`, "info"); - updateWidget(); - } else { - _ctx.ui.notify("Usage: /experts-grid <1-5>", "error"); - } - }, - }); - - // ── System Prompt ──────────────────────────── - - pi.on("before_agent_start", async (_event, _ctx) => { - const expertCatalog = Array.from(experts.values()) - .map(s => `### ${displayName(s.def.name)}\n**Query as:** \`${s.def.name}\`\n${s.def.description}`) - .join("\n\n"); - - const expertNames = Array.from(experts.values()).map(s => displayName(s.def.name)).join(", "); - - const orchestratorPath = join(_ctx.cwd, ".pi", "agents", "pi-pi", "pi-orchestrator.md"); - let systemPrompt = ""; - try { - const raw = readFileSync(orchestratorPath, "utf-8"); - const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/); - const template = match ? match[2].trim() : raw; - - systemPrompt = template - .replace("{{EXPERT_COUNT}}", experts.size.toString()) - .replace("{{EXPERT_NAMES}}", expertNames) - .replace("{{EXPERT_CATALOG}}", expertCatalog); - } catch (err) { - systemPrompt = "Error: Could not load pi-orchestrator.md. Make sure it exists in .pi/agents/pi-pi/."; - } - - return { systemPrompt }; - }); - - // ── Session Start ──────────────────────────── - - pi.on("session_start", async (_event, _ctx) => { - applyExtensionDefaults(import.meta.url, _ctx); - if (widgetCtx) { - widgetCtx.ui.setWidget("pi-pi-grid", undefined); - } - widgetCtx = _ctx; - - loadExperts(_ctx.cwd); - updateWidget(); - - const expertNames = Array.from(experts.values()).map(s => displayName(s.def.name)).join(", "); - _ctx.ui.setStatus("pi-pi", `Pi Pi (${experts.size} experts)`); - _ctx.ui.notify( - `Pi Pi loaded — ${experts.size} experts: ${expertNames}\n\n` + - `/experts List experts and status\n` + - `/experts-grid N Set grid columns (1-5)\n\n` + - `Ask me to build any Pi agent component!`, - "info", - ); - - // Custom footer - _ctx.ui.setFooter((_tui, theme, _footerData) => ({ - dispose: () => {}, - invalidate() {}, - render(width: number): string[] { - const model = _ctx.model?.id || "no-model"; - const usage = _ctx.getContextUsage(); - const pct = usage ? usage.percent : 0; - const filled = Math.round(pct / 10); - const bar = "#".repeat(filled) + "-".repeat(10 - filled); - - const active = Array.from(experts.values()).filter(e => e.status === "researching").length; - const done = Array.from(experts.values()).filter(e => e.status === "done").length; - - const left = theme.fg("dim", ` ${model}`) + - theme.fg("muted", " · ") + - theme.fg("accent", "Pi Pi"); - const mid = active > 0 - ? theme.fg("accent", ` ◉ ${active} researching`) - : done > 0 - ? theme.fg("success", ` ✓ ${done} done`) - : ""; - const right = theme.fg("dim", `[${bar}] ${Math.round(pct)}% `); - const pad = " ".repeat(Math.max(1, width - visibleWidth(left) - visibleWidth(mid) - visibleWidth(right))); - - return [truncateToWidth(left + mid + pad + right, width)]; - }, - })); - }); -} diff --git a/extensions/pure-focus.ts b/extensions/pure-focus.ts deleted file mode 100644 index 8d8dea5..0000000 --- a/extensions/pure-focus.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Pure Focus — Strip all footer and status line UI - * - * Removes the footer bar and status line entirely, leaving only - * the conversation and editor. Pure distraction-free mode. - * - * Usage: pi -e examples/extensions/pure-focus.ts - */ - -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; -import { applyExtensionDefaults } from "./themeMap.ts"; - -export default function (pi: ExtensionAPI) { - pi.on("session_start", async (_event, ctx) => { - applyExtensionDefaults(import.meta.url, ctx); - ctx.ui.setFooter((_tui, _theme, _footerData) => ({ - dispose: () => {}, - invalidate() {}, - render(_width: number): string[] { - return []; - }, - })); - }); -} diff --git a/extensions/purpose-gate.ts b/extensions/purpose-gate.ts deleted file mode 100644 index cb9a4c3..0000000 --- a/extensions/purpose-gate.ts +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Purpose Gate — Forces the engineer to declare intent before working - * - * On session start, immediately asks "What is the purpose of this agent?" - * via a text input dialog. A persistent widget shows the purpose for the - * rest of the session, keeping focus. Blocks all prompts until answered. - * - * Usage: pi -e extensions/purpose-gate.ts - */ - -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; -import { Text, truncateToWidth } from "@mariozechner/pi-tui"; -import { applyExtensionDefaults } from "./themeMap.ts"; - -// synthwave: bgWarm #4a1e6a → rgb(74,30,106) -function bg(s: string): string { - return `\x1b[48;2;74;30;106m${s}\x1b[49m`; -} - -// synthwave: pink #ff7edb -function pink(s: string): string { - return `\x1b[38;2;255;126;219m${s}\x1b[39m`; -} - -// synthwave: cyan #36f9f6 -function cyan(s: string): string { - return `\x1b[38;2;54;249;246m${s}\x1b[39m`; -} - -function bold(s: string): string { - return `\x1b[1m${s}\x1b[22m`; -} - -export default function (pi: ExtensionAPI) { - let purpose: string | undefined; - - async function askForPurpose(ctx: any) { - while (!purpose) { - const answer = await ctx.ui.input( - "What is the purpose of this agent?", - "e.g. Refactor the auth module to use JWT" - ); - - if (answer && answer.trim()) { - purpose = answer.trim(); - } else { - ctx.ui.notify("Purpose is required.", "warning"); - } - } - - ctx.ui.setWidget("purpose", () => { - return { - render(width: number): string[] { - const pad = bg(" ".repeat(width)); - const label = pink(bold(" PURPOSE: ")); - const msg = cyan(bold(purpose!)); - const content = bg(truncateToWidth(label + msg + " ".repeat(width), width, "")); - return [pad, content, pad]; - }, - invalidate() {}, - }; - }); - } - - pi.on("session_start", async (_event, ctx) => { - applyExtensionDefaults(import.meta.url, ctx); - void askForPurpose(ctx); - }); - - pi.on("before_agent_start", async (event) => { - if (!purpose) return; - return { - systemPrompt: event.systemPrompt + `\n\n\nYour singular purpose this session: ${purpose}\nStay focused on this goal. If a request drifts from this purpose, gently remind the user.\n`, - }; - }); - - pi.on("input", async (_event, ctx) => { - if (!purpose) { - ctx.ui.notify("Set a purpose first.", "warning"); - return { action: "handled" as const }; - } - return { action: "continue" as const }; - }); -} diff --git a/extensions/session-replay.ts b/extensions/session-replay.ts deleted file mode 100644 index d26b320..0000000 --- a/extensions/session-replay.ts +++ /dev/null @@ -1,216 +0,0 @@ -import { ExtensionAPI } from "@mariozechner/pi-coding-agent"; -import { applyExtensionDefaults } from "./themeMap.ts"; -import { - Box, Text, Markdown, Container, Spacer, - matchesKey, Key, truncateToWidth, getMarkdownTheme -} from "@mariozechner/pi-tui"; -import { DynamicBorder, getMarkdownTheme as getPiMdTheme } from "@mariozechner/pi-coding-agent"; - -// Minimal shim for timestamp handling if not directly in Message objects -function formatTime(date: Date): string { - return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }); -} - -function getElapsedTime(start: Date, end: Date): string { - const diffMs = end.getTime() - start.getTime(); - const diffSec = Math.floor(diffMs / 1000); - if (diffSec < 60) return `${diffSec}s`; - const diffMin = Math.floor(diffSec / 60); - return `${diffMin}m ${diffSec % 60}s`; -} - -interface HistoryItem { - type: 'user' | 'assistant' | 'tool'; - title: string; - content: string; - timestamp: Date; - elapsed?: string; -} - -class SessionReplayUI { - private selectedIndex = 0; - private expandedIndex: number | null = null; - private scrollOffset = 0; - - constructor( - private items: HistoryItem[], - private onDone: () => void - ) { - // Start selected at the bottom (most recent) - this.selectedIndex = Math.max(0, items.length - 1); - this.ensureVisible(20); // rough height estimate - } - - handleInput(data: string, tui: any): void { - if (matchesKey(data, Key.up)) { - this.selectedIndex = Math.max(0, this.selectedIndex - 1); - } else if (matchesKey(data, Key.down)) { - this.selectedIndex = Math.min(this.items.length - 1, this.selectedIndex + 1); - } else if (matchesKey(data, Key.enter)) { - this.expandedIndex = this.expandedIndex === this.selectedIndex ? null : this.selectedIndex; - } else if (matchesKey(data, Key.escape)) { - this.onDone(); - return; - } - tui.requestRender(); - } - - private ensureVisible(height: number) { - // Simple scroll window logic - const pageSize = Math.floor(height / 3); // Approx items per page - if (this.selectedIndex < this.scrollOffset) { - this.scrollOffset = this.selectedIndex; - } else if (this.selectedIndex >= this.scrollOffset + pageSize) { - this.scrollOffset = this.selectedIndex - pageSize + 1; - } - } - - render(width: number, height: number, theme: any): string[] { - this.ensureVisible(height); - - const container = new Container(); - const mdTheme = getPiMdTheme(); - - // Header - container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s))); - container.addChild(new Text(`${theme.fg("accent", theme.bold(" SESSION REPLAY"))} ${theme.fg("dim", "|")} ${theme.fg("success", this.items.length.toString())} entries`, 1, 0)); - container.addChild(new Spacer(1)); - - // Calculate visible range - const visibleItems = this.items.slice(this.scrollOffset); - - visibleItems.forEach((item, idx) => { - const absoluteIndex = idx + this.scrollOffset; - const isSelected = absoluteIndex === this.selectedIndex; - const isExpanded = absoluteIndex === this.expandedIndex; - - const cardBox = new Box(1, 0, (s) => isSelected ? theme.bg("selectedBg", s) : s); - - // Icon and Title - let icon = "○"; - let color = "dim"; - if (item.type === 'user') { icon = "👤"; color = "success"; } - else if (item.type === 'assistant') { icon = "🤖"; color = "accent"; } - else if (item.type === 'tool') { icon = "🛠️"; color = "warning"; } - - const timeStr = theme.fg("success", `[${formatTime(item.timestamp)}]`); - const elapsedStr = item.elapsed ? theme.fg("dim", ` (+${item.elapsed})`) : ""; - - const titleLine = `${theme.fg(color, icon)} ${theme.bold(item.title)} ${timeStr}${elapsedStr}`; - cardBox.addChild(new Text(titleLine, 0, 0)); - - if (isExpanded) { - cardBox.addChild(new Spacer(1)); - cardBox.addChild(new Markdown(item.content, 2, 0, mdTheme)); - } else { - // Truncated preview - const preview = item.content.replace(/\n/g, ' ').substring(0, width - 10); - cardBox.addChild(new Text(theme.fg("dim", " " + preview + "..."), 0, 0)); - } - - container.addChild(cardBox); - // Don't add too many spacers if we have many items - if (visibleItems.length < 15) container.addChild(new Spacer(1)); - }); - - // Footer - container.addChild(new Spacer(1)); - container.addChild(new Text(theme.fg("dim", " ↑/↓ Navigate • Enter Expand • Esc Close"), 1, 0)); - container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s))); - - return container.render(width); - } -} - -function extractContent(entry: any): string { - const msg = entry.message; - if (!msg) return ""; - const content = msg.content; - if (!content) return ""; - if (typeof content === "string") return content; - if (Array.isArray(content)) { - return content - .map((c: any) => { - if (c.type === "text") return c.text || ""; - if (c.type === "toolCall") return `Tool: ${c.name}(${JSON.stringify(c.arguments).slice(0, 200)})`; - return ""; - }) - .filter(Boolean) - .join("\n"); - } - return JSON.stringify(content).slice(0, 500); -} - -export default function(pi: ExtensionAPI) { - pi.registerCommand("replay", { - description: "Show a scrollable timeline of the current session", - handler: async (args, ctx) => { - const branch = ctx.sessionManager.getBranch(); - const items: HistoryItem[] = []; - - let prevTime: Date | null = null; - - for (const entry of branch) { - if (entry.type !== "message") continue; - const msg = entry.message; - if (!msg) continue; - - const ts = msg.timestamp ? new Date(msg.timestamp) : new Date(); - const elapsed = prevTime ? getElapsedTime(prevTime, ts) : undefined; - prevTime = ts; - - const role = msg.role; - const text = extractContent(entry); - if (!text) continue; - - if (role === "user") { - items.push({ - type: "user", - title: "User Prompt", - content: text, - timestamp: ts, - elapsed, - }); - } else if (role === "assistant") { - items.push({ - type: "assistant", - title: "Assistant", - content: text, - timestamp: ts, - elapsed, - }); - } else if (role === "toolResult") { - const toolName = (msg as any).toolName || "tool"; - items.push({ - type: "tool", - title: `Tool: ${toolName}`, - content: text, - timestamp: ts, - elapsed, - }); - } - } - - if (items.length === 0) { - ctx.ui.notify("No session history found.", "warning"); - return; - } - - await ctx.ui.custom((tui, theme, kb, done) => { - const component = new SessionReplayUI(items, () => done(undefined)); - return { - render: (w) => component.render(w, 30, theme), - handleInput: (data) => component.handleInput(data, tui), - invalidate: () => {}, - }; - }, { - overlay: true, - overlayOptions: { width: "80%", anchor: "center" }, - }); - }, - }); - - pi.on("session_start", async (_event, ctx) => { - applyExtensionDefaults(import.meta.url, ctx); - }); -} diff --git a/extensions/stop.ts b/extensions/stop.ts deleted file mode 100644 index 9afd325..0000000 --- a/extensions/stop.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Stop — Immediately interrupt the active chat session - * - * Registers a /stop slash command that aborts the current agent turn. - * Also supports /stop with a reason message for logging clarity. - * - * Usage: pi -e extensions/stop.ts - * - * Commands: - * /stop — abort the current agent turn immediately - * /stop — abort with a logged reason - */ - -import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent"; -import { applyExtensionDefaults } from "./themeMap.ts"; - -export default function (pi: ExtensionAPI) { - let activeCtx: ExtensionContext | undefined; - - pi.on("session_start", async (_event, ctx) => { - applyExtensionDefaults(import.meta.url, ctx); - activeCtx = ctx; - }); - - pi.on("session_switch", async (_event, ctx) => { - activeCtx = ctx; - }); - - pi.registerCommand("stop", { - description: "Immediately interrupt the active agent turn. Usage: /stop [reason]", - handler: async (args, ctx) => { - activeCtx = ctx; - const reason = (args || "").trim(); - ctx.abort(); - const msg = reason - ? `🛑 Session aborted: ${reason}` - : "🛑 Session aborted."; - ctx.ui.notify(msg, "warning"); - }, - }); -} diff --git a/extensions/subagent-widget.ts b/extensions/subagent-widget.ts deleted file mode 100644 index a31ac6e..0000000 --- a/extensions/subagent-widget.ts +++ /dev/null @@ -1,481 +0,0 @@ -/** - * Subagent Widget — /sub, /subclear, /subrm, /subcont commands with stacking live widgets - * - * Each /sub spawns a background Pi subagent with its own persistent session, - * enabling conversation continuations via /subcont. - * - * Usage: pi -e extensions/subagent-widget.ts - * Then: - * /sub list files and summarize — spawn a new subagent - * /subcont 1 now write tests for it — continue subagent #1's conversation - * /subrm 2 — remove subagent #2 widget - * /subclear — clear all subagent widgets - */ - -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; -import { DynamicBorder } from "@mariozechner/pi-coding-agent"; -import { Container, Text } from "@mariozechner/pi-tui"; -import { Type } from "@sinclair/typebox"; -const { spawn } = require("child_process") as any; -import * as fs from "fs"; -import * as os from "os"; -import * as path from "path"; -import { applyExtensionDefaults } from "./themeMap.ts"; - -interface SubState { - id: number; - status: "running" | "done" | "error"; - task: string; - textChunks: string[]; - toolCount: number; - elapsed: number; - sessionFile: string; // persistent JSONL session path — used by /subcont to resume - turnCount: number; // increments each time /subcont continues this agent - proc?: any; // active ChildProcess ref (for kill on /subrm) -} - -export default function (pi: ExtensionAPI) { - const agents: Map = new Map(); - let nextId = 1; - let widgetCtx: any; - - // ── Session file helpers ────────────────────────────────────────────────── - - function makeSessionFile(id: number): string { - const dir = path.join(os.homedir(), ".pi", "agent", "sessions", "subagents"); - fs.mkdirSync(dir, { recursive: true }); - return path.join(dir, `subagent-${id}-${Date.now()}.jsonl`); - } - - // ── Widget rendering ────────────────────────────────────────────────────── - - function updateWidgets() { - if (!widgetCtx) return; - - for (const [id, state] of Array.from(agents.entries())) { - const key = `sub-${id}`; - widgetCtx.ui.setWidget(key, (_tui: any, theme: any) => { - const container = new Container(); - const borderFn = (s: string) => theme.fg("dim", s); - - container.addChild(new Text("", 0, 0)); // top margin - container.addChild(new DynamicBorder(borderFn)); - const content = new Text("", 1, 0); - container.addChild(content); - container.addChild(new DynamicBorder(borderFn)); - - return { - render(width: number): string[] { - const lines: string[] = []; - const statusColor = state.status === "running" ? "accent" - : state.status === "done" ? "success" : "error"; - const statusIcon = state.status === "running" ? "●" - : state.status === "done" ? "✓" : "✗"; - - const taskPreview = state.task.length > 40 - ? state.task.slice(0, 37) + "..." - : state.task; - - const turnLabel = state.turnCount > 1 - ? theme.fg("dim", ` · Turn ${state.turnCount}`) - : ""; - - lines.push( - theme.fg(statusColor, `${statusIcon} Subagent #${state.id}`) + - turnLabel + - theme.fg("dim", ` ${taskPreview}`) + - theme.fg("dim", ` (${Math.round(state.elapsed / 1000)}s)`) + - theme.fg("dim", ` | Tools: ${state.toolCount}`) - ); - - const fullText = state.textChunks.join(""); - const lastLine = fullText.split("\n").filter((l: string) => l.trim()).pop() || ""; - if (lastLine) { - const trimmed = lastLine.length > width - 10 - ? lastLine.slice(0, width - 13) + "..." - : lastLine; - lines.push(theme.fg("muted", ` ${trimmed}`)); - } - - content.setText(lines.join("\n")); - return container.render(width); - }, - invalidate() { - container.invalidate(); - }, - }; - }); - } - } - - // ── Streaming helpers ───────────────────────────────────────────────────── - - function processLine(state: SubState, line: string) { - if (!line.trim()) return; - try { - const event = JSON.parse(line); - const type = event.type; - - if (type === "message_update") { - const delta = event.assistantMessageEvent; - if (delta?.type === "text_delta") { - state.textChunks.push(delta.delta || ""); - updateWidgets(); - } - } else if (type === "tool_execution_start") { - state.toolCount++; - updateWidgets(); - } - } catch {} - } - - function spawnAgent( - state: SubState, - prompt: string, - ctx: any, - ): Promise { - const model = ctx.model - ? `${ctx.model.provider}/${ctx.model.id}` - : "openrouter/google/gemini-3-flash-preview"; - - return new Promise((resolve) => { - const proc = spawn("pi", [ - "--mode", "json", - "-p", - "--session", state.sessionFile, // persistent session for /subcont resumption - "--no-extensions", - "--model", model, - "--tools", "read,bash,grep,find,ls", - "--thinking", "off", - prompt, - ], { - stdio: ["ignore", "pipe", "pipe"], - env: { ...process.env }, - }); - - state.proc = proc; - - const startTime = Date.now(); - const timer = setInterval(() => { - state.elapsed = Date.now() - startTime; - updateWidgets(); - }, 1000); - - let buffer = ""; - - proc.stdout!.setEncoding("utf-8"); - proc.stdout!.on("data", (chunk: string) => { - buffer += chunk; - const lines = buffer.split("\n"); - buffer = lines.pop() || ""; - for (const line of lines) processLine(state, line); - }); - - proc.stderr!.setEncoding("utf-8"); - proc.stderr!.on("data", (chunk: string) => { - if (chunk.trim()) { - state.textChunks.push(chunk); - updateWidgets(); - } - }); - - proc.on("close", (code) => { - if (buffer.trim()) processLine(state, buffer); - clearInterval(timer); - state.elapsed = Date.now() - startTime; - state.status = code === 0 ? "done" : "error"; - state.proc = undefined; - updateWidgets(); - - const result = state.textChunks.join(""); - ctx.ui.notify( - `Subagent #${state.id} ${state.status} in ${Math.round(state.elapsed / 1000)}s`, - state.status === "done" ? "success" : "error" - ); - - pi.sendMessage({ - customType: "subagent-result", - content: `Subagent #${state.id}${state.turnCount > 1 ? ` (Turn ${state.turnCount})` : ""} finished "${prompt}" in ${Math.round(state.elapsed / 1000)}s.\n\nResult:\n${result.slice(0, 8000)}${result.length > 8000 ? "\n\n... [truncated]" : ""}`, - display: true, - }, { deliverAs: "followUp", triggerTurn: true }); - - resolve(); - }); - - proc.on("error", (err) => { - clearInterval(timer); - state.status = "error"; - state.proc = undefined; - state.textChunks.push(`Error: ${err.message}`); - updateWidgets(); - resolve(); - }); - }); - } - - // ── Tools for the Main Agent ────────────────────────────────────────────── - - pi.registerTool({ - name: "subagent_create", - description: "Spawn a background subagent to perform a task. Returns the subagent ID immediately while it runs in the background. Results will be delivered as a follow-up message when finished.", - parameters: Type.Object({ - task: Type.String({ description: "The complete task description for the subagent to perform" }), - }), - execute: async (callId, args, _signal, _onUpdate, ctx) => { - widgetCtx = ctx; - const id = nextId++; - const state: SubState = { - id, - status: "running", - task: args.task, - textChunks: [], - toolCount: 0, - elapsed: 0, - sessionFile: makeSessionFile(id), - turnCount: 1, - }; - agents.set(id, state); - updateWidgets(); - - // Fire-and-forget - spawnAgent(state, args.task, ctx); - - return { - content: [{ type: "text", text: `Subagent #${id} spawned and running in background.` }], - }; - }, - }); - - pi.registerTool({ - name: "subagent_continue", - description: "Continue an existing subagent's conversation. Use this to give further instructions to a finished subagent. Returns immediately while it runs in the background.", - parameters: Type.Object({ - id: Type.Number({ description: "The ID of the subagent to continue" }), - prompt: Type.String({ description: "The follow-up prompt or new instructions" }), - }), - execute: async (callId, args, _signal, _onUpdate, ctx) => { - widgetCtx = ctx; - const state = agents.get(args.id); - if (!state) { - return { content: [{ type: "text", text: `Error: No subagent #${args.id} found.` }] }; - } - if (state.status === "running") { - return { content: [{ type: "text", text: `Error: Subagent #${args.id} is still running.` }] }; - } - - state.status = "running"; - state.task = args.prompt; - state.textChunks = []; - state.elapsed = 0; - state.turnCount++; - updateWidgets(); - - ctx.ui.notify(`Continuing Subagent #${args.id} (Turn ${state.turnCount})…`, "info"); - spawnAgent(state, args.prompt, ctx); - - return { - content: [{ type: "text", text: `Subagent #${args.id} continuing conversation in background.` }], - }; - }, - }); - - pi.registerTool({ - name: "subagent_remove", - description: "Remove a specific subagent. Kills it if it's currently running.", - parameters: Type.Object({ - id: Type.Number({ description: "The ID of the subagent to remove" }), - }), - execute: async (callId, args, _signal, _onUpdate, ctx) => { - widgetCtx = ctx; - const state = agents.get(args.id); - if (!state) { - return { content: [{ type: "text", text: `Error: No subagent #${args.id} found.` }] }; - } - - if (state.proc && state.status === "running") { - state.proc.kill("SIGTERM"); - } - ctx.ui.setWidget(`sub-${args.id}`, undefined); - agents.delete(args.id); - - return { - content: [{ type: "text", text: `Subagent #${args.id} removed successfully.` }], - }; - }, - }); - - pi.registerTool({ - name: "subagent_list", - description: "List all active and finished subagents, showing their IDs, tasks, and status.", - parameters: Type.Object({}), - execute: async () => { - if (agents.size === 0) { - return { content: [{ type: "text", text: "No active subagents." }] }; - } - - const list = Array.from(agents.values()).map(s => - `#${s.id} [${s.status.toUpperCase()}] (Turn ${s.turnCount}) - ${s.task}` - ).join("\n"); - - return { - content: [{ type: "text", text: `Subagents:\n${list}` }], - }; - }, - }); - - - - // ── /sub ─────────────────────────────────────────────────────────── - - pi.registerCommand("sub", { - description: "Spawn a subagent with live widget: /sub ", - handler: async (args, ctx) => { - widgetCtx = ctx; - - const task = args?.trim(); - if (!task) { - ctx.ui.notify("Usage: /sub ", "error"); - return; - } - - const id = nextId++; - const state: SubState = { - id, - status: "running", - task, - textChunks: [], - toolCount: 0, - elapsed: 0, - sessionFile: makeSessionFile(id), - turnCount: 1, - }; - agents.set(id, state); - updateWidgets(); - - // Fire-and-forget - spawnAgent(state, task, ctx); - }, - }); - - // ── /subcont ──────────────────────────────────────────── - - pi.registerCommand("subcont", { - description: "Continue an existing subagent's conversation: /subcont ", - handler: async (args, ctx) => { - widgetCtx = ctx; - - const trimmed = args?.trim() ?? ""; - const spaceIdx = trimmed.indexOf(" "); - if (spaceIdx === -1) { - ctx.ui.notify("Usage: /subcont ", "error"); - return; - } - - const num = parseInt(trimmed.slice(0, spaceIdx), 10); - const prompt = trimmed.slice(spaceIdx + 1).trim(); - - if (isNaN(num) || !prompt) { - ctx.ui.notify("Usage: /subcont ", "error"); - return; - } - - const state = agents.get(num); - if (!state) { - ctx.ui.notify(`No subagent #${num} found. Use /sub to create one.`, "error"); - return; - } - - if (state.status === "running") { - ctx.ui.notify(`Subagent #${num} is still running — wait for it to finish first.`, "warning"); - return; - } - - // Resume: update state for a new turn - state.status = "running"; - state.task = prompt; - state.textChunks = []; - state.elapsed = 0; - state.turnCount++; - updateWidgets(); - - ctx.ui.notify(`Continuing Subagent #${num} (Turn ${state.turnCount})…`, "info"); - - // Fire-and-forget — reuses the same sessionFile for conversation history - spawnAgent(state, prompt, ctx); - }, - }); - - // ── /subrm ─────────────────────────────────────────────────────── - - pi.registerCommand("subrm", { - description: "Remove a specific subagent widget: /subrm ", - handler: async (args, ctx) => { - widgetCtx = ctx; - - const num = parseInt(args?.trim() ?? "", 10); - if (isNaN(num)) { - ctx.ui.notify("Usage: /subrm ", "error"); - return; - } - - const state = agents.get(num); - if (!state) { - ctx.ui.notify(`No subagent #${num} found.`, "error"); - return; - } - - // Kill the process if still running - if (state.proc && state.status === "running") { - state.proc.kill("SIGTERM"); - ctx.ui.notify(`Subagent #${num} killed and removed.`, "warning"); - } else { - ctx.ui.notify(`Subagent #${num} removed.`, "info"); - } - - ctx.ui.setWidget(`sub-${num}`, undefined); - agents.delete(num); - }, - }); - - // ── /subclear ───────────────────────────────────────────────────────────── - - pi.registerCommand("subclear", { - description: "Clear all subagent widgets", - handler: async (_args, ctx) => { - widgetCtx = ctx; - - let killed = 0; - for (const [id, state] of Array.from(agents.entries())) { - if (state.proc && state.status === "running") { - state.proc.kill("SIGTERM"); - killed++; - } - ctx.ui.setWidget(`sub-${id}`, undefined); - } - - const total = agents.size; - agents.clear(); - nextId = 1; - - const msg = total === 0 - ? "No subagents to clear." - : `Cleared ${total} subagent${total !== 1 ? "s" : ""}${killed > 0 ? ` (${killed} killed)` : ""}.`; - ctx.ui.notify(msg, total === 0 ? "info" : "success"); - }, - }); - - // ── Session lifecycle ───────────────────────────────────────────────────── - - pi.on("session_start", async (_event, ctx) => { - applyExtensionDefaults(import.meta.url, ctx); - for (const [id, state] of Array.from(agents.entries())) { - if (state.proc && state.status === "running") { - state.proc.kill("SIGTERM"); - } - ctx.ui.setWidget(`sub-${id}`, undefined); - } - agents.clear(); - nextId = 1; - widgetCtx = ctx; - }); -} diff --git a/extensions/system-select.ts b/extensions/system-select.ts deleted file mode 100644 index 8991658..0000000 --- a/extensions/system-select.ts +++ /dev/null @@ -1,167 +0,0 @@ -/** - * System Select — Switch the system prompt via /system - * - * Scans .pi/agents/, .claude/agents/, .gemini/agents/, .codex/agents/ - * (project-local and global) for agent definition .md files. - * - * /system opens a select dialog to pick a system prompt. The selected - * agent's body is prepended to Pi's default instructions so tool usage - * still works. Tools are restricted to the agent's declared tool set - * if specified. - * - * Usage: pi -e extensions/system-select.ts -e extensions/minimal.ts - */ - -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; -import { readdirSync, readFileSync, existsSync } from "node:fs"; -import { join, basename } from "node:path"; -import { homedir } from "node:os"; -import { applyExtensionDefaults } from "./themeMap.ts"; - -interface AgentDef { - name: string; - description: string; - tools: string[]; - body: string; - source: string; -} - -function parseFrontmatter(raw: string): { fields: Record; body: string } { - const match = raw.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/); - if (!match) return { fields: {}, body: raw }; - const fields: Record = {}; - for (const line of match[1].split("\n")) { - const idx = line.indexOf(":"); - if (idx > 0) fields[line.slice(0, idx).trim()] = line.slice(idx + 1).trim(); - } - return { fields, body: match[2] }; -} - -function scanAgents(dir: string, source: string): AgentDef[] { - if (!existsSync(dir)) return []; - const agents: AgentDef[] = []; - try { - for (const file of readdirSync(dir)) { - if (!file.endsWith(".md")) continue; - const raw = readFileSync(join(dir, file), "utf-8"); - const { fields, body } = parseFrontmatter(raw); - agents.push({ - name: fields.name || basename(file, ".md"), - description: fields.description || "", - tools: fields.tools ? fields.tools.split(",").map((t) => t.trim()) : [], - body: body.trim(), - source, - }); - } - } catch {} - return agents; -} - -function displayName(name: string): string { - return name.split("-").map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(" "); -} - -export default function (pi: ExtensionAPI) { - let activeAgent: AgentDef | null = null; - let allAgents: AgentDef[] = []; - let defaultTools: string[] = []; - - pi.on("session_start", async (_event, ctx) => { - applyExtensionDefaults(import.meta.url, ctx); - activeAgent = null; - allAgents = []; - - const home = homedir(); - const cwd = ctx.cwd; - - const dirs: [string, string][] = [ - [join(cwd, ".pi", "agents"), ".pi"], - [join(cwd, ".claude", "agents"), ".claude"], - [join(cwd, ".gemini", "agents"), ".gemini"], - [join(cwd, ".codex", "agents"), ".codex"], - [join(home, ".pi", "agent", "agents"), "~/.pi"], - [join(home, ".claude", "agents"), "~/.claude"], - [join(home, ".gemini", "agents"), "~/.gemini"], - [join(home, ".codex", "agents"), "~/.codex"], - ]; - - const seen = new Set(); - const sourceCounts: Record = {}; - - for (const [dir, source] of dirs) { - const agents = scanAgents(dir, source); - for (const agent of agents) { - const key = agent.name.toLowerCase(); - if (seen.has(key)) continue; - seen.add(key); - allAgents.push(agent); - sourceCounts[source] = (sourceCounts[source] || 0) + 1; - } - } - - defaultTools = pi.getActiveTools(); - ctx.ui.setStatus("system-prompt", "System Prompt: Default"); - - const defaultPrompt = ctx.getSystemPrompt(); - const lines = defaultPrompt.split("\n").length; - const chars = defaultPrompt.length; - - const loadedSources = Object.entries(sourceCounts) - .map(([src, count]) => `${count} from ${src}`) - .join(", "); - - const notifyLines = []; - if (allAgents.length > 0) { - notifyLines.push(`Loaded ${allAgents.length} agents (${loadedSources})`); - } - notifyLines.push(`System Prompt: Default (${lines} lines, ${chars} chars)`); - - ctx.ui.notify(notifyLines.join("\n"), "info"); - }); - - pi.registerCommand("system", { - description: "Select a system prompt from discovered agents", - handler: async (_args, ctx) => { - if (allAgents.length === 0) { - ctx.ui.notify("No agents found in .*/agents/*.md", "warning"); - return; - } - - const options = [ - "Reset to Default", - ...allAgents.map((a) => `${a.name} — ${a.description} [${a.source}]`), - ]; - - const choice = await ctx.ui.select("Select System Prompt", options); - if (choice === undefined) return; - - if (choice === options[0]) { - activeAgent = null; - pi.setActiveTools(defaultTools); - ctx.ui.setStatus("system-prompt", "System Prompt: Default"); - ctx.ui.notify("System Prompt reset to Default", "success"); - return; - } - - const idx = options.indexOf(choice) - 1; - const agent = allAgents[idx]; - activeAgent = agent; - - if (agent.tools.length > 0) { - pi.setActiveTools(agent.tools); - } else { - pi.setActiveTools(defaultTools); - } - - ctx.ui.setStatus("system-prompt", `System Prompt: ${displayName(agent.name)}`); - ctx.ui.notify(`System Prompt switched to: ${displayName(agent.name)}`, "success"); - }, - }); - - pi.on("before_agent_start", async (event, _ctx) => { - if (!activeAgent) return; - return { - systemPrompt: activeAgent.body + "\n\n" + event.systemPrompt, - }; - }); -} diff --git a/extensions/theme-cycler.ts b/extensions/theme-cycler.ts deleted file mode 100644 index 3b16d4c..0000000 --- a/extensions/theme-cycler.ts +++ /dev/null @@ -1,181 +0,0 @@ -/** - * Theme Cycler — Keyboard shortcuts to cycle through available themes - * - * Shortcuts: - * Ctrl+X — Cycle theme forward - * Ctrl+Q — Cycle theme backward - * - * Commands: - * /theme — Open select picker to choose a theme - * /theme — Switch directly by name - * - * Features: - * - Status line shows current theme name with accent color - * - Color swatch widget flashes briefly after each switch - * - Auto-dismisses swatch after 3 seconds - * - * Usage: pi -e extensions/theme-cycler.ts -e extensions/minimal.ts - */ - -import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent"; -import { truncateToWidth } from "@mariozechner/pi-tui"; -import { applyExtensionDefaults } from "./themeMap.ts"; - -export default function (pi: ExtensionAPI) { - let currentCtx: ExtensionContext | undefined; - let swatchTimer: ReturnType | null = null; - - function updateStatus(ctx: ExtensionContext) { - if (!ctx.hasUI) return; - const name = ctx.ui.theme.name; - ctx.ui.setStatus("theme", `🎨 ${name}`); - } - - function showSwatch(ctx: ExtensionContext) { - if (!ctx.hasUI) return; - - if (swatchTimer) { - clearTimeout(swatchTimer); - swatchTimer = null; - } - - ctx.ui.setWidget( - "theme-swatch", - (_tui, theme) => ({ - invalidate() {}, - render(width: number): string[] { - const block = "\u2588\u2588\u2588"; - const swatch = - theme.fg("success", block) + - " " + - theme.fg("accent", block) + - " " + - theme.fg("warning", block) + - " " + - theme.fg("dim", block) + - " " + - theme.fg("muted", block); - const label = theme.fg("accent", " 🎨 ") + theme.fg("muted", ctx.ui.theme.name) + " " + swatch; - const border = theme.fg("borderMuted", "─".repeat(Math.max(0, width))); - return [border, truncateToWidth(" " + label, width), border]; - }, - }), - { placement: "belowEditor" }, - ); - - swatchTimer = setTimeout(() => { - ctx.ui.setWidget("theme-swatch", undefined); - swatchTimer = null; - }, 3000); - } - - function getThemeList(ctx: ExtensionContext) { - return ctx.ui.getAllThemes(); - } - - function findCurrentIndex(ctx: ExtensionContext): number { - const themes = getThemeList(ctx); - const current = ctx.ui.theme.name; - return themes.findIndex((t) => t.name === current); - } - - function cycleTheme(ctx: ExtensionContext, direction: 1 | -1) { - if (!ctx.hasUI) return; - - const themes = getThemeList(ctx); - if (themes.length === 0) { - ctx.ui.notify("No themes available", "warning"); - return; - } - - let index = findCurrentIndex(ctx); - if (index === -1) index = 0; - - index = (index + direction + themes.length) % themes.length; - const theme = themes[index]; - const result = ctx.ui.setTheme(theme.name); - - if (result.success) { - updateStatus(ctx); - showSwatch(ctx); - ctx.ui.notify(`${theme.name} (${index + 1}/${themes.length})`, "info"); - } else { - ctx.ui.notify(`Failed to set theme: ${result.error}`, "error"); - } - } - - // --- Shortcuts --- - - pi.registerShortcut("ctrl+x", { - description: "Cycle theme forward", - handler: async (ctx) => { - currentCtx = ctx; - cycleTheme(ctx, 1); - }, - }); - - pi.registerShortcut("ctrl+q", { - description: "Cycle theme backward", - handler: async (ctx) => { - currentCtx = ctx; - cycleTheme(ctx, -1); - }, - }); - - // --- Command: /theme --- - - pi.registerCommand("theme", { - description: "Select a theme: /theme or /theme ", - handler: async (args, ctx) => { - currentCtx = ctx; - if (!ctx.hasUI) return; - - const themes = getThemeList(ctx); - const arg = args.trim(); - - if (arg) { - const result = ctx.ui.setTheme(arg); - if (result.success) { - updateStatus(ctx); - showSwatch(ctx); - ctx.ui.notify(`Theme: ${arg}`, "info"); - } else { - ctx.ui.notify(`Theme not found: ${arg}. Use /theme to see available themes.`, "error"); - } - return; - } - - const items = themes.map((t) => { - const desc = t.path ? t.path : "built-in"; - const active = t.name === ctx.ui.theme.name ? " (active)" : ""; - return `${t.name}${active} — ${desc}`; - }); - - const selected = await ctx.ui.select("Select Theme", items); - if (!selected) return; - - const selectedName = selected.split(/\s/)[0]; - const result = ctx.ui.setTheme(selectedName); - if (result.success) { - updateStatus(ctx); - showSwatch(ctx); - ctx.ui.notify(`Theme: ${selectedName}`, "info"); - } - }, - }); - - // --- Session init --- - - pi.on("session_start", async (_event, ctx) => { - currentCtx = ctx; - applyExtensionDefaults(import.meta.url, ctx); - updateStatus(ctx); - }); - - pi.on("session_shutdown", async () => { - if (swatchTimer) { - clearTimeout(swatchTimer); - swatchTimer = null; - } - }); -} diff --git a/extensions/themeMap.ts b/extensions/themeMap.ts deleted file mode 100644 index 19adcfb..0000000 --- a/extensions/themeMap.ts +++ /dev/null @@ -1,145 +0,0 @@ -/** - * themeMap.ts — Per-extension default theme assignments - * - * Themes live in .pi/themes/ and are mapped by extension filename (no extension). - * Each extension calls applyExtensionTheme(import.meta.url, ctx) in its session_start - * hook to automatically load its designated theme on boot. - * - * Available themes (.pi/themes/): - * catppuccin-mocha · cyberpunk · dracula · everforest · gruvbox - * midnight-ocean · nord · ocean-breeze · rose-pine - * synthwave · tokyo-night - */ - -import type { ExtensionContext } from "@mariozechner/pi-coding-agent"; -import { basename } from "path"; -import { fileURLToPath } from "url"; - -// ── Theme assignments ────────────────────────────────────────────────────── -// -// Key = extension filename without extension (matches extensions/.ts) -// Value = theme name from .pi/themes/.json -// -export const THEME_MAP: Record = { - "agent-chain": "midnight-ocean", // deep sequential pipeline - "agent-dashboard": "tokyo-night", // unified monitoring hub - "agent-team": "dracula", // rich orchestration palette - "cross-agent": "ocean-breeze", // cross-boundary, connecting - "damage-control": "gruvbox", // grounded, earthy safety - "minimal": "synthwave", // synthwave by default now! - "observatory": "cyberpunk", // futuristic observation deck - "pi-pi": "rose-pine", // warm creative meta-agent - "pure-focus": "everforest", // calm, distraction-free - "purpose-gate": "tokyo-night", // intentional, sharp focus - "session-replay": "catppuccin-mocha", // soft, reflective history - "subagent-widget": "cyberpunk", // multi-agent futuristic - "system-select": "catppuccin-mocha", // soft selection UI - "theme-cycler": "synthwave", // neon, it's a theme tool - "tilldone": "everforest", // task-focused calm - "tool-counter": "synthwave", // techy metrics - "tool-counter-widget":"synthwave", // same family -}; - -// ── Helpers ─────────────────────────────────────────────────────────────── - -/** Derive the extension name (e.g. "minimal") from its import.meta.url. */ -function extensionName(fileUrl: string): string { - const filePath = fileUrl.startsWith("file://") ? fileURLToPath(fileUrl) : fileUrl; - return basename(filePath).replace(/\.[^.]+$/, ""); -} - -// ── Theme ────────────────────────────────────────────────────────────────── - -/** - * Apply the mapped theme for an extension on session boot. - * - * @param fileUrl Pass `import.meta.url` from the calling extension file. - * @param ctx The ExtensionContext from the session_start handler. - * @returns true if the theme was applied successfully, false otherwise. - */ -export function applyExtensionTheme(fileUrl: string, ctx: ExtensionContext): boolean { - if (!ctx.hasUI) return false; - - const name = extensionName(fileUrl); - - // If there are multiple extensions stacked in 'ipi', they each fire session_start - // and try to apply their own mapped theme. The LAST one to fire wins. - // Since system-select is last in the ipi alias array, it was setting 'catppuccin-mocha'. - - // We want to skip theme application for all secondary extensions if they are stacked, - // so the primary extension (first in the array) dictates the theme. - const primaryExt = primaryExtensionName(); - if (primaryExt && primaryExt !== name) { - return true; // Pretend we succeeded, but don't overwrite the primary theme - } - - let themeName = THEME_MAP[name]; - - if (!themeName) { - themeName = "synthwave"; - } - - const result = ctx.ui.setTheme(themeName); - - if (!result.success && themeName !== "synthwave") { - return ctx.ui.setTheme("synthwave").success; - } - - return result.success; -} -// ── Title ────────────────────────────────────────────────────────────────── - -/** - * Read process.argv to find the first -e / --extension flag value. - * - * When Pi is launched as: - * pi -e extensions/subagent-widget.ts -e extensions/pure-focus.ts - * - * process.argv contains those paths verbatim. Every stacked extension calls - * this and gets the same answer ("subagent-widget"), so all setTitle calls - * are idempotent — no shared state or deduplication needed. - * - * Returns null if no -e flag is present (e.g. plain `pi` with no extensions). - */ -function primaryExtensionName(): string | null { - const argv = process.argv; - for (let i = 0; i < argv.length - 1; i++) { - if (argv[i] === "-e" || argv[i] === "--extension") { - return basename(argv[i + 1]).replace(/\.[^.]+$/, ""); - } - } - return null; -} - -/** - * Set the terminal title to "π - " on session boot. - * Reads the title from process.argv so all stacked extensions agree on the - * same value — no coordination or shared state required. - * - * Deferred 150 ms to fire after Pi's own startup title-set. - */ -function applyExtensionTitle(ctx: ExtensionContext): void { - if (!ctx.hasUI) return; - const name = primaryExtensionName(); - if (!name) return; - setTimeout(() => ctx.ui.setTitle(`π - ${name}`), 150); -} - -// ── Combined default ─────────────────────────────────────────────────────── - -/** - * Apply both the mapped theme AND the terminal title for an extension. - * Drop-in replacement for applyExtensionTheme — call this in every session_start. - * - * Usage: - * import { applyExtensionDefaults } from "./themeMap.ts"; - * - * pi.on("session_start", async (_event, ctx) => { - * applyExtensionDefaults(import.meta.url, ctx); - * // ... rest of handler - * }); - */ -export function applyExtensionDefaults(fileUrl: string, ctx: ExtensionContext): void { - applyExtensionTheme(fileUrl, ctx); - applyExtensionTitle(ctx); -} diff --git a/extensions/tilldone.ts b/extensions/tilldone.ts deleted file mode 100644 index 66eae0d..0000000 --- a/extensions/tilldone.ts +++ /dev/null @@ -1,726 +0,0 @@ -/** - * TillDone Extension — Work Till It's Done - * - * A task-driven discipline extension. The agent MUST define what it's going - * to do (via `tilldone add`) before it can use any other tools. On agent - * completion, if tasks remain incomplete, the agent gets nudged to continue - * or mark them done. Play on words: "todo" → "tilldone" (work till done). - * - * Three-state lifecycle: idle → inprogress → done - * - * Each list has a title and description that give the tasks a theme. - * Use `new-list` to start a fresh list. `clear` wipes tasks with user confirm. - * - * UI surfaces: - * - Footer: persistent task list with live progress + list title - * - Widget: prominent "current task" display (the inprogress task) - * - Status: compact summary in the status line - * - /tilldone: interactive overlay with full task details - * - * Usage: pi -e extensions/tilldone.ts - */ - -import { StringEnum } from "@mariozechner/pi-ai"; -import type { ExtensionAPI, ExtensionContext, Theme } from "@mariozechner/pi-coding-agent"; -import { DynamicBorder } from "@mariozechner/pi-coding-agent"; -import { Container, matchesKey, Text, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui"; -import { Type } from "@sinclair/typebox"; -import { applyExtensionDefaults } from "./themeMap.ts"; - -// ── Types ────────────────────────────────────────────────────────────── - -type TaskStatus = "idle" | "inprogress" | "done"; - -interface Task { - id: number; - text: string; - status: TaskStatus; -} - -interface TillDoneDetails { - action: string; - tasks: Task[]; - nextId: number; - listTitle?: string; - listDescription?: string; - error?: string; -} - -const TillDoneParams = Type.Object({ - action: StringEnum(["new-list", "add", "toggle", "remove", "update", "list", "clear"] as const), - text: Type.Optional(Type.String({ description: "Task text (for add/update), or list title (for new-list)" })), - texts: Type.Optional(Type.Array(Type.String(), { description: "Multiple task texts (for add). Use this to batch-add several tasks at once." })), - description: Type.Optional(Type.String({ description: "List description (for new-list)" })), - id: Type.Optional(Type.Number({ description: "Task ID (for toggle/remove/update)" })), -}); - -// ── Status helpers ───────────────────────────────────────────────────── - -const STATUS_ICON: Record = { idle: "○", inprogress: "●", done: "✓" }; -const NEXT_STATUS: Record = { idle: "inprogress", inprogress: "done", done: "idle" }; -const STATUS_LABEL: Record = { idle: "idle", inprogress: "in progress", done: "done" }; - -// ── /tilldone overlay component ──────────────────────────────────────── - -class TillDoneListComponent { - private tasks: Task[]; - private title: string | undefined; - private desc: string | undefined; - private theme: Theme; - private onClose: () => void; - private cachedWidth?: number; - private cachedLines?: string[]; - - constructor(tasks: Task[], title: string | undefined, desc: string | undefined, theme: Theme, onClose: () => void) { - this.tasks = tasks; - this.title = title; - this.desc = desc; - this.theme = theme; - this.onClose = onClose; - } - - handleInput(data: string): void { - if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) { - this.onClose(); - } - } - - render(width: number): string[] { - if (this.cachedLines && this.cachedWidth === width) return this.cachedLines; - - const lines: string[] = []; - const th = this.theme; - - lines.push(""); - const heading = this.title - ? th.fg("accent", ` ${this.title} `) - : th.fg("accent", " TillDone "); - const headingLen = this.title ? this.title.length + 2 : 10; - lines.push(truncateToWidth( - th.fg("borderMuted", "─".repeat(3)) + heading + - th.fg("borderMuted", "─".repeat(Math.max(0, width - 3 - headingLen))), - width, - )); - - if (this.desc) { - lines.push(truncateToWidth(` ${th.fg("muted", this.desc)}`, width)); - } - lines.push(""); - - if (this.tasks.length === 0) { - lines.push(truncateToWidth(` ${th.fg("dim", "No tasks yet. Ask the agent to add some!")}`, width)); - } else { - const done = this.tasks.filter((t) => t.status === "done").length; - const active = this.tasks.filter((t) => t.status === "inprogress").length; - const idle = this.tasks.filter((t) => t.status === "idle").length; - - lines.push(truncateToWidth( - " " + - th.fg("success", `${done} done`) + th.fg("dim", " ") + - th.fg("accent", `${active} active`) + th.fg("dim", " ") + - th.fg("muted", `${idle} idle`), - width, - )); - lines.push(""); - - for (const task of this.tasks) { - const icon = task.status === "done" - ? th.fg("success", STATUS_ICON.done) - : task.status === "inprogress" - ? th.fg("accent", STATUS_ICON.inprogress) - : th.fg("dim", STATUS_ICON.idle); - const id = th.fg("accent", `#${task.id}`); - const text = task.status === "done" - ? th.fg("dim", task.text) - : task.status === "inprogress" - ? th.fg("success", task.text) - : th.fg("muted", task.text); - lines.push(truncateToWidth(` ${icon} ${id} ${text}`, width)); - } - } - - lines.push(""); - lines.push(truncateToWidth(` ${th.fg("dim", "Press Escape to close")}`, width)); - lines.push(""); - - this.cachedWidth = width; - this.cachedLines = lines; - return lines; - } - - invalidate(): void { - this.cachedWidth = undefined; - this.cachedLines = undefined; - } -} - -// ── Extension entry point ────────────────────────────────────────────── - -export default function (pi: ExtensionAPI) { - let tasks: Task[] = []; - let nextId = 1; - let listTitle: string | undefined; - let listDescription: string | undefined; - let nudgedThisCycle = false; - - // ── Snapshot for details ─────────────────────────────────────────── - - const makeDetails = (action: string, error?: string): TillDoneDetails => ({ - action, - tasks: [...tasks], - nextId, - listTitle, - listDescription, - ...(error ? { error } : {}), - }); - - // ── UI refresh ───────────────────────────────────────────────────── - - const refreshWidget = (ctx: ExtensionContext) => { - const current = tasks.find((t) => t.status === "inprogress"); - - if (!current) { - ctx.ui.setWidget("tilldone-current", undefined); - return; - } - - ctx.ui.setWidget("tilldone-current", (_tui, theme) => { - const container = new Container(); - const borderFn = (s: string) => theme.fg("dim", s); - - container.addChild(new Text("", 0, 0)); - container.addChild(new DynamicBorder(borderFn)); - const content = new Text("", 1, 0); - container.addChild(content); - container.addChild(new DynamicBorder(borderFn)); - - return { - render(width: number): string[] { - const cur = tasks.find((t) => t.status === "inprogress"); - if (!cur) return []; - - const line = - theme.fg("accent", "● ") + - theme.fg("dim", "WORKING ON ") + - theme.fg("accent", `#${cur.id}`) + - theme.fg("dim", " ") + - theme.fg("success", cur.text); - - content.setText(truncateToWidth(line, width - 4)); - return container.render(width); - }, - invalidate() { container.invalidate(); }, - }; - }, { placement: "belowEditor" }); - }; - - const refreshFooter = (ctx: ExtensionContext) => { - ctx.ui.setFooter((tui, theme, footerData) => { - const unsub = footerData.onBranchChange(() => tui.requestRender()); - - return { - dispose: unsub, - invalidate() {}, - render(width: number): string[] { - const done = tasks.filter((t) => t.status === "done").length; - const active = tasks.filter((t) => t.status === "inprogress").length; - const idle = tasks.filter((t) => t.status === "idle").length; - const total = tasks.length; - - // ── Line 1: list title + progress (left), counts (right) ── - const titleDisplay = listTitle - ? theme.fg("accent", ` ${listTitle} `) - : theme.fg("dim", " TillDone "); - - const l1Left = total === 0 - ? titleDisplay + theme.fg("muted", "no tasks") - : titleDisplay + - theme.fg("warning", "[") + - theme.fg("success", `${done}`) + - theme.fg("dim", "/") + - theme.fg("success", `${total}`) + - theme.fg("warning", "]"); - - const l1Right = total === 0 - ? "" - : theme.fg("dim", STATUS_ICON.idle + " ") + theme.fg("muted", `${idle}`) + - theme.fg("dim", " ") + - theme.fg("accent", STATUS_ICON.inprogress + " ") + theme.fg("accent", `${active}`) + - theme.fg("dim", " ") + - theme.fg("success", STATUS_ICON.done + " ") + theme.fg("success", `${done}`) + - theme.fg("dim", " "); - - const pad1 = " ".repeat(Math.max(1, width - visibleWidth(l1Left) - visibleWidth(l1Right))); - const line1 = truncateToWidth(l1Left + pad1 + l1Right, width, ""); - - if (total === 0) return [line1]; - - // ── Rows: inprogress first, then most recent done, max 5 ── - const activeTasks = tasks.filter((t) => t.status === "inprogress"); - const doneTasks = tasks.filter((t) => t.status === "done").reverse(); - const visible = [...activeTasks, ...doneTasks].slice(0, 5); - const remaining = total - visible.length; - - const rows = visible.map((t) => { - const icon = t.status === "done" - ? theme.fg("success", STATUS_ICON.done) - : theme.fg("accent", STATUS_ICON.inprogress); - const text = t.status === "done" - ? theme.fg("dim", t.text) - : theme.fg("success", t.text); - return truncateToWidth(` ${icon} ${text}`, width, ""); - }); - - if (remaining > 0) { - rows.push(truncateToWidth( - ` ${theme.fg("dim", ` +${remaining} more`)}`, - width, "", - )); - } - - return [line1, ...rows]; - }, - }; - }); - }; - - const refreshUI = (ctx: ExtensionContext) => { - if (tasks.length === 0) { - ctx.ui.setStatus("📋 TillDone: no tasks", "tilldone"); - } else { - const remaining = tasks.filter((t) => t.status !== "done").length; - const label = listTitle ? `📋 ${listTitle}` : "📋 TillDone"; - ctx.ui.setStatus(`${label}: ${tasks.length} tasks (${remaining} remaining)`, "tilldone"); - } - - refreshWidget(ctx); - refreshFooter(ctx); - }; - - // ── State reconstruction from session ────────────────────────────── - - const reconstructState = (ctx: ExtensionContext) => { - tasks = []; - nextId = 1; - listTitle = undefined; - listDescription = undefined; - - for (const entry of ctx.sessionManager.getBranch()) { - if (entry.type !== "message") continue; - const msg = entry.message; - if (msg.role !== "toolResult" || msg.toolName !== "tilldone") continue; - - const details = msg.details as TillDoneDetails | undefined; - if (details) { - tasks = details.tasks; - nextId = details.nextId; - listTitle = details.listTitle; - listDescription = details.listDescription; - } - } - - refreshUI(ctx); - }; - - pi.on("session_start", async (_event, ctx) => { - applyExtensionDefaults(import.meta.url, ctx); - reconstructState(ctx); - }); - pi.on("session_switch", async (_event, ctx) => reconstructState(ctx)); - pi.on("session_fork", async (_event, ctx) => reconstructState(ctx)); - pi.on("session_tree", async (_event, ctx) => reconstructState(ctx)); - - // ── Blocking gate ────────────────────────────────────────────────── - - pi.on("tool_call", async (event, _ctx) => { - if (event.toolName === "tilldone") return { block: false }; - - const pending = tasks.filter((t) => t.status !== "done"); - const active = tasks.filter((t) => t.status === "inprogress"); - - if (tasks.length === 0) { - return { - block: true, - reason: "🚫 No TillDone tasks defined. You MUST use `tilldone new-list` or `tilldone add` to define your tasks before using any other tools. Plan your work first!", - }; - } - if (pending.length === 0) { - return { - block: true, - reason: "🚫 All TillDone tasks are done. You MUST use `tilldone add` for new tasks or `tilldone new-list` to start a fresh list before using any other tools.", - }; - } - if (active.length === 0) { - return { - block: true, - reason: "🚫 No task is in progress. You MUST use `tilldone toggle` to mark a task as inprogress before doing any work.", - }; - } - - return { block: false }; - }); - - // ── Auto-nudge on agent_end ──────────────────────────────────────── - - pi.on("agent_end", async (_event, _ctx) => { - const incomplete = tasks.filter((t) => t.status !== "done"); - if (incomplete.length === 0 || nudgedThisCycle) return; - - nudgedThisCycle = true; - - const taskList = incomplete - .map((t) => ` ${STATUS_ICON[t.status]} #${t.id} [${STATUS_LABEL[t.status]}]: ${t.text}`) - .join("\n"); - - pi.sendMessage( - { - customType: "tilldone-nudge", - content: `⚠️ You still have ${incomplete.length} incomplete task(s):\n\n${taskList}\n\nEither continue working on them or mark them done with \`tilldone toggle\`. Don't stop until it's done!`, - display: true, - }, - { triggerTurn: true }, - ); - }); - - pi.on("input", async () => { - nudgedThisCycle = false; - return { action: "continue" as const }; - }); - - // ── Register tilldone tool ───────────────────────────────────────── - - pi.registerTool({ - name: "tilldone", - label: "TillDone", - description: - "Manage your task list. You MUST add tasks before using any other tools. " + - "Actions: new-list (text=title, description), add (text or texts[] for batch), toggle (id) — cycles idle→inprogress→done, remove (id), update (id + text), list, clear. " + - "Always toggle a task to inprogress before starting work on it, and to done when finished. " + - "Use new-list to start a themed list with a title and description. " + - "IMPORTANT: If the user's new request does not fit the current list's theme, use clear to wipe the slate and new-list to start fresh.", - parameters: TillDoneParams, - - async execute(_toolCallId, params, _signal, _onUpdate, ctx) { - switch (params.action) { - case "new-list": { - if (!params.text) { - return { - content: [{ type: "text" as const, text: "Error: text (title) required for new-list" }], - details: makeDetails("new-list", "text required"), - }; - } - - // If a list already exists, confirm before replacing - if (tasks.length > 0 || listTitle) { - const confirmed = await ctx.ui.confirm( - "Start a new list?", - `This will replace${listTitle ? ` "${listTitle}"` : " the current list"} (${tasks.length} task(s)). Continue?`, - { timeout: 30000 }, - ); - if (!confirmed) { - return { - content: [{ type: "text" as const, text: "New list cancelled by user." }], - details: makeDetails("new-list", "cancelled"), - }; - } - } - - tasks = []; - nextId = 1; - listTitle = params.text; - listDescription = params.description || undefined; - - const result = { - content: [{ - type: "text" as const, - text: `New list: "${listTitle}"${listDescription ? ` — ${listDescription}` : ""}`, - }], - details: makeDetails("new-list"), - }; - refreshUI(ctx); - return result; - } - - case "list": { - const header = listTitle ? `${listTitle}:` : ""; - const result = { - content: [{ - type: "text" as const, - text: tasks.length - ? (header ? header + "\n" : "") + - tasks.map((t) => `[${STATUS_ICON[t.status]}] #${t.id} (${t.status}): ${t.text}`).join("\n") - : "No tasks defined yet.", - }], - details: makeDetails("list"), - }; - refreshUI(ctx); - return result; - } - - case "add": { - const items = params.texts?.length ? params.texts : params.text ? [params.text] : []; - if (items.length === 0) { - return { - content: [{ type: "text" as const, text: "Error: text or texts required for add" }], - details: makeDetails("add", "text required"), - }; - } - const added: Task[] = []; - for (const item of items) { - const t: Task = { id: nextId++, text: item, status: "idle" }; - tasks.push(t); - added.push(t); - } - const msg = added.length === 1 - ? `Added task #${added[0].id}: ${added[0].text}` - : `Added ${added.length} tasks: ${added.map((t) => `#${t.id}`).join(", ")}`; - const result = { - content: [{ type: "text" as const, text: msg }], - details: makeDetails("add"), - }; - refreshUI(ctx); - return result; - } - - case "toggle": { - if (params.id === undefined) { - return { - content: [{ type: "text" as const, text: "Error: id required for toggle" }], - details: makeDetails("toggle", "id required"), - }; - } - const task = tasks.find((t) => t.id === params.id); - if (!task) { - return { - content: [{ type: "text" as const, text: `Task #${params.id} not found` }], - details: makeDetails("toggle", `#${params.id} not found`), - }; - } - const prev = task.status; - task.status = NEXT_STATUS[task.status]; - - // Enforce single inprogress — demote any other active task - const demoted: Task[] = []; - if (task.status === "inprogress") { - for (const t of tasks) { - if (t.id !== task.id && t.status === "inprogress") { - t.status = "idle"; - demoted.push(t); - } - } - } - - let msg = `Task #${task.id}: ${prev} → ${task.status}`; - if (demoted.length > 0) { - msg += `\n(Auto-paused ${demoted.map((t) => `#${t.id}`).join(", ")} → idle. Only one task can be in progress at a time.)`; - } - - const result = { - content: [{ - type: "text" as const, - text: msg, - }], - details: makeDetails("toggle"), - }; - refreshUI(ctx); - return result; - } - - case "remove": { - if (params.id === undefined) { - return { - content: [{ type: "text" as const, text: "Error: id required for remove" }], - details: makeDetails("remove", "id required"), - }; - } - const idx = tasks.findIndex((t) => t.id === params.id); - if (idx === -1) { - return { - content: [{ type: "text" as const, text: `Task #${params.id} not found` }], - details: makeDetails("remove", `#${params.id} not found`), - }; - } - const removed = tasks.splice(idx, 1)[0]; - const result = { - content: [{ type: "text" as const, text: `Removed task #${removed.id}: ${removed.text}` }], - details: makeDetails("remove"), - }; - refreshUI(ctx); - return result; - } - - case "update": { - if (params.id === undefined) { - return { - content: [{ type: "text" as const, text: "Error: id required for update" }], - details: makeDetails("update", "id required"), - }; - } - if (!params.text) { - return { - content: [{ type: "text" as const, text: "Error: text required for update" }], - details: makeDetails("update", "text required"), - }; - } - const toUpdate = tasks.find((t) => t.id === params.id); - if (!toUpdate) { - return { - content: [{ type: "text" as const, text: `Task #${params.id} not found` }], - details: makeDetails("update", `#${params.id} not found`), - }; - } - const oldText = toUpdate.text; - toUpdate.text = params.text; - const result = { - content: [{ type: "text" as const, text: `Updated #${toUpdate.id}: "${oldText}" → "${toUpdate.text}"` }], - details: makeDetails("update"), - }; - refreshUI(ctx); - return result; - } - - case "clear": { - if (tasks.length > 0) { - const confirmed = await ctx.ui.confirm( - "Clear TillDone list?", - `This will remove all ${tasks.length} task(s)${listTitle ? ` from "${listTitle}"` : ""}. Continue?`, - { timeout: 30000 }, - ); - if (!confirmed) { - return { - content: [{ type: "text" as const, text: "Clear cancelled by user." }], - details: makeDetails("clear", "cancelled"), - }; - } - } - - const count = tasks.length; - tasks = []; - nextId = 1; - listTitle = undefined; - listDescription = undefined; - - const result = { - content: [{ type: "text" as const, text: `Cleared ${count} task(s)` }], - details: makeDetails("clear"), - }; - refreshUI(ctx); - return result; - } - - default: - return { - content: [{ type: "text" as const, text: `Unknown action: ${params.action}` }], - details: makeDetails("list", `unknown action: ${params.action}`), - }; - } - }, - - renderCall(args, theme) { - let text = theme.fg("toolTitle", theme.bold("tilldone ")) + theme.fg("muted", args.action); - if (args.texts?.length) text += ` ${theme.fg("dim", `${args.texts.length} tasks`)}`; - else if (args.text) text += ` ${theme.fg("dim", `"${args.text}"`)}`; - if (args.description) text += ` ${theme.fg("dim", `— ${args.description}`)}`; - if (args.id !== undefined) text += ` ${theme.fg("accent", `#${args.id}`)}`; - return new Text(text, 0, 0); - }, - - renderResult(result, { expanded }, theme) { - const details = result.details as TillDoneDetails | undefined; - if (!details) { - const text = result.content[0]; - return new Text(text?.type === "text" ? text.text : "", 0, 0); - } - - if (details.error) { - return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0); - } - - const taskList = details.tasks; - - switch (details.action) { - case "new-list": { - let msg = theme.fg("success", "✓ New list ") + theme.fg("accent", `"${details.listTitle}"`); - if (details.listDescription) { - msg += theme.fg("dim", ` — ${details.listDescription}`); - } - return new Text(msg, 0, 0); - } - - case "list": { - if (taskList.length === 0) return new Text(theme.fg("dim", "No tasks"), 0, 0); - - let listText = ""; - if (details.listTitle) { - listText += theme.fg("accent", details.listTitle) + theme.fg("dim", " "); - } - listText += theme.fg("muted", `${taskList.length} task(s):`); - const display = expanded ? taskList : taskList.slice(0, 5); - for (const t of display) { - const icon = t.status === "done" - ? theme.fg("success", STATUS_ICON.done) - : t.status === "inprogress" - ? theme.fg("accent", STATUS_ICON.inprogress) - : theme.fg("dim", STATUS_ICON.idle); - const itemText = t.status === "done" - ? theme.fg("dim", t.text) - : t.status === "inprogress" - ? theme.fg("success", t.text) - : theme.fg("muted", t.text); - listText += `\n${icon} ${theme.fg("accent", `#${t.id}`)} ${itemText}`; - } - if (!expanded && taskList.length > 5) { - listText += `\n${theme.fg("dim", `... ${taskList.length - 5} more`)}`; - } - return new Text(listText, 0, 0); - } - - case "add": { - const text = result.content[0]; - const msg = text?.type === "text" ? text.text : ""; - return new Text(theme.fg("success", "✓ ") + theme.fg("muted", msg), 0, 0); - } - - case "toggle": { - const text = result.content[0]; - const msg = text?.type === "text" ? text.text : ""; - return new Text(theme.fg("accent", "⟳ ") + theme.fg("muted", msg), 0, 0); - } - - case "remove": { - const text = result.content[0]; - const msg = text?.type === "text" ? text.text : ""; - return new Text(theme.fg("warning", "✕ ") + theme.fg("muted", msg), 0, 0); - } - - case "update": { - const text = result.content[0]; - const msg = text?.type === "text" ? text.text : ""; - return new Text(theme.fg("success", "✓ ") + theme.fg("muted", msg), 0, 0); - } - - case "clear": - return new Text(theme.fg("success", "✓ ") + theme.fg("muted", "Cleared all tasks"), 0, 0); - - default: - return new Text(theme.fg("dim", "done"), 0, 0); - } - }, - }); - - // ── /tilldone command ────────────────────────────────────────────── - - pi.registerCommand("tilldone", { - description: "Show all TillDone tasks on the current branch", - handler: async (_args, ctx) => { - if (!ctx.hasUI) { - ctx.ui.notify("/tilldone requires interactive mode", "error"); - return; - } - - await ctx.ui.custom((_tui, theme, _kb, done) => { - return new TillDoneListComponent(tasks, listTitle, listDescription, theme, () => done()); - }); - }, - }); -} diff --git a/extensions/tool-counter-widget.ts b/extensions/tool-counter-widget.ts deleted file mode 100644 index 0cee9b2..0000000 --- a/extensions/tool-counter-widget.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Tool Counter Widget — Tool call counts in a widget above the editor - * - * Shows a persistent, live-updating widget with per-tool background colors. - * Format: Tools (N): [Bash 3] [Read 7] [Write 2] - * - * Usage: pi -e extensions/tool-counter-widget.ts - */ - -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; -import { Box, Text } from "@mariozechner/pi-tui"; -import { applyExtensionDefaults } from "./themeMap.ts"; - -const palette = [ - [12, 40, 80], // deep navy - [50, 20, 70], // dark purple - [10, 55, 45], // dark teal - [70, 30, 10], // dark rust - [55, 15, 40], // dark plum - [15, 50, 65], // dark ocean - [45, 45, 15], // dark olive - [65, 18, 25], // dark wine -]; - -function bg(rgb: number[], s: string): string { - return `\x1b[48;2;${rgb[0]};${rgb[1]};${rgb[2]}m${s}\x1b[49m`; -} - -export default function (pi: ExtensionAPI) { - const counts: Record = {}; - const toolColors: Record = {}; - let total = 0; - let colorIdx = 0; - - pi.on("tool_execution_end", async (event) => { - if (!(event.toolName in toolColors)) { - toolColors[event.toolName] = palette[colorIdx % palette.length]; - colorIdx++; - } - counts[event.toolName] = (counts[event.toolName] || 0) + 1; - total++; - }); - - pi.on("session_start", async (_event, ctx) => { - applyExtensionDefaults(import.meta.url, ctx); - ctx.ui.setWidget("tool-counter", (_tui, theme) => { - const text = new Text("", 1, 1); - - return { - render(width: number): string[] { - const entries = Object.entries(counts); - const parts = entries.map(([name, count]) => { - const rgb = toolColors[name]; - return bg(rgb, `\x1b[38;2;220;220;220m ${name} ${count} \x1b[39m`); - }); - text.setText( - theme.fg("accent", `Tools (${total}):`) + - (entries.length > 0 ? " " + parts.join(" ") : "") - ); - return text.render(width); - }, - invalidate() { - text.invalidate(); - }, - }; - }); - }); -} diff --git a/extensions/tool-counter.ts b/extensions/tool-counter.ts deleted file mode 100644 index db02ba5..0000000 --- a/extensions/tool-counter.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Tool Counter — Rich two-line custom footer - * - * Line 1: model + context meter on left, tokens in/out + cost on right - * Line 2: cwd (branch) on left, tool call tally on right - * - * Demonstrates: setFooter, footerData.getGitBranch(), onBranchChange(), - * session branch traversal for token/cost accumulation. - * - * Usage: pi -e extensions/tool-counter.ts - */ - -import type { AssistantMessage } from "@mariozechner/pi-ai"; -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; -import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui"; -import { basename } from "node:path"; -import { applyExtensionDefaults } from "./themeMap.ts"; - -export default function (pi: ExtensionAPI) { - const counts: Record = {}; - - pi.on("tool_execution_end", async (event) => { - counts[event.toolName] = (counts[event.toolName] || 0) + 1; - }); - - pi.on("session_start", async (_event, ctx) => { - applyExtensionDefaults(import.meta.url, ctx); - ctx.ui.setFooter((tui, theme, footerData) => { - const unsub = footerData.onBranchChange(() => tui.requestRender()); - - return { - dispose: unsub, - invalidate() {}, - render(width: number): string[] { - // --- Line 1: cwd + branch (left), tokens + cost (right) --- - let tokIn = 0; - let tokOut = 0; - let cost = 0; - for (const entry of ctx.sessionManager.getBranch()) { - if (entry.type === "message" && entry.message.role === "assistant") { - const m = entry.message as AssistantMessage; - tokIn += m.usage.input; - tokOut += m.usage.output; - cost += m.usage.cost.total; - } - } - - const fmt = (n: number) => n < 1000 ? `${n}` : `${(n / 1000).toFixed(1)}k`; - const dir = basename(ctx.cwd); - const branch = footerData.getGitBranch(); - - // --- Line 1: model + context meter (left), tokens + cost (right) --- - const usage = ctx.getContextUsage(); - const pct = usage ? usage.percent : 0; - const filled = Math.round(pct / 10) || 1; - const bar = "#".repeat(filled) + "-".repeat(10 - filled); - const model = ctx.model?.id || "no-model"; - - const l1Left = - theme.fg("dim", ` ${model} `) + - theme.fg("warning", "[") + - theme.fg("success", "#".repeat(filled)) + - theme.fg("dim", "-".repeat(10 - filled)) + - theme.fg("warning", "]") + - theme.fg("dim", " ") + - theme.fg("accent", `${Math.round(pct)}%`); - - const l1Right = - theme.fg("success", `${fmt(tokIn)}`) + - theme.fg("dim", " in ") + - theme.fg("accent", `${fmt(tokOut)}`) + - theme.fg("dim", " out ") + - theme.fg("warning", `$${cost.toFixed(4)}`) + - theme.fg("dim", " "); - - const pad1 = " ".repeat(Math.max(1, width - visibleWidth(l1Left) - visibleWidth(l1Right))); - const line1 = truncateToWidth(l1Left + pad1 + l1Right, width, ""); - - // --- Line 2: cwd + branch (left), tool tally (right) --- - const l2Left = - theme.fg("dim", ` ${dir}`) + - (branch - ? theme.fg("dim", " ") + theme.fg("warning", "(") + theme.fg("success", branch) + theme.fg("warning", ")") - : ""); - - const entries = Object.entries(counts); - const l2Right = entries.length === 0 - ? theme.fg("dim", "waiting for tools ") - : entries.map( - ([name, count]) => - theme.fg("accent", name) + theme.fg("dim", " ") + theme.fg("success", `${count}`) - ).join(theme.fg("warning", " | ")) + theme.fg("dim", " "); - - const pad2 = " ".repeat(Math.max(1, width - visibleWidth(l2Left) - visibleWidth(l2Right))); - const line2 = truncateToWidth(l2Left + pad2 + l2Right, width, ""); - - return [line1, line2]; - }, - }; - }); - }); -} diff --git a/images/pi-logo.png b/images/pi-logo.png deleted file mode 100644 index 6b7fc49a53f9c0dc756cbc064f9b6d30d4a11e88..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3778 zcmeAS@N?(olHy`uVBq!ia0y~yU{(NO4mP03?EPX@vYcv+Z3iPP7r+e#*u|{Nt@0UsY^#n^&J<*%+ptU9`fN` zTYB0xCZJKHVC05?T#7$`!+qJ~9}Cxht*T-_VEiTV{omXwmJeHEm$4tPja$2Q-78E{J3}5dWE#nYs%0)STipE`_4@A@4u*tz3=a)&fduZ}`<+(B!eDS- zTxYZf9&M7)ra1<#mTzypcKW5@oL9fUFg$RqTemfDnR~&i+o0y)C>Y5h@Mm$notM&s T7o2P*pkVNH^>bP0l+XkK-?P3& diff --git a/images/pi-logo.svg b/images/pi-logo.svg deleted file mode 100644 index ed14b63..0000000 --- a/images/pi-logo.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - diff --git a/job-application-guide.md b/job-application-guide.md deleted file mode 100644 index 69ba801..0000000 --- a/job-application-guide.md +++ /dev/null @@ -1,229 +0,0 @@ -# 🎯 Killer Application Guide — Full-Stack Engineer @ Calvana LTD - ---- - -## 📋 JOB REVIEW SUMMARY - -### Company: Calvana LTD -- **B2B SaaS startup** solving client acquisition for B2B companies -- Starting by dominating the "internet marketing" agency & coaching market -- Claims: cash ✅, audience ✅, distribution ✅, product-market fit ✅ -- Looking for **first engineering hires** — massive ownership opportunity -- Has a Loom video: https://www.loom.com/share/1e6f7f6255d74e7785a7a8e48c2d5788 -- $2,000 referral bonus signals they're actively hunting - -### Role: Full-Stack Engineer (Early Hire) -- **Location:** Remote (ideally London timezone) -- **Type:** Full-time -- **Stack:** Next.js (frontend) + Django/PostgreSQL (backend) + Pulumi/AWS (infra) -- **Nature:** End-to-end ownership, microservices, AI-powered features, 3rd-party API integrations - -### 🔑 What They REALLY Want (Reading Between the Lines) -1. **A builder, not an employee** — someone who acts like a co-founder -2. **Self-directed** — no PM, no Figma specs, no hand-holding -3. **Speed over perfection** — ship fast, iterate, "high velocity" -4. **AI-native** — not just curious, but has actually BUILT with AI APIs -5. **Full ownership** — from idea → architecture → code → deploy → monitor -6. **Communication** — small team, you explain your own decisions - -### ⚠️ Red/Yellow Flags to Be Aware Of -- "Multi-billion dollar vision" is ambitious language — be prepared for startup chaos -- "No AI screening" = the founder (Charlie) reads every app personally → **personalize everything** -- Early hire = wear many hats, likely no work-life balance initially - ---- - -## 📝 APPLICATION FORM — FIELD-BY-FIELD STRATEGY - -The Google Form has **14 fields**. Here's how to make each one count: - ---- - -### 1. Full Name *(required)* -> Just your name. No tricks here. - -### 2. Email Address *(required)* -> Use a professional email. If you have a custom domain, use it — it signals you're technical. - -### 3. LinkedIn / Personal Site / Portfolio *(required)* -> **Priority order:** Personal site > LinkedIn > Portfolio -> If you have a personal site with projects, that's gold. It shows you ship. -> Make sure your LinkedIn headline matches what they want: "Full-Stack Engineer | Next.js + Django | Building AI-powered products" - -### 4. GitHub or Equivalent *(required)* -> **Make sure your pinned repos showcase:** -> - A full-stack project (React/Next.js + Python backend) -> - Something with AI/ML APIs -> - Clean READMEs with screenshots, architecture diagrams -> - Recent commit activity (shows you're active) - -### 5. Location *(required)* -> Be honest. If you're not in London, emphasize timezone overlap willingness. -> Example: "Manila, Philippines (happy to work London hours / significant overlap)" - -### 6. Employment Status *(required, radio)* -> Options: Employed full-time | Employed part-time | Between roles | Freelancing | Running my own thing -> **"Running my own thing" or "Freelancing"** are the strongest signals for this role — it shows self-direction. -> "Employed full-time" is fine too — shows you're in demand. - ---- - -### 7. 🔥 CRITICAL: "Describe something you built end-to-end" *(required)* - -**This is the MAKE-OR-BREAK question.** They explicitly want: problem → decisions → deployment. - -**Structure your answer like this (aim for 200-350 words):** - -``` -PROBLEM: [1-2 sentences — what pain point existed] - -WHAT I BUILT: [What the product/feature was, who it served] - -KEY DECISIONS: -- Chose [X] over [Y] because [reason] → shows architectural thinking -- Used [specific tech] for [specific reason] → shows you don't just follow tutorials -- Handled [edge case/challenge] by [solution] → shows production mindset - -RESULT: [Quantifiable if possible — users, performance, revenue, time saved] - -SHIPPED TO: [Where it's live — URL, app store, internal tool] -``` - -**EXAMPLE (adapt to your experience):** - -> I noticed freelancers in my network were losing 5-10 hours/week manually creating client proposals. I built ProposalPilot — an AI-powered proposal generator. -> -> Frontend: Next.js with TailwindCSS, deployed on Vercel. Backend: Django REST API on AWS ECS with PostgreSQL. The AI pipeline used OpenAI's API for content generation and a custom prompt chaining system I built to maintain brand voice consistency across sections. -> -> Key decisions: I chose Django over Express because I needed robust ORM support for complex relational data (clients, templates, proposal versions). I containerized each service with Docker and used GitHub Actions for CI/CD. For the AI layer, I implemented streaming responses so users see content generating in real-time rather than waiting 15-20 seconds for a full response. -> -> The hardest part was handling rate limits and failures from OpenAI gracefully — I built a retry queue with exponential backoff and a fallback template system so proposals never fail completely. -> -> Result: 40+ active users, avg. proposal creation time dropped from 3 hours to 20 minutes. The project is live at [URL]. - ---- - -### 8. Link to Something You've Built *(optional but DO IT)* -> This is your proof. Link to: -> - A live product URL (best) -> - A GitHub repo with a stellar README + demo GIF -> - A Loom walkthrough of your project -> - A technical blog post about the build - -### 9. 🔥 AI/ML API Experience *(optional but CRITICAL for this role)* - -**They specifically mention: OpenAI, ElevenLabs, Replicate, Whisper, Stable Diffusion** - -**Structure:** -``` -WHAT I BUILT: [Specific project using AI APIs] -APIS USED: [List them — the more the better] -WHAT I LEARNED: [Focus on production challenges, not just "I called the API"] -``` - -**EXAMPLE:** - -> I built an AI voice-over tool for content creators using ElevenLabs for TTS, OpenAI for script optimization, and Whisper for transcription/captioning. The pipeline: user uploads a script → GPT-4 optimizes it for spoken delivery → ElevenLabs generates audio with voice cloning → Whisper generates timestamped subtitles. -> -> Key learnings: ElevenLabs' streaming API is great for previews but you need the non-streaming endpoint for production-quality audio. I learned to manage API costs by implementing a caching layer — identical scripts don't regenerate audio. Also built a webhook system since audio generation is async and can take 10-30 seconds for long content. -> -> The biggest insight was that prompt engineering for TTS scripts is fundamentally different from chat — you need to engineer for prosody, pacing, and emphasis, not just content accuracy. - ---- - -### 10. Tech Skills Grid *(required)* - -Rate honestly — they'll verify in interviews. Here's the scale: -| Tech | never used | used once | decent | strong | production-level | -|------|-----------|-----------|--------|--------|-----------------| -| React / Next.js | | | | ← aim here | ← or here | -| Python / Django | | | | ← aim here | ← or here | -| PostgreSQL | | | | ← aim here | ← or here | -| AWS | | | ← minimum | ← ideal | | -| REST API design | | | | | ← aim here | -| OAuth | | | ← minimum | ← ideal | | -| CI/CD | | | ← minimum | ← ideal | | -| Docker | | | ← minimum | ← ideal | | - -**Don't lie.** "Decent experience" with honesty beats "production-level" that crumbles in an interview. - ---- - -### 11. 🔥 "Why does this role interest you?" *(required)* - -**DO NOT write generic "I love startups" garbage.** They read every application personally. - -**Formula: Mirror their language + show you understand the stage + add a personal hook** - -**EXAMPLE:** - -> Three things stood out: -> -> First, the ownership. I've worked in teams where I owned a component, not a problem. You're describing the opposite — pick up a problem space, scope it, build it, ship it. That's exactly how I work best. My best projects happened when nobody told me what to build. -> -> Second, the timing. Being an early engineering hire at a company with existing revenue and PMF is the sweet spot. You've de-risked the "will anyone pay for this?" question, and now it's about building fast enough to capture the market. That's where I thrive. -> -> Third, the stack and the AI angle. I've been building with Next.js and Django professionally, and I've been deep in the AI API ecosystem for the past year. The idea of owning AI-powered features end-to-end at a company that's actually shipping (not just experimenting) is exactly where I want to be. -> -> I watched the Loom — Charlie's energy and clarity about the vision is compelling. I want to be part of building this. - -**(Note: mentioning the Loom video by name shows you actually watched it — huge signal)** - ---- - -### 12. Salary Expectation *(required)* -> Research tips: -> - Remote full-stack roles in London-adjacent timezone: £50k-£80k+ for early hires -> - If you're outside UK, adjust for cost-of-living but don't lowball yourself -> - Frame it: "$XX,000 USD / year — open to discussion based on equity/benefits package" -> - Showing flexibility on comp structure (salary + equity) signals founder-mindset - -### 13. How Soon Could You Start? *(required)* -> **"Immediately" or "< 2 weeks"** are strongest signals for an early-stage startup that needs to move fast. -> If you need to give notice, "< 1 month" is still fine. - -### 14. Loom Video *(optional — but THIS is your secret weapon)* - -**This is how you separate yourself from 95% of applicants.** - -**Record a 2-minute Loom with this structure:** -- **0:00-0:15** — "Hi Charlie, I'm [name], [one-line positioning]" -- **0:15-0:45** — Quick walkthrough of something you built (screen share a project) -- **0:45-1:30** — Why THIS role specifically (mirror their language: ownership, velocity, AI) -- **1:30-2:00** — "Here's what I'd build first if I joined" (show you've thought about their product) - -**Tips:** -- Use their founder's name (Charlie — from the Loom video) -- Show energy and enthusiasm — match their "going to the moon" vibe -- Share your screen showing a real project, not just a talking head -- Keep it under 2 minutes — respect their time - ---- - -## 🏆 APPLICATION CHECKLIST - -Before you submit, verify: - -- [ ] GitHub pinned repos are updated with best projects + clean READMEs -- [ ] LinkedIn headline/summary reflects full-stack + AI capabilities -- [ ] "Built end-to-end" answer follows Problem → Decisions → Result structure -- [ ] AI/ML answer shows PRODUCTION challenges, not just tutorial-level usage -- [ ] "Why this role" mentions specifics from THEIR posting (Loom, microservices, PMF) -- [ ] Salary research is done — give a confident range -- [ ] Loom video recorded (2 min, high energy, shows a real project) -- [ ] All required fields filled (13 required, 1 optional) -- [ ] Re-read everything — no typos, no generic language - ---- - -## 💡 POWER MOVES (Stand Out Tactics) - -1. **Build a mini demo** — Before applying, spend 2-4 hours building a tiny microservice that solves a problem relevant to their space (e.g., an AI-powered lead qualifier). Link it in your "built something" answer. Nothing says "I ship" like shipping something FOR them. - -2. **Reference the Loom** — The founder recorded a 7-minute Loom. Most applicants won't watch it. Reference specific things from it to prove you did. - -3. **Show, don't tell** — Every claim should have a link, a repo, or a demo. "I've built with AI APIs" < "Here's the repo where I integrated OpenAI + ElevenLabs: [link]" - -4. **Think like a founder** — In your "why this role" answer, mention what you'd want to build first. Shows you're already thinking about their product, not just your career. - -5. **Follow up** — If you can find Charlie on LinkedIn/Twitter, send a short "Just applied — excited about [specific thing]" message 24h after applying. diff --git a/job-page-top.png b/job-page-top.png deleted file mode 100644 index 9a421e17296075b1549daf358f867058ce97dbd6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 146812 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 Y0NQ2S%{GpifdBvi07*qoM6N<$f|hTjK>z>% diff --git a/justfile b/justfile deleted file mode 100644 index 0a4160f..0000000 --- a/justfile +++ /dev/null @@ -1,114 +0,0 @@ -set dotenv-load := true -set shell := ["pwsh", "-NoProfile", "-Command"] - -default: - @just --list - -# g1 - -# 1. default pi -pi: - pi - -# 2. Pure focus pi: strip footer and status line entirely -ext-pure-focus: - pi -e extensions/pure-focus.ts - -# 3. Minimal pi: model name + 10-block context meter -ext-minimal: - pi -e extensions/minimal.ts -e extensions/theme-cycler.ts - -# 4. Cross-agent pi: load commands from .claude/, .gemini/, .codex/ dirs -ext-cross-agent: - pi -e extensions/cross-agent.ts -e extensions/minimal.ts - -# 5. Purpose gate pi: declare intent before working, persistent widget, focus the system prompt on the ONE PURPOSE for this agent -ext-purpose-gate: - pi -e extensions/purpose-gate.ts -e extensions/minimal.ts - -# 6. Customized footer pi: Tool counter, model, branch, cwd, cost, etc. -ext-tool-counter: - pi -e extensions/tool-counter.ts - -# 7. Tool counter widget: tool call counts in a below-editor widget -ext-tool-counter-widget: - pi -e extensions/tool-counter-widget.ts -e extensions/minimal.ts - -# 8. Subagent widget: /sub with live streaming progress -ext-subagent-widget: - pi -e extensions/subagent-widget.ts -e extensions/pure-focus.ts -e extensions/theme-cycler.ts - -# 9. TillDone: task-driven discipline — define tasks before working -ext-tilldone: - pi -e extensions/tilldone.ts -e extensions/theme-cycler.ts - -#g2 - -# 10. Agent team: dispatcher orchestrator with team select and grid dashboard -ext-agent-team: - pi -e extensions/agent-team.ts -e extensions/theme-cycler.ts - -# 11. System select: /system to pick an agent persona as system prompt -ext-system-select: - pi -e extensions/system-select.ts -e extensions/minimal.ts -e extensions/theme-cycler.ts - -# 12. Launch with Damage-Control safety auditing -ext-damage-control: - pi -e extensions/damage-control.ts -e extensions/minimal.ts -e extensions/theme-cycler.ts - -# 13. Agent chain: sequential pipeline orchestrator -ext-agent-chain: - pi -e extensions/agent-chain.ts -e extensions/theme-cycler.ts - -#g3 - -# 14. Pi Pi: meta-agent that builds Pi agents with parallel expert research -ext-pi-pi: - pi -e extensions/pi-pi.ts -e extensions/theme-cycler.ts - -#ext - -# 15. Observatory: comprehensive observability dashboard with live widget, overlay, and export -ext-observatory: - pi -e extensions/observatory.ts -e extensions/theme-cycler.ts - -# 16. Agent Dashboard: unified observability across team, subagent, and chain interfaces -ext-agent-dashboard: - pi -e extensions/agent-dashboard.ts -e extensions/theme-cycler.ts - -# 17. Session Replay: scrollable timeline overlay of session history (legit) -ext-session-replay: - pi -e extensions/session-replay.ts -e extensions/minimal.ts - -# 18. Theme cycler: Ctrl+X forward, Ctrl+Q backward, /theme picker -ext-theme-cycler: - pi -e extensions/theme-cycler.ts -e extensions/minimal.ts - -# utils - -# Open pi with one or more stacked extensions in a new terminal: just open minimal tool-counter -open +exts: - #!/usr/bin/env pwsh - $args_str = "" - foreach ($ext in "{{exts}}".Split(" ")) { $args_str += " -e extensions/$ext.ts" } - $cmd = "cd '{{justfile_directory()}}'; pi$args_str" - Start-Process wt -ArgumentList "pwsh", "-NoExit", "-Command", $cmd - -# Open every extension in its own terminal window -all: - just open pi - just open pure-focus - just open minimal theme-cycler - just open cross-agent minimal - just open purpose-gate minimal - just open tool-counter - just open tool-counter-widget minimal - just open subagent-widget pure-focus theme-cycler - just open tilldone theme-cycler - just open agent-team theme-cycler - just open system-select minimal theme-cycler - just open damage-control minimal theme-cycler - just open agent-chain theme-cycler - just open pi-pi theme-cycler - just open observatory theme-cycler - just open agent-dashboard theme-cycler \ No newline at end of file diff --git a/package.json b/package.json deleted file mode 100644 index c92ac68..0000000 --- a/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "pi-vs-cc", - "private": true, - "type": "module", - "description": "Pi Coding Agent extension playground", - "dependencies": { - "yaml": "^2.8.0" - }, - "devDependencies": { - "@playwright/cli": "^0.1.1" - } -} diff --git a/pledge-now-pay-later/.env.example b/pledge-now-pay-later/.env.example deleted file mode 100644 index fe12ac4..0000000 --- a/pledge-now-pay-later/.env.example +++ /dev/null @@ -1,7 +0,0 @@ -DATABASE_URL=postgresql://user:password@localhost:5432/dbname -NEXTAUTH_SECRET=your-secret-here -NEXTAUTH_URL=http://localhost:3000 -GOCARDLESS_ACCESS_TOKEN=your-gocardless-token -GOCARDLESS_ENVIRONMENT=sandbox -REDIS_URL=redis://localhost:6379 -BASE_URL=http://localhost:3000 diff --git a/pledge-now-pay-later/.eslintrc.json b/pledge-now-pay-later/.eslintrc.json deleted file mode 100644 index 3722418..0000000 --- a/pledge-now-pay-later/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": ["next/core-web-vitals", "next/typescript"] -} diff --git a/pledge-now-pay-later/.gitignore b/pledge-now-pay-later/.gitignore deleted file mode 100644 index 2db86ff..0000000 --- a/pledge-now-pay-later/.gitignore +++ /dev/null @@ -1,43 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js -.yarn/install-state.gz - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# local env files -.env -.env*.local - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts - -/src/generated/prisma - -# SQLite dev database -*.db -*.db-journal diff --git a/pledge-now-pay-later/Dockerfile b/pledge-now-pay-later/Dockerfile deleted file mode 100644 index eed254d..0000000 --- a/pledge-now-pay-later/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -FROM node:20-alpine AS base - -FROM base AS deps -WORKDIR /app -COPY package.json package-lock.json ./ -RUN npm ci - -FROM base AS builder -WORKDIR /app -COPY --from=deps /app/node_modules ./node_modules -COPY . . -RUN npx prisma generate -RUN npm run build - -FROM base AS runner -WORKDIR /app -ENV NODE_ENV=production -RUN addgroup --system --gid 1001 nodejs -RUN adduser --system --uid 1001 nextjs -COPY --from=builder /app/public ./public -COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ -COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static -COPY --from=builder /app/prisma ./prisma -USER nextjs -EXPOSE 3000 -ENV PORT=3000 -CMD ["node", "server.js"] diff --git a/pledge-now-pay-later/README.md b/pledge-now-pay-later/README.md deleted file mode 100644 index e80a36f..0000000 --- a/pledge-now-pay-later/README.md +++ /dev/null @@ -1,139 +0,0 @@ -# Pledge Now, Pay Later - -> Convert "I'll donate later" into tracked pledges with automatic payment follow-up. Free forever for UK charities. - -## Features - -- **15-Second Pledge Flow**: Mobile-first, 3-screen donor experience -- **QR Code Attribution**: Every pledge tied to event + volunteer/table -- **Pay by Bank Transfer**: Zero fees — unique reference for matching -- **Direct Debit**: GoCardless integration for automatic collection -- **Automated Reminders**: 4-step follow-up sequence (export/webhook) -- **Bank Statement Reconciliation**: Upload CSV, auto-match payments -- **CRM Export**: Full attribution data ready for import -- **Pipeline Dashboard**: Track pledges from new → paid - -## Quick Start - -### Prerequisites -- Node.js 18+ -- Docker & Docker Compose -- npm or pnpm - -### Setup - -```bash -# 1. Clone and install -cd pledge-now-pay-later -npm install - -# 2. Start database -docker compose up -d - -# 3. Run migrations -npx prisma migrate dev --name init - -# 4. Seed demo data -npx prisma db seed - -# 5. Start dev server -npm run dev -``` - -Visit [http://localhost:3000](http://localhost:3000) - -### Demo URLs -- **Landing**: http://localhost:3000 -- **Donor Flow**: http://localhost:3000/p/demo -- **Dashboard**: http://localhost:3000/dashboard - -## Tech Stack - -| Layer | Technology | -|-------|-----------| -| Frontend | Next.js 14 (App Router) | -| Language | TypeScript | -| Styling | Tailwind CSS + shadcn/ui | -| Database | PostgreSQL 16 | -| ORM | Prisma | -| QR Codes | qrcode (node) | -| CSV Parsing | PapaParse | -| Icons | Lucide React | -| Auth | NextAuth.js (ready) | - -## Architecture - -``` -src/ -├── app/ -│ ├── api/ # API routes -│ │ ├── analytics/ # Event tracking -│ │ ├── dashboard/ # Stats & pipeline -│ │ ├── events/ # CRUD + QR management -│ │ ├── exports/ # CRM pack CSV -│ │ ├── imports/ # Bank statement matching -│ │ ├── pledges/ # Create, update, mark paid -│ │ ├── qr/ # Resolve QR tokens -│ │ └── webhooks/ # Reminder event polling -│ ├── dashboard/ # Staff UI -│ │ ├── events/ # Event management + QR codes -│ │ ├── pledges/ # Pledge pipeline -│ │ ├── reconcile/ # Bank CSV import -│ │ ├── exports/ # Download CRM data -│ │ ├── settings/ # Org config -│ │ └── apply/ # Fractional CTO upsell -│ └── p/[token]/ # Donor pledge flow -│ └── steps/ # Amount → Payment → Identity → Instructions -├── components/ui/ # Reusable UI components -└── lib/ # Core utilities - ├── prisma.ts # DB client - ├── reference.ts # Bank-safe ref generator - ├── qr.ts # QR code generation - ├── matching.ts # Bank statement matching - ├── reminders.ts # Reminder sequences - ├── analytics.ts # Event tracking - ├── exports.ts # CRM export formatting - └── validators.ts # Zod schemas -``` - -## API Reference - -| Endpoint | Method | Purpose | -|----------|--------|---------| -| `/api/qr/{token}` | GET | Resolve QR code → event info | -| `/api/pledges` | POST | Create pledge | -| `/api/pledges/{id}` | PATCH | Update pledge status | -| `/api/pledges/{id}/mark-initiated` | POST | Donor "I've paid" | -| `/api/events` | GET/POST | List & create events | -| `/api/events/{id}/qr` | GET/POST | Manage QR sources | -| `/api/events/{id}/qr/{qrId}/download` | GET | Download QR PNG | -| `/api/dashboard` | GET | Dashboard stats | -| `/api/imports/bank-statement` | POST | Upload & match CSV | -| `/api/exports/crm-pack` | GET | Download CRM CSV | -| `/api/webhooks` | GET | Poll pending reminders | -| `/api/analytics` | POST | Track events | - -## Payment Reference Format - -References follow format: `PREFIX-XXXX-NN` -- **PREFIX**: Configurable per org (default: PNPL), max 4 chars -- **XXXX**: 4-char alphanumeric (human-safe: no 0/O, 1/I/l) -- **NN**: Amount in pounds (helps manual matching) -- Total max 18 chars (UK BACS limit) - -Example: `PNPL-7K4P-50` (£50 pledge) - -## Reminder Sequence - -| Step | Delay | Message | -|------|-------|---------| -| 0 | T+0 | Payment instructions with bank details | -| 1 | T+2 days | Gentle nudge | -| 2 | T+7 days | Impact story + urgency | -| 3 | T+14 days | Final reminder + easy cancel | - -Reminders auto-stop when pledge is marked paid. - -## License - -Proprietary — © Omair. All rights reserved. diff --git a/pledge-now-pay-later/bun.lock b/pledge-now-pay-later/bun.lock deleted file mode 100644 index e105390..0000000 --- a/pledge-now-pay-later/bun.lock +++ /dev/null @@ -1,1242 +0,0 @@ -{ - "lockfileVersion": 1, - "configVersion": 0, - "workspaces": { - "": { - "name": "pledge-now-pay-later", - "dependencies": { - "@auth/prisma-adapter": "^2.11.1", - "@prisma/adapter-pg": "^7.4.2", - "@prisma/client": "^7.4.2", - "@types/bcryptjs": "^2.4.6", - "@types/qrcode": "^1.5.6", - "bcryptjs": "^3.0.3", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "date-fns": "^4.1.0", - "lucide-react": "^0.575.0", - "nanoid": "^5.1.6", - "next": "14.2.35", - "next-auth": "^4.24.13", - "papaparse": "^5.5.3", - "pg": "^8.19.0", - "prisma": "^7.4.2", - "qrcode": "^1.5.4", - "react": "^18", - "react-dom": "^18", - "tailwind-merge": "^3.5.0", - "zod": "^4.3.6", - }, - "devDependencies": { - "@types/node": "^20", - "@types/papaparse": "^5.5.2", - "@types/pg": "^8.18.0", - "@types/react": "^18", - "@types/react-dom": "^18", - "eslint": "^8", - "eslint-config-next": "14.2.35", - "postcss": "^8", - "tailwindcss": "^3.4.1", - "tsx": "^4.21.0", - "typescript": "^5", - }, - }, - }, - "packages": { - "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], - - "@auth/core": ["@auth/core@0.41.1", "", { "dependencies": { "@panva/hkdf": "^1.2.1", "jose": "^6.0.6", "oauth4webapi": "^3.3.0", "preact": "10.24.3", "preact-render-to-string": "6.5.11" }, "peerDependencies": { "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.2", "nodemailer": "^7.0.7" }, "optionalPeers": ["@simplewebauthn/browser", "@simplewebauthn/server", "nodemailer"] }, "sha512-t9cJ2zNYAdWMacGRMT6+r4xr1uybIdmYa49calBPeTqwgAFPV/88ac9TEvCR85pvATiSPt8VaNf+Gt24JIT/uw=="], - - "@auth/prisma-adapter": ["@auth/prisma-adapter@2.11.1", "", { "dependencies": { "@auth/core": "0.41.1" }, "peerDependencies": { "@prisma/client": ">=2.26.0 || >=3 || >=4 || >=5 || >=6" } }, "sha512-Ke7DXP0Fy0Mlmjz/ZJLXwQash2UkA4621xCM0rMtEczr1kppLc/njCbUkHkIQ/PnmILjqSPEKeTjDPsYruvkug=="], - - "@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="], - - "@chevrotain/cst-dts-gen": ["@chevrotain/cst-dts-gen@10.5.0", "", { "dependencies": { "@chevrotain/gast": "10.5.0", "@chevrotain/types": "10.5.0", "lodash": "4.17.21" } }, "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw=="], - - "@chevrotain/gast": ["@chevrotain/gast@10.5.0", "", { "dependencies": { "@chevrotain/types": "10.5.0", "lodash": "4.17.21" } }, "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A=="], - - "@chevrotain/types": ["@chevrotain/types@10.5.0", "", {}, "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A=="], - - "@chevrotain/utils": ["@chevrotain/utils@10.5.0", "", {}, "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ=="], - - "@electric-sql/pglite": ["@electric-sql/pglite@0.3.15", "", {}, "sha512-Cj++n1Mekf9ETfdc16TlDi+cDDQF0W7EcbyRHYOAeZdsAe8M/FJg18itDTSwyHfar2WIezawM9o0EKaRGVKygQ=="], - - "@electric-sql/pglite-socket": ["@electric-sql/pglite-socket@0.0.20", "", { "peerDependencies": { "@electric-sql/pglite": "0.3.15" }, "bin": { "pglite-server": "dist/scripts/server.js" } }, "sha512-J5nLGsicnD9wJHnno9r+DGxfcZWh+YJMCe0q/aCgtG6XOm9Z7fKeite8IZSNXgZeGltSigM9U/vAWZQWdgcSFg=="], - - "@electric-sql/pglite-tools": ["@electric-sql/pglite-tools@0.2.20", "", { "peerDependencies": { "@electric-sql/pglite": "0.3.15" } }, "sha512-BK50ZnYa3IG7ztXhtgYf0Q7zijV32Iw1cYS8C+ThdQlwx12V5VZ9KRJ42y82Hyb4PkTxZQklVQA9JHyUlex33A=="], - - "@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], - - "@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], - - "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], - - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="], - - "@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="], - - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="], - - "@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="], - - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="], - - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="], - - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="], - - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="], - - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="], - - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="], - - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="], - - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="], - - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="], - - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="], - - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="], - - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="], - - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="], - - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="], - - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="], - - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="], - - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="], - - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="], - - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="], - - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="], - - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="], - - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="], - - "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], - - "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], - - "@eslint/eslintrc": ["@eslint/eslintrc@2.1.4", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ=="], - - "@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="], - - "@hono/node-server": ["@hono/node-server@1.19.9", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw=="], - - "@humanwhocodes/config-array": ["@humanwhocodes/config-array@0.13.0", "", { "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } }, "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw=="], - - "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], - - "@humanwhocodes/object-schema": ["@humanwhocodes/object-schema@2.0.3", "", {}, "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="], - - "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], - - "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], - - "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], - - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], - - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - - "@mrleebo/prisma-ast": ["@mrleebo/prisma-ast@0.13.1", "", { "dependencies": { "chevrotain": "^10.5.0", "lilconfig": "^2.1.0" } }, "sha512-XyroGQXcHrZdvmrGJvsA9KNeOOgGMg1Vg9OlheUsBOSKznLMDl+YChxbkboRHvtFYJEMRYmlV3uoo/njCw05iw=="], - - "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="], - - "@next/env": ["@next/env@14.2.35", "", {}, "sha512-DuhvCtj4t9Gwrx80dmz2F4t/zKQ4ktN8WrMwOuVzkJfBilwAwGr6v16M5eI8yCuZ63H9TTuEU09Iu2HqkzFPVQ=="], - - "@next/eslint-plugin-next": ["@next/eslint-plugin-next@14.2.35", "", { "dependencies": { "glob": "10.3.10" } }, "sha512-Jw9A3ICz2183qSsqwi7fgq4SBPiNfmOLmTPXKvlnzstUwyvBrtySiY+8RXJweNAs9KThb1+bYhZh9XWcNOr2zQ=="], - - "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@14.2.33", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HqYnb6pxlsshoSTubdXKu15g3iivcbsMXg4bYpjL2iS/V6aQot+iyF4BUc2qA/J/n55YtvE4PHMKWBKGCF/+wA=="], - - "@next/swc-darwin-x64": ["@next/swc-darwin-x64@14.2.33", "", { "os": "darwin", "cpu": "x64" }, "sha512-8HGBeAE5rX3jzKvF593XTTFg3gxeU4f+UWnswa6JPhzaR6+zblO5+fjltJWIZc4aUalqTclvN2QtTC37LxvZAA=="], - - "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@14.2.33", "", { "os": "linux", "cpu": "arm64" }, "sha512-JXMBka6lNNmqbkvcTtaX8Gu5by9547bukHQvPoLe9VRBx1gHwzf5tdt4AaezW85HAB3pikcvyqBToRTDA4DeLw=="], - - "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@14.2.33", "", { "os": "linux", "cpu": "arm64" }, "sha512-Bm+QulsAItD/x6Ih8wGIMfRJy4G73tu1HJsrccPW6AfqdZd0Sfm5Imhgkgq2+kly065rYMnCOxTBvmvFY1BKfg=="], - - "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@14.2.33", "", { "os": "linux", "cpu": "x64" }, "sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg=="], - - "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@14.2.33", "", { "os": "linux", "cpu": "x64" }, "sha512-345tsIWMzoXaQndUTDv1qypDRiebFxGYx9pYkhwY4hBRaOLt8UGfiWKr9FSSHs25dFIf8ZqIFaPdy5MljdoawA=="], - - "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@14.2.33", "", { "os": "win32", "cpu": "arm64" }, "sha512-nscpt0G6UCTkrT2ppnJnFsYbPDQwmum4GNXYTeoTIdsmMydSKFz9Iny2jpaRupTb+Wl298+Rh82WKzt9LCcqSQ=="], - - "@next/swc-win32-ia32-msvc": ["@next/swc-win32-ia32-msvc@14.2.33", "", { "os": "win32", "cpu": "ia32" }, "sha512-pc9LpGNKhJ0dXQhZ5QMmYxtARwwmWLpeocFmVG5Z0DzWq5Uf0izcI8tLc+qOpqxO1PWqZ5A7J1blrUIKrIFc7Q=="], - - "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@14.2.33", "", { "os": "win32", "cpu": "x64" }, "sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg=="], - - "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], - - "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], - - "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], - - "@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="], - - "@panva/hkdf": ["@panva/hkdf@1.2.1", "", {}, "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw=="], - - "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], - - "@prisma/adapter-pg": ["@prisma/adapter-pg@7.4.2", "", { "dependencies": { "@prisma/driver-adapter-utils": "7.4.2", "pg": "^8.16.3", "postgres-array": "3.0.4" } }, "sha512-oUo2Zhe9Tf6YwVL8kLPuOLTK1Z2pwi/Ua77t2PuGyBan2w7shRKqHvYK+3XXmRH9RWhPJ4SMtHZKpNo6Ax/4bQ=="], - - "@prisma/client": ["@prisma/client@7.4.2", "", { "dependencies": { "@prisma/client-runtime-utils": "7.4.2" }, "peerDependencies": { "prisma": "*", "typescript": ">=5.4.0" } }, "sha512-ts2mu+cQHriAhSxngO3StcYubBGTWDtu/4juZhXCUKOwgh26l+s4KD3vT2kMUzFyrYnll9u/3qWrtzRv9CGWzA=="], - - "@prisma/client-runtime-utils": ["@prisma/client-runtime-utils@7.4.2", "", {}, "sha512-cID+rzOEb38VyMsx5LwJMEY4NGIrWCNpKu/0ImbeooQ2Px7TI+kOt7cm0NelxUzF2V41UVVXAmYjANZQtCu1/Q=="], - - "@prisma/config": ["@prisma/config@7.4.2", "", { "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", "effect": "3.18.4", "empathic": "2.0.0" } }, "sha512-CftBjWxav99lzY1Z4oDgomdb1gh9BJFAOmWF6P2v1xRfXqQb56DfBub+QKcERRdNoAzCb3HXy3Zii8Vb4AsXhg=="], - - "@prisma/debug": ["@prisma/debug@7.4.2", "", {}, "sha512-aP7qzu+g/JnbF6U69LMwHoUkELiserKmWsE2shYuEpNUJ4GrtxBCvZwCyCBHFSH2kLTF2l1goBlBh4wuvRq62w=="], - - "@prisma/dev": ["@prisma/dev@0.20.0", "", { "dependencies": { "@electric-sql/pglite": "0.3.15", "@electric-sql/pglite-socket": "0.0.20", "@electric-sql/pglite-tools": "0.2.20", "@hono/node-server": "1.19.9", "@mrleebo/prisma-ast": "0.13.1", "@prisma/get-platform": "7.2.0", "@prisma/query-plan-executor": "7.2.0", "foreground-child": "3.3.1", "get-port-please": "3.2.0", "hono": "4.11.4", "http-status-codes": "2.3.0", "pathe": "2.0.3", "proper-lockfile": "4.1.2", "remeda": "2.33.4", "std-env": "3.10.0", "valibot": "1.2.0", "zeptomatch": "2.1.0" } }, "sha512-ovlBYwWor0OzG+yH4J3Ot+AneD818BttLA+Ii7wjbcLHUrnC4tbUPVGyNd3c/+71KETPKZfjhkTSpdS15dmXNQ=="], - - "@prisma/driver-adapter-utils": ["@prisma/driver-adapter-utils@7.4.2", "", { "dependencies": { "@prisma/debug": "7.4.2" } }, "sha512-REdjFpT/ye9KdDs+CXAXPIbMQkVLhne9G5Pe97sNY4Ovx4r2DAbWM9hOFvvB1Oq8H8bOCdu0Ri3AoGALquQqVw=="], - - "@prisma/engines": ["@prisma/engines@7.4.2", "", { "dependencies": { "@prisma/debug": "7.4.2", "@prisma/engines-version": "7.5.0-10.94a226be1cf2967af2541cca5529f0f7ba866919", "@prisma/fetch-engine": "7.4.2", "@prisma/get-platform": "7.4.2" } }, "sha512-B+ZZhI4rXlzjVqRw/93AothEKOU5/x4oVyJFGo9RpHPnBwaPwk4Pi0Q4iGXipKxeXPs/dqljgNBjK0m8nocOJA=="], - - "@prisma/engines-version": ["@prisma/engines-version@7.5.0-10.94a226be1cf2967af2541cca5529f0f7ba866919", "", {}, "sha512-5FIKY3KoYQlBuZC2yc16EXfVRQ8HY+fLqgxkYfWCtKhRb3ajCRzP/rPeoSx11+NueJDANdh4hjY36mdmrTcGSg=="], - - "@prisma/fetch-engine": ["@prisma/fetch-engine@7.4.2", "", { "dependencies": { "@prisma/debug": "7.4.2", "@prisma/engines-version": "7.5.0-10.94a226be1cf2967af2541cca5529f0f7ba866919", "@prisma/get-platform": "7.4.2" } }, "sha512-f/c/MwYpdJO7taLETU8rahEstLeXfYgQGlz5fycG7Fbmva3iPdzGmjiSWHeSWIgNnlXnelUdCJqyZnFocurZuA=="], - - "@prisma/get-platform": ["@prisma/get-platform@7.2.0", "", { "dependencies": { "@prisma/debug": "7.2.0" } }, "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA=="], - - "@prisma/query-plan-executor": ["@prisma/query-plan-executor@7.2.0", "", {}, "sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ=="], - - "@prisma/studio-core": ["@prisma/studio-core@0.13.1", "", { "peerDependencies": { "@types/react": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-agdqaPEePRHcQ7CexEfkX1RvSH9uWDb6pXrZnhCRykhDFAV0/0P3d07WtfiY8hZWb7oRU4v+NkT4cGFHkQJIPg=="], - - "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="], - - "@rushstack/eslint-patch": ["@rushstack/eslint-patch@1.16.1", "", {}, "sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag=="], - - "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="], - - "@swc/helpers": ["@swc/helpers@0.5.5", "", { "dependencies": { "@swc/counter": "^0.1.3", "tslib": "^2.4.0" } }, "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A=="], - - "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], - - "@types/bcryptjs": ["@types/bcryptjs@2.4.6", "", {}, "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ=="], - - "@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="], - - "@types/node": ["@types/node@20.19.35", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ=="], - - "@types/papaparse": ["@types/papaparse@5.5.2", "", { "dependencies": { "@types/node": "*" } }, "sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA=="], - - "@types/pg": ["@types/pg@8.18.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-gT+oueVQkqnj6ajGJXblFR4iavIXWsGAFCk3dP4Kki5+a9R4NMt0JARdk6s8cUKcfUoqP5dAtDSLU8xYUTFV+Q=="], - - "@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="], - - "@types/qrcode": ["@types/qrcode@1.5.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw=="], - - "@types/react": ["@types/react@18.3.28", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw=="], - - "@types/react-dom": ["@types/react-dom@18.3.7", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="], - - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.56.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/type-utils": "8.56.1", "@typescript-eslint/utils": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.56.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A=="], - - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.56.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg=="], - - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.56.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.56.1", "@typescript-eslint/types": "^8.56.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ=="], - - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1" } }, "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w=="], - - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.56.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ=="], - - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1", "@typescript-eslint/utils": "8.56.1", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg=="], - - "@typescript-eslint/types": ["@typescript-eslint/types@8.56.1", "", {}, "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw=="], - - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.56.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.56.1", "@typescript-eslint/tsconfig-utils": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg=="], - - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.56.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA=="], - - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw=="], - - "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], - - "@unrs/resolver-binding-android-arm-eabi": ["@unrs/resolver-binding-android-arm-eabi@1.11.1", "", { "os": "android", "cpu": "arm" }, "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw=="], - - "@unrs/resolver-binding-android-arm64": ["@unrs/resolver-binding-android-arm64@1.11.1", "", { "os": "android", "cpu": "arm64" }, "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g=="], - - "@unrs/resolver-binding-darwin-arm64": ["@unrs/resolver-binding-darwin-arm64@1.11.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g=="], - - "@unrs/resolver-binding-darwin-x64": ["@unrs/resolver-binding-darwin-x64@1.11.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ=="], - - "@unrs/resolver-binding-freebsd-x64": ["@unrs/resolver-binding-freebsd-x64@1.11.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw=="], - - "@unrs/resolver-binding-linux-arm-gnueabihf": ["@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1", "", { "os": "linux", "cpu": "arm" }, "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw=="], - - "@unrs/resolver-binding-linux-arm-musleabihf": ["@unrs/resolver-binding-linux-arm-musleabihf@1.11.1", "", { "os": "linux", "cpu": "arm" }, "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw=="], - - "@unrs/resolver-binding-linux-arm64-gnu": ["@unrs/resolver-binding-linux-arm64-gnu@1.11.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ=="], - - "@unrs/resolver-binding-linux-arm64-musl": ["@unrs/resolver-binding-linux-arm64-musl@1.11.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w=="], - - "@unrs/resolver-binding-linux-ppc64-gnu": ["@unrs/resolver-binding-linux-ppc64-gnu@1.11.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA=="], - - "@unrs/resolver-binding-linux-riscv64-gnu": ["@unrs/resolver-binding-linux-riscv64-gnu@1.11.1", "", { "os": "linux", "cpu": "none" }, "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ=="], - - "@unrs/resolver-binding-linux-riscv64-musl": ["@unrs/resolver-binding-linux-riscv64-musl@1.11.1", "", { "os": "linux", "cpu": "none" }, "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew=="], - - "@unrs/resolver-binding-linux-s390x-gnu": ["@unrs/resolver-binding-linux-s390x-gnu@1.11.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg=="], - - "@unrs/resolver-binding-linux-x64-gnu": ["@unrs/resolver-binding-linux-x64-gnu@1.11.1", "", { "os": "linux", "cpu": "x64" }, "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w=="], - - "@unrs/resolver-binding-linux-x64-musl": ["@unrs/resolver-binding-linux-x64-musl@1.11.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA=="], - - "@unrs/resolver-binding-wasm32-wasi": ["@unrs/resolver-binding-wasm32-wasi@1.11.1", "", { "dependencies": { "@napi-rs/wasm-runtime": "^0.2.11" }, "cpu": "none" }, "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ=="], - - "@unrs/resolver-binding-win32-arm64-msvc": ["@unrs/resolver-binding-win32-arm64-msvc@1.11.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw=="], - - "@unrs/resolver-binding-win32-ia32-msvc": ["@unrs/resolver-binding-win32-ia32-msvc@1.11.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ=="], - - "@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.11.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g=="], - - "acorn": ["acorn@8.16.0", "", { "bin": "bin/acorn" }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], - - "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], - - "ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], - - "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - - "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], - - "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], - - "arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="], - - "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], - - "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], - - "array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="], - - "array-includes": ["array-includes@3.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.0", "es-object-atoms": "^1.1.1", "get-intrinsic": "^1.3.0", "is-string": "^1.1.1", "math-intrinsics": "^1.1.0" } }, "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ=="], - - "array.prototype.findlast": ["array.prototype.findlast@1.2.5", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ=="], - - "array.prototype.findlastindex": ["array.prototype.findlastindex@1.2.6", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-shim-unscopables": "^1.1.0" } }, "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ=="], - - "array.prototype.flat": ["array.prototype.flat@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg=="], - - "array.prototype.flatmap": ["array.prototype.flatmap@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg=="], - - "array.prototype.tosorted": ["array.prototype.tosorted@1.1.4", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3", "es-errors": "^1.3.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA=="], - - "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="], - - "ast-types-flow": ["ast-types-flow@0.0.8", "", {}, "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ=="], - - "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], - - "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], - - "aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="], - - "axe-core": ["axe-core@4.11.1", "", {}, "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A=="], - - "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], - - "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - - "bcryptjs": ["bcryptjs@3.0.3", "", { "bin": { "bcrypt": "bin/bcrypt" } }, "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g=="], - - "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], - - "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], - - "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - - "busboy": ["busboy@1.6.0", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="], - - "c12": ["c12@3.1.0", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^16.6.1", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.4.2", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^1.0.0", "pkg-types": "^2.2.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw=="], - - "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="], - - "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], - - "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], - - "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], - - "camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], - - "camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="], - - "caniuse-lite": ["caniuse-lite@1.0.30001774", "", {}, "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA=="], - - "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - - "chevrotain": ["chevrotain@10.5.0", "", { "dependencies": { "@chevrotain/cst-dts-gen": "10.5.0", "@chevrotain/gast": "10.5.0", "@chevrotain/types": "10.5.0", "@chevrotain/utils": "10.5.0", "lodash": "4.17.21", "regexp-to-ast": "0.5.0" } }, "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A=="], - - "chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], - - "citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="], - - "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], - - "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="], - - "cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="], - - "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], - - "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], - - "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], - - "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], - - "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], - - "confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="], - - "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], - - "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], - - "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - - "cssesc": ["cssesc@3.0.0", "", { "bin": "bin/cssesc" }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], - - "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - - "damerau-levenshtein": ["damerau-levenshtein@1.0.8", "", {}, "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="], - - "data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="], - - "data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="], - - "data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="], - - "date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="], - - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - - "decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="], - - "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], - - "deepmerge-ts": ["deepmerge-ts@7.1.5", "", {}, "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw=="], - - "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], - - "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], - - "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], - - "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], - - "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="], - - "didyoumean": ["didyoumean@1.2.2", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="], - - "dijkstrajs": ["dijkstrajs@1.0.3", "", {}, "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="], - - "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="], - - "doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="], - - "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], - - "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], - - "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], - - "effect": ["effect@3.18.4", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA=="], - - "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - - "empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="], - - "es-abstract": ["es-abstract@1.24.1", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw=="], - - "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], - - "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], - - "es-iterator-helpers": ["es-iterator-helpers@1.2.2", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.1", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.3.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", "safe-array-concat": "^1.1.3" } }, "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w=="], - - "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], - - "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], - - "es-shim-unscopables": ["es-shim-unscopables@1.1.0", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw=="], - - "es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="], - - "esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": "bin/esbuild" }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], - - "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - - "eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": "bin/eslint.js" }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="], - - "eslint-config-next": ["eslint-config-next@14.2.35", "", { "dependencies": { "@next/eslint-plugin-next": "14.2.35", "@rushstack/eslint-patch": "^1.3.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.28.1", "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" }, "peerDependencies": { "eslint": "^7.23.0 || ^8.0.0", "typescript": ">=3.3.1" } }, "sha512-BpLsv01UisH193WyT/1lpHqq5iJ/Orfz9h/NOOlAmTUq4GY349PextQ62K4XpnaM9supeiEn3TaOTeQO07gURg=="], - - "eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.9", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", "resolve": "^1.22.4" } }, "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g=="], - - "eslint-import-resolver-typescript": ["eslint-import-resolver-typescript@3.10.1", "", { "dependencies": { "@nolyfill/is-core-module": "1.0.39", "debug": "^4.4.0", "get-tsconfig": "^4.10.0", "is-bun-module": "^2.0.0", "stable-hash": "^0.0.5", "tinyglobby": "^0.2.13", "unrs-resolver": "^1.6.2" }, "peerDependencies": { "eslint": "*", "eslint-plugin-import": "*", "eslint-plugin-import-x": "*" }, "optionalPeers": ["eslint-plugin-import-x"] }, "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ=="], - - "eslint-module-utils": ["eslint-module-utils@2.12.1", "", { "dependencies": { "debug": "^3.2.7" } }, "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw=="], - - "eslint-plugin-import": ["eslint-plugin-import@2.32.0", "", { "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", "array.prototype.findlastindex": "^1.2.6", "array.prototype.flat": "^1.3.3", "array.prototype.flatmap": "^1.3.3", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", "eslint-module-utils": "^2.12.1", "hasown": "^2.0.2", "is-core-module": "^2.16.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "object.groupby": "^1.0.3", "object.values": "^1.2.1", "semver": "^6.3.1", "string.prototype.trimend": "^1.0.9", "tsconfig-paths": "^3.15.0" }, "peerDependencies": { "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA=="], - - "eslint-plugin-jsx-a11y": ["eslint-plugin-jsx-a11y@6.10.2", "", { "dependencies": { "aria-query": "^5.3.2", "array-includes": "^3.1.8", "array.prototype.flatmap": "^1.3.2", "ast-types-flow": "^0.0.8", "axe-core": "^4.10.0", "axobject-query": "^4.1.0", "damerau-levenshtein": "^1.0.8", "emoji-regex": "^9.2.2", "hasown": "^2.0.2", "jsx-ast-utils": "^3.3.5", "language-tags": "^1.0.9", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "safe-regex-test": "^1.0.3", "string.prototype.includes": "^2.0.1" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q=="], - - "eslint-plugin-react": ["eslint-plugin-react@7.37.5", "", { "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.3", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", "es-iterator-helpers": "^1.2.1", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.9", "object.fromentries": "^2.0.8", "object.values": "^1.2.1", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", "string.prototype.matchall": "^4.0.12", "string.prototype.repeat": "^1.0.0" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA=="], - - "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@5.0.0-canary-7118f5dd7-20230705", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" } }, "sha512-AZYbMo/NW9chdL7vk6HQzQhT+PvTAEVqWk9ziruUoW2kAOcN5qNyelv70e0F1VNQAbvutOC9oc+xfWycI9FxDw=="], - - "eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="], - - "eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - - "espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="], - - "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], - - "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], - - "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], - - "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], - - "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], - - "fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], - - "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], - - "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], - - "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], - - "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], - - "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], - - "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], - - "file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="], - - "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], - - "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], - - "flat-cache": ["flat-cache@3.2.0", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw=="], - - "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], - - "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], - - "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], - - "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], - - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - - "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], - - "function.prototype.name": ["function.prototype.name@1.1.8", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "functions-have-names": "^1.2.3", "hasown": "^2.0.2", "is-callable": "^1.2.7" } }, "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q=="], - - "functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="], - - "generate-function": ["generate-function@2.3.1", "", { "dependencies": { "is-property": "^1.0.2" } }, "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ=="], - - "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="], - - "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], - - "get-intrinsic": ["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" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], - - "get-port-please": ["get-port-please@3.2.0", "", {}, "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A=="], - - "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], - - "get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="], - - "get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="], - - "giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": "dist/cli.mjs" }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="], - - "glob": ["glob@10.3.10", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^2.3.5", "minimatch": "^9.0.1", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", "path-scurry": "^1.10.1" }, "bin": "dist/esm/bin.mjs" }, "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g=="], - - "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], - - "globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="], - - "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], - - "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], - - "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], - - "grammex": ["grammex@3.1.12", "", {}, "sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ=="], - - "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], - - "graphmatch": ["graphmatch@1.1.1", "", {}, "sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg=="], - - "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="], - - "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - - "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], - - "has-proto": ["has-proto@1.2.0", "", { "dependencies": { "dunder-proto": "^1.0.0" } }, "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ=="], - - "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], - - "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], - - "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], - - "hono": ["hono@4.11.4", "", {}, "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA=="], - - "http-status-codes": ["http-status-codes@2.3.0", "", {}, "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA=="], - - "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], - - "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - - "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], - - "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], - - "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], - - "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], - - "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], - - "is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="], - - "is-async-function": ["is-async-function@2.1.1", "", { "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ=="], - - "is-bigint": ["is-bigint@1.1.0", "", { "dependencies": { "has-bigints": "^1.0.2" } }, "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ=="], - - "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], - - "is-boolean-object": ["is-boolean-object@1.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A=="], - - "is-bun-module": ["is-bun-module@2.0.0", "", { "dependencies": { "semver": "^7.7.1" } }, "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ=="], - - "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="], - - "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], - - "is-data-view": ["is-data-view@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" } }, "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw=="], - - "is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="], - - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], - - "is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="], - - "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], - - "is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="], - - "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], - - "is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="], - - "is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="], - - "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], - - "is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="], - - "is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="], - - "is-property": ["is-property@1.0.2", "", {}, "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="], - - "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="], - - "is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="], - - "is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="], - - "is-string": ["is-string@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA=="], - - "is-symbol": ["is-symbol@1.1.1", "", { "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", "safe-regex-test": "^1.1.0" } }, "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w=="], - - "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="], - - "is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="], - - "is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="], - - "is-weakset": ["is-weakset@2.0.4", "", { "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ=="], - - "isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], - - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - - "iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="], - - "jackspeak": ["jackspeak@2.3.6", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ=="], - - "jiti": ["jiti@1.21.7", "", { "bin": "bin/jiti.js" }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="], - - "jose": ["jose@4.15.9", "", {}, "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA=="], - - "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - - "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": "bin/js-yaml.js" }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], - - "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], - - "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], - - "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], - - "json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": "lib/cli.js" }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="], - - "jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="], - - "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], - - "language-subtag-registry": ["language-subtag-registry@0.3.23", "", {}, "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ=="], - - "language-tags": ["language-tags@1.0.9", "", { "dependencies": { "language-subtag-registry": "^0.3.20" } }, "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA=="], - - "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], - - "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], - - "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], - - "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], - - "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], - - "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], - - "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], - - "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": "cli.js" }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], - - "lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], - - "lru.min": ["lru.min@1.1.4", "", {}, "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA=="], - - "lucide-react": ["lucide-react@0.575.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-VuXgKZrk0uiDlWjGGXmKV6MSk9Yy4l10qgVvzGn2AWBx1Ylt0iBexKOAoA6I7JO3m+M9oeovJd3yYENfkUbOeg=="], - - "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], - - "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], - - "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], - - "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - - "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], - - "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], - - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "mysql2": ["mysql2@3.15.3", "", { "dependencies": { "aws-ssl-profiles": "^1.1.1", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.7.0", "long": "^5.2.1", "lru.min": "^1.0.0", "named-placeholders": "^1.1.3", "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" } }, "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg=="], - - "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], - - "named-placeholders": ["named-placeholders@1.1.6", "", { "dependencies": { "lru.min": "^1.1.0" } }, "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w=="], - - "nanoid": ["nanoid@5.1.6", "", { "bin": "bin/nanoid.js" }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="], - - "napi-postinstall": ["napi-postinstall@0.3.4", "", { "bin": "lib/cli.js" }, "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ=="], - - "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], - - "next": ["next@14.2.35", "", { "dependencies": { "@next/env": "14.2.35", "@swc/helpers": "0.5.5", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "graceful-fs": "^4.2.11", "postcss": "8.4.31", "styled-jsx": "5.1.1" }, "optionalDependencies": { "@next/swc-darwin-arm64": "14.2.33", "@next/swc-darwin-x64": "14.2.33", "@next/swc-linux-arm64-gnu": "14.2.33", "@next/swc-linux-arm64-musl": "14.2.33", "@next/swc-linux-x64-gnu": "14.2.33", "@next/swc-linux-x64-musl": "14.2.33", "@next/swc-win32-arm64-msvc": "14.2.33", "@next/swc-win32-ia32-msvc": "14.2.33", "@next/swc-win32-x64-msvc": "14.2.33" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "sass"], "bin": "dist/bin/next" }, "sha512-KhYd2Hjt/O1/1aZVX3dCwGXM1QmOV4eNM2UTacK5gipDdPN/oHHK/4oVGy7X8GMfPMsUTUEmGlsy0EY1YGAkig=="], - - "next-auth": ["next-auth@4.24.13", "", { "dependencies": { "@babel/runtime": "^7.20.13", "@panva/hkdf": "^1.0.2", "cookie": "^0.7.0", "jose": "^4.15.5", "oauth": "^0.9.15", "openid-client": "^5.4.0", "preact": "^10.6.3", "preact-render-to-string": "^5.1.19", "uuid": "^8.3.2" }, "peerDependencies": { "@auth/core": "0.34.3", "next": "^12.2.5 || ^13 || ^14 || ^15 || ^16", "nodemailer": "^7.0.7", "react": "^17.0.2 || ^18 || ^19", "react-dom": "^17.0.2 || ^18 || ^19" }, "optionalPeers": ["@auth/core", "nodemailer"] }, "sha512-sgObCfcfL7BzIK76SS5TnQtc3yo2Oifp/yIpfv6fMfeBOiBJkDWF3A2y9+yqnmJ4JKc2C+nMjSjmgDeTwgN1rQ=="], - - "node-exports-info": ["node-exports-info@1.6.0", "", { "dependencies": { "array.prototype.flatmap": "^1.3.3", "es-errors": "^1.3.0", "object.entries": "^1.1.9", "semver": "^6.3.1" } }, "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw=="], - - "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], - - "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], - - "nypm": ["nypm@0.6.5", "", { "dependencies": { "citty": "^0.2.0", "pathe": "^2.0.3", "tinyexec": "^1.0.2" }, "bin": "dist/cli.mjs" }, "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ=="], - - "oauth": ["oauth@0.9.15", "", {}, "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA=="], - - "oauth4webapi": ["oauth4webapi@3.8.5", "", {}, "sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg=="], - - "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], - - "object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="], - - "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], - - "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="], - - "object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="], - - "object.entries": ["object.entries@1.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-object-atoms": "^1.1.1" } }, "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw=="], - - "object.fromentries": ["object.fromentries@2.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-object-atoms": "^1.0.0" } }, "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ=="], - - "object.groupby": ["object.groupby@1.0.3", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2" } }, "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ=="], - - "object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="], - - "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], - - "oidc-token-hash": ["oidc-token-hash@5.2.0", "", {}, "sha512-6gj2m8cJZ+iSW8bm0FXdGF0YhIQbKrfP4yWTNzxc31U6MOjfEmB1rHvlYvxI1B7t7BCi1F2vYTT6YhtQRG4hxw=="], - - "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], - - "openid-client": ["openid-client@5.7.1", "", { "dependencies": { "jose": "^4.15.9", "lru-cache": "^6.0.0", "object-hash": "^2.2.0", "oidc-token-hash": "^5.0.3" } }, "sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew=="], - - "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], - - "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], - - "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], - - "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], - - "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], - - "papaparse": ["papaparse@5.5.3", "", {}, "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A=="], - - "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], - - "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], - - "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], - - "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - - "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], - - "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - - "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], - - "perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="], - - "pg": ["pg@8.19.0", "", { "dependencies": { "pg-connection-string": "^2.11.0", "pg-pool": "^3.12.0", "pg-protocol": "^1.12.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.3.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-QIcLGi508BAHkQ3pJNptsFz5WQMlpGbuBGBaIaXsWK8mel2kQ/rThYI+DbgjUvZrIr7MiuEuc9LcChJoEZK1xQ=="], - - "pg-cloudflare": ["pg-cloudflare@1.3.0", "", {}, "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ=="], - - "pg-connection-string": ["pg-connection-string@2.11.0", "", {}, "sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ=="], - - "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="], - - "pg-pool": ["pg-pool@3.12.0", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-eIJ0DES8BLaziFHW7VgJEBPi5hg3Nyng5iKpYtj3wbcAUV9A1wLgWiY7ajf/f/oO1wfxt83phXPY8Emztg7ITg=="], - - "pg-protocol": ["pg-protocol@1.12.0", "", {}, "sha512-uOANXNRACNdElMXJ0tPz6RBM0XQ61nONGAwlt8da5zs/iUOOCLBQOHSXnrC6fMsvtjxbOJrZZl5IScGv+7mpbg=="], - - "pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="], - - "pgpass": ["pgpass@1.0.5", "", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="], - - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - - "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - - "pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="], - - "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], - - "pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], - - "pngjs": ["pngjs@5.0.0", "", {}, "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="], - - "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], - - "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], - - "postcss-import": ["postcss-import@15.1.0", "", { "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew=="], - - "postcss-js": ["postcss-js@4.1.0", "", { "dependencies": { "camelcase-css": "^2.0.1" }, "peerDependencies": { "postcss": "^8.4.21" } }, "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw=="], - - "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], - - "postcss-nested": ["postcss-nested@6.2.0", "", { "dependencies": { "postcss-selector-parser": "^6.1.1" }, "peerDependencies": { "postcss": "^8.2.14" } }, "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ=="], - - "postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="], - - "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], - - "postgres": ["postgres@3.4.7", "", {}, "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw=="], - - "postgres-array": ["postgres-array@3.0.4", "", {}, "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ=="], - - "postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="], - - "postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="], - - "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], - - "preact": ["preact@10.24.3", "", {}, "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA=="], - - "preact-render-to-string": ["preact-render-to-string@5.2.6", "", { "dependencies": { "pretty-format": "^3.8.0" }, "peerDependencies": { "preact": ">=10" } }, "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw=="], - - "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], - - "pretty-format": ["pretty-format@3.8.0", "", {}, "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew=="], - - "prisma": ["prisma@7.4.2", "", { "dependencies": { "@prisma/config": "7.4.2", "@prisma/dev": "0.20.0", "@prisma/engines": "7.4.2", "@prisma/studio-core": "0.13.1", "mysql2": "3.15.3", "postgres": "3.4.7" }, "peerDependencies": { "better-sqlite3": ">=9.0.0", "typescript": ">=5.4.0" }, "optionalPeers": ["better-sqlite3"], "bin": "build/index.js" }, "sha512-2bP8Ruww3Q95Z2eH4Yqh4KAENRsj/SxbdknIVBfd6DmjPwmpsC4OVFMLOeHt6tM3Amh8ebjvstrUz3V/hOe1dA=="], - - "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], - - "proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="], - - "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - - "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], - - "qrcode": ["qrcode@1.5.4", "", { "dependencies": { "dijkstrajs": "^1.0.1", "pngjs": "^5.0.0", "yargs": "^15.3.1" }, "bin": "bin/qrcode" }, "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg=="], - - "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], - - "rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="], - - "react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], - - "react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="], - - "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], - - "read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="], - - "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], - - "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="], - - "regexp-to-ast": ["regexp-to-ast@0.5.0", "", {}, "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw=="], - - "regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="], - - "remeda": ["remeda@2.33.4", "", {}, "sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ=="], - - "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], - - "require-main-filename": ["require-main-filename@2.0.0", "", {}, "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="], - - "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], - - "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], - - "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], - - "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], - - "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], - - "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": "bin.js" }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], - - "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], - - "safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="], - - "safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="], - - "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], - - "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], - - "scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], - - "semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "seq-queue": ["seq-queue@0.0.5", "", {}, "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="], - - "set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="], - - "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], - - "set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="], - - "set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="], - - "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], - - "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - - "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], - - "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], - - "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], - - "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], - - "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - - "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], - - "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], - - "sqlstring": ["sqlstring@2.3.3", "", {}, "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg=="], - - "stable-hash": ["stable-hash@0.0.5", "", {}, "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA=="], - - "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], - - "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], - - "streamsearch": ["streamsearch@1.1.0", "", {}, "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="], - - "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "string.prototype.includes": ["string.prototype.includes@2.0.1", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3" } }, "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg=="], - - "string.prototype.matchall": ["string.prototype.matchall@4.0.12", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA=="], - - "string.prototype.repeat": ["string.prototype.repeat@1.0.0", "", { "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" } }, "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w=="], - - "string.prototype.trim": ["string.prototype.trim@1.2.10", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-object-atoms": "^1.0.0", "has-property-descriptors": "^1.0.2" } }, "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA=="], - - "string.prototype.trimend": ["string.prototype.trimend@1.0.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ=="], - - "string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="], - - "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], - - "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], - - "styled-jsx": ["styled-jsx@5.1.1", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" } }, "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw=="], - - "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], - - "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - - "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], - - "tailwind-merge": ["tailwind-merge@3.5.0", "", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="], - - "tailwindcss": ["tailwindcss@3.4.19", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ=="], - - "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], - - "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], - - "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], - - "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], - - "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], - - "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], - - "ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="], - - "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], - - "tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="], - - "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": "dist/cli.mjs" }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="], - - "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], - - "type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="], - - "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="], - - "typed-array-byte-length": ["typed-array-byte-length@1.0.3", "", { "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.14" } }, "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg=="], - - "typed-array-byte-offset": ["typed-array-byte-offset@1.0.4", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.15", "reflect.getprototypeof": "^1.0.9" } }, "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ=="], - - "typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="], - - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - - "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], - - "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - - "unrs-resolver": ["unrs-resolver@1.11.1", "", { "dependencies": { "napi-postinstall": "^0.3.0" }, "optionalDependencies": { "@unrs/resolver-binding-android-arm-eabi": "1.11.1", "@unrs/resolver-binding-android-arm64": "1.11.1", "@unrs/resolver-binding-darwin-arm64": "1.11.1", "@unrs/resolver-binding-darwin-x64": "1.11.1", "@unrs/resolver-binding-freebsd-x64": "1.11.1", "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-musl": "1.11.1", "@unrs/resolver-binding-wasm32-wasi": "1.11.1", "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg=="], - - "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], - - "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], - - "uuid": ["uuid@8.3.2", "", { "bin": "dist/bin/uuid" }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], - - "valibot": ["valibot@1.2.0", "", { "peerDependencies": { "typescript": ">=5" } }, "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg=="], - - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - - "which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="], - - "which-builtin-type": ["which-builtin-type@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", "is-date-object": "^1.1.0", "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", "is-regex": "^1.2.1", "is-weakref": "^1.0.2", "isarray": "^2.0.5", "which-boxed-primitive": "^1.1.0", "which-collection": "^1.0.2", "which-typed-array": "^1.1.16" } }, "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q=="], - - "which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="], - - "which-module": ["which-module@2.0.1", "", {}, "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="], - - "which-typed-array": ["which-typed-array@1.1.20", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg=="], - - "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], - - "wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], - - "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - - "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], - - "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], - - "y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="], - - "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], - - "yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="], - - "yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="], - - "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - - "zeptomatch": ["zeptomatch@2.1.0", "", { "dependencies": { "grammex": "^3.1.11", "graphmatch": "^1.1.0" } }, "sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA=="], - - "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - - "@auth/core/jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="], - - "@auth/core/preact-render-to-string": ["preact-render-to-string@6.5.11", "", { "peerDependencies": { "preact": ">=10" } }, "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw=="], - - "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], - - "@isaacs/cliui/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], - - "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - - "@mrleebo/prisma-ast/lilconfig": ["lilconfig@2.1.0", "", {}, "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="], - - "@prisma/engines/@prisma/get-platform": ["@prisma/get-platform@7.4.2", "", { "dependencies": { "@prisma/debug": "7.4.2" } }, "sha512-UTnChXRwiauzl/8wT4hhe7Xmixja9WE28oCnGpBtRejaHhvekx5kudr3R4Y9mLSA0kqGnAMeyTiKwDVMjaEVsw=="], - - "@prisma/fetch-engine/@prisma/get-platform": ["@prisma/get-platform@7.4.2", "", { "dependencies": { "@prisma/debug": "7.4.2" } }, "sha512-UTnChXRwiauzl/8wT4hhe7Xmixja9WE28oCnGpBtRejaHhvekx5kudr3R4Y9mLSA0kqGnAMeyTiKwDVMjaEVsw=="], - - "@prisma/get-platform/@prisma/debug": ["@prisma/debug@7.2.0", "", {}, "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw=="], - - "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], - - "@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], - - "@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - - "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], - - "c12/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], - - "c12/jiti": ["jiti@2.6.1", "", { "bin": "lib/jiti-cli.mjs" }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], - - "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - - "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], - - "eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], - - "eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], - - "eslint-plugin-import/doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="], - - "eslint-plugin-react/doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="], - - "eslint-plugin-react/resolve": ["resolve@2.0.0-next.6", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA=="], - - "eslint-plugin-react/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - - "fdir/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - - "glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], - - "is-bun-module/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - - "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], - - "node-exports-info/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "nypm/citty": ["citty@0.2.1", "", {}, "sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg=="], - - "openid-client/object-hash": ["object-hash@2.2.0", "", {}, "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw=="], - - "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - - "pg-types/postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], - - "postcss/nanoid": ["nanoid@3.3.11", "", { "bin": "bin/nanoid.cjs" }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - - "proper-lockfile/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - - "rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], - - "string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - - "wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "yargs/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], - - "@isaacs/cliui/string-width/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], - - "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - - "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - - "@isaacs/cliui/wrap-ansi/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], - - "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="], - - "c12/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], - - "cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], - - "next/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": "bin/nanoid.cjs" }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - - "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "yargs/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], - - "@isaacs/cliui/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - - "@isaacs/cliui/wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - - "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - - "yargs/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], - - "yargs/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], - } -} diff --git a/pledge-now-pay-later/docker-compose.yml b/pledge-now-pay-later/docker-compose.yml deleted file mode 100644 index 7a6bc51..0000000 --- a/pledge-now-pay-later/docker-compose.yml +++ /dev/null @@ -1,23 +0,0 @@ -version: "3.8" - -services: - postgres: - image: postgres:16-alpine - restart: unless-stopped - ports: - - "5432:5432" - environment: - POSTGRES_DB: pnpl - POSTGRES_USER: pnpl - POSTGRES_PASSWORD: pnpl_dev - volumes: - - pgdata:/var/lib/postgresql/data - - redis: - image: redis:7-alpine - restart: unless-stopped - ports: - - "6379:6379" - -volumes: - pgdata: diff --git a/pledge-now-pay-later/docs/EMBED_GUIDE.md b/pledge-now-pay-later/docs/EMBED_GUIDE.md deleted file mode 100644 index 771163e..0000000 --- a/pledge-now-pay-later/docs/EMBED_GUIDE.md +++ /dev/null @@ -1,134 +0,0 @@ -# Embed Guide — Add Pledge Now, Pay Later to Your Website - -## Option 1: QR Code + Direct Link (Recommended) - -The simplest way — just share your pledge page URL or print the QR code. - -### Get Your Link -Every QR source generates a unique link: -``` -https://your-domain.com/p/{code} -``` - -### Add a Button to Your Website -```html - - Pledge Now → - -``` - -### Embed as Full-Page iframe -```html - -``` - -## Option 2: QR Code for Print Materials - -1. Go to **Dashboard → Events → [Your Event] → QR Codes** -2. Click **Download PNG** for each QR code -3. Print on table cards, flyers, or banners - -### Recommended Sizes -- **Table cards**: 5cm × 5cm QR with label below -- **Banners**: 15cm × 15cm QR -- **Flyers**: 3cm × 3cm QR (still scannable) - -### Print Template (HTML) -```html -
- -

Scan to Pledge

-

Table 5 · Ramadan Gala 2025

-
-``` - -## Option 3: Webhook Integration (Advanced) - -Connect reminders to your existing email/SMS tools: - -### Poll for Due Reminders -```bash -curl -X GET "https://your-domain.com/api/webhooks?since=2025-01-01T00:00:00Z" \ - -H "x-org-id: YOUR_ORG_ID" -``` - -### Response Format -```json -{ - "events": [ - { - "event": "reminder.due", - "timestamp": "2025-03-17T10:00:00Z", - "data": { - "reminderId": "clx...", - "pledgeId": "clx...", - "step": 1, - "channel": "email", - "donor": { - "name": "Sarah Khan", - "email": "sarah@example.com", - "phone": "07700900001" - }, - "pledge": { - "reference": "PNPL-7K4P-50", - "amount": 5000, - "rail": "bank" - }, - "event": "Ramadan Gala 2025" - } - } - ] -} -``` - -### Zapier / Make Setup -1. Create a scheduled trigger (every 15 minutes) -2. HTTP GET to `/api/webhooks?since={{last_run}}` -3. For each event, send email/SMS via your provider -4. Templates available in Dashboard → Exports - -## Option 4: CRM Integration - -### Export Pledge Data -```bash -curl -X GET "https://your-domain.com/api/exports/crm-pack" \ - -H "x-org-id: YOUR_ORG_ID" \ - -o pledges.csv -``` - -### CSV Fields -| Field | Description | -|-------|-------------| -| pledge_reference | Unique ref (PNPL-XXXX-NN) | -| donor_name | Donor's name | -| donor_email | Email address | -| donor_phone | Phone number | -| amount_gbp | Amount in pounds | -| payment_method | bank / gocardless / card | -| status | new / initiated / paid / overdue / cancelled | -| event_name | Source event | -| source_label | QR source label | -| volunteer_name | Assigned volunteer | -| table_name | Table assignment | -| gift_aid | Yes / No | -| pledged_at | ISO timestamp | -| paid_at | ISO timestamp (if paid) | -| days_to_collect | Number of days to payment | - -### Salesforce Import -1. Download CRM pack CSV -2. Salesforce → Setup → Data Import Wizard -3. Map fields: pledge_reference → External ID, amount_gbp → Amount, etc. - -### Beacon CRM Import -1. Download CRM pack CSV -2. Beacon → Contacts → Import -3. Map donor fields and donation amount diff --git a/pledge-now-pay-later/docs/PRODUCT_SPEC.md b/pledge-now-pay-later/docs/PRODUCT_SPEC.md deleted file mode 100644 index 3357b7c..0000000 --- a/pledge-now-pay-later/docs/PRODUCT_SPEC.md +++ /dev/null @@ -1,1364 +0,0 @@ -# Pledge Now, Pay Later — Product Specification - -> **Version:** 1.0 -> **Last updated:** 2026-02-28 -> **Status:** Implementation in progress - ---- - -## Table of Contents - -1. [Overview](#1-overview) -2. [User Personas](#2-user-personas) -3. [Core Flows](#3-core-flows) -4. [Data Model](#4-data-model) -5. [API Contracts](#5-api-contracts) -6. [Payment Reference Design](#6-payment-reference-design) -7. [Analytics Events](#7-analytics-events) -8. [Lead Qualification](#8-lead-qualification) -9. [Integration Points](#9-integration-points) -10. [Non-Functional Requirements](#10-non-functional-requirements) - ---- - -## 1. Overview - -**Pledge Now, Pay Later** (PNPL) is a free-forever micro-SaaS that helps UK charities capture donation intent at live events — dinners, auctions, fun runs, Friday prayers — and follow through to actual payment. - -### The Problem - -At charity events, donors say "I'll donate later" and never do. Event teams lose 40–60% of pledged income because there's no system to: -- Capture pledges quickly on mobile -- Attribute donations to tables, volunteers, or campaigns -- Follow up automatically -- Reconcile bank payments against pledges - -### The Solution - -PNPL converts verbal intent into tracked, attributed digital pledges in **15 seconds**, then drives payment collection through the donor's preferred method: - -| Payment Rail | Fees | Collection | Best For | -|-----------------|-------|----------------|--------------------| -| Bank transfer | £0 | Manual + match | Most donors (UK) | -| Direct Debit | ~1% | Automatic | Recurring/high-value| -| Card | ~1.4% | Instant | Convenience | - -### Business Model - -``` -┌─────────────────────────────────────────────────────┐ -│ FREE FOREVER │ -│ Event setup · QR codes · Pledge flow · Reminders │ -│ Bank reconciliation · CRM export · Dashboard │ -└──────────────────────┬──────────────────────────────┘ - │ - Qualified Lead Signal - (events created + pledges collected) - │ - ▼ -┌─────────────────────────────────────────────────────┐ -│ FRACTIONAL HEAD OF TECHNOLOGY │ -│ Omair's consultancy — pre-filled application with │ -│ event performance metrics from PNPL usage │ -└─────────────────────────────────────────────────────┘ -``` - -The product is genuinely free. No tiered pricing, no feature gates. Revenue comes from qualifying charity organisations that need broader technology leadership — PNPL usage data pre-fills the consultancy application with proof of operational maturity. - ---- - -## 2. User Personas - -### 2.1 Event Lead / Fundraising Manager - -| Attribute | Detail | -|--------------|---------------------------------------------------------| -| **Role** | Creates events, manages QR codes, monitors pledge pipeline | -| **Goal** | Maximise pledge-to-payment conversion | -| **Pain** | Spreadsheets, lost pledges, no attribution | -| **Key screens** | Dashboard, Event setup, Reconciliation, CRM export | -| **Tech comfort** | Moderate — can upload CSVs, follow guided workflows | - -### 2.2 Donor - -| Attribute | Detail | -|--------------|---------------------------------------------------------| -| **Role** | Scans QR code at event, makes a pledge, pays later | -| **Goal** | Pledge quickly without friction, pay when convenient | -| **Pain** | Long forms, app installs, payment pressure at events | -| **Key screens** | 3-step pledge flow (Amount → Method → Identity) | -| **Tech comfort** | Any — mobile web only, no account required | - -### 2.3 Finance / Admin - -| Attribute | Detail | -|--------------|---------------------------------------------------------| -| **Role** | Reconciles bank statements, exports data for CRM/Gift Aid | -| **Goal** | Match bank payments to pledges, produce accurate records | -| **Pain** | Manual bank statement line-matching, Gift Aid declarations | -| **Key screens** | Reconciliation tool, CRM export, Pledge list | -| **Tech comfort** | Comfortable with CSV imports/exports | - -### 2.4 Volunteer - -| Attribute | Detail | -|--------------|---------------------------------------------------------| -| **Role** | Assigned a personal QR code, encourages table donations | -| **Goal** | Show QR, let donors pledge painlessly | -| **Pain** | Collecting cash, keeping track of who pledged what | -| **Key screens** | None — shows printed QR or phone screen to donors | -| **Tech comfort** | Low — just needs to hold up a QR code | - ---- - -## 3. Core Flows - -### 3.1 Event Setup Flow - -``` -┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ -│ 1. Create │───▶│ 2. Add QR │───▶│ 3. Download │───▶│ 4. Share │ -│ Event │ │ Sources │ │ QR sheets │ │ Event Link │ -└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ -``` - -**Step 1 — Create Event** -- Name (required), date, location, fundraising goal -- Auto-generates URL slug: `{name}-{timestamp_base36}` -- Status starts as `active` (also: `draft`, `closed`, `archived`) - -**Step 2 — Add QR Sources** -- Each QR source represents a table, volunteer, or campaign channel -- Label examples: `"Table 5"`, `"Volunteer: Ahmed"`, `"Instagram Story"` -- Each gets a unique 8-character code (human-safe alphabet) -- QR encodes: `{BASE_URL}/p/{code}` - -**Step 3 — Download QR Sheets** -- Individual PNG download per QR source (800×800px) -- QR colour: org primary colour (default `#1e40af`) -- Error correction: Level M (15% damage tolerance) - -**Step 4 — Share Event Link** -- Direct URL for digital channels (no QR needed) -- Attribution still tracked via `qrSourceId` - ---- - -### 3.2 Donor Pledge Flow (3 screens, 15 seconds) - -``` - ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ - │ Screen 1 │────▶│ Screen 2 │────▶│ Screen 3 │────▶│ Confirmation │ - │ Amount │ │ Method │ │ Identity │ │ + Instructions │ - └─────────────┘ └─────────────┘ └─────────────┘ └─────────────────┘ - - 6 presets + Bank (★) Email OR phone Bank → ref + copy - custom entry Direct Debit Name (optional) DD → mandate link - Card Gift Aid checkbox Card → checkout -``` - -**Screen 1 — Amount** -- Six preset buttons (e.g., £10, £25, £50, £100, £250, £500) -- Custom amount input (min £1, max £1,000,000) -- Analytics: `amount_selected` fires on tap - -**Screen 2 — Payment Method** -- **Bank Transfer** (recommended) — zero fees, donor sends manually -- **Direct Debit** — GoCardless mandate, auto-collected -- **Card** — Stripe checkout (future) -- Bank is visually highlighted as the recommended option -- Analytics: `rail_selected` fires on tap - -**Screen 3 — Identity** -- Email **or** phone required (at least one) -- Name optional -- Gift Aid checkbox with eligibility explainer -- Analytics: `identity_submitted` fires on submit - -**Confirmation — Payment Instructions** -- Varies by rail: - -| Rail | Confirmation Content | -|------|---------------------| -| **Bank** | Sort code, account number, account name, **unique reference** with one-tap copy button, "I've paid" button | -| **Direct Debit** | GoCardless mandate link (redirects to authorisation) | -| **Card** | Stripe payment link (future) | - -- Analytics: `pledge_completed` fires on render - ---- - -### 3.3 Payment Collection Flow - -#### Bank Transfer (Primary) - -``` - Donor Charity's Bank PNPL - │ │ │ - │ Transfer with ref │ │ - │ PNPL-7K4P-50 │ │ - │─────────────────────────────▶│ │ - │ │ │ - │ │ Export CSV │ - │ │──────────────────────▶│ - │ │ │ - │ │ │ Auto-match by - │ │ │ reference code - │ │ │ - │ │ Pledge marked "paid" │ - │ │◀──────────────────────│ - │ │ │ - │ Reminders stop │ │ - │◀─────────────────────────────────────────────────────│ -``` - -1. Donor transfers money using the unique reference -2. Charity exports bank statement as CSV -3. Uploads CSV to PNPL reconciliation tool -4. PNPL auto-matches references → marks pledges as paid -5. Remaining reminders are skipped - -#### GoCardless Direct Debit - -``` - Donor GoCardless PNPL - │ │ │ - │ Authorise mandate │ │ - │─────────────────────────────▶│ │ - │ │ │ - │ │ Payment collected │ - │ │──────────────────────▶│ - │ │ │ - │ │ Webhook: confirmed │ - │ │──────────────────────▶│ - │ │ │ - │ │ Pledge marked "paid" │ - │ │◀──────────────────────│ -``` - -#### Card (Future — Stripe) - -``` - Donor ──▶ Stripe Checkout ──▶ Webhook confirms ──▶ Pledge marked "paid" -``` - ---- - -### 3.4 Reminder Sequence - -| Step | Timing | Template Key | Subject | Description | -|------|--------|-------------------|----------------------------------|-------------------------------------------------------| -| 0 | T+0 | `instructions` | Payment details for your £X pledge | Bank details, reference, copy button | -| 1 | T+2d | `gentle_nudge` | Quick reminder about your pledge | Friendly — "if you've already paid, thank you!" | -| 2 | T+7d | `urgency_impact` | Your pledge is making a difference | Impact story + urgency framing | -| 3 | T+14d | `final_reminder` | Final reminder about your pledge | Clear options: pay now or cancel | - -**Stop Rules:** -- Auto-stop on payment match (bank reconciliation or webhook) -- Auto-stop on manual "mark as paid" by staff -- Auto-stop on pledge cancellation -- Donor can self-cancel via link in every reminder - -**Channel:** Email (default). SMS and WhatsApp channels are defined in the schema but not yet implemented. - -**Delivery:** Reminders are exposed via a polling webhook endpoint (`GET /api/webhooks`). External automation tools (Zapier, Make, n8n) poll for due reminders and handle actual email/SMS delivery. - ---- - -### 3.5 Reconciliation Flow - -``` - ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ - │ 1. Export │───▶│ 2. Upload │───▶│ 3. Map │───▶│ 4. Review │───▶│ 5. Confirm │ - │ Bank CSV │ │ to PNPL │ │ Columns │ │ Matches │ │ & Apply │ - └────────────┘ └────────────┘ └────────────┘ └────────────┘ └────────────┘ -``` - -**Step 1 — Export Bank Statement** -- Download CSV from online banking (any UK bank format) - -**Step 2 — Upload to PNPL** -- `POST /api/imports/bank-statement` with `multipart/form-data` -- Accepts `.csv` files, parsed with PapaParse - -**Step 3 — Configure Column Mapping** -- Map bank-specific column names: - - `dateCol` → transaction date - - `descriptionCol` → transaction description - - `amountCol` or `creditCol` → payment amount - - `referenceCol` → payment reference (optional — also searched in description) -- Only credit rows (positive amounts) are processed - -**Step 4 — Auto-Match** -Three matching strategies applied in order: - -| Priority | Strategy | Confidence | Description | -|----------|--------------------|-----------|--------------------------------------------| -| 1 | Exact reference | `exact` | Normalised ref matches `reference` column | -| 2 | Description search | `exact` | Normalised ref found within `description` | -| 3 | Partial code match | `partial` | 4-char code portion found in `description` | - -Normalisation: strip spaces, strip dashes, uppercase. e.g., `pnpl 7k4p 50` → `PNPL7K4P50` - -**Step 5 — Confirm & Apply** -- Exact matches are auto-confirmed: - - Pledge status → `paid`, `paidAt` set - - Payment record created (`matchedBy: "auto"`) - - Pending reminders → `skipped` -- Partial matches flagged for manual review -- Import record saved with stats (total rows, credits, matches, unmatched) - ---- - -### 3.6 CRM Export Flow - -**Endpoint:** `GET /api/exports/crm-pack` - -Downloads a CSV with full pledge attribution. Filterable by `?eventId=`. - -**Export Fields:** - -| Column | Description | Example | -|--------------------|------------------------------------------|-----------------------| -| `pledge_reference` | Unique bank-safe reference | `PNPL-7K4P-50` | -| `donor_name` | Donor's name (if provided) | `Sarah Ahmed` | -| `donor_email` | Donor's email | `sarah@example.com` | -| `donor_phone` | Donor's phone | `07700900123` | -| `amount_gbp` | Pledge amount in pounds | `50.00` | -| `payment_method` | Payment rail | `bank` | -| `status` | Current pledge status | `paid` | -| `event_name` | Source event | `Annual Gala 2026` | -| `source_label` | QR source label | `Table 5` | -| `volunteer_name` | Volunteer assigned to QR source | `Ahmed` | -| `table_name` | Table assigned to QR source | `VIP Table` | -| `gift_aid` | Gift Aid eligibility | `Yes` | -| `pledged_at` | Pledge creation timestamp (ISO 8601) | `2026-03-15T19:32:00Z`| -| `paid_at` | Payment confirmation timestamp | `2026-03-17T10:15:00Z`| -| `days_to_collect` | Days between pledge and payment | `2` | - ---- - -## 4. Data Model - -Ten tables in PostgreSQL 16, managed via Prisma ORM. - -### Entity Relationship Diagram - -``` -┌──────────────────┐ -│ Organization │ -│──────────────────│ -│ id │ -│ name │ ┌──────────────────┐ -│ slug (unique) │───────▶│ User │ -│ country │ 1:N │──────────────────│ -│ timezone │ │ id │ -│ bankName │ │ email (unique) │ -│ bankSortCode │ │ name │ -│ bankAccountNo │ │ hashedPassword │ -│ bankAccountName │ │ role │ -│ refPrefix │ │ organizationId │ -│ logo │ └──────────────────┘ -│ primaryColor │ -│ gcAccessToken │ -│ gcEnvironment │ -└────────┬─────────┘ - │ 1:N - ▼ -┌──────────────────┐ 1:N ┌──────────────────┐ -│ Event │───────▶│ QrSource │ -│──────────────────│ │──────────────────│ -│ id │ │ id │ -│ name │ │ label │ -│ slug │ │ code (unique) │ -│ description │ │ volunteerName │ -│ eventDate │ │ tableName │ -│ location │ │ eventId │ -│ goalAmount │ │ scanCount │ -│ currency │ └────────┬─────────┘ -│ status │ │ -│ organizationId │ │ 0:N -└────────┬─────────┘ │ - │ 1:N │ - ▼ │ -┌──────────────────┐◀────────────────┘ -│ Pledge │ -│──────────────────│ -│ id │ 1:1 ┌────────────────────────┐ -│ reference │───────▶│ PaymentInstruction │ -│ amountPence │ │────────────────────────│ -│ currency │ │ id │ -│ rail │ │ pledgeId (unique) │ -│ status │ │ bankReference │ -│ donorName │ │ bankDetails (JSON) │ -│ donorEmail │ │ gcMandateId │ -│ donorPhone │ │ gcMandateUrl │ -│ giftAid │ │ sentAt │ -│ iPaidClickedAt │ └────────────────────────┘ -│ eventId │ -│ qrSourceId │ 1:N ┌──────────────────┐ -│ organizationId │───────▶│ Payment │ -│ paidAt │ │──────────────────│ -│ cancelledAt │ │ id │ -└────────┬─────────┘ │ pledgeId │ - │ │ provider │ - │ │ providerRef │ - │ 1:N │ amountPence │ - ▼ │ status │ -┌──────────────────┐ │ matchedBy │ -│ Reminder │ │ receivedAt │ -│──────────────────│ │ importId │ -│ id │ └──────────────────┘ -│ pledgeId │ -│ step │ -│ channel │ -│ scheduledAt │ -│ sentAt │ -│ status │ -│ payload (JSON) │ -└──────────────────┘ - -┌──────────────────┐ 1:N ┌──────────────────┐ -│ Import │───────▶│ Payment │ -│──────────────────│ │ (via importId) │ -│ id │ └──────────────────┘ -│ organizationId │ -│ kind │ -│ fileName │ -│ rowCount │ ┌──────────────────┐ -│ matchedCount │ │ AnalyticsEvent │ -│ unmatchedCount │ │──────────────────│ -│ mappingConfig │ │ id │ -│ stats (JSON) │ │ eventType │ -│ status │ │ pledgeId │ -└──────────────────┘ │ eventId │ - │ qrSourceId │ - │ metadata (JSON) │ - │ createdAt │ - └──────────────────┘ -``` - -### Table Details - -#### 4.1 `Organization` - -The top-level tenant. All data is scoped to an organization. - -| Field | Type | Description | -|-----------------|----------|--------------------------------------------------| -| `id` | `cuid` | Primary key | -| `name` | `string` | Organisation display name | -| `slug` | `string` | URL-safe unique identifier | -| `country` | `string` | Default `"UK"` | -| `timezone` | `string` | Default `"Europe/London"` | -| `bankName` | `string?`| Bank name for payment instructions | -| `bankSortCode` | `string?`| 6-digit sort code | -| `bankAccountNo` | `string?`| 8-digit account number | -| `bankAccountName`| `string?`| Name on the bank account | -| `refPrefix` | `string` | Reference prefix, default `"PNPL"`, max 4 chars | -| `logo` | `string?`| Logo URL | -| `primaryColor` | `string` | Brand colour, default `"#1e40af"` | -| `gcAccessToken` | `string?`| GoCardless API token | -| `gcEnvironment` | `string` | `"sandbox"` or `"live"` | - -#### 4.2 `User` - -Staff/admin accounts for the dashboard. - -| Field | Type | Description | -|-----------------|----------|--------------------------------------------------| -| `id` | `cuid` | Primary key | -| `email` | `string` | Unique email address | -| `name` | `string?`| Display name | -| `hashedPassword`| `string?`| bcrypt hash (nullable for SSO) | -| `role` | `string` | `super_admin`, `org_admin`, `staff`, `volunteer` | -| `organizationId`| `string` | FK → Organization | - -#### 4.3 `Event` - -A fundraising event that donors pledge at. - -| Field | Type | Description | -|-----------------|------------|------------------------------------------------| -| `id` | `cuid` | Primary key | -| `name` | `string` | Event name (1–200 chars) | -| `slug` | `string` | URL-safe slug (auto-generated) | -| `description` | `string?` | Event description (max 2000 chars) | -| `eventDate` | `DateTime?`| When the event takes place | -| `location` | `string?` | Venue (max 500 chars) | -| `goalAmount` | `int?` | Fundraising target **in pence** | -| `currency` | `string` | Default `"GBP"` | -| `status` | `string` | `draft`, `active`, `closed`, `archived` | -| `organizationId`| `string` | FK → Organization | - -Unique constraint: `(organizationId, slug)` - -#### 4.4 `QrSource` - -An attribution source — a specific QR code tied to a table, volunteer, or channel. - -| Field | Type | Description | -|----------------|----------|---------------------------------------------------| -| `id` | `cuid` | Primary key | -| `label` | `string` | Human-readable label (1–100 chars) | -| `code` | `string` | Unique 8-char token for URLs | -| `volunteerName`| `string?`| Volunteer's name (for attribution) | -| `tableName` | `string?`| Table identifier (for attribution) | -| `eventId` | `string` | FK → Event | -| `scanCount` | `int` | Number of times QR was scanned (auto-incremented) | - -#### 4.5 `Pledge` - -The core entity — a donor's promise to pay. - -| Field | Type | Description | -|-----------------|------------|------------------------------------------------| -| `id` | `cuid` | Primary key | -| `reference` | `string` | Unique bank-safe reference (see §6) | -| `amountPence` | `int` | Pledge amount in pence (min 100 = £1) | -| `currency` | `string` | Default `"GBP"` | -| `rail` | `string` | `bank`, `gocardless`, `card` | -| `status` | `string` | `new`, `initiated`, `paid`, `overdue`, `cancelled` | -| `donorName` | `string?` | Donor's name | -| `donorEmail` | `string?` | Donor's email | -| `donorPhone` | `string?` | Donor's phone | -| `giftAid` | `boolean` | Gift Aid declaration (default `false`) | -| `iPaidClickedAt`| `DateTime?`| When donor clicked "I've paid" | -| `notes` | `string?` | Staff notes | -| `eventId` | `string` | FK → Event | -| `qrSourceId` | `string?` | FK → QrSource (null if direct link) | -| `organizationId`| `string` | FK → Organization | -| `paidAt` | `DateTime?`| When payment was confirmed | -| `cancelledAt` | `DateTime?`| When pledge was cancelled | - -Validation: `donorEmail` or `donorPhone` must be present (enforced by Zod schema). - -**Status State Machine:** - -``` - ┌──────────────────────┐ - ▼ │ - ┌───────┐ "I've paid" ┌──────────┐│ bank match / - │ new │─────────────────▶│initiated ││ webhook - │ │ │ ││ - └───┬───┘ └────┬─────┘│ - │ │ │ - │ bank match / webhook │ │ - │ │ │ - ▼ ▼ │ - ┌───────┐ ┌──────────┐│ - │ paid │◀─────────────────│ paid ││ - └───────┘ └──────────┘│ - ▲ │ - │ ┌──────────┐ │ - │ │ overdue │─────────────┘ - │ └────┬─────┘ - │ │ - │ ▼ - │ ┌──────────┐ - └─────────│cancelled │ - └──────────┘ -``` - -#### 4.6 `PaymentInstruction` - -Bank transfer details stored per pledge. Created automatically for `rail: "bank"`. - -| Field | Type | Description | -|----------------|----------|---------------------------------------------------| -| `id` | `cuid` | Primary key | -| `pledgeId` | `string` | FK → Pledge (unique — 1:1) | -| `bankReference`| `string` | The reference donor must use | -| `bankDetails` | `JSON` | `{sortCode, accountNo, accountName, bankName}` | -| `gcMandateId` | `string?`| GoCardless mandate ID | -| `gcMandateUrl` | `string?`| GoCardless mandate authorisation URL | -| `sentAt` | `DateTime?`| When instructions were first sent | - -#### 4.7 `Payment` - -A confirmed money movement against a pledge. - -| Field | Type | Description | -|--------------|------------|--------------------------------------------------| -| `id` | `cuid` | Primary key | -| `pledgeId` | `string` | FK → Pledge | -| `provider` | `string` | `bank`, `gocardless`, `stripe` | -| `providerRef`| `string?` | External payment/transaction ID | -| `amountPence`| `int` | Amount received in pence | -| `status` | `string` | `pending`, `confirmed`, `failed` | -| `matchedBy` | `string?` | `auto` (reconciliation) or `manual` (staff) | -| `receivedAt` | `DateTime?`| When money was received | -| `importId` | `string?` | FK → Import (if matched via bank statement) | - -#### 4.8 `Reminder` - -Scheduled follow-up messages for a pledge. - -| Field | Type | Description | -|--------------|------------|--------------------------------------------------| -| `id` | `cuid` | Primary key | -| `pledgeId` | `string` | FK → Pledge | -| `step` | `int` | Sequence number: `0`, `1`, `2`, `3` | -| `channel` | `string` | `email`, `sms`, `whatsapp` | -| `scheduledAt`| `DateTime` | When to send | -| `sentAt` | `DateTime?`| When actually sent | -| `status` | `string` | `pending`, `sent`, `skipped`, `failed` | -| `payload` | `JSON?` | Template key + subject line | - -#### 4.9 `Import` - -Record of a bank statement upload and its results. - -| Field | Type | Description | -|----------------|------------|------------------------------------------------| -| `id` | `cuid` | Primary key | -| `organizationId`| `string` | FK → Organization | -| `kind` | `string` | `bank_statement`, `gocardless_export`, `crm_export` | -| `fileName` | `string?` | Original upload filename | -| `rowCount` | `int` | Total rows in CSV | -| `matchedCount` | `int` | Rows matched to pledges | -| `unmatchedCount`| `int` | Rows that didn't match | -| `mappingConfig`| `JSON?` | Column mapping used | -| `stats` | `JSON?` | Detailed match statistics | -| `status` | `string` | `pending`, `processing`, `completed`, `failed` | - -#### 4.10 `AnalyticsEvent` - -Append-only event log for funnel tracking. - -| Field | Type | Description | -|-------------|------------|---------------------------------------------------| -| `id` | `cuid` | Primary key | -| `eventType` | `string` | Event name (see §7) | -| `pledgeId` | `string?` | FK → Pledge (if applicable) | -| `eventId` | `string?` | FK → Event (if applicable) | -| `qrSourceId`| `string?` | FK → QrSource (if applicable) | -| `metadata` | `JSON?` | Arbitrary key-value data | -| `createdAt` | `DateTime` | Timestamp | - ---- - -## 5. API Contracts - -All endpoints are Next.js API routes. Authentication is via `x-org-id` header (NextAuth.js integration ready but not yet enforced). - -### 5.1 `GET /api/qr/{token}` — Resolve QR Code - -Resolves a QR source token to event info. Increments `scanCount`. - -**Path params:** `token` — 8-char QR source code - -**Response `200`:** -```json -{ - "id": "clx...", - "name": "Annual Gala 2026", - "organizationName": "Hope Foundation", - "qrSourceId": "clx...", - "qrSourceLabel": "Table 5" -} -``` - -**Response `404`:** -```json -{ "error": "This pledge link is no longer active" } -``` - ---- - -### 5.2 `POST /api/pledges` — Create Pledge - -Creates a pledge with payment instruction and reminder schedule in a single transaction. - -**Request body:** -```json -{ - "amountPence": 5000, - "rail": "bank", - "donorName": "Sarah Ahmed", - "donorEmail": "sarah@example.com", - "donorPhone": "07700900123", - "giftAid": true, - "eventId": "clx...", - "qrSourceId": "clx..." -} -``` - -**Validation (Zod):** -- `amountPence`: int, 100–100,000,000 (£1–£1M) -- `rail`: `"bank" | "gocardless" | "card"` -- `donorEmail` or `donorPhone`: at least one required -- `eventId`: required - -**Response `201` (bank rail):** -```json -{ - "id": "clx...", - "reference": "PNPL-7K4P-50", - "bankDetails": { - "bankName": "Barclays", - "sortCode": "20-00-00", - "accountNo": "12345678", - "accountName": "Hope Foundation" - } -} -``` - -**Response `201` (other rails):** -```json -{ - "id": "clx...", - "reference": "PNPL-7K4P-50" -} -``` - -**Side effects:** -- Generates collision-resistant reference (up to 10 retries) -- Creates `PaymentInstruction` (bank rail) -- Creates 4 `Reminder` records (T+0, T+2d, T+7d, T+14d) -- Tracks `pledge_completed` analytics event - ---- - -### 5.3 `PATCH /api/pledges/{id}` — Update Pledge Status - -**Request body:** -```json -{ - "status": "paid", - "notes": "Confirmed via bank statement" -} -``` - -**Validation:** -- `status`: `"new" | "initiated" | "paid" | "overdue" | "cancelled"` -- `notes`: optional, max 1000 chars - -**Response `200`:** Full pledge object. - -**Side effects:** -- Sets `paidAt` when status → `paid` -- Sets `cancelledAt` when status → `cancelled` -- Skips all pending reminders when status → `paid` or `cancelled` - ---- - -### 5.4 `POST /api/pledges/{id}/mark-initiated` — Donor "I've Paid" - -Called when donor taps the "I've paid" button on the confirmation screen. - -**Request body:** None - -**Response `200`:** -```json -{ "ok": true } -``` - -**Side effects:** -- Sets `status` → `"initiated"`, `iPaidClickedAt` → now - ---- - -### 5.5 `GET /api/events` — List Events - -Returns all events for the organisation with pledge aggregates. - -**Headers:** `x-org-id` - -**Response `200`:** -```json -[ - { - "id": "clx...", - "name": "Annual Gala 2026", - "slug": "annual-gala-2026-m3k9a", - "eventDate": "2026-03-15T18:00:00.000Z", - "location": "Grand Hall, London", - "goalAmount": 5000000, - "status": "active", - "pledgeCount": 47, - "qrSourceCount": 12, - "totalPledged": 3750000, - "totalCollected": 2100000, - "createdAt": "2026-02-01T10:00:00.000Z" - } -] -``` - -Note: `goalAmount`, `totalPledged`, and `totalCollected` are in **pence**. - ---- - -### 5.6 `POST /api/events` — Create Event - -**Headers:** `x-org-id` - -**Request body:** -```json -{ - "name": "Annual Gala 2026", - "description": "Our biggest fundraiser of the year", - "eventDate": "2026-03-15T18:00:00.000Z", - "location": "Grand Hall, London", - "goalAmount": 5000000, - "currency": "GBP" -} -``` - -**Validation:** -- `name`: required, 1–200 chars -- `description`: optional, max 2000 chars -- `eventDate`: optional, ISO 8601 -- `location`: optional, max 500 chars -- `goalAmount`: optional, positive int (pence) - -**Response `201`:** Full event object. - -**Slug generation:** `{name_slugified}-{timestamp_base36}` - ---- - -### 5.7 `GET /api/events/{id}/qr` — List QR Sources - -Returns QR sources for an event with pledge stats. - -**Response `200`:** -```json -[ - { - "id": "clx...", - "label": "Table 5", - "code": "abc23def", - "volunteerName": "Ahmed", - "tableName": "VIP Table", - "scanCount": 23, - "pledgeCount": 8, - "totalPledged": 450000, - "createdAt": "2026-02-10T14:00:00.000Z" - } -] -``` - ---- - -### 5.8 `POST /api/events/{id}/qr` — Create QR Source - -**Request body:** -```json -{ - "label": "Table 5", - "volunteerName": "Ahmed", - "tableName": "VIP Table" -} -``` - -**Validation:** -- `label`: required, 1–100 chars -- `volunteerName`: optional, max 100 chars -- `tableName`: optional, max 100 chars - -**Response `201`:** Full QrSource object including generated `code`. - ---- - -### 5.9 `GET /api/events/{id}/qr/{qrId}/download` — Download QR PNG - -Returns an 800×800px PNG image of the QR code. - -**Query params:** `code` — QR source code (optional, falls back to `qrId`) - -**Response:** `image/png` binary with `Content-Disposition: attachment` - ---- - -### 5.10 `GET /api/dashboard` — Dashboard Stats - -Returns full pipeline data with funnel analytics. - -**Headers:** `x-org-id` -**Query params:** `eventId` (optional — filter to single event) - -**Response `200`:** -```json -{ - "summary": { - "totalPledges": 47, - "totalPledgedPence": 3750000, - "totalCollectedPence": 2100000, - "collectionRate": 56, - "overdueRate": 12 - }, - "byStatus": { - "new": 10, - "initiated": 5, - "paid": 25, - "overdue": 4, - "cancelled": 3 - }, - "byRail": { - "bank": 38, - "gocardless": 7, - "card": 2 - }, - "topSources": [ - { "label": "Table 5", "count": 8, "amount": 450000 } - ], - "funnel": { - "pledge_start": 120, - "amount_selected": 95, - "rail_selected": 80, - "identity_submitted": 55, - "pledge_completed": 47 - }, - "pledges": [ - { - "id": "clx...", - "reference": "PNPL-7K4P-50", - "amountPence": 5000, - "status": "paid", - "rail": "bank", - "donorName": "Sarah Ahmed", - "donorEmail": "sarah@example.com", - "donorPhone": null, - "eventName": "Annual Gala 2026", - "source": "Table 5", - "volunteerName": "Ahmed", - "giftAid": true, - "createdAt": "2026-03-15T19:32:00.000Z", - "paidAt": "2026-03-17T10:15:00.000Z", - "nextReminder": null, - "lastTouch": "2026-03-15T19:32:00.000Z" - } - ] -} -``` - ---- - -### 5.11 `POST /api/imports/bank-statement` — Upload & Match Bank CSV - -**Content-Type:** `multipart/form-data` - -**Form fields:** -- `file` — CSV file -- `mapping` — JSON string with column mapping - -**Mapping schema:** -```json -{ - "dateCol": "Date", - "descriptionCol": "Description", - "amountCol": "Amount", - "creditCol": "Credit", - "referenceCol": "Reference" -} -``` - -**Response `200`:** -```json -{ - "importId": "clx...", - "summary": { - "totalRows": 150, - "credits": 45, - "exactMatches": 12, - "partialMatches": 3, - "unmatched": 30, - "autoConfirmed": 12 - }, - "matches": [ - { - "bankRow": { - "date": "2026-03-17", - "description": "PNPL-7K4P-50 S AHMED", - "amount": 50.00, - "reference": "PNPL-7K4P-50" - }, - "pledgeId": "clx...", - "pledgeReference": "PNPL-7K4P-50", - "confidence": "exact", - "matchedAmount": 50.00, - "autoConfirmed": true - } - ] -} -``` - -**Side effects (exact matches):** -- Pledge status → `paid`, `paidAt` set -- Payment record created (`provider: "bank"`, `matchedBy: "auto"`) -- Pending reminders → `skipped` - ---- - -### 5.12 `GET /api/exports/crm-pack` — Download CRM CSV - -**Headers:** `x-org-id` -**Query params:** `eventId` (optional) - -**Response:** `text/csv` with `Content-Disposition: attachment; filename="crm-export-YYYY-MM-DD.csv"` - -See §3.6 for field definitions. - ---- - -### 5.13 `GET /api/webhooks` — Poll Pending Reminders - -Polling endpoint for external automation (Zapier, Make, n8n). - -**Query params:** -- `since` — ISO 8601 timestamp (only reminders scheduled after this time) -- `limit` — max results (default 50) - -**Response `200`:** -```json -{ - "events": [ - { - "event": "reminder.due", - "timestamp": "2026-03-17T10:00:00.000Z", - "data": { - "reminderId": "clx...", - "pledgeId": "clx...", - "step": 1, - "channel": "email", - "scheduledAt": "2026-03-17T19:32:00.000Z", - "donor": { - "name": "Sarah Ahmed", - "email": "sarah@example.com", - "phone": null - }, - "pledge": { - "reference": "PNPL-7K4P-50", - "amount": 5000, - "rail": "bank" - }, - "event": "Annual Gala 2026", - "organization": "Hope Foundation", - "payload": { - "templateKey": "gentle_nudge", - "subject": "Quick reminder about your pledge" - } - } - } - ], - "count": 1 -} -``` - ---- - -### 5.14 `POST /api/analytics` — Track Event - -Fire-and-forget analytics tracking. Never returns errors to avoid breaking donor flow. - -**Request body:** -```json -{ - "eventType": "amount_selected", - "pledgeId": null, - "eventId": "clx...", - "qrSourceId": "clx...", - "metadata": { "amount": 5000, "preset": true } -} -``` - -**Response `200`:** -```json -{ "ok": true } -``` - ---- - -## 6. Payment Reference Design - -The payment reference is the critical link between a pledge in PNPL and a transaction on a bank statement. It must be simultaneously human-readable, bank-compatible, and collision-resistant. - -### Format - -``` -┌────────┐ ┌──────┐ ┌─────┐ -│ PREFIX │─│ CODE │─│ AMT │ -└────────┘ └──────┘ └─────┘ - 1–4 ch 4 ch 1–3 ch - -Example: PNPL-7K4P-50 -``` - -| Segment | Length | Source | Purpose | -|----------|---------|-------------------------------------|----------------------------| -| `PREFIX` | 1–4 ch | Org `refPrefix` (default `"PNPL"`) | Identify the charity | -| `CODE` | 4 ch | Random (human-safe alphabet) | Unique pledge identifier | -| `AMT` | 1–3 ch | Last 3 digits of `£` amount | Aide manual matching | - -### Human-Safe Alphabet - -``` -2 3 4 5 6 7 8 9 -A B C D E F G H J K L M N P Q R S T U V W X Y Z -``` - -**Excluded:** `0` (confused with `O`), `1` (confused with `I`/`l`), `I`, `O`, `l` - -31 characters → 4-char code = 31⁴ = **923,521 combinations per prefix** - -### Constraints - -| Constraint | Limit | Reason | -|---------------------|----------------|---------------------------------------------| -| Max total length | 18 characters | UK BACS payment reference field limit | -| Unique per database | Enforced | Prisma `@unique` constraint on `reference` | -| Collision retry | Up to 10 times | Generate new code if collision detected | -| Overflow protection | Truncate prefix| If ref > 18 chars, prefix truncated to 4 | - -### Matching Normalisation - -When matching bank statement descriptions against references: - -``` -Input: "pnpl 7k4p 50" → Normalised: "PNPL7K4P50" -Input: "PNPL-7K4P-50" → Normalised: "PNPL7K4P50" -Input: " Pnpl 7K4P " → Normalised: "PNPL7K4P" (trimmed) -``` - -Algorithm: strip all whitespace and dashes, uppercase. - ---- - -## 7. Analytics Events - -### Event Types - -| Event | When Fired | Metadata | -|----------------------------|---------------------------------------------------|-------------------------------| -| `pledge_start` | Donor opens pledge flow (QR scanned) | `{eventId, qrSourceId}` | -| `amount_selected` | Donor selects/enters amount | `{amount, preset: boolean}` | -| `rail_selected` | Donor chooses payment method | `{rail}` | -| `identity_submitted` | Donor submits contact details | `{hasEmail, hasPhone, giftAid}` | -| `pledge_completed` | Pledge created successfully | `{amountPence, rail}` | -| `instruction_copy_clicked` | Donor copies bank reference | `{reference}` | -| `i_paid_clicked` | Donor clicks "I've paid" | `{pledgeId}` | -| `payment_matched` | Payment confirmed (reconciliation or webhook) | `{matchedBy, provider}` | - -### Dashboard Metrics - -**Pledge Funnel:** -``` -pledge_start ████████████████████████████████████ 120 -amount_selected ████████████████████████████ 95 (79%) -rail_selected ██████████████████████ 80 (67%) -identity_submitted ██████████████ 55 (46%) -pledge_completed ████████████ 47 (39%) -``` - -**Collection Pipeline:** -- **Collection rate:** `totalCollectedPence / totalPledgedPence` (percentage) -- **Overdue rate:** `overdueCount / totalPledges` (percentage) -- **Top sources:** QR sources ranked by total amount pledged -- **By status:** Breakdown across `new`, `initiated`, `paid`, `overdue`, `cancelled` -- **By rail:** Breakdown across `bank`, `gocardless`, `card` - ---- - -## 8. Lead Qualification - -PNPL's business model generates qualified leads for Omair's fractional Head of Technology consultancy. The qualification system is passive — it observes usage patterns rather than gating features. - -### Trigger Conditions - -A lead is qualified when **any** of the following are met: -- Organisation has created ≥2 events **and** received ≥20 pledges total -- Organisation has received ≥£5,000 in total pledged amount -- Organisation has used reconciliation (≥1 bank statement import) - -### Qualification Score - -Score is calculated from observable usage signals: - -| Signal | Weight | Description | -|--------------------------------|--------|------------------------------------------| -| Attribution usage | High | Multiple QR sources per event | -| Follow-up behaviour | High | Checking dashboard, updating pledge statuses | -| Reconciliation imports | High | Uploading bank statements = operational maturity | -| Event frequency | Medium | Creating events regularly | -| Collection rate | Medium | Higher rate = engaged with the tool | -| CRM exports | Low | Using export = integrating with other systems | - -### Application Flow - -When the qualification threshold is met, the dashboard shows an "Apply for Fractional CTO" link at `/dashboard/apply`. - -The application form is **pre-filled** with: -- Organisation name and size (from event data) -- Number of events run -- Total pledges collected -- Collection rate -- Payment rails used -- Whether reconciliation is active - -This gives Omair immediate context on the charity's operational maturity without the applicant needing to self-report. - ---- - -## 9. Integration Points - -### 9.1 Webhook Polling (Zapier / Make / n8n) - -PNPL does not push webhooks. Instead, external automation tools **poll** for pending events: - -``` -GET /api/webhooks?since=2026-03-17T00:00:00Z&limit=50 -``` - -**Typical Zapier workflow:** -1. Schedule: poll every 5 minutes -2. Trigger: new `reminder.due` events -3. Action: send email via SendGrid / Mailchimp -4. Action: mark reminder as sent (future endpoint) - -**Event format:** See §5.13. - -### 9.2 CSV Export - -`GET /api/exports/crm-pack` produces a standard CSV importable into: -- Salesforce -- HubSpot -- Beacon CRM -- Donorfy -- Any spreadsheet tool - -### 9.3 GoCardless (Direct Debit) - -| Setting | Storage | Notes | -|------------------|--------------------------------|-------------------------------| -| Access token | `Organization.gcAccessToken` | Encrypted at rest | -| Environment | `Organization.gcEnvironment` | `"sandbox"` or `"live"` | -| Mandate ID | `PaymentInstruction.gcMandateId` | Stored per pledge | -| Mandate URL | `PaymentInstruction.gcMandateUrl` | Redirect URL for donor | - -**Flow:** Pledge created → mandate URL generated → donor authorises → GoCardless collects → webhook confirms → pledge marked paid. - -### 9.4 Future Integrations - -| Integration | Priority | Description | -|-----------------|----------|--------------------------------------------------| -| Stripe | High | Card payments via Checkout Sessions | -| Open Banking | Medium | Real-time payment initiation (no reference needed)| -| SMS (Twilio) | Medium | Reminder delivery via SMS | -| WhatsApp | Low | Reminder delivery via WhatsApp Business API | -| Stripe Identity | Low | Gift Aid address verification | - ---- - -## 10. Non-Functional Requirements - -### 10.1 Performance - -| Metric | Target | Rationale | -|-------------------------|-------------------|----------------------------------------| -| API response time | < 200ms (p95) | Mobile users on 4G at events | -| Pledge flow completion | > 80% | 3 screens, 15 seconds | -| QR scan → first screen | < 1s | Instant feel on scan | -| Bank CSV import (500 rows) | < 5s | Blocking UI operation | - -### 10.2 Reliability - -| Requirement | Implementation | -|--------------------------|---------------------------------------------------| -| Idempotent pledge creation | Reference uniqueness check + retry loop (10 attempts) | -| Analytics never fails | `POST /api/analytics` always returns `200`, catches all errors | -| Transaction safety | Pledge + PaymentInstruction + Reminders created in `$transaction` | -| Reminder stop guarantee | Paid/cancelled status change skips all pending reminders atomically | - -### 10.3 Mobile-First Design - -- **No account required** for donors — scan QR, pledge, done -- **One-tap reference copy** — copy button on confirmation screen -- **6 preset amounts** — big tap targets, no typing needed -- **Progressive identity** — email OR phone, name optional -- **Responsive** — Tailwind CSS, mobile-first breakpoints - -### 10.4 Security - -| Concern | Mitigation | -|----------------------|-------------------------------------------------------| -| Org data isolation | All queries scoped by `organizationId` | -| PII handling | Donor email/phone stored, not exposed in QR codes | -| Bank credentials | Stored in DB (future: encrypt at rest, vault) | -| GoCardless tokens | `gcAccessToken` in DB (future: encrypted) | -| Auth | NextAuth.js ready, `x-org-id` header interim | -| Rate limiting | Not yet implemented (future: Redis-based) | -| CSRF | Next.js built-in protections | - -### 10.5 Deployment - -``` -┌─────────────────────────────────────────────┐ -│ Docker Compose Stack │ -│ │ -│ ┌───────────────┐ ┌───────────────────┐ │ -│ │ PostgreSQL │ │ Redis │ │ -│ │ 16-alpine │ │ 7-alpine │ │ -│ │ port: 5432 │ │ port: 6379 │ │ -│ └───────────────┘ └───────────────────┘ │ -│ │ -│ ┌───────────────────────────────────────┐ │ -│ │ Next.js App │ │ -│ │ Node 18+ · port: 3000 │ │ -│ │ Prisma ORM · App Router │ │ -│ └───────────────────────────────────────┘ │ -└─────────────────────────────────────────────┘ -``` - -**Single command:** -```bash -docker compose up -d -npx prisma migrate deploy -npm run build && npm start -``` - -**Environment variables** (see `.env.example`): -- `DATABASE_URL` — PostgreSQL connection string -- `BASE_URL` — Public URL for QR codes -- `NEXTAUTH_SECRET` — Auth session secret -- GoCardless credentials (when ready) - -### 10.6 Observability - -| Layer | Tool | Notes | -|---------------|-------------------------|------------------------------------| -| Error logging | `console.error` | Structured in API routes | -| Analytics | `AnalyticsEvent` table | Queryable funnel data | -| Import audits | `Import` table | Full history of reconciliation runs| -| Scan tracking | `QrSource.scanCount` | QR engagement metric | - ---- - -## Appendix A: Glossary - -| Term | Definition | -|--------------------|-------------------------------------------------------------------| -| **Pledge** | A donor's declared intent to pay a specific amount | -| **Rail** | Payment method — bank transfer, Direct Debit, or card | -| **Reference** | Human-safe, bank-compatible unique code for matching payments | -| **QR Source** | An attribution point (table, volunteer, channel) with a unique QR | -| **Reconciliation** | Process of matching bank statement transactions to pledges | -| **Collection rate** | Percentage of pledged amount that has been confirmed as paid | -| **Initiated** | Donor has clicked "I've paid" but payment not yet confirmed | - -## Appendix B: Tech Stack Summary - -| Layer | Technology | Version | -|------------|--------------------------|---------| -| Framework | Next.js (App Router) | 14 | -| Language | TypeScript | — | -| Styling | Tailwind CSS + shadcn/ui | — | -| Database | PostgreSQL | 16 | -| ORM | Prisma | — | -| Validation | Zod | — | -| QR Codes | `qrcode` (node) | — | -| CSV | PapaParse | — | -| ID Gen | `nanoid` | — | -| Icons | Lucide React | — | -| Auth | NextAuth.js (ready) | — | -| Cache | Redis | 7 | diff --git a/pledge-now-pay-later/next.config.mjs b/pledge-now-pay-later/next.config.mjs deleted file mode 100644 index 8c8bab6..0000000 --- a/pledge-now-pay-later/next.config.mjs +++ /dev/null @@ -1,6 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = { - output: "standalone", -}; - -export default nextConfig; diff --git a/pledge-now-pay-later/package-lock.json b/pledge-now-pay-later/package-lock.json deleted file mode 100644 index 00ade23..0000000 --- a/pledge-now-pay-later/package-lock.json +++ /dev/null @@ -1,8166 +0,0 @@ -{ - "name": "pledge-now-pay-later", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "pledge-now-pay-later", - "version": "0.1.0", - "dependencies": { - "@auth/prisma-adapter": "^2.11.1", - "@prisma/adapter-pg": "^7.4.2", - "@prisma/client": "^7.4.2", - "@stripe/stripe-js": "^8.8.0", - "@types/bcryptjs": "^2.4.6", - "@types/qrcode": "^1.5.6", - "bcryptjs": "^3.0.3", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "date-fns": "^4.1.0", - "lucide-react": "^0.575.0", - "nanoid": "^5.1.6", - "next": "14.2.35", - "next-auth": "^4.24.13", - "papaparse": "^5.5.3", - "pg": "^8.19.0", - "prisma": "^7.4.2", - "qrcode": "^1.5.4", - "react": "^18", - "react-dom": "^18", - "stripe": "^20.4.0", - "tailwind-merge": "^3.5.0", - "zod": "^4.3.6" - }, - "devDependencies": { - "@types/node": "^20", - "@types/papaparse": "^5.5.2", - "@types/pg": "^8.18.0", - "@types/react": "^18", - "@types/react-dom": "^18", - "eslint": "^8", - "eslint-config-next": "14.2.35", - "postcss": "^8", - "tailwindcss": "^3.4.1", - "tsx": "^4.21.0", - "typescript": "^5" - } - }, - "node_modules/@alloc/quick-lru": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@auth/prisma-adapter": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@auth/prisma-adapter/-/prisma-adapter-2.11.1.tgz", - "integrity": "sha512-Ke7DXP0Fy0Mlmjz/ZJLXwQash2UkA4621xCM0rMtEczr1kppLc/njCbUkHkIQ/PnmILjqSPEKeTjDPsYruvkug==", - "license": "ISC", - "dependencies": { - "@auth/core": "0.41.1" - }, - "peerDependencies": { - "@prisma/client": ">=2.26.0 || >=3 || >=4 || >=5 || >=6" - } - }, - "node_modules/@auth/prisma-adapter/node_modules/@auth/core": { - "version": "0.41.1", - "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.41.1.tgz", - "integrity": "sha512-t9cJ2zNYAdWMacGRMT6+r4xr1uybIdmYa49calBPeTqwgAFPV/88ac9TEvCR85pvATiSPt8VaNf+Gt24JIT/uw==", - "license": "ISC", - "dependencies": { - "@panva/hkdf": "^1.2.1", - "jose": "^6.0.6", - "oauth4webapi": "^3.3.0", - "preact": "10.24.3", - "preact-render-to-string": "6.5.11" - }, - "peerDependencies": { - "@simplewebauthn/browser": "^9.0.1", - "@simplewebauthn/server": "^9.0.2", - "nodemailer": "^7.0.7" - }, - "peerDependenciesMeta": { - "@simplewebauthn/browser": { - "optional": true - }, - "@simplewebauthn/server": { - "optional": true - }, - "nodemailer": { - "optional": true - } - } - }, - "node_modules/@auth/prisma-adapter/node_modules/jose": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", - "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/@auth/prisma-adapter/node_modules/oauth4webapi": { - "version": "3.8.5", - "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.5.tgz", - "integrity": "sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/@auth/prisma-adapter/node_modules/preact-render-to-string": { - "version": "6.5.11", - "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.11.tgz", - "integrity": "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==", - "license": "MIT", - "peerDependencies": { - "preact": ">=10" - } - }, - "node_modules/@babel/runtime": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", - "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@chevrotain/cst-dts-gen": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.5.0.tgz", - "integrity": "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/gast": "10.5.0", - "@chevrotain/types": "10.5.0", - "lodash": "4.17.21" - } - }, - "node_modules/@chevrotain/gast": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-10.5.0.tgz", - "integrity": "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/types": "10.5.0", - "lodash": "4.17.21" - } - }, - "node_modules/@chevrotain/types": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-10.5.0.tgz", - "integrity": "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==", - "license": "Apache-2.0" - }, - "node_modules/@chevrotain/utils": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-10.5.0.tgz", - "integrity": "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==", - "license": "Apache-2.0" - }, - "node_modules/@electric-sql/pglite": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.15.tgz", - "integrity": "sha512-Cj++n1Mekf9ETfdc16TlDi+cDDQF0W7EcbyRHYOAeZdsAe8M/FJg18itDTSwyHfar2WIezawM9o0EKaRGVKygQ==", - "license": "Apache-2.0" - }, - "node_modules/@electric-sql/pglite-socket": { - "version": "0.0.20", - "resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.0.20.tgz", - "integrity": "sha512-J5nLGsicnD9wJHnno9r+DGxfcZWh+YJMCe0q/aCgtG6XOm9Z7fKeite8IZSNXgZeGltSigM9U/vAWZQWdgcSFg==", - "license": "Apache-2.0", - "bin": { - "pglite-server": "dist/scripts/server.js" - }, - "peerDependencies": { - "@electric-sql/pglite": "0.3.15" - } - }, - "node_modules/@electric-sql/pglite-tools": { - "version": "0.2.20", - "resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.2.20.tgz", - "integrity": "sha512-BK50ZnYa3IG7ztXhtgYf0Q7zijV32Iw1cYS8C+ThdQlwx12V5VZ9KRJ42y82Hyb4PkTxZQklVQA9JHyUlex33A==", - "license": "Apache-2.0", - "peerDependencies": { - "@electric-sql/pglite": "0.3.15" - } - }, - "node_modules/@emnapi/core": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", - "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", - "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@hono/node-server": { - "version": "1.19.9", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", - "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", - "license": "MIT", - "engines": { - "node": ">=18.14.1" - }, - "peerDependencies": { - "hono": "^4" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@mrleebo/prisma-ast": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/@mrleebo/prisma-ast/-/prisma-ast-0.13.1.tgz", - "integrity": "sha512-XyroGQXcHrZdvmrGJvsA9KNeOOgGMg1Vg9OlheUsBOSKznLMDl+YChxbkboRHvtFYJEMRYmlV3uoo/njCw05iw==", - "license": "MIT", - "dependencies": { - "chevrotain": "^10.5.0", - "lilconfig": "^2.1.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@mrleebo/prisma-ast/node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" - } - }, - "node_modules/@next/env": { - "version": "14.2.35", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.35.tgz", - "integrity": "sha512-DuhvCtj4t9Gwrx80dmz2F4t/zKQ4ktN8WrMwOuVzkJfBilwAwGr6v16M5eI8yCuZ63H9TTuEU09Iu2HqkzFPVQ==", - "license": "MIT" - }, - "node_modules/@next/eslint-plugin-next": { - "version": "14.2.35", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.35.tgz", - "integrity": "sha512-Jw9A3ICz2183qSsqwi7fgq4SBPiNfmOLmTPXKvlnzstUwyvBrtySiY+8RXJweNAs9KThb1+bYhZh9XWcNOr2zQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob": "10.3.10" - } - }, - "node_modules/@next/swc-darwin-arm64": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.33.tgz", - "integrity": "sha512-HqYnb6pxlsshoSTubdXKu15g3iivcbsMXg4bYpjL2iS/V6aQot+iyF4BUc2qA/J/n55YtvE4PHMKWBKGCF/+wA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.33.tgz", - "integrity": "sha512-8HGBeAE5rX3jzKvF593XTTFg3gxeU4f+UWnswa6JPhzaR6+zblO5+fjltJWIZc4aUalqTclvN2QtTC37LxvZAA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.33.tgz", - "integrity": "sha512-JXMBka6lNNmqbkvcTtaX8Gu5by9547bukHQvPoLe9VRBx1gHwzf5tdt4AaezW85HAB3pikcvyqBToRTDA4DeLw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.33.tgz", - "integrity": "sha512-Bm+QulsAItD/x6Ih8wGIMfRJy4G73tu1HJsrccPW6AfqdZd0Sfm5Imhgkgq2+kly065rYMnCOxTBvmvFY1BKfg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.33.tgz", - "integrity": "sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.33.tgz", - "integrity": "sha512-345tsIWMzoXaQndUTDv1qypDRiebFxGYx9pYkhwY4hBRaOLt8UGfiWKr9FSSHs25dFIf8ZqIFaPdy5MljdoawA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.33.tgz", - "integrity": "sha512-nscpt0G6UCTkrT2ppnJnFsYbPDQwmum4GNXYTeoTIdsmMydSKFz9Iny2jpaRupTb+Wl298+Rh82WKzt9LCcqSQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.33.tgz", - "integrity": "sha512-pc9LpGNKhJ0dXQhZ5QMmYxtARwwmWLpeocFmVG5Z0DzWq5Uf0izcI8tLc+qOpqxO1PWqZ5A7J1blrUIKrIFc7Q==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.33.tgz", - "integrity": "sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nolyfill/is-core-module": { - "version": "1.0.39", - "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", - "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.4.0" - } - }, - "node_modules/@panva/hkdf": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", - "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@prisma/adapter-pg": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.4.2.tgz", - "integrity": "sha512-oUo2Zhe9Tf6YwVL8kLPuOLTK1Z2pwi/Ua77t2PuGyBan2w7shRKqHvYK+3XXmRH9RWhPJ4SMtHZKpNo6Ax/4bQ==", - "license": "Apache-2.0", - "dependencies": { - "@prisma/driver-adapter-utils": "7.4.2", - "pg": "^8.16.3", - "postgres-array": "3.0.4" - } - }, - "node_modules/@prisma/client": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.4.2.tgz", - "integrity": "sha512-ts2mu+cQHriAhSxngO3StcYubBGTWDtu/4juZhXCUKOwgh26l+s4KD3vT2kMUzFyrYnll9u/3qWrtzRv9CGWzA==", - "license": "Apache-2.0", - "dependencies": { - "@prisma/client-runtime-utils": "7.4.2" - }, - "engines": { - "node": "^20.19 || ^22.12 || >=24.0" - }, - "peerDependencies": { - "prisma": "*", - "typescript": ">=5.4.0" - }, - "peerDependenciesMeta": { - "prisma": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/@prisma/client-runtime-utils": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.4.2.tgz", - "integrity": "sha512-cID+rzOEb38VyMsx5LwJMEY4NGIrWCNpKu/0ImbeooQ2Px7TI+kOt7cm0NelxUzF2V41UVVXAmYjANZQtCu1/Q==", - "license": "Apache-2.0" - }, - "node_modules/@prisma/config": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.4.2.tgz", - "integrity": "sha512-CftBjWxav99lzY1Z4oDgomdb1gh9BJFAOmWF6P2v1xRfXqQb56DfBub+QKcERRdNoAzCb3HXy3Zii8Vb4AsXhg==", - "license": "Apache-2.0", - "dependencies": { - "c12": "3.1.0", - "deepmerge-ts": "7.1.5", - "effect": "3.18.4", - "empathic": "2.0.0" - } - }, - "node_modules/@prisma/debug": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.4.2.tgz", - "integrity": "sha512-aP7qzu+g/JnbF6U69LMwHoUkELiserKmWsE2shYuEpNUJ4GrtxBCvZwCyCBHFSH2kLTF2l1goBlBh4wuvRq62w==", - "license": "Apache-2.0" - }, - "node_modules/@prisma/dev": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.20.0.tgz", - "integrity": "sha512-ovlBYwWor0OzG+yH4J3Ot+AneD818BttLA+Ii7wjbcLHUrnC4tbUPVGyNd3c/+71KETPKZfjhkTSpdS15dmXNQ==", - "license": "ISC", - "dependencies": { - "@electric-sql/pglite": "0.3.15", - "@electric-sql/pglite-socket": "0.0.20", - "@electric-sql/pglite-tools": "0.2.20", - "@hono/node-server": "1.19.9", - "@mrleebo/prisma-ast": "0.13.1", - "@prisma/get-platform": "7.2.0", - "@prisma/query-plan-executor": "7.2.0", - "foreground-child": "3.3.1", - "get-port-please": "3.2.0", - "hono": "4.11.4", - "http-status-codes": "2.3.0", - "pathe": "2.0.3", - "proper-lockfile": "4.1.2", - "remeda": "2.33.4", - "std-env": "3.10.0", - "valibot": "1.2.0", - "zeptomatch": "2.1.0" - } - }, - "node_modules/@prisma/driver-adapter-utils": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.4.2.tgz", - "integrity": "sha512-REdjFpT/ye9KdDs+CXAXPIbMQkVLhne9G5Pe97sNY4Ovx4r2DAbWM9hOFvvB1Oq8H8bOCdu0Ri3AoGALquQqVw==", - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.4.2" - } - }, - "node_modules/@prisma/engines": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.4.2.tgz", - "integrity": "sha512-B+ZZhI4rXlzjVqRw/93AothEKOU5/x4oVyJFGo9RpHPnBwaPwk4Pi0Q4iGXipKxeXPs/dqljgNBjK0m8nocOJA==", - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.4.2", - "@prisma/engines-version": "7.5.0-10.94a226be1cf2967af2541cca5529f0f7ba866919", - "@prisma/fetch-engine": "7.4.2", - "@prisma/get-platform": "7.4.2" - } - }, - "node_modules/@prisma/engines-version": { - "version": "7.5.0-10.94a226be1cf2967af2541cca5529f0f7ba866919", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.5.0-10.94a226be1cf2967af2541cca5529f0f7ba866919.tgz", - "integrity": "sha512-5FIKY3KoYQlBuZC2yc16EXfVRQ8HY+fLqgxkYfWCtKhRb3ajCRzP/rPeoSx11+NueJDANdh4hjY36mdmrTcGSg==", - "license": "Apache-2.0" - }, - "node_modules/@prisma/engines/node_modules/@prisma/get-platform": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.4.2.tgz", - "integrity": "sha512-UTnChXRwiauzl/8wT4hhe7Xmixja9WE28oCnGpBtRejaHhvekx5kudr3R4Y9mLSA0kqGnAMeyTiKwDVMjaEVsw==", - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.4.2" - } - }, - "node_modules/@prisma/fetch-engine": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.4.2.tgz", - "integrity": "sha512-f/c/MwYpdJO7taLETU8rahEstLeXfYgQGlz5fycG7Fbmva3iPdzGmjiSWHeSWIgNnlXnelUdCJqyZnFocurZuA==", - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.4.2", - "@prisma/engines-version": "7.5.0-10.94a226be1cf2967af2541cca5529f0f7ba866919", - "@prisma/get-platform": "7.4.2" - } - }, - "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.4.2.tgz", - "integrity": "sha512-UTnChXRwiauzl/8wT4hhe7Xmixja9WE28oCnGpBtRejaHhvekx5kudr3R4Y9mLSA0kqGnAMeyTiKwDVMjaEVsw==", - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.4.2" - } - }, - "node_modules/@prisma/get-platform": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz", - "integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==", - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.2.0" - } - }, - "node_modules/@prisma/get-platform/node_modules/@prisma/debug": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz", - "integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==", - "license": "Apache-2.0" - }, - "node_modules/@prisma/query-plan-executor": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-7.2.0.tgz", - "integrity": "sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ==", - "license": "Apache-2.0" - }, - "node_modules/@prisma/studio-core": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.13.1.tgz", - "integrity": "sha512-agdqaPEePRHcQ7CexEfkX1RvSH9uWDb6pXrZnhCRykhDFAV0/0P3d07WtfiY8hZWb7oRU4v+NkT4cGFHkQJIPg==", - "license": "Apache-2.0", - "peerDependencies": { - "@types/react": "^18.0.0 || ^19.0.0", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rushstack/eslint-patch": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.16.1.tgz", - "integrity": "sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "license": "MIT" - }, - "node_modules/@stripe/stripe-js": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-8.8.0.tgz", - "integrity": "sha512-NNYuyW8qmLjyHnpyFgs/23wUrjB8k0xN9YIZFOMLewCa/pIkIji9e9aY/EgdNryEDDRptc6TcPIHRvG1R0ClFw==", - "license": "MIT", - "engines": { - "node": ">=12.16" - } - }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "license": "Apache-2.0" - }, - "node_modules/@swc/helpers": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", - "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", - "license": "Apache-2.0", - "dependencies": { - "@swc/counter": "^0.1.3", - "tslib": "^2.4.0" - } - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/bcryptjs": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", - "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", - "license": "MIT" - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "20.19.35", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.35.tgz", - "integrity": "sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/papaparse": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.5.2.tgz", - "integrity": "sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/pg": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.18.0.tgz", - "integrity": "sha512-gT+oueVQkqnj6ajGJXblFR4iavIXWsGAFCk3dP4Kki5+a9R4NMt0JARdk6s8cUKcfUoqP5dAtDSLU8xYUTFV+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "pg-protocol": "*", - "pg-types": "^2.2.0" - } - }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "license": "MIT" - }, - "node_modules/@types/qrcode": { - "version": "1.5.6", - "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz", - "integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/react": { - "version": "18.3.28", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", - "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", - "license": "MIT", - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "18.3.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", - "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^18.0.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", - "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/type-utils": "8.56.1", - "@typescript-eslint/utils": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.56.1", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", - "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", - "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.56.1", - "@typescript-eslint/types": "^8.56.1", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", - "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", - "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", - "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1", - "@typescript-eslint/utils": "8.56.1", - "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", - "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", - "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.56.1", - "@typescript-eslint/tsconfig-utils": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", - "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", - "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.56.1", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, - "license": "ISC" - }, - "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", - "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", - "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", - "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", - "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", - "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", - "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", - "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", - "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", - "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", - "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", - "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", - "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", - "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", - "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", - "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", - "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.11" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", - "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", - "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", - "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, - "license": "MIT" - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true, - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-includes": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", - "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.24.0", - "es-object-atoms": "^1.1.1", - "get-intrinsic": "^1.3.0", - "is-string": "^1.1.1", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.findlast": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", - "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", - "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-shim-unscopables": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", - "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", - "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.tosorted": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", - "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.3", - "es-errors": "^1.3.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/ast-types-flow": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", - "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/async-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/aws-ssl-profiles": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", - "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", - "license": "MIT", - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/axe-core": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.1.tgz", - "integrity": "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==", - "dev": true, - "license": "MPL-2.0", - "engines": { - "node": ">=4" - } - }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/bcryptjs": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", - "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", - "license": "BSD-3-Clause", - "bin": { - "bcrypt": "bin/bcrypt" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, - "node_modules/c12": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", - "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", - "license": "MIT", - "dependencies": { - "chokidar": "^4.0.3", - "confbox": "^0.2.2", - "defu": "^6.1.4", - "dotenv": "^16.6.1", - "exsolve": "^1.0.7", - "giget": "^2.0.0", - "jiti": "^2.4.2", - "ohash": "^2.0.11", - "pathe": "^2.0.3", - "perfect-debounce": "^1.0.0", - "pkg-types": "^2.2.0", - "rc9": "^2.1.2" - }, - "peerDependencies": { - "magicast": "^0.3.5" - }, - "peerDependenciesMeta": { - "magicast": { - "optional": true - } - } - }, - "node_modules/c12/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/c12/node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/c12/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001774", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz", - "integrity": "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chevrotain": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-10.5.0.tgz", - "integrity": "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/cst-dts-gen": "10.5.0", - "@chevrotain/gast": "10.5.0", - "@chevrotain/types": "10.5.0", - "@chevrotain/utils": "10.5.0", - "lodash": "4.17.21", - "regexp-to-ast": "0.5.0" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/citty": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", - "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", - "license": "MIT", - "dependencies": { - "consola": "^3.2.3" - } - }, - "node_modules/class-variance-authority": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", - "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", - "license": "Apache-2.0", - "dependencies": { - "clsx": "^2.1.1" - }, - "funding": { - "url": "https://polar.sh/cva" - } - }, - "node_modules/client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", - "license": "MIT" - }, - "node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/confbox": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", - "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", - "license": "MIT" - }, - "node_modules/consola": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "license": "MIT", - "engines": { - "node": "^14.18.0 || >=16.10.0" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" - }, - "node_modules/damerau-levenshtein": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", - "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/inspect-js" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/date-fns": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", - "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/kossnocorp" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/deepmerge-ts": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", - "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/defu": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", - "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", - "license": "MIT" - }, - "node_modules/denque": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", - "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/destr": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", - "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", - "license": "MIT" - }, - "node_modules/didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/dijkstrajs": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", - "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", - "license": "MIT" - }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true, - "license": "MIT" - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/effect": { - "version": "3.18.4", - "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz", - "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==", - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.0.0", - "fast-check": "^3.23.1" - } - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/empathic": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", - "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/es-abstract": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", - "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.3.0", - "get-proto": "^1.0.1", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.2.1", - "is-set": "^2.0.3", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.1", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.4", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "stop-iteration-iterator": "^1.1.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.19" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-iterator-helpers": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz", - "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.24.1", - "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.1.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.3.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "internal-slot": "^1.1.0", - "iterator.prototype": "^1.1.5", - "safe-array-concat": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-shim-unscopables": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", - "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-next": { - "version": "14.2.35", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.35.tgz", - "integrity": "sha512-BpLsv01UisH193WyT/1lpHqq5iJ/Orfz9h/NOOlAmTUq4GY349PextQ62K4XpnaM9supeiEn3TaOTeQO07gURg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@next/eslint-plugin-next": "14.2.35", - "@rushstack/eslint-patch": "^1.3.3", - "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", - "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-import-resolver-typescript": "^3.5.2", - "eslint-plugin-import": "^2.28.1", - "eslint-plugin-jsx-a11y": "^6.7.1", - "eslint-plugin-react": "^7.33.2", - "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" - }, - "peerDependencies": { - "eslint": "^7.23.0 || ^8.0.0", - "typescript": ">=3.3.1" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-import-resolver-typescript": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", - "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "@nolyfill/is-core-module": "1.0.39", - "debug": "^4.4.0", - "get-tsconfig": "^4.10.0", - "is-bun-module": "^2.0.0", - "stable-hash": "^0.0.5", - "tinyglobby": "^0.2.13", - "unrs-resolver": "^1.6.2" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-import-resolver-typescript" - }, - "peerDependencies": { - "eslint": "*", - "eslint-plugin-import": "*", - "eslint-plugin-import-x": "*" - }, - "peerDependenciesMeta": { - "eslint-plugin-import": { - "optional": true - }, - "eslint-plugin-import-x": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", - "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.32.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", - "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rtsao/scc": "^1.1.0", - "array-includes": "^3.1.9", - "array.prototype.findlastindex": "^1.2.6", - "array.prototype.flat": "^1.3.3", - "array.prototype.flatmap": "^1.3.3", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.12.1", - "hasown": "^2.0.2", - "is-core-module": "^2.16.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.8", - "object.groupby": "^1.0.3", - "object.values": "^1.2.1", - "semver": "^6.3.1", - "string.prototype.trimend": "^1.0.9", - "tsconfig-paths": "^3.15.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-plugin-jsx-a11y": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", - "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "aria-query": "^5.3.2", - "array-includes": "^3.1.8", - "array.prototype.flatmap": "^1.3.2", - "ast-types-flow": "^0.0.8", - "axe-core": "^4.10.0", - "axobject-query": "^4.1.0", - "damerau-levenshtein": "^1.0.8", - "emoji-regex": "^9.2.2", - "hasown": "^2.0.2", - "jsx-ast-utils": "^3.3.5", - "language-tags": "^1.0.9", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.8", - "safe-regex-test": "^1.0.3", - "string.prototype.includes": "^2.0.1" - }, - "engines": { - "node": ">=4.0" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" - } - }, - "node_modules/eslint-plugin-react": { - "version": "7.37.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", - "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.1.8", - "array.prototype.findlast": "^1.2.5", - "array.prototype.flatmap": "^1.3.3", - "array.prototype.tosorted": "^1.1.4", - "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.2.1", - "estraverse": "^5.3.0", - "hasown": "^2.0.2", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.9", - "object.fromentries": "^2.0.8", - "object.values": "^1.2.1", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.5", - "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.12", - "string.prototype.repeat": "^1.0.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "5.0.0-canary-7118f5dd7-20230705", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0-canary-7118f5dd7-20230705.tgz", - "integrity": "sha512-AZYbMo/NW9chdL7vk6HQzQhT+PvTAEVqWk9ziruUoW2kAOcN5qNyelv70e0F1VNQAbvutOC9oc+xfWycI9FxDw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.6", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", - "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "is-core-module": "^2.16.1", - "node-exports-info": "^1.6.0", - "object-keys": "^1.1.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/eslint-plugin-react/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/exsolve": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", - "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", - "license": "MIT" - }, - "node_modules/fast-check": { - "version": "3.23.2", - "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", - "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT", - "dependencies": { - "pure-rand": "^6.1.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" - }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/function.prototype.name": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/generate-function": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", - "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", - "license": "MIT", - "dependencies": { - "is-property": "^1.0.2" - } - }, - "node_modules/generator-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", - "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-port-please": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.2.0.tgz", - "integrity": "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==", - "license": "MIT" - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-symbol-description": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", - "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-tsconfig": { - "version": "4.13.6", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", - "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/giget": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", - "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", - "license": "MIT", - "dependencies": { - "citty": "^0.1.6", - "consola": "^3.4.0", - "defu": "^6.1.4", - "node-fetch-native": "^1.6.6", - "nypm": "^0.6.0", - "pathe": "^2.0.3" - }, - "bin": { - "giget": "dist/cli.mjs" - } - }, - "node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/grammex": { - "version": "3.1.12", - "resolved": "https://registry.npmjs.org/grammex/-/grammex-3.1.12.tgz", - "integrity": "sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ==", - "license": "MIT" - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "node_modules/graphmatch": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/graphmatch/-/graphmatch-1.1.1.tgz", - "integrity": "sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==", - "license": "MIT" - }, - "node_modules/has-bigints": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", - "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hono": { - "version": "4.11.4", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.4.tgz", - "integrity": "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==", - "license": "MIT", - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/http-status-codes": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", - "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==", - "license": "MIT" - }, - "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/internal-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-async-function": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", - "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bun-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", - "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.7.1" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-view": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-finalizationregistry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-function": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", - "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.4", - "generator-function": "^2.0.0", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", - "license": "MIT" - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", - "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/iterator.prototype": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", - "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "get-proto": "^1.0.0", - "has-symbols": "^1.1.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jiti": { - "version": "1.21.7", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", - "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "bin/jiti.js" - } - }, - "node_modules/jose": { - "version": "4.15.9", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", - "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/jsx-ast-utils": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", - "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "object.assign": "^4.1.4", - "object.values": "^1.1.6" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/language-subtag-registry": { - "version": "0.3.23", - "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", - "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/language-tags": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", - "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", - "dev": true, - "license": "MIT", - "dependencies": { - "language-subtag-registry": "^0.3.20" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "license": "Apache-2.0" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/lru.min": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", - "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", - "license": "MIT", - "engines": { - "bun": ">=1.0.0", - "deno": ">=1.30.0", - "node": ">=8.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wellwelwel" - } - }, - "node_modules/lucide-react": { - "version": "0.575.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.575.0.tgz", - "integrity": "sha512-VuXgKZrk0uiDlWjGGXmKV6MSk9Yy4l10qgVvzGn2AWBx1Ylt0iBexKOAoA6I7JO3m+M9oeovJd3yYENfkUbOeg==", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/mysql2": { - "version": "3.15.3", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz", - "integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==", - "license": "MIT", - "dependencies": { - "aws-ssl-profiles": "^1.1.1", - "denque": "^2.1.0", - "generate-function": "^2.3.1", - "iconv-lite": "^0.7.0", - "long": "^5.2.1", - "lru.min": "^1.0.0", - "named-placeholders": "^1.1.3", - "seq-queue": "^0.0.5", - "sqlstring": "^2.3.2" - }, - "engines": { - "node": ">= 8.0" - } - }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/named-placeholders": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", - "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", - "license": "MIT", - "dependencies": { - "lru.min": "^1.1.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/nanoid": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", - "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" - }, - "engines": { - "node": "^18 || >=20" - } - }, - "node_modules/napi-postinstall": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", - "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", - "dev": true, - "license": "MIT", - "bin": { - "napi-postinstall": "lib/cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/napi-postinstall" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/next": { - "version": "14.2.35", - "resolved": "https://registry.npmjs.org/next/-/next-14.2.35.tgz", - "integrity": "sha512-KhYd2Hjt/O1/1aZVX3dCwGXM1QmOV4eNM2UTacK5gipDdPN/oHHK/4oVGy7X8GMfPMsUTUEmGlsy0EY1YGAkig==", - "license": "MIT", - "dependencies": { - "@next/env": "14.2.35", - "@swc/helpers": "0.5.5", - "busboy": "1.6.0", - "caniuse-lite": "^1.0.30001579", - "graceful-fs": "^4.2.11", - "postcss": "8.4.31", - "styled-jsx": "5.1.1" - }, - "bin": { - "next": "dist/bin/next" - }, - "engines": { - "node": ">=18.17.0" - }, - "optionalDependencies": { - "@next/swc-darwin-arm64": "14.2.33", - "@next/swc-darwin-x64": "14.2.33", - "@next/swc-linux-arm64-gnu": "14.2.33", - "@next/swc-linux-arm64-musl": "14.2.33", - "@next/swc-linux-x64-gnu": "14.2.33", - "@next/swc-linux-x64-musl": "14.2.33", - "@next/swc-win32-arm64-msvc": "14.2.33", - "@next/swc-win32-ia32-msvc": "14.2.33", - "@next/swc-win32-x64-msvc": "14.2.33" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.1.0", - "@playwright/test": "^1.41.2", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "sass": "^1.3.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "@playwright/test": { - "optional": true - }, - "sass": { - "optional": true - } - } - }, - "node_modules/next-auth": { - "version": "4.24.13", - "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.13.tgz", - "integrity": "sha512-sgObCfcfL7BzIK76SS5TnQtc3yo2Oifp/yIpfv6fMfeBOiBJkDWF3A2y9+yqnmJ4JKc2C+nMjSjmgDeTwgN1rQ==", - "license": "ISC", - "dependencies": { - "@babel/runtime": "^7.20.13", - "@panva/hkdf": "^1.0.2", - "cookie": "^0.7.0", - "jose": "^4.15.5", - "oauth": "^0.9.15", - "openid-client": "^5.4.0", - "preact": "^10.6.3", - "preact-render-to-string": "^5.1.19", - "uuid": "^8.3.2" - }, - "peerDependencies": { - "@auth/core": "0.34.3", - "next": "^12.2.5 || ^13 || ^14 || ^15 || ^16", - "nodemailer": "^7.0.7", - "react": "^17.0.2 || ^18 || ^19", - "react-dom": "^17.0.2 || ^18 || ^19" - }, - "peerDependenciesMeta": { - "@auth/core": { - "optional": true - }, - "nodemailer": { - "optional": true - } - } - }, - "node_modules/next/node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/next/node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/node-exports-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", - "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "array.prototype.flatmap": "^1.3.3", - "es-errors": "^1.3.0", - "object.entries": "^1.1.9", - "semver": "^6.3.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/node-exports-info/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/node-fetch-native": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", - "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nypm": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.5.tgz", - "integrity": "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==", - "license": "MIT", - "dependencies": { - "citty": "^0.2.0", - "pathe": "^2.0.3", - "tinyexec": "^1.0.2" - }, - "bin": { - "nypm": "dist/cli.mjs" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/nypm/node_modules/citty": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.1.tgz", - "integrity": "sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==", - "license": "MIT" - }, - "node_modules/oauth": { - "version": "0.9.15", - "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", - "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==", - "license": "MIT" - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.entries": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", - "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.fromentries": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", - "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.groupby": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", - "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.values": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", - "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/ohash": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", - "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", - "license": "MIT" - }, - "node_modules/oidc-token-hash": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.2.0.tgz", - "integrity": "sha512-6gj2m8cJZ+iSW8bm0FXdGF0YhIQbKrfP4yWTNzxc31U6MOjfEmB1rHvlYvxI1B7t7BCi1F2vYTT6YhtQRG4hxw==", - "license": "MIT", - "engines": { - "node": "^10.13.0 || >=12.0.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/openid-client": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.7.1.tgz", - "integrity": "sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew==", - "license": "MIT", - "dependencies": { - "jose": "^4.15.9", - "lru-cache": "^6.0.0", - "object-hash": "^2.2.0", - "oidc-token-hash": "^5.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/openid-client/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/openid-client/node_modules/object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/own-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", - "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.6", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/papaparse": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.3.tgz", - "integrity": "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==", - "license": "MIT" - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "license": "MIT" - }, - "node_modules/perfect-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", - "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", - "license": "MIT" - }, - "node_modules/pg": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.19.0.tgz", - "integrity": "sha512-QIcLGi508BAHkQ3pJNptsFz5WQMlpGbuBGBaIaXsWK8mel2kQ/rThYI+DbgjUvZrIr7MiuEuc9LcChJoEZK1xQ==", - "license": "MIT", - "dependencies": { - "pg-connection-string": "^2.11.0", - "pg-pool": "^3.12.0", - "pg-protocol": "^1.12.0", - "pg-types": "2.2.0", - "pgpass": "1.0.5" - }, - "engines": { - "node": ">= 16.0.0" - }, - "optionalDependencies": { - "pg-cloudflare": "^1.3.0" - }, - "peerDependencies": { - "pg-native": ">=3.0.1" - }, - "peerDependenciesMeta": { - "pg-native": { - "optional": true - } - } - }, - "node_modules/pg-cloudflare": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz", - "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==", - "license": "MIT", - "optional": true - }, - "node_modules/pg-connection-string": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.11.0.tgz", - "integrity": "sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ==", - "license": "MIT" - }, - "node_modules/pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", - "license": "ISC", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/pg-pool": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.12.0.tgz", - "integrity": "sha512-eIJ0DES8BLaziFHW7VgJEBPi5hg3Nyng5iKpYtj3wbcAUV9A1wLgWiY7ajf/f/oO1wfxt83phXPY8Emztg7ITg==", - "license": "MIT", - "peerDependencies": { - "pg": ">=8.0" - } - }, - "node_modules/pg-protocol": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.12.0.tgz", - "integrity": "sha512-uOANXNRACNdElMXJ0tPz6RBM0XQ61nONGAwlt8da5zs/iUOOCLBQOHSXnrC6fMsvtjxbOJrZZl5IScGv+7mpbg==", - "license": "MIT" - }, - "node_modules/pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "license": "MIT", - "dependencies": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pg-types/node_modules/postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/pgpass": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", - "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", - "license": "MIT", - "dependencies": { - "split2": "^4.1.0" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-types": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", - "license": "MIT", - "dependencies": { - "confbox": "^0.2.2", - "exsolve": "^1.0.7", - "pathe": "^2.0.3" - } - }, - "node_modules/pngjs": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", - "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-js": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", - "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "peerDependencies": { - "postcss": "^8.4.21" - } - }, - "node_modules/postcss-load-config": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", - "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "lilconfig": "^3.1.1" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "jiti": ">=1.21.0", - "postcss": ">=8.0.9", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - }, - "postcss": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/postcss-nested": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", - "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.1.1" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/postgres": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz", - "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==", - "license": "Unlicense", - "engines": { - "node": ">=12" - }, - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/porsager" - } - }, - "node_modules/postgres-array": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz", - "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/postgres-bytea": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", - "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "license": "MIT", - "dependencies": { - "xtend": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/preact": { - "version": "10.24.3", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz", - "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, - "node_modules/preact-render-to-string": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz", - "integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==", - "license": "MIT", - "dependencies": { - "pretty-format": "^3.8.0" - }, - "peerDependencies": { - "preact": ">=10" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/pretty-format": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", - "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==", - "license": "MIT" - }, - "node_modules/prisma": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.4.2.tgz", - "integrity": "sha512-2bP8Ruww3Q95Z2eH4Yqh4KAENRsj/SxbdknIVBfd6DmjPwmpsC4OVFMLOeHt6tM3Amh8ebjvstrUz3V/hOe1dA==", - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/config": "7.4.2", - "@prisma/dev": "0.20.0", - "@prisma/engines": "7.4.2", - "@prisma/studio-core": "0.13.1", - "mysql2": "3.15.3", - "postgres": "3.4.7" - }, - "bin": { - "prisma": "build/index.js" - }, - "engines": { - "node": "^20.19 || ^22.12 || >=24.0" - }, - "peerDependencies": { - "better-sqlite3": ">=9.0.0", - "typescript": ">=5.4.0" - }, - "peerDependenciesMeta": { - "better-sqlite3": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, - "license": "MIT", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/proper-lockfile": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", - "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } - }, - "node_modules/proper-lockfile/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC" - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/pure-rand": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", - "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, - "node_modules/qrcode": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", - "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", - "license": "MIT", - "dependencies": { - "dijkstrajs": "^1.0.1", - "pngjs": "^5.0.0", - "yargs": "^15.3.1" - }, - "bin": { - "qrcode": "bin/qrcode" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/rc9": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", - "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", - "license": "MIT", - "dependencies": { - "defu": "^6.1.4", - "destr": "^2.0.3" - } - }, - "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" - }, - "peerDependencies": { - "react": "^18.3.1" - } - }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^2.3.0" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", - "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexp-to-ast": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", - "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==", - "license": "MIT" - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", - "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/remeda": { - "version": "2.33.4", - "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.33.4.tgz", - "integrity": "sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/remeda" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "license": "ISC" - }, - "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-push-apply": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", - "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/seq-queue": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", - "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "license": "ISC" - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", - "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/sqlstring": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", - "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/stable-hash": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", - "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", - "license": "MIT" - }, - "node_modules/stop-iteration-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", - "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "internal-slot": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/string.prototype.includes": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", - "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.3" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/string.prototype.matchall": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", - "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.6", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "internal-slot": "^1.1.0", - "regexp.prototype.flags": "^1.5.3", - "set-function-name": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.repeat": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", - "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/stripe": { - "version": "20.4.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-20.4.0.tgz", - "integrity": "sha512-F/aN1IQ9vHmlyLNi3DkiIbyzQb6gyBG0uYFd/VrEVQSc9BLtlgknPUx0EvzZdBMRLFuRaPFIFd7Mxwtg7Pbwzw==", - "license": "MIT", - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "@types/node": ">=16" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/styled-jsx": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", - "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", - "license": "MIT", - "dependencies": { - "client-only": "0.0.1" - }, - "engines": { - "node": ">= 12.0.0" - }, - "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/sucrase": { - "version": "3.35.1", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", - "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "tinyglobby": "^0.2.11", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tailwind-merge": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz", - "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/dcastil" - } - }, - "node_modules/tailwindcss": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", - "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.6.0", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.3.2", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.21.7", - "lilconfig": "^3.1.3", - "micromatch": "^4.0.8", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.4.47", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", - "postcss-nested": "^6.2.0", - "postcss-selector-parser": "^6.1.2", - "resolve": "^1.22.8", - "sucrase": "^3.35.0" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/tsconfig-paths": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", - "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/tsx": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", - "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.27.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", - "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", - "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/unbox-primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", - "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "license": "MIT" - }, - "node_modules/unrs-resolver": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", - "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "napi-postinstall": "^0.3.0" - }, - "funding": { - "url": "https://opencollective.com/unrs-resolver" - }, - "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.11.1", - "@unrs/resolver-binding-android-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-x64": "1.11.1", - "@unrs/resolver-binding-freebsd-x64": "1.11.1", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", - "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-musl": "1.11.1", - "@unrs/resolver-binding-wasm32-wasi": "1.11.1", - "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", - "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", - "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/valibot": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz", - "integrity": "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==", - "license": "MIT", - "peerDependencies": { - "typescript": ">=5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", - "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", - "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "function.prototype.name": "^1.1.6", - "has-tostringtag": "^1.0.2", - "is-async-function": "^2.0.0", - "is-date-object": "^1.1.0", - "is-finalizationregistry": "^1.1.0", - "is-generator-function": "^1.0.10", - "is-regex": "^1.2.1", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.1.0", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "license": "ISC" - }, - "node_modules/which-typed-array": { - "version": "1.1.20", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", - "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "license": "ISC" - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, - "node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "license": "MIT", - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "license": "ISC", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/yargs/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zeptomatch": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.1.0.tgz", - "integrity": "sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==", - "license": "MIT", - "dependencies": { - "grammex": "^3.1.11", - "graphmatch": "^1.1.0" - } - }, - "node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/pledge-now-pay-later/package.json b/pledge-now-pay-later/package.json deleted file mode 100644 index 88cb47a..0000000 --- a/pledge-now-pay-later/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "pledge-now-pay-later", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "next lint" - }, - "dependencies": { - "@auth/prisma-adapter": "^2.11.1", - "@prisma/adapter-pg": "^7.4.2", - "@prisma/client": "^7.4.2", - "@stripe/stripe-js": "^8.8.0", - "@types/bcryptjs": "^2.4.6", - "@types/qrcode": "^1.5.6", - "bcryptjs": "^3.0.3", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "date-fns": "^4.1.0", - "lucide-react": "^0.575.0", - "nanoid": "^5.1.6", - "next": "14.2.35", - "next-auth": "^4.24.13", - "papaparse": "^5.5.3", - "pg": "^8.19.0", - "prisma": "^7.4.2", - "qrcode": "^1.5.4", - "react": "^18", - "react-dom": "^18", - "stripe": "^20.4.0", - "tailwind-merge": "^3.5.0", - "zod": "^4.3.6" - }, - "devDependencies": { - "@types/node": "^20", - "@types/papaparse": "^5.5.2", - "@types/pg": "^8.18.0", - "@types/react": "^18", - "@types/react-dom": "^18", - "eslint": "^8", - "eslint-config-next": "14.2.35", - "postcss": "^8", - "tailwindcss": "^3.4.1", - "tsx": "^4.21.0", - "typescript": "^5" - } -} diff --git a/pledge-now-pay-later/postcss.config.mjs b/pledge-now-pay-later/postcss.config.mjs deleted file mode 100644 index 1a69fd2..0000000 --- a/pledge-now-pay-later/postcss.config.mjs +++ /dev/null @@ -1,8 +0,0 @@ -/** @type {import('postcss-load-config').Config} */ -const config = { - plugins: { - tailwindcss: {}, - }, -}; - -export default config; diff --git a/pledge-now-pay-later/prisma.config.ts b/pledge-now-pay-later/prisma.config.ts deleted file mode 100644 index dfa9dc3..0000000 --- a/pledge-now-pay-later/prisma.config.ts +++ /dev/null @@ -1,15 +0,0 @@ -// This file was generated by Prisma, and assumes you have installed the following: -// npm install --save-dev prisma dotenv -import "dotenv/config"; -import { defineConfig } from "prisma/config"; - -export default defineConfig({ - schema: "prisma/schema.prisma", - migrations: { - path: "prisma/migrations", - seed: "bun prisma/seed.mts", - }, - datasource: { - url: process.env["DATABASE_URL"], - }, -}); diff --git a/pledge-now-pay-later/prisma/schema.prisma b/pledge-now-pay-later/prisma/schema.prisma deleted file mode 100644 index 7a7aafc..0000000 --- a/pledge-now-pay-later/prisma/schema.prisma +++ /dev/null @@ -1,208 +0,0 @@ -generator client { - provider = "prisma-client" - output = "../src/generated/prisma" -} - -datasource db { - provider = "postgresql" -} - -model Organization { - id String @id @default(cuid()) - name String - slug String @unique - country String @default("UK") - timezone String @default("Europe/London") - bankName String? - bankSortCode String? - bankAccountNo String? - bankAccountName String? - refPrefix String @default("PNPL") - logo String? - primaryColor String @default("#1e40af") - gcAccessToken String? - gcEnvironment String @default("sandbox") - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - users User[] - events Event[] - pledges Pledge[] - imports Import[] - - @@index([slug]) -} - -model User { - id String @id @default(cuid()) - email String @unique - name String? - hashedPassword String? - role String @default("staff") // super_admin, org_admin, staff, volunteer - organizationId String - organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - @@index([organizationId]) -} - -model Event { - id String @id @default(cuid()) - name String - slug String - description String? - eventDate DateTime? - location String? - goalAmount Int? // in pence - currency String @default("GBP") - status String @default("active") // draft, active, closed, archived - organizationId String - organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - qrSources QrSource[] - pledges Pledge[] - - @@unique([organizationId, slug]) - @@index([organizationId, status]) -} - -model QrSource { - id String @id @default(cuid()) - label String // "Table 5", "Volunteer: Ahmed" - code String @unique // short token for URL - volunteerName String? - tableName String? - eventId String - event Event @relation(fields: [eventId], references: [id], onDelete: Cascade) - scanCount Int @default(0) - createdAt DateTime @default(now()) - - pledges Pledge[] - - @@index([eventId]) - @@index([code]) -} - -model Pledge { - id String @id @default(cuid()) - reference String @unique // human-safe bank ref e.g. "PNPL-7K4P-50" - amountPence Int - currency String @default("GBP") - rail String // bank, gocardless, card - status String @default("new") // new, initiated, paid, overdue, cancelled - donorName String? - donorEmail String? - donorPhone String? - giftAid Boolean @default(false) - iPaidClickedAt DateTime? - notes String? - - eventId String - event Event @relation(fields: [eventId], references: [id]) - qrSourceId String? - qrSource QrSource? @relation(fields: [qrSourceId], references: [id]) - organizationId String - organization Organization @relation(fields: [organizationId], references: [id]) - - paymentInstruction PaymentInstruction? - payments Payment[] - reminders Reminder[] - - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - paidAt DateTime? - cancelledAt DateTime? - - @@index([organizationId, status]) - @@index([reference]) - @@index([eventId, status]) - @@index([donorEmail]) - @@index([donorPhone]) -} - -model PaymentInstruction { - id String @id @default(cuid()) - pledgeId String @unique - pledge Pledge @relation(fields: [pledgeId], references: [id], onDelete: Cascade) - bankReference String // the unique ref to use - bankDetails Json // {sortCode, accountNo, accountName, bankName} - gcMandateId String? - gcMandateUrl String? - sentAt DateTime? - createdAt DateTime @default(now()) - - @@index([bankReference]) -} - -model Payment { - id String @id @default(cuid()) - pledgeId String - pledge Pledge @relation(fields: [pledgeId], references: [id], onDelete: Cascade) - provider String // bank, gocardless, stripe - providerRef String? // external ID - amountPence Int - status String @default("pending") // pending, confirmed, failed - matchedBy String? // auto, manual - receivedAt DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - importId String? - import Import? @relation(fields: [importId], references: [id]) - - @@index([pledgeId]) - @@index([providerRef]) -} - -model Reminder { - id String @id @default(cuid()) - pledgeId String - pledge Pledge @relation(fields: [pledgeId], references: [id], onDelete: Cascade) - step Int // 0=instructions, 1=nudge, 2=urgency, 3=final - channel String @default("email") // email, sms, whatsapp - scheduledAt DateTime - sentAt DateTime? - status String @default("pending") // pending, sent, skipped, failed - payload Json? - createdAt DateTime @default(now()) - - @@index([pledgeId]) - @@index([scheduledAt, status]) -} - -model Import { - id String @id @default(cuid()) - organizationId String - organization Organization @relation(fields: [organizationId], references: [id]) - kind String // bank_statement, gocardless_export, crm_export - fileName String? - rowCount Int @default(0) - matchedCount Int @default(0) - unmatchedCount Int @default(0) - mappingConfig Json? - stats Json? - status String @default("pending") // pending, processing, completed, failed - uploadedAt DateTime @default(now()) - - payments Payment[] - - @@index([organizationId]) -} - -model AnalyticsEvent { - id String @id @default(cuid()) - eventType String // pledge_start, amount_selected, rail_selected, identity_submitted, pledge_completed, instruction_copy_clicked, i_paid_clicked, payment_matched - pledgeId String? - eventId String? - qrSourceId String? - metadata Json? - createdAt DateTime @default(now()) - - @@index([eventType]) - @@index([pledgeId]) - @@index([eventId]) - @@index([createdAt]) -} diff --git a/pledge-now-pay-later/prisma/seed.mts b/pledge-now-pay-later/prisma/seed.mts deleted file mode 100644 index 6cc4277..0000000 --- a/pledge-now-pay-later/prisma/seed.mts +++ /dev/null @@ -1,306 +0,0 @@ -import "dotenv/config" -import pg from "pg" -import { PrismaPg } from "@prisma/adapter-pg" -import { PrismaClient } from "../src/generated/prisma/client.ts" - -const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL }) -const adapter = new PrismaPg(pool) -const prisma = new PrismaClient({ adapter }) - -function daysFromNow(days: number): Date { - return new Date(Date.now() + days * 86400000) -} - -function daysAgo(days: number): Date { - return new Date(Date.now() - days * 86400000) -} - -async function main() { - // ── Organisation ── - const org = await prisma.organization.upsert({ - where: { slug: "demo-charity" }, - update: { - bankName: "Barclays", - bankSortCode: "20-00-00", - bankAccountNo: "12345678", - bankAccountName: "Charity Right", - }, - create: { - name: "Charity Right", - slug: "demo-charity", - country: "UK", - timezone: "Europe/London", - bankName: "Barclays", - bankSortCode: "20-00-00", - bankAccountNo: "12345678", - bankAccountName: "Charity Right", - refPrefix: "DEMO", - primaryColor: "#1e40af", - }, - }) - - // ── Admin user ── - await prisma.user.upsert({ - where: { email: "admin@charityright.org" }, - update: {}, - create: { - email: "admin@charityright.org", - name: "Azreen Jamal", - role: "org_admin", - organizationId: org.id, - }, - }) - - // ── Events ── - const galaEvent = await prisma.event.upsert({ - where: { organizationId_slug: { organizationId: org.id, slug: "ramadan-gala-2026" } }, - update: { name: "Ramadan Gala 2026", eventDate: daysFromNow(14), goalAmount: 5000000 }, - create: { - name: "Ramadan Gala 2026", - slug: "ramadan-gala-2026", - description: "Annual fundraising gala dinner — all proceeds support orphan education in Bangladesh, Pakistan, and Syria.", - eventDate: daysFromNow(14), - location: "Bradford Hilton, Hall Lane, BD1 4QR", - goalAmount: 5000000, // £50,000 - currency: "GBP", - status: "active", - organizationId: org.id, - }, - }) - - const eidEvent = await prisma.event.upsert({ - where: { organizationId_slug: { organizationId: org.id, slug: "eid-community-lunch-2026" } }, - update: {}, - create: { - name: "Eid Community Lunch 2026", - slug: "eid-community-lunch-2026", - description: "Community lunch and fundraiser for local food bank programme.", - eventDate: daysFromNow(45), - location: "East London Mosque, Whitechapel Road, E1 1JX", - goalAmount: 1500000, // £15,000 - currency: "GBP", - status: "active", - organizationId: org.id, - }, - }) - - // ── QR Sources for Gala ── - const qrCodes = [ - { label: "Table 1 - Ahmed", volunteerName: "Ahmed Khan", tableName: "Table 1", code: "gala-tbl1" }, - { label: "Table 2 - Fatima", volunteerName: "Fatima Patel", tableName: "Table 2", code: "gala-tbl2" }, - { label: "Table 3 - Yusuf", volunteerName: "Yusuf Ali", tableName: "Table 3", code: "gala-tbl3" }, - { label: "Table 4 - Khadijah", volunteerName: "Khadijah Begum", tableName: "Table 4", code: "gala-tbl4" }, - { label: "Table 5 - Omar", volunteerName: "Omar Malik", tableName: "Table 5", code: "gala-tbl5" }, - { label: "Main Entrance", volunteerName: null, tableName: null, code: "gala-entrance" }, - { label: "Stage Banner", volunteerName: null, tableName: null, code: "gala-stage" }, - { label: "Online Link", volunteerName: null, tableName: null, code: "gala-online" }, - ] - - const qrSourceIds: Record = {} - for (const qr of qrCodes) { - const source = await prisma.qrSource.upsert({ - where: { code: qr.code }, - update: { label: qr.label, volunteerName: qr.volunteerName, scanCount: Math.floor(Math.random() * 40) + 5 }, - create: { - label: qr.label, - code: qr.code, - volunteerName: qr.volunteerName, - tableName: qr.tableName, - eventId: galaEvent.id, - scanCount: Math.floor(Math.random() * 40) + 5, - }, - }) - qrSourceIds[qr.code] = source.id - } - - // ── QR Sources for Eid ── - const eidQrs = [ - { label: "Registration Desk", volunteerName: "Ibrahim Hassan", tableName: null, code: "eid-reg" }, - { label: "Online Link", volunteerName: null, tableName: null, code: "eid-online" }, - ] - for (const qr of eidQrs) { - await prisma.qrSource.upsert({ - where: { code: qr.code }, - update: {}, - create: { - label: qr.label, - code: qr.code, - volunteerName: qr.volunteerName, - tableName: qr.tableName, - eventId: eidEvent.id, - scanCount: Math.floor(Math.random() * 10) + 2, - }, - }) - } - - // ── Sample Pledges ── - const samplePledges = [ - // Paid pledges - { name: "Sarah Khan", email: "sarah@example.com", phone: "07700900001", amount: 10000, rail: "bank", status: "paid", giftAid: true, qr: "gala-tbl1", daysAgo: 5 }, - { name: "Ali Hassan", email: "ali.hassan@gmail.com", phone: "07700900002", amount: 25000, rail: "bank", status: "paid", giftAid: false, qr: "gala-tbl1", daysAgo: 4 }, - { name: "Amina Begum", email: "amina.b@hotmail.com", phone: "", amount: 5000, rail: "card", status: "paid", giftAid: true, qr: "gala-tbl2", daysAgo: 3 }, - { name: "Mohammed Raza", email: "m.raza@outlook.com", phone: "07700900004", amount: 50000, rail: "gocardless", status: "paid", giftAid: true, qr: "gala-stage", daysAgo: 6 }, - { name: "Zainab Ahmed", email: "zainab@example.com", phone: "", amount: 10000, rail: "bank", status: "paid", giftAid: false, qr: "gala-tbl3", daysAgo: 7 }, - { name: "Hassan Malik", email: "hassan.malik@gmail.com", phone: "07700900006", amount: 20000, rail: "card", status: "paid", giftAid: true, qr: "gala-entrance", daysAgo: 2 }, - - // Initiated (payment in progress) - { name: "Ruqayyah Patel", email: "ruqayyah@example.com", phone: "07700900007", amount: 15000, rail: "bank", status: "initiated", giftAid: true, qr: "gala-tbl4", daysAgo: 1 }, - { name: "Ibrahim Shah", email: "ibrahim.shah@gmail.com", phone: "", amount: 10000, rail: "gocardless", status: "initiated", giftAid: false, qr: "gala-tbl5", daysAgo: 1 }, - - // New pledges (just created) - { name: "Maryam Siddiqui", email: "maryam.s@yahoo.com", phone: "07700900009", amount: 5000, rail: "bank", status: "new", giftAid: false, qr: "gala-tbl2", daysAgo: 0 }, - { name: "Usman Chaudhry", email: "usman.c@gmail.com", phone: "", amount: 100000, rail: "bank", status: "new", giftAid: true, qr: "gala-entrance", daysAgo: 0 }, - { name: "Aisha Rahman", email: "aisha.r@hotmail.com", phone: "07700900011", amount: 7500, rail: "card", status: "new", giftAid: true, qr: "gala-online", daysAgo: 0 }, - { name: null, email: "anon.donor@gmail.com", phone: "", amount: 20000, rail: "bank", status: "new", giftAid: false, qr: "gala-tbl3", daysAgo: 0 }, - - // Overdue - { name: "Tariq Hussain", email: "tariq.h@example.com", phone: "07700900013", amount: 25000, rail: "bank", status: "overdue", giftAid: true, qr: "gala-tbl1", daysAgo: 12 }, - { name: "Nadia Akhtar", email: "nadia.a@outlook.com", phone: "", amount: 10000, rail: "bank", status: "overdue", giftAid: false, qr: "gala-tbl5", daysAgo: 10 }, - - // Cancelled - { name: "Omar Farooq", email: "omar.f@gmail.com", phone: "07700900015", amount: 5000, rail: "card", status: "cancelled", giftAid: false, qr: "gala-tbl4", daysAgo: 8 }, - - // FPX pledge (Malaysian donor) - { name: "Ahmad bin Abdullah", email: "ahmad@example.my", phone: "+60123456789", amount: 50000, rail: "fpx", status: "paid", giftAid: false, qr: "gala-online", daysAgo: 3 }, - - // Eid event pledges - { name: "Hafsa Nawaz", email: "hafsa@example.com", phone: "07700900017", amount: 5000, rail: "bank", status: "new", giftAid: true, qr: null, daysAgo: 1 }, - { name: "Bilal Iqbal", email: "bilal.i@gmail.com", phone: "", amount: 10000, rail: "gocardless", status: "paid", giftAid: false, qr: null, daysAgo: 5 }, - ] - - let pledgeIndex = 0 - for (const p of samplePledges) { - pledgeIndex++ - const ref = `DEMO-SEED${String(pledgeIndex).padStart(2, "0")}-${Math.floor(p.amount / 100)}` - const isEid = p.qr === null - const eventId = isEid ? eidEvent.id : galaEvent.id - const createdAt = daysAgo(p.daysAgo) - const paidAt = p.status === "paid" ? daysAgo(Math.max(p.daysAgo - 1, 0)) : null - - // Skip if reference already exists - const existing = await prisma.pledge.findUnique({ where: { reference: ref } }) - if (existing) continue - - const pledge = await prisma.pledge.create({ - data: { - reference: ref, - amountPence: p.amount, - currency: "GBP", - rail: p.rail, - status: p.status, - donorName: p.name, - donorEmail: p.email || null, - donorPhone: p.phone || null, - giftAid: p.giftAid, - eventId, - qrSourceId: p.qr ? qrSourceIds[p.qr] || null : null, - organizationId: org.id, - createdAt, - paidAt, - cancelledAt: p.status === "cancelled" ? daysAgo(p.daysAgo - 1) : null, - }, - }) - - // Payment instruction for bank transfers - if (p.rail === "bank") { - await prisma.paymentInstruction.create({ - data: { - pledgeId: pledge.id, - bankReference: ref, - bankDetails: { - bankName: "Barclays", - sortCode: "20-00-00", - accountNo: "12345678", - accountName: "Charity Right", - }, - }, - }) - } - - // Payment record for paid pledges - if (p.status === "paid") { - await prisma.payment.create({ - data: { - pledgeId: pledge.id, - provider: p.rail === "gocardless" ? "gocardless" : p.rail === "card" || p.rail === "fpx" ? "stripe" : "bank", - providerRef: p.rail === "bank" ? null : `sim_${pledge.id.slice(0, 8)}`, - amountPence: p.amount, - status: "confirmed", - matchedBy: p.rail === "bank" ? "auto" : "webhook", - receivedAt: paidAt, - }, - }) - } - - // Reminders for non-paid pledges - if (["new", "initiated", "overdue"].includes(p.status)) { - const steps = [ - { step: 0, delayDays: 0, key: "instructions" }, - { step: 1, delayDays: 2, key: "gentle_nudge" }, - { step: 2, delayDays: 7, key: "urgency_impact" }, - { step: 3, delayDays: 14, key: "final_reminder" }, - ] - for (const s of steps) { - const scheduledAt = new Date(createdAt.getTime() + s.delayDays * 86400000) - const isSent = scheduledAt < new Date() && p.status !== "new" - await prisma.reminder.create({ - data: { - pledgeId: pledge.id, - step: s.step, - channel: "email", - scheduledAt, - status: p.status === "overdue" && s.step <= 2 ? "sent" : isSent ? "sent" : "pending", - sentAt: isSent ? scheduledAt : null, - payload: { templateKey: s.key }, - }, - }) - } - } - - // Analytics events - await prisma.analyticsEvent.create({ - data: { - eventType: "pledge_completed", - pledgeId: pledge.id, - eventId, - qrSourceId: p.qr ? qrSourceIds[p.qr] || null : null, - metadata: { amountPence: p.amount, rail: p.rail }, - createdAt, - }, - }) - } - - // ── Funnel analytics (scans → starts → completions) ── - const funnelEvents = [ - ...Array.from({ length: 45 }, () => ({ eventType: "pledge_start", eventId: galaEvent.id })), - ...Array.from({ length: 8 }, () => ({ eventType: "pledge_start", eventId: eidEvent.id })), - ...Array.from({ length: 12 }, () => ({ eventType: "instruction_copy_clicked", eventId: galaEvent.id })), - ...Array.from({ length: 6 }, () => ({ eventType: "i_paid_clicked", eventId: galaEvent.id })), - ] - for (const fe of funnelEvents) { - await prisma.analyticsEvent.create({ - data: { - eventType: fe.eventType, - eventId: fe.eventId, - createdAt: daysAgo(Math.floor(Math.random() * 7)), - }, - }) - } - - // Count totals - const pledgeCount = await prisma.pledge.count({ where: { organizationId: org.id } }) - const totalAmount = await prisma.pledge.aggregate({ where: { organizationId: org.id }, _sum: { amountPence: true } }) - - console.log("✅ Seed data created") - console.log(` Org: ${org.name} (${org.slug})`) - console.log(` Events: ${galaEvent.name}, ${eidEvent.name}`) - console.log(` QR Codes: ${qrCodes.length + eidQrs.length}`) - console.log(` Pledges: ${pledgeCount} (£${((totalAmount._sum.amountPence || 0) / 100).toLocaleString()})`) -} - -main() - .catch(console.error) - .finally(async () => { - await prisma.$disconnect() - await pool.end() - }) diff --git a/pledge-now-pay-later/src/app/api/analytics/route.ts b/pledge-now-pay-later/src/app/api/analytics/route.ts deleted file mode 100644 index 56706f5..0000000 --- a/pledge-now-pay-later/src/app/api/analytics/route.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { NextRequest, NextResponse } from "next/server" -import prisma from "@/lib/prisma" - -export async function POST(request: NextRequest) { - try { - const body = await request.json() - const { eventType, pledgeId, eventId, qrSourceId, metadata } = body - - // Fire and forget - don't block on errors - if (pledgeId?.startsWith("demo-")) { - return NextResponse.json({ ok: true }) - } - - if (!prisma) { - return NextResponse.json({ ok: true }) - } - - await prisma.analyticsEvent.create({ - data: { - eventType: eventType || "unknown", - pledgeId: pledgeId || null, - eventId: eventId || null, - qrSourceId: qrSourceId || null, - metadata: metadata || {}, - }, - }) - - return NextResponse.json({ ok: true }) - } catch { - // Never fail analytics - return NextResponse.json({ ok: true }) - } -} diff --git a/pledge-now-pay-later/src/app/api/dashboard/route.ts b/pledge-now-pay-later/src/app/api/dashboard/route.ts deleted file mode 100644 index 25c0b46..0000000 --- a/pledge-now-pay-later/src/app/api/dashboard/route.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { NextRequest, NextResponse } from "next/server" -import prisma from "@/lib/prisma" -import { resolveOrgId } from "@/lib/org" - -interface PledgeRow { - id: string - reference: string - amountPence: number - status: string - rail: string - donorName: string | null - donorEmail: string | null - donorPhone: string | null - giftAid: boolean - createdAt: Date - paidAt: Date | null - event: { name: string } - qrSource: { label: string; volunteerName: string | null; tableName: string | null } | null - reminders: Array<{ step: number; status: string; scheduledAt: Date }> -} - -interface AnalyticsRow { - eventType: string - _count: number -} - -interface ReminderRow { - step: number - status: string - scheduledAt: Date -} - -export async function GET(request: NextRequest) { - try { - if (!prisma) { - return NextResponse.json({ - summary: { - totalPledges: 12, - totalPledgedPence: 2450000, - totalCollectedPence: 1820000, - collectionRate: 74, - overdueRate: 8, - }, - byStatus: { paid: 8, pending: 2, overdue: 1, cancelled: 1 }, - byRail: { bank_transfer: 10, card: 2 }, - topSources: [ - { label: "Table 1 - Ahmed", count: 4, amount: 850000 }, - { label: "Table 2 - Fatima", count: 3, amount: 620000 }, - ], - funnel: { qr_scan: 45, pledge_started: 32, pledge_completed: 12 }, - pledges: [], - }) - } - - const orgId = await resolveOrgId(request.headers.get("x-org-id")) - if (!orgId) { - return NextResponse.json({ error: "Organization not found" }, { status: 404 }) - } - const eventId = request.nextUrl.searchParams.get("eventId") - - const where = { - organizationId: orgId, - ...(eventId ? { eventId } : {}), - } - - const [pledges, analytics] = await Promise.all([ - prisma.pledge.findMany({ - where, - include: { - event: { select: { name: true } }, - qrSource: { select: { label: true, volunteerName: true, tableName: true } }, - reminders: { select: { step: true, status: true, scheduledAt: true } }, - }, - orderBy: { createdAt: "desc" }, - }), - prisma.analyticsEvent.groupBy({ - by: ["eventType"], - where: eventId ? { eventId } : {}, - _count: true, - }), - ]) as [PledgeRow[], AnalyticsRow[]] - - const totalPledged = pledges.reduce((s: number, p: PledgeRow) => s + p.amountPence, 0) - const totalCollected = pledges - .filter((p: PledgeRow) => p.status === "paid") - .reduce((s: number, p: PledgeRow) => s + p.amountPence, 0) - const collectionRate = totalPledged > 0 ? totalCollected / totalPledged : 0 - const overdueCount = pledges.filter((p: PledgeRow) => p.status === "overdue").length - const overdueRate = pledges.length > 0 ? overdueCount / pledges.length : 0 - - // Status breakdown - const byStatus: Record = {} - pledges.forEach((p: PledgeRow) => { - byStatus[p.status] = (byStatus[p.status] || 0) + 1 - }) - - // Rail breakdown - const byRail: Record = {} - pledges.forEach((p: PledgeRow) => { - byRail[p.rail] = (byRail[p.rail] || 0) + 1 - }) - - // Top QR sources - const qrStats: Record = {} - pledges.forEach((p: PledgeRow) => { - if (p.qrSource) { - const key = p.qrSource.label - if (!qrStats[key]) qrStats[key] = { label: key, count: 0, amount: 0 } - qrStats[key].count++ - qrStats[key].amount += p.amountPence - } - }) - - // Funnel from analytics - const funnel = Object.fromEntries(analytics.map((a: AnalyticsRow) => [a.eventType, a._count])) - - return NextResponse.json({ - summary: { - totalPledges: pledges.length, - totalPledgedPence: totalPledged, - totalCollectedPence: totalCollected, - collectionRate: Math.round(collectionRate * 100), - overdueRate: Math.round(overdueRate * 100), - }, - byStatus, - byRail, - topSources: Object.values(qrStats).sort((a: { amount: number }, b: { amount: number }) => b.amount - a.amount).slice(0, 10), - funnel, - pledges: pledges.map((p: PledgeRow) => ({ - id: p.id, - reference: p.reference, - amountPence: p.amountPence, - status: p.status, - rail: p.rail, - donorName: p.donorName, - donorEmail: p.donorEmail, - donorPhone: p.donorPhone, - eventName: p.event.name, - source: p.qrSource?.label || null, - volunteerName: p.qrSource?.volunteerName || null, - giftAid: p.giftAid, - createdAt: p.createdAt, - paidAt: p.paidAt, - nextReminder: p.reminders - .filter((r: ReminderRow) => r.status === "pending") - .sort((a: ReminderRow, b: ReminderRow) => a.scheduledAt.getTime() - b.scheduledAt.getTime())[0]?.scheduledAt || null, - lastTouch: p.reminders - .filter((r: ReminderRow) => r.status === "sent") - .sort((a: ReminderRow, b: ReminderRow) => b.scheduledAt.getTime() - a.scheduledAt.getTime())[0]?.scheduledAt || null, - })), - }) - } catch (error) { - console.error("Dashboard error:", error) - return NextResponse.json({ error: "Internal error" }, { status: 500 }) - } -} diff --git a/pledge-now-pay-later/src/app/api/events/[id]/qr/[qrId]/download/route.ts b/pledge-now-pay-later/src/app/api/events/[id]/qr/[qrId]/download/route.ts deleted file mode 100644 index 22ae137..0000000 --- a/pledge-now-pay-later/src/app/api/events/[id]/qr/[qrId]/download/route.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { NextRequest, NextResponse } from "next/server" -import { generateQrBuffer } from "@/lib/qr" - -export async function GET( - request: NextRequest, - { params }: { params: Promise<{ id: string; qrId: string }> } -) { - try { - const { qrId } = await params - const baseUrl = process.env.BASE_URL || "http://localhost:3000" - - // qrId is actually used to look up the code, but for simplicity use the code from query - const code = request.nextUrl.searchParams.get("code") || qrId - - const buffer = await generateQrBuffer({ - baseUrl, - code, - width: 800, - margin: 2, - }) - - return new NextResponse(new Uint8Array(buffer), { - headers: { - "Content-Type": "image/png", - "Content-Disposition": `attachment; filename="qr-${code}.png"`, - }, - }) - } catch (error) { - console.error("QR download error:", error) - return NextResponse.json({ error: "Internal error" }, { status: 500 }) - } -} diff --git a/pledge-now-pay-later/src/app/api/events/[id]/qr/route.ts b/pledge-now-pay-later/src/app/api/events/[id]/qr/route.ts deleted file mode 100644 index 68f008d..0000000 --- a/pledge-now-pay-later/src/app/api/events/[id]/qr/route.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { NextRequest, NextResponse } from "next/server" -import prisma from "@/lib/prisma" -import { createQrSourceSchema } from "@/lib/validators" -import { customAlphabet } from "nanoid" - -const generateCode = customAlphabet("23456789abcdefghjkmnpqrstuvwxyz", 8) - -interface QrPledge { - amountPence: number - status: string -} - -interface QrRow { - id: string - label: string - code: string - volunteerName: string | null - tableName: string | null - scanCount: number - createdAt: Date - _count: { pledges: number } - pledges: QrPledge[] -} - -// GET QR sources for event -export async function GET( - request: NextRequest, - { params }: { params: Promise<{ id: string }> } -) { - try { - const { id } = await params - if (!prisma) { - return NextResponse.json([]) - } - const sources = await prisma.qrSource.findMany({ - where: { eventId: id }, - include: { - _count: { select: { pledges: true } }, - pledges: { select: { amountPence: true, status: true } }, - }, - orderBy: { createdAt: "desc" }, - }) as QrRow[] - - return NextResponse.json( - sources.map((s: QrRow) => ({ - id: s.id, - label: s.label, - code: s.code, - volunteerName: s.volunteerName, - tableName: s.tableName, - scanCount: s.scanCount, - pledgeCount: s._count.pledges, - totalPledged: s.pledges.reduce((sum: number, p: QrPledge) => sum + p.amountPence, 0), - createdAt: s.createdAt, - })) - ) - } catch (error) { - console.error("QR sources GET error:", error) - return NextResponse.json({ error: "Internal error" }, { status: 500 }) - } -} - -// POST create QR source -export async function POST( - request: NextRequest, - { params }: { params: Promise<{ id: string }> } -) { - try { - const { id } = await params - if (!prisma) { - return NextResponse.json({ error: "Database not configured" }, { status: 503 }) - } - const body = await request.json() - - const parsed = createQrSourceSchema.safeParse(body) - if (!parsed.success) { - return NextResponse.json({ error: "Invalid data", details: parsed.error.flatten() }, { status: 400 }) - } - - const event = await prisma.event.findUnique({ - where: { id }, - select: { id: true }, - }) - - if (!event) { - return NextResponse.json({ error: "Event not found" }, { status: 404 }) - } - - const code = generateCode() - - const qrSource = await prisma.qrSource.create({ - data: { - ...parsed.data, - code, - eventId: id, - }, - }) - - return NextResponse.json(qrSource, { status: 201 }) - } catch (error) { - console.error("QR source creation error:", error) - return NextResponse.json({ error: "Internal error" }, { status: 500 }) - } -} diff --git a/pledge-now-pay-later/src/app/api/events/route.ts b/pledge-now-pay-later/src/app/api/events/route.ts deleted file mode 100644 index 685ee77..0000000 --- a/pledge-now-pay-later/src/app/api/events/route.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { NextRequest, NextResponse } from "next/server" -import prisma from "@/lib/prisma" -import { createEventSchema } from "@/lib/validators" -import { resolveOrgId } from "@/lib/org" - -interface PledgeSummary { - amountPence: number - status: string -} - -interface EventRow { - id: string - name: string - slug: string - eventDate: Date | null - location: string | null - goalAmount: number | null - status: string - createdAt: Date - _count: { pledges: number; qrSources: number } - pledges: PledgeSummary[] -} - -// GET all events for org (TODO: auth middleware) -export async function GET(request: NextRequest) { - try { - if (!prisma) { - return NextResponse.json([]) - } - const orgId = await resolveOrgId(request.headers.get("x-org-id")) - if (!orgId) { - return NextResponse.json({ error: "Organization not found" }, { status: 404 }) - } - - const events = await prisma.event.findMany({ - where: { organizationId: orgId }, - include: { - _count: { select: { pledges: true, qrSources: true } }, - pledges: { - select: { amountPence: true, status: true }, - }, - }, - orderBy: { createdAt: "desc" }, - }) as EventRow[] - - const formatted = events.map((e: EventRow) => ({ - id: e.id, - name: e.name, - slug: e.slug, - eventDate: e.eventDate, - location: e.location, - goalAmount: e.goalAmount, - status: e.status, - pledgeCount: e._count.pledges, - qrSourceCount: e._count.qrSources, - totalPledged: e.pledges.reduce((sum: number, p: PledgeSummary) => sum + p.amountPence, 0), - totalCollected: e.pledges - .filter((p: PledgeSummary) => p.status === "paid") - .reduce((sum: number, p: PledgeSummary) => sum + p.amountPence, 0), - createdAt: e.createdAt, - })) - - return NextResponse.json(formatted) - } catch (error) { - console.error("Events GET error:", error) - return NextResponse.json({ error: "Internal error" }, { status: 500 }) - } -} - -// POST create event -export async function POST(request: NextRequest) { - try { - if (!prisma) { - return NextResponse.json({ error: "Database not configured" }, { status: 503 }) - } - const orgId = await resolveOrgId(request.headers.get("x-org-id")) - if (!orgId) { - return NextResponse.json({ error: "Organization not found" }, { status: 404 }) - } - const body = await request.json() - - const parsed = createEventSchema.safeParse(body) - if (!parsed.success) { - return NextResponse.json({ error: "Invalid data", details: parsed.error.flatten() }, { status: 400 }) - } - - const slug = parsed.data.name - .toLowerCase() - .replace(/[^a-z0-9]+/g, "-") - .replace(/^-|-$/g, "") - .slice(0, 50) - - const event = await prisma.event.create({ - data: { - ...parsed.data, - slug: slug + "-" + Date.now().toString(36), - eventDate: parsed.data.eventDate ? new Date(parsed.data.eventDate) : null, - organizationId: orgId, - }, - }) - - return NextResponse.json(event, { status: 201 }) - } catch (error) { - console.error("Event creation error:", error) - return NextResponse.json({ error: "Internal error" }, { status: 500 }) - } -} diff --git a/pledge-now-pay-later/src/app/api/exports/crm-pack/route.ts b/pledge-now-pay-later/src/app/api/exports/crm-pack/route.ts deleted file mode 100644 index 6a04421..0000000 --- a/pledge-now-pay-later/src/app/api/exports/crm-pack/route.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { NextRequest, NextResponse } from "next/server" -import prisma from "@/lib/prisma" -import { formatCrmExportCsv, type CrmExportRow } from "@/lib/exports" -import { resolveOrgId } from "@/lib/org" - -interface ExportPledge { - reference: string - donorName: string | null - donorEmail: string | null - donorPhone: string | null - amountPence: number - rail: string - status: string - giftAid: boolean - createdAt: Date - paidAt: Date | null - event: { name: string } - qrSource: { label: string; volunteerName: string | null; tableName: string | null } | null -} - -export async function GET(request: NextRequest) { - try { - if (!prisma) { - return NextResponse.json({ error: "Database not configured" }, { status: 503 }) - } - const orgId = await resolveOrgId( - request.headers.get("x-org-id") || request.nextUrl.searchParams.get("orgId") || "demo" - ) - if (!orgId) { - return NextResponse.json({ error: "Organization not found" }, { status: 404 }) - } - const eventId = request.nextUrl.searchParams.get("eventId") - - const pledges = await prisma.pledge.findMany({ - where: { - organizationId: orgId, - ...(eventId ? { eventId } : {}), - }, - include: { - event: { select: { name: true } }, - qrSource: { select: { label: true, volunteerName: true, tableName: true } }, - }, - orderBy: { createdAt: "desc" }, - }) as ExportPledge[] - - const rows: CrmExportRow[] = pledges.map((p: ExportPledge) => ({ - pledge_reference: p.reference, - donor_name: p.donorName || "", - donor_email: p.donorEmail || "", - donor_phone: p.donorPhone || "", - amount_gbp: (p.amountPence / 100).toFixed(2), - payment_method: p.rail, - status: p.status, - event_name: p.event.name, - source_label: p.qrSource?.label || "", - volunteer_name: p.qrSource?.volunteerName || "", - table_name: p.qrSource?.tableName || "", - gift_aid: p.giftAid ? "Yes" : "No", - pledged_at: p.createdAt.toISOString(), - paid_at: p.paidAt?.toISOString() || "", - days_to_collect: p.paidAt - ? Math.ceil((p.paidAt.getTime() - p.createdAt.getTime()) / (1000 * 60 * 60 * 24)).toString() - : "", - })) - - const csv = formatCrmExportCsv(rows) - - return new NextResponse(csv, { - headers: { - "Content-Type": "text/csv", - "Content-Disposition": `attachment; filename="crm-export-${new Date().toISOString().slice(0, 10)}.csv"`, - }, - }) - } catch (error) { - console.error("CRM export error:", error) - return NextResponse.json({ error: "Internal error" }, { status: 500 }) - } -} diff --git a/pledge-now-pay-later/src/app/api/gocardless/callback/route.ts b/pledge-now-pay-later/src/app/api/gocardless/callback/route.ts deleted file mode 100644 index d3b059a..0000000 --- a/pledge-now-pay-later/src/app/api/gocardless/callback/route.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { NextRequest, NextResponse } from "next/server" -import prisma from "@/lib/prisma" -import { completeRedirectFlow, createPayment } from "@/lib/gocardless" - -export async function GET(request: NextRequest) { - try { - const pledgeId = request.nextUrl.searchParams.get("pledge_id") - const redirectFlowId = request.nextUrl.searchParams.get("redirect_flow_id") - - if (!pledgeId) { - return NextResponse.redirect(new URL("/", request.url)) - } - - const pledge = await prisma.pledge.findUnique({ - where: { id: pledgeId }, - include: { event: true, paymentInstruction: true }, - }) - - if (!pledge) { - return NextResponse.redirect(new URL("/", request.url)) - } - - // If we have a redirect flow ID, complete the GoCardless flow - if (redirectFlowId) { - const result = await completeRedirectFlow(redirectFlowId, pledgeId) - - if (result) { - // Save mandate ID - if (pledge.paymentInstruction) { - await prisma.paymentInstruction.update({ - where: { id: pledge.paymentInstruction.id }, - data: { gcMandateId: result.mandateId }, - }) - } - - // Create the payment against the mandate - const payment = await createPayment({ - amountPence: pledge.amountPence, - mandateId: result.mandateId, - reference: pledge.reference, - pledgeId: pledge.id, - description: `${pledge.event.name} — ${pledge.reference}`, - }) - - if (payment) { - // Update pledge status - await prisma.pledge.update({ - where: { id: pledgeId }, - data: { status: "initiated" }, - }) - - // Record payment - await prisma.payment.create({ - data: { - pledgeId: pledge.id, - provider: "gocardless", - providerRef: payment.paymentId, - amountPence: pledge.amountPence, - status: "pending", - matchedBy: "auto", - }, - }) - } - } - } - - // Redirect to success page - const successUrl = `/p/success?pledge_id=${pledgeId}&rail=gocardless` - return NextResponse.redirect(new URL(successUrl, request.url)) - } catch (error) { - console.error("GoCardless callback error:", error) - return NextResponse.redirect(new URL("/", request.url)) - } -} diff --git a/pledge-now-pay-later/src/app/api/gocardless/create-flow/route.ts b/pledge-now-pay-later/src/app/api/gocardless/create-flow/route.ts deleted file mode 100644 index 5af611b..0000000 --- a/pledge-now-pay-later/src/app/api/gocardless/create-flow/route.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { NextRequest, NextResponse } from "next/server" -import prisma from "@/lib/prisma" -import { createRedirectFlow } from "@/lib/gocardless" -import { generateReference } from "@/lib/reference" - -export async function POST(request: NextRequest) { - try { - const body = await request.json() - const { amountPence, donorName, donorEmail, donorPhone, giftAid, eventId, qrSourceId } = body - - if (!prisma) { - return NextResponse.json({ error: "Database not configured" }, { status: 503 }) - } - - // Get event + org - const event = await prisma.event.findUnique({ - where: { id: eventId }, - include: { organization: true }, - }) - if (!event) { - return NextResponse.json({ error: "Event not found" }, { status: 404 }) - } - - const org = event.organization - - // Generate reference - let reference = "" - let attempts = 0 - while (attempts < 10) { - reference = generateReference(org.refPrefix || "PNPL", amountPence) - const exists = await prisma.pledge.findUnique({ where: { reference } }) - if (!exists) break - attempts++ - } - - // Create pledge in DB - const pledge = await prisma.pledge.create({ - data: { - reference, - amountPence, - currency: "GBP", - rail: "gocardless", - status: "new", - donorName: donorName || null, - donorEmail: donorEmail || null, - donorPhone: donorPhone || null, - giftAid: giftAid || false, - eventId, - qrSourceId: qrSourceId || null, - organizationId: org.id, - }, - }) - - // Create reminder schedule - const { calculateReminderSchedule } = await import("@/lib/reminders") - const schedule = calculateReminderSchedule(new Date()) - await prisma.reminder.createMany({ - data: schedule.map((s) => ({ - pledgeId: pledge.id, - step: s.step, - channel: s.channel, - scheduledAt: s.scheduledAt, - status: "pending", - payload: { templateKey: s.templateKey, subject: s.subject }, - })), - }) - - // Track analytics - await prisma.analyticsEvent.create({ - data: { - eventType: "pledge_completed", - pledgeId: pledge.id, - eventId, - qrSourceId: qrSourceId || null, - metadata: { amountPence, rail: "gocardless" }, - }, - }) - - // Try real GoCardless flow - // GoCardless live mode requires HTTPS redirect URLs - const baseUrl = process.env.BASE_URL || "http://localhost:3000" - const isHttps = baseUrl.startsWith("https://") - const redirectUrl = `${baseUrl}/api/gocardless/callback?pledge_id=${pledge.id}` - - if (!isHttps && process.env.GOCARDLESS_ENVIRONMENT === "live") { - // Can't use GC live with HTTP — return simulated mode - // Set BASE_URL to your HTTPS domain to enable live GoCardless - console.warn("GoCardless live mode requires HTTPS BASE_URL. Falling back to simulated.") - return NextResponse.json({ - mode: "simulated", - pledgeId: pledge.id, - reference, - id: pledge.id, - }) - } - - const flow = await createRedirectFlow({ - description: `${event.name} — ${reference}`, - reference, - pledgeId: pledge.id, - successRedirectUrl: redirectUrl, - }) - - if (flow) { - // Save the redirect flow ID for completion - await prisma.paymentInstruction.create({ - data: { - pledgeId: pledge.id, - bankReference: reference, - bankDetails: {}, - gcMandateUrl: flow.redirectUrl, - }, - }) - - return NextResponse.json({ - mode: "live", - pledgeId: pledge.id, - reference, - redirectUrl: flow.redirectUrl, - redirectFlowId: flow.redirectFlowId, - }) - } - - // Fallback: no GoCardless configured — return pledge for simulated flow - return NextResponse.json({ - mode: "simulated", - pledgeId: pledge.id, - reference, - id: pledge.id, - }) - } catch (error) { - console.error("GoCardless create flow error:", error) - return NextResponse.json({ error: "Internal error" }, { status: 500 }) - } -} diff --git a/pledge-now-pay-later/src/app/api/gocardless/webhook/route.ts b/pledge-now-pay-later/src/app/api/gocardless/webhook/route.ts deleted file mode 100644 index 798a792..0000000 --- a/pledge-now-pay-later/src/app/api/gocardless/webhook/route.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { NextRequest, NextResponse } from "next/server" -import prisma from "@/lib/prisma" - -// GoCardless sends webhook events for payment status changes -export async function POST(request: NextRequest) { - try { - const body = await request.json() - const events = body.events || [] - - for (const event of events) { - const { resource_type, action, links } = event - - if (resource_type === "payments") { - const paymentId = links?.payment - - if (!paymentId) continue - - // Find our payment record - const payment = await prisma.payment.findFirst({ - where: { providerRef: paymentId, provider: "gocardless" }, - include: { pledge: true }, - }) - - if (!payment) continue - - switch (action) { - case "confirmed": - case "paid_out": - await prisma.pledge.update({ - where: { id: payment.pledgeId }, - data: { status: "paid", paidAt: new Date() }, - }) - await prisma.payment.update({ - where: { id: payment.id }, - data: { status: "confirmed", receivedAt: new Date() }, - }) - await prisma.analyticsEvent.create({ - data: { - eventType: "payment_matched", - pledgeId: payment.pledgeId, - metadata: { provider: "gocardless", action, paymentId }, - }, - }) - break - - case "failed": - case "cancelled": - await prisma.pledge.update({ - where: { id: payment.pledgeId }, - data: { status: action === "cancelled" ? "cancelled" : "overdue" }, - }) - await prisma.payment.update({ - where: { id: payment.id }, - data: { status: "failed" }, - }) - break - } - } - - if (resource_type === "mandates" && action === "cancelled") { - // Mandate cancelled by bank/customer - const mandateId = links?.mandate - if (mandateId) { - const instruction = await prisma.paymentInstruction.findFirst({ - where: { gcMandateId: mandateId }, - }) - if (instruction) { - await prisma.pledge.update({ - where: { id: instruction.pledgeId }, - data: { status: "cancelled", cancelledAt: new Date() }, - }) - } - } - } - } - - return NextResponse.json({ received: true }) - } catch (error) { - console.error("GoCardless webhook error:", error) - return NextResponse.json({ error: "Webhook handler failed" }, { status: 500 }) - } -} diff --git a/pledge-now-pay-later/src/app/api/imports/bank-statement/route.ts b/pledge-now-pay-later/src/app/api/imports/bank-statement/route.ts deleted file mode 100644 index d143d6a..0000000 --- a/pledge-now-pay-later/src/app/api/imports/bank-statement/route.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { NextRequest, NextResponse } from "next/server" -import prisma from "@/lib/prisma" -import Papa from "papaparse" -import { matchBankRow } from "@/lib/matching" -import { resolveOrgId } from "@/lib/org" - -export async function POST(request: NextRequest) { - try { - if (!prisma) { - return NextResponse.json({ error: "Database not configured" }, { status: 503 }) - } - const orgId = await resolveOrgId(request.headers.get("x-org-id")) - if (!orgId) { - return NextResponse.json({ error: "Organization not found" }, { status: 404 }) - } - const formData = await request.formData() - const file = formData.get("file") as File - const mappingJson = formData.get("mapping") as string - - if (!file) { - return NextResponse.json({ error: "No file uploaded" }, { status: 400 }) - } - - let mapping: Record = {} - try { - mapping = mappingJson ? JSON.parse(mappingJson) : {} - } catch { - return NextResponse.json({ error: "Invalid column mapping JSON" }, { status: 400 }) - } - const csvText = await file.text() - const parsed = Papa.parse(csvText, { header: true, skipEmptyLines: true }) - - if (parsed.errors.length > 0 && parsed.data.length === 0) { - return NextResponse.json({ error: "CSV parse error", details: parsed.errors }, { status: 400 }) - } - - // Get all unmatched pledges for this org - const openPledges = await prisma.pledge.findMany({ - where: { - organizationId: orgId, - status: { in: ["new", "initiated", "overdue"] }, - }, - select: { id: true, reference: true, amountPence: true }, - }) - - const pledgeMap = new Map( - openPledges.map((p: { id: string; reference: string; amountPence: number }) => [p.reference, { id: p.id, amountPence: p.amountPence }]) - ) - - // Convert rows and match - const rows = (parsed.data as Record[]).map((raw) => ({ - date: raw[mapping.dateCol || "Date"] || "", - description: raw[mapping.descriptionCol || "Description"] || "", - amount: parseFloat(raw[mapping.creditCol || mapping.amountCol || "Amount"] || "0"), - reference: raw[mapping.referenceCol || "Reference"] || "", - raw, - })) - - const results = rows - .filter((r) => r.amount > 0) // only credits - .map((r) => matchBankRow(r, pledgeMap)) - - // Create import record - const importRecord = await prisma.import.create({ - data: { - organizationId: orgId, - kind: "bank_statement", - fileName: file.name, - rowCount: rows.length, - matchedCount: results.filter((r) => r.confidence === "exact").length, - unmatchedCount: results.filter((r) => r.confidence === "none").length, - mappingConfig: mapping, - status: "completed", - stats: { - totalRows: rows.length, - credits: rows.filter((r) => r.amount > 0).length, - exactMatches: results.filter((r) => r.confidence === "exact").length, - partialMatches: results.filter((r) => r.confidence === "partial").length, - unmatched: results.filter((r) => r.confidence === "none").length, - }, - }, - }) - - // Auto-confirm exact matches - const confirmed: string[] = [] - for (const result of results) { - if (result.confidence === "exact" && result.pledgeId) { - await prisma.$transaction([ - prisma.pledge.update({ - where: { id: result.pledgeId }, - data: { status: "paid", paidAt: new Date() }, - }), - prisma.payment.create({ - data: { - pledgeId: result.pledgeId, - provider: "bank", - amountPence: Math.round(result.matchedAmount * 100), - status: "confirmed", - matchedBy: "auto", - receivedAt: new Date(result.bankRow.date) || new Date(), - importId: importRecord.id, - }, - }), - // Skip remaining reminders - prisma.reminder.updateMany({ - where: { pledgeId: result.pledgeId, status: "pending" }, - data: { status: "skipped" }, - }), - ]) - confirmed.push(result.pledgeId) - } - } - - return NextResponse.json({ - importId: importRecord.id, - summary: { - totalRows: rows.length, - credits: rows.filter((r) => r.amount > 0).length, - exactMatches: results.filter((r) => r.confidence === "exact").length, - partialMatches: results.filter((r) => r.confidence === "partial").length, - unmatched: results.filter((r) => r.confidence === "none").length, - autoConfirmed: confirmed.length, - }, - matches: results.map((r) => ({ - ...r, - autoConfirmed: r.pledgeId ? confirmed.includes(r.pledgeId) : false, - })), - }) - } catch (error) { - console.error("Bank import error:", error) - return NextResponse.json({ error: "Internal error" }, { status: 500 }) - } -} diff --git a/pledge-now-pay-later/src/app/api/pledges/[id]/mark-initiated/route.ts b/pledge-now-pay-later/src/app/api/pledges/[id]/mark-initiated/route.ts deleted file mode 100644 index edc84a9..0000000 --- a/pledge-now-pay-later/src/app/api/pledges/[id]/mark-initiated/route.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { NextRequest, NextResponse } from "next/server" -import prisma from "@/lib/prisma" - -export async function POST( - request: NextRequest, - { params }: { params: Promise<{ id: string }> } -) { - try { - if (!prisma) { - return NextResponse.json({ error: "Database not configured" }, { status: 503 }) - } - const { id } = await params - - if (id.startsWith("demo-")) { - return NextResponse.json({ ok: true }) - } - - await prisma.pledge.update({ - where: { id }, - data: { - status: "initiated", - iPaidClickedAt: new Date(), - }, - }) - - return NextResponse.json({ ok: true }) - } catch (error) { - console.error("Mark initiated error:", error) - return NextResponse.json({ error: "Internal error" }, { status: 500 }) - } -} diff --git a/pledge-now-pay-later/src/app/api/pledges/[id]/route.ts b/pledge-now-pay-later/src/app/api/pledges/[id]/route.ts deleted file mode 100644 index 196011a..0000000 --- a/pledge-now-pay-later/src/app/api/pledges/[id]/route.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { NextRequest, NextResponse } from "next/server" -import prisma from "@/lib/prisma" -import { updatePledgeStatusSchema } from "@/lib/validators" - -export async function GET( - request: NextRequest, - { params }: { params: Promise<{ id: string }> } -) { - try { - if (!prisma) { - return NextResponse.json({ error: "Database not configured" }, { status: 503 }) - } - const { id } = await params - const pledge = await prisma.pledge.findUnique({ - where: { id }, - include: { event: { select: { name: true } } }, - }) - if (!pledge) { - return NextResponse.json({ error: "Not found" }, { status: 404 }) - } - return NextResponse.json({ - id: pledge.id, - reference: pledge.reference, - amountPence: pledge.amountPence, - rail: pledge.rail, - status: pledge.status, - donorName: pledge.donorName, - donorEmail: pledge.donorEmail, - eventName: pledge.event.name, - }) - } catch (error) { - console.error("Pledge GET error:", error) - return NextResponse.json({ error: "Internal error" }, { status: 500 }) - } -} - -export async function PATCH( - request: NextRequest, - { params }: { params: Promise<{ id: string }> } -) { - try { - if (!prisma) { - return NextResponse.json({ error: "Database not configured" }, { status: 503 }) - } - const { id } = await params - const body = await request.json() - - const parsed = updatePledgeStatusSchema.safeParse(body) - if (!parsed.success) { - return NextResponse.json({ error: "Invalid data" }, { status: 400 }) - } - - const existing = await prisma.pledge.findUnique({ where: { id } }) - if (!existing) { - return NextResponse.json({ error: "Pledge not found" }, { status: 404 }) - } - - const updateData: Record = { - status: parsed.data.status, - notes: parsed.data.notes, - } - - if (parsed.data.status === "paid") { - updateData.paidAt = new Date() - } - if (parsed.data.status === "cancelled") { - updateData.cancelledAt = new Date() - } - - const pledge = await prisma.pledge.update({ - where: { id }, - data: updateData, - }) - - // If paid or cancelled, skip remaining reminders - if (["paid", "cancelled"].includes(parsed.data.status)) { - await prisma.reminder.updateMany({ - where: { pledgeId: id, status: "pending" }, - data: { status: "skipped" }, - }) - } - - return NextResponse.json(pledge) - } catch (error) { - console.error("Pledge update error:", error) - return NextResponse.json({ error: "Internal error" }, { status: 500 }) - } -} diff --git a/pledge-now-pay-later/src/app/api/pledges/route.ts b/pledge-now-pay-later/src/app/api/pledges/route.ts deleted file mode 100644 index c941fbd..0000000 --- a/pledge-now-pay-later/src/app/api/pledges/route.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { NextRequest, NextResponse } from "next/server" -import prisma from "@/lib/prisma" -import { createPledgeSchema } from "@/lib/validators" -import { generateReference } from "@/lib/reference" -import { calculateReminderSchedule } from "@/lib/reminders" - -export async function POST(request: NextRequest) { - try { - const body = await request.json() - - if (!prisma) { - return NextResponse.json({ error: "Database not configured" }, { status: 503 }) - } - - const parsed = createPledgeSchema.safeParse(body) - if (!parsed.success) { - return NextResponse.json( - { error: "Invalid data", details: parsed.error.flatten() }, - { status: 400 } - ) - } - - const { amountPence, rail, donorName, donorEmail, donorPhone, giftAid, eventId, qrSourceId } = parsed.data - - // Get event + org - const event = await prisma.event.findUnique({ - where: { id: eventId }, - include: { organization: true }, - }) - - if (!event) { - return NextResponse.json({ error: "Event not found" }, { status: 404 }) - } - - const org = event.organization - - // Generate unique reference (retry on collision) - let reference = "" - let attempts = 0 - while (attempts < 10) { - reference = generateReference(org.refPrefix || "PNPL", amountPence) - const exists = await prisma.pledge.findUnique({ where: { reference } }) - if (!exists) break - attempts++ - } - if (attempts >= 10) { - return NextResponse.json({ error: "Could not generate unique reference" }, { status: 500 }) - } - - // Create pledge + payment instruction + reminder schedule in transaction - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const pledge = await prisma.$transaction(async (tx: any) => { - const p = await tx.pledge.create({ - data: { - reference, - amountPence, - currency: "GBP", - rail, - status: "new", - donorName: donorName || null, - donorEmail: donorEmail || null, - donorPhone: donorPhone || null, - giftAid, - eventId, - qrSourceId: qrSourceId || null, - organizationId: org.id, - }, - }) - - // Create payment instruction for bank transfers - if (rail === "bank" && org.bankSortCode && org.bankAccountNo) { - await tx.paymentInstruction.create({ - data: { - pledgeId: p.id, - bankReference: reference, - bankDetails: { - bankName: org.bankName || "", - sortCode: org.bankSortCode, - accountNo: org.bankAccountNo, - accountName: org.bankAccountName || org.name, - }, - }, - }) - } - - // Create reminder schedule - const schedule = calculateReminderSchedule(new Date()) - await tx.reminder.createMany({ - data: schedule.map((s) => ({ - pledgeId: p.id, - step: s.step, - channel: s.channel, - scheduledAt: s.scheduledAt, - status: "pending", - payload: { templateKey: s.templateKey, subject: s.subject }, - })), - }) - - // Track analytics - await tx.analyticsEvent.create({ - data: { - eventType: "pledge_completed", - pledgeId: p.id, - eventId, - qrSourceId: qrSourceId || null, - metadata: { amountPence, rail }, - }, - }) - - return p - }) - - // Build response - const response: Record = { - id: pledge.id, - reference: pledge.reference, - } - - if (rail === "bank" && org.bankSortCode) { - response.bankDetails = { - bankName: org.bankName || "", - sortCode: org.bankSortCode, - accountNo: org.bankAccountNo || "", - accountName: org.bankAccountName || org.name, - } - } - - return NextResponse.json(response, { status: 201 }) - } catch (error) { - console.error("Pledge creation error:", error) - return NextResponse.json({ error: "Internal error" }, { status: 500 }) - } -} diff --git a/pledge-now-pay-later/src/app/api/qr/[token]/route.ts b/pledge-now-pay-later/src/app/api/qr/[token]/route.ts deleted file mode 100644 index 9e8cbaa..0000000 --- a/pledge-now-pay-later/src/app/api/qr/[token]/route.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { NextRequest, NextResponse } from "next/server" -import prisma from "@/lib/prisma" - -export async function GET( - request: NextRequest, - { params }: { params: Promise<{ token: string }> } -) { - try { - const { token } = await params - - if (!prisma) { - return NextResponse.json({ error: "Database not configured" }, { status: 503 }) - } - - // Handle "demo" token — resolve to the first active event - if (token === "demo") { - const event = await prisma.event.findFirst({ - where: { status: "active" }, - include: { organization: { select: { name: true } } }, - orderBy: { createdAt: "asc" }, - }) - if (!event) { - return NextResponse.json({ error: "No active events found" }, { status: 404 }) - } - return NextResponse.json({ - id: event.id, - name: event.name, - organizationName: event.organization.name, - qrSourceId: null, - qrSourceLabel: null, - }) - } - - const qrSource = await prisma.qrSource.findUnique({ - where: { code: token }, - include: { - event: { - include: { - organization: { select: { name: true } }, - }, - }, - }, - }) - - if (!qrSource || qrSource.event.status !== "active") { - return NextResponse.json({ error: "This pledge link is no longer active" }, { status: 404 }) - } - - // Increment scan count - await prisma.qrSource.update({ - where: { id: qrSource.id }, - data: { scanCount: { increment: 1 } }, - }) - - return NextResponse.json({ - id: qrSource.event.id, - name: qrSource.event.name, - organizationName: qrSource.event.organization.name, - qrSourceId: qrSource.id, - qrSourceLabel: qrSource.label, - }) - } catch (error) { - console.error("QR resolve error:", error) - return NextResponse.json({ error: "Internal error" }, { status: 500 }) - } -} diff --git a/pledge-now-pay-later/src/app/api/settings/route.ts b/pledge-now-pay-later/src/app/api/settings/route.ts deleted file mode 100644 index 37223c4..0000000 --- a/pledge-now-pay-later/src/app/api/settings/route.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { NextRequest, NextResponse } from "next/server" -import prisma from "@/lib/prisma" -import { resolveOrgId } from "@/lib/org" - -export async function GET(request: NextRequest) { - try { - if (!prisma) return NextResponse.json({ error: "DB not configured" }, { status: 503 }) - const orgId = await resolveOrgId(request.headers.get("x-org-id") || "demo") - if (!orgId) return NextResponse.json({ error: "Org not found" }, { status: 404 }) - - const org = await prisma.organization.findUnique({ where: { id: orgId } }) - if (!org) return NextResponse.json({ error: "Org not found" }, { status: 404 }) - - return NextResponse.json({ - id: org.id, - name: org.name, - slug: org.slug, - country: org.country, - bankName: org.bankName || "", - bankSortCode: org.bankSortCode || "", - bankAccountNo: org.bankAccountNo || "", - bankAccountName: org.bankAccountName || "", - refPrefix: org.refPrefix, - logo: org.logo, - primaryColor: org.primaryColor, - gcAccessToken: org.gcAccessToken ? "••••••••" : "", - gcEnvironment: org.gcEnvironment, - }) - } catch (error) { - console.error("Settings GET error:", error) - return NextResponse.json({ error: "Internal error" }, { status: 500 }) - } -} - -export async function PATCH(request: NextRequest) { - try { - if (!prisma) return NextResponse.json({ error: "DB not configured" }, { status: 503 }) - const orgId = await resolveOrgId(request.headers.get("x-org-id") || "demo") - if (!orgId) return NextResponse.json({ error: "Org not found" }, { status: 404 }) - - const body = await request.json() - const allowed = ["name", "bankName", "bankSortCode", "bankAccountNo", "bankAccountName", "refPrefix", "primaryColor", "logo", "gcAccessToken", "gcEnvironment"] - const data: Record = {} - for (const key of allowed) { - if (key in body && body[key] !== undefined && body[key] !== "••••••••") { - data[key] = body[key] - } - } - - const org = await prisma.organization.update({ - where: { id: orgId }, - data, - }) - - return NextResponse.json({ success: true, name: org.name }) - } catch (error) { - console.error("Settings PATCH error:", error) - return NextResponse.json({ error: "Internal error" }, { status: 500 }) - } -} diff --git a/pledge-now-pay-later/src/app/api/stripe/checkout/route.ts b/pledge-now-pay-later/src/app/api/stripe/checkout/route.ts deleted file mode 100644 index 8c9ec5d..0000000 --- a/pledge-now-pay-later/src/app/api/stripe/checkout/route.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { NextRequest, NextResponse } from "next/server" -import prisma from "@/lib/prisma" -import { createCheckoutSession } from "@/lib/stripe" -import { generateReference } from "@/lib/reference" - -export async function POST(request: NextRequest) { - try { - const body = await request.json() - const { amountPence, donorName, donorEmail, donorPhone, giftAid, eventId, qrSourceId } = body - - if (!prisma) { - return NextResponse.json({ error: "Database not configured" }, { status: 503 }) - } - - // Get event + org - const event = await prisma.event.findUnique({ - where: { id: eventId }, - include: { organization: true }, - }) - if (!event) { - return NextResponse.json({ error: "Event not found" }, { status: 404 }) - } - - const org = event.organization - - // Generate reference - let reference = "" - let attempts = 0 - while (attempts < 10) { - reference = generateReference(org.refPrefix || "PNPL", amountPence) - const exists = await prisma.pledge.findUnique({ where: { reference } }) - if (!exists) break - attempts++ - } - - // Create pledge in DB - const pledge = await prisma.pledge.create({ - data: { - reference, - amountPence, - currency: "GBP", - rail: "card", - status: "new", - donorName: donorName || null, - donorEmail: donorEmail || null, - donorPhone: donorPhone || null, - giftAid: giftAid || false, - eventId, - qrSourceId: qrSourceId || null, - organizationId: org.id, - }, - }) - - // Track analytics - await prisma.analyticsEvent.create({ - data: { - eventType: "pledge_completed", - pledgeId: pledge.id, - eventId, - qrSourceId: qrSourceId || null, - metadata: { amountPence, rail: "card" }, - }, - }) - - // Try real Stripe checkout - const baseUrl = process.env.BASE_URL || "http://localhost:3000" - - const session = await createCheckoutSession({ - amountPence, - currency: "GBP", - pledgeId: pledge.id, - reference, - eventName: event.name, - organizationName: org.name, - donorEmail: donorEmail || undefined, - successUrl: `${baseUrl}/p/success?pledge_id=${pledge.id}&rail=card&session_id={CHECKOUT_SESSION_ID}`, - cancelUrl: `${baseUrl}/p/success?pledge_id=${pledge.id}&rail=card&cancelled=true`, - }) - - if (session) { - // Save Stripe session reference - await prisma.payment.create({ - data: { - pledgeId: pledge.id, - provider: "stripe", - providerRef: session.sessionId, - amountPence, - status: "pending", - matchedBy: "auto", - }, - }) - - await prisma.pledge.update({ - where: { id: pledge.id }, - data: { status: "initiated" }, - }) - - return NextResponse.json({ - mode: "live", - pledgeId: pledge.id, - reference, - checkoutUrl: session.checkoutUrl, - sessionId: session.sessionId, - }) - } - - // Fallback: no Stripe configured — return pledge for simulated flow - return NextResponse.json({ - mode: "simulated", - pledgeId: pledge.id, - reference, - id: pledge.id, - }) - } catch (error) { - console.error("Stripe checkout error:", error) - return NextResponse.json({ error: "Internal error" }, { status: 500 }) - } -} diff --git a/pledge-now-pay-later/src/app/api/stripe/webhook/route.ts b/pledge-now-pay-later/src/app/api/stripe/webhook/route.ts deleted file mode 100644 index 7296b0d..0000000 --- a/pledge-now-pay-later/src/app/api/stripe/webhook/route.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { NextRequest, NextResponse } from "next/server" -import prisma from "@/lib/prisma" -import { constructWebhookEvent } from "@/lib/stripe" - -export async function POST(request: NextRequest) { - try { - const body = await request.text() - const signature = request.headers.get("stripe-signature") || "" - - const event = constructWebhookEvent(body, signature) - if (!event) { - return NextResponse.json({ error: "Invalid signature" }, { status: 400 }) - } - - switch (event.type) { - case "checkout.session.completed": { - const session = event.data.object as { id: string; metadata: Record; payment_status: string } - const pledgeId = session.metadata?.pledge_id - - if (pledgeId && session.payment_status === "paid") { - await prisma.pledge.update({ - where: { id: pledgeId }, - data: { - status: "paid", - paidAt: new Date(), - }, - }) - - // Update payment record - await prisma.payment.updateMany({ - where: { - pledgeId, - providerRef: session.id, - }, - data: { - status: "confirmed", - receivedAt: new Date(), - }, - }) - - // Track analytics - await prisma.analyticsEvent.create({ - data: { - eventType: "payment_matched", - pledgeId, - metadata: { provider: "stripe", sessionId: session.id }, - }, - }) - } - break - } - - case "payment_intent.succeeded": { - const pi = event.data.object as { id: string; metadata: Record } - const pledgeId = pi.metadata?.pledge_id - - if (pledgeId) { - await prisma.pledge.update({ - where: { id: pledgeId }, - data: { - status: "paid", - paidAt: new Date(), - }, - }) - } - break - } - - case "payment_intent.payment_failed": { - const pi = event.data.object as { id: string; metadata: Record } - const pledgeId = pi.metadata?.pledge_id - - if (pledgeId) { - await prisma.pledge.update({ - where: { id: pledgeId }, - data: { status: "overdue" }, - }) - } - break - } - } - - return NextResponse.json({ received: true }) - } catch (error) { - console.error("Stripe webhook error:", error) - return NextResponse.json({ error: "Webhook handler failed" }, { status: 500 }) - } -} diff --git a/pledge-now-pay-later/src/app/api/webhooks/route.ts b/pledge-now-pay-later/src/app/api/webhooks/route.ts deleted file mode 100644 index 599782e..0000000 --- a/pledge-now-pay-later/src/app/api/webhooks/route.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { NextRequest, NextResponse } from "next/server" -import prisma from "@/lib/prisma" -import { formatWebhookPayload } from "@/lib/exports" - -interface ReminderWithPledge { - id: string - pledgeId: string - step: number - channel: string - scheduledAt: Date - payload: unknown - pledge: { - donorName: string | null - donorEmail: string | null - donorPhone: string | null - reference: string - amountPence: number - rail: string - event: { name: string } - organization: { name: string } - } -} - -// GET pending webhook events (for external polling) -export async function GET(request: NextRequest) { - try { - if (!prisma) { - return NextResponse.json([]) - } - const since = request.nextUrl.searchParams.get("since") - const limit = parseInt(request.nextUrl.searchParams.get("limit") || "50") - - const reminders = await prisma.reminder.findMany({ - where: { - status: "pending", - scheduledAt: { lte: new Date() }, - ...(since ? { scheduledAt: { gte: new Date(since) } } : {}), - }, - include: { - pledge: { - include: { - event: { select: { name: true } }, - organization: { select: { name: true } }, - }, - }, - }, - take: limit, - orderBy: { scheduledAt: "asc" }, - }) as ReminderWithPledge[] - - const events = reminders.map((r: ReminderWithPledge) => - formatWebhookPayload("reminder.due", { - reminderId: r.id, - pledgeId: r.pledgeId, - step: r.step, - channel: r.channel, - scheduledAt: r.scheduledAt, - donor: { - name: r.pledge.donorName, - email: r.pledge.donorEmail, - phone: r.pledge.donorPhone, - }, - pledge: { - reference: r.pledge.reference, - amount: r.pledge.amountPence, - rail: r.pledge.rail, - }, - event: r.pledge.event.name, - organization: r.pledge.organization.name, - payload: r.payload, - }) - ) - - return NextResponse.json({ events, count: events.length }) - } catch (error) { - console.error("Webhooks error:", error) - return NextResponse.json({ error: "Internal error" }, { status: 500 }) - } -} diff --git a/pledge-now-pay-later/src/app/dashboard/apply/page.tsx b/pledge-now-pay-later/src/app/dashboard/apply/page.tsx deleted file mode 100644 index 862ff58..0000000 --- a/pledge-now-pay-later/src/app/dashboard/apply/page.tsx +++ /dev/null @@ -1,92 +0,0 @@ -"use client" - -import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" -import { Textarea } from "@/components/ui/textarea" -import { Select } from "@/components/ui/select" -import { TrendingUp, Shield, Zap } from "lucide-react" - -export default function ApplyPage() { - return ( -
-
-

- Fractional Head of Technology -

-

- Get expert technology leadership for your charity — without the full-time cost. -

-
- - {/* Benefits */} -
- {[ - { icon: TrendingUp, title: "Optimise Your Stack", desc: "Reduce costs, improve donor experience, integrate tools" }, - { icon: Shield, title: "Data & Compliance", desc: "GDPR, consent management, security best practices" }, - { icon: Zap, title: "Automate Everything", desc: "Connect your CRM, comms, payments, and reporting" }, - ].map((b, i) => ( - - - -

{b.title}

-

{b.desc}

-
-
- ))} -
- - {/* Application form */} - - - Apply - Tell us about your charity's tech needs - - -
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
- -