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.
@@ -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 skill = await installSkill(trimmed);
102
- res.status(201).json({ skill });
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);
@@ -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,
@@ -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
- * Throws if the repo/file does not contain a valid SKILL.md.
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 (!existsSync(skillMdPath)) {
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
- const content = readFileSync(skillMdPath, "utf-8");
186
- const { name, description } = parseSkillMd(content);
187
- return {
188
- name: name || repoName,
189
- slug: repoName,
190
- description,
191
- path: destDir,
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
@@ -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 skill = await deps.installSkill(repo_url);
788
- return `Skill "${skill.name}" installed successfully.\nSlug: ${skill.slug}\nDescription: ${skill.description || "(none)"}`;
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 skill = await installSkill(repoUrl);
77
- console.log(`✓ Installed: ${skill.name} (${skill.slug})`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "heyio",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "IO — a personal AI assistant built on the GitHub Copilot SDK",
5
5
  "bin": {
6
6
  "io": "dist/index.js"
@@ -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}