diff --git a/apps/mesh/src/web/components/home/agents-list.tsx b/apps/mesh/src/web/components/home/agents-list.tsx index 3ae709cecd..73aaf4775a 100644 --- a/apps/mesh/src/web/components/home/agents-list.tsx +++ b/apps/mesh/src/web/components/home/agents-list.tsx @@ -29,6 +29,7 @@ import { ChevronRight, Plus, Users03 } from "@untitledui/icons"; import { ImportFromDecoDialog } from "@/web/components/import-from-deco-dialog.tsx"; import { SiteDiagnosticsRecruitModal } from "@/web/components/home/site-diagnostics-recruit-modal.tsx"; import { LeanCanvasRecruitModal } from "@/web/components/home/lean-canvas-recruit-modal.tsx"; +import { DecoFlightsRecruitModal } from "@/web/components/home/deco-flights-recruit-modal.tsx"; import { useCreateVirtualMCP } from "@/web/hooks/use-create-virtual-mcp"; import { useNavigateToAgent } from "@/web/hooks/use-navigate-to-agent"; import { Suspense, useState } from "react"; @@ -159,6 +160,7 @@ function AgentsListContent() { const [importDecoOpen, setImportDecoOpen] = useState(false); const [diagnosticsModalOpen, setDiagnosticsModalOpen] = useState(false); const [leanCanvasModalOpen, setLeanCanvasModalOpen] = useState(false); + const [decoFlightsModalOpen, setDecoFlightsModalOpen] = useState(false); const navigateToAgent = useNavigateToAgent(); const siteEditorAgent = WELL_KNOWN_AGENT_TEMPLATES.find( @@ -170,6 +172,9 @@ function AgentsListContent() { const leanCanvasAgent = WELL_KNOWN_AGENT_TEMPLATES.find( (t) => t.id === "lean-canvas", )!; + const decoFlightsAgent = WELL_KNOWN_AGENT_TEMPLATES.find( + (t) => t.id === "deco-flights", + )!; const recentIds = readRecentAgentIds(locator); @@ -209,6 +214,15 @@ function AgentsListContent() { a.title === leanCanvasAgent.title), ); + // Check if Deco Flights agent already exists + const existingDecoFlights = virtualMcps.find( + (a): a is typeof a & { id: string } => + a.id !== null && + ((a as { metadata?: { type?: string } }).metadata?.type === + decoFlightsAgent.id || + a.title === decoFlightsAgent.title), + ); + const hasAgents = agents.length > 0; return ( @@ -238,11 +252,21 @@ function AgentsListContent() { : () => setLeanCanvasModalOpen(true) } /> + navigateToAgent(existingDecoFlights.id) + : () => setDecoFlightsModalOpen(true) + } + /> {agents .filter( (a) => a.id !== existingDiagnostics?.id && - a.id !== existingLeanCanvas?.id, + a.id !== existingLeanCanvas?.id && + a.id !== existingDecoFlights?.id, ) .map((agent) => ( + + ); } diff --git a/apps/mesh/src/web/components/home/deco-flights-recruit-modal.tsx b/apps/mesh/src/web/components/home/deco-flights-recruit-modal.tsx new file mode 100644 index 0000000000..a3664622bb --- /dev/null +++ b/apps/mesh/src/web/components/home/deco-flights-recruit-modal.tsx @@ -0,0 +1,287 @@ +/** + * Deco Flights Recruitment Modal + * + * Shown when the user clicks the Flights template on the home page. + * Creates an HTTP connection to the local deco-flights MCP + virtual MCP, + * then navigates to the agent view. + */ + +import { useState } from "react"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@deco/ui/components/dialog.tsx"; +import { + Drawer, + DrawerContent, + DrawerHeader, + DrawerTitle, +} from "@deco/ui/components/drawer.tsx"; +import { Button } from "@deco/ui/components/button.tsx"; +import { useIsMobile } from "@deco/ui/hooks/use-mobile.ts"; +import { IntegrationIcon } from "@/web/components/integration-icon.tsx"; +import { + SELF_MCP_ALIAS_ID, + WELL_KNOWN_AGENT_TEMPLATES, + WellKnownOrgMCPId, + useConnectionActions, + useMCPClient, + useMCPToolCallMutation, + useProjectContext, + useVirtualMCPActions, +} from "@decocms/mesh-sdk"; +import type { CollectionListOutput } from "@decocms/bindings/collections"; +import type { ConnectionEntity } from "@decocms/mesh-sdk"; +import { useNavigateToAgent } from "@/web/hooks/use-navigate-to-agent"; + +const DECO_FLIGHTS_MCP_URL = "http://localhost:4747/mcp"; + +const AGENT_INSTRUCTIONS = `You are a flight research assistant powered by Deco Flights. Help users plan trips by searching for the best flights across flexible date ranges. + +## Workflow +1. Ask the user where they want to go, when, how long, and any preferences +2. Use TRIP_CREATE to save the trip with all gathered details +3. Review the search plan and confirm with the user +4. Use TRIP_EXECUTE to run all searches +5. Use TRIP_GET to display the ranked results + +## Tips +- Confirm destination IATA codes (e.g., LAX for Los Angeles, SFO for San Francisco) +- Ask about stops, layover limits, airline preferences, budget +- Suggest wider date ranges for more options +- Use TRIP_LIST to show all saved trips`; + +interface DecoFlightsRecruitModalProps { + open: boolean; + onOpenChange: (open: boolean) => void; + existingAgent?: { id: string } | null; +} + +const CAPABILITIES = [ + "Search flights across flexible date ranges via Google Flights", + "Save trip research plans with preferences and constraints", + "Automated multi-search execution across date combinations", + "Score and rank results by price, stops, layovers, and preferences", + "Interactive trip planner UI with sortable results table", + "Persistent local storage — come back to review results anytime", +]; + +function RecruitContent({ + onRecruit, + isRecruiting, +}: { + onRecruit: () => void; + isRecruiting: boolean; +}) { + return ( +
+

+ Add a Flights agent that helps you research and compare flights. + Describe your trip and it searches across date ranges, scores results, + and presents the best options. +

+ +
+

Capabilities

+
    + {CAPABILITIES.map((cap) => ( +
  • + + + {cap} +
  • + ))} +
+
+ +
+

+ Start the flights server with{" "} + + bun run --cwd packages/deco-flights dev + +

+
+ + +
+ ); +} + +export function DecoFlightsRecruitModal({ + open, + onOpenChange, + existingAgent, +}: DecoFlightsRecruitModalProps) { + const isMobile = useIsMobile(); + const { org } = useProjectContext(); + const navigateToAgent = useNavigateToAgent(); + const connectionActions = useConnectionActions(); + const virtualMcpActions = useVirtualMCPActions(); + const client = useMCPClient({ + connectionId: SELF_MCP_ALIAS_ID, + orgId: org.id, + }); + const connectionQuery = useMCPToolCallMutation({ client }); + const [isRecruiting, setIsRecruiting] = useState(false); + + const template = WELL_KNOWN_AGENT_TEMPLATES.find( + (t) => t.id === "deco-flights", + )!; + + const headerIcon = ( + + ); + + const handleRecruit = async () => { + if (existingAgent) { + onOpenChange(false); + navigateToAgent(existingAgent.id); + return; + } + + setIsRecruiting(true); + try { + // 1. Find or create the HTTP connection to the local flights MCP + const existingConnectionResult = await connectionQuery.mutateAsync({ + name: "COLLECTION_CONNECTIONS_LIST", + arguments: { + where: { + field: ["app_id"], + operator: "eq", + value: "deco-flights", + }, + limit: 1, + offset: 0, + }, + }); + + let connectionId: string; + const existingConnections = ( + existingConnectionResult as { + structuredContent?: CollectionListOutput; + } + )?.structuredContent?.items; + + const matchingConnection = existingConnections?.find( + (c) => c.app_id === "deco-flights", + ); + + const selfConnectionId = WellKnownOrgMCPId.SELF(org.id); + + if (matchingConnection) { + connectionId = matchingConnection.id; + } else { + const connection = await connectionActions.create.mutateAsync({ + title: template.title, + description: "Flight research assistant powered by Google Flights", + icon: template.icon, + connection_type: "HTTP", + connection_url: DECO_FLIGHTS_MCP_URL, + app_name: "deco-flights", + app_id: "deco-flights", + metadata: { + type: "deco-flights", + source: "local", + }, + }); + connectionId = connection.id; + } + + // 2. Create a virtual MCP (agent) with the connection + self MCP attached + const virtualMcp = await virtualMcpActions.create.mutateAsync({ + title: template.title, + description: "Flight research assistant powered by Google Flights", + icon: template.icon, + status: "active", + connections: [ + { + connection_id: connectionId, + selected_tools: null, + selected_resources: null, + selected_prompts: null, + }, + { + connection_id: selfConnectionId, + selected_tools: null, + selected_resources: null, + selected_prompts: null, + }, + ], + metadata: { + type: "deco-flights", + instructions: AGENT_INSTRUCTIONS, + ui: { + pinnedViews: [ + { + connectionId, + toolName: "TRIP_LIST", + label: "My Trips", + icon: null, + }, + ], + layout: { + defaultMainView: { + type: "ext-apps", + id: connectionId, + toolName: "TRIP_LIST", + }, + chatDefaultOpen: true, + }, + }, + }, + }); + + // 3. Navigate to the new agent + onOpenChange(false); + navigateToAgent(virtualMcp.id!); + } catch (error) { + console.error("Failed to create Flights agent:", error); + } finally { + setIsRecruiting(false); + } + }; + + const title = `Add ${template.title}`; + + return isMobile ? ( + + + +
+ {headerIcon} + {title} +
+
+
+ +
+
+
+ ) : ( + + + +
+ {headerIcon} + {title} +
+
+ +
+
+ ); +} diff --git a/apps/mesh/src/web/routes/agents-list.tsx b/apps/mesh/src/web/routes/agents-list.tsx index 9deb179ee4..b938558148 100644 --- a/apps/mesh/src/web/routes/agents-list.tsx +++ b/apps/mesh/src/web/routes/agents-list.tsx @@ -17,6 +17,7 @@ import { ImportFromDecoDialog } from "@/web/components/import-from-deco-dialog.t import { SiteDiagnosticsRecruitModal } from "@/web/components/home/site-diagnostics-recruit-modal.tsx"; import { StudioPackRecruitModal } from "@/web/components/home/studio-pack-recruit-modal.tsx"; import { LeanCanvasRecruitModal } from "@/web/components/home/lean-canvas-recruit-modal.tsx"; +import { DecoFlightsRecruitModal } from "@/web/components/home/deco-flights-recruit-modal.tsx"; import { AlertDialog, AlertDialogAction, @@ -56,6 +57,7 @@ export default function AgentsListPage() { const [diagnosticsModalOpen, setDiagnosticsModalOpen] = useState(false); const [studioPackModalOpen, setStudioPackModalOpen] = useState(false); const [leanCanvasModalOpen, setLeanCanvasModalOpen] = useState(false); + const [decoFlightsModalOpen, setDecoFlightsModalOpen] = useState(false); const lowerSearch = search.toLowerCase(); @@ -91,6 +93,12 @@ export default function AgentsListPage() { (a as { metadata?: { type?: string } }).metadata?.type === "lean-canvas", ); + // Find existing recruited Deco Flights agent + const existingDecoFlights = agents.find( + (a) => + (a as { metadata?: { type?: string } }).metadata?.type === "deco-flights", + ); + const handleTemplateClick = (templateId: string) => { if (templateId === "site-editor") { setImportDecoOpen(true); @@ -106,6 +114,12 @@ export default function AgentsListPage() { } else { setLeanCanvasModalOpen(true); } + } else if (templateId === "deco-flights") { + if (existingDecoFlights) { + navigateToAgent(existingDecoFlights.id); + } else { + setDecoFlightsModalOpen(true); + } } else if (templateId === "studio-pack") { setStudioPackModalOpen(true); } @@ -276,6 +290,11 @@ export default function AgentsListPage() { onOpenChange={setLeanCanvasModalOpen} existingAgent={existingLeanCanvas} /> + = 4.11" } }, "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw=="], @@ -2068,6 +2116,8 @@ "fast-equals": ["fast-equals@5.4.0", "", {}, "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw=="], + "fast-flights-ts": ["fast-flights-ts@4.1.0", "", { "optionalDependencies": { "node-libcurl": "^4.0.0" } }, "sha512-E5MkU5blEYaEtSQPFCOCQfyAoiWT6Ths/MXMY1TS9qkAkC1reZYFyNioFyudtA+sJD2aNeIEC8RMYwe5WAz66Q=="], + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], @@ -2102,6 +2152,8 @@ "fontkitten": ["fontkitten@1.0.3", "", { "dependencies": { "tiny-inflate": "^1.0.3" } }, "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw=="], + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + "format": ["format@0.2.2", "", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="], "formatly": ["formatly@0.3.0", "", { "dependencies": { "fd-package-json": "^2.0.0" }, "bin": { "formatly": "bin/index.mjs" } }, "sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w=="], @@ -2114,12 +2166,16 @@ "fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="], + "fs-minipass": ["fs-minipass@2.1.0", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg=="], + "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + "gauge": ["gauge@5.0.2", "", { "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.3", "console-control-strings": "^1.1.0", "has-unicode": "^2.0.1", "signal-exit": "^4.0.1", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "wide-align": "^1.1.5" } }, "sha512-pMaFftXPtiGIHCJHdcUUx9Rby/rFT/Kkt3fIIGCs+9PMDIljSyRiqraTlxNtBReJRDfUefpa263RQ3vnp5G/LQ=="], + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], @@ -2158,6 +2214,8 @@ "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + "has-unicode": ["has-unicode@2.0.1", "", {}, "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="], + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], "hast-util-from-html": ["hast-util-from-html@2.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.1.0", "hast-util-from-parse5": "^8.0.0", "parse5": "^7.0.0", "vfile": "^6.0.0", "vfile-message": "^4.0.0" } }, "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw=="], @@ -2206,6 +2264,8 @@ "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], @@ -2220,6 +2280,8 @@ "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + "indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="], "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], @@ -2266,6 +2328,8 @@ "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], + "is-lambda": ["is-lambda@1.0.1", "", {}, "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ=="], + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], @@ -2284,6 +2348,8 @@ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + "jake": ["jake@10.9.4", "", { "dependencies": { "async": "^3.2.6", "filelist": "^1.0.4", "picocolors": "^1.1.1" }, "bin": { "jake": "bin/cli.js" } }, "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA=="], "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], @@ -2386,6 +2452,10 @@ "magicast": ["magicast@0.5.2", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "source-map-js": "^1.2.1" } }, "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ=="], + "make-dir": ["make-dir@3.1.0", "", { "dependencies": { "semver": "^6.0.0" } }, "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw=="], + + "make-fetch-happen": ["make-fetch-happen@13.0.1", "", { "dependencies": { "@npmcli/agent": "^2.0.0", "cacache": "^18.0.0", "http-cache-semantics": "^4.1.1", "is-lambda": "^1.0.1", "minipass": "^7.0.2", "minipass-fetch": "^3.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.3", "proc-log": "^4.2.0", "promise-retry": "^2.0.1", "ssri": "^10.0.0" } }, "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA=="], + "markdown-extensions": ["markdown-extensions@2.0.0", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="], "markdown-it": ["markdown-it@14.1.1", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA=="], @@ -2530,8 +2600,20 @@ "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + "minipass-collect": ["minipass-collect@2.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw=="], + + "minipass-fetch": ["minipass-fetch@3.0.5", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", "minizlib": "^2.1.2" }, "optionalDependencies": { "encoding": "^0.1.13" } }, "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg=="], + + "minipass-flush": ["minipass-flush@1.0.7", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA=="], + + "minipass-pipeline": ["minipass-pipeline@1.2.4", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A=="], + + "minipass-sized": ["minipass-sized@1.0.3", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g=="], + "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], + "mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], + "mlly": ["mlly@1.8.1", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ=="], "module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="], @@ -2546,6 +2628,8 @@ "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], + "nan": ["nan@github:JCMais/nan#0ec2eca", {}, "JCMais-nan-0ec2eca", "sha512-AuR6UeuiQhmNK2Zx2nggKGrWAohYBhqMqQd8lZeOTfh2ZatkmYITHTFQlblSLaUJ+c5jPvj4iq+XzRrCczGAGQ=="], + "nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="], "nanostores": ["nanostores@1.1.1", "", {}, "sha512-EYJqS25r2iBeTtGQCHidXl1VfZ1jXM7Q04zXJOrMlxVVmD0ptxJaNux92n1mJ7c5lN3zTq12MhH/8x59nP+qmg=="], @@ -2568,8 +2652,12 @@ "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], + "node-gyp": ["node-gyp@10.2.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "glob": "^10.3.10", "graceful-fs": "^4.2.6", "make-fetch-happen": "^13.0.0", "nopt": "^7.0.0", "proc-log": "^4.1.0", "semver": "^7.3.5", "tar": "^6.2.1", "which": "^4.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-sp3FonBAaFe4aYTcFdZUn2NYkbP7xroPGYvQmP4Nl5PxamznItBnNCgjrVTKrEfQynInMsJvZrdmqUnysCJ8rw=="], + "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="], + "node-libcurl": ["node-libcurl@4.1.0", "", { "dependencies": { "@mapbox/node-pre-gyp": "1.0.11", "env-paths": "2.2.0", "nan": "github:JCMais/nan#fix/electron-failures", "node-gyp": "10.2.0", "npmlog": "7.0.1", "rimraf": "5.0.5", "tslib": "2.6.2" } }, "sha512-cwJ4pEqFmzUivMl0CtS2yYjBmZJ3/63Fl1WJCGzw45jXTCL04Ygbqvl+I5blMZm4ZOQChbATmQ6H4lxAnlIU+g=="], + "node-mock-http": ["node-mock-http@1.0.4", "", {}, "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ=="], "node-releases": ["node-releases@2.0.36", "", {}, "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA=="], @@ -2580,6 +2668,8 @@ "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + "npmlog": ["npmlog@7.0.1", "", { "dependencies": { "are-we-there-yet": "^4.0.0", "console-control-strings": "^1.1.0", "gauge": "^5.0.0", "set-blocking": "^2.0.0" } }, "sha512-uJ0YFk/mCQpLBt+bxN88AKd+gyqZvZDbtiNxk6Waqcj2aPRyfVx8ITawkyQynxUagInjdYT1+qj4NfA5KJJUxg=="], + "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], @@ -2608,10 +2698,14 @@ "p-limit": ["p-limit@6.2.0", "", { "dependencies": { "yocto-queue": "^1.1.1" } }, "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA=="], + "p-map": ["p-map@4.0.0", "", { "dependencies": { "aggregate-error": "^3.0.0" } }, "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ=="], + "p-queue": ["p-queue@8.1.1", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^6.1.2" } }, "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ=="], "p-timeout": ["p-timeout@6.1.4", "", {}, "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg=="], + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + "package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], "parse-entities": ["parse-entities@2.0.0", "", { "dependencies": { "character-entities": "^1.0.0", "character-entities-legacy": "^1.0.0", "character-reference-invalid": "^1.0.0", "is-alphanumerical": "^1.0.0", "is-decimal": "^1.0.0", "is-hexadecimal": "^1.0.0" } }, "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ=="], @@ -2694,6 +2788,10 @@ "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], + "proc-log": ["proc-log@4.2.0", "", {}, "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA=="], + + "promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="], + "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], @@ -2800,6 +2898,8 @@ "react-transition-group": ["react-transition-group@4.4.5", "", { "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", "prop-types": "^15.6.2" }, "peerDependencies": { "react": ">=16.6.0", "react-dom": ">=16.6.0" } }, "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g=="], + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], "recharts": ["recharts@2.15.1", "", { "dependencies": { "clsx": "^2.0.0", "eventemitter3": "^4.0.1", "lodash": "^4.17.21", "react-is": "^18.3.1", "react-smooth": "^4.0.4", "recharts-scale": "^0.4.4", "tiny-invariant": "^1.3.1", "victory-vendor": "^36.6.8" }, "peerDependencies": { "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-v8PUTUlyiDe56qUj82w/EDVuzEFXwEHp9/xOowGAZwfLjB9uAy3GllQVIYMWF6nU+qibx85WF75zD7AjqoT54Q=="], @@ -2876,8 +2976,12 @@ "retext-stringify": ["retext-stringify@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "nlcst-to-string": "^4.0.0", "unified": "^11.0.0" } }, "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA=="], + "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + "rimraf": ["rimraf@5.0.5", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A=="], + "rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="], "rope-sequence": ["rope-sequence@1.3.4", "", {}, "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ=="], @@ -2890,6 +2994,8 @@ "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="], + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], @@ -2952,6 +3058,8 @@ "serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="], + "set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="], + "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], @@ -2986,8 +3094,14 @@ "slice-ansi": ["slice-ansi@8.0.0", "", { "dependencies": { "ansi-styles": "^6.2.3", "is-fullwidth-code-point": "^5.1.0" } }, "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg=="], + "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], + "smol-toml": ["smol-toml@1.6.0", "", {}, "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw=="], + "socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="], + + "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], + "sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="], "sorted-btree": ["sorted-btree@1.8.1", "", {}, "sha512-395+XIP+wqNn3USkFSrNz7G3Ss/MXlZEqesxvzCRFwL14h6e8LukDHdLBePn5pwbm5OQ9vGu8mDyz2lLDIqamQ=="], @@ -3000,6 +3114,8 @@ "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + "ssri": ["ssri@10.0.6", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ=="], + "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="], "state-local": ["state-local@1.0.7", "", {}, "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w=="], @@ -3008,10 +3124,16 @@ "string-width": ["string-width@8.2.0", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw=="], + "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "strip-json-comments": ["strip-json-comments@5.0.3", "", {}, "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw=="], "strnum": ["strnum@2.2.0", "", {}, "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg=="], @@ -3132,6 +3254,10 @@ "unifont": ["unifont@0.7.4", "", { "dependencies": { "css-tree": "^3.1.0", "ofetch": "^1.5.1", "ohash": "^2.0.11" } }, "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg=="], + "unique-filename": ["unique-filename@3.0.0", "", { "dependencies": { "unique-slug": "^4.0.0" } }, "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g=="], + + "unique-slug": ["unique-slug@4.0.0", "", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ=="], + "unist-util-find-after": ["unist-util-find-after@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ=="], "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], @@ -3208,6 +3334,8 @@ "which-pm-runs": ["which-pm-runs@1.1.0", "", {}, "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA=="], + "wide-align": ["wide-align@1.1.5", "", { "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } }, "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg=="], + "widest-line": ["widest-line@6.0.0", "", { "dependencies": { "string-width": "^8.1.0" } }, "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA=="], "wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="], @@ -3220,6 +3348,8 @@ "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], "ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], @@ -3300,6 +3430,8 @@ "@daveyplate/better-auth-ui/@better-fetch/fetch": ["@better-fetch/fetch@1.1.21", "", {}, "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A=="], + "@decocms/deco-flights/@types/bun": ["@types/bun@1.3.12", "", { "dependencies": { "bun-types": "1.3.12" } }, "sha512-DBv81elK+/VSwXHDlnH3Qduw+KxkTIWi7TXkAeh24zpi5l0B2kUg9Ga3tb4nJaPcOFswflgi/yAvMVBPrxMB+A=="], + "@decocms/runtime/@types/bun": ["@types/bun@1.3.12", "", { "dependencies": { "bun-types": "1.3.12" } }, "sha512-DBv81elK+/VSwXHDlnH3Qduw+KxkTIWi7TXkAeh24zpi5l0B2kUg9Ga3tb4nJaPcOFswflgi/yAvMVBPrxMB+A=="], "@grpc/proto-loader/protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], @@ -3310,6 +3442,12 @@ "@instantdb/react/eventsource": ["eventsource@4.1.0", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-2GuF51iuHX6A9xdTccMTsNb7VO0lHZihApxhvQzJB5A03DvHDd2FQepodbMaztPBmBcE/ox7o2gqaxGhYB9LhQ=="], + "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "@npmcli/agent/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "@oclif/core/ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="], "@oclif/core/cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], @@ -3560,6 +3698,10 @@ "@vercel/nft/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + "aggregate-error/clean-stack": ["clean-stack@2.2.0", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="], + + "aggregate-error/indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], + "ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], @@ -3582,6 +3724,14 @@ "boxen/widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="], + "cacache/fs-minipass": ["fs-minipass@3.0.3", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw=="], + + "cacache/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + + "cacache/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "cacache/tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="], + "chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], @@ -3612,12 +3762,24 @@ "dotenv-expand/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], + "encoding/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], "filelist/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="], + "foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "freestyle-sandboxes/yargs": ["yargs@18.0.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^7.2.0", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg=="], + "fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "gauge/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "gauge/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "gauge/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "git-diff/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="], "git-diff/diff": ["diff@3.5.1", "", {}, "sha512-Z3u54A8qGyqFOSr2pk0ijYs8mOE9Qz8kTvtKeBI+upoG9j04Sq+oI7W8zAJiQybDcESET8/uIdHzs0p3k4fZlw=="], @@ -3648,6 +3810,10 @@ "kysely-pglite/jiti": ["jiti@2.0.0-beta.3", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-pmfRbVRs/7khFrSAYnSiJ8C0D5GvzkE4Ey2pAvUcJsw1ly/p+7ut27jbJrjY79BpAJQJ4gXYFtK6d1Aub+9baQ=="], + "make-dir/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "make-fetch-happen/negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="], + "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], "mdast-util-mdx-jsx/parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], @@ -3656,8 +3822,28 @@ "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "minipass-fetch/minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="], + + "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-sized/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + "monaco-editor/marked": ["marked@14.0.0", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ=="], + "node-gyp/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + + "node-gyp/nopt": ["nopt@7.2.1", "", { "dependencies": { "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w=="], + + "node-gyp/tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="], + + "node-gyp/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], + + "node-libcurl/@mapbox/node-pre-gyp": ["@mapbox/node-pre-gyp@1.0.11", "", { "dependencies": { "detect-libc": "^2.0.0", "https-proxy-agent": "^5.0.0", "make-dir": "^3.1.0", "node-fetch": "^2.6.7", "nopt": "^5.0.0", "npmlog": "^5.0.1", "rimraf": "^3.0.2", "semver": "^7.3.5", "tar": "^6.1.11" }, "bin": { "node-pre-gyp": "bin/node-pre-gyp" } }, "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ=="], + + "node-libcurl/tslib": ["tslib@2.6.2", "", {}, "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="], + "p-queue/eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], "parse-entities/character-entities-legacy": ["character-entities-legacy@1.1.4", "", {}, "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA=="], @@ -3672,6 +3858,8 @@ "refractor/prismjs": ["prismjs@1.27.0", "", {}, "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA=="], + "rimraf/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + "router/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], "samlify/camelcase": ["camelcase@9.0.0", "", {}, "sha512-TO9xmyXTZ9HUHI8M1OnvExxYB0eYVS/1e5s7IDMTAoIcwUd+aNcFODs6Xk83mobk0velyHFQgA1yIrvYc6wclw=="], @@ -3682,6 +3870,14 @@ "stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], + "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "string-width-cjs/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], "svgo/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], @@ -3692,8 +3888,16 @@ "unstorage/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], + "wide-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "xml-crypto/xpath": ["xpath@0.0.33", "", {}, "sha512-NNXnzrkDrAzalLhIUc01jO2mOzXGXh1JwPgkihcLLzw98c0WgYDmmjSh1Kl3wzaxSVWMuA+fe0WTWOBDWCBmNA=="], "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -3714,8 +3918,12 @@ "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + "@decocms/deco-flights/@types/bun/bun-types": ["bun-types@1.3.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="], + "@decocms/runtime/@types/bun/bun-types": ["bun-types@1.3.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="], + "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + "@oclif/core/ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], "@oclif/core/is-wsl/is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="], @@ -3828,6 +4036,20 @@ "astro/vite/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + "cacache/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], + + "cacache/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + + "cacache/tar/chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="], + + "cacache/tar/fs-minipass": ["fs-minipass@2.1.0", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg=="], + + "cacache/tar/minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="], + + "cacache/tar/minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="], + + "cacache/tar/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "cliui/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], @@ -3852,6 +4074,14 @@ "freestyle-sandboxes/yargs/yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="], + "fs-minipass/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "gauge/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "gauge/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "gauge/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "git-diff/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="], "git-diff/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], @@ -3876,10 +4106,64 @@ "mesh-plugin-workflows/@types/bun/bun-types": ["bun-types@1.3.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="], + "minipass-fetch/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-fetch/minizlib/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "minipass-flush/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "minipass-pipeline/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "minipass-sized/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "node-gyp/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], + + "node-gyp/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + + "node-gyp/nopt/abbrev": ["abbrev@2.0.0", "", {}, "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ=="], + + "node-gyp/tar/chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="], + + "node-gyp/tar/minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="], + + "node-gyp/tar/minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="], + + "node-gyp/tar/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "node-gyp/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], + + "node-libcurl/@mapbox/node-pre-gyp/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], + + "node-libcurl/@mapbox/node-pre-gyp/nopt": ["nopt@5.0.0", "", { "dependencies": { "abbrev": "1" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ=="], + + "node-libcurl/@mapbox/node-pre-gyp/npmlog": ["npmlog@5.0.1", "", { "dependencies": { "are-we-there-yet": "^2.0.0", "console-control-strings": "^1.1.0", "gauge": "^3.0.0", "set-blocking": "^2.0.0" } }, "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw=="], + + "node-libcurl/@mapbox/node-pre-gyp/rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], + + "node-libcurl/@mapbox/node-pre-gyp/tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="], + + "rimraf/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], + + "rimraf/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + "shelljs/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "unstorage/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], + "wide-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "wide-align/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "wide-align/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "wrap-ansi-cjs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "yargs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], @@ -4004,6 +4288,12 @@ "astro/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + "cacache/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "cacache/tar/fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "cacache/tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + "filelist/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "git-diff/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="], @@ -4014,12 +4304,66 @@ "mdast-util-mdx-jsx/parse-entities/is-alphanumerical/is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], + "node-gyp/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "node-gyp/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "node-gyp/tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "node-libcurl/@mapbox/node-pre-gyp/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + + "node-libcurl/@mapbox/node-pre-gyp/nopt/abbrev": ["abbrev@1.1.1", "", {}, "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="], + + "node-libcurl/@mapbox/node-pre-gyp/npmlog/are-we-there-yet": ["are-we-there-yet@2.0.0", "", { "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" } }, "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw=="], + + "node-libcurl/@mapbox/node-pre-gyp/npmlog/gauge": ["gauge@3.0.2", "", { "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.2", "console-control-strings": "^1.0.0", "has-unicode": "^2.0.1", "object-assign": "^4.1.1", "signal-exit": "^3.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "wide-align": "^1.1.2" } }, "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q=="], + + "node-libcurl/@mapbox/node-pre-gyp/rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + + "node-libcurl/@mapbox/node-pre-gyp/tar/chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="], + + "node-libcurl/@mapbox/node-pre-gyp/tar/minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="], + + "node-libcurl/@mapbox/node-pre-gyp/tar/minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="], + + "node-libcurl/@mapbox/node-pre-gyp/tar/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "rimraf/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "rimraf/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "shelljs/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + "wide-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "cacache/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "git-diff/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], + "node-gyp/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "node-libcurl/@mapbox/node-pre-gyp/npmlog/gauge/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "node-libcurl/@mapbox/node-pre-gyp/npmlog/gauge/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "node-libcurl/@mapbox/node-pre-gyp/rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "node-libcurl/@mapbox/node-pre-gyp/tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "shelljs/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "node-libcurl/@mapbox/node-pre-gyp/npmlog/gauge/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "node-libcurl/@mapbox/node-pre-gyp/npmlog/gauge/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "node-libcurl/@mapbox/node-pre-gyp/npmlog/gauge/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "node-libcurl/@mapbox/node-pre-gyp/rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + + "node-libcurl/@mapbox/node-pre-gyp/rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], } } diff --git a/packages/deco-flights/package.json b/packages/deco-flights/package.json new file mode 100644 index 0000000000..f64bc51c6a --- /dev/null +++ b/packages/deco-flights/package.json @@ -0,0 +1,27 @@ +{ + "name": "@decocms/deco-flights", + "version": "0.1.0", + "type": "module", + "description": "Flight research MCP agent for Deco Studio", + "scripts": { + "dev": "bun run --hot server/index.ts", + "start": "bun run server/index.ts", + "check": "tsc --noEmit" + }, + "dependencies": { + "@decocms/runtime": "workspace:*", + "@modelcontextprotocol/sdk": "1.27.1", + "fast-flights-ts": "4.1.0", + "zod": "^4.0.0" + }, + "devDependencies": { + "@types/bun": "latest", + "typescript": "^5.9.3" + }, + "exports": { + ".": "./server/index.ts" + }, + "engines": { + "node": ">=24.0.0" + } +} diff --git a/packages/deco-flights/server/index.ts b/packages/deco-flights/server/index.ts new file mode 100644 index 0000000000..965bc30ae7 --- /dev/null +++ b/packages/deco-flights/server/index.ts @@ -0,0 +1,208 @@ +import { withRuntime } from "@decocms/runtime"; +import { createPublicPrompt, createPublicResource } from "@decocms/runtime"; +import { FLIGHT_SEARCH } from "./tools/flight-search.ts"; +import { TRIP_CREATE } from "./tools/trip-create.ts"; +import { TRIP_LIST } from "./tools/trip-list.ts"; +import { TRIP_GET } from "./tools/trip-get.ts"; +import { TRIP_UPDATE } from "./tools/trip-update.ts"; +import { TRIP_DELETE } from "./tools/trip-delete.ts"; +import { TRIP_EXECUTE } from "./tools/trip-execute.ts"; +import { TRIP_STOP } from "./tools/trip-stop.ts"; +import { TRIP_ADD_SEARCHES } from "./tools/trip-add-searches.ts"; +import { AGENT_INSTRUCTIONS } from "./lib/agent-instructions.ts"; +import { renderSearchResults } from "./ui/search-results.ts"; +import { renderTripCard } from "./ui/trip-card.ts"; +import { renderTripsDashboard } from "./ui/trips-dashboard.ts"; +import { renderTripPlanner } from "./ui/trip-planner.ts"; +import { loadTrip, listFullTrips } from "./lib/storage.ts"; +import { isWorkerRunning } from "./lib/worker.ts"; + +const RESOURCE_MIME = "text/html;profile=mcp-app"; +const port = Number(process.env.PORT) || 4747; +const API_ORIGIN = `http://localhost:${port}`; + +// CSP override to allow the UI iframes to fetch from our REST API +const resourceCsp = { + connectDomains: [API_ORIGIN], +}; + +const flightAssistantPrompt = createPublicPrompt({ + name: "flight-assistant", + title: "Flight Assistant", + description: + "Instructions for the flight research workflow — how to help users plan trips, create searches, and present results.", + execute: () => ({ + messages: [ + { + role: "user" as const, + content: { + type: "text" as const, + text: AGENT_INSTRUCTIONS, + }, + }, + ], + }), +}); + +const searchResultsResource = createPublicResource({ + uri: "ui://deco-flights/search-results", + name: "Flight Search Results", + description: "Inline UI showing flight search results", + mimeType: RESOURCE_MIME, + read: () => ({ + uri: "ui://deco-flights/search-results", + mimeType: RESOURCE_MIME, + text: renderSearchResults(), + _meta: { ui: { csp: resourceCsp } }, + }), +}); + +const tripCardResource = createPublicResource({ + uri: "ui://deco-flights/trip-card", + name: "Trip Card", + description: "Inline UI showing a trip summary card", + mimeType: RESOURCE_MIME, + read: () => ({ + uri: "ui://deco-flights/trip-card", + mimeType: RESOURCE_MIME, + text: renderTripCard(), + _meta: { ui: { csp: resourceCsp } }, + }), +}); + +const tripsDashboardResource = createPublicResource({ + uri: "ui://deco-flights/trips-dashboard", + name: "Trips Dashboard", + description: "Fullscreen UI showing all saved trips with drill-down", + mimeType: RESOURCE_MIME, + read: () => ({ + uri: "ui://deco-flights/trips-dashboard", + mimeType: RESOURCE_MIME, + text: renderTripsDashboard(), + _meta: { ui: { csp: resourceCsp } }, + }), +}); + +const tripPlannerResource = createPublicResource({ + uri: "ui://deco-flights/trip-planner", + name: "Trip Planner", + description: + "Fullscreen UI showing trip details, preferences, and ranked results", + mimeType: RESOURCE_MIME, + read: () => ({ + uri: "ui://deco-flights/trip-planner", + mimeType: RESOURCE_MIME, + text: renderTripPlanner(), + _meta: { ui: { csp: resourceCsp } }, + }), +}); + +const mcpServer = withRuntime({ + serverInfo: { + name: "deco-flights", + version: "0.1.0", + instructions: AGENT_INSTRUCTIONS, + }, + tools: [ + FLIGHT_SEARCH, + TRIP_CREATE, + TRIP_LIST, + TRIP_GET, + TRIP_UPDATE, + TRIP_DELETE, + TRIP_EXECUTE, + TRIP_STOP, + TRIP_ADD_SEARCHES, + ], + prompts: [flightAssistantPrompt], + resources: [ + searchResultsResource, + tripCardResource, + tripsDashboardResource, + tripPlannerResource, + ], + cors: { + origin: "*", + }, +}); + +// REST API handler for direct fetch from UI iframes +function handleApi(req: Request): Response | null { + const url = new URL(req.url); + + // CORS preflight + if (req.method === "OPTIONS") { + return new Response(null, { + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type", + }, + }); + } + + const corsHeaders = { + "Access-Control-Allow-Origin": "*", + "Content-Type": "application/json", + }; + + // GET /api/trips — list all trips (trimmed) + if (url.pathname === "/api/trips" && req.method === "GET") { + return (async () => { + const trips = await listFullTrips(); + const trimmed = trips.map((t) => ({ + ...t, + results: (t.results ?? []).slice(0, 20), + _totalResults: t.results?.length ?? 0, + workerRunning: isWorkerRunning(t.id), + })); + return new Response(JSON.stringify({ trips: trimmed }), { + headers: corsHeaders, + }); + })() as unknown as Response; + } + + // GET /api/trips/:id — get single trip (trimmed) + const tripMatch = url.pathname.match(/^\/api\/trips\/([^/]+)$/); + if (tripMatch && req.method === "GET") { + return (async () => { + const trip = await loadTrip(tripMatch[1]); + if (!trip) { + return new Response(JSON.stringify({ error: "Not found" }), { + status: 404, + headers: corsHeaders, + }); + } + const trimmed = { + ...trip, + results: (trip.results ?? []).slice(0, 20), + _totalResults: trip.results?.length ?? 0, + }; + return new Response( + JSON.stringify({ + trip: trimmed, + workerRunning: isWorkerRunning(trip.id), + }), + { headers: corsHeaders }, + ); + })() as unknown as Response; + } + + return null; +} + +export default { + fetch: async (req: Request, env?: unknown, ctx?: unknown) => { + // Try REST API first + const apiResponse = handleApi(req); + if (apiResponse) return apiResponse; + // Fall through to MCP handler + return (mcpServer.fetch as Function)(req, env, ctx); + }, + port, +}; + +console.log( + `[deco-flights] MCP server running on http://localhost:${port}/mcp`, +); +console.log(`[deco-flights] REST API at http://localhost:${port}/api/trips`); diff --git a/packages/deco-flights/server/lib/agent-instructions.ts b/packages/deco-flights/server/lib/agent-instructions.ts new file mode 100644 index 0000000000..02dbcd933d --- /dev/null +++ b/packages/deco-flights/server/lib/agent-instructions.ts @@ -0,0 +1,54 @@ +export const AGENT_INSTRUCTIONS = `You are a flight research assistant powered by Deco Flights. You help users plan trips by searching for the best flights. + +## CRITICAL RULES + +1. **MAX 10 SEARCHES PER TRIP.** The system hard-caps at 10. Keep date ranges tight. +2. **ALWAYS set currency in preferences.** Default to USD. Ask the user if unclear. + Example: \`preferences: { currency: "USD", maxStops: 1 }\` +3. **Narrow date ranges.** Don't pass a 2-week departure window. Pick 3-4 specific dates. + +## Search Strategy — Tiered, Fast + +### Tier 1 — Quick Scan (5-8 searches) +- Pick 3-4 departure dates spread across the user's desired range +- Use ONE return length (midpoint of their min/max) +- This gives fast results in under 30 seconds + +### Tier 2 — Drill Down (use TRIP_ADD_SEARCHES on the SAME trip) +- Look at Tier 1: which dates were cheapest? +- Use TRIP_ADD_SEARCHES to add +/- 1-2 day variations around the best finds +- Try different return lengths or routes +- All results accumulate in one place — no need for separate trips + +### Tier 3 — Refine +- User says "now try without COPA" → update preferences with avoidAirlines, add new searches +- User says "what about flying back from LAX?" → add open-jaw searches with returnFrom +- Use FLIGHT_SEARCH for quick one-off comparisons + +### Example +User: "Flights GIG to SFO, May 21 to June 7, 7-9 days, business" + +GOOD: Create trip with departures [May 22, May 26, May 30, Jun 3], return length 8 days → 4 searches +BAD: Create trip with earliestDeparture May 21, latestDeparture Jun 7 → 50+ combinations + +Then Tier 2: "Best price on May 26. Let me add May 25 and May 27." +→ TRIP_ADD_SEARCHES with 2 new searches on the same trip. Results merge in. + +## Workflow + +1. **Understand the trip**: Ask where, when, how long, preferences, currency +2. **Create a focused trip**: Tight dates, 5-8 searches max, always set currency +3. **Execute**: Use TRIP_EXECUTE — results show live in the dashboard +4. **Present results**: Share the best finds +5. **Drill down if asked**: Create a second focused trip around the sweet spot + +## Tips + +- Confirm IATA codes (LAX, SFO, JFK, GIG, etc.) +- For open-jaw (fly into LAX, return from SFO): use returnOrigins field +- Use preferredAirlines with IATA codes (DL, AA, UA, LA) to filter at Google level +- avoidAirlines filters results after fetch +- Use TRIP_LIST to show all saved trips +- Use TRIP_STOP to cancel running research +- Each search task has a "GF↗" link to open that exact search on Google Flights +`; diff --git a/packages/deco-flights/server/lib/planner.ts b/packages/deco-flights/server/lib/planner.ts new file mode 100644 index 0000000000..bd5e945d28 --- /dev/null +++ b/packages/deco-flights/server/lib/planner.ts @@ -0,0 +1,97 @@ +import type { Trip, SearchPlan, SearchSpec } from "./types.ts"; + +const MAX_SEARCHES = 10; + +function addDays(dateStr: string, days: number): string { + const d = new Date(dateStr + "T00:00:00Z"); + d.setUTCDate(d.getUTCDate() + days); + return d.toISOString().slice(0, 10); +} + +function dateRange(start: string, end: string): string[] { + const dates: string[] = []; + let current = start; + while (current <= end) { + dates.push(current); + current = addDays(current, 1); + } + return dates; +} + +export function generateSearchPlan(trip: Trip): SearchPlan { + const searches: SearchSpec[] = []; + const departureDates = dateRange( + trip.earliestDeparture, + trip.latestDeparture, + ); + + const hasOpenJaw = trip.returnOrigins && trip.returnOrigins.length > 0; + + interface RoutePair { + to: string; + returnFrom?: string; + } + + const routePairs: RoutePair[] = []; + + if (hasOpenJaw) { + for (const dest of trip.destinations) { + for (const retOrigin of trip.returnOrigins!) { + if (dest !== retOrigin) + routePairs.push({ to: dest, returnFrom: retOrigin }); + } + } + for (const dest of trip.destinations) { + routePairs.push({ to: dest }); + } + } else { + for (const dest of trip.destinations) { + routePairs.push({ to: dest }); + } + } + + for (const departDate of departureDates) { + const minReturn = addDays(departDate, trip.tripLengthDays.min); + const maxReturn = addDays(departDate, trip.tripLengthDays.max); + const effectiveLatestReturn = + maxReturn < trip.latestReturn ? maxReturn : trip.latestReturn; + + if (minReturn > effectiveLatestReturn) continue; + + const returnDates = dateRange(minReturn, effectiveLatestReturn); + + for (const returnDate of returnDates) { + for (const route of routePairs) { + searches.push({ + from: trip.origin, + to: route.to, + departDate, + returnDate, + returnFrom: route.returnFrom, + }); + } + } + } + + const totalCombinations = searches.length; + const capped = totalCombinations > MAX_SEARCHES; + + if (capped) { + const step = Math.ceil(totalCombinations / MAX_SEARCHES); + const sampled: SearchSpec[] = []; + for ( + let i = 0; + i < totalCombinations && sampled.length < MAX_SEARCHES; + i += step + ) { + sampled.push(searches[i]); + } + return { searches: sampled, totalCombinations, capped }; + } + + return { searches, totalCombinations, capped }; +} + +export function specKey(s: SearchSpec): string { + return `${s.from}-${s.to}-${s.departDate}-${s.returnDate}-${s.returnFrom || ""}`; +} diff --git a/packages/deco-flights/server/lib/scorer.ts b/packages/deco-flights/server/lib/scorer.ts new file mode 100644 index 0000000000..c8acea8cf7 --- /dev/null +++ b/packages/deco-flights/server/lib/scorer.ts @@ -0,0 +1,134 @@ +import type { + FlightResult, + ScoredFlightResult, + TripPreferences, +} from "./types.ts"; + +const WEIGHTS = { + price: 0.4, + stops: 0.25, + layover: 0.15, + preferredAirports: 0.1, + totalTime: 0.1, +}; + +function maxLayoverMinutes(result: FlightResult): number { + let maxGap = 0; + for (let i = 1; i < result.flights.length; i++) { + const prevArrival = new Date(result.flights[i - 1].arrival.time).getTime(); + const nextDepart = new Date(result.flights[i].departure.time).getTime(); + const gap = (nextDepart - prevArrival) / (1000 * 60); + if (gap > maxGap) maxGap = gap; + } + return maxGap; +} + +function normalize(value: number, min: number, max: number): number { + if (max === min) return 0.5; + return 1 - (value - min) / (max - min); +} + +export function scoreResults( + results: FlightResult[], + prefs: TripPreferences, +): ScoredFlightResult[] { + // Apply hard filters + let filtered = results; + + if (prefs.maxStops !== undefined) { + filtered = filtered.filter((r) => r.stops <= prefs.maxStops!); + } + + if (prefs.maxLayoverHours !== undefined) { + const maxMinutes = prefs.maxLayoverHours * 60; + filtered = filtered.filter((r) => maxLayoverMinutes(r) <= maxMinutes); + } + + if (prefs.maxPrice !== undefined) { + filtered = filtered.filter((r) => r.price <= prefs.maxPrice!); + } + + if (prefs.avoidAirlines?.length) { + const avoid = new Set(prefs.avoidAirlines.map((a) => a.toLowerCase())); + filtered = filtered.filter( + (r) => !r.flights.some((f) => avoid.has(f.airline.toLowerCase())), + ); + } + + if (filtered.length === 0) { + // Relax numeric filters but always respect avoidAirlines + let fallback = results; + if (prefs.avoidAirlines?.length) { + const avoid = new Set(prefs.avoidAirlines.map((a) => a.toLowerCase())); + fallback = fallback.filter( + (r) => !r.flights.some((f) => avoid.has(f.airline.toLowerCase())), + ); + } + return fallback + .slice(0, 20) + .map((r, i) => ({ ...r, score: 0, rank: i + 1 })); + } + + // Compute ranges for normalization + const prices = filtered.map((r) => r.price); + const stops = filtered.map((r) => r.stops); + const layovers = filtered.map((r) => maxLayoverMinutes(r)); + const durations = filtered.map((r) => r.totalDurationMinutes); + + const minPrice = Math.min(...prices); + const maxPrice = Math.max(...prices); + const minStops = Math.min(...stops); + const maxStops = Math.max(...stops); + const minLayover = Math.min(...layovers); + const maxLayover = Math.max(...layovers); + const minDuration = Math.min(...durations); + const maxDuration = Math.max(...durations); + + const preferredSet = new Set( + prefs.preferredAirports?.map((a) => a.toUpperCase()) ?? [], + ); + const preferredAirlineSet = new Set( + prefs.preferredAirlines?.map((a) => a.toLowerCase()) ?? [], + ); + + const scored: ScoredFlightResult[] = filtered.map((r) => { + let score = 0; + + // Price score (lower is better) + score += WEIGHTS.price * normalize(r.price, minPrice, maxPrice); + + // Stops score (fewer is better) + score += WEIGHTS.stops * normalize(r.stops, minStops, maxStops); + + // Layover score (shorter is better) + score += + WEIGHTS.layover * normalize(maxLayoverMinutes(r), minLayover, maxLayover); + + // Preferred airports bonus + const hasPreferred = r.flights.some( + (f) => + preferredSet.has(f.departure.airport.toUpperCase()) || + preferredSet.has(f.arrival.airport.toUpperCase()), + ); + const hasPreferredAirline = r.flights.some((f) => + preferredAirlineSet.has(f.airline.toLowerCase()), + ); + score += + WEIGHTS.preferredAirports * + ((hasPreferred ? 0.5 : 0) + (hasPreferredAirline ? 0.5 : 0)); + + // Total travel time (shorter is better) + score += + WEIGHTS.totalTime * + normalize(r.totalDurationMinutes, minDuration, maxDuration); + + return { ...r, score: Math.round(score * 1000) / 1000, rank: 0 }; + }); + + scored.sort((a, b) => b.score - a.score); + scored.forEach((r, i) => { + r.rank = i + 1; + }); + + return scored; +} diff --git a/packages/deco-flights/server/lib/scraper.ts b/packages/deco-flights/server/lib/scraper.ts new file mode 100644 index 0000000000..4d23899cd5 --- /dev/null +++ b/packages/deco-flights/server/lib/scraper.ts @@ -0,0 +1,140 @@ +import { + createQuery, + getFlights, + Passengers, + CaptchaError, + HttpError, + TimeoutError, + ParseError, + type FlightQueryInput, + type SeatType, +} from "fast-flights-ts"; +import type { FlightResult, SearchSpec } from "./types.ts"; + +export interface SearchOptions { + from: string; + to: string; + date: string; + returnDate?: string; + returnFrom?: string; + passengers?: number; + seatClass?: string; + maxStops?: number; + airlines?: string[]; + currency?: string; +} + +export interface SearchResponse { + results: FlightResult[]; + googleFlightsUrl: string; + error?: string; +} + +function formatDt(dt: { + date: readonly [number, number, number]; + time: readonly [number, number]; +}): string { + const [y, m, d] = dt.date; + const [h, min] = dt.time; + return `${y}-${String(m).padStart(2, "0")}-${String(d).padStart(2, "0")}T${String(h).padStart(2, "0")}:${String(min).padStart(2, "0")}`; +} + +export async function searchFlights( + opts: SearchOptions, + searchSpec: SearchSpec, + signal?: AbortSignal, +): Promise { + try { + const flights: FlightQueryInput[] = [ + { + date: opts.date, + from_airport: opts.from, + to_airport: opts.to, + max_stops: opts.maxStops, + airlines: opts.airlines, + }, + ]; + + const returnOrigin = opts.returnFrom || opts.to; + const isOpenJaw = opts.returnDate && returnOrigin !== opts.to; + + if (opts.returnDate) { + flights.push({ + date: opts.returnDate, + from_airport: returnOrigin, + to_airport: opts.from, + max_stops: opts.maxStops, + airlines: opts.airlines, + }); + } + + const tripType = isOpenJaw + ? "multi-city" + : opts.returnDate + ? "round-trip" + : "one-way"; + + const query = createQuery({ + flights, + seat: (opts.seatClass as SeatType) || "economy", + trip: tripType, + passengers: new Passengers({ adults: opts.passengers || 1 }), + currency: opts.currency || "USD", + language: "en-US", + }); + + const googleFlightsUrl = query.url(); + + const rawResults = await getFlights(query, { + timeout: isOpenJaw ? 45000 : 25000, + maxRetries: 1, + retryDelay: 2000, + signal, + }); + + const results: FlightResult[] = rawResults.map((r) => ({ + price: r.price, + currency: opts.currency || "USD", + flights: r.flights.map((leg) => ({ + airline: r.airlines[0] || "Unknown", + flightNumber: "", + departure: { + airport: leg.from_airport.code, + time: formatDt(leg.departure), + }, + arrival: { + airport: leg.to_airport.code, + time: formatDt(leg.arrival), + }, + durationMinutes: leg.duration, + aircraft: leg.plane_type, + })), + totalDurationMinutes: r.flights.reduce((s, l) => s + l.duration, 0), + stops: Math.max(0, r.flights.length - 1), + emissions: r.carbon + ? { typical: r.carbon.typical_on_route, actual: r.carbon.emission } + : undefined, + searchSpec, + })); + + return { results, googleFlightsUrl }; + } catch (err) { + const fallbackUrl = `https://www.google.com/travel/flights?q=${encodeURIComponent(`flights from ${opts.from} to ${opts.to} on ${opts.date}`)}`; + const errResponse = (msg: string) => ({ + results: [] as FlightResult[], + googleFlightsUrl: fallbackUrl, + error: msg, + }); + + if (err instanceof CaptchaError) + return errResponse("Google CAPTCHA — rate limited. Wait and retry."); + if (err instanceof HttpError) + return errResponse(`HTTP ${err.status} from Google Flights`); + if (err instanceof TimeoutError) return errResponse("Search timed out"); + if (err instanceof ParseError) + return errResponse(`Parse error: ${err.message}`); + return errResponse( + `Search error: ${err instanceof Error ? err.message : String(err)}`, + ); + } +} diff --git a/packages/deco-flights/server/lib/storage.ts b/packages/deco-flights/server/lib/storage.ts new file mode 100644 index 0000000000..9bca4493c4 --- /dev/null +++ b/packages/deco-flights/server/lib/storage.ts @@ -0,0 +1,96 @@ +import { join } from "node:path"; +import { homedir } from "node:os"; +import type { Trip, TripSummary } from "./types.ts"; + +const TRIPS_DIR = join(homedir(), ".deco", "flights", "trips"); + +async function ensureDir(dir: string): Promise { + const { mkdir } = await import("node:fs/promises"); + await mkdir(dir, { recursive: true }); +} + +function tripPath(id: string): string { + return join(TRIPS_DIR, `${id}.json`); +} + +export async function saveTrip(trip: Trip): Promise { + await ensureDir(TRIPS_DIR); + await Bun.write(tripPath(trip.id), JSON.stringify(trip, null, 2)); +} + +export async function loadTrip(id: string): Promise { + const file = Bun.file(tripPath(id)); + if (!(await file.exists())) return null; + return file.json() as Promise; +} + +export async function listTrips(statusFilter?: string): Promise { + await ensureDir(TRIPS_DIR); + const { readdir } = await import("node:fs/promises"); + const files = await readdir(TRIPS_DIR); + const summaries: TripSummary[] = []; + + for (const file of files) { + if (!file.endsWith(".json")) continue; + const trip = (await Bun.file(join(TRIPS_DIR, file)).json()) as Trip; + if ( + statusFilter && + statusFilter !== "all" && + trip.status !== statusFilter + ) { + continue; + } + const bestPrice = trip.results?.length + ? Math.min(...trip.results.map((r) => r.price)) + : undefined; + summaries.push({ + id: trip.id, + name: trip.name, + status: trip.status, + origin: trip.origin, + destinations: trip.destinations, + earliestDeparture: trip.earliestDeparture, + latestReturn: trip.latestReturn, + resultCount: trip.results?.length ?? 0, + bestPrice, + }); + } + + return summaries.sort((a, b) => + b.earliestDeparture.localeCompare(a.earliestDeparture), + ); +} + +export async function listFullTrips(statusFilter?: string): Promise { + await ensureDir(TRIPS_DIR); + const { readdir } = await import("node:fs/promises"); + const files = await readdir(TRIPS_DIR); + const trips: Trip[] = []; + + for (const file of files) { + if (!file.endsWith(".json")) continue; + const trip = (await Bun.file(join(TRIPS_DIR, file)).json()) as Trip; + if ( + statusFilter && + statusFilter !== "all" && + trip.status !== statusFilter + ) { + continue; + } + trips.push(trip); + } + + return trips.sort((a, b) => + b.earliestDeparture.localeCompare(a.earliestDeparture), + ); +} + +export async function deleteTrip(id: string): Promise { + const { unlink } = await import("node:fs/promises"); + try { + await unlink(tripPath(id)); + return true; + } catch { + return false; + } +} diff --git a/packages/deco-flights/server/lib/types.ts b/packages/deco-flights/server/lib/types.ts new file mode 100644 index 0000000000..267983fc42 --- /dev/null +++ b/packages/deco-flights/server/lib/types.ts @@ -0,0 +1,103 @@ +export interface TripPreferences { + maxStops?: number; + maxLayoverHours?: number; + preferredAirports?: string[]; + avoidAirlines?: string[]; + preferredAirlines?: string[]; + maxPrice?: number; + currency?: string; +} + +export interface TripLengthDays { + min: number; + max: number; +} + +export type TripStatus = "draft" | "researching" | "complete"; + +export type SearchTaskStatus = "pending" | "running" | "done" | "error"; + +export interface SearchTask { + id: number; + spec: SearchSpec; + status: SearchTaskStatus; + resultCount: number; + googleFlightsUrl?: string; + tier?: number; + error?: string; + durationMs?: number; + startedAt?: string; + finishedAt?: string; +} + +export interface Trip { + id: string; + name: string; + status: TripStatus; + origin: string; + destinations: string[]; + returnOrigins?: string[]; + earliestDeparture: string; + latestDeparture: string; + earliestReturn: string; + latestReturn: string; + tripLengthDays: TripLengthDays; + passengers: number; + seatClass: string; + preferences: TripPreferences; + searchPlan?: SearchPlan; + searchTasks?: SearchTask[]; + results?: ScoredFlightResult[]; + createdAt: string; + updatedAt: string; +} + +export interface SearchSpec { + from: string; + to: string; + departDate: string; + returnDate: string; + returnFrom?: string; +} + +export interface SearchPlan { + searches: SearchSpec[]; + totalCombinations: number; + capped: boolean; +} + +export interface FlightLeg { + airline: string; + flightNumber: string; + departure: { airport: string; time: string }; + arrival: { airport: string; time: string }; + durationMinutes: number; + aircraft: string; +} + +export interface FlightResult { + price: number; + currency: string; + flights: FlightLeg[]; + totalDurationMinutes: number; + stops: number; + emissions?: { typical: number; actual: number }; + searchSpec: SearchSpec; +} + +export interface ScoredFlightResult extends FlightResult { + score: number; + rank: number; +} + +export interface TripSummary { + id: string; + name: string; + status: TripStatus; + origin: string; + destinations: string[]; + earliestDeparture: string; + latestReturn: string; + resultCount: number; + bestPrice?: number; +} diff --git a/packages/deco-flights/server/lib/worker.ts b/packages/deco-flights/server/lib/worker.ts new file mode 100644 index 0000000000..25b89a499e --- /dev/null +++ b/packages/deco-flights/server/lib/worker.ts @@ -0,0 +1,179 @@ +import { loadTrip, saveTrip } from "./storage.ts"; +import { generateSearchPlan } from "./planner.ts"; +import { searchFlights } from "./scraper.ts"; +import { scoreResults } from "./scorer.ts"; +import type { FlightResult, SearchTask, Trip } from "./types.ts"; + +const CONCURRENCY = 3; +const DELAY_BETWEEN_WAVES_MS = 1000; + +const activeWorkers = new Map< + string, + { stop: () => void; abort: AbortController } +>(); + +export function isWorkerRunning(tripId: string): boolean { + return activeWorkers.has(tripId); +} + +export function stopWorker(tripId: string): boolean { + const worker = activeWorkers.get(tripId); + if (worker) { + worker.stop(); + worker.abort.abort(); + return true; + } + return false; +} + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +export function startWorker(tripId: string): void { + if (activeWorkers.has(tripId)) return; + + let stopped = false; + const abort = new AbortController(); + const handle = { + stop: () => { + stopped = true; + }, + abort, + }; + activeWorkers.set(tripId, handle); + + runWorker(tripId, () => stopped, abort.signal).finally(() => { + activeWorkers.delete(tripId); + }); +} + +async function executeTask( + task: SearchTask, + trip: Trip, + signal: AbortSignal, +): Promise { + task.status = "running"; + task.startedAt = new Date().toISOString(); + task.error = undefined; + + const startTime = Date.now(); + + try { + const response = await searchFlights( + { + from: task.spec.from, + to: task.spec.to, + date: task.spec.departDate, + returnDate: task.spec.returnDate, + returnFrom: task.spec.returnFrom, + passengers: trip.passengers, + seatClass: trip.seatClass, + maxStops: trip.preferences.maxStops, + airlines: trip.preferences.preferredAirlines, + currency: trip.preferences.currency || "USD", + }, + task.spec, + signal, + ); + + task.durationMs = Date.now() - startTime; + task.finishedAt = new Date().toISOString(); + task.googleFlightsUrl = response.googleFlightsUrl; + + if (response.results.length > 0) { + task.status = "done"; + task.resultCount = response.results.length; + return response.results; + } else if (response.error) { + task.status = "error"; + task.error = response.error; + } else { + task.status = "done"; + task.resultCount = 0; + } + } catch (err) { + task.durationMs = Date.now() - startTime; + task.finishedAt = new Date().toISOString(); + task.status = "error"; + task.error = err instanceof Error ? err.message : "Unknown error"; + } + return []; +} + +async function runWorker( + tripId: string, + isStopped: () => boolean, + signal: AbortSignal, +): Promise { + const trip = await loadTrip(tripId); + if (!trip) return; + + if (!trip.searchPlan) { + trip.searchPlan = generateSearchPlan(trip); + } + + if (!trip.searchTasks) { + trip.searchTasks = trip.searchPlan.searches.map((spec, i) => ({ + id: i, + spec, + status: "pending" as const, + resultCount: 0, + })); + } + + for (const t of trip.searchTasks) { + if (t.status === "running") { + t.status = "pending"; + t.error = undefined; + t.startedAt = undefined; + } + } + + trip.status = "researching"; + await saveTrip(trip); + + const allResults: FlightResult[] = [...(trip.results ?? [])]; + + // Run searches in parallel waves of CONCURRENCY + while (!isStopped()) { + const pending = trip.searchTasks.filter( + (t) => t.status === "pending" || t.status === "error", + ); + if (pending.length === 0) break; + + const wave = pending.slice(0, CONCURRENCY); + await saveTrip(trip); // save "running" states + + const waveResults = await Promise.all( + wave.map((task) => executeTask(task, trip, signal)), + ); + + for (const results of waveResults) { + allResults.push(...results); + } + + // Score and persist after each wave + trip.results = scoreResults(allResults, trip.preferences); + trip.updatedAt = new Date().toISOString(); + await saveTrip(trip); + + if (!isStopped() && pending.length > CONCURRENCY) { + await sleep(DELAY_BETWEEN_WAVES_MS); + } + } + + for (const t of trip.searchTasks ?? []) { + if (t.status === "running") { + t.status = "pending"; + t.startedAt = undefined; + } + } + + const remaining = (trip.searchTasks ?? []).filter( + (t) => t.status === "pending", + ).length; + trip.status = remaining > 0 ? "draft" : "complete"; + trip.updatedAt = new Date().toISOString(); + await saveTrip(trip); +} diff --git a/packages/deco-flights/server/tools/flight-search.ts b/packages/deco-flights/server/tools/flight-search.ts new file mode 100644 index 0000000000..2c4d5171ad --- /dev/null +++ b/packages/deco-flights/server/tools/flight-search.ts @@ -0,0 +1,85 @@ +import { createTool } from "@decocms/runtime/tools"; +import { z } from "zod"; +import { searchFlights } from "../lib/scraper.ts"; + +export const FLIGHT_SEARCH = createTool({ + id: "FLIGHT_SEARCH", + description: + "Search for flights between two airports on a specific date. Returns flight options with prices, airlines, stops, and durations.", + annotations: { + title: "Search Flights", + readOnlyHint: true, + }, + _meta: { + ui: { resourceUri: "ui://deco-flights/search-results" }, + }, + inputSchema: z.object({ + from: z.string().describe("Departure airport IATA code (e.g., SFO)"), + to: z.string().describe("Arrival airport IATA code (e.g., LAX)"), + date: z.string().describe("Departure date in YYYY-MM-DD format"), + returnDate: z + .string() + .optional() + .describe("Return date in YYYY-MM-DD format (for round trips)"), + returnFrom: z + .string() + .optional() + .describe( + "Return departure airport if different from arrival (open-jaw). E.g., fly into LAX, return from SFO.", + ), + passengers: z + .number() + .optional() + .default(1) + .describe("Number of adult passengers"), + seatClass: z + .enum(["economy", "premium-economy", "business", "first"]) + .optional() + .default("economy") + .describe("Seat class"), + maxStops: z + .number() + .optional() + .describe("Maximum number of stops (0 for nonstop)"), + airlines: z + .array(z.string()) + .optional() + .describe( + "Only show flights from these airlines (IATA codes, e.g. ['DL', 'AA', 'UA'])", + ), + currency: z.string().optional().default("USD").describe("Currency code"), + }), + execute: async ({ context }) => { + const returnFrom = context.returnFrom?.toUpperCase(); + const searchSpec = { + from: context.from.toUpperCase(), + to: context.to.toUpperCase(), + departDate: context.date, + returnDate: context.returnDate ?? context.date, + returnFrom, + }; + + const response = await searchFlights( + { + from: searchSpec.from, + to: searchSpec.to, + date: context.date, + returnDate: context.returnDate, + returnFrom, + passengers: context.passengers, + seatClass: context.seatClass, + maxStops: context.maxStops, + airlines: context.airlines, + currency: context.currency, + }, + searchSpec, + ); + + return { + results: response.results, + resultCount: response.results.length, + googleFlightsUrl: response.googleFlightsUrl, + error: response.error, + }; + }, +}); diff --git a/packages/deco-flights/server/tools/trip-add-searches.ts b/packages/deco-flights/server/tools/trip-add-searches.ts new file mode 100644 index 0000000000..dc91bd1907 --- /dev/null +++ b/packages/deco-flights/server/tools/trip-add-searches.ts @@ -0,0 +1,103 @@ +import { createTool } from "@decocms/runtime/tools"; +import { z } from "zod"; +import { loadTrip, saveTrip } from "../lib/storage.ts"; +import { specKey } from "../lib/planner.ts"; +import { startWorker, isWorkerRunning } from "../lib/worker.ts"; +import type { SearchTask } from "../lib/types.ts"; + +export const TRIP_ADD_SEARCHES = createTool({ + id: "TRIP_ADD_SEARCHES", + description: + "Add more searches to an existing trip and start executing them. Use this to drill down around good dates, try different routes, or refine results. Duplicates are skipped.", + annotations: { + title: "Add Searches to Trip", + }, + inputSchema: z.object({ + tripId: z.string().describe("The trip ID to add searches to"), + searches: z + .array( + z.object({ + from: z.string().describe("Departure airport IATA code"), + to: z.string().describe("Arrival airport IATA code"), + departDate: z.string().describe("Departure date YYYY-MM-DD"), + returnDate: z.string().describe("Return date YYYY-MM-DD"), + returnFrom: z + .string() + .optional() + .describe("Return from different airport (open-jaw)"), + }), + ) + .describe("New searches to add"), + execute: z + .boolean() + .optional() + .default(true) + .describe("Start executing immediately (default true)"), + }), + execute: async ({ context }) => { + const trip = await loadTrip(context.tripId); + if (!trip) { + throw new Error(`Trip not found: ${context.tripId}`); + } + + if (!trip.searchTasks) trip.searchTasks = []; + + // Build set of existing spec keys for dedup + const existing = new Set(trip.searchTasks.map((t) => specKey(t.spec))); + + const startId = trip.searchTasks.length; + let added = 0; + let skipped = 0; + + for (const s of context.searches) { + const spec = { + from: s.from.toUpperCase(), + to: s.to.toUpperCase(), + departDate: s.departDate, + returnDate: s.returnDate, + returnFrom: s.returnFrom?.toUpperCase(), + }; + + const key = specKey(spec); + if (existing.has(key)) { + skipped++; + continue; + } + existing.add(key); + + const task: SearchTask = { + id: startId + added, + spec, + status: "pending", + resultCount: 0, + }; + trip.searchTasks.push(task); + added++; + } + + // Update search plan to reflect new total + if (trip.searchPlan) { + trip.searchPlan.searches = trip.searchTasks.map((t) => t.spec); + trip.searchPlan.totalCombinations = trip.searchTasks.length; + } + + trip.updatedAt = new Date().toISOString(); + await saveTrip(trip); + + // Start worker if requested and not already running + if (context.execute && added > 0 && !isWorkerRunning(context.tripId)) { + startWorker(context.tripId); + } + + return { + added, + skipped, + totalSearches: trip.searchTasks.length, + workerRunning: isWorkerRunning(context.tripId), + message: + added > 0 + ? `Added ${added} searches${skipped ? ` (${skipped} duplicates skipped)` : ""}. ${context.execute ? "Research started." : "Ready to execute."}` + : `All ${skipped} searches already exist in this trip.`, + }; + }, +}); diff --git a/packages/deco-flights/server/tools/trip-create.ts b/packages/deco-flights/server/tools/trip-create.ts new file mode 100644 index 0000000000..582a7e608c --- /dev/null +++ b/packages/deco-flights/server/tools/trip-create.ts @@ -0,0 +1,97 @@ +import { createTool } from "@decocms/runtime/tools"; +import { z } from "zod"; +import { saveTrip } from "../lib/storage.ts"; +import { generateSearchPlan } from "../lib/planner.ts"; +import type { Trip } from "../lib/types.ts"; + +export const TRIP_CREATE = createTool({ + id: "TRIP_CREATE", + description: + "Create a new trip research plan. Specify destinations, date ranges, trip length, and preferences. The system generates a search plan covering all valid date combinations.", + annotations: { + title: "Create Trip", + }, + _meta: { + ui: { resourceUri: "ui://deco-flights/trip-card" }, + }, + inputSchema: z.object({ + name: z.string().describe('Trip name (e.g., "Trip to California")'), + origin: z.string().describe("Departure airport IATA code"), + destinations: z + .array(z.string()) + .describe("Destination airport IATA codes"), + returnOrigins: z + .array(z.string()) + .optional() + .describe( + "Return departure airports for open-jaw itineraries (e.g., fly into LAX, return from SFO). Omit for standard round-trips.", + ), + earliestDeparture: z + .string() + .describe("Earliest acceptable departure date (YYYY-MM-DD)"), + latestDeparture: z + .string() + .describe("Latest acceptable departure date (YYYY-MM-DD)"), + earliestReturn: z + .string() + .describe("Earliest acceptable return date (YYYY-MM-DD)"), + latestReturn: z + .string() + .describe("Latest acceptable return date (YYYY-MM-DD)"), + tripLengthDays: z.object({ + min: z.number().describe("Minimum trip length in days"), + max: z.number().describe("Maximum trip length in days"), + }), + passengers: z.number().optional().default(1), + seatClass: z + .enum(["economy", "premium-economy", "business", "first"]) + .optional() + .default("economy"), + preferences: z + .object({ + maxStops: z.number().optional(), + maxLayoverHours: z.number().optional(), + preferredAirports: z.array(z.string()).optional(), + avoidAirlines: z.array(z.string()).optional(), + preferredAirlines: z.array(z.string()).optional(), + maxPrice: z.number().optional(), + currency: z + .string() + .optional() + .describe("Currency code for prices (default USD)"), + }) + .optional() + .default({}), + }), + execute: async ({ context }) => { + const id = crypto.randomUUID().slice(0, 8); + const now = new Date().toISOString(); + + const trip: Trip = { + id, + name: context.name, + status: "draft", + origin: context.origin.toUpperCase(), + destinations: context.destinations.map((d) => d.toUpperCase()), + returnOrigins: context.returnOrigins?.map((d) => d.toUpperCase()), + earliestDeparture: context.earliestDeparture, + latestDeparture: context.latestDeparture, + earliestReturn: context.earliestReturn, + latestReturn: context.latestReturn, + tripLengthDays: context.tripLengthDays, + passengers: context.passengers, + seatClass: context.seatClass, + preferences: context.preferences, + createdAt: now, + updatedAt: now, + }; + + trip.searchPlan = generateSearchPlan(trip); + await saveTrip(trip); + + return { + trip, + message: `Trip "${trip.name}" created with ${trip.searchPlan.searches.length} planned searches${trip.searchPlan.capped ? ` (capped from ${trip.searchPlan.totalCombinations} total combinations)` : ""}.`, + }; + }, +}); diff --git a/packages/deco-flights/server/tools/trip-delete.ts b/packages/deco-flights/server/tools/trip-delete.ts new file mode 100644 index 0000000000..2ab16a83a0 --- /dev/null +++ b/packages/deco-flights/server/tools/trip-delete.ts @@ -0,0 +1,22 @@ +import { createTool } from "@decocms/runtime/tools"; +import { z } from "zod"; +import { deleteTrip } from "../lib/storage.ts"; + +export const TRIP_DELETE = createTool({ + id: "TRIP_DELETE", + description: "Delete a saved trip and its results.", + annotations: { + title: "Delete Trip", + destructiveHint: true, + }, + inputSchema: z.object({ + tripId: z.string().describe("The trip ID to delete"), + }), + execute: async ({ context }) => { + const deleted = await deleteTrip(context.tripId); + if (!deleted) { + throw new Error(`Trip not found: ${context.tripId}`); + } + return { success: true }; + }, +}); diff --git a/packages/deco-flights/server/tools/trip-execute.ts b/packages/deco-flights/server/tools/trip-execute.ts new file mode 100644 index 0000000000..ebbebab187 --- /dev/null +++ b/packages/deco-flights/server/tools/trip-execute.ts @@ -0,0 +1,57 @@ +import { createTool } from "@decocms/runtime/tools"; +import { z } from "zod"; +import { loadTrip } from "../lib/storage.ts"; +import { isWorkerRunning, startWorker } from "../lib/worker.ts"; + +export const TRIP_EXECUTE = createTool({ + id: "TRIP_EXECUTE", + description: + "Start flight research for a trip. Launches a background worker that runs ALL searches automatically. " + + "Returns immediately with the trip data and a live UI dashboard. Use TRIP_STOP to cancel.", + annotations: { + title: "Execute Trip Research", + }, + _meta: { + ui: { resourceUri: "ui://deco-flights/trip-planner" }, + }, + inputSchema: z.object({ + tripId: z.string().describe("The trip ID to research"), + }), + execute: async ({ context }) => { + const trip = await loadTrip(context.tripId); + if (!trip) { + throw new Error(`Trip not found: ${context.tripId}`); + } + + const alreadyRunning = isWorkerRunning(context.tripId); + + if (!alreadyRunning) { + startWorker(context.tripId); + } + + // Re-read trip after worker may have initialized tasks + const freshTrip = (await loadTrip(context.tripId)) ?? trip; + + const tasks = freshTrip.searchTasks ?? []; + const done = tasks.filter( + (t) => t.status === "done" || t.status === "error", + ).length; + const totalSearches = + tasks.length || freshTrip.searchPlan?.searches.length || 0; + + const trimmedTrip = { + ...freshTrip, + results: (freshTrip.results ?? []).slice(0, 20), + _totalResults: freshTrip.results?.length ?? 0, + }; + + return { + trip: trimmedTrip, + workerRunning: true, + started: !alreadyRunning, + message: alreadyRunning + ? `Research running (${done}/${totalSearches} done). Dashboard is live.` + : `Research started! ${totalSearches} searches running in background.`, + }; + }, +}); diff --git a/packages/deco-flights/server/tools/trip-get.ts b/packages/deco-flights/server/tools/trip-get.ts new file mode 100644 index 0000000000..cea3b731c0 --- /dev/null +++ b/packages/deco-flights/server/tools/trip-get.ts @@ -0,0 +1,40 @@ +import { createTool } from "@decocms/runtime/tools"; +import { z } from "zod"; +import { loadTrip } from "../lib/storage.ts"; +import { isWorkerRunning } from "../lib/worker.ts"; + +export const TRIP_GET = createTool({ + id: "TRIP_GET", + description: + "Get trip details including search tasks progress and top results. " + + "Returns at most 20 results to keep response size manageable.", + annotations: { + title: "Get Trip", + readOnlyHint: true, + }, + _meta: { + ui: { resourceUri: "ui://deco-flights/trip-planner" }, + }, + inputSchema: z.object({ + tripId: z.string().describe("The trip ID"), + }), + execute: async ({ context }) => { + const trip = await loadTrip(context.tripId); + if (!trip) { + throw new Error(`Trip not found: ${context.tripId}`); + } + + // Return a trimmed trip: full searchTasks but capped results + const totalResults = trip.results?.length ?? 0; + const trimmedTrip = { + ...trip, + results: (trip.results ?? []).slice(0, 20), + _totalResults: totalResults, + }; + + return { + trip: trimmedTrip, + workerRunning: isWorkerRunning(context.tripId), + }; + }, +}); diff --git a/packages/deco-flights/server/tools/trip-list.ts b/packages/deco-flights/server/tools/trip-list.ts new file mode 100644 index 0000000000..a29738dc9b --- /dev/null +++ b/packages/deco-flights/server/tools/trip-list.ts @@ -0,0 +1,34 @@ +import { createTool } from "@decocms/runtime/tools"; +import { z } from "zod"; +import { listFullTrips } from "../lib/storage.ts"; +import { isWorkerRunning } from "../lib/worker.ts"; + +export const TRIP_LIST = createTool({ + id: "TRIP_LIST", + description: + "List all saved trips with search tasks and top results (capped at 10 per trip).", + annotations: { + title: "List Trips", + readOnlyHint: true, + }, + _meta: { + ui: { resourceUri: "ui://deco-flights/trips-dashboard" }, + }, + inputSchema: z.object({ + status: z + .enum(["draft", "researching", "complete", "all"]) + .optional() + .default("all") + .describe("Filter by trip status"), + }), + execute: async ({ context }) => { + const trips = await listFullTrips(context.status); + const trimmed = trips.map((t) => ({ + ...t, + results: (t.results ?? []).slice(0, 20), + _totalResults: t.results?.length ?? 0, + workerRunning: isWorkerRunning(t.id), + })); + return { trips: trimmed, totalCount: trips.length }; + }, +}); diff --git a/packages/deco-flights/server/tools/trip-stop.ts b/packages/deco-flights/server/tools/trip-stop.ts new file mode 100644 index 0000000000..8e111865c2 --- /dev/null +++ b/packages/deco-flights/server/tools/trip-stop.ts @@ -0,0 +1,53 @@ +import { createTool } from "@decocms/runtime/tools"; +import { z } from "zod"; +import { loadTrip, saveTrip } from "../lib/storage.ts"; +import { stopWorker } from "../lib/worker.ts"; + +export const TRIP_STOP = createTool({ + id: "TRIP_STOP", + description: + "Stop an in-progress trip research. The background worker will finish its current search then stop. All results found so far are kept.", + annotations: { + title: "Stop Research", + }, + inputSchema: z.object({ + tripId: z.string().describe("The trip ID to stop researching"), + }), + execute: async ({ context }) => { + const trip = await loadTrip(context.tripId); + if (!trip) { + throw new Error(`Trip not found: ${context.tripId}`); + } + + const wasRunning = stopWorker(context.tripId); + + // Reset running tasks + for (const task of trip.searchTasks ?? []) { + if (task.status === "running") { + task.status = "pending"; + task.startedAt = undefined; + task.error = undefined; + } + } + + trip.status = "draft"; + trip.updatedAt = new Date().toISOString(); + await saveTrip(trip); + + const done = (trip.searchTasks ?? []).filter( + (t) => t.status === "done", + ).length; + const pending = (trip.searchTasks ?? []).filter( + (t) => t.status === "pending", + ).length; + + return { + success: true, + wasRunning, + resultsKept: trip.results?.length ?? 0, + done, + pending, + message: `Stopped "${trip.name}". ${trip.results?.length ?? 0} results kept, ${done} searches done, ${pending} remaining.`, + }; + }, +}); diff --git a/packages/deco-flights/server/tools/trip-update.ts b/packages/deco-flights/server/tools/trip-update.ts new file mode 100644 index 0000000000..8f83ad67da --- /dev/null +++ b/packages/deco-flights/server/tools/trip-update.ts @@ -0,0 +1,97 @@ +import { createTool } from "@decocms/runtime/tools"; +import { z } from "zod"; +import { loadTrip, saveTrip } from "../lib/storage.ts"; +import { generateSearchPlan } from "../lib/planner.ts"; + +export const TRIP_UPDATE = createTool({ + id: "TRIP_UPDATE", + description: + "Update a trip's configuration or preferences. Regenerates the search plan if date/destination fields change.", + annotations: { + title: "Update Trip", + }, + inputSchema: z.object({ + tripId: z.string().describe("The trip ID to update"), + name: z.string().optional(), + origin: z.string().optional(), + destinations: z.array(z.string()).optional(), + earliestDeparture: z.string().optional(), + latestDeparture: z.string().optional(), + earliestReturn: z.string().optional(), + latestReturn: z.string().optional(), + tripLengthDays: z + .object({ + min: z.number(), + max: z.number(), + }) + .optional(), + passengers: z.number().optional(), + seatClass: z + .enum(["economy", "premium-economy", "business", "first"]) + .optional(), + preferences: z + .object({ + maxStops: z.number().optional(), + maxLayoverHours: z.number().optional(), + preferredAirports: z.array(z.string()).optional(), + avoidAirlines: z.array(z.string()).optional(), + preferredAirlines: z.array(z.string()).optional(), + maxPrice: z.number().optional(), + currency: z + .string() + .optional() + .describe("Currency code (e.g. USD, BRL, EUR)"), + }) + .optional(), + }), + execute: async ({ context }) => { + const trip = await loadTrip(context.tripId); + if (!trip) { + throw new Error(`Trip not found: ${context.tripId}`); + } + + const { tripId: _, ...updates } = context; + let planChanged = false; + + for (const [key, value] of Object.entries(updates)) { + if (value !== undefined) { + (trip as unknown as Record)[key] = + key === "origin" + ? (value as string).toUpperCase() + : key === "destinations" + ? (value as string[]).map((d) => d.toUpperCase()) + : value; + + if ( + [ + "origin", + "destinations", + "earliestDeparture", + "latestDeparture", + "earliestReturn", + "latestReturn", + "tripLengthDays", + ].includes(key) + ) { + planChanged = true; + } + } + } + + if (planChanged) { + trip.searchPlan = generateSearchPlan(trip); + trip.results = undefined; + trip.status = "draft"; + } + + trip.updatedAt = new Date().toISOString(); + await saveTrip(trip); + + return { + trip, + message: planChanged + ? `Trip updated. New search plan: ${trip.searchPlan!.searches.length} searches.` + : "Trip preferences updated.", + }; + }, +}); diff --git a/packages/deco-flights/server/ui/search-results.ts b/packages/deco-flights/server/ui/search-results.ts new file mode 100644 index 0000000000..296a947694 --- /dev/null +++ b/packages/deco-flights/server/ui/search-results.ts @@ -0,0 +1,99 @@ +import { SHARED_STYLES, APPBRIDGE_SCRIPT } from "./shared-styles.ts"; + +export function renderSearchResults(): string { + return ` + +
+
+ +

Waiting for search results...

+
+
+${APPBRIDGE_SCRIPT} +`; +} diff --git a/packages/deco-flights/server/ui/shared-styles.ts b/packages/deco-flights/server/ui/shared-styles.ts new file mode 100644 index 0000000000..8cff6ad56e --- /dev/null +++ b/packages/deco-flights/server/ui/shared-styles.ts @@ -0,0 +1,228 @@ +export const SHARED_STYLES = ` + * { margin: 0; padding: 0; box-sizing: border-box; } + body { + font-family: var(--font-sans, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif); + background: var(--color-background-primary, #ffffff); + color: var(--color-text-primary, #0f172a); + line-height: 1.5; + padding: 16px; + } + .muted { color: var(--color-text-secondary, #64748b); } + .small { font-size: 12px; } + .badge { + display: inline-block; + padding: 2px 8px; + border-radius: 9999px; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + } + .badge-draft { background: #f1f5f9; color: #475569; } + .badge-researching { background: #dbeafe; color: #1d4ed8; } + .badge-complete { background: #dcfce7; color: #16a34a; } + .card { + border: 1px solid var(--color-border-primary, #e2e8f0); + border-radius: var(--border-radius-md, 8px); + padding: 12px 16px; + background: var(--color-background-primary, #ffffff); + } + .card:hover { background: var(--color-background-secondary, #f8fafc); } + .grid { display: grid; gap: 12px; } + .flex { display: flex; align-items: center; } + .gap-2 { gap: 8px; } + .gap-3 { gap: 12px; } + .gap-4 { gap: 16px; } + .justify-between { justify-content: space-between; } + .font-medium { font-weight: 500; } + .font-semibold { font-weight: 600; } + .text-lg { font-size: 18px; } + .text-sm { font-size: 14px; } + .text-xs { font-size: 12px; } + .text-2xl { font-size: 24px; } + .price { color: #16a34a; font-weight: 700; } + .price-high { color: #dc2626; } + .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + .w-full { width: 100%; } + .mt-2 { margin-top: 8px; } + .mt-4 { margin-top: 16px; } + .mb-2 { margin-bottom: 8px; } + .mb-4 { margin-bottom: 16px; } + .p-3 { padding: 12px; } + .p-4 { padding: 16px; } + .rounded { border-radius: var(--border-radius-md, 8px); } + .border { border: 1px solid var(--color-border-primary, #e2e8f0); } + .bg-muted { background: var(--color-background-secondary, #f8fafc); } + button { + cursor: pointer; + border: none; + border-radius: var(--border-radius-md, 6px); + padding: 8px 16px; + font-size: 13px; + font-weight: 500; + font-family: inherit; + transition: all 0.15s; + } + .btn-primary { + background: #0f172a; + color: white; + } + .btn-primary:hover { background: #1e293b; } + .btn-ghost { + background: transparent; + color: var(--color-text-secondary, #64748b); + } + .btn-ghost:hover { background: var(--color-background-secondary, #f1f5f9); } + table { + width: 100%; + border-collapse: collapse; + font-size: 13px; + } + th { + text-align: left; + padding: 8px 12px; + font-weight: 600; + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--color-text-secondary, #64748b); + border-bottom: 2px solid var(--color-border-primary, #e2e8f0); + } + td { + padding: 10px 12px; + border-bottom: 1px solid var(--color-border-primary, #e2e8f0); + vertical-align: middle; + } + tr:hover td { background: var(--color-background-secondary, #f8fafc); } + .arrow { color: var(--color-text-secondary, #94a3b8); margin: 0 4px; } + .rank-badge { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border-radius: 50%; + font-size: 11px; + font-weight: 700; + } + .rank-1 { background: #fef3c7; color: #92400e; } + .rank-2 { background: #e2e8f0; color: #475569; } + .rank-3 { background: #fed7aa; color: #9a3412; } + .rank-other { background: #f1f5f9; color: #64748b; } + .score-bar { + height: 4px; + border-radius: 2px; + background: #e2e8f0; + overflow: hidden; + } + .score-fill { + height: 100%; + border-radius: 2px; + background: #16a34a; + transition: width 0.3s; + } + .empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 48px 24px; + text-align: center; + } + .empty-state svg { margin-bottom: 16px; opacity: 0.4; } +`; + +export const APPBRIDGE_SCRIPT = ` + +`; diff --git a/packages/deco-flights/server/ui/trip-card.ts b/packages/deco-flights/server/ui/trip-card.ts new file mode 100644 index 0000000000..f138408356 --- /dev/null +++ b/packages/deco-flights/server/ui/trip-card.ts @@ -0,0 +1,102 @@ +import { SHARED_STYLES, APPBRIDGE_SCRIPT } from "./shared-styles.ts"; + +export function renderTripCard(): string { + return ` + +
+
+ +

Loading trip...

+
+
+${APPBRIDGE_SCRIPT} +`; +} diff --git a/packages/deco-flights/server/ui/trip-planner.ts b/packages/deco-flights/server/ui/trip-planner.ts new file mode 100644 index 0000000000..7e70bd8756 --- /dev/null +++ b/packages/deco-flights/server/ui/trip-planner.ts @@ -0,0 +1,243 @@ +import { SHARED_STYLES, APPBRIDGE_SCRIPT } from "./shared-styles.ts"; + +const API_BASE = `http://localhost:${Number(process.env.PORT) || 4747}`; + +export function renderTripPlanner(): string { + return ` + +
+
+ +

Loading trip details...

+
+
+${APPBRIDGE_SCRIPT} +`; +} diff --git a/packages/deco-flights/server/ui/trips-dashboard.ts b/packages/deco-flights/server/ui/trips-dashboard.ts new file mode 100644 index 0000000000..b47688a5bb --- /dev/null +++ b/packages/deco-flights/server/ui/trips-dashboard.ts @@ -0,0 +1,354 @@ +import { SHARED_STYLES, APPBRIDGE_SCRIPT } from "./shared-styles.ts"; + +const API_BASE = `http://localhost:${Number(process.env.PORT) || 4747}`; + +export function renderTripsDashboard(): string { + return ` + +
+
+ +

Loading trips...

+
+
+${APPBRIDGE_SCRIPT} +`; +} diff --git a/packages/deco-flights/tsconfig.json b/packages/deco-flights/tsconfig.json new file mode 100644 index 0000000000..1df7a2fa35 --- /dev/null +++ b/packages/deco-flights/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": ".", + "declaration": true, + "declarationMap": true + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "dist", "**/*.test.ts"] +} diff --git a/packages/mesh-sdk/src/lib/constants.ts b/packages/mesh-sdk/src/lib/constants.ts index b4a46673a3..0973ebebc8 100644 --- a/packages/mesh-sdk/src/lib/constants.ts +++ b/packages/mesh-sdk/src/lib/constants.ts @@ -288,6 +288,12 @@ export const StudioPackAgentId = { /** * Check if a connection or virtual MCP ID is a Studio Pack agent. */ +export function isDecoFlights(id: string | null | undefined): string | null { + if (!id) return null; + const match = id.match(/^deco-flights_(.+)$/); + return match?.[1] ?? null; +} + export function isStudioPackAgent(id: string | null | undefined): boolean { if (!id) return false; return ( @@ -333,6 +339,12 @@ export const WELL_KNOWN_AGENT_TEMPLATES = [ icon: "icon://Package?color=blue", type: "pack" as const, }, + { + id: "deco-flights", + title: "Flights", + icon: "icon://Compass03?color=sky", + type: "registry-agent" as const, + }, ] as const; export type WellKnownAgentTemplate =