diff --git a/.env.example b/.env.example index c67f360..ea3bd5e 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,4 @@ -VERSION=0.1.13 +VERSION=0.1.14 # Nest run in docker, change host to database container name DB_HOST=localhost diff --git a/apps/init/package.json b/apps/init/package.json index 7477ad6..b7c2101 100644 --- a/apps/init/package.json +++ b/apps/init/package.json @@ -62,7 +62,6 @@ "clean": "rimraf dist .tshy .tshy-build .turbo", "typecheck": "tsc -p tsconfig.src.json --noEmit", "build": "tshy", - "dev": "tshy --watch", "test": "vitest", "test:e2e": "vitest --run -c ./e2e/vitest.config.ts" }, diff --git a/apps/init/src/utils/env.ts b/apps/init/src/utils/env.ts index f50166c..b9e9acf 100644 --- a/apps/init/src/utils/env.ts +++ b/apps/init/src/utils/env.ts @@ -2,7 +2,7 @@ import { z } from "zod"; const EnvironmentSchema = z.object({ // Version - VERSION: z.string().default("0.1.13"), + VERSION: z.string().default("0.1.14"), // Database DB_HOST: z.string().default("localhost"), diff --git a/apps/webapp/app/components/activity/contribution-graph.tsx b/apps/webapp/app/components/activity/contribution-graph.tsx new file mode 100644 index 0000000..e654481 --- /dev/null +++ b/apps/webapp/app/components/activity/contribution-graph.tsx @@ -0,0 +1,71 @@ +import React, { useMemo } from "react"; +import CalendarHeatmap from "react-calendar-heatmap"; +import { cn } from "~/lib/utils"; + +interface ContributionGraphProps { + data: Array<{ + date: string; + count: number; + status?: string; + }>; + className?: string; +} + +export function ContributionGraph({ data, className }: ContributionGraphProps) { + const processedData = useMemo(() => { + const endDate = new Date(); + const startDate = new Date(); + startDate.setFullYear(endDate.getFullYear() - 1); + + return data.map((item) => ({ + date: item.date, + count: item.count, + status: item.status, + })); + }, [data]); + + const getClassForValue = (value: any) => { + if (!value || value.count === 0) { + return "fill-background dark:fill-background"; + } + + const count = value.count; + if (count >= 20) return "fill-success"; + if (count >= 15) return "fill-success/85"; + if (count >= 10) return "fill-success/70"; + if (count >= 5) return "fill-success/50"; + return "fill-success/30"; + }; + + const getTitleForValue = (value: any) => { + if (!value || value.count === 0) { + return `No activity on ${value?.date || "this date"}`; + } + + const count = value.count; + const date = new Date(value.date).toLocaleDateString(); + return `${count} ${count === 1 ? "activity" : "activities"} on ${date}`; + }; + + const endDate = new Date(); + const startDate = new Date(); + startDate.setFullYear(endDate.getFullYear() - 1); + + return ( +
+
+ +
+
+ ); +} diff --git a/apps/webapp/app/components/graph/graph-client.tsx b/apps/webapp/app/components/graph/graph-client.tsx index 40e2949..5126c6f 100644 --- a/apps/webapp/app/components/graph/graph-client.tsx +++ b/apps/webapp/app/components/graph/graph-client.tsx @@ -1,15 +1,21 @@ +import { GraphClusteringProps } from "./graph-clustering"; +import { type GraphClusteringVisualizationProps } from "./graph-clustering-visualization"; import { type GraphVisualizationProps } from "./graph-visualization"; import { useState, useEffect } from "react"; -export function GraphVisualizationClient(props: GraphVisualizationProps) { +export function GraphVisualizationClient( + props: GraphClusteringVisualizationProps, +) { const [Component, setComponent] = useState(undefined); useEffect(() => { if (typeof window === "undefined") return; - import("./graph-visualization").then(({ GraphVisualization }) => { - setComponent(GraphVisualization); - }); + import("./graph-clustering-visualization").then( + ({ GraphClusteringVisualization }) => { + setComponent(GraphClusteringVisualization); + }, + ); }, []); if (!Component) { diff --git a/apps/webapp/app/components/graph/graph-clustering-visualization.tsx b/apps/webapp/app/components/graph/graph-clustering-visualization.tsx new file mode 100644 index 0000000..99a535a --- /dev/null +++ b/apps/webapp/app/components/graph/graph-clustering-visualization.tsx @@ -0,0 +1,271 @@ +import { useState, useMemo, forwardRef, useEffect } from "react"; +import { useTheme } from "remix-themes"; +import { GraphClustering, type GraphClusteringRef } from "./graph-clustering"; +import { GraphPopovers } from "./graph-popover"; +import type { RawTriplet, NodePopupContent, EdgePopupContent } from "./type"; +import { Card, CardContent } from "~/components/ui/card"; + +import { createLabelColorMap, nodeColorPalette } from "./node-colors"; +import { toGraphTriplets } from "./utils"; +import { cn } from "~/lib/utils"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "../ui/select"; + +interface ClusterData { + uuid: string; + name: string; + description?: string; + size: number; + cohesionScore?: number; + aspectType?: "thematic" | "social" | "activity"; +} + +export interface GraphClusteringVisualizationProps { + triplets: RawTriplet[]; + clusters: ClusterData[]; + width?: number; + height?: number; + zoomOnMount?: boolean; + className?: string; + selectedClusterId?: string | null; + onClusterSelect?: (clusterId: string | null) => void; +} + +export const GraphClusteringVisualization = forwardRef< + GraphClusteringRef, + GraphClusteringVisualizationProps +>( + ( + { + triplets, + clusters, + width = window.innerWidth * 0.85, + height = window.innerHeight * 0.85, + zoomOnMount = true, + className = "rounded-md h-full overflow-hidden relative", + selectedClusterId, + onClusterSelect, + }, + ref, + ) => { + const [themeMode] = useTheme(); + + // Graph state for popovers + const [showNodePopup, setShowNodePopup] = useState(false); + const [showEdgePopup, setShowEdgePopup] = useState(false); + const [nodePopupContent, setNodePopupContent] = + useState(null); + const [edgePopupContent, setEdgePopupContent] = + useState(null); + + // Filter triplets based on selected cluster (like Marvel's comic filter) + const filteredTriplets = useMemo(() => { + if (!selectedClusterId) return triplets; + + // Filter triplets to show only nodes from the selected cluster + return triplets.filter( + (triplet) => + triplet.sourceNode.attributes?.clusterId === selectedClusterId || + triplet.targetNode.attributes?.clusterId === selectedClusterId, + ); + }, [triplets, selectedClusterId]); + + // Convert filtered triplets to graph triplets + const graphTriplets = useMemo( + () => toGraphTriplets(filteredTriplets), + [filteredTriplets], + ); + + // Extract all unique labels from triplets + const allLabels = useMemo(() => { + const labels = new Set(); + labels.add("Entity"); // Always include Entity as default + + graphTriplets.forEach((triplet) => { + if (triplet.source.primaryLabel) + labels.add(triplet.source.primaryLabel); + if (triplet.target.primaryLabel) + labels.add(triplet.target.primaryLabel); + }); + + return Array.from(labels).sort((a, b) => { + // Always put "Entity" first + if (a === "Entity") return -1; + if (b === "Entity") return 1; + // Sort others alphabetically + return a.localeCompare(b); + }); + }, [graphTriplets]); + + // Create a shared label color map + const sharedLabelColorMap = useMemo(() => { + return createLabelColorMap(allLabels); + }, [allLabels]); + + // Handle node click + const handleNodeClick = (nodeId: string) => { + // Find the triplet that contains this node by searching through graphTriplets + let foundNode = null; + for (const triplet of filteredTriplets) { + if (triplet.sourceNode.uuid === nodeId) { + foundNode = triplet.sourceNode; + break; + } else if (triplet.targetNode.uuid === nodeId) { + foundNode = triplet.targetNode; + break; + } + } + + if (!foundNode) { + // Try to find in the converted graph triplets + for (const graphTriplet of graphTriplets) { + if (graphTriplet.source.id === nodeId) { + foundNode = { + uuid: graphTriplet.source.id, + value: graphTriplet.source.value, + primaryLabel: graphTriplet.source.primaryLabel, + attributes: graphTriplet.source, + } as any; + break; + } else if (graphTriplet.target.id === nodeId) { + foundNode = { + uuid: graphTriplet.target.id, + value: graphTriplet.target.value, + primaryLabel: graphTriplet.target.primaryLabel, + attributes: graphTriplet.target, + }; + break; + } + } + } + + if (!foundNode) return; + + // Set popup content and show the popup + setNodePopupContent({ + id: nodeId, + node: foundNode, + }); + setShowNodePopup(true); + setShowEdgePopup(false); + }; + + // Handle edge click + const handleEdgeClick = (edgeId: string) => { + // Find the triplet that contains this edge + const triplet = triplets.find((t) => t.edge.uuid === edgeId); + + if (!triplet) return; + + // Set popup content and show the popup + setEdgePopupContent({ + id: edgeId, + source: triplet.sourceNode, + target: triplet.targetNode, + relation: triplet.edge, + }); + setShowEdgePopup(true); + setShowNodePopup(false); + }; + + // Handle cluster click - toggle filter like Marvel + const handleClusterClick = (clusterId: string) => { + if (onClusterSelect) { + const newSelection = selectedClusterId === clusterId ? null : clusterId; + onClusterSelect(newSelection); + } + }; + + // Handle popover close + const handlePopoverClose = () => { + setShowNodePopup(false); + setShowEdgePopup(false); + }; + + return ( +
+ {/* Cluster Filter Dropdown - Marvel style */} +
+ + +
+ +
+
+
+
+ + {filteredTriplets.length > 0 ? ( + + ) : ( +
+

No graph data to visualize.

+
+ )} + + {/* Standard Graph Popovers */} + +
+ ); + }, +); diff --git a/apps/webapp/app/components/graph/graph-clustering.tsx b/apps/webapp/app/components/graph/graph-clustering.tsx new file mode 100644 index 0000000..820fc81 --- /dev/null +++ b/apps/webapp/app/components/graph/graph-clustering.tsx @@ -0,0 +1,851 @@ +import { + useEffect, + useRef, + useMemo, + useCallback, + useImperativeHandle, + forwardRef, +} from "react"; +import Sigma from "sigma"; +import GraphologyGraph from "graphology"; +import forceAtlas2 from "graphology-layout-forceatlas2"; +import FA2Layout from "graphology-layout-forceatlas2/worker"; +import { EdgeLineProgram } from "sigma/rendering"; +import colors from "tailwindcss/colors"; +import type { GraphTriplet, IdValue, GraphNode } from "./type"; +import { + createLabelColorMap, + getNodeColor as getNodeColorByLabel, + nodeColorPalette, +} from "./node-colors"; +import { useTheme } from "remix-themes"; +import { drawHover } from "./utils"; + +interface ClusterData { + uuid: string; + name: string; + description?: string; + size: number; + cohesionScore?: number; +} + +export interface GraphClusteringProps { + triplets: GraphTriplet[]; + clusters: ClusterData[]; + width?: number; + height?: number; + zoomOnMount?: boolean; + onNodeClick?: (nodeId: string) => void; + onEdgeClick?: (edgeId: string) => void; + onClusterClick?: (clusterId: string) => void; + onBlur?: () => void; + labelColorMap?: Map; + showClusterLabels?: boolean; + enableClusterColors?: boolean; +} + +export interface GraphClusteringRef { + zoomToLinkById: (linkId: string) => void; + zoomToCluster: (clusterId: string) => void; + highlightCluster: (clusterId: string) => void; + resetHighlights: () => void; +} + +// Use node-colors palette for cluster colors +const generateClusterColors = ( + clusterCount: number, + isDarkMode: boolean, +): string[] => { + const palette = isDarkMode ? nodeColorPalette.dark : nodeColorPalette.light; + const colors: string[] = []; + + for (let i = 0; i < clusterCount; i++) { + colors.push(palette[i % palette.length]); + } + + return colors; +}; + +export const GraphClustering = forwardRef< + GraphClusteringRef, + GraphClusteringProps +>( + ( + { + triplets, + clusters, + width = 1000, + height = 800, + zoomOnMount = false, + onNodeClick, + onEdgeClick, + onClusterClick, + onBlur, + labelColorMap: externalLabelColorMap, + showClusterLabels = true, + enableClusterColors = true, + }, + ref, + ) => { + const containerRef = useRef(null); + const sigmaRef = useRef(null); + const graphRef = useRef(null); + const clustersLayerRef = useRef(null); + const [themeMode] = useTheme(); + + const isInitializedRef = useRef(false); + const selectedNodeRef = useRef(null); + const selectedEdgeRef = useRef(null); + const selectedClusterRef = useRef(null); + + // Create cluster color mapping + const clusterColorMap = useMemo(() => { + if (!enableClusterColors) return new Map(); + + const clusterIds = clusters.map((c) => c.uuid); + const clusterColors = generateClusterColors( + clusterIds.length, + themeMode === "dark", + ); + const colorMap = new Map(); + + clusterIds.forEach((id, index) => { + colorMap.set(id, clusterColors[index]); + }); + + return colorMap; + }, [clusters, enableClusterColors, themeMode]); + + // Memoize theme to prevent unnecessary recreation + const theme = useMemo( + () => ({ + node: { + fill: colors.pink[500], + stroke: themeMode === "dark" ? colors.slate[100] : colors.slate[900], + hover: "#646464", + text: themeMode === "dark" ? colors.slate[100] : colors.slate[900], + selected: "#646464", + dimmed: colors.pink[300], + }, + link: { + stroke: colors.gray[400], + selected: "#646464", + dimmed: themeMode === "dark" ? colors.slate[800] : colors.slate[200], + }, + cluster: { + labelColor: + themeMode === "dark" ? colors.slate[100] : colors.slate[900], + labelBg: + themeMode === "dark" + ? colors.slate[800] + "CC" + : colors.slate[200] + "CC", + }, + background: + themeMode === "dark" ? colors.slate[900] : colors.slate[100], + }), + [themeMode], + ); + + // Extract all unique labels from triplets + const allLabels = useMemo(() => { + if (externalLabelColorMap) return []; + const labels = new Set(); + labels.add("Entity"); + triplets.forEach((triplet) => { + if (triplet.source.primaryLabel) + labels.add(triplet.source.primaryLabel); + if (triplet.target.primaryLabel) + labels.add(triplet.target.primaryLabel); + }); + return Array.from(labels); + }, [triplets, externalLabelColorMap]); + + // Create a mapping of label to color + const labelColorMap = useMemo(() => { + return externalLabelColorMap || createLabelColorMap(allLabels); + }, [allLabels, externalLabelColorMap]); + + // Create a mapping of node IDs to their data + const nodeDataMap = useMemo(() => { + const result = new Map(); + triplets.forEach((triplet) => { + result.set(triplet.source.id, triplet.source); + result.set(triplet.target.id, triplet.target); + }); + return result; + }, [triplets]); + + // Function to get node color (with cluster coloring support) + const getNodeColor = useCallback( + (node: any): string => { + if (!node) { + return getNodeColorByLabel(null, themeMode === "dark", labelColorMap); + } + + const nodeData = nodeDataMap.get(node.id) || node; + + // Check if this is a Statement node + const isStatementNode = + nodeData.attributes.nodeType === "Statement" || + (nodeData.labels && nodeData.labels.includes("Statement")); + + if (isStatementNode) { + // Statement nodes with cluster IDs use cluster colors + if ( + enableClusterColors && + nodeData.clusterId && + clusterColorMap.has(nodeData.clusterId) + ) { + return clusterColorMap.get(nodeData.clusterId)!; + } + + // Unclustered statement nodes use a specific light color + return themeMode === "dark" ? "#2b9684" : "#54935b"; // Teal/Green from palette + } + + // Entity nodes use light gray + return themeMode === "dark" ? "#6B7280" : "#9CA3AF"; // Tailwind gray-500/gray-400 + }, + [ + labelColorMap, + nodeDataMap, + themeMode, + enableClusterColors, + clusterColorMap, + ], + ); + + // Process graph data for Sigma + const { nodes, edges } = useMemo(() => { + const nodeMap = new Map(); + triplets.forEach((triplet) => { + if (!nodeMap.has(triplet.source.id)) { + const nodeColor = getNodeColor(triplet.source); + const isStatementNode = + triplet.source.attributes?.nodeType === "Statement" || + (triplet.source.labels && + triplet.source.labels.includes("Statement")); + + nodeMap.set(triplet.source.id, { + id: triplet.source.id, + label: triplet.source.value + ? triplet.source.value.split(/\s+/).slice(0, 4).join(" ") + + (triplet.source.value.split(/\s+/).length > 4 ? " ..." : "") + : "", + size: isStatementNode ? 4 : 2, // Statement nodes slightly larger + color: nodeColor, + x: width, + y: height, + nodeData: triplet.source, + clusterId: triplet.source.clusterId, + // Enhanced border for visual appeal, thicker for Statement nodes + borderSize: 1, + borderColor: nodeColor, + }); + } + if (!nodeMap.has(triplet.target.id)) { + const nodeColor = getNodeColor(triplet.target); + const isStatementNode = + triplet.target.attributes?.nodeType === "Statement" || + (triplet.target.labels && + triplet.target.labels.includes("Statement")); + + nodeMap.set(triplet.target.id, { + id: triplet.target.id, + label: triplet.target.value + ? triplet.target.value.split(/\s+/).slice(0, 4).join(" ") + + (triplet.target.value.split(/\s+/).length > 4 ? " ..." : "") + : "", + size: isStatementNode ? 4 : 2, // Statement nodes slightly larger + color: nodeColor, + x: width, + y: height, + nodeData: triplet.target, + clusterId: triplet.target.clusterId, + // Enhanced border for visual appeal, thicker for Statement nodes + borderSize: 1, + borderColor: nodeColor, + }); + } + }); + + const linkGroups = triplets.reduce( + (groups, triplet) => { + if (triplet.relation.type === "_isolated_node_") { + return groups; + } + let key = `${triplet.source.id}-${triplet.target.id}`; + const reverseKey = `${triplet.target.id}-${triplet.source.id}`; + if (groups[reverseKey]) { + key = reverseKey; + } + if (!groups[key]) { + groups[key] = { + id: key, + source: triplet.source.id, + target: triplet.target.id, + relations: [], + relationData: [], + label: "", + color: "#0000001A", + labelColor: "#0000001A", + size: 1, + }; + } + groups[key].relations.push(triplet.relation.value); + groups[key].relationData.push(triplet.relation); + + return groups; + }, + {} as Record, + ); + + return { + nodes: Array.from(nodeMap.values()), + edges: Object.values(linkGroups), + }; + }, [triplets, getNodeColor, width, height]); + + // Helper function to reset highlights without affecting camera + const resetHighlights = useCallback(() => { + if (!graphRef.current || !sigmaRef.current) return; + const graph = graphRef.current; + const sigma = sigmaRef.current; + + // Store camera state before making changes + const camera = sigma.getCamera(); + const currentState = camera.getState(); + + graph.forEachNode((node) => { + const nodeData = graph.getNodeAttribute(node, "nodeData"); + const originalColor = getNodeColor(nodeData); + const isStatementNode = + nodeData?.attributes.nodeType === "Statement" || + (nodeData?.labels && nodeData.labels.includes("Statement")); + + graph.setNodeAttribute(node, "highlighted", false); + graph.setNodeAttribute(node, "color", originalColor); + graph.setNodeAttribute(node, "size", isStatementNode ? 4 : 2); + graph.setNodeAttribute(node, "zIndex", 1); + }); + graph.forEachEdge((edge) => { + graph.setEdgeAttribute(edge, "highlighted", false); + graph.setEdgeAttribute(edge, "color", "#0000001A"); + graph.setEdgeAttribute(edge, "size", 1); + }); + + // Restore camera state to prevent unwanted movements + camera.setState(currentState); + + selectedNodeRef.current = null; + selectedEdgeRef.current = null; + selectedClusterRef.current = null; + }, [getNodeColor]); + + // Highlight entire cluster + const highlightCluster = useCallback( + (clusterId: string) => { + if (!graphRef.current || !sigmaRef.current) return; + + const graph = graphRef.current; + const sigma = sigmaRef.current; + + resetHighlights(); + selectedClusterRef.current = clusterId; + + const clusterNodes: string[] = []; + const clusterColor = + clusterColorMap.get(clusterId) || theme.node.selected; + + // Find all nodes in the cluster + graph.forEachNode((nodeId, attributes) => { + if (attributes.clusterId === clusterId) { + clusterNodes.push(nodeId); + graph.setNodeAttribute(nodeId, "highlighted", true); + graph.setNodeAttribute(nodeId, "color", clusterColor); + graph.setNodeAttribute(nodeId, "size", attributes.size * 1.75); + graph.setNodeAttribute(nodeId, "zIndex", 2); + } else { + // Dim other nodes + graph.setNodeAttribute(nodeId, "color", theme.node.dimmed); + graph.setNodeAttribute(nodeId, "size", attributes.size * 0.7); + graph.setNodeAttribute(nodeId, "zIndex", 0); + } + }); + + // Highlight edges within the cluster + graph.forEachEdge((edgeId, attributes, source, target) => { + const sourceInCluster = clusterNodes.includes(source); + const targetInCluster = clusterNodes.includes(target); + + if (sourceInCluster && targetInCluster) { + graph.setEdgeAttribute(edgeId, "highlighted", true); + graph.setEdgeAttribute(edgeId, "color", clusterColor); + graph.setEdgeAttribute(edgeId, "size", 3); + } else { + graph.setEdgeAttribute(edgeId, "color", theme.link.dimmed); + graph.setEdgeAttribute(edgeId, "size", 1); + } + }); + }, + [graphRef, sigmaRef, clusterColorMap, theme, resetHighlights], + ); + + // Zoom to cluster + const zoomToCluster = useCallback( + (clusterId: string) => { + if (!graphRef.current || !sigmaRef.current) return; + + const graph = graphRef.current; + const sigma = sigmaRef.current; + const clusterNodes: string[] = []; + + // Find all nodes in the cluster + graph.forEachNode((nodeId, attributes) => { + if (attributes.clusterId === clusterId) { + clusterNodes.push(nodeId); + } + }); + + if (clusterNodes.length === 0) return; + + // Calculate bounding box of cluster nodes + let minX = Infinity, + maxX = -Infinity; + let minY = Infinity, + maxY = -Infinity; + + clusterNodes.forEach((nodeId) => { + const pos = sigma.getNodeDisplayData(nodeId); + if (pos) { + minX = Math.min(minX, pos.x); + maxX = Math.max(maxX, pos.x); + minY = Math.min(minY, pos.y); + maxY = Math.max(maxY, pos.y); + } + }); + + // Calculate center and zoom level + const centerX = (minX + maxX) / 2; + const centerY = (minY + maxY) / 2; + const containerRect = containerRef.current?.getBoundingClientRect(); + + if (containerRect) { + const padding = 100; + const clusterWidth = maxX - minX + padding; + const clusterHeight = maxY - minY + padding; + const ratio = Math.min( + containerRect.width / clusterWidth, + containerRect.height / clusterHeight, + 2.0, // Maximum zoom + ); + + sigma + .getCamera() + .animate({ x: centerX, y: centerY, ratio }, { duration: 500 }); + } + + highlightCluster(clusterId); + }, + [highlightCluster], + ); + + // Expose methods via ref + useImperativeHandle(ref, () => ({ + zoomToLinkById: (linkId: string) => { + // Implementation similar to original graph component + if (!sigmaRef.current || !graphRef.current) return; + // ... existing zoomToLinkById logic + }, + zoomToCluster, + highlightCluster, + resetHighlights, + })); + + // Calculate optimal ForceAtlas2 parameters based on graph properties + const calculateOptimalParameters = useCallback((graph: GraphologyGraph) => { + const nodeCount = graph.order; + const edgeCount = graph.size; + + if (nodeCount === 0) + return { scalingRatio: 30, gravity: 5, iterations: 600 }; + + // Similar logic to original implementation + const maxPossibleEdges = (nodeCount * (nodeCount - 1)) / 2; + const density = maxPossibleEdges > 0 ? edgeCount / maxPossibleEdges : 0; + + let scalingRatio: number; + if (nodeCount < 10) { + scalingRatio = 15; + } else if (nodeCount < 50) { + scalingRatio = 20 + (nodeCount - 10) * 0.5; + } else if (nodeCount < 200) { + scalingRatio = 40 + (nodeCount - 50) * 0.2; + } else { + scalingRatio = Math.min(80, 70 + (nodeCount - 200) * 0.05); + } + + let gravity: number; + if (density > 0.3) { + gravity = 1 + density * 2; + } else if (density > 0.1) { + gravity = 3 + density * 5; + } else { + gravity = Math.min(8, 5 + (1 - density) * 3); + } + + if (nodeCount < 20) { + gravity *= 1.5; + } else if (nodeCount > 100) { + gravity *= 0.8; + } + + const complexity = nodeCount + edgeCount; + let durationSeconds: number; + if (complexity < 50) { + durationSeconds = 1.5; + } else if (complexity < 200) { + durationSeconds = 2.5; + } else if (complexity < 500) { + durationSeconds = 3.5; + } else { + durationSeconds = Math.min(6, 4 + (complexity - 500) * 0.004); + } + + return { + scalingRatio: Math.round(scalingRatio * 10) / 10, + gravity: Math.round(gravity * 10) / 10, + duration: Math.round(durationSeconds * 100) / 100, // in seconds + }; + }, []); + + useEffect(() => { + if (isInitializedRef.current || !containerRef.current) return; + isInitializedRef.current = true; + + // Create graphology graph + const graph = new GraphologyGraph(); + graphRef.current = graph; + + // Add nodes + nodes.forEach((node) => { + graph.addNode(node.id, node); + }); + + // Add edges + edges.forEach((edge) => { + if (graph.hasNode(edge.source) && graph.hasNode(edge.target)) { + graph.addEdge(edge.source, edge.target, { ...edge }); + } + }); + + // No virtual edges - let the natural graph structure determine layout + + // Apply layout + if (graph.order > 0) { + // Strong cluster-based positioning for Statement nodes only + const clusterNodeMap = new Map(); + const entityNodes: string[] = []; + + // Group Statement nodes by their cluster ID, separate Entity nodes + graph.forEachNode((nodeId, attributes) => { + const isStatementNode = + attributes.nodeData?.nodeType === "Statement" || + (attributes.nodeData?.labels && + attributes.nodeData.labels.includes("Statement")); + + if (isStatementNode && attributes.clusterId) { + // Statement nodes with cluster IDs go into clusters + if (!clusterNodeMap.has(attributes.clusterId)) { + clusterNodeMap.set(attributes.clusterId, []); + } + clusterNodeMap.get(attributes.clusterId)!.push(nodeId); + } else { + // Entity nodes (or unclustered nodes) positioned separately + entityNodes.push(nodeId); + } + }); + + const clusterIds = Array.from(clusterNodeMap.keys()); + + if (clusterIds.length > 0) { + // Use a more aggressive clustering approach - create distinct regions + const padding = Math.min(width, height) * 0.1; // 10% padding + const availableWidth = width - 2 * padding; + const availableHeight = height - 2 * padding; + + // Calculate optimal grid layout + const cols = Math.ceil(Math.sqrt(clusterIds.length)); + const rows = Math.ceil(clusterIds.length / cols); + const cellWidth = availableWidth / cols; + const cellHeight = availableHeight / rows; + + clusterIds.forEach((clusterId, index) => { + const col = index % cols; + const row = Math.floor(index / cols); + + // Calculate cluster region with more separation + const regionLeft = padding + col * cellWidth; + const regionTop = padding + row * cellHeight; + const regionCenterX = regionLeft + cellWidth / 2; + const regionCenterY = regionTop + cellHeight / 2; + + // Get nodes in this cluster + const nodesInCluster = clusterNodeMap.get(clusterId)!; + const clusterSize = nodesInCluster.length; + + // Create cluster radius with Marvel-style spacing - more generous + const maxRadius = Math.min(cellWidth, cellHeight) * 0.35; + const baseSpacing = 150; // Larger base spacing between nodes + const clusterRadius = Math.max( + baseSpacing, + Math.min(maxRadius, Math.sqrt(clusterSize) * baseSpacing * 1.2), + ); + + if (clusterSize === 1) { + // Single node at region center + graph.setNodeAttribute(nodesInCluster[0], "x", regionCenterX); + graph.setNodeAttribute(nodesInCluster[0], "y", regionCenterY); + } else if (clusterSize <= 6) { + // Small clusters - tight circle + nodesInCluster.forEach((nodeId, nodeIndex) => { + const angle = (nodeIndex / clusterSize) * 2 * Math.PI; + const x = regionCenterX + Math.cos(angle) * clusterRadius; + const y = regionCenterY + Math.sin(angle) * clusterRadius; + graph.setNodeAttribute(nodeId, "x", x); + graph.setNodeAttribute(nodeId, "y", y); + }); + } else { + // Larger clusters - dense spiral pattern + nodesInCluster.forEach((nodeId, nodeIndex) => { + const spiralTurns = Math.ceil(clusterSize / 8); + const angle = + (nodeIndex / clusterSize) * 2 * Math.PI * spiralTurns; + const radius = (nodeIndex / clusterSize) * clusterRadius; + const x = regionCenterX + Math.cos(angle) * radius; + const y = regionCenterY + Math.sin(angle) * radius; + graph.setNodeAttribute(nodeId, "x", x); + graph.setNodeAttribute(nodeId, "y", y); + }); + } + }); + } + + // Position Entity nodes using ForceAtlas2 natural positioning + // They will be positioned by the algorithm based on their connections to Statement nodes + entityNodes.forEach((nodeId) => { + // Give them initial random positions, ForceAtlas2 will adjust based on connections + graph.setNodeAttribute(nodeId, "x", Math.random() * width); + graph.setNodeAttribute(nodeId, "y", Math.random() * height); + }); + + const optimalParams = calculateOptimalParameters(graph); + const settings = forceAtlas2.inferSettings(graph); + + console.log(optimalParams); + const layout = new FA2Layout(graph, { + settings: { + ...settings, + barnesHutOptimize: true, + strongGravityMode: false, // Marvel doesn't use strong gravity + gravity: Math.max(0.1, optimalParams.gravity * 0.005), // Much weaker gravity like Marvel + scalingRatio: optimalParams.scalingRatio * 10, // Higher scaling for more spacing + slowDown: 20, // Much slower to preserve cluster positions + outboundAttractionDistribution: false, // Use standard distribution + linLogMode: false, // Linear mode + edgeWeightInfluence: 0, // Disable edge weight influence to maintain positioning + }, + }); + + layout.start(); + setTimeout(() => layout.stop(), (optimalParams.duration ?? 2) * 1000); + } + + // Create Sigma instance + const sigma = new Sigma(graph, containerRef.current, { + renderEdgeLabels: true, + defaultEdgeColor: "#0000001A", + defaultNodeColor: theme.node.fill, + defaultEdgeType: "edges-fast", + edgeProgramClasses: { + "edges-fast": EdgeLineProgram, + }, + renderLabels: false, + enableEdgeEvents: true, + minCameraRatio: 0.01, + defaultDrawNodeHover: drawHover, + + maxCameraRatio: 2, + allowInvalidContainer: false, + }); + + sigmaRef.current = sigma; + + // Set up camera for zoom on mount + if (zoomOnMount) { + setTimeout(() => { + sigma + .getCamera() + .animate(sigma.getCamera().getState(), { duration: 750 }); + }, 100); + } + + // Update cluster labels after any camera movement + sigma.getCamera().on("updated", () => { + if (showClusterLabels) { + } + }); + + // Drag and drop implementation (same as original) + let draggedNode: string | null = null; + let isDragging = false; + + sigma.on("downNode", (e) => { + isDragging = true; + draggedNode = e.node; + graph.setNodeAttribute(draggedNode, "highlighted", true); + if (!sigma.getCustomBBox()) sigma.setCustomBBox(sigma.getBBox()); + }); + + sigma.on("moveBody", ({ event }) => { + if (!isDragging || !draggedNode) return; + const pos = sigma.viewportToGraph(event); + graph.setNodeAttribute(draggedNode, "x", pos.x); + graph.setNodeAttribute(draggedNode, "y", pos.y); + event.preventSigmaDefault?.(); + event.original?.preventDefault?.(); + event.original?.stopPropagation?.(); + }); + + const handleUp = () => { + if (draggedNode) { + graph.removeNodeAttribute(draggedNode, "highlighted"); + } + isDragging = false; + draggedNode = null; + }; + sigma.on("upNode", handleUp); + sigma.on("upStage", handleUp); + + // Node click handler + sigma.on("clickNode", (event) => { + const { node } = event; + + // Store current camera state to prevent unwanted movements + const camera = sigma.getCamera(); + const currentState = camera.getState(); + + resetHighlights(); // Clear previous highlights first + + // Restore camera state after reset to prevent zoom changes + setTimeout(() => { + camera.setState(currentState); + }, 0); + + if (onNodeClick) { + onNodeClick(node); + } + + // Highlight the clicked node + graph.setNodeAttribute(node, "highlighted", true); + graph.setNodeAttribute(node, "color", theme.node.selected); + graph.setNodeAttribute( + node, + "size", + graph.getNodeAttribute(node, "size"), + ); + // Enhanced border for selected node + graph.setNodeAttribute(node, "borderSize", 3); + graph.setNodeAttribute(node, "borderColor", theme.node.selected); + graph.setNodeAttribute(node, "zIndex", 3); + selectedNodeRef.current = node; + + // Highlight connected edges and nodes + graph.forEachEdge(node, (edge, _attributes, source, target) => { + graph.setEdgeAttribute(edge, "highlighted", true); + graph.setEdgeAttribute(edge, "color", theme.link.selected); + graph.setEdgeAttribute(edge, "size", 2); + const otherNode = source === node ? target : source; + graph.setNodeAttribute(otherNode, "highlighted", true); + graph.setNodeAttribute(otherNode, "color", theme.node.hover); + graph.setNodeAttribute( + otherNode, + "size", + graph.getNodeAttribute(otherNode, "size"), + ); + graph.setNodeAttribute(otherNode, "zIndex", 2); + }); + }); + + // Edge click handler + sigma.on("clickEdge", (event) => { + const { edge } = event; + resetHighlights(); + const edgeAttrs = graph.getEdgeAttributes(edge); + if (edgeAttrs.relationData && edgeAttrs.relationData.length > 0) { + const relation = edgeAttrs.relationData[0]; + if (onEdgeClick) { + onEdgeClick(relation.id); + } + } + graph.setEdgeAttribute(edge, "highlighted", true); + graph.setEdgeAttribute(edge, "color", theme.link.selected); + selectedEdgeRef.current = edge; + const source = graph.source(edge); + const target = graph.target(edge); + graph.setNodeAttribute(source, "highlighted", true); + graph.setNodeAttribute(source, "color", theme.node.selected); + graph.setNodeAttribute(target, "highlighted", true); + graph.setNodeAttribute(target, "color", theme.node.selected); + }); + + // Background click handler + sigma.on("clickStage", (event) => { + // Store camera state before reset + const camera = sigma.getCamera(); + const currentState = camera.getState(); + + resetHighlights(); + + // Restore camera state + camera.setState(currentState); + + if (onBlur) { + onBlur(); + } + }); + + // Cleanup function + return () => { + if (sigmaRef.current) { + sigmaRef.current.kill(); + sigmaRef.current = null; + } + if (graphRef.current) { + graphRef.current.clear(); + graphRef.current = null; + } + if (clustersLayerRef.current) { + clustersLayerRef.current.remove(); + clustersLayerRef.current = null; + } + isInitializedRef.current = false; + }; + }, [nodes, edges, clusters, showClusterLabels]); + + return ( +
+ ); + }, +); diff --git a/apps/webapp/app/components/graph/graph-popover.tsx b/apps/webapp/app/components/graph/graph-popover.tsx index 36dd8d5..369fce2 100644 --- a/apps/webapp/app/components/graph/graph-popover.tsx +++ b/apps/webapp/app/components/graph/graph-popover.tsx @@ -36,9 +36,7 @@ interface GraphPopoversProps { export function GraphPopovers({ showNodePopup, - showEdgePopup, nodePopupContent, - edgePopupContent, onOpenChange, labelColorMap, }: GraphPopoversProps) { @@ -52,8 +50,12 @@ export function GraphPopovers({ // Check if node has primaryLabel property (GraphNode) const nodeAny = nodePopupContent.node as any; - if (nodeAny.primaryLabel && typeof nodeAny.primaryLabel === "string") { - return nodeAny.primaryLabel; + + if ( + nodeAny.attributes.nodeType && + typeof nodeAny.attributes.nodeType === "string" + ) { + return nodeAny.attributes.nodeType; } // Fall back to original logic with labels @@ -93,7 +95,7 @@ export function GraphPopovers({
Node Details {primaryNodeLabel && ( {primaryNodeLabel} @@ -118,7 +120,9 @@ export function GraphPopovers({ {attributesToDisplay.map(({ key, value }) => (

- {key}: + {key.charAt(0).toUpperCase() + + key.slice(1).toLowerCase()} + : {" "} {typeof value === "object" @@ -134,48 +138,6 @@ export function GraphPopovers({

- - - -
- - e.preventDefault()} - > -
-

- Episode → {edgePopupContent?.target.name || "Unknown"} -

-
-
-

Relationship

-
-

- - UUID: - - {edgePopupContent?.relation.uuid || "Unknown"} -

-

- - Type: - - {edgePopupContent?.relation.type || "Unknown"} -

-

- - Created: - - {formatDate(edgePopupContent?.relation.createdAt)} -

-
-
-
-
); } diff --git a/apps/webapp/app/components/graph/node-colors.ts b/apps/webapp/app/components/graph/node-colors.ts index 78222f5..52000ee 100644 --- a/apps/webapp/app/components/graph/node-colors.ts +++ b/apps/webapp/app/components/graph/node-colors.ts @@ -1,5 +1,3 @@ -import colors from "tailwindcss/colors"; - // Define a color palette for node coloring using hex values directly export const nodeColorPalette = { light: [ diff --git a/apps/webapp/app/components/graph/type.ts b/apps/webapp/app/components/graph/type.ts index 2394581..75bc6f3 100644 --- a/apps/webapp/app/components/graph/type.ts +++ b/apps/webapp/app/components/graph/type.ts @@ -5,6 +5,7 @@ export interface Node { labels?: string[]; attributes?: Record; createdAt: string; + clusterId?: string; } export interface Edge { @@ -25,6 +26,7 @@ export interface GraphNode extends Node { id: string; value: string; primaryLabel?: string; + clusterId?: string; // Add cluster information } export interface GraphEdge extends Edge { diff --git a/apps/webapp/app/components/graph/utils.ts b/apps/webapp/app/components/graph/utils.ts index d3b90d7..9b14260 100644 --- a/apps/webapp/app/components/graph/utils.ts +++ b/apps/webapp/app/components/graph/utils.ts @@ -21,6 +21,7 @@ export function toGraphNode(node: Node): GraphNode { summary: node.summary, labels: node.labels, primaryLabel, + clusterId: node?.clusterId, // Extract cluster ID from attributes }; } @@ -44,62 +45,109 @@ export function toGraphTriplets(triplets: RawTriplet[]): GraphTriplet[] { return triplets.map(toGraphTriplet); } -export function createTriplets(edges: Edge[], nodes: Node[]): RawTriplet[] { - // Create a Set of node UUIDs that are connected by edges - const connectedNodeIds = new Set(); - - // Create triplets from edges - const edgeTriplets = edges - .map((edge) => { - const sourceNode = nodes.find( - (node) => node.uuid === edge.source_node_uuid, - ); - const targetNode = nodes.find( - (node) => node.uuid === edge.target_node_uuid, - ); - - if (!sourceNode || !targetNode) return null; - - // Add source and target node IDs to connected set - connectedNodeIds.add(sourceNode.uuid); - connectedNodeIds.add(targetNode.uuid); - - return { - sourceNode, - edge, - targetNode, - }; - }) - .filter( - (t): t is RawTriplet => - t !== null && t.sourceNode !== undefined && t.targetNode !== undefined, - ); - - // Find isolated nodes (nodes that don't appear in any edge) - const isolatedNodes = nodes.filter( - (node) => !connectedNodeIds.has(node.uuid), - ); - - // For isolated nodes, create special triplets - const isolatedTriplets: RawTriplet[] = isolatedNodes.map((node) => { - // Create a special marker edge for isolated nodes - const virtualEdge: Edge = { - uuid: `isolated-node-${node.uuid}`, - source_node_uuid: node.uuid, - target_node_uuid: node.uuid, - // Use a special type that we can filter out in the Graph component - type: "_isolated_node_", - - createdAt: node.createdAt, - }; - - return { - sourceNode: node, - edge: virtualEdge, - targetNode: node, - }; - }); - - // Combine edge triplets with isolated node triplets - return [...edgeTriplets, ...isolatedTriplets]; +export function drawRoundRect( + ctx: CanvasRenderingContext2D, + x: number, + y: number, + width: number, + height: number, + radius: number, +): void { + ctx.beginPath(); + ctx.moveTo(x + radius, y); + ctx.lineTo(x + width - radius, y); + ctx.quadraticCurveTo(x + width, y, x + width, y + radius); + ctx.lineTo(x + width, y + height - radius); + ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); + ctx.lineTo(x + radius, y + height); + ctx.quadraticCurveTo(x, y + height, x, y + height - radius); + ctx.lineTo(x, y + radius); + ctx.quadraticCurveTo(x, y, x + radius, y); + ctx.closePath(); +} + +const TEXT_COLOR = "#000000"; + +export function drawHover( + context: CanvasRenderingContext2D, + data: any, + settings: any, +) { + const size = settings.labelSize; + const font = settings.labelFont; + const weight = settings.labelWeight; + const subLabelSize = size - 2; + + const label = data.label; + const subLabel = data.tag !== "unknown" ? data.tag : ""; + const entityLabel = data.nodeData.attributes.nodeType; + + // Simulate the --shadow-1 Tailwind shadow: + // lch(0 0 0 / 0.022) 0px 3px 6px -2px, lch(0 0 0 / 0.044) 0px 1px 1px; + // Canvas only supports a single shadow, so we approximate with the stronger one. + // lch(0 0 0 / 0.044) is roughly rgba(0,0,0,0.044) + context.beginPath(); + context.fillStyle = "#fff"; + context.shadowOffsetX = 0; + context.shadowOffsetY = 1; + context.shadowBlur = 1; + context.shadowColor = "rgba(0,0,0,0.044)"; + + context.font = `${weight} ${size}px ${font}`; + const labelWidth = context.measureText(label).width; + context.font = `${weight} ${subLabelSize}px ${font}`; + const subLabelWidth = subLabel ? context.measureText(subLabel).width : 0; + context.font = `${weight} ${subLabelSize}px ${font}`; + const entityLabelWidth = entityLabel + ? context.measureText(entityLabel).width + : 0; + + const textWidth = Math.max(labelWidth, subLabelWidth, entityLabelWidth); + + const x = Math.round(data.x); + const y = Math.round(data.y); + const w = Math.round(textWidth + size / 2 + data.size + 3); + const hLabel = Math.round(size / 2 + 4); + const hSubLabel = subLabel ? Math.round(subLabelSize / 2 + 9) : 0; + const hentityLabel = Math.round(subLabelSize / 2 + 9); + + drawRoundRect( + context, + x, + y - hSubLabel - 12, + w, + hentityLabel + hLabel + hSubLabel + 12, + 5, + ); + context.closePath(); + context.fill(); + + // Remove shadow for text + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + context.shadowBlur = 0; + context.shadowColor = "transparent"; + + // And finally we draw the labels + context.fillStyle = TEXT_COLOR; + context.font = `${weight} ${size}px ${font}`; + context.fillText(label, data.x + data.size + 3, data.y + size / 3); + + if (subLabel) { + context.fillStyle = TEXT_COLOR; + context.font = `${weight} ${subLabelSize}px ${font}`; + context.fillText( + subLabel, + data.x + data.size + 3, + data.y - (2 * size) / 3 - 2, + ); + } + + context.fillStyle = data.color; + context.font = `${weight} ${subLabelSize}px ${font}`; + context.fillText( + entityLabel, + data.x + data.size + 3, + data.y + size / 3 + 3 + subLabelSize, + ); } diff --git a/apps/webapp/app/components/icon-utils.tsx b/apps/webapp/app/components/icon-utils.tsx index cc45617..db5e00b 100644 --- a/apps/webapp/app/components/icon-utils.tsx +++ b/apps/webapp/app/components/icon-utils.tsx @@ -6,6 +6,10 @@ import { } from "@remixicon/react"; import { LayoutGrid } from "lucide-react"; import { LinearIcon, SlackIcon } from "./icons"; +import { Cursor } from "./icons/cursor"; +import { Claude } from "./icons/claude"; +import { Cline } from "./icons/cline"; +import { VSCode } from "./icons/vscode"; export const ICON_MAPPING = { slack: SlackIcon, @@ -15,6 +19,10 @@ export const ICON_MAPPING = { gmail: RiMailFill, linear: LinearIcon, + cursor: Cursor, + claude: Claude, + cline: Cline, + vscode: VSCode, // Default icon integration: LayoutGrid, diff --git a/apps/webapp/app/components/icons/claude.tsx b/apps/webapp/app/components/icons/claude.tsx new file mode 100644 index 0000000..bb81634 --- /dev/null +++ b/apps/webapp/app/components/icons/claude.tsx @@ -0,0 +1,20 @@ +import type { IconProps } from "./types"; + +export function Claude({ size = 18, className }: IconProps) { + return ( + + Claude + + + ); +} diff --git a/apps/webapp/app/components/icons/cline.tsx b/apps/webapp/app/components/icons/cline.tsx new file mode 100644 index 0000000..669a25a --- /dev/null +++ b/apps/webapp/app/components/icons/cline.tsx @@ -0,0 +1,19 @@ +import type { IconProps } from "./types"; + +export function Cline({ size = 18, className }: IconProps) { + return ( + + Cline + + + + ); +} diff --git a/apps/webapp/app/components/icons/cursor.tsx b/apps/webapp/app/components/icons/cursor.tsx new file mode 100644 index 0000000..d3bd83d --- /dev/null +++ b/apps/webapp/app/components/icons/cursor.tsx @@ -0,0 +1,64 @@ +import type { IconProps } from "./types"; + +export function Cursor({ size = 18, className }: IconProps) { + return ( + + Cursor + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/apps/webapp/app/components/icons/vscode.tsx b/apps/webapp/app/components/icons/vscode.tsx new file mode 100644 index 0000000..56db7d5 --- /dev/null +++ b/apps/webapp/app/components/icons/vscode.tsx @@ -0,0 +1,26 @@ +import type { IconProps } from "./types"; + +export function VSCode({ size = 18, className }: IconProps) { + return ( + + + + + + ); +} diff --git a/apps/webapp/app/components/integrations/integration-card.tsx b/apps/webapp/app/components/integrations/integration-card.tsx index 5d4bb47..a71e943 100644 --- a/apps/webapp/app/components/integrations/integration-card.tsx +++ b/apps/webapp/app/components/integrations/integration-card.tsx @@ -1,10 +1,7 @@ -import React from "react"; import { Link } from "@remix-run/react"; import { Card, - CardContent, CardDescription, - CardFooter, CardHeader, CardTitle, } from "~/components/ui/card"; @@ -35,23 +32,24 @@ export function IntegrationCard({ > -
- +
+
+ +
+ + {isConnected && ( +
+ + Connected + +
+ )}
{integration.name} {integration.description || `Connect to ${integration.name}`} - {isConnected && ( - -
- - Connected - -
-
- )} ); diff --git a/apps/webapp/app/components/integrations/utils.tsx b/apps/webapp/app/components/integrations/utils.tsx new file mode 100644 index 0000000..05ac9b3 --- /dev/null +++ b/apps/webapp/app/components/integrations/utils.tsx @@ -0,0 +1,34 @@ +export const FIXED_INTEGRATIONS = [ + { + id: "claude", + name: "Claude", + description: "AI assistant for coding, writing, and analysis", + icon: "claude", + slug: "claude", + spec: {}, + }, + { + id: "cursor", + name: "Cursor", + description: "AI-powered code editor", + icon: "cursor", + slug: "cursor", + spec: {}, + }, + { + id: "cline", + name: "Cline", + description: "AI coding assistant for terminal and command line", + icon: "cline", + slug: "cline", + spec: {}, + }, + { + id: "vscode", + name: "Visual Studio Code", + description: "Popular code editor with extensive extensions", + icon: "vscode", + slug: "vscode", + spec: {}, + }, +]; diff --git a/apps/webapp/app/components/logs/log-text-collapse.tsx b/apps/webapp/app/components/logs/log-text-collapse.tsx index dfc6123..5ad3eb0 100644 --- a/apps/webapp/app/components/logs/log-text-collapse.tsx +++ b/apps/webapp/app/components/logs/log-text-collapse.tsx @@ -4,6 +4,7 @@ import { AlertCircle, Info, Trash } from "lucide-react"; import { cn } from "~/lib/utils"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog"; import { Button } from "../ui"; +import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; import { AlertDialog, AlertDialogAction, @@ -15,21 +16,42 @@ import { AlertDialogTitle, AlertDialogTrigger, } from "../ui/alert-dialog"; +import { Badge } from "../ui/badge"; +import { type LogItem } from "~/hooks/use-logs"; interface LogTextCollapseProps { text?: string; error?: string; logData: any; + log: LogItem; id: string; episodeUUID?: string; } +const getStatusColor = (status: string) => { + switch (status) { + case "PROCESSING": + return "bg-blue-100 text-blue-800 hover:bg-blue-100 hover:text-blue-800"; + case "PENDING": + return "bg-yellow-100 text-yellow-800 hover:bg-yellow-100 hover:text-yellow-800"; + case "COMPLETED": + return "bg-success/10 text-success hover:bg-success/10 hover:text-success"; + case "FAILED": + return "bg-destructive/10 text-destructive hover:bg-destructive/10 hover:text-destructive"; + case "CANCELLED": + return "bg-gray-100 text-gray-800 hover:bg-gray-100 hover:text-gray-800"; + default: + return "bg-gray-100 text-gray-800 hover:bg-gray-100 hover:text-gray-800"; + } +}; + export function LogTextCollapse({ episodeUUID, text, error, id, logData, + log, }: LogTextCollapseProps) { const [dialogOpen, setDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); @@ -75,19 +97,28 @@ export function LogTextCollapse({ } return ( - <> -
-

+

+
+ > +
+
setDialogOpen(true)} + > +
+
- {isLong && ( - <> @@ -101,66 +132,84 @@ export function LogTextCollapse({ style={{ lineHeight: "1.5" }} dangerouslySetInnerHTML={{ __html: text }} /> + {error && ( +
+
+ +
+

+ Error Details +

+

+ {error} +

+
+
+
+ )}
- - )} -
-
- {isLong && ( -
- - {episodeUUID && ( - - - - - - - Delete Episode - - Are you sure you want to delete this episode? This action - cannot be undone. - - - - Cancel - - Continue - - - - - )} +
+
+ + {log.status.charAt(0).toUpperCase() + + log.status.slice(1).toLowerCase()} + + +
+ {new Date(log.time).toLocaleString()} +
+ + + {episodeUUID && ( + + + + + + + Delete Episode + + Are you sure you want to delete this episode? This + action cannot be undone. + + + + Cancel + + Continue + + + + + )} +
+
- )} - {error && ( -
- - - {error} - -
- )} +
- +
); } diff --git a/apps/webapp/app/components/logs/logs-filters.tsx b/apps/webapp/app/components/logs/logs-filters.tsx index 4d391f5..4ee1d47 100644 --- a/apps/webapp/app/components/logs/logs-filters.tsx +++ b/apps/webapp/app/components/logs/logs-filters.tsx @@ -51,7 +51,7 @@ export function LogsFilters({ const handleBack = () => setStep("main"); return ( -
+
{ diff --git a/apps/webapp/app/components/logs/virtual-logs-list.tsx b/apps/webapp/app/components/logs/virtual-logs-list.tsx index 39d5dc1..c8e15f8 100644 --- a/apps/webapp/app/components/logs/virtual-logs-list.tsx +++ b/apps/webapp/app/components/logs/virtual-logs-list.tsx @@ -9,7 +9,6 @@ import { } from "react-virtualized"; import { type LogItem } from "~/hooks/use-logs"; import { Badge } from "~/components/ui/badge"; -import { Card, CardContent } from "~/components/ui/card"; import { cn } from "~/lib/utils"; import { ScrollManagedList } from "../virtualized-list"; import { LogTextCollapse } from "./log-text-collapse"; @@ -46,23 +45,6 @@ function LogItemRenderer( ); } - const getStatusColor = (status: string) => { - switch (status) { - case "PROCESSING": - return "bg-blue-100 text-blue-800 hover:bg-blue-100 hover:text-blue-800"; - case "PENDING": - return "bg-yellow-100 text-yellow-800 hover:bg-yellow-100 hover:text-yellow-800"; - case "COMPLETED": - return "bg-green-100 text-green-800 hover:bg-green-100 hover:text-green-800"; - case "FAILED": - return "bg-red-100 text-red-800 hover:bg-red-100 hover:text-red-800"; - case "CANCELLED": - return "bg-gray-100 text-gray-800 hover:bg-gray-100 hover:text-gray-800"; - default: - return "bg-gray-100 text-gray-800 hover:bg-gray-100 hover:text-gray-800"; - } - }; - return ( -
- - -
-
- - {log.source} - -
- - {log.status.charAt(0).toUpperCase() + - log.status.slice(1).toLowerCase()} - -
-
-
- {new Date(log.time).toLocaleString()} -
-
- - -
-
+
+
+ +
); diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts index 42aad77..98ef69c 100644 --- a/apps/webapp/app/env.server.ts +++ b/apps/webapp/app/env.server.ts @@ -9,6 +9,7 @@ const EnvironmentSchema = z.object({ z.literal("production"), z.literal("test"), ]), + POSTGRES_DB: z.string(), DATABASE_URL: z .string() .refine( diff --git a/apps/webapp/app/lib/neo4j.server.ts b/apps/webapp/app/lib/neo4j.server.ts index f82a424..43fe71c 100644 --- a/apps/webapp/app/lib/neo4j.server.ts +++ b/apps/webapp/app/lib/neo4j.server.ts @@ -102,6 +102,153 @@ export const getNodeLinks = async (userId: string) => { return triplets; }; +// Get graph data with cluster information for reified graph +export const getClusteredGraphData = async (userId: string) => { + const session = driver.session(); + try { + // Get the proper reified graph structure: Entity -> Statement -> Entity + const result = await session.run( + `// Get all statements and their entity connections for reified graph + MATCH (s:Statement) + WHERE s.userId = $userId AND s.invalidAt IS NULL + + // Get all entities connected to each statement + MATCH (s)-[:HAS_SUBJECT]->(subj:Entity) + MATCH (s)-[:HAS_PREDICATE]->(pred:Entity) + MATCH (s)-[:HAS_OBJECT]->(obj:Entity) + + // Return both Entity->Statement and Statement->Entity relationships + WITH s, subj, pred, obj + UNWIND [ + // Subject Entity -> Statement + {source: subj, target: s, type: 'HAS_SUBJECT', isEntityToStatement: true}, + // Statement -> Predicate Entity + {source: s, target: pred, type: 'HAS_PREDICATE', isStatementToEntity: true}, + // Statement -> Object Entity + {source: s, target: obj, type: 'HAS_OBJECT', isStatementToEntity: true} + ] AS rel + + RETURN DISTINCT + rel.source.uuid as sourceUuid, + rel.source.name as sourceName, + rel.source.labels as sourceLabels, + rel.source.type as sourceType, + rel.source.properties as sourceProperties, + rel.target.uuid as targetUuid, + rel.target.name as targetName, + rel.target.type as targetType, + rel.target.labels as targetLabels, + rel.target.properties as targetProperties, + rel.type as relationshipType, + s.uuid as statementUuid, + s.clusterId as clusterId, + s.fact as fact, + s.createdAt as createdAt, + rel.isEntityToStatement as isEntityToStatement, + rel.isStatementToEntity as isStatementToEntity`, + { userId }, + ); + + const triplets: RawTriplet[] = []; + const processedEdges = new Set(); + + result.records.forEach((record) => { + const sourceUuid = record.get("sourceUuid"); + const sourceName = record.get("sourceName"); + const sourceType = record.get("sourceType"); + const sourceLabels = record.get("sourceLabels") || []; + const sourceProperties = record.get("sourceProperties") || {}; + + const targetUuid = record.get("targetUuid"); + const targetName = record.get("targetName"); + const targetLabels = record.get("targetLabels") || []; + const targetProperties = record.get("targetProperties") || {}; + const targetType = record.get("targetType"); + + const relationshipType = record.get("relationshipType"); + const statementUuid = record.get("statementUuid"); + const clusterId = record.get("clusterId"); + const fact = record.get("fact"); + const createdAt = record.get("createdAt"); + + // Create unique edge identifier to avoid duplicates + const edgeKey = `${sourceUuid}-${targetUuid}-${relationshipType}`; + if (processedEdges.has(edgeKey)) return; + processedEdges.add(edgeKey); + + // Determine node types and add appropriate cluster information + const isSourceStatement = + sourceLabels.includes("Statement") || sourceUuid === statementUuid; + const isTargetStatement = + targetLabels.includes("Statement") || targetUuid === statementUuid; + + // Statement nodes get cluster info, Entity nodes get default attributes + const sourceAttributes = isSourceStatement + ? { + ...sourceProperties, + clusterId, + nodeType: "Statement", + fact, + } + : { + ...sourceProperties, + nodeType: "Entity", + type: sourceType, + name: sourceName, + }; + + const targetAttributes = isTargetStatement + ? { + ...targetProperties, + clusterId, + nodeType: "Statement", + fact, + } + : { + ...targetProperties, + nodeType: "Entity", + type: targetType, + name: targetName, + }; + + triplets.push({ + sourceNode: { + uuid: sourceUuid, + labels: sourceLabels, + attributes: sourceAttributes, + name: isSourceStatement ? fact : sourceName || sourceUuid, + clusterId, + createdAt: createdAt || "", + }, + edge: { + uuid: `${sourceUuid}-${targetUuid}-${relationshipType}`, + type: relationshipType, + source_node_uuid: sourceUuid, + target_node_uuid: targetUuid, + createdAt: createdAt || "", + }, + targetNode: { + uuid: targetUuid, + labels: targetLabels, + attributes: targetAttributes, + clusterId, + name: isTargetStatement ? fact : targetName || targetUuid, + createdAt: createdAt || "", + }, + }); + }); + + return triplets; + } catch (error) { + logger.error( + `Error getting clustered graph data for user ${userId}: ${error}`, + ); + throw error; + } finally { + await session.close(); + } +}; + export async function initNeo4jSchemaOnce() { if (schemaInitialized) return; @@ -141,6 +288,9 @@ const initializeSchema = async () => { await runQuery( "CREATE CONSTRAINT statement_uuid IF NOT EXISTS FOR (n:Statement) REQUIRE n.uuid IS UNIQUE", ); + await runQuery( + "CREATE CONSTRAINT cluster_uuid IF NOT EXISTS FOR (n:Cluster) REQUIRE n.uuid IS UNIQUE", + ); // Create indexes for better query performance await runQuery( @@ -152,9 +302,18 @@ const initializeSchema = async () => { await runQuery( "CREATE INDEX statement_invalid_at IF NOT EXISTS FOR (n:Statement) ON (n.invalidAt)", ); + await runQuery( + "CREATE INDEX statement_cluster_id IF NOT EXISTS FOR (n:Statement) ON (n.clusterId)", + ); await runQuery( "CREATE INDEX entity_name IF NOT EXISTS FOR (n:Entity) ON (n.name)", ); + await runQuery( + "CREATE INDEX cluster_user_id IF NOT EXISTS FOR (n:Cluster) ON (n.userId)", + ); + await runQuery( + "CREATE INDEX cluster_aspect_type IF NOT EXISTS FOR (n:Cluster) ON (n.aspectType)", + ); // Create vector indexes for semantic search (if using Neo4j 5.0+) await runQuery(` diff --git a/apps/webapp/app/routes/api.v1.activity.contribution.tsx b/apps/webapp/app/routes/api.v1.activity.contribution.tsx new file mode 100644 index 0000000..41ecf76 --- /dev/null +++ b/apps/webapp/app/routes/api.v1.activity.contribution.tsx @@ -0,0 +1,67 @@ +import { type LoaderFunctionArgs, json } from "@remix-run/node"; +import { requireUserId } from "~/services/session.server"; +import { prisma } from "~/db.server"; + +export async function loader({ request }: LoaderFunctionArgs) { + const userId = await requireUserId(request); + + // Get user's workspace + const user = await prisma.user.findUnique({ + where: { id: userId }, + select: { Workspace: { select: { id: true } } }, + }); + + if (!user?.Workspace) { + throw new Response("Workspace not found", { status: 404 }); + } + + // Get activity data for the last year + const oneYearAgo = new Date(); + oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1); + + const activities = await prisma.ingestionQueue.findMany({ + where: { + workspaceId: user.Workspace.id, + createdAt: { + gte: oneYearAgo, + }, + }, + select: { + createdAt: true, + status: true, + }, + orderBy: { + createdAt: "desc", + }, + }); + + // Group activities by date + const activityByDate = activities.reduce( + (acc, activity) => { + const date = activity.createdAt.toISOString().split("T")[0]; + if (!acc[date]) { + acc[date] = { count: 0, status: activity.status }; + } + acc[date].count += 1; + return acc; + }, + {} as Record, + ); + + // Convert to array format for the component + const contributionData = Object.entries(activityByDate).map( + ([date, data]) => ({ + date, + count: data.count, + status: data.status, + }), + ); + + return json({ + success: true, + data: { + contributionData, + totalActivities: activities.length, + }, + }); +} diff --git a/apps/webapp/app/routes/api.v1.clusters.$clusterId.statements.tsx b/apps/webapp/app/routes/api.v1.clusters.$clusterId.statements.tsx new file mode 100644 index 0000000..fb267f1 --- /dev/null +++ b/apps/webapp/app/routes/api.v1.clusters.$clusterId.statements.tsx @@ -0,0 +1,50 @@ +import { json } from "@remix-run/node"; +import { z } from "zod"; +import { logger } from "~/services/logger.service"; +import { + createLoaderApiRoute, +} from "~/services/routeBuilders/apiBuilder.server"; +import { ClusteringService } from "~/services/clustering.server"; + +const clusteringService = new ClusteringService(); + +const loader = createLoaderApiRoute( + { + allowJWT: true, + findResource: async () => 1, // Dummy resource + authorization: { + action: "search", + }, + corsStrategy: "all", + params: z.object({ + clusterId: z.string(), + }), + }, + async ({ authentication, params }) => { + try { + const statements = await clusteringService.getClusterStatements( + params.clusterId, + authentication.userId, + ); + + return json({ + success: true, + data: { + clusterId: params.clusterId, + statements: statements, + }, + }); + } catch (error) { + logger.error("Error getting cluster statements:", { error }); + return json( + { + success: false, + error: error instanceof Error ? error.message : "Unknown error", + }, + { status: 500 }, + ); + } + }, +); + +export { loader }; \ No newline at end of file diff --git a/apps/webapp/app/routes/api.v1.clusters.$clusterId.tsx b/apps/webapp/app/routes/api.v1.clusters.$clusterId.tsx new file mode 100644 index 0000000..60274aa --- /dev/null +++ b/apps/webapp/app/routes/api.v1.clusters.$clusterId.tsx @@ -0,0 +1,40 @@ +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import { ClusteringService } from "~/services/clustering.server"; +import { logger } from "~/services/logger.service"; +import { requireUser } from "~/services/session.server"; + +const clusteringService = new ClusteringService(); + +export async function loader({ request, params }: LoaderFunctionArgs) { + try { + const user = await requireUser(request); + const { clusterId } = params; + + if (!clusterId) { + return json( + { success: false, error: "Cluster ID is required" }, + { status: 400 } + ); + } + + const statements = await clusteringService.getClusterStatements(clusterId, user.id); + + return json({ + success: true, + data: { + clusterId, + statements + } + }); + + } catch (error) { + logger.error("Error fetching cluster statements:", { error }); + return json( + { + success: false, + error: error instanceof Error ? error.message : "Unknown error" + }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/apps/webapp/app/routes/api.v1.clusters.drift.tsx b/apps/webapp/app/routes/api.v1.clusters.drift.tsx new file mode 100644 index 0000000..f565d2c --- /dev/null +++ b/apps/webapp/app/routes/api.v1.clusters.drift.tsx @@ -0,0 +1,29 @@ +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import { ClusteringService } from "~/services/clustering.server"; +import { logger } from "~/services/logger.service"; +import { requireUser } from "~/services/session.server"; + +const clusteringService = new ClusteringService(); + +export async function loader({ request }: LoaderFunctionArgs) { + try { + const user = await requireUser(request); + + const driftMetrics = await clusteringService.detectClusterDrift(user.id); + + return json({ + success: true, + data: driftMetrics + }); + + } catch (error) { + logger.error("Error checking cluster drift:", { error }); + return json( + { + success: false, + error: error instanceof Error ? error.message : "Unknown error" + }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/apps/webapp/app/routes/api.v1.graph.clustered.tsx b/apps/webapp/app/routes/api.v1.graph.clustered.tsx new file mode 100644 index 0000000..c285a34 --- /dev/null +++ b/apps/webapp/app/routes/api.v1.graph.clustered.tsx @@ -0,0 +1,46 @@ +import { json } from "@remix-run/node"; +import { logger } from "~/services/logger.service"; +import { + createHybridLoaderApiRoute, + createLoaderApiRoute, +} from "~/services/routeBuilders/apiBuilder.server"; +import { getClusteredGraphData } from "~/lib/neo4j.server"; +import { ClusteringService } from "~/services/clustering.server"; + +const clusteringService = new ClusteringService(); + +const loader = createHybridLoaderApiRoute( + { + allowJWT: true, + corsStrategy: "all", + findResource: async () => 1, + }, + async ({ authentication }) => { + try { + // Get clustered graph data and cluster metadata in parallel + const [graphData, clusters] = await Promise.all([ + getClusteredGraphData(authentication.userId), + clusteringService.getClusters(authentication.userId), + ]); + + return json({ + success: true, + data: { + triplets: graphData, + clusters: clusters, + }, + }); + } catch (error) { + logger.error("Error in clustered graph loader:", { error }); + return json( + { + success: false, + error: error instanceof Error ? error.message : "Unknown error", + }, + { status: 500 }, + ); + } + }, +); + +export { loader }; diff --git a/apps/webapp/app/routes/api.v1.integration_account.disconnect.tsx b/apps/webapp/app/routes/api.v1.integration_account.disconnect.tsx index 95defab..189d946 100644 --- a/apps/webapp/app/routes/api.v1.integration_account.disconnect.tsx +++ b/apps/webapp/app/routes/api.v1.integration_account.disconnect.tsx @@ -4,6 +4,8 @@ import { requireUserId } from "~/services/session.server"; import { logger } from "~/services/logger.service"; import { prisma } from "~/db.server"; import { triggerIntegrationWebhook } from "~/trigger/webhooks/integration-webhook-delivery"; +import { scheduler } from "~/trigger/integrations/scheduler"; +import { schedules } from "@trigger.dev/sdk"; export async function action({ request }: ActionFunctionArgs) { if (request.method !== "POST") { @@ -28,6 +30,10 @@ export async function action({ request }: ActionFunctionArgs) { }, }); + const integrationAccountSettings = updatedAccount.settings as any; + + await schedules.del(integrationAccountSettings.scheduleId); + await triggerIntegrationWebhook( integrationAccountId, userId, diff --git a/apps/webapp/app/routes/api.v1.mcp.memory.tsx b/apps/webapp/app/routes/api.v1.mcp.memory.tsx index 3a26fc7..76013a1 100644 --- a/apps/webapp/app/routes/api.v1.mcp.memory.tsx +++ b/apps/webapp/app/routes/api.v1.mcp.memory.tsx @@ -17,22 +17,6 @@ const transports: { }; } = {}; -// Cleanup old sessions every 5 minutes -setInterval( - () => { - const now = Date.now(); - const maxAge = 30 * 60 * 1000; // 30 minutes - - Object.keys(transports).forEach((sessionId) => { - if (now - transports[sessionId].createdAt > maxAge) { - transports[sessionId].transport.close(); - delete transports[sessionId]; - } - }); - }, - 5 * 60 * 1000, -); - // MCP request body schema const MCPRequestSchema = z.object({}).passthrough(); const SourceParams = z.object({ diff --git a/apps/webapp/app/routes/home.dashboard.tsx b/apps/webapp/app/routes/home.dashboard.tsx index c079ff6..cddc8b7 100644 --- a/apps/webapp/app/routes/home.dashboard.tsx +++ b/apps/webapp/app/routes/home.dashboard.tsx @@ -1,11 +1,13 @@ -import { useState, useEffect } from "react"; +import React, { useState } from "react"; +import { useFetcher } from "@remix-run/react"; import { type LoaderFunctionArgs } from "@remix-run/server-runtime"; import { requireUserId } from "~/services/session.server"; import { useTypedLoaderData } from "remix-typedjson"; -import { GraphVisualizationClient } from "~/components/graph/graph-client"; import { LoaderCircle } from "lucide-react"; import { PageHeader } from "~/components/common/page-header"; +import { GraphVisualizationClient } from "~/components/graph/graph-client"; +import { GraphNode } from "~/components/graph/type"; export async function loader({ request }: LoaderFunctionArgs) { // Only return userId, not the heavy nodeLinks @@ -15,38 +17,31 @@ export async function loader({ request }: LoaderFunctionArgs) { export default function Dashboard() { const { userId } = useTypedLoaderData(); + const fetcher = useFetcher(); + const [selectedClusterId, setSelectedClusterId] = useState( + null, + ); - // State for nodeLinks and loading - const [nodeLinks, setNodeLinks] = useState(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - let cancelled = false; - async function fetchNodeLinks() { - setLoading(true); - try { - const res = await fetch( - "/node-links?userId=" + - encodeURIComponent("cmc0x85jv0000nu1wiu1yla73"), - ); - if (!res.ok) throw new Error("Failed to fetch node links"); - const data = await res.json(); - if (!cancelled) { - setNodeLinks(data); - setLoading(false); - } - } catch (e) { - if (!cancelled) { - setNodeLinks([]); - setLoading(false); - } - } + // Kick off the fetcher on mount if not already done + React.useEffect(() => { + if (userId && fetcher.state === "idle" && !fetcher.data) { + fetcher.load("/api/v1/graph/clustered"); } - fetchNodeLinks(); - return () => { - cancelled = true; - }; - }, [userId]); + }, [userId, fetcher]); + + // Determine loading state + const loading = + fetcher.state === "loading" || + fetcher.state === "submitting" || + !fetcher.data; + + // Get graph data from fetcher + let graphData: any = null; + if (fetcher.data && fetcher.data.success) { + graphData = fetcher.data.data; + } else if (fetcher.data && !fetcher.data.success) { + graphData = { triplets: [], clusters: [] }; + } return ( <> @@ -60,7 +55,15 @@ export default function Dashboard() {
) : ( typeof window !== "undefined" && - nodeLinks && + graphData && ( + + ) )}
diff --git a/apps/webapp/app/routes/home.integration.$slug.tsx b/apps/webapp/app/routes/home.integration.$slug.tsx index 75aae0b..1d99356 100644 --- a/apps/webapp/app/routes/home.integration.$slug.tsx +++ b/apps/webapp/app/routes/home.integration.$slug.tsx @@ -1,10 +1,10 @@ -import React, { useMemo } from "react"; +import React, { useMemo, useState } from "react"; import { json, type LoaderFunctionArgs, type ActionFunctionArgs, } from "@remix-run/node"; -import { useLoaderData } from "@remix-run/react"; +import { useLoaderData, useParams } from "@remix-run/react"; import { requireUserId, requireWorkpace } from "~/services/session.server"; import { getIntegrationDefinitions } from "~/services/integrationDefinition.server"; import { getIntegrationAccounts } from "~/services/integrationAccount.server"; @@ -21,7 +21,15 @@ import { } from "~/services/ingestionRule.server"; import { Section } from "~/components/integrations/section"; import { PageHeader } from "~/components/common/page-header"; -import { Plus } from "lucide-react"; +import { Check, Copy, Plus } from "lucide-react"; +import { FIXED_INTEGRATIONS } from "~/components/integrations/utils"; +import { + IngestionRule, + type IntegrationAccount, + IntegrationDefinitionV2, +} from "@prisma/client"; +import { Input } from "~/components/ui/input"; +import { Button } from "~/components/ui"; export async function loader({ request, params }: LoaderFunctionArgs) { const userId = await requireUserId(request); @@ -33,7 +41,10 @@ export async function loader({ request, params }: LoaderFunctionArgs) { getIntegrationAccounts(userId), ]); - const integration = integrationDefinitions.find( + // Combine fixed integrations with dynamic ones + const allIntegrations = [...FIXED_INTEGRATIONS, ...integrationDefinitions]; + + const integration = allIntegrations.find( (def) => def.slug === slug || def.id === slug, ); @@ -78,7 +89,10 @@ export async function action({ request, params }: ActionFunctionArgs) { getIntegrationAccounts(userId), ]); - const integration = integrationDefinitions.find( + // Combine fixed integrations with dynamic ones + const allIntegrations = [...FIXED_INTEGRATIONS, ...integrationDefinitions]; + + const integration = allIntegrations.find( (def) => def.slug === slug || def.id === slug, ); @@ -119,14 +133,311 @@ function parseSpec(spec: any) { return spec; } -export default function IntegrationDetail() { - const { integration, integrationAccounts, ingestionRule } = - useLoaderData(); +function CustomIntegrationContent({ integration }: { integration: any }) { + const memoryUrl = `https://core.heysol.ai/api/v1/mcp/memory?source=${integration.slug}`; + const [copied, setCopied] = useState(false); + const copyToClipboard = async () => { + try { + await navigator.clipboard.writeText(memoryUrl); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (err) { + console.error("Failed to copy:", err); + } + }; + + const getCustomContent = () => { + switch (integration.id) { + case "claude": + return { + title: "About Claude", + content: ( +
+

+ Claude is an AI assistant created by Anthropic. It can help with + a wide variety of tasks including: +

+
    +
  • Code generation and debugging
  • +
  • Writing and editing
  • +
  • Analysis and research
  • +
  • Problem-solving
  • +
+ +

+ For Claude Web, Desktop, and Code - OAuth authentication handled + automatically +

+ +
+ + +
+
+ ), + }; + case "cursor": + return { + title: "About Cursor", + content: ( +
+

+ Cursor is an AI-powered code editor that helps developers write + code faster and more efficiently. +

+
    +
  • AI-powered code completion
  • +
  • Natural language to code conversion
  • +
  • Code explanation and debugging
  • +
  • Refactoring assistance
  • +
+
+
+                  {JSON.stringify(
+                    {
+                      memory: {
+                        url: memoryUrl,
+                      },
+                    },
+                    null,
+                    2,
+                  )}
+                
+ +
+
+ ), + }; + case "cline": + return { + title: "About Cline", + content: ( +
+

+ Cline is an AI coding assistant that works directly in your + terminal and command line environment. +

+
    +
  • Command line AI assistance
  • +
  • Terminal-based code generation
  • +
  • Shell script optimization
  • +
  • DevOps automation help
  • +
+
+ + +
+
+ ), + }; + case "vscode": + return { + title: "About Visual Studio Code", + content: ( +
+

+ Visual Studio Code is a lightweight but powerful source code + editor with extensive extension support. +

+
    +
  • Intelligent code completion
  • +
  • Built-in Git integration
  • +
  • Extensive extension marketplace
  • +
  • Debugging and testing tools
  • +
+

You need to enable MCP in settings

+
+
+                  {JSON.stringify(
+                    {
+                      "chat.mcp.enabled": true,
+                      "chat.mcp.discovery.enabled": true,
+                    },
+                    null,
+                    2,
+                  )}
+                
+
+
+
+                  {JSON.stringify(
+                    {
+                      memory: {
+                        type: "http",
+                        url: memoryUrl,
+                      },
+                    },
+                    null,
+                    2,
+                  )}
+                
+ +
+
+ ), + }; + default: + return null; + } + }; + + const customContent = getCustomContent(); + + if (!customContent) return null; + const Component = getIcon(integration.icon as IconType); + + return ( +
+ , + onClick: () => + window.open( + "https://github.com/redplanethq/core/issues/new", + "_blank", + ), + variant: "secondary", + }, + ]} + /> +
+
+
+ +
+ } + > +
{customContent.content}
+ +
+
+
+ ); +} + +interface IntegrationDetailProps { + integration: any; + integrationAccounts: any; + ingestionRule: any; +} + +export function IntegrationDetail({ + integration, + integrationAccounts, + ingestionRule, +}: IntegrationDetailProps) { const activeAccount = useMemo( () => integrationAccounts.find( - (acc) => acc.integrationDefinitionId === integration.id && acc.isActive, + (acc: IntegrationAccount) => + acc.integrationDefinitionId === integration.id && acc.isActive, ), [integrationAccounts, integration.id], ); @@ -181,21 +492,21 @@ export default function IntegrationDetail() {
{hasApiKey && (
- + API Key authentication
)} {hasOAuth2 && (
- + OAuth 2.0 authentication
)} {!hasApiKey && !hasOAuth2 && !hasMCPAuth && ( -
+
No authentication method specified
)} @@ -226,7 +537,7 @@ export default function IntegrationDetail() { )} {/* Connected Account Info */} - + {/* MCP Authentication Section */} ); } + +export default function IntegrationDetailWrapper() { + const { integration, integrationAccounts, ingestionRule } = + useLoaderData(); + + const { slug } = useParams(); + // You can now use the `slug` param in your component + + const fixedIntegration = FIXED_INTEGRATIONS.some( + (fixedInt) => fixedInt.slug === slug, + ); + + return ( + <> + {fixedIntegration ? ( + + ) : ( + + )} + + ); +} diff --git a/apps/webapp/app/routes/home.integrations.tsx b/apps/webapp/app/routes/home.integrations.tsx index 1a8af63..65a83e9 100644 --- a/apps/webapp/app/routes/home.integrations.tsx +++ b/apps/webapp/app/routes/home.integrations.tsx @@ -8,6 +8,7 @@ import { getIntegrationAccounts } from "~/services/integrationAccount.server"; import { IntegrationGrid } from "~/components/integrations/integration-grid"; import { PageHeader } from "~/components/common/page-header"; import { Plus } from "lucide-react"; +import { FIXED_INTEGRATIONS } from "~/components/integrations/utils"; export async function loader({ request }: LoaderFunctionArgs) { const userId = await requireUserId(request); @@ -18,8 +19,11 @@ export async function loader({ request }: LoaderFunctionArgs) { getIntegrationAccounts(userId), ]); + // Combine fixed integrations with dynamic ones + const allIntegrations = [...FIXED_INTEGRATIONS, ...integrationDefinitions]; + return json({ - integrationDefinitions, + integrationDefinitions: allIntegrations, integrationAccounts, userId, }); diff --git a/apps/webapp/app/routes/home.logs.all.tsx b/apps/webapp/app/routes/home.logs.all.tsx index 439c520..dc96362 100644 --- a/apps/webapp/app/routes/home.logs.all.tsx +++ b/apps/webapp/app/routes/home.logs.all.tsx @@ -1,5 +1,5 @@ -import { useState } from "react"; -import { useNavigate } from "@remix-run/react"; +import { useState, useEffect } from "react"; +import { useNavigate, useFetcher } from "@remix-run/react"; import { useLogs } from "~/hooks/use-logs"; import { LogsFilters } from "~/components/logs/logs-filters"; import { VirtualLogsList } from "~/components/logs/virtual-logs-list"; @@ -7,11 +7,13 @@ import { AppContainer, PageContainer } from "~/components/layout/app-layout"; import { Card, CardContent } from "~/components/ui/card"; import { Database, LoaderCircle } from "lucide-react"; import { PageHeader } from "~/components/common/page-header"; +import { ContributionGraph } from "~/components/activity/contribution-graph"; export default function LogsAll() { const navigate = useNavigate(); const [selectedSource, setSelectedSource] = useState(); const [selectedStatus, setSelectedStatus] = useState(); + const contributionFetcher = useFetcher(); const { logs, @@ -26,17 +28,41 @@ export default function LogsAll() { status: selectedStatus, }); + // Fetch contribution data on mount + useEffect(() => { + if (contributionFetcher.state === "idle" && !contributionFetcher.data) { + contributionFetcher.load("/api/v1/activity/contribution"); + } + }, [contributionFetcher]); + + // Get contribution data from fetcher + const contributionData = contributionFetcher.data?.success + ? contributionFetcher.data.data.contributionData + : []; + const totalActivities = contributionFetcher.data?.success + ? contributionFetcher.data.data.totalActivities + : 0; + const isContributionLoading = + contributionFetcher.state === "loading" || !contributionFetcher.data; + return ( <> -
+
+ {/* Contribution Graph */} +
+ {isContributionLoading ? ( + + ) : ( + + )} +
{isInitialLoad ? ( <> - {" "} + ) : ( <> - {" "} {/* Filters */} {logs.length > 0 && ( ("Claude"); const [form, fields] = useForm({ lastSubmission: lastSubmission as any, @@ -117,7 +121,12 @@ export default function Onboarding() { }, }); - const memoryUrl = "https://core.heysol.ai/api/v1/mcp/memory"; + const getMemoryUrl = (source: "Claude" | "Cursor" | "Other") => { + const baseUrl = "https://core.heysol.ai/api/v1/mcp/memory"; + return `${baseUrl}?Source=${source}`; + }; + + const memoryUrl = getMemoryUrl(selectedSource); const copyToClipboard = async () => { try { @@ -144,7 +153,25 @@ export default function Onboarding() {
-
+
+
+ {(["Claude", "Cursor", "Other"] as const).map((source) => ( + + ))} +
+
{ + logger.info( + `Creating statement similarity graph for clustering (${incremental ? "incremental" : "complete"})`, + ); + + const query = ` + MATCH (s1:Statement)-[:HAS_SUBJECT|HAS_PREDICATE|HAS_OBJECT]->(e:Entity)<-[:HAS_SUBJECT|HAS_PREDICATE|HAS_OBJECT]-(s2:Statement) + WHERE s1.userId = $userId + AND s2.userId = $userId + AND s1.invalidAt IS NULL + AND s2.invalidAt IS NULL + AND id(s1) < id(s2) + WITH s1, s2, collect(DISTINCT e.uuid) as sharedEntities + WHERE size(sharedEntities) > 0 + MERGE (s1)-[r:SIMILAR_TO]-(s2) + SET r.weight = size(sharedEntities) * 2, + r.sharedEntities = sharedEntities, + r.createdAt = datetime() + RETURN count(r) as edgesCreated + `; + const result = await runQuery(query, { userId }); + const edgesCreated = result[0]?.get("edgesCreated") || 0; + + logger.info( + `${incremental ? "Updated" : "Created"} ${edgesCreated} similarity edges between statements`, + ); + } + + /** + * Execute Leiden algorithm for community detection on statement similarity graph + */ + async executeLeidenClustering( + userId: string, + incremental: boolean = false, + ): Promise { + logger.info( + `Executing Leiden clustering algorithm (${incremental ? "incremental" : "complete"})`, + ); + + // Create/update the similarity graph + await this.createStatementSimilarityGraph(userId, incremental); + + const clusteringQuery = ` + MATCH (source:Statement) WHERE source.userId = $userId + OPTIONAL MATCH (source)-[r:SIMILAR_TO]->(target:Statement) + WHERE target.userId = $userId + WITH gds.graph.project( + 'statementSimilarity_' + $userId, + source, + target, + { + relationshipProperties: r { .weight } + }, + { undirectedRelationshipTypes: ['*'] } + ) AS g + + CALL gds.leiden.write( + g.graphName, + { + writeProperty: 'tempClusterId', + relationshipWeightProperty: 'weight', + gamma: 0.7, + maxLevels: 10, + tolerance: 0.001 + } + ) + YIELD communityCount + + CALL gds.graph.drop(g.graphName) + YIELD graphName as droppedGraphName + + RETURN communityCount, g.nodeCount, g.relationshipCount + `; + + const result = await runQuery(clusteringQuery, { + userId, + gamma: this.LEIDEN_GAMMA, + maxLevels: this.LEIDEN_MAX_LEVELS, + tolerance: this.LEIDEN_TOLERANCE, + }); + + const communityCount = result[0]?.get("communityCount") || 0; + logger.info(`Leiden clustering found ${communityCount} communities`); + + // Filter clusters by minimum size and assign final cluster IDs + await this.filterAndAssignClusters(userId, incremental); + + const removeRelationsQuery = ` + MATCH (s1:Statement)-[r:SIMILAR_TO]-(s2:Statement) + WHERE s1.userId = $userId AND s2.userId = $userId + DELETE r`; + + await runQuery(removeRelationsQuery, { userId }); + } + + /** + * Perform incremental clustering for new statements + */ + async performIncrementalClustering(userId: string): Promise<{ + newStatementsProcessed: number; + newClustersCreated: number; + }> { + logger.info(`Starting incremental clustering for user ${userId}`); + + try { + // Check if there are unclustered statements + const unclusteredQuery = ` + MATCH (s:Statement) + WHERE s.userId = $userId AND s.clusterId IS NULL AND s.invalidAt IS NULL + RETURN count(s) as unclusteredCount + `; + + const unclusteredResult = await runQuery(unclusteredQuery, { userId }); + const unclusteredCount = + unclusteredResult[0]?.get("unclusteredCount") || 0; + + if (unclusteredCount === 0) { + logger.info( + "No unclustered statements found, skipping incremental clustering", + ); + return { + newStatementsProcessed: 0, + newClustersCreated: 0, + }; + } + + logger.info(`Found ${unclusteredCount} unclustered statements`); + + let newClustersCreated = 0; + // Run incremental clustering on remaining statements + await this.executeLeidenClustering(userId, true); + await this.createClusterNodes(userId); + + // Count new clusters created + const newClustersQuery = ` + MATCH (c:Cluster) + WHERE c.userId = $userId AND c.createdAt > datetime() - duration('PT5M') + RETURN count(c) as newClusters + `; + const newClustersResult = await runQuery(newClustersQuery, { userId }); + newClustersCreated = newClustersResult[0]?.get("newClusters") || 0; + + const drift = await this.detectClusterDrift(userId); + const newClustersCreatedDrift = 0; + if (drift.driftDetected) { + logger.info("Cluster drift detected, evolving clusters"); + const { newClustersCreated: newClustersCreatedDrift, splitClusters } = + await this.handleClusterDrift(userId); + + if (splitClusters.length > 0) { + logger.info("Split clusters detected, evolving clusters"); + } + } + + return { + newStatementsProcessed: unclusteredCount, + newClustersCreated: newClustersCreated + newClustersCreatedDrift, + }; + } catch (error) { + logger.error("Error in incremental clustering:", { error }); + throw error; + } + } + + /** + * Perform complete clustering (for new users or full rebuilds) + */ + async performCompleteClustering(userId: string): Promise<{ + clustersCreated: number; + statementsProcessed: number; + }> { + logger.info(`Starting complete clustering for user ${userId}`); + + try { + // Clear any existing cluster assignments + await runQuery( + ` + MATCH (s:Statement) + WHERE s.userId = $userId + REMOVE s.clusterId, s.tempClusterId + `, + { userId }, + ); + + // Clear statement-to-statement similarity relationships + await runQuery( + ` + MATCH (s1:Statement)-[r:SIMILAR_TO]-(s2:Statement) + WHERE s1.userId = $userId AND s2.userId = $userId + DELETE r + `, + { userId }, + ); + + // Clear existing cluster nodes + await runQuery( + ` + MATCH (c:Cluster) + WHERE c.userId = $userId + DETACH DELETE c + `, + { userId }, + ); + + // Execute complete clustering pipeline + await this.executeLeidenClustering(userId, false); + await this.createClusterNodes(userId); + + // Get results + const resultsQuery = ` + MATCH (c:Cluster) WHERE c.userId = $userId + WITH count(c) as clusters + MATCH (s:Statement) WHERE s.userId = $userId AND s.clusterId IS NOT NULL + RETURN clusters, count(s) as statementsProcessed + `; + + const results = await runQuery(resultsQuery, { userId }); + const clustersCreated = results[0]?.get("clusters") || 0; + const statementsProcessed = results[0]?.get("statementsProcessed") || 0; + + logger.info( + `Complete clustering finished: ${clustersCreated} clusters, ${statementsProcessed} statements processed`, + ); + + return { clustersCreated, statementsProcessed }; + } catch (error) { + logger.error("Error in complete clustering:", { error }); + throw error; + } + } + + /** + * Filter clusters by minimum size and assign final cluster IDs + */ + private async filterAndAssignClusters( + userId: string, + incremental: boolean = false, + ): Promise { + const filterQuery = ` + // Step 1: Get all temp cluster groups and their total sizes + MATCH (s:Statement) + WHERE s.userId = $userId AND s.tempClusterId IS NOT NULL + WITH s.tempClusterId as tempId, collect(s) as allStatements + + // Step 2: Filter by minimum size + WHERE size(allStatements) >= $minSize + + // Step 3: Check if any statements already have a permanent clusterId + WITH tempId, allStatements, + [stmt IN allStatements WHERE stmt.clusterId IS NOT NULL] as existingClustered, + [stmt IN allStatements WHERE stmt.clusterId IS NULL] as newStatements + + // Step 4: Determine the final cluster ID + WITH tempId, allStatements, existingClustered, newStatements, + CASE + WHEN size(existingClustered) > 0 THEN existingClustered[0].clusterId + ELSE toString(randomUUID()) + END as finalClusterId + + // Step 5: Assign cluster ID to new statements (handles empty arrays gracefully) + FOREACH (stmt IN newStatements | + SET stmt.clusterId = finalClusterId + REMOVE stmt.tempClusterId + ) + + // Step 6: Clean up temp IDs from existing statements + FOREACH (existingStmt IN existingClustered | + REMOVE existingStmt.tempClusterId + ) + + RETURN count(DISTINCT finalClusterId) as validClusters + `; + + const result = await runQuery(filterQuery, { + userId, + minSize: this.MIN_CLUSTER_SIZE, + }); + + // Remove temp cluster IDs from statements that didn't meet minimum size + await runQuery( + ` + MATCH (s:Statement) + WHERE s.userId = $userId AND s.tempClusterId IS NOT NULL + REMOVE s.tempClusterId + `, + { userId }, + ); + + const validClusters = result[0]?.get("validClusters") || 0; + + if (incremental) { + await this.updateClusterEmbeddings(userId); + } + logger.info( + `${incremental ? "Updated" : "Created"} ${validClusters} valid clusters after size filtering`, + ); + } + + /** + * Create Cluster nodes with metadata (hybrid storage approach) + * Only creates cluster nodes for cluster IDs that don't already exist + */ + async createClusterNodes(userId: string): Promise { + logger.info("Creating cluster metadata nodes for new clusters only"); + + const query = ` + MATCH (s:Statement) + WHERE s.userId = $userId AND s.clusterId IS NOT NULL + WITH s.clusterId as clusterId, collect(s) as statements + + // Only process cluster IDs that don't already have a Cluster node + WHERE NOT EXISTS { + MATCH (existing:Cluster {uuid: clusterId, userId: $userId}) + } + + // Get representative entities for naming + UNWIND statements as stmt + MATCH (stmt)-[:HAS_SUBJECT]->(subj:Entity) + MATCH (stmt)-[:HAS_PREDICATE]->(pred:Entity) + MATCH (stmt)-[:HAS_OBJECT]->(obj:Entity) + + WITH clusterId, statements, + collect(DISTINCT subj.name) as subjects, + collect(DISTINCT pred.name) as predicates, + collect(DISTINCT obj.name) as objects + + // Get top 10 most frequent entities of each type + WITH clusterId, statements, + apoc.coll.frequencies(subjects)[0..10] as topSubjects, + apoc.coll.frequencies(predicates)[0..10] as topPredicates, + apoc.coll.frequencies(objects)[0..10] as topObjects + + // Calculate cluster embedding as average of statement embeddings + WITH clusterId, statements, topSubjects, topPredicates, topObjects, + [stmt IN statements WHERE stmt.factEmbedding IS NOT NULL | stmt.factEmbedding] as validEmbeddings + + // Calculate average embedding (centroid) + WITH clusterId, statements, topSubjects, topPredicates, topObjects, validEmbeddings, + CASE + WHEN size(validEmbeddings) > 0 THEN + reduce(avg = [i IN range(0, size(validEmbeddings[0])-1) | 0.0], + embedding IN validEmbeddings | + [i IN range(0, size(embedding)-1) | avg[i] + embedding[i] / size(validEmbeddings)]) + ELSE null + END as clusterEmbedding + + CREATE (c:Cluster { + uuid: clusterId, + size: size(statements), + createdAt: datetime(), + userId: $userId, + topSubjects: [item in topSubjects | item.item], + topPredicates: [item in topPredicates | item.item], + topObjects: [item in topObjects | item.item], + clusterEmbedding: clusterEmbedding, + embeddingCount: size(validEmbeddings), + needsNaming: true + }) + + RETURN count(c) as clustersCreated + `; + + const result = await runQuery(query, { userId }); + const clustersCreated = result[0]?.get("clustersCreated") || 0; + + logger.info(`Created ${clustersCreated} new cluster metadata nodes`); + + // Only generate names for new clusters (those with needsNaming = true) + if (clustersCreated > 0) { + await this.generateClusterNames(userId); + } + } + + /** + * Calculate TF-IDF scores for a specific cluster + * + * Uses cluster-based document frequency (not statement-based) for optimal cluster naming: + * - TF: How often a term appears within this specific cluster + * - DF: How many clusters (not statements) contain this term + * - IDF: log(total_clusters / clusters_containing_term) + * + * This approach identifies terms that are frequent in THIS cluster but rare across OTHER clusters, + * making them highly distinctive for cluster naming and differentiation. + * + * Example: "SOL" appears in 100/100 statements in Cluster A, but only 1/10 total clusters + * - Cluster-based IDF: log(10/1) = high distinctiveness ✓ (good for naming) + * - Statement-based IDF: log(1000/100) = lower distinctiveness (less useful for naming) + */ + private async calculateClusterTFIDFForCluster( + userId: string, + targetClusterId: string, + ): Promise<{ + subjects: Array<{ term: string; score: number }>; + predicates: Array<{ term: string; score: number }>; + objects: Array<{ term: string; score: number }>; + } | null> { + // Get all clusters and their entity frequencies (needed for cluster-based IDF calculation) + // We need ALL clusters to calculate how rare each term is across the cluster space + const allClustersQuery = ` + MATCH (s:Statement) + WHERE s.userId = $userId AND s.clusterId IS NOT NULL + MATCH (s)-[:HAS_SUBJECT]->(subj:Entity) + MATCH (s)-[:HAS_PREDICATE]->(pred:Entity) + MATCH (s)-[:HAS_OBJECT]->(obj:Entity) + WITH s.clusterId as clusterId, + collect(DISTINCT subj.name) as subjects, + collect(DISTINCT pred.name) as predicates, + collect(DISTINCT obj.name) as objects + RETURN clusterId, subjects, predicates, objects + `; + + const allClusters = await runQuery(allClustersQuery, { + userId, + }); + + // Build document frequency maps from all clusters + // DF = number of clusters that contain each term (not number of statements) + const subjectDF = new Map(); + const predicateDF = new Map(); + const objectDF = new Map(); + const totalClusters = allClusters.length; + + // Calculate cluster-based document frequencies + // For each term, count how many different clusters it appears in + for (const record of allClusters) { + const subjects = (record.get("subjects") as string[]) || []; + const predicates = (record.get("predicates") as string[]) || []; + const objects = (record.get("objects") as string[]) || []; + + // Count unique terms per cluster (each cluster contributes max 1 to DF for each term) + new Set(subjects).forEach((term) => { + subjectDF.set(term, (subjectDF.get(term) || 0) + 1); + }); + new Set(predicates).forEach((term) => { + predicateDF.set(term, (predicateDF.get(term) || 0) + 1); + }); + new Set(objects).forEach((term) => { + objectDF.set(term, (objectDF.get(term) || 0) + 1); + }); + } + + // Find the target cluster data for TF calculation + const targetCluster = allClusters.find( + (record) => record.get("clusterId") === targetClusterId, + ); + + if (!targetCluster) { + return null; + } + + const subjects = (targetCluster.get("subjects") as string[]) || []; + const predicates = (targetCluster.get("predicates") as string[]) || []; + const objects = (targetCluster.get("objects") as string[]) || []; + + // Calculate term frequencies within this specific cluster + // TF = how often each term appears in this cluster's statements + const subjectTF = new Map(); + const predicateTF = new Map(); + const objectTF = new Map(); + + subjects.forEach((term) => { + subjectTF.set(term, (subjectTF.get(term) || 0) + 1); + }); + predicates.forEach((term) => { + predicateTF.set(term, (predicateTF.get(term) || 0) + 1); + }); + objects.forEach((term) => { + objectTF.set(term, (objectTF.get(term) || 0) + 1); + }); + + // Calculate TF-IDF scores using cluster-based document frequency + // Higher scores = terms frequent in THIS cluster but rare across OTHER clusters + const calculateTFIDF = ( + tf: Map, + df: Map, + totalTerms: number, + ) => { + return Array.from(tf.entries()) + .map(([term, freq]) => { + // TF: Normalized frequency within this cluster + const termFreq = freq / totalTerms; + + // DF: Number of clusters containing this term + const docFreq = df.get(term) || 1; + + // IDF: Inverse document frequency (cluster-based) + // Higher when term appears in fewer clusters + const idf = Math.log(totalClusters / docFreq); + + // TF-IDF: Final distinctiveness score + const tfidf = termFreq * idf; + + return { term, score: tfidf }; + }) + .sort((a, b) => b.score - a.score) + .slice(0, 10); // Top 10 most distinctive terms + }; + + return { + subjects: calculateTFIDF(subjectTF, subjectDF, subjects.length), + predicates: calculateTFIDF(predicateTF, predicateDF, predicates.length), + objects: calculateTFIDF(objectTF, objectDF, objects.length), + }; + } + + /** + * Generate cluster names using LLM based on TF-IDF analysis + */ + private async generateClusterNames(userId: string): Promise { + logger.info("Generating cluster names using TF-IDF analysis"); + + const getClustersQuery = ` + MATCH (c:Cluster) + WHERE c.userId = $userId AND c.needsNaming = true + RETURN c.uuid as clusterId, c.size as size + `; + + const clusters = await runQuery(getClustersQuery, { userId }); + + for (const record of clusters) { + const clusterId = record.get("clusterId"); + const size = record.get("size"); + + // Calculate TF-IDF only for this specific cluster + const tfidfData = await this.calculateClusterTFIDFForCluster( + userId, + clusterId, + ); + if (!tfidfData) { + logger.warn(`No TF-IDF data found for cluster ${clusterId}`); + continue; + } + + const namingPrompt = this.createTFIDFClusterNamingPrompt({ + ...tfidfData, + size, + }); + + let responseText = ""; + await makeModelCall(false, namingPrompt, (text) => { + responseText = text; + }); + + try { + const outputMatch = responseText.match(/([\s\S]*?)<\/output>/); + if (outputMatch && outputMatch[1]) { + const response = JSON.parse(outputMatch[1].trim()); + + const updateQuery = ` + MATCH (c:Cluster {uuid: $clusterId}) + SET c.name = $name, + c.description = $description, + c.needsNaming = false + `; + + await runQuery(updateQuery, { + clusterId, + name: response.name || `Cluster ${clusterId}`, + description: response.description || null, + }); + } + } catch (error) { + logger.error(`Error naming cluster ${clusterId}:`, { error }); + + // Fallback naming + await runQuery( + ` + MATCH (c:Cluster {uuid: $clusterId}) + SET c.name = 'Cluster ' + substring($clusterId, 0, 8), + c.needsNaming = false + `, + { clusterId }, + ); + } + } + } + + /** + * Create prompt for unsupervised cluster naming using TF-IDF scores + */ + private createTFIDFClusterNamingPrompt(data: { + subjects: Array<{ term: string; score: number }>; + predicates: Array<{ term: string; score: number }>; + objects: Array<{ term: string; score: number }>; + size: number; + }): CoreMessage[] { + const formatTerms = (terms: Array<{ term: string; score: number }>) => + terms.map((t) => `"${t.term}" (${t.score.toFixed(3)})`).join(", "); + + return [ + { + role: "system", + content: `You are an expert at analyzing semantic patterns and creating descriptive cluster names. You will receive TF-IDF scores showing the most distinctive terms for a cluster of knowledge graph statements. + + TF-IDF Analysis: + - Higher scores = terms that are frequent in THIS cluster but rare in OTHER clusters + - These scores reveal what makes this cluster semantically unique + - Focus on the highest-scoring terms as they are the most distinctive + + Knowledge Graph Context: + - Subjects: Who or what is performing actions + - Predicates: The relationships, actions, or connections + - Objects: Who or what is being acted upon or referenced + + Naming Guidelines: + 1. Create a 2-4 word descriptive name that captures the core semantic theme + 2. Focus on the highest TF-IDF scoring terms - they reveal the cluster's uniqueness + 3. Look for patterns across subjects, predicates, and objects together + 4. Use natural language that a user would understand + 5. Avoid generic terms - be specific based on the distinctive patterns + + Return only a JSON object: + + { + "name": "Descriptive cluster name", + "description": "Brief explanation of the semantic pattern based on TF-IDF analysis" + } + `, + }, + { + role: "user", + content: `Analyze this cluster of ${data.size} statements. The TF-IDF scores show what makes this cluster distinctive: + +**Distinctive Subjects (TF-IDF):** +${formatTerms(data.subjects)} + +**Distinctive Predicates (TF-IDF):** +${formatTerms(data.predicates)} + +**Distinctive Objects (TF-IDF):** +${formatTerms(data.objects)} + +Based on these distinctive patterns, what is the most accurate name for this semantic cluster?`, + }, + ]; + } + + /** + * Update cluster embeddings incrementally when new statements are added + */ + private async updateClusterEmbeddings(userId: string): Promise { + logger.info("Updating cluster embeddings after new statements"); + + const updateQuery = ` + MATCH (c:Cluster) + WHERE c.userId = $userId + + MATCH (s:Statement {clusterId: c.uuid, userId: $userId}) + WHERE s.factEmbedding IS NOT NULL + + WITH c, collect(s.factEmbedding) as allEmbeddings + WHERE size(allEmbeddings) > 0 + + // Recalculate average embedding + WITH c, allEmbeddings, + reduce(avg = [i IN range(0, size(allEmbeddings[0])-1) | 0.0], + embedding IN allEmbeddings | + [i IN range(0, size(embedding)-1) | avg[i] + embedding[i] / size(allEmbeddings)]) as newEmbedding + + SET c.clusterEmbedding = newEmbedding, + c.embeddingCount = size(allEmbeddings), + c.lastEmbeddingUpdate = datetime() + + RETURN count(c) as updatedClusters + `; + + const result = await runQuery(updateQuery, { userId }); + const updatedClusters = result[0]?.get("updatedClusters") || 0; + + logger.info(`Updated embeddings for ${updatedClusters} clusters`); + } + + /** + * Detect cluster drift using embedding-based cohesion analysis + */ + async detectClusterDrift(userId: string): Promise<{ + driftDetected: boolean; + lowCohesionClusters: string[]; + avgCohesion: number; + reason: string; + }> { + logger.info("Detecting cluster drift using embedding cohesion analysis"); + + // First update cluster embeddings to ensure they're current + await this.updateClusterEmbeddings(userId); + + // Calculate cohesion for all clusters + const cohesionQuery = ` + MATCH (c:Cluster) + WHERE c.userId = $userId AND c.clusterEmbedding IS NOT NULL + + MATCH (s:Statement {clusterId: c.uuid, userId: $userId}) + WHERE s.factEmbedding IS NOT NULL + + WITH c, collect(s.factEmbedding) as statementEmbeddings, c.clusterEmbedding as clusterEmbedding + WHERE size(statementEmbeddings) >= $minClusterSize + + // Calculate average cosine similarity for this cluster + UNWIND statementEmbeddings as stmtEmb + WITH c, stmtEmb, clusterEmbedding, + reduce(dot = 0.0, i IN range(0, size(stmtEmb)-1) | dot + stmtEmb[i] * clusterEmbedding[i]) as dotProduct, + sqrt(reduce(mag1 = 0.0, i IN range(0, size(stmtEmb)-1) | mag1 + stmtEmb[i] * stmtEmb[i])) as stmtMagnitude, + sqrt(reduce(mag2 = 0.0, i IN range(0, size(clusterEmbedding)-1) | mag2 + clusterEmbedding[i] * clusterEmbedding[i])) as clusterMagnitude + + WITH c, + CASE + WHEN stmtMagnitude > 0 AND clusterMagnitude > 0 + THEN dotProduct / (stmtMagnitude * clusterMagnitude) + ELSE 0.0 + END as cosineSimilarity + + WITH c, avg(cosineSimilarity) as clusterCohesion + + // Update cluster with cohesion score + SET c.cohesionScore = clusterCohesion + + RETURN c.uuid as clusterId, c.size as clusterSize, clusterCohesion + ORDER BY clusterCohesion ASC + `; + + const cohesionResults = await runQuery(cohesionQuery, { + userId, + minClusterSize: this.MIN_CLUSTER_SIZE, + }); + + const clusterCohesions = cohesionResults.map((record) => ({ + clusterId: record.get("clusterId"), + size: record.get("clusterSize"), + cohesion: record.get("clusterCohesion") || 0.0, + })); + + const avgCohesion = + clusterCohesions.length > 0 + ? clusterCohesions.reduce((sum, c) => sum + c.cohesion, 0) / + clusterCohesions.length + : 0.0; + + const lowCohesionClusters = clusterCohesions + .filter((c) => c.cohesion < this.COHESION_THRESHOLD) + .map((c) => c.clusterId); + + const driftDetected = + lowCohesionClusters.length > 0 || avgCohesion < this.COHESION_THRESHOLD; + + let reason = ""; + if (lowCohesionClusters.length > 0) { + reason = `${lowCohesionClusters.length} clusters have low cohesion (< ${this.COHESION_THRESHOLD})`; + } else if (avgCohesion < this.COHESION_THRESHOLD) { + reason = `Overall average cohesion (${avgCohesion.toFixed(3)}) below threshold (${this.COHESION_THRESHOLD})`; + } + + logger.info( + `Drift detection completed: ${driftDetected ? "DRIFT DETECTED" : "NO DRIFT"} - ${reason || "Clusters are cohesive"}`, + ); + + return { + driftDetected, + lowCohesionClusters, + avgCohesion, + reason: reason || "Clusters are cohesive", + }; + } + + /** + * Handle cluster evolution when drift is detected by splitting low-cohesion clusters + */ + async evolveCluster(oldClusterId: string, userId: string): Promise { + logger.info(`Splitting cluster ${oldClusterId} due to low cohesion`); + + try { + // Step 1: Get all statements from the low-cohesion cluster + const statementsQuery = ` + MATCH (s:Statement) + WHERE s.clusterId = $oldClusterId AND s.userId = $userId + RETURN collect(s.uuid) as statementIds, count(s) as statementCount + `; + const statementsResult = await runQuery(statementsQuery, { + oldClusterId, + userId, + }); + const statementIds = statementsResult[0]?.get("statementIds") || []; + const statementCount = statementsResult[0]?.get("statementCount") || 0; + + if (statementCount < this.MIN_CLUSTER_SIZE * 2) { + logger.info( + `Cluster ${oldClusterId} too small to split (${statementCount} statements)`, + ); + return [oldClusterId]; // Return original cluster if too small to split + } + + // Step 2: Create similarity edges within this cluster's statements + const similarityQuery = ` + MATCH (s1:Statement)-[:HAS_SUBJECT|HAS_PREDICATE|HAS_OBJECT]->(e:Entity)<-[:HAS_SUBJECT|HAS_PREDICATE|HAS_OBJECT]-(s2:Statement) + WHERE s1.clusterId = $oldClusterId AND s2.clusterId = $oldClusterId + AND s1.userId = $userId AND s2.userId = $userId + AND s1.invalidAt IS NULL AND s2.invalidAt IS NULL + AND id(s1) < id(s2) + WITH s1, s2, collect(DISTINCT e.uuid) as sharedEntities + WHERE size(sharedEntities) > 0 + MERGE (s1)-[r:TEMP_SIMILAR_TO]-(s2) + SET r.weight = size(sharedEntities) * 2, + r.sharedEntities = sharedEntities + RETURN count(r) as edgesCreated + `; + await runQuery(similarityQuery, { oldClusterId, userId }); + + // Step 3: Run Leiden clustering on the cluster's statements + const leidenQuery = ` + MATCH (source:Statement) WHERE source.userId = $userId + OPTIONAL MATCH (source)-[r:TEMP_SIMILAR_TO]->(target:Statement) + WHERE target.userId = $userId and target.clusterId = $oldClusterId + WITH gds.graph.project( + 'cluster_split_' + $userId + '_' + $oldClusterId, + source, + target, + { + relationshipProperties: r { .weight } + }, + { undirectedRelationshipTypes: ['*'] } + ) AS g + + CALL gds.leiden.write( + g.graphName, + { + writeProperty: 'tempClusterId', + relationshipWeightProperty: 'weight', + gamma: $gamma, + maxLevels: $maxLevels, + tolerance: $tolerance, + } + ) + YIELD communityCount + + CALL gds.graph.drop(g.graphName) + YIELD graphName as droppedGraphName + + RETURN communityCount, g.nodeCount, g.relationshipCount + `; + + const leidenResult = await runQuery(leidenQuery, { + oldClusterId, + userId, + gamma: this.LEIDEN_GAMMA, + maxLevels: this.LEIDEN_MAX_LEVELS, + tolerance: this.LEIDEN_TOLERANCE, + }); + const subClusterCount = leidenResult[0]?.get("communityCount") || 1; + + // Step 4: Create new cluster IDs for sub-clusters that meet minimum size + const newClusterIds: string[] = []; + const assignClustersQuery = ` + MATCH (s:Statement) + WHERE s.clusterId = $oldClusterId AND s.userId = $userId AND s.tempClusterId IS NOT NULL + WITH s.tempClusterId as tempId, collect(s) as statements + WHERE size(statements) >= $minSize + + WITH tempId, statements, randomUUID() as newClusterId + + FOREACH (stmt IN statements | + SET stmt.clusterId = newClusterId + REMOVE stmt.tempClusterId + ) + + RETURN collect(newClusterId) as newClusterIds, count(DISTINCT newClusterId) as validSubClusters + `; + + const assignResult = await runQuery(assignClustersQuery, { + oldClusterId, + userId, + minSize: this.MIN_CLUSTER_SIZE, + }); + const validNewClusterIds = assignResult[0]?.get("newClusterIds") || []; + newClusterIds.push(...validNewClusterIds); + + // Step 5: Handle statements that didn't make it into valid sub-clusters + const orphanQuery = ` + MATCH (s:Statement) + WHERE s.clusterId = $oldClusterId AND s.userId = $userId + REMOVE s.tempClusterId + + // If we have valid sub-clusters, assign orphans to the largest one + WITH count(s) as orphanCount + MATCH (s2:Statement) + WHERE s2.clusterId IN $newClusterIds AND s2.userId = $userId + WITH s2.clusterId as clusterId, count(s2) as clusterSize, orphanCount + ORDER BY clusterSize DESC + LIMIT 1 + + MATCH (orphan:Statement) + WHERE orphan.clusterId = $oldClusterId AND orphan.userId = $userId + SET orphan.clusterId = clusterId + + RETURN count(orphan) as orphansReassigned + `; + + if (newClusterIds.length > 0) { + await runQuery(orphanQuery, { oldClusterId, userId, newClusterIds }); + } + + // Step 6: Create new Cluster nodes and evolution relationships + if (newClusterIds.length > 1) { + const createClustersQuery = ` + MATCH (oldCluster:Cluster {uuid: $oldClusterId}) + + UNWIND $newClusterIds as newClusterId + + MATCH (s:Statement {clusterId: newClusterId, userId: $userId}) + WITH oldCluster, newClusterId, count(s) as statementCount + + CREATE (newCluster:Cluster { + uuid: newClusterId, + createdAt: datetime(), + userId: $userId, + size: statementCount, + needsNaming: true, + aspectType: oldCluster.aspectType + }) + + CREATE (oldCluster)-[:SPLIT_INTO { + createdAt: datetime(), + reason: 'low_cohesion', + originalSize: $originalSize, + newSize: statementCount + }]->(newCluster) + + RETURN count(newCluster) as clustersCreated + `; + + await runQuery(createClustersQuery, { + oldClusterId, + newClusterIds, + originalSize: statementCount, + userId, + }); + + // Mark old cluster as evolved + await runQuery( + ` + MATCH (c:Cluster {uuid: $oldClusterId}) + SET c.evolved = true, c.evolvedAt = datetime() + `, + { oldClusterId }, + ); + + logger.info( + `Successfully split cluster ${oldClusterId} into ${newClusterIds.length} sub-clusters`, + ); + } else { + logger.info(`Cluster ${oldClusterId} could not be meaningfully split`); + newClusterIds.push(oldClusterId); // Keep original if splitting didn't work + } + + // Step 7: Clean up temporary relationships + await runQuery( + ` + MATCH ()-[r:TEMP_SIMILAR_TO]-() + DELETE r + `, + {}, + ); + + return newClusterIds; + } catch (error) { + logger.error(`Error splitting cluster ${oldClusterId}:`, { error }); + // Clean up on error + await runQuery( + ` + MATCH ()-[r:TEMP_SIMILAR_TO]-() + DELETE r + + MATCH (s:Statement) + WHERE s.clusterId = $oldClusterId AND s.userId = $userId + REMOVE s.tempClusterId + `, + { oldClusterId, userId }, + ); + throw error; + } + } + + /** + * Handle drift by splitting low-cohesion clusters + */ + async handleClusterDrift(userId: string): Promise<{ + clustersProcessed: number; + newClustersCreated: number; + splitClusters: string[]; + }> { + logger.info(`Handling cluster drift for user ${userId}`); + + try { + // Detect drift and get low-cohesion clusters + const driftMetrics = await this.detectClusterDrift(userId); + + if ( + !driftMetrics.driftDetected || + driftMetrics.lowCohesionClusters.length === 0 + ) { + logger.info("No drift detected or no clusters need splitting"); + return { + clustersProcessed: 0, + newClustersCreated: 0, + splitClusters: [], + }; + } + + logger.info( + `Found ${driftMetrics.lowCohesionClusters.length} clusters with low cohesion`, + ); + + let totalNewClusters = 0; + const splitClusters: string[] = []; + + // Process each low-cohesion cluster + for (const clusterId of driftMetrics.lowCohesionClusters) { + try { + const newClusterIds = await this.evolveCluster(clusterId, userId); + + if (newClusterIds.length > 1) { + // Cluster was successfully split + totalNewClusters += newClusterIds.length; + splitClusters.push(clusterId); + logger.info( + `Split cluster ${clusterId} into ${newClusterIds.length} sub-clusters`, + ); + } else { + logger.info(`Cluster ${clusterId} could not be split meaningfully`); + } + } catch (error) { + logger.error(`Failed to split cluster ${clusterId}:`, { error }); + // Continue with other clusters even if one fails + } + } + + // Update cluster embeddings for new clusters + if (totalNewClusters > 0) { + await this.updateClusterEmbeddings(userId); + await this.generateClusterNames(userId); + } + + logger.info( + `Drift handling completed: ${splitClusters.length} clusters split, ${totalNewClusters} new clusters created`, + ); + + return { + clustersProcessed: splitClusters.length, + newClustersCreated: totalNewClusters, + splitClusters, + }; + } catch (error) { + logger.error("Error handling cluster drift:", { error }); + throw error; + } + } + + /** + * Main clustering orchestration method - intelligently chooses between incremental and complete clustering + */ + async performClustering( + userId: string, + forceComplete: boolean = false, + ): Promise<{ + clustersCreated: number; + statementsProcessed: number; + driftMetrics?: DriftMetrics; + approach: "incremental" | "complete"; + }> { + logger.info(`Starting clustering process for user ${userId}`); + + try { + // Check if user has any existing clusters + const existingClustersQuery = ` + MATCH (c:Cluster) + WHERE c.userId = $userId + RETURN count(c) as existingClusters + `; + const existingResult = await runQuery(existingClustersQuery, { userId }); + const existingClusters = existingResult[0]?.get("existingClusters") || 0; + + // Check total statement count + // const totalStatementsQuery = ` + // MATCH (s:Statement) + // WHERE s.userId = $userId AND s.invalidAt IS NULL + // RETURN count(s) as totalStatements + // `; + // const totalResult = await runQuery(totalStatementsQuery, { userId }); + // const totalStatements = totalResult[0]?.get("totalStatements") || 0; + + // Determine clustering approach + let useIncremental = + existingClusters > 0 && !forceComplete ? true : false; + let driftMetrics: DriftMetrics | undefined; + + // if ( + // !forceComplete && + // existingClusters > 0 && + // totalStatements >= this.MIN_CLUSTER_SIZE + // ) { + // // Check for drift to decide approach + // driftMetrics = await this.detectClusterDrift(userId); + + // if (!driftMetrics.shouldRecluster) { + // useIncremental = true; + // logger.info("Using incremental clustering approach"); + // } else { + // logger.info("Drift detected, using complete clustering approach"); + // } + // } else if (totalStatements < this.MIN_CLUSTER_SIZE) { + // logger.info( + // `Insufficient statements (${totalStatements}) for clustering, minimum required: ${this.MIN_CLUSTER_SIZE}`, + // ); + // return { + // clustersCreated: 0, + // statementsProcessed: 0, + // driftMetrics, + // approach: "incremental", + // }; + // } else { + // logger.info("Using complete clustering approach (new user or forced)"); + // } + + // Execute appropriate clustering strategy + if (useIncremental) { + const incrementalResult = + await this.performIncrementalClustering(userId); + return { + clustersCreated: incrementalResult.newClustersCreated, + statementsProcessed: incrementalResult.newStatementsProcessed, + driftMetrics, + approach: "incremental", + }; + } else { + const completeResult = await this.performCompleteClustering(userId); + return { + clustersCreated: completeResult.clustersCreated, + statementsProcessed: completeResult.statementsProcessed, + driftMetrics, + approach: "complete", + }; + } + } catch (error) { + logger.error("Error in clustering process:", { error }); + throw error; + } + } + + /** + * Force complete reclustering (useful for maintenance or when drift is too high) + */ + async forceCompleteClustering(userId: string): Promise<{ + clustersCreated: number; + statementsProcessed: number; + }> { + return await this.performCompleteClustering(userId); + } + + /** + * Get cluster information for a user + */ + async getClusters(userId: string): Promise { + const query = ` + MATCH (c:Cluster) + WHERE c.userId = $userId + RETURN c + ORDER BY c.size DESC + `; + + const result = await runQuery(query, { userId }); + + return result.map((record) => { + const cluster = record.get("c").properties; + return { + uuid: cluster.uuid, + name: cluster.name || `Cluster ${cluster.uuid.substring(0, 8)}`, + aspectType: cluster.aspectType || "thematic", + description: cluster.description, + size: cluster.size || 0, + createdAt: new Date(cluster.createdAt), + userId: cluster.userId, + cohesionScore: cluster.cohesionScore, + }; + }); + } + + /** + * Get statements in a specific cluster + */ + async getClusterStatements( + clusterId: string, + userId: string, + ): Promise { + const query = ` + MATCH (s:Statement) + WHERE s.clusterId = $clusterId AND s.userId = $userId + MATCH (s)-[:HAS_SUBJECT]->(subj:Entity) + MATCH (s)-[:HAS_PREDICATE]->(pred:Entity) + MATCH (s)-[:HAS_OBJECT]->(obj:Entity) + RETURN s, subj.name as subject, pred.name as predicate, obj.name as object + ORDER BY s.createdAt DESC + `; + + const result = await runQuery(query, { clusterId, userId }); + + return result.map((record) => { + const statement = record.get("s").properties; + return { + uuid: statement.uuid, + fact: statement.fact, + subject: record.get("subject"), + predicate: record.get("predicate"), + object: record.get("object"), + createdAt: new Date(statement.createdAt), + validAt: new Date(statement.validAt), + }; + }); + } +} diff --git a/apps/webapp/app/services/knowledgeGraph.server.ts b/apps/webapp/app/services/knowledgeGraph.server.ts index 50c1a25..892c7bb 100644 --- a/apps/webapp/app/services/knowledgeGraph.server.ts +++ b/apps/webapp/app/services/knowledgeGraph.server.ts @@ -8,6 +8,7 @@ import { type Triple, } from "@core/types"; import { logger } from "./logger.service"; +import { ClusteringService } from "./clustering.server"; import crypto from "crypto"; import { dedupeNodes, @@ -53,6 +54,12 @@ import { type PrismaClient } from "@prisma/client"; const DEFAULT_EPISODE_WINDOW = 5; export class KnowledgeGraphService { + private clusteringService: ClusteringService; + + constructor() { + this.clusteringService = new ClusteringService(); + } + async getEmbedding(text: string) { return getEmbedding(text); } @@ -188,6 +195,26 @@ export class KnowledgeGraphService { // Invalidate invalidated statements await invalidateStatements({ statementIds: invalidatedStatements }); + // Trigger incremental clustering process after successful ingestion + if (resolvedStatements.length > 0) { + try { + logger.info( + "Triggering incremental clustering process after episode ingestion", + ); + const clusteringResult = + await this.clusteringService.performClustering( + params.userId, + false, + ); + logger.info( + `Incremental clustering completed: ${clusteringResult.clustersCreated} clusters created, ${clusteringResult.statementsProcessed} statements processed`, + ); + } catch (clusteringError) { + logger.error("Error in incremental clustering process:"); + // Don't fail the entire ingestion if clustering fails + } + } + const endTime = Date.now(); const processingTimeMs = endTime - startTime; logger.log(`Processing time: ${processingTimeMs} ms`); diff --git a/apps/webapp/app/services/routeBuilders/apiBuilder.server.ts b/apps/webapp/app/services/routeBuilders/apiBuilder.server.ts index bce2d42..cef70bb 100644 --- a/apps/webapp/app/services/routeBuilders/apiBuilder.server.ts +++ b/apps/webapp/app/services/routeBuilders/apiBuilder.server.ts @@ -957,3 +957,291 @@ export function createHybridActionApiRoute< return { loader, action }; } + +// Hybrid Loader API Route types and builder +type HybridLoaderRouteBuilderOptions< + TParamsSchema extends AnyZodSchema | undefined = undefined, + TSearchParamsSchema extends AnyZodSchema | undefined = undefined, + THeadersSchema extends AnyZodSchema | undefined = undefined, + TResource = never, +> = { + params?: TParamsSchema; + searchParams?: TSearchParamsSchema; + headers?: THeadersSchema; + allowJWT?: boolean; + corsStrategy?: "all" | "none"; + findResource: ( + params: TParamsSchema extends + | z.ZodFirstPartySchemaTypes + | z.ZodDiscriminatedUnion + ? z.infer + : undefined, + authentication: HybridAuthenticationResult, + searchParams: TSearchParamsSchema extends + | z.ZodFirstPartySchemaTypes + | z.ZodDiscriminatedUnion + ? z.infer + : undefined, + ) => Promise; + shouldRetryNotFound?: boolean; + authorization?: { + action: AuthorizationAction; + resource: ( + resource: NonNullable, + params: TParamsSchema extends + | z.ZodFirstPartySchemaTypes + | z.ZodDiscriminatedUnion + ? z.infer + : undefined, + searchParams: TSearchParamsSchema extends + | z.ZodFirstPartySchemaTypes + | z.ZodDiscriminatedUnion + ? z.infer + : undefined, + headers: THeadersSchema extends + | z.ZodFirstPartySchemaTypes + | z.ZodDiscriminatedUnion + ? z.infer + : undefined, + ) => AuthorizationResources; + superScopes?: string[]; + }; +}; + +type HybridLoaderHandlerFunction< + TParamsSchema extends AnyZodSchema | undefined, + TSearchParamsSchema extends AnyZodSchema | undefined, + THeadersSchema extends AnyZodSchema | undefined = undefined, + TResource = never, +> = (args: { + params: TParamsSchema extends + | z.ZodFirstPartySchemaTypes + | z.ZodDiscriminatedUnion + ? z.infer + : undefined; + searchParams: TSearchParamsSchema extends + | z.ZodFirstPartySchemaTypes + | z.ZodDiscriminatedUnion + ? z.infer + : undefined; + headers: THeadersSchema extends + | z.ZodFirstPartySchemaTypes + | z.ZodDiscriminatedUnion + ? z.infer + : undefined; + authentication: HybridAuthenticationResult; + request: Request; + resource: NonNullable; +}) => Promise; + +export function createHybridLoaderApiRoute< + TParamsSchema extends AnyZodSchema | undefined = undefined, + TSearchParamsSchema extends AnyZodSchema | undefined = undefined, + THeadersSchema extends AnyZodSchema | undefined = undefined, + TResource = never, +>( + options: HybridLoaderRouteBuilderOptions< + TParamsSchema, + TSearchParamsSchema, + THeadersSchema, + TResource + >, + handler: HybridLoaderHandlerFunction< + TParamsSchema, + TSearchParamsSchema, + THeadersSchema, + TResource + >, +) { + return async function loader({ request, params }: LoaderFunctionArgs) { + const { + params: paramsSchema, + searchParams: searchParamsSchema, + headers: headersSchema, + allowJWT = false, + corsStrategy = "none", + authorization, + findResource, + shouldRetryNotFound, + } = options; + + if (corsStrategy !== "none" && request.method.toUpperCase() === "OPTIONS") { + return apiCors(request, json({})); + } + + try { + const authenticationResult = await authenticateHybridRequest(request, { + allowJWT, + }); + + if (!authenticationResult) { + return await wrapResponse( + request, + json({ error: "Authentication required" }, { status: 401 }), + corsStrategy !== "none", + ); + } + + let parsedParams: any = undefined; + if (paramsSchema) { + const parsed = paramsSchema.safeParse(params); + if (!parsed.success) { + return await wrapResponse( + request, + json( + { + error: "Params Error", + details: fromZodError(parsed.error).details, + }, + { status: 400 }, + ), + corsStrategy !== "none", + ); + } + parsedParams = parsed.data; + } + + let parsedSearchParams: any = undefined; + if (searchParamsSchema) { + const searchParams = Object.fromEntries( + new URL(request.url).searchParams, + ); + const parsed = searchParamsSchema.safeParse(searchParams); + if (!parsed.success) { + return await wrapResponse( + request, + json( + { + error: "Query Error", + details: fromZodError(parsed.error).details, + }, + { status: 400 }, + ), + corsStrategy !== "none", + ); + } + parsedSearchParams = parsed.data; + } + + let parsedHeaders: any = undefined; + if (headersSchema) { + const rawHeaders = Object.fromEntries(request.headers); + const headers = headersSchema.safeParse(rawHeaders); + if (!headers.success) { + return await wrapResponse( + request, + json( + { + error: "Headers Error", + details: fromZodError(headers.error).details, + }, + { status: 400 }, + ), + corsStrategy !== "none", + ); + } + parsedHeaders = headers.data; + } + + // Find the resource + const resource = await findResource( + parsedParams, + authenticationResult, + parsedSearchParams, + ); + + if (!resource) { + return await wrapResponse( + request, + json( + { error: "Not found" }, + { + status: 404, + headers: { + "x-should-retry": shouldRetryNotFound ? "true" : "false", + }, + }, + ), + corsStrategy !== "none", + ); + } + + // Authorization check - only applies to API key authentication + if (authorization && authenticationResult.type === "PRIVATE") { + const { action, resource: authResource, superScopes } = authorization; + const $authResource = authResource( + resource, + parsedParams, + parsedSearchParams, + parsedHeaders, + ); + + logger.debug("Checking authorization", { + action, + resource: $authResource, + superScopes, + scopes: authenticationResult.scopes, + }); + + const authorizationResult = checkAuthorization(authenticationResult); + + if (!authorizationResult.authorized) { + return await wrapResponse( + request, + json( + { + error: `Unauthorized: ${authorizationResult.reason}`, + code: "unauthorized", + param: "access_token", + type: "authorization", + }, + { status: 403 }, + ), + corsStrategy !== "none", + ); + } + } + + const result = await handler({ + params: parsedParams, + searchParams: parsedSearchParams, + headers: parsedHeaders, + authentication: authenticationResult, + request, + resource, + }); + return await wrapResponse(request, result, corsStrategy !== "none"); + } catch (error) { + try { + if (error instanceof Response) { + return await wrapResponse(request, error, corsStrategy !== "none"); + } + + logger.error("Error in hybrid loader", { + error: + error instanceof Error + ? { + name: error.name, + message: error.message, + stack: error.stack, + } + : String(error), + url: request.url, + }); + + return await wrapResponse( + request, + json({ error: "Internal Server Error" }, { status: 500 }), + corsStrategy !== "none", + ); + } catch (innerError) { + logger.error("[apiBuilder] Failed to handle error", { + error, + innerError, + }); + + return json({ error: "Internal Server Error" }, { status: 500 }); + } + } + }; +} diff --git a/apps/webapp/app/services/search.server.ts b/apps/webapp/app/services/search.server.ts index eded062..d226db8 100644 --- a/apps/webapp/app/services/search.server.ts +++ b/apps/webapp/app/services/search.server.ts @@ -1,4 +1,4 @@ -import type { StatementNode } from "@core/types"; +import type { EpisodicNode, StatementNode } from "@core/types"; import { logger } from "./logger.service"; import { applyCrossEncoderReranking, applyWeightedRRF } from "./search/rerank"; import { @@ -8,6 +8,8 @@ import { performVectorSearch, } from "./search/utils"; import { getEmbedding } from "~/lib/model.server"; +import { prisma } from "~/db.server"; +import { runQuery } from "~/lib/neo4j.server"; /** * SearchService provides methods to search the reified + temporal knowledge graph @@ -30,6 +32,7 @@ export class SearchService { userId: string, options: SearchOptions = {}, ): Promise<{ episodes: string[]; facts: string[] }> { + const startTime = Date.now(); // Default options const opts: Required = { @@ -70,6 +73,21 @@ export class SearchService { // 3. Return top results const episodes = await getEpisodesByStatements(filteredResults); + + // Log recall asynchronously (don't await to avoid blocking response) + const responseTime = Date.now() - startTime; + this.logRecallAsync( + query, + userId, + filteredResults, + opts, + responseTime, + ).catch((error) => { + logger.error("Failed to log recall event:", error); + }); + + this.updateRecallCount(userId, episodes, filteredResults); + return { episodes: episodes.map((episode) => episode.content), facts: filteredResults.map((statement) => statement.fact), @@ -201,6 +219,100 @@ export class SearchService { // Otherwise use weighted RRF for multiple sources return applyWeightedRRF(results); } + + private async logRecallAsync( + query: string, + userId: string, + results: StatementNode[], + options: Required, + responseTime: number, + ): Promise { + try { + // Determine target type based on results + let targetType = "mixed_results"; + if (results.length === 1) { + targetType = "statement"; + } else if (results.length === 0) { + targetType = "no_results"; + } + + // Calculate average similarity score if available + let averageSimilarityScore: number | null = null; + const scoresWithValues = results + .map((result) => { + // Try to extract score from various possible score fields + const score = + (result as any).rrfScore || + (result as any).mmrScore || + (result as any).crossEncoderScore || + (result as any).finalScore || + (result as any).score; + return score && typeof score === "number" ? score : null; + }) + .filter((score): score is number => score !== null); + + if (scoresWithValues.length > 0) { + averageSimilarityScore = + scoresWithValues.reduce((sum, score) => sum + score, 0) / + scoresWithValues.length; + } + + await prisma.recallLog.create({ + data: { + accessType: "search", + query, + targetType, + searchMethod: "hybrid", // BM25 + Vector + BFS + minSimilarity: options.scoreThreshold, + maxResults: options.limit, + resultCount: results.length, + similarityScore: averageSimilarityScore, + context: JSON.stringify({ + entityTypes: options.entityTypes, + predicateTypes: options.predicateTypes, + maxBfsDepth: options.maxBfsDepth, + includeInvalidated: options.includeInvalidated, + validAt: options.validAt.toISOString(), + startTime: options.startTime?.toISOString() || null, + endTime: options.endTime.toISOString(), + }), + source: "search_api", + responseTimeMs: responseTime, + userId, + }, + }); + + logger.debug( + `Logged recall event for user ${userId}: ${results.length} results in ${responseTime}ms`, + ); + } catch (error) { + logger.error("Error creating recall log entry:", { error }); + // Don't throw - we don't want logging failures to affect the search response + } + } + + private async updateRecallCount( + userId: string, + episodes: EpisodicNode[], + statements: StatementNode[], + ) { + const episodeIds = episodes.map((episode) => episode.uuid); + const statementIds = statements.map((statement) => statement.uuid); + + const cypher = ` + MATCH (e:Episode) + WHERE e.uuid IN $episodeUuids and e.userId = $userId + SET e.recallCount = coalesce(e.recallCount, 0) + 1 + `; + await runQuery(cypher, { episodeUuids: episodeIds, userId }); + + const cypher2 = ` + MATCH (s:Statement) + WHERE s.uuid IN $statementUuids and s.userId = $userId + SET s.recallCount = coalesce(s.recallCount, 0) + 1 + `; + await runQuery(cypher2, { statementUuids: statementIds, userId }); + } } /** diff --git a/apps/webapp/app/tailwind.css b/apps/webapp/app/tailwind.css index 80f78c5..d4921d2 100644 --- a/apps/webapp/app/tailwind.css +++ b/apps/webapp/app/tailwind.css @@ -465,6 +465,27 @@ } @layer base { + .react-calendar-heatmap { + font-size: 9px; + } + .react-calendar-heatmap .react-calendar-heatmap-month-label { + font-size: 9px; + fill: hsl(var(--muted-foreground)); + } + .react-calendar-heatmap .react-calendar-heatmap-weekday-label { + font-size: 9px; + fill: hsl(var(--muted-foreground)); + } + .react-calendar-heatmap rect { + + rx: 2; + } + .react-calendar-heatmap rect:hover { + + } + + + .tiptap { :first-child { margin-top: 0; @@ -535,3 +556,4 @@ } } } + diff --git a/apps/webapp/app/trigger/cluster/index.ts b/apps/webapp/app/trigger/cluster/index.ts new file mode 100644 index 0000000..7a62d93 --- /dev/null +++ b/apps/webapp/app/trigger/cluster/index.ts @@ -0,0 +1,115 @@ +import { queue, task } from "@trigger.dev/sdk"; +import { z } from "zod"; +import { ClusteringService } from "~/services/clustering.server"; +import { logger } from "~/services/logger.service"; + +const clusteringService = new ClusteringService(); + +// Define the payload schema for cluster tasks +export const ClusterPayload = z.object({ + userId: z.string(), + mode: z.enum(["auto", "incremental", "complete", "drift"]).default("auto"), + forceComplete: z.boolean().default(false), +}); + +const clusterQueue = queue({ + name: "cluster-queue", + concurrencyLimit: 10, +}); + +/** + * Single clustering task that handles all clustering operations based on payload mode + */ +export const clusterTask = task({ + id: "cluster", + queue: clusterQueue, + maxDuration: 1800, // 30 minutes max + run: async (payload: z.infer) => { + logger.info(`Starting ${payload.mode} clustering task for user ${payload.userId}`); + + try { + let result; + + switch (payload.mode) { + case "incremental": + result = await clusteringService.performIncrementalClustering( + payload.userId, + ); + logger.info(`Incremental clustering completed for user ${payload.userId}:`, { + newStatementsProcessed: result.newStatementsProcessed, + newClustersCreated: result.newClustersCreated, + }); + break; + + case "complete": + result = await clusteringService.performCompleteClustering( + payload.userId, + ); + logger.info(`Complete clustering completed for user ${payload.userId}:`, { + clustersCreated: result.clustersCreated, + statementsProcessed: result.statementsProcessed, + }); + break; + + case "drift": + // First detect drift + const driftMetrics = await clusteringService.detectClusterDrift( + payload.userId, + ); + + if (driftMetrics.driftDetected) { + // Handle drift by splitting low-cohesion clusters + const driftResult = await clusteringService.handleClusterDrift( + payload.userId, + ); + + logger.info(`Cluster drift handling completed for user ${payload.userId}:`, { + driftDetected: true, + clustersProcessed: driftResult.clustersProcessed, + newClustersCreated: driftResult.newClustersCreated, + splitClusters: driftResult.splitClusters, + }); + + result = { + driftDetected: true, + ...driftResult, + driftMetrics, + }; + } else { + logger.info(`No cluster drift detected for user ${payload.userId}`); + result = { + driftDetected: false, + clustersProcessed: 0, + newClustersCreated: 0, + splitClusters: [], + driftMetrics, + }; + } + break; + + case "auto": + default: + result = await clusteringService.performClustering( + payload.userId, + payload.forceComplete, + ); + logger.info(`Auto clustering completed for user ${payload.userId}:`, { + clustersCreated: result.clustersCreated, + statementsProcessed: result.statementsProcessed, + approach: result.approach, + }); + break; + } + + return { + success: true, + data: result, + }; + } catch (error) { + logger.error(`${payload.mode} clustering failed for user ${payload.userId}:`, { + error, + }); + throw error; + } + }, +}); diff --git a/apps/webapp/app/utils/startup.ts b/apps/webapp/app/utils/startup.ts index e190fce..f1549d1 100644 --- a/apps/webapp/app/utils/startup.ts +++ b/apps/webapp/app/utils/startup.ts @@ -108,7 +108,7 @@ const Keys = [ export async function addEnvVariablesInTrigger() { const { APP_ORIGIN, - TRIGGER_DB, + POSTGRES_DB, EMBEDDING_MODEL, MODEL, ENCRYPTION_KEY, @@ -121,7 +121,7 @@ export async function addEnvVariablesInTrigger() { TRIGGER_SECRET_KEY, } = env; - const DATABASE_URL = getDatabaseUrl(TRIGGER_DB); + const DATABASE_URL = getDatabaseUrl(POSTGRES_DB); // Helper to replace 'localhost' with 'host.docker.internal' function replaceLocalhost(val: string | undefined): string | undefined { diff --git a/apps/webapp/integrations/slack/main b/apps/webapp/integrations/slack/main new file mode 100755 index 0000000..c7d7849 Binary files /dev/null and b/apps/webapp/integrations/slack/main differ diff --git a/apps/webapp/package.json b/apps/webapp/package.json index cf85652..ec16e13 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -75,6 +75,7 @@ "@tiptap/starter-kit": "2.11.9", "@trigger.dev/react-hooks": "^4.0.0-v4-beta.22", "@trigger.dev/sdk": "^4.0.0-v4-beta.22", + "@types/react-calendar-heatmap": "^1.9.0", "ai": "4.3.14", "axios": "^1.10.0", "bullmq": "^5.53.2", @@ -111,6 +112,7 @@ "ollama-ai-provider": "1.2.0", "posthog-js": "^1.116.6", "react": "^18.2.0", + "react-calendar-heatmap": "^1.10.0", "react-dom": "^18.2.0", "react-resizable-panels": "^1.0.9", "react-virtualized": "^9.22.6", diff --git a/docker-compose.aws.yaml b/docker-compose.aws.yaml index 8c1d6e4..eefae80 100644 --- a/docker-compose.aws.yaml +++ b/docker-compose.aws.yaml @@ -50,8 +50,8 @@ services: image: neo4j:5.25-community environment: - NEO4J_AUTH=${NEO4J_AUTH} - - NEO4J_dbms_security_procedures_unrestricted=gds.* - - NEO4J_dbms_security_procedures_allowlist=gds.* + - NEO4J_dbms_security_procedures_unrestricted=gds.*,apoc.* + - NEO4J_dbms_security_procedures_allowlist=gds.*,apoc.* ports: - "7474:7474" - "7687:7687" diff --git a/hosting/docker/core/.env b/hosting/docker/core/.env index 2c286f4..54bf003 100644 --- a/hosting/docker/core/.env +++ b/hosting/docker/core/.env @@ -1,4 +1,4 @@ -VERSION=0.1.13 +VERSION=0.1.14 # Nest run in docker, change host to database container name DB_HOST=localhost diff --git a/hosting/docker/core/docker-compose.yaml b/hosting/docker/core/docker-compose.yaml index 37a5aeb..f4db74d 100644 --- a/hosting/docker/core/docker-compose.yaml +++ b/hosting/docker/core/docker-compose.yaml @@ -76,8 +76,13 @@ services: image: neo4j:5 environment: - NEO4J_AUTH=${NEO4J_AUTH} - - NEO4J_dbms_security_procedures_unrestricted=gds.* - - NEO4J_dbms_security_procedures_allowlist=gds.* + - NEO4J_dbms_security_procedures_unrestricted=gds.*,apoc.* + - NEO4J_dbms_security_procedures_allowlist=gds.*,apoc.* + - NEO4J_apoc_export_file_enabled=true # Enable file export + - NEO4J_apoc_import_file_enabled=true # Enable file import + - NEO4J_apoc_import_file_use_neo4j_config=true + - NEO4J_dbms_memory_heap_initial__size=2G + - NEO4J_dbms_memory_heap_max__size=4G ports: - "7474:7474" - "7687:7687" diff --git a/hosting/docker/trigger/.env b/hosting/docker/trigger/.env index 5a71043..9829e15 100644 --- a/hosting/docker/trigger/.env +++ b/hosting/docker/trigger/.env @@ -136,4 +136,4 @@ OBJECT_STORE_SECRET_ACCESS_KEY=very-safe-password # TRAEFIK_HTTPS_PUBLISH_IP=0.0.0.0 # TRAEFIK_DASHBOARD_PUBLISH_IP=127.0.0.1 -CORE_VERSION=0.1.13 \ No newline at end of file +CORE_VERSION=0.1.14 \ No newline at end of file diff --git a/integrations/github/.gitignore b/integrations/github/.gitignore new file mode 100644 index 0000000..9b60c0a --- /dev/null +++ b/integrations/github/.gitignore @@ -0,0 +1,2 @@ +bin +node_modules \ No newline at end of file diff --git a/integrations/github/.prettierrc b/integrations/github/.prettierrc new file mode 100644 index 0000000..b61e355 --- /dev/null +++ b/integrations/github/.prettierrc @@ -0,0 +1,22 @@ +{ + "arrowParens": "always", + "tabWidth": 2, + "useTabs": false, + "semi": true, + "bracketSpacing": true, + "jsxBracketSameLine": false, + "requirePragma": false, + "proseWrap": "preserve", + "singleQuote": true, + "formatOnSave": true, + "trailingComma": "all", + "printWidth": 100, + "overrides": [ + { + "files": ".prettierrc", + "options": { + "parser": "json" + } + } + ] +} diff --git a/integrations/github/eslint.config.js b/integrations/github/eslint.config.js new file mode 100644 index 0000000..e190363 --- /dev/null +++ b/integrations/github/eslint.config.js @@ -0,0 +1,98 @@ +const eslint = require('@eslint/js'); +const tseslint = require('typescript-eslint'); +const reactPlugin = require('eslint-plugin-react'); +const jestPlugin = require('eslint-plugin-jest'); +const importPlugin = require('eslint-plugin-import'); +const prettierPlugin = require('eslint-plugin-prettier'); +const unusedImportsPlugin = require('eslint-plugin-unused-imports'); +const jsxA11yPlugin = require('eslint-plugin-jsx-a11y'); + +module.exports = [ + eslint.configs.recommended, + ...tseslint.configs.recommended, + { + files: ['**/*.{js,jsx,ts,tsx}'], + plugins: { + react: reactPlugin, + jest: jestPlugin, + import: importPlugin, + prettier: prettierPlugin, + 'unused-imports': unusedImportsPlugin, + 'jsx-a11y': jsxA11yPlugin, + }, + languageOptions: { + ecmaVersion: 2020, + sourceType: 'module', + parser: tseslint.parser, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, + rules: { + 'jsx-a11y/label-has-associated-control': 'error', + curly: 'warn', + 'dot-location': 'warn', + eqeqeq: 'error', + 'prettier/prettier': 'warn', + 'unused-imports/no-unused-imports': 'warn', + 'no-else-return': 'warn', + 'no-lonely-if': 'warn', + 'no-inner-declarations': 'off', + 'no-unused-vars': 'off', + 'no-useless-computed-key': 'warn', + 'no-useless-return': 'warn', + 'no-var': 'warn', + 'object-shorthand': ['warn', 'always'], + 'prefer-arrow-callback': 'warn', + 'prefer-const': 'warn', + 'prefer-destructuring': ['warn', { AssignmentExpression: { array: true } }], + 'prefer-object-spread': 'warn', + 'prefer-template': 'warn', + 'spaced-comment': ['warn', 'always', { markers: ['/'] }], + yoda: 'warn', + 'import/order': [ + 'warn', + { + 'newlines-between': 'always', + groups: ['type', 'builtin', 'external', 'internal', ['parent', 'sibling'], 'index'], + pathGroupsExcludedImportTypes: ['builtin'], + pathGroups: [], + alphabetize: { + order: 'asc', + caseInsensitive: true, + }, + }, + ], + '@typescript-eslint/array-type': ['warn', { default: 'array-simple' }], + '@typescript-eslint/ban-ts-comment': [ + 'warn', + { + 'ts-expect-error': 'allow-with-description', + }, + ], + '@typescript-eslint/consistent-indexed-object-style': ['warn', 'record'], + '@typescript-eslint/consistent-type-definitions': ['warn', 'interface'], + '@typescript-eslint/no-unused-vars': 'warn', + 'react/function-component-definition': [ + 'warn', + { + namedComponents: 'arrow-function', + unnamedComponents: 'arrow-function', + }, + ], + 'react/jsx-boolean-value': 'warn', + 'react/jsx-curly-brace-presence': 'warn', + 'react/jsx-fragments': 'warn', + 'react/jsx-no-useless-fragment': ['warn', { allowExpressions: true }], + 'react/self-closing-comp': 'warn', + }, + }, + { + files: ['scripts/**/*'], + rules: { + '@typescript-eslint/no-var-requires': 'off', + }, + }, +]; diff --git a/integrations/github/package.json b/integrations/github/package.json new file mode 100644 index 0000000..e500406 --- /dev/null +++ b/integrations/github/package.json @@ -0,0 +1,64 @@ +{ + "name": "@core/github", + "version": "0.1.0", + "description": "github extension for CORE", + "main": "./bin/index.js", + "module": "./bin/index.mjs", + "type": "module", + "files": [ + "github", + "bin" + ], + "bin": { + "github": "./bin/index.js" + }, + "scripts": { + "build": "rimraf bin && npx tsup", + "lint": "eslint --ext js,ts,tsx backend/ frontend/ --fix", + "prettier": "prettier --config .prettierrc --write .", + "copy:spec": "cp spec.json bin/" + }, + "peerDependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@babel/preset-typescript": "^7.26.0", + "@types/node": "^18.0.20", + "eslint": "^9.24.0", + "eslint-config-prettier": "^10.1.2", + "eslint-import-resolver-alias": "^1.1.2", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jest": "^27.9.0", + "eslint-plugin-prettier": "^5.2.1", + "eslint-plugin-unused-imports": "^2.0.0", + "prettier": "^3.4.2", + "rimraf": "^3.0.2", + "tslib": "^2.8.1", + "typescript": "^4.7.2", + "tsup": "^8.0.1", + "ncc": "0.3.6" + }, + "publishConfig": { + "access": "public" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "dependencies": { + "axios": "^1.7.9", + "commander": "^12.0.0", + "openai": "^4.0.0", + "react-query": "^3.39.3", + "@redplanethq/sdk": "0.1.2" + } +} diff --git a/integrations/github/pnpm-lock.yaml b/integrations/github/pnpm-lock.yaml new file mode 100644 index 0000000..17f1db0 --- /dev/null +++ b/integrations/github/pnpm-lock.yaml @@ -0,0 +1,4217 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@redplanethq/sdk': + specifier: 0.1.1 + version: 0.1.1 + axios: + specifier: ^1.7.9 + version: 1.11.0 + commander: + specifier: ^12.0.0 + version: 12.1.0 + openai: + specifier: ^4.0.0 + version: 4.104.0 + react: + specifier: ^18.2.0 + version: 18.3.1 + react-dom: + specifier: ^18.2.0 + version: 18.3.1(react@18.3.1) + react-query: + specifier: ^3.39.3 + version: 3.39.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + devDependencies: + '@babel/preset-typescript': + specifier: ^7.26.0 + version: 7.27.1(@babel/core@7.28.0) + '@types/node': + specifier: ^18.0.20 + version: 18.19.121 + eslint: + specifier: ^9.24.0 + version: 9.32.0 + eslint-config-prettier: + specifier: ^10.1.2 + version: 10.1.8(eslint@9.32.0) + eslint-import-resolver-alias: + specifier: ^1.1.2 + version: 1.1.2(eslint-plugin-import@2.32.0(eslint@9.32.0)) + eslint-plugin-import: + specifier: ^2.31.0 + version: 2.32.0(eslint@9.32.0) + eslint-plugin-jest: + specifier: ^27.9.0 + version: 27.9.0(eslint@9.32.0)(typescript@4.9.5) + eslint-plugin-prettier: + specifier: ^5.2.1 + version: 5.5.3(eslint-config-prettier@10.1.8(eslint@9.32.0))(eslint@9.32.0)(prettier@3.6.2) + eslint-plugin-unused-imports: + specifier: ^2.0.0 + version: 2.0.0(eslint@9.32.0) + ncc: + specifier: 0.3.6 + version: 0.3.6 + prettier: + specifier: ^3.4.2 + version: 3.6.2 + rimraf: + specifier: ^3.0.2 + version: 3.0.2 + tslib: + specifier: ^2.8.1 + version: 2.8.1 + tsup: + specifier: ^8.0.1 + version: 8.5.0(typescript@4.9.5) + typescript: + specifier: ^4.7.2 + version: 4.9.5 + +packages: + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.0': + resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.0': + resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.0': + resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.27.1': + resolution: {integrity: sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.27.1': + resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.27.3': + resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-replace-supers@7.27.1': + resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.2': + resolution: {integrity: sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.0': + resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-jsx@7.27.1': + resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.27.1': + resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.27.1': + resolution: {integrity: sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.28.0': + resolution: {integrity: sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-typescript@7.27.1': + resolution: {integrity: sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.28.2': + resolution: {integrity: sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.0': + resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.2': + resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.25.8': + resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.8': + resolution: {integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.8': + resolution: {integrity: sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.8': + resolution: {integrity: sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.8': + resolution: {integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.8': + resolution: {integrity: sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.8': + resolution: {integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.8': + resolution: {integrity: sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.8': + resolution: {integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.8': + resolution: {integrity: sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.8': + resolution: {integrity: sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.8': + resolution: {integrity: sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.8': + resolution: {integrity: sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.8': + resolution: {integrity: sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.8': + resolution: {integrity: sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.8': + resolution: {integrity: sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.8': + resolution: {integrity: sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.8': + resolution: {integrity: sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.8': + resolution: {integrity: sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.8': + resolution: {integrity: sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.8': + resolution: {integrity: sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.8': + resolution: {integrity: sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.8': + resolution: {integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.8': + resolution: {integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.8': + resolution: {integrity: sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.8': + resolution: {integrity: sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.7.0': + resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.0': + resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.3.0': + resolution: {integrity: sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.15.1': + resolution: {integrity: sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.32.0': + resolution: {integrity: sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.3.4': + resolution: {integrity: sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.12': + resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.4': + resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} + + '@jridgewell/trace-mapping@0.3.29': + resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@pkgr/core@0.2.9': + resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@redplanethq/sdk@0.1.1': + resolution: {integrity: sha512-tfR1c9p7vNeCL5jsF9QlEZcRFLsihaHe/ZQWVKZYXzAZ6GugoIFBaayGfVvjNjyEnN3nlrl3usKBX+hhaKzg0g==} + engines: {node: '>=18.0.0'} + + '@rollup/rollup-android-arm-eabi@4.46.2': + resolution: {integrity: sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.46.2': + resolution: {integrity: sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.46.2': + resolution: {integrity: sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.46.2': + resolution: {integrity: sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.46.2': + resolution: {integrity: sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.46.2': + resolution: {integrity: sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.46.2': + resolution: {integrity: sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.46.2': + resolution: {integrity: sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.46.2': + resolution: {integrity: sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.46.2': + resolution: {integrity: sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.46.2': + resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.46.2': + resolution: {integrity: sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.46.2': + resolution: {integrity: sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.46.2': + resolution: {integrity: sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.46.2': + resolution: {integrity: sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.46.2': + resolution: {integrity: sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.46.2': + resolution: {integrity: sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.46.2': + resolution: {integrity: sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.46.2': + resolution: {integrity: sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.46.2': + resolution: {integrity: sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==} + cpu: [x64] + os: [win32] + + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + + '@types/node-fetch@2.6.13': + resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==} + + '@types/node@18.19.121': + resolution: {integrity: sha512-bHOrbyztmyYIi4f1R0s17QsPs1uyyYnGcXeZoGEd227oZjry0q6XQBQxd82X1I57zEfwO8h9Xo+Kl5gX1d9MwQ==} + + '@types/semver@7.7.0': + resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==} + + '@typescript-eslint/scope-manager@5.62.0': + resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/types@5.62.0': + resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/typescript-estree@5.62.0': + resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@5.62.0': + resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + + '@typescript-eslint/visitor-keys@5.62.0': + resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + agentkeepalive@4.6.0: + resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} + engines: {node: '>= 8.0.0'} + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + array.prototype.findlastindex@1.2.6: + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + axios@1.11.0: + resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + big-integer@1.6.52: + resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} + engines: {node: '>=0.6'} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + broadcast-channel@3.7.0: + resolution: {integrity: sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==} + + browserslist@4.25.1: + resolution: {integrity: sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bundle-require@5.1.0: + resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001731: + resolution: {integrity: sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + colors@1.2.3: + resolution: {integrity: sha512-qTfM2pNFeMZcLvf/RbrVAzDEVttZjFhaApfx9dplNjvHSX88Ui66zBRb/4YGob/xUWxDceirgoC1lT676asfCQ==} + engines: {node: '>=0.1.90'} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + + commander@14.0.0: + resolution: {integrity: sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==} + engines: {node: '>=20'} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + dateformat@3.0.3: + resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + detect-node@2.1.0: + resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + electron-to-chromium@1.5.194: + resolution: {integrity: sha512-SdnWJwSUot04UR51I2oPD8kuP2VI37/CADR1OHsFOUzZIvfWJBO6q11k5P/uKNyTT3cdOsnyjkrZ+DDShqYqJA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + es-abstract@1.24.0: + resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + + esbuild@0.25.8: + resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@10.1.8: + resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-import-resolver-alias@1.1.2: + resolution: {integrity: sha512-WdviM1Eu834zsfjHtcGHtGfcu+F30Od3V7I9Fi57uhBEwPkjDcii7/yW8jAT+gOhn4P/vOxxNAXbFAKsrrc15w==} + engines: {node: '>= 4'} + peerDependencies: + eslint-plugin-import: '>=1.4.0' + + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + + eslint-module-utils@2.12.1: + resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-import@2.32.0: + resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-plugin-jest@27.9.0: + resolution: {integrity: sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^5.0.0 || ^6.0.0 || ^7.0.0 + eslint: ^7.0.0 || ^8.0.0 + jest: '*' + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + jest: + optional: true + + eslint-plugin-prettier@5.5.3: + resolution: {integrity: sha512-NAdMYww51ehKfDyDhv59/eIItUVzU0Io9H2E8nHNGKEeeqlnci+1gCvrHib6EmZdf6GxF+LCV5K7UC65Ezvw7w==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-plugin-unused-imports@2.0.0: + resolution: {integrity: sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^5.0.0 + eslint: ^8.0.0 + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + + eslint-rule-composer@0.3.0: + resolution: {integrity: sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==} + engines: {node: '>=4.0.0'} + + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.32.0: + resolution: {integrity: sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + fdir@6.4.6: + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + fix-dts-default-cjs-exports@1.0.1: + resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + form-data-encoder@1.7.2: + resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} + + form-data@4.0.4: + resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} + engines: {node: '>= 6'} + + formdata-node@4.4.1: + resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} + engines: {node: '>= 12.20'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-function@1.1.0: + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + js-sha3@0.8.0: + resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + match-sorter@6.3.4: + resolution: {integrity: sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + microseconds@0.2.0: + resolution: {integrity: sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + + mlly@1.7.4: + resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nano-time@1.0.0: + resolution: {integrity: sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + ncc@0.3.6: + resolution: {integrity: sha512-OXudTB2Ebt/FnOuDoPQbaa17+tdVqSOWA+gLfPxccWwsNED1uA2zEhpoB1hwdFC9yYbio/mdV5cvOtQI3Zrx1w==} + + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + + oblivious-set@1.0.0: + resolution: {integrity: sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + openai@4.104.0: + resolution: {integrity: sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==} + hasBin: true + peerDependencies: + ws: ^8.18.0 + zod: ^3.23.8 + peerDependenciesMeta: + ws: + optional: true + zod: + optional: true + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} + hasBin: true + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-query@3.39.3: + resolution: {integrity: sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + remove-accents@0.5.0: + resolution: {integrity: sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rollup@4.46.2: + resolution: {integrity: sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + + safe-buffer@5.0.1: + resolution: {integrity: sha512-cr7dZWLwOeaFBLTIuZeYdkfO7UzGIKhjYENJFAxUOMKWGaWDm2nJM2rzxNRm5Owu0DH3ApwNo6kx5idXZfb/Iw==} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + source-map@0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + synckit@0.11.11: + resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} + engines: {node: ^14.18.0 || >=16.0.0} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} + + tinytim@0.1.1: + resolution: {integrity: sha512-NIpsp9lBIxPNzB++HnMmUd4byzJSVbbO4F+As1Gb1IG/YQT5QvmBDjpx8SpDS8fhGC+t+Qw8ldQgbcAIaU+2cA==} + engines: {node: '>= 0.2.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + tr46@1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + + tracer@0.8.15: + resolution: {integrity: sha512-ZQzlhd6zZFIpAhACiZkxLjl65XqVwi8t8UEBVGRIHAQN6nj55ftJWiFell+WSqWCP/vEycrIbUSuiyMwul+TFw==} + engines: {node: '>= 0.10.0'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsup@8.5.0: + resolution: {integrity: sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + + tsutils@3.21.0: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + + typescript@4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} + engines: {node: '>=4.2.0'} + hasBin: true + + ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + + ultron@1.1.1: + resolution: {integrity: sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==} + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + unload@2.2.0: + resolution: {integrity: sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==} + + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + web-streams-polyfill@4.0.0-beta.3: + resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} + engines: {node: '>= 14'} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + webidl-conversions@4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + whatwg-url@7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@2.3.1: + resolution: {integrity: sha512-61a+9LgtYZxTq1hAonhX8Xwpo2riK4IOR/BIVxioFbCfc3QFKmpE4x9dLExfLHKtUfVZigYa36tThVhO57erEw==} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.0': {} + + '@babel/core@7.28.0': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.0 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0) + '@babel/helpers': 7.28.2 + '@babel/parser': 7.28.0 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.2 + convert-source-map: 2.0.0 + debug: 4.4.1 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.0': + dependencies: + '@babel/parser': 7.28.0 + '@babel/types': 7.28.2 + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.28.2 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.25.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.27.1(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.27.1 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.28.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-member-expression-to-functions@7.27.1': + dependencies: + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.2 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.2 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.27.1': + dependencies: + '@babel/types': 7.28.2 + + '@babel/helper-plugin-utils@7.27.1': {} + + '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-member-expression-to-functions': 7.27.1 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.28.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + dependencies: + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.2 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.2': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + + '@babel/parser@7.28.0': + dependencies: + '@babel/types': 7.28.2 + + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0) + '@babel/helper-plugin-utils': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-typescript@7.28.0(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.28.0) + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.0) + transitivePeerDependencies: + - supports-color + + '@babel/preset-typescript@7.27.1(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.0) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.0) + '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.0) + transitivePeerDependencies: + - supports-color + + '@babel/runtime@7.28.2': {} + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.0 + '@babel/types': 7.28.2 + + '@babel/traverse@7.28.0': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.0 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.0 + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.2': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@esbuild/aix-ppc64@0.25.8': + optional: true + + '@esbuild/android-arm64@0.25.8': + optional: true + + '@esbuild/android-arm@0.25.8': + optional: true + + '@esbuild/android-x64@0.25.8': + optional: true + + '@esbuild/darwin-arm64@0.25.8': + optional: true + + '@esbuild/darwin-x64@0.25.8': + optional: true + + '@esbuild/freebsd-arm64@0.25.8': + optional: true + + '@esbuild/freebsd-x64@0.25.8': + optional: true + + '@esbuild/linux-arm64@0.25.8': + optional: true + + '@esbuild/linux-arm@0.25.8': + optional: true + + '@esbuild/linux-ia32@0.25.8': + optional: true + + '@esbuild/linux-loong64@0.25.8': + optional: true + + '@esbuild/linux-mips64el@0.25.8': + optional: true + + '@esbuild/linux-ppc64@0.25.8': + optional: true + + '@esbuild/linux-riscv64@0.25.8': + optional: true + + '@esbuild/linux-s390x@0.25.8': + optional: true + + '@esbuild/linux-x64@0.25.8': + optional: true + + '@esbuild/netbsd-arm64@0.25.8': + optional: true + + '@esbuild/netbsd-x64@0.25.8': + optional: true + + '@esbuild/openbsd-arm64@0.25.8': + optional: true + + '@esbuild/openbsd-x64@0.25.8': + optional: true + + '@esbuild/openharmony-arm64@0.25.8': + optional: true + + '@esbuild/sunos-x64@0.25.8': + optional: true + + '@esbuild/win32-arm64@0.25.8': + optional: true + + '@esbuild/win32-ia32@0.25.8': + optional: true + + '@esbuild/win32-x64@0.25.8': + optional: true + + '@eslint-community/eslint-utils@4.7.0(eslint@9.32.0)': + dependencies: + eslint: 9.32.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.21.0': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.1 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.3.0': {} + + '@eslint/core@0.15.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.1 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.32.0': {} + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.3.4': + dependencies: + '@eslint/core': 0.15.1 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.12': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.4 + '@jridgewell/trace-mapping': 0.3.29 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.4': {} + + '@jridgewell/trace-mapping@0.3.29': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.4 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@pkgr/core@0.2.9': {} + + '@redplanethq/sdk@0.1.1': + dependencies: + commander: 14.0.0 + + '@rollup/rollup-android-arm-eabi@4.46.2': + optional: true + + '@rollup/rollup-android-arm64@4.46.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.46.2': + optional: true + + '@rollup/rollup-darwin-x64@4.46.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.46.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.46.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.46.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.46.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.46.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.46.2': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.46.2': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.46.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.46.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.46.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.46.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.46.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.46.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.46.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.46.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.46.2': + optional: true + + '@rtsao/scc@1.1.0': {} + + '@types/estree@1.0.8': {} + + '@types/json-schema@7.0.15': {} + + '@types/json5@0.0.29': {} + + '@types/node-fetch@2.6.13': + dependencies: + '@types/node': 18.19.121 + form-data: 4.0.4 + + '@types/node@18.19.121': + dependencies: + undici-types: 5.26.5 + + '@types/semver@7.7.0': {} + + '@typescript-eslint/scope-manager@5.62.0': + dependencies: + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 + + '@typescript-eslint/types@5.62.0': {} + + '@typescript-eslint/typescript-estree@5.62.0(typescript@4.9.5)': + dependencies: + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 + debug: 4.4.1 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.7.2 + tsutils: 3.21.0(typescript@4.9.5) + optionalDependencies: + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@5.62.0(eslint@9.32.0)(typescript@4.9.5)': + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0) + '@types/json-schema': 7.0.15 + '@types/semver': 7.7.0 + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5) + eslint: 9.32.0 + eslint-scope: 5.1.1 + semver: 7.7.2 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@5.62.0': + dependencies: + '@typescript-eslint/types': 5.62.0 + eslint-visitor-keys: 3.4.3 + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + agentkeepalive@4.6.0: + dependencies: + humanize-ms: 1.2.1 + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-regex@6.1.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + any-promise@1.3.0: {} + + argparse@2.0.1: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-includes@3.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 + + array-union@2.1.0: {} + + array.prototype.findlastindex@1.2.6: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + async-function@1.0.0: {} + + asynckit@0.4.0: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + axios@1.11.0: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.4 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + balanced-match@1.0.2: {} + + big-integer@1.6.52: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + broadcast-channel@3.7.0: + dependencies: + '@babel/runtime': 7.28.2 + detect-node: 2.1.0 + js-sha3: 0.8.0 + microseconds: 0.2.0 + nano-time: 1.0.0 + oblivious-set: 1.0.0 + rimraf: 3.0.2 + unload: 2.2.0 + + browserslist@4.25.1: + dependencies: + caniuse-lite: 1.0.30001731 + electron-to-chromium: 1.5.194 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.25.1) + + bundle-require@5.1.0(esbuild@0.25.8): + dependencies: + esbuild: 0.25.8 + load-tsconfig: 0.2.5 + + cac@6.7.14: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001731: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + colors@1.2.3: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@12.1.0: {} + + commander@14.0.0: {} + + commander@4.1.1: {} + + concat-map@0.0.1: {} + + confbox@0.1.8: {} + + consola@3.4.2: {} + + convert-source-map@2.0.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + dateformat@3.0.3: {} + + debug@3.2.7: + dependencies: + ms: 2.1.3 + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + delayed-stream@1.0.0: {} + + detect-node@2.1.0: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + electron-to-chromium@1.5.194: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + es-abstract@1.24.0: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.19 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + esbuild@0.25.8: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.8 + '@esbuild/android-arm': 0.25.8 + '@esbuild/android-arm64': 0.25.8 + '@esbuild/android-x64': 0.25.8 + '@esbuild/darwin-arm64': 0.25.8 + '@esbuild/darwin-x64': 0.25.8 + '@esbuild/freebsd-arm64': 0.25.8 + '@esbuild/freebsd-x64': 0.25.8 + '@esbuild/linux-arm': 0.25.8 + '@esbuild/linux-arm64': 0.25.8 + '@esbuild/linux-ia32': 0.25.8 + '@esbuild/linux-loong64': 0.25.8 + '@esbuild/linux-mips64el': 0.25.8 + '@esbuild/linux-ppc64': 0.25.8 + '@esbuild/linux-riscv64': 0.25.8 + '@esbuild/linux-s390x': 0.25.8 + '@esbuild/linux-x64': 0.25.8 + '@esbuild/netbsd-arm64': 0.25.8 + '@esbuild/netbsd-x64': 0.25.8 + '@esbuild/openbsd-arm64': 0.25.8 + '@esbuild/openbsd-x64': 0.25.8 + '@esbuild/openharmony-arm64': 0.25.8 + '@esbuild/sunos-x64': 0.25.8 + '@esbuild/win32-arm64': 0.25.8 + '@esbuild/win32-ia32': 0.25.8 + '@esbuild/win32-x64': 0.25.8 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@10.1.8(eslint@9.32.0): + dependencies: + eslint: 9.32.0 + + eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.32.0(eslint@9.32.0)): + dependencies: + eslint-plugin-import: 2.32.0(eslint@9.32.0) + + eslint-import-resolver-node@0.3.9: + dependencies: + debug: 3.2.7 + is-core-module: 2.16.1 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.1(eslint-import-resolver-node@0.3.9)(eslint@9.32.0): + dependencies: + debug: 3.2.7 + optionalDependencies: + eslint: 9.32.0 + eslint-import-resolver-node: 0.3.9 + transitivePeerDependencies: + - supports-color + + eslint-plugin-import@2.32.0(eslint@9.32.0): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.32.0 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.1(eslint-import-resolver-node@0.3.9)(eslint@9.32.0) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-jest@27.9.0(eslint@9.32.0)(typescript@4.9.5): + dependencies: + '@typescript-eslint/utils': 5.62.0(eslint@9.32.0)(typescript@4.9.5) + eslint: 9.32.0 + transitivePeerDependencies: + - supports-color + - typescript + + eslint-plugin-prettier@5.5.3(eslint-config-prettier@10.1.8(eslint@9.32.0))(eslint@9.32.0)(prettier@3.6.2): + dependencies: + eslint: 9.32.0 + prettier: 3.6.2 + prettier-linter-helpers: 1.0.0 + synckit: 0.11.11 + optionalDependencies: + eslint-config-prettier: 10.1.8(eslint@9.32.0) + + eslint-plugin-unused-imports@2.0.0(eslint@9.32.0): + dependencies: + eslint: 9.32.0 + eslint-rule-composer: 0.3.0 + + eslint-rule-composer@0.3.0: {} + + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.32.0: + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.21.0 + '@eslint/config-helpers': 0.3.0 + '@eslint/core': 0.15.1 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.32.0 + '@eslint/plugin-kit': 0.3.4 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.1 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@4.3.0: {} + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + event-target-shim@5.0.1: {} + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fdir@6.4.6(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + fix-dts-default-cjs-exports@1.0.1: + dependencies: + magic-string: 0.30.17 + mlly: 1.7.4 + rollup: 4.46.2 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + follow-redirects@1.15.11: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + form-data-encoder@1.7.2: {} + + form-data@4.0.4: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + formdata-node@4.4.1: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + gensync@1.0.0-beta.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.4.5: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@14.0.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + gopd@1.2.0: {} + + has-bigints@1.1.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-fullwidth-code-point@3.0.0: {} + + is-generator-function@1.1.0: + dependencies: + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.19 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + joycon@3.1.1: {} + + js-sha3@0.8.0: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@1.0.2: + dependencies: + minimist: 1.2.8 + + json5@2.2.3: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + load-tsconfig@0.2.5: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + lodash.sortby@4.7.0: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.4 + + match-sorter@6.3.4: + dependencies: + '@babel/runtime': 7.28.2 + remove-accents: 0.5.0 + + math-intrinsics@1.1.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + microseconds@0.2.0: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + mkdirp@0.5.6: + dependencies: + minimist: 1.2.8 + + mlly@1.7.4: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.1 + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nano-time@1.0.0: + dependencies: + big-integer: 1.6.52 + + natural-compare@1.4.0: {} + + ncc@0.3.6: + dependencies: + mkdirp: 0.5.6 + rimraf: 2.7.1 + tracer: 0.8.15 + ws: 2.3.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + node-domexception@1.0.0: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-releases@2.0.19: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + oblivious-set@1.0.0: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + openai@4.104.0: + dependencies: + '@types/node': 18.19.121 + '@types/node-fetch': 2.6.13 + abort-controller: 3.0.0 + agentkeepalive: 4.6.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + package-json-from-dist@1.0.1: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-type@4.0.0: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pirates@4.0.7: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.7.4 + pathe: 2.0.3 + + possible-typed-array-names@1.1.0: {} + + postcss-load-config@6.0.1: + dependencies: + lilconfig: 3.1.3 + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + + prettier@3.6.2: {} + + proxy-from-env@1.1.0: {} + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-query@3.39.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.28.2 + broadcast-channel: 3.7.0 + match-sorter: 6.3.4 + react: 18.3.1 + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + readdirp@4.1.2: {} + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + remove-accents@0.5.0: {} + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + rimraf@2.7.1: + dependencies: + glob: 7.2.3 + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + rollup@4.46.2: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.46.2 + '@rollup/rollup-android-arm64': 4.46.2 + '@rollup/rollup-darwin-arm64': 4.46.2 + '@rollup/rollup-darwin-x64': 4.46.2 + '@rollup/rollup-freebsd-arm64': 4.46.2 + '@rollup/rollup-freebsd-x64': 4.46.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.46.2 + '@rollup/rollup-linux-arm-musleabihf': 4.46.2 + '@rollup/rollup-linux-arm64-gnu': 4.46.2 + '@rollup/rollup-linux-arm64-musl': 4.46.2 + '@rollup/rollup-linux-loongarch64-gnu': 4.46.2 + '@rollup/rollup-linux-ppc64-gnu': 4.46.2 + '@rollup/rollup-linux-riscv64-gnu': 4.46.2 + '@rollup/rollup-linux-riscv64-musl': 4.46.2 + '@rollup/rollup-linux-s390x-gnu': 4.46.2 + '@rollup/rollup-linux-x64-gnu': 4.46.2 + '@rollup/rollup-linux-x64-musl': 4.46.2 + '@rollup/rollup-win32-arm64-msvc': 4.46.2 + '@rollup/rollup-win32-ia32-msvc': 4.46.2 + '@rollup/rollup-win32-x64-msvc': 4.46.2 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-buffer@5.0.1: {} + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + semver@6.3.1: {} + + semver@7.7.2: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@4.1.0: {} + + slash@3.0.0: {} + + source-map@0.8.0-beta.0: + dependencies: + whatwg-url: 7.1.0 + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + strip-bom@3.0.0: {} + + strip-json-comments@3.1.1: {} + + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.12 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + ts-interface-checker: 0.1.13 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + synckit@0.11.11: + dependencies: + '@pkgr/core': 0.2.9 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinyexec@0.3.2: {} + + tinyglobby@0.2.14: + dependencies: + fdir: 6.4.6(picomatch@4.0.3) + picomatch: 4.0.3 + + tinytim@0.1.1: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tr46@0.0.3: {} + + tr46@1.0.1: + dependencies: + punycode: 2.3.1 + + tracer@0.8.15: + dependencies: + colors: 1.2.3 + dateformat: 3.0.3 + mkdirp: 0.5.6 + tinytim: 0.1.1 + + tree-kill@1.2.2: {} + + ts-interface-checker@0.1.13: {} + + tsconfig-paths@3.15.0: + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@1.14.1: {} + + tslib@2.8.1: {} + + tsup@8.5.0(typescript@4.9.5): + dependencies: + bundle-require: 5.1.0(esbuild@0.25.8) + cac: 6.7.14 + chokidar: 4.0.3 + consola: 3.4.2 + debug: 4.4.1 + esbuild: 0.25.8 + fix-dts-default-cjs-exports: 1.0.1 + joycon: 3.1.1 + picocolors: 1.1.1 + postcss-load-config: 6.0.1 + resolve-from: 5.0.0 + rollup: 4.46.2 + source-map: 0.8.0-beta.0 + sucrase: 3.35.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.14 + tree-kill: 1.2.2 + optionalDependencies: + typescript: 4.9.5 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + + tsutils@3.21.0(typescript@4.9.5): + dependencies: + tslib: 1.14.1 + typescript: 4.9.5 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript@4.9.5: {} + + ufo@1.6.1: {} + + ultron@1.1.1: {} + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + undici-types@5.26.5: {} + + unload@2.2.0: + dependencies: + '@babel/runtime': 7.28.2 + detect-node: 2.1.0 + + update-browserslist-db@1.1.3(browserslist@4.25.1): + dependencies: + browserslist: 4.25.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + web-streams-polyfill@4.0.0-beta.3: {} + + webidl-conversions@3.0.1: {} + + webidl-conversions@4.0.2: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + whatwg-url@7.1.0: + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.0 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.19 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.19: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + wrappy@1.0.2: {} + + ws@2.3.1: + dependencies: + safe-buffer: 5.0.1 + ultron: 1.1.1 + + yallist@3.1.1: {} + + yocto-queue@0.1.0: {} diff --git a/integrations/github/rollup.config.mjs b/integrations/github/rollup.config.mjs new file mode 100644 index 0000000..aa2fbd7 --- /dev/null +++ b/integrations/github/rollup.config.mjs @@ -0,0 +1,35 @@ +import commonjs from '@rollup/plugin-commonjs'; +import json from '@rollup/plugin-json'; +import resolve from '@rollup/plugin-node-resolve'; +import nodePolyfills from 'rollup-plugin-node-polyfills'; + +import { terser } from 'rollup-plugin-terser'; +import typescript from 'rollup-plugin-typescript2'; + +export default [ + { + input: 'backend/index.ts', + external: ['axios'], + output: [ + { + file: 'dist/backend/index.js', + sourcemap: true, + format: 'cjs', + exports: 'named', + preserveModules: false, + }, + ], + plugins: [ + nodePolyfills(), + json(), + resolve({ extensions: ['.js', '.ts'] }), + commonjs({ + include: /\/node_modules\//, + }), + typescript({ + tsconfig: 'tsconfig.json', + }), + terser(), + ], + }, +]; diff --git a/integrations/github/spec.json b/integrations/github/spec.json new file mode 100644 index 0000000..2c424bf --- /dev/null +++ b/integrations/github/spec.json @@ -0,0 +1,25 @@ +{ + "name": "GitHub extension", + "key": "github", + "description": "Plan, track, and manage your agile and software development projects in GitHub. Customize your workflow, collaborate, and release great software.", + "icon": "github", + "schedule": { + "frequency": "*/5 * * * *" + }, + "auth": { + "OAuth2": { + "token_url": "https://github.com/login/oauth/access_token", + "authorization_url": "https://github.com/login/oauth/authorize", + "scopes": ["user", "public_repo", "repo", "notifications", "gist", "read:org", "repo_hooks"], + "scope_separator": "," + } + }, + "mcp": { + "type": "http", + "url": "https://api.githubcopilot.com/mcp/", + "headers": { + "Authorization": "Bearer ${config:access_token}", + "Content-Type": "application/json" + } + } +} diff --git a/integrations/github/src/account-create.ts b/integrations/github/src/account-create.ts new file mode 100644 index 0000000..503463f --- /dev/null +++ b/integrations/github/src/account-create.ts @@ -0,0 +1,37 @@ +import { getGithubData } from './utils'; + +export async function integrationCreate( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data: any, +) { + const { oauthResponse } = data; + const integrationConfiguration = { + refresh_token: oauthResponse.refresh_token, + access_token: oauthResponse.access_token, + }; + + const user = await getGithubData( + 'https://api.github.com/user', + integrationConfiguration.access_token, + ); + + return [ + { + type: 'account', + data: { + settings: { + login: user.login, + username: user.login, + schedule: { + frequency: '*/15 * * * *', + }, + }, + accountId: user.id.toString(), + config: { + ...integrationConfiguration, + mcp: { tokens: { access_token: integrationConfiguration.access_token } }, + }, + }, + }, + ]; +} diff --git a/integrations/github/src/index.ts b/integrations/github/src/index.ts new file mode 100644 index 0000000..e94aa79 --- /dev/null +++ b/integrations/github/src/index.ts @@ -0,0 +1,69 @@ +import { handleSchedule } from './schedule'; +import { integrationCreate } from './account-create'; + +import { + IntegrationCLI, + IntegrationEventPayload, + IntegrationEventType, + Spec, +} from '@redplanethq/sdk'; + +export async function run(eventPayload: IntegrationEventPayload) { + switch (eventPayload.event) { + case IntegrationEventType.SETUP: + console.log(eventPayload.eventBody); + return await integrationCreate(eventPayload.eventBody); + + case IntegrationEventType.SYNC: + return await handleSchedule(eventPayload.config, eventPayload.state); + + default: + return { message: `The event payload type is ${eventPayload.event}` }; + } +} + +// CLI implementation that extends the base class +class GitHubCLI extends IntegrationCLI { + constructor() { + super('github', '1.0.0'); + } + + protected async handleEvent(eventPayload: IntegrationEventPayload): Promise { + return await run(eventPayload); + } + + protected async getSpec(): Promise { + return { + name: 'GitHub extension', + key: 'github', + description: + 'Plan, track, and manage your agile and software development projects in GitHub. Customize your workflow, collaborate, and release great software.', + icon: 'github', + auth: { + OAuth2: { + token_url: 'https://github.com/login/oauth/access_token', + authorization_url: 'https://github.com/login/oauth/authorize', + scopes: [ + 'user', + 'public_repo', + 'repo', + 'notifications', + 'gist', + 'read:org', + 'repo_hooks', + ], + scope_separator: ',', + }, + }, + }; + } +} + +// Define a main function and invoke it directly. +// This works after bundling to JS and running with `node index.js`. +function main() { + const githubCLI = new GitHubCLI(); + githubCLI.parse(); +} + +main(); diff --git a/integrations/github/src/schedule.ts b/integrations/github/src/schedule.ts new file mode 100644 index 0000000..65199aa --- /dev/null +++ b/integrations/github/src/schedule.ts @@ -0,0 +1,360 @@ +import { getUserEvents, getGithubData } from './utils'; + +interface GitHubActivityCreateParams { + text: string; + sourceURL: string; +} + +interface GitHubSettings { + lastSyncTime?: string; + lastUserEventTime?: string; + username?: string; +} + +/** + * Creates an activity message based on GitHub data + */ +function createActivityMessage(params: GitHubActivityCreateParams) { + return { + type: 'activity', + data: { + text: params.text, + sourceURL: params.sourceURL, + }, + }; +} + +/** + * Gets default sync time (24 hours ago) + */ +function getDefaultSyncTime(): string { + return new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); +} + +/** + * Fetches user information from GitHub + */ +async function fetchUserInfo(accessToken: string) { + try { + return await getGithubData('https://api.github.com/user', accessToken); + } catch (error) { + console.error('Error fetching GitHub user info:', error); + return null; + } +} + +/** + * Processes GitHub notifications into activity messages + */ +async function processNotifications(accessToken: string, lastSyncTime: string): Promise { + const activities = []; + const allowedReasons = [ + 'assign', + 'review_requested', + 'mention', + 'state_change', + 'subscribed', + 'author', + 'approval_requested', + 'comment', + 'ci_activity', + 'invitation', + 'member_feature_requested', + 'security_alert', + 'security_advisory_credit', + 'team_mention', + ]; + + let page = 1; + let hasMorePages = true; + + while (hasMorePages) { + try { + const notifications = await getGithubData( + `https://api.github.com/notifications?page=${page}&per_page=50&all=true&since=${lastSyncTime}`, + accessToken, + ); + + if (!notifications || notifications.length === 0) { + hasMorePages = false; + break; + } + + if (notifications.length < 50) { + hasMorePages = false; + } else { + page++; + } + + for (const notification of notifications) { + try { + if (!allowedReasons.includes(notification.reason)) { + continue; + } + + const repository = notification.repository; + const subject = notification.subject; + let title = ''; + let sourceURL = ''; + + // Get the actual GitHub data for the notification + let githubData: any = {}; + if (subject.url) { + try { + githubData = await getGithubData(subject.url, accessToken); + } catch (error) { + console.error('Error fetching GitHub data for notification:', error); + continue; + } + } + + const url = githubData.html_url || notification.subject.url || ''; + sourceURL = url; + + const isIssue = subject.type === 'Issue'; + const isPullRequest = subject.type === 'PullRequest'; + const isComment = notification.reason === 'comment'; + + switch (notification.reason) { + case 'assign': + title = `${isIssue ? 'Issue' : 'PR'} assigned to you: #${githubData.number} - ${githubData.title}`; + break; + + case 'author': + if (isComment) { + title = `New comment on your ${isIssue ? 'issue' : 'PR'} by ${githubData.user?.login}: ${githubData.body}`; + } else { + title = `You created this ${isIssue ? 'issue' : 'PR'}: #${githubData.number} - ${githubData.title}`; + } + break; + + case 'comment': + title = `New comment by ${githubData.user?.login} in ${repository.full_name}: ${githubData.body}`; + break; + + case 'manual': + title = `You subscribed to: #${githubData.number} - ${githubData.title}`; + break; + + case 'mention': + title = `@mentioned by ${githubData.user?.login} in ${repository.full_name}: ${githubData.body}`; + break; + + case 'review_requested': + title = `PR review requested in ${repository.full_name}: #${githubData.number} - ${githubData.title}`; + break; + + case 'state_change': { + let stateInfo = ''; + if (githubData.state) { + stateInfo = `to ${githubData.state}`; + } else if (githubData.merged) { + stateInfo = 'to merged'; + } else if (githubData.closed_at) { + stateInfo = 'to closed'; + } + title = `State changed ${stateInfo} in ${repository.full_name}: #${githubData.number} - ${githubData.title}`; + break; + } + + case 'subscribed': + if (isComment) { + title = `New comment on watched ${isIssue ? 'issue' : 'PR'} in ${repository.full_name} by ${githubData.user?.login}: ${githubData.body}`; + } else if (isPullRequest) { + title = `New PR created in watched repo ${repository.full_name}: #${githubData.number} - ${githubData.title}`; + } else if (isIssue) { + title = `New issue created in watched repo ${repository.full_name}: #${githubData.number} - ${githubData.title}`; + } else { + title = `Update in watched repo ${repository.full_name}: #${githubData.number} - ${githubData.title}`; + } + break; + + case 'team_mention': + title = `Your team was mentioned in ${repository.full_name}`; + break; + + default: + title = `GitHub notification: ${repository.full_name}`; + break; + } + + if (title && sourceURL) { + activities.push( + createActivityMessage({ + text: title, + sourceURL: sourceURL, + }), + ); + } + } catch (error) { + // Silently ignore errors to prevent stdout pollution + } + } + } catch (error) { + // Silently ignore errors to prevent stdout pollution + hasMorePages = false; + } + } + + return activities; +} + +/** + * Processes user events (PRs, issues, comments) into activity messages + */ +async function processUserEvents( + username: string, + accessToken: string, + lastUserEventTime: string, +): Promise { + const activities = []; + let page = 1; + let hasMorePages = true; + + console.log('Processing user events'); + + while (hasMorePages) { + try { + const userEvents = await getUserEvents(username, page, accessToken, lastUserEventTime); + console.log('User events', userEvents); + + if (!userEvents || userEvents.length === 0) { + hasMorePages = false; + break; + } + + if (userEvents.length < 30) { + hasMorePages = false; + } else { + page++; + } + + for (const event of userEvents) { + try { + let title = ''; + const sourceURL = event.html_url || ''; + + switch (event.type) { + case 'pr': + title = `You created PR #${event.number}: ${event.title}`; + break; + case 'issue': + title = `You created issue #${event.number}: ${event.title}`; + break; + case 'pr_comment': + title = `You commented on PR #${event.number}: ${event.title}`; + break; + case 'issue_comment': + title = `You commented on issue #${event.number}: ${event.title}`; + break; + case 'self_assigned_issue': + title = `You assigned yourself to issue #${event.number}: ${event.title}`; + break; + default: + title = `GitHub activity: ${event.title || 'Unknown'}`; + break; + } + + if (title && sourceURL) { + activities.push( + createActivityMessage({ + text: title, + sourceURL: sourceURL, + }), + ); + } + + console.log('Activities', activities); + } catch (error) { + // Silently ignore errors to prevent stdout pollution + } + } + } catch (error) { + // Silently ignore errors to prevent stdout pollution + hasMorePages = false; + } + } + + return activities; +} + +export async function handleSchedule(config: any, state: any) { + try { + const integrationConfiguration = config; + + // Check if we have a valid access token + if (!integrationConfiguration?.access_token) { + return []; + } + + // Get settings or initialize if not present + let settings = (state || {}) as GitHubSettings; + + // Default to 24 hours ago if no last sync times + const lastSyncTime = settings.lastSyncTime || getDefaultSyncTime(); + const lastUserEventTime = settings.lastUserEventTime || getDefaultSyncTime(); + + // Fetch user info to get username if not available + let user; + try { + user = await fetchUserInfo(integrationConfiguration.access_token); + } catch (error) { + return []; + } + + if (!user) { + return []; + } + + // Update username in settings if not present + if (!settings.username && user.login) { + settings.username = user.login; + } + + // Collect all messages + const messages = []; + + // Process notifications + try { + const notificationActivities = await processNotifications( + integrationConfiguration.access_token, + lastSyncTime, + ); + messages.push(...notificationActivities); + } catch (error) { + // Silently ignore errors to prevent stdout pollution + } + + // Process user events if we have a username + if (settings.username) { + console.log('Processing user events'); + try { + const userEventActivities = await processUserEvents( + settings.username, + integrationConfiguration.access_token, + lastUserEventTime, + ); + messages.push(...userEventActivities); + } catch (error) { + // Silently ignore errors to prevent stdout pollution + } + } + + // Update last sync times + const newSyncTime = new Date().toISOString(); + + // Add state message for saving settings + messages.push({ + type: 'state', + data: { + ...settings, + lastSyncTime: newSyncTime, + lastUserEventTime: newSyncTime, + }, + }); + + return messages; + } catch (error) { + return []; + } +} diff --git a/integrations/github/src/utils.ts b/integrations/github/src/utils.ts new file mode 100644 index 0000000..e71bd2f --- /dev/null +++ b/integrations/github/src/utils.ts @@ -0,0 +1,97 @@ +import axios from 'axios'; + +export async function getGithubData(url: string, accessToken: string) { + return ( + await axios.get(url, { + headers: { + Authorization: `Bearer ${accessToken}`, + Accept: 'application/vnd.github+json', + 'X-GitHub-Api-Version': '2022-11-28', + }, + }) + ).data; +} + +/** + * Get user events (PRs, issues, comments) and also issues assigned to the user by themselves. + */ +export async function getUserEvents( + username: string, + page: number, + accessToken: string, + since?: string, +) { + try { + const formattedDate = since ? encodeURIComponent(since.split('T')[0]) : ''; + // Search for user's PRs, issues, and comments since the last sync + const [ + prsResponse, + issuesResponse, + commentsResponse, + // For self-assigned issues, we need to fetch issues assigned to the user and authored by the user + assignedIssuesResponse, + ] = await Promise.all([ + // Search for PRs created by user + getGithubData( + `https://api.github.com/search/issues?q=author:${username}+type:pr+created:>${formattedDate}&sort=created&order=desc&page=${page}&per_page=10`, + accessToken, + ), + // Search for issues created by user + getGithubData( + `https://api.github.com/search/issues?q=author:${username}+type:issue+created:>${formattedDate}&sort=created&order=desc&page=${page}&per_page=10`, + accessToken, + ), + // Search for issues/PRs the user commented on + getGithubData( + `https://api.github.com/search/issues?q=commenter:${username}+updated:>${formattedDate}&sort=updated&order=desc&page=${page}&per_page=10`, + accessToken, + ), + // Search for issues assigned to the user and authored by the user (self-assigned) + getGithubData( + `https://api.github.com/search/issues?q=assignee:${username}+author:${username}+type:issue+updated:>${formattedDate}&sort=updated&order=desc&page=${page}&per_page=10`, + accessToken, + ), + ]); + + console.log('PRs found:', prsResponse?.items?.length || 0); + console.log('Issues found:', issuesResponse?.items?.length || 0); + console.log('Comments found:', commentsResponse?.items?.length || 0); + console.log('Self-assigned issues found:', assignedIssuesResponse?.items?.length || 0); + + // Return simplified results - combine PRs, issues, commented items, and self-assigned issues + const results = [ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ...(prsResponse?.items || []).map((item: any) => ({ ...item, type: 'pr' })), + ...(issuesResponse?.items || []) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .filter((item: any) => !item.pull_request) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .map((item: any) => ({ ...item, type: 'issue' })), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ...(commentsResponse?.items || []).map((item: any) => ({ + ...item, + type: item.pull_request ? 'pr_comment' : 'issue_comment', + })), + // Add self-assigned issues, but only if not already present in issuesResponse + ...(assignedIssuesResponse?.items || []) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .filter((item: any) => { + // Only include if not already in issuesResponse (by id) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return !(issuesResponse?.items || []).some((issue: any) => issue.id === item.id); + }) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .map((item: any) => ({ + ...item, + type: 'self_assigned_issue', + })), + ]; + + // Sort by created_at descending + results.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); + return results; + } catch (error) { + console.error('Error fetching user activity via search:', error); + return []; + } +} diff --git a/integrations/github/tsconfig.json b/integrations/github/tsconfig.json new file mode 100644 index 0000000..c719870 --- /dev/null +++ b/integrations/github/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "es2022", + "lib": ["dom", "dom.iterable", "esnext"], + "baseUrl": "frontend", + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "strictNullChecks": true, + "removeComments": true, + "preserveConstEnums": true, + "sourceMap": true, + "noUnusedParameters": true, + "noUnusedLocals": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": true, + "noFallthroughCasesInSwitch": true, + "useUnknownInCatchVariables": false + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "build", "dist", "scripts", "acceptance-tests", "webpack", "jest"], + "types": ["typePatches"] +} diff --git a/integrations/github/tsup.config.ts b/integrations/github/tsup.config.ts new file mode 100644 index 0000000..40ea9d9 --- /dev/null +++ b/integrations/github/tsup.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from 'tsup'; +import { dependencies } from './package.json'; + +export default defineConfig({ + entry: ['src/index.ts'], + format: ['cjs'], // or esm if you're using that + bundle: true, + target: 'node16', + outDir: 'bin', + splitting: false, + shims: true, + clean: true, + name: 'github', + platform: 'node', + legacyOutput: false, + noExternal: Object.keys(dependencies || {}), // ⬅️ bundle all deps + treeshake: { + preset: 'recommended', + }, +}); diff --git a/package.json b/package.json index e3a12f6..74119ae 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,14 @@ { "name": "core", "private": true, - "version": "0.1.13", + "version": "0.1.14", "workspaces": [ "apps/*", "packages/*" ], "scripts": { "build": "dotenv -- turbo run build", - "dev": "dotenv -- turbo run dev --filter=!@redplanethq/core", + "dev": "dotenv -- turbo run dev", "lint": "dotenv -- turbo run lint", "format": "dotenv -- prettier --write \"**/*.{ts,tsx,md}\"", "check-types": "dotenv -- turbo run check-types", diff --git a/packages/database/prisma/migrations/20250804053927_add_recall_log_model/migration.sql b/packages/database/prisma/migrations/20250804053927_add_recall_log_model/migration.sql new file mode 100644 index 0000000..2a68ce0 --- /dev/null +++ b/packages/database/prisma/migrations/20250804053927_add_recall_log_model/migration.sql @@ -0,0 +1,35 @@ +-- CreateTable +CREATE TABLE "RecallLog" ( + "id" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "deleted" TIMESTAMP(3), + "accessType" TEXT NOT NULL, + "query" TEXT, + "targetType" TEXT, + "targetId" TEXT, + "searchMethod" TEXT, + "minSimilarity" DOUBLE PRECISION, + "maxResults" INTEGER, + "resultCount" INTEGER NOT NULL DEFAULT 0, + "similarityScore" DOUBLE PRECISION, + "context" TEXT, + "source" TEXT, + "sessionId" TEXT, + "responseTimeMs" INTEGER, + "userId" TEXT NOT NULL, + "workspaceId" TEXT, + "conversationId" TEXT, + "metadata" JSONB DEFAULT '{}', + + CONSTRAINT "RecallLog_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "RecallLog" ADD CONSTRAINT "RecallLog_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "RecallLog" ADD CONSTRAINT "RecallLog_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "RecallLog" ADD CONSTRAINT "RecallLog_conversationId_fkey" FOREIGN KEY ("conversationId") REFERENCES "Conversation"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/packages/database/prisma/schema.prisma b/packages/database/prisma/schema.prisma index 60837e1..dc2e249 100644 --- a/packages/database/prisma/schema.prisma +++ b/packages/database/prisma/schema.prisma @@ -64,6 +64,7 @@ model Conversation { status String @default("pending") // Can be "pending", "running", "completed", "failed", "need_attention" ConversationHistory ConversationHistory[] + RecallLog RecallLog[] } model ConversationExecutionStep { @@ -423,6 +424,51 @@ model PersonalAccessToken { authorizationCodes AuthorizationCode[] } +model RecallLog { + id String @id @default(uuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deleted DateTime? + + // Access details + accessType String // "search", "recall", "direct_access" + query String? // Search query (null for direct access) + + // Target information + targetType String? // "episode", "statement", "entity", "mixed_results" + targetId String? // UUID of specific target (null for search with multiple results) + + // Search/access parameters + searchMethod String? // "semantic", "keyword", "hybrid", "contextual", "graph_traversal" + minSimilarity Float? // Minimum similarity threshold used + maxResults Int? // Maximum results requested + + // Results and interaction + resultCount Int @default(0) // Number of results returned + similarityScore Float? // Similarity score (for single result access) + + // Context and source + context String? // Additional context + source String? // Source of the access (e.g., "chat", "api", "integration") + sessionId String? // Session identifier + + // Performance metrics + responseTimeMs Int? // Response time in milliseconds + + // Relations + user User @relation(fields: [userId], references: [id]) + userId String + + workspace Workspace? @relation(fields: [workspaceId], references: [id]) + workspaceId String? + + conversation Conversation? @relation(fields: [conversationId], references: [id]) + conversationId String? + + // Metadata for additional tracking data + metadata Json? @default("{}") +} + model Space { id String @id @default(cuid()) name String @@ -505,6 +551,7 @@ model User { oauthIntegrationGrants OAuthIntegrationGrant[] oAuthClientInstallation OAuthClientInstallation[] UserUsage UserUsage? + RecallLog RecallLog[] } model UserUsage { @@ -579,6 +626,7 @@ model Workspace { OAuthAuthorizationCode OAuthAuthorizationCode[] OAuthAccessToken OAuthAccessToken[] OAuthRefreshToken OAuthRefreshToken[] + RecallLog RecallLog[] } enum AuthenticationMethod { diff --git a/packages/types/src/graph/graph.entity.ts b/packages/types/src/graph/graph.entity.ts index 520c010..1b4ab21 100644 --- a/packages/types/src/graph/graph.entity.ts +++ b/packages/types/src/graph/graph.entity.ts @@ -20,6 +20,7 @@ export interface EpisodicNode { userId: string; space?: string; sessionId?: string; + recallCount?: number; } /** @@ -52,6 +53,7 @@ export interface StatementNode { attributes: Record; userId: string; space?: string; + recallCount?: number; } /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d4aeb90..dd9fe51 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -463,6 +463,9 @@ importers: '@trigger.dev/sdk': specifier: ^4.0.0-v4-beta.22 version: 4.0.0-v4-beta.22(ai@4.3.14(react@18.3.1)(zod@3.23.8))(zod@3.23.8) + '@types/react-calendar-heatmap': + specifier: ^1.9.0 + version: 1.9.0 ai: specifier: 4.3.14 version: 4.3.14(react@18.3.1)(zod@3.23.8) @@ -571,6 +574,9 @@ importers: react: specifier: ^18.2.0 version: 18.3.1 + react-calendar-heatmap: + specifier: ^1.10.0 + version: 1.10.0(react@18.3.1) react-dom: specifier: ^18.2.0 version: 18.3.1(react@18.3.1) @@ -747,253 +753,6 @@ importers: specifier: ^4.2.1 version: 4.3.2(typescript@5.8.3)(vite@6.3.5(@types/node@20.19.7)(jiti@2.4.2)(less@4.4.0)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.42.0)(tsx@4.17.0)(yaml@2.8.0)) - packages/core-cli: - dependencies: - '@clack/prompts': - specifier: ^0.10.0 - version: 0.10.1 - '@depot/cli': - specifier: 0.0.1-cli.2.80.0 - version: 0.0.1-cli.2.80.0 - '@opentelemetry/api': - specifier: 1.9.0 - version: 1.9.0 - '@opentelemetry/api-logs': - specifier: 0.52.1 - version: 0.52.1 - '@opentelemetry/exporter-logs-otlp-http': - specifier: 0.52.1 - version: 0.52.1(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-trace-otlp-http': - specifier: 0.52.1 - version: 0.52.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': - specifier: 0.52.1 - version: 0.52.1(@opentelemetry/api@1.9.0)(supports-color@10.0.0) - '@opentelemetry/instrumentation-fetch': - specifier: 0.52.1 - version: 0.52.1(@opentelemetry/api@1.9.0)(supports-color@10.0.0) - '@opentelemetry/resources': - specifier: 1.25.1 - version: 1.25.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-logs': - specifier: 0.52.1 - version: 0.52.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-node': - specifier: 0.52.1 - version: 0.52.1(@opentelemetry/api@1.9.0)(supports-color@10.0.0) - '@opentelemetry/sdk-trace-base': - specifier: 1.25.1 - version: 1.25.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-node': - specifier: 1.25.1 - version: 1.25.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': - specifier: 1.25.1 - version: 1.25.1 - ansi-escapes: - specifier: ^7.0.0 - version: 7.0.0 - braces: - specifier: ^3.0.3 - version: 3.0.3 - c12: - specifier: ^1.11.1 - version: 1.11.2(magicast@0.3.5) - chalk: - specifier: ^5.2.0 - version: 5.4.1 - chokidar: - specifier: ^3.6.0 - version: 3.6.0 - cli-table3: - specifier: ^0.6.3 - version: 0.6.5 - commander: - specifier: ^9.4.1 - version: 9.5.0 - defu: - specifier: ^6.1.4 - version: 6.1.4 - dotenv: - specifier: ^16.4.5 - version: 16.5.0 - dotenv-expand: - specifier: ^12.0.2 - version: 12.0.2 - esbuild: - specifier: ^0.23.0 - version: 0.23.1 - eventsource: - specifier: ^3.0.2 - version: 3.0.7 - evt: - specifier: ^2.4.13 - version: 2.5.9 - fast-npm-meta: - specifier: ^0.2.2 - version: 0.2.2 - git-last-commit: - specifier: ^1.0.1 - version: 1.0.1 - gradient-string: - specifier: ^2.0.2 - version: 2.0.2 - has-flag: - specifier: ^5.0.1 - version: 5.0.1 - import-in-the-middle: - specifier: 1.11.0 - version: 1.11.0 - import-meta-resolve: - specifier: ^4.1.0 - version: 4.1.0 - ini: - specifier: ^5.0.0 - version: 5.0.0 - jsonc-parser: - specifier: 3.2.1 - version: 3.2.1 - knex: - specifier: 3.1.0 - version: 3.1.0(pg@8.16.3)(supports-color@10.0.0) - magicast: - specifier: ^0.3.4 - version: 0.3.5 - minimatch: - specifier: ^10.0.1 - version: 10.0.2 - mlly: - specifier: ^1.7.1 - version: 1.7.4 - nanoid: - specifier: 3.3.8 - version: 3.3.8 - nypm: - specifier: ^0.5.4 - version: 0.5.4 - object-hash: - specifier: ^3.0.0 - version: 3.0.0 - open: - specifier: ^10.0.3 - version: 10.2.0 - p-limit: - specifier: ^6.2.0 - version: 6.2.0 - p-retry: - specifier: ^6.1.0 - version: 6.2.1 - partysocket: - specifier: ^1.0.2 - version: 1.1.4 - pg: - specifier: 8.16.3 - version: 8.16.3 - pkg-types: - specifier: ^1.1.3 - version: 1.3.1 - polka: - specifier: ^0.5.2 - version: 0.5.2 - resolve: - specifier: ^1.22.8 - version: 1.22.10 - semver: - specifier: ^7.5.0 - version: 7.7.2 - signal-exit: - specifier: ^4.1.0 - version: 4.1.0 - source-map-support: - specifier: 0.5.21 - version: 0.5.21 - std-env: - specifier: ^3.7.0 - version: 3.9.0 - supports-color: - specifier: ^10.0.0 - version: 10.0.0 - tiny-invariant: - specifier: ^1.2.0 - version: 1.3.3 - tinyexec: - specifier: ^0.3.1 - version: 0.3.2 - tinyglobby: - specifier: ^0.2.10 - version: 0.2.14 - uuid: - specifier: 11.1.0 - version: 11.1.0 - ws: - specifier: ^8.18.0 - version: 8.18.3 - xdg-app-paths: - specifier: ^8.3.0 - version: 8.3.0 - zod: - specifier: 3.23.8 - version: 3.23.8 - zod-validation-error: - specifier: ^1.5.0 - version: 1.5.0(zod@3.23.8) - devDependencies: - '@epic-web/test-server': - specifier: ^0.1.0 - version: 0.1.6 - '@types/gradient-string': - specifier: ^1.1.2 - version: 1.1.6 - '@types/ini': - specifier: ^4.1.1 - version: 4.1.1 - '@types/object-hash': - specifier: 3.0.6 - version: 3.0.6 - '@types/polka': - specifier: ^0.5.7 - version: 0.5.7 - '@types/react': - specifier: ^18.2.48 - version: 18.2.69 - '@types/resolve': - specifier: ^1.20.6 - version: 1.20.6 - '@types/rimraf': - specifier: ^4.0.5 - version: 4.0.5 - '@types/semver': - specifier: ^7.5.0 - version: 7.7.0 - '@types/source-map-support': - specifier: 0.5.10 - version: 0.5.10 - '@types/ws': - specifier: ^8.5.3 - version: 8.18.1 - cpy-cli: - specifier: ^5.0.0 - version: 5.0.0 - execa: - specifier: ^8.0.1 - version: 8.0.1 - find-up: - specifier: ^7.0.0 - version: 7.0.0 - rimraf: - specifier: ^5.0.7 - version: 5.0.10 - ts-essentials: - specifier: 10.0.1 - version: 10.0.1(typescript@5.8.3) - tshy: - specifier: ^3.0.2 - version: 3.0.2 - tsx: - specifier: 4.17.0 - version: 4.17.0 - packages/database: dependencies: '@prisma/client': @@ -5218,6 +4977,9 @@ packages: '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/react-calendar-heatmap@1.9.0': + resolution: {integrity: sha512-BH8M/nsXoLGa3hxWbrq3guPwlK0cV+w1i4c/ktrTxTzN5fBths6WbeUZ4dK0+tE76qiGoVSo9Tse8WVVuMIV+w==} + '@types/react-dom@18.2.18': resolution: {integrity: sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==} @@ -8519,6 +8281,9 @@ packages: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} + memoize-one@5.2.1: + resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} + memorystream@0.3.1: resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} engines: {node: '>= 0.10.0'} @@ -9902,6 +9667,11 @@ packages: rc9@2.1.2: resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + react-calendar-heatmap@1.10.0: + resolution: {integrity: sha512-e5vcrzMWzKIF710egr1FpjWyuDEFeZm39nvV25muc8Wtqqi8iDOfqREELeQ9Wouqf9hhj939gq0i+iAxo7KdSw==} + peerDependencies: + react: '>=0.14.0' + react-css-styled@1.1.9: resolution: {integrity: sha512-M7fJZ3IWFaIHcZEkoFOnkjdiUFmwd8d+gTh2bpqMOcnxy/0Gsykw4dsL4QBiKsxcGow6tETUa4NAUcmJF+/nfw==} @@ -16153,6 +15923,10 @@ snapshots: '@types/range-parser@1.2.7': {} + '@types/react-calendar-heatmap@1.9.0': + dependencies: + '@types/react': 18.2.69 + '@types/react-dom@18.2.18': dependencies: '@types/react': 18.2.69 @@ -20145,6 +19919,8 @@ snapshots: media-typer@1.1.0: {} + memoize-one@5.2.1: {} + memorystream@0.3.1: {} meow@12.1.1: {} @@ -21735,6 +21511,12 @@ snapshots: defu: 6.1.4 destr: 2.0.5 + react-calendar-heatmap@1.10.0(react@18.3.1): + dependencies: + memoize-one: 5.2.1 + prop-types: 15.8.1 + react: 18.3.1 + react-css-styled@1.1.9: dependencies: css-styled: 1.0.8