datapeek 0.1.5 → 0.1.7

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.
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env node
2
+
3
+
4
+ // src/server/db/mssql.ts
5
+ import sql from "mssql";
6
+ var pool = null;
7
+ function parseConnectionString(connectionString) {
8
+ const config = {
9
+ server: "",
10
+ database: "",
11
+ options: {
12
+ encrypt: true,
13
+ trustServerCertificate: false,
14
+ enableArithAbort: true
15
+ }
16
+ };
17
+ const parts = connectionString.split(";");
18
+ for (const part of parts) {
19
+ const [key, ...valueParts] = part.split("=");
20
+ const value = valueParts.join("=").trim();
21
+ const keyLower = key.trim().toLowerCase();
22
+ switch (keyLower) {
23
+ case "server":
24
+ case "data source":
25
+ let serverValue = value;
26
+ if (serverValue.startsWith("tcp:")) {
27
+ serverValue = serverValue.substring(4);
28
+ }
29
+ const [serverHost, serverPort] = serverValue.split(",");
30
+ config.server = serverHost.trim();
31
+ if (serverPort) {
32
+ config.port = parseInt(serverPort.trim(), 10);
33
+ }
34
+ break;
35
+ case "database":
36
+ case "initial catalog":
37
+ config.database = value;
38
+ break;
39
+ case "user id":
40
+ case "userid":
41
+ case "uid":
42
+ config.user = value;
43
+ break;
44
+ case "password":
45
+ case "pwd":
46
+ config.password = value;
47
+ break;
48
+ case "port":
49
+ config.port = parseInt(value, 10);
50
+ break;
51
+ case "encrypt":
52
+ config.options.encrypt = value.toLowerCase() === "true";
53
+ break;
54
+ case "trustservercertificate":
55
+ case "trust server certificate":
56
+ config.options.trustServerCertificate = value.toLowerCase() === "true";
57
+ break;
58
+ }
59
+ }
60
+ return config;
61
+ }
62
+ async function testConnection(config) {
63
+ const testPool = typeof config === "string" ? new sql.ConnectionPool(parseConnectionString(config)) : new sql.ConnectionPool(config);
64
+ try {
65
+ await testPool.connect();
66
+ await testPool.close();
67
+ } catch (error) {
68
+ throw error;
69
+ }
70
+ }
71
+ async function connect(config) {
72
+ await disconnect();
73
+ const connectionConfig = typeof config === "string" ? parseConnectionString(config) : config;
74
+ pool = new sql.ConnectionPool(connectionConfig);
75
+ try {
76
+ await pool.connect();
77
+ } catch (error) {
78
+ pool = null;
79
+ throw error;
80
+ }
81
+ }
82
+ async function disconnect() {
83
+ if (pool) {
84
+ try {
85
+ await pool.close();
86
+ } catch (error) {
87
+ }
88
+ pool = null;
89
+ }
90
+ }
91
+ function getConnection() {
92
+ return pool;
93
+ }
94
+ async function executeQuery(query, parameters) {
95
+ if (!pool || !pool.connected) {
96
+ throw new Error("Not connected to database");
97
+ }
98
+ const request = pool.request();
99
+ if (parameters) {
100
+ for (const param of parameters) {
101
+ if (param.type) {
102
+ request.input(param.name, param.type, param.value);
103
+ } else {
104
+ request.input(param.name, param.value);
105
+ }
106
+ }
107
+ }
108
+ const result = await request.query(query);
109
+ return result.recordset || [];
110
+ }
111
+ async function executeQueryMultiple(query, parameters) {
112
+ if (!pool || !pool.connected) {
113
+ throw new Error("Not connected to database");
114
+ }
115
+ const request = pool.request();
116
+ if (parameters) {
117
+ for (const param of parameters) {
118
+ if (param.type) {
119
+ request.input(param.name, param.type, param.value);
120
+ } else {
121
+ request.input(param.name, param.value);
122
+ }
123
+ }
124
+ }
125
+ const result = await request.query(query);
126
+ return result.recordsets || [];
127
+ }
128
+
129
+ export {
130
+ parseConnectionString,
131
+ testConnection,
132
+ connect,
133
+ disconnect,
134
+ getConnection,
135
+ executeQuery,
136
+ executeQueryMultiple
137
+ };
package/dist/cli/dev.js CHANGED
@@ -4,9 +4,10 @@ import {
4
4
  connect,
5
5
  disconnect,
6
6
  executeQuery,
7
+ executeQueryMultiple,
7
8
  getConnection,
8
9
  testConnection
9
- } from "./chunk-PTI2CUG6.js";
10
+ } from "./chunk-5X2YZYYM.js";
10
11
 
11
12
  // src/server/index.ts
12
13
  import express from "express";
@@ -87,15 +88,20 @@ connectionRoutes.delete("/", async (req, res) => {
87
88
  });
88
89
  connectionRoutes.get("/status", async (req, res) => {
89
90
  try {
90
- const { getConnection: getConnection2, executeQuery: executeQuery2 } = await import("./mssql-5VFXLOGV.js");
91
+ const { getConnection: getConnection2, executeQuery: executeQuery3 } = await import("./mssql-D7NDIUG7.js");
91
92
  const pool = getConnection2();
92
93
  if (pool && pool.connected) {
93
94
  try {
94
- const result = await executeQuery2("SELECT DB_NAME() as databaseName");
95
+ const result = await executeQuery3("SELECT DB_NAME() as databaseName");
95
96
  const databaseName = result[0]?.databaseName || null;
96
97
  res.json({ connected: true, databaseName });
97
- } catch {
98
- res.json({ connected: true, databaseName: null });
98
+ } catch (error) {
99
+ const errorMessage = error.message || "";
100
+ if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
101
+ const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
102
+ await disconnect2();
103
+ }
104
+ res.json({ connected: false });
99
105
  }
100
106
  } else {
101
107
  res.json({ connected: false });
@@ -126,6 +132,11 @@ tableRoutes.get("/", async (req, res) => {
126
132
  const result = await executeQuery(query);
127
133
  res.json(result);
128
134
  } catch (error) {
135
+ const errorMessage = error.message || "";
136
+ if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
137
+ const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
138
+ await disconnect2();
139
+ }
129
140
  res.status(500).json({ error: error.message || "Failed to fetch tables" });
130
141
  }
131
142
  });
@@ -164,6 +175,11 @@ tableRoutes.get("/:schema/:table", async (req, res) => {
164
175
  ]);
165
176
  res.json(result);
166
177
  } catch (error) {
178
+ const errorMessage = error.message || "";
179
+ if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
180
+ const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
181
+ await disconnect2();
182
+ }
167
183
  res.status(500).json({ error: error.message || "Failed to fetch table structure" });
168
184
  }
169
185
  });
@@ -274,7 +290,11 @@ ORDER BY rn`;
274
290
  });
275
291
  } catch (error) {
276
292
  console.error("Error fetching table data:", error);
277
- const errorMessage = error.message || "Failed to fetch table data";
293
+ const errorMessage = error.message || "";
294
+ if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
295
+ const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
296
+ await disconnect2();
297
+ }
278
298
  const errorDetails = error.originalError?.message || error.originalError?.info?.message || "";
279
299
  res.status(500).json({
280
300
  error: errorMessage,
@@ -299,17 +319,29 @@ queryRoutes.post("/", async (req, res) => {
299
319
  });
300
320
  }
301
321
  const dangerousKeywords = ["DROP", "DELETE", "INSERT", "UPDATE", "ALTER", "CREATE", "TRUNCATE", "EXEC", "EXECUTE"];
302
- const hasDangerousKeyword = dangerousKeywords.some(
303
- (keyword) => trimmedQuery.includes(keyword)
304
- );
322
+ const hasDangerousKeyword = dangerousKeywords.some((keyword) => {
323
+ const regex = new RegExp(`\\b${keyword}\\b`, "i");
324
+ return regex.test(sqlQuery);
325
+ });
305
326
  if (hasDangerousKeyword) {
306
327
  return res.status(400).json({
307
328
  error: "Query contains prohibited keywords. Only SELECT queries are allowed."
308
329
  });
309
330
  }
310
- const result = await executeQuery(sqlQuery);
311
- res.json({ data: result });
331
+ const startTime = Date.now();
332
+ const resultSets = await executeQueryMultiple(sqlQuery);
333
+ const executionTime = Date.now() - startTime;
334
+ res.json({
335
+ data: resultSets[0] || [],
336
+ resultSets: resultSets.length > 0 ? resultSets : [],
337
+ executionTime
338
+ });
312
339
  } catch (error) {
340
+ const errorMessage = error.message || "";
341
+ if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
342
+ const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
343
+ await disconnect2();
344
+ }
313
345
  res.status(500).json({
314
346
  error: error.message || "Query execution failed",
315
347
  details: error.originalError?.message
package/dist/cli/index.js CHANGED
@@ -4,9 +4,10 @@ import {
4
4
  connect,
5
5
  disconnect,
6
6
  executeQuery,
7
+ executeQueryMultiple,
7
8
  getConnection,
8
9
  testConnection
9
- } from "./chunk-PTI2CUG6.js";
10
+ } from "./chunk-5X2YZYYM.js";
10
11
 
11
12
  // src/cli/index.ts
12
13
  import { Command } from "commander";
@@ -90,15 +91,20 @@ connectionRoutes.delete("/", async (req, res) => {
90
91
  });
91
92
  connectionRoutes.get("/status", async (req, res) => {
92
93
  try {
93
- const { getConnection: getConnection2, executeQuery: executeQuery2 } = await import("./mssql-5VFXLOGV.js");
94
+ const { getConnection: getConnection2, executeQuery: executeQuery3 } = await import("./mssql-D7NDIUG7.js");
94
95
  const pool = getConnection2();
95
96
  if (pool && pool.connected) {
96
97
  try {
97
- const result = await executeQuery2("SELECT DB_NAME() as databaseName");
98
+ const result = await executeQuery3("SELECT DB_NAME() as databaseName");
98
99
  const databaseName = result[0]?.databaseName || null;
99
100
  res.json({ connected: true, databaseName });
100
- } catch {
101
- res.json({ connected: true, databaseName: null });
101
+ } catch (error) {
102
+ const errorMessage = error.message || "";
103
+ if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
104
+ const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
105
+ await disconnect2();
106
+ }
107
+ res.json({ connected: false });
102
108
  }
103
109
  } else {
104
110
  res.json({ connected: false });
@@ -129,6 +135,11 @@ tableRoutes.get("/", async (req, res) => {
129
135
  const result = await executeQuery(query);
130
136
  res.json(result);
131
137
  } catch (error) {
138
+ const errorMessage = error.message || "";
139
+ if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
140
+ const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
141
+ await disconnect2();
142
+ }
132
143
  res.status(500).json({ error: error.message || "Failed to fetch tables" });
133
144
  }
134
145
  });
@@ -167,6 +178,11 @@ tableRoutes.get("/:schema/:table", async (req, res) => {
167
178
  ]);
168
179
  res.json(result);
169
180
  } catch (error) {
181
+ const errorMessage = error.message || "";
182
+ if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
183
+ const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
184
+ await disconnect2();
185
+ }
170
186
  res.status(500).json({ error: error.message || "Failed to fetch table structure" });
171
187
  }
172
188
  });
@@ -277,7 +293,11 @@ ORDER BY rn`;
277
293
  });
278
294
  } catch (error) {
279
295
  console.error("Error fetching table data:", error);
280
- const errorMessage = error.message || "Failed to fetch table data";
296
+ const errorMessage = error.message || "";
297
+ if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
298
+ const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
299
+ await disconnect2();
300
+ }
281
301
  const errorDetails = error.originalError?.message || error.originalError?.info?.message || "";
282
302
  res.status(500).json({
283
303
  error: errorMessage,
@@ -302,17 +322,29 @@ queryRoutes.post("/", async (req, res) => {
302
322
  });
303
323
  }
304
324
  const dangerousKeywords = ["DROP", "DELETE", "INSERT", "UPDATE", "ALTER", "CREATE", "TRUNCATE", "EXEC", "EXECUTE"];
305
- const hasDangerousKeyword = dangerousKeywords.some(
306
- (keyword) => trimmedQuery.includes(keyword)
307
- );
325
+ const hasDangerousKeyword = dangerousKeywords.some((keyword) => {
326
+ const regex = new RegExp(`\\b${keyword}\\b`, "i");
327
+ return regex.test(sqlQuery);
328
+ });
308
329
  if (hasDangerousKeyword) {
309
330
  return res.status(400).json({
310
331
  error: "Query contains prohibited keywords. Only SELECT queries are allowed."
311
332
  });
312
333
  }
313
- const result = await executeQuery(sqlQuery);
314
- res.json({ data: result });
334
+ const startTime = Date.now();
335
+ const resultSets = await executeQueryMultiple(sqlQuery);
336
+ const executionTime = Date.now() - startTime;
337
+ res.json({
338
+ data: resultSets[0] || [],
339
+ resultSets: resultSets.length > 0 ? resultSets : [],
340
+ executionTime
341
+ });
315
342
  } catch (error) {
343
+ const errorMessage = error.message || "";
344
+ if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
345
+ const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
346
+ await disconnect2();
347
+ }
316
348
  res.status(500).json({
317
349
  error: error.message || "Query execution failed",
318
350
  details: error.originalError?.message
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {
4
+ connect,
5
+ disconnect,
6
+ executeQuery,
7
+ executeQueryMultiple,
8
+ getConnection,
9
+ parseConnectionString,
10
+ testConnection
11
+ } from "./chunk-5X2YZYYM.js";
12
+ export {
13
+ connect,
14
+ disconnect,
15
+ executeQuery,
16
+ executeQueryMultiple,
17
+ getConnection,
18
+ parseConnectionString,
19
+ testConnection
20
+ };
@@ -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}:root{--background: 0 0% 100%;--foreground: 222.2 84% 4.9%;--card: 0 0% 100%;--card-foreground: 222.2 84% 4.9%;--popover: 0 0% 100%;--popover-foreground: 222.2 84% 4.9%;--primary: 222.2 47.4% 11.2%;--primary-foreground: 210 40% 98%;--secondary: 210 40% 96.1%;--secondary-foreground: 222.2 47.4% 11.2%;--muted: 210 40% 96.1%;--muted-foreground: 215.4 16.3% 46.9%;--accent: 210 40% 96.1%;--accent-foreground: 222.2 47.4% 11.2%;--destructive: 0 84.2% 60.2%;--destructive-foreground: 210 40% 98%;--border: 214.3 31.8% 91.4%;--input: 214.3 31.8% 91.4%;--ring: 222.2 84% 4.9%;--radius: .5rem;--chart-1: 12 76% 61%;--chart-2: 173 58% 39%;--chart-3: 197 37% 24%;--chart-4: 43 74% 66%;--chart-5: 27 87% 67%;--header-bg: 0 0% 100%;--sidebar-bg: 0 0% 100%;--content-bg: 0 0% 100%;--tabs-bg: 0 0% 100%;--grid-bg: 0 0% 100%}.dark{--background: 222.2 84% 4.9%;--foreground: 210 40% 98%;--card: 222.2 84% 4.9%;--card-foreground: 210 40% 98%;--popover: 222.2 84% 4.9%;--popover-foreground: 210 40% 98%;--primary: 210 40% 98%;--primary-foreground: 222.2 47.4% 11.2%;--secondary: 217.2 32.6% 17.5%;--secondary-foreground: 210 40% 98%;--muted: 217.2 32.6% 17.5%;--muted-foreground: 215 20.2% 65.1%;--accent: 217.2 32.6% 17.5%;--accent-foreground: 210 40% 98%;--destructive: 0 70% 55%;--destructive-foreground: 210 40% 98%;--border: 217.2 32.6% 17.5%;--input: 217.2 32.6% 17.5%;--ring: 212.7 26.8% 83.9%;--chart-1: 220 70% 50%;--chart-2: 160 60% 45%;--chart-3: 30 80% 55%;--chart-4: 280 65% 60%;--chart-5: 340 75% 55%;--header-bg: 222.2 84% 6.5%;--sidebar-bg: 222.2 84% 5.5%;--content-bg: 222.2 84% 4.9%;--tabs-bg: 222.2 84% 5.2%;--grid-bg: 222.2 84% 5%}*{border-color:hsl(var(--border))}body{background-color:hsl(var(--background));color:hsl(var(--foreground));font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif}code,pre{font-family:JetBrains Mono,Menlo,Monaco,Courier New,monospace}.container{width:100%}@media(min-width:640px){.container{max-width:640px}}@media(min-width:768px){.container{max-width:768px}}@media(min-width:1024px){.container{max-width:1024px}}@media(min-width:1280px){.container{max-width:1280px}}@media(min-width:1536px){.container{max-width:1536px}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.bottom-0{bottom:0}.left-0{left:0}.left-2{left:.5rem}.right-0{right:0}.right-2{right:.5rem}.right-3{right:.75rem}.right-4{right:1rem}.top-0{top:0}.top-1\/2{top:50%}.top-4{top:1rem}.top-full{top:100%}.z-10{z-index:10}.z-50{z-index:50}.mx-1{margin-left:.25rem;margin-right:.25rem}.mx-auto{margin-left:auto;margin-right:auto}.my-1{margin-top:.25rem;margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.ml-4{margin-left:1rem}.mr-1{margin-right:.25rem}.mr-1\.5{margin-right:.375rem}.mr-2{margin-right:.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.flex{display:flex}.inline-flex{display:inline-flex}.\!table{display:table!important}.table{display:table}.grid{display:grid}.hidden{display:none}.h-1{height:.25rem}.h-10{height:2.5rem}.h-12{height:3rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-64{height:16rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-auto{height:auto}.h-full{height:100%}.h-px{height:1px}.h-screen{height:100vh}.max-h-64{max-height:16rem}.max-h-96{max-height:24rem}.w-12{width:3rem}.w-2{width:.5rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-64{width:16rem}.w-7{width:1.75rem}.w-8{width:2rem}.w-9{width:2.25rem}.w-auto{width:auto}.w-full{width:100%}.w-px{width:1px}.min-w-\[8rem\]{min-width:8rem}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.flex-shrink-0,.shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-cell{cursor:cell}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.cursor-row-resize{cursor:row-resize}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-4{gap:1rem}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.125rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem * var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-sm{border-radius:calc(var(--radius) - 4px)}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-l-2{border-left-width:2px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-border\/50{border-color:hsl(var(--border) / .5)}.border-gray-300{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity, 1))}.border-input{border-color:hsl(var(--input))}.border-primary{border-color:hsl(var(--primary))}.bg-accent{background-color:hsl(var(--accent))}.bg-background{background-color:hsl(var(--background))}.bg-black\/50{background-color:#00000080}.bg-border{background-color:hsl(var(--border))}.bg-card{background-color:hsl(var(--card))}.bg-content-bg{background-color:hsl(var(--content-bg))}.bg-destructive{background-color:hsl(var(--destructive))}.bg-destructive\/10{background-color:hsl(var(--destructive) / .1)}.bg-destructive\/90{background-color:hsl(var(--destructive) / .9)}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-500\/10{background-color:#22c55e1a}.bg-grid-bg{background-color:hsl(var(--grid-bg))}.bg-header-bg{background-color:hsl(var(--header-bg))}.bg-muted{background-color:hsl(var(--muted))}.bg-muted\/30{background-color:hsl(var(--muted) / .3)}.bg-muted\/50{background-color:hsl(var(--muted) / .5)}.bg-popover{background-color:hsl(var(--popover))}.bg-primary{background-color:hsl(var(--primary))}.bg-primary\/20{background-color:hsl(var(--primary) / .2)}.bg-primary\/90{background-color:hsl(var(--primary) / .9)}.bg-secondary{background-color:hsl(var(--secondary))}.bg-secondary\/80{background-color:hsl(var(--secondary) / .8)}.bg-sidebar-bg{background-color:hsl(var(--sidebar-bg))}.bg-tabs-bg{background-color:hsl(var(--tabs-bg))}.bg-transparent{background-color:transparent}.fill-yellow-500{fill:#eab308}.p-0{padding:0}.p-0\.5{padding:.125rem}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.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}.px-8{padding-left:2rem;padding-right:2rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pl-8{padding-left:2rem}.pr-8{padding-right:2rem}.pt-0{padding-top:0}.pt-2{padding-top:.5rem}.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}.text-2xl{font-size:1.5rem;line-height:2rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.font-semibold{font-weight:600}.italic{font-style:italic}.leading-none{line-height:1}.tracking-tight{letter-spacing:-.025em}.text-accent-foreground{color:hsl(var(--accent-foreground))}.text-card-foreground{color:hsl(var(--card-foreground))}.text-destructive{color:hsl(var(--destructive))}.text-destructive-foreground{color:hsl(var(--destructive-foreground))}.text-green-600{--tw-text-opacity: 1;color:rgb(22 163 74 / var(--tw-text-opacity, 1))}.text-muted-foreground{color:hsl(var(--muted-foreground))}.text-popover-foreground{color:hsl(var(--popover-foreground))}.text-primary{color:hsl(var(--primary))}.text-primary-foreground{color:hsl(var(--primary-foreground))}.text-secondary-foreground{color:hsl(var(--secondary-foreground))}.text-yellow-500{--tw-text-opacity: 1;color:rgb(234 179 8 / var(--tw-text-opacity, 1))}.underline{text-decoration-line:underline}.underline-offset-4{text-underline-offset:4px}.opacity-0{opacity:0}.opacity-30{opacity:.3}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px 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-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px 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-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px 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-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.ring-offset-background{--tw-ring-offset-color: hsl(var(--background))}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.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}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}@keyframes enter{0%{opacity:var(--tw-enter-opacity, 1);transform:translate3d(var(--tw-enter-translate-x, 0),var(--tw-enter-translate-y, 0),0) scale3d(var(--tw-enter-scale, 1),var(--tw-enter-scale, 1),var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity, 1);transform:translate3d(var(--tw-exit-translate-x, 0),var(--tw-exit-translate-y, 0),0) scale3d(var(--tw-exit-scale, 1),var(--tw-exit-scale, 1),var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0))}}.file\:border-0::file-selector-button{border-width:0px}.file\:bg-transparent::file-selector-button{background-color:transparent}.file\:text-sm::file-selector-button{font-size:.875rem;line-height:1.25rem}.file\:font-medium::file-selector-button{font-weight:500}.placeholder\:text-muted-foreground::-moz-placeholder{color:hsl(var(--muted-foreground))}.placeholder\:text-muted-foreground::placeholder{color:hsl(var(--muted-foreground))}.last\:border-b-0:last-child{border-bottom-width:0px}.last\:border-r-0:last-child{border-right-width:0px}.hover\:bg-accent:hover{background-color:hsl(var(--accent))}.hover\:bg-destructive\/90:hover{background-color:hsl(var(--destructive) / .9)}.hover\:bg-green-500\/20:hover{background-color:#22c55e33}.hover\:bg-muted\/30:hover{background-color:hsl(var(--muted) / .3)}.hover\:bg-muted\/70:hover{background-color:hsl(var(--muted) / .7)}.hover\:bg-primary\/50:hover{background-color:hsl(var(--primary) / .5)}.hover\:bg-primary\/90:hover{background-color:hsl(var(--primary) / .9)}.hover\:bg-secondary\/80:hover{background-color:hsl(var(--secondary) / .8)}.hover\:bg-transparent:hover{background-color:transparent}.hover\:text-accent-foreground:hover{color:hsl(var(--accent-foreground))}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-100:hover{opacity:1}.focus\:bg-accent:focus{background-color:hsl(var(--accent))}.focus\:bg-destructive\/10:focus{background-color:hsl(var(--destructive) / .1)}.focus\:text-accent-foreground:focus{color:hsl(var(--accent-foreground))}.focus\:text-destructive:focus{color:hsl(var(--destructive))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-ring:focus{--tw-ring-color: hsl(var(--ring))}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring-1:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-ring:focus-visible{--tw-ring-color: hsl(var(--ring))}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:opacity-100{opacity:1}.peer:disabled~.peer-disabled\:cursor-not-allowed{cursor:not-allowed}.peer:disabled~.peer-disabled\:opacity-70{opacity:.7}.data-\[disabled\]\:pointer-events-none[data-disabled]{pointer-events:none}.data-\[disabled\]\:opacity-50[data-disabled]{opacity:.5}.dark\:bg-content-bg:is(.dark *){background-color:hsl(var(--content-bg))}.dark\:bg-grid-bg:is(.dark *){background-color:hsl(var(--grid-bg))}.dark\:bg-header-bg:is(.dark *){background-color:hsl(var(--header-bg))}.dark\:bg-sidebar-bg:is(.dark *){background-color:hsl(var(--sidebar-bg))}.dark\:bg-tabs-bg:is(.dark *){background-color:hsl(var(--tabs-bg))}.dark\:text-green-400:is(.dark *){--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}@media(min-width:640px){.sm\:max-w-\[500px\]{max-width:500px}.sm\:rounded-lg{border-radius:var(--radius)}.sm\:text-left{text-align:left}}