crewdesk 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -1,14 +1,17 @@
1
1
  # Crewdesk
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/crewdesk)](https://www.npmjs.com/package/crewdesk)
4
+ [![CI](https://github.com/HusseinTaha/crewdesk/actions/workflows/ci.yml/badge.svg)](https://github.com/HusseinTaha/crewdesk/actions/workflows/ci.yml)
5
+ ![node](https://img.shields.io/node/v/crewdesk)
6
+ ![platforms](https://img.shields.io/badge/platforms-macOS%20%7C%20Linux%20%7C%20Windows-blue)
7
+ [![license](https://img.shields.io/npm/l/crewdesk)](LICENSE)
8
+
3
9
  One local dashboard for all your Claude Code sessions. When any session asks a question, wants permission, or finishes a turn, it shows up in one attention queue at **http://127.0.0.1:7777**. Your answer goes back to the exact session that asked. Routing uses the event id and the session id, never the name or terminal.
4
10
 
5
- ```text
6
- Claude #1 ─┐
7
- Claude #2 ─┤ hooks ┌──────────────┐ WebSocket ┌───────────┐
8
- Claude #3 ─┼──────────▶ │ crewdesk │ ◀──────────▶ │ Dashboard │
9
- Claude #4 ─┘ ◀──────────│ SQLite + API │ REST └───────────┘
10
- decisions └──────────────┘
11
- ```
11
+ <picture>
12
+ <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/HusseinTaha/crewdesk/main/docs/images/dashboard-light.png">
13
+ <img alt="Crewdesk dashboard: five Claude Code sessions, a destructive permission request, a question and recent activity" src="https://raw.githubusercontent.com/HusseinTaha/crewdesk/main/docs/images/dashboard-dark.png">
14
+ </picture>
12
15
 
13
16
  ## Quick start
14
17
 
@@ -23,6 +26,46 @@ Install it globally rather than running it through `npx`. The hooks point at the
23
26
 
24
27
  Start Claude Code sessions normally. Sessions that were already running need a restart, or run `/hooks`, to pick up the hooks.
25
28
 
29
+ ## How it works
30
+
31
+ ```mermaid
32
+ flowchart LR
33
+ subgraph sessions["Claude Code sessions (any project, any account)"]
34
+ C1["payments-api #1"]
35
+ C2["storefront-web #1"]
36
+ C3["mobile-app #1"]
37
+ end
38
+ H["crewdesk-hook<br/>(one short process per hook call)"]
39
+ HUB[("Crewdesk hub<br/>Fastify + SQLite<br/>127.0.0.1:7777")]
40
+ UI["Dashboard<br/>(browser)"]
41
+
42
+ C1 & C2 & C3 -- "hook JSON on stdin" --> H
43
+ H -- "POST event, then long-poll" --> HUB
44
+ HUB -- "WebSocket push" --> UI
45
+ UI -- "Allow / Deny / answer / next prompt" --> HUB
46
+ HUB -- "decision" --> H
47
+ H -- "hook output" --> C1 & C2 & C3
48
+ ```
49
+
50
+ Each hook call is routed by **event id → the session id stored with that event**, so an answer can only ever reach the session that asked. The terminal stays live the whole time. Whichever answers first, the dashboard or the terminal, wins.
51
+
52
+ ```mermaid
53
+ sequenceDiagram
54
+ autonumber
55
+ participant CC as Claude Code
56
+ participant HK as crewdesk-hook
57
+ participant HUB as Crewdesk hub
58
+ participant UI as Dashboard
59
+ CC->>HK: PermissionRequest (Bash: rm -rf dist && pnpm build)
60
+ HK->>HUB: POST /api/events
61
+ HUB-->>UI: WebSocket: new request (desktop notification + chime)
62
+ HK->>HUB: GET /api/events/:id/wait (long-poll)
63
+ UI->>HUB: Allow
64
+ HUB-->>HK: { action: "allow" }
65
+ HK-->>CC: allow → the tool runs
66
+ Note over HK,HUB: Hub down or unreachable? The hook exits 0 and Claude Code carries on as if no hook existed.
67
+ ```
68
+
26
69
  ## What reaches the dashboard
27
70
 
28
71
  | Claude Code does… | Hook | Dashboard shows | Terminal meanwhile |
@@ -35,6 +78,37 @@ Start Claude Code sessions normally. Sessions that were already running need a r
35
78
 
36
79
  If the hub is down, every hook fails open: Claude Code behaves exactly as if no hook existed.
37
80
 
81
+ ## Screenshots
82
+
83
+ **Permission requests.** Destructive commands are flagged and need a second confirm.
84
+
85
+ ![Permission request for rm -rf with a destructive-command warning](https://raw.githubusercontent.com/HusseinTaha/crewdesk/main/docs/images/permission.png)
86
+
87
+ **Questions.** `AskUserQuestion` options become buttons, and you can also type a custom answer.
88
+
89
+ ![AskUserQuestion rendered as option buttons](https://raw.githubusercontent.com/HusseinTaha/crewdesk/main/docs/images/question.png)
90
+
91
+ **Idle sessions.** When Claude finishes a turn, send the next instruction straight from the dashboard.
92
+
93
+ ![Waiting for instructions card with a follow-up prompt typed in](https://raw.githubusercontent.com/HusseinTaha/crewdesk/main/docs/images/waiting.png)
94
+
95
+ **Desktop notifications.** Opt in with **Notify**. They fire while the dashboard tab is in the background.
96
+
97
+ ![Desktop notification: storefront-web #2 needs your attention, permission request git push](https://raw.githubusercontent.com/HusseinTaha/crewdesk/main/docs/images/notification.png)
98
+
99
+ <table>
100
+ <tr>
101
+ <td width="50%"><b>History</b>: every event, filterable by type, status, project and agent.<br><img alt="Event history table" src="https://raw.githubusercontent.com/HusseinTaha/crewdesk/main/docs/images/history.png"></td>
102
+ <td width="50%"><b>Agent detail</b>: project, directory, pid, account, current task and pending requests.<br><img alt="Agent detail page" src="https://raw.githubusercontent.com/HusseinTaha/crewdesk/main/docs/images/agent-detail.png"></td>
103
+ </tr>
104
+ <tr>
105
+ <td width="50%"><b>Keyboard-first</b>: <code>J</code>/<code>K</code> to move, <code>A</code>/<code>D</code> to allow or deny, <code>?</code> for help.<br><img alt="Keyboard shortcuts dialog" src="https://raw.githubusercontent.com/HusseinTaha/crewdesk/main/docs/images/shortcuts.png"></td>
106
+ <td width="50%" align="center"><b>Responsive</b>: fits a narrow window or a split screen next to your terminal.<br><img alt="Dashboard at phone width" src="https://raw.githubusercontent.com/HusseinTaha/crewdesk/main/docs/images/mobile.png" width="260"></td>
107
+ </tr>
108
+ </table>
109
+
110
+ <sub>Screenshots use made-up sessions and are regenerated with <code>node scripts/screenshots.mjs</code>.</sub>
111
+
38
112
  ## Features
39
113
 
40
114
  - **Agents:** multiple sessions per project, multiple accounts, named `<project> #N` and renamable. Session reconnects are detected. A session goes OFFLINE when its Claude process (`CLAUDE_PID`) dies.
@@ -44,6 +118,7 @@ If the hub is down, every hook fails open: Claude Code behaves exactly as if no
44
118
  - **Notifications:** desktop notifications (opt-in) and sound. Dark and light themes.
45
119
  - **Permission policies:** in `~/.crewdesk/policies.yaml`. Destructive or chained shell commands are never auto-allowed.
46
120
  - **Persistence:** everything is persisted in SQLite, so a browser refresh or hub restart loses nothing.
121
+ - **Cross-platform:** tested in CI on macOS, Linux and Windows with Node 22 and 24.
47
122
 
48
123
  ## Commands
49
124
 
@@ -0,0 +1 @@
1
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:"Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;--font-mono:"JetBrains Mono", ui-monospace, "Cascadia Code", Consolas, monospace;--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-4xl:56rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--radius-md:.375rem;--radius-xl:.75rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-card:var(--card);--color-card-2:var(--card-2);--color-line:var(--line);--color-fg:var(--fg);--color-muted:var(--muted);--color-st-blue:#3b82f6;--color-st-green:#22c55e;--color-st-yellow:#eab308;--color-st-orange:#f97316;--color-st-red:#ef4444;--color-st-purple:#a855f7;--color-st-gray:#6b7280}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}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;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.fixed{position:fixed}.sticky{position:sticky}.inset-0{inset:0}.top-0{top:0}.z-50{z-index:50}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mr-2{margin-right:calc(var(--spacing) * 2)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.ml-1{margin-left:var(--spacing)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-auto{margin-left:auto}.line-clamp-2{-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-flex{display:inline-flex}.table{display:table}.h-2{height:calc(var(--spacing) * 2)}.h-full{height:100%}.max-h-48{max-height:calc(var(--spacing) * 48)}.max-h-60{max-height:calc(var(--spacing) * 60)}.max-h-72{max-height:calc(var(--spacing) * 72)}.min-h-0{min-height:0}.w-2{width:calc(var(--spacing) * 2)}.w-16{width:calc(var(--spacing) * 16)}.w-36{width:calc(var(--spacing) * 36)}.w-44{width:calc(var(--spacing) * 44)}.w-56{width:calc(var(--spacing) * 56)}.w-full{width:100%}.max-w-4xl{max-width:var(--container-4xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-full{max-width:100%}.max-w-sm{max-width:var(--container-sm)}.min-w-0{min-width:0}.min-w-32{min-width:calc(var(--spacing) * 32)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.basis-full{flex-basis:100%}.resize-y{resize:vertical}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.content-start{align-content:flex-start}.items-baseline{align-items:baseline}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}:where(.space-y-1\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 5) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-y-2{row-gap:calc(var(--spacing) * 2)}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-line>:not(:last-child)){border-color:var(--color-line)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-line{border-color:var(--color-line)}.border-st-blue{border-color:var(--color-st-blue)}.border-st-orange{border-color:var(--color-st-orange)}.border-st-orange\/60{border-color:#f9731699}@supports (color:color-mix(in lab,red,red)){.border-st-orange\/60{border-color:color-mix(in oklab,var(--color-st-orange) 60%,transparent)}}.border-st-purple{border-color:var(--color-st-purple)}.border-st-red{border-color:var(--color-st-red)}.border-st-yellow{border-color:var(--color-st-yellow)}.bg-black\/60{background-color:#0009}@supports (color:color-mix(in lab,red,red)){.bg-black\/60{background-color:color-mix(in oklab,var(--color-black) 60%,transparent)}}.bg-card{background-color:var(--color-card)}.bg-card-2{background-color:var(--color-card-2)}.bg-st-blue{background-color:var(--color-st-blue)}.bg-st-blue\/15{background-color:#3b82f626}@supports (color:color-mix(in lab,red,red)){.bg-st-blue\/15{background-color:color-mix(in oklab,var(--color-st-blue) 15%,transparent)}}.bg-st-gray{background-color:var(--color-st-gray)}.bg-st-green{background-color:var(--color-st-green)}.bg-st-orange{background-color:var(--color-st-orange)}.bg-st-orange\/15{background-color:#f9731626}@supports (color:color-mix(in lab,red,red)){.bg-st-orange\/15{background-color:color-mix(in oklab,var(--color-st-orange) 15%,transparent)}}.bg-st-orange\/20{background-color:#f9731633}@supports (color:color-mix(in lab,red,red)){.bg-st-orange\/20{background-color:color-mix(in oklab,var(--color-st-orange) 20%,transparent)}}.bg-st-purple{background-color:var(--color-st-purple)}.bg-st-purple\/15{background-color:#a855f726}@supports (color:color-mix(in lab,red,red)){.bg-st-purple\/15{background-color:color-mix(in oklab,var(--color-st-purple) 15%,transparent)}}.bg-st-red{background-color:var(--color-st-red)}.bg-st-red\/10{background-color:#ef44441a}@supports (color:color-mix(in lab,red,red)){.bg-st-red\/10{background-color:color-mix(in oklab,var(--color-st-red) 10%,transparent)}}.bg-st-red\/15{background-color:#ef444426}@supports (color:color-mix(in lab,red,red)){.bg-st-red\/15{background-color:color-mix(in oklab,var(--color-st-red) 15%,transparent)}}.bg-st-yellow{background-color:var(--color-st-yellow)}.bg-st-yellow\/15{background-color:#eab30826}@supports (color:color-mix(in lab,red,red)){.bg-st-yellow\/15{background-color:color-mix(in oklab,var(--color-st-yellow) 15%,transparent)}}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.p-8{padding:calc(var(--spacing) * 8)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pr-1{padding-right:var(--spacing)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.\[overflow-wrap\:normal\]{overflow-wrap:normal}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-black{color:var(--color-black)}.text-fg{color:var(--color-fg)}.text-muted{color:var(--color-muted)}.text-st-blue{color:var(--color-st-blue)}.text-st-gray{color:var(--color-st-gray)}.text-st-green{color:var(--color-st-green)}.text-st-orange{color:var(--color-st-orange)}.text-st-purple{color:var(--color-st-purple)}.text-st-red{color:var(--color-st-red)}.text-st-yellow{color:var(--color-st-yellow)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.line-through{text-decoration-line:line-through}.underline{text-decoration-line:underline}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-st-blue\/60{--tw-ring-color:#3b82f699}@supports (color:color-mix(in lab,red,red)){.ring-st-blue\/60{--tw-ring-color:color-mix(in oklab, var(--color-st-blue) 60%, transparent)}}.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,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.placeholder\:text-muted::placeholder{color:var(--color-muted)}@media(hover:hover){.hover\:border-st-blue\/60:hover{border-color:#3b82f699}@supports (color:color-mix(in lab,red,red)){.hover\:border-st-blue\/60:hover{border-color:color-mix(in oklab,var(--color-st-blue) 60%,transparent)}}.hover\:border-st-orange:hover{border-color:var(--color-st-orange)}.hover\:bg-card-2:hover{background-color:var(--color-card-2)}.hover\:bg-st-green\/85:hover{background-color:#22c55ed9}@supports (color:color-mix(in lab,red,red)){.hover\:bg-st-green\/85:hover{background-color:color-mix(in oklab,var(--color-st-green) 85%,transparent)}}.hover\:bg-st-orange\/85:hover{background-color:#f97316d9}@supports (color:color-mix(in lab,red,red)){.hover\:bg-st-orange\/85:hover{background-color:color-mix(in oklab,var(--color-st-orange) 85%,transparent)}}.hover\:bg-st-red\/85:hover{background-color:#ef4444d9}@supports (color:color-mix(in lab,red,red)){.hover\:bg-st-red\/85:hover{background-color:color-mix(in oklab,var(--color-st-red) 85%,transparent)}}.hover\:bg-st-yellow\/85:hover{background-color:#eab308d9}@supports (color:color-mix(in lab,red,red)){.hover\:bg-st-yellow\/85:hover{background-color:color-mix(in oklab,var(--color-st-yellow) 85%,transparent)}}.hover\:text-fg:hover{color:var(--color-fg)}.hover\:underline:hover{text-decoration-line:underline}}.focus\:border-st-blue:focus{border-color:var(--color-st-blue)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media(min-width:40rem){.sm\:table-cell{display:table-cell}}@media(min-width:48rem){.md\:line-clamp-none{-webkit-line-clamp:unset;-webkit-box-orient:horizontal;display:block;overflow:visible}.md\:table-cell{display:table-cell}.md\:flex-1{flex:1}.md\:basis-auto{flex-basis:auto}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-nowrap{flex-wrap:nowrap}.md\:truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}}@media(min-width:64rem){.lg\:inline{display:inline}.lg\:min-h-0{min-height:0}.lg\:flex-1{flex:1}.lg\:grid-cols-\[320px_minmax\(0\,1fr\)\]{grid-template-columns:320px minmax(0,1fr)}.lg\:content-stretch{align-content:stretch}.lg\:overflow-hidden{overflow:hidden}.lg\:overflow-y-auto{overflow-y:auto}}}:root{--bg:#f5f7fa;--card:#fff;--card-2:#eef1f5;--line:#d9dee6;--fg:#111827;--muted:#5b6574;color-scheme:light}:root.dark{--bg:#0b0d10;--card:#151922;--card-2:#1b202b;--line:#252b36;--fg:#f4f7fa;--muted:#8b95a5;color-scheme:dark}html,body,#root{height:100%}body{background:var(--bg);color:var(--fg);font-family:var(--font-sans);-webkit-font-smoothing:antialiased;overflow-wrap:anywhere}:focus-visible{outline-offset:2px;outline:2px solid #3b82f6}.pulse-dot{animation:1.6s ease-in-out infinite pulse-dot}@keyframes pulse-dot{0%,to{opacity:1}50%{opacity:.35}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}