@nettee/beacon 0.4.6 → 0.4.8

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
@@ -53,20 +53,20 @@ Copy the files under [`examples`](examples) into a private Beacon home and repla
53
53
  ├── profiles/
54
54
  │ └── example/
55
55
  │ └── profile.yaml
56
- └── workspace/
56
+ └── playbook/
57
57
  └── .beacon-profile/
58
58
  ├── persona.md
59
59
  └── task.md
60
60
  ```
61
61
 
62
- yaml names the workspace directory only. Beacon then loads `persona.md`
63
- (identity) and `task.md` (process) as a pair: `{workspace}/.beacon-profile/`
62
+ yaml names the playbook directory only. Beacon then loads `persona.md`
63
+ (identity) and `task.md` (process) as a pair: `{playbook}/.beacon-profile/`
64
64
  first; if that pair is missing, `{profileDir}/`. It never mixes the two
65
65
  places, never reads yaml `prompt:` / persona / task paths, and never falls
66
66
  back to `prompt.md`. If neither place has a complete pair, Profile load fails
67
- and lists the missing paths. Both pairs may exist; the workspace pair wins.
67
+ and lists the missing paths. Both pairs may exist; the playbook pair wins.
68
68
 
69
- Beacon prepends an English platform template for this Run: workspace limit,
69
+ Beacon prepends an English platform template for this Run: playbook limit,
70
70
  inbound vs Schedule vs event vs manual capabilities, the `reply_text` / `reply_card` /
71
71
  `no_reply` / `notify_card` contract, and optional `submit_feedback` (instruction gaps).
72
72
 
@@ -336,7 +336,7 @@ as errors with a nonzero exit status; Beacon does not fall back to another confi
336
336
  An operator can run one Profile without Feishu delivery by piping input to `trigger`; the Final Outcome is printed to stdout:
337
337
 
338
338
  ```sh
339
- printf '%s\n' 'Summarize the workspace status.' | \
339
+ printf '%s\n' 'Summarize the playbook status.' | \
340
340
  beacon trigger --profile example --input -
341
341
  ```
342
342
 
@@ -2,6 +2,8 @@ import { type EventListener } from "../events/cloudevent.js";
2
2
  import { type ChannelRegistry } from "./channels.js";
3
3
  export declare const profilePersonaFile = "persona.md";
4
4
  export declare const profileTaskFile = "task.md";
5
+ export declare const playbookProfileDirectoryName = ".beacon-profile";
6
+ /** @deprecated Use playbookProfileDirectoryName. */
5
7
  export declare const workspaceProfileDirectoryName = ".beacon-profile";
6
8
  export type DeliveryDestination = {
7
9
  chatId: string;
@@ -11,7 +13,7 @@ export type DeliveryDestination = {
11
13
  /** Resolved Profile skills. Omitted on Profile means Pi auto-discovery (today's behavior). */
12
14
  export type ProfileSkills = {
13
15
  mode: "explicit";
14
- /** Absolute paths after ~/$HOME/workspace resolution. */
16
+ /** Absolute paths after ~/$HOME/playbook resolution. */
15
17
  paths: string[];
16
18
  };
17
19
  export type Profile = {
@@ -19,7 +21,7 @@ export type Profile = {
19
21
  directory: string;
20
22
  persona: string;
21
23
  task: string;
22
- workspace: string;
24
+ playbook: string;
23
25
  runtime: "pi";
24
26
  model: {
25
27
  provider: string;
@@ -40,8 +42,10 @@ export type LoadProfileOptions = {
40
42
  channels?: ChannelRegistry;
41
43
  channelsPath?: string;
42
44
  };
43
- export declare function workspaceProfileDirectory(workspace: string): string;
44
- /** Expand `~` / `$HOME` and resolve relative paths against the Profile workspace. */
45
- export declare function resolveSkillPath(rawPath: string, workspace: string): string;
45
+ export declare function playbookProfileDirectory(playbook: string): string;
46
+ /** @deprecated Use playbookProfileDirectory. */
47
+ export declare function workspaceProfileDirectory(playbook: string): string;
48
+ /** Expand `~` / `$HOME` and resolve relative paths against the Profile playbook. */
49
+ export declare function resolveSkillPath(rawPath: string, playbook: string): string;
46
50
  export declare function loadProfile(profileId: string, profilesDirectory?: string, options?: LoadProfileOptions): Promise<Profile>;
47
51
  export declare function profileAdminChatId(profile: Profile): string;
@@ -9,7 +9,9 @@ import { parseStrictYaml } from "./yaml.js";
9
9
  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
- export const workspaceProfileDirectoryName = ".beacon-profile";
12
+ export const playbookProfileDirectoryName = ".beacon-profile";
13
+ /** @deprecated Use playbookProfileDirectoryName. */
14
+ export const workspaceProfileDirectoryName = playbookProfileDirectoryName;
13
15
  /** Admin DM: raw Feishu chat_id only (bot×person DMs are not channel registry entries). */
14
16
  const adminDestinationSchema = z
15
17
  .object({ chat_id: z.string().trim().min(1) })
@@ -61,9 +63,12 @@ const skillsSchema = z
61
63
  paths: z.array(z.string().trim().min(1)).default([]),
62
64
  })
63
65
  .strict();
64
- const profileDocumentSchema = z
66
+ const profileDocumentFieldsSchema = z
65
67
  .object({
66
- workspace: z.string().min(1),
68
+ /** Preferred config key for the Profile playbook directory. */
69
+ playbook: z.string().min(1).optional(),
70
+ /** Legacy alias for `playbook`. Dual-read during rename; prefer `playbook`. */
71
+ workspace: z.string().min(1).optional(),
67
72
  runtime: z.literal("pi"),
68
73
  model: z
69
74
  .object({
@@ -85,6 +90,34 @@ const profileDocumentSchema = z
85
90
  skills: skillsSchema.optional(),
86
91
  })
87
92
  .strict();
93
+ const profileDocumentSchema = profileDocumentFieldsSchema
94
+ .superRefine((document, context) => {
95
+ if (!document.playbook && !document.workspace) {
96
+ context.addIssue({
97
+ code: "custom",
98
+ message: "Profile must declare playbook (legacy key: workspace)",
99
+ path: ["playbook"],
100
+ });
101
+ return;
102
+ }
103
+ if (document.playbook &&
104
+ document.workspace &&
105
+ document.playbook !== document.workspace) {
106
+ context.addIssue({
107
+ code: "custom",
108
+ message: "Profile playbook and legacy workspace must match when both are set",
109
+ path: ["playbook"],
110
+ });
111
+ }
112
+ })
113
+ .transform((document) => {
114
+ const playbook = document.playbook ?? document.workspace;
115
+ if (!playbook) {
116
+ throw new Error("Profile must declare playbook (legacy key: workspace)");
117
+ }
118
+ const { workspace: _legacy, playbook: _preferred, ...rest } = document;
119
+ return { ...rest, playbook };
120
+ });
88
121
  function assertProfileId(profileId) {
89
122
  if (!profileIdPattern.test(profileId)) {
90
123
  throw new Error(`Invalid Profile ID: ${JSON.stringify(profileId)}`);
@@ -94,8 +127,12 @@ function isWithin(parent, child) {
94
127
  const path = relative(parent, child);
95
128
  return path === "" || (!path.startsWith("..") && !isAbsolute(path));
96
129
  }
97
- export function workspaceProfileDirectory(workspace) {
98
- return join(workspace, workspaceProfileDirectoryName);
130
+ export function playbookProfileDirectory(playbook) {
131
+ return join(playbook, playbookProfileDirectoryName);
132
+ }
133
+ /** @deprecated Use playbookProfileDirectory. */
134
+ export function workspaceProfileDirectory(playbook) {
135
+ return playbookProfileDirectory(playbook);
99
136
  }
100
137
  async function isExistingFile(path) {
101
138
  try {
@@ -162,16 +199,16 @@ async function loadMarkdownPair(directory, boundary) {
162
199
  task: await loadRequiredMarkdown(canonicalDirectory, profileTaskFile),
163
200
  };
164
201
  }
165
- async function loadPersonaAndTask(profileId, workspace, profileDirectory) {
166
- const workspaceDirectory = workspaceProfileDirectory(workspace);
167
- if (await markdownPairExists(workspaceDirectory)) {
168
- return loadMarkdownPair(workspaceDirectory, workspace);
202
+ async function loadPersonaAndTask(profileId, playbook, profileDirectory) {
203
+ const playbookDirectory = playbookProfileDirectory(playbook);
204
+ if (await markdownPairExists(playbookDirectory)) {
205
+ return loadMarkdownPair(playbookDirectory, playbook);
169
206
  }
170
207
  if (await markdownPairExists(profileDirectory)) {
171
208
  return loadMarkdownPair(profileDirectory, profileDirectory);
172
209
  }
173
210
  const missing = [
174
- ...(await missingMarkdownPaths(workspaceDirectory)),
211
+ ...(await missingMarkdownPaths(playbookDirectory)),
175
212
  ...(await missingMarkdownPaths(profileDirectory)),
176
213
  ];
177
214
  throw new Error(`Profile ${profileId} has no complete ${profilePersonaFile} + ${profileTaskFile} pair. Missing: ${missing.join(", ")}`);
@@ -187,8 +224,8 @@ function resolveNotifyDestination(destination, channels, channelsPath) {
187
224
  chatId: channel.chatId,
188
225
  };
189
226
  }
190
- /** Expand `~` / `$HOME` and resolve relative paths against the Profile workspace. */
191
- export function resolveSkillPath(rawPath, workspace) {
227
+ /** Expand `~` / `$HOME` and resolve relative paths against the Profile playbook. */
228
+ export function resolveSkillPath(rawPath, playbook) {
192
229
  const trimmed = rawPath.trim();
193
230
  const home = homedir();
194
231
  if (trimmed === "~")
@@ -202,7 +239,7 @@ export function resolveSkillPath(rawPath, workspace) {
202
239
  }
203
240
  if (isAbsolute(trimmed))
204
241
  return trimmed;
205
- return resolve(workspace, trimmed);
242
+ return resolve(playbook, trimmed);
206
243
  }
207
244
  async function assertValidSkillPath(profileId, resolvedPath) {
208
245
  let pathStat;
@@ -227,10 +264,10 @@ async function assertValidSkillPath(profileId, resolvedPath) {
227
264
  }
228
265
  throw new Error(`Profile ${profileId} skill path must be a directory or .md file: ${resolvedPath}`);
229
266
  }
230
- async function resolveProfileSkills(profileId, workspace, skills) {
267
+ async function resolveProfileSkills(profileId, playbook, skills) {
231
268
  const paths = [];
232
269
  for (const rawPath of skills.paths) {
233
- const resolved = resolveSkillPath(rawPath, workspace);
270
+ const resolved = resolveSkillPath(rawPath, playbook);
234
271
  await assertValidSkillPath(profileId, resolved);
235
272
  paths.push(resolved);
236
273
  }
@@ -266,31 +303,31 @@ export async function loadProfile(profileId, profilesDirectory = join(homedir(),
266
303
  }
267
304
  const admin = config.admin ? { chatId: config.admin.chat_id } : undefined;
268
305
  const canonicalProfileDirectory = await realpath(profileDirectory);
269
- const workspace = isAbsolute(config.workspace)
270
- ? config.workspace
271
- : resolve(dirname(configPath), config.workspace);
272
- let workspaceStat;
306
+ const playbook = isAbsolute(config.playbook)
307
+ ? config.playbook
308
+ : resolve(dirname(configPath), config.playbook);
309
+ let playbookStat;
273
310
  try {
274
- workspaceStat = await stat(workspace);
311
+ playbookStat = await stat(playbook);
275
312
  }
276
313
  catch (error) {
277
- throw new Error(`Cannot access Profile workspace at ${workspace}`, {
314
+ throw new Error(`Cannot access Profile playbook at ${playbook}`, {
278
315
  cause: error,
279
316
  });
280
317
  }
281
- if (!workspaceStat.isDirectory()) {
282
- throw new Error(`Profile workspace is not a directory: ${workspace}`);
318
+ if (!playbookStat.isDirectory()) {
319
+ throw new Error(`Profile playbook is not a directory: ${playbook}`);
283
320
  }
284
- const { persona, task } = await loadPersonaAndTask(profileId, workspace, canonicalProfileDirectory);
321
+ const { persona, task } = await loadPersonaAndTask(profileId, playbook, canonicalProfileDirectory);
285
322
  const skills = config.skills
286
- ? await resolveProfileSkills(profileId, workspace, config.skills)
323
+ ? await resolveProfileSkills(profileId, playbook, config.skills)
287
324
  : undefined;
288
325
  return {
289
326
  id: profileId,
290
327
  directory: canonicalProfileDirectory,
291
328
  persona,
292
329
  task,
293
- workspace,
330
+ playbook,
294
331
  runtime: config.runtime,
295
332
  model: config.model,
296
333
  ...(admin ? { admin } : {}),
@@ -39,7 +39,8 @@ const skillsSchema = z
39
39
  .strict();
40
40
  const profileDocumentSchema = z
41
41
  .object({
42
- workspace: z.string().min(1),
42
+ playbook: z.string().min(1).optional(),
43
+ workspace: z.string().min(1).optional(),
43
44
  runtime: z.literal("pi"),
44
45
  model: z
45
46
  .object({
@@ -59,7 +60,34 @@ const profileDocumentSchema = z
59
60
  .optional(),
60
61
  skills: skillsSchema.optional(),
61
62
  })
62
- .strict();
63
+ .strict()
64
+ .superRefine((document, context) => {
65
+ if (!document.playbook && !document.workspace) {
66
+ context.addIssue({
67
+ code: "custom",
68
+ message: "Profile must declare playbook (legacy key: workspace)",
69
+ path: ["playbook"],
70
+ });
71
+ return;
72
+ }
73
+ if (document.playbook &&
74
+ document.workspace &&
75
+ document.playbook !== document.workspace) {
76
+ context.addIssue({
77
+ code: "custom",
78
+ message: "Profile playbook and legacy workspace must match when both are set",
79
+ path: ["playbook"],
80
+ });
81
+ }
82
+ })
83
+ .transform((document) => {
84
+ const playbook = document.playbook ?? document.workspace;
85
+ if (!playbook) {
86
+ throw new Error("Profile must declare playbook (legacy key: workspace)");
87
+ }
88
+ const { workspace: _legacy, playbook: _preferred, ...rest } = document;
89
+ return { ...rest, playbook };
90
+ });
63
91
  function toNotifyView(notify) {
64
92
  if (!notify)
65
93
  return null;
@@ -75,7 +103,7 @@ function toNotifyView(notify) {
75
103
  function toDetailView(profileId, document) {
76
104
  return {
77
105
  id: profileId,
78
- workspace: document.workspace,
106
+ playbook: document.playbook,
79
107
  runtime: document.runtime,
80
108
  model: document.model,
81
109
  admin: document.admin ? { chatId: document.admin.chat_id } : null,
@@ -137,7 +165,7 @@ export async function listProfileViews(profilesDirectory) {
137
165
  const document = await readProfileYaml(profilesDirectory, id);
138
166
  items.push({
139
167
  id,
140
- workspace: document.workspace,
168
+ playbook: document.playbook,
141
169
  runtime: document.runtime,
142
170
  model: document.model,
143
171
  scheduleCount: document.schedules.length,
@@ -149,7 +177,7 @@ export async function listProfileViews(profilesDirectory) {
149
177
  catch (error) {
150
178
  items.push({
151
179
  id,
152
- workspace: "",
180
+ playbook: "",
153
181
  runtime: "pi",
154
182
  model: { provider: "", id: "" },
155
183
  scheduleCount: 0,
@@ -18,7 +18,7 @@ export async function runDoctor(configPath) {
18
18
  await createFeishuGateway(credentials).checkReady();
19
19
  const result = await runPiAgent({
20
20
  prompt: "Reply with exactly: BEACON_PI_RPC_OK",
21
- workspace: profile.workspace,
21
+ playbook: profile.playbook,
22
22
  provider: profile.model.provider,
23
23
  model: profile.model.id,
24
24
  systemPrompt: "Follow the user's instruction exactly. Do not use tools.",
@@ -111,7 +111,7 @@ export type RunRecord = {
111
111
  finishedAt?: string | undefined;
112
112
  provider: string;
113
113
  model: string;
114
- workspace: string;
114
+ playbook: string;
115
115
  promptDigest: string;
116
116
  /** Full `--system-prompt` text sent to Pi. Absent on records written before this field. */
117
117
  systemPrompt?: string | undefined;
@@ -184,7 +184,7 @@ export class RunOrchestrator {
184
184
  ...(state === "failed" ? { finishedAt: timestamp } : {}),
185
185
  provider: this.options.profile.model.provider,
186
186
  model: this.options.profile.model.id,
187
- workspace: this.options.profile.workspace,
187
+ playbook: this.options.profile.playbook,
188
188
  promptDigest: profilePromptDigest(this.options.profile),
189
189
  systemPrompt: continued?.systemPrompt ?? this.agentSystemPrompt(trigger),
190
190
  ...(failure ? { failure } : {}),
@@ -319,7 +319,7 @@ export class RunOrchestrator {
319
319
  try {
320
320
  const completion = await this.options.runAgent({
321
321
  prompt: promptFor(input),
322
- workspace: this.options.profile.workspace,
322
+ playbook: this.options.profile.playbook,
323
323
  provider: this.options.profile.model.provider,
324
324
  model: this.options.profile.model.id,
325
325
  systemPrompt: record.run.systemPrompt,
@@ -24,7 +24,7 @@ export function createProfileRunner(profile, outcomes, beaconCliPath, runAgent =
24
24
  const submission = outcomes.openRun();
25
25
  await runAgent({
26
26
  prompt: formatFeishuTriggerPrompt(trigger),
27
- workspace: profile.workspace,
27
+ playbook: profile.playbook,
28
28
  provider: profile.model.provider,
29
29
  model: profile.model.id,
30
30
  systemPrompt: buildAgentSystemPrompt(profile, {
@@ -2,7 +2,7 @@ import { runPiAgent } from "./pi-rpc.js";
2
2
  export async function runPiDoctor(provider, model) {
3
3
  const result = await runPiAgent({
4
4
  prompt: "Reply with exactly: BEACON_PI_RPC_OK",
5
- workspace: process.cwd(),
5
+ playbook: process.cwd(),
6
6
  provider,
7
7
  model,
8
8
  systemPrompt: "Follow the user's instruction exactly. Do not use tools.",
@@ -5,7 +5,7 @@ type PiOutcomeBinding = OutcomeBinding & {
5
5
  };
6
6
  export type PiRunRequest = {
7
7
  prompt: string;
8
- workspace: string;
8
+ playbook: string;
9
9
  provider?: string | undefined;
10
10
  model?: string | undefined;
11
11
  systemPrompt?: string | undefined;
@@ -239,8 +239,8 @@ export function createCappedNdjsonReader(maxFrameBytes, onFrame) {
239
239
  export async function runPiAgent(request, options = {}) {
240
240
  if (!request.prompt.trim())
241
241
  throw new Error("Pi Run prompt must not be empty");
242
- if (!request.workspace.trim())
243
- throw new Error("Pi Run workspace must not be empty");
242
+ if (!request.playbook.trim())
243
+ throw new Error("Pi Run playbook must not be empty");
244
244
  if ((request.provider === undefined) !== (request.model === undefined)) {
245
245
  throw new Error("Pi Run provider and model must either both be set or both be omitted");
246
246
  }
@@ -276,7 +276,7 @@ export async function runPiAgent(request, options = {}) {
276
276
  throw new Error("Pi RPC frame limit must be a positive integer");
277
277
  }
278
278
  const child = spawn(executable, buildArguments(request), {
279
- cwd: request.workspace,
279
+ cwd: request.playbook,
280
280
  env: buildPiEnvironment(request.outcome, options.environment, options.runtimeEnvironment),
281
281
  stdio: ["pipe", "pipe", "pipe"],
282
282
  });
@@ -7,4 +7,4 @@ export type AgentSystemPromptTrigger = {
7
7
  };
8
8
  export declare function agentSystemPromptTrigger(input: TriggerInput | undefined, notifyTarget: DeliveryTarget | undefined): AgentSystemPromptTrigger;
9
9
  export declare function profilePromptDigest(profile: Pick<Profile, "persona" | "task">): string;
10
- export declare function buildAgentSystemPrompt(profile: Pick<Profile, "persona" | "task" | "workspace">, trigger: AgentSystemPromptTrigger): string;
10
+ export declare function buildAgentSystemPrompt(profile: Pick<Profile, "persona" | "task" | "playbook">, trigger: AgentSystemPromptTrigger): string;
@@ -22,7 +22,7 @@ export function profilePromptDigest(profile) {
22
22
  }
23
23
  export function buildAgentSystemPrompt(profile, trigger) {
24
24
  return [
25
- `All local file reads, searches, and modifications must stay within the workspace directory and its descendants: ${profile.workspace}`,
25
+ `All local file reads, searches, and modifications must stay within the playbook directory and its descendants: ${profile.playbook}`,
26
26
  "",
27
27
  ...thisRunCapabilityInstructions(trigger),
28
28
  "",
@@ -44,7 +44,24 @@ const inputSchema = z.discriminatedUnion("kind", [
44
44
  .strict(),
45
45
  z.object({ kind: z.literal("manual"), text: z.string().min(1) }).strict(),
46
46
  ]);
47
- const runSchema = z
47
+ const runSchema = z.preprocess((value) => {
48
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
49
+ return value;
50
+ }
51
+ const record = value;
52
+ if (typeof record.playbook === "string") {
53
+ if ("workspace" in record) {
54
+ const { workspace: _legacy, ...rest } = record;
55
+ return rest;
56
+ }
57
+ return record;
58
+ }
59
+ if (typeof record.workspace === "string") {
60
+ const { workspace, ...rest } = record;
61
+ return { ...rest, playbook: workspace };
62
+ }
63
+ return record;
64
+ }, z
48
65
  .object({
49
66
  runId: z.string().min(1),
50
67
  sessionId: z
@@ -62,12 +79,12 @@ const runSchema = z
62
79
  finishedAt: timestamp.optional(),
63
80
  provider: z.string().min(1),
64
81
  model: z.string().min(1),
65
- workspace: z.string().min(1),
82
+ playbook: z.string().min(1),
66
83
  promptDigest: z.string().regex(/^[a-f0-9]{64}$/),
67
84
  systemPrompt: z.string().min(1).optional(),
68
85
  failure: failureSchema.optional(),
69
86
  })
70
- .strict();
87
+ .strict());
71
88
  const outcomeSchema = z.union([
72
89
  z
73
90
  .object({
@@ -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-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;--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;--tw-outline-style:solid}}}@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-100:oklch(95.1% .026 236.824);--color-sky-200:oklch(90.1% .058 230.902);--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-violet-100:oklch(94.3% .029 294.588);--color-violet-800:oklch(43.2% .232 292.759);--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;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--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-normal:0em;--tracking-wide:.025em;--radius-md:.375rem;--radius-lg:.5rem;--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{.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.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}.m-0{margin:0}.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)}.flex{display:flex}.grid{display:grid}.inline-flex{display:inline-flex}.size-10{width:calc(var(--spacing) * 10);height:calc(var(--spacing) * 10)}.h-3{height:calc(var(--spacing) * 3)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-7{height:calc(var(--spacing) * 7)}.max-h-72{max-height:calc(var(--spacing) * 72)}.min-h-screen{min-height:100vh}.w-3{width:calc(var(--spacing) * 3)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-7{width:calc(var(--spacing) * 7)}.w-10{width:calc(var(--spacing) * 10)}.w-56{width:calc(var(--spacing) * 56)}.w-64{width:calc(var(--spacing) * 64)}.w-full{width:100%}.min-w-0{min-width:0}.min-w-52{min-width:calc(var(--spacing) * 52)}.min-w-\[760px\]{min-width:760px}.min-w-\[960px\]{min-width:960px}.flex-1{flex:1}.shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.rotate-90{rotate:90deg}.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}.justify-center{justify-content:center}.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-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)))}: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-zinc-200>:not(:last-child)){border-color:var(--color-zinc-200)}.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)}.border{border-style:var(--tw-border-style);border-width:1px}.border-y{border-block-style:var(--tw-border-style);border-block-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-violet-100{background-color:var(--color-violet-100)}.bg-white{background-color:var(--color-white)}.bg-white\/70{background-color:#ffffffb3}@supports (color:color-mix(in lab, red, red)){.bg-white\/70{background-color:color-mix(in oklab, var(--color-white) 70%, transparent)}}.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)}}.bg-zinc-50\/90{background-color:#fafafae6}@supports (color:color-mix(in lab, red, red)){.bg-zinc-50\/90{background-color:color-mix(in oklab, var(--color-zinc-50) 90%, transparent)}}.bg-zinc-100{background-color:var(--color-zinc-100)}.bg-zinc-200{background-color:var(--color-zinc-200)}.p-0\.5{padding:calc(var(--spacing) * .5)}.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-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)}.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-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-10{padding-block:calc(var(--spacing) * 10)}.pt-0{padding-top:0}.pt-3{padding-top:calc(var(--spacing) * 3)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pl-8{padding-left:calc(var(--spacing) * 8)}.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-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))}.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-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-violet-800{color:var(--color-violet-800)}.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-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)}.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)}}.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,)}.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-transform{transition-property:transform,translate,scale,rotate;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}.select-text{-webkit-user-select:text;user-select:text}.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-zinc-300:hover{border-color:var(--color-zinc-300)}.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-800:hover{color:var(--color-sky-800)}.hover\:text-sky-900:hover{color:var(--color-sky-900)}.hover\:text-zinc-600:hover{color:var(--color-zinc-600)}.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)}.focus-visible\:outline-2:focus-visible{outline-style:var(--tw-outline-style);outline-width:2px}.focus-visible\:outline-offset-4:focus-visible{outline-offset:4px}.focus-visible\:outline-sky-600:focus-visible{outline-color:var(--color-sky-600)}@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)}}@media (width>=64rem){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}}@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}@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}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}