channelkit 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +829 -0
  3. package/config.example.yaml +37 -0
  4. package/dist/api/middleware/auth.d.ts +14 -0
  5. package/dist/api/middleware/auth.d.ts.map +1 -0
  6. package/dist/api/middleware/auth.js +130 -0
  7. package/dist/api/middleware/auth.js.map +1 -0
  8. package/dist/api/routes/config.d.ts +4 -0
  9. package/dist/api/routes/config.d.ts.map +1 -0
  10. package/dist/api/routes/config.js +794 -0
  11. package/dist/api/routes/config.js.map +1 -0
  12. package/dist/api/routes/dashboard.d.ts +4 -0
  13. package/dist/api/routes/dashboard.d.ts.map +1 -0
  14. package/dist/api/routes/dashboard.js +89 -0
  15. package/dist/api/routes/dashboard.js.map +1 -0
  16. package/dist/api/routes/inbound.d.ts +4 -0
  17. package/dist/api/routes/inbound.d.ts.map +1 -0
  18. package/dist/api/routes/inbound.js +293 -0
  19. package/dist/api/routes/inbound.js.map +1 -0
  20. package/dist/api/routes/logs.d.ts +4 -0
  21. package/dist/api/routes/logs.d.ts.map +1 -0
  22. package/dist/api/routes/logs.js +49 -0
  23. package/dist/api/routes/logs.js.map +1 -0
  24. package/dist/api/routes/mcp.d.ts +4 -0
  25. package/dist/api/routes/mcp.d.ts.map +1 -0
  26. package/dist/api/routes/mcp.js +100 -0
  27. package/dist/api/routes/mcp.js.map +1 -0
  28. package/dist/api/routes/restart.d.ts +4 -0
  29. package/dist/api/routes/restart.d.ts.map +1 -0
  30. package/dist/api/routes/restart.js +11 -0
  31. package/dist/api/routes/restart.js.map +1 -0
  32. package/dist/api/routes/send.d.ts +4 -0
  33. package/dist/api/routes/send.d.ts.map +1 -0
  34. package/dist/api/routes/send.js +66 -0
  35. package/dist/api/routes/send.js.map +1 -0
  36. package/dist/api/routes/settings.d.ts +4 -0
  37. package/dist/api/routes/settings.d.ts.map +1 -0
  38. package/dist/api/routes/settings.js +133 -0
  39. package/dist/api/routes/settings.js.map +1 -0
  40. package/dist/api/routes/tunnel.d.ts +4 -0
  41. package/dist/api/routes/tunnel.d.ts.map +1 -0
  42. package/dist/api/routes/tunnel.js +209 -0
  43. package/dist/api/routes/tunnel.js.map +1 -0
  44. package/dist/api/routes/twilio.d.ts +4 -0
  45. package/dist/api/routes/twilio.d.ts.map +1 -0
  46. package/dist/api/routes/twilio.js +138 -0
  47. package/dist/api/routes/twilio.js.map +1 -0
  48. package/dist/api/routes/update.d.ts +4 -0
  49. package/dist/api/routes/update.d.ts.map +1 -0
  50. package/dist/api/routes/update.js +42 -0
  51. package/dist/api/routes/update.js.map +1 -0
  52. package/dist/api/server.d.ts +52 -0
  53. package/dist/api/server.d.ts.map +1 -0
  54. package/dist/api/server.js +415 -0
  55. package/dist/api/server.js.map +1 -0
  56. package/dist/api/types.d.ts +61 -0
  57. package/dist/api/types.d.ts.map +1 -0
  58. package/dist/api/types.js +3 -0
  59. package/dist/api/types.js.map +1 -0
  60. package/dist/channels/base.d.ts +15 -0
  61. package/dist/channels/base.d.ts.map +1 -0
  62. package/dist/channels/base.js +20 -0
  63. package/dist/channels/base.js.map +1 -0
  64. package/dist/channels/email/gmail.d.ts +36 -0
  65. package/dist/channels/email/gmail.d.ts.map +1 -0
  66. package/dist/channels/email/gmail.js +351 -0
  67. package/dist/channels/email/gmail.js.map +1 -0
  68. package/dist/channels/email/index.d.ts +3 -0
  69. package/dist/channels/email/index.d.ts.map +1 -0
  70. package/dist/channels/email/index.js +8 -0
  71. package/dist/channels/email/index.js.map +1 -0
  72. package/dist/channels/email/resend.d.ts +29 -0
  73. package/dist/channels/email/resend.d.ts.map +1 -0
  74. package/dist/channels/email/resend.js +155 -0
  75. package/dist/channels/email/resend.js.map +1 -0
  76. package/dist/channels/endpoint/index.d.ts +21 -0
  77. package/dist/channels/endpoint/index.d.ts.map +1 -0
  78. package/dist/channels/endpoint/index.js +80 -0
  79. package/dist/channels/endpoint/index.js.map +1 -0
  80. package/dist/channels/sms/index.d.ts +37 -0
  81. package/dist/channels/sms/index.d.ts.map +1 -0
  82. package/dist/channels/sms/index.js +163 -0
  83. package/dist/channels/sms/index.js.map +1 -0
  84. package/dist/channels/telegram/index.d.ts +24 -0
  85. package/dist/channels/telegram/index.d.ts.map +1 -0
  86. package/dist/channels/telegram/index.js +231 -0
  87. package/dist/channels/telegram/index.js.map +1 -0
  88. package/dist/channels/voice/index.d.ts +62 -0
  89. package/dist/channels/voice/index.d.ts.map +1 -0
  90. package/dist/channels/voice/index.js +286 -0
  91. package/dist/channels/voice/index.js.map +1 -0
  92. package/dist/channels/whatsapp/index.d.ts +31 -0
  93. package/dist/channels/whatsapp/index.d.ts.map +1 -0
  94. package/dist/channels/whatsapp/index.js +383 -0
  95. package/dist/channels/whatsapp/index.js.map +1 -0
  96. package/dist/cli/commands/demo.d.ts +4 -0
  97. package/dist/cli/commands/demo.d.ts.map +1 -0
  98. package/dist/cli/commands/demo.js +55 -0
  99. package/dist/cli/commands/demo.js.map +1 -0
  100. package/dist/cli/commands/init.d.ts +2 -0
  101. package/dist/cli/commands/init.d.ts.map +1 -0
  102. package/dist/cli/commands/init.js +254 -0
  103. package/dist/cli/commands/init.js.map +1 -0
  104. package/dist/cli/commands/install-skill.d.ts +4 -0
  105. package/dist/cli/commands/install-skill.d.ts.map +1 -0
  106. package/dist/cli/commands/install-skill.js +60 -0
  107. package/dist/cli/commands/install-skill.js.map +1 -0
  108. package/dist/cli/commands/send.d.ts +5 -0
  109. package/dist/cli/commands/send.d.ts.map +1 -0
  110. package/dist/cli/commands/send.js +46 -0
  111. package/dist/cli/commands/send.js.map +1 -0
  112. package/dist/cli/commands/start.d.ts +5 -0
  113. package/dist/cli/commands/start.d.ts.map +1 -0
  114. package/dist/cli/commands/start.js +129 -0
  115. package/dist/cli/commands/start.js.map +1 -0
  116. package/dist/cli/helpers.d.ts +26 -0
  117. package/dist/cli/helpers.d.ts.map +1 -0
  118. package/dist/cli/helpers.js +120 -0
  119. package/dist/cli/helpers.js.map +1 -0
  120. package/dist/cli/index.d.ts +3 -0
  121. package/dist/cli/index.d.ts.map +1 -0
  122. package/dist/cli/index.js +282 -0
  123. package/dist/cli/index.js.map +1 -0
  124. package/dist/cli/wizards/channel.d.ts +4 -0
  125. package/dist/cli/wizards/channel.d.ts.map +1 -0
  126. package/dist/cli/wizards/channel.js +285 -0
  127. package/dist/cli/wizards/channel.js.map +1 -0
  128. package/dist/cli/wizards/provision.d.ts +4 -0
  129. package/dist/cli/wizards/provision.d.ts.map +1 -0
  130. package/dist/cli/wizards/provision.js +213 -0
  131. package/dist/cli/wizards/provision.js.map +1 -0
  132. package/dist/cli/wizards/service.d.ts +5 -0
  133. package/dist/cli/wizards/service.d.ts.map +1 -0
  134. package/dist/cli/wizards/service.js +212 -0
  135. package/dist/cli/wizards/service.js.map +1 -0
  136. package/dist/cli.d.ts +3 -0
  137. package/dist/cli.d.ts.map +1 -0
  138. package/dist/cli.js +6 -0
  139. package/dist/cli.js.map +1 -0
  140. package/dist/config/parser.d.ts +6 -0
  141. package/dist/config/parser.d.ts.map +1 -0
  142. package/dist/config/parser.js +37 -0
  143. package/dist/config/parser.js.map +1 -0
  144. package/dist/config/types.d.ts +170 -0
  145. package/dist/config/types.d.ts.map +1 -0
  146. package/dist/config/types.js +3 -0
  147. package/dist/config/types.js.map +1 -0
  148. package/dist/core/apiServer.d.ts +2 -0
  149. package/dist/core/apiServer.d.ts.map +1 -0
  150. package/dist/core/apiServer.js +7 -0
  151. package/dist/core/apiServer.js.map +1 -0
  152. package/dist/core/groupStore.d.ts +19 -0
  153. package/dist/core/groupStore.d.ts.map +1 -0
  154. package/dist/core/groupStore.js +48 -0
  155. package/dist/core/groupStore.js.map +1 -0
  156. package/dist/core/logger.d.ts +42 -0
  157. package/dist/core/logger.d.ts.map +1 -0
  158. package/dist/core/logger.js +142 -0
  159. package/dist/core/logger.js.map +1 -0
  160. package/dist/core/messageHandler.d.ts +15 -0
  161. package/dist/core/messageHandler.d.ts.map +1 -0
  162. package/dist/core/messageHandler.js +309 -0
  163. package/dist/core/messageHandler.js.map +1 -0
  164. package/dist/core/restart.d.ts +3 -0
  165. package/dist/core/restart.d.ts.map +1 -0
  166. package/dist/core/restart.js +35 -0
  167. package/dist/core/restart.js.map +1 -0
  168. package/dist/core/router.d.ts +56 -0
  169. package/dist/core/router.d.ts.map +1 -0
  170. package/dist/core/router.js +168 -0
  171. package/dist/core/router.js.map +1 -0
  172. package/dist/core/tunnel.d.ts +16 -0
  173. package/dist/core/tunnel.d.ts.map +1 -0
  174. package/dist/core/tunnel.js +99 -0
  175. package/dist/core/tunnel.js.map +1 -0
  176. package/dist/core/types.d.ts +54 -0
  177. package/dist/core/types.d.ts.map +1 -0
  178. package/dist/core/types.js +3 -0
  179. package/dist/core/types.js.map +1 -0
  180. package/dist/core/updater.d.ts +44 -0
  181. package/dist/core/updater.d.ts.map +1 -0
  182. package/dist/core/updater.js +264 -0
  183. package/dist/core/updater.js.map +1 -0
  184. package/dist/core/webhook.d.ts +26 -0
  185. package/dist/core/webhook.d.ts.map +1 -0
  186. package/dist/core/webhook.js +224 -0
  187. package/dist/core/webhook.js.map +1 -0
  188. package/dist/dashboard/assets/browser-D_-rzKir.js +8 -0
  189. package/dist/dashboard/assets/index-CNa084vI.js +88 -0
  190. package/dist/dashboard/assets/index-CRvIEyjF.css +1 -0
  191. package/dist/dashboard/index.html +17 -0
  192. package/dist/index.d.ts +22 -0
  193. package/dist/index.d.ts.map +1 -0
  194. package/dist/index.js +551 -0
  195. package/dist/index.js.map +1 -0
  196. package/dist/mcp/index.d.ts +3 -0
  197. package/dist/mcp/index.d.ts.map +1 -0
  198. package/dist/mcp/index.js +6 -0
  199. package/dist/mcp/index.js.map +1 -0
  200. package/dist/mcp/server.d.ts +45 -0
  201. package/dist/mcp/server.d.ts.map +1 -0
  202. package/dist/mcp/server.js +197 -0
  203. package/dist/mcp/server.js.map +1 -0
  204. package/dist/mcp/tools.d.ts +16 -0
  205. package/dist/mcp/tools.d.ts.map +1 -0
  206. package/dist/mcp/tools.js +502 -0
  207. package/dist/mcp/tools.js.map +1 -0
  208. package/dist/media/formatter.d.ts +12 -0
  209. package/dist/media/formatter.d.ts.map +1 -0
  210. package/dist/media/formatter.js +147 -0
  211. package/dist/media/formatter.js.map +1 -0
  212. package/dist/media/processor.d.ts +33 -0
  213. package/dist/media/processor.d.ts.map +1 -0
  214. package/dist/media/processor.js +145 -0
  215. package/dist/media/processor.js.map +1 -0
  216. package/dist/media/stt.d.ts +16 -0
  217. package/dist/media/stt.d.ts.map +1 -0
  218. package/dist/media/stt.js +298 -0
  219. package/dist/media/stt.js.map +1 -0
  220. package/dist/media/tts.d.ts +19 -0
  221. package/dist/media/tts.d.ts.map +1 -0
  222. package/dist/media/tts.js +135 -0
  223. package/dist/media/tts.js.map +1 -0
  224. package/dist/onboarding/index.d.ts +28 -0
  225. package/dist/onboarding/index.d.ts.map +1 -0
  226. package/dist/onboarding/index.js +144 -0
  227. package/dist/onboarding/index.js.map +1 -0
  228. package/dist/paths.d.ts +9 -0
  229. package/dist/paths.d.ts.map +1 -0
  230. package/dist/paths.js +14 -0
  231. package/dist/paths.js.map +1 -0
  232. package/dist/provisioning/twilio.d.ts +51 -0
  233. package/dist/provisioning/twilio.d.ts.map +1 -0
  234. package/dist/provisioning/twilio.js +175 -0
  235. package/dist/provisioning/twilio.js.map +1 -0
  236. package/echo-server.js +163 -0
  237. package/package.json +79 -0
package/echo-server.js ADDED
@@ -0,0 +1,163 @@
1
+ const http = require("http");
2
+ const fs = require("fs");
3
+ const path = require("path");
4
+
5
+ const UPLOADS_DIR = path.join(__dirname, "uploads");
6
+ if (!fs.existsSync(UPLOADS_DIR)) fs.mkdirSync(UPLOADS_DIR);
7
+
8
+ /**
9
+ * Parse a multipart/form-data body and return { fields, files }.
10
+ * fields: { name: string_value }
11
+ * files: { name: { filename, mimetype, data: Buffer } }
12
+ */
13
+ function parseMultipart(buf, boundary) {
14
+ const fields = {};
15
+ const files = {};
16
+ const sep = Buffer.from(`--${boundary}`);
17
+
18
+ // Split on boundary
19
+ let start = 0;
20
+ const parts = [];
21
+ while (true) {
22
+ const idx = buf.indexOf(sep, start);
23
+ if (idx === -1) break;
24
+ if (start > 0) parts.push(buf.slice(start, idx));
25
+ start = idx + sep.length;
26
+ // skip \r\n after boundary
27
+ if (buf[start] === 0x0d && buf[start + 1] === 0x0a) start += 2;
28
+ // closing --
29
+ if (buf[start] === 0x2d && buf[start + 1] === 0x2d) break;
30
+ }
31
+
32
+ for (const part of parts) {
33
+ // Find the blank line separating headers from body (\r\n\r\n)
34
+ const headerEnd = part.indexOf("\r\n\r\n");
35
+ if (headerEnd === -1) continue;
36
+ const headerStr = part.slice(0, headerEnd).toString();
37
+ // Body is between header end and trailing \r\n
38
+ let body = part.slice(headerEnd + 4);
39
+ if (body.length >= 2 && body[body.length - 2] === 0x0d && body[body.length - 1] === 0x0a) {
40
+ body = body.slice(0, -2);
41
+ }
42
+
43
+ const nameMatch = headerStr.match(/name="([^"]+)"/);
44
+ if (!nameMatch) continue;
45
+ const name = nameMatch[1];
46
+
47
+ const filenameMatch = headerStr.match(/filename="([^"]+)"/);
48
+ if (filenameMatch) {
49
+ const mimeMatch = headerStr.match(/Content-Type:\s*(.+)/i);
50
+ files[name] = {
51
+ filename: filenameMatch[1],
52
+ mimetype: mimeMatch ? mimeMatch[1].trim() : "application/octet-stream",
53
+ data: body,
54
+ };
55
+ } else {
56
+ fields[name] = body.toString();
57
+ }
58
+ }
59
+ return { fields, files };
60
+ }
61
+
62
+ function handleRequest(req, msg, savedFile) {
63
+ const channel = msg.channel || "?";
64
+ const text = msg.text || "(no text)";
65
+
66
+ console.log(`\n${"=".repeat(60)}`);
67
+ console.log(`📨 Incoming Request`);
68
+ console.log(`${"=".repeat(60)}`);
69
+ console.log(` Method: ${req.method}`);
70
+ console.log(` URL: ${req.url}`);
71
+ console.log(` Headers:`);
72
+ for (const [key, value] of Object.entries(req.headers)) {
73
+ console.log(` ${key}: ${value}`);
74
+ }
75
+ console.log(`\n Body (parsed):`);
76
+ console.log(JSON.stringify(msg, null, 2).split("\n").map(l => ` ${l}`).join("\n"));
77
+ if (savedFile) console.log(`\n 💾 Saved attachment: ${savedFile}`);
78
+
79
+ let reply;
80
+ if (req.url.startsWith("/expenses")) {
81
+ reply = { text: "💰 Onkosto: got your message!" };
82
+ } else if (req.url.startsWith("/home")) {
83
+ reply = { text: "🏠 Smart Home: got your message!" };
84
+ } else if (req.url.startsWith("/support")) {
85
+ // Demo: echo back with voice (triggers TTS if configured)
86
+ reply = { text: `You said: ${text}`, voice: true };
87
+ } else if (req.url.startsWith("/email")) {
88
+ reply = {
89
+ text: `Thanks for your email!`,
90
+ email: { subject: `Re: ${msg.email?.subject || "Your message"}` },
91
+ };
92
+ } else if (channel === "voice") {
93
+ // Voice: respond with text (will be spoken back via TTS or <Say>)
94
+ reply = { text: `You said: ${text}`, voice: true };
95
+ } else {
96
+ reply = { text: `Echo [${channel}]: ${text}` };
97
+ }
98
+
99
+ if (savedFile) {
100
+ reply.text += ` (attachment saved: ${path.basename(savedFile)})`;
101
+ }
102
+
103
+ console.log(`📤 ${reply.text}${reply.voice ? " 🔊" : ""}`);
104
+ return reply;
105
+ }
106
+
107
+ const server = http.createServer((req, res) => {
108
+ const chunks = [];
109
+ req.on("data", (c) => chunks.push(c));
110
+ req.on("end", () => {
111
+ const contentType = req.headers["content-type"] || "";
112
+ let msg = {};
113
+ let savedFile = null;
114
+
115
+ if (contentType.includes("multipart/form-data")) {
116
+ // Parse multipart — ChannelKit sends: metadata (JSON) + file (binary)
117
+ const boundaryMatch = contentType.match(/boundary=(.+)/);
118
+ if (boundaryMatch) {
119
+ const buf = Buffer.concat(chunks);
120
+ const { fields, files } = parseMultipart(buf, boundaryMatch[1]);
121
+
122
+ if (fields.metadata) {
123
+ try { msg = JSON.parse(fields.metadata); } catch {}
124
+ }
125
+
126
+ if (files.file) {
127
+ const { filename, mimetype, data } = files.file;
128
+ const safeName = `${Date.now()}-${filename.replace(/[^a-zA-Z0-9._-]/g, "_")}`;
129
+ const dest = path.join(UPLOADS_DIR, safeName);
130
+ fs.writeFileSync(dest, data);
131
+ savedFile = dest;
132
+
133
+ // Populate media info on msg so logging picks it up
134
+ msg.media = { mimetype, filename };
135
+ }
136
+ }
137
+ } else {
138
+ // JSON body (the normal path)
139
+ const raw = Buffer.concat(chunks).toString();
140
+ if (raw.length) {
141
+ try { msg = JSON.parse(raw); } catch {}
142
+ }
143
+ }
144
+
145
+ const reply = handleRequest(req, msg, savedFile);
146
+ res.writeHead(200, { "Content-Type": "application/json" });
147
+ res.end(JSON.stringify(reply));
148
+ });
149
+ });
150
+
151
+ const PORT = process.env.PORT || 3000;
152
+ server.listen(PORT, () => {
153
+ console.log(`🔗 Echo server listening on port ${PORT}\n`);
154
+ console.log(" Routes:");
155
+ console.log(" /expenses → Onkosto (text reply)");
156
+ console.log(" /home → Smart Home (text reply)");
157
+ console.log(" /support → Support (voice reply — TTS)");
158
+ console.log(" /email → Email (reply with subject)");
159
+ console.log(" /* → Generic echo");
160
+ console.log(" voice → Echo with TTS 🔊\n");
161
+ console.log(" Supports: WhatsApp, Telegram, Gmail, Resend, SMS, Voice");
162
+ console.log(` Attachments saved to: ${UPLOADS_DIR}\n`);
163
+ });
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "channelkit",
3
+ "version": "1.0.0",
4
+ "description": "A self-hosted messaging gateway that connects chat channels to any application via webhooks",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "channelkit": "dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist/",
11
+ "config.example.yaml",
12
+ "echo-server.js",
13
+ "LICENSE"
14
+ ],
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "scripts": {
19
+ "build": "tsc && cd src/dashboard && npx vite build",
20
+ "build:dashboard": "cd src/dashboard && npx vite build",
21
+ "start": "NODE_NO_WARNINGS=1 tsx src/cli.ts start",
22
+ "dev": "npm run build:dashboard && NODE_NO_WARNINGS=1 tsx watch src/cli.ts start",
23
+ "init": "NODE_NO_WARNINGS=1 tsx src/cli.ts init",
24
+ "service": "NODE_NO_WARNINGS=1 tsx src/cli.ts service",
25
+ "channel": "NODE_NO_WARNINGS=1 tsx src/cli.ts channel",
26
+ "demo": "NODE_NO_WARNINGS=1 tsx src/cli.ts demo",
27
+ "postinstall": "echo \"\n \u2705 ChannelKit installed!\n\n Run 'channelkit' to get started.\n\""
28
+ },
29
+ "keywords": [
30
+ "whatsapp",
31
+ "telegram",
32
+ "messaging",
33
+ "gateway",
34
+ "webhook",
35
+ "chatbot"
36
+ ],
37
+ "author": "dirbalak",
38
+ "license": "MIT",
39
+ "preferGlobal": true,
40
+ "dependencies": {
41
+ "@hapi/boom": "^10.0.1",
42
+ "@modelcontextprotocol/sdk": "^1.27.1",
43
+ "@types/better-sqlite3": "^7.6.13",
44
+ "@types/express": "^5.0.6",
45
+ "@types/hapi__boom": "^7.4.1",
46
+ "@types/node": "^25.3.0",
47
+ "@types/ws": "^8.18.1",
48
+ "@whiskeysockets/baileys": "^7.0.0-rc.9",
49
+ "better-sqlite3": "^12.6.2",
50
+ "cloudflared": "^0.7.1",
51
+ "commander": "^14.0.3",
52
+ "dotenv": "^17.3.1",
53
+ "express": "^5.2.1",
54
+ "express-rate-limit": "^8.3.0",
55
+ "grammy": "^1.40.0",
56
+ "helmet": "^8.1.0",
57
+ "qrcode": "^1.5.4",
58
+ "qrcode-terminal": "^0.12.0",
59
+ "react": "^19.2.4",
60
+ "react-dom": "^19.2.4",
61
+ "tsx": "^4.21.0",
62
+ "twilio": "^5.12.2",
63
+ "typescript": "^5.9.3",
64
+ "ws": "^8.19.0",
65
+ "yaml": "^2.8.2"
66
+ },
67
+ "peerDependenciesMeta": {
68
+ "@whiskeysockets/baileys": {
69
+ "optional": true
70
+ }
71
+ },
72
+ "devDependencies": {
73
+ "@tailwindcss/vite": "^4.2.1",
74
+ "@types/qrcode": "^1.5.6",
75
+ "@vitejs/plugin-react": "^4.7.0",
76
+ "tailwindcss": "^4.2.1",
77
+ "vite": "^6.4.1"
78
+ }
79
+ }