heyio 0.8.0 → 0.9.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 +66 -0
- package/dist/copilot/skills.js +44 -21
- package/package.json +1 -1
- package/web-dist/assets/index-CikqmObM.js +78 -0
- package/web-dist/assets/index-Tc2XV93a.css +1 -0
- package/web-dist/index.html +2 -2
- package/web-dist/assets/index-CUwy4ylb.js +0 -74
- package/web-dist/assets/index-oSVFpNBp.css +0 -1
package/dist/api/server.js
CHANGED
|
@@ -13,6 +13,8 @@ import { IO_VERSION } 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";
|
|
16
|
+
import { getScheduleRuns } from "../store/schedule-runs.js";
|
|
17
|
+
import { listPages, readPage } from "../wiki/fs.js";
|
|
16
18
|
import { runScheduleNow } from "../copilot/scheduler.js";
|
|
17
19
|
import { runIoScheduleNow } from "../copilot/io-scheduler.js";
|
|
18
20
|
import { listRecentNotifications, listUnreadNotifications, countUnreadNotifications, markNotificationRead, markAllNotificationsRead, } from "../store/notifications.js";
|
|
@@ -452,6 +454,31 @@ export async function startApiServer() {
|
|
|
452
454
|
res.status(500).json({ error: (e instanceof Error ? e.message : String(e)) });
|
|
453
455
|
}
|
|
454
456
|
});
|
|
457
|
+
// Schedule run history (issue #65)
|
|
458
|
+
api.get("/schedules/:type/:id/runs", (req, res) => {
|
|
459
|
+
const rawType = Array.isArray(req.params.type) ? req.params.type[0] : req.params.type;
|
|
460
|
+
const id = Number(Array.isArray(req.params.id) ? req.params.id[0] : req.params.id);
|
|
461
|
+
if (Number.isNaN(id)) {
|
|
462
|
+
res.status(400).json({ error: "Invalid schedule id" });
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
const scheduleTypeMap = { squads: "squad", io: "io" };
|
|
466
|
+
const scheduleType = scheduleTypeMap[rawType];
|
|
467
|
+
if (!scheduleType) {
|
|
468
|
+
res.status(400).json({ error: "type must be 'squads' or 'io'" });
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
const rawLimit = Number.parseInt(String(req.query.limit ?? ""), 10);
|
|
472
|
+
const limit = Number.isNaN(rawLimit) ? 25 : Math.min(rawLimit, 100);
|
|
473
|
+
try {
|
|
474
|
+
const runs = getScheduleRuns(scheduleType, id, limit);
|
|
475
|
+
res.json({ runs });
|
|
476
|
+
}
|
|
477
|
+
catch (e) {
|
|
478
|
+
console.error("Error fetching schedule runs:", e);
|
|
479
|
+
res.status(500).json({ error: (e instanceof Error ? e.message : String(e)) });
|
|
480
|
+
}
|
|
481
|
+
});
|
|
455
482
|
// Notifications endpoints
|
|
456
483
|
api.get("/notifications", (_req, res) => {
|
|
457
484
|
try {
|
|
@@ -550,6 +577,45 @@ export async function startApiServer() {
|
|
|
550
577
|
sseConnections.delete(res);
|
|
551
578
|
});
|
|
552
579
|
});
|
|
580
|
+
// Wiki endpoints (issue #105)
|
|
581
|
+
function extractWikiTitle(pageContent, fallback) {
|
|
582
|
+
const match = pageContent.match(/^#\s+(.+)/m);
|
|
583
|
+
return match ? match[1].trim() : fallback;
|
|
584
|
+
}
|
|
585
|
+
api.get("/wiki", (_req, res) => {
|
|
586
|
+
try {
|
|
587
|
+
const pages = listPages();
|
|
588
|
+
const result = pages.map((pagePath) => {
|
|
589
|
+
const pageContent = readPage(pagePath);
|
|
590
|
+
const title = pageContent ? extractWikiTitle(pageContent, pagePath) : pagePath;
|
|
591
|
+
return { path: pagePath, title };
|
|
592
|
+
});
|
|
593
|
+
res.json({ pages: result });
|
|
594
|
+
}
|
|
595
|
+
catch (e) {
|
|
596
|
+
console.error("Error listing wiki pages:", e);
|
|
597
|
+
res.status(500).json({ error: "Failed to list wiki pages" });
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
api.get("/wiki/*", (req, res) => {
|
|
601
|
+
try {
|
|
602
|
+
const pagePath = Array.isArray(req.params[0]) ? req.params[0][0] : req.params[0];
|
|
603
|
+
if (!pagePath) {
|
|
604
|
+
res.status(400).json({ error: "Missing page path" });
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
const pageContent = readPage(pagePath);
|
|
608
|
+
if (pageContent === undefined) {
|
|
609
|
+
res.status(404).json({ error: "Page not found" });
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
res.json({ path: pagePath, content: pageContent });
|
|
613
|
+
}
|
|
614
|
+
catch (e) {
|
|
615
|
+
console.error("Error reading wiki page:", e);
|
|
616
|
+
res.status(500).json({ error: "Failed to read wiki page" });
|
|
617
|
+
}
|
|
618
|
+
});
|
|
553
619
|
// Mount API at /api (for frontend)
|
|
554
620
|
app.use("/api", api);
|
|
555
621
|
// Serve Vue frontend if built assets exist (before backward-compat API mount)
|
package/dist/copilot/skills.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "fs";
|
|
2
2
|
import { join, basename } from "path";
|
|
3
|
-
import {
|
|
3
|
+
import { execFileSync } from "child_process";
|
|
4
4
|
import { SKILLS_DIR } from "../paths.js";
|
|
5
5
|
/**
|
|
6
6
|
* Scan SKILLS_DIR for subdirectories that contain a SKILL.md file.
|
|
@@ -111,7 +111,13 @@ export function parseSkillUrl(input) {
|
|
|
111
111
|
if (!input.startsWith("https://")) {
|
|
112
112
|
throw new Error("Only https:// URLs are supported for SKILL.md installs.");
|
|
113
113
|
}
|
|
114
|
-
|
|
114
|
+
let urlObj;
|
|
115
|
+
try {
|
|
116
|
+
urlObj = new URL(input);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
throw new Error(`Invalid URL: ${input}`);
|
|
120
|
+
}
|
|
115
121
|
const segments = urlObj.pathname.split("/").filter(Boolean);
|
|
116
122
|
// Use the segment before SKILL.md, or the hostname as slug fallback
|
|
117
123
|
const slug = segments.length >= 2
|
|
@@ -154,27 +160,44 @@ async function installSkillFromFile(rawUrl, slug) {
|
|
|
154
160
|
* Throws if the repo/file does not contain a valid SKILL.md.
|
|
155
161
|
*/
|
|
156
162
|
export async function installSkill(input) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
163
|
+
let destDir;
|
|
164
|
+
try {
|
|
165
|
+
const parsed = parseSkillUrl(input);
|
|
166
|
+
if (parsed.type === "file") {
|
|
167
|
+
return await installSkillFromFile(parsed.rawUrl, parsed.slug);
|
|
168
|
+
}
|
|
169
|
+
const repoUrl = parsed.url;
|
|
170
|
+
const repoName = basename(repoUrl, ".git").replace(/\.git$/, "");
|
|
171
|
+
if (!repoName) {
|
|
172
|
+
throw new Error("Could not determine skill name from URL.");
|
|
173
|
+
}
|
|
174
|
+
destDir = join(SKILLS_DIR, repoName);
|
|
175
|
+
execFileSync("git", ["clone", repoUrl, destDir], {
|
|
176
|
+
stdio: "pipe",
|
|
177
|
+
timeout: 60_000,
|
|
178
|
+
});
|
|
179
|
+
const skillMdPath = join(destDir, "SKILL.md");
|
|
180
|
+
if (!existsSync(skillMdPath)) {
|
|
181
|
+
rmSync(destDir, { recursive: true, force: true });
|
|
182
|
+
destDir = undefined;
|
|
183
|
+
throw new Error(`Repository "${repoUrl}" does not contain a SKILL.md file.`);
|
|
184
|
+
}
|
|
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
|
+
};
|
|
160
193
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
rmSync(destDir, { recursive: true, force: true });
|
|
168
|
-
throw new Error(`Repository "${repoUrl}" does not contain a SKILL.md file.`);
|
|
194
|
+
catch (e) {
|
|
195
|
+
// Clean up partially-created directory on failure
|
|
196
|
+
if (destDir && existsSync(destDir)) {
|
|
197
|
+
rmSync(destDir, { recursive: true, force: true });
|
|
198
|
+
}
|
|
199
|
+
throw e instanceof Error ? e : new Error(String(e));
|
|
169
200
|
}
|
|
170
|
-
const content = readFileSync(skillMdPath, "utf-8");
|
|
171
|
-
const { name, description } = parseSkillMd(content);
|
|
172
|
-
return {
|
|
173
|
-
name: name || repoName,
|
|
174
|
-
slug: repoName,
|
|
175
|
-
description,
|
|
176
|
-
path: destDir,
|
|
177
|
-
};
|
|
178
201
|
}
|
|
179
202
|
/**
|
|
180
203
|
* Remove a skill directory by its slug. Returns true if it existed.
|