@loghead/core 0.1.18 → 0.1.20

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.
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.startApiServer = startApiServer;
7
7
  const express_1 = __importDefault(require("express"));
8
8
  const cors_1 = __importDefault(require("cors"));
9
+ const path_1 = __importDefault(require("path"));
9
10
  const auth_1 = require("../services/auth");
10
11
  const chalk_1 = __importDefault(require("chalk"));
11
12
  const auth = new auth_1.AuthService();
@@ -14,9 +15,106 @@ async function startApiServer(db) {
14
15
  const port = process.env.PORT || 4567;
15
16
  app.use((0, cors_1.default)());
16
17
  app.use(express_1.default.json());
18
+ // Serve static frontend files
19
+ // Determine path based on whether we are running in src (dev) or dist (prod)
20
+ let publicPath = path_1.default.join(__dirname, "../public");
21
+ if (!require("fs").existsSync(publicPath)) {
22
+ // Try looking in dist/public if we are in src
23
+ publicPath = path_1.default.join(__dirname, "../../dist/public");
24
+ }
25
+ if (require("fs").existsSync(publicPath)) {
26
+ app.use(express_1.default.static(publicPath));
27
+ }
28
+ else {
29
+ console.warn(chalk_1.default.yellow("Frontend build not found. Run 'npm run build' in packages/core/frontend to build the UI."));
30
+ }
17
31
  await auth.initialize();
18
- console.log(chalk_1.default.bold.green(`\nšŸ’» MCP server running on:`));
19
- console.log(chalk_1.default.green(`http://localhost:${port}`));
32
+ // console.log(chalk.bold.green(`\nšŸ’» API server running on:`));
33
+ // console.log(chalk.green(`http://localhost:${port}`));
34
+ // Helper to parse OTLP attributes
35
+ const parseOtlpAttributes = (attributes) => {
36
+ if (!Array.isArray(attributes))
37
+ return {};
38
+ const result = {};
39
+ for (const attr of attributes) {
40
+ if (attr.key && attr.value) {
41
+ // Extract value based on type (stringValue, intValue, boolValue, etc.)
42
+ const val = attr.value;
43
+ if (val.stringValue !== undefined)
44
+ result[attr.key] = val.stringValue;
45
+ else if (val.intValue !== undefined)
46
+ result[attr.key] = parseInt(val.intValue);
47
+ else if (val.doubleValue !== undefined)
48
+ result[attr.key] = val.doubleValue;
49
+ else if (val.boolValue !== undefined)
50
+ result[attr.key] = val.boolValue;
51
+ else if (val.arrayValue !== undefined)
52
+ result[attr.key] = val.arrayValue; // Simplified
53
+ else if (val.kvlistValue !== undefined)
54
+ result[attr.key] = val.kvlistValue; // Simplified
55
+ else
56
+ result[attr.key] = val;
57
+ }
58
+ }
59
+ return result;
60
+ };
61
+ // OTLP Logs Ingestion Endpoint
62
+ app.post("/v1/logs", async (req, res) => {
63
+ try {
64
+ const authHeader = req.headers.authorization;
65
+ if (!authHeader || !authHeader.startsWith("Bearer ")) {
66
+ return res.status(401).json({ code: 16, message: "Unauthenticated" });
67
+ }
68
+ const token = authHeader.split(" ")[1];
69
+ const payload = await auth.verifyToken(token);
70
+ if (!payload || !payload.streamId) {
71
+ return res.status(401).json({ code: 16, message: "Invalid token" });
72
+ }
73
+ const streamId = payload.streamId;
74
+ const { resourceLogs } = req.body;
75
+ if (!resourceLogs || !Array.isArray(resourceLogs)) {
76
+ return res.status(400).json({ code: 3, message: "Invalid payload" });
77
+ }
78
+ let count = 0;
79
+ for (const resourceLog of resourceLogs) {
80
+ const resourceAttrs = parseOtlpAttributes(resourceLog.resource?.attributes);
81
+ if (resourceLog.scopeLogs) {
82
+ for (const scopeLog of resourceLog.scopeLogs) {
83
+ const scopeName = scopeLog.scope?.name;
84
+ if (scopeLog.logRecords) {
85
+ for (const log of scopeLog.logRecords) {
86
+ let content = "";
87
+ if (log.body?.stringValue)
88
+ content = log.body.stringValue;
89
+ else if (log.body?.kvlistValue)
90
+ content = JSON.stringify(log.body.kvlistValue);
91
+ else if (typeof log.body === 'string')
92
+ content = log.body; // Fallback
93
+ const logAttrs = parseOtlpAttributes(log.attributes);
94
+ // Merge attributes: Resource > Scope (if any) > Log
95
+ const metadata = {
96
+ ...resourceAttrs,
97
+ ...logAttrs,
98
+ severity: log.severityText || log.severityNumber,
99
+ scope: scopeName,
100
+ timestamp: log.timeUnixNano
101
+ };
102
+ if (content) {
103
+ await db.addLog(streamId, content, metadata);
104
+ count++;
105
+ }
106
+ }
107
+ }
108
+ }
109
+ }
110
+ }
111
+ res.json({ partialSuccess: {}, logsIngested: count });
112
+ }
113
+ catch (e) {
114
+ console.error("OTLP Ingest error:", e);
115
+ res.status(500).json({ code: 13, message: String(e) });
116
+ }
117
+ });
20
118
  app.post("/api/ingest", async (req, res) => {
21
119
  try {
22
120
  const authHeader = req.headers.authorization;
@@ -57,6 +155,18 @@ async function startApiServer(db) {
57
155
  res.status(500).json({ error: String(e) });
58
156
  }
59
157
  });
158
+ app.get("/api/connection", async (req, res) => {
159
+ try {
160
+ const token = await auth.getOrCreateMcpToken();
161
+ res.json({
162
+ token,
163
+ mcpUrl: `http://localhost:${port}/sse` // Assuming default MCP behavior or just provide base URL
164
+ });
165
+ }
166
+ catch (e) {
167
+ res.status(500).json({ error: String(e) });
168
+ }
169
+ });
60
170
  app.get("/api/projects", (req, res) => {
61
171
  const projects = db.listProjects();
62
172
  res.json(projects);
@@ -88,6 +198,16 @@ async function startApiServer(db) {
88
198
  db.deleteStream(id);
89
199
  res.json({ success: true });
90
200
  });
201
+ app.get("/api/streams/:id/token", async (req, res) => {
202
+ const { id } = req.params;
203
+ try {
204
+ const token = await auth.createStreamToken(id);
205
+ res.json({ token });
206
+ }
207
+ catch (e) {
208
+ res.status(500).json({ error: String(e) });
209
+ }
210
+ });
91
211
  app.post("/api/streams", (req, res) => {
92
212
  // Deprecated or just listing? The previous code had this returning listStreams for POST?
93
213
  // I'll remove it or keep it if CLI uses it?
@@ -132,6 +252,18 @@ async function startApiServer(db) {
132
252
  }
133
253
  res.json(logs);
134
254
  });
255
+ // SPA fallback
256
+ app.get("*", (req, res) => {
257
+ if (req.path.startsWith("/api")) {
258
+ return res.status(404).json({ error: "Not Found" });
259
+ }
260
+ if (require("fs").existsSync(path_1.default.join(publicPath, "index.html"))) {
261
+ res.sendFile(path_1.default.join(publicPath, "index.html"));
262
+ }
263
+ else {
264
+ res.status(404).send("Dashboard not found. Please build the frontend.");
265
+ }
266
+ });
135
267
  app.listen(port, () => {
136
268
  // listening
137
269
  });
package/dist/cli_main.js CHANGED
@@ -10,21 +10,40 @@ const db_1 = require("./services/db");
10
10
  const server_1 = require("./api/server");
11
11
  const migrate_1 = require("./db/migrate");
12
12
  // import { ensureInfrastructure } from "./utils/startup"; // Might need adjustment
13
- const main_1 = require("./ui/main");
14
13
  const auth_1 = require("./services/auth");
14
+ const chalk_1 = __importDefault(require("chalk"));
15
+ const open_1 = __importDefault(require("open"));
15
16
  const db = new db_1.DbService();
16
17
  const auth = new auth_1.AuthService();
17
18
  async function main() {
18
19
  const argv = await (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
19
20
  .command(["start", "$0"], "Start API Server", {}, async () => {
20
- console.log("Ensuring database is initialized...");
21
+ // console.log("Ensuring database is initialized...");
21
22
  await (0, migrate_1.migrate)(false); // Run migrations silently
22
23
  const token = await auth.getOrCreateMcpToken();
23
24
  // Start API Server (this sets up express listen)
24
25
  await (0, server_1.startApiServer)(db);
25
- // Start TUI (this will clear screen and take over)
26
- await (0, main_1.startTui)(db, token);
27
- process.exit(0);
26
+ console.clear();
27
+ console.log(chalk_1.default.bold.green(`
28
+ __ __ __
29
+ / / ___ ___ ____ / / ___ ___ ____ ___/ /
30
+ / /__/ _ \\/ _ \`/ _ \\/ _ \\/ -_) _ \`/ _ \\/ _ /
31
+ /____/\\___/\\_, /_//_/_//_/\\__/\\_,_/\\___/\\_,_/
32
+ /___/
33
+ `));
34
+ console.log(chalk_1.default.gray("--------------------------------------------------"));
35
+ console.log(chalk_1.default.bold(" 🟢 Loghead is running"));
36
+ console.log(chalk_1.default.gray("--------------------------------------------------"));
37
+ console.log("");
38
+ console.log(chalk_1.default.bold(" šŸ–„ļø Dashboard : ") + chalk_1.default.cyan("http://localhost:4567"));
39
+ console.log(chalk_1.default.bold(" šŸ”Œ MCP Server: ") + chalk_1.default.cyan("http://localhost:4567/sse"));
40
+ console.log("");
41
+ console.log(chalk_1.default.bold(" šŸ”‘ MCP Token : "));
42
+ console.log(chalk_1.default.yellow(token));
43
+ console.log("");
44
+ console.log(chalk_1.default.gray("--------------------------------------------------"));
45
+ console.log(chalk_1.default.gray(" Press Ctrl+C to stop"));
46
+ (0, open_1.default)("http://localhost:4567");
28
47
  })
29
48
  .command("projects <cmd> [name]", "Manage projects", (yargs) => {
30
49
  yargs
@@ -0,0 +1 @@
1
+ *,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.left-0{left:0}.right-2{right:.5rem}.top-0{top:0}.top-2{top:.5rem}.top-full{top:100%}.z-50{z-index:50}.z-\[100\]{z-index:100}.col-span-full{grid-column:1 / -1}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mr-4{margin-right:1rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.block{display:block}.flex{display:flex}.grid{display:grid}.h-12{height:3rem}.h-16{height:4rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-8{height:2rem}.h-\[50vh\]{height:50vh}.min-h-\[60px\]{min-height:60px}.min-h-screen{min-height:100vh}.w-12{width:3rem}.w-16{width:4rem}.w-2{width:.5rem}.w-3{width:.75rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-64{width:16rem}.w-8{width:2rem}.w-full{width:100%}.max-w-7xl{max-width:80rem}.max-w-lg{max-width:32rem}.max-w-sm{max-width:24rem}.flex-1{flex:1 1 0%}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.select-all{-webkit-user-select:all;-moz-user-select:all;user-select:all}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-2{gap:.5rem}.gap-4{gap:1rem}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-current{border-color:currentColor}.border-gray-800{--tw-border-opacity: 1;border-color:rgb(31 41 55 / var(--tw-border-opacity, 1))}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1))}.bg-black\/50{background-color:#00000080}.bg-black\/80{background-color:#000c}.bg-blue-500\/20{background-color:#3b82f633}.bg-gray-800{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity, 1))}.bg-gray-900\/30{background-color:#1118274d}.bg-gray-900\/50{background-color:#11182780}.bg-gray-950{--tw-bg-opacity: 1;background-color:rgb(3 7 18 / var(--tw-bg-opacity, 1))}.bg-gray-950\/50{background-color:#03071280}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-purple-500\/20{background-color:#a855f733}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-gradient-to-tr{background-image:linear-gradient(to top right,var(--tw-gradient-stops))}.from-green-500{--tw-gradient-from: #22c55e var(--tw-gradient-from-position);--tw-gradient-to: rgb(34 197 94 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.to-emerald-600{--tw-gradient-to: #059669 var(--tw-gradient-to-position)}.p-1\.5{padding:.375rem}.p-12{padding:3rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.pb-4{padding-bottom:1rem}.pr-12{padding-right:3rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-sans{font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[10px\]{font-size:10px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.tracking-tight{letter-spacing:-.025em}.tracking-wider{letter-spacing:.05em}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-gray-100{--tw-text-opacity: 1;color:rgb(243 244 246 / var(--tw-text-opacity, 1))}.text-gray-200{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-purple-400{--tw-text-opacity: 1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.opacity-20{opacity:.2}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.backdrop-blur-sm{--tw-backdrop-blur: blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-100{transition-duration:.1s}.duration-200{transition-duration:.2s}.hover\:border-gray-700:hover{--tw-border-opacity: 1;border-color:rgb(55 65 81 / var(--tw-border-opacity, 1))}.hover\:bg-gray-200:hover{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-700:hover{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-800:hover{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-900:hover{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity, 1))}.hover\:text-gray-200:hover{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.group:hover .group-hover\:bg-gray-700{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.group:hover .group-hover\:text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}@media (min-width: 640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width: 1024px){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}