diff --git a/.gitignore b/.gitignore
index 32f44c5..57ef722 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,7 @@
node_modules/
data/
+.next/
*.log
.DS_Store
-web/dist/
+*.tsbuildinfo
+.env.local
diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts
new file mode 100644
index 0000000..7fb5855
--- /dev/null
+++ b/app/api/chat/route.ts
@@ -0,0 +1,5 @@
+import { handleChatRequest } from "@/src/server/chat-handler";
+
+export async function POST(req: Request) {
+ return handleChatRequest(req);
+}
diff --git a/app/api/chats/[id]/route.ts b/app/api/chats/[id]/route.ts
new file mode 100644
index 0000000..26d3666
--- /dev/null
+++ b/app/api/chats/[id]/route.ts
@@ -0,0 +1,8 @@
+import { getChat } from "@/src/store/chats";
+
+export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
+ const { id } = await params;
+ const chat = getChat(id);
+ if (!chat) return Response.json({ error: "Not found" }, { status: 404 });
+ return Response.json(chat);
+}
diff --git a/app/api/chats/route.ts b/app/api/chats/route.ts
new file mode 100644
index 0000000..9f6dd3e
--- /dev/null
+++ b/app/api/chats/route.ts
@@ -0,0 +1,12 @@
+import { createChat, listChats, titleFromPrompt } from "@/src/store/chats";
+
+export async function GET() {
+ return Response.json({ chats: listChats() });
+}
+
+export async function POST(req: Request) {
+ const body = (await req.json().catch(() => ({}))) as { title?: unknown };
+ const title = typeof body.title === "string" ? body.title : "";
+ const chat = createChat(titleFromPrompt(title || "Untitled session"));
+ return Response.json({ id: chat.id, title: chat.title });
+}
diff --git a/app/chat/[id]/page.tsx b/app/chat/[id]/page.tsx
new file mode 100644
index 0000000..e463947
--- /dev/null
+++ b/app/chat/[id]/page.tsx
@@ -0,0 +1,26 @@
+import { notFound } from "next/navigation";
+import { Chat } from "@/components/chat";
+import { getChat } from "@/src/store/chats";
+import type { TeacherUIMessage } from "@/src/agent/ui-message";
+
+export const dynamic = "force-dynamic";
+
+export default async function ChatPage({
+ params,
+ searchParams,
+}: {
+ params: Promise<{ id: string }>;
+ searchParams: Promise<{ prompt?: string }>;
+}) {
+ const [{ id }, { prompt }] = await Promise.all([params, searchParams]);
+ const chat = getChat(id);
+ if (!chat) notFound();
+ return (
+
+ );
+}
diff --git a/app/globals.css b/app/globals.css
new file mode 100644
index 0000000..937c93b
--- /dev/null
+++ b/app/globals.css
@@ -0,0 +1,86 @@
+@import "tailwindcss";
+
+@custom-variant dark (&:is(.dark *));
+
+/*
+ * Design tokens — the single place to restyle the app.
+ * (The owner has a specific visual design planned; it lands here and in
+ * component edits, not in structural changes.)
+ */
+
+:root {
+ --radius: 0.625rem;
+ --background: oklch(1 0 0);
+ --foreground: oklch(0.145 0 0);
+ --card: oklch(1 0 0);
+ --card-foreground: oklch(0.145 0 0);
+ --popover: oklch(1 0 0);
+ --popover-foreground: oklch(0.145 0 0);
+ --primary: oklch(0.205 0 0);
+ --primary-foreground: oklch(0.985 0 0);
+ --secondary: oklch(0.97 0 0);
+ --secondary-foreground: oklch(0.205 0 0);
+ --muted: oklch(0.97 0 0);
+ --muted-foreground: oklch(0.556 0 0);
+ --accent: oklch(0.97 0 0);
+ --accent-foreground: oklch(0.205 0 0);
+ --destructive: oklch(0.577 0.245 27.325);
+ --border: oklch(0.922 0 0);
+ --input: oklch(0.922 0 0);
+ --ring: oklch(0.708 0 0);
+}
+
+.dark {
+ --background: oklch(0.145 0 0);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.205 0 0);
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.205 0 0);
+ --popover-foreground: oklch(0.985 0 0);
+ --primary: oklch(0.922 0 0);
+ --primary-foreground: oklch(0.205 0 0);
+ --secondary: oklch(0.269 0 0);
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.269 0 0);
+ --muted-foreground: oklch(0.708 0 0);
+ --accent: oklch(0.269 0 0);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: oklch(0.704 0.191 22.216);
+ --border: oklch(1 0 0 / 10%);
+ --input: oklch(1 0 0 / 15%);
+ --ring: oklch(0.556 0 0);
+}
+
+@theme inline {
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+}
+
+* {
+ border-color: var(--color-border);
+}
+
+body {
+ background: var(--color-background);
+ color: var(--color-foreground);
+}
diff --git a/app/layout.tsx b/app/layout.tsx
new file mode 100644
index 0000000..34e07e7
--- /dev/null
+++ b/app/layout.tsx
@@ -0,0 +1,15 @@
+import type { Metadata } from "next";
+import "./globals.css";
+
+export const metadata: Metadata = {
+ title: "Learning Harness",
+ description: "A teacher agent with a memory — sessions, quizzes, and a knowledge graph of what you know.",
+};
+
+export default function RootLayout({ children }: { children: React.ReactNode }) {
+ return (
+
+
{children}
+
+ );
+}
diff --git a/app/page.tsx b/app/page.tsx
new file mode 100644
index 0000000..518248c
--- /dev/null
+++ b/app/page.tsx
@@ -0,0 +1,8 @@
+import { listChats } from "@/src/store/chats";
+import { Home } from "@/components/home";
+
+export const dynamic = "force-dynamic";
+
+export default function HomePage() {
+ return ;
+}
diff --git a/components.json b/components.json
new file mode 100644
index 0000000..6e57ca9
--- /dev/null
+++ b/components.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "",
+ "css": "app/globals.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ },
+ "iconLibrary": "lucide"
+}
diff --git a/components/ai-elements/conversation.tsx b/components/ai-elements/conversation.tsx
new file mode 100644
index 0000000..21e5d82
--- /dev/null
+++ b/components/ai-elements/conversation.tsx
@@ -0,0 +1,168 @@
+"use client";
+
+import { Button } from "@/components/ui/button";
+import { cn } from "@/lib/utils";
+import type { UIMessage } from "ai";
+import { ArrowDownIcon, DownloadIcon } from "lucide-react";
+import type { ComponentProps } from "react";
+import { useCallback } from "react";
+import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
+
+export type ConversationProps = ComponentProps;
+
+export const Conversation = ({ className, ...props }: ConversationProps) => (
+
+);
+
+export type ConversationContentProps = ComponentProps<
+ typeof StickToBottom.Content
+>;
+
+export const ConversationContent = ({
+ className,
+ ...props
+}: ConversationContentProps) => (
+
+);
+
+export type ConversationEmptyStateProps = ComponentProps<"div"> & {
+ title?: string;
+ description?: string;
+ icon?: React.ReactNode;
+};
+
+export const ConversationEmptyState = ({
+ className,
+ title = "No messages yet",
+ description = "Start a conversation to see messages here",
+ icon,
+ children,
+ ...props
+}: ConversationEmptyStateProps) => (
+
+ {children ?? (
+ <>
+ {icon &&
{icon}
}
+
+
{title}
+ {description && (
+
{description}
+ )}
+
+ >
+ )}
+
+);
+
+export type ConversationScrollButtonProps = ComponentProps;
+
+export const ConversationScrollButton = ({
+ className,
+ ...props
+}: ConversationScrollButtonProps) => {
+ const { isAtBottom, scrollToBottom } = useStickToBottomContext();
+
+ const handleScrollToBottom = useCallback(() => {
+ scrollToBottom();
+ }, [scrollToBottom]);
+
+ return (
+ !isAtBottom && (
+
+ )
+ );
+};
+
+const getMessageText = (message: UIMessage): string =>
+ message.parts
+ .filter((part) => part.type === "text")
+ .map((part) => part.text)
+ .join("");
+
+export type ConversationDownloadProps = Omit<
+ ComponentProps,
+ "onClick"
+> & {
+ messages: UIMessage[];
+ filename?: string;
+ formatMessage?: (message: UIMessage, index: number) => string;
+};
+
+const defaultFormatMessage = (message: UIMessage): string => {
+ const roleLabel =
+ message.role.charAt(0).toUpperCase() + message.role.slice(1);
+ return `**${roleLabel}:** ${getMessageText(message)}`;
+};
+
+export const messagesToMarkdown = (
+ messages: UIMessage[],
+ formatMessage: (
+ message: UIMessage,
+ index: number
+ ) => string = defaultFormatMessage
+): string => messages.map((msg, i) => formatMessage(msg, i)).join("\n\n");
+
+export const ConversationDownload = ({
+ messages,
+ filename = "conversation.md",
+ formatMessage = defaultFormatMessage,
+ className,
+ children,
+ ...props
+}: ConversationDownloadProps) => {
+ const handleDownload = useCallback(() => {
+ const markdown = messagesToMarkdown(messages, formatMessage);
+ const blob = new Blob([markdown], { type: "text/markdown" });
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement("a");
+ link.href = url;
+ link.download = filename;
+ document.body.append(link);
+ link.click();
+ link.remove();
+ URL.revokeObjectURL(url);
+ }, [messages, filename, formatMessage]);
+
+ return (
+
+ );
+};
diff --git a/components/ai-elements/message.tsx b/components/ai-elements/message.tsx
new file mode 100644
index 0000000..f75eded
--- /dev/null
+++ b/components/ai-elements/message.tsx
@@ -0,0 +1,363 @@
+"use client";
+
+import { Button } from "@/components/ui/button";
+import {
+ ButtonGroup,
+ ButtonGroupText,
+} from "@/components/ui/button-group";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
+import { cn } from "@/lib/utils";
+import { cjk } from "@streamdown/cjk";
+import { code } from "@streamdown/code";
+import { math } from "@streamdown/math";
+import { mermaid } from "@streamdown/mermaid";
+import type { UIMessage } from "ai";
+import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
+import type { ComponentProps, HTMLAttributes, ReactElement } from "react";
+import {
+ createContext,
+ memo,
+ useCallback,
+ useContext,
+ useEffect,
+ useMemo,
+ useState,
+} from "react";
+import { Streamdown, type PluginConfig } from "streamdown";
+
+export type MessageProps = HTMLAttributes & {
+ from: UIMessage["role"];
+};
+
+export const Message = ({ className, from, ...props }: MessageProps) => (
+
+);
+
+export type MessageContentProps = HTMLAttributes;
+
+export const MessageContent = ({
+ children,
+ className,
+ ...props
+}: MessageContentProps) => (
+
+ {children}
+
+);
+
+export type MessageActionsProps = ComponentProps<"div">;
+
+export const MessageActions = ({
+ className,
+ children,
+ ...props
+}: MessageActionsProps) => (
+
+ {children}
+
+);
+
+export type MessageActionProps = ComponentProps & {
+ tooltip?: string;
+ label?: string;
+};
+
+export const MessageAction = ({
+ tooltip,
+ children,
+ label,
+ variant = "ghost",
+ size = "icon-sm",
+ ...props
+}: MessageActionProps) => {
+ const button = (
+
+ );
+
+ if (tooltip) {
+ return (
+
+
+ {button}
+
+ {tooltip}
+
+
+
+ );
+ }
+
+ return button;
+};
+
+interface MessageBranchContextType {
+ currentBranch: number;
+ totalBranches: number;
+ goToPrevious: () => void;
+ goToNext: () => void;
+ branches: ReactElement[];
+ setBranches: (branches: ReactElement[]) => void;
+}
+
+const MessageBranchContext = createContext(
+ null
+);
+
+const useMessageBranch = () => {
+ const context = useContext(MessageBranchContext);
+
+ if (!context) {
+ throw new Error(
+ "MessageBranch components must be used within MessageBranch"
+ );
+ }
+
+ return context;
+};
+
+export type MessageBranchProps = HTMLAttributes & {
+ defaultBranch?: number;
+ onBranchChange?: (branchIndex: number) => void;
+};
+
+export const MessageBranch = ({
+ defaultBranch = 0,
+ onBranchChange,
+ className,
+ ...props
+}: MessageBranchProps) => {
+ const [currentBranch, setCurrentBranch] = useState(defaultBranch);
+ const [branches, setBranches] = useState([]);
+
+ const handleBranchChange = useCallback(
+ (newBranch: number) => {
+ setCurrentBranch(newBranch);
+ onBranchChange?.(newBranch);
+ },
+ [onBranchChange]
+ );
+
+ const goToPrevious = useCallback(() => {
+ const newBranch =
+ currentBranch > 0 ? currentBranch - 1 : branches.length - 1;
+ handleBranchChange(newBranch);
+ }, [currentBranch, branches.length, handleBranchChange]);
+
+ const goToNext = useCallback(() => {
+ const newBranch =
+ currentBranch < branches.length - 1 ? currentBranch + 1 : 0;
+ handleBranchChange(newBranch);
+ }, [currentBranch, branches.length, handleBranchChange]);
+
+ const contextValue = useMemo(
+ () => ({
+ branches,
+ currentBranch,
+ goToNext,
+ goToPrevious,
+ setBranches,
+ totalBranches: branches.length,
+ }),
+ [branches, currentBranch, goToNext, goToPrevious]
+ );
+
+ return (
+
+ div]:pb-0", className)}
+ {...props}
+ />
+
+ );
+};
+
+export type MessageBranchContentProps = HTMLAttributes
;
+
+export const MessageBranchContent = ({
+ children,
+ ...props
+}: MessageBranchContentProps) => {
+ const { currentBranch, setBranches, branches } = useMessageBranch();
+ const childrenArray = useMemo(
+ () => (Array.isArray(children) ? children : [children]),
+ [children]
+ );
+
+ // Use useEffect to update branches when they change
+ useEffect(() => {
+ if (branches.length !== childrenArray.length) {
+ setBranches(childrenArray);
+ }
+ }, [childrenArray, branches, setBranches]);
+
+ return childrenArray.map((branch, index) => (
+ div]:pb-0",
+ index === currentBranch ? "block" : "hidden"
+ )}
+ key={branch.key}
+ {...props}
+ >
+ {branch}
+
+ ));
+};
+
+export type MessageBranchSelectorProps = ComponentProps;
+
+export const MessageBranchSelector = ({
+ className,
+ ...props
+}: MessageBranchSelectorProps) => {
+ const { totalBranches } = useMessageBranch();
+
+ // Don't render if there's only one branch
+ if (totalBranches <= 1) {
+ return null;
+ }
+
+ return (
+ *:not(:first-child)]:rounded-l-md [&>*:not(:last-child)]:rounded-r-md",
+ className
+ )}
+ orientation="horizontal"
+ {...props}
+ />
+ );
+};
+
+export type MessageBranchPreviousProps = ComponentProps;
+
+export const MessageBranchPrevious = ({
+ children,
+ ...props
+}: MessageBranchPreviousProps) => {
+ const { goToPrevious, totalBranches } = useMessageBranch();
+
+ return (
+
+ );
+};
+
+export type MessageBranchNextProps = ComponentProps;
+
+export const MessageBranchNext = ({
+ children,
+ ...props
+}: MessageBranchNextProps) => {
+ const { goToNext, totalBranches } = useMessageBranch();
+
+ return (
+
+ );
+};
+
+export type MessageBranchPageProps = HTMLAttributes;
+
+export const MessageBranchPage = ({
+ className,
+ ...props
+}: MessageBranchPageProps) => {
+ const { currentBranch, totalBranches } = useMessageBranch();
+
+ return (
+
+ {currentBranch + 1} of {totalBranches}
+
+ );
+};
+
+export type MessageResponseProps = ComponentProps;
+
+// Type-only version skew: @streamdown/code bundles shiki@3 types while
+// streamdown@2.5 expects shiki@4 types. Both work at runtime (each ships its
+// own highlighter); the cast papers over the .d.ts mismatch.
+const streamdownPlugins = { cjk, code, math, mermaid } as unknown as PluginConfig;
+
+export const MessageResponse = memo(
+ ({ className, ...props }: MessageResponseProps) => (
+ *:first-child]:mt-0 [&>*:last-child]:mb-0",
+ className
+ )}
+ plugins={streamdownPlugins}
+ {...props}
+ />
+ ),
+ (prevProps, nextProps) =>
+ prevProps.children === nextProps.children &&
+ nextProps.isAnimating === prevProps.isAnimating
+);
+
+MessageResponse.displayName = "MessageResponse";
+
+export type MessageToolbarProps = ComponentProps<"div">;
+
+export const MessageToolbar = ({
+ className,
+ children,
+ ...props
+}: MessageToolbarProps) => (
+
+ {children}
+
+);
diff --git a/components/ai-elements/prompt-input.tsx b/components/ai-elements/prompt-input.tsx
new file mode 100644
index 0000000..5be6019
--- /dev/null
+++ b/components/ai-elements/prompt-input.tsx
@@ -0,0 +1,1463 @@
+"use client";
+
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+ CommandSeparator,
+} from "@/components/ui/command";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import {
+ HoverCard,
+ HoverCardContent,
+ HoverCardTrigger,
+} from "@/components/ui/hover-card";
+import {
+ InputGroup,
+ InputGroupAddon,
+ InputGroupButton,
+ InputGroupTextarea,
+} from "@/components/ui/input-group";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { Spinner } from "@/components/ui/spinner";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
+import { cn } from "@/lib/utils";
+import type { ChatStatus, FileUIPart, SourceDocumentUIPart } from "ai";
+import {
+ CornerDownLeftIcon,
+ ImageIcon,
+ Monitor,
+ PlusIcon,
+ SquareIcon,
+ XIcon,
+} from "lucide-react";
+import { nanoid } from "nanoid";
+import type {
+ ChangeEvent,
+ ChangeEventHandler,
+ ClipboardEventHandler,
+ ComponentProps,
+ FormEvent,
+ FormEventHandler,
+ HTMLAttributes,
+ KeyboardEventHandler,
+ PropsWithChildren,
+ ReactNode,
+ RefObject,
+} from "react";
+import {
+ Children,
+ createContext,
+ useCallback,
+ useContext,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from "react";
+
+// ============================================================================
+// Helpers
+// ============================================================================
+
+const convertBlobUrlToDataUrl = async (url: string): Promise => {
+ try {
+ const response = await fetch(url);
+ const blob = await response.blob();
+ // FileReader uses callback-based API, wrapping in Promise is necessary
+ // oxlint-disable-next-line eslint-plugin-promise(avoid-new)
+ return new Promise((resolve) => {
+ const reader = new FileReader();
+ // oxlint-disable-next-line eslint-plugin-unicorn(prefer-add-event-listener)
+ reader.onloadend = () => resolve(reader.result as string);
+ // oxlint-disable-next-line eslint-plugin-unicorn(prefer-add-event-listener)
+ reader.onerror = () => resolve(null);
+ reader.readAsDataURL(blob);
+ });
+ } catch {
+ return null;
+ }
+};
+
+const captureScreenshot = async (): Promise => {
+ if (
+ typeof navigator === "undefined" ||
+ !navigator.mediaDevices?.getDisplayMedia
+ ) {
+ return null;
+ }
+
+ let stream: MediaStream | null = null;
+ const video = document.createElement("video");
+ video.muted = true;
+ video.playsInline = true;
+
+ try {
+ stream = await navigator.mediaDevices.getDisplayMedia({
+ audio: false,
+ video: true,
+ });
+
+ video.srcObject = stream;
+
+ // Video element uses callback-based API, wrapping in Promise is necessary
+ // oxlint-disable-next-line eslint-plugin-promise(avoid-new)
+ await new Promise((resolve, reject) => {
+ // oxlint-disable-next-line eslint-plugin-unicorn(prefer-add-event-listener)
+ video.onloadedmetadata = () => resolve();
+ // oxlint-disable-next-line eslint-plugin-unicorn(prefer-add-event-listener)
+ video.onerror = () => reject(new Error("Failed to load screen stream"));
+ });
+
+ await video.play();
+
+ const width = video.videoWidth;
+ const height = video.videoHeight;
+ if (!width || !height) {
+ return null;
+ }
+
+ const canvas = document.createElement("canvas");
+ canvas.width = width;
+ canvas.height = height;
+ const context = canvas.getContext("2d");
+ if (!context) {
+ return null;
+ }
+
+ context.drawImage(video, 0, 0, width, height);
+ // canvas.toBlob uses callback-based API, wrapping in Promise is necessary
+ // oxlint-disable-next-line eslint-plugin-promise(avoid-new)
+ const blob = await new Promise((resolve) => {
+ canvas.toBlob(resolve, "image/png");
+ });
+ if (!blob) {
+ return null;
+ }
+
+ const timestamp = new Date()
+ .toISOString()
+ .replaceAll(/[:.]/g, "-")
+ .replace("T", "_")
+ .replace("Z", "");
+
+ return new File([blob], `screenshot-${timestamp}.png`, {
+ lastModified: Date.now(),
+ type: "image/png",
+ });
+ } finally {
+ if (stream) {
+ for (const track of stream.getTracks()) {
+ track.stop();
+ }
+ }
+ video.pause();
+ video.srcObject = null;
+ }
+};
+
+// ============================================================================
+// Provider Context & Types
+// ============================================================================
+
+export interface AttachmentsContext {
+ files: (FileUIPart & { id: string })[];
+ add: (files: File[] | FileList) => void;
+ remove: (id: string) => void;
+ clear: () => void;
+ openFileDialog: () => void;
+ fileInputRef: RefObject;
+}
+
+export interface TextInputContext {
+ value: string;
+ setInput: (v: string) => void;
+ clear: () => void;
+}
+
+export interface PromptInputControllerProps {
+ textInput: TextInputContext;
+ attachments: AttachmentsContext;
+ /** INTERNAL: Allows PromptInput to register its file textInput + "open" callback */
+ __registerFileInput: (
+ ref: RefObject,
+ open: () => void
+ ) => void;
+}
+
+const PromptInputController = createContext(
+ null
+);
+const ProviderAttachmentsContext = createContext(
+ null
+);
+
+export const usePromptInputController = () => {
+ const ctx = useContext(PromptInputController);
+ if (!ctx) {
+ throw new Error(
+ "Wrap your component inside to use usePromptInputController()."
+ );
+ }
+ return ctx;
+};
+
+// Optional variants (do NOT throw). Useful for dual-mode components.
+const useOptionalPromptInputController = () =>
+ useContext(PromptInputController);
+
+export const useProviderAttachments = () => {
+ const ctx = useContext(ProviderAttachmentsContext);
+ if (!ctx) {
+ throw new Error(
+ "Wrap your component inside to use useProviderAttachments()."
+ );
+ }
+ return ctx;
+};
+
+const useOptionalProviderAttachments = () =>
+ useContext(ProviderAttachmentsContext);
+
+export type PromptInputProviderProps = PropsWithChildren<{
+ initialInput?: string;
+}>;
+
+/**
+ * Optional global provider that lifts PromptInput state outside of PromptInput.
+ * If you don't use it, PromptInput stays fully self-managed.
+ */
+export const PromptInputProvider = ({
+ initialInput: initialTextInput = "",
+ children,
+}: PromptInputProviderProps) => {
+ // ----- textInput state
+ const [textInput, setTextInput] = useState(initialTextInput);
+ const clearInput = useCallback(() => setTextInput(""), []);
+
+ // ----- attachments state (global when wrapped)
+ const [attachmentFiles, setAttachmentFiles] = useState<
+ (FileUIPart & { id: string })[]
+ >([]);
+ const fileInputRef = useRef(null);
+ // oxlint-disable-next-line eslint(no-empty-function)
+ const openRef = useRef<() => void>(() => {});
+
+ const add = useCallback((files: File[] | FileList) => {
+ const incoming = [...files];
+ if (incoming.length === 0) {
+ return;
+ }
+
+ setAttachmentFiles((prev) => [
+ ...prev,
+ ...incoming.map((file) => ({
+ filename: file.name,
+ id: nanoid(),
+ mediaType: file.type,
+ type: "file" as const,
+ url: URL.createObjectURL(file),
+ })),
+ ]);
+ }, []);
+
+ const remove = useCallback((id: string) => {
+ setAttachmentFiles((prev) => {
+ const found = prev.find((f) => f.id === id);
+ if (found?.url) {
+ URL.revokeObjectURL(found.url);
+ }
+ return prev.filter((f) => f.id !== id);
+ });
+ }, []);
+
+ const clear = useCallback(() => {
+ setAttachmentFiles((prev) => {
+ for (const f of prev) {
+ if (f.url) {
+ URL.revokeObjectURL(f.url);
+ }
+ }
+ return [];
+ });
+ }, []);
+
+ // Keep a ref to attachments for cleanup on unmount (avoids stale closure)
+ const attachmentsRef = useRef(attachmentFiles);
+
+ useEffect(() => {
+ attachmentsRef.current = attachmentFiles;
+ }, [attachmentFiles]);
+
+ // Cleanup blob URLs on unmount to prevent memory leaks
+ useEffect(
+ () => () => {
+ for (const f of attachmentsRef.current) {
+ if (f.url) {
+ URL.revokeObjectURL(f.url);
+ }
+ }
+ },
+ []
+ );
+
+ const openFileDialog = useCallback(() => {
+ openRef.current?.();
+ }, []);
+
+ const attachments = useMemo(
+ () => ({
+ add,
+ clear,
+ fileInputRef,
+ files: attachmentFiles,
+ openFileDialog,
+ remove,
+ }),
+ [attachmentFiles, add, remove, clear, openFileDialog]
+ );
+
+ const __registerFileInput = useCallback(
+ (ref: RefObject, open: () => void) => {
+ fileInputRef.current = ref.current;
+ openRef.current = open;
+ },
+ []
+ );
+
+ const controller = useMemo(
+ () => ({
+ __registerFileInput,
+ attachments,
+ textInput: {
+ clear: clearInput,
+ setInput: setTextInput,
+ value: textInput,
+ },
+ }),
+ [textInput, clearInput, attachments, __registerFileInput]
+ );
+
+ return (
+
+
+ {children}
+
+
+ );
+};
+
+// ============================================================================
+// Component Context & Hooks
+// ============================================================================
+
+const LocalAttachmentsContext = createContext(null);
+
+export const usePromptInputAttachments = () => {
+ // Prefer local context (inside PromptInput) as it has validation, fall back to provider
+ const provider = useOptionalProviderAttachments();
+ const local = useContext(LocalAttachmentsContext);
+ const context = local ?? provider;
+ if (!context) {
+ throw new Error(
+ "usePromptInputAttachments must be used within a PromptInput or PromptInputProvider"
+ );
+ }
+ return context;
+};
+
+// ============================================================================
+// Referenced Sources (Local to PromptInput)
+// ============================================================================
+
+export interface ReferencedSourcesContext {
+ sources: (SourceDocumentUIPart & { id: string })[];
+ add: (sources: SourceDocumentUIPart[] | SourceDocumentUIPart) => void;
+ remove: (id: string) => void;
+ clear: () => void;
+}
+
+export const LocalReferencedSourcesContext =
+ createContext(null);
+
+export const usePromptInputReferencedSources = () => {
+ const ctx = useContext(LocalReferencedSourcesContext);
+ if (!ctx) {
+ throw new Error(
+ "usePromptInputReferencedSources must be used within a LocalReferencedSourcesContext.Provider"
+ );
+ }
+ return ctx;
+};
+
+export type PromptInputActionAddAttachmentsProps = ComponentProps<
+ typeof DropdownMenuItem
+> & {
+ label?: string;
+};
+
+export const PromptInputActionAddAttachments = ({
+ label = "Add photos or files",
+ ...props
+}: PromptInputActionAddAttachmentsProps) => {
+ const attachments = usePromptInputAttachments();
+
+ const handleSelect = useCallback(
+ (e: Event) => {
+ e.preventDefault();
+ attachments.openFileDialog();
+ },
+ [attachments]
+ );
+
+ return (
+
+ {label}
+
+ );
+};
+
+export type PromptInputActionAddScreenshotProps = ComponentProps<
+ typeof DropdownMenuItem
+> & {
+ label?: string;
+};
+
+export const PromptInputActionAddScreenshot = ({
+ label = "Take screenshot",
+ onSelect,
+ ...props
+}: PromptInputActionAddScreenshotProps) => {
+ const attachments = usePromptInputAttachments();
+
+ const handleSelect = useCallback(
+ async (event: Event) => {
+ onSelect?.(event);
+ if (event.defaultPrevented) {
+ return;
+ }
+
+ try {
+ const screenshot = await captureScreenshot();
+ if (screenshot) {
+ attachments.add([screenshot]);
+ }
+ } catch (error) {
+ if (
+ error instanceof DOMException &&
+ (error.name === "NotAllowedError" || error.name === "AbortError")
+ ) {
+ return;
+ }
+ throw error;
+ }
+ },
+ [onSelect, attachments]
+ );
+
+ return (
+
+
+ {label}
+
+ );
+};
+
+export interface PromptInputMessage {
+ text: string;
+ files: FileUIPart[];
+}
+
+export type PromptInputProps = Omit<
+ HTMLAttributes,
+ "onSubmit" | "onError"
+> & {
+ // e.g., "image/*" or leave undefined for any
+ accept?: string;
+ multiple?: boolean;
+ // When true, accepts drops anywhere on document. Default false (opt-in).
+ globalDrop?: boolean;
+ // Render a hidden input with given name and keep it in sync for native form posts. Default false.
+ syncHiddenInput?: boolean;
+ // Minimal constraints
+ maxFiles?: number;
+ // bytes
+ maxFileSize?: number;
+ onError?: (err: {
+ code: "max_files" | "max_file_size" | "accept";
+ message: string;
+ }) => void;
+ onSubmit: (
+ message: PromptInputMessage,
+ event: FormEvent
+ ) => void | Promise;
+};
+
+export const PromptInput = ({
+ className,
+ accept,
+ multiple,
+ globalDrop,
+ syncHiddenInput,
+ maxFiles,
+ maxFileSize,
+ onError,
+ onSubmit,
+ children,
+ ...props
+}: PromptInputProps) => {
+ // Try to use a provider controller if present
+ const controller = useOptionalPromptInputController();
+ const usingProvider = !!controller;
+
+ // Refs
+ const inputRef = useRef(null);
+ const formRef = useRef(null);
+
+ // ----- Local attachments (only used when no provider)
+ const [items, setItems] = useState<(FileUIPart & { id: string })[]>([]);
+ const files = usingProvider ? controller.attachments.files : items;
+
+ // ----- Local referenced sources (always local to PromptInput)
+ const [referencedSources, setReferencedSources] = useState<
+ (SourceDocumentUIPart & { id: string })[]
+ >([]);
+
+ // Keep a ref to files for cleanup on unmount (avoids stale closure)
+ const filesRef = useRef(files);
+
+ useEffect(() => {
+ filesRef.current = files;
+ }, [files]);
+
+ const openFileDialogLocal = useCallback(() => {
+ inputRef.current?.click();
+ }, []);
+
+ const matchesAccept = useCallback(
+ (f: File) => {
+ if (!accept || accept.trim() === "") {
+ return true;
+ }
+
+ const patterns = accept
+ .split(",")
+ .map((s) => s.trim())
+ .filter(Boolean);
+
+ return patterns.some((pattern) => {
+ if (pattern.endsWith("/*")) {
+ // e.g: image/* -> image/
+ const prefix = pattern.slice(0, -1);
+ return f.type.startsWith(prefix);
+ }
+ return f.type === pattern;
+ });
+ },
+ [accept]
+ );
+
+ const addLocal = useCallback(
+ (fileList: File[] | FileList) => {
+ const incoming = [...fileList];
+ const accepted = incoming.filter((f) => matchesAccept(f));
+ if (incoming.length && accepted.length === 0) {
+ onError?.({
+ code: "accept",
+ message: "No files match the accepted types.",
+ });
+ return;
+ }
+ const withinSize = (f: File) =>
+ maxFileSize ? f.size <= maxFileSize : true;
+ const sized = accepted.filter(withinSize);
+ if (accepted.length > 0 && sized.length === 0) {
+ onError?.({
+ code: "max_file_size",
+ message: "All files exceed the maximum size.",
+ });
+ return;
+ }
+
+ setItems((prev) => {
+ const capacity =
+ typeof maxFiles === "number"
+ ? Math.max(0, maxFiles - prev.length)
+ : undefined;
+ const capped =
+ typeof capacity === "number" ? sized.slice(0, capacity) : sized;
+ if (typeof capacity === "number" && sized.length > capacity) {
+ onError?.({
+ code: "max_files",
+ message: "Too many files. Some were not added.",
+ });
+ }
+ const next: (FileUIPart & { id: string })[] = [];
+ for (const file of capped) {
+ next.push({
+ filename: file.name,
+ id: nanoid(),
+ mediaType: file.type,
+ type: "file",
+ url: URL.createObjectURL(file),
+ });
+ }
+ return [...prev, ...next];
+ });
+ },
+ [matchesAccept, maxFiles, maxFileSize, onError]
+ );
+
+ const removeLocal = useCallback(
+ (id: string) =>
+ setItems((prev) => {
+ const found = prev.find((file) => file.id === id);
+ if (found?.url) {
+ URL.revokeObjectURL(found.url);
+ }
+ return prev.filter((file) => file.id !== id);
+ }),
+ []
+ );
+
+ // Wrapper that validates files before calling provider's add
+ const addWithProviderValidation = useCallback(
+ (fileList: File[] | FileList) => {
+ const incoming = [...fileList];
+ const accepted = incoming.filter((f) => matchesAccept(f));
+ if (incoming.length && accepted.length === 0) {
+ onError?.({
+ code: "accept",
+ message: "No files match the accepted types.",
+ });
+ return;
+ }
+ const withinSize = (f: File) =>
+ maxFileSize ? f.size <= maxFileSize : true;
+ const sized = accepted.filter(withinSize);
+ if (accepted.length > 0 && sized.length === 0) {
+ onError?.({
+ code: "max_file_size",
+ message: "All files exceed the maximum size.",
+ });
+ return;
+ }
+
+ const currentCount = files.length;
+ const capacity =
+ typeof maxFiles === "number"
+ ? Math.max(0, maxFiles - currentCount)
+ : undefined;
+ const capped =
+ typeof capacity === "number" ? sized.slice(0, capacity) : sized;
+ if (typeof capacity === "number" && sized.length > capacity) {
+ onError?.({
+ code: "max_files",
+ message: "Too many files. Some were not added.",
+ });
+ }
+
+ if (capped.length > 0) {
+ controller?.attachments.add(capped);
+ }
+ },
+ [matchesAccept, maxFileSize, maxFiles, onError, files.length, controller]
+ );
+
+ const clearAttachments = useCallback(
+ () =>
+ usingProvider
+ ? controller?.attachments.clear()
+ : setItems((prev) => {
+ for (const file of prev) {
+ if (file.url) {
+ URL.revokeObjectURL(file.url);
+ }
+ }
+ return [];
+ }),
+ [usingProvider, controller]
+ );
+
+ const clearReferencedSources = useCallback(
+ () => setReferencedSources([]),
+ []
+ );
+
+ const add = usingProvider ? addWithProviderValidation : addLocal;
+ const remove = usingProvider ? controller.attachments.remove : removeLocal;
+ const openFileDialog = usingProvider
+ ? controller.attachments.openFileDialog
+ : openFileDialogLocal;
+
+ const clear = useCallback(() => {
+ clearAttachments();
+ clearReferencedSources();
+ }, [clearAttachments, clearReferencedSources]);
+
+ // Let provider know about our hidden file input so external menus can call openFileDialog()
+ useEffect(() => {
+ if (!usingProvider) {
+ return;
+ }
+ controller.__registerFileInput(inputRef, () => inputRef.current?.click());
+ }, [usingProvider, controller]);
+
+ // Note: File input cannot be programmatically set for security reasons
+ // The syncHiddenInput prop is no longer functional
+ useEffect(() => {
+ if (syncHiddenInput && inputRef.current && files.length === 0) {
+ inputRef.current.value = "";
+ }
+ }, [files, syncHiddenInput]);
+
+ // Attach drop handlers on nearest form and document (opt-in)
+ useEffect(() => {
+ const form = formRef.current;
+ if (!form) {
+ return;
+ }
+ if (globalDrop) {
+ // when global drop is on, let the document-level handler own drops
+ return;
+ }
+
+ const onDragOver = (e: DragEvent) => {
+ if (e.dataTransfer?.types?.includes("Files")) {
+ e.preventDefault();
+ }
+ };
+ const onDrop = (e: DragEvent) => {
+ if (e.dataTransfer?.types?.includes("Files")) {
+ e.preventDefault();
+ }
+ if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {
+ add(e.dataTransfer.files);
+ }
+ };
+ form.addEventListener("dragover", onDragOver);
+ form.addEventListener("drop", onDrop);
+ return () => {
+ form.removeEventListener("dragover", onDragOver);
+ form.removeEventListener("drop", onDrop);
+ };
+ }, [add, globalDrop]);
+
+ useEffect(() => {
+ if (!globalDrop) {
+ return;
+ }
+
+ const onDragOver = (e: DragEvent) => {
+ if (e.dataTransfer?.types?.includes("Files")) {
+ e.preventDefault();
+ }
+ };
+ const onDrop = (e: DragEvent) => {
+ if (e.dataTransfer?.types?.includes("Files")) {
+ e.preventDefault();
+ }
+ if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {
+ add(e.dataTransfer.files);
+ }
+ };
+ document.addEventListener("dragover", onDragOver);
+ document.addEventListener("drop", onDrop);
+ return () => {
+ document.removeEventListener("dragover", onDragOver);
+ document.removeEventListener("drop", onDrop);
+ };
+ }, [add, globalDrop]);
+
+ useEffect(
+ () => () => {
+ if (!usingProvider) {
+ for (const f of filesRef.current) {
+ if (f.url) {
+ URL.revokeObjectURL(f.url);
+ }
+ }
+ }
+ },
+ [usingProvider]
+ );
+
+ const handleChange: ChangeEventHandler = useCallback(
+ (event) => {
+ if (event.currentTarget.files) {
+ add(event.currentTarget.files);
+ }
+ // Reset input value to allow selecting files that were previously removed
+ event.currentTarget.value = "";
+ },
+ [add]
+ );
+
+ const attachmentsCtx = useMemo(
+ () => ({
+ add,
+ clear: clearAttachments,
+ fileInputRef: inputRef,
+ files: files.map((item) => ({ ...item, id: item.id })),
+ openFileDialog,
+ remove,
+ }),
+ [files, add, remove, clearAttachments, openFileDialog]
+ );
+
+ const refsCtx = useMemo(
+ () => ({
+ add: (incoming: SourceDocumentUIPart[] | SourceDocumentUIPart) => {
+ const array = Array.isArray(incoming) ? incoming : [incoming];
+ setReferencedSources((prev) => [
+ ...prev,
+ ...array.map((s) => ({ ...s, id: nanoid() })),
+ ]);
+ },
+ clear: clearReferencedSources,
+ remove: (id: string) => {
+ setReferencedSources((prev) => prev.filter((s) => s.id !== id));
+ },
+ sources: referencedSources,
+ }),
+ [referencedSources, clearReferencedSources]
+ );
+
+ const handleSubmit: FormEventHandler = useCallback(
+ async (event) => {
+ event.preventDefault();
+
+ const form = event.currentTarget;
+ const text = usingProvider
+ ? controller.textInput.value
+ : (() => {
+ const formData = new FormData(form);
+ return (formData.get("message") as string) || "";
+ })();
+
+ // Reset form immediately after capturing text to avoid race condition
+ // where user input during async blob conversion would be lost
+ if (!usingProvider) {
+ form.reset();
+ }
+
+ try {
+ // Convert blob URLs to data URLs asynchronously
+ const convertedFiles: FileUIPart[] = await Promise.all(
+ files.map(async ({ id: _id, ...item }) => {
+ if (item.url?.startsWith("blob:")) {
+ const dataUrl = await convertBlobUrlToDataUrl(item.url);
+ // If conversion failed, keep the original blob URL
+ return {
+ ...item,
+ url: dataUrl ?? item.url,
+ };
+ }
+ return item;
+ })
+ );
+
+ const result = onSubmit({ files: convertedFiles, text }, event);
+
+ // Handle both sync and async onSubmit
+ if (result instanceof Promise) {
+ try {
+ await result;
+ clear();
+ if (usingProvider) {
+ controller.textInput.clear();
+ }
+ } catch {
+ // Don't clear on error - user may want to retry
+ }
+ } else {
+ // Sync function completed without throwing, clear inputs
+ clear();
+ if (usingProvider) {
+ controller.textInput.clear();
+ }
+ }
+ } catch {
+ // Don't clear on error - user may want to retry
+ }
+ },
+ [usingProvider, controller, files, onSubmit, clear]
+ );
+
+ // Render with or without local provider
+ const inner = (
+ <>
+
+
+ >
+ );
+
+ const withReferencedSources = (
+
+ {inner}
+
+ );
+
+ // Always provide LocalAttachmentsContext so children get validated add function
+ return (
+
+ {withReferencedSources}
+
+ );
+};
+
+export type PromptInputBodyProps = HTMLAttributes;
+
+export const PromptInputBody = ({
+ className,
+ ...props
+}: PromptInputBodyProps) => (
+
+);
+
+export type PromptInputTextareaProps = ComponentProps<
+ typeof InputGroupTextarea
+>;
+
+export const PromptInputTextarea = ({
+ onChange,
+ onKeyDown,
+ className,
+ placeholder = "What would you like to know?",
+ ...props
+}: PromptInputTextareaProps) => {
+ const controller = useOptionalPromptInputController();
+ const attachments = usePromptInputAttachments();
+ const [isComposing, setIsComposing] = useState(false);
+
+ const handleKeyDown: KeyboardEventHandler = useCallback(
+ (e) => {
+ // Call the external onKeyDown handler first
+ onKeyDown?.(e);
+
+ // If the external handler prevented default, don't run internal logic
+ if (e.defaultPrevented) {
+ return;
+ }
+
+ if (e.key === "Enter") {
+ if (isComposing || e.nativeEvent.isComposing) {
+ return;
+ }
+ if (e.shiftKey) {
+ return;
+ }
+ e.preventDefault();
+
+ // Check if the submit button is disabled before submitting
+ const { form } = e.currentTarget;
+ const submitButton = form?.querySelector(
+ 'button[type="submit"]'
+ ) as HTMLButtonElement | null;
+ if (submitButton?.disabled) {
+ return;
+ }
+
+ form?.requestSubmit();
+ }
+
+ // Remove last attachment when Backspace is pressed and textarea is empty
+ if (
+ e.key === "Backspace" &&
+ e.currentTarget.value === "" &&
+ attachments.files.length > 0
+ ) {
+ e.preventDefault();
+ const lastAttachment = attachments.files.at(-1);
+ if (lastAttachment) {
+ attachments.remove(lastAttachment.id);
+ }
+ }
+ },
+ [onKeyDown, isComposing, attachments]
+ );
+
+ const handlePaste: ClipboardEventHandler = useCallback(
+ (event) => {
+ const items = event.clipboardData?.items;
+
+ if (!items) {
+ return;
+ }
+
+ const files: File[] = [];
+
+ for (const item of items) {
+ if (item.kind === "file") {
+ const file = item.getAsFile();
+ if (file) {
+ files.push(file);
+ }
+ }
+ }
+
+ if (files.length > 0) {
+ event.preventDefault();
+ attachments.add(files);
+ }
+ },
+ [attachments]
+ );
+
+ const handleCompositionEnd = useCallback(() => setIsComposing(false), []);
+ const handleCompositionStart = useCallback(() => setIsComposing(true), []);
+
+ const controlledProps = controller
+ ? {
+ onChange: (e: ChangeEvent) => {
+ controller.textInput.setInput(e.currentTarget.value);
+ onChange?.(e);
+ },
+ value: controller.textInput.value,
+ }
+ : {
+ onChange,
+ };
+
+ return (
+
+ );
+};
+
+export type PromptInputHeaderProps = Omit<
+ ComponentProps,
+ "align"
+>;
+
+export const PromptInputHeader = ({
+ className,
+ ...props
+}: PromptInputHeaderProps) => (
+
+);
+
+export type PromptInputFooterProps = Omit<
+ ComponentProps,
+ "align"
+>;
+
+export const PromptInputFooter = ({
+ className,
+ ...props
+}: PromptInputFooterProps) => (
+
+);
+
+export type PromptInputToolsProps = HTMLAttributes;
+
+export const PromptInputTools = ({
+ className,
+ ...props
+}: PromptInputToolsProps) => (
+
+);
+
+export type PromptInputButtonTooltip =
+ | string
+ | {
+ content: ReactNode;
+ shortcut?: string;
+ side?: ComponentProps["side"];
+ };
+
+export type PromptInputButtonProps = ComponentProps & {
+ tooltip?: PromptInputButtonTooltip;
+};
+
+export const PromptInputButton = ({
+ variant = "ghost",
+ className,
+ size,
+ tooltip,
+ ...props
+}: PromptInputButtonProps) => {
+ const newSize =
+ size ?? (Children.count(props.children) > 1 ? "sm" : "icon-sm");
+
+ const button = (
+
+ );
+
+ if (!tooltip) {
+ return button;
+ }
+
+ const tooltipContent =
+ typeof tooltip === "string" ? tooltip : tooltip.content;
+ const shortcut = typeof tooltip === "string" ? undefined : tooltip.shortcut;
+ const side = typeof tooltip === "string" ? "top" : (tooltip.side ?? "top");
+
+ return (
+
+ {button}
+
+ {tooltipContent}
+ {shortcut && (
+ {shortcut}
+ )}
+
+
+ );
+};
+
+export type PromptInputActionMenuProps = ComponentProps;
+export const PromptInputActionMenu = (props: PromptInputActionMenuProps) => (
+
+);
+
+export type PromptInputActionMenuTriggerProps = PromptInputButtonProps;
+
+export const PromptInputActionMenuTrigger = ({
+ className,
+ children,
+ ...props
+}: PromptInputActionMenuTriggerProps) => (
+
+
+ {children ?? }
+
+
+);
+
+export type PromptInputActionMenuContentProps = ComponentProps<
+ typeof DropdownMenuContent
+>;
+export const PromptInputActionMenuContent = ({
+ className,
+ ...props
+}: PromptInputActionMenuContentProps) => (
+
+);
+
+export type PromptInputActionMenuItemProps = ComponentProps<
+ typeof DropdownMenuItem
+>;
+export const PromptInputActionMenuItem = ({
+ className,
+ ...props
+}: PromptInputActionMenuItemProps) => (
+
+);
+
+// Note: Actions that perform side-effects (like opening a file dialog)
+// are provided in opt-in modules (e.g., prompt-input-attachments).
+
+export type PromptInputSubmitProps = ComponentProps & {
+ status?: ChatStatus;
+ onStop?: () => void;
+};
+
+export const PromptInputSubmit = ({
+ className,
+ variant = "default",
+ size = "icon-sm",
+ status,
+ onStop,
+ onClick,
+ children,
+ ...props
+}: PromptInputSubmitProps) => {
+ const isGenerating = status === "submitted" || status === "streaming";
+
+ let Icon = ;
+
+ if (status === "submitted") {
+ Icon = ;
+ } else if (status === "streaming") {
+ Icon = ;
+ } else if (status === "error") {
+ Icon = ;
+ }
+
+ const handleClick = useCallback(
+ (e: React.MouseEvent) => {
+ if (isGenerating && onStop) {
+ e.preventDefault();
+ onStop();
+ return;
+ }
+ onClick?.(e);
+ },
+ [isGenerating, onStop, onClick]
+ );
+
+ return (
+
+ {children ?? Icon}
+
+ );
+};
+
+export type PromptInputSelectProps = ComponentProps;
+
+export const PromptInputSelect = (props: PromptInputSelectProps) => (
+
+);
+
+export type PromptInputSelectTriggerProps = ComponentProps<
+ typeof SelectTrigger
+>;
+
+export const PromptInputSelectTrigger = ({
+ className,
+ ...props
+}: PromptInputSelectTriggerProps) => (
+
+);
+
+export type PromptInputSelectContentProps = ComponentProps<
+ typeof SelectContent
+>;
+
+export const PromptInputSelectContent = ({
+ className,
+ ...props
+}: PromptInputSelectContentProps) => (
+
+);
+
+export type PromptInputSelectItemProps = ComponentProps;
+
+export const PromptInputSelectItem = ({
+ className,
+ ...props
+}: PromptInputSelectItemProps) => (
+
+);
+
+export type PromptInputSelectValueProps = ComponentProps;
+
+export const PromptInputSelectValue = ({
+ className,
+ ...props
+}: PromptInputSelectValueProps) => (
+
+);
+
+export type PromptInputHoverCardProps = ComponentProps;
+
+export const PromptInputHoverCard = ({
+ openDelay = 0,
+ closeDelay = 0,
+ ...props
+}: PromptInputHoverCardProps) => (
+
+);
+
+export type PromptInputHoverCardTriggerProps = ComponentProps<
+ typeof HoverCardTrigger
+>;
+
+export const PromptInputHoverCardTrigger = (
+ props: PromptInputHoverCardTriggerProps
+) => ;
+
+export type PromptInputHoverCardContentProps = ComponentProps<
+ typeof HoverCardContent
+>;
+
+export const PromptInputHoverCardContent = ({
+ align = "start",
+ ...props
+}: PromptInputHoverCardContentProps) => (
+
+);
+
+export type PromptInputTabsListProps = HTMLAttributes;
+
+export const PromptInputTabsList = ({
+ className,
+ ...props
+}: PromptInputTabsListProps) => ;
+
+export type PromptInputTabProps = HTMLAttributes;
+
+export const PromptInputTab = ({
+ className,
+ ...props
+}: PromptInputTabProps) => ;
+
+export type PromptInputTabLabelProps = HTMLAttributes;
+
+export const PromptInputTabLabel = ({
+ className,
+ ...props
+}: PromptInputTabLabelProps) => (
+ // Content provided via children in props
+ // oxlint-disable-next-line eslint-plugin-jsx-a11y(heading-has-content)
+
+);
+
+export type PromptInputTabBodyProps = HTMLAttributes;
+
+export const PromptInputTabBody = ({
+ className,
+ ...props
+}: PromptInputTabBodyProps) => (
+
+);
+
+export type PromptInputTabItemProps = HTMLAttributes;
+
+export const PromptInputTabItem = ({
+ className,
+ ...props
+}: PromptInputTabItemProps) => (
+
+);
+
+export type PromptInputCommandProps = ComponentProps;
+
+export const PromptInputCommand = ({
+ className,
+ ...props
+}: PromptInputCommandProps) => ;
+
+export type PromptInputCommandInputProps = ComponentProps;
+
+export const PromptInputCommandInput = ({
+ className,
+ ...props
+}: PromptInputCommandInputProps) => (
+
+);
+
+export type PromptInputCommandListProps = ComponentProps;
+
+export const PromptInputCommandList = ({
+ className,
+ ...props
+}: PromptInputCommandListProps) => (
+
+);
+
+export type PromptInputCommandEmptyProps = ComponentProps;
+
+export const PromptInputCommandEmpty = ({
+ className,
+ ...props
+}: PromptInputCommandEmptyProps) => (
+
+);
+
+export type PromptInputCommandGroupProps = ComponentProps;
+
+export const PromptInputCommandGroup = ({
+ className,
+ ...props
+}: PromptInputCommandGroupProps) => (
+
+);
+
+export type PromptInputCommandItemProps = ComponentProps;
+
+export const PromptInputCommandItem = ({
+ className,
+ ...props
+}: PromptInputCommandItemProps) => (
+
+);
+
+export type PromptInputCommandSeparatorProps = ComponentProps<
+ typeof CommandSeparator
+>;
+
+export const PromptInputCommandSeparator = ({
+ className,
+ ...props
+}: PromptInputCommandSeparatorProps) => (
+
+);
diff --git a/components/ai-elements/reasoning.tsx b/components/ai-elements/reasoning.tsx
new file mode 100644
index 0000000..765521f
--- /dev/null
+++ b/components/ai-elements/reasoning.tsx
@@ -0,0 +1,227 @@
+"use client";
+
+import { useControllableState } from "@radix-ui/react-use-controllable-state";
+import {
+ Collapsible,
+ CollapsibleContent,
+ CollapsibleTrigger,
+} from "@/components/ui/collapsible";
+import { cn } from "@/lib/utils";
+import { cjk } from "@streamdown/cjk";
+import { code } from "@streamdown/code";
+import { math } from "@streamdown/math";
+import { mermaid } from "@streamdown/mermaid";
+import { BrainIcon, ChevronDownIcon } from "lucide-react";
+import type { ComponentProps, ReactNode } from "react";
+import {
+ createContext,
+ memo,
+ useCallback,
+ useContext,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from "react";
+import { Streamdown, type PluginConfig } from "streamdown";
+
+import { Shimmer } from "./shimmer";
+
+interface ReasoningContextValue {
+ isStreaming: boolean;
+ isOpen: boolean;
+ setIsOpen: (open: boolean) => void;
+ duration: number | undefined;
+}
+
+const ReasoningContext = createContext(null);
+
+export const useReasoning = () => {
+ const context = useContext(ReasoningContext);
+ if (!context) {
+ throw new Error("Reasoning components must be used within Reasoning");
+ }
+ return context;
+};
+
+export type ReasoningProps = ComponentProps & {
+ isStreaming?: boolean;
+ open?: boolean;
+ defaultOpen?: boolean;
+ onOpenChange?: (open: boolean) => void;
+ duration?: number;
+};
+
+const AUTO_CLOSE_DELAY = 1000;
+const MS_IN_S = 1000;
+
+export const Reasoning = memo(
+ ({
+ className,
+ isStreaming = false,
+ open,
+ defaultOpen,
+ onOpenChange,
+ duration: durationProp,
+ children,
+ ...props
+ }: ReasoningProps) => {
+ const resolvedDefaultOpen = defaultOpen ?? isStreaming;
+ // Track if defaultOpen was explicitly set to false (to prevent auto-open)
+ const isExplicitlyClosed = defaultOpen === false;
+
+ const [isOpen, setIsOpen] = useControllableState({
+ defaultProp: resolvedDefaultOpen,
+ onChange: onOpenChange,
+ prop: open,
+ });
+ const [duration, setDuration] = useControllableState({
+ defaultProp: undefined,
+ prop: durationProp,
+ });
+
+ const hasEverStreamedRef = useRef(isStreaming);
+ const [hasAutoClosed, setHasAutoClosed] = useState(false);
+ const startTimeRef = useRef(null);
+
+ // Track when streaming starts and compute duration
+ useEffect(() => {
+ if (isStreaming) {
+ hasEverStreamedRef.current = true;
+ if (startTimeRef.current === null) {
+ startTimeRef.current = Date.now();
+ }
+ } else if (startTimeRef.current !== null) {
+ setDuration(Math.ceil((Date.now() - startTimeRef.current) / MS_IN_S));
+ startTimeRef.current = null;
+ }
+ }, [isStreaming, setDuration]);
+
+ // Auto-open when streaming starts (unless explicitly closed)
+ useEffect(() => {
+ if (isStreaming && !isOpen && !isExplicitlyClosed) {
+ setIsOpen(true);
+ }
+ }, [isStreaming, isOpen, setIsOpen, isExplicitlyClosed]);
+
+ // Auto-close when streaming ends (once only, and only if it ever streamed)
+ useEffect(() => {
+ if (
+ hasEverStreamedRef.current &&
+ !isStreaming &&
+ isOpen &&
+ !hasAutoClosed
+ ) {
+ const timer = setTimeout(() => {
+ setIsOpen(false);
+ setHasAutoClosed(true);
+ }, AUTO_CLOSE_DELAY);
+
+ return () => clearTimeout(timer);
+ }
+ }, [isStreaming, isOpen, setIsOpen, hasAutoClosed]);
+
+ const handleOpenChange = useCallback(
+ (newOpen: boolean) => {
+ setIsOpen(newOpen);
+ },
+ [setIsOpen]
+ );
+
+ const contextValue = useMemo(
+ () => ({ duration, isOpen, isStreaming, setIsOpen }),
+ [duration, isOpen, isStreaming, setIsOpen]
+ );
+
+ return (
+
+
+ {children}
+
+
+ );
+ }
+);
+
+export type ReasoningTriggerProps = ComponentProps<
+ typeof CollapsibleTrigger
+> & {
+ getThinkingMessage?: (isStreaming: boolean, duration?: number) => ReactNode;
+};
+
+const defaultGetThinkingMessage = (isStreaming: boolean, duration?: number) => {
+ if (isStreaming || duration === 0) {
+ return Thinking...;
+ }
+ if (duration === undefined) {
+ return Thought for a few seconds
;
+ }
+ return Thought for {duration} seconds
;
+};
+
+export const ReasoningTrigger = memo(
+ ({
+ className,
+ children,
+ getThinkingMessage = defaultGetThinkingMessage,
+ ...props
+ }: ReasoningTriggerProps) => {
+ const { isStreaming, isOpen, duration } = useReasoning();
+
+ return (
+
+ {children ?? (
+ <>
+
+ {getThinkingMessage(isStreaming, duration)}
+
+ >
+ )}
+
+ );
+ }
+);
+
+export type ReasoningContentProps = ComponentProps<
+ typeof CollapsibleContent
+> & {
+ children: string;
+};
+
+// See message.tsx — same streamdown plugin type skew.
+const streamdownPlugins = { cjk, code, math, mermaid } as unknown as PluginConfig;
+
+export const ReasoningContent = memo(
+ ({ className, children, ...props }: ReasoningContentProps) => (
+
+ {children}
+
+ )
+);
+
+Reasoning.displayName = "Reasoning";
+ReasoningTrigger.displayName = "ReasoningTrigger";
+ReasoningContent.displayName = "ReasoningContent";
diff --git a/components/ai-elements/shimmer.tsx b/components/ai-elements/shimmer.tsx
new file mode 100644
index 0000000..6b635d0
--- /dev/null
+++ b/components/ai-elements/shimmer.tsx
@@ -0,0 +1,77 @@
+"use client";
+
+import { cn } from "@/lib/utils";
+import type { MotionProps } from "motion/react";
+import { motion } from "motion/react";
+import type { CSSProperties, ElementType, JSX } from "react";
+import { memo, useMemo } from "react";
+
+type MotionHTMLProps = MotionProps & Record;
+
+// Cache motion components at module level to avoid creating during render
+const motionComponentCache = new Map<
+ keyof JSX.IntrinsicElements,
+ React.ComponentType
+>();
+
+const getMotionComponent = (element: keyof JSX.IntrinsicElements) => {
+ let component = motionComponentCache.get(element);
+ if (!component) {
+ component = motion.create(element);
+ motionComponentCache.set(element, component);
+ }
+ return component;
+};
+
+export interface TextShimmerProps {
+ children: string;
+ as?: ElementType;
+ className?: string;
+ duration?: number;
+ spread?: number;
+}
+
+const ShimmerComponent = ({
+ children,
+ as: Component = "p",
+ className,
+ duration = 2,
+ spread = 2,
+}: TextShimmerProps) => {
+ const MotionComponent = getMotionComponent(
+ Component as keyof JSX.IntrinsicElements
+ );
+
+ const dynamicSpread = useMemo(
+ () => (children?.length ?? 0) * spread,
+ [children, spread]
+ );
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const Shimmer = memo(ShimmerComponent);
diff --git a/components/answered-card.tsx b/components/answered-card.tsx
new file mode 100644
index 0000000..f75bfed
--- /dev/null
+++ b/components/answered-card.tsx
@@ -0,0 +1,84 @@
+"use client";
+
+import { cn } from "@/lib/utils";
+import type { QuestionAnswerData } from "@/src/core/question-core";
+
+/**
+ * Static rendering of an answered question (tool part in `output-available`).
+ *
+ * Quiz answers are GRADED: ✓/✗ marks, the correct answers, and the
+ * explanation. Ask answers are NOT graded — a plain record of what was said.
+ * (The v1 bug this pins: ask answers must never get quiz grading UI.)
+ */
+export function AnsweredCard({ answer }: { answer: QuestionAnswerData }) {
+ if (answer.dismissed) {
+ return (
+
+ {answer.questionText} — skipped
+
+ );
+ }
+
+ const isQuiz = answer.kind === "quiz";
+ const selectedIdx = new Set(answer.answers.map((a) => a.index));
+ const correctIdx = new Set(answer.correctIndices ?? []);
+
+ return (
+
+
+ {isQuiz ? "Quiz" : "Question"}
+ {isQuiz &&
+ (answer.dontKnow ? (
+ didn't know
+ ) : answer.correct ? (
+ correct
+ ) : (
+ incorrect
+ ))}
+
+
{answer.questionText}
+
+ {answer.options && answer.options.length > 0 && (
+
+ {answer.options.map((opt) => {
+ const sel = selectedIdx.has(opt.index);
+ const key = isQuiz && correctIdx.has(opt.index);
+ return (
+
+ {isQuiz && (
+
+ {sel && key && "✓"}
+ {sel && !key && "✗"}
+ {!sel && key && "✓"}
+
+ )}
+ {opt.label}
+
+ );
+ })}
+
+ )}
+
+ {answer.other &&
“{answer.other}”
}
+ {answer.text &&
“{answer.text}”
}
+ {answer.dontKnow && isQuiz &&
Answered “I don't know”.
}
+
+ {isQuiz && answer.explanation && (
+
+ Why:
+ {answer.explanation}
+
+ )}
+ {answer.note &&
Note: {answer.note}
}
+
+ );
+}
diff --git a/components/chat.tsx b/components/chat.tsx
new file mode 100644
index 0000000..d1396e6
--- /dev/null
+++ b/components/chat.tsx
@@ -0,0 +1,190 @@
+"use client";
+
+import { useEffect, useRef } from "react";
+import { useRouter } from "next/navigation";
+import { useChat } from "@ai-sdk/react";
+import { DefaultChatTransport, isToolUIPart, lastAssistantMessageIsCompleteWithToolCalls } from "ai";
+import {
+ Conversation,
+ ConversationContent,
+ ConversationEmptyState,
+ ConversationScrollButton,
+} from "@/components/ai-elements/conversation";
+import { Message, MessageContent, MessageResponse } from "@/components/ai-elements/message";
+import { Reasoning, ReasoningContent, ReasoningTrigger } from "@/components/ai-elements/reasoning";
+import {
+ PromptInput,
+ PromptInputBody,
+ PromptInputFooter,
+ PromptInputSubmit,
+ PromptInputTextarea,
+ PromptInputTools,
+ type PromptInputMessage,
+} from "@/components/ai-elements/prompt-input";
+import { Shimmer } from "@/components/ai-elements/shimmer";
+import { Button } from "@/components/ui/button";
+import { Separator } from "@/components/ui/separator";
+import { QuizCard } from "@/components/quiz-card";
+import { AnsweredCard } from "@/components/answered-card";
+import type { TeacherUIMessage } from "@/src/agent/ui-message";
+import type { QuestionAnswerData } from "@/src/core/question-core";
+import { findPendingToolCall } from "@/src/core/pending";
+
+/**
+ * The teaching session. All chat state lives in useChat; the server persists
+ * the canonical transcript after every turn. A pending quiz/ask is just an
+ * unanswered tool part — so reload/resume mid-question works for free.
+ */
+export function Chat({
+ chatId,
+ title,
+ initialMessages,
+ initialPrompt,
+}: {
+ chatId: string;
+ title: string;
+ initialMessages: TeacherUIMessage[];
+ initialPrompt?: string;
+}) {
+ const router = useRouter();
+ const { messages, sendMessage, addToolOutput, status, error, stop } =
+ useChat({
+ id: chatId,
+ messages: initialMessages,
+ transport: new DefaultChatTransport({ api: "/api/chat", body: { chatId } }),
+ // After the learner answers (addToolOutput), automatically send the
+ // continuation request so the teacher reacts to the answer.
+ sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
+ });
+
+ // New chat with a topic: send the first message exactly once.
+ const sentInitial = useRef(false);
+ useEffect(() => {
+ if (!sentInitial.current && initialPrompt && initialMessages.length === 0) {
+ sentInitial.current = true;
+ void sendMessage({ text: initialPrompt });
+ }
+ }, [initialPrompt, initialMessages.length, sendMessage]);
+
+ const pending = findPendingToolCall(messages);
+ const busy = status === "submitted" || status === "streaming";
+
+ const submitAnswer = (toolCallId: string, tool: "quiz" | "ask_user_question", output: QuestionAnswerData) => {
+ void addToolOutput({ tool, toolCallId, output });
+ };
+
+ const onComposerSubmit = (msg: PromptInputMessage) => {
+ if (!msg.text.trim() || busy || pending) return;
+ void sendMessage({ text: msg.text });
+ };
+
+ return (
+
+
+
+ {title}
+ {busy && (
+
+ )}
+
+
+
+
+ {messages.length === 0 && !busy && (
+
+ )}
+
+ {messages.map((m, mi) => (
+
+
+ {m.role === "user" ? (
+ {m.parts.filter((p) => p.type === "text").map((p) => p.text).join("\n")}
+ ) : (
+ m.parts.map((part, pi) => {
+ const isLastPart = mi === messages.length - 1 && pi === m.parts.length - 1;
+ switch (part.type) {
+ case "text":
+ return {part.text};
+ case "reasoning":
+ return (
+
+
+ {part.text}
+
+ );
+ case "step-start":
+ return pi > 0 ? : null;
+ case "tool-quiz":
+ case "tool-ask_user_question": {
+ const tool = part.type === "tool-quiz" ? ("quiz" as const) : ("ask_user_question" as const);
+ if (part.state === "output-available") {
+ return ;
+ }
+ if (part.state === "input-available") {
+ return (
+ submitAnswer(part.toolCallId, tool, output)}
+ />
+ );
+ }
+ // input-streaming: the question is still being generated
+ return (
+
+ Preparing a question…
+
+ );
+ }
+ default:
+ return null;
+ }
+ })
+ )}
+
+
+ ))}
+
+ {status === "submitted" && Thinking…}
+ {error && (
+
+ Something went wrong: {error.message}
+
+ )}
+
+
+
+
+
+
+ {pending && (
+
+ Answer the question above to continue.
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/components/home.tsx b/components/home.tsx
new file mode 100644
index 0000000..78f1303
--- /dev/null
+++ b/components/home.tsx
@@ -0,0 +1,78 @@
+"use client";
+
+import { useState } from "react";
+import { useRouter } from "next/navigation";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import type { ChatSummary } from "@/src/store/chats";
+
+export function Home({ chats }: { chats: ChatSummary[] }) {
+ const router = useRouter();
+ const [topic, setTopic] = useState("");
+ const [busy, setBusy] = useState(false);
+
+ const start = async () => {
+ const t = topic.trim();
+ if (!t || busy) return;
+ setBusy(true);
+ try {
+ const res = await fetch("/api/chats", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ title: t }),
+ });
+ const { id } = (await res.json()) as { id: string };
+ router.push(`/chat/${id}?prompt=${encodeURIComponent(t)}`);
+ } catch {
+ setBusy(false);
+ }
+ };
+
+ return (
+
+ Learning Harness
+
+ A teacher with a memory. Start a session on anything you want to understand.
+
+
+
+
+ Sessions
+ {chats.length === 0 ? (
+ No sessions yet.
+ ) : (
+
+ {chats.map((c) => (
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/components/quiz-card.tsx b/components/quiz-card.tsx
new file mode 100644
index 0000000..6ee8446
--- /dev/null
+++ b/components/quiz-card.tsx
@@ -0,0 +1,219 @@
+"use client";
+
+import { useMemo, useState } from "react";
+import { Button } from "@/components/ui/button";
+import { cn } from "@/lib/utils";
+import type { AskToolInput, QuizToolInput } from "@/src/agent/tools";
+import {
+ buildAnswer,
+ coerceCorrectAnswer,
+ normalizeOptions,
+ resolveCorrect,
+ shuffleOptions,
+ type AnswerRequest,
+ type QuestionAnswerData,
+ type QuestionData,
+ type QuestionMode,
+} from "@/src/core/question-core";
+
+/**
+ * The pending interaction card — renders when a quiz/ask tool part is in
+ * `input-available` state. Owns selection state (single source of truth —
+ * buttons, not native inputs), grades via question-core, and submits the
+ * structured answer record as the tool output.
+ *
+ * Quiz: graded (single/multi-select + "I don't know").
+ * Ask: not graded (single/multi-select + "Other" free text, or text mode).
+ */
+
+export interface QuizCardProps {
+ toolName: "quiz" | "ask_user_question";
+ toolCallId: string;
+ input: QuizToolInput | AskToolInput;
+ onSubmit: (output: QuestionAnswerData) => void;
+}
+
+export function QuizCard({ toolName, toolCallId, input, onSubmit }: QuizCardProps) {
+ const kind = toolName === "quiz" ? ("quiz" as const) : ("ask" as const);
+
+ // Build the canonical QuestionData once per question. Quiz options are
+ // shuffled so position never leaks correctness; the display order is
+ // snapshotted into the answer record at submit time.
+ const qd = useMemo(() => {
+ const quizInput = kind === "quiz" ? (input as QuizToolInput) : null;
+ const askInput = kind === "ask" ? (input as AskToolInput) : null;
+ const multi = input.multiSelect === true;
+ const mode: QuestionMode =
+ kind === "quiz"
+ ? multi
+ ? "multi-select"
+ : "single-select"
+ : (askInput?.mode ?? (askInput?.options?.length ? (multi ? "multi-select" : "single-select") : "text"));
+ const options = normalizeOptions((quizInput?.options ?? askInput?.options ?? []) as { label: string; value?: string }[]);
+ const ordered = kind === "quiz" && options.length > 0 ? shuffleOptions(options) : options;
+ const q: QuestionData = {
+ id: toolCallId,
+ kind,
+ question: input.question,
+ context: input.details,
+ mode,
+ options: ordered,
+ createdAt: new Date().toISOString(),
+ };
+ if (quizInput) {
+ const resolved = resolveCorrect(coerceCorrectAnswer(quizInput.correctAnswer), ordered);
+ if (resolved.error) console.error("[quiz] correctAnswer did not resolve:", resolved.error);
+ q.correctIndices = resolved.indices;
+ q.explanation = quizInput.explanation;
+ }
+ return q;
+ }, [toolCallId, kind, input]);
+
+ const [selected, setSelected] = useState>(new Set()); // 0-based indices
+ const [dontKnow, setDontKnow] = useState(false);
+ const [showOther, setShowOther] = useState(false);
+ const [other, setOther] = useState("");
+ const [text, setText] = useState("");
+ const [note, setNote] = useState("");
+
+ const toggle = (idx: number) => {
+ setDontKnow(false);
+ setShowOther(false);
+ setSelected((prev) => {
+ if (qd.mode === "multi-select") {
+ const next = new Set(prev);
+ if (next.has(idx)) next.delete(idx);
+ else next.add(idx);
+ return next;
+ }
+ return new Set([idx]);
+ });
+ };
+
+ const pickDontKnow = () => {
+ setSelected(new Set());
+ setShowOther(false);
+ setDontKnow((v) => !v);
+ };
+
+ const pickOther = () => {
+ setSelected(new Set());
+ setDontKnow(false);
+ setShowOther(true);
+ };
+
+ const canSubmit =
+ qd.mode === "text"
+ ? text.trim().length > 0
+ : dontKnow || (showOther && other.trim().length > 0) || selected.size > 0;
+
+ const submit = () => {
+ if (!canSubmit) return;
+ const req: AnswerRequest = {
+ dontKnow,
+ values: [...selected].map((i) => qd.options[i]!.value),
+ other: showOther ? other : undefined,
+ text: qd.mode === "text" ? text : undefined,
+ note: note.trim() || undefined,
+ };
+ onSubmit(buildAnswer(qd, req));
+ };
+
+ return (
+
+
+ {kind === "quiz" ? "Quiz" : "Question"}
+ {qd.mode === "multi-select" && " · select all that apply"}
+
+
{qd.question}
+ {qd.context &&
{qd.context}
}
+
+ {qd.mode !== "text" && (
+
+ {qd.options.map((opt, i) => {
+ const isSel = selected.has(i);
+ return (
+
+ );
+ })}
+
+ {kind === "quiz" && (
+
+ )}
+
+ {kind === "ask" && (
+
+ )}
+
+ )}
+
+ {(qd.mode === "text" || showOther) && (
+
+ );
+}
diff --git a/components/ui/button-group.tsx b/components/ui/button-group.tsx
new file mode 100644
index 0000000..cd550d7
--- /dev/null
+++ b/components/ui/button-group.tsx
@@ -0,0 +1,83 @@
+import { cva, type VariantProps } from "class-variance-authority"
+import { Slot } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+import { Separator } from "@/components/ui/separator"
+
+const buttonGroupVariants = cva(
+ "flex w-fit items-stretch has-[>[data-slot=button-group]]:gap-2 [&>*]:focus-visible:relative [&>*]:focus-visible:z-10 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
+ {
+ variants: {
+ orientation: {
+ horizontal:
+ "[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none",
+ vertical:
+ "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none",
+ },
+ },
+ defaultVariants: {
+ orientation: "horizontal",
+ },
+ }
+)
+
+function ButtonGroup({
+ className,
+ orientation,
+ ...props
+}: React.ComponentProps<"div"> & VariantProps) {
+ return (
+
+ )
+}
+
+function ButtonGroupText({
+ className,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"div"> & {
+ asChild?: boolean
+}) {
+ const Comp = asChild ? Slot.Root : "div"
+
+ return (
+
+ )
+}
+
+function ButtonGroupSeparator({
+ className,
+ orientation = "vertical",
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ ButtonGroup,
+ ButtonGroupSeparator,
+ ButtonGroupText,
+ buttonGroupVariants,
+}
diff --git a/components/ui/button.tsx b/components/ui/button.tsx
new file mode 100644
index 0000000..4d38506
--- /dev/null
+++ b/components/ui/button.tsx
@@ -0,0 +1,64 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+import { Slot } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+
+const buttonVariants = cva(
+ "inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
+ outline:
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
+ secondary:
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ ghost:
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
+ xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
+ sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
+ icon: "size-9",
+ "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
+ "icon-sm": "size-8",
+ "icon-lg": "size-10",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+function Button({
+ className,
+ variant = "default",
+ size = "default",
+ asChild = false,
+ ...props
+}: React.ComponentProps<"button"> &
+ VariantProps & {
+ asChild?: boolean
+ }) {
+ const Comp = asChild ? Slot.Root : "button"
+
+ return (
+
+ )
+}
+
+export { Button, buttonVariants }
diff --git a/components/ui/collapsible.tsx b/components/ui/collapsible.tsx
new file mode 100644
index 0000000..2f7a4e7
--- /dev/null
+++ b/components/ui/collapsible.tsx
@@ -0,0 +1,33 @@
+"use client"
+
+import { Collapsible as CollapsiblePrimitive } from "radix-ui"
+
+function Collapsible({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function CollapsibleTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function CollapsibleContent({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Collapsible, CollapsibleTrigger, CollapsibleContent }
diff --git a/components/ui/command.tsx b/components/ui/command.tsx
new file mode 100644
index 0000000..8fe3ccb
--- /dev/null
+++ b/components/ui/command.tsx
@@ -0,0 +1,184 @@
+"use client"
+
+import * as React from "react"
+import { Command as CommandPrimitive } from "cmdk"
+import { SearchIcon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog"
+
+function Command({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function CommandDialog({
+ title = "Command Palette",
+ description = "Search for a command to run...",
+ children,
+ className,
+ showCloseButton = true,
+ ...props
+}: React.ComponentProps & {
+ title?: string
+ description?: string
+ className?: string
+ showCloseButton?: boolean
+}) {
+ return (
+
+ )
+}
+
+function CommandInput({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+ )
+}
+
+function CommandList({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function CommandEmpty({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function CommandGroup({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function CommandSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function CommandItem({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function CommandShortcut({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+ )
+}
+
+export {
+ Command,
+ CommandDialog,
+ CommandInput,
+ CommandList,
+ CommandEmpty,
+ CommandGroup,
+ CommandItem,
+ CommandShortcut,
+ CommandSeparator,
+}
diff --git a/components/ui/dialog.tsx b/components/ui/dialog.tsx
new file mode 100644
index 0000000..84bdef4
--- /dev/null
+++ b/components/ui/dialog.tsx
@@ -0,0 +1,158 @@
+"use client"
+
+import * as React from "react"
+import { XIcon } from "lucide-react"
+import { Dialog as DialogPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+
+function Dialog({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogTrigger({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogPortal({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogClose({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DialogContent({
+ className,
+ children,
+ showCloseButton = true,
+ ...props
+}: React.ComponentProps & {
+ showCloseButton?: boolean
+}) {
+ return (
+
+
+
+ {children}
+ {showCloseButton && (
+
+
+ Close
+
+ )}
+
+
+ )
+}
+
+function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function DialogFooter({
+ className,
+ showCloseButton = false,
+ children,
+ ...props
+}: React.ComponentProps<"div"> & {
+ showCloseButton?: boolean
+}) {
+ return (
+
+ {children}
+ {showCloseButton && (
+
+
+
+ )}
+
+ )
+}
+
+function DialogTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DialogDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogOverlay,
+ DialogPortal,
+ DialogTitle,
+ DialogTrigger,
+}
diff --git a/components/ui/dropdown-menu.tsx b/components/ui/dropdown-menu.tsx
new file mode 100644
index 0000000..ae1fcf6
--- /dev/null
+++ b/components/ui/dropdown-menu.tsx
@@ -0,0 +1,257 @@
+"use client"
+
+import * as React from "react"
+import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
+import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+
+function DropdownMenu({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DropdownMenuPortal({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuContent({
+ className,
+ sideOffset = 4,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+function DropdownMenuGroup({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuItem({
+ className,
+ inset,
+ variant = "default",
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean
+ variant?: "default" | "destructive"
+}) {
+ return (
+
+ )
+}
+
+function DropdownMenuCheckboxItem({
+ className,
+ children,
+ checked,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function DropdownMenuRadioGroup({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuRadioItem({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function DropdownMenuLabel({
+ className,
+ inset,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean
+}) {
+ return (
+
+ )
+}
+
+function DropdownMenuSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuShortcut({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+ )
+}
+
+function DropdownMenuSub({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DropdownMenuSubTrigger({
+ className,
+ inset,
+ children,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean
+}) {
+ return (
+
+ {children}
+
+
+ )
+}
+
+function DropdownMenuSubContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ DropdownMenu,
+ DropdownMenuPortal,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuLabel,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioGroup,
+ DropdownMenuRadioItem,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuSub,
+ DropdownMenuSubTrigger,
+ DropdownMenuSubContent,
+}
diff --git a/components/ui/hover-card.tsx b/components/ui/hover-card.tsx
new file mode 100644
index 0000000..91e869c
--- /dev/null
+++ b/components/ui/hover-card.tsx
@@ -0,0 +1,44 @@
+"use client"
+
+import * as React from "react"
+import { HoverCard as HoverCardPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+
+function HoverCard({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function HoverCardTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function HoverCardContent({
+ className,
+ align = "center",
+ sideOffset = 4,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+export { HoverCard, HoverCardTrigger, HoverCardContent }
diff --git a/components/ui/input-group.tsx b/components/ui/input-group.tsx
new file mode 100644
index 0000000..a7652d9
--- /dev/null
+++ b/components/ui/input-group.tsx
@@ -0,0 +1,170 @@
+"use client"
+
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Textarea } from "@/components/ui/textarea"
+
+function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+ textarea]:h-auto",
+
+ // Variants based on alignment.
+ "has-[>[data-align=inline-start]]:[&>input]:pl-2",
+ "has-[>[data-align=inline-end]]:[&>input]:pr-2",
+ "has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
+ "has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
+
+ // Focus state.
+ "has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50",
+
+ // Error state.
+ "has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
+
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+const inputGroupAddonVariants = cva(
+ "flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",
+ {
+ variants: {
+ align: {
+ "inline-start":
+ "order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]",
+ "inline-end":
+ "order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]",
+ "block-start":
+ "order-first w-full justify-start px-3 pt-3 group-has-[>input]/input-group:pt-2.5 [.border-b]:pb-3",
+ "block-end":
+ "order-last w-full justify-start px-3 pb-3 group-has-[>input]/input-group:pb-2.5 [.border-t]:pt-3",
+ },
+ },
+ defaultVariants: {
+ align: "inline-start",
+ },
+ }
+)
+
+function InputGroupAddon({
+ className,
+ align = "inline-start",
+ ...props
+}: React.ComponentProps<"div"> & VariantProps
) {
+ return (
+ {
+ if ((e.target as HTMLElement).closest("button")) {
+ return
+ }
+ e.currentTarget.parentElement?.querySelector("input")?.focus()
+ }}
+ {...props}
+ />
+ )
+}
+
+const inputGroupButtonVariants = cva(
+ "flex items-center gap-2 text-sm shadow-none",
+ {
+ variants: {
+ size: {
+ xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-2 has-[>svg]:px-2 [&>svg:not([class*='size-'])]:size-3.5",
+ sm: "h-8 gap-1.5 rounded-md px-2.5 has-[>svg]:px-2.5",
+ "icon-xs":
+ "size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
+ "icon-sm": "size-8 p-0 has-[>svg]:p-0",
+ },
+ },
+ defaultVariants: {
+ size: "xs",
+ },
+ }
+)
+
+function InputGroupButton({
+ className,
+ type = "button",
+ variant = "ghost",
+ size = "xs",
+ ...props
+}: Omit
, "size"> &
+ VariantProps) {
+ return (
+
+ )
+}
+
+function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
+ return (
+
+ )
+}
+
+function InputGroupInput({
+ className,
+ ...props
+}: React.ComponentProps<"input">) {
+ return (
+
+ )
+}
+
+function InputGroupTextarea({
+ className,
+ ...props
+}: React.ComponentProps<"textarea">) {
+ return (
+
+ )
+}
+
+export {
+ InputGroup,
+ InputGroupAddon,
+ InputGroupButton,
+ InputGroupText,
+ InputGroupInput,
+ InputGroupTextarea,
+}
diff --git a/components/ui/input.tsx b/components/ui/input.tsx
new file mode 100644
index 0000000..f1124ae
--- /dev/null
+++ b/components/ui/input.tsx
@@ -0,0 +1,21 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Input({ className, type, ...props }: React.ComponentProps<"input">) {
+ return (
+
+ )
+}
+
+export { Input }
diff --git a/components/ui/select.tsx b/components/ui/select.tsx
new file mode 100644
index 0000000..c0dc712
--- /dev/null
+++ b/components/ui/select.tsx
@@ -0,0 +1,190 @@
+"use client"
+
+import * as React from "react"
+import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
+import { Select as SelectPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+
+function Select({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SelectGroup({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SelectValue({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SelectTrigger({
+ className,
+ size = "default",
+ children,
+ ...props
+}: React.ComponentProps & {
+ size?: "sm" | "default"
+}) {
+ return (
+
+ {children}
+
+
+
+
+ )
+}
+
+function SelectContent({
+ className,
+ children,
+ position = "item-aligned",
+ align = "center",
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+ {children}
+
+
+
+
+ )
+}
+
+function SelectLabel({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SelectItem({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function SelectSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SelectScrollUpButton({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+function SelectScrollDownButton({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+export {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectScrollDownButton,
+ SelectScrollUpButton,
+ SelectSeparator,
+ SelectTrigger,
+ SelectValue,
+}
diff --git a/components/ui/separator.tsx b/components/ui/separator.tsx
new file mode 100644
index 0000000..cd873e3
--- /dev/null
+++ b/components/ui/separator.tsx
@@ -0,0 +1,28 @@
+"use client"
+
+import * as React from "react"
+import { Separator as SeparatorPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+
+function Separator({
+ className,
+ orientation = "horizontal",
+ decorative = true,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Separator }
diff --git a/components/ui/spinner.tsx b/components/ui/spinner.tsx
new file mode 100644
index 0000000..a70e713
--- /dev/null
+++ b/components/ui/spinner.tsx
@@ -0,0 +1,16 @@
+import { Loader2Icon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
+ return (
+
+ )
+}
+
+export { Spinner }
diff --git a/components/ui/textarea.tsx b/components/ui/textarea.tsx
new file mode 100644
index 0000000..e67d8fe
--- /dev/null
+++ b/components/ui/textarea.tsx
@@ -0,0 +1,18 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
+ return (
+
+ )
+}
+
+export { Textarea }
diff --git a/components/ui/tooltip.tsx b/components/ui/tooltip.tsx
new file mode 100644
index 0000000..ec65c1e
--- /dev/null
+++ b/components/ui/tooltip.tsx
@@ -0,0 +1,57 @@
+"use client"
+
+import * as React from "react"
+import { Tooltip as TooltipPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+
+function TooltipProvider({
+ delayDuration = 0,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function Tooltip({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function TooltipTrigger({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function TooltipContent({
+ className,
+ sideOffset = 0,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+ {children}
+
+
+
+ )
+}
+
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
diff --git a/config.ts b/config.ts
index 167bd7b..5a96da6 100644
--- a/config.ts
+++ b/config.ts
@@ -6,11 +6,12 @@ export const ROOT = path.dirname(fileURLToPath(import.meta.url));
export const PATHS = {
root: ROOT,
- extensions: path.join(ROOT, "extensions"),
- teacherPrompt: path.join(ROOT, "teacher", "system-prompt.md"),
+ /** Teacher system prompt — the agent's instructions (src/agent/). */
+ teacherPrompt: path.join(ROOT, "src", "agent", "system-prompt.md"),
data: path.join(ROOT, "data"),
- /** Hermetic pi agentDir: settings, auth, and NOTHING from ~/.pi/agent except a symlinked auth.json. */
- agentDir: path.join(ROOT, "data", "agent"),
+ /** Chat sessions (AI SDK UIMessage transcripts, one JSON file per chat). */
+ chats: path.join(ROOT, "data", "chats"),
+ /** Old pi session JSONLs — retroactive bootstrap input only. */
sessions: path.join(ROOT, "data", "sessions"),
graph: path.join(ROOT, "data", "graph"),
} as const;
diff --git a/extensions/ask-user-question.ts b/extensions/ask-user-question.ts
deleted file mode 100644
index d048d7e..0000000
--- a/extensions/ask-user-question.ts
+++ /dev/null
@@ -1,669 +0,0 @@
-import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
-import {
- Editor,
- type EditorTheme,
- Key,
- Text,
- matchesKey,
- truncateToWidth,
- wrapTextWithAnsi,
-} from "@mariozechner/pi-tui";
-import { Type } from "@sinclair/typebox";
-
-interface AskOption {
- label: string;
- value: string;
- description?: string;
-}
-
-interface DisplayOption extends AskOption {
- id: string;
- index?: number;
- isOther?: boolean;
- isSubmit?: boolean;
-}
-
-interface TextAnswer {
- type: "text";
- label: string;
- value: string;
-}
-
-interface OptionAnswer {
- type: "option";
- label: string;
- value: string;
- index: number;
-}
-
-interface OtherAnswer {
- type: "other";
- label: string;
- value: string;
-}
-
-type AskAnswer = TextAnswer | OptionAnswer | OtherAnswer;
-type AskUserQuestionStatus = "answered" | "cancelled" | "unavailable";
-type AskUserQuestionMode = "text" | "single-select" | "multi-select";
-
-interface AskUserQuestionResultDetails {
- status: AskUserQuestionStatus;
- question: string;
- context?: string;
- mode: AskUserQuestionMode;
- answers: AskAnswer[];
- message?: string;
-}
-
-const OptionSchema = Type.Object({
- label: Type.String({
- description:
- 'Display label for the option. If you recommend an option, place it first and append "(Recommended)" to the label.',
- }),
- value: Type.Optional(
- Type.String({
- description: "Optional machine-readable value returned for the option. Defaults to the label.",
- }),
- ),
- description: Type.Optional(Type.String({ description: "Optional extra detail shown below the option." })),
-});
-
-const AskUserQuestionParams = Type.Object({
- question: Type.String({
- description: "The single question to ask the user. Ask exactly one question per tool call.",
- }),
- details: Type.Optional(
- Type.String({
- description: "Optional extra context or instructions shown under the question.",
- }),
- ),
- options: Type.Optional(
- Type.Array(OptionSchema, {
- description:
- "Optional multiple-choice options. Omit or pass an empty array for free-form text input. Users will always be able to choose Other and type a custom answer when options are provided.",
- }),
- ),
- multiSelect: Type.Optional(
- Type.Boolean({
- description: "Set to true to allow multiple answers to be selected for a question.",
- }),
- ),
-});
-
-function normalizeOptions(options: Array<{ label: string; value?: string; description?: string }> | undefined): AskOption[] {
- return (options || [])
- .map((option) => ({
- label: option.label.trim(),
- value: option.value?.trim() || option.label.trim(),
- description: option.description?.trim() || undefined,
- }))
- .filter((option) => option.label.length > 0);
-}
-
-function getOtherLabel(options: AskOption[]): string {
- return options.some((option) => option.label.toLowerCase() === "other") ? "Other (custom)" : "Other";
-}
-
-function createEditorTheme(theme: any): EditorTheme {
- return {
- borderColor: (s) => theme.fg("accent", s),
- selectList: {
- selectedPrefix: (t) => theme.fg("accent", t),
- selectedText: (t) => theme.fg("accent", t),
- description: (t) => theme.fg("muted", t),
- scrollInfo: (t) => theme.fg("dim", t),
- noMatch: (t) => theme.fg("warning", t),
- },
- };
-}
-
-function addWrapped(lines: string[], text: string, width: number, indent = ""): void {
- const contentWidth = Math.max(1, width - indent.length);
- for (const line of wrapTextWithAnsi(text, contentWidth)) {
- lines.push(truncateToWidth(`${indent}${line}`, width));
- }
-}
-
-function formatAnswerForModel(answer: AskAnswer): string {
- switch (answer.type) {
- case "text":
- return answer.label;
- case "other":
- return `Other: ${answer.label}`;
- case "option":
- return `${answer.index}. ${answer.label}`;
- }
-}
-
-function answerSortRank(answer: AskAnswer): number {
- switch (answer.type) {
- case "option":
- return answer.index;
- case "other":
- return Number.MAX_SAFE_INTEGER - 1;
- case "text":
- return Number.MAX_SAFE_INTEGER;
- }
-}
-
-function sortAnswers(answers: AskAnswer[]): AskAnswer[] {
- return [...answers].sort((a, b) => answerSortRank(a) - answerSortRank(b));
-}
-
-function buildStructuredResult(
- status: AskUserQuestionStatus,
- question: string,
- mode: AskUserQuestionMode,
- answers: AskAnswer[],
- context?: string,
- message?: string,
-) {
- return {
- status,
- question,
- context,
- mode,
- answers,
- message,
- } as AskUserQuestionResultDetails;
-}
-
-function cancelledResult(question: string, mode: AskUserQuestionMode, context?: string) {
- const message = "User cancelled the question";
- return {
- content: [{ type: "text" as const, text: message }],
- details: buildStructuredResult("cancelled", question, mode, [], context, message),
- };
-}
-
-function unavailableResult(question: string, mode: AskUserQuestionMode, message: string, context?: string) {
- return {
- content: [{ type: "text" as const, text: message }],
- details: buildStructuredResult("unavailable", question, mode, [], context, message),
- };
-}
-
-function buildResult(question: string, context: string | undefined, mode: AskUserQuestionMode, answers: AskAnswer[]) {
- let text: string;
- if (mode === "text") {
- const answer = answers[0];
- text = answer.label.trim().length > 0 ? `User answered: ${answer.label}` : "User submitted an empty response";
- } else if (mode === "single-select") {
- text = `User selected: ${formatAnswerForModel(answers[0])}`;
- } else {
- text = `User selected:\n${answers.map((answer) => `- ${formatAnswerForModel(answer)}`).join("\n")}`;
- }
-
- return {
- content: [{ type: "text" as const, text }],
- details: buildStructuredResult("answered", question, mode, answers, context),
- };
-}
-
-async function askSingleChoice(
- ctx: any,
- question: string,
- context: string | undefined,
- options: AskOption[],
-): Promise {
- const otherLabel = getOtherLabel(options);
- const allOptions: DisplayOption[] = [
- ...options.map((option, index) => ({ ...option, id: `option:${index}`, index: index + 1 })),
- { id: "other", label: otherLabel, value: "__other__", isOther: true },
- ];
-
- return ctx.ui.custom((tui: any, theme: any, _kb: any, done: (result: AskAnswer | null) => void) => {
- let optionIndex = 0;
- let editMode = false;
- let cachedLines: string[] | undefined;
- let cachedWidth = -1;
- const editor = new Editor(tui, createEditorTheme(theme));
-
- editor.onSubmit = (value) => {
- const trimmed = value.trim();
- if (!trimmed) return;
- done({ type: "other", label: trimmed, value: trimmed });
- };
-
- function refresh() {
- cachedLines = undefined;
- tui.requestRender();
- }
-
- function handleInput(data: string) {
- if (editMode) {
- if (matchesKey(data, Key.escape)) {
- editMode = false;
- editor.setText("");
- refresh();
- return;
- }
- editor.handleInput(data);
- refresh();
- return;
- }
-
- if (matchesKey(data, Key.up)) {
- optionIndex = Math.max(0, optionIndex - 1);
- refresh();
- return;
- }
- if (matchesKey(data, Key.down)) {
- optionIndex = Math.min(allOptions.length - 1, optionIndex + 1);
- refresh();
- return;
- }
- if (matchesKey(data, Key.enter)) {
- const selected = allOptions[optionIndex];
- if (selected.isOther) {
- editMode = true;
- editor.setText("");
- refresh();
- return;
- }
- done({
- type: "option",
- label: selected.label,
- value: selected.value,
- index: selected.index!,
- });
- return;
- }
- if (matchesKey(data, Key.escape)) {
- done(null);
- }
- }
-
- function render(width: number): string[] {
- // The cache MUST be keyed on width: pi-tui calls requestRender() but NOT
- // invalidate() on terminal resize, so render() can be re-entered with a
- // new width. Returning stale wider lines trips the TUI width guard and
- // crashes the process.
- if (cachedLines && cachedWidth === width) return cachedLines;
-
- const lines: string[] = [];
- const add = (text: string) => lines.push(truncateToWidth(text, width));
-
- add(theme.fg("accent", "─".repeat(width)));
- addWrapped(lines, theme.fg("text", ` ${question}`), width);
- if (context) {
- lines.push("");
- addWrapped(lines, theme.fg("muted", ` ${context}`), width);
- }
- lines.push("");
-
- for (let i = 0; i < allOptions.length; i++) {
- const option = allOptions[i];
- const selected = i === optionIndex;
- const prefix = selected ? theme.fg("accent", "> ") : " ";
- const label = option.isOther ? option.label : `${option.index}. ${option.label}`;
- const styled = selected ? theme.fg("accent", label) : theme.fg("text", label);
- add(`${prefix}${styled}`);
- if (option.description) {
- addWrapped(lines, theme.fg("muted", option.description), width, " ");
- }
- }
-
- if (editMode) {
- lines.push("");
- add(theme.fg("muted", " Write your custom answer:"));
- for (const line of editor.render(Math.max(1, width - 2))) {
- add(` ${line}`);
- }
- lines.push("");
- add(theme.fg("dim", " Enter to submit • Esc to go back"));
- } else {
- lines.push("");
- add(theme.fg("dim", " ↑↓ navigate • Enter select • Esc cancel"));
- }
-
- add(theme.fg("accent", "─".repeat(width)));
- cachedLines = lines;
- cachedWidth = width;
- return lines;
- }
-
- return {
- render,
- invalidate: () => {
- cachedLines = undefined;
- },
- handleInput,
- };
- });
-}
-
-async function askMultiChoice(
- ctx: any,
- question: string,
- context: string | undefined,
- options: AskOption[],
-): Promise {
- const otherLabel = getOtherLabel(options);
- const choiceItems: DisplayOption[] = options.map((option, index) => ({
- ...option,
- id: `option:${index}`,
- index: index + 1,
- }));
- const submitItem: DisplayOption = { id: "submit", label: "Submit", value: "__submit__", isSubmit: true };
- const allItems: DisplayOption[] = [
- ...choiceItems,
- { id: "other", label: otherLabel, value: "__other__", isOther: true },
- submitItem,
- ];
-
- return ctx.ui.custom((tui: any, theme: any, _kb: any, done: (result: AskAnswer[] | null) => void) => {
- let optionIndex = 0;
- let editMode = false;
- let cachedLines: string[] | undefined;
- let cachedWidth = -1;
- const selected = new Map();
- const editor = new Editor(tui, createEditorTheme(theme));
-
- editor.onSubmit = (value) => {
- const trimmed = value.trim();
- if (!trimmed) return;
- selected.set("other", { type: "other", label: trimmed, value: trimmed });
- editMode = false;
- refresh();
- };
-
- function refresh() {
- cachedLines = undefined;
- tui.requestRender();
- }
-
- function toggleOption(item: DisplayOption) {
- if (selected.has(item.id)) {
- selected.delete(item.id);
- } else {
- selected.set(item.id, {
- type: "option",
- label: item.label,
- value: item.value,
- index: item.index!,
- });
- }
- refresh();
- }
-
- function handleInput(data: string) {
- if (editMode) {
- if (matchesKey(data, Key.escape)) {
- editMode = false;
- editor.setText(selected.get("other")?.label || "");
- refresh();
- return;
- }
- editor.handleInput(data);
- refresh();
- return;
- }
-
- if (matchesKey(data, Key.up)) {
- optionIndex = Math.max(0, optionIndex - 1);
- refresh();
- return;
- }
- if (matchesKey(data, Key.down)) {
- optionIndex = Math.min(allItems.length - 1, optionIndex + 1);
- refresh();
- return;
- }
-
- const current = allItems[optionIndex];
- if (matchesKey(data, Key.space)) {
- if (current.isSubmit) return;
- if (current.isOther) {
- if (selected.has("other")) {
- selected.delete("other");
- refresh();
- } else {
- editMode = true;
- editor.setText("");
- refresh();
- }
- return;
- }
- toggleOption(current);
- return;
- }
-
- if (matchesKey(data, Key.enter)) {
- if (current.isSubmit) {
- if (selected.size > 0) {
- done(sortAnswers(Array.from(selected.values())));
- }
- return;
- }
- if (current.isOther) {
- editMode = true;
- editor.setText(selected.get("other")?.label || "");
- refresh();
- return;
- }
- toggleOption(current);
- return;
- }
-
- if (matchesKey(data, Key.escape)) {
- done(null);
- }
- }
-
- function render(width: number): string[] {
- // The cache MUST be keyed on width: pi-tui calls requestRender() but NOT
- // invalidate() on terminal resize, so render() can be re-entered with a
- // new width. Returning stale wider lines trips the TUI width guard and
- // crashes the process.
- if (cachedLines && cachedWidth === width) return cachedLines;
-
- const lines: string[] = [];
- const add = (text: string) => lines.push(truncateToWidth(text, width));
-
- add(theme.fg("accent", "─".repeat(width)));
- addWrapped(lines, theme.fg("text", ` ${question}`), width);
- if (context) {
- lines.push("");
- addWrapped(lines, theme.fg("muted", ` ${context}`), width);
- }
- lines.push("");
-
- for (let i = 0; i < allItems.length; i++) {
- const item = allItems[i];
- const isFocused = i === optionIndex;
- const prefix = isFocused ? theme.fg("accent", "> ") : " ";
-
- if (item.isSubmit) {
- const label = selected.size > 0 ? `✓ ${item.label} (${selected.size} selected)` : `○ ${item.label}`;
- const styled = isFocused
- ? theme.fg("accent", label)
- : theme.fg(selected.size > 0 ? "success" : "dim", label);
- add(`${prefix}${styled}`);
- continue;
- }
-
- if (item.isOther) {
- const other = selected.get("other");
- const marker = other ? "[x]" : "[ ]";
- const suffix = other ? ` — ${other.label}` : "";
- const styled = isFocused
- ? theme.fg("accent", `${marker} ${item.label}${suffix}`)
- : theme.fg(other ? "success" : "text", `${marker} ${item.label}${suffix}`);
- add(`${prefix}${styled}`);
- continue;
- }
-
- const checked = selected.has(item.id);
- const marker = checked ? "[x]" : "[ ]";
- const label = `${marker} ${item.index}. ${item.label}`;
- const styled = isFocused
- ? theme.fg("accent", label)
- : theme.fg(checked ? "success" : "text", label);
- add(`${prefix}${styled}`);
- if (item.description) {
- addWrapped(lines, theme.fg("muted", item.description), width, " ");
- }
- }
-
- if (editMode) {
- lines.push("");
- add(theme.fg("muted", " Write your custom answer:"));
- for (const line of editor.render(Math.max(1, width - 2))) {
- add(` ${line}`);
- }
- lines.push("");
- add(theme.fg("dim", " Enter to save • Esc to go back"));
- } else {
- lines.push("");
- if (selected.size === 0) {
- add(theme.fg("warning", " Select at least one answer before submitting."));
- }
- add(theme.fg("dim", " ↑↓ navigate • Space toggle • Enter edit/submit • Esc cancel"));
- }
-
- add(theme.fg("accent", "─".repeat(width)));
- cachedLines = lines;
- cachedWidth = width;
- return lines;
- }
-
- return {
- render,
- invalidate: () => {
- cachedLines = undefined;
- },
- handleInput,
- };
- });
-}
-
-// Shared UI mutex. ctx.ui.custom()/editor can only handle one active call at
-// a time, so ALL pop-up-style tools (ask_user_question, quiz, ...) must
-// serialize against each other, not just against themselves. We stash one
-// mutex on globalThis so separate extension files can share it without
-// importing each other.
-const SHARED_UI_LOCK_KEY = "__piSharedUiLock";
-function getSharedUiLock() {
- const g = globalThis as any;
- if (!g[SHARED_UI_LOCK_KEY]) {
- let chain: Promise = Promise.resolve();
- g[SHARED_UI_LOCK_KEY] = {
- withLock(fn: () => T | Promise): Promise {
- const prev = chain;
- let release: () => void;
- chain = new Promise((r) => { release = r; });
- return prev.then(fn).finally(() => release!());
- },
- };
- }
- return g[SHARED_UI_LOCK_KEY] as { withLock(fn: () => T | Promise): Promise };
-}
-const sharedUiLock = getSharedUiLock();
-
-function withUILock(fn: () => Promise): Promise {
- return sharedUiLock.withLock(fn);
-}
-
-export default function askUserQuestion(pi: ExtensionAPI) {
- pi.registerTool({
- name: "ask_user_question",
- label: "ask_user_question",
- description:
- "Ask the user a single question and pause execution until they answer. Use this when requirements are ambiguous, user preferences are needed, a decision would materially affect implementation, or you need confirmation before proceeding. Ask exactly one question per tool call, and prefer multiple separate tool calls over bundling unrelated questions together.",
- promptSnippet:
- "Use this tool to ask exactly one clarifying question, missing-requirement question, preference question, or decision question before continuing.",
- promptGuidelines: [
- "Ask exactly one question per tool call.",
- "If you need answers to multiple questions, make multiple separate ask_user_question tool calls instead of combining them into one prompt.",
- 'Users will always be able to select "Other" to provide custom text input when options are provided.',
- "Use multiSelect: true only when you need multiple answers to the same question.",
- 'If you recommend a specific option, make it the first option in the list and add "(Recommended)" at the end of the label.',
- "Prefer this tool over guessing when requirements, preferences, or implementation choices are unclear.",
- "Use this tool when multiple valid implementation paths exist and the preferred path depends on user choice.",
- ],
- parameters: AskUserQuestionParams,
-
- async execute(_toolCallId, params, signal, _onUpdate, ctx) {
- const options = normalizeOptions(params.options);
- const context = params.details?.trim() || undefined;
- const mode: AskUserQuestionMode = options.length === 0 ? "text" : params.multiSelect ? "multi-select" : "single-select";
-
- if (signal?.aborted) {
- return cancelledResult(params.question, mode, context);
- }
-
- if (!ctx.hasUI) {
- return unavailableResult(params.question, mode, "ask_user_question requires interactive mode UI", context);
- }
-
- return withUILock(async () => {
- if (mode === "text") {
- const editorTitle = context ? `${params.question}\n\n${context}` : params.question;
- const answer = await ctx.ui.editor(editorTitle);
- if (answer === undefined) {
- return cancelledResult(params.question, mode, context);
- }
- return buildResult(params.question, context, mode, [
- { type: "text", label: answer.trim(), value: answer.trim() },
- ]);
- }
-
- if (mode === "single-select") {
- const answer = await askSingleChoice(ctx, params.question, context, options);
- if (!answer) {
- return cancelledResult(params.question, mode, context);
- }
- return buildResult(params.question, context, mode, [answer]);
- }
-
- const answers = await askMultiChoice(ctx, params.question, context, options);
- if (!answers) {
- return cancelledResult(params.question, mode, context);
- }
- return buildResult(params.question, context, mode, answers);
- });
- },
-
- renderCall(args, theme) {
- const options = normalizeOptions(args.options as Array<{ label: string; value?: string; description?: string }> | undefined);
- let text = theme.fg("toolTitle", theme.bold("ask_user_question ")) + theme.fg("muted", args.question);
- if (args.multiSelect) {
- text += theme.fg("dim", " [multi-select]");
- }
- if (options.length > 0) {
- const labels = [...options.map((option) => option.label), getOtherLabel(options)].join(", ");
- text += `\n${theme.fg("dim", ` Options: ${labels}`)}`;
- }
- return new Text(text, 0, 0);
- },
-
- renderResult(result, _options, theme) {
- const details = result.details as AskUserQuestionResultDetails | undefined;
- if (!details) {
- const first = result.content[0];
- return new Text(first?.type === "text" ? first.text : "", 0, 0);
- }
-
- if (details.status === "cancelled") {
- return new Text(theme.fg("warning", details.message || "Cancelled"), 0, 0);
- }
-
- if (details.status === "unavailable") {
- return new Text(theme.fg("warning", details.message || "ask_user_question unavailable"), 0, 0);
- }
-
- const lines = details.answers.map((answer) => {
- switch (answer.type) {
- case "text":
- return `${theme.fg("success", "✓ ")}${theme.fg("accent", answer.label || "(empty response)")}`;
- case "other":
- return `${theme.fg("success", "✓ ")}${theme.fg("muted", "Other: ")}${theme.fg("accent", answer.label)}`;
- case "option":
- return `${theme.fg("success", "✓ ")}${theme.fg("accent", `${answer.index}. ${answer.label}`)}`;
- }
- });
- return new Text(lines.join("\n"), 0, 0);
- },
- });
-}
diff --git a/extensions/ask-web.ts b/extensions/ask-web.ts
deleted file mode 100644
index b128ac2..0000000
--- a/extensions/ask-web.ts
+++ /dev/null
@@ -1,147 +0,0 @@
-/**
- * ask-web — the ask_user_question tool in WEB DISPATCH mode.
- *
- * Same dispatch shape as quiz-web: the tool validates and persists the
- * question (lh_question custom entry), returns immediately, and the learner's
- * answer arrives as the next user message once the server calls the bridge.
- * Not graded — this is the preference/decision sibling of quiz.
- */
-
-import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
-import { Text } from "@mariozechner/pi-tui";
-import { Type } from "@sinclair/typebox";
-import {
- QUESTION_ANSWERED_ENTRY,
- QUESTION_ENTRY,
- type QuestionData,
- normalizeOptions,
-} from "./core/question-core.ts";
-import { getBridge } from "./core/bridge.ts";
-
-const OptionSchema = Type.Object({
- label: Type.String({
- description:
- 'Display label for the option. If you recommend an option, place it first and append "(Recommended)" to the label.',
- }),
- value: Type.Optional(
- Type.String({ description: "Optional machine-readable value returned for the option. Defaults to the label." }),
- ),
- description: Type.Optional(Type.String({ description: "Optional extra detail shown below the option." })),
-});
-
-const AskUserQuestionParams = Type.Object({
- question: Type.String({
- description: "The single question to ask the user. Ask exactly one question per tool call.",
- }),
- details: Type.Optional(
- Type.String({ description: "Optional extra context or instructions shown under the question." }),
- ),
- options: Type.Optional(
- Type.Array(OptionSchema, {
- description:
- "Optional multiple-choice options. Omit or pass an empty array for free-form text input. Users will always be able to choose Other and type a custom answer when options are provided.",
- }),
- ),
- multiSelect: Type.Optional(
- Type.Boolean({ description: "Set to true to allow multiple answers to be selected for a question." }),
- ),
-});
-
-const PRESENTED_MESSAGE =
- "Question displayed to the learner. End your turn now and wait: their answer will arrive as the next user message. Do not call any other tool before receiving it.";
-
-function unavailableResult(question: string, mode: string, message: string, context?: string) {
- return {
- content: [{ type: "text" as const, text: message }],
- details: { status: "unavailable", question, context, mode, message },
- };
-}
-
-export default function askWeb(pi: ExtensionAPI) {
- const bridge = getBridge();
-
- let currentCtx: { sessionManager: { getEntries(): Array<{ type: string; customType?: string; data?: unknown }> } } | null = null;
-
- bridge.registerBackend({
- kind: "ask",
- appendAnswered: (data) => pi.appendEntry(QUESTION_ANSWERED_ENTRY, data),
- sendUserMessage: (text) => pi.sendUserMessage(text, { deliverAs: "followUp" }),
- getEntries: () => currentCtx?.sessionManager.getEntries() ?? [],
- });
-
- // Re-arm pending questions on session (re)load — exit/resume mid-question.
- pi.on("session_start", (_event, ctx) => {
- currentCtx = ctx;
- bridge.clear("ask");
- bridge.rearm("ask");
- });
-
- pi.registerTool({
- name: "ask_user_question",
- label: "ask_user_question",
- description:
- "Ask the user a single question and pause execution until they answer. Use this when requirements are ambiguous, user preferences are needed, a decision would materially affect implementation, or you need confirmation before proceeding. Ask exactly one question per tool call, and prefer multiple separate tool calls over bundling unrelated questions together.",
- promptSnippet:
- "Use this tool to ask exactly one clarifying question, missing-requirement question, preference question, or decision question before continuing.",
- promptGuidelines: [
- "Ask exactly one question per tool call.",
- "If you need answers to multiple questions, make multiple separate ask_user_question tool calls instead of combining them into one prompt.",
- 'Users will always be able to select "Other" to provide custom text input when options are provided.',
- "Use multiSelect: true only when you need multiple answers to the same question.",
- 'If you recommend a specific option, make it the first option in the list and add "(Recommended)" at the end of the label.',
- "Prefer this tool over guessing when requirements, preferences, or implementation choices are unclear.",
- "Use this tool when multiple valid implementation paths exist and the preferred path depends on user choice.",
- "After calling ask_user_question, END YOUR TURN. The answer arrives as the next user message; do not call any other tool while waiting.",
- ],
- parameters: AskUserQuestionParams,
-
- async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
- const context = params.details?.trim() || undefined;
- let options;
- try {
- options = normalizeOptions(params.options);
- } catch (e) {
- return unavailableResult(params.question, "single-select", `ask_user_question ${(e as Error).message}`, context);
- }
- const mode = options.length === 0 ? "text" : params.multiSelect ? "multi-select" : "single-select";
-
- const data: QuestionData = {
- id: `q_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`,
- kind: "ask",
- question: params.question,
- context,
- mode,
- options,
- createdAt: new Date().toISOString(),
- };
-
- pi.appendEntry(QUESTION_ENTRY, data);
- bridge.publish(data);
-
- return {
- content: [{ type: "text" as const, text: PRESENTED_MESSAGE }],
- details: {
- status: "presented",
- question: params.question,
- context,
- mode,
- options: options.map((o, i) => ({ index: i + 1, label: o.label })),
- },
- };
- },
-
- renderCall(args, theme) {
- let text = theme.fg("toolTitle", theme.bold("ask_user_question ")) + theme.fg("muted", args.question);
- if (args.multiSelect) text += theme.fg("dim", " [multi-select]");
- return new Text(text, 0, 0);
- },
-
- renderResult(result, _options, theme) {
- const details = result.details as { status?: string; message?: string } | undefined;
- if (details?.status === "unavailable") {
- return new Text(theme.fg("warning", details.message || "ask_user_question unavailable"), 0, 0);
- }
- return new Text(theme.fg("muted", "Question displayed to the learner — awaiting their answer."), 0, 0);
- },
- });
-}
diff --git a/extensions/core/bridge.ts b/extensions/core/bridge.ts
deleted file mode 100644
index e535305..0000000
--- a/extensions/core/bridge.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-/**
- * bridge — in-process communication between the web server and the question
- * extensions. Both run in the same Node process (the server embeds pi via the
- * SDK), so a globalThis registry is the simplest robust channel.
- *
- * The server only ever needs ONE operation: `answer()` / `dismiss()` — which
- * must run inside an extension (it needs the ExtensionAPI: appendEntry +
- * sendUserMessage). Everything else (new questions, answered questions) the
- * server observes directly from the session event stream (`entry_appended`),
- * which is the single source of truth.
- *
- * The registry is rebuilt on every `session_start` (resume re-arm) by scanning
- * the session entries for lh_question / lh_question_answered.
- */
-
-import {
- type AnswerRequest,
- type QuestionData,
- QUESTION_ANSWERED_ENTRY,
- QUESTION_ENTRY,
- type QuestionKind,
- buildAnswer,
- formatAnswerMessage,
-} from "./question-core.ts";
-
-interface Answerer {
- (data: QuestionData, req: AnswerRequest): void;
-}
-
-interface Backend {
- kind: QuestionKind;
- /** Append a custom entry to the session (not sent to the LLM). */
- appendAnswered(data: unknown): void;
- /** Send a user message into the session (triggers an agent turn). */
- sendUserMessage(text: string): void;
- /** All entries of the current session branch. */
- getEntries(): Array<{ type: string; customType?: string; data?: unknown }>;
-}
-
-export interface AnswerOutcome {
- ok: boolean;
- error?: string;
-}
-
-class QuestionBridge {
- /** kind → backend (registered by each web extension at load) */
- private backends = new Map();
- /** questionId → data, insertion-ordered (oldest first) */
- private pending = new Map();
-
- registerBackend(backend: Backend): void {
- this.backends.set(backend.kind, backend);
- }
-
- /** Called by a web extension's tool execute(): record + persist the question. */
- publish(data: QuestionData): void {
- this.pending.set(data.id, data);
- }
-
- /** Clear this kind's pending questions (called on session_start before re-arm). */
- clear(kind: QuestionKind): void {
- for (const [id, qd] of this.pending) {
- if (qd.kind === kind) this.pending.delete(id);
- }
- }
-
- /** Re-arm from session entries (called on session_start after clear). */
- rearm(kind: QuestionKind): void {
- const backend = this.backends.get(kind);
- if (!backend) return;
- const answered = new Set();
- for (const e of backend.getEntries()) {
- if (e.type === "custom" && e.customType === QUESTION_ANSWERED_ENTRY) {
- const d = e.data as { questionId?: string } | undefined;
- if (d?.questionId) answered.add(d.questionId);
- }
- }
- for (const e of backend.getEntries()) {
- if (e.type === "custom" && e.customType === QUESTION_ENTRY) {
- const d = e.data as QuestionData | undefined;
- if (d?.kind === kind && !answered.has(d.id)) this.pending.set(d.id, d);
- }
- }
- }
-
- answer(questionId: string, req: AnswerRequest): AnswerOutcome {
- const qd = this.pending.get(questionId);
- if (!qd) return { ok: false, error: "unknown or already-answered question" };
- const backend = this.backends.get(qd.kind);
- if (!backend) return { ok: false, error: "no backend registered" };
- this.pending.delete(questionId);
- const answerData = buildAnswer(qd, req);
- backend.appendAnswered(answerData);
- backend.sendUserMessage(formatAnswerMessage(answerData));
- return { ok: true };
- }
-
- dismiss(questionId: string): AnswerOutcome {
- const qd = this.pending.get(questionId);
- if (!qd) return { ok: false, error: "unknown or already-answered question" };
- const backend = this.backends.get(qd.kind);
- if (!backend) return { ok: false, error: "no backend registered" };
- this.pending.delete(questionId);
- const answerData = buildAnswer(qd, { dontKnow: false });
- answerData.dismissed = true;
- backend.appendAnswered(answerData);
- backend.sendUserMessage(formatAnswerMessage(answerData));
- return { ok: true };
- }
-}
-
-const BRIDGE_KEY = "__learningHarnessQuestionBridge";
-
-export function getBridge(): QuestionBridge {
- const g = globalThis as Record;
- if (!g[BRIDGE_KEY]) g[BRIDGE_KEY] = new QuestionBridge();
- return g[BRIDGE_KEY] as QuestionBridge;
-}
diff --git a/extensions/quiz-web.ts b/extensions/quiz-web.ts
deleted file mode 100644
index b3a25cf..0000000
--- a/extensions/quiz-web.ts
+++ /dev/null
@@ -1,200 +0,0 @@
-/**
- * quiz-web — the quiz tool in WEB DISPATCH mode.
- *
- * Unlike the terminal quiz (which blocks on an interactive component), this
- * variant returns immediately after persisting the question:
- *
- * 1. execute() validates + shuffles + resolves correct indices, appends an
- * `lh_question` custom entry (NOT in LLM context — the correct answer and
- * explanation are safe there), publishes to the bridge, and returns.
- * 2. The tool result tells the agent to end its turn — the learner's graded
- * answer arrives as the next user message.
- * 3. The server calls bridge.answer() when the learner answers; the
- * extension grades, appends `lh_question_answered`, and sends the graded
- * answer into the session as a user message.
- *
- * This shape makes exit/resume mid-quiz free: the pending state lives in the
- * session file itself, and is re-armed on session_start.
- */
-
-import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
-import { Text } from "@mariozechner/pi-tui";
-import { Type } from "@sinclair/typebox";
-import {
- QUESTION_ANSWERED_ENTRY,
- QUESTION_ENTRY,
- type QuestionData,
- normalizeOptions,
- resolveCorrect,
- shuffleOptions,
-} from "./core/question-core.ts";
-import { getBridge } from "./core/bridge.ts";
-
-const OptionSchema = Type.Object({
- label: Type.String({ description: "Display label for the answer option." }),
- value: Type.Optional(
- Type.String({ description: "Optional machine-readable value returned for the option. Defaults to the label." }),
- ),
- description: Type.Optional(Type.String({ description: "Optional extra detail shown below the option." })),
-});
-
-const QuizParams = Type.Object({
- question: Type.String({
- description: "The single quiz question to ask. Ask exactly one question per tool call.",
- }),
- details: Type.Optional(
- Type.String({ description: "Optional extra context or instructions shown under the question." }),
- ),
- options: Type.Array(OptionSchema, {
- description:
- "The answer options (2 or more). Options only — there is no free-text mode. Give each option a stable `value`; you reference the correct one by that value in correctAnswer.",
- minItems: 2,
- }),
- multiSelect: Type.Optional(
- Type.Boolean({ description: "Set to true when more than one option is correct and the user must select all of them." }),
- ),
- correctAnswer: Type.Union([Type.String(), Type.Array(Type.String())], {
- description:
- 'REQUIRED. The correct answer as the option value(s) — the `value` field of the option you intend. Single-select: a single string (e.g. "mercury"). Multi-select: an array of strings (e.g. ["belize", "niue"]); the user is only correct if their selection matches this set exactly. Always pass the value, not a position number — this is self-checking and prevents miscounting.',
- }),
- explanation: Type.String({
- description:
- "REQUIRED. Explanation revealed AFTER the user answers (shown whether they got it right or wrong). Use it to reinforce why the correct answer is correct.",
- }),
- shuffle: Type.Optional(
- Type.Boolean({
- description:
- "Defaults to true: options are randomly reordered before display so the correct answer isn't always in the same position. Set to false only when option order is meaningful (e.g. ordered numeric values, or an 'All/None of the above' option that must stay last).",
- }),
- ),
-});
-
-const PRESENTED_MESSAGE =
- "Quiz displayed to the learner. End your turn now and wait: their graded answer will arrive as the next user message. Do not call any other tool before receiving it.";
-
-function unavailableResult(question: string, mode: string, message: string, context?: string) {
- return {
- content: [{ type: "text" as const, text: message }],
- details: { status: "unavailable", question, context, mode, message },
- };
-}
-
-export default function quizWeb(pi: ExtensionAPI) {
- const bridge = getBridge();
-
- // The web server drives answering through the bridge. This extension is
- // the only place with ExtensionAPI access (appendEntry + sendUserMessage).
- // ctx (with sessionManager) is captured from event handlers.
- let currentCtx: { sessionManager: { getEntries(): Array<{ type: string; customType?: string; data?: unknown }> } } | null = null;
-
- bridge.registerBackend({
- kind: "quiz",
- appendAnswered: (data) => pi.appendEntry(QUESTION_ANSWERED_ENTRY, data),
- sendUserMessage: (text) => pi.sendUserMessage(text, { deliverAs: "followUp" }),
- getEntries: () => currentCtx?.sessionManager.getEntries() ?? [],
- });
-
- // Re-arm pending questions on session (re)load — exit/resume mid-quiz.
- pi.on("session_start", (_event, ctx) => {
- currentCtx = ctx;
- bridge.clear("quiz");
- bridge.rearm("quiz");
- });
-
- pi.registerTool({
- name: "quiz",
- label: "quiz",
- description:
- "Ask the user a GRADED question with a known correct answer, then instantly grade and give feedback. Unlike ask_user_question (which collects preferences/decisions with no right answer), quiz always has a correct answer supplied by you, marks the user's selection right/wrong (✓/✗), reveals the correct answer, and can show an explanation. Use it to (1) assess what the learner already understands before teaching, and (2) run tight practice/retrieval loops after explaining, or probe understanding whenever you're unsure they've got it. Options-only: single-select or multi-select, plus an automatic 'I don't know' choice so the user can signal a genuine gap instead of guessing. An always-present optional note field lets the user attach a free-text note to ANY answer; it reaches you only when non-empty. No free-text answers — for non-graded questions use ask_user_question instead.",
- promptSnippet:
- "Use the quiz tool to test the user with a graded multiple-choice or multi-select question (required correct answer + required explanation). For non-graded questions, use ask_user_question.",
- promptGuidelines: [
- "quiz is GRADED; ask_user_question is not. If the question has a correct answer, use quiz. If you just need a preference, decision, or open-ended input, use ask_user_question.",
- 'correctAnswer is REQUIRED and is the option value, not a position number. Single-select: one string (e.g. "mercury"). Multi-select: an array of strings (e.g. ["belice", "niue"]).',
- "Always pass the option's `value` string as correctAnswer — it is self-checking and prevents miscounting positions. A value that matches no option is a hard error.",
- "explanation is REQUIRED — always say why the correct answer is correct.",
- "Multi-select is graded as an exact-set match: the user is correct only if they select every correct option and no incorrect ones.",
- "There is no free-text mode. An 'I don't know' choice is ALWAYS added automatically — provide ONLY the real, gradable options (at least two). Never add your own uncertainty/opt-out option like 'I don't know', 'I'm not sure', or 'Not sure'; that is handled for you and a manual one would be redundant or gradable-as-wrong.",
- "If a result comes back as dontKnow, the user honestly did not know and did NOT guess — treat it as a genuine knowledge gap to teach into, not as a wrong answer.",
- "Any answer (right, wrong, or 'I don't know') may carry an optional free-text `note` the user typed in the always-present note field. When present it reflects what they were thinking or unsure about — read it and let it steer your follow-up. It is omitted entirely when empty.",
- "Treat each wrong answer (distractor) as a diagnostic probe, not just filler: make it a specific, believable mistake the user might actually hold — a common misconception, or an adjacent/easily-confused concept — so that WHICH wrong answer they pick reveals WHICH nuance of their understanding is off. You learn far more from a targeted wrong choice than from a binary right/wrong, and the choice tells you exactly which gap to teach into next (and what the explanation should address).",
- "Guardrail: every distractor must be unambiguously wrong on the intended reading — tempting, but a real error, not a defensible alternative. Don't drift into trick questions.",
- "Anti-guessing hygiene: don't let the correct answer stand out by form (longest, most precise, most hedged, or the only one in the right format). Keep options similar in length, specificity, and phrasing so it can't be picked from shape alone.",
- "Set multiSelect: true only when more than one option is correct.",
- "Options are shuffled before display by default, so don't worry about which position you list the correct answer in. Set shuffle: false only when option order is meaningful (ordered values, or an 'All/None of the above' option that must stay last).",
- "To probe nuance, ask several quick quiz questions and adapt each one based on the previous answers, rather than writing one giant question.",
- "Don't leak the answer through formatting: keep option phrasing/length even and don't hint which is correct.",
- "After calling quiz, END YOUR TURN. The graded answer arrives as the next user message; do not call any other tool while waiting.",
- ],
- parameters: QuizParams,
-
- async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
- const context = params.details?.trim() || undefined;
- const explanation = params.explanation.trim();
- const mode = params.multiSelect ? "multi-select" : "single-select";
-
- let options;
- try {
- options = normalizeOptions(params.options);
- } catch (e) {
- return unavailableResult(params.question, mode, `quiz ${(e as Error).message}`, context);
- }
- if (options.length < 2) {
- return unavailableResult(params.question, mode, "quiz requires at least two options", context);
- }
- if (params.shuffle !== false) {
- options = shuffleOptions(options);
- }
- const { indices: correctIndices, error } = resolveCorrect(
- params.correctAnswer as string | string[],
- options,
- );
- if (error) {
- return unavailableResult(params.question, mode, `quiz ${error}`, context);
- }
-
- const data: QuestionData = {
- id: `q_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`,
- kind: "quiz",
- question: params.question,
- context,
- mode,
- options,
- correctIndices,
- explanation,
- createdAt: new Date().toISOString(),
- };
-
- // Persist the full question (answer included — custom entries are NOT
- // sent to the LLM) and register it as pending, then return immediately.
- pi.appendEntry(QUESTION_ENTRY, data);
- bridge.publish(data);
-
- return {
- content: [{ type: "text" as const, text: PRESENTED_MESSAGE }],
- details: {
- status: "presented",
- question: params.question,
- context,
- mode,
- options: options.map((o, i) => ({ index: i + 1, label: o.label })),
- },
- };
- },
-
- renderCall(args, theme) {
- // Never render correctAnswer/explanation — it would leak the answer.
- let text = theme.fg("toolTitle", theme.bold("quiz ")) + theme.fg("muted", args.question);
- if (args.multiSelect) text += theme.fg("dim", " [multi-select]");
- return new Text(text, 0, 0);
- },
-
- renderResult(result, _options, theme) {
- const details = result.details as { status?: string; message?: string } | undefined;
- if (details?.status === "unavailable") {
- return new Text(theme.fg("warning", details.message || "quiz unavailable"), 0, 0);
- }
- return new Text(theme.fg("muted", "Quiz displayed to the learner — awaiting their answer."), 0, 0);
- },
- });
-}
diff --git a/extensions/quiz.ts b/extensions/quiz.ts
deleted file mode 100644
index 9922570..0000000
--- a/extensions/quiz.ts
+++ /dev/null
@@ -1,981 +0,0 @@
-import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
-import {
- Editor,
- type EditorTheme,
- Key,
- Text,
- matchesKey,
- truncateToWidth,
- wrapTextWithAnsi,
-} from "@mariozechner/pi-tui";
-import { Type } from "@sinclair/typebox";
-import {
- type OptionAnswer,
- type QuizOption,
- isCorrect,
- normalizeOptions,
- resolveCorrect,
- shuffleOptions,
-} from "./core/question-core.ts";
-
-// ────────────────────────────────────────────────────────────────────────────
-// quiz — a GRADED sibling of ask_user_question (TERMINAL mode: blocks on an
-// interactive pi-tui component until the learner answers).
-//
-// Where ask_user_question collects a preference/decision with no notion of
-// right or wrong, `quiz` poses a question that HAS a correct answer, grades the
-// user's selection instantly, and shows tight feedback (✓/✗ + the correct
-// answer + an optional explanation) to both the user and the agent.
-//
-// It is intentionally options-only: single-select or multi-select. There is no
-// free-text mode and no "Other" option, because a free-text answer can't be
-// graded against a correct index.
-//
-// Pure logic (normalize/shuffle/resolve/grade) lives in ./core/question-core.ts,
-// shared with the web dispatch variant (quiz-web.ts).
-// ────────────────────────────────────────────────────────────────────────────
-
-interface DisplayOption extends QuizOption {
- id: string;
- index: number;
- isSubmit?: boolean;
-}
-
-// The always-present "I don't know" choice. It is NOT a real option: it never
-// participates in shuffling, has no correct-answer value, and produces a
-// distinct signal (dontKnow) rather than a right/wrong grade — so an honest
-// "I don't know" is never confused with a lucky or unlucky guess.
-const DONT_KNOW_VALUE = "__dont_know__";
-const DONT_KNOW_LABEL = "I don't know";
-const DONT_KNOW_INDEX = 0; // real options are 1-based; submit uses -1
-
-// Unified response from either ask* component. answers holds the real
-// selections (empty when dontKnow); note is the optional free-text the user
-// typed in the always-present note field (kept only when non-empty).
-interface QuizResponse {
- dontKnow: boolean;
- note?: string;
- answers: OptionAnswer[];
-}
-
-type QuizStatus = "answered" | "cancelled" | "unavailable";
-type QuizMode = "single-select" | "multi-select";
-
-const OptionSchema = Type.Object({
- label: Type.String({ description: "Display label for the answer option." }),
- value: Type.Optional(
- Type.String({ description: "Optional machine-readable value returned for the option. Defaults to the label." }),
- ),
- description: Type.Optional(Type.String({ description: "Optional extra detail shown below the option." })),
-});
-
-const QuizParams = Type.Object({
- question: Type.String({
- description: "The single quiz question to ask. Ask exactly one question per tool call.",
- }),
- details: Type.Optional(
- Type.String({ description: "Optional extra context or instructions shown under the question." }),
- ),
- options: Type.Array(OptionSchema, {
- description:
- "The answer options (2 or more). Options only — there is no free-text mode. Give each option a stable `value`; you reference the correct one by that value in correctAnswer.",
- minItems: 2,
- }),
- multiSelect: Type.Optional(
- Type.Boolean({ description: "Set to true when more than one option is correct and the user must select all of them." }),
- ),
- correctAnswer: Type.Union([Type.String(), Type.Array(Type.String())], {
- description:
- 'REQUIRED. The correct answer as the option value(s) — the `value` field of the option you intend. Single-select: a single string (e.g. "mercury"). Multi-select: an array of strings (e.g. ["belize", "niue"]); the user is only correct if their selection matches this set exactly. Always pass the value, not a position number — this is self-checking and prevents miscounting.',
- }),
- explanation: Type.String({
- description:
- "REQUIRED. Explanation revealed AFTER the user answers (shown whether they got it right or wrong). Use it to reinforce why the correct answer is correct.",
- }),
- shuffle: Type.Optional(
- Type.Boolean({
- description:
- "Defaults to true: options are randomly reordered before display so the correct answer isn't always in the same position. Set to false only when option order is meaningful (e.g. ordered numeric values, or an 'All/None of the above' option that must stay last).",
- }),
- ),
-});
-
-function createEditorTheme(theme: any): EditorTheme {
- return {
- borderColor: (s) => theme.fg("accent", s),
- selectList: {
- selectedPrefix: (t) => theme.fg("accent", t),
- selectedText: (t) => theme.fg("accent", t),
- description: (t) => theme.fg("muted", t),
- scrollInfo: (t) => theme.fg("dim", t),
- noMatch: (t) => theme.fg("warning", t),
- },
- };
-}
-
-function addWrapped(lines: string[], text: string, width: number, indent = ""): void {
- const contentWidth = Math.max(1, width - indent.length);
- for (const line of wrapTextWithAnsi(text, contentWidth)) {
- lines.push(truncateToWidth(`${indent}${line}`, width));
- }
-}
-
-function buildStructuredResult(
- status: QuizStatus,
- question: string,
- mode: QuizMode,
- answers: OptionAnswer[],
- correctIndices: number[],
- correct: boolean | undefined,
- explanation: string | undefined,
- context?: string,
- message?: string,
- options?: DisplayedOption[],
- dontKnow?: boolean,
- note?: string,
-): QuizResultDetails {
- return { status, question, context, mode, answers, correctIndices, options, correct, dontKnow, note, explanation, message };
-}
-
-function cancelledResult(question: string, mode: QuizMode, correctIndices: number[], context?: string) {
- const message = "User cancelled the quiz";
- return {
- content: [{ type: "text" as const, text: message }],
- details: buildStructuredResult("cancelled", question, mode, [], correctIndices, undefined, undefined, context, message),
- };
-}
-
-function unavailableResult(question: string, mode: QuizMode, message: string, correctIndices: number[], context?: string) {
- return {
- content: [{ type: "text" as const, text: message }],
- details: buildStructuredResult("unavailable", question, mode, [], correctIndices, undefined, undefined, context, message),
- };
-}
-
-function formatOptionRef(options: QuizOption[], index: number): string {
- const opt = options.find((o, i) => i + 1 === index);
- return `${index}. ${opt ? opt.label : "(unknown)"}`;
-}
-
-interface DisplayedOption {
- index: number; // 1-based, in the final (possibly shuffled) display order
- label: string;
-}
-
-interface QuizResultDetails {
- status: QuizStatus;
- question: string;
- context?: string;
- mode: QuizMode;
- answers: OptionAnswer[];
- correctIndices: number[];
- options?: DisplayedOption[]; // full option list in display order, for the transcript
- correct?: boolean;
- dontKnow?: boolean; // user selected "I don't know" instead of guessing
- note?: string; // optional free-text from the always-present note field (any answer)
- explanation?: string;
- message?: string;
-}
-
-function buildResult(
- question: string,
- context: string | undefined,
- mode: QuizMode,
- options: QuizOption[],
- response: QuizResponse,
- correctIndices: number[],
- explanation: string | undefined,
-) {
- const { dontKnow, note, answers } = response;
- const selectedIndices = answers.map((a) => a.index);
- // "I don't know" is never counted as correct — it's a distinct outcome.
- const correct = dontKnow ? false : isCorrect(selectedIndices, correctIndices);
- const correctStr = correctIndices.map((i) => formatOptionRef(options, i)).join(", ");
- const displayedOptions: DisplayedOption[] = options.map((o, i) => ({ index: i + 1, label: o.label }));
-
- let text: string;
- if (dontKnow) {
- // Make the signal explicit for the agent: the user did NOT guess, so this
- // is a genuine knowledge gap, not a wrong answer to correct against.
- text = `User selected "I don't know" — they did not attempt an answer (a genuine knowledge gap, not a wrong guess).`;
- text += `\nCorrect: ${correctStr}`;
- if (note) text += `\nUser's note: ${note}`;
- } else {
- const verdict = correct ? "correctly" : "incorrectly";
- const selectedStr = answers.map((a) => `${a.index}. ${a.label}`).join(", ");
- text = `User answered ${verdict}.\nSelected: ${selectedStr}\nCorrect: ${correctStr}`;
- if (note) text += `\nUser's note: ${note}`;
- }
- if (explanation) text += `\nExplanation: ${explanation}`;
-
- return {
- content: [{ type: "text" as const, text }],
- details: buildStructuredResult(
- "answered",
- question,
- mode,
- answers,
- correctIndices,
- correct,
- explanation,
- context,
- undefined,
- displayedOptions,
- dontKnow,
- note,
- ),
- };
-}
-
-// Shared feedback block, rendered after the user submits.
-function renderFeedback(
- lines: string[],
- theme: any,
- width: number,
- options: QuizOption[],
- selectedIndices: number[],
- correctIndices: number[],
- explanation: string | undefined,
- dontKnow = false,
- note?: string,
-): void {
- const add = (text: string) => lines.push(truncateToWidth(text, width));
- const correct = !dontKnow && isCorrect(selectedIndices, correctIndices);
- const selectedSet = new Set(selectedIndices);
- const correctSet = new Set(correctIndices);
-
- lines.push("");
- for (let i = 0; i < options.length; i++) {
- const index = i + 1;
- const opt = options[i];
- const isSelected = selectedSet.has(index);
- const isKey = correctSet.has(index);
- let marker: string;
- let color: string;
- if (dontKnow) {
- // No guess was made — only reveal the correct answer(s); never show ✗.
- marker = isKey ? "✓" : " ";
- color = isKey ? "success" : "dim";
- } else if (isSelected && isKey) {
- marker = "✓";
- color = "success";
- } else if (isSelected && !isKey) {
- marker = "✗";
- color = "error";
- } else if (!isSelected && isKey) {
- // correct answer the user missed
- marker = "✓";
- color = "success";
- } else {
- marker = " ";
- color = "dim";
- }
- add(theme.fg(color, ` ${marker} ${index}. ${opt.label}`));
- }
-
- lines.push("");
- if (dontKnow) {
- add(theme.fg("warning", " · You said: I don't know"));
- const correctStr = correctIndices.map((i) => formatOptionRef(options, i)).join(", ");
- addWrapped(lines, theme.fg("muted", `Correct answer: ${correctStr}`), width, " ");
- } else if (correct) {
- add(theme.fg("success", " ✓ Correct!"));
- } else {
- add(theme.fg("error", " ✗ Incorrect."));
- const correctStr = correctIndices.map((i) => formatOptionRef(options, i)).join(", ");
- addWrapped(lines, theme.fg("muted", `Correct answer: ${correctStr}`), width, " ");
- }
- if (note) {
- addWrapped(lines, theme.fg("muted", `Your note: ${note}`), width, " ");
- }
- if (explanation) {
- lines.push("");
- addWrapped(lines, theme.fg("text", explanation), width, " ");
- }
- lines.push("");
- add(theme.fg("dim", " Enter/Esc to continue"));
-}
-
-// Top border + question + optional context. Shared by both components.
-function pushHeader(lines: string[], theme: any, width: number, question: string, context: string | undefined): void {
- lines.push(truncateToWidth(theme.fg("accent", "─".repeat(width)), width));
- addWrapped(lines, theme.fg("text", question), width, " ");
- if (context) {
- lines.push("");
- addWrapped(lines, theme.fg("muted", context), width, " ");
- }
-}
-
-// The "I don't know" row in the selection list — visually separated and dimmed
-// so it reads as distinct from the real, gradable options.
-function pushDontKnowRow(lines: string[], theme: any, width: number, focused: boolean): void {
- lines.push("");
- const prefix = focused ? theme.fg("accent", "> ") : " ";
- const styled = focused ? theme.fg("accent", DONT_KNOW_LABEL) : theme.fg("dim", DONT_KNOW_LABEL);
- lines.push(truncateToWidth(`${prefix}${styled}`, width));
-}
-
-// Persistent, always-present note field rendered under the options during the
-// select phase. Applies to ANY answer (including "I don't know") and is only
-// surfaced to the agent when non-empty.
-function pushNoteField(lines: string[], theme: any, width: number, editor: Editor, focused: boolean): void {
- lines.push("");
- const label = focused ? theme.fg("accent", "Note (optional):") : theme.fg("muted", "Note (optional):");
- addWrapped(lines, label, width, " ");
- for (const line of editor.render(width)) lines.push(line);
-}
-
-// Build the note Editor. `disableSubmit` is set because Enter must NOT submit
-// here: the editor's submit path clears the buffer, which would wipe the note.
-// Instead the host intercepts Enter to return focus to the options while
-// keeping the text. Ctrl+J still inserts a newline (pi convention), so
-// multi-line notes work.
-function makeNoteEditor(tui: any, theme: any): Editor {
- const editor = new Editor(tui, createEditorTheme(theme));
- editor.focused = false;
- editor.disableSubmit = true;
- return editor;
-}
-
-async function askSingleChoice(
- ctx: any,
- question: string,
- context: string | undefined,
- options: QuizOption[],
- correctIndices: number[],
- explanation: string | undefined,
-): Promise {
- const allOptions: DisplayOption[] = options.map((option, index) => ({
- ...option,
- id: `option:${index}`,
- index: index + 1,
- }));
- const dontKnowNav = allOptions.length; // nav index of the "I don't know" row
-
- return ctx.ui.custom(
- (tui: any, theme: any, _kb: any, done: (result: QuizResponse | null) => void) => {
- let optionIndex = 0;
- let phase: "select" | "feedback" = "select";
- let focus: "options" | "note" = "options";
- let chosen: OptionAnswer | null = null;
- let dontKnow = false;
- const editor = makeNoteEditor(tui, theme);
- let cachedLines: string[] | undefined;
- let cachedWidth = -1;
-
- function refresh() {
- cachedLines = undefined;
- tui.requestRender();
- }
-
- function noteText(): string | undefined {
- const t = editor.getText().trim();
- return t.length ? t : undefined;
- }
-
- function toOptions() {
- focus = "options";
- editor.focused = false;
- refresh();
- }
-
- function response(): QuizResponse {
- const note = noteText();
- return dontKnow
- ? { dontKnow: true, note, answers: [] }
- : { dontKnow: false, note, answers: chosen ? [chosen] : [] };
- }
-
- function handleInput(data: string) {
- if (phase === "feedback") {
- if (matchesKey(data, Key.enter) || matchesKey(data, Key.escape)) {
- done(response());
- }
- return;
- }
-
- // Tab toggles focus between the options list and the note field.
- if (matchesKey(data, Key.tab)) {
- focus = focus === "options" ? "note" : "options";
- editor.focused = focus === "note";
- refresh();
- return;
- }
-
- if (focus === "note") {
- // Enter and Esc both return to the options and keep the note text.
- // (Enter must be intercepted here: the editor's own submit clears
- // the buffer. Ctrl+J still reaches the editor as a newline.)
- if (matchesKey(data, Key.enter) || matchesKey(data, Key.escape)) {
- toOptions();
- return;
- }
- editor.handleInput(data);
- tui.requestRender();
- return;
- }
-
- // focus === "options"
- if (matchesKey(data, Key.up)) {
- optionIndex = Math.max(0, optionIndex - 1);
- refresh();
- return;
- }
- if (matchesKey(data, Key.down)) {
- optionIndex = Math.min(dontKnowNav, optionIndex + 1);
- refresh();
- return;
- }
- if (matchesKey(data, Key.enter)) {
- if (optionIndex === dontKnowNav) {
- dontKnow = true;
- chosen = null;
- } else {
- const selected = allOptions[optionIndex];
- chosen = { label: selected.label, value: selected.value, index: selected.index };
- dontKnow = false;
- }
- phase = "feedback";
- refresh();
- return;
- }
- if (matchesKey(data, Key.escape)) {
- done(null);
- }
- }
-
- function render(width: number): string[] {
- // The cache MUST be keyed on width: pi-tui calls requestRender() but NOT
- // invalidate() on terminal resize, so render() can be re-entered with a
- // new width. Returning stale wider lines trips the TUI width guard and
- // crashes the process.
- if (cachedLines && cachedWidth === width) return cachedLines;
-
- const lines: string[] = [];
- const add = (text: string) => lines.push(truncateToWidth(text, width));
- pushHeader(lines, theme, width, question, context);
-
- if (phase === "feedback") {
- renderFeedback(
- lines,
- theme,
- width,
- options,
- chosen ? [chosen.index] : [],
- correctIndices,
- explanation,
- dontKnow,
- noteText(),
- );
- add(theme.fg("accent", "─".repeat(width)));
- cachedLines = lines;
- cachedWidth = width;
- return lines;
- }
-
- lines.push("");
- for (let i = 0; i < allOptions.length; i++) {
- const option = allOptions[i];
- const selected = focus === "options" && i === optionIndex;
- const prefix = selected ? theme.fg("accent", "> ") : " ";
- const label = `${option.index}. ${option.label}`;
- const styled = selected ? theme.fg("accent", label) : theme.fg("text", label);
- add(`${prefix}${styled}`);
- if (option.description) {
- addWrapped(lines, theme.fg("muted", option.description), width, " ");
- }
- }
-
- pushDontKnowRow(lines, theme, width, focus === "options" && optionIndex === dontKnowNav);
-
- pushNoteField(lines, theme, width, editor, focus === "note");
-
- lines.push("");
- if (focus === "note") {
- add(theme.fg("dim", " Type note • Ctrl+J newline • Enter back to options • Tab options • Esc back"));
- } else {
- add(theme.fg("dim", " ↑↓ navigate • Enter answer • Tab note • Esc cancel"));
- }
- add(theme.fg("accent", "─".repeat(width)));
- // Not cached when the note is focused: the editor renders a live cursor.
- if (focus !== "note") {
- cachedLines = lines;
- cachedWidth = width;
- }
- return lines;
- }
-
- return {
- render,
- invalidate: () => {
- cachedLines = undefined;
- editor.invalidate();
- },
- handleInput,
- };
- },
- );
-}
-
-async function askMultiChoice(
- ctx: any,
- question: string,
- context: string | undefined,
- options: QuizOption[],
- correctIndices: number[],
- explanation: string | undefined,
-): Promise {
- const DONT_KNOW_ID = "dont-know";
- const choiceItems: DisplayOption[] = options.map((option, index) => ({
- ...option,
- id: `option:${index}`,
- index: index + 1,
- }));
- const dontKnowItem: DisplayOption = {
- id: DONT_KNOW_ID,
- label: DONT_KNOW_LABEL,
- value: DONT_KNOW_VALUE,
- index: DONT_KNOW_INDEX,
- };
- const submitItem: DisplayOption = { id: "submit", label: "Submit", value: "__submit__", index: -1, isSubmit: true };
- const allItems: DisplayOption[] = [...choiceItems, dontKnowItem, submitItem];
-
- return ctx.ui.custom(
- (tui: any, theme: any, _kb: any, done: (result: QuizResponse | null) => void) => {
- let optionIndex = 0;
- let phase: "select" | "feedback" = "select";
- let focus: "options" | "note" = "options";
- const editor = makeNoteEditor(tui, theme);
- let cachedLines: string[] | undefined;
- let cachedWidth = -1;
- const selected = new Map();
-
- function refresh() {
- cachedLines = undefined;
- tui.requestRender();
- }
-
- function noteText(): string | undefined {
- const t = editor.getText().trim();
- return t.length ? t : undefined;
- }
-
- function toOptions() {
- focus = "options";
- editor.focused = false;
- refresh();
- }
-
- const choseDontKnow = () => selected.has(DONT_KNOW_ID);
- const realAnswers = () =>
- sortAnswers(Array.from(selected.values()).filter((a) => a.index !== DONT_KNOW_INDEX));
-
- function response(): QuizResponse {
- const note = noteText();
- return choseDontKnow()
- ? { dontKnow: true, note, answers: [] }
- : { dontKnow: false, note, answers: realAnswers() };
- }
-
- // "I don't know" is exclusive: choosing it clears real selections, and
- // choosing any real option clears "I don't know".
- function toggleOption(item: DisplayOption) {
- if (item.id === DONT_KNOW_ID) {
- if (selected.has(DONT_KNOW_ID)) {
- selected.delete(DONT_KNOW_ID);
- } else {
- selected.clear();
- selected.set(DONT_KNOW_ID, { label: item.label, value: item.value, index: item.index });
- }
- } else {
- selected.delete(DONT_KNOW_ID);
- if (selected.has(item.id)) {
- selected.delete(item.id);
- } else {
- selected.set(item.id, { label: item.label, value: item.value, index: item.index });
- }
- }
- refresh();
- }
-
- function submit() {
- if (selected.size === 0) return;
- phase = "feedback";
- refresh();
- }
-
- function handleInput(data: string) {
- if (phase === "feedback") {
- if (matchesKey(data, Key.enter) || matchesKey(data, Key.escape)) {
- done(response());
- }
- return;
- }
-
- // Tab toggles focus between the options list and the note field.
- if (matchesKey(data, Key.tab)) {
- focus = focus === "options" ? "note" : "options";
- editor.focused = focus === "note";
- refresh();
- return;
- }
-
- if (focus === "note") {
- // Enter and Esc both return to the options and keep the note text.
- // (Enter must be intercepted here: the editor's own submit clears
- // the buffer. Ctrl+J still reaches the editor as a newline.)
- if (matchesKey(data, Key.enter) || matchesKey(data, Key.escape)) {
- toOptions();
- return;
- }
- editor.handleInput(data);
- tui.requestRender();
- return;
- }
-
- // focus === "options"
- if (matchesKey(data, Key.up)) {
- optionIndex = Math.max(0, optionIndex - 1);
- refresh();
- return;
- }
- if (matchesKey(data, Key.down)) {
- optionIndex = Math.min(allItems.length - 1, optionIndex + 1);
- refresh();
- return;
- }
-
- const current = allItems[optionIndex];
- if (matchesKey(data, Key.space)) {
- if (current.isSubmit) return;
- toggleOption(current);
- return;
- }
-
- if (matchesKey(data, Key.enter)) {
- if (current.isSubmit) {
- submit();
- return;
- }
- toggleOption(current);
- return;
- }
-
- if (matchesKey(data, Key.escape)) {
- done(null);
- }
- }
-
- function render(width: number): string[] {
- // The cache MUST be keyed on width: pi-tui calls requestRender() but NOT
- // invalidate() on terminal resize, so render() can be re-entered with a
- // new width. Returning stale wider lines trips the TUI width guard and
- // crashes the process.
- if (cachedLines && cachedWidth === width) return cachedLines;
-
- const lines: string[] = [];
- const add = (text: string) => lines.push(truncateToWidth(text, width));
- pushHeader(lines, theme, width, question, context);
-
- if (phase === "feedback") {
- renderFeedback(
- lines,
- theme,
- width,
- options,
- realAnswers().map((a) => a.index),
- correctIndices,
- explanation,
- choseDontKnow(),
- noteText(),
- );
- add(theme.fg("accent", "─".repeat(width)));
- cachedLines = lines;
- cachedWidth = width;
- return lines;
- }
-
- lines.push("");
- for (let i = 0; i < allItems.length; i++) {
- const item = allItems[i];
- const isFocused = focus === "options" && i === optionIndex;
- const prefix = isFocused ? theme.fg("accent", "> ") : " ";
-
- if (item.isSubmit) {
- const label = selected.size > 0 ? `✓ ${item.label} (${selected.size} selected)` : `○ ${item.label}`;
- const styled = isFocused
- ? theme.fg("accent", label)
- : theme.fg(selected.size > 0 ? "success" : "dim", label);
- add(`${prefix}${styled}`);
- continue;
- }
-
- if (item.id === DONT_KNOW_ID) {
- lines.push(""); // visual separation from the real options
- const checked = selected.has(item.id);
- const label = `${checked ? "[x]" : "[ ]"} ${item.label}`;
- const styled = isFocused ? theme.fg("accent", label) : theme.fg(checked ? "warning" : "dim", label);
- add(`${prefix}${styled}`);
- continue;
- }
-
- const checked = selected.has(item.id);
- const marker = checked ? "[x]" : "[ ]";
- const label = `${marker} ${item.index}. ${item.label}`;
- const styled = isFocused ? theme.fg("accent", label) : theme.fg(checked ? "success" : "text", label);
- add(`${prefix}${styled}`);
- if (item.description) {
- addWrapped(lines, theme.fg("muted", item.description), width, " ");
- }
- }
-
- pushNoteField(lines, theme, width, editor, focus === "note");
-
- lines.push("");
- if (selected.size === 0) {
- add(theme.fg("warning", " Select at least one answer before submitting."));
- }
- if (focus === "note") {
- add(theme.fg("dim", " Type note • Ctrl+J newline • Enter back to options • Tab options • Esc back"));
- } else {
- add(theme.fg("dim", " ↑↓ navigate • Space toggle • Enter submit • Tab note • Esc cancel"));
- }
- add(theme.fg("accent", "─".repeat(width)));
- // Not cached when the note is focused: the editor renders a live cursor.
- if (focus !== "note") {
- cachedLines = lines;
- cachedWidth = width;
- }
- return lines;
- }
-
- return {
- render,
- invalidate: () => {
- cachedLines = undefined;
- editor.invalidate();
- },
- handleInput,
- };
- },
- );
-}
-
-function sortAnswers(answers: OptionAnswer[]): OptionAnswer[] {
- return [...answers].sort((a, b) => a.index - b.index);
-}
-
-// Shared UI mutex. ctx.ui.custom()/editor can only handle one active call at
-// a time, so ALL pop-up-style tools (quiz, ask_user_question, ...) must
-// serialize against each other, not just against themselves. We stash one
-// mutex on globalThis so separate extension files can share it without
-// importing each other.
-const SHARED_UI_LOCK_KEY = "__piSharedUiLock";
-function getSharedUiLock() {
- const g = globalThis as any;
- if (!g[SHARED_UI_LOCK_KEY]) {
- let chain: Promise = Promise.resolve();
- g[SHARED_UI_LOCK_KEY] = {
- withLock(fn: () => T | Promise): Promise {
- const prev = chain;
- let release: () => void;
- chain = new Promise((r) => { release = r; });
- return prev.then(fn).finally(() => release!());
- },
- };
- }
- return g[SHARED_UI_LOCK_KEY] as { withLock(fn: () => T | Promise): Promise };
-}
-const sharedUiLock = getSharedUiLock();
-
-function withUILock(fn: () => Promise): Promise {
- return sharedUiLock.withLock(fn);
-}
-
-export default function quiz(pi: ExtensionAPI) {
- pi.registerTool({
- name: "quiz",
- label: "quiz",
- description:
- "Ask the user a GRADED question with a known correct answer, then instantly grade and give feedback. Unlike ask_user_question (which collects preferences/decisions with no right answer), quiz always has a correct answer supplied by you, marks the user's selection right/wrong (✓/✗), reveals the correct answer, and can show an explanation. Use it to (1) assess what the learner already understands before teaching, and (2) run tight practice/retrieval loops after explaining, or probe understanding whenever you're unsure they've got it. Options-only: single-select or multi-select, plus an automatic 'I don't know' choice so the user can signal a genuine gap instead of guessing. An always-present optional note field (Tab to focus it) lets the user attach a free-text note to ANY answer; it reaches you only when non-empty. No free-text answers — for non-graded questions use ask_user_question instead.",
- promptSnippet:
- "Use the quiz tool to test the user with a graded multiple-choice or multi-select question (required correct answer + required explanation). For non-graded questions, use ask_user_question.",
- promptGuidelines: [
- "quiz is GRADED; ask_user_question is not. If the question has a correct answer, use quiz. If you just need a preference, decision, or open-ended input, use ask_user_question.",
- 'correctAnswer is REQUIRED and is the option value, not a position number. Single-select: one string (e.g. "mercury"). Multi-select: an array of strings (e.g. ["belize", "niue"]).',
- "Always pass the option's `value` string as correctAnswer — it is self-checking and prevents miscounting positions. A value that matches no option is a hard error.",
- "explanation is REQUIRED — always say why the correct answer is correct.",
- "Multi-select is graded as an exact-set match: the user is correct only if they select every correct option and no incorrect ones.",
- "There is no free-text mode. An 'I don't know' choice is ALWAYS added automatically — provide ONLY the real, gradable options (at least two). Never add your own uncertainty/opt-out option like 'I don't know', 'I'm not sure', or 'Not sure'; that is handled for you and a manual one would be redundant or gradable-as-wrong.",
- "If a result comes back as dontKnow, the user honestly did not know and did NOT guess — treat it as a genuine knowledge gap to teach into, not as a wrong answer.",
- "Any answer (right, wrong, or 'I don't know') may carry an optional free-text `note` the user typed in the always-present note field. When present it reflects what they were thinking or unsure about — read it and let it steer your follow-up. It is omitted entirely when empty.",
- "Treat each wrong answer (distractor) as a diagnostic probe, not just filler: make it a specific, believable mistake the user might actually hold — a common misconception, or an adjacent/easily-confused concept — so that WHICH wrong answer they pick reveals WHICH nuance of their understanding is off. You learn far more from a targeted wrong choice than from a binary right/wrong, and the choice tells you exactly which gap to teach into next (and what the explanation should address).",
- "Guardrail: every distractor must be unambiguously wrong on the intended reading — tempting, but a real error, not a defensible alternative. Don't drift into trick questions.",
- "Anti-guessing hygiene: don't let the correct answer stand out by form (longest, most precise, most hedged, or the only one in the right format). Keep options similar in length, specificity, and phrasing so it can't be picked from shape alone.",
- "Set multiSelect: true only when more than one option is correct.",
- "Options are shuffled before display by default, so don't worry about which position you list the correct answer in. Set shuffle: false only when option order is meaningful (ordered values, or an 'All/None of the above' option that must stay last).",
- "To probe nuance, ask several quick quiz questions and adapt each one based on the previous answers, rather than writing one giant question.",
- "Don't leak the answer through formatting: keep option phrasing/length even and don't hint which is correct.",
- ],
- parameters: QuizParams,
-
- async execute(_toolCallId, params, signal, onUpdate, ctx) {
- const context = params.details?.trim() || undefined;
- const explanation = params.explanation.trim();
- const mode: QuizMode = params.multiSelect ? "multi-select" : "single-select";
-
- let options: QuizOption[];
- try {
- options = normalizeOptions(params.options);
- } catch (e) {
- return unavailableResult(params.question, mode, `quiz ${(e as Error).message}`, [], context);
- }
-
- // Shuffle for display (default on) BEFORE resolving correct indices, so
- // grading matches the order the user sees.
- if (params.shuffle !== false) {
- options = shuffleOptions(options);
- }
-
- // Emit the true (post-shuffle) display order immediately, before the UI
- // blocks on the user's answer. Listeners such as md-log rely on this to
- // show the question in the SAME order the user actually sees it, instead
- // of the pre-shuffle order the agent originally wrote in its tool call.
- // Deliberately omits correctIndices/explanation — this fires before the
- // user has answered and must not leak the answer.
- onUpdate?.({
- content: [{ type: "text", text: "Awaiting user response..." }],
- details: { options: options.map((o, i) => ({ index: i + 1, label: o.label })) },
- });
-
- const { indices: correctIndices, error: correctError } = resolveCorrect(
- params.correctAnswer as string | string[],
- options,
- );
-
- if (signal?.aborted) {
- return cancelledResult(params.question, mode, correctIndices, context);
- }
-
- if (options.length < 2) {
- return unavailableResult(
- params.question,
- mode,
- "quiz requires at least two options",
- correctIndices,
- context,
- );
- }
-
- if (correctError) {
- return unavailableResult(params.question, mode, `quiz ${correctError}`, correctIndices, context);
- }
-
- if (!ctx.hasUI) {
- return unavailableResult(params.question, mode, "quiz requires interactive mode UI", correctIndices, context);
- }
-
- return withUILock(async () => {
- const response =
- mode === "single-select"
- ? await askSingleChoice(ctx, params.question, context, options, correctIndices, explanation)
- : await askMultiChoice(ctx, params.question, context, options, correctIndices, explanation);
- if (!response) {
- return cancelledResult(params.question, mode, correctIndices, context);
- }
- return buildResult(params.question, context, mode, options, response, correctIndices, explanation);
- });
- },
-
- renderCall(args, theme) {
- // NOTE: never render correctAnswer or explanation here — it would leak
- // the answer into the transcript before the user responds. We also do NOT
- // enumerate the options here: they are shuffled at execute time, so any
- // order shown during streaming would be stale/misleading. The full option
- // list is rendered — in its true display order — by renderResult after the
- // user answers.
- const options = normalizeOptions(
- args.options as Array<{ label: string; value?: string; description?: string }> | undefined,
- );
- let text = theme.fg("toolTitle", theme.bold("quiz ")) + theme.fg("muted", args.question);
- if (args.multiSelect) {
- text += theme.fg("dim", " [multi-select]");
- }
- if (options.length > 0) {
- const noun = options.length === 1 ? "option" : "options";
- text += theme.fg("dim", ` (${options.length} ${noun})`);
- }
- return new Text(text, 0, 0);
- },
-
- renderResult(result, _options, theme) {
- const details = result.details as QuizResultDetails | undefined;
- if (!details) {
- const first = result.content[0];
- return new Text(first?.type === "text" ? first.text : "", 0, 0);
- }
-
- if (details.status === "cancelled") {
- return new Text(theme.fg("warning", details.message || "Cancelled"), 0, 0);
- }
- if (details.status === "unavailable") {
- return new Text(theme.fg("warning", details.message || "quiz unavailable"), 0, 0);
- }
-
- const correctSet = new Set(details.correctIndices);
- const selectedSet = new Set(details.answers.map((a) => a.index));
- const lines: string[] = [];
-
- // Full option list in the true (shuffled) display order, with ✓/✗ marks.
- // Falls back to just the selected answers for older results that predate
- // details.options.
- const displayed =
- details.options && details.options.length > 0
- ? details.options
- : details.answers.map((a) => ({ index: a.index, label: a.label }));
-
- for (const opt of displayed) {
- const isSelected = selectedSet.has(opt.index);
- const isKey = correctSet.has(opt.index);
- let mark: string;
- let body: string;
- if (details.dontKnow) {
- // No guess — only reveal the correct answer(s); never show ✗.
- mark = isKey ? theme.fg("success", "✓ ") : " ";
- body = isKey ? theme.fg("success", `${opt.index}. ${opt.label}`) : theme.fg("dim", `${opt.index}. ${opt.label}`);
- } else if (isSelected && isKey) {
- mark = theme.fg("success", "✓ ");
- body = theme.fg("accent", `${opt.index}. ${opt.label}`);
- } else if (isSelected && !isKey) {
- mark = theme.fg("error", "✗ ");
- body = theme.fg("error", `${opt.index}. ${opt.label}`);
- } else if (!isSelected && isKey) {
- mark = theme.fg("success", "✓ ");
- body = theme.fg("success", `${opt.index}. ${opt.label}`);
- } else {
- mark = " ";
- body = theme.fg("dim", `${opt.index}. ${opt.label}`);
- }
- lines.push(`${mark}${body}`);
- }
-
- lines.push("");
- const verdict = details.dontKnow
- ? theme.fg("warning", "I don't know")
- : details.correct
- ? theme.fg("success", "Correct!")
- : theme.fg("error", "Incorrect");
- lines.push(verdict);
-
- if (details.note) {
- lines.push(theme.fg("muted", `Note: ${details.note}`));
- }
-
- if (details.explanation) {
- lines.push(theme.fg("muted", details.explanation));
- }
-
- return new Text(lines.join("\n"), 0, 0);
- },
- });
-}
diff --git a/lib/utils.ts b/lib/utils.ts
new file mode 100644
index 0000000..3200be2
--- /dev/null
+++ b/lib/utils.ts
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
diff --git a/next-env.d.ts b/next-env.d.ts
new file mode 100644
index 0000000..ce4e94a
--- /dev/null
+++ b/next-env.d.ts
@@ -0,0 +1,7 @@
+///
+///
+import "./.next/types/routes.d.ts";
+import "./.next/types/root-params.d.ts";
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/next.config.ts b/next.config.ts
new file mode 100644
index 0000000..15e4dd2
--- /dev/null
+++ b/next.config.ts
@@ -0,0 +1,7 @@
+import type { NextConfig } from "next";
+
+const nextConfig: NextConfig = {
+ // Framework-free core lives in src/ (plain TS, imported by routes and CLI alike).
+};
+
+export default nextConfig;
diff --git a/package-lock.json b/package-lock.json
index 2c3c88c..9d1ca27 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,34 +1,150 @@
{
"name": "learning-harness",
- "version": "0.1.0",
+ "version": "0.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "learning-harness",
- "version": "0.1.0",
+ "version": "0.2.0",
"dependencies": {
- "@earendil-works/pi-coding-agent": "0.84.2",
- "@fastify/static": "^8.0.0",
- "@fastify/websocket": "^11.0.0",
- "fastify": "^5.0.0",
- "katex": "^0.16.11",
- "mermaid": "^11.4.0",
- "react": "^18.3.1",
- "react-dom": "^18.3.1",
- "react-markdown": "^9.0.1",
- "rehype-katex": "^7.0.1",
- "remark-math": "^6.0.0"
+ "@ai-sdk/react": "^4.0.79",
+ "@openrouter/ai-sdk-provider": "^3.0.0",
+ "@radix-ui/react-use-controllable-state": "^1.2.6",
+ "@rive-app/react-webgl2": "^4.32.1",
+ "@streamdown/cjk": "^1.0.3",
+ "@streamdown/code": "^1.1.1",
+ "@streamdown/math": "^1.0.2",
+ "@streamdown/mermaid": "^1.0.2",
+ "@xyflow/react": "^12.11.3",
+ "ai": "^7.0.76",
+ "ansi-to-react": "^6.2.6",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "cmdk": "^1.1.1",
+ "embla-carousel-react": "^8.6.0",
+ "lucide-react": "^1.33.0",
+ "media-chrome": "^4.19.2",
+ "motion": "^13.1.1",
+ "nanoid": "^6.0.1",
+ "next": "^16.3.2",
+ "next-themes": "^0.4.6",
+ "radix-ui": "^1.6.7",
+ "react": "^19.2.8",
+ "react-dom": "^19.2.8",
+ "react-jsx-parser": "^2.4.1",
+ "streamdown": "^2.5.0",
+ "tailwind-merge": "^3.6.0",
+ "tokenlens": "^1.3.1",
+ "use-stick-to-bottom": "^1.1.6",
+ "zod": "^4.4.3"
},
"devDependencies": {
+ "@tailwindcss/postcss": "^4.3.3",
"@types/node": "^24.0.0",
- "@types/react": "^18.3.5",
- "@types/react-dom": "^18.3.0",
- "@vitejs/plugin-react": "^4.3.1",
- "concurrently": "^9.0.0",
+ "@types/react": "^19.2.18",
+ "@types/react-dom": "^19.2.4",
+ "tailwindcss": "^4.3.3",
"tsx": "^4.19.0",
- "typescript": "^5.6.0",
- "vite": "^6.0.0"
+ "typescript": "^5.6.0"
+ }
+ },
+ "node_modules/@ai-sdk/gateway": {
+ "version": "4.0.61",
+ "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-4.0.61.tgz",
+ "integrity": "sha512-b+PRdwM9/x0427CKqfd4zpCRgSzQeZsCZ9PtBOaLU2S1l+eRj8++Bwt766VnLerd/F5snKWAq13miV+OPcvclA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@ai-sdk/provider": "4.0.7",
+ "@ai-sdk/provider-utils": "5.0.28",
+ "@vercel/oidc": "3.2.0"
+ },
+ "engines": {
+ "node": ">=22"
+ },
+ "peerDependencies": {
+ "zod": "^3.25.76 || ^4.1.8"
+ }
+ },
+ "node_modules/@ai-sdk/mcp": {
+ "version": "2.0.35",
+ "resolved": "https://registry.npmjs.org/@ai-sdk/mcp/-/mcp-2.0.35.tgz",
+ "integrity": "sha512-hyaryid0qH/d8VtNZoJKeqcLy6b3v9xlkBL3JklbFXMAZJ8nmkxngrxQtjbNRTKjSFoyQ95/7IuRIijrSljMww==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@ai-sdk/provider": "4.0.7",
+ "@ai-sdk/provider-utils": "5.0.28",
+ "pkce-challenge": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=22"
+ },
+ "peerDependencies": {
+ "zod": "^3.25.76 || ^4.1.8"
+ }
+ },
+ "node_modules/@ai-sdk/provider": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-4.0.7.tgz",
+ "integrity": "sha512-6or44XprPzKbr8zkmzosowSE0pxkvJcoojBL+mCZvPUt3kvXp3XSNqeVun9golb1acEfSo6yaEBRT18h2VU+1Q==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "json-schema": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=22"
+ }
+ },
+ "node_modules/@ai-sdk/provider-utils": {
+ "version": "5.0.28",
+ "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-5.0.28.tgz",
+ "integrity": "sha512-TnHUyd/rCYQqHg5RuiOaz/hUql6U+kbUaBW0Rp+0N5UhnAInA9CzzV0HXvuAAPwppDsK6fAx9Rd+tRawpJ/3pg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@ai-sdk/provider": "4.0.7",
+ "@standard-schema/spec": "^1.1.0",
+ "@workflow/serde": "4.1.0",
+ "eventsource-parser": "^3.0.8",
+ "undici": "^7.28.0"
+ },
+ "engines": {
+ "node": ">=22"
+ },
+ "peerDependencies": {
+ "zod": "^3.25.76 || ^4.1.8"
+ }
+ },
+ "node_modules/@ai-sdk/react": {
+ "version": "4.0.79",
+ "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-4.0.79.tgz",
+ "integrity": "sha512-x7dZ/teRKXgJfukoOSiKA4pMv1AbMLjTGA8/d9Dofl8Mgf4DfRNuBxs6xlcedPypWTJXkK8whac2THkOpoFzug==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@ai-sdk/mcp": "2.0.35",
+ "@ai-sdk/provider": "4.0.7",
+ "@ai-sdk/provider-utils": "5.0.28",
+ "ai": "7.0.76",
+ "swr": "^2.4.1",
+ "throttleit": "2.1.0"
+ },
+ "engines": {
+ "node": ">=22"
+ },
+ "peerDependencies": {
+ "react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1"
+ }
+ },
+ "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/@antfu/install-pkg": {
@@ -44,288 +160,6 @@
"url": "https://github.com/sponsors/antfu"
}
},
- "node_modules/@babel/code-frame": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
- "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-validator-identifier": "^7.29.7",
- "js-tokens": "^4.0.0",
- "picocolors": "^1.1.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/compat-data": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
- "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/core": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
- "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.29.7",
- "@babel/generator": "^7.29.7",
- "@babel/helper-compilation-targets": "^7.29.7",
- "@babel/helper-module-transforms": "^7.29.7",
- "@babel/helpers": "^7.29.7",
- "@babel/parser": "^7.29.7",
- "@babel/template": "^7.29.7",
- "@babel/traverse": "^7.29.7",
- "@babel/types": "^7.29.7",
- "@jridgewell/remapping": "^2.3.5",
- "convert-source-map": "^2.0.0",
- "debug": "^4.1.0",
- "gensync": "^1.0.0-beta.2",
- "json5": "^2.2.3",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/babel"
- }
- },
- "node_modules/@babel/generator": {
- "version": "7.29.8",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz",
- "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.29.8",
- "@babel/types": "^7.29.8",
- "@jridgewell/gen-mapping": "^0.3.12",
- "@jridgewell/trace-mapping": "^0.3.28",
- "jsesc": "^3.0.2"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-compilation-targets": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
- "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/compat-data": "^7.29.7",
- "@babel/helper-validator-option": "^7.29.7",
- "browserslist": "^4.24.0",
- "lru-cache": "^5.1.1",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-globals": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
- "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-module-imports": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
- "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/traverse": "^7.29.7",
- "@babel/types": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-module-transforms": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
- "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-imports": "^7.29.7",
- "@babel/helper-validator-identifier": "^7.29.7",
- "@babel/traverse": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/helper-plugin-utils": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
- "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-string-parser": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
- "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-identifier": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
- "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-option": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
- "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helpers": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
- "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/template": "^7.29.7",
- "@babel/types": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/parser": {
- "version": "7.29.8",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz",
- "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.29.8"
- },
- "bin": {
- "parser": "bin/babel-parser.js"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-react-jsx-self": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz",
- "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-react-jsx-source": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz",
- "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/template": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
- "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.29.7",
- "@babel/parser": "^7.29.7",
- "@babel/types": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/traverse": {
- "version": "7.29.8",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz",
- "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.29.7",
- "@babel/generator": "^7.29.8",
- "@babel/helper-globals": "^7.29.7",
- "@babel/parser": "^7.29.8",
- "@babel/template": "^7.29.7",
- "@babel/types": "^7.29.8",
- "debug": "^4.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/types": {
- "version": "7.29.8",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
- "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-string-parser": "^7.29.7",
- "@babel/helper-validator-identifier": "^7.29.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
"node_modules/@braintree/sanitize-url": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz",
@@ -338,1816 +172,14 @@
"integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==",
"license": "Apache-2.0"
},
- "node_modules/@earendil-works/pi-coding-agent": {
- "version": "0.84.2",
- "resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.84.2.tgz",
- "integrity": "sha512-l4E+B7hgXKWddRo8bC/eSue2aWZjEgJ9xIpf5p0Og+lq8a2TArCwJ0HCoCPCgaBP/tN4zbYH/wOwvx9pJpeLCA==",
- "hasShrinkwrap": true,
- "license": "MIT",
- "dependencies": {
- "@earendil-works/pi-agent-core": "^0.84.2",
- "@earendil-works/pi-ai": "^0.84.2",
- "@earendil-works/pi-client": "^0.84.2",
- "@earendil-works/pi-protocol": "^0.84.2",
- "@earendil-works/pi-tui": "^0.84.2",
- "@silvia-odwyer/photon-node": "0.3.4",
- "chalk": "5.6.2",
- "cross-spawn": "7.0.6",
- "diff": "8.0.4",
- "glob": "13.0.6",
- "grok-mermaid": "0.2.2",
- "highlight.js": "10.7.3",
- "hosted-git-info": "9.0.3",
- "ignore": "7.0.5",
- "jiti": "2.7.0",
- "minimatch": "10.2.5",
- "proper-lockfile": "4.1.2",
- "semver": "7.8.0",
- "typebox": "1.3.7",
- "undici": "8.9.0",
- "yaml": "2.9.0"
- },
- "bin": {
- "pi": "dist/cli.js"
- },
- "engines": {
- "node": ">=22.19.0"
- },
- "optionalDependencies": {
- "@mariozechner/clipboard": "0.3.9"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@anthropic-ai/sdk": {
- "version": "0.91.1",
- "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.91.1.tgz",
- "integrity": "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==",
- "license": "MIT",
- "dependencies": {
- "json-schema-to-ts": "^3.1.1"
- },
- "bin": {
- "anthropic-ai-sdk": "bin/cli"
- },
- "peerDependencies": {
- "zod": "^3.25.0 || ^4.0.0"
- },
- "peerDependenciesMeta": {
- "zod": {
- "optional": true
- }
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/crc32": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz",
- "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-crypto/util": "^5.2.0",
- "@aws-sdk/types": "^3.222.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/sha256-browser": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz",
- "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-crypto/sha256-js": "^5.2.0",
- "@aws-crypto/supports-web-crypto": "^5.2.0",
- "@aws-crypto/util": "^5.2.0",
- "@aws-sdk/types": "^3.222.0",
- "@aws-sdk/util-locate-window": "^3.0.0",
- "@smithy/util-utf8": "^2.0.0",
- "tslib": "^2.6.2"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/sha256-js": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz",
- "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-crypto/util": "^5.2.0",
- "@aws-sdk/types": "^3.222.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/supports-web-crypto": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz",
- "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.6.2"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/util": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz",
- "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "^3.222.0",
- "@smithy/util-utf8": "^2.0.0",
- "tslib": "^2.6.2"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/client-bedrock-runtime": {
- "version": "3.1048.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1048.0.tgz",
- "integrity": "sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-crypto/sha256-browser": "5.2.0",
- "@aws-crypto/sha256-js": "5.2.0",
- "@aws-sdk/core": "^3.974.11",
- "@aws-sdk/credential-provider-node": "^3.972.42",
- "@aws-sdk/eventstream-handler-node": "^3.972.16",
- "@aws-sdk/middleware-eventstream": "^3.972.12",
- "@aws-sdk/middleware-websocket": "^3.972.19",
- "@aws-sdk/token-providers": "3.1048.0",
- "@aws-sdk/types": "^3.973.8",
- "@smithy/core": "^3.24.2",
- "@smithy/fetch-http-handler": "^5.4.2",
- "@smithy/node-http-handler": "^4.7.2",
- "@smithy/types": "^4.14.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/core": {
- "version": "3.974.11",
- "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.11.tgz",
- "integrity": "sha512-QpnINq5FZH6EOaDEkmHdT7eUunbvD27pDNQypaWjFyYz7Zl1q3UCMQErBZxpmfGfI7MvI2TlK8KTkgNpv8b1ug==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "^3.973.8",
- "@aws-sdk/xml-builder": "^3.972.24",
- "@aws/lambda-invoke-store": "^0.2.2",
- "@smithy/core": "^3.24.2",
- "@smithy/signature-v4": "^5.4.2",
- "@smithy/types": "^4.14.1",
- "bowser": "^2.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-env": {
- "version": "3.972.37",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.37.tgz",
- "integrity": "sha512-/jpPvEh6f7ntmIzf7dNxoNX6Q8vt8UpesCjbW6mFfk4V1NW6bIy9qxcQ6WbA8As5yQhsZOe+xeNd4xHX8kdY2Q==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "^3.974.11",
- "@aws-sdk/types": "^3.973.8",
- "@smithy/core": "^3.24.2",
- "@smithy/types": "^4.14.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-http": {
- "version": "3.972.39",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.39.tgz",
- "integrity": "sha512-pIgTpisWyWg7X1bUbzSjuUYosYTD0Ghz2M0hkSTmb3a6i3qV3uU+NYJPI/E2XSC0HcsZh5rsLPzeXrkb2DS0Cg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "^3.974.11",
- "@aws-sdk/types": "^3.973.8",
- "@smithy/core": "^3.24.2",
- "@smithy/fetch-http-handler": "^5.4.2",
- "@smithy/node-http-handler": "^4.7.2",
- "@smithy/types": "^4.14.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-ini": {
- "version": "3.972.41",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.41.tgz",
- "integrity": "sha512-u2tyjaxJJzW8UtW4SM1ZcPMDwO6y+kV+llvou+Adts0FAKyzes5jG4izQN+KX3yE8ZROpS5y1LJ//xL2iSf76w==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "^3.974.11",
- "@aws-sdk/credential-provider-env": "^3.972.37",
- "@aws-sdk/credential-provider-http": "^3.972.39",
- "@aws-sdk/credential-provider-login": "^3.972.41",
- "@aws-sdk/credential-provider-process": "^3.972.37",
- "@aws-sdk/credential-provider-sso": "^3.972.41",
- "@aws-sdk/credential-provider-web-identity": "^3.972.41",
- "@aws-sdk/nested-clients": "^3.997.9",
- "@aws-sdk/types": "^3.973.8",
- "@smithy/core": "^3.24.2",
- "@smithy/credential-provider-imds": "^4.3.2",
- "@smithy/types": "^4.14.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-login": {
- "version": "3.972.41",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.41.tgz",
- "integrity": "sha512-0LBitxXiAiaE5nlFPfpNIww/8FRY/I7WIndWsc9GmNFOM7cE1wNpVNQEGEk9Outg5l8xl+3vybxFyUy4l9q/LQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "^3.974.11",
- "@aws-sdk/nested-clients": "^3.997.9",
- "@aws-sdk/types": "^3.973.8",
- "@smithy/core": "^3.24.2",
- "@smithy/types": "^4.14.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-node": {
- "version": "3.972.42",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.42.tgz",
- "integrity": "sha512-D4oon2zbqqsWOJUM99Gm3/ZyJ0IJvTXVN3PyloGb3kQEyI36fjCZheZj422lAgTWWd6TSHgiImLt3RIaLdv3dQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/credential-provider-env": "^3.972.37",
- "@aws-sdk/credential-provider-http": "^3.972.39",
- "@aws-sdk/credential-provider-ini": "^3.972.41",
- "@aws-sdk/credential-provider-process": "^3.972.37",
- "@aws-sdk/credential-provider-sso": "^3.972.41",
- "@aws-sdk/credential-provider-web-identity": "^3.972.41",
- "@aws-sdk/types": "^3.973.8",
- "@smithy/core": "^3.24.2",
- "@smithy/credential-provider-imds": "^4.3.2",
- "@smithy/types": "^4.14.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-process": {
- "version": "3.972.37",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.37.tgz",
- "integrity": "sha512-7nVaHBUaWIddASYfVaA9O4D5ZVjewU3sCol9WqZPGfW0nR+0WqE0xHZnD/U2L33PlOB8KNXGKZ6wOES/QijKzg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "^3.974.11",
- "@aws-sdk/types": "^3.973.8",
- "@smithy/core": "^3.24.2",
- "@smithy/types": "^4.14.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-sso": {
- "version": "3.972.41",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.41.tgz",
- "integrity": "sha512-IOWAWEHe5LkjSKkkUUX9ciV6Y1scHTsnfEkdt5yyC4Slrc7AGbkLPrpntjqh18ksJAMOaVhoBsO8p2WyTcY2wQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "^3.974.11",
- "@aws-sdk/nested-clients": "^3.997.9",
- "@aws-sdk/token-providers": "3.1048.0",
- "@aws-sdk/types": "^3.973.8",
- "@smithy/core": "^3.24.2",
- "@smithy/types": "^4.14.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-web-identity": {
- "version": "3.972.41",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.41.tgz",
- "integrity": "sha512-mbACk9Yypa8nm4iGZLs0PofOXEcTDOUw6wDnsPXNDNSd2WNXs1tSo+6nc/fh0jLYdfVZThhBL98PHW4aXFsG5A==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "^3.974.11",
- "@aws-sdk/nested-clients": "^3.997.9",
- "@aws-sdk/types": "^3.973.8",
- "@smithy/core": "^3.24.2",
- "@smithy/types": "^4.14.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/eventstream-handler-node": {
- "version": "3.972.16",
- "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.16.tgz",
- "integrity": "sha512-yedpPgKftqjU5SlPFHfqWpOw6xSCRieWRG1euWOlXn4WJxt2VX92VprCa2PpSOXjVCAeK6dTjW9eJRXVig9yGA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "^3.973.8",
- "@smithy/core": "^3.24.2",
- "@smithy/types": "^4.14.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/middleware-eventstream": {
- "version": "3.972.12",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.12.tgz",
- "integrity": "sha512-tHTHHCHNrq6XklQvlzHBDJG4Iuhh7NVPRdtmvP+nHFA+5sxPlIDzlAHHgfoYHGvT3NXP1yVP/L5c3opUn6T3Qg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "^3.973.8",
- "@smithy/core": "^3.24.2",
- "@smithy/types": "^4.14.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/middleware-websocket": {
- "version": "3.972.19",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.19.tgz",
- "integrity": "sha512-mkEhOGYozqKQkbFaVrjwr0faiwwZza1v5/jSY6Tucm3bD+uKTazIUH/4Yo6aMnQD2ua2W9cMP6s8mvwTcjtqHw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "^3.974.11",
- "@aws-sdk/types": "^3.973.8",
- "@smithy/core": "^3.24.2",
- "@smithy/fetch-http-handler": "^5.4.2",
- "@smithy/signature-v4": "^5.4.2",
- "@smithy/types": "^4.14.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">= 14.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/nested-clients": {
- "version": "3.997.9",
- "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.9.tgz",
- "integrity": "sha512-jPR3rnmRI4hWYyzfmTGBr7NblMp8QYYeflHXba1H6+7CGrWVqWKQzaXFQ4qbExqPRsXN3T3L3JxFhr6aouXUGQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-crypto/sha256-browser": "5.2.0",
- "@aws-crypto/sha256-js": "5.2.0",
- "@aws-sdk/core": "^3.974.11",
- "@aws-sdk/signature-v4-multi-region": "^3.996.27",
- "@aws-sdk/types": "^3.973.8",
- "@smithy/core": "^3.24.2",
- "@smithy/fetch-http-handler": "^5.4.2",
- "@smithy/node-http-handler": "^4.7.2",
- "@smithy/types": "^4.14.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/signature-v4-multi-region": {
- "version": "3.996.27",
- "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.27.tgz",
- "integrity": "sha512-0Phbz4t6HI3D3skxvG2uI+VWU034/nSIw1T8d+FPzzQG9EQTrw94o9mOKO2Gv3n3Oc8P7JD7RAUxkoneLWv5Eg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "^3.973.8",
- "@smithy/core": "^3.24.2",
- "@smithy/signature-v4": "^5.4.2",
- "@smithy/types": "^4.14.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/token-providers": {
- "version": "3.1048.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1048.0.tgz",
- "integrity": "sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "^3.974.11",
- "@aws-sdk/nested-clients": "^3.997.9",
- "@aws-sdk/types": "^3.973.8",
- "@smithy/core": "^3.24.2",
- "@smithy/types": "^4.14.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/types": {
- "version": "3.973.8",
- "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.8.tgz",
- "integrity": "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/types": "^4.14.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/util-locate-window": {
- "version": "3.965.5",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz",
- "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/xml-builder": {
- "version": "3.972.24",
- "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.24.tgz",
- "integrity": "sha512-V8z5YcDPfsvzrBlj0xR1vhRtocblhYbqdreCJB/voGd4Sr5zjNAeWxexbnqVtskTJe0vFb5KMqbSL++ePl+zRw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@nodable/entities": "2.1.0",
- "@smithy/types": "^4.14.1",
- "fast-xml-parser": "5.7.3",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws/lambda-invoke-store": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz",
- "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@babel/runtime": {
- "version": "7.29.2",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
- "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-agent-core": {
- "version": "0.84.2",
- "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.84.2.tgz",
- "license": "MIT",
- "dependencies": {
- "@earendil-works/pi-ai": "^0.84.2",
- "@earendil-works/pi-telemetry": "^0.84.2",
- "diff": "8.0.4",
- "ignore": "7.0.5",
- "typebox": "1.3.7",
- "yaml": "2.9.0"
- },
- "engines": {
- "node": ">=22.19.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-ai": {
- "version": "0.84.2",
- "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.84.2.tgz",
- "license": "MIT",
- "dependencies": {
- "@anthropic-ai/sdk": "0.91.1",
- "@aws-sdk/client-bedrock-runtime": "3.1048.0",
- "@earendil-works/pi-telemetry": "^0.84.2",
- "@google/genai": "1.52.0",
- "@opentelemetry/api": "1.9.0",
- "@smithy/node-http-handler": "4.7.3",
- "http-proxy-agent": "7.0.2",
- "https-proxy-agent": "7.0.6",
- "openai": "6.40.0",
- "partial-json": "0.1.7",
- "typebox": "1.3.7"
- },
- "bin": {
- "pi-ai": "dist/cli.js"
- },
- "engines": {
- "node": ">=22.19.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-client": {
- "version": "0.84.2",
- "resolved": "https://registry.npmjs.org/@earendil-works/pi-client/-/pi-client-0.84.2.tgz",
- "license": "MIT",
- "dependencies": {
- "@earendil-works/pi-protocol": "^0.84.2"
- },
- "engines": {
- "node": ">=22.19.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-protocol": {
- "version": "0.84.2",
- "resolved": "https://registry.npmjs.org/@earendil-works/pi-protocol/-/pi-protocol-0.84.2.tgz",
- "license": "MIT",
- "dependencies": {
- "typebox": "1.3.7"
- },
- "engines": {
- "node": ">=22.19.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-telemetry": {
- "version": "0.84.2",
- "resolved": "https://registry.npmjs.org/@earendil-works/pi-telemetry/-/pi-telemetry-0.84.2.tgz",
- "license": "MIT",
- "engines": {
- "node": ">=22.19.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-tui": {
- "version": "0.84.2",
- "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.84.2.tgz",
- "license": "MIT",
- "dependencies": {
- "get-east-asian-width": "1.6.0",
- "marked": "18.0.5"
- },
- "engines": {
- "node": ">=22.19.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@google/genai": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.52.0.tgz",
- "integrity": "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==",
- "hasInstallScript": true,
- "license": "Apache-2.0",
- "dependencies": {
- "google-auth-library": "^10.3.0",
- "p-retry": "^4.6.2",
- "protobufjs": "^7.5.4",
- "ws": "^8.18.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@modelcontextprotocol/sdk": "^1.25.2"
- },
- "peerDependenciesMeta": {
- "@modelcontextprotocol/sdk": {
- "optional": true
- }
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.9.tgz",
- "integrity": "sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA==",
+ "node_modules/@emnapi/runtime": {
+ "version": "1.11.3",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz",
+ "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==",
"license": "MIT",
"optional": true,
- "engines": {
- "node": ">= 10"
- },
- "optionalDependencies": {
- "@mariozechner/clipboard-darwin-arm64": "0.3.9",
- "@mariozechner/clipboard-darwin-universal": "0.3.9",
- "@mariozechner/clipboard-darwin-x64": "0.3.9",
- "@mariozechner/clipboard-linux-arm64-gnu": "0.3.9",
- "@mariozechner/clipboard-linux-arm64-musl": "0.3.9",
- "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.9",
- "@mariozechner/clipboard-linux-x64-gnu": "0.3.9",
- "@mariozechner/clipboard-linux-x64-musl": "0.3.9",
- "@mariozechner/clipboard-win32-arm64-msvc": "0.3.9",
- "@mariozechner/clipboard-win32-x64-msvc": "0.3.9"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-arm64": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.9.tgz",
- "integrity": "sha512-BfgV7vCEWZwJwZJw03r6bP5+tf0iI/ANuQYCxi9RNn7FrWB3yzGuMKCrNLRl6V761vXRdL8+OqZ0wd4TqlsNOQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-universal": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.9.tgz",
- "integrity": "sha512-BGGR4iA9Z2shAjI65eI5xtyb3LYNlDW9X3gxKxDbqtbnREohsrqznov6zpKoIrsRWpzlYVEdKphS7ksJ0/ndSQ==",
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-x64": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.9.tgz",
- "integrity": "sha512-4kURmCbS6nt8uYhtmWpUcJWyPHfmAr5dTpXD1nO3pIfa+TSQ9DbrGOYCKH+aEFW47XhQ4Vp8ZTszie+wfFvDKg==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-arm64-gnu": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.9.tgz",
- "integrity": "sha512-g59OkUGP2DDfCOIKypHeYgv2M55u/cKvXa5dSxFbEJ34XvIQMdcVmpKCkGUro3ZgefXiGVdwguvTMQGpHWzIXw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-arm64-musl": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.9.tgz",
- "integrity": "sha512-AGuJdgKsmJdm4Pych7kv3sqe591ERRaAHW3xjLooiFzn8J+PxUyof++7YZrB5Y5tpnTO+K18Og3taj2NpluCRQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-riscv64-gnu": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.9.tgz",
- "integrity": "sha512-DXBEAiuMpk7dhS1a9NzNxVAFi1vaKoPu7rQNgY8LIDLGrK3lnIp3nT10DUum+PKVJoJppIP+NAA8IZe4DMNDPw==",
- "cpu": [
- "riscv64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-x64-gnu": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.9.tgz",
- "integrity": "sha512-WORrMLd6EpElEME7JRKfSaY34nW1P5LbdgK5YNCS1ncG2LqmITsSMEJ8nh2mpvxb3TxqbOOKgY7k9eMJYlW9Mw==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-x64-musl": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.9.tgz",
- "integrity": "sha512-/DHn+1DrfL6oRaPPWXaOKvonFFrni666fxd+zFqiQEfvBH0tsHVWjq9iqBk0oDp0qaPA72lIMy5BptxISBEhZQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-win32-arm64-msvc": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.9.tgz",
- "integrity": "sha512-O5FHD3ErkMwMhNzAfu3ggy0ug4z7btZuoQgwwxlzPrwV2bxlD6WDpqBY4NCgICAgZdDKdp+loUEKVAVt8aYnhQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-win32-x64-msvc": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.9.tgz",
- "integrity": "sha512-ihQC3EufqEY81vhXBgVBtK4prL+wc62zJsSvxrgz7K1hsdt6OObz6v9p3Rn1OG3GJksTTKMJF0u/guMISHPhSA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@nodable/entities": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz",
- "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/nodable"
- }
- ],
- "license": "MIT"
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@opentelemetry/api": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
- "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=8.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/aspromise": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
- "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/base64": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
- "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/codegen": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz",
- "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/eventemitter": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz",
- "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/fetch": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz",
- "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==",
- "license": "BSD-3-Clause",
"dependencies": {
- "@protobufjs/aspromise": "^1.1.1"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/float": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
- "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/path": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
- "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/pool": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
- "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/utf8": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz",
- "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@silvia-odwyer/photon-node": {
- "version": "0.3.4",
- "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz",
- "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==",
- "license": "Apache-2.0"
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/core": {
- "version": "3.24.3",
- "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.3.tgz",
- "integrity": "sha512-Ep/7tPamGY8mgESE3LyLKtxJyy6U52WWAqr/3wial47Sj4u3PiIF73AOGI27UyLy9duTkhZbgzodOfLV4TduZg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-crypto/crc32": "5.2.0",
- "@smithy/types": "^4.14.2",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/credential-provider-imds": {
- "version": "4.3.3",
- "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.3.3.tgz",
- "integrity": "sha512-I2Bti0DKFo2IJyN28ijCsx51BAumEYR4/1yZ1FXyBygy9MqbnMqCev4JPth/MbpRfBSRAX35hITSnAdJRo1u5w==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/core": "^3.24.3",
- "@smithy/types": "^4.14.2",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/fetch-http-handler": {
- "version": "5.4.3",
- "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.4.3.tgz",
- "integrity": "sha512-F+DRf8IJazRJgYog2A/yJK7eYVc0rqTlRzO+5ZxjJd4WkZoKz0IJRncf7G6t1pdVT3kryJcwuTFhN1c5m6N47A==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/core": "^3.24.3",
- "@smithy/types": "^4.14.2",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/is-array-buffer": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz",
- "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/node-http-handler": {
- "version": "4.7.3",
- "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.3.tgz",
- "integrity": "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/core": "^3.24.3",
- "@smithy/types": "^4.14.2",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/signature-v4": {
- "version": "5.4.3",
- "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.4.3.tgz",
- "integrity": "sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/core": "^3.24.3",
- "@smithy/types": "^4.14.2",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/types": {
- "version": "4.14.2",
- "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.2.tgz",
- "integrity": "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/util-buffer-from": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz",
- "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/is-array-buffer": "^2.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/util-utf8": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz",
- "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/util-buffer-from": "^2.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/@types/node": {
- "version": "22.19.19",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz",
- "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==",
- "license": "MIT",
- "dependencies": {
- "undici-types": "~6.21.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/agent-base": {
- "version": "7.1.4",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
- "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/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==",
- "license": "MIT",
- "engines": {
- "node": "18 || 20 || >=22"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/base64-js": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
- "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/bignumber.js": {
- "version": "9.3.1",
- "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
- "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
- "license": "MIT",
- "engines": {
- "node": "*"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/bowser": {
- "version": "2.14.1",
- "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz",
- "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==",
- "license": "MIT"
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/brace-expansion": {
- "version": "5.0.9",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
- "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^4.0.2"
- },
- "engines": {
- "node": "20 || >=22"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/buffer-equal-constant-time": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
- "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/chalk": {
- "version": "5.6.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
- "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
- "license": "MIT",
- "engines": {
- "node": "^12.17.0 || ^14.13 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/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/@earendil-works/pi-coding-agent/node_modules/data-uri-to-buffer": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
- "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
- "license": "MIT",
- "engines": {
- "node": ">= 12"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/diff": {
- "version": "8.0.4",
- "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz",
- "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==",
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.3.1"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/ecdsa-sig-formatter": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
- "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "safe-buffer": "^5.0.1"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/extend": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
- "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
- "license": "MIT"
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/fast-xml-builder": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz",
- "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/NaturalIntelligence"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "path-expression-matcher": "^1.5.0",
- "xml-naming": "^0.1.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/fast-xml-parser": {
- "version": "5.7.3",
- "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.3.tgz",
- "integrity": "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/NaturalIntelligence"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "@nodable/entities": "^2.1.0",
- "fast-xml-builder": "^1.1.7",
- "path-expression-matcher": "^1.5.0",
- "strnum": "^2.2.3"
- },
- "bin": {
- "fxparser": "src/cli/cli.js"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/fetch-blob": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
- "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/jimmywarting"
- },
- {
- "type": "paypal",
- "url": "https://paypal.me/jimmywarting"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "node-domexception": "^1.0.0",
- "web-streams-polyfill": "^3.0.3"
- },
- "engines": {
- "node": "^12.20 || >= 14.13"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/formdata-polyfill": {
- "version": "4.0.10",
- "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
- "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
- "license": "MIT",
- "dependencies": {
- "fetch-blob": "^3.1.2"
- },
- "engines": {
- "node": ">=12.20.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/gaxios": {
- "version": "7.1.4",
- "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz",
- "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==",
- "license": "Apache-2.0",
- "dependencies": {
- "extend": "^3.0.2",
- "https-proxy-agent": "^7.0.1",
- "node-fetch": "^3.3.2"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/gcp-metadata": {
- "version": "8.1.2",
- "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz",
- "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==",
- "license": "Apache-2.0",
- "dependencies": {
- "gaxios": "^7.0.0",
- "google-logging-utils": "^1.0.0",
- "json-bigint": "^1.0.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/get-east-asian-width": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
- "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/glob": {
- "version": "13.0.6",
- "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
- "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "minimatch": "^10.2.2",
- "minipass": "^7.1.3",
- "path-scurry": "^2.0.2"
- },
- "engines": {
- "node": "18 || 20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/google-auth-library": {
- "version": "10.6.2",
- "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz",
- "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==",
- "license": "Apache-2.0",
- "dependencies": {
- "base64-js": "^1.3.0",
- "ecdsa-sig-formatter": "^1.0.11",
- "gaxios": "^7.1.4",
- "gcp-metadata": "8.1.2",
- "google-logging-utils": "1.1.3",
- "jws": "^4.0.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/google-logging-utils": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz",
- "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/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/@earendil-works/pi-coding-agent/node_modules/grok-mermaid": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/grok-mermaid/-/grok-mermaid-0.2.2.tgz",
- "integrity": "sha512-XcJEP5dDC8liHBh52mlLjU18fNvu1ckFsu0QpIG3+APZ270fsj9wxpiA6cOURmbUEuoMVgjbC2+UYgTdCqqgzA==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/highlight.js": {
- "version": "10.7.3",
- "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
- "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==",
- "license": "BSD-3-Clause",
- "engines": {
- "node": "*"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/hosted-git-info": {
- "version": "9.0.3",
- "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz",
- "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==",
- "license": "ISC",
- "dependencies": {
- "lru-cache": "^11.1.0"
- },
- "engines": {
- "node": "^20.17.0 || >=22.9.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/http-proxy-agent": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
- "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
- "license": "MIT",
- "dependencies": {
- "agent-base": "^7.1.0",
- "debug": "^4.3.4"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/https-proxy-agent": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
- "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
- "license": "MIT",
- "dependencies": {
- "agent-base": "^7.1.2",
- "debug": "4"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/ignore": {
- "version": "7.0.5",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
- "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
- "license": "MIT",
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/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/@earendil-works/pi-coding-agent/node_modules/jiti": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
- "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
- "license": "MIT",
- "bin": {
- "jiti": "lib/jiti-cli.mjs"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/json-bigint": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
- "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
- "license": "MIT",
- "dependencies": {
- "bignumber.js": "^9.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/json-schema-to-ts": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz",
- "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.18.3",
- "ts-algebra": "^2.0.0"
- },
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/jwa": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
- "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
- "license": "MIT",
- "dependencies": {
- "buffer-equal-constant-time": "^1.0.1",
- "ecdsa-sig-formatter": "1.0.11",
- "safe-buffer": "^5.0.1"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/jws": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
- "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
- "license": "MIT",
- "dependencies": {
- "jwa": "^2.0.1",
- "safe-buffer": "^5.0.1"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/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/@earendil-works/pi-coding-agent/node_modules/lru-cache": {
- "version": "11.4.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.4.0.tgz",
- "integrity": "sha512-W+R+kFL4HgVxONq2bhXPi3bGpzGe/yEhVOp233qw9wCRtgncJ15P3bC+e4zZMu4Cq7d+WAJjXGW0uUkifhcatA==",
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": "20 || >=22"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/marked": {
- "version": "18.0.5",
- "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz",
- "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==",
- "license": "MIT",
- "bin": {
- "marked": "bin/marked.js"
- },
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/minimatch": {
- "version": "10.2.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
- "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "brace-expansion": "^5.0.5"
- },
- "engines": {
- "node": "18 || 20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/minipass": {
- "version": "7.1.3",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
- "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": ">=16 || 14 >=14.17"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "license": "MIT"
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/node-domexception": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
- "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
- "deprecated": "Use your platform's native DOMException instead",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/jimmywarting"
- },
- {
- "type": "github",
- "url": "https://paypal.me/jimmywarting"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=10.5.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/node-fetch": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
- "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
- "license": "MIT",
- "dependencies": {
- "data-uri-to-buffer": "^4.0.0",
- "fetch-blob": "^3.1.4",
- "formdata-polyfill": "^4.0.10"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/node-fetch"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/openai": {
- "version": "6.40.0",
- "resolved": "https://registry.npmjs.org/openai/-/openai-6.40.0.tgz",
- "integrity": "sha512-MWtTjd/gQt4jpbji61NTgFWJLoY/PdRJ6wG9/ZDRMYNMlBKrCrSlkLI+KgHP1vR1qT6LKSAyAqIxno6lcK9JiA==",
- "license": "Apache-2.0",
- "peerDependencies": {
- "ws": "^8.18.0",
- "zod": "^3.25 || ^4.0"
- },
- "peerDependenciesMeta": {
- "ws": {
- "optional": true
- },
- "zod": {
- "optional": true
- }
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/p-retry": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
- "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==",
- "license": "MIT",
- "dependencies": {
- "@types/retry": "0.12.0",
- "retry": "^0.13.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/p-retry/node_modules/@types/retry": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
- "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
- "license": "MIT"
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/partial-json": {
- "version": "0.1.7",
- "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz",
- "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==",
- "license": "MIT"
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/path-expression-matcher": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz",
- "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/NaturalIntelligence"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/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/@earendil-works/pi-coding-agent/node_modules/path-scurry": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
- "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "lru-cache": "^11.0.0",
- "minipass": "^7.1.2"
- },
- "engines": {
- "node": "18 || 20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/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/@earendil-works/pi-coding-agent/node_modules/proper-lockfile/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/@earendil-works/pi-coding-agent/node_modules/protobufjs": {
- "version": "7.6.5",
- "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz",
- "integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==",
- "hasInstallScript": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@protobufjs/aspromise": "^1.1.2",
- "@protobufjs/base64": "^1.1.2",
- "@protobufjs/codegen": "^2.0.5",
- "@protobufjs/eventemitter": "^1.1.1",
- "@protobufjs/fetch": "^1.1.1",
- "@protobufjs/float": "^1.0.2",
- "@protobufjs/path": "^1.1.2",
- "@protobufjs/pool": "^1.1.0",
- "@protobufjs/utf8": "^1.1.1",
- "@types/node": ">=13.7.0",
- "long": "^5.3.2"
- },
- "engines": {
- "node": ">=12.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/retry": {
- "version": "0.13.1",
- "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
- "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
- "license": "MIT",
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/semver": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
- "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/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/@earendil-works/pi-coding-agent/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/@earendil-works/pi-coding-agent/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/@earendil-works/pi-coding-agent/node_modules/strnum": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz",
- "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/NaturalIntelligence"
- }
- ],
- "license": "MIT"
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/ts-algebra": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz",
- "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==",
- "license": "MIT"
- },
- "node_modules/@earendil-works/pi-coding-agent/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/@earendil-works/pi-coding-agent/node_modules/typebox": {
- "version": "1.3.7",
- "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.3.7.tgz",
- "integrity": "sha512-meKuifc33Pccx0O6PdIzYMq3Og8zvP4TIi/a+Bw3AEMZMxOD0+RHGQvpglEe6Zdy3wZ8nqn/j95h8LUZLk/6Hg==",
- "license": "MIT"
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/undici": {
- "version": "8.9.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-8.9.0.tgz",
- "integrity": "sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==",
- "license": "MIT",
- "engines": {
- "node": ">=22.19.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/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/@earendil-works/pi-coding-agent/node_modules/web-streams-polyfill": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
- "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/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/@earendil-works/pi-coding-agent/node_modules/ws": {
- "version": "8.21.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
- "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
- "license": "MIT",
- "engines": {
- "node": ">=10.0.0"
- },
- "peerDependencies": {
- "bufferutil": "^4.0.1",
- "utf-8-validate": ">=5.0.2"
- },
- "peerDependenciesMeta": {
- "bufferutil": {
- "optional": true
- },
- "utf-8-validate": {
- "optional": true
- }
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/xml-naming": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz",
- "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/NaturalIntelligence"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/@earendil-works/pi-coding-agent/node_modules/yaml": {
- "version": "2.9.0",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
- "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
- "license": "ISC",
- "bin": {
- "yaml": "bin.mjs"
- },
- "engines": {
- "node": ">= 14.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/eemeli"
+ "tslib": "^2.4.0"
}
},
"node_modules/@esbuild/aix-ppc64": {
@@ -2592,215 +624,42 @@
"node": ">=18"
}
},
- "node_modules/@fastify/accept-negotiator": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-2.1.0.tgz",
- "integrity": "sha512-F3EVbzWt+xcnVaOHmWyIlpuFtbxOln7HDZQsh09MtMmMm/CipMayNt8hnIL8VQi54u2ZociDbf+iluGYkf7B1A==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
- "license": "MIT"
- },
- "node_modules/@fastify/ajv-compiler": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.6.tgz",
- "integrity": "sha512-NtuzM0SfaMJbGlnjr9LWQUN5LzgSrbB8tf/wRZNas+4E1O/Nmzl53e7ruT61HDZyRCJGC6FxIogmNZO1c5ETBA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
+ "node_modules/@floating-ui/core": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz",
+ "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==",
"license": "MIT",
"dependencies": {
- "ajv": "^8.12.0",
- "ajv-formats": "^3.0.1",
- "fast-uri": "^4.0.0"
+ "@floating-ui/utils": "^0.2.12"
}
},
- "node_modules/@fastify/error": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.0.tgz",
- "integrity": "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
- "license": "MIT"
- },
- "node_modules/@fastify/fast-json-stringify-compiler": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.1.0.tgz",
- "integrity": "sha512-PxcYtKLbQ8Z+yApiqjK8FwxIwvEj38k2OiLc17u8dkJSlmfi2wHHPaSnaoqBPQqtvF8YVsDgDpP2snDCfFrpfw==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
+ "node_modules/@floating-ui/dom": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz",
+ "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==",
"license": "MIT",
"dependencies": {
- "fast-json-stringify": "^7.0.0"
+ "@floating-ui/core": "^1.8.0",
+ "@floating-ui/utils": "^0.2.12"
}
},
- "node_modules/@fastify/forwarded": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.2.tgz",
- "integrity": "sha512-NE8HgKLgYejV9lDpqkEFaDKMLYelJBVfHekhB0UKvX0ghagXRJqg68feg8er1NPXxG4N9i6vPxzt8E+3wHfcmA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
- "license": "MIT"
- },
- "node_modules/@fastify/merge-json-schemas": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz",
- "integrity": "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
+ "node_modules/@floating-ui/react-dom": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz",
+ "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==",
"license": "MIT",
"dependencies": {
- "dequal": "^2.0.3"
+ "@floating-ui/dom": "^1.8.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
}
},
- "node_modules/@fastify/proxy-addr": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.1.0.tgz",
- "integrity": "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "@fastify/forwarded": "^3.0.0",
- "ipaddr.js": "^2.1.0"
- }
- },
- "node_modules/@fastify/send": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@fastify/send/-/send-4.1.1.tgz",
- "integrity": "sha512-BYo+EiaKwlxH+WetGk6hAs1d39iP0y1gqB8lGF/qwkJ9ZZ/cBY1vx5NvExb9Sc3yRMFjD5X4Eyh4e4+TzRkzdw==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "@lukeed/ms": "^2.0.2",
- "escape-html": "~1.0.3",
- "fast-decode-uri-component": "^1.0.1",
- "http-errors": "^2.0.0",
- "mime": "^3"
- }
- },
- "node_modules/@fastify/static": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/@fastify/static/-/static-8.3.0.tgz",
- "integrity": "sha512-yKxviR5PH1OKNnisIzZKmgZSus0r2OZb8qCSbqmw34aolT4g3UlzYfeBRym+HJ1J471CR8e2ldNub4PubD1coA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "@fastify/accept-negotiator": "^2.0.0",
- "@fastify/send": "^4.0.0",
- "content-disposition": "^0.5.4",
- "fastify-plugin": "^5.0.0",
- "fastq": "^1.17.1",
- "glob": "^11.0.0"
- }
- },
- "node_modules/@fastify/websocket": {
- "version": "11.3.0",
- "resolved": "https://registry.npmjs.org/@fastify/websocket/-/websocket-11.3.0.tgz",
- "integrity": "sha512-g89ag4BCcD9YP5wBZXixzoLnuf5j89p/sXFcfpCiv2pdEkYYukBEoK3heVzqsp0EAtszVDc2BBZG0KZqeAShIA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "duplexify": "^4.1.3",
- "fastify-plugin": "^6.0.0",
- "ws": "^8.16.0"
- }
- },
- "node_modules/@fastify/websocket/node_modules/fastify-plugin": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-6.0.0.tgz",
- "integrity": "sha512-fZOty7z3O7vOliF6d8bHE3wiEh1KcNnKEQensSgTk9C1DvN6nRLS++XVd86v33Hw/8u9Un8A1zDrQ8ujcQDHEg==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.12",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz",
+ "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==",
"license": "MIT"
},
"node_modules/@iconify/types": {
@@ -2820,15 +679,507 @@
"import-meta-resolve": "^4.2.0"
}
},
- "node_modules/@isaacs/cliui": {
- "version": "9.0.0",
- "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz",
- "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==",
- "license": "BlueOak-1.0.0",
+ "node_modules/@img/colour": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
+ "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
+ "license": "MIT",
+ "optional": true,
"engines": {
"node": ">=18"
}
},
+ "node_modules/@img/sharp-darwin-arm64": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz",
+ "integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-arm64": "1.3.2"
+ }
+ },
+ "node_modules/@img/sharp-darwin-x64": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz",
+ "integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-x64": "1.3.2"
+ }
+ },
+ "node_modules/@img/sharp-freebsd-wasm32": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz",
+ "integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "dependencies": {
+ "@img/sharp-wasm32": "0.35.3"
+ },
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz",
+ "integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-x64": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz",
+ "integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz",
+ "integrity": "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm64": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz",
+ "integrity": "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-ppc64": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz",
+ "integrity": "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-riscv64": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz",
+ "integrity": "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-s390x": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz",
+ "integrity": "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-x64": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz",
+ "integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz",
+ "integrity": "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz",
+ "integrity": "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz",
+ "integrity": "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm": "1.3.2"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm64": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz",
+ "integrity": "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm64": "1.3.2"
+ }
+ },
+ "node_modules/@img/sharp-linux-ppc64": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz",
+ "integrity": "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-ppc64": "1.3.2"
+ }
+ },
+ "node_modules/@img/sharp-linux-riscv64": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz",
+ "integrity": "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-riscv64": "1.3.2"
+ }
+ },
+ "node_modules/@img/sharp-linux-s390x": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz",
+ "integrity": "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-s390x": "1.3.2"
+ }
+ },
+ "node_modules/@img/sharp-linux-x64": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz",
+ "integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-x64": "1.3.2"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-arm64": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz",
+ "integrity": "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-arm64": "1.3.2"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-x64": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz",
+ "integrity": "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-x64": "1.3.2"
+ }
+ },
+ "node_modules/@img/sharp-wasm32": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz",
+ "integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==",
+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/runtime": "^1.11.1"
+ },
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-webcontainers-wasm32": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz",
+ "integrity": "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==",
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "@img/sharp-wasm32": "0.35.3"
+ },
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-arm64": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz",
+ "integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-ia32": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz",
+ "integrity": "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-x64": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz",
+ "integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@@ -2879,15 +1230,6 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
- "node_modules/@lukeed/ms": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz",
- "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/@mermaid-js/parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.2.1.tgz",
@@ -2897,10 +1239,1980 @@
"@chevrotain/types": "~11.1.2"
}
},
- "node_modules/@napi-rs/lzma-linux-x64-gnu": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz",
- "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==",
+ "node_modules/@next/env": {
+ "version": "16.3.2",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.2.tgz",
+ "integrity": "sha512-8k4YoG8cM7LWlkfzGNYCRBbFNlernLiMw4s0btVl+CmmWqn3VpYypA72/5Feb1UWdxe6tHqr5KHP4p4Y4m9luA==",
+ "license": "MIT"
+ },
+ "node_modules/@next/swc-darwin-arm64": {
+ "version": "16.3.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.3.2.tgz",
+ "integrity": "sha512-ib5Llm93YCKoKWDh6ZaHq6QWTuOZ2bRkSnUwMmX8dsRIOkBNL1vVlSiUKSfixPL9SSh9pvukzqajk/klkn5vqg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-darwin-x64": {
+ "version": "16.3.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.2.tgz",
+ "integrity": "sha512-qd98fX2+I5nYJDioW2o7nSjoxM5KvWdeDefM80igia4+C/qSIEhH4MhTE+hO/7qKM7W37/Mq+dOWp8UePSyLHw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-gnu": {
+ "version": "16.3.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.2.tgz",
+ "integrity": "sha512-vqsgb6FAOzcrCccsLXiKtAy5t8EzO+uOazuFaSkQxeY0tNONG3vpHYy8pyBafcI5SNFPTeyard6yTr6SzNGo2A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-musl": {
+ "version": "16.3.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.2.tgz",
+ "integrity": "sha512-xIe1eujfHUB2XcxHGddxJyu6TJRPjC5NpIkQYB/32ESkt5VkQyIAjmLRS38c+s6QY+qjtY/4KarVDzXRuD7lZQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-gnu": {
+ "version": "16.3.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.2.tgz",
+ "integrity": "sha512-Fe0SA2j8X0kmc3aveuHD7UktO3AE2+mH3LguP60vGbz7u0z+MrDXbeb5iZFYAwR7EzzzXJ2Yk966w9mGTFMqfA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-musl": {
+ "version": "16.3.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.2.tgz",
+ "integrity": "sha512-TFBipb+gyesI/2Ve4zVu7kGltBWN/R466G5/1gtt2lECfc22G1pjkTxu68Q9aFcOaXiRGTQfvDbQQFe7mYgxiQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-arm64-msvc": {
+ "version": "16.3.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.2.tgz",
+ "integrity": "sha512-rVtmnNpBYIosDnKD/96dKxFsJnwnn1WRGG/HioSe8XCm2ksSHNrd2R6+hSjvTBxeMNhJ9pYeu/90cWB1nQLuNA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-x64-msvc": {
+ "version": "16.3.2",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.2.tgz",
+ "integrity": "sha512-H4Y2o2/JcHu8LtwzD5CXfHhwxwz8gfsx2HXDEw46Mtev5xHnEmB7HNtZtmriw5ReUOjRtcDqo7XSbU01FT9NlA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@openrouter/ai-sdk-provider": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@openrouter/ai-sdk-provider/-/ai-sdk-provider-3.0.0.tgz",
+ "integrity": "sha512-m9XTSWoODH2RM5OsZpaGiN7QRR8cdP5paBWq699Tu3JVmGPBKT8xF8XwV0ZBVVsjikD/JgWfak4VSsTR4wAVbg==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=22"
+ },
+ "peerDependencies": {
+ "ai": "^7.0.0",
+ "zod": "^3.25.76 || ^4.1.8"
+ }
+ },
+ "node_modules/@radix-ui/number": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.3.tgz",
+ "integrity": "sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/primitive": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz",
+ "integrity": "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-accessible-icon": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.15.tgz",
+ "integrity": "sha512-WTQwcAvQf5sOcuUyi90lKPbhwcvQ+j55cjrSmeaN+L2vKU3DooOvlKw2MDeiJ5IkV5N905KW0/fGojKOBhD11A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-visually-hidden": "1.2.11"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-accordion": {
+ "version": "1.2.20",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.20.tgz",
+ "integrity": "sha512-jDhG9FvAEnlhnjrsINbNXcUa4G+L1KqSkJSunkbKEzFRcAb52jvM0PjPxPRvhe1HNc5F5yc0yzzWeeqlH4yBIg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-collapsible": "1.1.20",
+ "@radix-ui/react-collection": "1.1.15",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-controllable-state": "1.2.6"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-alert-dialog": {
+ "version": "1.1.23",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.23.tgz",
+ "integrity": "sha512-VAYOiQRqj3GPpYJE0I9J+X8Ip05cyVlNdKOFeiGS2Ou1HHGfpl0BxOyZm6nmVDyU+W+NF3/XLzmjHmVGydhwgA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-dialog": "1.1.23",
+ "@radix-ui/react-primitive": "2.1.10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-arrow": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.15.tgz",
+ "integrity": "sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-aspect-ratio": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.15.tgz",
+ "integrity": "sha512-fy+dyVR+90nelK8rqIznFlxzx7uPcGbhxH8Nfr2bHb4UfSe+e3hklOC0luK0hDwVwnRX7xTRySpsrQVeW+/oNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-avatar": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.2.6.tgz",
+ "integrity": "sha512-4ULOTJ/mqy2hT9GlWa/MFHxHSvH3nJzHnZM1waNsc5Bonv7i70aNenghXmD97S6OJ81ekXONGGt4nT1r0PfEdA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-callback-ref": "1.1.4",
+ "@radix-ui/react-use-is-hydrated": "0.1.3",
+ "@radix-ui/react-use-layout-effect": "1.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-checkbox": {
+ "version": "1.3.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.11.tgz",
+ "integrity": "sha512-Gnptr9pDDQxD3hgq2dtPbtrp/c2qH1mBwIzw3X/ivrMb2e1t0jMTi606fVEqFPaQR1ggXIVQWKj3P2WW9v7zGQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-size": "1.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collapsible": {
+ "version": "1.1.20",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.20.tgz",
+ "integrity": "sha512-mcGesGplBnzN2sbvJETzpCNfSMyPnb29q1GRLU+Ib7bJrpIG2ywmRoh2V5VbA2uNvKikKUlVbAPks7JDjz4A8Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-layout-effect": "1.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collection": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.15.tgz",
+ "integrity": "sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-slot": "1.3.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz",
+ "integrity": "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-context": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.2.tgz",
+ "integrity": "sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-context-menu": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.3.7.tgz",
+ "integrity": "sha512-CtXP35dxaB5T3zXSd+E3uHe/QpXcpYnZmxp6OaIbfthtfW4wyb77M23BG+bwIJDtsMwEP/YssdsmNyZu7jhWew==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-menu": "2.1.24",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-controllable-state": "1.2.6"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dialog": {
+ "version": "1.1.23",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.23.tgz",
+ "integrity": "sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-dismissable-layer": "1.1.19",
+ "@radix-ui/react-focus-guards": "1.1.6",
+ "@radix-ui/react-focus-scope": "1.1.16",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-portal": "1.1.17",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-slot": "1.3.3",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-layout-effect": "1.1.4",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.7.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-direction": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.4.tgz",
+ "integrity": "sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dismissable-layer": {
+ "version": "1.1.19",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.19.tgz",
+ "integrity": "sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-callback-ref": "1.1.4",
+ "@radix-ui/react-use-effect-event": "0.0.5"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dropdown-menu": {
+ "version": "2.1.24",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.24.tgz",
+ "integrity": "sha512-geq8l2rJkxvkXsT9RMgtUE3P8pITFpTsvYpbySi1IH4fZEABD/Gp85myayFgxk0ktljGMJnCbeFkyTusvSvv7g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-menu": "2.1.24",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-controllable-state": "1.2.6"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-guards": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.6.tgz",
+ "integrity": "sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-scope": {
+ "version": "1.1.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.16.tgz",
+ "integrity": "sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-callback-ref": "1.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-form": {
+ "version": "0.1.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.16.tgz",
+ "integrity": "sha512-Q4TLEn2A7TAypxwmd6R9EwrlXDvkfYSDMrq9/887AXAGh+G1rH+kYJKSTv+Si9Y0JPKTwKYv6PviAJosysNimA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-label": "2.1.15",
+ "@radix-ui/react-primitive": "2.1.10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-hover-card": {
+ "version": "1.1.23",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.23.tgz",
+ "integrity": "sha512-H8qONfZd3ltrU3+jHCIgITbWo6e1iTKvP9DHdrvYbX48ooRM5FjEDTn16AMwdfuOGkWdZEhpl3PLL/Wk/AnHDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-dismissable-layer": "1.1.19",
+ "@radix-ui/react-popper": "1.3.7",
+ "@radix-ui/react-portal": "1.1.17",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-controllable-state": "1.2.6"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-id": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.4.tgz",
+ "integrity": "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-label": {
+ "version": "2.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.15.tgz",
+ "integrity": "sha512-o/rdYEwZTTo5tjknnPeyQFU45kUC4i/XyeDPP+HGyi6XqpOP6Zf5Ya5vh/Yfe9Id5JiuWnnAx2XqIeD3UYZt0g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menu": {
+ "version": "2.1.24",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.24.tgz",
+ "integrity": "sha512-uW7RVuU6Lp/ZtfeY4b3kL32zccgEWvPv1+cf17ubYzHa9cL8AHokmk36cG/XEiH/smbQvumnieXX9j/e9RqJWA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-collection": "1.1.15",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-dismissable-layer": "1.1.19",
+ "@radix-ui/react-focus-guards": "1.1.6",
+ "@radix-ui/react-focus-scope": "1.1.16",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-popper": "1.3.7",
+ "@radix-ui/react-portal": "1.1.17",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-roving-focus": "1.1.19",
+ "@radix-ui/react-slot": "1.3.3",
+ "@radix-ui/react-use-callback-ref": "1.1.4",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.7.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menubar": {
+ "version": "1.1.24",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.24.tgz",
+ "integrity": "sha512-eeVs0vf7cuqXaM0qLQCPcufImiJNVBXdJDLu7ZGYl2732UH23Qat/foNGrr6vYV3/DdTsBqASoggUFgH14OcZA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-collection": "1.1.15",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-menu": "2.1.24",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-roving-focus": "1.1.19",
+ "@radix-ui/react-use-controllable-state": "1.2.6"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-navigation-menu": {
+ "version": "1.2.22",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.22.tgz",
+ "integrity": "sha512-ou7iLEJ+yrhQndkkA4U21XIdS/CS45F4iXIkTZcb6/Ne9EMsOuDudVmCwmDnfFZZ+y1FZqXRNSIgBy+YMvZVZg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-collection": "1.1.15",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-dismissable-layer": "1.1.19",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-callback-ref": "1.1.4",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-layout-effect": "1.1.4",
+ "@radix-ui/react-use-previous": "1.1.4",
+ "@radix-ui/react-visually-hidden": "1.2.11"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-one-time-password-field": {
+ "version": "0.1.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.16.tgz",
+ "integrity": "sha512-Tj9P6ntAJEw52oq/F0AGknXR4XncxEt7XU47O3xJQOiWfLzEy3d9gtgKfvjSzGxzHkfL+VzvxGu2KTFsloJqXw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.3",
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-collection": "1.1.15",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-roving-focus": "1.1.19",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-effect-event": "0.0.5",
+ "@radix-ui/react-use-is-hydrated": "0.1.3",
+ "@radix-ui/react-use-layout-effect": "1.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-password-toggle-field": {
+ "version": "0.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.11.tgz",
+ "integrity": "sha512-4gvFnmDXu3dgj21CqsufzIameRvlRd4SBqaWhcrlrNhRo0Y5i/49AmRJYe1fdAM3G2VNBbmin4b0D6cdQocwgw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-effect-event": "0.0.5",
+ "@radix-ui/react-use-is-hydrated": "0.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover": {
+ "version": "1.1.23",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.23.tgz",
+ "integrity": "sha512-mw58MrBlyHWFisTOYignD0vf/3gdcgAR+9of1s9G/38CbFiUwH1nCDkc0AUM9IrXFgN5Ue8n45j9WCgyM1sbiQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-dismissable-layer": "1.1.19",
+ "@radix-ui/react-focus-guards": "1.1.6",
+ "@radix-ui/react-focus-scope": "1.1.16",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-popper": "1.3.7",
+ "@radix-ui/react-portal": "1.1.17",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-slot": "1.3.3",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.7.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popper": {
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.7.tgz",
+ "integrity": "sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react-dom": "^2.0.0",
+ "@radix-ui/react-arrow": "1.1.15",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-callback-ref": "1.1.4",
+ "@radix-ui/react-use-layout-effect": "1.1.4",
+ "@radix-ui/react-use-rect": "1.1.4",
+ "@radix-ui/react-use-size": "1.1.4",
+ "@radix-ui/rect": "1.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-portal": {
+ "version": "1.1.17",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.17.tgz",
+ "integrity": "sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-layout-effect": "1.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-presence": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.10.tgz",
+ "integrity": "sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.10.tgz",
+ "integrity": "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.3.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-progress": {
+ "version": "1.1.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.16.tgz",
+ "integrity": "sha512-5XnomAsoZZCY+KNTxbIghpGqPruZvKFNlvcAljVAOdDRDsH4/OZQxhtwo5wdtoDM5R6MhJBb2sPnDuRFep3lzg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-primitive": "2.1.10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group": {
+ "version": "1.4.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.4.7.tgz",
+ "integrity": "sha512-cgYFEkntCxppHZgtSZ+7vh0wbZQ+IC7PPMw8DSnRG27B6kDd32/Zw0OJt7dGDigCoprMuWHjg2PvUn3PYvPFoQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-roving-focus": "1.1.19",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-size": "1.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-roving-focus": {
+ "version": "1.1.19",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.19.tgz",
+ "integrity": "sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-collection": "1.1.15",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-callback-ref": "1.1.4",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-is-hydrated": "0.1.3",
+ "@radix-ui/react-use-layout-effect": "1.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-scroll-area": {
+ "version": "1.2.18",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.18.tgz",
+ "integrity": "sha512-Zn5Cd171wxsO3Dfg8HaW6RifTb9CYTKQJHs/G4+LN1GfmJpaQMZQyQxMprVPHpaz7QY4l9BxK2JwQuzHsXC8nA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.3",
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-callback-ref": "1.1.4",
+ "@radix-ui/react-use-layout-effect": "1.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.7.tgz",
+ "integrity": "sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.3",
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-collection": "1.1.15",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-dismissable-layer": "1.1.19",
+ "@radix-ui/react-focus-guards": "1.1.6",
+ "@radix-ui/react-focus-scope": "1.1.16",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-popper": "1.3.7",
+ "@radix-ui/react-portal": "1.1.17",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-slot": "1.3.3",
+ "@radix-ui/react-use-callback-ref": "1.1.4",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-layout-effect": "1.1.4",
+ "@radix-ui/react-use-previous": "1.1.4",
+ "@radix-ui/react-visually-hidden": "1.2.11",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.7.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-separator": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.15.tgz",
+ "integrity": "sha512-jOLO4lssEzWpoDu7G+Ze4VjwMRUBt291pnZD0gmalREZipnTX3wadQo7Fy48GCTfe14/YRN6rw/rOJqrE85Wxw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slider": {
+ "version": "1.4.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.4.7.tgz",
+ "integrity": "sha512-mTSLf1GC/C0moWjTbvCM6Qn/gBjvlFt1azuWF2v7MN5C3Zq2U2J2lN3ZEYkpujuOU5Ro7A28wkviSxaKnG0BYg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.3",
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-collection": "1.1.15",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-layout-effect": "1.1.4",
+ "@radix-ui/react-use-previous": "1.1.4",
+ "@radix-ui/react-use-size": "1.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slot": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.3.tgz",
+ "integrity": "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.5"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-switch": {
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.3.7.tgz",
+ "integrity": "sha512-48tB/4dn2UVLBCYhTu9AuR63IHl73l/qLbLgxd86noTUor4/K4LFDAcYjK+isP5313qxaFpjPVogE7+Y0/V3Kw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-size": "1.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tabs": {
+ "version": "1.1.21",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.21.tgz",
+ "integrity": "sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-roving-focus": "1.1.19",
+ "@radix-ui/react-use-controllable-state": "1.2.6"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toast": {
+ "version": "1.2.23",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.23.tgz",
+ "integrity": "sha512-ofhyAsYaocRGOs/n0XWdUOSVzEAG6BfrMVM8z0c0kLEWY38w/0WuMFPTJP/HVaZPYkMvHZoKIIhNcjbTCBILPg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-collection": "1.1.15",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-dismissable-layer": "1.1.19",
+ "@radix-ui/react-portal": "1.1.17",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-callback-ref": "1.1.4",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-layout-effect": "1.1.4",
+ "@radix-ui/react-visually-hidden": "1.2.11"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toggle": {
+ "version": "1.1.18",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.18.tgz",
+ "integrity": "sha512-7lonPlKfSacd20GlOBx2ltuVKz9oqWYZz+oMQyOltw6t1y2nyftj2ZmwwUHYn49kqfDWcp8dNZm5NgV+5Z+mug==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-use-controllable-state": "1.2.6"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toggle-group": {
+ "version": "1.1.19",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.19.tgz",
+ "integrity": "sha512-OtnwuSVjd1Ofi+AdnvhsjQdyuhCDwYs1w9RyB5BN/OavXOVQo42SYqQjwUnbPnaiPFBpQ9aX70dWeee+v2oBLA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-roving-focus": "1.1.19",
+ "@radix-ui/react-toggle": "1.1.18",
+ "@radix-ui/react-use-controllable-state": "1.2.6"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toolbar": {
+ "version": "1.1.19",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.19.tgz",
+ "integrity": "sha512-Ph0IvtYw4VB12ZnZg+YtrGs8yJQsnizwo/zu0R4Y/nWugtJzA7Pg1eWeuDR9+LSqn+xjamss+UOSOJJJ4gx8jw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-roving-focus": "1.1.19",
+ "@radix-ui/react-separator": "1.1.15",
+ "@radix-ui/react-toggle-group": "1.1.19"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tooltip": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.16.tgz",
+ "integrity": "sha512-6EamKFRRnlpdadndbZ6LMwycfwkwPte1B42hs6QA0gYhjaOKqW4PZ4pjaW9UrlDX5eVt/OjncE7BFTPL5nmZhg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-dismissable-layer": "1.1.19",
+ "@radix-ui/react-id": "1.1.4",
+ "@radix-ui/react-popper": "1.3.7",
+ "@radix-ui/react-portal": "1.1.17",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-slot": "1.3.3",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-layout-effect": "1.1.4",
+ "@radix-ui/react-visually-hidden": "1.2.11"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-callback-ref": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.4.tgz",
+ "integrity": "sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.6.tgz",
+ "integrity": "sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-use-effect-event": "0.0.5",
+ "@radix-ui/react-use-layout-effect": "1.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-effect-event": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.5.tgz",
+ "integrity": "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-escape-keydown": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.5.tgz",
+ "integrity": "sha512-ge3ipobwSXTj4JyVtswQ7qZj0ZHdtbGuOno/LrgAAeSxtsJ6Vs4Gz5IkPH2bmqpjcLUFoqGhA/mueuIf63UXlA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-callback-ref": "1.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-is-hydrated": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.3.tgz",
+ "integrity": "sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.4.tgz",
+ "integrity": "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-previous": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.4.tgz",
+ "integrity": "sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-rect": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.4.tgz",
+ "integrity": "sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/rect": "1.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-size": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.4.tgz",
+ "integrity": "sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-visually-hidden": {
+ "version": "1.2.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.11.tgz",
+ "integrity": "sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/rect": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.3.tgz",
+ "integrity": "sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==",
+ "license": "MIT"
+ },
+ "node_modules/@rive-app/react-webgl2": {
+ "version": "4.32.1",
+ "resolved": "https://registry.npmjs.org/@rive-app/react-webgl2/-/react-webgl2-4.32.1.tgz",
+ "integrity": "sha512-hFEC74lGTfp0p2v/Hpg04QiHadtvhWlw7uYJWGvgpL//xbiHy3ReRDJsC1HIrcOXvelP6VeZwfM+EodDvLAdSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@rive-app/webgl2": "2.40.1"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0"
+ }
+ },
+ "node_modules/@rive-app/webgl2": {
+ "version": "2.40.1",
+ "resolved": "https://registry.npmjs.org/@rive-app/webgl2/-/webgl2-2.40.1.tgz",
+ "integrity": "sha512-O+18wvdSYs+7pqXuFYULv4L4Bg5xWsCNkQZTp3s69mOO13USfTMbuE4BGysH3QsRts7UYWCaSMsgCKteEuVphQ==",
+ "license": "MIT"
+ },
+ "node_modules/@shikijs/vscode-textmate": {
+ "version": "10.0.2",
+ "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz",
+ "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==",
+ "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/@streamdown/cjk": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@streamdown/cjk/-/cjk-1.0.3.tgz",
+ "integrity": "sha512-WRg8HR/gHbBoTgsMd91OKFUClIoDcEFVofJvluvEAyjx3KpU0aGgD9tGDqHkHj14ShoMSkX0IYetWGegTcwIJw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "remark-cjk-friendly": "^2.0.1",
+ "remark-cjk-friendly-gfm-strikethrough": "^2.0.1",
+ "unist-util-visit": "^5.0.0"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@streamdown/code": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@streamdown/code/-/code-1.1.1.tgz",
+ "integrity": "sha512-i7HTNuDgZWb+VdrNVOam9gQhIc5MSSDXKWXgbUrn/4vSRaSMM+Rtl10MQj4wLWPNpF7p80waJsAqFP8HZfb0Jg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "shiki": "^3.19.0"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@streamdown/code/node_modules/@shikijs/core": {
+ "version": "3.23.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.23.0.tgz",
+ "integrity": "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "3.23.0",
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "@types/hast": "^3.0.4",
+ "hast-util-to-html": "^9.0.5"
+ }
+ },
+ "node_modules/@streamdown/code/node_modules/@shikijs/engine-javascript": {
+ "version": "3.23.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.23.0.tgz",
+ "integrity": "sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "3.23.0",
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "oniguruma-to-es": "^4.3.4"
+ }
+ },
+ "node_modules/@streamdown/code/node_modules/@shikijs/engine-oniguruma": {
+ "version": "3.23.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz",
+ "integrity": "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "3.23.0",
+ "@shikijs/vscode-textmate": "^10.0.2"
+ }
+ },
+ "node_modules/@streamdown/code/node_modules/@shikijs/langs": {
+ "version": "3.23.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.23.0.tgz",
+ "integrity": "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "3.23.0"
+ }
+ },
+ "node_modules/@streamdown/code/node_modules/@shikijs/themes": {
+ "version": "3.23.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.23.0.tgz",
+ "integrity": "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "3.23.0"
+ }
+ },
+ "node_modules/@streamdown/code/node_modules/@shikijs/types": {
+ "version": "3.23.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.23.0.tgz",
+ "integrity": "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "@types/hast": "^3.0.4"
+ }
+ },
+ "node_modules/@streamdown/code/node_modules/shiki": {
+ "version": "3.23.0",
+ "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.23.0.tgz",
+ "integrity": "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==",
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/core": "3.23.0",
+ "@shikijs/engine-javascript": "3.23.0",
+ "@shikijs/engine-oniguruma": "3.23.0",
+ "@shikijs/langs": "3.23.0",
+ "@shikijs/themes": "3.23.0",
+ "@shikijs/types": "3.23.0",
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "@types/hast": "^3.0.4"
+ }
+ },
+ "node_modules/@streamdown/math": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@streamdown/math/-/math-1.0.2.tgz",
+ "integrity": "sha512-r8Ur9/lBuFnzZAFdEWrLUF2s/gRwRRRwruqltdZibyjbCBnuW7SJbFm26nXqvpJPW/gzpBUMrBVBzd88z05D5g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "katex": "^0.16.27",
+ "rehype-katex": "^7.0.1",
+ "remark-math": "^6.0.0"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@streamdown/mermaid": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@streamdown/mermaid/-/mermaid-1.0.2.tgz",
+ "integrity": "sha512-Fr/4sBWnAeSnxM3PcrV/+DiZe5oPMq9gOkUIAH7ZauJeuwrZ/DVzD4g0zlav6AH0axh2m/sOfrfLtY5aLT7niw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "mermaid": "^11.12.2"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.23",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz",
+ "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz",
+ "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.5",
+ "enhanced-resolve": "^5.24.1",
+ "jiti": "^2.7.0",
+ "lightningcss": "1.32.0",
+ "magic-string": "^0.30.21",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.3.3"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz",
+ "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.3.3",
+ "@tailwindcss/oxide-darwin-arm64": "4.3.3",
+ "@tailwindcss/oxide-darwin-x64": "4.3.3",
+ "@tailwindcss/oxide-freebsd-x64": "4.3.3",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.3.3",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.3.3",
+ "@tailwindcss/oxide-linux-x64-musl": "4.3.3",
+ "@tailwindcss/oxide-wasm32-wasi": "4.3.3",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.3.3"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz",
+ "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz",
+ "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz",
+ "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz",
+ "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz",
+ "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz",
+ "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz",
+ "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz",
+ "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==",
"cpu": [
"x64"
],
@@ -2911,415 +3223,136 @@
"linux"
],
"engines": {
- "node": "^22.20 || ^24.12 || >=25"
+ "node": ">= 20"
}
},
- "node_modules/@pinojs/redact": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz",
- "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==",
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz",
+ "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz",
+ "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.11.1",
+ "@emnapi/runtime": "^1.11.1",
+ "@emnapi/wasi-threads": "^1.2.2",
+ "@napi-rs/wasm-runtime": "^1.1.4",
+ "@tybys/wasm-util": "^0.10.2",
+ "tslib": "^2.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz",
+ "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz",
+ "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/postcss": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.3.tgz",
+ "integrity": "sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "@tailwindcss/node": "4.3.3",
+ "@tailwindcss/oxide": "4.3.3",
+ "postcss": "^8.5.16",
+ "tailwindcss": "4.3.3"
+ }
+ },
+ "node_modules/@tokenlens/core": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@tokenlens/core/-/core-1.3.0.tgz",
+ "integrity": "sha512-d8YNHNC+q10bVpi95fELJwJyPVf1HfvBEI18eFQxRSZTdByXrP+f/ZtlhSzkx0Jl0aEmYVeBA5tPeeYRioLViQ==",
"license": "MIT"
},
- "node_modules/@rolldown/pluginutils": {
- "version": "1.0.0-beta.27",
- "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
- "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.62.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.5.tgz",
- "integrity": "sha512-jfkGfTwhQpsiSckPF8r9bU3pn3vyd72NlWaO+TgEO6WPSDnUhXzrNYCHBMOYj0ACaUgjm6eERLF+XV9a6RstoA==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.62.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.5.tgz",
- "integrity": "sha512-oGVqyQlxnrz9/ty89oHpU857VUHEl5/Xu4R2lS+aivCTrNnSsbiENzTnNaBsjxH0CNWGPhzHArOLFwo+oKXveA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.62.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.5.tgz",
- "integrity": "sha512-bW7B8xMEq8n99Q3ieEcPRGuphurdZAaFzQc9Efyyw3FL6DZO6pMy9xhdN+kBoD7Sy05xNXSr4OyPPnpkYriS/A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.62.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.5.tgz",
- "integrity": "sha512-YSwBS86QeHOGlrxJ1PSOIZSkzRL/JmKeunhc+lV6M1a6En8QuVCD/T/qIA0J4Gd2Y86RIOBYrLcOUtqGh9+/1w==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.62.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.5.tgz",
- "integrity": "sha512-2fST8lILgl7cKbme/1KDdPCmbXbG+gqoV3bHp19L0ypX/3akYMBVdOunPleRCwonoLnXOZ/0F+Mt/v8POFmfcQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.62.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.5.tgz",
- "integrity": "sha512-cpIxQCP9J+EVad0a6LO1kY3ZGODlk80VlI+2I96B8xMcdHZ4pLVhfQ49JFpYqjPF91FFkQWftf57YlDcTiw9yQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.62.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.5.tgz",
- "integrity": "sha512-r9fGh3eFs3e/udWh5ZjXQtxiYK/xoFxQaYR/cELxac/Udkl5Th+IsFm0CX3Kl9hmUH/we7EoMpjJgeQNnE0+IA==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.62.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.5.tgz",
- "integrity": "sha512-xdvFdp7OM6KLJviJT2g/YuRSUjnZgGHk4RNgwIbN7X6cPugOucV60DdHXWzsBVCUdrGb6qSXnJQrrAKMmQuj3Q==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.62.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.5.tgz",
- "integrity": "sha512-rRqILAndyzHzP7T9NFQrq+4HFWNhqkqkKur7eiBpfLmz01PO0JKx5Vchu3YllE4YXI/Ftgq/szrDWg5GJ0mI8g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.62.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.5.tgz",
- "integrity": "sha512-Gf4X3qVMucayUvux6aXXPgXovocSFUC0rrffDuPI/S2nHhNMhjcZxsrAFYCOF350PRreW1XwzFj3CT/3bKsWCw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-loong64-gnu": {
- "version": "4.62.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.5.tgz",
- "integrity": "sha512-+s5qA0TNM0qm8PK/a5gt/1Hpx+NV08uSuCncvhziIlQzT6AEV2fnUQo7eBtFTFO0nA9scauvoR2HusfXmQnO4w==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-loong64-musl": {
- "version": "4.62.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.5.tgz",
- "integrity": "sha512-ybb6QvWwWJCbBWqERpc8K3pYVGIrXlG8MEQ8IIuJY6Y9KdHQxoFoNyfkAOtKn1VHu3KuLidXvwrvGR1mEjeWCw==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-ppc64-gnu": {
- "version": "4.62.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.5.tgz",
- "integrity": "sha512-nZb1DtnOyhCmYvsC8A2CwOkopVg+IS1+fPUa7rMOAXtNw5+lLCLLPqd6XAiNrGtoQKsbvIBOwsHnBH/3wnb4HQ==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-ppc64-musl": {
- "version": "4.62.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.5.tgz",
- "integrity": "sha512-yMbj63Sp89ryrXLWyz+sy+fYD2HpOnMCLGbe4Oa1smclFSUukdtD/BgdiHaAetJNb74URD8U4hM+qG5KVzMEkg==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.62.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.5.tgz",
- "integrity": "sha512-mhoan3OJw2kYV/e1jtIdmvUZgyBFeA6zGWsOswmR0Tg19TQbowZuR+JMLID6spbbBN7Zee2ejrgmy3+FxGrIdA==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.62.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.5.tgz",
- "integrity": "sha512-5ZTLmjWbb1VZdjuyhe83K/8QO0/h11midQCBP+X5OYn32ra7eOBoM0ZqtaY4nkgNsYgmdVhMYPoyVPTjUpHf3w==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.62.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.5.tgz",
- "integrity": "sha512-m53kG+br6PGxOTmgBEM2DHSDs9RVjsyEbUwjJPJGTFm1grWOG8EKJggDCTb60unD4Tjby8fi7/m9XfkEWasVWg==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.62.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.5.tgz",
- "integrity": "sha512-6RHPJR1g/uvdYU8uXBnfq3nlqyZCP82Fr6NHgfGoaIeSh0YEqnX/x6uA9MmJJbnSH7swqX4F+CkGdUF+6doiQA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.62.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.5.tgz",
- "integrity": "sha512-xs+OXQtEXgpXT0DmA5+U3qnRZHdCST/5HRQxS8wSPZTUZN/EMWeHuSIod32LQklTBZBV9DyfncKBQ8n5V3eFdw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-openbsd-x64": {
- "version": "4.62.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.5.tgz",
- "integrity": "sha512-e7hD+sl3s+mcLQDZ8pbudBVsdG6r5yN4w3LqG2TJ8sQHDpblWj5lrJs/3m01Cvlxbt4x13zu5thLjgypgtkYzw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ]
- },
- "node_modules/@rollup/rollup-openharmony-arm64": {
- "version": "4.62.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.5.tgz",
- "integrity": "sha512-GiyJaCf+WpMub/17aPcKk27QMl5W6f+KhdPTjlFOn5akH5Wa/DCM9Stdx5cDfmasyKB08MqpVQ1uJE2RkkpbXg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ]
- },
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.62.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.5.tgz",
- "integrity": "sha512-+OQ8U2DdoEfXl8T4Fb18AjmEwbXMerKDKCL8yCPAYhKCEEKoul7rkbeGCBFCbAlaGaa7pmtRTpkAJM2LE/i5FA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.62.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.5.tgz",
- "integrity": "sha512-KanvAZrPKbDBFwrgiU9yEVpQoox9QPV1WZOXX7HudJQY+eSlu82CtWxDU8WtuRRvtN5EGkLczkd6Y6DTcvm9wA==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-gnu": {
- "version": "4.62.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.5.tgz",
- "integrity": "sha512-1aC3UEWTtRl3RK3VpDJ/Tqk1XI4SLTmXIthAq6wRWo8XiSXJNd+VprJM4/1P4+i6HIaFEFlVi9sTTziniD2tOQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.62.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.5.tgz",
- "integrity": "sha512-/gDJaRs4gl0NPIwqCz+6PkpmhhjRAD2j6P4rSNHBzUkO3naEx2mIU0pRle1vUNRQ7mE/+8OOeXLTv/J56FKiQg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@types/babel__core": {
- "version": "7.20.5",
- "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
- "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
- "dev": true,
+ "node_modules/@tokenlens/fetch": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@tokenlens/fetch/-/fetch-1.3.0.tgz",
+ "integrity": "sha512-RONDRmETYly9xO8XMKblmrZjKSwCva4s5ebJwQNfNlChZoA5kplPoCgnWceHnn1J1iRjLVlrCNB43ichfmGBKQ==",
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.20.7",
- "@babel/types": "^7.20.7",
- "@types/babel__generator": "*",
- "@types/babel__template": "*",
- "@types/babel__traverse": "*"
+ "@tokenlens/core": "1.3.0"
}
},
- "node_modules/@types/babel__generator": {
- "version": "7.27.0",
- "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
- "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
- "dev": true,
+ "node_modules/@tokenlens/helpers": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@tokenlens/helpers/-/helpers-1.3.1.tgz",
+ "integrity": "sha512-t6yL8N6ES8337E6eVSeH4hCKnPdWkZRFpupy9w5E66Q9IeqQ9IO7XQ6gh12JKjvWiRHuyyJ8MBP5I549Cr41EQ==",
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.0.0"
+ "@tokenlens/core": "1.3.0",
+ "@tokenlens/fetch": "1.3.0"
}
},
- "node_modules/@types/babel__template": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
- "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
- "dev": true,
+ "node_modules/@tokenlens/models": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@tokenlens/models/-/models-1.3.0.tgz",
+ "integrity": "sha512-9mx7ZGeewW4ndXAiD7AT1bbCk4OpJeortbjHHyNkgap+pMPPn1chY6R5zqe1ggXIUzZ2l8VOAKfPqOvpcrisJw==",
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.1.0",
- "@babel/types": "^7.0.0"
- }
- },
- "node_modules/@types/babel__traverse": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
- "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.28.2"
+ "@tokenlens/core": "1.3.0"
}
},
"node_modules/@types/d3": {
@@ -3649,26 +3682,27 @@
"version": "15.7.15",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
- "license": "MIT"
+ "license": "MIT",
+ "optional": true
},
"node_modules/@types/react": {
- "version": "18.3.31",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz",
- "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==",
+ "version": "19.2.18",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz",
+ "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
+ "devOptional": true,
"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,
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz",
+ "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==",
+ "devOptional": true,
"license": "MIT",
"peerDependencies": {
- "@types/react": "^18.0.0"
+ "@types/react": "^19.2.0"
}
},
"node_modules/@types/trusted-types": {
@@ -3700,135 +3734,132 @@
"d3-transition": "^3.0.1"
}
},
- "node_modules/@vitejs/plugin-react": {
- "version": "4.7.0",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
- "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/core": "^7.28.0",
- "@babel/plugin-transform-react-jsx-self": "^7.27.1",
- "@babel/plugin-transform-react-jsx-source": "^7.27.1",
- "@rolldown/pluginutils": "1.0.0-beta.27",
- "@types/babel__core": "^7.20.5",
- "react-refresh": "^0.17.0"
- },
+ "node_modules/@vercel/oidc": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.2.0.tgz",
+ "integrity": "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==",
+ "license": "Apache-2.0",
"engines": {
- "node": "^14.18.0 || >=16.0.0"
- },
- "peerDependencies": {
- "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ "node": ">= 20"
}
},
- "node_modules/abstract-logging": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz",
- "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==",
- "license": "MIT"
+ "node_modules/@workflow/serde": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/@workflow/serde/-/serde-4.1.0.tgz",
+ "integrity": "sha512-pav4F2BoirECWR7Nf1TKt+2eETcBj7jj4cBefQ8VXQCA6NPkaKeLfj/zMgi+3zYV5ZIBT4GuUiphsj0/b9hPQQ==",
+ "license": "Apache-2.0"
},
- "node_modules/ajv": {
- "version": "8.20.0",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
- "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
+ "node_modules/@xyflow/react": {
+ "version": "12.11.3",
+ "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.11.3.tgz",
+ "integrity": "sha512-G3jogHz2GWUtIOkhavUGno2YzY9u6fILIJBttfsBendb0/HWB90JG+sOTAvlIMEwyvq9zgy9V9ZQSwyQjR5QzQ==",
"license": "MIT",
"dependencies": {
- "fast-deep-equal": "^3.1.3",
- "fast-uri": "^3.0.1",
- "json-schema-traverse": "^1.0.0",
- "require-from-string": "^2.0.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/ajv-formats": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
- "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
- "license": "MIT",
- "dependencies": {
- "ajv": "^8.0.0"
+ "@xyflow/system": "0.0.80",
+ "classcat": "^5.0.3",
+ "zustand": "^4.4.0"
},
"peerDependencies": {
- "ajv": "^8.0.0"
+ "@types/react": ">=17",
+ "@types/react-dom": ">=17",
+ "react": ">=17",
+ "react-dom": ">=17"
},
"peerDependenciesMeta": {
- "ajv": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
"optional": true
}
}
},
- "node_modules/ajv/node_modules/fast-uri": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
- "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
- "license": "BSD-3-Clause"
- },
- "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==",
- "dev": true,
- "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==",
- "dev": true,
+ "node_modules/@xyflow/system": {
+ "version": "0.0.80",
+ "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.80.tgz",
+ "integrity": "sha512-ywc3ZqG91brzWrH1WlwMdIX4goOfrpBy6AbLdVSaof/Xx9l138ijIKRExM6EkMro2F+OImGmSiA/WKcXvKVcfA==",
"license": "MIT",
"dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "@types/d3-drag": "^3.0.7",
+ "@types/d3-interpolate": "^3.0.4",
+ "@types/d3-selection": "^3.0.10",
+ "@types/d3-transition": "^3.0.8",
+ "@types/d3-zoom": "^3.0.8",
+ "d3-drag": "^3.0.0",
+ "d3-interpolate": "^3.0.1",
+ "d3-selection": "^3.0.0",
+ "d3-zoom": "^3.0.0"
}
},
- "node_modules/atomic-sleep": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
- "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
+ "node_modules/acorn": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz",
+ "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
"license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
"engines": {
- "node": ">=8.0.0"
+ "node": ">=0.4.0"
}
},
- "node_modules/avvio": {
- "version": "9.3.0",
- "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.3.0.tgz",
- "integrity": "sha512-g2tQ7LE7oOSqDfwEm3M+ZCMTJc7KiZCdJ4UwyZJb5ckTKyYu50OYmvv0mCFXPuYXoM4zkSt8zM9XQ9KCvxA74A==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
+ "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==",
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ai": {
+ "version": "7.0.76",
+ "resolved": "https://registry.npmjs.org/ai/-/ai-7.0.76.tgz",
+ "integrity": "sha512-Mytm/RFJzwJ0nzXiHYAE3dgNpFG/2K2Tks5alwm+PzAQeB6RJAJ97eGuZ8wssy66PcvtGWGIscS4tRi6dg0KLQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@ai-sdk/gateway": "4.0.61",
+ "@ai-sdk/provider": "4.0.7",
+ "@ai-sdk/provider-utils": "5.0.28"
+ },
+ "engines": {
+ "node": ">=22"
+ },
+ "peerDependencies": {
+ "zod": "^3.25.76 || ^4.1.8"
+ }
+ },
+ "node_modules/anser": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/anser/-/anser-2.3.5.tgz",
+ "integrity": "sha512-vcZjxvvVoxTeR5XBNJB38oTu/7eDCZlwdz32N1eNgpyPF7j/Z7Idf+CUwQOkKKpJ7RJyjxgLHCM7vdIK0iCNMQ==",
+ "license": "MIT"
+ },
+ "node_modules/ansi-to-react": {
+ "version": "6.2.6",
+ "resolved": "https://registry.npmjs.org/ansi-to-react/-/ansi-to-react-6.2.6.tgz",
+ "integrity": "sha512-Eqi0iaMK5OZ3jsVFxWvU2B74UZBnGuHlkflKMX6wTOeH+luy9KE2O0gUkc2PxhIP1R4IO0xohv62UMFInQOSeg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "anser": "^2.3.2",
+ "escape-carriage": "^1.3.1",
+ "linkify-it": "^3.0.3"
+ },
+ "peerDependencies": {
+ "react": "^16.3.2 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.3.2 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/aria-hidden": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
+ "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
"license": "MIT",
"dependencies": {
- "@fastify/error": "^4.0.0",
- "fastq": "^1.17.1"
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
}
},
"node_modules/bail": {
@@ -3841,20 +3872,10 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "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==",
- "license": "MIT",
- "engines": {
- "node": "18 || 20 || >=22"
- }
- },
"node_modules/baseline-browser-mapping": {
"version": "2.11.17",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.17.tgz",
"integrity": "sha512-KAUDn1OSS0fmPlGO+NOUMRcOQ/b/shUBH3OgkG73mPgdf+JD/BQ6fHboGxNOxnUmlwcq+lLq3dTkayRPuSfXwg==",
- "dev": true,
"license": "Apache-2.0",
"bin": {
"baseline-browser-mapping": "dist/cli.cjs"
@@ -3863,57 +3884,10 @@
"node": ">=6.0.0"
}
},
- "node_modules/brace-expansion": {
- "version": "5.0.9",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
- "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^4.0.2"
- },
- "engines": {
- "node": "20 || >=22"
- }
- },
- "node_modules/browserslist": {
- "version": "4.28.8",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz",
- "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "baseline-browser-mapping": "^2.11.12",
- "caniuse-lite": "^1.0.30001809",
- "electron-to-chromium": "^1.5.402",
- "node-releases": "^2.0.53",
- "update-browserslist-db": "^1.3.0"
- },
- "bin": {
- "browserslist": "cli.js"
- },
- "engines": {
- "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
- }
- },
"node_modules/caniuse-lite": {
"version": "1.0.30001809",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz",
"integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==",
- "dev": true,
"funding": [
{
"type": "opencollective",
@@ -3940,34 +3914,13 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "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/chalk/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/ce-la-react": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/ce-la-react/-/ce-la-react-0.3.2.tgz",
+ "integrity": "sha512-QJ6k4lOD/btI08xG8jBPxRCGXvCnusGGkTsiXk0u3NqUu/W+BXRnFD4PYjwtqh8AWmGa5LDbGk0fLQsqr0nSMA==",
+ "license": "BSD-3-Clause",
+ "peerDependencies": {
+ "react": ">=17.0.0"
}
},
"node_modules/character-entities": {
@@ -4010,41 +3963,55 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/cliui": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
- "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
- "dev": true,
- "license": "ISC",
+ "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": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.1",
- "wrap-ansi": "^7.0.0"
+ "clsx": "^2.1.1"
},
- "engines": {
- "node": ">=12"
+ "funding": {
+ "url": "https://polar.sh/cva"
}
},
- "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==",
- "dev": true,
+ "node_modules/classcat": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz",
+ "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==",
+ "license": "MIT"
+ },
+ "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/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/cmdk": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz",
+ "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==",
"license": "MIT",
"dependencies": {
- "color-name": "~1.1.4"
+ "@radix-ui/react-compose-refs": "^1.1.1",
+ "@radix-ui/react-dialog": "^1.1.6",
+ "@radix-ui/react-id": "^1.1.0",
+ "@radix-ui/react-primitive": "^2.0.2"
},
- "engines": {
- "node": ">=7.0.0"
+ "peerDependencies": {
+ "react": "^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^18 || ^19 || ^19.0.0-rc"
}
},
- "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==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/comma-separated-tokens": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
@@ -4064,63 +4031,6 @@
"node": ">= 12"
}
},
- "node_modules/concurrently": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.4.tgz",
- "integrity": "sha512-TZ0CEhyzvFjgtAvHTusDMgj7wNdihCh7LLLrzdUOXIhdlnL2JBBGA9eJxR24rtqgmdjh3OA3hrN1rCHj6HM8qA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "chalk": "4.1.2",
- "rxjs": "7.8.2",
- "shell-quote": "1.9.0",
- "supports-color": "8.1.1",
- "tree-kill": "1.2.2",
- "yargs": "17.7.2"
- },
- "bin": {
- "conc": "dist/bin/concurrently.js",
- "concurrently": "dist/bin/concurrently.js"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
- }
- },
- "node_modules/content-disposition": {
- "version": "0.5.4",
- "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
- "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "5.2.1"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/cookie": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
- "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
"node_modules/cose-base": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz",
@@ -4130,24 +4040,11 @@
"layout-base": "^1.0.0"
}
},
- "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/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "devOptional": true,
"license": "MIT"
},
"node_modules/cytoscape": {
@@ -4703,15 +4600,6 @@
"robust-predicates": "^3.0.2"
}
},
- "node_modules/depd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
@@ -4721,6 +4609,22 @@
"node": ">=6"
}
},
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/detect-node-es": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
+ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
+ "license": "MIT"
+ },
"node_modules/devlop": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
@@ -4743,39 +4647,46 @@
"@types/trusted-types": "^2.0.7"
}
},
- "node_modules/duplexify": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz",
- "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==",
- "license": "MIT",
- "dependencies": {
- "end-of-stream": "^1.4.1",
- "inherits": "^2.0.3",
- "readable-stream": "^3.1.1",
- "stream-shift": "^1.0.2"
- }
- },
- "node_modules/electron-to-chromium": {
- "version": "1.5.412",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.412.tgz",
- "integrity": "sha512-z4rMe3esBzlzovKHj4gxJnsCGZRK5l4baUvm+gCGJBPE+gsyUMKsuU9tnEUtI1dOebXz1ytAPGjvXhmQ7rIPwA==",
- "dev": true,
- "license": "ISC"
- },
- "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,
+ "node_modules/embla-carousel": {
+ "version": "8.6.0",
+ "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
+ "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
"license": "MIT"
},
- "node_modules/end-of-stream": {
- "version": "1.4.5",
- "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
- "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
+ "node_modules/embla-carousel-react": {
+ "version": "8.6.0",
+ "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.6.0.tgz",
+ "integrity": "sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==",
"license": "MIT",
"dependencies": {
- "once": "^1.4.0"
+ "embla-carousel": "8.6.0",
+ "embla-carousel-reactive-utils": "8.6.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/embla-carousel-reactive-utils": {
+ "version": "8.6.0",
+ "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.6.0.tgz",
+ "integrity": "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==",
+ "license": "MIT",
+ "peerDependencies": {
+ "embla-carousel": "8.6.0"
+ }
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.24.5",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz",
+ "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.3.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
}
},
"node_modules/entities": {
@@ -4844,22 +4755,24 @@
"@esbuild/win32-x64": "0.28.2"
}
},
- "node_modules/escalade": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
- "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
- "dev": true,
+ "node_modules/escape-carriage": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/escape-carriage/-/escape-carriage-1.3.1.tgz",
+ "integrity": "sha512-GwBr6yViW3ttx1kb7/Oh+gKQ1/TrhYwxKqVmg5gS+BK+Qe2KrOa/Vh7w3HPBvgGf0LfcDGoY9I6NHKoA5Hozhw==",
+ "license": "MIT"
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
"license": "MIT",
"engines": {
- "node": ">=6"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/escape-html": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
- "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
- "license": "MIT"
- },
"node_modules/estree-util-is-identifier-name": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz",
@@ -4870,73 +4783,21 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/eventsource-parser": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz",
+ "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"license": "MIT"
},
- "node_modules/fast-decode-uri-component": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz",
- "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==",
- "license": "MIT"
- },
- "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==",
- "license": "MIT"
- },
- "node_modules/fast-json-stringify": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-7.0.1.tgz",
- "integrity": "sha512-eRSayARSbbwlBjpP4vnTTIRD5QPcIrmihPxDeN1DtKnHPg66UuJLx+8hlK1kaFdjvzyQ/dzALoi4vwAQ+T+iZA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "@fastify/merge-json-schemas": "^0.2.0",
- "ajv": "^8.12.0",
- "ajv-formats": "^3.0.1",
- "fast-uri": "^4.0.0",
- "json-schema-ref-resolver": "^3.0.0",
- "rfdc": "^1.2.0"
- }
- },
- "node_modules/fast-querystring": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz",
- "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==",
- "license": "MIT",
- "dependencies": {
- "fast-decode-uri-component": "^1.0.1"
- }
- },
- "node_modules/fast-uri": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-4.1.2.tgz",
- "integrity": "sha512-TyGmBcbDTZXcb2cj5MV89DrF42DKvb3y5DDUNh95iO+IMeAzMkVSxK1PZRrRIpc9yg8U2GhGdbofNa0LS/a4Bw==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
- "license": "BSD-3-Clause"
- },
"node_modules/fastdom": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/fastdom/-/fastdom-1.0.12.tgz",
@@ -4946,124 +4807,29 @@
"strictdom": "^1.0.1"
}
},
- "node_modules/fastify": {
- "version": "5.12.1",
- "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.12.1.tgz",
- "integrity": "sha512-FWi+tQvwxR/PeRX7Z2mhfEF5ozJ3jn9asiiclzKXNSzJRHAYcU924aIOKAdHFJ+YIKieh3cqr1IwCOvTr41B3Q==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
+ "node_modules/framer-motion": {
+ "version": "13.1.1",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-13.1.1.tgz",
+ "integrity": "sha512-B/xn2TPS4f61cEBLFjiYlQFnBZUW1YVj/LM+C+N4OP8Rs95VLEI2ot/RlfBg111la/EiyECFaJJi/A3FWA8MUA==",
"license": "MIT",
"dependencies": {
- "@fastify/ajv-compiler": "^4.0.5",
- "@fastify/error": "^4.0.0",
- "@fastify/fast-json-stringify-compiler": "^5.0.0",
- "@fastify/proxy-addr": "^5.0.0",
- "abstract-logging": "^2.0.1",
- "avvio": "^9.0.0",
- "fast-json-stringify": "^7.0.0",
- "find-my-way": "^9.6.0",
- "light-my-request": "^6.0.0",
- "pino": "^9.14.0 || ^10.1.0",
- "process-warning": "^5.1.0",
- "rfdc": "^1.3.1",
- "secure-json-parse": "^4.0.0",
- "semver": "^7.6.0",
- "toad-cache": "^3.7.0"
- }
- },
- "node_modules/fastify-plugin": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-5.1.0.tgz",
- "integrity": "sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
- "license": "MIT"
- },
- "node_modules/fastify/node_modules/semver": {
- "version": "7.8.5",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
- "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/fastq": {
- "version": "1.20.1",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
- "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
- "license": "ISC",
- "dependencies": {
- "reusify": "^1.0.4"
- }
- },
- "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"
+ "motion-dom": "^13.1.1",
+ "motion-utils": "^13.0.0",
+ "tslib": "^2.4.0"
},
"peerDependencies": {
- "picomatch": "^3 || ^4"
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
- "picomatch": {
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
"optional": true
}
}
},
- "node_modules/find-my-way": {
- "version": "9.9.0",
- "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.9.0.tgz",
- "integrity": "sha512-sJsgZ1sQH2UDuowPuMKg8az7Qc8F0jnj+SKkFWU/+T0xcFlgV5skgXOGUqmQzOdmW6ALA7AhJINWx3qFBkbLHA==",
- "license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.3",
- "fast-querystring": "^1.0.0",
- "safe-regex2": "^5.0.0"
- },
- "engines": {
- "node": ">=20"
- }
- },
- "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/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -5079,66 +4845,40 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
- "node_modules/gensync": {
- "version": "1.0.0-beta.2",
- "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
- "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
- "dev": true,
+ "node_modules/get-east-asian-width": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
+ "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
"license": "MIT",
"engines": {
- "node": ">=6.9.0"
- }
- },
- "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==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": "6.* || 8.* || >= 10.*"
- }
- },
- "node_modules/glob": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz",
- "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==",
- "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",
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "foreground-child": "^3.3.1",
- "jackspeak": "^4.1.1",
- "minimatch": "^10.1.1",
- "minipass": "^7.1.2",
- "package-json-from-dist": "^1.0.0",
- "path-scurry": "^2.0.0"
- },
- "bin": {
- "glob": "dist/esm/bin.mjs"
- },
- "engines": {
- "node": "20 || >=22"
+ "node": ">=18"
},
"funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/get-nonce": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
+ "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/hachure-fill": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz",
"integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==",
"license": "MIT"
},
- "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/hast-util-from-dom": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz",
@@ -5234,6 +4974,69 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/hast-util-raw": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz",
+ "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "@ungap/structured-clone": "^1.0.0",
+ "hast-util-from-parse5": "^8.0.0",
+ "hast-util-to-parse5": "^8.0.0",
+ "html-void-elements": "^3.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "parse5": "^7.0.0",
+ "unist-util-position": "^5.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0",
+ "web-namespaces": "^2.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-sanitize": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-5.0.2.tgz",
+ "integrity": "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@ungap/structured-clone": "^1.0.0",
+ "unist-util-position": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-to-html": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
+ "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "ccount": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "hast-util-whitespace": "^3.0.0",
+ "html-void-elements": "^3.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "property-information": "^7.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "stringify-entities": "^4.0.0",
+ "zwitch": "^2.0.4"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/hast-util-to-jsx-runtime": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
@@ -5261,6 +5064,25 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/hast-util-to-parse5": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz",
+ "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "devlop": "^1.0.0",
+ "property-information": "^7.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "web-namespaces": "^2.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/hast-util-to-text": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz",
@@ -5317,24 +5139,14 @@
"url": "https://opencollective.com/unified"
}
},
- "node_modules/http-errors": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
- "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "node_modules/html-void-elements": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz",
+ "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==",
"license": "MIT",
- "dependencies": {
- "depd": "~2.0.0",
- "inherits": "~2.0.4",
- "setprototypeof": "~1.2.0",
- "statuses": "~2.0.2",
- "toidentifier": "~1.0.1"
- },
- "engines": {
- "node": ">= 0.8"
- },
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/iconv-lite": {
@@ -5359,12 +5171,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "license": "ISC"
- },
"node_modules/inline-style-parser": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz",
@@ -5380,15 +5186,6 @@
"node": ">=12"
}
},
- "node_modules/ipaddr.js": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.5.0.tgz",
- "integrity": "sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w==",
- "license": "MIT",
- "engines": {
- "node": ">= 10"
- }
- },
"node_modules/is-alphabetical": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
@@ -5423,16 +5220,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "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==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/is-hexadecimal": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
@@ -5455,83 +5242,21 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "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/jackspeak": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz",
- "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==",
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "@isaacs/cliui": "^9.0.0"
- },
- "engines": {
- "node": "20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "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/jsesc": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
- "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "node_modules/jiti": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
+ "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
"dev": true,
"license": "MIT",
"bin": {
- "jsesc": "bin/jsesc"
- },
- "engines": {
- "node": ">=6"
+ "jiti": "lib/jiti-cli.mjs"
}
},
- "node_modules/json-schema-ref-resolver": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.0.tgz",
- "integrity": "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "dequal": "^2.0.3"
- }
- },
- "node_modules/json-schema-traverse": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "license": "MIT"
- },
- "node_modules/json5": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
- "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "json5": "lib/cli.js"
- },
- "engines": {
- "node": ">=6"
- }
+ "node_modules/json-schema": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
+ "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
+ "license": "(AFL-2.1 OR BSD-3-Clause)"
},
"node_modules/katex": {
"version": "0.16.47",
@@ -5560,42 +5285,275 @@
"integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==",
"license": "MIT"
},
- "node_modules/light-my-request": {
- "version": "6.6.0",
- "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz",
- "integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
- "license": "BSD-3-Clause",
+ "node_modules/lightningcss": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+ "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "dev": true,
+ "license": "MPL-2.0",
"dependencies": {
- "cookie": "^1.0.1",
- "process-warning": "^4.0.0",
- "set-cookie-parser": "^2.6.0"
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.32.0",
+ "lightningcss-darwin-arm64": "1.32.0",
+ "lightningcss-darwin-x64": "1.32.0",
+ "lightningcss-freebsd-x64": "1.32.0",
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
+ "lightningcss-linux-arm64-gnu": "1.32.0",
+ "lightningcss-linux-arm64-musl": "1.32.0",
+ "lightningcss-linux-x64-gnu": "1.32.0",
+ "lightningcss-linux-x64-musl": "1.32.0",
+ "lightningcss-win32-arm64-msvc": "1.32.0",
+ "lightningcss-win32-x64-msvc": "1.32.0"
}
},
- "node_modules/light-my-request/node_modules/process-warning": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz",
- "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+ "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "cpu": [
+ "arm64"
],
- "license": "MIT"
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+ "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+ "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+ "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+ "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+ "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+ "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+ "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+ "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+ "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+ "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/linkify-it": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz",
+ "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==",
+ "license": "MIT",
+ "dependencies": {
+ "uc.micro": "^1.0.1"
+ }
},
"node_modules/lodash-es": {
"version": "4.18.1",
@@ -5613,26 +5571,33 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "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/lucide-react": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.33.0.tgz",
+ "integrity": "sha512-MTRwMy0ZlL8Ur/vOAiJ9XGHE+kFPC7brq6MxAm0GiGXEBj0qy0jA/pG4N675oSzciO/UCdX8T+5yUQdmDeTLxg==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
- "node_modules/lru-cache": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
- "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"dev": true,
- "license": "ISC",
+ "license": "MIT",
"dependencies": {
- "yallist": "^3.0.2"
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/markdown-table": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
+ "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/marked": {
@@ -5647,6 +5612,22 @@
"node": ">= 20"
}
},
+ "node_modules/mdast-util-find-and-replace": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz",
+ "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "escape-string-regexp": "^5.0.0",
+ "unist-util-is": "^6.0.0",
+ "unist-util-visit-parents": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/mdast-util-from-markdown": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz",
@@ -5671,6 +5652,107 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/mdast-util-gfm": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz",
+ "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==",
+ "license": "MIT",
+ "dependencies": {
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-gfm-autolink-literal": "^2.0.0",
+ "mdast-util-gfm-footnote": "^2.0.0",
+ "mdast-util-gfm-strikethrough": "^2.0.0",
+ "mdast-util-gfm-table": "^2.0.0",
+ "mdast-util-gfm-task-list-item": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-autolink-literal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz",
+ "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "ccount": "^2.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-find-and-replace": "^3.0.0",
+ "micromark-util-character": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-footnote": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz",
+ "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.1.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-strikethrough": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz",
+ "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-table": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz",
+ "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "markdown-table": "^3.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-task-list-item": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz",
+ "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/mdast-util-math": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz",
@@ -5806,6 +5888,51 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/mdast-util-to-markdown-cjk-friendly": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-markdown-cjk-friendly/-/mdast-util-to-markdown-cjk-friendly-1.0.0.tgz",
+ "integrity": "sha512-BoaAm8mlJ+LAYz0Qs532Y3ciTuQYgBUPZcSFbvC/ZKmEMAKgulw84YvQK1gI34t/vL2euSfuaWlqczkTBgamkw==",
+ "license": "MIT",
+ "dependencies": {
+ "mdast-util-to-markdown": "^2.1.2",
+ "micromark-extension-cjk-friendly-util": "3.0.1",
+ "micromark-util-symbol": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/mdast": "*"
+ },
+ "peerDependenciesMeta": {
+ "@types/mdast": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/mdast-util-to-markdown-cjk-friendly-gfm-strikethrough": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-markdown-cjk-friendly-gfm-strikethrough/-/mdast-util-to-markdown-cjk-friendly-gfm-strikethrough-1.0.0.tgz",
+ "integrity": "sha512-1ePVfB4P/vz3xSsm6H3D32r6VYGErxclnuLLFK02/2ReF+UdEKm7caulK6Vm0LBIp5gPRtB2Z1OYDznCkX3k2w==",
+ "license": "MIT",
+ "dependencies": {
+ "mdast-util-gfm-strikethrough": "^2.0.0",
+ "mdast-util-to-markdown": "^2.1.2",
+ "micromark-extension-cjk-friendly-util": "3.0.1",
+ "micromark-util-symbol": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/mdast": "*"
+ },
+ "peerDependenciesMeta": {
+ "@types/mdast": {
+ "optional": true
+ }
+ }
+ },
"node_modules/mdast-util-to-string": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
@@ -5819,6 +5946,15 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/media-chrome": {
+ "version": "4.19.2",
+ "resolved": "https://registry.npmjs.org/media-chrome/-/media-chrome-4.19.2.tgz",
+ "integrity": "sha512-4ai1ITN8wBhwugQcRgqe3tN0z6OSKGOXqHLNrS04MgKFfsLqu6Dm8MPq02pI9Y9ZKoXtFjIl85jOryIW9es3BA==",
+ "license": "MIT",
+ "dependencies": {
+ "ce-la-react": "^0.3.2"
+ }
+ },
"node_modules/mermaid": {
"version": "11.17.0",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.17.0.tgz",
@@ -5918,6 +6054,198 @@
"micromark-util-types": "^2.0.0"
}
},
+ "node_modules/micromark-extension-cjk-friendly": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-extension-cjk-friendly/-/micromark-extension-cjk-friendly-2.0.1.tgz",
+ "integrity": "sha512-OkzoYVTL1ChbvQ8Cc1ayTIz7paFQz8iS9oIYmewncweUSwmWR+hkJF9spJ1lxB90XldJl26A1F4IkPOKS3bDXw==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.1.0",
+ "micromark-extension-cjk-friendly-util": "3.0.1",
+ "micromark-util-chunked": "^2.0.1",
+ "micromark-util-resolve-all": "^2.0.1",
+ "micromark-util-symbol": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "micromark": "^4.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "peerDependenciesMeta": {
+ "micromark-util-types": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/micromark-extension-cjk-friendly-gfm-strikethrough": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-extension-cjk-friendly-gfm-strikethrough/-/micromark-extension-cjk-friendly-gfm-strikethrough-2.0.1.tgz",
+ "integrity": "sha512-wVC0zwjJNqQeX+bb07YTPu/CvSAyCTafyYb7sMhX1r62/Lw5M/df3JyYaANyp8g15c1ypJRFSsookTqA1IDsUg==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.1.0",
+ "get-east-asian-width": "^1.4.0",
+ "micromark-extension-cjk-friendly-util": "3.0.1",
+ "micromark-util-character": "^2.1.1",
+ "micromark-util-chunked": "^2.0.1",
+ "micromark-util-resolve-all": "^2.0.1",
+ "micromark-util-symbol": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "micromark": "^4.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "peerDependenciesMeta": {
+ "micromark-util-types": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/micromark-extension-cjk-friendly-util": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-extension-cjk-friendly-util/-/micromark-extension-cjk-friendly-util-3.0.1.tgz",
+ "integrity": "sha512-GcbXqTTHOsiZHyF753oIddP/J2eH8j9zpyQPhkof6B2JNxfEJabnQqxbCgzJNuNes0Y2jTNJ3LiYPSXr6eJA8w==",
+ "license": "MIT",
+ "dependencies": {
+ "get-east-asian-width": "^1.4.0",
+ "micromark-util-character": "^2.1.1",
+ "micromark-util-symbol": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "micromark-util-types": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/micromark-extension-gfm": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz",
+ "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==",
+ "license": "MIT",
+ "dependencies": {
+ "micromark-extension-gfm-autolink-literal": "^2.0.0",
+ "micromark-extension-gfm-footnote": "^2.0.0",
+ "micromark-extension-gfm-strikethrough": "^2.0.0",
+ "micromark-extension-gfm-table": "^2.0.0",
+ "micromark-extension-gfm-tagfilter": "^2.0.0",
+ "micromark-extension-gfm-task-list-item": "^2.0.0",
+ "micromark-util-combine-extensions": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-autolink-literal": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz",
+ "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==",
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-footnote": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz",
+ "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-core-commonmark": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-strikethrough": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz",
+ "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-table": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz",
+ "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-tagfilter": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz",
+ "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==",
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-task-list-item": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz",
+ "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/micromark-extension-math": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz",
@@ -6310,42 +6638,43 @@
],
"license": "MIT"
},
- "node_modules/mime": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
- "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
+ "node_modules/motion": {
+ "version": "13.1.1",
+ "resolved": "https://registry.npmjs.org/motion/-/motion-13.1.1.tgz",
+ "integrity": "sha512-WNZoK6xiF+kkTqkZ5K7FDDh6A8BG4i5Hc7KXtW8gtTxkpJFds+hIOrDaQGKjQj/AE/i4hJqAaUHEqp/Qo02y6Q==",
"license": "MIT",
- "bin": {
- "mime": "cli.js"
- },
- "engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/minimatch": {
- "version": "10.2.6",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz",
- "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==",
- "license": "BlueOak-1.0.0",
"dependencies": {
- "brace-expansion": "^5.0.8"
+ "framer-motion": "^13.1.1",
+ "tslib": "^2.4.0"
},
- "engines": {
- "node": "18 || 20 || >=22"
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
},
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/minipass": {
- "version": "7.1.3",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
- "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": ">=16 || 14 >=14.17"
+ "node_modules/motion-dom": {
+ "version": "13.1.1",
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-13.1.1.tgz",
+ "integrity": "sha512-XSf8VYWSB6G/0IY3rWVbyLcxWXtAVHkN1PQE2agTaCv3u8RGvbwu56TyyR/MNzBqqNavEBTZzErcxI1TxBrjcA==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-utils": "^13.0.0"
}
},
+ "node_modules/motion-utils": {
+ "version": "13.0.0",
+ "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-13.0.0.tgz",
+ "integrity": "sha512-7DnN7TmbLcYXcG4RVadXIihWlyuM9afoUww8Y5Agg431kGKiuL2/OMyP4mJ5wLz+pvN3t5ySClLOaVXJ+wekRQ==",
+ "license": "MIT"
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -6353,10 +6682,90 @@
"license": "MIT"
},
"node_modules/nanoid": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-6.0.1.tgz",
+ "integrity": "sha512-3wVS3i51pE2pi1k5FFL/95BGfVS0kSsvDVuGXHOtxox/TywUmtgq+3qiTOTbs9J7KfHaXPiN171k/A6dBnaXFw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.js"
+ },
+ "engines": {
+ "node": "^22 || ^24 || >=26"
+ }
+ },
+ "node_modules/next": {
+ "version": "16.3.2",
+ "resolved": "https://registry.npmjs.org/next/-/next-16.3.2.tgz",
+ "integrity": "sha512-/ZCaubUy17Lld1SiPWxuPbCk2ihqAxF2QNQaPZeEaEb7t1I58qhsJN187D7AfpapHAqUPXH0f/thtdW9dWgWFg==",
+ "license": "MIT",
+ "dependencies": {
+ "@next/env": "16.3.2",
+ "@swc/helpers": "0.5.23",
+ "baseline-browser-mapping": "^2.9.19",
+ "caniuse-lite": "^1.0.30001579",
+ "postcss": "8.5.23",
+ "styled-jsx": "5.1.6"
+ },
+ "bin": {
+ "next": "dist/bin/next"
+ },
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "optionalDependencies": {
+ "@next/swc-darwin-arm64": "16.3.2",
+ "@next/swc-darwin-x64": "16.3.2",
+ "@next/swc-linux-arm64-gnu": "16.3.2",
+ "@next/swc-linux-arm64-musl": "16.3.2",
+ "@next/swc-linux-x64-gnu": "16.3.2",
+ "@next/swc-linux-x64-musl": "16.3.2",
+ "@next/swc-win32-arm64-msvc": "16.3.2",
+ "@next/swc-win32-x64-msvc": "16.3.2",
+ "sharp": "^0.35.3"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": "^1.1.0",
+ "@playwright/test": "^1.51.1",
+ "babel-plugin-react-compiler": "*",
+ "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
+ "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
+ "sass": "^1.3.0"
+ },
+ "peerDependenciesMeta": {
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@playwright/test": {
+ "optional": true
+ },
+ "babel-plugin-react-compiler": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/next-themes": {
+ "version": "0.4.6",
+ "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
+ "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/next/node_modules/nanoid": {
"version": "3.3.18",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
"integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -6371,39 +6780,50 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
- "node_modules/node-releases": {
- "version": "2.0.53",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz",
- "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==",
- "dev": true,
+ "node_modules/next/node_modules/postcss": {
+ "version": "8.5.23",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz",
+ "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==",
+ "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",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/on-exit-leak-free": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
- "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==",
- "license": "MIT",
- "engines": {
- "node": ">=14.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==",
- "license": "ISC",
"dependencies": {
- "wrappy": "1"
+ "nanoid": "^3.3.16",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
}
},
- "node_modules/package-json-from-dist": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
- "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
- "license": "BlueOak-1.0.0"
+ "node_modules/oniguruma-parser": {
+ "version": "0.12.2",
+ "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz",
+ "integrity": "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==",
+ "license": "MIT"
+ },
+ "node_modules/oniguruma-to-es": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.6.tgz",
+ "integrity": "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==",
+ "license": "MIT",
+ "dependencies": {
+ "oniguruma-parser": "^0.12.2",
+ "regex": "^6.1.0",
+ "regex-recursion": "^6.0.2"
+ }
},
"node_modules/package-manager-detector": {
"version": "1.8.0",
@@ -6454,97 +6874,21 @@
"integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==",
"license": "MIT"
},
- "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-scurry": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
- "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "lru-cache": "^11.0.0",
- "minipass": "^7.1.2"
- },
- "engines": {
- "node": "18 || 20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/path-scurry/node_modules/lru-cache": {
- "version": "11.5.2",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz",
- "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==",
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": "20 || >=22"
- }
- },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "dev": true,
"license": "ISC"
},
- "node_modules/picomatch": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
- "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
- "dev": true,
+ "node_modules/pkce-challenge": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz",
+ "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==",
"license": "MIT",
"engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
+ "node": ">=16.20.0"
}
},
- "node_modules/pino": {
- "version": "10.3.1",
- "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz",
- "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==",
- "license": "MIT",
- "dependencies": {
- "@pinojs/redact": "^0.4.0",
- "atomic-sleep": "^1.0.0",
- "on-exit-leak-free": "^2.1.0",
- "pino-abstract-transport": "^3.0.0",
- "pino-std-serializers": "^7.0.0",
- "process-warning": "^5.0.0",
- "quick-format-unescaped": "^4.0.3",
- "real-require": "^0.2.0",
- "safe-stable-stringify": "^2.3.1",
- "sonic-boom": "^4.0.1",
- "thread-stream": "^4.0.0"
- },
- "bin": {
- "pino": "bin.js"
- }
- },
- "node_modules/pino-abstract-transport": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz",
- "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==",
- "license": "MIT",
- "dependencies": {
- "split2": "^4.0.0"
- }
- },
- "node_modules/pino-std-serializers": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz",
- "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==",
- "license": "MIT"
- },
"node_modules/points-on-curve": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz",
@@ -6590,21 +6934,24 @@
"node": "^10 || ^12 || >=14"
}
},
- "node_modules/process-warning": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.1.0.tgz",
- "integrity": "sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw==",
+ "node_modules/postcss/node_modules/nanoid": {
+ "version": "3.3.18",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
+ "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
+ "dev": true,
"funding": [
{
"type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
+ "url": "https://github.com/sponsors/ai"
}
],
- "license": "MIT"
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
},
"node_modules/property-information": {
"version": "7.2.0",
@@ -6616,95 +6963,246 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/quick-format-unescaped": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
- "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==",
- "license": "MIT"
- },
- "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==",
+ "node_modules/radix-ui": {
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.6.7.tgz",
+ "integrity": "sha512-QBdhh1arIEUvPC0dQ5+nwWAxt7+N+oP/9jPwjJkGFoSk/sqxg32gJtSXGtFh8frAIcS6oC9cx2Q+7KYCQLOAeA==",
"license": "MIT",
"dependencies": {
- "loose-envify": "^1.1.0"
+ "@radix-ui/primitive": "1.1.7",
+ "@radix-ui/react-accessible-icon": "1.1.15",
+ "@radix-ui/react-accordion": "1.2.20",
+ "@radix-ui/react-alert-dialog": "1.1.23",
+ "@radix-ui/react-arrow": "1.1.15",
+ "@radix-ui/react-aspect-ratio": "1.1.15",
+ "@radix-ui/react-avatar": "1.2.6",
+ "@radix-ui/react-checkbox": "1.3.11",
+ "@radix-ui/react-collapsible": "1.1.20",
+ "@radix-ui/react-collection": "1.1.15",
+ "@radix-ui/react-compose-refs": "1.1.5",
+ "@radix-ui/react-context": "1.2.2",
+ "@radix-ui/react-context-menu": "2.3.7",
+ "@radix-ui/react-dialog": "1.1.23",
+ "@radix-ui/react-direction": "1.1.4",
+ "@radix-ui/react-dismissable-layer": "1.1.19",
+ "@radix-ui/react-dropdown-menu": "2.1.24",
+ "@radix-ui/react-focus-guards": "1.1.6",
+ "@radix-ui/react-focus-scope": "1.1.16",
+ "@radix-ui/react-form": "0.1.16",
+ "@radix-ui/react-hover-card": "1.1.23",
+ "@radix-ui/react-label": "2.1.15",
+ "@radix-ui/react-menu": "2.1.24",
+ "@radix-ui/react-menubar": "1.1.24",
+ "@radix-ui/react-navigation-menu": "1.2.22",
+ "@radix-ui/react-one-time-password-field": "0.1.16",
+ "@radix-ui/react-password-toggle-field": "0.1.11",
+ "@radix-ui/react-popover": "1.1.23",
+ "@radix-ui/react-popper": "1.3.7",
+ "@radix-ui/react-portal": "1.1.17",
+ "@radix-ui/react-presence": "1.1.10",
+ "@radix-ui/react-primitive": "2.1.10",
+ "@radix-ui/react-progress": "1.1.16",
+ "@radix-ui/react-radio-group": "1.4.7",
+ "@radix-ui/react-roving-focus": "1.1.19",
+ "@radix-ui/react-scroll-area": "1.2.18",
+ "@radix-ui/react-select": "2.3.7",
+ "@radix-ui/react-separator": "1.1.15",
+ "@radix-ui/react-slider": "1.4.7",
+ "@radix-ui/react-slot": "1.3.3",
+ "@radix-ui/react-switch": "1.3.7",
+ "@radix-ui/react-tabs": "1.1.21",
+ "@radix-ui/react-toast": "1.2.23",
+ "@radix-ui/react-toggle": "1.1.18",
+ "@radix-ui/react-toggle-group": "1.1.19",
+ "@radix-ui/react-toolbar": "1.1.19",
+ "@radix-ui/react-tooltip": "1.2.16",
+ "@radix-ui/react-use-callback-ref": "1.1.4",
+ "@radix-ui/react-use-controllable-state": "1.2.6",
+ "@radix-ui/react-use-effect-event": "0.0.5",
+ "@radix-ui/react-use-escape-keydown": "1.1.5",
+ "@radix-ui/react-use-is-hydrated": "0.1.3",
+ "@radix-ui/react-use-layout-effect": "1.1.4",
+ "@radix-ui/react-use-size": "1.1.4",
+ "@radix-ui/react-visually-hidden": "1.2.11"
},
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.8",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
+ "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
+ "license": "MIT",
"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==",
+ "version": "19.2.8",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
+ "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
"license": "MIT",
"dependencies": {
- "loose-envify": "^1.1.0",
- "scheduler": "^0.23.2"
+ "scheduler": "^0.27.0"
},
"peerDependencies": {
- "react": "^18.3.1"
+ "react": "^19.2.8"
}
},
- "node_modules/react-markdown": {
- "version": "9.1.0",
- "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.1.0.tgz",
- "integrity": "sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw==",
+ "node_modules/react-jsx-parser": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/react-jsx-parser/-/react-jsx-parser-2.4.1.tgz",
+ "integrity": "sha512-gE25DiHseuKmSbb5O1K1IkyQ/bwth3zl4z3EdW8XMhwrb2agUpuh8GvqHFvGwh/wdZjDDuadGrSR2lEgjcOvqQ==",
"license": "MIT",
"dependencies": {
- "@types/hast": "^3.0.0",
- "@types/mdast": "^4.0.0",
- "devlop": "^1.0.0",
- "hast-util-to-jsx-runtime": "^2.0.0",
- "html-url-attributes": "^3.0.0",
- "mdast-util-to-hast": "^13.0.0",
- "remark-parse": "^11.0.0",
- "remark-rehype": "^11.0.0",
- "unified": "^11.0.0",
- "unist-util-visit": "^5.0.0",
- "vfile": "^6.0.0"
+ "acorn": "^8.12.1",
+ "acorn-jsx": "^5.3.2"
},
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
+ "engines": {
+ "bun": "^1.1.27"
+ },
+ "optionalDependencies": {
+ "@types/react": "^18.3.12",
+ "@types/react-dom": "^18.3.1"
},
"peerDependencies": {
- "@types/react": ">=18",
- "react": ">=18"
+ "react": ">=18",
+ "react-dom": ">=18"
}
},
- "node_modules/react-refresh": {
- "version": "0.17.0",
- "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
- "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
- "dev": true,
+ "node_modules/react-jsx-parser/node_modules/@types/react": {
+ "version": "18.3.31",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz",
+ "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==",
"license": "MIT",
- "engines": {
- "node": ">=0.10.0"
+ "optional": true,
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.2.2"
}
},
- "node_modules/readable-stream": {
- "version": "3.6.2",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
- "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "node_modules/react-jsx-parser/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==",
+ "license": "MIT",
+ "optional": true,
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/react-remove-scroll": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz",
+ "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==",
"license": "MIT",
"dependencies": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
+ "react-remove-scroll-bar": "^2.3.7",
+ "react-style-singleton": "^2.2.3",
+ "tslib": "^2.1.0",
+ "use-callback-ref": "^1.3.3",
+ "use-sidecar": "^1.1.3"
},
"engines": {
- "node": ">= 6"
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
}
},
- "node_modules/real-require": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
- "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==",
+ "node_modules/react-remove-scroll-bar": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
+ "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
"license": "MIT",
+ "dependencies": {
+ "react-style-singleton": "^2.2.2",
+ "tslib": "^2.0.0"
+ },
"engines": {
- "node": ">= 12.13.0"
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-style-singleton": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
+ "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
+ "license": "MIT",
+ "dependencies": {
+ "get-nonce": "^1.0.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/regex": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz",
+ "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==",
+ "license": "MIT",
+ "dependencies": {
+ "regex-utilities": "^2.3.0"
+ }
+ },
+ "node_modules/regex-recursion": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz",
+ "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==",
+ "license": "MIT",
+ "dependencies": {
+ "regex-utilities": "^2.3.0"
+ }
+ },
+ "node_modules/regex-utilities": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz",
+ "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==",
+ "license": "MIT"
+ },
+ "node_modules/rehype-harden": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/rehype-harden/-/rehype-harden-1.1.8.tgz",
+ "integrity": "sha512-Qn7vR1xrf6fZCrkm9TDWi/AB4ylrHy+jqsNm1EHOAmbARYA6gsnVJBq/sdBh6kmT4NEZxH5vgIjrscefJAOXcw==",
+ "license": "MIT",
+ "dependencies": {
+ "unist-util-visit": "^5.0.0"
}
},
"node_modules/rehype-katex": {
@@ -6726,6 +7224,97 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/rehype-raw": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz",
+ "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "hast-util-raw": "^9.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/rehype-sanitize": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/rehype-sanitize/-/rehype-sanitize-6.0.0.tgz",
+ "integrity": "sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "hast-util-sanitize": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-cjk-friendly": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/remark-cjk-friendly/-/remark-cjk-friendly-2.3.1.tgz",
+ "integrity": "sha512-f+pKZRxCRwNEGFBKNRAZAqU91GIK1SAo3ZyFHWRUgC9zcxRR0BXKd6YwqgSsxtW0rNpUDtONj7H5nje2WL3fcA==",
+ "license": "MIT",
+ "dependencies": {
+ "mdast-util-to-markdown-cjk-friendly": "1.0.0",
+ "micromark-extension-cjk-friendly": "2.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/mdast": "^4.0.0",
+ "unified": "^11.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/mdast": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/remark-cjk-friendly-gfm-strikethrough": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/remark-cjk-friendly-gfm-strikethrough/-/remark-cjk-friendly-gfm-strikethrough-2.3.1.tgz",
+ "integrity": "sha512-JE3TGgouk/sy92SemNMEUhO5mNP4on04cmzOV3s3R5Dbk160ewmpM4tgPiinKKvoJ5UW2fTu7FOYsjVbusSA9w==",
+ "license": "MIT",
+ "dependencies": {
+ "mdast-util-to-markdown-cjk-friendly-gfm-strikethrough": "1.0.0",
+ "micromark-extension-cjk-friendly-gfm-strikethrough": "2.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/mdast": "^4.0.0",
+ "unified": "^11.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/mdast": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/remark-gfm": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
+ "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-gfm": "^3.0.0",
+ "micromark-extension-gfm": "^3.0.0",
+ "remark-parse": "^11.0.0",
+ "remark-stringify": "^11.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/remark-math": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz",
@@ -6775,49 +7364,26 @@
"url": "https://opencollective.com/unified"
}
},
- "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==",
- "dev": true,
+ "node_modules/remark-stringify": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz",
+ "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==",
"license": "MIT",
- "engines": {
- "node": ">=0.10.0"
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-to-markdown": "^2.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/require-from-string": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
- "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/ret": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz",
- "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==",
- "license": "MIT",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/reusify": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
- "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
- "license": "MIT",
- "engines": {
- "iojs": ">=1.0.0",
- "node": ">=0.10.0"
- }
- },
- "node_modules/rfdc": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
- "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
- "license": "MIT"
+ "node_modules/remend": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/remend/-/remend-1.3.0.tgz",
+ "integrity": "sha512-iIhggPkhW3hFImKtB10w0dz4EZbs28mV/dmbcYVonWEJ6UGHHpP+bFZnTh6GNWJONg5m+U56JrL+8IxZRdgWjw==",
+ "license": "Apache-2.0"
},
"node_modules/robust-predicates": {
"version": "3.0.3",
@@ -6825,52 +7391,6 @@
"integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==",
"license": "Unlicense"
},
- "node_modules/rollup": {
- "version": "4.62.5",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.5.tgz",
- "integrity": "sha512-/tqMfgP7GPA3PHhCmuiS4vIjrSVhHLgY++i+dhbG462euyAj7FpM4D9uq1X3BgjlqRdpcOrYhcQtfiQLNc8tqw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/estree": "1.0.9"
- },
- "bin": {
- "rollup": "dist/bin/rollup"
- },
- "engines": {
- "node": ">=18.0.0",
- "npm": ">=8.0.0"
- },
- "optionalDependencies": {
- "@napi-rs/lzma-linux-x64-gnu": "1.5.1",
- "@rollup/rollup-android-arm-eabi": "4.62.5",
- "@rollup/rollup-android-arm64": "4.62.5",
- "@rollup/rollup-darwin-arm64": "4.62.5",
- "@rollup/rollup-darwin-x64": "4.62.5",
- "@rollup/rollup-freebsd-arm64": "4.62.5",
- "@rollup/rollup-freebsd-x64": "4.62.5",
- "@rollup/rollup-linux-arm-gnueabihf": "4.62.5",
- "@rollup/rollup-linux-arm-musleabihf": "4.62.5",
- "@rollup/rollup-linux-arm64-gnu": "4.62.5",
- "@rollup/rollup-linux-arm64-musl": "4.62.5",
- "@rollup/rollup-linux-loong64-gnu": "4.62.5",
- "@rollup/rollup-linux-loong64-musl": "4.62.5",
- "@rollup/rollup-linux-ppc64-gnu": "4.62.5",
- "@rollup/rollup-linux-ppc64-musl": "4.62.5",
- "@rollup/rollup-linux-riscv64-gnu": "4.62.5",
- "@rollup/rollup-linux-riscv64-musl": "4.62.5",
- "@rollup/rollup-linux-s390x-gnu": "4.62.5",
- "@rollup/rollup-linux-x64-gnu": "4.62.5",
- "@rollup/rollup-linux-x64-musl": "4.62.5",
- "@rollup/rollup-openbsd-x64": "4.62.5",
- "@rollup/rollup-openharmony-arm64": "4.62.5",
- "@rollup/rollup-win32-arm64-msvc": "4.62.5",
- "@rollup/rollup-win32-ia32-msvc": "4.62.5",
- "@rollup/rollup-win32-x64-gnu": "4.62.5",
- "@rollup/rollup-win32-x64-msvc": "4.62.5",
- "fsevents": "~2.3.2"
- }
- },
"node_modules/roughjs": {
"version": "4.6.6",
"resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz",
@@ -6889,67 +7409,6 @@
"integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
"license": "BSD-3-Clause"
},
- "node_modules/rxjs": {
- "version": "7.8.2",
- "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
- "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.1.0"
- }
- },
- "node_modules/safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/safe-regex2": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.1.1.tgz",
- "integrity": "sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "ret": "~0.5.0"
- },
- "bin": {
- "safe-regex2": "bin/safe-regex2.js"
- }
- },
- "node_modules/safe-stable-stringify": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
- "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
- "license": "MIT",
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -6957,112 +7416,78 @@
"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/secure-json-parse": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz",
- "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
- "license": "BSD-3-Clause"
- },
- "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/set-cookie-parser": {
- "version": "2.7.2",
- "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
- "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
"license": "MIT"
},
- "node_modules/setprototypeof": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
- "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
- "license": "ISC"
- },
- "node_modules/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",
+ "node_modules/sharp": {
+ "version": "0.35.3",
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz",
+ "integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==",
+ "license": "Apache-2.0",
+ "optional": true,
"dependencies": {
- "shebang-regex": "^3.0.0"
+ "@img/colour": "^1.1.0",
+ "detect-libc": "^2.1.2",
+ "semver": "^7.8.5"
},
"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/shell-quote": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz",
- "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
+ "node": ">=20.9.0"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-darwin-arm64": "0.35.3",
+ "@img/sharp-darwin-x64": "0.35.3",
+ "@img/sharp-freebsd-wasm32": "0.35.3",
+ "@img/sharp-libvips-darwin-arm64": "1.3.2",
+ "@img/sharp-libvips-darwin-x64": "1.3.2",
+ "@img/sharp-libvips-linux-arm": "1.3.2",
+ "@img/sharp-libvips-linux-arm64": "1.3.2",
+ "@img/sharp-libvips-linux-ppc64": "1.3.2",
+ "@img/sharp-libvips-linux-riscv64": "1.3.2",
+ "@img/sharp-libvips-linux-s390x": "1.3.2",
+ "@img/sharp-libvips-linux-x64": "1.3.2",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.3.2",
+ "@img/sharp-libvips-linuxmusl-x64": "1.3.2",
+ "@img/sharp-linux-arm": "0.35.3",
+ "@img/sharp-linux-arm64": "0.35.3",
+ "@img/sharp-linux-ppc64": "0.35.3",
+ "@img/sharp-linux-riscv64": "0.35.3",
+ "@img/sharp-linux-s390x": "0.35.3",
+ "@img/sharp-linux-x64": "0.35.3",
+ "@img/sharp-linuxmusl-arm64": "0.35.3",
+ "@img/sharp-linuxmusl-x64": "0.35.3",
+ "@img/sharp-webcontainers-wasm32": "0.35.3",
+ "@img/sharp-win32-arm64": "0.35.3",
+ "@img/sharp-win32-ia32": "0.35.3",
+ "@img/sharp-win32-x64": "0.35.3"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
}
},
- "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==",
+ "node_modules/sharp/node_modules/semver": {
+ "version": "7.8.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
+ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
"license": "ISC",
- "engines": {
- "node": ">=14"
+ "optional": true,
+ "bin": {
+ "semver": "bin/semver.js"
},
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/sonic-boom": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz",
- "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==",
- "license": "MIT",
- "dependencies": {
- "atomic-sleep": "^1.0.0"
+ "engines": {
+ "node": ">=10"
}
},
"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==",
- "dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
@@ -7078,60 +7503,52 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "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/streamdown": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/streamdown/-/streamdown-2.5.0.tgz",
+ "integrity": "sha512-/tTnURfIOxZK/pqJAxsfCvETG/XCJHoWnk3jq9xLcuz6CSpnjjuxSRBTTL4PKGhxiZQf0lqPxGhImdpwcZ2XwA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "clsx": "^2.1.1",
+ "hast-util-to-jsx-runtime": "^2.3.6",
+ "html-url-attributes": "^3.0.1",
+ "marked": "^17.0.1",
+ "mermaid": "^11.12.2",
+ "rehype-harden": "^1.1.8",
+ "rehype-raw": "^7.0.0",
+ "rehype-sanitize": "^6.0.0",
+ "remark-gfm": "^4.0.1",
+ "remark-parse": "^11.0.0",
+ "remark-rehype": "^11.1.2",
+ "remend": "1.3.0",
+ "tailwind-merge": "^3.4.0",
+ "unified": "^11.0.5",
+ "unist-util-visit": "^5.0.0",
+ "unist-util-visit-parents": "^6.0.0"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
}
},
- "node_modules/statuses": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
- "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "node_modules/streamdown/node_modules/marked": {
+ "version": "17.0.6",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.6.tgz",
+ "integrity": "sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA==",
"license": "MIT",
+ "bin": {
+ "marked": "bin/marked.js"
+ },
"engines": {
- "node": ">= 0.8"
+ "node": ">= 20"
}
},
- "node_modules/stream-shift": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz",
- "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==",
- "license": "MIT"
- },
"node_modules/strictdom": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/strictdom/-/strictdom-1.0.1.tgz",
"integrity": "sha512-cEmp9QeXXRmjj/rVp9oyiqcvyocWab/HaoN4+bwFeZ7QzykJD6L3yD4v12K1x0tHpqRqVpJevN3gW7kyM39Bqg==",
"license": "MIT"
},
- "node_modules/string_decoder": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
- "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "~5.2.0"
- }
- },
- "node_modules/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/stringify-entities": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
@@ -7146,19 +7563,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "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==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/style-to-js": {
"version": "1.1.21",
"resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz",
@@ -7177,46 +7581,91 @@
"inline-style-parser": "0.2.7"
}
},
+ "node_modules/styled-jsx": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
+ "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
+ "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 || ^19.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "babel-plugin-macros": {
+ "optional": true
+ }
+ }
+ },
"node_modules/stylis": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz",
"integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==",
"license": "MIT"
},
- "node_modules/supports-color": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
- "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "node_modules/swr": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/swr/-/swr-2.5.1.tgz",
+ "integrity": "sha512-BRw55e8r0B7SpDN20CAzoQAHl7y1yP7/Zt7oqUjMv0vSt2u2Xnkm88Ws+VypbV9BXHQVuSuyVq7zMjO16wSExw==",
+ "license": "MIT",
+ "dependencies": {
+ "dequal": "^2.0.3",
+ "use-sync-external-store": "^1.6.0"
+ },
+ "peerDependencies": {
+ "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/tailwind-merge": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz",
+ "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz",
+ "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
+ "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
"engines": {
- "node": ">=10"
+ "node": ">=6"
},
"funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
}
},
- "node_modules/thread-stream": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.2.0.tgz",
- "integrity": "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==",
+ "node_modules/throttleit": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz",
+ "integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==",
"license": "MIT",
- "dependencies": {
- "real-require": "^1.0.0"
- },
"engines": {
- "node": ">=20"
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/thread-stream/node_modules/real-require": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/real-require/-/real-require-1.0.0.tgz",
- "integrity": "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==",
- "license": "MIT"
- },
"node_modules/tinyexec": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz",
@@ -7226,49 +7675,16 @@
"node": ">=18"
}
},
- "node_modules/tinyglobby": {
- "version": "0.2.17",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
- "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
- "dev": true,
+ "node_modules/tokenlens": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/tokenlens/-/tokenlens-1.3.1.tgz",
+ "integrity": "sha512-7oxmsS5PNCX3z+b+z07hL5vCzlgHKkCGrEQjQmWl5l+v5cUrtL7S1cuST4XThaL1XyjbTX8J5hfP0cjDJRkaLA==",
"license": "MIT",
"dependencies": {
- "fdir": "^6.5.0",
- "picomatch": "^4.0.4"
- },
- "engines": {
- "node": ">=12.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/SuperchupuDev"
- }
- },
- "node_modules/toad-cache": {
- "version": "3.7.4",
- "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.4.tgz",
- "integrity": "sha512-m1TdR/rvT7kgGJZhspNtXdsdYk0fddFpJJFlG5s+UkPFo6lkLoZ3YLOaovPYjq1R75NP5JfeTlSHaOsE09peCg==",
- "license": "MIT",
- "engines": {
- "node": ">=20"
- }
- },
- "node_modules/toidentifier": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
- "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
- "license": "MIT",
- "engines": {
- "node": ">=0.6"
- }
- },
- "node_modules/tree-kill": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
- "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "tree-kill": "cli.js"
+ "@tokenlens/core": "1.3.0",
+ "@tokenlens/fetch": "1.3.0",
+ "@tokenlens/helpers": "1.3.1",
+ "@tokenlens/models": "1.3.0"
}
},
"node_modules/trim-lines": {
@@ -7304,7 +7720,6 @@
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "dev": true,
"license": "0BSD"
},
"node_modules/tsx": {
@@ -7340,6 +7755,21 @@
"node": ">=14.17"
}
},
+ "node_modules/uc.micro": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
+ "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==",
+ "license": "MIT"
+ },
+ "node_modules/undici": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
+ "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.18.1"
+ }
+ },
"node_modules/undici-types": {
"version": "7.18.2",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
@@ -7462,42 +7892,72 @@
"url": "https://opencollective.com/unified"
}
},
- "node_modules/update-browserslist-db": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz",
- "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==",
- "dev": true,
+ "node_modules/use-callback-ref": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
+ "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sidecar": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
+ "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "detect-node-es": "^1.1.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-stick-to-bottom": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/use-stick-to-bottom/-/use-stick-to-bottom-1.1.6.tgz",
+ "integrity": "sha512-z3Up8jYQGTkUCsGBnwg6/wj70KgXoW5Kz1AAc1j8MtQuYMBo6ZsdhrIXoegxa7gaMMilgQYyTohTrt3p94jHog==",
"funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
{
"type": "github",
- "url": "https://github.com/sponsors/ai"
+ "url": "https://github.com/sponsors/samdenty"
}
],
"license": "MIT",
- "dependencies": {
- "escalade": "^3.2.0",
- "picocolors": "^1.1.1"
- },
- "bin": {
- "update-browserslist-db": "cli.js"
- },
"peerDependencies": {
- "browserslist": ">= 4.21.0"
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.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==",
- "license": "MIT"
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
},
"node_modules/uuid": {
"version": "14.0.2",
@@ -7554,565 +8014,6 @@
"url": "https://opencollective.com/unified"
}
},
- "node_modules/vite": {
- "version": "6.4.3",
- "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz",
- "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "esbuild": "^0.25.0",
- "fdir": "^6.4.4",
- "picomatch": "^4.0.2",
- "postcss": "^8.5.3",
- "rollup": "^4.34.9",
- "tinyglobby": "^0.2.13"
- },
- "bin": {
- "vite": "bin/vite.js"
- },
- "engines": {
- "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
- },
- "funding": {
- "url": "https://github.com/vitejs/vite?sponsor=1"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.3"
- },
- "peerDependencies": {
- "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
- "jiti": ">=1.21.0",
- "less": "*",
- "lightningcss": "^1.21.0",
- "sass": "*",
- "sass-embedded": "*",
- "stylus": "*",
- "sugarss": "*",
- "terser": "^5.16.0",
- "tsx": "^4.8.1",
- "yaml": "^2.4.2"
- },
- "peerDependenciesMeta": {
- "@types/node": {
- "optional": true
- },
- "jiti": {
- "optional": true
- },
- "less": {
- "optional": true
- },
- "lightningcss": {
- "optional": true
- },
- "sass": {
- "optional": true
- },
- "sass-embedded": {
- "optional": true
- },
- "stylus": {
- "optional": true
- },
- "sugarss": {
- "optional": true
- },
- "terser": {
- "optional": true
- },
- "tsx": {
- "optional": true
- },
- "yaml": {
- "optional": true
- }
- }
- },
- "node_modules/vite/node_modules/@esbuild/aix-ppc64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
- "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/android-arm": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
- "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/android-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
- "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/android-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
- "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/darwin-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
- "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/darwin-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
- "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
- "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/freebsd-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
- "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-arm": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
- "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
- "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-ia32": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
- "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-loong64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
- "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-mips64el": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
- "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-ppc64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
- "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-riscv64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
- "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-s390x": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
- "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
- "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/netbsd-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
- "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/netbsd-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
- "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/openbsd-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
- "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/openbsd-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
- "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/openharmony-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
- "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/sunos-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
- "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/win32-arm64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
- "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/win32-ia32": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
- "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/win32-x64": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
- "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/esbuild": {
- "version": "0.25.12",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
- "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=18"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.25.12",
- "@esbuild/android-arm": "0.25.12",
- "@esbuild/android-arm64": "0.25.12",
- "@esbuild/android-x64": "0.25.12",
- "@esbuild/darwin-arm64": "0.25.12",
- "@esbuild/darwin-x64": "0.25.12",
- "@esbuild/freebsd-arm64": "0.25.12",
- "@esbuild/freebsd-x64": "0.25.12",
- "@esbuild/linux-arm": "0.25.12",
- "@esbuild/linux-arm64": "0.25.12",
- "@esbuild/linux-ia32": "0.25.12",
- "@esbuild/linux-loong64": "0.25.12",
- "@esbuild/linux-mips64el": "0.25.12",
- "@esbuild/linux-ppc64": "0.25.12",
- "@esbuild/linux-riscv64": "0.25.12",
- "@esbuild/linux-s390x": "0.25.12",
- "@esbuild/linux-x64": "0.25.12",
- "@esbuild/netbsd-arm64": "0.25.12",
- "@esbuild/netbsd-x64": "0.25.12",
- "@esbuild/openbsd-arm64": "0.25.12",
- "@esbuild/openbsd-x64": "0.25.12",
- "@esbuild/openharmony-arm64": "0.25.12",
- "@esbuild/sunos-x64": "0.25.12",
- "@esbuild/win32-arm64": "0.25.12",
- "@esbuild/win32-ia32": "0.25.12",
- "@esbuild/win32-x64": "0.25.12"
- }
- },
"node_modules/web-namespaces": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
@@ -8123,112 +8024,43 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "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/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,
+ "node_modules/zod": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
+ "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
"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"
+ "url": "https://github.com/sponsors/colinhacks"
}
},
- "node_modules/wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "license": "ISC"
- },
- "node_modules/ws": {
- "version": "8.21.3",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz",
- "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==",
+ "node_modules/zustand": {
+ "version": "4.5.7",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
+ "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
"license": "MIT",
+ "dependencies": {
+ "use-sync-external-store": "^1.2.2"
+ },
"engines": {
- "node": ">=10.0.0"
+ "node": ">=12.7.0"
},
"peerDependencies": {
- "bufferutil": "^4.0.1",
- "utf-8-validate": ">=5.0.2"
+ "@types/react": ">=16.8",
+ "immer": ">=9.0.6",
+ "react": ">=16.8"
},
"peerDependenciesMeta": {
- "bufferutil": {
+ "@types/react": {
"optional": true
},
- "utf-8-validate": {
+ "immer": {
+ "optional": true
+ },
+ "react": {
"optional": true
}
}
},
- "node_modules/y18n": {
- "version": "5.0.8",
- "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
- "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/yallist": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
- "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/yargs": {
- "version": "17.7.2",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
- "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cliui": "^8.0.1",
- "escalade": "^3.1.1",
- "get-caller-file": "^2.0.5",
- "require-directory": "^2.1.1",
- "string-width": "^4.2.3",
- "y18n": "^5.0.5",
- "yargs-parser": "^21.1.1"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/yargs-parser": {
- "version": "21.1.1",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
- "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/zwitch": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
diff --git a/package.json b/package.json
index b942a08..de1f053 100644
--- a/package.json
+++ b/package.json
@@ -1,47 +1,58 @@
{
"name": "learning-harness",
- "version": "0.1.0",
- "description": "A standalone learning harness built on the pi agent SDK: teacher agent, session artifacts, and a derived knowledge graph of what the learner knows.",
+ "version": "0.2.0",
+ "description": "A standalone learning harness: a teacher agent (AI SDK) with quiz/ask interactions, session artifacts, and a derived knowledge graph of what the learner knows.",
"type": "module",
"private": true,
"scripts": {
- "teach": "tsx src/cli.ts teach",
- "doctor": "tsx src/cli.ts doctor",
- "doctor:web": "tsx src/cli.ts doctor --web",
- "test": "tsx --test tests/*.test.ts",
- "test:unit": "tsx --test tests/question-core.test.ts tests/bridge.test.ts",
- "e2e:live": "tsx scripts/e2e.ts",
- "serve": "tsx src/server/index.ts",
- "web": "vite",
- "dev": "concurrently -n server,web -c blue,green \"npm:serve\" \"npm:web\"",
- "build": "vite build",
- "start": "npm run build && npm run serve",
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
"ingest": "tsx src/cli.ts ingest",
"graph": "tsx src/cli.ts graph",
"what-next": "tsx src/cli.ts what-next",
- "typecheck": "tsc --noEmit && tsc --noEmit -p web/tsconfig.json"
+ "test": "tsx --test tests/*.test.ts tests/*.test.tsx",
+ "typecheck": "tsc --noEmit"
},
"dependencies": {
- "@earendil-works/pi-coding-agent": "0.84.2",
- "@fastify/static": "^8.0.0",
- "@fastify/websocket": "^11.0.0",
- "fastify": "^5.0.0",
- "katex": "^0.16.11",
- "mermaid": "^11.4.0",
- "react": "^18.3.1",
- "react-dom": "^18.3.1",
- "react-markdown": "^9.0.1",
- "rehype-katex": "^7.0.1",
- "remark-math": "^6.0.0"
+ "@ai-sdk/react": "^4.0.79",
+ "@openrouter/ai-sdk-provider": "^3.0.0",
+ "@radix-ui/react-use-controllable-state": "^1.2.6",
+ "@rive-app/react-webgl2": "^4.32.1",
+ "@streamdown/cjk": "^1.0.3",
+ "@streamdown/code": "^1.1.1",
+ "@streamdown/math": "^1.0.2",
+ "@streamdown/mermaid": "^1.0.2",
+ "@xyflow/react": "^12.11.3",
+ "ai": "^7.0.76",
+ "ansi-to-react": "^6.2.6",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "cmdk": "^1.1.1",
+ "embla-carousel-react": "^8.6.0",
+ "lucide-react": "^1.33.0",
+ "media-chrome": "^4.19.2",
+ "motion": "^13.1.1",
+ "nanoid": "^6.0.1",
+ "next": "^16.3.2",
+ "next-themes": "^0.4.6",
+ "radix-ui": "^1.6.7",
+ "react": "^19.2.8",
+ "react-dom": "^19.2.8",
+ "react-jsx-parser": "^2.4.1",
+ "streamdown": "^2.5.0",
+ "tailwind-merge": "^3.6.0",
+ "tokenlens": "^1.3.1",
+ "use-stick-to-bottom": "^1.1.6",
+ "zod": "^4.4.3"
},
"devDependencies": {
+ "@tailwindcss/postcss": "^4.3.3",
"@types/node": "^24.0.0",
- "@types/react": "^18.3.5",
- "@types/react-dom": "^18.3.0",
- "@vitejs/plugin-react": "^4.3.1",
- "concurrently": "^9.0.0",
+ "@types/react": "^19.2.18",
+ "@types/react-dom": "^19.2.4",
+ "tailwindcss": "^4.3.3",
"tsx": "^4.19.0",
- "typescript": "^5.6.0",
- "vite": "^6.0.0"
+ "typescript": "^5.6.0"
}
}
diff --git a/postcss.config.mjs b/postcss.config.mjs
new file mode 100644
index 0000000..017b34b
--- /dev/null
+++ b/postcss.config.mjs
@@ -0,0 +1,5 @@
+export default {
+ plugins: {
+ "@tailwindcss/postcss": {},
+ },
+};
diff --git a/scripts/e2e.ts b/scripts/e2e.ts
deleted file mode 100644
index cd024f7..0000000
--- a/scripts/e2e.ts
+++ /dev/null
@@ -1,163 +0,0 @@
-/**
- * e2e test — drives the web server like a browser would:
- * 1. creates a session (topic prompt)
- * 2. waits for the teacher to present a quiz (lh_question → pending)
- * 3. answers it over WS
- * 4. verifies the graded answer arrives as a user message and the agent
- * continues (settles again)
- * 5. exits and RESUMES the session mid-flow to prove resume works
- *
- * Run: npx tsx scripts/e2e.ts (server must be running)
- */
-
-const BASE = "http://127.0.0.1:8787";
-
-function connectWs(): Promise {
- return new Promise((resolve, reject) => {
- const ws = new WebSocket("ws://127.0.0.1:8787/ws");
- ws.onopen = () => resolve(ws);
- ws.onerror = (e) => reject(e);
- });
-}
-
-function makeCollector(ws: WebSocket) {
- const seen: any[] = [];
- ws.addEventListener("message", (ev) => {
- try {
- seen.push(JSON.parse(String(ev.data)));
- } catch {}
- });
- return seen;
-}
-
-function waitEvent(
- seen: any[],
- ws: WebSocket,
- pred: (msg: any) => boolean,
- timeoutMs = 120_000,
-): Promise {
- const already = seen.find(pred);
- if (already) return Promise.resolve(already);
- return new Promise((resolve, reject) => {
- const timer = setTimeout(() => reject(new Error("timeout waiting for event")), timeoutMs);
- const handler = (ev: MessageEvent) => {
- try {
- const msg = JSON.parse(String(ev.data));
- if (pred(msg)) {
- clearTimeout(timer);
- ws.removeEventListener("message", handler);
- resolve(msg);
- }
- } catch {
- // ignore non-JSON
- }
- };
- ws.addEventListener("message", handler);
- });
-}
-
-async function main() {
- console.log("── creating session");
- const res = await fetch(`${BASE}/api/sessions`, {
- method: "POST",
- headers: { "content-type": "application/json" },
- body: JSON.stringify({ topic: "Teach me why the sky is blue." }),
- });
- const { id } = (await res.json()) as { id: string };
- console.log(" session:", id);
-
- console.log("── connecting WS");
- const ws = await connectWs();
- const seen = makeCollector(ws);
-
- // Wait for a pending question (the teacher probes with ask_user_question or quiz).
- // Fallback: if the first turn settles without a question, ask for one.
- console.log("── waiting for a pending question…");
- let pending: any;
- try {
- pending = await waitEvent(seen, ws, (m) => m.type === "pending" && m.question !== null, 120_000);
- } catch {
- console.log(" no question yet — prompting for one");
- ws.send(JSON.stringify({ type: "prompt", text: "Quiz me on something first before teaching." }));
- pending = await waitEvent(seen, ws, (m) => m.type === "pending" && m.question !== null, 180_000);
- }
- const q = pending.question;
- console.log(` ${q.kind} [${q.mode}]: ${q.question}`);
- console.log(` options: ${q.options?.map((o: any) => o.label).join(" | ") ?? "(text)"}`);
-
- // Verify the correct answer is NOT in the payload.
- const raw = JSON.stringify(q);
- if (raw.includes("correct") || raw.includes("explanation")) {
- throw new Error("LEAK: pending question payload contains correct/explanation!");
- }
- console.log(" ✓ no answer leak in client payload");
-
- // Answer: pick the first option (or text mode).
- if (q.mode === "text") {
- ws.send(JSON.stringify({ type: "answer", questionId: q.id, text: "not sure — just teach me" }));
- } else {
- ws.send(JSON.stringify({ type: "answer", questionId: q.id, values: [q.options[0].value] }));
- }
- console.log("── answered; waiting for the graded answer to enter the session…");
-
- // Register BOTH waits before proceeding (avoids missing fast events).
- const settledAgain = waitEvent(seen, ws, (m) => m.type === "streaming" && m.value === false, 180_000);
- const answeredEntry = await waitEvent(seen, ws, (m) => m.type === "entry" && m.entry?.type === "questionAnswered");
- const ad = answeredEntry.entry.data;
- console.log(` answered: correct=${ad.correct} dontKnow=${ad.dontKnow} dismissed=${ad.dismissed}`);
-
- // The agent turn that follows the answer must stream and settle.
- console.log("── waiting for the agent to continue and settle…");
- await settledAgain;
- console.log(" ✓ agent continued after the answer and settled");
-
- // Check transcript state: user message with [Quiz answer] prefix must exist.
- const userAnswer = seen.find(
- (m) => m.type === "entry" && m.entry?.type === "user" && m.entry?.text?.startsWith("["),
- );
- console.log(userAnswer ? ` ✓ answer user message: ${userAnswer.entry.text.split("\n")[0]}` : " (no answer user message seen — check server logs)");
-
- // Prompt while nothing pending should work.
- const settlePrompt = waitEvent(seen, ws, (m) => m.type === "streaming" && m.value === false, 180_000);
- ws.send(JSON.stringify({ type: "prompt", text: "thanks, that's enough for now" }));
- await settlePrompt;
- console.log(" ✓ follow-up prompt worked");
-
- // Dismiss any second question the teacher may have asked, so the resume
- // test starts from a clean (no pending) state.
- const pendingNow = seen.find((m) => m.type === "pending" && m.question !== null && !seen.some((o) => o.type === "entry" && o.entry?.type === "questionAnswered" && o.entry.data.questionId === m.question.id));
- if (pendingNow) {
- ws.send(JSON.stringify({ type: "dismiss", questionId: pendingNow.question.id }));
- await waitEvent(seen, ws, (m) => m.type === "pending" && m.question === null, 60_000);
- console.log(" ✓ extra question dismissed");
- }
-
- ws.close();
-
- // ── resume test: list sessions, resume the one we just made, verify backfill
- console.log("── resume test");
- const list = (await (await fetch(`${BASE}/api/sessions`)).json()) as { sessions: any[] };
- const mine = list.sessions.find((s) => s.id === id);
- console.log(` listed: "${mine?.name}" (${mine?.messageCount} msgs)`);
- const rres = await fetch(`${BASE}/api/sessions/${encodeURIComponent(mine!.file)}/resume`, { method: "POST" });
- if (!rres.ok) throw new Error(`resume failed: ${rres.status}`);
- const ws2 = await connectWs();
- const seen2 = makeCollector(ws2);
- const backfill = await waitEvent(seen2, ws2, (m) => m.type === "backfill");
- const kinds = backfill.entries.reduce((acc: Record, e: any) => {
- acc[e.type] = (acc[e.type] ?? 0) + 1;
- return acc;
- }, {});
- console.log(" backfill entries:", JSON.stringify(kinds));
- const pending2 = await waitEvent(seen2, ws2, (m) => m.type === "pending");
- console.log(` pending after resume: ${pending2.question === null ? "none (correct)" : "UNEXPECTED: " + pending2.question.id}`);
- ws2.close();
-
- console.log("\n✓ E2E PASSED");
- process.exit(0);
-}
-
-main().catch((err) => {
- console.error("\n✗ E2E FAILED:", err);
- process.exit(1);
-});
diff --git a/teacher/system-prompt.md b/src/agent/system-prompt.md
similarity index 100%
rename from teacher/system-prompt.md
rename to src/agent/system-prompt.md
diff --git a/src/agent/teacher-agent.ts b/src/agent/teacher-agent.ts
new file mode 100644
index 0000000..93e84ba
--- /dev/null
+++ b/src/agent/teacher-agent.ts
@@ -0,0 +1,36 @@
+import * as fs from "node:fs";
+import { ToolLoopAgent, stepCountIs, type LanguageModel } from "ai";
+import { openrouter } from "@openrouter/ai-sdk-provider";
+import { PATHS } from "../../config.ts";
+import { teacherTools } from "./tools.ts";
+
+/**
+ * The teacher agent. Everything pi used to provide (agent loop, tool calling,
+ * streaming, multi-step) is these few lines now.
+ *
+ * The model is injectable so tests can pass a MockLanguageModelV4 and drive
+ * deterministic scripted lessons (see tests/chat-flow.test.ts).
+ */
+
+const DEFAULT_MODEL = process.env.LH_MODEL ?? "anthropic/claude-sonnet-4.5";
+
+const instructions = fs.readFileSync(PATHS.teacherPrompt, "utf-8");
+
+export function createTeacherAgent(model?: LanguageModel) {
+ return new ToolLoopAgent({
+ model:
+ model ??
+ openrouter(DEFAULT_MODEL, {
+ // Ask OpenRouter for reasoning tokens when the model supports them;
+ // they stream as reasoning parts into the UI's collapsible block.
+ reasoning: { enabled: true, effort: "medium" },
+ }),
+ instructions,
+ tools: teacherTools,
+ // Both tools are client-side (no execute) — a tool call ends the server
+ // turn automatically. stepCountIs is just the safety net.
+ stopWhen: stepCountIs(20),
+ });
+}
+
+export type TeacherAgent = ReturnType;
diff --git a/src/agent/tools.ts b/src/agent/tools.ts
new file mode 100644
index 0000000..2b8ba24
--- /dev/null
+++ b/src/agent/tools.ts
@@ -0,0 +1,85 @@
+import { tool } from "ai";
+import { z } from "zod";
+
+/**
+ * The teacher's interaction tools — CLIENT-SIDE tools (no `execute`).
+ *
+ * The model's tool call is not executed on the server; it streams to the
+ * browser as a typed tool part in `input-available` state. The learner
+ * answers in the rendered card; the client grades (quiz) via
+ * src/core/question-core.ts and sends the verdict back with addToolOutput.
+ *
+ * Parameter names mirror src/agent/system-prompt.md exactly — the prompt
+ * documents these fields to the model. Keep them in sync.
+ */
+
+const optionSchema = z.object({
+ label: z.string().describe("Display label for the answer option"),
+ value: z
+ .string()
+ .optional()
+ .describe("Optional machine-readable value returned for the option. Defaults to the label."),
+ description: z.string().optional().describe("Optional extra detail shown below the option"),
+});
+
+// Raw schemas stay exported: `tool()` wraps them into its internal Schema
+// type (erasing z.infer), so clients infer input types from these instead.
+export const quizInputSchema = z.object({
+ question: z.string().describe("The single quiz question to ask"),
+ options: z
+ .array(optionSchema)
+ .min(2)
+ .describe("The answer options (2 or more). Options only — no free-text mode. Never add your own 'I don't know' option."),
+ correctAnswer: z
+ .union([z.string(), z.array(z.string())])
+ .describe(
+ "The correct answer as the option value(s) — the `value` field of the option you intend. " +
+ "Single-select: one string. Multi-select: an array; the learner is only correct if their selection matches this set exactly.",
+ ),
+ explanation: z
+ .string()
+ .describe("Explanation revealed AFTER the learner answers. Always say why the correct answer is correct."),
+ multiSelect: z.boolean().optional().describe("Set true only when more than one option is correct"),
+ details: z.string().optional().describe("Optional extra context or instructions shown under the question"),
+});
+
+export const askInputSchema = z.object({
+ question: z.string().describe("The single question to ask"),
+ mode: z
+ .enum(["single-select", "multi-select", "text"])
+ .optional()
+ .describe("Answer mode. Omit for single-select when options are given, or text when they are not."),
+ options: z
+ .array(optionSchema)
+ .min(2)
+ .optional()
+ .describe("Optional multiple-choice options. Omit for free-form text input. The learner can always choose 'Other' and type a custom answer."),
+ multiSelect: z.boolean().optional().describe("Set true to allow multiple answers to be selected"),
+ details: z.string().optional().describe("Optional extra context or instructions shown under the question"),
+});
+
+export const quizTool = tool({
+ description:
+ "Ask the learner a GRADED question with a known correct answer, then instantly grade and give feedback. " +
+ "Use it to (1) assess what the learner already understands before teaching, and (2) run tight practice/retrieval loops after explaining. " +
+ "Options-only: single-select or multi-select, plus an automatic 'I don't know' choice so the learner can signal a genuine gap instead of guessing. " +
+ "No free-text answers — for non-graded questions use ask_user_question instead. " +
+ "Ask exactly one question per tool call; after calling, end your turn and wait for the graded result.",
+ inputSchema: quizInputSchema,
+});
+
+export const askUserQuestionTool = tool({
+ description:
+ "Ask the learner a single question and pause until they answer. NOT graded — there is no correct answer. " +
+ "Use when requirements are ambiguous, preferences are needed, a decision would materially affect the lesson, " +
+ "or you need an open-ended response. Ask exactly one question per tool call; after calling, end your turn and wait.",
+ inputSchema: askInputSchema,
+});
+
+export const teacherTools = {
+ quiz: quizTool,
+ ask_user_question: askUserQuestionTool,
+};
+
+export type QuizToolInput = z.infer;
+export type AskToolInput = z.infer;
diff --git a/src/agent/ui-message.ts b/src/agent/ui-message.ts
new file mode 100644
index 0000000..b5e0b0b
--- /dev/null
+++ b/src/agent/ui-message.ts
@@ -0,0 +1,19 @@
+import type { UIMessage } from "ai";
+import type { AskToolInput, QuizToolInput } from "./tools.ts";
+import type { QuestionAnswerData } from "../core/question-core.ts";
+
+/**
+ * The typed UI message for the teacher chat — gives the frontend typed tool
+ * parts ("tool-quiz" / "tool-ask_user_question" with typed input/output)
+ * without importing the server-side agent into the client bundle.
+ *
+ * Declared explicitly (not via InferUITools) because a client-side tool has
+ * no execute function, so its OUTPUT type lives only here: the structured
+ * answer record the client builds and sends via addToolOutput.
+ */
+export type TeacherUITools = {
+ quiz: { input: QuizToolInput; output: QuestionAnswerData };
+ ask_user_question: { input: AskToolInput; output: QuestionAnswerData };
+};
+
+export type TeacherUIMessage = UIMessage;
diff --git a/src/cli.ts b/src/cli.ts
index b15e1cd..69745b1 100644
--- a/src/cli.ts
+++ b/src/cli.ts
@@ -2,18 +2,15 @@
/**
* learning-harness CLI
*
- * teach [message] launch an interactive teaching session (full pi TUI)
- * doctor [--web] non-interactive smoke test of the teacher wiring
- * (--web checks the web-dispatch extension set)
- * serve start the web server (teacher agent + REST + WebSocket)
* ingest normalize a session JSONL and print the canonical document
* (Stage 1 only — extractor/merger not yet implemented)
* graph print graph/ledger stats
* what-next (not yet implemented)
+ *
+ * The web app (Next.js + AI SDK): npm run dev
*/
import * as fs from "node:fs";
-import { doctor, runTeachingSession } from "./teacher/create-session.ts";
import { normalizeSession } from "./extract/normalize.ts";
import { ledgers } from "./ledger.ts";
import { deriveAll } from "./derive.ts";
@@ -23,15 +20,6 @@ async function main() {
const [command, ...rest] = process.argv.slice(2);
switch (command) {
- case "teach": {
- await runTeachingSession(rest.join(" ") || undefined);
- break;
- }
- case "doctor": {
- const mode = rest.includes("--web") ? "web" : "tui";
- await doctor(mode);
- break;
- }
case "ingest": {
const file = rest[0];
if (!file) {
@@ -69,7 +57,7 @@ async function main() {
process.exit(1);
}
default: {
- console.log("Commands: teach [message] | doctor | ingest | graph | what-next");
+ console.log("Commands: ingest | graph | what-next");
process.exit(command ? 1 : 0);
}
}
diff --git a/src/core/pending.ts b/src/core/pending.ts
new file mode 100644
index 0000000..f51856d
--- /dev/null
+++ b/src/core/pending.ts
@@ -0,0 +1,24 @@
+import { isToolUIPart, type UIMessage } from "ai";
+
+/**
+ * The pending-question gate: an unanswered interaction tool part (quiz/ask)
+ * blocks the composer — one question at a time, mirroring the teacher's
+ * prompt contract. Derived client state; the server enforces nothing.
+ */
+export interface PendingToolCall {
+ toolCallId: string;
+ tool: string;
+}
+
+export function findPendingToolCall(messages: UIMessage[]): PendingToolCall | null {
+ for (let m = messages.length - 1; m >= 0; m--) {
+ const msg = messages[m]!;
+ for (let p = msg.parts.length - 1; p >= 0; p--) {
+ const part = msg.parts[p]!;
+ if (isToolUIPart(part) && part.state === "input-available") {
+ return { toolCallId: part.toolCallId, tool: part.type.slice("tool-".length) };
+ }
+ }
+ }
+ return null;
+}
diff --git a/extensions/core/question-core.ts b/src/core/question-core.ts
similarity index 90%
rename from extensions/core/question-core.ts
rename to src/core/question-core.ts
index 92e69a0..da00eb4 100644
--- a/extensions/core/question-core.ts
+++ b/src/core/question-core.ts
@@ -1,21 +1,23 @@
/**
- * question-core — pure logic shared by the terminal (blocking) and web
- * (dispatch) quiz/ask extensions. No pi imports, no UI: everything here is
- * deterministic and unit-testable.
+ * question-core — pure logic for quiz/ask questions. No framework imports,
+ * no UI: everything here is deterministic and unit-testable.
*
- * The WEB dispatch shape:
- * 1. Tool execute() validates + shuffles + resolves the correct answer,
- * appends an `lh_question` custom entry (NOT in LLM context — safe to
- * store correctAnswer/explanation there) and returns immediately.
- * 2. The agent ends its turn. The server pushes the question to the client.
- * 3. The learner answers; the server calls back into the extension bridge.
- * 4. The extension grades, appends `lh_question_answered` (gold-standard
- * structured data for the extractor), and sends the graded answer into
- * the session as a user message so the agent can continue.
+ * Used by:
+ * - the TUI quiz extension (extensions/quiz.ts) — blocking terminal UI
+ * - the web app (AI SDK client-side tools) — the browser validates,
+ * shuffles, grades with these functions, then sends the verdict back
+ * as the tool output via addToolOutput
*
- * Because nothing blocks, a session can be exited and resumed mid-question:
- * the pending state lives in the session file itself (lh_question entries
- * with no matching lh_question_answered).
+ * The web shape (AI SDK):
+ * 1. The teacher model calls the `quiz` tool (question + options +
+ * correctAnswer + explanation). No server execute → the call lands in
+ * the UI as a typed tool part in `input-available` state.
+ * 2. The learner answers in the card; the client grades with this module.
+ * 3. addToolOutput sends the verdict (selection + correctness + note —
+ * never the explanation, the model authored that) back to the model.
+ *
+ * Because the pending question IS a persisted message part, a session can be
+ * exited and resumed mid-question for free.
*/
// ─── shared types ───────────────────────────────────────────────────────────
diff --git a/src/server/chat-handler.ts b/src/server/chat-handler.ts
new file mode 100644
index 0000000..4c9f835
--- /dev/null
+++ b/src/server/chat-handler.ts
@@ -0,0 +1,42 @@
+import { createAgentUIStreamResponse, type UIMessage } from "ai";
+import { createTeacherAgent, type TeacherAgent } from "../agent/teacher-agent.ts";
+import { getChat, saveMessages } from "../store/chats.ts";
+
+/**
+ * One chat turn: run the teacher agent over the client's message list and
+ * stream the UI message stream back. Framework-free (plain Request/Response)
+ * so tests can drive it directly with a scripted mock model — the Next route
+ * handler in app/api/chat is a one-line wrapper.
+ *
+ * The client owns the message list between turns (useChat); the server
+ * persists the canonical state (client messages + agent response) onFinish.
+ */
+export async function handleChatRequest(req: Request, agent?: TeacherAgent): Promise {
+ let body: { chatId?: unknown; messages?: unknown };
+ try {
+ body = await req.json();
+ } catch {
+ return Response.json({ error: "Invalid JSON body" }, { status: 400 });
+ }
+ const chatId = typeof body.chatId === "string" ? body.chatId : "";
+ if (!chatId || !getChat(chatId)) {
+ return Response.json({ error: "Unknown chat" }, { status: 404 });
+ }
+ if (!Array.isArray(body.messages)) {
+ return Response.json({ error: "messages must be an array" }, { status: 400 });
+ }
+
+ return createAgentUIStreamResponse({
+ agent: agent ?? createTeacherAgent(),
+ uiMessages: body.messages as UIMessage[],
+ onFinish: ({ messages, isAborted }) => {
+ // Persist even when aborted: the partial transcript (including any
+ // pending question part) is what makes resume work.
+ try {
+ saveMessages(chatId, messages as UIMessage[]);
+ } catch (err) {
+ console.error(`[chat ${chatId}] persist failed:`, err, { isAborted });
+ }
+ },
+ });
+}
diff --git a/src/server/index.ts b/src/server/index.ts
deleted file mode 100644
index b514356..0000000
--- a/src/server/index.ts
+++ /dev/null
@@ -1,484 +0,0 @@
-/**
- * web server — Fastify + WebSocket server embedding the teacher agent via the
- * pi SDK (web dispatch mode: quiz/ask return immediately, answers arrive as
- * user messages — nothing blocks, so sessions can be exited/resumed mid-question).
- *
- * Delivery model (the part that must never double-send or drop):
- * - ONE append-only entry log per active session (`entryLog`), plus a cursor
- * (`syncedCount`) into the session manager's entries.
- * - Every entry enters the log exactly once — either at activation (seeded
- * from history, no broadcast) or live via syncEntries (broadcast to all
- * connected sockets).
- * - A socket connecting at ANY time receives the whole log as backfill.
- * There is no per-socket marking and no window in which an entry can be
- * both backfilled and broadcast — the duplication race from v1 is
- * structurally impossible now.
- *
- * - Streaming: thinking and text deltas are forwarded as they happen;
- * ephemeral (a socket that connects mid-turn gets `streaming: true` in its
- * snapshot and the entries when they land).
- *
- * v1 scope: one active session at a time; extraction pipeline NOT wired yet.
- */
-
-import fs from "node:fs";
-import path from "node:path";
-import Fastify from "fastify";
-import fastifyWebsocket from "@fastify/websocket";
-import fastifyStatic from "@fastify/static";
-import {
- type AgentSessionEvent,
- type AgentSessionRuntimeDiagnostic,
- createAgentSessionRuntime,
- SessionManager,
-} from "@earendil-works/pi-coding-agent";
-import { PATHS } from "../../config.ts";
-import { buildTeacherRuntimeFactory } from "../teacher/create-session.ts";
-import {
- QUESTION_ANSWERED_ENTRY,
- QUESTION_ENTRY,
- findPendingQuestion,
- type QuestionAnswerData,
- type QuestionData,
- toClientQuestion,
-} from "../../extensions/core/question-core.ts";
-import { getBridge } from "../../extensions/core/bridge.ts";
-import type { ClientEntry, ServerMessage, SessionSummary } from "../shared/protocol.ts";
-
-/** What the server needs from a teacher runtime (real or scripted). */
-export interface RuntimeLike {
- session: {
- subscribe(listener: (evt: AgentSessionEvent) => void): () => void;
- dispose(): void;
- isStreaming: boolean;
- prompt(text: string): Promise;
- abort(): unknown;
- setSessionName(name: string): unknown;
- sessionManager: SessionManager;
- };
- diagnostics: readonly AgentSessionRuntimeDiagnostic[];
-}
-
-export type RuntimeFactory = (options: {
- cwd: string;
- agentDir: string;
- sessionManager: SessionManager;
-}) => Promise;
-
-export interface ServerOptions {
- /** TCP port; 0 = pick a free port (tests). Default: LH_PORT or 8787. */
- port?: number;
- /** Teacher runtime factory — inject a scripted one in tests. */
- runtimeFactory?: RuntimeFactory;
-}
-
-const defaultRuntimeFactory: RuntimeFactory = (options) =>
- createAgentSessionRuntime(buildTeacherRuntimeFactory("web"), options) as unknown as Promise;
-
-// ─── entry serialization ────────────────────────────────────────────────────
-
-function extractText(content: unknown[]): string {
- return content
- .filter((c): c is { type: "text"; text: string } => (c as { type?: string }).type === "text")
- .map((c) => c.text)
- .join("");
-}
-
-function extractThinking(content: unknown[]): string {
- return content
- .filter(
- (c): c is { type: "thinking"; thinking: string } => (c as { type?: string }).type === "thinking",
- )
- .map((c) => c.thinking)
- .join("\n");
-}
-
-function serializeEntry(entry: {
- type: string;
- id: string;
- customType?: string;
- data?: unknown;
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- message?: any;
-}): ClientEntry | null {
- if (entry.type === "message" && entry.message) {
- const m = entry.message;
- if (m.role === "user") {
- return { type: "user", id: entry.id, text: extractText(m.content ?? []) };
- }
- if (m.role === "assistant") {
- const toolCalls = ((m.content ?? []) as unknown[])
- .filter(
- (c): c is { type: "toolCall"; name: string; arguments?: unknown } =>
- (c as { type?: string }).type === "toolCall",
- )
- .map((c) => ({ name: c.name, args: (c.arguments ?? {}) as Record }));
- const thinking = extractThinking(m.content ?? []);
- return {
- type: "assistant",
- id: entry.id,
- text: extractText(m.content ?? []),
- thinking: thinking || undefined,
- toolCalls,
- };
- }
- if (m.role === "toolResult") {
- return { type: "toolResult", id: entry.id, toolName: m.toolName ?? "", text: extractText(m.content ?? []) };
- }
- return null;
- }
- if (entry.type === "custom" && entry.customType === QUESTION_ENTRY) {
- const data = entry.data as QuestionData | undefined;
- if (!data) return null;
- return { type: "question", id: entry.id, data: toClientQuestion(data) };
- }
- if (entry.type === "custom" && entry.customType === QUESTION_ANSWERED_ENTRY) {
- const data = entry.data as QuestionAnswerData | undefined;
- if (!data) return null;
- return { type: "questionAnswered", id: entry.id, data };
- }
- return { type: "meta", id: entry.id, kind: entry.type };
-}
-
-// ─── session listing (reads data/sessions/*.jsonl) ──────────────────────────
-
-function listSessions(): SessionSummary[] {
- const dir = PATHS.sessions;
- const files = fs.existsSync(dir) ? fs.readdirSync(dir).filter((f) => f.endsWith(".jsonl")) : [];
- const summaries: SessionSummary[] = [];
- for (const file of files) {
- try {
- const lines = fs.readFileSync(path.join(dir, file), "utf8").split("\n").filter(Boolean);
- if (lines.length === 0) continue;
- const header = JSON.parse(lines[0] ?? "") as { id?: string; timestamp?: string };
- const id = header.id;
- if (!id) continue;
- let name: string | undefined;
- let preview: string | undefined;
- let updatedAt = header.timestamp ?? "";
- let messageCount = 0;
- for (const line of lines.slice(1)) {
- let entry: { type?: string; message?: { role?: string; content?: unknown[] }; timestamp?: string; name?: string };
- try {
- entry = JSON.parse(line);
- } catch {
- continue;
- }
- if (entry.timestamp) updatedAt = entry.timestamp;
- if (entry.type === "session_info" && entry.name) name = entry.name;
- if (entry.type === "message" && entry.message?.role === "user") {
- messageCount++;
- if (!preview) preview = extractText(entry.message.content ?? []).slice(0, 120);
- }
- }
- summaries.push({
- id,
- file,
- name,
- createdAt: header.timestamp ?? "",
- updatedAt,
- messageCount,
- preview,
- });
- } catch {
- // unreadable session file — skip
- }
- }
- summaries.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
- return summaries;
-}
-
-// ─── server ─────────────────────────────────────────────────────────────────
-
-export async function startServer(options: ServerOptions = {}): Promise<{
- port: number;
- close: () => Promise;
-}> {
- const runtimeFactory = options.runtimeFactory ?? defaultRuntimeFactory;
-
- // Active session state. `entryLog` is the single source of truth for what
- // clients have been able to see; `syncedCount` is how many session entries
- // it reflects. Both reset together on activation.
- interface ActiveSession {
- runtime: RuntimeLike;
- sessionId: string;
- file: string;
- }
- let active: ActiveSession | null = null;
- let unsubscribeEvents: (() => void) | null = null;
- let entryLog: ClientEntry[] = [];
- let syncedCount = 0;
-
- const sockets = new Set();
-
- function broadcast(msg: ServerMessage): void {
- const raw = JSON.stringify(msg);
- for (const ws of sockets) {
- if (ws.readyState === ws.OPEN) ws.send(raw);
- }
- }
-
- function broadcastPending(): void {
- if (!active) return;
- const pending = findPendingQuestion(active.runtime.session.sessionManager.getEntries());
- broadcast({ type: "pending", question: pending });
- }
-
- /** Append every not-yet-logged session entry to the log (broadcasting each). */
- function syncEntries(): void {
- if (!active) return;
- const entries = active.runtime.session.sessionManager.getEntries();
- for (; syncedCount < entries.length; syncedCount++) {
- const entry = entries[syncedCount]!;
- const client = serializeEntry(entry as never);
- if (client) {
- entryLog.push(client);
- broadcast({ type: "entry", entry: client });
- }
- if (
- entry.type === "custom" &&
- ((entry as { customType?: string }).customType === QUESTION_ENTRY ||
- (entry as { customType?: string }).customType === QUESTION_ANSWERED_ENTRY)
- ) {
- broadcastPending();
- }
- }
- }
-
- function handleSessionEvent(evt: AgentSessionEvent): void {
- if (!active) return;
- switch (evt.type) {
- case "message_update": {
- const e = evt.assistantMessageEvent;
- if (e?.type === "text_delta") {
- broadcast({ type: "delta", kind: "text", delta: e.delta });
- } else if (e?.type === "thinking_delta") {
- broadcast({ type: "delta", kind: "thinking", delta: e.delta });
- }
- break;
- }
- case "agent_start":
- broadcast({ type: "streaming", value: true });
- break;
- case "agent_settled":
- broadcast({ type: "streaming", value: false });
- break;
- }
- syncEntries();
- }
-
- async function activate(sessionManager: SessionManager): Promise {
- if (unsubscribeEvents) {
- unsubscribeEvents();
- unsubscribeEvents = null;
- }
- if (active) {
- active.runtime.session.dispose();
- active = null;
- }
- const runtime = await runtimeFactory({
- cwd: PATHS.root,
- agentDir: PATHS.agentDir,
- sessionManager,
- });
- for (const d of runtime.diagnostics) {
- if (d.type === "error") console.error(`[teacher] ${d.message}`);
- }
- unsubscribeEvents = runtime.session.subscribe(handleSessionEvent);
- const file = path.basename(sessionManager.getSessionFile() ?? "");
- active = { runtime, sessionId: sessionManager.getSessionId(), file };
-
- // Seed the log from history WITHOUT broadcasting: sockets that connect
- // later get it via their connect-backfill; sockets already connected get
- // it via the hello+backfill below. Either way, exactly once.
- entryLog = sessionManager
- .getEntries()
- .map((e) => serializeEntry(e as never))
- .filter((e): e is ClientEntry => e !== null);
- syncedCount = sessionManager.getEntries().length;
-
- broadcast({ type: "hello", sessionId: active.sessionId });
- broadcast({ type: "backfill", entries: entryLog });
- broadcastPending();
- broadcast({ type: "streaming", value: runtime.session.isStreaming });
- return active;
- }
-
- async function createSession(topic: string): Promise {
- const sessionManager = SessionManager.create(PATHS.root, PATHS.sessions);
- const result = await activate(sessionManager);
- // Name the session after the topic (shown in the session list).
- const name = topic.trim().slice(0, 60) || "New session";
- result.runtime.session.setSessionName(name);
- // Fire the first prompt (don't await — clients follow along via WS).
- void result.runtime.session.prompt(topic).catch((err) => {
- console.error("[teacher] prompt failed:", err);
- broadcast({ type: "error", message: String(err) });
- });
- return result;
- }
-
- async function resumeSession(file: string): Promise {
- const sessionManager = SessionManager.open(path.join(PATHS.sessions, file), PATHS.sessions, PATHS.root);
- return activate(sessionManager);
- }
-
- // ─── HTTP ────────────────────────────────────────────────────────────────
-
- const app = Fastify({ logger: false });
- await app.register(fastifyWebsocket);
-
- // Serve the built frontend (web/dist) when it exists.
- const distDir = path.join(PATHS.root, "web", "dist");
- if (fs.existsSync(distDir)) {
- await app.register(fastifyStatic, { root: distDir });
- app.setNotFoundHandler((req, reply) => {
- if (req.url.startsWith("/api") || req.url.startsWith("/ws")) {
- reply.code(404).send({ error: "not found" });
- } else {
- reply.sendFile("index.html");
- }
- });
- }
-
- app.get("/api/sessions", async () => ({
- sessions: listSessions(),
- activeId: active?.sessionId ?? null,
- }));
-
- app.post<{ Body: { topic?: string } }>("/api/sessions", async (req, reply) => {
- const topic = (req.body?.topic ?? "").trim();
- if (!topic) return reply.code(400).send({ error: "topic is required" });
- const result = await createSession(topic);
- return { id: result.sessionId, file: result.file };
- });
-
- app.post<{ Params: { file: string } }>("/api/sessions/:file/resume", async (req, reply) => {
- const file = req.params.file;
- const full = path.join(PATHS.sessions, file);
- if (!file.endsWith(".jsonl") || !fs.existsSync(full)) {
- return reply.code(404).send({ error: "session not found" });
- }
- try {
- const result = await resumeSession(file);
- return { id: result.sessionId, file: result.file };
- } catch (err) {
- return reply.code(500).send({ error: String(err) });
- }
- });
-
- // ─── WebSocket ───────────────────────────────────────────────────────────
-
- app.get("/ws", { websocket: true }, (socket) => {
- sockets.add(socket);
- socket.on("close", () => sockets.delete(socket));
-
- // Initial snapshot: everything the session has produced so far.
- socket.send(JSON.stringify({ type: "hello", sessionId: active?.sessionId ?? null } satisfies ServerMessage));
- socket.send(JSON.stringify({ type: "backfill", entries: entryLog } satisfies ServerMessage));
- if (active) {
- socket.send(
- JSON.stringify({
- type: "pending",
- question: findPendingQuestion(active.runtime.session.sessionManager.getEntries()),
- } satisfies ServerMessage),
- );
- socket.send(
- JSON.stringify({ type: "streaming", value: active.runtime.session.isStreaming } satisfies ServerMessage),
- );
- }
-
- socket.on("message", (raw: Buffer) => {
- let msg: { type?: string } & Record;
- try {
- msg = JSON.parse(raw.toString());
- } catch {
- return;
- }
- if (!active) {
- socket.send(JSON.stringify({ type: "error", message: "no active session" } satisfies ServerMessage));
- return;
- }
- const session = active.runtime.session;
- switch (msg.type) {
- case "prompt": {
- const text = String(msg.text ?? "").trim();
- if (!text) return;
- // One question at a time: don't accept prompts while a question
- // is waiting to be answered.
- if (findPendingQuestion(session.sessionManager.getEntries())) {
- socket.send(
- JSON.stringify({
- type: "error",
- message: "Answer the pending question first (or dismiss it).",
- } satisfies ServerMessage),
- );
- return;
- }
- void session.prompt(text).catch((err) => {
- console.error("[teacher] prompt failed:", err);
- broadcast({ type: "error", message: String(err) });
- });
- break;
- }
- case "answer": {
- const outcome = getBridge().answer(String(msg.questionId ?? ""), {
- dontKnow: Boolean(msg.dontKnow),
- values: Array.isArray(msg.values) ? msg.values.map(String) : undefined,
- other: msg.other ? String(msg.other) : undefined,
- text: msg.text ? String(msg.text) : undefined,
- note: msg.note ? String(msg.note) : undefined,
- });
- if (!outcome.ok) {
- socket.send(
- JSON.stringify({ type: "error", message: outcome.error ?? "answer failed" } satisfies ServerMessage),
- );
- }
- break;
- }
- case "dismiss": {
- const outcome = getBridge().dismiss(String(msg.questionId ?? ""));
- if (!outcome.ok) {
- socket.send(
- JSON.stringify({ type: "error", message: outcome.error ?? "dismiss failed" } satisfies ServerMessage),
- );
- }
- break;
- }
- case "abort": {
- void session.abort();
- break;
- }
- }
- });
- });
-
- const port = options.port ?? Number(process.env.LH_PORT ?? 8787);
- await app.listen({ port, host: "127.0.0.1" });
- const actualPort = (app.server.address() as { port: number }).port;
-
- return {
- port: actualPort,
- close: async () => {
- if (unsubscribeEvents) unsubscribeEvents();
- if (active) active.runtime.session.dispose();
- for (const ws of sockets) ws.close();
- sockets.clear();
- await app.close();
- },
- };
-}
-
-// ─── entrypoint ─────────────────────────────────────────────────────────────
-
-async function main(): Promise {
- const server = await startServer();
- console.log(`learning-harness web server: http://127.0.0.1:${server.port}`);
-}
-
-if (process.argv[1] && process.argv[1].endsWith("server/index.ts")) {
- main().catch((err) => {
- console.error(err);
- process.exit(1);
- });
-}
diff --git a/src/shared/protocol.ts b/src/shared/protocol.ts
deleted file mode 100644
index 5a412cf..0000000
--- a/src/shared/protocol.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-/**
- * protocol — shared types between the web server and the browser client.
- * One source of truth for the WS protocol and REST DTOs.
- */
-
-import type { ClientQuestion, QuestionAnswerData } from "../../extensions/core/question-core.ts";
-
-export type { ClientQuestion, QuestionAnswerData } from "../../extensions/core/question-core.ts";
-
-// ─── entries (session → client) ─────────────────────────────────────────────
-
-export interface ClientToolCall {
- name: string;
- args: Record;
-}
-
-export type ClientEntry =
- | { type: "user"; id: string; text: string }
- | { type: "assistant"; id: string; text: string; thinking?: string; toolCalls: ClientToolCall[] }
- | { type: "toolResult"; id: string; toolName: string; text: string }
- | { type: "question"; id: string; data: ClientQuestion }
- | { type: "questionAnswered"; id: string; data: QuestionAnswerData }
- | { type: "meta"; id: string; kind: string };
-
-// ─── REST DTOs ──────────────────────────────────────────────────────────────
-
-export interface SessionSummary {
- id: string;
- file: string;
- name?: string;
- createdAt: string;
- updatedAt: string;
- messageCount: number;
- preview?: string;
-}
-
-export interface NewSessionResponse {
- id: string;
- file: string;
-}
-
-// ─── WS protocol ────────────────────────────────────────────────────────────
-
-export type ServerMessage =
- | { type: "hello"; sessionId: string | null }
- | { type: "backfill"; entries: ClientEntry[] }
- | { type: "entry"; entry: ClientEntry }
- | { type: "pending"; question: ClientQuestion | null }
- | { type: "delta"; kind: "thinking" | "text"; delta: string }
- | { type: "streaming"; value: boolean }
- | { type: "error"; message: string };
-
-export type ClientMessage =
- | { type: "prompt"; text: string }
- | {
- type: "answer";
- questionId: string;
- dontKnow?: boolean;
- values?: string[];
- other?: string;
- text?: string;
- note?: string;
- }
- | { type: "dismiss"; questionId: string }
- | { type: "abort" };
diff --git a/src/store/chats.ts b/src/store/chats.ts
new file mode 100644
index 0000000..7b1dcee
--- /dev/null
+++ b/src/store/chats.ts
@@ -0,0 +1,87 @@
+import * as fs from "node:fs";
+import * as path from "node:path";
+import type { UIMessage } from "ai";
+import { PATHS } from "../../config.ts";
+
+/**
+ * Chat store — one JSON file per chat in data/chats/.
+ *
+ * The stored messages are full AI SDK UIMessages (parts included). This is
+ * the canonical session record: a pending quiz is a tool part in
+ * `input-available` state, which is exactly what makes resume-mid-question
+ * free. The extraction pipeline (src/extract) will read these later; old pi
+ * sessions (data/sessions) remain bootstrap input only.
+ */
+
+export interface ChatRecord {
+ id: string;
+ title: string;
+ createdAt: string;
+ updatedAt: string;
+ messages: UIMessage[];
+}
+
+export interface ChatSummary {
+ id: string;
+ title: string;
+ createdAt: string;
+ updatedAt: string;
+ messageCount: number;
+}
+
+function chatFile(id: string): string {
+ if (!/^[A-Za-z0-9_-]+$/.test(id)) throw new Error(`Invalid chat id: ${id}`);
+ return path.join(PATHS.chats, `${id}.json`);
+}
+
+export function createChat(title: string): ChatRecord {
+ const now = new Date().toISOString();
+ const record: ChatRecord = {
+ id: `chat_${Date.now().toString(36)}_${crypto.randomUUID().slice(0, 8)}`,
+ title: title.trim() || "Untitled session",
+ createdAt: now,
+ updatedAt: now,
+ messages: [],
+ };
+ fs.mkdirSync(PATHS.chats, { recursive: true });
+ fs.writeFileSync(chatFile(record.id), JSON.stringify(record, null, "\t"), "utf-8");
+ return record;
+}
+
+export function getChat(id: string): ChatRecord | null {
+ try {
+ return JSON.parse(fs.readFileSync(chatFile(id), "utf-8")) as ChatRecord;
+ } catch {
+ return null;
+ }
+}
+
+export function listChats(): ChatSummary[] {
+ try {
+ fs.mkdirSync(PATHS.chats, { recursive: true });
+ return fs
+ .readdirSync(PATHS.chats)
+ .filter((f) => f.endsWith(".json"))
+ .map((f) => getChat(f.slice(0, -5)))
+ .filter((r): r is ChatRecord => r !== null)
+ .map(({ messages, ...summary }) => ({ ...summary, messageCount: messages.length }))
+ .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
+ } catch {
+ return [];
+ }
+}
+
+/** Persist the full message list (client state + new response) after a turn. */
+export function saveMessages(id: string, messages: UIMessage[]): void {
+ const record = getChat(id);
+ if (!record) throw new Error(`Chat not found: ${id}`);
+ record.messages = messages;
+ record.updatedAt = new Date().toISOString();
+ fs.writeFileSync(chatFile(id), JSON.stringify(record, null, "\t"), "utf-8");
+}
+
+/** Derive a short title from the first user message. */
+export function titleFromPrompt(prompt: string): string {
+ const oneLine = prompt.replace(/\s+/g, " ").trim();
+ return oneLine.length > 72 ? `${oneLine.slice(0, 72)}…` : oneLine;
+}
diff --git a/src/teacher/create-session.ts b/src/teacher/create-session.ts
deleted file mode 100644
index 47ef80f..0000000
--- a/src/teacher/create-session.ts
+++ /dev/null
@@ -1,162 +0,0 @@
-/**
- * Teacher session — the teaching runtime of the learning harness.
- *
- * pi (@earendil-works/pi-coding-agent) is embedded as a library dependency.
- * This is NOT the user's global pi: hermetic isolation is enforced by
- * - agentDir → data/agent/ (settings/auth/skills/extensions; nothing from ~/.pi/agent
- * loads except auth.json, which is a symlink — credentials only)
- * - sessions → data/sessions/ (harness-owned session JSONLs)
- * - systemPrompt → teacher/system-prompt.md (fully replaces pi's coding-agent prompt)
- * - tools → noTools: "builtin" (read/bash/edit/write off; only extension tools remain)
- * - extensions→ explicitly: quiz.ts + ask-user-question.ts via additionalExtensionPaths
- */
-
-import path from "node:path";
-import {
- createAgentSessionFromServices,
- createAgentSessionRuntime,
- createAgentSessionServices,
- InteractiveMode,
- initTheme,
- SessionManager,
- type CreateAgentSessionRuntimeFactory,
-} from "@earendil-works/pi-coding-agent";
-import { PATHS } from "../../config.ts";
-
-export type TeacherMode = "tui" | "web";
-
-/** Test-only injection: replace the model + model runtime with a scripted fake. */
-export interface TeacherRuntimeOverrides {
- model?: unknown;
- modelRuntime?: unknown;
-}
-
-export function buildTeacherRuntimeFactory(
- mode: TeacherMode = "tui",
- overrides?: TeacherRuntimeOverrides,
-): CreateAgentSessionRuntimeFactory {
- // Terminal mode: quiz/ask block on interactive pi-tui components.
- // Web mode: quiz/ask use the dispatch shape (return immediately; the graded
- // answer arrives as the next user message — see extensions/quiz-web.ts).
- const extensionFiles =
- mode === "web"
- ? [path.join(PATHS.extensions, "quiz-web.ts"), path.join(PATHS.extensions, "ask-web.ts")]
- : [path.join(PATHS.extensions, "quiz.ts"), path.join(PATHS.extensions, "ask-user-question.ts")];
- return async ({ cwd, sessionManager, sessionStartEvent }) => {
- const services = await createAgentSessionServices({
- cwd,
- agentDir: PATHS.agentDir,
- resourceLoaderOptions: {
- // Only the extensions we explicitly list. No discovery of ~/.pi/agent
- // or .pi/extensions beyond what agentDir/cwd contain (they contain none).
- additionalExtensionPaths: extensionFiles,
- // The teacher system prompt — replaces pi's coding prompt entirely.
- systemPrompt: PATHS.teacherPrompt,
- // Hermetic: no skills, no prompt templates, no AGENTS.md context files.
- // Themes stay enabled for a good TUI.
- noSkills: true,
- noPromptTemplates: true,
- noContextFiles: true,
- },
- });
- // Test injection: swap the real model runtime for a scripted fake BEFORE
- // the session is constructed from services. Production never passes this.
- if (overrides?.modelRuntime) {
- (services as { modelRuntime: unknown }).modelRuntime = overrides.modelRuntime;
- }
- return {
- ...(await createAgentSessionFromServices({
- services,
- sessionManager,
- sessionStartEvent,
- // No read/bash/edit/write. quiz + ask_user_question (extension tools)
- // remain enabled.
- noTools: "builtin",
- model: overrides?.model as never,
- })),
- services,
- diagnostics: services.diagnostics,
- };
- };
-}
-
-/** Launch the interactive teaching session (full pi TUI). */
-export async function runTeachingSession(initialMessage?: string): Promise {
- const createRuntime = buildTeacherRuntimeFactory();
- const sessionManager = SessionManager.create(PATHS.root, PATHS.sessions);
-
- const runtime = await createAgentSessionRuntime(createRuntime, {
- cwd: PATHS.root,
- agentDir: PATHS.agentDir,
- sessionManager,
- });
-
- for (const diagnostic of runtime.diagnostics) {
- console.error(`[${diagnostic.type}] ${diagnostic.message}`);
- }
- if (runtime.diagnostics.some((d) => d.type === "error")) {
- console.error("Fatal diagnostics — aborting.");
- process.exit(1);
- }
-
- initTheme("dark", true);
-
- const mode = new InteractiveMode(runtime, {
- migratedProviders: [],
- modelFallbackMessage: undefined,
- initialMessage,
- initialImages: [],
- initialMessages: [],
- });
- await mode.run();
-}
-
-/** Non-interactive smoke test: verify the whole wiring boots correctly. */
-export async function doctor(mode: TeacherMode = "tui"): Promise {
- const sessionManager = SessionManager.inMemory(PATHS.root);
- const runtime = await buildTeacherRuntimeFactory(mode)({
- cwd: PATHS.root,
- agentDir: PATHS.agentDir,
- sessionManager,
- sessionStartEvent: { type: "session_start", reason: "startup" },
- });
- const { session, services } = runtime;
-
- for (const diagnostic of runtime.diagnostics) {
- console.log(`[${diagnostic.type}] ${diagnostic.message}`);
- }
-
- const extensions = services.resourceLoader.getExtensions();
- console.log("\nExtensions loaded:");
- for (const ext of extensions.extensions) {
- const tools = Array.from(ext.tools.keys());
- console.log(` - ${path.basename(ext.path)} tools: ${tools.join(", ") || "(none)"}`);
- }
- if (extensions.errors.length > 0) {
- console.log("Extension errors:");
- for (const { path: p, error } of extensions.errors) console.log(` ! ${p}: ${error}`);
- }
-
- const state = session.agent.state;
- console.log(`\nTools visible to the agent: ${state.tools.map((t) => t.name).join(", ")}`);
- console.log(`Model: ${state.model ? `${state.model.provider}/${state.model.id}` : "(none — restore from settings failed)"}`);
- console.log(`Thinking level: ${state.thinkingLevel}`);
-
- const prompt = state.systemPrompt;
- console.log(`\nSystem prompt: ${prompt.length} chars`);
- console.log("--- first 300 chars ---");
- console.log(prompt.slice(0, 300));
- console.log("--- last 200 chars ---");
- console.log(prompt.slice(-200));
-
- const expectedTools = ["quiz", "ask_user_question"];
- const toolNames = state.tools.map((t) => t.name);
- const missing = expectedTools.filter((t) => !toolNames.includes(t));
- const leaked = toolNames.filter((t) => ["read", "bash", "edit", "write", "grep", "find", "ls"].includes(t));
- console.log(`\n${missing.length === 0 ? "✓" : "✗"} expected tools present${missing.length ? ` — MISSING: ${missing.join(", ")}` : ""}`);
- console.log(`${leaked.length === 0 ? "✓" : "✗"} no coding tools leaked${leaked.length ? ` — LEAKED: ${leaked.join(", ")}` : ""}`);
- console.log(`${prompt.includes("You are the teacher agent of a learning harness") ? "✓" : "✗"} teacher system prompt active`);
- console.log(`${prompt.includes("quiz") && prompt.includes("ask_user_question") ? "✓" : "✗"} tool guidelines folded into system prompt`);
-
- session.dispose();
-}
diff --git a/src/testing/scripted-model.ts b/src/testing/scripted-model.ts
deleted file mode 100644
index ed79406..0000000
--- a/src/testing/scripted-model.ts
+++ /dev/null
@@ -1,228 +0,0 @@
-/**
- * scripted-model — a deterministic fake LLM for integration tests.
- *
- * Replaces the real model runtime inside the teacher session: the FULL pi
- * agent loop runs (extensions, tool execution, session persistence, event
- * stream), but every assistant response comes from a fixed script. No network,
- * no tokens, no variance — the same script always produces the same session.
- */
-
-// AgentMessage comes from a transitive dep that is not directly importable;
-// a structural local type is enough for the helpers below.
-export interface AnyMessage {
- role?: string;
- content?: Array<{ type?: string; text?: string }>;
-}
-
-// ─── types ──────────────────────────────────────────────────────────────────
-
-export interface ScriptedToolCall {
- name: string;
- arguments: Record;
-}
-
-/** One scripted assistant turn: reasoning, tool calls, and/or final text. */
-export interface ScriptedTurn {
- text?: string;
- thinking?: string;
- toolCalls?: ScriptedToolCall[];
-}
-
-interface FakeAssistantMessage {
- role: "assistant";
- content: Array>;
- api: string;
- provider: string;
- model: string;
- usage: Record;
- stopReason: string;
-}
-
-// ─── fake event stream (what the agent loop consumes) ──────────────────────
-
-class FakeEventStream {
- private queue: Array> = [];
- private done = false;
- private resolvers: Array<(r: IteratorResult>) => void> = [];
- private finalMessage: FakeAssistantMessage | null = null;
- private resultResolve!: (m: FakeAssistantMessage) => void;
- readonly resultPromise: Promise;
-
- constructor() {
- this.resultPromise = new Promise((resolve) => {
- this.resultResolve = resolve;
- });
- }
-
- push(event: Record): void {
- if (this.done) return;
- const r = this.resolvers.shift();
- if (r) r({ value: event, done: false });
- else this.queue.push(event);
- }
-
- end(message: FakeAssistantMessage): void {
- this.done = true;
- this.finalMessage = message;
- this.resultResolve(message);
- while (this.resolvers.length > 0) {
- this.resolvers.shift()!({ value: undefined as never, done: true });
- }
- }
-
- async result(): Promise {
- return this.resultPromise;
- }
-
- [Symbol.asyncIterator](): AsyncIterator> {
- return {
- next: (): Promise>> => {
- const queued = this.queue.shift();
- if (queued !== undefined) return Promise.resolve({ value: queued, done: false });
- if (this.done) return Promise.resolve({ value: undefined as never, done: true });
- return new Promise((resolve) => this.resolvers.push(resolve));
- },
- };
- }
-}
-
-// ─── fake model + runtime ───────────────────────────────────────────────────
-
-function baseMessage(turnIndex: number): FakeAssistantMessage {
- return {
- role: "assistant",
- content: [],
- api: "openai-completions",
- provider: "scripted",
- model: `scripted-turn-${turnIndex}`,
- usage: { input: 100, output: 10, cacheRead: 0, cacheWrite: 0, totalTokens: 110, cost: { input: 0, output: 0, total: 0 } },
- stopReason: "stop",
- };
-}
-
-export interface ScriptedRuntime {
- /** The model object to pass as `model` when creating the session. */
- model: Record;
- /** The ModelRuntime replacement to inject into services. */
- modelRuntime: {
- getModel: () => unknown;
- hasConfiguredAuth: () => boolean;
- streamSimple: (model: unknown, context: unknown, options?: unknown) => FakeEventStream;
- };
- /** All contexts the "model" was called with (for assertions). */
- contextsSeen: Array<{ systemPrompt?: string; messages: unknown[] }>;
-}
-
-export function createScriptedRuntime(script: ScriptedTurn[]): ScriptedRuntime {
- let turnIndex = 0;
- const contextsSeen: Array<{ systemPrompt?: string; messages: unknown[] }> = [];
-
- const model = {
- id: "scripted-model",
- name: "Scripted Test Model",
- api: "openai-completions",
- provider: "scripted",
- baseUrl: "http://localhost:0",
- reasoning: false,
- input: ["text"] as const,
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
- contextWindow: 200_000,
- maxTokens: 8_192,
- };
-
- function nextStream(): FakeEventStream {
- const stream = new FakeEventStream();
- const turn = script[turnIndex] ?? { text: "(script exhausted)" };
- turnIndex++;
-
- const msg = baseMessage(turnIndex);
- const content: Array> = [];
-
- // "start"
- stream.push({ type: "start", partial: { ...msg, content: [] } });
-
- // thinking (reasoning) — split into two deltas so accumulation is tested
- if (turn.thinking !== undefined && turn.thinking.length > 0) {
- content.push({ type: "thinking", thinking: turn.thinking });
- const idx = content.length - 1;
- stream.push({ type: "thinking_start", contentIndex: idx, partial: { ...msg, content: [...content] } });
- const mid = Math.ceil(turn.thinking.length / 2);
- stream.push({ type: "thinking_delta", contentIndex: idx, delta: turn.thinking.slice(0, mid), partial: { ...msg, content: [...content] } });
- stream.push({ type: "thinking_delta", contentIndex: idx, delta: turn.thinking.slice(mid), partial: { ...msg, content: [...content] } });
- stream.push({ type: "thinking_end", contentIndex: idx, content: turn.thinking, partial: { ...msg, content: [...content] } });
- }
-
- // tool calls
- for (const tc of turn.toolCalls ?? []) {
- const toolCall = {
- type: "toolCall",
- id: `call_${turnIndex}_${content.length}`,
- name: tc.name,
- arguments: tc.arguments,
- };
- content.push(toolCall);
- stream.push({
- type: "toolcall_end",
- contentIndex: content.length - 1,
- toolCall,
- partial: { ...msg, content: [...content] },
- });
- }
-
- // text
- if (turn.text !== undefined && turn.text.length > 0) {
- content.push({ type: "text", text: turn.text });
- stream.push({
- type: "text_start",
- contentIndex: content.length - 1,
- partial: { ...msg, content: [...content] },
- });
- stream.push({
- type: "text_delta",
- contentIndex: content.length - 1,
- delta: turn.text,
- partial: { ...msg, content: [...content] },
- });
- stream.push({
- type: "text_end",
- contentIndex: content.length - 1,
- content: turn.text,
- partial: { ...msg, content: [...content] },
- });
- }
-
- // "done" + result
- const final = { ...msg, content, stopReason: turn.toolCalls?.length ? "toolUse" : "stop" };
- stream.push({
- type: "done",
- reason: turn.toolCalls?.length ? "toolUse" : "stop",
- message: final,
- });
- stream.end(final);
- return stream;
- }
-
- return {
- model,
- contextsSeen,
- modelRuntime: {
- getModel: () => model,
- hasConfiguredAuth: () => true,
- streamSimple: (_model: unknown, context: unknown) => {
- const ctx = context as { systemPrompt?: string; messages: unknown[] };
- contextsSeen.push(ctx);
- return nextStream();
- },
- },
- };
-}
-
-// ─── helpers for assertions on session messages ─────────────────────────────
-
-export function messageText(m: AnyMessage): string {
- const content = (m as { content?: Array<{ type?: string; text?: string }> }).content ?? [];
- return content
- .filter((c) => c.type === "text" && typeof c.text === "string")
- .map((c) => c.text)
- .join("");
-}
diff --git a/tests/bridge.test.ts b/tests/bridge.test.ts
deleted file mode 100644
index 641cb88..0000000
--- a/tests/bridge.test.ts
+++ /dev/null
@@ -1,171 +0,0 @@
-/**
- * Unit tests — the question bridge (state machine, fake backends only).
- *
- * The bridge is globalThis-singleton state shared with other tests in the same
- * process, so every test creates its own bridge instance via the class-free
- * export path... (it doesn't export the class — we exercise the singleton and
- * reset it between tests with clear()).
- */
-
-import assert from "node:assert/strict";
-import { beforeEach, describe, it } from "node:test";
-import {
- type AnswerRequest,
- type QuestionData,
- QUESTION_ANSWERED_ENTRY,
- QUESTION_ENTRY,
-} from "../extensions/core/question-core.ts";
-import { getBridge } from "../extensions/core/bridge.ts";
-
-interface Recorded {
- answered: unknown[];
- messages: string[];
- entries: Array<{ type: string; customType?: string; data?: unknown }>;
-}
-
-function fakeBackend(kind: "quiz" | "ask"): { recorded: Recorded } {
- const recorded: Recorded = { answered: [], messages: [], entries: [] };
- getBridge().registerBackend({
- kind,
- appendAnswered: (data) => recorded.answered.push(data),
- sendUserMessage: (text) => recorded.messages.push(text),
- getEntries: () => recorded.entries,
- });
- return { recorded };
-}
-
-function quizData(id: string): QuestionData {
- return {
- id,
- kind: "quiz",
- question: `Question ${id}?`,
- mode: "single-select",
- options: [
- { label: "Right", value: "right" },
- { label: "Wrong", value: "wrong" },
- ],
- correctIndices: [1],
- explanation: "because",
- createdAt: new Date().toISOString(),
- };
-}
-
-describe("question bridge", () => {
- let quiz: { recorded: Recorded };
- let ask: { recorded: Recorded };
-
- beforeEach(() => {
- const bridge = getBridge();
- bridge.clear("quiz");
- bridge.clear("ask");
- quiz = fakeBackend("quiz");
- ask = fakeBackend("ask");
- });
-
- it("publish makes a question answerable; answer grades and sends a user message", () => {
- const bridge = getBridge();
- const qd = quizData("q1");
- bridge.publish(qd);
-
- const outcome = bridge.answer("q1", { values: ["right"] });
- assert.equal(outcome.ok, true);
-
- assert.equal(quiz.recorded.answered.length, 1);
- const ad = quiz.recorded.answered[0] as { correct: boolean; questionId: string };
- assert.equal(ad.questionId, "q1");
- assert.equal(ad.correct, true);
-
- assert.equal(quiz.recorded.messages.length, 1);
- assert.ok(quiz.recorded.messages[0].startsWith("[Quiz answer]"));
- // ask backend untouched
- assert.equal(ask.recorded.messages.length, 0);
- });
-
- it("answering a question twice fails the second time", () => {
- const bridge = getBridge();
- bridge.publish(quizData("q2"));
- assert.equal(bridge.answer("q2", { values: ["wrong"] }).ok, true);
- const second = bridge.answer("q2", { values: ["right"] });
- assert.equal(second.ok, false);
- assert.match(second.error ?? "", /already-answered/);
- });
-
- it("answering an unknown question fails", () => {
- const outcome = getBridge().answer("nope", { values: [] });
- assert.equal(outcome.ok, false);
- });
-
- it("dismiss records a dismissed answer and notifies the agent", () => {
- const bridge = getBridge();
- bridge.publish(quizData("q3"));
- assert.equal(bridge.dismiss("q3").ok, true);
-
- const ad = quiz.recorded.answered[0] as { dismissed: boolean };
- assert.equal(ad.dismissed, true);
- assert.match(quiz.recorded.messages[0], /dismissed/);
- });
-
- it("dontKnow answers are never correct", () => {
- const bridge = getBridge();
- bridge.publish(quizData("q4"));
- bridge.answer("q4", { dontKnow: true });
- const ad = quiz.recorded.answered[0] as { dontKnow: boolean; correct: boolean };
- assert.equal(ad.dontKnow, true);
- assert.equal(ad.correct, false);
- });
-
- it("rearm picks up unanswered questions from session entries", () => {
- const bridge = getBridge();
- // Simulate a resumed session whose entries contain an unanswered question.
- quiz.recorded.entries = [
- { type: "custom", customType: QUESTION_ENTRY, data: quizData("q5") },
- {
- type: "custom",
- customType: QUESTION_ANSWERED_ENTRY,
- data: { questionId: "other", answers: [] },
- },
- ];
- bridge.rearm("quiz");
-
- // Now answerable through the bridge.
- assert.equal(bridge.answer("q5", { values: ["right"] }).ok, true);
- });
-
- it("rearm ignores already-answered questions", () => {
- const bridge = getBridge();
- quiz.recorded.entries = [
- { type: "custom", customType: QUESTION_ENTRY, data: quizData("q6") },
- {
- type: "custom",
- customType: QUESTION_ANSWERED_ENTRY,
- data: { questionId: "q6", answers: [], dontKnow: true },
- },
- ];
- bridge.rearm("quiz");
- const outcome = bridge.answer("q6", { values: ["right"] });
- assert.equal(outcome.ok, false);
- });
-
- it("clear removes only its kind's pending questions", () => {
- const bridge = getBridge();
- bridge.publish(quizData("q7"));
- const askQd: QuestionData = { ...quizData("q8"), kind: "ask", correctIndices: undefined };
- bridge.publish(askQd);
-
- bridge.clear("quiz");
- assert.equal(bridge.answer("q7", { values: ["right"] }).ok, false);
- assert.equal(bridge.answer("q8", { other: "x" }).ok, true);
- });
-
- it("ask answers route to the ask backend ungraded", () => {
- const bridge = getBridge();
- const askQd: QuestionData = { ...quizData("q9"), kind: "ask", correctIndices: undefined };
- bridge.publish(askQd);
-
- bridge.answer("q9", { other: "custom answer" });
- const ad = ask.recorded.answered[0] as { other?: string; correct?: boolean };
- assert.equal(ad.other, "custom answer");
- assert.equal(ad.correct, undefined);
- assert.ok(ask.recorded.messages[0].startsWith("[Answer to your question]"));
- });
-});
diff --git a/tests/chat-flow.test.ts b/tests/chat-flow.test.ts
new file mode 100644
index 0000000..eebd9d8
--- /dev/null
+++ b/tests/chat-flow.test.ts
@@ -0,0 +1,218 @@
+/**
+ * Scripted integration test — the full teacher loop with zero tokens:
+ *
+ * learner opens chat → mock model calls `quiz` → server streams + persists
+ * → client grades and sends the answer back as the tool output
+ * → mock model reacts → transcript persisted.
+ *
+ * Drives handleChatRequest directly (plain Request/Response — no Next server
+ * needed) with a MockLanguageModelV4. This is the AI SDK equivalent of the
+ * old pi dispatch contract test: the quiz bridge is gone, the tool part IS
+ * the pending state, and the persisted transcript is the resume record.
+ */
+
+import assert from "node:assert/strict";
+import * as fs from "node:fs";
+import * as path from "node:path";
+import { after, describe, it } from "node:test";
+import { MockLanguageModelV4, simulateReadableStream } from "ai/test";
+import type { LanguageModelV4StreamPart } from "@ai-sdk/provider";
+import { createTeacherAgent } from "../src/agent/teacher-agent.ts";
+import { handleChatRequest } from "../src/server/chat-handler.ts";
+import { createChat, getChat, saveMessages } from "../src/store/chats.ts";
+import { buildAnswer, coerceCorrectAnswer, normalizeOptions, resolveCorrect, type QuestionData } from "../src/core/question-core.ts";
+import { findPendingToolCall } from "../src/core/pending.ts";
+import { PATHS } from "../config.ts";
+
+const QUIZ_INPUT = {
+ question: "Which planet is closest to the sun?",
+ options: [
+ { label: "Venus" },
+ { label: "Mercury" },
+ { label: "Mars" },
+ ],
+ correctAnswer: "Mercury",
+ explanation: "Mercury is the innermost planet.",
+};
+
+function usage() {
+ return {
+ inputTokens: { total: 10, noCache: 10, cacheRead: 0, cacheWrite: 0 },
+ outputTokens: { total: 5, text: 5, reasoning: 0 },
+ };
+}
+
+/** Turn 1: a bit of reasoning, then the quiz tool call. Turn 2: plain text. */
+function scriptedModel(calls: string[][]) {
+ let turn = 0;
+ return new MockLanguageModelV4({
+ doStream: async (options) => {
+ const promptTexts = options.prompt.map((m) => JSON.stringify(m.content));
+ calls.push(promptTexts);
+ turn++;
+ const chunks: LanguageModelV4StreamPart[] =
+ turn === 1
+ ? [
+ { type: "stream-start", warnings: [] },
+ { type: "reasoning-start", id: "r1" },
+ { type: "reasoning-delta", id: "r1", delta: "Probe the basics first." },
+ { type: "reasoning-end", id: "r1" },
+ {
+ type: "tool-call",
+ toolCallId: "call-1",
+ toolName: "quiz",
+ input: JSON.stringify(QUIZ_INPUT),
+ },
+ { type: "finish", finishReason: { unified: "tool-calls", raw: "tool_calls" }, usage: usage() },
+ ]
+ : [
+ { type: "stream-start", warnings: [] },
+ { type: "text-start", id: "t1" },
+ { type: "text-delta", id: "t1", delta: "Right — Mercury it is." },
+ { type: "text-end", id: "t1" },
+ { type: "finish", finishReason: { unified: "stop", raw: "stop" }, usage: usage() },
+ ];
+ return { stream: simulateReadableStream({ chunks, initialDelayInMs: 0, chunkDelayInMs: 0 }) };
+ },
+ });
+}
+
+async function drain(res: Response): Promise {
+ if (res.status !== 200) {
+ assert.fail(`expected 200, got ${res.status}: ${await res.text()}`);
+ }
+ return res.text(); // consumes the SSE stream to completion (fires onFinish)
+}
+
+function post(chatId: string, messages: unknown[]): Request {
+ return new Request("http://test/api/chat", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ chatId, messages }),
+ });
+}
+
+const createdChats: string[] = [];
+after(() => {
+ for (const id of createdChats) {
+ try {
+ fs.unlinkSync(path.join(PATHS.chats, `${id}.json`));
+ } catch {
+ /* already gone */
+ }
+ }
+});
+
+describe("teacher chat flow (scripted model)", () => {
+ it("quiz tool call persists as a pending part; the answer continues the lesson", async () => {
+ const calls: string[][] = [];
+ const agent = createTeacherAgent(scriptedModel(calls));
+ const chat = createChat("Test: solar system");
+ createdChats.push(chat.id);
+
+ // ── Turn 1: learner opens with a topic; teacher probes with a quiz ──
+ const firstUser = { id: "u1", role: "user", parts: [{ type: "text", text: "teach me the solar system" }] };
+ await drain(await handleChatRequest(post(chat.id, [firstUser]), agent));
+
+ const saved1 = getChat(chat.id)!;
+ assert.equal(saved1.messages.length, 2, "user message + assistant response persisted");
+ const assistantMsg = saved1.messages[1]!;
+ assert.equal(assistantMsg.role, "assistant");
+
+ const reasoningPart = assistantMsg.parts.find((p) => p.type === "reasoning");
+ assert.ok(reasoningPart && reasoningPart.type === "reasoning", "reasoning part streamed + persisted");
+ assert.match(reasoningPart.text, /Probe the basics/);
+
+ const quizPart = assistantMsg.parts.find((p) => p.type === "tool-quiz");
+ assert.ok(quizPart && quizPart.type === "tool-quiz", "quiz tool part persisted");
+ assert.equal(quizPart.state, "input-available", "unanswered quiz = pending state (resume record)");
+ assert.deepEqual(quizPart.input, QUIZ_INPUT);
+
+ // The pending-question gate sees it.
+ const pending = findPendingToolCall(saved1.messages);
+ assert.deepEqual(pending, { toolCallId: "call-1", tool: "quiz" });
+
+ // ── The learner answers: client grades, output goes back on the part ──
+ const options = normalizeOptions(QUIZ_INPUT.options);
+ const qd: QuestionData = {
+ id: "call-1",
+ kind: "quiz",
+ question: QUIZ_INPUT.question,
+ mode: "single-select",
+ options,
+ correctIndices: resolveCorrect(coerceCorrectAnswer(QUIZ_INPUT.correctAnswer), options).indices,
+ explanation: QUIZ_INPUT.explanation,
+ createdAt: new Date().toISOString(),
+ };
+ const answer = buildAnswer(qd, { values: ["Mercury"] });
+ assert.equal(answer.correct, true);
+
+ const messages2 = saved1.messages.map((m) =>
+ m.id === assistantMsg.id
+ ? {
+ ...m,
+ parts: m.parts.map((p) =>
+ p.type === "tool-quiz" ? { ...p, state: "output-available" as const, output: answer } : p,
+ ),
+ }
+ : m,
+ );
+ await drain(await handleChatRequest(post(chat.id, messages2), agent));
+
+ // ── The teacher reacts — as a CONTINUATION of the same assistant message
+ // (the last client message is the assistant message carrying the tool
+ // output, so the new text appends to that message's parts). ──
+ const saved2 = getChat(chat.id)!;
+ assert.equal(saved2.messages.length, 2, "the reaction continues the same assistant message");
+ const finalParts = saved2.messages[1]!.parts;
+ const textPart = finalParts.find((p) => p.type === "text");
+ assert.ok(textPart && textPart.type === "text", "reaction text appended after the tool part");
+ assert.match(textPart.text, /Mercury it is/);
+ // The answered quiz part is still there, now with its output.
+ const answeredQuiz = finalParts.find((p) => p.type === "tool-quiz");
+ assert.ok(answeredQuiz && answeredQuiz.type === "tool-quiz" && answeredQuiz.state === "output-available");
+
+ // The model actually received the graded answer as a tool result.
+ assert.equal(calls.length, 2, "model saw exactly two prompts");
+ const toolResultSent = calls[1]!.some((c) => c.includes("tool-result") || c.includes("Mercury"));
+ assert.ok(toolResultSent, "continuation prompt carries the tool result to the model");
+
+ // No pending question remains.
+ assert.equal(findPendingToolCall(saved2.messages), null);
+ });
+
+ it("rejects unknown chats", async () => {
+ const res = await handleChatRequest(post("chat_nonexistent", []));
+ assert.equal(res.status, 404);
+ });
+
+ it("persists even when the stream is aborted (resume keeps partial state)", async () => {
+ const calls: string[][] = [];
+ const agent = createTeacherAgent(scriptedModel(calls));
+ const chat = createChat("Test: abort");
+ createdChats.push(chat.id);
+
+ const req = post(chat.id, [{ id: "u1", role: "user", parts: [{ type: "text", text: "quiz me" }] }]);
+ const res = await handleChatRequest(req, agent);
+ assert.equal(res.status, 200);
+ // Read one chunk then cancel — simulates the browser closing mid-stream.
+ const reader = res.body!.getReader();
+ await reader.read();
+ await reader.cancel();
+ // Give onFinish a tick to fire.
+ await new Promise((r) => setTimeout(r, 50));
+ const saved = getChat(chat.id)!;
+ assert.ok(saved.messages.length >= 1, "partial transcript persisted");
+ });
+});
+
+describe("chat store", () => {
+ it("round-trips and lists chats", async () => {
+ const chat = createChat("Store test");
+ createdChats.push(chat.id);
+ saveMessages(chat.id, [{ id: "m1", role: "user", parts: [{ type: "text", text: "hi" }] }]);
+ const loaded = getChat(chat.id)!;
+ assert.equal(loaded.messages.length, 1);
+ assert.equal(loaded.title, "Store test");
+ });
+});
diff --git a/tests/integration-quiz.test.ts b/tests/integration-quiz.test.ts
deleted file mode 100644
index b6056e4..0000000
--- a/tests/integration-quiz.test.ts
+++ /dev/null
@@ -1,246 +0,0 @@
-/**
- * Integration test — the full teaching flow with a SCRIPTED model.
- *
- * Real pi agent loop, real web extensions (quiz-web/ask-web), real bridge,
- * real session persistence — the only fake is the LLM. Verifies the dispatch
- * contract end to end:
- *
- * prompt → scripted turn calls quiz → extension persists lh_question and
- * returns immediately → agent ends turn → bridge.answer() grades and sends
- * the [Quiz answer] user message → scripted final turn → settle.
- *
- * Then reloads the session file into a NEW runtime (resume) and verifies the
- * pending-question re-arm from persisted entries.
- */
-
-import assert from "node:assert/strict";
-import fs from "node:fs";
-import os from "node:os";
-import path from "node:path";
-import { after, before, describe, it } from "node:test";
-import { createAgentSessionRuntime, SessionManager } from "@earendil-works/pi-coding-agent";
-import { PATHS } from "../config.ts";
-import { buildTeacherRuntimeFactory } from "../src/teacher/create-session.ts";
-import {
- QUESTION_ANSWERED_ENTRY,
- QUESTION_ENTRY,
- findPendingQuestion,
- toClientQuestion,
- type QuestionAnswerData,
- type QuestionData,
-} from "../extensions/core/question-core.ts";
-import { getBridge } from "../extensions/core/bridge.ts";
-import { createScriptedRuntime } from "../src/testing/scripted-model.ts";
-
-const QUIZ_ARGS = {
- question: "Which is closest to the Sun?",
- options: [
- { label: "Mercury", value: "mercury" },
- { label: "Venus", value: "venus" },
- ],
- correctAnswer: "mercury",
- explanation: "Mercury is the innermost planet.",
-};
-
-let tmpDir: string;
-let sessionDir: string;
-
-before(() => {
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "lh-int-"));
- sessionDir = path.join(tmpDir, "sessions");
- fs.mkdirSync(sessionDir, { recursive: true });
-});
-
-after(() => {
- fs.rmSync(tmpDir, { recursive: true, force: true });
-});
-
-function entriesOf(sm: SessionManager) {
- return sm.getEntries() as Array<{ type: string; customType?: string; data?: unknown; id: string }>;
-}
-
-function customEntries(sm: SessionManager, customType: string) {
- return entriesOf(sm).filter((e) => e.type === "custom" && e.customType === customType);
-}
-
-function userMessages(sm: SessionManager) {
- return entriesOf(sm)
- .filter((e) => e.type === "message" && (e as { message: { role: string } }).message.role === "user")
- .map((e) => (e as unknown as { message: { content: Array<{ type: string; text?: string }> } }).message);
-}
-
-function waitForIdle(session: { waitForIdle(): Promise }, timeoutMs = 10_000): Promise {
- return Promise.race([
- session.waitForIdle(),
- new Promise((_, reject) => setTimeout(() => reject(new Error("timeout waiting for idle")), timeoutMs)),
- ]);
-}
-
-/** Poll until pred() is true (bridge sendUserMessage is fire-and-forget async,
- * so waitForIdle alone can win a race against the incoming prompt). */
-function waitFor(pred: () => boolean, what: string, timeoutMs = 10_000): Promise {
- const start = Date.now();
- return new Promise((resolve, reject) => {
- const tick = () => {
- if (pred()) return resolve();
- if (Date.now() - start > timeoutMs) return reject(new Error(`timeout waiting for ${what}`));
- setTimeout(tick, 25);
- };
- tick();
- });
-}
-
-describe("scripted teaching flow (web dispatch mode)", () => {
- it("runs the quiz dispatch cycle and persists gold-standard data", async () => {
- const scripted = createScriptedRuntime([
- // Turn 1: call quiz, end turn (toolUse stop reason).
- { toolCalls: [{ name: "quiz", arguments: QUIZ_ARGS }] },
- // Turn 2 (after the tool result, model declines to continue): final text.
- { text: "Take a shot." },
- // Turn 3 (after the [Quiz answer] user message): wrap up.
- { text: "Right — Mercury it is. Lesson over." },
- ]);
-
- const sessionManager = SessionManager.create(PATHS.root, sessionDir);
- const runtime = await createAgentSessionRuntime(buildTeacherRuntimeFactory("web", {
- model: scripted.model,
- modelRuntime: scripted.modelRuntime,
- }), {
- cwd: PATHS.root,
- agentDir: PATHS.agentDir,
- sessionManager,
- });
- assert.ok(!runtime.diagnostics.some((d) => d.type === "error"), "no fatal diagnostics");
-
- // ── Turn 1: prompt → quiz presented ────────────────────────────────
- const promptDone = runtime.session.prompt("Teach me about the solar system.");
- await waitForIdle(runtime.session);
- await promptDone;
-
- const questions = customEntries(sessionManager, QUESTION_ENTRY) as Array<{ data?: QuestionData }>;
- assert.equal(questions.length, 1, "exactly one lh_question entry");
- const qd = questions[0]!.data!;
- assert.equal(qd.kind, "quiz");
- assert.equal(qd.mode, "single-select");
- assert.deepEqual(qd.correctIndices, [qd.options.findIndex((o) => o.value === "mercury") + 1], "correct index resolved post-shuffle");
- assert.equal(qd.explanation, QUIZ_ARGS.explanation);
-
- // Pending question visible without leaking the answer.
- const pending = findPendingQuestion(entriesOf(sessionManager));
- assert.ok(pending, "pending question found");
- const clientPayload = JSON.stringify(toClientQuestion(qd));
- assert.ok(!clientPayload.includes("correctIndices"));
- assert.ok(!clientPayload.includes("Mercury is the innermost"));
-
- // ── Answer: graded + injected as user message ──────────────────────
- const outcome = getBridge().answer(qd.id, { values: ["mercury"] });
- assert.equal(outcome.ok, true);
- await waitFor(() => userMessages(sessionManager).length === 2, "quiz answer user message");
- await waitForIdle(runtime.session);
-
- const answers = customEntries(sessionManager, QUESTION_ANSWERED_ENTRY) as Array<{
- data?: QuestionAnswerData;
- }>;
- assert.equal(answers.length, 1, "exactly one lh_question_answered entry");
- const ad = answers[0]!.data!;
- assert.equal(ad.correct, true);
- assert.equal(ad.questionId, qd.id);
- assert.equal(ad.answers.length, 1);
- assert.equal(ad.answers[0]!.value, "mercury");
-
- const users = userMessages(sessionManager);
- assert.equal(users.length, 2, "topic prompt + quiz answer");
- const answerMsg = users[1]!.content.find((c) => c.type === "text")!.text!;
- assert.ok(answerMsg.startsWith("[Quiz answer]"), "answer arrives as user message");
- assert.match(answerMsg, /Result: CORRECT/);
- assert.match(answerMsg, /Selected: \d\. Mercury/);
-
- // Pending is cleared after answering.
- assert.equal(findPendingQuestion(entriesOf(sessionManager)), null);
-
- // The scripted model saw the answer in its context (turn 3).
- assert.ok(scripted.contextsSeen.length >= 3);
- const lastContext = scripted.contextsSeen[scripted.contextsSeen.length - 1]!;
- const lastMessages = JSON.stringify(lastContext.messages);
- assert.ok(lastMessages.includes("[Quiz answer]"), "answer message reached the LLM context");
- // …but the tool RESULT must never reveal the answer (the correct answer
- // and explanation live only in the lh_question custom entry, which is
- // excluded from LLM context). The toolCall arguments legitimately contain
- // them — the model wrote those itself.
- for (const ctx of scripted.contextsSeen) {
- const messages = ctx.messages as Array<{ role?: string; content?: Array<{ type?: string; text?: string; name?: string }> }>;
- for (const m of messages) {
- if (m.role !== "toolResult") continue;
- for (const c of m.content ?? []) {
- if (c.type === "text" && c.text) {
- assert.ok(!c.text.includes("Mercury is the innermost"), "no explanation leak via tool result");
- assert.ok(!/correct/i.test(c.text), "no correct-answer leak via tool result");
- }
- }
- }
- }
-
- runtime.session.dispose();
-
- // ── Resume: pending re-arm from the session file ───────────────────
- const sessionFile = sessionManager.getSessionFile()!;
- const sm2 = SessionManager.open(sessionFile, sessionDir, PATHS.root);
- const scripted2 = createScriptedRuntime([{ text: "resumed" }]);
- const runtime2 = await createAgentSessionRuntime(buildTeacherRuntimeFactory("web", {
- model: scripted2.model,
- modelRuntime: scripted2.modelRuntime,
- }), {
- cwd: PATHS.root,
- agentDir: PATHS.agentDir,
- sessionManager: sm2,
- });
- // session_start fired during construction → extensions re-armed.
- assert.equal(findPendingQuestion(entriesOf(sm2)), null, "answered question is NOT re-armed");
- assert.equal(customEntries(sm2, QUESTION_ENTRY).length, 1, "question entry survived on disk");
- runtime2.session.dispose();
-
- // ── Resume with an UNANSWERED question pending ─────────────────────
- // New session, fresh script: quiz presented but never answered.
- const scripted3 = createScriptedRuntime([
- { toolCalls: [{ name: "quiz", arguments: QUIZ_ARGS }] },
- { text: "Waiting for your answer." },
- ]);
- const sm3 = SessionManager.create(PATHS.root, sessionDir);
- const runtime3 = await createAgentSessionRuntime(buildTeacherRuntimeFactory("web", {
- model: scripted3.model,
- modelRuntime: scripted3.modelRuntime,
- }), {
- cwd: PATHS.root,
- agentDir: PATHS.agentDir,
- sessionManager: sm3,
- });
- const p3 = runtime3.session.prompt("Teach me something else.");
- await waitForIdle(runtime3.session);
- await p3;
- runtime3.session.dispose();
-
- const file3 = sm3.getSessionFile()!;
- const sm4 = SessionManager.open(file3, sessionDir, PATHS.root);
- const scripted4 = createScriptedRuntime([{ text: "ok" }]);
- const runtime4 = await createAgentSessionRuntime(buildTeacherRuntimeFactory("web", {
- model: scripted4.model,
- modelRuntime: scripted4.modelRuntime,
- }), {
- cwd: PATHS.root,
- agentDir: PATHS.agentDir,
- sessionManager: sm4,
- });
- const reArmed = findPendingQuestion(entriesOf(sm4));
- assert.ok(reArmed, "unanswered question re-arms on resume");
- assert.equal(reArmed!.kind, "quiz");
- // And it is answerable through the bridge (the re-armed id).
- const answerOutcome = getBridge().answer(reArmed!.id, { dontKnow: true, note: "forgot" });
- assert.equal(answerOutcome.ok, true);
- const answeredOnDisk = customEntries(sm4, QUESTION_ANSWERED_ENTRY) as Array<{
- data?: QuestionAnswerData;
- }>;
- assert.equal(answeredOnDisk.length, 1);
- assert.equal(answeredOnDisk[0]!.data!.dontKnow, true);
- runtime4.session.dispose();
- });
-});
diff --git a/tests/question-core.test.ts b/tests/question-core.test.ts
index 5c3bb30..bfb3f8b 100644
--- a/tests/question-core.test.ts
+++ b/tests/question-core.test.ts
@@ -1,5 +1,5 @@
/**
- * Unit tests — question-core (pure logic, no pi, no server, no LLM).
+ * Unit tests — question-core (pure logic, no server, no LLM).
*/
import assert from "node:assert/strict";
@@ -18,7 +18,7 @@ import {
shuffleOptions,
type QuestionData,
toClientQuestion,
-} from "../extensions/core/question-core.ts";
+} from "../src/core/question-core.ts";
// ─── normalizeOptions ───────────────────────────────────────────────────────
diff --git a/tests/render-cards.test.tsx b/tests/render-cards.test.tsx
new file mode 100644
index 0000000..a29ac82
--- /dev/null
+++ b/tests/render-cards.test.tsx
@@ -0,0 +1,104 @@
+/**
+ * Render smoke tests — the lesson from the v2 bugs: protocol tests don't see
+ * rendering. These render the interaction cards to static HTML and assert on
+ * the output. No DOM needed (react-dom/server).
+ *
+ * tsx note: tsx ignores tsconfig's `jsx` field and uses the CLASSIC JSX
+ * transform (React.createElement), so `React` must be a resolvable global.
+ * The shim below provides it; Vite/Next use the automatic runtime and don't
+ * care.
+ */
+
+import React from "react";
+(globalThis as Record).React = React;
+
+import assert from "node:assert/strict";
+import { describe, it } from "node:test";
+import { renderToStaticMarkup } from "react-dom/server";
+import { QuizCard } from "../components/quiz-card.tsx";
+import { AnsweredCard } from "../components/answered-card.tsx";
+import { buildAnswer, coerceCorrectAnswer, normalizeOptions, resolveCorrect, type QuestionData } from "../src/core/question-core.ts";
+
+const quizInput = {
+ question: "Which planet is closest to the sun?",
+ options: [{ label: "Venus" }, { label: "Mercury" }, { label: "Mars" }],
+ correctAnswer: "Mercury",
+ explanation: "Mercury is the innermost planet.",
+};
+
+function quizQD(): QuestionData {
+ const options = normalizeOptions(quizInput.options);
+ return {
+ id: "q1",
+ kind: "quiz",
+ question: quizInput.question,
+ mode: "single-select",
+ options,
+ correctIndices: resolveCorrect(coerceCorrectAnswer(quizInput.correctAnswer), options).indices,
+ explanation: quizInput.explanation,
+ createdAt: new Date().toISOString(),
+ };
+}
+
+describe("render smoke tests", () => {
+ it("QuizCard renders question, options, and the automatic 'I don't know'", () => {
+ const html = renderToStaticMarkup(
+ {}} />,
+ );
+ assert.match(html, /Which planet is closest to the sun\?/);
+ assert.match(html, /Mercury/);
+ assert.match(html, /I don't know|I don't know/);
+ // The v1 desync bug: options are buttons with ARIA roles, not native inputs.
+ assert.ok(!html.includes('type="radio"'), "no native radio inputs");
+ assert.ok(html.includes('role="radio"'), "button-based radio role present");
+ });
+
+ it("QuizCard never leaks the explanation or the correct answer before submit", () => {
+ const html = renderToStaticMarkup(
+ {}} />,
+ );
+ assert.ok(!html.includes("innermost"), "explanation must not render pre-answer");
+ });
+
+ it("ask card renders Other + note and no grading affordance", () => {
+ const html = renderToStaticMarkup(
+ {}}
+ />,
+ );
+ assert.match(html, /Other/);
+ assert.ok(!html.includes("I don't know"), "ask has no 'I don't know'");
+ });
+
+ it("AnsweredCard grades quiz answers (✓/✗ + explanation)", () => {
+ const answer = buildAnswer(quizQD(), { values: ["Venus"] });
+ const html = renderToStaticMarkup();
+ assert.match(html, /incorrect/);
+ assert.match(html, /innermost/, "explanation renders post-answer");
+ assert.ok(html.includes("✓") && html.includes("✗"));
+ });
+
+ it("AnsweredCard does NOT grade ask answers", () => {
+ const qd: QuestionData = {
+ id: "a1",
+ kind: "ask",
+ question: "How confident are you?",
+ mode: "single-select",
+ options: normalizeOptions([{ label: "Very" }, { label: "Somewhat" }]),
+ createdAt: new Date().toISOString(),
+ };
+ const answer = buildAnswer(qd, { values: ["Somewhat"] });
+ const html = renderToStaticMarkup();
+ assert.ok(!html.includes("incorrect") && !html.includes("✗"), "ask answers never get quiz grading UI");
+ assert.match(html, /Somewhat/);
+ });
+
+ it("AnsweredCard renders dismissal as skipped", () => {
+ const answer = { ...buildAnswer(quizQD(), { dontKnow: true }), dismissed: true };
+ const html = renderToStaticMarkup();
+ assert.match(html, /skipped/);
+ });
+});
diff --git a/tests/render.test.ts b/tests/render.test.ts
deleted file mode 100644
index a2e1c16..0000000
--- a/tests/render.test.ts
+++ /dev/null
@@ -1,160 +0,0 @@
-/**
- * Render smoke tests — the layer every earlier bug slipped through.
- *
- * Components are rendered to static HTML (no browser, no DOM) and we assert
- * the expected content is actually IN the output. These would have caught:
- * - Markdown never receiving its text (assistant messages rendered empty)
- * - ask answers rendered as graded quiz answers (red ✗)
- * - quiz options built from native radio inputs (the desync bug)
- *
- * Mermaid: SSR skips effects, so diagrams don't render here — that's fine,
- * we only assert the surrounding markup doesn't crash.
- *
- * Note: components carry `import React from "react"` because tsx (the test
- * runner) uses the classic JSX transform, which needs React in scope. Vite
- * uses the automatic runtime, where the import is simply unused.
- */
-
-import assert from "node:assert/strict";
-import { describe, it } from "node:test";
-import { createElement as h } from "react";
-import { renderToStaticMarkup } from "react-dom/server";
-import { Markdown } from "../web/src/components/Markdown.tsx";
-import { QuizCard } from "../web/src/components/QuizCard.tsx";
-import { AnsweredCard } from "../web/src/components/AnsweredCard.tsx";
-import { EntryBlock } from "../web/src/views/SessionView.tsx";
-import type { ClientQuestion, QuestionAnswerData } from "../src/shared/protocol.ts";
-
-describe("render smoke tests", () => {
- it("Markdown renders the actual text content", () => {
- const html = renderToStaticMarkup(h(Markdown, { text: "Hello **bold** world" }));
- assert.match(html, /Hello/);
- assert.match(html, /bold<\/strong>/);
- assert.match(html, /world/);
- });
-
- it("Markdown renders LaTeX via KaTeX", () => {
- const html = renderToStaticMarkup(h(Markdown, { text: "Energy is $E = mc^2$ here." }));
- assert.match(html, /here\./);
- assert.match(html, /katex/i, "KaTeX markup present");
- });
-
- it("Markdown survives a mermaid block without crashing", () => {
- const html = renderToStaticMarkup(h(Markdown, { text: "Before\n\n```mermaid\ngraph TD\nA-->B\n```\n\nAfter" }));
- assert.match(html, /Before/);
- assert.match(html, /After/);
- });
-
- it("QuizCard renders options as buttons (no native radio/checkbox inputs)", () => {
- const q: ClientQuestion = {
- id: "q1",
- kind: "quiz",
- question: "Which planet?",
- mode: "single-select",
- options: [
- { label: "Mercury", value: "mercury" },
- { label: "Venus", value: "venus" },
- ],
- multiSelect: false,
- };
- const html = renderToStaticMarkup(h(QuizCard, { question: q, send: () => {} }));
- assert.match(html, /Mercury/);
- assert.match(html, /Venus/);
- assert.match(html, /I don't know/);
- assert.ok(!/]*type="radio"/.test(html), "no native radio inputs (v1 desync bug)");
- assert.ok(!/]*type="checkbox"/.test(html), "no native checkbox inputs");
- assert.match(html, /