ccpanel 0.1.4 → 0.1.5

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.
@@ -371,6 +371,10 @@ import { z as z4 } from "zod";
371
371
  function projectRoutes(deps) {
372
372
  const app = new Hono4();
373
373
  app.get("/", async (c) => c.json({ projects: await deps.projects.list() }));
374
+ app.get(
375
+ "/known",
376
+ async (c) => c.json({ projects: await deps.projects.known() })
377
+ );
374
378
  app.post("/", async (c) => {
375
379
  const { path } = z4.object({ path: z4.string().min(1) }).parse(await c.req.json());
376
380
  return c.json({ projects: await deps.projects.add(path) });
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  assertNoConflict,
9
9
  createApp,
10
10
  currentMtime
11
- } from "./chunk-HRPQYUJV.js";
11
+ } from "./chunk-LZFMCC5M.js";
12
12
 
13
13
  // src/cli.ts
14
14
  import { homedir, networkInterfaces } from "os";
@@ -447,19 +447,37 @@ var SkillService = class {
447
447
  };
448
448
 
449
449
  // src/core/project-service.ts
450
+ import { stat as stat2 } from "fs/promises";
450
451
  var ProjectService = class {
451
- constructor(store, registryFile) {
452
+ constructor(store, registryFile, userClaudeJson) {
452
453
  this.store = store;
453
454
  this.registryFile = registryFile;
455
+ this.userClaudeJson = userClaudeJson;
454
456
  }
455
457
  store;
456
458
  registryFile;
459
+ userClaudeJson;
457
460
  async list() {
458
461
  const data = await this.store.readJson(
459
462
  this.registryFile
460
463
  );
461
464
  return data?.projects ?? [];
462
465
  }
466
+ // Claude Code 在 ~/.claude.json 的 projects 键下记录所有打开过的项目路径,
467
+ // 作为「添加项目」的候选来源;剔除已注册与已删除的目录。
468
+ async known() {
469
+ const data = await this.store.readJson(this.userClaudeJson);
470
+ const registered = new Set(await this.list());
471
+ const out = [];
472
+ for (const p of Object.keys(data?.projects ?? {})) {
473
+ if (registered.has(p)) continue;
474
+ try {
475
+ if ((await stat2(p)).isDirectory()) out.push(p);
476
+ } catch {
477
+ }
478
+ }
479
+ return out.sort();
480
+ }
463
481
  async add(path) {
464
482
  const current = await this.list();
465
483
  if (!current.includes(path)) current.push(path);
@@ -504,12 +522,12 @@ var MemoryService = class {
504
522
  };
505
523
 
506
524
  // src/core/memory-files-service.ts
507
- import { readdir as readdir3, readFile as readFile5, stat as stat3 } from "fs/promises";
525
+ import { readdir as readdir3, readFile as readFile5, stat as stat4 } from "fs/promises";
508
526
  import { join as join5 } from "path";
509
527
  import matter2 from "gray-matter";
510
528
 
511
529
  // src/core/session-service.ts
512
- import { readdir as readdir2, readFile as readFile4, stat as stat2 } from "fs/promises";
530
+ import { readdir as readdir2, readFile as readFile4, stat as stat3 } from "fs/promises";
513
531
  import { join as join4 } from "path";
514
532
  var MAX_BLOCK = 1e4;
515
533
  var NOISE_PREFIXES = ["<local-command", "<command-", "<system-reminder", "Caveat:"];
@@ -608,7 +626,7 @@ var SessionService = class {
608
626
  const out = [];
609
627
  for (const name of names) {
610
628
  const full = join4(dir, name);
611
- const st = await stat2(full);
629
+ const st = await stat3(full);
612
630
  const { firstPrompt, messageCount } = await this.summarize(full);
613
631
  out.push({
614
632
  id: name.replace(/\.jsonl$/, ""),
@@ -720,7 +738,7 @@ var MemoryFilesService = class {
720
738
  index = await readFile5(full, "utf8");
721
739
  continue;
722
740
  }
723
- const st = await stat3(full);
741
+ const st = await stat4(full);
724
742
  const fm = await this.parseFront(full, name);
725
743
  files.push({ file: name, ...fm, size: st.size, mtime: st.mtimeMs });
726
744
  }
@@ -750,7 +768,7 @@ var MemoryFilesService = class {
750
768
  };
751
769
 
752
770
  // src/core/plugin-service.ts
753
- import { readFile as readFile6, readdir as readdir4, stat as stat4 } from "fs/promises";
771
+ import { readFile as readFile6, readdir as readdir4, stat as stat5 } from "fs/promises";
754
772
  import { isAbsolute as isAbsolute2, join as join6, relative as relative2, resolve as resolve3 } from "path";
755
773
  import matter3 from "gray-matter";
756
774
  var PluginService = class {
@@ -759,7 +777,7 @@ var PluginService = class {
759
777
  }
760
778
  store;
761
779
  async pathExists(p) {
762
- return stat4(p).then(() => true).catch(() => false);
780
+ return stat5(p).then(() => true).catch(() => false);
763
781
  }
764
782
  // 插件元数据要尽量容错:单个文件损坏不应让整页报错(启停写入除外)。
765
783
  async safeReadJson(file) {
@@ -1013,7 +1031,7 @@ var PluginService = class {
1013
1031
  };
1014
1032
 
1015
1033
  // src/core/codex-plugin-service.ts
1016
- import { readFile as readFile7, readdir as readdir5, stat as stat5 } from "fs/promises";
1034
+ import { readFile as readFile7, readdir as readdir5, stat as stat6 } from "fs/promises";
1017
1035
  import { isAbsolute as isAbsolute3, join as join7, relative as relative3, resolve as resolve4 } from "path";
1018
1036
  import matter4 from "gray-matter";
1019
1037
  import { parse as parse2, stringify as stringify2 } from "smol-toml";
@@ -1028,7 +1046,7 @@ var CodexPluginService = class {
1028
1046
  }
1029
1047
  store;
1030
1048
  async pathExists(p) {
1031
- return stat5(p).then(() => true).catch(() => false);
1049
+ return stat6(p).then(() => true).catch(() => false);
1032
1050
  }
1033
1051
  async readConfig(paths) {
1034
1052
  let raw;
@@ -1260,7 +1278,7 @@ var CodexPluginService = class {
1260
1278
  }
1261
1279
  async setEnabled(paths, id, enabled, baseMtime) {
1262
1280
  if (baseMtime !== void 0) {
1263
- const st = await stat5(paths.codexConfigToml);
1281
+ const st = await stat6(paths.codexConfigToml);
1264
1282
  if (Math.abs(st.mtimeMs - baseMtime) > 1) {
1265
1283
  throw new Error("\u914D\u7F6E\u6587\u4EF6\u5DF2\u88AB\u5176\u4ED6\u8FDB\u7A0B\u4FEE\u6539\uFF0C\u8BF7\u5237\u65B0\u540E\u91CD\u8BD5");
1266
1284
  }
@@ -1274,7 +1292,7 @@ var CodexPluginService = class {
1274
1292
 
1275
1293
  // src/core/codex-memory-service.ts
1276
1294
  import Database from "better-sqlite3";
1277
- import { readFile as readFile8, readdir as readdir6, stat as stat6 } from "fs/promises";
1295
+ import { readFile as readFile8, readdir as readdir6, stat as stat7 } from "fs/promises";
1278
1296
  import { basename, extname, isAbsolute as isAbsolute4, join as join8, relative as relative4, resolve as resolve5 } from "path";
1279
1297
  var CodexDataCompatibilityError = class extends Error {
1280
1298
  constructor(message = "Codex \u8BB0\u5FC6\u6570\u636E\u5E93\u683C\u5F0F\u6682\u4E0D\u652F\u6301") {
@@ -1409,7 +1427,7 @@ var CodexMemoryService = class {
1409
1427
  for (const entry of entries) {
1410
1428
  if (!entry.isFile() || !FILE_EXTENSIONS.has(extname(entry.name))) continue;
1411
1429
  const file = join8(paths.codexMemoriesDir, entry.name);
1412
- const st = await stat6(file);
1430
+ const st = await stat7(file);
1413
1431
  out.push({
1414
1432
  id: `file:${entry.name}`,
1415
1433
  name: entry.name,
@@ -1435,7 +1453,7 @@ var CodexMemoryService = class {
1435
1453
  return content.replace(/\s+/g, " ").trim().slice(0, 120);
1436
1454
  }
1437
1455
  async exists(file) {
1438
- return stat6(file).then(() => true).catch((err) => {
1456
+ return stat7(file).then(() => true).catch((err) => {
1439
1457
  if (err.code === "ENOENT") return false;
1440
1458
  throw err;
1441
1459
  });
@@ -1443,7 +1461,7 @@ var CodexMemoryService = class {
1443
1461
  };
1444
1462
 
1445
1463
  // src/core/codex-session-service.ts
1446
- import { readdir as readdir7, readFile as readFile9, stat as stat7 } from "fs/promises";
1464
+ import { readdir as readdir7, readFile as readFile9, stat as stat8 } from "fs/promises";
1447
1465
  import { join as join9, resolve as resolve6 } from "path";
1448
1466
  var MAX_BLOCK2 = 1e4;
1449
1467
  function truncate2(s) {
@@ -1556,7 +1574,7 @@ var CodexSessionService = class {
1556
1574
  return out;
1557
1575
  }
1558
1576
  async summarizeRollout(file) {
1559
- const st = await stat7(file);
1577
+ const st = await stat8(file);
1560
1578
  const raw = await readFile9(file, "utf8");
1561
1579
  let id = "";
1562
1580
  let cwd = "";
@@ -1825,7 +1843,7 @@ var AgentCommandService = class {
1825
1843
  };
1826
1844
 
1827
1845
  // src/core/sync-service.ts
1828
- import { cp as cp2, lstat, mkdir as mkdir5, readlink as readlink2, rm as rm3, rmdir, stat as stat8, symlink, unlink } from "fs/promises";
1846
+ import { cp as cp2, lstat, mkdir as mkdir5, readlink as readlink2, rm as rm3, rmdir, stat as stat9, symlink, unlink } from "fs/promises";
1829
1847
  import { dirname as dirname5, join as join11, resolve as resolve7 } from "path";
1830
1848
  var SyncService = class {
1831
1849
  agentsSkillsDir;
@@ -1879,7 +1897,7 @@ var SyncService = class {
1879
1897
  assertSafeName(name);
1880
1898
  const src = join11(this.agentsSkillsDir, name);
1881
1899
  try {
1882
- if (!(await stat8(src)).isDirectory()) throw new Error();
1900
+ if (!(await stat9(src)).isDirectory()) throw new Error();
1883
1901
  } catch {
1884
1902
  throw new Error(`\u672A\u627E\u5230\u516C\u5171 skill\u300C${name}\u300D`);
1885
1903
  }
@@ -1961,7 +1979,7 @@ async function main() {
1961
1979
  store,
1962
1980
  mcp: new McpService(store),
1963
1981
  skills: new SkillService(),
1964
- projects: new ProjectService(store, registryFile),
1982
+ projects: new ProjectService(store, registryFile, join12(home, ".claude.json")),
1965
1983
  memory: new MemoryService(),
1966
1984
  memoryFiles: new MemoryFilesService(),
1967
1985
  sessions: new SessionService(),
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createApp
3
- } from "../chunk-HRPQYUJV.js";
3
+ } from "../chunk-LZFMCC5M.js";
4
4
  export {
5
5
  createApp
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccpanel",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "本地图形化管理 Claude Code 配置:MCP、Skills、子 Agent、Commands、配置文件、Memory、Sessions、Plugins",
5
5
  "type": "module",
6
6
  "repository": {
@@ -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}.visible{visibility:visible}.fixed{position:fixed}.absolute{position:absolute}.inset-0{top:0;right:0;bottom:0;left:0}.right-0{right:0}.top-0{top:0}.z-40{z-index:40}.mx-1{margin-left:.25rem;margin-right:.25rem}.mb-1{margin-bottom:.25rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-auto{margin-left:auto}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.h-40{height:10rem}.h-5{height:1.25rem}.h-full{height:100%}.min-h-0{min-height:0px}.min-h-\[2\.5rem\]{min-height:2.5rem}.min-h-screen{min-height:100vh}.w-2\/5{width:40%}.w-56{width:14rem}.w-full{width:100%}.w-px{width:1px}.min-w-0{min-width:0px}.max-w-3xl{max-width:48rem}.max-w-4xl{max-width:56rem}.max-w-5xl{max-width:64rem}.max-w-6xl{max-width:72rem}.max-w-\[10rem\]{max-width:10rem}.max-w-\[14rem\]{max-width:14rem}.max-w-lg{max-width:32rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.cursor-pointer{cursor:pointer}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-end{justify-content:flex-end}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.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-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-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * 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))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-amber-600{--tw-border-opacity: 1;border-color:rgb(217 119 6 / var(--tw-border-opacity, 1))}.border-blue-200{--tw-border-opacity: 1;border-color:rgb(191 219 254 / var(--tw-border-opacity, 1))}.border-emerald-200{--tw-border-opacity: 1;border-color:rgb(167 243 208 / var(--tw-border-opacity, 1))}.border-gray-300{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity, 1))}.border-violet-200{--tw-border-opacity: 1;border-color:rgb(221 214 254 / var(--tw-border-opacity, 1))}.bg-amber-100{--tw-bg-opacity: 1;background-color:rgb(254 243 199 / var(--tw-bg-opacity, 1))}.bg-amber-50{--tw-bg-opacity: 1;background-color:rgb(255 251 235 / var(--tw-bg-opacity, 1))}.bg-amber-600{--tw-bg-opacity: 1;background-color:rgb(217 119 6 / var(--tw-bg-opacity, 1))}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1))}.bg-black\/30{background-color:#0000004d}.bg-blue-100{--tw-bg-opacity: 1;background-color:rgb(219 234 254 / var(--tw-bg-opacity, 1))}.bg-blue-50{--tw-bg-opacity: 1;background-color:rgb(239 246 255 / var(--tw-bg-opacity, 1))}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-emerald-100{--tw-bg-opacity: 1;background-color:rgb(209 250 229 / var(--tw-bg-opacity, 1))}.bg-emerald-50{--tw-bg-opacity: 1;background-color:rgb(236 253 245 / var(--tw-bg-opacity, 1))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity, 1))}.bg-gray-200{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity, 1))}.bg-gray-300{--tw-bg-opacity: 1;background-color:rgb(209 213 219 / var(--tw-bg-opacity, 1))}.bg-gray-50{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1))}.bg-green-100{--tw-bg-opacity: 1;background-color:rgb(220 252 231 / var(--tw-bg-opacity, 1))}.bg-red-100{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity, 1))}.bg-red-50{--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity, 1))}.bg-slate-100{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity, 1))}.bg-violet-100{--tw-bg-opacity: 1;background-color:rgb(237 233 254 / var(--tw-bg-opacity, 1))}.bg-violet-50{--tw-bg-opacity: 1;background-color:rgb(245 243 255 / var(--tw-bg-opacity, 1))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.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-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.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}.pl-3{padding-left:.75rem}.pr-2{padding-right:.5rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.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-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.normal-case{text-transform:none}.italic{font-style:italic}.text-amber-700{--tw-text-opacity: 1;color:rgb(180 83 9 / var(--tw-text-opacity, 1))}.text-amber-800{--tw-text-opacity: 1;color:rgb(146 64 14 / var(--tw-text-opacity, 1))}.text-blue-600{--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity, 1))}.text-blue-700{--tw-text-opacity: 1;color:rgb(29 78 216 / var(--tw-text-opacity, 1))}.text-emerald-700{--tw-text-opacity: 1;color:rgb(4 120 87 / 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-gray-700{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity, 1))}.text-gray-800{--tw-text-opacity: 1;color:rgb(31 41 55 / var(--tw-text-opacity, 1))}.text-green-600{--tw-text-opacity: 1;color:rgb(22 163 74 / var(--tw-text-opacity, 1))}.text-green-700{--tw-text-opacity: 1;color:rgb(21 128 61 / var(--tw-text-opacity, 1))}.text-red-600{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity, 1))}.text-red-700{--tw-text-opacity: 1;color:rgb(185 28 28 / var(--tw-text-opacity, 1))}.text-slate-700{--tw-text-opacity: 1;color:rgb(51 65 85 / var(--tw-text-opacity, 1))}.text-violet-400{--tw-text-opacity: 1;color:rgb(167 139 250 / var(--tw-text-opacity, 1))}.text-violet-500{--tw-text-opacity: 1;color:rgb(139 92 246 / var(--tw-text-opacity, 1))}.text-violet-700{--tw-text-opacity: 1;color:rgb(109 40 217 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.underline{text-decoration-line:underline}.line-through{text-decoration-line:line-through}.opacity-70{opacity:.7}.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)}.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{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.hover\:bg-gray-200:hover{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity, 1))}.hover\:text-red-600:hover{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity, 1))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.hover\:shadow:hover{--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)}.disabled\:opacity-50:disabled{opacity:.5}@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))}.lg\:grid-cols-\[18rem_minmax\(0\,1fr\)\]{grid-template-columns:18rem minmax(0,1fr)}}