heyio 0.11.0 → 0.12.0
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/dist/api/server.js +46 -5
- package/dist/copilot/agents.js +3 -0
- package/dist/copilot/skills.js +97 -13
- package/dist/copilot/tools.js +3 -2
- package/dist/index.js +5 -2
- package/package.json +1 -1
- package/web-dist/assets/index-C6i8dB9S.css +1 -0
- package/web-dist/assets/index-I-mPEKj7.js +79 -0
- package/web-dist/index.html +2 -2
- package/web-dist/assets/index-Do8uw7iA.css +0 -1
- package/web-dist/assets/index-FoS7-hiM.js +0 -78
package/dist/api/server.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
import { existsSync } from "node:fs";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import express from "express";
|
|
5
5
|
import { config } from "../config.js";
|
|
6
|
-
import { listSkills, installSkill } from "../copilot/skills.js";
|
|
6
|
+
import { listSkills, installSkill, installSkillFromContent } from "../copilot/skills.js";
|
|
7
7
|
import { listSquads, createSquad, listSquadAgents } from "../store/squads.js";
|
|
8
8
|
import { getAgentInfo, cancelAgentTask, getTaskEvents, subscribeToTaskEvents } from "../copilot/agents.js";
|
|
9
9
|
import { summarize, summarizeEvent } from "../copilot/event-summary.js";
|
|
10
10
|
import { abortOrchestrator } from "../copilot/orchestrator.js";
|
|
11
11
|
import { getActiveTasks, getTask, listRecentTasks } from "../store/tasks.js";
|
|
12
|
-
import { IO_VERSION } from "../paths.js";
|
|
12
|
+
import { IO_VERSION, SKILLS_DIR } from "../paths.js";
|
|
13
13
|
import { requireAuth } from "./auth.js";
|
|
14
14
|
import { listSchedules, getSchedule, deleteSchedule, setScheduleEnabled } from "../store/schedules.js";
|
|
15
15
|
import { listIoSchedules, getIoSchedule, deleteIoSchedule, setIoScheduleEnabled } from "../store/io-schedules.js";
|
|
@@ -76,6 +76,46 @@ export async function startApiServer() {
|
|
|
76
76
|
res.status(500).json({ error: "Failed to list skills" });
|
|
77
77
|
}
|
|
78
78
|
});
|
|
79
|
+
// Get a single skill's SKILL.md content by slug (issue #119)
|
|
80
|
+
api.get("/skills/:slug", (req, res) => {
|
|
81
|
+
const slug = Array.isArray(req.params.slug) ? req.params.slug[0] : req.params.slug;
|
|
82
|
+
if (!slug || slug.includes("..") || slug.includes("/") || slug.includes("\\")) {
|
|
83
|
+
res.status(400).json({ error: "Invalid skill slug" });
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const skillFile = `${SKILLS_DIR}/${slug}/SKILL.md`;
|
|
87
|
+
try {
|
|
88
|
+
if (!existsSync(skillFile)) {
|
|
89
|
+
res.status(404).json({ error: "Skill not found" });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const content = readFileSync(skillFile, "utf-8");
|
|
93
|
+
res.json({ slug, content });
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
console.error("Error reading skill content:", e);
|
|
97
|
+
res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
// Install a skill from pasted SKILL.md content (issue #117)
|
|
101
|
+
api.post("/skills/paste", (req, res) => {
|
|
102
|
+
const { content: skillContent, slug } = req.body;
|
|
103
|
+
if (!skillContent || typeof skillContent !== "string" || skillContent.trim() === "") {
|
|
104
|
+
res.status(400).json({ error: "Missing or empty required field: content" });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (!slug || typeof slug !== "string" || slug.trim() === "") {
|
|
108
|
+
res.status(400).json({ error: "Missing or empty required field: slug" });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const skill = installSkillFromContent(skillContent, slug.trim());
|
|
113
|
+
res.status(201).json({ skill });
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
res.status(400).json({ error: e instanceof Error ? e.message : String(e) });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
79
119
|
// Install a skill from a git repo URL (mirrors the skill_install tool)
|
|
80
120
|
api.post("/skills", async (req, res) => {
|
|
81
121
|
const { repoUrl } = req.body;
|
|
@@ -98,8 +138,9 @@ export async function startApiServer() {
|
|
|
98
138
|
return;
|
|
99
139
|
}
|
|
100
140
|
try {
|
|
101
|
-
const
|
|
102
|
-
|
|
141
|
+
const result = await installSkill(trimmed);
|
|
142
|
+
const skills = Array.isArray(result) ? result : [result];
|
|
143
|
+
res.status(201).json({ skill: skills[0], skills });
|
|
103
144
|
}
|
|
104
145
|
catch (e) {
|
|
105
146
|
console.error("Error installing skill:", e);
|
package/dist/copilot/agents.js
CHANGED
|
@@ -7,6 +7,7 @@ import { homedir } from "os";
|
|
|
7
7
|
import { defineTool, approveAll } from "@github/copilot-sdk";
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
import { getClient } from "./client.js";
|
|
10
|
+
import { getSkillDirectories } from "./skills.js";
|
|
10
11
|
import { sendWithIdleTimeout } from "./session-timeout.js";
|
|
11
12
|
import { getModelForTask, getModelForTier, classifyComplexity } from "./model-router.js";
|
|
12
13
|
import { getSquad, updateSquadSession, updateSquadStatus, getDecisions, getDecisionsSummary, logDecision, listSquadAgents, getSquadAgent, getSquadLead, updateAgentSession, updateAgentStatus, } from "../store/squads.js";
|
|
@@ -460,6 +461,7 @@ Stay in character — let your personality color your work style and communicati
|
|
|
460
461
|
streaming: false,
|
|
461
462
|
systemMessage: { content: systemMessage },
|
|
462
463
|
tools: agentTools,
|
|
464
|
+
skillDirectories: getSkillDirectories(),
|
|
463
465
|
onPermissionRequest: approveAll,
|
|
464
466
|
infiniteSessions: {
|
|
465
467
|
enabled: true,
|
|
@@ -522,6 +524,7 @@ You are a coding agent. Use the shell tool to run commands and file_ops to read/
|
|
|
522
524
|
Log important decisions with squad_log_decision so they persist.`,
|
|
523
525
|
},
|
|
524
526
|
tools: agentTools,
|
|
527
|
+
skillDirectories: getSkillDirectories(),
|
|
525
528
|
onPermissionRequest: approveAll,
|
|
526
529
|
infiniteSessions: {
|
|
527
530
|
enabled: true,
|
package/dist/copilot/skills.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "fs";
|
|
2
|
-
import { join, basename } from "path";
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "fs";
|
|
2
|
+
import { join, basename, resolve } from "path";
|
|
3
3
|
import { execFileSync } from "child_process";
|
|
4
4
|
import { SKILLS_DIR } from "../paths.js";
|
|
5
5
|
/**
|
|
@@ -155,9 +155,61 @@ async function installSkillFromFile(rawUrl, slug) {
|
|
|
155
155
|
path: destDir,
|
|
156
156
|
};
|
|
157
157
|
}
|
|
158
|
+
function isValidSkillContent(content) {
|
|
159
|
+
const first10 = content.split(/\r?\n/).slice(0, 10);
|
|
160
|
+
return first10.some((line) => /^#\s+/.test(line));
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Install a skill from raw markdown content and a caller-chosen slug.
|
|
164
|
+
* Useful for paste-to-install workflows.
|
|
165
|
+
*/
|
|
166
|
+
export function installSkillFromContent(content, slug) {
|
|
167
|
+
if (!slug || /[\/\\]/.test(slug) || slug === "." || slug === ".." || slug.includes("..")) {
|
|
168
|
+
throw new Error("Invalid slug — must be a non-empty string without path separators or traversals.");
|
|
169
|
+
}
|
|
170
|
+
const destDir = join(SKILLS_DIR, slug);
|
|
171
|
+
if (!resolve(destDir).startsWith(resolve(SKILLS_DIR) + "/")) {
|
|
172
|
+
throw new Error("Invalid slug — resolved path escapes the skills directory.");
|
|
173
|
+
}
|
|
174
|
+
if (existsSync(destDir)) {
|
|
175
|
+
throw new Error(`Skill "${slug}" is already installed.`);
|
|
176
|
+
}
|
|
177
|
+
if (!isValidSkillContent(content)) {
|
|
178
|
+
throw new Error("Content does not appear to be a valid SKILL.md (no heading found in first 10 lines).");
|
|
179
|
+
}
|
|
180
|
+
mkdirSync(destDir, { recursive: true });
|
|
181
|
+
writeFileSync(join(destDir, "SKILL.md"), content, "utf-8");
|
|
182
|
+
const { name, description } = parseSkillMd(content);
|
|
183
|
+
return {
|
|
184
|
+
name: name || slug,
|
|
185
|
+
slug,
|
|
186
|
+
description,
|
|
187
|
+
path: destDir,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Scan a cloned repo for SKILL.md files in subdirectories (1 level deep).
|
|
192
|
+
* Returns an array of { subdir, skillMdPath } for each found skill.
|
|
193
|
+
*/
|
|
194
|
+
function discoverSkillsInRepo(repoDir) {
|
|
195
|
+
const found = [];
|
|
196
|
+
for (const entry of readdirSync(repoDir)) {
|
|
197
|
+
const subPath = join(repoDir, entry);
|
|
198
|
+
if (!statSync(subPath).isDirectory())
|
|
199
|
+
continue;
|
|
200
|
+
if (entry.startsWith(".") || entry === ".." || entry.includes(".."))
|
|
201
|
+
continue;
|
|
202
|
+
const mdPath = join(subPath, "SKILL.md");
|
|
203
|
+
if (existsSync(mdPath)) {
|
|
204
|
+
found.push({ subdir: entry, skillMdPath: mdPath });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return found;
|
|
208
|
+
}
|
|
158
209
|
/**
|
|
159
210
|
* Install a skill from a git repo URL or a direct SKILL.md file URL.
|
|
160
|
-
*
|
|
211
|
+
* Returns a single SkillInfo for single-skill repos/files, or an array
|
|
212
|
+
* for repos containing multiple SKILL.md files in subdirectories.
|
|
161
213
|
*/
|
|
162
214
|
export async function installSkill(input) {
|
|
163
215
|
let destDir;
|
|
@@ -177,19 +229,51 @@ export async function installSkill(input) {
|
|
|
177
229
|
timeout: 60_000,
|
|
178
230
|
});
|
|
179
231
|
const skillMdPath = join(destDir, "SKILL.md");
|
|
180
|
-
if (
|
|
232
|
+
if (existsSync(skillMdPath)) {
|
|
233
|
+
// Single-skill repo (root SKILL.md)
|
|
234
|
+
const content = readFileSync(skillMdPath, "utf-8");
|
|
235
|
+
const { name, description } = parseSkillMd(content);
|
|
236
|
+
return {
|
|
237
|
+
name: name || repoName,
|
|
238
|
+
slug: repoName,
|
|
239
|
+
description,
|
|
240
|
+
path: destDir,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
// No root SKILL.md — scan subdirectories for multi-skill repos
|
|
244
|
+
const discovered = discoverSkillsInRepo(destDir);
|
|
245
|
+
if (discovered.length === 0) {
|
|
181
246
|
rmSync(destDir, { recursive: true, force: true });
|
|
182
247
|
destDir = undefined;
|
|
183
|
-
throw new Error(`Repository "${repoUrl}" does not contain a SKILL.md file
|
|
248
|
+
throw new Error(`Repository "${repoUrl}" does not contain a SKILL.md file at the root or in any subdirectory. ` +
|
|
249
|
+
`If skills are nested deeper, try installing with a direct URL to the SKILL.md file.`);
|
|
184
250
|
}
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
251
|
+
// Install each discovered skill into its own SKILLS_DIR/<slug> directory
|
|
252
|
+
const installed = [];
|
|
253
|
+
for (const { subdir, skillMdPath: mdPath } of discovered) {
|
|
254
|
+
const skillDest = join(SKILLS_DIR, subdir);
|
|
255
|
+
if (existsSync(skillDest)) {
|
|
256
|
+
console.error(`[io] Skipping "${subdir}" — already installed.`);
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
mkdirSync(skillDest, { recursive: true });
|
|
260
|
+
copyFileSync(mdPath, join(skillDest, "SKILL.md"));
|
|
261
|
+
// Also copy agents/ subdirectory if present
|
|
262
|
+
const agentsDir = join(destDir, subdir, "agents");
|
|
263
|
+
if (existsSync(agentsDir) && statSync(agentsDir).isDirectory()) {
|
|
264
|
+
execFileSync("cp", ["-r", agentsDir, join(skillDest, "agents")], { stdio: "pipe" });
|
|
265
|
+
}
|
|
266
|
+
const content = readFileSync(mdPath, "utf-8");
|
|
267
|
+
const { name, description } = parseSkillMd(content);
|
|
268
|
+
installed.push({ name: name || subdir, slug: subdir, description, path: skillDest });
|
|
269
|
+
}
|
|
270
|
+
// Clean up cloned repo — individual skills have been extracted
|
|
271
|
+
rmSync(destDir, { recursive: true, force: true });
|
|
272
|
+
destDir = undefined;
|
|
273
|
+
if (installed.length === 0) {
|
|
274
|
+
throw new Error("All skills in the repository are already installed.");
|
|
275
|
+
}
|
|
276
|
+
return installed.length === 1 ? installed[0] : installed;
|
|
193
277
|
}
|
|
194
278
|
catch (e) {
|
|
195
279
|
// Clean up partially-created directory on failure
|
package/dist/copilot/tools.js
CHANGED
|
@@ -784,8 +784,9 @@ export function createTools(deps) {
|
|
|
784
784
|
handler: async ({ repo_url }) => {
|
|
785
785
|
console.error(`[io] skill_install called: ${repo_url}`);
|
|
786
786
|
try {
|
|
787
|
-
const
|
|
788
|
-
|
|
787
|
+
const result = await deps.installSkill(repo_url);
|
|
788
|
+
const skills = Array.isArray(result) ? result : [result];
|
|
789
|
+
return skills.map((skill) => `Skill "${skill.name}" installed successfully.\nSlug: ${skill.slug}\nDescription: ${skill.description || "(none)"}`).join("\n\n");
|
|
789
790
|
}
|
|
790
791
|
catch (err) {
|
|
791
792
|
return `Error installing skill: ${err instanceof Error ? err.message : String(err)}`;
|
package/dist/index.js
CHANGED
|
@@ -73,8 +73,11 @@ skillCmd
|
|
|
73
73
|
.action(async (repoUrl) => {
|
|
74
74
|
try {
|
|
75
75
|
console.log(`Installing skill from ${repoUrl}...`);
|
|
76
|
-
const
|
|
77
|
-
|
|
76
|
+
const result = await installSkill(repoUrl);
|
|
77
|
+
const skills = Array.isArray(result) ? result : [result];
|
|
78
|
+
for (const skill of skills) {
|
|
79
|
+
console.log(`✓ Installed: ${skill.name} (${skill.slug})`);
|
|
80
|
+
}
|
|
78
81
|
}
|
|
79
82
|
catch (err) {
|
|
80
83
|
console.error(`✗ ${err instanceof Error ? err.message : String(err)}`);
|
package/package.json
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.nav-sidebar[data-v-d1740758]{transition:width .2s ease;overflow:hidden}.nav-expanded[data-v-d1740758]{width:256px}.nav-collapsed[data-v-d1740758]{width:60px}@keyframes dot-pulse-ef4cff2c{0%,80%,to{opacity:.3;transform:scale(.8)}40%{opacity:1;transform:scale(1)}}.dot-pulse[data-v-ef4cff2c]{display:inline-block;width:8px;height:8px;border-radius:50%;background-color:#60a5fa;animation:dot-pulse-ef4cff2c 1.2s ease-in-out infinite}.chat-md[data-v-ef4cff2c] h1,.chat-md[data-v-ef4cff2c] h2,.chat-md[data-v-ef4cff2c] h3,.chat-md[data-v-ef4cff2c] h4{line-height:1.3}.chat-md[data-v-ef4cff2c] p{margin:.15rem 0}.chat-md[data-v-ef4cff2c] ul,.chat-md[data-v-ef4cff2c] ol{padding-left:.5rem}.chat-md[data-v-ef4cff2c] pre,.chat-md[data-v-ef4cff2c] table{font-size:.75rem}.skill-content[data-v-422f7a1e] h1,.skill-content[data-v-422f7a1e] h2,.skill-content[data-v-422f7a1e] h3,.skill-content[data-v-422f7a1e] h4{line-height:1.3}.bg-gray-750[data-v-6c7a72da]{background-color:#2d3748}.wiki-content[data-v-bc46a605] h1,.wiki-content[data-v-bc46a605] h2,.wiki-content[data-v-bc46a605] h3,.wiki-content[data-v-bc46a605] h4{line-height:1.3}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{top:0;right:0;bottom:0;left:0}.bottom-20{bottom:5rem}.right-0\.5{right:.125rem}.right-1\.5{right:.375rem}.right-8{right:2rem}.top-0\.5{top:.125rem}.top-1{top:.25rem}.z-10{z-index:10}.z-50{z-index:50}.my-1{margin-top:.25rem;margin-bottom:.25rem}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-4{margin-top:1rem;margin-bottom:1rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.mr-1{margin-right:.25rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-12{margin-top:3rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-2{height:.5rem}.h-3{height:.75rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-\[16px\]{height:16px}.h-full{height:100%}.h-screen{height:100vh}.max-h-48{max-height:12rem}.max-h-96{max-height:24rem}.max-h-\[85vh\]{max-height:85vh}.w-2{width:.5rem}.w-3{width:.75rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-72{width:18rem}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-\[16px\]{min-width:16px}.max-w-2xl{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-\[75\%\]{max-width:75%}.max-w-sm{max-width:24rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.rotate-90{--tw-rotate: 90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-none{resize:none}.resize-y{resize:vertical}.list-inside{list-style-position:inside}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-gray-700>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(55 65 81 / var(--tw-divide-opacity, 1))}.self-end{align-self:flex-end}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-sm{border-radius:.125rem}.rounded-xl{border-radius:.75rem}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-blue-500{--tw-border-opacity: 1;border-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.border-blue-700{--tw-border-opacity: 1;border-color:rgb(29 78 216 / var(--tw-border-opacity, 1))}.border-blue-800{--tw-border-opacity: 1;border-color:rgb(30 64 175 / var(--tw-border-opacity, 1))}.border-gray-600{--tw-border-opacity: 1;border-color:rgb(75 85 99 / var(--tw-border-opacity, 1))}.border-gray-700{--tw-border-opacity: 1;border-color:rgb(55 65 81 / var(--tw-border-opacity, 1))}.border-gray-800{--tw-border-opacity: 1;border-color:rgb(31 41 55 / var(--tw-border-opacity, 1))}.bg-black\/70{background-color:#000000b3}.bg-blue-400{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity, 1))}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-blue-700{--tw-bg-opacity: 1;background-color:rgb(29 78 216 / var(--tw-bg-opacity, 1))}.bg-blue-800{--tw-bg-opacity: 1;background-color:rgb(30 64 175 / var(--tw-bg-opacity, 1))}.bg-blue-900{--tw-bg-opacity: 1;background-color:rgb(30 58 138 / var(--tw-bg-opacity, 1))}.bg-blue-950{--tw-bg-opacity: 1;background-color:rgb(23 37 84 / var(--tw-bg-opacity, 1))}.bg-emerald-900{--tw-bg-opacity: 1;background-color:rgb(6 78 59 / var(--tw-bg-opacity, 1))}.bg-gray-500{--tw-bg-opacity: 1;background-color:rgb(107 114 128 / var(--tw-bg-opacity, 1))}.bg-gray-700{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.bg-gray-800{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity, 1))}.bg-gray-950{--tw-bg-opacity: 1;background-color:rgb(3 7 18 / var(--tw-bg-opacity, 1))}.bg-green-400{--tw-bg-opacity: 1;background-color:rgb(74 222 128 / var(--tw-bg-opacity, 1))}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-green-900{--tw-bg-opacity: 1;background-color:rgb(20 83 45 / var(--tw-bg-opacity, 1))}.bg-purple-900{--tw-bg-opacity: 1;background-color:rgb(88 28 135 / var(--tw-bg-opacity, 1))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.bg-red-800{--tw-bg-opacity: 1;background-color:rgb(153 27 27 / var(--tw-bg-opacity, 1))}.bg-red-900{--tw-bg-opacity: 1;background-color:rgb(127 29 29 / var(--tw-bg-opacity, 1))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-yellow-900{--tw-bg-opacity: 1;background-color:rgb(113 63 18 / var(--tw-bg-opacity, 1))}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-0\.5{padding-left:.125rem;padding-right:.125rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb-3{padding-bottom:.75rem}.pl-2{padding-left:.5rem}.pl-6{padding-left:1.5rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-sans{font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[9px\]{font-size:9px}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}.italic{font-style:italic}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-blue-100{--tw-text-opacity: 1;color:rgb(219 234 254 / var(--tw-text-opacity, 1))}.text-blue-200{--tw-text-opacity: 1;color:rgb(191 219 254 / var(--tw-text-opacity, 1))}.text-blue-300{--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity, 1))}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-emerald-200{--tw-text-opacity: 1;color:rgb(167 243 208 / var(--tw-text-opacity, 1))}.text-gray-100{--tw-text-opacity: 1;color:rgb(243 244 246 / var(--tw-text-opacity, 1))}.text-gray-200{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.text-green-100{--tw-text-opacity: 1;color:rgb(220 252 231 / var(--tw-text-opacity, 1))}.text-green-200{--tw-text-opacity: 1;color:rgb(187 247 208 / var(--tw-text-opacity, 1))}.text-green-300{--tw-text-opacity: 1;color:rgb(134 239 172 / var(--tw-text-opacity, 1))}.text-purple-100{--tw-text-opacity: 1;color:rgb(243 232 255 / var(--tw-text-opacity, 1))}.text-purple-200{--tw-text-opacity: 1;color:rgb(233 213 255 / var(--tw-text-opacity, 1))}.text-purple-300{--tw-text-opacity: 1;color:rgb(216 180 254 / var(--tw-text-opacity, 1))}.text-red-100{--tw-text-opacity: 1;color:rgb(254 226 226 / var(--tw-text-opacity, 1))}.text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}.text-red-300{--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-100{--tw-text-opacity: 1;color:rgb(254 249 195 / var(--tw-text-opacity, 1))}.text-yellow-200{--tw-text-opacity: 1;color:rgb(254 240 138 / var(--tw-text-opacity, 1))}.placeholder-gray-500::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(107 114 128 / var(--tw-placeholder-opacity, 1))}.placeholder-gray-500::placeholder{--tw-placeholder-opacity: 1;color:rgb(107 114 128 / var(--tw-placeholder-opacity, 1))}.opacity-60{opacity:.6}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}html{color-scheme:dark}body{--tw-bg-opacity: 1;background-color:rgb(3 7 18 / var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(243 244 246 / var(--tw-text-opacity, 1))}.hover\:border-blue-500:hover{--tw-border-opacity: 1;border-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.hover\:border-gray-600:hover{--tw-border-opacity: 1;border-color:rgb(75 85 99 / var(--tw-border-opacity, 1))}.hover\:bg-blue-600:hover{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.hover\:bg-blue-700:hover{--tw-bg-opacity: 1;background-color:rgb(29 78 216 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-600:hover{--tw-bg-opacity: 1;background-color:rgb(75 85 99 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-800:hover{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.hover\:bg-green-700:hover{--tw-bg-opacity: 1;background-color:rgb(21 128 61 / var(--tw-bg-opacity, 1))}.hover\:bg-red-700:hover{--tw-bg-opacity: 1;background-color:rgb(185 28 28 / var(--tw-bg-opacity, 1))}.hover\:bg-red-800:hover{--tw-bg-opacity: 1;background-color:rgb(153 27 27 / var(--tw-bg-opacity, 1))}.hover\:text-blue-300:hover{--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity, 1))}.hover\:text-gray-100:hover{--tw-text-opacity: 1;color:rgb(243 244 246 / var(--tw-text-opacity, 1))}.hover\:text-gray-200:hover{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.hover\:text-gray-300:hover{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.hover\:text-gray-400:hover{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-blue-500:focus{--tw-border-opacity: 1;border-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.focus\:border-transparent:focus{border-color:transparent}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-blue-600:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(37 99 235 / var(--tw-ring-opacity, 1))}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-gray-700:disabled{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.disabled\:opacity-50:disabled{opacity:.5}
|