loki-mode 10.10.1 → 10.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/SKILL.md +2 -2
  2. package/VERSION +1 -1
  3. package/dashboard/__init__.py +1 -1
  4. package/docs/CLI-REFERENCE.md +1 -1
  5. package/docs/INSTALLATION.md +1 -1
  6. package/docs/v10/BOARD.md +2 -0
  7. package/docs/v10/FAILURE-CLASSES.md +18 -2
  8. package/docs/v10/GUARDS.md +18 -0
  9. package/docs/v10/PROGRESS.md +9 -0
  10. package/docs/v10/cp-redesign/PARITY.md +188 -0
  11. package/docs/v10/cp-redesign/legacy/INDEX.md +1167 -0
  12. package/loki-ts/dist/loki.js +643 -641
  13. package/mcp/__init__.py +1 -1
  14. package/package.json +1 -1
  15. package/packages/control-plane/dist/server.js +714 -394
  16. package/packages/control-plane/src/server/diffstat.ts +77 -0
  17. package/packages/control-plane/src/server/repos.ts +5 -2
  18. package/packages/control-plane/src/server/routes/index.ts +1 -1
  19. package/packages/control-plane/src/server/runs.ts +3 -1
  20. package/packages/control-plane/ui/dist/assets/index-BinL6h3i.css +1 -0
  21. package/packages/control-plane/ui/dist/assets/index-D27veKDf.js +152 -0
  22. package/packages/control-plane/ui/dist/index.html +2 -2
  23. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  24. package/web-app/dist/assets/{AdminPage-B8RjG3sy.js → AdminPage-Dyx5OHmP.js} +1 -1
  25. package/web-app/dist/assets/{Avatar-Dttmvqc-.js → Avatar-WVxt-kYj.js} +1 -1
  26. package/web-app/dist/assets/{Badge-Bvyp8VrD.js → Badge-16vWJ-6u.js} +1 -1
  27. package/web-app/dist/assets/{Button-DtY5ZrZw.js → Button-CWVPIMTW.js} +1 -1
  28. package/web-app/dist/assets/{CockpitPage-B2gbOwRp.js → CockpitPage-D5WhogJK.js} +1 -1
  29. package/web-app/dist/assets/{ComparePage-CHi5nZTU.js → ComparePage-CjJfjxcT.js} +1 -1
  30. package/web-app/dist/assets/{ErrorBoundary-BUuTj-OQ.js → ErrorBoundary-CNnObh81.js} +1 -1
  31. package/web-app/dist/assets/{EvidenceReceiptPanel-C5aOn4ES.js → EvidenceReceiptPanel-Cn1PE_JV.js} +1 -1
  32. package/web-app/dist/assets/{GitHubIssuesPanel-isVxxHXb.js → GitHubIssuesPanel-CcH9neSU.js} +1 -1
  33. package/web-app/dist/assets/{GitHubPRsPanel-DtDpxyQl.js → GitHubPRsPanel-C-n6inCN.js} +1 -1
  34. package/web-app/dist/assets/{HomePage-Casf-LUB.js → HomePage-BZ7PCPFR.js} +2 -2
  35. package/web-app/dist/assets/{LoginPage-DvB78691.js → LoginPage-CJsigIUa.js} +1 -1
  36. package/web-app/dist/assets/{MagicPage-DU6hmbnC.js → MagicPage-ClZaKmXT.js} +1 -1
  37. package/web-app/dist/assets/{MetricsPage-DcPwzoS8.js → MetricsPage-ELQn9-cf.js} +1 -1
  38. package/web-app/dist/assets/{NotFoundPage-Bs6WxAZ8.js → NotFoundPage-Dc_zp3RF.js} +1 -1
  39. package/web-app/dist/assets/{ProjectPage-RBIqC-DJ.js → ProjectPage-B5K1NDuH.js} +3 -3
  40. package/web-app/dist/assets/{ProjectsPage-CogXD7jM.js → ProjectsPage-C2l5SiIN.js} +1 -1
  41. package/web-app/dist/assets/{SettingsPage-pAyJAfPt.js → SettingsPage-DkDNp6Pd.js} +1 -1
  42. package/web-app/dist/assets/{ShowcasePage-D6bT2bDd.js → ShowcasePage-1zasFSKz.js} +1 -1
  43. package/web-app/dist/assets/{SystemSettingsPage-CE8xiJRh.js → SystemSettingsPage-hIm_4NUx.js} +1 -1
  44. package/web-app/dist/assets/{TeamsPage-wMS55TDT.js → TeamsPage-DUb_TA22.js} +1 -1
  45. package/web-app/dist/assets/{TemplatesPage-COJNVQqu.js → TemplatesPage-B1mj-eA4.js} +1 -1
  46. package/web-app/dist/assets/{TerminalOutput-Cc2XASxd.js → TerminalOutput-BX1UwelI.js} +1 -1
  47. package/web-app/dist/assets/{activity-C6eHFDjL.js → activity-B8APKMuh.js} +1 -1
  48. package/web-app/dist/assets/{bell-BJpVYj4n.js → bell-BS8WL-tI.js} +1 -1
  49. package/web-app/dist/assets/{bot-BwRVVUQL.js → bot-e9I6yy12.js} +1 -1
  50. package/web-app/dist/assets/{check-CGDwoKy8.js → check-BbgR7Ovw.js} +1 -1
  51. package/web-app/dist/assets/{chevron-left-CF2Nilj6.js → chevron-left-BcDqv7aX.js} +1 -1
  52. package/web-app/dist/assets/{circle-alert-D0RyRec1.js → circle-alert-BnDT8wxe.js} +1 -1
  53. package/web-app/dist/assets/{clock-xeF3gW0W.js → clock-LPz79lks.js} +1 -1
  54. package/web-app/dist/assets/{cloud-D5Sy69ZZ.js → cloud-BcgBi5T1.js} +1 -1
  55. package/web-app/dist/assets/{code-xml-C6-5Ql9I.js → code-xml-5zcykgva.js} +1 -1
  56. package/web-app/dist/assets/{database-BSjG1_z0.js → database-D9KLqhHq.js} +1 -1
  57. package/web-app/dist/assets/{dollar-sign-FnDR2QUu.js → dollar-sign-CrINoZ4o.js} +1 -1
  58. package/web-app/dist/assets/{file-code-corner-BiOK9vR6.js → file-code-corner-CA7G72ip.js} +1 -1
  59. package/web-app/dist/assets/{file-plus-3WALQDEE.js → file-plus-D8tHdHn_.js} +1 -1
  60. package/web-app/dist/assets/{globe-vUdUrQqp.js → globe-CT0Ts5bO.js} +1 -1
  61. package/web-app/dist/assets/{hammer-0VwHUEpa.js → hammer-tUpu0LJk.js} +1 -1
  62. package/web-app/dist/assets/{index-BMKIwteZ.js → index-DdjeiWUI.js} +3 -3
  63. package/web-app/dist/assets/{layers-C6q8kzST.js → layers-BzzJ7w3C.js} +1 -1
  64. package/web-app/dist/assets/{loader-circle-B4Tz724d.js → loader-circle-BBdMdxIV.js} +1 -1
  65. package/web-app/dist/assets/{lock-yxvEA65n.js → lock-Cg9DkEh7.js} +1 -1
  66. package/web-app/dist/assets/{package-BpKD0Ot0.js → package-DHs8IEJR.js} +1 -1
  67. package/web-app/dist/assets/{plus-Ibqx_Vo9.js → plus-70JSAqB1.js} +1 -1
  68. package/web-app/dist/assets/{refresh-cw-C9auyvi-.js → refresh-cw-DjggkboT.js} +1 -1
  69. package/web-app/dist/assets/{rotate-ccw-2cEiXm6O.js → rotate-ccw-c1NNyyL5.js} +1 -1
  70. package/web-app/dist/assets/{scroll-text-BPAWML-w.js → scroll-text-B49wQrJ2.js} +1 -1
  71. package/web-app/dist/assets/{server-CZh6B1zx.js → server-BHKG-dDi.js} +1 -1
  72. package/web-app/dist/assets/{shield-alert-CivolUIx.js → shield-alert-Dx9MoBGm.js} +1 -1
  73. package/web-app/dist/assets/{thumbs-up-BP9covEH.js → thumbs-up-frEbS59j.js} +1 -1
  74. package/web-app/dist/assets/{trash-2-BCDfTcNx.js → trash-2-1Gd8pXFG.js} +1 -1
  75. package/web-app/dist/assets/{trending-down-5LypivXh.js → trending-down-BpUeO-FP.js} +1 -1
  76. package/web-app/dist/assets/{trending-up-DNt4SFdk.js → trending-up-CvhWp8qO.js} +1 -1
  77. package/web-app/dist/assets/{upload-DBmHbY6y.js → upload-DZuMugqd.js} +1 -1
  78. package/web-app/dist/assets/{usePolling-DOPZcbUL.js → usePolling-D5KUSkfs.js} +1 -1
  79. package/web-app/dist/assets/{user-BQTJJDCG.js → user-BF9vws84.js} +1 -1
  80. package/web-app/dist/index.html +1 -1
  81. package/packages/control-plane/ui/dist/assets/index-BQbFv65M.css +0 -1
  82. package/packages/control-plane/ui/dist/assets/index-NAMkcA9N.js +0 -165
@@ -0,0 +1,77 @@
1
+ // Changed files for a finished run when its events carry no diff: the run's receipt.json names base_sha and head_sha, and
2
+ // `git diff --numstat base head` in the run's discovered repo gives the per-file counts. Shas are validated as hex, git is
3
+ // spawned without a shell and with an explicit env, and a failure of any step reads as null (unmeasured), never as zero files.
4
+ import { execFileSync } from "node:child_process";
5
+ import { closeSync, fstatSync, openSync, readSync, realpathSync } from "node:fs";
6
+ import { join, sep } from "node:path";
7
+ import { and, eq } from "drizzle-orm";
8
+ import type { Db } from "../db/migrate.ts";
9
+ import { localRepos } from "../db/schema.ts";
10
+
11
+ export interface DiffStat {
12
+ base: string;
13
+ head: string;
14
+ files: { path: string; added: number | null; removed: number | null }[];
15
+ added: number;
16
+ removed: number;
17
+ source: "git";
18
+ }
19
+
20
+ const ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
21
+ const SHA = /^[0-9a-f]{40,64}$/;
22
+ const MAX_RECEIPT_BYTES = 1024 * 1024;
23
+ const GIT_TIMEOUT_MS = 5000;
24
+ const cache = new Map<string, DiffStat>();
25
+
26
+ const gitEnv = (): NodeJS.ProcessEnv => ({ PATH: process.env.PATH ?? "", HOME: process.env.HOME ?? "", LC_ALL: "C", GIT_TERMINAL_PROMPT: "0", GIT_OPTIONAL_LOCKS: "0" });
27
+
28
+ /** Parses `git diff --numstat` output; a binary file ("-\t-") keeps null counts. */
29
+ export function parseNumstat(out: string): DiffStat["files"] {
30
+ const files: DiffStat["files"] = [];
31
+ for (const line of out.split("\n")) {
32
+ const m = /^(\d+|-)\t(\d+|-)\t(.+)$/.exec(line);
33
+ if (!m) continue;
34
+ files.push({ path: m[3]!, added: m[1] === "-" ? null : Number(m[1]), removed: m[2] === "-" ? null : Number(m[2]) });
35
+ }
36
+ return files;
37
+ }
38
+
39
+ function readReceipt(runDir: string): { base_sha?: unknown; head_sha?: unknown } | null {
40
+ let fd: number;
41
+ try { fd = openSync(join(runDir, "receipt.json"), "r"); } catch { return null; }
42
+ try {
43
+ const st = fstatSync(fd);
44
+ if (!st.isFile() || st.size > MAX_RECEIPT_BYTES) return null;
45
+ const buf = Buffer.alloc(st.size);
46
+ readSync(fd, buf, 0, st.size, 0);
47
+ const j = JSON.parse(buf.toString("utf8"));
48
+ return j && typeof j === "object" ? j : null;
49
+ } catch { return null; } finally { closeSync(fd); }
50
+ }
51
+
52
+ /** Null when the run is unknown, has no local repo or receipt, has no valid shas, or git cannot answer. Only successes are cached (a finished run's range never changes). */
53
+ export function diffStatFor(db: Db, source: string, run: string): DiffStat | null {
54
+ if (!ID.test(source) || !ID.test(run) || source.includes("..") || run.includes("..")) return null;
55
+ const key = `${source}/${run}`;
56
+ const hit = cache.get(key);
57
+ if (hit) return hit;
58
+ const repo = db.select({ p: localRepos.realpath }).from(localRepos).where(and(eq(localRepos.sourceId, source))).get();
59
+ if (!repo) return null;
60
+ let repoReal: string, runsRoot: string, runDir: string;
61
+ try {
62
+ repoReal = realpathSync(repo.p);
63
+ runsRoot = realpathSync(join(repoReal, ".loki", "runs"));
64
+ runDir = realpathSync(join(runsRoot, run));
65
+ } catch { return null; }
66
+ if (runDir !== join(runsRoot, run) || !runDir.startsWith(runsRoot + sep)) return null;
67
+ const r = readReceipt(runDir);
68
+ const base = typeof r?.base_sha === "string" ? r.base_sha : "", head = typeof r?.head_sha === "string" ? r.head_sha : "";
69
+ if (!SHA.test(base) || !SHA.test(head)) return null;
70
+ try {
71
+ const out = execFileSync("git", ["-C", repoReal, "-c", "core.quotePath=false", "diff", "--numstat", "--no-renames", `${base}..${head}`, "--"], { env: gitEnv(), timeout: GIT_TIMEOUT_MS, maxBuffer: 8 * 1024 * 1024, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
72
+ const files = parseNumstat(out);
73
+ const stat: DiffStat = { base, head, files, added: files.reduce((n, f) => n + (f.added ?? 0), 0), removed: files.reduce((n, f) => n + (f.removed ?? 0), 0), source: "git" };
74
+ cache.set(key, stat);
75
+ return stat;
76
+ } catch { return null; }
77
+ }
@@ -1,5 +1,6 @@
1
1
  // local_repos: the server-side map from source_id to a real repo path. Filled only by local discovery (never by /v1/ingest);
2
2
  // the path never leaves the server. The API exposes display names only.
3
+ import { basename, resolve } from "node:path";
3
4
  import type { Hono } from "hono";
4
5
  import type { Db } from "../db/migrate.ts";
5
6
  import { localRepos } from "../db/schema.ts";
@@ -22,6 +23,8 @@ export function repoNames(db: Db): string[] {
22
23
  }
23
24
 
24
25
  /** GET /v1/repos. `act` is the loopback-only router; `peerIsLoopback` checks the real socket address. */
25
- export function mountRepos(act: Hono, db: Db, peerIsLoopback: (c: Parameters<Parameters<Hono["get"]>[1]>[0]) => boolean): void {
26
- act.get("/v1/repos", (c) => peerIsLoopback(c) ? c.json({ repos: repoNames(db) }) : c.json({ error: "loopback only" }, 403));
26
+ export function mountRepos(act: Hono, db: Db, peerIsLoopback: (c: Parameters<Parameters<Hono["get"]>[1]>[0]) => boolean, repoDir?: string): void {
27
+ // default_repo is the folder name of the directory the service was launched from (what a run with no repo chip uses); the path itself is never sent.
28
+ const defaultRepo = repoDir ? basename(resolve(repoDir)) || null : null;
29
+ act.get("/v1/repos", (c) => peerIsLoopback(c) ? c.json({ repos: repoNames(db), default_repo: defaultRepo }) : c.json({ error: "loopback only" }, 403));
27
30
  }
@@ -46,7 +46,7 @@ export interface RouteCtx {
46
46
  export const routeModules: ReadonlyArray<(ctx: RouteCtx) => void> = [artifacts, stream, start, control, stats, cost, costLedger, fleet, metrics, checkpoints, memory, sessionControl, config, providers, verify, integrations, notify, audit, mergeRisk];
47
47
 
48
48
  export function registerRoutes(ctx: RouteCtx): void {
49
- mountRepos(ctx.act, ctx.db, ctx.peerIsLoopback);
49
+ mountRepos(ctx.act, ctx.db, ctx.peerIsLoopback, ctx.repoDir);
50
50
  for (const m of routeModules) m(ctx);
51
51
  }
52
52
 
@@ -4,6 +4,7 @@ import type { EventEnvelope } from "../../../../loki-ts/src/engine10/types.ts";
4
4
  import type { Db } from "../db/migrate.ts";
5
5
  import { events, runs } from "../db/schema.ts";
6
6
  import { blockedQuestion } from "./answer.ts";
7
+ import { diffStatFor } from "./diffstat.ts";
7
8
  import { effectiveVerdict, pubkeysFromEnv, sealedPrefix, SUCCESS_VERDICTS, verifyRunIntegrity } from "./integrity.ts";
8
9
 
9
10
  const str = (x: unknown): string | null => (typeof x === "string" ? x : null);
@@ -65,7 +66,8 @@ export function taskTitle(db: Db, sourceId: string, runId: string): string | nul
65
66
  // A run with no run.completed yet is running: add its live fields (stage, files) from the stored events.
66
67
  const withLive = (db: Db, r: typeof runs.$inferSelect) => {
67
68
  const p = { ...parseRun(r), title: taskTitle(db, r.sourceId, r.runId) };
68
- return r.endedAt ? { ...p, current_stage: null, files_touched: [] as string[] } : { ...p, ...liveInfo(loadEvents(db, r.sourceId, r.runId)) };
69
+ // A finished run's events carry no diff; its receipt's base and head give one (git numstat, null when unmeasurable).
70
+ return r.endedAt ? { ...p, current_stage: null, files_touched: [] as string[], diff_stat: diffStatFor(db, r.sourceId, r.runId) } : { ...p, ...liveInfo(loadEvents(db, r.sourceId, r.runId)), diff_stat: null };
69
71
  };
70
72
 
71
73
  /** Live view of one run from its events so far: current stage, files touched. Elapsed is computed per request. */
@@ -0,0 +1 @@
1
+ @import"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap";:root,[data-theme=light]{--cp-bg: #F2EEE7;--cp-bg-2: #EBE6DC;--cp-bg-3: #E4DFD4;--cp-card: #FFFEFB;--cp-hover: #EBE6DC;--cp-glass: rgba(255,254,251,.82);--cp-glass-border: rgba(32,21,21,.08);--cp-glass-shadow: 0 4px 24px rgba(0,0,0,.06), 0 1px 2px rgba(0,0,0,.04);--cp-text: #201515;--cp-text-2: #36342E;--cp-text-muted: #6B675C;--cp-text-inverse: #ffffff;--cp-border: #E2DDD2;--cp-border-light: #ECEAE3;--cp-accent: #553DE9;--cp-accent-hover: #4432c4;--cp-accent-active: #3828a0;--cp-accent-glow: rgba(85,61,233,.15);--cp-accent-muted: rgba(85,61,233,.1);--cp-success: #1f8a52;--cp-warning: #9a6a12;--cp-error: #b23a3a;--cp-info: #2F71E3;--cp-success-fill: #1FC5A8;--cp-success-muted: rgba(31,197,168,.12);--cp-warning-fill: #D4A03C;--cp-warning-muted: rgba(212,160,60,.12);--cp-error-fill: #C45B5B;--cp-error-muted: rgba(196,91,91,.12);--cp-info-muted: rgba(47,113,227,.12);--cp-accent-solid: #553DE9;--cp-accent-ink: #553DE9;--cp-error-ink: #b23a3a;--cp-success-ink: #1a7546;--cp-warning-ink: #895e10;--cp-info-ink: #2963c8;--cp-text-subtle: #5f5b52;--cp-opus: #d97706;--cp-sonnet: #553DE9;--cp-haiku: #1FC5A8;--cp-shadow-sm: 0 1px 2px rgba(32,21,21,.04);--cp-shadow-md: 0 4px 6px rgba(32,21,21,.06);--cp-shadow-lg: 0 10px 15px rgba(32,21,21,.08);--cp-focus: 0 0 0 3px rgba(85,61,233,.25)}[data-theme=dark]{--cp-bg: #0F0B16;--cp-bg-2: #17121F;--cp-bg-3: #211A2C;--cp-card: #17121F;--cp-hover: #211A2C;--cp-glass: rgba(15,11,22,.82);--cp-glass-border: rgba(255,255,255,.08);--cp-glass-shadow: 0 4px 24px rgba(0,0,0,.25), 0 1px 2px rgba(0,0,0,.12);--cp-text: #F6F2EA;--cp-text-2: #CFC9BD;--cp-text-muted: #968F84;--cp-text-inverse: #0F0B16;--cp-border: rgba(255,255,255,.08);--cp-border-light: rgba(255,255,255,.04);--cp-accent: #8b7bf5;--cp-accent-hover: #9c8ff7;--cp-accent-active: #6258D0;--cp-accent-glow: rgba(139,123,245,.2);--cp-accent-muted: rgba(123,107,240,.18);--cp-success: #2ED8B6;--cp-warning: #E8B84A;--cp-error: #E07070;--cp-info: #5A9CF5;--cp-success-fill: #2ED8B6;--cp-success-muted: rgba(46,216,182,.18);--cp-warning-fill: #E8B84A;--cp-warning-muted: rgba(232,184,74,.18);--cp-error-fill: #E07070;--cp-error-muted: rgba(224,112,112,.18);--cp-info-muted: rgba(90,156,245,.18);--cp-success-ink: #2ED8B6;--cp-warning-ink: #E8B84A;--cp-info-ink: #6DA8F6;--cp-accent-solid: #6a55f2;--cp-accent-ink: #9c8ef6;--cp-error-ink: #E58989;--cp-text-subtle: #968F84;--cp-opus: #f59e0b;--cp-sonnet: #8b7bf5;--cp-haiku: #2ED8B6;--cp-shadow-sm: 0 1px 2px rgba(0,0,0,.4);--cp-shadow-md: 0 4px 12px rgba(0,0,0,.5);--cp-shadow-lg: 0 10px 25px rgba(0,0,0,.6);--cp-focus: 0 0 0 3px rgba(123,107,240,.3)}@media(prefers-color-scheme:dark){:root:not([data-theme=light]){--cp-bg: #0F0B16;--cp-bg-2: #17121F;--cp-bg-3: #211A2C;--cp-card: #17121F;--cp-hover: #211A2C;--cp-glass: rgba(15,11,22,.82);--cp-glass-border: rgba(255,255,255,.08);--cp-glass-shadow: 0 4px 24px rgba(0,0,0,.25), 0 1px 2px rgba(0,0,0,.12);--cp-text: #F6F2EA;--cp-text-2: #CFC9BD;--cp-text-muted: #968F84;--cp-text-inverse: #0F0B16;--cp-border: rgba(255,255,255,.08);--cp-border-light: rgba(255,255,255,.04);--cp-accent: #8b7bf5;--cp-accent-hover: #9c8ff7;--cp-accent-active: #6258D0;--cp-accent-glow: rgba(139,123,245,.2);--cp-accent-muted: rgba(123,107,240,.18);--cp-success: #2ED8B6;--cp-warning: #E8B84A;--cp-error: #E07070;--cp-info: #5A9CF5;--cp-success-fill: #2ED8B6;--cp-success-muted: rgba(46,216,182,.18);--cp-warning-fill: #E8B84A;--cp-warning-muted: rgba(232,184,74,.18);--cp-error-fill: #E07070;--cp-error-muted: rgba(224,112,112,.18);--cp-info-muted: rgba(90,156,245,.18);--cp-success-ink: #2ED8B6;--cp-warning-ink: #E8B84A;--cp-info-ink: #6DA8F6;--cp-accent-solid: #6a55f2;--cp-accent-ink: #9c8ef6;--cp-error-ink: #E58989;--cp-text-subtle: #968F84;--cp-opus: #f59e0b;--cp-sonnet: #8b7bf5;--cp-haiku: #2ED8B6;--cp-shadow-sm: 0 1px 2px rgba(0,0,0,.4);--cp-shadow-md: 0 4px 12px rgba(0,0,0,.5);--cp-shadow-lg: 0 10px 25px rgba(0,0,0,.6);--cp-focus: 0 0 0 3px rgba(123,107,240,.3)}}:root{--cp-font-serif: "Inter", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;--cp-font-sans: "Inter", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;--cp-font-mono: "JetBrains Mono", "Fira Code", "SF Mono", Menlo, monospace;--cp-text-xs: 10px;--cp-text-sm: 11px;--cp-text-base: 12px;--cp-text-md: 13px;--cp-text-lg: 14px;--cp-text-xl: 16px;--cp-text-2xl: 18px;--cp-text-3xl: 24px;--cp-space-xs: 4px;--cp-space-sm: 8px;--cp-space-md: 12px;--cp-space-lg: 16px;--cp-space-xl: 24px;--cp-space-2xl: 32px;--cp-space-3xl: 48px;--cp-radius-sm: 6px;--cp-radius-md: 12px;--cp-radius-lg: 16px;--cp-radius-nav: 9999px;--cp-radius-full: 9999px;--cp-ease: cubic-bezier(.4,0,.2,1);--cp-dur-fast: .1s;--cp-dur: .2s;--cp-dur-slow: .3s;--cp-sidebar-w: 264px;--cp-detail-w: 288px;--cp-rail-w: 48px;--cp-main-pad: 28px 32px;--cp-thread-max: 820px;--cp-term-bg: #0B0B0B;--cp-term-bar: #111111;--cp-term-side: #0D0D0D;--cp-term-line: #1D1D1D;--cp-term-card: #101010;--cp-term-text: #E8E4DA;--cp-term-strong: #F6F2EA;--cp-term-muted: #968F84;--cp-term-dim: #7A716A;--cp-term-purple: #7E74EE;--cp-term-lilac: #C8BEFF;--cp-term-green: #26C39B;--cp-term-amber: #F0B84A;--cp-term-red: #F07A7A;--cp-z-dropdown: 100;--cp-z-sticky: 200;--cp-z-modal: 300;--cp-z-popover: 400;--cp-z-toast: 600}.cp-root{background:var(--cp-bg);color:var(--cp-text);font-family:var(--cp-font-sans);font-size:var(--cp-text-md);line-height:1.5;-webkit-font-smoothing:antialiased;font-feature-settings:"cv02","cv03","cv04","cv11"}@keyframes cp-pulse{0%,to{opacity:1}50%{opacity:.4}}@keyframes cp-spin{to{transform:rotate(360deg)}}@keyframes cp-fade-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}.cp-fade-in{animation:cp-fade-in var(--cp-dur) var(--cp-ease)}.cp-pulse{animation:cp-pulse 2s var(--cp-ease) infinite}.cp-spinner{animation:cp-spin .8s linear infinite}@media(prefers-reduced-motion:reduce){.cp-fade-in,.cp-pulse,.cp-spinner{animation:none}}*,: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:var(--cp-font-sans);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:var(--cp-font-mono);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}.collapse{visibility:collapse}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.ml-2{margin-left:.5rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.min-h-11{min-height:2.75rem}.min-h-screen{min-height:100vh}.w-full{width:100%}.min-w-0{min-width:0px}.flex-1{flex:1 1 0%}.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))}.resize{resize:both}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-\[auto_1fr\]{grid-template-columns:auto 1fr}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-y-1{row-gap:.25rem}.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))}.overflow-x-auto{overflow-x:auto}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.border{border-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-red-500\/50{border-color:#ef444480}.border-slate-200{--tw-border-opacity: 1;border-color:rgb(226 232 240 / var(--tw-border-opacity, 1))}.border-slate-300{--tw-border-opacity: 1;border-color:rgb(203 213 225 / var(--tw-border-opacity, 1))}.bg-amber-500\/15{background-color:#f59e0b26}.bg-emerald-500\/15{background-color:#10b98126}.bg-red-500\/15{background-color:#ef444426}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.bg-red-600\/20{background-color:#dc262633}.bg-sky-500\/15{background-color:#0ea5e926}.bg-sky-600{--tw-bg-opacity: 1;background-color:rgb(2 132 199 / var(--tw-bg-opacity, 1))}.bg-sky-700{--tw-bg-opacity: 1;background-color:rgb(3 105 161 / var(--tw-bg-opacity, 1))}.bg-slate-100{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity, 1))}.bg-slate-200{--tw-bg-opacity: 1;background-color:rgb(226 232 240 / var(--tw-bg-opacity, 1))}.bg-slate-500\/15{background-color:#64748b26}.bg-violet-500\/15{background-color:#8b5cf626}.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-8{padding:2rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.pl-5{padding-left:1.25rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:var(--cp-font-mono)}.font-sans{font-family:var(--cp-font-sans)}.font-serif{font-family:var(--cp-font-serif)}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-base{font-size:1rem;line-height:1.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-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.text-amber-600{--tw-text-opacity: 1;color:rgb(217 119 6 / var(--tw-text-opacity, 1))}.text-amber-700{--tw-text-opacity: 1;color:rgb(180 83 9 / var(--tw-text-opacity, 1))}.text-emerald-600{--tw-text-opacity: 1;color:rgb(5 150 105 / var(--tw-text-opacity, 1))}.text-emerald-700{--tw-text-opacity: 1;color:rgb(4 120 87 / var(--tw-text-opacity, 1))}.text-inverse{color:var(--cp-text-inverse)}.text-muted{color:var(--cp-text-muted)}.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-sky-600{--tw-text-opacity: 1;color:rgb(2 132 199 / var(--tw-text-opacity, 1))}.text-sky-700{--tw-text-opacity: 1;color:rgb(3 105 161 / var(--tw-text-opacity, 1))}.text-slate-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.text-slate-500{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.text-slate-600{--tw-text-opacity: 1;color:rgb(71 85 105 / 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))}.accent-hover{accent-color:var(--cp-hover)}.accent-muted{accent-color:var(--cp-text-muted)}.shadow-lg{--tw-shadow: var(--cp-shadow-lg);--tw-shadow-colored: var(--cp-shadow-lg);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow: var(--cp-shadow-md);--tw-shadow-colored: var(--cp-shadow-md);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: var(--cp-shadow-sm);--tw-shadow-colored: var(--cp-shadow-sm);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline{outline-style:solid}.ring-1{--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)}.ring-amber-500\/30{--tw-ring-color: rgb(245 158 11 / .3)}.ring-emerald-500\/30{--tw-ring-color: rgb(16 185 129 / .3)}.ring-red-500\/30{--tw-ring-color: rgb(239 68 68 / .3)}.ring-red-600\/50{--tw-ring-color: rgb(220 38 38 / .5)}.ring-sky-500\/30{--tw-ring-color: rgb(14 165 233 / .3)}.ring-slate-500\/30{--tw-ring-color: rgb(100 116 139 / .3)}.ring-violet-500\/30{--tw-ring-color: rgb(139 92 246 / .3)}.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}body{margin:0;background:var(--cp-bg);color:var(--cp-text);font-family:var(--cp-font-sans)}.cp-term{background:var(--cp-term-bg);color:var(--cp-term-text);border:1px solid #1A1A1A;border-radius:20px;overflow:hidden;box-shadow:0 40px 80px -40px #0a0a0a80}.cp-term-bar{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 20px;background:var(--cp-term-bar);border-bottom:1px solid var(--cp-term-line);font-family:var(--cp-font-mono);font-size:12px;color:var(--cp-term-muted)}.cp-term-body{display:grid;grid-template-columns:1fr;min-height:320px}@media(min-width:768px){.cp-term-body{grid-template-columns:220px minmax(0,1fr)}}.cp-term-side{padding:16px;background:var(--cp-term-side);border-bottom:1px solid var(--cp-term-line);font-family:var(--cp-font-mono);font-size:12px}@media(min-width:768px){.cp-term-side{border-bottom:0;border-right:1px solid var(--cp-term-line)}}.cp-term-main{padding:20px 24px;min-width:0}.cp-term-foot{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 24px;border-top:1px solid var(--cp-term-line);font-family:var(--cp-font-mono);font-size:11px;color:var(--cp-term-muted)}.cp-tl-row{display:grid;grid-template-columns:64px 96px minmax(0,1fr);gap:4px 12px;padding:8px 0;border-bottom:1px solid var(--cp-term-line);font-size:12px;line-height:1.55}.cp-tl-meta{grid-column:3;display:flex;flex-wrap:wrap;gap:4px 14px;font-family:var(--cp-font-mono);font-size:11px;color:var(--cp-term-muted)}.cp-trunc{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.cp-eyebrow{font-family:var(--cp-font-mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--cp-text-muted)}.cp-display{font-family:var(--cp-font-sans);font-weight:650;letter-spacing:-.025em;line-height:1.12}.cp-pill-btn{border-radius:9999px}.hover\:underline:hover{text-decoration-line:underline}.disabled\:opacity-50:disabled{opacity:.5}.dark\:border-slate-700:is([data-theme=dark] *){--tw-border-opacity: 1;border-color:rgb(51 65 85 / var(--tw-border-opacity, 1))}.dark\:border-slate-800:is([data-theme=dark] *){--tw-border-opacity: 1;border-color:rgb(30 41 59 / var(--tw-border-opacity, 1))}.dark\:bg-slate-800:is([data-theme=dark] *){--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity, 1))}.dark\:bg-slate-900:is([data-theme=dark] *){--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity, 1))}.dark\:text-amber-300:is([data-theme=dark] *){--tw-text-opacity: 1;color:rgb(252 211 77 / var(--tw-text-opacity, 1))}.dark\:text-emerald-300:is([data-theme=dark] *){--tw-text-opacity: 1;color:rgb(110 231 183 / var(--tw-text-opacity, 1))}.dark\:text-emerald-400:is([data-theme=dark] *){--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity, 1))}.dark\:text-red-300:is([data-theme=dark] *){--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.dark\:text-sky-300:is([data-theme=dark] *){--tw-text-opacity: 1;color:rgb(125 211 252 / var(--tw-text-opacity, 1))}.dark\:text-sky-400:is([data-theme=dark] *){--tw-text-opacity: 1;color:rgb(56 189 248 / var(--tw-text-opacity, 1))}.dark\:text-slate-300:is([data-theme=dark] *){--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity, 1))}.dark\:text-slate-400:is([data-theme=dark] *){--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.dark\:text-violet-300:is([data-theme=dark] *){--tw-text-opacity: 1;color:rgb(196 181 253 / var(--tw-text-opacity, 1))}@media(min-width:640px){.sm\:flex{display:flex}.sm\:flex-row{flex-direction:row}.sm\:flex-wrap{flex-wrap:wrap}.sm\:items-end{align-items:flex-end}}@media(min-width:768px){.md\:flex{display:flex}.md\:table-cell{display:table-cell}.md\:hidden{display:none}.md\:min-h-0{min-height:0px}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:p-6{padding:1.5rem}.md\:px-3{padding-left:.75rem;padding-right:.75rem}.md\:py-0{padding-top:0;padding-bottom:0}.md\:py-1{padding-top:.25rem;padding-bottom:.25rem}}