@bytebase/dbhub 0.11.7 → 0.11.9
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.
- package/dist/{demo-loader-EOVFD32T.js → demo-loader-PSMTLZ2T.js} +3 -3
- package/dist/index.js +80 -52
- package/dist/public/assets/index--LC9Foha.css +1 -0
- package/dist/public/assets/index-BZTaHJm6.js +51 -0
- package/dist/public/assets/postgres-BpcazhJg.svg +22 -0
- package/dist/public/assets/sqlserver-ByfFYYpV.svg +11 -0
- package/dist/public/favicon.svg +57 -0
- package/dist/public/index.html +14 -0
- package/dist/public/logo-full-light.svg +58 -0
- package/package.json +1 -1
- package/dist/resources/employee-sqlite/object.sql +0 -74
- package/dist/resources/employee-sqlite/show_elapsed.sql +0 -4
- package/dist/resources/employee-sqlite/test_employee_md5.sql +0 -119
- /package/dist/{resources → demo}/employee-sqlite/employee.sql +0 -0
- /package/dist/{resources → demo}/employee-sqlite/load_department.sql +0 -0
- /package/dist/{resources → demo}/employee-sqlite/load_dept_emp.sql +0 -0
- /package/dist/{resources → demo}/employee-sqlite/load_dept_manager.sql +0 -0
- /package/dist/{resources → demo}/employee-sqlite/load_employee.sql +0 -0
- /package/dist/{resources → demo}/employee-sqlite/load_salary1.sql +0 -0
- /package/dist/{resources → demo}/employee-sqlite/load_title.sql +0 -0
|
@@ -6,14 +6,14 @@ var __filename = fileURLToPath(import.meta.url);
|
|
|
6
6
|
var __dirname = path.dirname(__filename);
|
|
7
7
|
var DEMO_DATA_DIR;
|
|
8
8
|
var projectRootPath = path.join(__dirname, "..", "..", "..");
|
|
9
|
-
var projectResourcesPath = path.join(projectRootPath, "
|
|
10
|
-
var distPath = path.join(__dirname, "
|
|
9
|
+
var projectResourcesPath = path.join(projectRootPath, "demo", "employee-sqlite");
|
|
10
|
+
var distPath = path.join(__dirname, "demo", "employee-sqlite");
|
|
11
11
|
if (fs.existsSync(projectResourcesPath)) {
|
|
12
12
|
DEMO_DATA_DIR = projectResourcesPath;
|
|
13
13
|
} else if (fs.existsSync(distPath)) {
|
|
14
14
|
DEMO_DATA_DIR = distPath;
|
|
15
15
|
} else {
|
|
16
|
-
DEMO_DATA_DIR = path.join(process.cwd(), "
|
|
16
|
+
DEMO_DATA_DIR = path.join(process.cwd(), "demo", "employee-sqlite");
|
|
17
17
|
if (!fs.existsSync(DEMO_DATA_DIR)) {
|
|
18
18
|
throw new Error(`Could not find employee-sqlite resources in any of the expected locations:
|
|
19
19
|
- ${projectResourcesPath}
|
package/dist/index.js
CHANGED
|
@@ -1028,6 +1028,8 @@ Expected: ${expectedFormat}`
|
|
|
1028
1028
|
} else {
|
|
1029
1029
|
if (url.pathname.startsWith("//")) {
|
|
1030
1030
|
dbPath = url.pathname.substring(2);
|
|
1031
|
+
} else if (url.pathname.match(/^\/[A-Za-z]:\//)) {
|
|
1032
|
+
dbPath = url.pathname.substring(1);
|
|
1031
1033
|
} else {
|
|
1032
1034
|
dbPath = url.pathname;
|
|
1033
1035
|
}
|
|
@@ -2624,9 +2626,11 @@ function resolveSSHConfig() {
|
|
|
2624
2626
|
};
|
|
2625
2627
|
}
|
|
2626
2628
|
async function resolveSourceConfigs() {
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2629
|
+
if (!isDemoMode()) {
|
|
2630
|
+
const tomlConfig = loadTomlConfig();
|
|
2631
|
+
if (tomlConfig) {
|
|
2632
|
+
return tomlConfig;
|
|
2633
|
+
}
|
|
2630
2634
|
}
|
|
2631
2635
|
const dsnResult = resolveDSN();
|
|
2632
2636
|
if (dsnResult) {
|
|
@@ -2673,7 +2677,7 @@ async function resolveSourceConfigs() {
|
|
|
2673
2677
|
source.max_rows = maxRowsResult.maxRows;
|
|
2674
2678
|
}
|
|
2675
2679
|
if (dsnResult.isDemo) {
|
|
2676
|
-
const { getSqliteInMemorySetupSql } = await import("./demo-loader-
|
|
2680
|
+
const { getSqliteInMemorySetupSql } = await import("./demo-loader-PSMTLZ2T.js");
|
|
2677
2681
|
source.init_script = getSqliteInMemorySetupSql();
|
|
2678
2682
|
}
|
|
2679
2683
|
return {
|
|
@@ -3435,15 +3439,31 @@ async function procedureDetailResourceHandler(uri, variables, _extra) {
|
|
|
3435
3439
|
|
|
3436
3440
|
// src/resources/index.ts
|
|
3437
3441
|
function registerResources(server) {
|
|
3438
|
-
server.resource(
|
|
3442
|
+
server.resource(
|
|
3443
|
+
"schemas",
|
|
3444
|
+
"db://schemas",
|
|
3445
|
+
{
|
|
3446
|
+
description: "List all schemas/databases available in the connected database",
|
|
3447
|
+
mimeType: "application/json"
|
|
3448
|
+
},
|
|
3449
|
+
schemasResourceHandler
|
|
3450
|
+
);
|
|
3439
3451
|
server.resource(
|
|
3440
3452
|
"tables_in_schema",
|
|
3441
3453
|
new ResourceTemplate("db://schemas/{schemaName}/tables", { list: void 0 }),
|
|
3454
|
+
{
|
|
3455
|
+
description: "List all tables within a specific schema",
|
|
3456
|
+
mimeType: "application/json"
|
|
3457
|
+
},
|
|
3442
3458
|
tablesResourceHandler
|
|
3443
3459
|
);
|
|
3444
3460
|
server.resource(
|
|
3445
3461
|
"table_structure_in_schema",
|
|
3446
3462
|
new ResourceTemplate("db://schemas/{schemaName}/tables/{tableName}", { list: void 0 }),
|
|
3463
|
+
{
|
|
3464
|
+
description: "Get detailed structure information for a specific table, including columns, data types, and constraints",
|
|
3465
|
+
mimeType: "application/json"
|
|
3466
|
+
},
|
|
3447
3467
|
tableStructureResourceHandler
|
|
3448
3468
|
);
|
|
3449
3469
|
server.resource(
|
|
@@ -3451,11 +3471,19 @@ function registerResources(server) {
|
|
|
3451
3471
|
new ResourceTemplate("db://schemas/{schemaName}/tables/{tableName}/indexes", {
|
|
3452
3472
|
list: void 0
|
|
3453
3473
|
}),
|
|
3474
|
+
{
|
|
3475
|
+
description: "List all indexes defined on a specific table",
|
|
3476
|
+
mimeType: "application/json"
|
|
3477
|
+
},
|
|
3454
3478
|
indexesResourceHandler
|
|
3455
3479
|
);
|
|
3456
3480
|
server.resource(
|
|
3457
3481
|
"procedures_in_schema",
|
|
3458
3482
|
new ResourceTemplate("db://schemas/{schemaName}/procedures", { list: void 0 }),
|
|
3483
|
+
{
|
|
3484
|
+
description: "List all stored procedures/functions in a schema (not supported by SQLite)",
|
|
3485
|
+
mimeType: "application/json"
|
|
3486
|
+
},
|
|
3459
3487
|
proceduresResourceHandler
|
|
3460
3488
|
);
|
|
3461
3489
|
server.resource(
|
|
@@ -3463,6 +3491,10 @@ function registerResources(server) {
|
|
|
3463
3491
|
new ResourceTemplate("db://schemas/{schemaName}/procedures/{procedureName}", {
|
|
3464
3492
|
list: void 0
|
|
3465
3493
|
}),
|
|
3494
|
+
{
|
|
3495
|
+
description: "Get detailed information about a specific stored procedure, including parameters and definition (not supported by SQLite)",
|
|
3496
|
+
mimeType: "application/json"
|
|
3497
|
+
},
|
|
3466
3498
|
procedureDetailResourceHandler
|
|
3467
3499
|
);
|
|
3468
3500
|
}
|
|
@@ -4121,10 +4153,10 @@ See documentation for more details on configuring database connections.
|
|
|
4121
4153
|
await connectorManager.connectWithSources(sources);
|
|
4122
4154
|
const transportData = resolveTransport();
|
|
4123
4155
|
console.error(`MCP transport: ${transportData.type}`);
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4156
|
+
const portData = transportData.type === "http" ? resolvePort() : null;
|
|
4157
|
+
if (portData) {
|
|
4158
|
+
console.error(`HTTP server port: ${portData.port} (source: ${portData.source})`);
|
|
4159
|
+
}
|
|
4128
4160
|
const readonly = isReadOnlyMode();
|
|
4129
4161
|
const activeModes = [];
|
|
4130
4162
|
const modeDescriptions = [];
|
|
@@ -4144,30 +4176,31 @@ See documentation for more details on configuring database connections.
|
|
|
4144
4176
|
console.error(`Running in ${activeModes.join(" and ")} mode - ${modeDescriptions.join(", ")}`);
|
|
4145
4177
|
}
|
|
4146
4178
|
console.error(generateBanner(SERVER_VERSION, activeModes));
|
|
4147
|
-
const app = express();
|
|
4148
|
-
app.use(express.json());
|
|
4149
|
-
app.use((req, res, next) => {
|
|
4150
|
-
const origin = req.headers.origin;
|
|
4151
|
-
if (origin && !origin.startsWith("http://localhost") && !origin.startsWith("https://localhost")) {
|
|
4152
|
-
return res.status(403).json({ error: "Forbidden origin" });
|
|
4153
|
-
}
|
|
4154
|
-
res.header("Access-Control-Allow-Origin", origin || "http://localhost");
|
|
4155
|
-
res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
4156
|
-
res.header("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id");
|
|
4157
|
-
res.header("Access-Control-Allow-Credentials", "true");
|
|
4158
|
-
if (req.method === "OPTIONS") {
|
|
4159
|
-
return res.sendStatus(200);
|
|
4160
|
-
}
|
|
4161
|
-
next();
|
|
4162
|
-
});
|
|
4163
|
-
const frontendPath = path3.join(__dirname2, "..", "frontend", "dist");
|
|
4164
|
-
app.use(express.static(frontendPath));
|
|
4165
|
-
app.get("/healthz", (req, res) => {
|
|
4166
|
-
res.status(200).send("OK");
|
|
4167
|
-
});
|
|
4168
|
-
app.get("/api/sources", listSources);
|
|
4169
|
-
app.get("/api/sources/:sourceId", getSource);
|
|
4170
4179
|
if (transportData.type === "http") {
|
|
4180
|
+
const port = portData.port;
|
|
4181
|
+
const app = express();
|
|
4182
|
+
app.use(express.json());
|
|
4183
|
+
app.use((req, res, next) => {
|
|
4184
|
+
const origin = req.headers.origin;
|
|
4185
|
+
if (origin && !origin.startsWith("http://localhost") && !origin.startsWith("https://localhost")) {
|
|
4186
|
+
return res.status(403).json({ error: "Forbidden origin" });
|
|
4187
|
+
}
|
|
4188
|
+
res.header("Access-Control-Allow-Origin", origin || "http://localhost");
|
|
4189
|
+
res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
4190
|
+
res.header("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id");
|
|
4191
|
+
res.header("Access-Control-Allow-Credentials", "true");
|
|
4192
|
+
if (req.method === "OPTIONS") {
|
|
4193
|
+
return res.sendStatus(200);
|
|
4194
|
+
}
|
|
4195
|
+
next();
|
|
4196
|
+
});
|
|
4197
|
+
const frontendPath = path3.join(__dirname2, "public");
|
|
4198
|
+
app.use(express.static(frontendPath));
|
|
4199
|
+
app.get("/healthz", (req, res) => {
|
|
4200
|
+
res.status(200).send("OK");
|
|
4201
|
+
});
|
|
4202
|
+
app.get("/api/sources", listSources);
|
|
4203
|
+
app.get("/api/sources/:sourceId", getSource);
|
|
4171
4204
|
app.post("/mcp", async (req, res) => {
|
|
4172
4205
|
try {
|
|
4173
4206
|
const transport = new StreamableHTTPServerTransport({
|
|
@@ -4186,30 +4219,25 @@ See documentation for more details on configuring database connections.
|
|
|
4186
4219
|
}
|
|
4187
4220
|
}
|
|
4188
4221
|
});
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
} else {
|
|
4202
|
-
console.error(`Frontend accessible at http://0.0.0.0:${port}/`);
|
|
4203
|
-
}
|
|
4204
|
-
if (transportData.type === "http") {
|
|
4222
|
+
app.get("*", (req, res) => {
|
|
4223
|
+
res.sendFile(path3.join(frontendPath, "index.html"));
|
|
4224
|
+
});
|
|
4225
|
+
app.listen(port, "0.0.0.0", () => {
|
|
4226
|
+
if (process.env.NODE_ENV === "development") {
|
|
4227
|
+
console.error("Development mode detected!");
|
|
4228
|
+
console.error(" Admin console dev server (with HMR): http://localhost:5173");
|
|
4229
|
+
console.error(" Backend API: http://localhost:8080");
|
|
4230
|
+
console.error("");
|
|
4231
|
+
} else {
|
|
4232
|
+
console.error(`Admin console at http://0.0.0.0:${port}/`);
|
|
4233
|
+
}
|
|
4205
4234
|
console.error(`MCP server endpoint at http://0.0.0.0:${port}/mcp`);
|
|
4206
|
-
}
|
|
4207
|
-
}
|
|
4208
|
-
if (transportData.type === "stdio") {
|
|
4235
|
+
});
|
|
4236
|
+
} else {
|
|
4209
4237
|
const server = createServer2();
|
|
4210
4238
|
const transport = new StdioServerTransport();
|
|
4211
|
-
console.error("Starting MCP with STDIO transport");
|
|
4212
4239
|
await server.connect(transport);
|
|
4240
|
+
console.error("MCP server running on stdio");
|
|
4213
4241
|
process.on("SIGINT", async () => {
|
|
4214
4242
|
console.error("Shutting down...");
|
|
4215
4243
|
await transport.close();
|
|
@@ -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}*{border-color:hsl(var(--border))}body{background-color:hsl(var(--background));color:hsl(var(--foreground))}.container{width:100%;margin-right:auto;margin-left:auto;padding-right:2rem;padding-left:2rem}@media (min-width: 1400px){.container{max-width:1400px}}.static{position:static}.fixed{position:fixed}.bottom-4{bottom:1rem}.right-4{right:1rem}.z-50{z-index:50}.mx-auto{margin-left:auto;margin-right:auto}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mt-1{margin-top:.25rem}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.h-5{height:1.25rem}.h-auto{height:auto}.h-screen{height:100vh}.min-h-screen{min-height:100vh}.w-5{width:1.25rem}.w-\[200px\]{width:200px}.w-full{width:100%}.max-w-4xl{max-width:56rem}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-center{justify-content:center}.gap-3{gap:.75rem}.gap-4{gap:1rem}.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-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-auto{overflow:auto}.overflow-hidden{overflow:hidden}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-r{border-right-width:1px}.border-border{border-color:hsl(var(--border))}.border-destructive\/20{border-color:hsl(var(--destructive) / .2)}.bg-accent{background-color:hsl(var(--accent))}.bg-background{background-color:hsl(var(--background))}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-card{background-color:hsl(var(--card))}.bg-destructive\/10{background-color:hsl(var(--destructive) / .1)}.bg-green-500\/10{background-color:#22c55e1a}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-muted{background-color:hsl(var(--muted))}.bg-primary{background-color:hsl(var(--primary))}.bg-primary\/10{background-color:hsl(var(--primary) / .1)}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.bg-yellow-500\/10{background-color:#eab3081a}.p-6{padding:1.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.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-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.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-wider{letter-spacing:.05em}.text-accent-foreground{color:hsl(var(--accent-foreground))}.text-destructive{color:hsl(var(--destructive))}.text-destructive\/90{color:hsl(var(--destructive) / .9)}.text-foreground{color:hsl(var(--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-primary{color:hsl(var(--primary))}.text-primary-foreground{color:hsl(var(--primary-foreground))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-600{--tw-text-opacity: 1;color:rgb(202 138 4 / var(--tw-text-opacity, 1))}.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)}.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}.hover\:bg-accent:hover{background-color:hsl(var(--accent))}.hover\:bg-primary\/90:hover{background-color:hsl(var(--primary) / .9)}.hover\:text-accent-foreground:hover{color:hsl(var(--accent-foreground))}.hover\:text-gray-200:hover{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.dark\:text-green-400:is(.dark *){--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.dark\:text-yellow-400:is(.dark *){--tw-text-opacity: 1;color:rgb(250 204 21 / var(--tw-text-opacity, 1))}@media (min-width: 640px){.sm\:w-\[220px\]{width:220px}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width: 768px){.md\:w-\[240px\]{width:240px}}@media (min-width: 1024px){.lg\:w-\[280px\]{width:280px}}
|