@nettee/beacon 0.3.4 → 0.3.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.
@@ -0,0 +1,173 @@
1
+ import { readdir, stat } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { z } from "zod";
4
+ import { channelNamePattern } from "../config/channels.js";
5
+ import { parseStrictYaml } from "../config/yaml.js";
6
+ const profileIdPattern = /^[a-z0-9](?:[a-z0-9_-]{0,62})$/;
7
+ const adminDestinationSchema = z
8
+ .object({ chat_id: z.string().trim().min(1) })
9
+ .strict();
10
+ const namedDestinationSchema = z
11
+ .object({
12
+ name: z
13
+ .string()
14
+ .regex(channelNamePattern, "Channel name must be a lowercase slug (a-z, 0-9, _, -)"),
15
+ description: z.string().trim().min(1).optional(),
16
+ })
17
+ .strict();
18
+ const legacyChatDestinationSchema = z
19
+ .object({ chat_id: z.string().trim().min(1) })
20
+ .strict();
21
+ const notifyDestinationSchema = z.union([
22
+ namedDestinationSchema,
23
+ legacyChatDestinationSchema,
24
+ ]);
25
+ const scheduleSchema = z
26
+ .object({
27
+ id: z.string().regex(profileIdPattern),
28
+ cron: z.string().trim().min(1),
29
+ timezone: z.string().min(1),
30
+ input: z.string().trim().min(1),
31
+ notify: notifyDestinationSchema.optional(),
32
+ })
33
+ .strict();
34
+ const profileDocumentSchema = z
35
+ .object({
36
+ workspace: z.string().min(1),
37
+ runtime: z.literal("pi"),
38
+ model: z
39
+ .object({
40
+ provider: z.string().min(1),
41
+ id: z.string().min(1),
42
+ })
43
+ .strict(),
44
+ admin: adminDestinationSchema.optional(),
45
+ schedules: z.array(scheduleSchema).default([]),
46
+ listener: z
47
+ .object({
48
+ sources: z.array(z.string().min(1)).min(1),
49
+ types: z.array(z.string().min(1)).min(1),
50
+ notify: notifyDestinationSchema.optional(),
51
+ })
52
+ .strict()
53
+ .optional(),
54
+ })
55
+ .strict();
56
+ function toNotifyView(notify) {
57
+ if (!notify)
58
+ return null;
59
+ if ("name" in notify) {
60
+ return {
61
+ kind: "channel",
62
+ name: notify.name,
63
+ ...(notify.description ? { description: notify.description } : {}),
64
+ };
65
+ }
66
+ return { kind: "chat_id", chatId: notify.chat_id };
67
+ }
68
+ function toDetailView(profileId, document) {
69
+ return {
70
+ id: profileId,
71
+ workspace: document.workspace,
72
+ runtime: document.runtime,
73
+ model: document.model,
74
+ admin: document.admin ? { chatId: document.admin.chat_id } : null,
75
+ schedules: document.schedules.map((schedule) => ({
76
+ id: schedule.id,
77
+ cron: schedule.cron,
78
+ timezone: schedule.timezone,
79
+ input: schedule.input,
80
+ notify: toNotifyView(schedule.notify),
81
+ })),
82
+ listener: document.listener
83
+ ? {
84
+ sources: document.listener.sources,
85
+ types: document.listener.types,
86
+ notify: toNotifyView(document.listener.notify),
87
+ }
88
+ : null,
89
+ secrets: {
90
+ present: false,
91
+ note: "Feishu app credentials are stored in secrets.json and are not exposed by the dashboard.",
92
+ },
93
+ };
94
+ }
95
+ export function isProfileId(value) {
96
+ return profileIdPattern.test(value);
97
+ }
98
+ async function readProfileYaml(profilesDirectory, profileId) {
99
+ const configPath = join(profilesDirectory, profileId, "profile.yaml");
100
+ return profileDocumentSchema.parse(await parseStrictYaml(configPath));
101
+ }
102
+ export async function listProfileViews(profilesDirectory) {
103
+ let names;
104
+ try {
105
+ names = await readdir(profilesDirectory);
106
+ }
107
+ catch (error) {
108
+ throw new Error(`Cannot read Beacon profiles directory ${profilesDirectory}`, { cause: error });
109
+ }
110
+ const ids = [];
111
+ for (const name of names) {
112
+ if (!isProfileId(name))
113
+ continue;
114
+ try {
115
+ const metadata = await stat(join(profilesDirectory, name));
116
+ if (metadata.isDirectory())
117
+ ids.push(name);
118
+ }
119
+ catch {
120
+ // Skip unreadable entries.
121
+ }
122
+ }
123
+ ids.sort((left, right) => left.localeCompare(right));
124
+ const items = [];
125
+ for (const id of ids) {
126
+ try {
127
+ const document = await readProfileYaml(profilesDirectory, id);
128
+ items.push({
129
+ id,
130
+ workspace: document.workspace,
131
+ runtime: document.runtime,
132
+ model: document.model,
133
+ scheduleCount: document.schedules.length,
134
+ hasAdmin: Boolean(document.admin),
135
+ hasListener: Boolean(document.listener),
136
+ error: null,
137
+ });
138
+ }
139
+ catch (error) {
140
+ items.push({
141
+ id,
142
+ workspace: "",
143
+ runtime: "pi",
144
+ model: { provider: "", id: "" },
145
+ scheduleCount: 0,
146
+ hasAdmin: false,
147
+ hasListener: false,
148
+ error: error instanceof Error ? error.message : String(error),
149
+ });
150
+ }
151
+ }
152
+ return items;
153
+ }
154
+ export async function getProfileDetailView(profilesDirectory, profileId) {
155
+ if (!isProfileId(profileId))
156
+ return null;
157
+ const directory = join(profilesDirectory, profileId);
158
+ try {
159
+ const metadata = await stat(directory);
160
+ if (!metadata.isDirectory())
161
+ return null;
162
+ }
163
+ catch {
164
+ return null;
165
+ }
166
+ try {
167
+ const document = await readProfileYaml(profilesDirectory, profileId);
168
+ return toDetailView(profileId, document);
169
+ }
170
+ catch (error) {
171
+ throw new Error(`Cannot load Profile ${profileId}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
172
+ }
173
+ }
@@ -5,6 +5,7 @@ import { defaultChannelsPath, loadChannelRegistry, } from "../config/channels.js
5
5
  import { loadProfile } from "../config/profile.js";
6
6
  import { buildAgentSystemPrompt, } from "../runtime/system-prompt.js";
7
7
  import { exportSessionHtml } from "./export.js";
8
+ import { getProfileDetailView, isProfileId, listProfileViews, } from "./profiles.js";
8
9
  import { findRunSummary, listRunSummaries, runIdPattern, toRunListItem, } from "./records.js";
9
10
  import { sendStaticFile } from "./static.js";
10
11
  function send(response, status, contentType, body) {
@@ -68,6 +69,32 @@ export async function startDashboard(options) {
68
69
  send(response, 200, "application/json; charset=utf-8", `${JSON.stringify({ runs: rows.map(toRunListItem) })}\n`);
69
70
  return;
70
71
  }
72
+ if (url.pathname === "/api/profiles") {
73
+ const profiles = await listProfileViews(options.profilesDirectory);
74
+ send(response, 200, "application/json; charset=utf-8", `${JSON.stringify({ profiles })}\n`);
75
+ return;
76
+ }
77
+ const profileMatch = /^\/api\/profiles\/([^/]+)$/.exec(url.pathname);
78
+ if (profileMatch) {
79
+ const profileId = decodeURIComponent(profileMatch[1] ?? "");
80
+ if (!isProfileId(profileId)) {
81
+ sendText(response, 404, "Unknown profile");
82
+ return;
83
+ }
84
+ try {
85
+ const profile = await getProfileDetailView(options.profilesDirectory, profileId);
86
+ if (!profile) {
87
+ sendText(response, 404, `Unknown profile ${profileId}`);
88
+ return;
89
+ }
90
+ send(response, 200, "application/json; charset=utf-8", `${JSON.stringify({ profile })}\n`);
91
+ }
92
+ catch (error) {
93
+ const message = error instanceof Error ? error.message : String(error);
94
+ sendText(response, 500, message);
95
+ }
96
+ return;
97
+ }
71
98
  const match = /^\/runs\/([^/]+)$/.exec(url.pathname);
72
99
  if (match) {
73
100
  const runId = decodeURIComponent(match[1] ?? "");
@@ -0,0 +1,2 @@
1
+ /*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
2
+ @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-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-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial}}}@layer theme{:root,:host{--font-sans:-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-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-100:oklch(93.6% .032 17.717);--color-red-600:oklch(57.7% .245 27.325);--color-red-800:oklch(44.4% .177 26.899);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-800:oklch(47.3% .137 46.201);--color-emerald-700:oklch(50.8% .118 165.612);--color-sky-50:oklch(97.7% .013 236.62);--color-sky-100:oklch(95.1% .026 236.824);--color-sky-200:oklch(90.1% .058 230.902);--color-sky-300:oklch(82.8% .111 230.318);--color-sky-500:oklch(68.5% .169 237.323);--color-sky-600:oklch(58.8% .158 241.966);--color-sky-700:oklch(50% .134 242.749);--color-sky-800:oklch(44.3% .11 240.79);--color-sky-900:oklch(39.1% .09 240.876);--color-zinc-50:oklch(98.5% 0 none);--color-zinc-100:oklch(96.7% .001 286.375);--color-zinc-200:oklch(92% .004 286.32);--color-zinc-300:oklch(87.1% .006 286.286);--color-zinc-400:oklch(70.5% .015 286.067);--color-zinc-500:oklch(55.2% .016 285.938);--color-zinc-600:oklch(44.2% .017 285.786);--color-zinc-700:oklch(37% .013 285.805);--color-zinc-800:oklch(27.4% .006 286.033);--color-zinc-900:oklch(21% .006 285.885);--color-white:#fff;--spacing:.25rem;--container-3xl:48rem;--container-4xl:56rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-normal:0em;--tracking-wide:.025em;--radius-md:.375rem;--radius-lg:.5rem;--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)}}@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;-webkit-text-decoration:inherit;-webkit-text-decoration: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{.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.top-0{top:0}.top-full{top:100%}.left-0{left:0}.z-10{z-index:10}.z-30{z-index:30}.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)}.ml-1{margin-left:var(--spacing)}.ml-2{margin-left:calc(var(--spacing) * 2)}.block{display:block}.flex{display:flex}.grid{display:grid}.inline-flex{display:inline-flex}.h-3{height:calc(var(--spacing) * 3)}.max-h-72{max-height:calc(var(--spacing) * 72)}.min-h-screen{min-height:100vh}.w-3{width:calc(var(--spacing) * 3)}.w-64{width:calc(var(--spacing) * 64)}.w-full{width:100%}.max-w-3xl{max-width:var(--container-3xl)}.max-w-4xl{max-width:var(--container-4xl)}.min-w-0{min-width:0}.min-w-52{min-width:calc(var(--spacing) * 52)}.min-w-\[720px\]{min-width:720px}.min-w-\[960px\]{min-width:960px}.flex-1{flex:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.border-collapse{border-collapse:collapse}.cursor-pointer{cursor:pointer}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.gap-1{gap:var(--spacing)}.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>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * 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-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-x-auto{overflow-x:auto}.rounded{border-radius:.25rem}.rounded-lg{border-radius:var(--radius-lg)}.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-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-zinc-100{border-color:var(--color-zinc-100)}.border-zinc-200{border-color:var(--color-zinc-200)}.border-zinc-300{border-color:var(--color-zinc-300)}.bg-amber-100{background-color:var(--color-amber-100)}.bg-red-100{background-color:var(--color-red-100)}.bg-sky-100{background-color:var(--color-sky-100)}.bg-sky-200{background-color:var(--color-sky-200)}.bg-white{background-color:var(--color-white)}.bg-white\/90{background-color:#ffffffe6}@supports (color:color-mix(in lab, red, red)){.bg-white\/90{background-color:color-mix(in oklab, var(--color-white) 90%, transparent)}}.bg-zinc-50{background-color:var(--color-zinc-50)}.bg-zinc-50\/80{background-color:#fafafacc}@supports (color:color-mix(in lab, red, red)){.bg-zinc-50\/80{background-color:color-mix(in oklab, var(--color-zinc-50) 80%, transparent)}}.px-1{padding-inline:var(--spacing)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.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-4{padding-block:calc(var(--spacing) * 4)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-10{padding-block:calc(var(--spacing) * 10)}.pt-0{padding-top:0}.pt-4{padding-top:calc(var(--spacing) * 4)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.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-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-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.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-\[0\.2em\]{--tw-tracking:.2em;letter-spacing:.2em}.tracking-normal{--tw-tracking:var(--tracking-normal);letter-spacing:var(--tracking-normal)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-amber-600{color:var(--color-amber-600)}.text-amber-800{color:var(--color-amber-800)}.text-emerald-700{color:var(--color-emerald-700)}.text-red-600{color:var(--color-red-600)}.text-red-800{color:var(--color-red-800)}.text-sky-600{color:var(--color-sky-600)}.text-sky-700{color:var(--color-sky-700)}.text-sky-800{color:var(--color-sky-800)}.text-sky-900{color:var(--color-sky-900)}.text-zinc-400{color:var(--color-zinc-400)}.text-zinc-500{color:var(--color-zinc-500)}.text-zinc-600{color:var(--color-zinc-600)}.text-zinc-700{color:var(--color-zinc-700)}.text-zinc-800{color:var(--color-zinc-800)}.text-zinc-900{color:var(--color-zinc-900)}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.opacity-80{opacity:.8}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px 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-sky-500\/40{--tw-ring-color:#00a5ef66}@supports (color:color-mix(in lab, red, red)){.ring-sky-500\/40{--tw-ring-color:color-mix(in oklab, var(--color-sky-500) 40%, transparent)}}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.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))}.outline-none{--tw-outline-style:none;outline-style:none}.placeholder\:text-zinc-400::placeholder{color:var(--color-zinc-400)}.first\:border-t-0:first-child{border-top-style:var(--tw-border-style);border-top-width:0}.first\:pt-0:first-child{padding-top:0}@media (hover:hover){.hover\:border-sky-300:hover{border-color:var(--color-sky-300)}.hover\:bg-sky-50\/40:hover{background-color:#f0f9ff66}@supports (color:color-mix(in lab, red, red)){.hover\:bg-sky-50\/40:hover{background-color:color-mix(in oklab, var(--color-sky-50) 40%, transparent)}}.hover\:bg-white:hover{background-color:var(--color-white)}.hover\:bg-zinc-50:hover{background-color:var(--color-zinc-50)}.hover\:bg-zinc-100:hover{background-color:var(--color-zinc-100)}.hover\:text-sky-900:hover{color:var(--color-sky-900)}.hover\:text-zinc-800:hover{color:var(--color-zinc-800)}.hover\:text-zinc-900:hover{color:var(--color-zinc-900)}}.focus\:ring-2:focus{--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)}@media (width>=40rem){.sm\:col-span-2{grid-column:span 2/span 2}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (width>=48rem){.md\:sticky{position:sticky}.md\:top-0{top:0}.md\:flex{display:flex}.md\:h-screen{height:100vh}.md\:w-56{width:calc(var(--spacing) * 56)}.md\:flex-1{flex:1}.md\:flex-col{flex-direction:column}.md\:gap-0\.5{gap:calc(var(--spacing) * .5)}.md\:overflow-visible{overflow:visible}.md\:border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.md\:border-b-0{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.md\:px-3{padding-inline:calc(var(--spacing) * 3)}.md\:px-5{padding-inline:calc(var(--spacing) * 5)}.md\:pt-5{padding-top:calc(var(--spacing) * 5)}.md\:pb-3{padding-bottom:calc(var(--spacing) * 3)}.md\:pb-4{padding-bottom:calc(var(--spacing) * 4)}}}@property --tw-space-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-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}