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.
Files changed (100) hide show
  1. package/README.md +40 -52
  2. package/dist/api/auth.js +35 -38
  3. package/dist/api/server.js +157 -1134
  4. package/dist/config.js +49 -32
  5. package/dist/copilot/agents.js +72 -1055
  6. package/dist/copilot/client.js +6 -17
  7. package/dist/copilot/io-scheduler.js +55 -139
  8. package/dist/copilot/model-router.js +100 -72
  9. package/dist/copilot/orchestrator.js +91 -515
  10. package/dist/copilot/scheduler.js +67 -189
  11. package/dist/copilot/skills.js +41 -366
  12. package/dist/copilot/system-message.js +40 -200
  13. package/dist/copilot/tools.js +191 -2042
  14. package/dist/daemon.js +54 -201
  15. package/dist/index.js +15 -133
  16. package/dist/mcp/config.js +23 -31
  17. package/dist/mcp/index.js +2 -3
  18. package/dist/mcp/registry.js +33 -88
  19. package/dist/notify.js +18 -100
  20. package/dist/paths.js +13 -24
  21. package/dist/setup.js +35 -0
  22. package/dist/store/db.js +111 -297
  23. package/dist/store/feed.js +29 -97
  24. package/dist/store/instances.js +56 -121
  25. package/dist/store/schedules.js +21 -73
  26. package/dist/store/squads.js +35 -186
  27. package/dist/store/tasks.js +25 -168
  28. package/dist/telegram/bot.js +20 -312
  29. package/dist/telegram/handlers.js +39 -3
  30. package/dist/watchdog.js +31 -45
  31. package/dist/wiki/fs.js +38 -155
  32. package/dist/wiki/search.js +31 -44
  33. package/package.json +5 -8
  34. package/web-dist/assets/ChatView-EFFiln1H.js +11 -0
  35. package/web-dist/assets/FeedView-bN4NMOL7.js +6 -0
  36. package/web-dist/assets/LoginView-CNtasq3n.js +1 -0
  37. package/web-dist/assets/McpView-C2CHiwsi.js +1 -0
  38. package/web-dist/assets/SchedulesView-CyilLban.js +1 -0
  39. package/web-dist/assets/SettingsView-1wLXKEF4.js +1 -0
  40. package/web-dist/assets/SkillsView-BLsD-0u0.js +1 -0
  41. package/web-dist/assets/SquadDetailView-CsCw2ZLp.js +21 -0
  42. package/web-dist/assets/SquadsView-DQ3vFlyO.js +6 -0
  43. package/web-dist/assets/WikiView-19M3oqnq.js +21 -0
  44. package/web-dist/assets/api-WGvTsXaE.js +1 -0
  45. package/web-dist/assets/index-D7M5O-_l.css +1 -0
  46. package/web-dist/assets/index-DZOS9syn.js +95 -0
  47. package/web-dist/assets/plus-BOvyX1BC.js +6 -0
  48. package/web-dist/assets/trash-2-DHoetkC4.js +6 -0
  49. package/web-dist/favicon.svg +4 -1
  50. package/web-dist/index.html +7 -10
  51. package/dist/api/logout.test.js +0 -128
  52. package/dist/api/mcp.test.js +0 -285
  53. package/dist/api/wiki.test.js +0 -283
  54. package/dist/auth/session-logic.js +0 -79
  55. package/dist/auth/session-logic.test.js +0 -201
  56. package/dist/copilot/auto-complete-instance.test.js +0 -104
  57. package/dist/copilot/cron.js +0 -136
  58. package/dist/copilot/event-summary.js +0 -286
  59. package/dist/copilot/instance-deactivate.test.js +0 -119
  60. package/dist/copilot/model-router.test.js +0 -71
  61. package/dist/copilot/review-backfill.js +0 -57
  62. package/dist/copilot/session-timeout.js +0 -112
  63. package/dist/copilot/session-timeout.test.js +0 -372
  64. package/dist/copilot/skills.test.js +0 -55
  65. package/dist/copilot/universes.js +0 -469
  66. package/dist/instance-watchdog.js +0 -104
  67. package/dist/instance-watchdog.test.js +0 -183
  68. package/dist/mcp/client.js +0 -109
  69. package/dist/mcp/client.test.js +0 -99
  70. package/dist/mcp/config.test.js +0 -49
  71. package/dist/mcp/registry.test.js +0 -79
  72. package/dist/notify.test.js +0 -232
  73. package/dist/store/feed.test.js +0 -279
  74. package/dist/store/instances.test.js +0 -310
  75. package/dist/store/io-schedules.js +0 -63
  76. package/dist/store/notifications.js +0 -79
  77. package/dist/store/notifications.test.js +0 -197
  78. package/dist/store/schedule-runs.js +0 -46
  79. package/dist/store/squads.test.js +0 -405
  80. package/dist/store/tasks.test.js +0 -150
  81. package/dist/store/worktrees.js +0 -83
  82. package/dist/tui/index.js +0 -286
  83. package/dist/update.js +0 -81
  84. package/dist/watchdog.test.js +0 -83
  85. package/dist/wiki/wiki-squad.test.js +0 -54
  86. package/web-dist/assets/AgentActivityView-B1PaNYy8.js +0 -1
  87. package/web-dist/assets/ChatView-BbpWnrtC.js +0 -4
  88. package/web-dist/assets/FeedView-B5LaMV0I.js +0 -1
  89. package/web-dist/assets/InboxView-Cwqt8rH7.js +0 -1
  90. package/web-dist/assets/LoginView-refmPLKT.js +0 -1
  91. package/web-dist/assets/McpView-B1w0dRFY.js +0 -1
  92. package/web-dist/assets/SchedulesView-D9l2DI7X.js +0 -1
  93. package/web-dist/assets/SettingsTabs.vue_vue_type_script_setup_true_lang-DncOVVEB.js +0 -1
  94. package/web-dist/assets/SkillsView-uFX0q1mV.js +0 -1
  95. package/web-dist/assets/SquadsView-B1nZW4ml.js +0 -1
  96. package/web-dist/assets/StatusIndicator.vue_vue_type_script_setup_true_lang-pTrJJwX1.js +0 -1
  97. package/web-dist/assets/WikiView-B54cCKIK.js +0 -1
  98. package/web-dist/assets/index-C0VEUWQ1.js +0 -81
  99. package/web-dist/assets/index-eluTyieM.css +0 -10
  100. package/web-dist/icons.svg +0 -24
@@ -1,385 +1,60 @@
1
- import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "fs";
2
- import { join, basename, resolve } from "path";
3
- import { execFileSync } from "child_process";
4
- import { SKILLS_DIR } from "../paths.js";
5
- /**
6
- * Scan SKILLS_DIR for subdirectories that contain a SKILL.md file.
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 readdirSync(SKILLS_DIR)) {
66
- const skillDir = join(SKILLS_DIR, entry);
67
- if (!statSync(skillDir).isDirectory())
10
+ for (const entry of entries) {
11
+ if (!entry.isDirectory())
68
12
  continue;
69
- const skillMdPath = join(skillDir, "SKILL.md");
70
- if (!existsSync(skillMdPath))
13
+ const skillMd = join(PATHS.skills, entry.name, "SKILL.md");
14
+ if (!existsSync(skillMd))
71
15
  continue;
72
- const content = readFileSync(skillMdPath, "utf-8");
73
- const { name, description } = parseSkillMd(content);
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: name || entry,
76
- slug: entry,
24
+ name,
25
+ slug: entry.name,
77
26
  description,
78
- path: skillDir,
27
+ path: join(PATHS.skills, entry.name),
79
28
  });
80
29
  }
81
30
  return skills;
82
31
  }
83
- const GITHUB_BLOB_RE = /^https:\/\/github\.com\/([^\/]+)\/([^\/]+)\/blob\/([^\/]+)\/(.+\/)?SKILL\.md$/i;
84
- const RAW_GH_RE = /^https:\/\/raw\.githubusercontent\.com\/([^\/]+)\/([^\/]+)\/([^\/]+)\/(.+\/)?SKILL\.md$/i;
85
- const GENERIC_SKILL_MD_RE = /\/SKILL\.md$/i;
86
- function deriveSlug(repo, pathPrefix) {
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
- if (!isValidSkillContent(content)) {
178
- throw new Error("Content does not appear to be a valid SKILL.md (no heading found in first 10 lines).");
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
- * 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
- }
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
- return found;
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
- * Search the skills registry for skills matching the given query.
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
- * Update a skill's metadata (name and/or description).
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