@nettee/beacon 0.3.2 → 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.
- package/README.md +13 -12
- package/dist/config/profile.js +13 -10
- package/dist/dashboard/profiles.js +173 -0
- package/dist/dashboard/server.js +27 -0
- package/dist/dashboard/ui/assets/index-BNrf6SEW.css +2 -0
- package/dist/dashboard/ui/assets/index-DNeyL6DY.js +9 -0
- package/dist/dashboard/ui/index.html +3 -3
- package/examples/channels.yaml.example +2 -3
- package/examples/profiles/example/profile.yaml +1 -1
- package/package.json +1 -1
- package/dist/dashboard/ui/assets/index-BzTPJCjj.js +0 -9
- package/dist/dashboard/ui/assets/index-Cr5sBPBC.css +0 -2
package/README.md
CHANGED
|
@@ -111,16 +111,16 @@ per-Run directories with mode `0700` when Pi starts.
|
|
|
111
111
|
|
|
112
112
|
Configuration is strict: unknown YAML/JSON fields, YAML aliases or warnings, missing paths, duplicate Schedule IDs, invalid timezones/cron expressions, a missing complete `persona.md` + `task.md` pair, escaped Profile markdown, permissive secret or runtime-environment permissions, missing Profile credentials, and unknown Feishu channel names all fail startup. Beacon validates all Profiles before opening a Feishu connection.
|
|
113
113
|
|
|
114
|
-
|
|
115
|
-
(beside `config.yaml`), then referenced by semantic `name` from each Profile
|
|
114
|
+
Group notify destinations are registered globally in `~/.beacon/channels.yaml`
|
|
115
|
+
(beside `config.yaml`), then referenced by semantic `name` from each Profile's
|
|
116
|
+
`notify`. Admin DMs are **not** channel entries: each Profile keeps a raw
|
|
117
|
+
`admin.chat_id` (one bot×person private chat; putting those in the registry
|
|
118
|
+
would explode as bots and people grow).
|
|
116
119
|
|
|
117
120
|
```yaml
|
|
118
|
-
# ~/.beacon/channels.yaml
|
|
121
|
+
# ~/.beacon/channels.yaml — group chats only
|
|
119
122
|
version: 1
|
|
120
123
|
channels:
|
|
121
|
-
- name: admin-dm
|
|
122
|
-
description: Example admin direct chat
|
|
123
|
-
chat_id: REPLACE_WITH_ADMIN_DIRECT_CHAT_ID
|
|
124
124
|
- name: weekday-brief-group
|
|
125
125
|
description: Example weekday brief notify group
|
|
126
126
|
chat_id: REPLACE_WITH_GROUP_CHAT_ID
|
|
@@ -129,7 +129,7 @@ channels:
|
|
|
129
129
|
```yaml
|
|
130
130
|
# profile.yaml destinations
|
|
131
131
|
admin:
|
|
132
|
-
|
|
132
|
+
chat_id: REPLACE_WITH_ADMIN_DIRECT_CHAT_ID
|
|
133
133
|
schedules:
|
|
134
134
|
- id: weekday-brief
|
|
135
135
|
cron: "0 9 * * 1-5"
|
|
@@ -140,11 +140,12 @@ schedules:
|
|
|
140
140
|
```
|
|
141
141
|
|
|
142
142
|
Each channel has `name` (stable slug), `description` (human-readable Chinese or
|
|
143
|
-
English label), and `chat_id` (the real Feishu chat id). Profile
|
|
144
|
-
|
|
143
|
+
English label), and `chat_id` (the real Feishu group chat id). Profile `notify`
|
|
144
|
+
binds `name` and may optionally override `description` for local readability;
|
|
145
145
|
Beacon resolves `name` → `chat_id` at load time. Unknown names fail startup.
|
|
146
|
-
|
|
147
|
-
hosts can migrate one Profile at a time; new
|
|
146
|
+
`admin` must use bare `chat_id` (not a channel `name`). Legacy bare `chat_id`
|
|
147
|
+
under `notify` still loads so hosts can migrate one Profile at a time; new
|
|
148
|
+
`notify` entries should use `name`.
|
|
148
149
|
|
|
149
150
|
Each Schedule uses a five-field cron expression and an IANA timezone. Profile
|
|
150
151
|
`admin` is the private chat used for schedule `reply_text` /
|
|
@@ -216,7 +217,7 @@ Subscribe in the desired `profile.yaml`:
|
|
|
216
217
|
|
|
217
218
|
```yaml
|
|
218
219
|
admin:
|
|
219
|
-
|
|
220
|
+
chat_id: REPLACE_WITH_ADMIN_DIRECT_CHAT_ID
|
|
220
221
|
listener:
|
|
221
222
|
sources:
|
|
222
223
|
- https://deploy.example.com/production
|
package/dist/config/profile.js
CHANGED
|
@@ -10,6 +10,11 @@ const profileIdPattern = /^[a-z0-9](?:[a-z0-9_-]{0,62})$/;
|
|
|
10
10
|
export const profilePersonaFile = "persona.md";
|
|
11
11
|
export const profileTaskFile = "task.md";
|
|
12
12
|
export const workspaceProfileDirectoryName = ".beacon-profile";
|
|
13
|
+
/** Admin DM: raw Feishu chat_id only (bot×person DMs are not channel registry entries). */
|
|
14
|
+
const adminDestinationSchema = z
|
|
15
|
+
.object({ chat_id: z.string().trim().min(1) })
|
|
16
|
+
.strict();
|
|
17
|
+
/** Group notify: prefer semantic name from ~/.beacon/channels.yaml; bare chat_id still loads. */
|
|
13
18
|
const namedDestinationSchema = z
|
|
14
19
|
.object({
|
|
15
20
|
name: z
|
|
@@ -21,7 +26,7 @@ const namedDestinationSchema = z
|
|
|
21
26
|
const legacyChatDestinationSchema = z
|
|
22
27
|
.object({ chat_id: z.string().trim().min(1) })
|
|
23
28
|
.strict();
|
|
24
|
-
const
|
|
29
|
+
const notifyDestinationSchema = z.union([
|
|
25
30
|
namedDestinationSchema,
|
|
26
31
|
legacyChatDestinationSchema,
|
|
27
32
|
]);
|
|
@@ -47,7 +52,7 @@ const scheduleSchema = z
|
|
|
47
52
|
}
|
|
48
53
|
}, "Schedule timezone must be a valid IANA timezone"),
|
|
49
54
|
input: z.string().trim().min(1),
|
|
50
|
-
notify:
|
|
55
|
+
notify: notifyDestinationSchema.optional(),
|
|
51
56
|
})
|
|
52
57
|
.strict();
|
|
53
58
|
const profileDocumentSchema = z
|
|
@@ -60,13 +65,13 @@ const profileDocumentSchema = z
|
|
|
60
65
|
id: z.string().min(1),
|
|
61
66
|
})
|
|
62
67
|
.strict(),
|
|
63
|
-
admin:
|
|
68
|
+
admin: adminDestinationSchema.optional(),
|
|
64
69
|
schedules: z.array(scheduleSchema).default([]),
|
|
65
70
|
listener: z
|
|
66
71
|
.object({
|
|
67
72
|
sources: z.array(eventSourceSchema).min(1),
|
|
68
73
|
types: z.array(z.string().min(1)).min(1),
|
|
69
|
-
notify:
|
|
74
|
+
notify: notifyDestinationSchema.optional(),
|
|
70
75
|
})
|
|
71
76
|
.strict()
|
|
72
77
|
.optional(),
|
|
@@ -163,7 +168,7 @@ async function loadPersonaAndTask(profileId, workspace, profileDirectory) {
|
|
|
163
168
|
];
|
|
164
169
|
throw new Error(`Profile ${profileId} has no complete ${profilePersonaFile} + ${profileTaskFile} pair. Missing: ${missing.join(", ")}`);
|
|
165
170
|
}
|
|
166
|
-
function
|
|
171
|
+
function resolveNotifyDestination(destination, channels, channelsPath) {
|
|
167
172
|
if ("chat_id" in destination) {
|
|
168
173
|
return { chatId: destination.chat_id };
|
|
169
174
|
}
|
|
@@ -202,9 +207,7 @@ export async function loadProfile(profileId, profilesDirectory = join(homedir(),
|
|
|
202
207
|
if (config.listener && !config.admin) {
|
|
203
208
|
throw new Error(`Profile ${profileId} with listener must declare admin`);
|
|
204
209
|
}
|
|
205
|
-
const admin = config.admin
|
|
206
|
-
? resolveDestination(config.admin, channels, channelsPath)
|
|
207
|
-
: undefined;
|
|
210
|
+
const admin = config.admin ? { chatId: config.admin.chat_id } : undefined;
|
|
208
211
|
const canonicalProfileDirectory = await realpath(profileDirectory);
|
|
209
212
|
const workspace = isAbsolute(config.workspace)
|
|
210
213
|
? config.workspace
|
|
@@ -238,7 +241,7 @@ export async function loadProfile(profileId, profilesDirectory = join(homedir(),
|
|
|
238
241
|
types: config.listener.types,
|
|
239
242
|
...(config.listener.notify
|
|
240
243
|
? {
|
|
241
|
-
notify:
|
|
244
|
+
notify: resolveNotifyDestination(config.listener.notify, channels, channelsPath),
|
|
242
245
|
}
|
|
243
246
|
: {}),
|
|
244
247
|
},
|
|
@@ -251,7 +254,7 @@ export async function loadProfile(profileId, profilesDirectory = join(homedir(),
|
|
|
251
254
|
input: schedule.input,
|
|
252
255
|
...(schedule.notify
|
|
253
256
|
? {
|
|
254
|
-
notify:
|
|
257
|
+
notify: resolveNotifyDestination(schedule.notify, channels, channelsPath),
|
|
255
258
|
}
|
|
256
259
|
: {}),
|
|
257
260
|
})),
|
|
@@ -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
|
+
}
|
package/dist/dashboard/server.js
CHANGED
|
@@ -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}
|