vskill 0.2.80 → 0.2.82

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.
@@ -0,0 +1,239 @@
1
+ // ---------------------------------------------------------------------------
2
+ // skill-create-routes.ts -- Skill creation & project layout detection
3
+ // ---------------------------------------------------------------------------
4
+ import { existsSync, readdirSync, mkdirSync, writeFileSync } from "node:fs";
5
+ import { join, basename } from "node:path";
6
+ import { homedir } from "node:os";
7
+ import { sendJson, readBody } from "./router.js";
8
+ // ---------------------------------------------------------------------------
9
+ // Helpers
10
+ // ---------------------------------------------------------------------------
11
+ const EXCLUDE_DIRS = new Set([
12
+ "skills", "plugins", "node_modules", ".git", ".specweave",
13
+ "dist", "evals", ".claude", ".cursor", ".agents", "coverage",
14
+ ]);
15
+ /** List subdirs of a skills/ dir that contain SKILL.md */
16
+ function listSkillDirs(skillsDir) {
17
+ if (!existsSync(skillsDir))
18
+ return [];
19
+ try {
20
+ return readdirSync(skillsDir, { withFileTypes: true })
21
+ .filter((d) => d.isDirectory() && existsSync(join(skillsDir, d.name, "SKILL.md")))
22
+ .map((d) => d.name);
23
+ }
24
+ catch {
25
+ return [];
26
+ }
27
+ }
28
+ /** Detect project layout — mirrors scanSkills() logic from skill-scanner.ts */
29
+ function detectProjectLayout(root) {
30
+ const layouts = [];
31
+ const allSkills = [];
32
+ // Layout 4: Self — root IS the skill
33
+ if (existsSync(join(root, "SKILL.md"))) {
34
+ layouts.push({
35
+ layout: 4,
36
+ label: "Self (root is the skill)",
37
+ pathTemplate: `${root}/SKILL.md`,
38
+ existingPlugins: [],
39
+ });
40
+ }
41
+ // Layout 3: Root skills/ directory
42
+ const rootSkillsDir = join(root, "skills");
43
+ if (existsSync(rootSkillsDir)) {
44
+ const skills = listSkillDirs(rootSkillsDir);
45
+ const pluginName = basename(root) || "default";
46
+ layouts.push({
47
+ layout: 3,
48
+ label: "Root skills/",
49
+ pathTemplate: "{root}/skills/{skill}/",
50
+ existingPlugins: [pluginName],
51
+ });
52
+ for (const s of skills)
53
+ allSkills.push({ plugin: pluginName, skill: s });
54
+ }
55
+ // Layout 1: Direct plugin dirs — {root}/{plugin}/skills/{skill}/
56
+ const directPlugins = [];
57
+ try {
58
+ const entries = readdirSync(root, { withFileTypes: true })
59
+ .filter((d) => d.isDirectory() && !EXCLUDE_DIRS.has(d.name) && !d.name.startsWith("."));
60
+ for (const entry of entries) {
61
+ const skillsPath = join(root, entry.name, "skills");
62
+ if (existsSync(skillsPath)) {
63
+ directPlugins.push(entry.name);
64
+ const skills = listSkillDirs(skillsPath);
65
+ for (const s of skills)
66
+ allSkills.push({ plugin: entry.name, skill: s });
67
+ }
68
+ }
69
+ }
70
+ catch { /* ignore */ }
71
+ if (directPlugins.length > 0) {
72
+ layouts.push({
73
+ layout: 1,
74
+ label: "Direct plugins",
75
+ pathTemplate: "{root}/{plugin}/skills/{skill}/",
76
+ existingPlugins: directPlugins,
77
+ });
78
+ }
79
+ // Layout 2: Nested plugins/ dir — {root}/plugins/{plugin}/skills/{skill}/
80
+ const pluginsDir = join(root, "plugins");
81
+ if (existsSync(pluginsDir)) {
82
+ const nestedPlugins = [];
83
+ try {
84
+ const entries = readdirSync(pluginsDir, { withFileTypes: true })
85
+ .filter((d) => d.isDirectory());
86
+ for (const entry of entries) {
87
+ const skillsPath = join(pluginsDir, entry.name, "skills");
88
+ if (existsSync(skillsPath)) {
89
+ nestedPlugins.push(entry.name);
90
+ const skills = listSkillDirs(skillsPath);
91
+ for (const s of skills)
92
+ allSkills.push({ plugin: entry.name, skill: s });
93
+ }
94
+ }
95
+ }
96
+ catch { /* ignore */ }
97
+ if (nestedPlugins.length > 0) {
98
+ layouts.push({
99
+ layout: 2,
100
+ label: "Nested plugins/",
101
+ pathTemplate: "{root}/plugins/{plugin}/skills/{skill}/",
102
+ existingPlugins: nestedPlugins,
103
+ });
104
+ }
105
+ }
106
+ // Suggestion priority: Layout 2 > Layout 1 > Layout 3
107
+ let suggestedLayout = 3;
108
+ if (layouts.find((l) => l.layout === 2))
109
+ suggestedLayout = 2;
110
+ else if (layouts.find((l) => l.layout === 1))
111
+ suggestedLayout = 1;
112
+ return { root, detectedLayouts: layouts, suggestedLayout, existingSkills: allSkills };
113
+ }
114
+ /** Build SKILL.md content from form fields */
115
+ function buildSkillMd(data) {
116
+ const lines = ["---"];
117
+ // Description — always quote to handle special chars
118
+ lines.push(`description: "${data.description.replace(/"/g, '\\"')}"`);
119
+ if (data.allowedTools?.trim()) {
120
+ lines.push(`allowed-tools: ${data.allowedTools.trim()}`);
121
+ }
122
+ if (data.model) {
123
+ lines.push(`model: ${data.model}`);
124
+ }
125
+ lines.push("---");
126
+ lines.push("");
127
+ if (data.body.trim()) {
128
+ lines.push(data.body.trim());
129
+ }
130
+ else {
131
+ lines.push(`# /${data.name}`);
132
+ lines.push("");
133
+ lines.push("You are a helpful assistant. Describe what this skill does.");
134
+ }
135
+ return lines.join("\n") + "\n";
136
+ }
137
+ /** Compute target directory for a new skill based on layout */
138
+ function computeSkillDir(root, layout, plugin, name) {
139
+ switch (layout) {
140
+ case 1: return join(root, plugin, "skills", name);
141
+ case 2: return join(root, "plugins", plugin, "skills", name);
142
+ case 3: return join(root, "skills", name);
143
+ }
144
+ }
145
+ /** Check if skill-creator skill is installed */
146
+ function checkSkillCreatorInstalled() {
147
+ const home = homedir();
148
+ const candidates = [
149
+ join(home, ".claude", "skills", "skill-creator"),
150
+ join(home, ".claude", "plugins", "cache", "skill-creator"),
151
+ ];
152
+ for (const dir of candidates) {
153
+ if (existsSync(dir))
154
+ return true;
155
+ }
156
+ // Also check for plugin with nested structure
157
+ try {
158
+ const pluginsCache = join(home, ".claude", "plugins", "cache");
159
+ if (existsSync(pluginsCache)) {
160
+ const entries = readdirSync(pluginsCache, { withFileTypes: true });
161
+ for (const entry of entries) {
162
+ if (entry.isDirectory() && entry.name.includes("skill-creator"))
163
+ return true;
164
+ }
165
+ }
166
+ }
167
+ catch { /* ignore */ }
168
+ return false;
169
+ }
170
+ // ---------------------------------------------------------------------------
171
+ // Route registration
172
+ // ---------------------------------------------------------------------------
173
+ export function registerSkillCreateRoutes(router, root) {
174
+ // GET /api/project-layout — detect project layout and suggest placement
175
+ router.get("/api/project-layout", async (_req, res) => {
176
+ try {
177
+ const layout = detectProjectLayout(root);
178
+ sendJson(res, layout, 200, _req);
179
+ }
180
+ catch (err) {
181
+ sendJson(res, { error: err.message }, 500, _req);
182
+ }
183
+ });
184
+ // POST /api/skills/create — create a new skill
185
+ router.post("/api/skills/create", async (req, res) => {
186
+ const body = (await readBody(req));
187
+ // Validate name
188
+ if (!body.name || !/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(body.name)) {
189
+ sendJson(res, { error: "Name must be kebab-case (lowercase letters, numbers, hyphens)" }, 400, req);
190
+ return;
191
+ }
192
+ if (!body.description?.trim()) {
193
+ sendJson(res, { error: "Description is required" }, 400, req);
194
+ return;
195
+ }
196
+ if (!body.layout || ![1, 2, 3].includes(body.layout)) {
197
+ sendJson(res, { error: "Layout must be 1, 2, or 3" }, 400, req);
198
+ return;
199
+ }
200
+ if (body.layout !== 3 && !body.plugin?.trim()) {
201
+ sendJson(res, { error: "Plugin name is required for this layout" }, 400, req);
202
+ return;
203
+ }
204
+ const targetDir = computeSkillDir(root, body.layout, body.plugin || "", body.name);
205
+ // Check if already exists
206
+ if (existsSync(join(targetDir, "SKILL.md"))) {
207
+ sendJson(res, { error: `Skill already exists at ${targetDir}` }, 409, req);
208
+ return;
209
+ }
210
+ try {
211
+ // Create directories
212
+ mkdirSync(targetDir, { recursive: true });
213
+ mkdirSync(join(targetDir, "evals"), { recursive: true });
214
+ // Write SKILL.md
215
+ const content = buildSkillMd(body);
216
+ const skillMdPath = join(targetDir, "SKILL.md");
217
+ writeFileSync(skillMdPath, content, "utf-8");
218
+ sendJson(res, {
219
+ ok: true,
220
+ plugin: body.layout === 3 ? (basename(root) || "default") : body.plugin,
221
+ skill: body.name,
222
+ dir: targetDir,
223
+ skillMdPath,
224
+ }, 201, req);
225
+ }
226
+ catch (err) {
227
+ sendJson(res, { error: `Failed to create skill: ${err.message}` }, 500, req);
228
+ }
229
+ });
230
+ // GET /api/skill-creator-status — check if skill-creator is installed
231
+ router.get("/api/skill-creator-status", async (_req, res) => {
232
+ const installed = checkSkillCreatorInstalled();
233
+ sendJson(res, {
234
+ installed,
235
+ installCommand: "vskill install skill-creator:skill-creator",
236
+ }, 200, _req);
237
+ });
238
+ }
239
+ //# sourceMappingURL=skill-create-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-create-routes.js","sourceRoot":"","sources":["../../src/eval-server/skill-create-routes.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,sEAAsE;AACtE,8EAA8E;AAE9E,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC5E,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AA8BjD,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC;IAC3B,QAAQ,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY;IACzD,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU;CAC7D,CAAC,CAAC;AAEH,0DAA0D;AAC1D,SAAS,aAAa,CAAC,SAAiB;IACtC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,EAAE,CAAC;IACtC,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,SAAS,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;aACnD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,IAAI,UAAU,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC;aACjF,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,+EAA+E;AAC/E,SAAS,mBAAmB,CAAC,IAAY;IACvC,MAAM,OAAO,GAAqB,EAAE,CAAC;IACrC,MAAM,SAAS,GAA6C,EAAE,CAAC;IAE/D,qCAAqC;IACrC,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC;QACvC,OAAO,CAAC,IAAI,CAAC;YACX,MAAM,EAAE,CAAC;YACT,KAAK,EAAE,0BAA0B;YACjC,YAAY,EAAE,GAAG,IAAI,WAAW;YAChC,eAAe,EAAE,EAAE;SACpB,CAAC,CAAC;IACL,CAAC;IAED,mCAAmC;IACnC,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC3C,IAAI,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,aAAa,CAAC,aAAa,CAAC,CAAC;QAC5C,MAAM,UAAU,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC;QAC/C,OAAO,CAAC,IAAI,CAAC;YACX,MAAM,EAAE,CAAC;YACT,KAAK,EAAE,cAAc;YACrB,YAAY,EAAE,wBAAwB;YACtC,eAAe,EAAE,CAAC,UAAU,CAAC;SAC9B,CAAC,CAAC;QACH,KAAK,MAAM,CAAC,IAAI,MAAM;YAAE,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED,iEAAiE;IACjE,MAAM,aAAa,GAAa,EAAE,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;aACvD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QAC1F,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;YACpD,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC3B,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC/B,MAAM,MAAM,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;gBACzC,KAAK,MAAM,CAAC,IAAI,MAAM;oBAAE,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;YAC3E,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;IAExB,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,IAAI,CAAC;YACX,MAAM,EAAE,CAAC;YACT,KAAK,EAAE,gBAAgB;YACvB,YAAY,EAAE,iCAAiC;YAC/C,eAAe,EAAE,aAAa;SAC/B,CAAC,CAAC;IACL,CAAC;IAED,0EAA0E;IAC1E,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IACzC,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3B,MAAM,aAAa,GAAa,EAAE,CAAC;QACnC,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,WAAW,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;iBAC7D,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;YAClC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAC5B,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;gBAC1D,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;oBAC3B,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;oBAC/B,MAAM,MAAM,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;oBACzC,KAAK,MAAM,CAAC,IAAI,MAAM;wBAAE,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;gBAC3E,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;QAExB,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7B,OAAO,CAAC,IAAI,CAAC;gBACX,MAAM,EAAE,CAAC;gBACT,KAAK,EAAE,iBAAiB;gBACxB,YAAY,EAAE,yCAAyC;gBACvD,eAAe,EAAE,aAAa;aAC/B,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,sDAAsD;IACtD,IAAI,eAAe,GAAc,CAAC,CAAC;IACnC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;QAAE,eAAe,GAAG,CAAC,CAAC;SACxD,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;QAAE,eAAe,GAAG,CAAC,CAAC;IAElE,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,SAAS,EAAE,CAAC;AACxF,CAAC;AAED,8CAA8C;AAC9C,SAAS,YAAY,CAAC,IAAwB;IAC5C,MAAM,KAAK,GAAa,CAAC,KAAK,CAAC,CAAC;IAChC,qDAAqD;IACrD,KAAK,CAAC,IAAI,CAAC,iBAAiB,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IACtE,IAAI,IAAI,CAAC,YAAY,EAAE,IAAI,EAAE,EAAE,CAAC;QAC9B,KAAK,CAAC,IAAI,CAAC,kBAAkB,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC3D,CAAC;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,UAAU,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IACrC,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;QACrB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAC/B,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAC9B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,6DAA6D,CAAC,CAAC;IAC5E,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AACjC,CAAC;AAED,+DAA+D;AAC/D,SAAS,eAAe,CAAC,IAAY,EAAE,MAAiB,EAAE,MAAc,EAAE,IAAY;IACpF,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;QAClD,KAAK,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;QAC7D,KAAK,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC5C,CAAC;AACH,CAAC;AAED,gDAAgD;AAChD,SAAS,0BAA0B;IACjC,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,MAAM,UAAU,GAAG;QACjB,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,eAAe,CAAC;QAChD,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,CAAC;KAC3D,CAAC;IACF,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,IAAI,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;IACnC,CAAC;IACD,8CAA8C;IAC9C,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;QAC/D,IAAI,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YAC7B,MAAM,OAAO,GAAG,WAAW,CAAC,YAAY,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;YACnE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAC5B,IAAI,KAAK,CAAC,WAAW,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC;oBAAE,OAAO,IAAI,CAAC;YAC/E,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;IACxB,OAAO,KAAK,CAAC;AACf,CAAC;AAED,8EAA8E;AAC9E,qBAAqB;AACrB,8EAA8E;AAE9E,MAAM,UAAU,yBAAyB,CAAC,MAAc,EAAE,IAAY;IACpE,wEAAwE;IACxE,MAAM,CAAC,GAAG,CAAC,qBAAqB,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;QACpD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;YACzC,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;QACnC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,EAAE,EAAE,KAAK,EAAG,GAAa,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,+CAA+C;IAC/C,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QACnD,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAuB,CAAC;QAEzD,gBAAgB;QAChB,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,iCAAiC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACrE,QAAQ,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,+DAA+D,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;YACpG,OAAO;QACT,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,EAAE,EAAE,CAAC;YAC9B,QAAQ,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,yBAAyB,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;YAC9D,OAAO;QACT,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACrD,QAAQ,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,2BAA2B,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;YAChE,OAAO;QACT,CAAC;QACD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC;YAC9C,QAAQ,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,yCAAyC,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;YAC9E,OAAO;QACT,CAAC;QAED,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAEnF,0BAA0B;QAC1B,IAAI,UAAU,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC;YAC5C,QAAQ,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,2BAA2B,SAAS,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;YAC3E,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,qBAAqB;YACrB,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC1C,SAAS,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAEzD,iBAAiB;YACjB,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;YACnC,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;YAChD,aAAa,CAAC,WAAW,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;YAE7C,QAAQ,CAAC,GAAG,EAAE;gBACZ,EAAE,EAAE,IAAI;gBACR,MAAM,EAAE,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM;gBACvE,KAAK,EAAE,IAAI,CAAC,IAAI;gBAChB,GAAG,EAAE,SAAS;gBACd,WAAW;aACZ,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QACf,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,2BAA4B,GAAa,CAAC,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QAC1F,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,sEAAsE;IACtE,MAAM,CAAC,GAAG,CAAC,2BAA2B,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;QAC1D,MAAM,SAAS,GAAG,0BAA0B,EAAE,CAAC;QAC/C,QAAQ,CAAC,GAAG,EAAE;YACZ,SAAS;YACT,cAAc,EAAE,4CAA4C;SAC7D,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IAChB,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -2,3 +2,9 @@ import type * as http from "node:http";
2
2
  export declare function initSSE(res: http.ServerResponse, req?: http.IncomingMessage): void;
3
3
  export declare function sendSSE(res: http.ServerResponse, event: string, data: unknown): void;
4
4
  export declare function sendSSEDone(res: http.ServerResponse, data: unknown): void;
5
+ /**
6
+ * Wrap an async operation with periodic heartbeat SSE events.
7
+ * Emits a progress event every `intervalMs` with elapsed time so the
8
+ * frontend knows the system is still alive during long LLM calls.
9
+ */
10
+ export declare function withHeartbeat<T>(res: http.ServerResponse, evalId: number, phase: string, messagePrefix: string, fn: () => Promise<T>, intervalMs?: number): Promise<T>;
@@ -21,4 +21,26 @@ export function sendSSEDone(res, data) {
21
21
  sendSSE(res, "done", data);
22
22
  res.end();
23
23
  }
24
+ /**
25
+ * Wrap an async operation with periodic heartbeat SSE events.
26
+ * Emits a progress event every `intervalMs` with elapsed time so the
27
+ * frontend knows the system is still alive during long LLM calls.
28
+ */
29
+ export async function withHeartbeat(res, evalId, phase, messagePrefix, fn, intervalMs = 3000) {
30
+ const start = Date.now();
31
+ const timer = setInterval(() => {
32
+ const elapsed = Math.round((Date.now() - start) / 1000);
33
+ sendSSE(res, "progress", {
34
+ eval_id: evalId,
35
+ phase,
36
+ message: `${messagePrefix} (${elapsed}s)`,
37
+ });
38
+ }, intervalMs);
39
+ try {
40
+ return await fn();
41
+ }
42
+ finally {
43
+ clearInterval(timer);
44
+ }
45
+ }
24
46
  //# sourceMappingURL=sse-helpers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sse-helpers.js","sourceRoot":"","sources":["../../src/eval-server/sse-helpers.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,iDAAiD;AACjD,8EAA8E;AAI9E,MAAM,UAAU,OAAO,CACrB,GAAwB,EACxB,GAA0B;IAE1B,MAAM,OAAO,GAA2B;QACtC,cAAc,EAAE,mBAAmB;QACnC,eAAe,EAAE,UAAU;QAC3B,UAAU,EAAE,YAAY;QACxB,mBAAmB,EAAE,IAAI;KAC1B,CAAC;IACF,MAAM,MAAM,GAAG,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC;IACpC,IAAI,MAAM,IAAI,8CAA8C,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1E,OAAO,CAAC,6BAA6B,CAAC,GAAG,MAAM,CAAC;IAClD,CAAC;IACD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,OAAO,CACrB,GAAwB,EACxB,KAAa,EACb,IAAa;IAEb,GAAG,CAAC,KAAK,CAAC,UAAU,KAAK,WAAW,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAClE,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,GAAwB,EACxB,IAAa;IAEb,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;IAC3B,GAAG,CAAC,GAAG,EAAE,CAAC;AACZ,CAAC"}
1
+ {"version":3,"file":"sse-helpers.js","sourceRoot":"","sources":["../../src/eval-server/sse-helpers.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,iDAAiD;AACjD,8EAA8E;AAI9E,MAAM,UAAU,OAAO,CACrB,GAAwB,EACxB,GAA0B;IAE1B,MAAM,OAAO,GAA2B;QACtC,cAAc,EAAE,mBAAmB;QACnC,eAAe,EAAE,UAAU;QAC3B,UAAU,EAAE,YAAY;QACxB,mBAAmB,EAAE,IAAI;KAC1B,CAAC;IACF,MAAM,MAAM,GAAG,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC;IACpC,IAAI,MAAM,IAAI,8CAA8C,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1E,OAAO,CAAC,6BAA6B,CAAC,GAAG,MAAM,CAAC;IAClD,CAAC;IACD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,OAAO,CACrB,GAAwB,EACxB,KAAa,EACb,IAAa;IAEb,GAAG,CAAC,KAAK,CAAC,UAAU,KAAK,WAAW,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAClE,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,GAAwB,EACxB,IAAa;IAEb,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;IAC3B,GAAG,CAAC,GAAG,EAAE,CAAC;AACZ,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,GAAwB,EACxB,MAAc,EACd,KAAa,EACb,aAAqB,EACrB,EAAoB,EACpB,UAAU,GAAG,IAAI;IAEjB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;QACxD,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE;YACvB,OAAO,EAAE,MAAM;YACf,KAAK;YACL,OAAO,EAAE,GAAG,aAAa,KAAK,OAAO,IAAI;SAC1C,CAAC,CAAC;IACL,CAAC,EAAE,UAAU,CAAC,CAAC;IAEf,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,EAAE,CAAC;IACpB,CAAC;YAAS,CAAC;QACT,aAAa,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;AACH,CAAC"}