From f4881b21b0d119421415ef0065dcbba80060ef48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Guti=C3=A9rrez?= <35082514+alezmad@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:40:16 +0100 Subject: [PATCH] feat(broker): add claude-powered telegram bot with tool calling Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/broker/package.json | 1 + apps/broker/src/env.ts | 1 + apps/broker/src/telegram-ai.ts | 291 +++++++++++++++++++++++++++++ apps/broker/src/telegram-bridge.ts | 206 ++++++++++++++++++-- pnpm-lock.yaml | 153 ++++++++++++--- 5 files changed, 616 insertions(+), 36 deletions(-) create mode 100644 apps/broker/src/telegram-ai.ts diff --git a/apps/broker/package.json b/apps/broker/package.json index e6c390c..4b8fd72 100644 --- a/apps/broker/package.json +++ b/apps/broker/package.json @@ -15,6 +15,7 @@ }, "prettier": "@turbostarter/prettier-config", "dependencies": { + "@anthropic-ai/sdk": "0.71.2", "@qdrant/js-client-rest": "1.17.0", "@turbostarter/db": "workspace:*", "@turbostarter/shared": "workspace:*", diff --git a/apps/broker/src/env.ts b/apps/broker/src/env.ts index bbc6895..8324eb2 100644 --- a/apps/broker/src/env.ts +++ b/apps/broker/src/env.ts @@ -34,6 +34,7 @@ const envSchema = z.object({ CLI_SYNC_SECRET: z.string().default(""), // HS256 shared secret for dashboardβ†’broker sync JWTs. Required for /cli-sync. MAX_SERVICES_PER_MESH: z.coerce.number().int().positive().default(20), MAX_SERVICE_ZIP_BYTES: z.coerce.number().int().positive().default(50 * 1024 * 1024), + ANTHROPIC_API_KEY: z.string().default(""), // Claude API key for Telegram AI bot NODE_ENV: z .enum(["development", "production", "test"]) .default("development"), diff --git a/apps/broker/src/telegram-ai.ts b/apps/broker/src/telegram-ai.ts new file mode 100644 index 0000000..cd64d75 --- /dev/null +++ b/apps/broker/src/telegram-ai.ts @@ -0,0 +1,291 @@ +/** + * Claude-powered natural language processing for Telegram mesh interactions. + * + * Uses Claude Haiku 4.5 with tool calling to interpret user intent + * and map to mesh operations. Destructive/social actions require + * confirmation via Telegram inline buttons. + */ + +import Anthropic from "@anthropic-ai/sdk"; +import { env } from "./env"; +import { log } from "./logger"; + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +export interface AiTool { + name: string; + description: string; + input_schema: Record; +} + +export interface AiToolCall { + name: string; + input: Record; +} + +export interface AiResult { + type: "text" | "tool_call" | "error"; + text?: string; + toolCall?: AiToolCall; + requiresConfirmation?: boolean; +} + +// --------------------------------------------------------------------------- +// Tools definition +// --------------------------------------------------------------------------- + +const TOOLS: AiTool[] = [ + { + name: "send_message", + description: "Send a message to a peer in the mesh. Use when the user wants to tell, ask, or communicate something to a specific person or group.", + input_schema: { + type: "object", + properties: { + to: { type: "string", description: "Peer name, @group, or * for broadcast" }, + message: { type: "string", description: "The message content" }, + priority: { type: "string", enum: ["now", "next", "low"], description: "Delivery priority (default: next)" }, + }, + required: ["to", "message"], + }, + }, + { + name: "list_peers", + description: "List all connected peers in the mesh. Use when user asks who's online, who's available, or what everyone is doing.", + input_schema: { + type: "object", + properties: {}, + }, + }, + { + name: "remember", + description: "Store a memory/note in the mesh's shared knowledge. Use when user wants to save information for later.", + input_schema: { + type: "object", + properties: { + content: { type: "string", description: "The content to remember" }, + tags: { type: "array", items: { type: "string" }, description: "Tags for categorization" }, + }, + required: ["content"], + }, + }, + { + name: "recall", + description: "Search the mesh's shared memory. Use when user asks about something that was previously stored.", + input_schema: { + type: "object", + properties: { + query: { type: "string", description: "Search query" }, + }, + required: ["query"], + }, + }, + { + name: "get_state", + description: "Read a shared state value. Use when user asks about a specific key/variable.", + input_schema: { + type: "object", + properties: { + key: { type: "string", description: "State key to read" }, + }, + required: ["key"], + }, + }, + { + name: "set_state", + description: "Write a shared state value. Use when user wants to set/update a key.", + input_schema: { + type: "object", + properties: { + key: { type: "string", description: "State key" }, + value: { type: "string", description: "Value to set" }, + }, + required: ["key", "value"], + }, + }, + { + name: "create_mesh", + description: "Create a new mesh. Use when user wants to create a new workspace/mesh.", + input_schema: { + type: "object", + properties: { + name: { type: "string", description: "Mesh name" }, + }, + required: ["name"], + }, + }, + { + name: "share_mesh", + description: "Generate an invite link or send an invite email. Use when user wants to invite someone to the mesh.", + input_schema: { + type: "object", + properties: { + email: { type: "string", description: "Email to invite (optional β€” if omitted, generates a link)" }, + }, + }, + }, +]; + +// Actions that need user confirmation before executing +const CONFIRM_ACTIONS = new Set([ + "send_message", + "create_mesh", + "share_mesh", + "set_state", + "remember", +]); + +const SYSTEM_PROMPT = `You are the claudemesh Telegram assistant. You help users interact with their claudemesh peer network using natural language. + +You have access to tools for mesh operations. When the user's intent maps to a tool, use it. When it's a general question or conversation, respond directly. + +Rules: +- Be concise β€” Telegram messages should be short +- When sending messages to peers, preserve the user's tone and intent +- For ambiguous peer names, ask for clarification +- Never fabricate peer names or data β€” use list_peers to find real names +- If unsure which mesh to target, ask the user`; + +// --------------------------------------------------------------------------- +// AI Engine +// --------------------------------------------------------------------------- + +let client: Anthropic | null = null; + +function getClient(): Anthropic { + if (!client) { + const apiKey = env.ANTHROPIC_API_KEY; + if (!apiKey) throw new Error("ANTHROPIC_API_KEY not configured"); + client = new Anthropic({ apiKey }); + } + return client; +} + +/** + * Process a natural language message through Claude and return the intent. + */ +export async function processMessage( + userMessage: string, + context: { meshSlug?: string; userName?: string; recentPeers?: string[] }, +): Promise { + try { + const anthropic = getClient(); + + const contextInfo = [ + context.meshSlug ? `Current mesh: ${context.meshSlug}` : null, + context.userName ? `User's name: ${context.userName}` : null, + context.recentPeers?.length ? `Known peers: ${context.recentPeers.join(", ")}` : null, + ].filter(Boolean).join(". "); + + const response = await anthropic.messages.create({ + model: "claude-haiku-4-5-20251001", + max_tokens: 500, + system: SYSTEM_PROMPT + (contextInfo ? `\n\nContext: ${contextInfo}` : ""), + tools: TOOLS as Anthropic.Messages.Tool[], + messages: [{ role: "user", content: userMessage }], + }); + + // Check for tool use + for (const block of response.content) { + if (block.type === "tool_use") { + return { + type: "tool_call", + toolCall: { name: block.name, input: block.input as Record }, + requiresConfirmation: CONFIRM_ACTIONS.has(block.name), + }; + } + if (block.type === "text") { + return { type: "text", text: block.text }; + } + } + + return { type: "text", text: "I'm not sure how to help with that." }; + } catch (err) { + log.error("telegram-ai", { error: err instanceof Error ? err.message : String(err) }); + return { type: "error", text: "AI processing failed. Try a /command instead." }; + } +} + +/** + * Format a tool call as a human-readable confirmation message for Telegram. + */ +export function formatConfirmation(toolCall: AiToolCall): string { + const { name, input } = toolCall; + + switch (name) { + case "send_message": + return `πŸ“€ *Send message to ${escMd(String(input.to))}:*\n\n"${escMd(String(input.message))}"\n\nPriority: ${input.priority ?? "next"}`; + + case "create_mesh": + return `πŸ”§ *Create mesh:*\n\nName: ${escMd(String(input.name))}`; + + case "share_mesh": + return input.email + ? `πŸ“§ *Send invite to:*\n\n${escMd(String(input.email))}` + : `πŸ”— *Generate invite link*`; + + case "set_state": + return `πŸ“ *Set state:*\n\n\`${escMd(String(input.key))}\` = \`${escMd(String(input.value))}\``; + + case "remember": + return `πŸ’Ύ *Remember:*\n\n"${escMd(String(input.content))}"${input.tags ? `\nTags: ${(input.tags as string[]).join(", ")}` : ""}`; + + default: + return `βš™οΈ *${name}:*\n\n${JSON.stringify(input, null, 2)}`; + } +} + +/** + * Format a tool result as a Telegram reply. + */ +export function formatResult(toolName: string, result: unknown): string { + switch (toolName) { + case "send_message": + return "βœ… Message sent."; + + case "list_peers": { + const peers = result as Array<{ displayName: string; status: string; summary?: string }>; + if (!peers || peers.length === 0) return "No peers online."; + return "πŸ‘₯ *Online peers:*\n\n" + peers.map(p => { + const icon = p.status === "idle" ? "🟒" : p.status === "working" ? "🟑" : p.status === "dnd" ? "πŸ”΄" : "βšͺ"; + return `${icon} *${escMd(p.displayName)}*${p.summary ? ` β€” ${escMd(p.summary)}` : ""}`; + }).join("\n"); + } + + case "recall": { + const memories = result as Array<{ content: string; tags: string[] }>; + if (!memories || memories.length === 0) return "No memories found."; + return "🧠 *Memories:*\n\n" + memories.map(m => + `β€’ ${escMd(m.content)}${m.tags.length ? ` _[${m.tags.join(", ")}]_` : ""}` + ).join("\n"); + } + + case "get_state": { + const state = result as { key: string; value: unknown } | null; + if (!state) return "Key not found."; + return `πŸ“Š \`${escMd(state.key)}\` = \`${escMd(String(state.value))}\``; + } + + case "remember": + return "πŸ’Ύ Remembered."; + + case "set_state": + return "πŸ“ State updated."; + + case "create_mesh": + return "βœ… Mesh created."; + + case "share_mesh": + return typeof result === "string" ? `πŸ”— Invite: ${result}` : "βœ… Invite sent."; + + default: + return `βœ… Done: ${JSON.stringify(result)}`; + } +} + +function escMd(s: string): string { + return s.replace(/[_*[\]()~`>#+\-=|{}.!\\]/g, "\\$&"); +} + +export { CONFIRM_ACTIONS }; diff --git a/apps/broker/src/telegram-bridge.ts b/apps/broker/src/telegram-bridge.ts index 6787cfb..01940ae 100644 --- a/apps/broker/src/telegram-bridge.ts +++ b/apps/broker/src/telegram-bridge.ts @@ -535,6 +535,25 @@ const pendingVerifications = new Map< // Conversation state: chatId β†’ which input the bot is waiting for const conversationState = new Map(); +/** Pending AI actions awaiting user confirmation */ +const pendingAiActions = new Map }; + expiresAt: number; +}>(); + +/** Chat β†’ mesh slugs mapping for AI context */ +const chatMeshSlugs = new Map(); + +// Clean expired AI actions every 5 min +setInterval(() => { + const now = Date.now(); + for (const [k, v] of pendingAiActions) { + if (now > v.expiresAt) pendingAiActions.delete(k); + } +}, 5 * 60 * 1000); + /** Invite URL regex: https://claudemesh.com/join/ */ const INVITE_URL_RE = /https?:\/\/(?:www\.)?claudemesh\.com\/join\/([A-Za-z0-9_\-\.]+)/; @@ -1144,6 +1163,49 @@ function setupBotCommands( const chatId = ctx.chat?.id; if (!chatId) { await ctx.answerCallbackQuery(); return; } + // --- AI action confirmation --- + if (data.startsWith("ai_")) { + const [action, actionId] = data.split(":"); + if (!actionId) { await ctx.answerCallbackQuery(); return; } + + const pending = pendingAiActions.get(actionId); + if (!pending || pending.chatId !== chatId) { + await ctx.answerCallbackQuery({ text: "Expired. Send your message again." }); + return; + } + + if (action === "ai_cancel") { + pendingAiActions.delete(actionId); + await ctx.answerCallbackQuery({ text: "Cancelled" }); + await ctx.editMessageText("❌ Cancelled."); + return; + } + + if (action === "ai_edit") { + pendingAiActions.delete(actionId); + await ctx.answerCallbackQuery({ text: "Type your edited message" }); + await ctx.editMessageText("✏️ Type your message again with corrections."); + return; + } + + if (action === "ai_confirm") { + pendingAiActions.delete(actionId); + await ctx.answerCallbackQuery({ text: "Executing..." }); + + try { + const { formatResult } = await import("./telegram-ai"); + const result = await executeAiToolCall(pending.toolCall, pending.meshIds); + await ctx.editMessageText( + formatResult(pending.toolCall.name, result), + { parse_mode: "MarkdownV2" }, + ); + } catch (err) { + await ctx.editMessageText(`❌ Failed: ${err instanceof Error ? err.message : String(err)}`); + } + return; + } + } + // --- File recipient picker --- if (data.startsWith("file:")) { const pending = pendingFiles.get(chatId); @@ -1523,24 +1585,140 @@ function setupBotCommands( return; } - // --- No mention β†’ broadcast to all connected meshes --- - let sent = 0; - for (const meshId of meshIds) { - const conn = meshConnections.get(meshId); - if (!conn?.isConnected()) continue; - const ok = await conn.sendMessage( - "*", - `[via Telegram] ${text}`, - "next", - ); - if (ok) sent++; - } - if (sent === 0) { - await ctx.reply("❌ Not connected to any mesh."); + // --- No mention β†’ process through Claude AI --- + try { + const { processMessage, formatConfirmation, formatResult, CONFIRM_ACTIONS } = await import("./telegram-ai"); + + // Gather context for the AI + const firstMeshId = meshIds[0]!; + const firstConn = meshConnections.get(firstMeshId); + const meshSlug = chatMeshSlugs.get(chatId)?.[0]; + let recentPeers: string[] = []; + if (firstConn?.isConnected()) { + try { + const peers = await firstConn.listPeers(); + recentPeers = peers.map(p => p.displayName); + } catch {} + } + + const result = await processMessage(text, { + meshSlug, + userName: ctx.from?.first_name, + recentPeers, + }); + + if (result.type === "error") { + await ctx.reply(result.text ?? "Something went wrong."); + return; + } + + if (result.type === "text") { + await ctx.reply(result.text ?? ""); + return; + } + + if (result.type === "tool_call" && result.toolCall) { + if (result.requiresConfirmation) { + // Store pending action and show confirmation buttons + const actionId = `ai_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; + pendingAiActions.set(actionId, { + chatId, + meshIds, + toolCall: result.toolCall, + expiresAt: Date.now() + 5 * 60 * 1000, + }); + + const confirmText = formatConfirmation(result.toolCall); + await ctx.reply(confirmText, { + parse_mode: "MarkdownV2", + reply_markup: { + inline_keyboard: [ + [ + { text: "βœ… Confirm", callback_data: `ai_confirm:${actionId}` }, + { text: "✏️ Edit", callback_data: `ai_edit:${actionId}` }, + { text: "❌ Cancel", callback_data: `ai_cancel:${actionId}` }, + ], + ], + }, + }); + } else { + // Read-only action β€” execute immediately + const execResult = await executeAiToolCall(result.toolCall, meshIds); + await ctx.reply(formatResult(result.toolCall.name, execResult), { + parse_mode: "MarkdownV2", + }); + } + } + } catch (err) { + log.error("telegram-ai-handler", { error: err instanceof Error ? err.message : String(err) }); + // Fallback: broadcast the text directly + let sent = 0; + for (const meshId of meshIds) { + const conn = meshConnections.get(meshId); + if (!conn?.isConnected()) continue; + const ok = await conn.sendMessage("*", `[via Telegram] ${text}`, "next"); + if (ok) sent++; + } + if (sent === 0) await ctx.reply("❌ Not connected."); } }); } +// --------------------------------------------------------------------------- +// AI tool call executor +// --------------------------------------------------------------------------- + +async function executeAiToolCall( + toolCall: { name: string; input: Record }, + meshIds: string[], +): Promise { + const firstMeshId = meshIds[0]; + if (!firstMeshId) throw new Error("No mesh connected"); + + const conn = meshConnections.get(firstMeshId); + if (!conn?.isConnected()) throw new Error("Not connected to mesh"); + + switch (toolCall.name) { + case "send_message": { + const to = String(toolCall.input.to ?? "*"); + const message = String(toolCall.input.message ?? ""); + const priority = String(toolCall.input.priority ?? "next"); + + // Resolve peer name β†’ pubkey + let targetSpec = to; + if (!to.startsWith("@") && to !== "*" && !/^[0-9a-f]{64}$/.test(to)) { + const peers = await conn.listPeers(); + const match = peers.find(p => p.displayName.toLowerCase() === to.toLowerCase()); + if (!match) { + const partials = peers.filter(p => p.displayName.toLowerCase().includes(to.toLowerCase())); + if (partials.length === 1) targetSpec = partials[0]!.pubkey; + else throw new Error(`Peer "${to}" not found`); + } else { + targetSpec = match.pubkey; + } + } + + const ok = await conn.sendMessage(targetSpec, `[via Telegram] ${message}`, priority as "now" | "next" | "low"); + if (!ok) throw new Error("Send failed"); + return { ok: true }; + } + + case "list_peers": + return conn.listPeers(); + + case "remember": + case "recall": + case "get_state": + case "set_state": + // These operations require WS request/response patterns not yet + // implemented in MeshConnection. Coming in a future update. + throw new Error(`${toolCall.name} not yet available via Telegram. Use the CLI.`); + + default: + throw new Error(`Unknown tool: ${toolCall.name}`); + } +} + // --------------------------------------------------------------------------- // Ensure a mesh WS connection exists (create or reuse) // --------------------------------------------------------------------------- diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a45589e..5421112 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -110,6 +110,9 @@ importers: apps/broker: dependencies: + '@anthropic-ai/sdk': + specifier: 0.71.2 + version: 0.71.2(zod@4.1.13) '@qdrant/js-client-rest': specifier: 1.17.0 version: 1.17.0(typescript@5.9.3) @@ -121,7 +124,7 @@ importers: version: link:../../packages/shared drizzle-orm: specifier: 0.44.7 - version: 0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(better-sqlite3@12.4.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7) + version: 0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(better-sqlite3@12.4.1)(bun-types@1.3.12)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7) grammy: specifier: ^1.35.0 version: 1.42.0(encoding@0.1.13) @@ -221,6 +224,55 @@ importers: specifier: 'catalog:' version: 4.0.14(@opentelemetry/api@1.9.0)(@types/node@24.0.13)(@vitest/ui@4.0.14)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@26.0.0)(lightningcss@1.30.2)(sass@1.77.4)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.0) + apps/cli-v2: + dependencies: + '@modelcontextprotocol/sdk': + specifier: 1.27.1 + version: 1.27.1(zod@4.1.13) + citty: + specifier: 0.2.2 + version: 0.2.2 + libsodium-wrappers: + specifier: 0.7.15 + version: 0.7.15 + ws: + specifier: 8.20.0 + version: 8.20.0 + zod: + specifier: 4.1.13 + version: 4.1.13 + devDependencies: + '@turbostarter/eslint-config': + specifier: workspace:* + version: link:../../tooling/eslint + '@turbostarter/prettier-config': + specifier: workspace:* + version: link:../../tooling/prettier + '@turbostarter/tsconfig': + specifier: workspace:* + version: link:../../tooling/typescript + '@turbostarter/vitest-config': + specifier: workspace:* + version: link:../../tooling/vitest + '@types/libsodium-wrappers': + specifier: 0.7.14 + version: 0.7.14 + '@types/ws': + specifier: 8.5.13 + version: 8.5.13 + eslint: + specifier: 'catalog:' + version: 9.39.0(jiti@2.6.1) + prettier: + specifier: 'catalog:' + version: 3.6.2 + typescript: + specifier: 'catalog:' + version: 5.9.3 + vitest: + specifier: 'catalog:' + version: 4.0.14(@opentelemetry/api@1.9.0)(@types/node@24.0.13)(@vitest/ui@4.0.14)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@26.0.0)(lightningcss@1.30.2)(sass@1.77.4)(terser@5.43.1)(tsx@4.21.0)(yaml@2.8.0) + apps/telegram: dependencies: grammy: @@ -268,10 +320,10 @@ importers: version: 0.5.10(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@payloadcms/db-postgres': specifier: 3.81.0 - version: 3.81.0(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(better-sqlite3@12.4.1)(kysely@0.28.5)(payload@3.81.0(graphql@16.13.2)(typescript@5.9.3))(postgres@3.4.7) + version: 3.81.0(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(better-sqlite3@12.4.1)(bun-types@1.3.12)(kysely@0.28.5)(payload@3.81.0(graphql@16.13.2)(typescript@5.9.3))(postgres@3.4.7) '@payloadcms/db-sqlite': specifier: ^3.81.0 - version: 3.81.0(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(better-sqlite3@12.4.1)(kysely@0.28.5)(payload@3.81.0(graphql@16.13.2)(typescript@5.9.3))(pg@8.16.3)(postgres@3.4.7) + version: 3.81.0(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(better-sqlite3@12.4.1)(bun-types@1.3.12)(kysely@0.28.5)(payload@3.81.0(graphql@16.13.2)(typescript@5.9.3))(pg@8.16.3)(postgres@3.4.7) '@payloadcms/next': specifier: ^3.81.0 version: 3.81.0(@types/react@19.1.14)(graphql@16.13.2)(monaco-editor@0.55.1)(next@16.2.2(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.77.4))(payload@3.81.0(graphql@16.13.2)(typescript@5.9.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.9.3) @@ -801,10 +853,10 @@ importers: version: link:../shared drizzle-orm: specifier: 0.44.7 - version: 0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(better-sqlite3@12.4.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7) + version: 0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(better-sqlite3@12.4.1)(bun-types@1.3.12)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7) drizzle-zod: specifier: 0.8.3 - version: 0.8.3(drizzle-orm@0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(better-sqlite3@12.4.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7))(zod@4.1.13) + version: 0.8.3(drizzle-orm@0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(better-sqlite3@12.4.1)(bun-types@1.3.12)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7))(zod@4.1.13) envin: specifier: 'catalog:' version: 1.1.10(arktype@2.1.20)(typescript@5.9.3)(zod@4.1.13) @@ -829,7 +881,7 @@ importers: version: 0.31.7 drizzle-seed: specifier: 0.3.1 - version: 0.3.1(drizzle-orm@0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(better-sqlite3@12.4.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7)) + version: 0.3.1(drizzle-orm@0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(better-sqlite3@12.4.1)(bun-types@1.3.12)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7)) eslint: specifier: 'catalog:' version: 9.39.0(jiti@2.6.1) @@ -8241,6 +8293,9 @@ packages: buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + bun-types@1.3.12: + resolution: {integrity: sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA==} + busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} @@ -18253,13 +18308,13 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) - '@payloadcms/db-postgres@3.81.0(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(better-sqlite3@12.4.1)(kysely@0.28.5)(payload@3.81.0(graphql@16.13.2)(typescript@5.9.3))(postgres@3.4.7)': + '@payloadcms/db-postgres@3.81.0(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(better-sqlite3@12.4.1)(bun-types@1.3.12)(kysely@0.28.5)(payload@3.81.0(graphql@16.13.2)(typescript@5.9.3))(postgres@3.4.7)': dependencies: - '@payloadcms/drizzle': 3.81.0(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(better-sqlite3@12.4.1)(kysely@0.28.5)(payload@3.81.0(graphql@16.13.2)(typescript@5.9.3))(pg@8.16.3)(postgres@3.4.7) + '@payloadcms/drizzle': 3.81.0(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(better-sqlite3@12.4.1)(bun-types@1.3.12)(kysely@0.28.5)(payload@3.81.0(graphql@16.13.2)(typescript@5.9.3))(pg@8.16.3)(postgres@3.4.7) '@types/pg': 8.10.2 console-table-printer: 2.12.1 drizzle-kit: 0.31.7 - drizzle-orm: 0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(better-sqlite3@12.4.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7) + drizzle-orm: 0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(better-sqlite3@12.4.1)(bun-types@1.3.12)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7) payload: 3.81.0(graphql@16.13.2)(typescript@5.9.3) pg: 8.16.3 prompts: 2.4.2 @@ -18296,13 +18351,13 @@ snapshots: - sqlite3 - supports-color - '@payloadcms/db-sqlite@3.81.0(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(better-sqlite3@12.4.1)(kysely@0.28.5)(payload@3.81.0(graphql@16.13.2)(typescript@5.9.3))(pg@8.16.3)(postgres@3.4.7)': + '@payloadcms/db-sqlite@3.81.0(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(better-sqlite3@12.4.1)(bun-types@1.3.12)(kysely@0.28.5)(payload@3.81.0(graphql@16.13.2)(typescript@5.9.3))(pg@8.16.3)(postgres@3.4.7)': dependencies: '@libsql/client': 0.14.0 - '@payloadcms/drizzle': 3.81.0(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(better-sqlite3@12.4.1)(kysely@0.28.5)(payload@3.81.0(graphql@16.13.2)(typescript@5.9.3))(pg@8.16.3)(postgres@3.4.7) + '@payloadcms/drizzle': 3.81.0(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(better-sqlite3@12.4.1)(bun-types@1.3.12)(kysely@0.28.5)(payload@3.81.0(graphql@16.13.2)(typescript@5.9.3))(pg@8.16.3)(postgres@3.4.7) console-table-printer: 2.12.1 drizzle-kit: 0.31.7 - drizzle-orm: 0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(better-sqlite3@12.4.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7) + drizzle-orm: 0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(better-sqlite3@12.4.1)(bun-types@1.3.12)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7) payload: 3.81.0(graphql@16.13.2)(typescript@5.9.3) prompts: 2.4.2 to-snake-case: 1.0.0 @@ -18340,11 +18395,11 @@ snapshots: - supports-color - utf-8-validate - '@payloadcms/drizzle@3.81.0(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(better-sqlite3@12.4.1)(kysely@0.28.5)(payload@3.81.0(graphql@16.13.2)(typescript@5.9.3))(pg@8.16.3)(postgres@3.4.7)': + '@payloadcms/drizzle@3.81.0(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(better-sqlite3@12.4.1)(bun-types@1.3.12)(kysely@0.28.5)(payload@3.81.0(graphql@16.13.2)(typescript@5.9.3))(pg@8.16.3)(postgres@3.4.7)': dependencies: console-table-printer: 2.12.1 dequal: 2.0.3 - drizzle-orm: 0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(better-sqlite3@12.4.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7) + drizzle-orm: 0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(better-sqlite3@12.4.1)(bun-types@1.3.12)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7) payload: 3.81.0(graphql@16.13.2)(typescript@5.9.3) prompts: 2.4.2 to-snake-case: 1.0.0 @@ -21154,7 +21209,7 @@ snapshots: '@sentry/bundler-plugin-core': 4.6.1(encoding@0.1.13) unplugin: 1.0.1 uuid: 9.0.1 - webpack: 5.100.2(esbuild@0.25.0) + webpack: 5.100.2 transitivePeerDependencies: - encoding - supports-color @@ -23137,6 +23192,11 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + bun-types@1.3.12: + dependencies: + '@types/node': 24.0.13 + optional: true + busboy@1.6.0: dependencies: streamsearch: 1.1.0 @@ -23869,35 +23929,37 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(better-sqlite3@12.4.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7): + drizzle-orm@0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.10.2)(better-sqlite3@12.4.1)(bun-types@1.3.12)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7): optionalDependencies: '@libsql/client': 0.14.0 '@opentelemetry/api': 1.9.0 '@types/pg': 8.10.2 better-sqlite3: 12.4.1 + bun-types: 1.3.12 kysely: 0.28.5 pg: 8.16.3 postgres: 3.4.7 - drizzle-orm@0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(better-sqlite3@12.4.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7): + drizzle-orm@0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(better-sqlite3@12.4.1)(bun-types@1.3.12)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7): optionalDependencies: '@libsql/client': 0.14.0 '@opentelemetry/api': 1.9.0 '@types/pg': 8.16.0 better-sqlite3: 12.4.1 + bun-types: 1.3.12 kysely: 0.28.5 pg: 8.16.3 postgres: 3.4.7 - drizzle-seed@0.3.1(drizzle-orm@0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(better-sqlite3@12.4.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7)): + drizzle-seed@0.3.1(drizzle-orm@0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(better-sqlite3@12.4.1)(bun-types@1.3.12)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7)): dependencies: pure-rand: 6.1.0 optionalDependencies: - drizzle-orm: 0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(better-sqlite3@12.4.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7) + drizzle-orm: 0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(better-sqlite3@12.4.1)(bun-types@1.3.12)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7) - drizzle-zod@0.8.3(drizzle-orm@0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(better-sqlite3@12.4.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7))(zod@4.1.13): + drizzle-zod@0.8.3(drizzle-orm@0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(better-sqlite3@12.4.1)(bun-types@1.3.12)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7))(zod@4.1.13): dependencies: - drizzle-orm: 0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(better-sqlite3@12.4.1)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7) + drizzle-orm: 0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(better-sqlite3@12.4.1)(bun-types@1.3.12)(kysely@0.28.5)(pg@8.16.3)(postgres@3.4.7) zod: 4.1.13 dunder-proto@1.0.1: @@ -27217,7 +27279,7 @@ snapshots: postcss: 8.4.31 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - styled-jsx: 5.1.6(@babel/core@7.28.5)(react@19.2.3) + styled-jsx: 5.1.6(react@19.2.3) optionalDependencies: '@next/swc-darwin-arm64': 16.2.2 '@next/swc-darwin-x64': 16.2.2 @@ -29759,6 +29821,12 @@ snapshots: react: 19.2.3 optionalDependencies: '@babel/core': 7.28.5 + optional: true + + styled-jsx@5.1.6(react@19.2.3): + dependencies: + client-only: 0.0.1 + react: 19.2.3 styleq@0.1.3: optional: true @@ -29916,6 +29984,15 @@ snapshots: optionalDependencies: esbuild: 0.25.0 + terser-webpack-plugin@5.3.14(webpack@5.100.2): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.3.2 + serialize-javascript: 6.0.2 + terser: 5.43.1 + webpack: 5.100.2 + terser@5.43.1: dependencies: '@jridgewell/source-map': 0.3.10 @@ -30628,6 +30705,38 @@ snapshots: webpack-virtual-modules@0.5.0: {} + webpack@5.100.2: + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.16.0 + acorn-import-phases: 1.0.4(acorn@8.16.0) + browserslist: 4.25.1 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.18.3 + es-module-lexer: 1.7.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.0 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 4.3.2 + tapable: 2.2.2 + terser-webpack-plugin: 5.3.14(webpack@5.100.2) + watchpack: 2.4.4 + webpack-sources: 3.3.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + webpack@5.100.2(esbuild@0.25.0): dependencies: '@types/eslint-scope': 3.7.7