heyio 0.42.1 → 1.0.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/README.md +40 -52
- package/dist/api/auth.js +35 -38
- package/dist/api/server.js +157 -1134
- package/dist/config.js +49 -32
- package/dist/copilot/agents.js +72 -1055
- package/dist/copilot/client.js +6 -17
- package/dist/copilot/io-scheduler.js +55 -139
- package/dist/copilot/model-router.js +100 -72
- package/dist/copilot/orchestrator.js +91 -515
- package/dist/copilot/scheduler.js +67 -189
- package/dist/copilot/skills.js +41 -366
- package/dist/copilot/system-message.js +40 -200
- package/dist/copilot/tools.js +191 -2042
- package/dist/daemon.js +54 -201
- package/dist/index.js +15 -133
- package/dist/mcp/config.js +23 -31
- package/dist/mcp/index.js +2 -3
- package/dist/mcp/registry.js +33 -88
- package/dist/notify.js +18 -100
- package/dist/paths.js +13 -24
- package/dist/setup.js +35 -0
- package/dist/store/db.js +111 -297
- package/dist/store/feed.js +29 -97
- package/dist/store/instances.js +56 -121
- package/dist/store/schedules.js +21 -73
- package/dist/store/squads.js +35 -186
- package/dist/store/tasks.js +25 -168
- package/dist/telegram/bot.js +20 -312
- package/dist/telegram/handlers.js +39 -3
- package/dist/watchdog.js +31 -45
- package/dist/wiki/fs.js +38 -155
- package/dist/wiki/search.js +31 -44
- package/package.json +5 -8
- package/web-dist/assets/ChatView-EFFiln1H.js +11 -0
- package/web-dist/assets/FeedView-bN4NMOL7.js +6 -0
- package/web-dist/assets/LoginView-CNtasq3n.js +1 -0
- package/web-dist/assets/McpView-C2CHiwsi.js +1 -0
- package/web-dist/assets/SchedulesView-CyilLban.js +1 -0
- package/web-dist/assets/SettingsView-1wLXKEF4.js +1 -0
- package/web-dist/assets/SkillsView-BLsD-0u0.js +1 -0
- package/web-dist/assets/SquadDetailView-CsCw2ZLp.js +21 -0
- package/web-dist/assets/SquadsView-DQ3vFlyO.js +6 -0
- package/web-dist/assets/WikiView-19M3oqnq.js +21 -0
- package/web-dist/assets/api-WGvTsXaE.js +1 -0
- package/web-dist/assets/index-D7M5O-_l.css +1 -0
- package/web-dist/assets/index-DZOS9syn.js +95 -0
- package/web-dist/assets/plus-BOvyX1BC.js +6 -0
- package/web-dist/assets/trash-2-DHoetkC4.js +6 -0
- package/web-dist/favicon.svg +4 -1
- package/web-dist/index.html +7 -10
- package/dist/api/logout.test.js +0 -128
- package/dist/api/mcp.test.js +0 -285
- package/dist/api/wiki.test.js +0 -283
- package/dist/auth/session-logic.js +0 -79
- package/dist/auth/session-logic.test.js +0 -201
- package/dist/copilot/auto-complete-instance.test.js +0 -104
- package/dist/copilot/cron.js +0 -136
- package/dist/copilot/event-summary.js +0 -286
- package/dist/copilot/instance-deactivate.test.js +0 -119
- package/dist/copilot/model-router.test.js +0 -71
- package/dist/copilot/review-backfill.js +0 -57
- package/dist/copilot/session-timeout.js +0 -112
- package/dist/copilot/session-timeout.test.js +0 -372
- package/dist/copilot/skills.test.js +0 -55
- package/dist/copilot/universes.js +0 -469
- package/dist/instance-watchdog.js +0 -104
- package/dist/instance-watchdog.test.js +0 -183
- package/dist/mcp/client.js +0 -109
- package/dist/mcp/client.test.js +0 -99
- package/dist/mcp/config.test.js +0 -49
- package/dist/mcp/registry.test.js +0 -79
- package/dist/notify.test.js +0 -232
- package/dist/store/feed.test.js +0 -279
- package/dist/store/instances.test.js +0 -310
- package/dist/store/io-schedules.js +0 -63
- package/dist/store/notifications.js +0 -79
- package/dist/store/notifications.test.js +0 -197
- package/dist/store/schedule-runs.js +0 -46
- package/dist/store/squads.test.js +0 -405
- package/dist/store/tasks.test.js +0 -150
- package/dist/store/worktrees.js +0 -83
- package/dist/tui/index.js +0 -286
- package/dist/update.js +0 -81
- package/dist/watchdog.test.js +0 -83
- package/dist/wiki/wiki-squad.test.js +0 -54
- package/web-dist/assets/AgentActivityView-B1PaNYy8.js +0 -1
- package/web-dist/assets/ChatView-BbpWnrtC.js +0 -4
- package/web-dist/assets/FeedView-B5LaMV0I.js +0 -1
- package/web-dist/assets/InboxView-Cwqt8rH7.js +0 -1
- package/web-dist/assets/LoginView-refmPLKT.js +0 -1
- package/web-dist/assets/McpView-B1w0dRFY.js +0 -1
- package/web-dist/assets/SchedulesView-D9l2DI7X.js +0 -1
- package/web-dist/assets/SettingsTabs.vue_vue_type_script_setup_true_lang-DncOVVEB.js +0 -1
- package/web-dist/assets/SkillsView-uFX0q1mV.js +0 -1
- package/web-dist/assets/SquadsView-B1nZW4ml.js +0 -1
- package/web-dist/assets/StatusIndicator.vue_vue_type_script_setup_true_lang-pTrJJwX1.js +0 -1
- package/web-dist/assets/WikiView-B54cCKIK.js +0 -1
- package/web-dist/assets/index-C0VEUWQ1.js +0 -81
- package/web-dist/assets/index-eluTyieM.css +0 -10
- package/web-dist/icons.svg +0 -24
package/dist/copilot/skills.js
CHANGED
|
@@ -1,385 +1,60 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join, basename
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* Returns absolute paths to qualifying skill directories.
|
|
8
|
-
*/
|
|
9
|
-
export function getSkillDirectories() {
|
|
10
|
-
if (!existsSync(SKILLS_DIR))
|
|
11
|
-
return [];
|
|
12
|
-
const dirs = [];
|
|
13
|
-
for (const entry of readdirSync(SKILLS_DIR)) {
|
|
14
|
-
const skillDir = join(SKILLS_DIR, entry);
|
|
15
|
-
if (!statSync(skillDir).isDirectory())
|
|
16
|
-
continue;
|
|
17
|
-
const skillMd = join(skillDir, "SKILL.md");
|
|
18
|
-
if (!existsSync(skillMd))
|
|
19
|
-
continue;
|
|
20
|
-
dirs.push(skillDir);
|
|
21
|
-
// Check for an agents subdirectory (Copilot SDK custom agents)
|
|
22
|
-
const agentsDir = join(skillDir, "agents");
|
|
23
|
-
if (existsSync(agentsDir) && statSync(agentsDir).isDirectory()) {
|
|
24
|
-
dirs.push(agentsDir);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return dirs;
|
|
28
|
-
}
|
|
29
|
-
function parseSkillMd(content) {
|
|
30
|
-
const lines = content.split(/\r?\n/);
|
|
31
|
-
let name = "";
|
|
32
|
-
let description = "";
|
|
33
|
-
let foundHeading = false;
|
|
34
|
-
const descLines = [];
|
|
35
|
-
for (const line of lines) {
|
|
36
|
-
if (!foundHeading) {
|
|
37
|
-
const match = line.match(/^#\s+(.+)/);
|
|
38
|
-
if (match) {
|
|
39
|
-
name = match[1].trim();
|
|
40
|
-
foundHeading = true;
|
|
41
|
-
}
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
// Skip blank lines between heading and first paragraph
|
|
45
|
-
if (descLines.length === 0 && line.trim() === "")
|
|
46
|
-
continue;
|
|
47
|
-
// Stop at the next blank line after collecting description text
|
|
48
|
-
if (descLines.length > 0 && line.trim() === "")
|
|
49
|
-
break;
|
|
50
|
-
// Stop at another heading
|
|
51
|
-
if (line.match(/^#+\s/))
|
|
52
|
-
break;
|
|
53
|
-
descLines.push(line.trim());
|
|
54
|
-
}
|
|
55
|
-
description = descLines.join(" ");
|
|
56
|
-
return { name, description };
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* List all installed skills with metadata parsed from their SKILL.md files.
|
|
60
|
-
*/
|
|
61
|
-
export function listSkills() {
|
|
62
|
-
if (!existsSync(SKILLS_DIR))
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, rmSync } from "node:fs";
|
|
2
|
+
import { join, basename } from "node:path";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import { PATHS } from "../paths.js";
|
|
5
|
+
export async function listSkills() {
|
|
6
|
+
if (!existsSync(PATHS.skills))
|
|
63
7
|
return [];
|
|
8
|
+
const entries = readdirSync(PATHS.skills, { withFileTypes: true });
|
|
64
9
|
const skills = [];
|
|
65
|
-
for (const entry of
|
|
66
|
-
|
|
67
|
-
if (!statSync(skillDir).isDirectory())
|
|
10
|
+
for (const entry of entries) {
|
|
11
|
+
if (!entry.isDirectory())
|
|
68
12
|
continue;
|
|
69
|
-
const
|
|
70
|
-
if (!existsSync(
|
|
13
|
+
const skillMd = join(PATHS.skills, entry.name, "SKILL.md");
|
|
14
|
+
if (!existsSync(skillMd))
|
|
71
15
|
continue;
|
|
72
|
-
const content = readFileSync(
|
|
73
|
-
const
|
|
16
|
+
const content = readFileSync(skillMd, "utf-8");
|
|
17
|
+
const firstLine = content.split("\n").find((l) => l.startsWith("# "));
|
|
18
|
+
const name = firstLine?.replace(/^#\s+/, "") ?? entry.name;
|
|
19
|
+
const descLine = content
|
|
20
|
+
.split("\n")
|
|
21
|
+
.find((l) => l.trim() && !l.startsWith("#"));
|
|
22
|
+
const description = descLine?.trim() ?? "";
|
|
74
23
|
skills.push({
|
|
75
|
-
name
|
|
76
|
-
slug: entry,
|
|
24
|
+
name,
|
|
25
|
+
slug: entry.name,
|
|
77
26
|
description,
|
|
78
|
-
path:
|
|
27
|
+
path: join(PATHS.skills, entry.name),
|
|
79
28
|
});
|
|
80
29
|
}
|
|
81
30
|
return skills;
|
|
82
31
|
}
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
if (!pathPrefix)
|
|
88
|
-
return repo;
|
|
89
|
-
const segments = pathPrefix.replace(/\/$/, "").split("/").filter(Boolean);
|
|
90
|
-
const last = segments[segments.length - 1];
|
|
91
|
-
return last ? `${repo}-${last}` : repo;
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Determine whether the input URL points to a full repo or a specific
|
|
95
|
-
* SKILL.md file. For GitHub blob URLs the raw download URL is derived
|
|
96
|
-
* automatically.
|
|
97
|
-
*/
|
|
98
|
-
export function parseSkillUrl(input) {
|
|
99
|
-
const blobMatch = input.match(GITHUB_BLOB_RE);
|
|
100
|
-
if (blobMatch) {
|
|
101
|
-
const [, owner, repo, branch, pathPrefix] = blobMatch;
|
|
102
|
-
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${pathPrefix ?? ""}SKILL.md`;
|
|
103
|
-
return { type: "file", rawUrl, slug: deriveSlug(repo, pathPrefix) };
|
|
104
|
-
}
|
|
105
|
-
const rawMatch = input.match(RAW_GH_RE);
|
|
106
|
-
if (rawMatch) {
|
|
107
|
-
const [, _owner, repo, _branch, pathPrefix] = rawMatch;
|
|
108
|
-
return { type: "file", rawUrl: input, slug: deriveSlug(repo, pathPrefix) };
|
|
109
|
-
}
|
|
110
|
-
if (GENERIC_SKILL_MD_RE.test(input)) {
|
|
111
|
-
if (!input.startsWith("https://")) {
|
|
112
|
-
throw new Error("Only https:// URLs are supported for SKILL.md installs.");
|
|
113
|
-
}
|
|
114
|
-
let urlObj;
|
|
115
|
-
try {
|
|
116
|
-
urlObj = new URL(input);
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
throw new Error(`Invalid URL: ${input}`);
|
|
120
|
-
}
|
|
121
|
-
const segments = urlObj.pathname.split("/").filter(Boolean);
|
|
122
|
-
// Use the segment before SKILL.md, or the hostname as slug fallback
|
|
123
|
-
const slug = segments.length >= 2
|
|
124
|
-
? segments[segments.length - 2]
|
|
125
|
-
: urlObj.hostname.replace(/\./g, "-");
|
|
126
|
-
return { type: "file", rawUrl: input, slug };
|
|
127
|
-
}
|
|
128
|
-
return { type: "repo", url: input };
|
|
129
|
-
}
|
|
130
|
-
async function installSkillFromFile(rawUrl, slug) {
|
|
131
|
-
if (!rawUrl.startsWith("https://")) {
|
|
132
|
-
throw new Error("Only https:// URLs are supported for SKILL.md installs.");
|
|
133
|
-
}
|
|
134
|
-
const destDir = join(SKILLS_DIR, slug);
|
|
135
|
-
if (existsSync(destDir)) {
|
|
136
|
-
throw new Error(`Skill "${slug}" is already installed.`);
|
|
137
|
-
}
|
|
138
|
-
const response = await fetch(rawUrl);
|
|
139
|
-
if (!response.ok) {
|
|
140
|
-
throw new Error(`Failed to fetch SKILL.md from ${rawUrl} (HTTP ${response.status})`);
|
|
141
|
-
}
|
|
142
|
-
const content = await response.text();
|
|
143
|
-
// Validate: at least one markdown heading in the first 10 lines
|
|
144
|
-
const first10 = content.split(/\r?\n/).slice(0, 10);
|
|
145
|
-
if (!first10.some((line) => /^#{1,6}\s/.test(line))) {
|
|
146
|
-
throw new Error("URL does not appear to contain a valid SKILL.md file.");
|
|
147
|
-
}
|
|
148
|
-
mkdirSync(destDir, { recursive: true });
|
|
149
|
-
writeFileSync(join(destDir, "SKILL.md"), content, "utf-8");
|
|
150
|
-
const { name, description } = parseSkillMd(content);
|
|
151
|
-
return {
|
|
152
|
-
name: name || slug,
|
|
153
|
-
slug,
|
|
154
|
-
description,
|
|
155
|
-
path: destDir,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
function isValidSkillContent(content) {
|
|
159
|
-
const first10 = content.split(/\r?\n/).slice(0, 10);
|
|
160
|
-
return first10.some((line) => /^#{1,6}\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)) {
|
|
32
|
+
export async function addSkill(url) {
|
|
33
|
+
const slug = basename(url, ".git").replace(/[^a-z0-9-]/gi, "-").toLowerCase();
|
|
34
|
+
const dest = join(PATHS.skills, slug);
|
|
35
|
+
if (existsSync(dest)) {
|
|
175
36
|
throw new Error(`Skill "${slug}" is already installed.`);
|
|
176
37
|
}
|
|
177
|
-
|
|
178
|
-
|
|
38
|
+
execSync(`git clone --depth 1 ${url} ${dest}`, { stdio: "pipe" });
|
|
39
|
+
// Verify SKILL.md exists
|
|
40
|
+
if (!existsSync(join(dest, "SKILL.md"))) {
|
|
41
|
+
rmSync(dest, { recursive: true, force: true });
|
|
42
|
+
throw new Error("Repository does not contain a SKILL.md file.");
|
|
179
43
|
}
|
|
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
44
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
}
|
|
45
|
+
export async function removeSkill(slug) {
|
|
46
|
+
const dest = join(PATHS.skills, slug);
|
|
47
|
+
if (!existsSync(dest)) {
|
|
48
|
+
throw new Error(`Skill "${slug}" not found.`);
|
|
206
49
|
}
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Install a skill from a git repo URL or a direct SKILL.md file URL.
|
|
211
|
-
* Returns a single SkillInfo for single-skill repos/files, or an array
|
|
212
|
-
* for repos containing multiple SKILL.md files in subdirectories.
|
|
213
|
-
*/
|
|
214
|
-
export async function installSkill(input) {
|
|
215
|
-
let destDir;
|
|
216
|
-
try {
|
|
217
|
-
const parsed = parseSkillUrl(input);
|
|
218
|
-
if (parsed.type === "file") {
|
|
219
|
-
return await installSkillFromFile(parsed.rawUrl, parsed.slug);
|
|
220
|
-
}
|
|
221
|
-
const repoUrl = parsed.url;
|
|
222
|
-
const repoName = basename(repoUrl, ".git").replace(/\.git$/, "");
|
|
223
|
-
if (!repoName) {
|
|
224
|
-
throw new Error("Could not determine skill name from URL.");
|
|
225
|
-
}
|
|
226
|
-
destDir = join(SKILLS_DIR, repoName);
|
|
227
|
-
execFileSync("git", ["clone", repoUrl, destDir], {
|
|
228
|
-
stdio: "pipe",
|
|
229
|
-
timeout: 60_000,
|
|
230
|
-
});
|
|
231
|
-
const skillMdPath = join(destDir, "SKILL.md");
|
|
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) {
|
|
246
|
-
rmSync(destDir, { recursive: true, force: true });
|
|
247
|
-
destDir = undefined;
|
|
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.`);
|
|
250
|
-
}
|
|
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;
|
|
277
|
-
}
|
|
278
|
-
catch (e) {
|
|
279
|
-
// Clean up partially-created directory on failure
|
|
280
|
-
if (destDir && existsSync(destDir)) {
|
|
281
|
-
rmSync(destDir, { recursive: true, force: true });
|
|
282
|
-
}
|
|
283
|
-
throw e instanceof Error ? e : new Error(String(e));
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
/**
|
|
287
|
-
* Remove a skill directory by its slug. Returns true if it existed.
|
|
288
|
-
*/
|
|
289
|
-
export function removeSkill(slug) {
|
|
290
|
-
const skillDir = join(SKILLS_DIR, slug);
|
|
291
|
-
if (!existsSync(skillDir))
|
|
292
|
-
return false;
|
|
293
|
-
rmSync(skillDir, { recursive: true, force: true });
|
|
294
|
-
return true;
|
|
50
|
+
rmSync(dest, { recursive: true, force: true });
|
|
295
51
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
* Returns an empty array on network or parsing errors.
|
|
299
|
-
*/
|
|
300
|
-
export async function searchSkillsRegistry(query) {
|
|
301
|
-
try {
|
|
302
|
-
const url = `https://skills.sh/api/search?q=${encodeURIComponent(query)}`;
|
|
303
|
-
const response = await fetch(url);
|
|
304
|
-
if (!response.ok)
|
|
305
|
-
return [];
|
|
306
|
-
const data = (await response.json());
|
|
307
|
-
return Array.isArray(data) ? data : [];
|
|
308
|
-
}
|
|
309
|
-
catch {
|
|
52
|
+
export async function loadSkillDirectories() {
|
|
53
|
+
if (!existsSync(PATHS.skills))
|
|
310
54
|
return [];
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
* Rewrites the SKILL.md file with the updated frontmatter while preserving content.
|
|
316
|
-
* Throws if the skill doesn't exist.
|
|
317
|
-
*/
|
|
318
|
-
export function updateSkill(slug, updates) {
|
|
319
|
-
const skillDir = join(SKILLS_DIR, slug);
|
|
320
|
-
if (!existsSync(skillDir)) {
|
|
321
|
-
throw new Error(`Skill not found: ${slug}`);
|
|
322
|
-
}
|
|
323
|
-
const skillMdPath = join(skillDir, "SKILL.md");
|
|
324
|
-
if (!existsSync(skillMdPath)) {
|
|
325
|
-
throw new Error(`SKILL.md not found for skill: ${slug}`);
|
|
326
|
-
}
|
|
327
|
-
const currentContent = readFileSync(skillMdPath, "utf-8");
|
|
328
|
-
const { name: currentName, description: currentDescription } = parseSkillMd(currentContent);
|
|
329
|
-
// Use provided updates or fall back to current values
|
|
330
|
-
const newName = updates.name ?? currentName;
|
|
331
|
-
const newDescription = updates.description ?? currentDescription;
|
|
332
|
-
// Rebuild SKILL.md with new metadata
|
|
333
|
-
// Keep everything after the description intact (preserve any extra content)
|
|
334
|
-
const lines = currentContent.split(/\r?\n/);
|
|
335
|
-
const newLines = [];
|
|
336
|
-
// Add the new heading
|
|
337
|
-
newLines.push(`# ${newName}`);
|
|
338
|
-
newLines.push("");
|
|
339
|
-
// Add the new description
|
|
340
|
-
if (newDescription) {
|
|
341
|
-
newLines.push(newDescription);
|
|
342
|
-
newLines.push("");
|
|
343
|
-
}
|
|
344
|
-
// Find where the original description ends and append the rest
|
|
345
|
-
let foundHeading = false;
|
|
346
|
-
let skippedDescription = false;
|
|
347
|
-
let blankLinesSeen = 0;
|
|
348
|
-
for (const line of lines) {
|
|
349
|
-
if (!foundHeading) {
|
|
350
|
-
if (line.match(/^#\s+(.+)/)) {
|
|
351
|
-
foundHeading = true;
|
|
352
|
-
}
|
|
353
|
-
continue;
|
|
354
|
-
}
|
|
355
|
-
if (!skippedDescription) {
|
|
356
|
-
if (line.trim() === "") {
|
|
357
|
-
blankLinesSeen++;
|
|
358
|
-
if (blankLinesSeen >= 2) {
|
|
359
|
-
skippedDescription = true;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
else {
|
|
363
|
-
blankLinesSeen = 0;
|
|
364
|
-
}
|
|
365
|
-
continue;
|
|
366
|
-
}
|
|
367
|
-
// We're past the description now
|
|
368
|
-
newLines.push(line);
|
|
369
|
-
}
|
|
370
|
-
const newContent = newLines.join("\n");
|
|
371
|
-
writeFileSync(skillMdPath, newContent, "utf-8");
|
|
372
|
-
return {
|
|
373
|
-
name: newName,
|
|
374
|
-
slug,
|
|
375
|
-
description: newDescription,
|
|
376
|
-
path: skillDir,
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
/**
|
|
380
|
-
* Delete a skill (alias for removeSkill for consistency with other store modules).
|
|
381
|
-
*/
|
|
382
|
-
export function deleteSkill(slug) {
|
|
383
|
-
return removeSkill(slug);
|
|
55
|
+
const entries = readdirSync(PATHS.skills, { withFileTypes: true });
|
|
56
|
+
return entries
|
|
57
|
+
.filter((e) => e.isDirectory() && existsSync(join(PATHS.skills, e.name, "SKILL.md")))
|
|
58
|
+
.map((e) => join(PATHS.skills, e.name));
|
|
384
59
|
}
|
|
385
60
|
//# sourceMappingURL=skills.js.map
|