augure 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.
@@ -0,0 +1,1294 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ noopLogger
4
+ } from "./chunk-62OPINAX.js";
5
+
6
+ // ../skills/dist/manager.js
7
+ import { readFile, writeFile, mkdir, readdir, rm, access } from "fs/promises";
8
+ import { join } from "path";
9
+
10
+ // ../skills/dist/parser.js
11
+ import matter from "gray-matter";
12
+ function parseSkillMd(content) {
13
+ const { data, content: body } = matter(content);
14
+ const meta = validateSkillMeta(data);
15
+ return { meta, body: body.trim() };
16
+ }
17
+ function serializeSkillMd(meta, body) {
18
+ return matter.stringify(body, JSON.parse(JSON.stringify(meta)));
19
+ }
20
+ function validateSkillMeta(raw) {
21
+ if (!raw.id || typeof raw.id !== "string")
22
+ throw new Error("skill.md: missing or invalid 'id'");
23
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(raw.id))
24
+ throw new Error(`skill.md: invalid id format '${raw.id}' (must be lowercase slug)`);
25
+ if (!raw.name || typeof raw.name !== "string")
26
+ throw new Error("skill.md: missing or invalid 'name'");
27
+ const version = typeof raw.version === "number" ? raw.version : 1;
28
+ if (!Number.isInteger(version) || version < 1)
29
+ throw new Error("skill.md: 'version' must be a positive integer");
30
+ const now = (/* @__PURE__ */ new Date()).toISOString();
31
+ const created = typeof raw.created === "string" ? raw.created : now;
32
+ const updated = typeof raw.updated === "string" ? raw.updated : now;
33
+ const status = typeof raw.status === "string" ? raw.status : "draft";
34
+ const validStatuses = ["draft", "testing", "active", "paused", "broken"];
35
+ if (!validStatuses.includes(status))
36
+ throw new Error(`skill.md: invalid status '${status}'`);
37
+ const trigger = raw.trigger;
38
+ if (!trigger || typeof trigger !== "object")
39
+ throw new Error("skill.md: missing 'trigger'");
40
+ const triggerType = trigger.type;
41
+ if (!["cron", "manual", "event"].includes(triggerType))
42
+ throw new Error(`skill.md: invalid trigger.type '${triggerType}'`);
43
+ if (triggerType === "cron" && (!trigger.schedule || typeof trigger.schedule !== "string")) {
44
+ throw new Error("skill.md: cron trigger requires 'schedule'");
45
+ }
46
+ const sandbox = typeof raw.sandbox === "boolean" ? raw.sandbox : true;
47
+ const tools = Array.isArray(raw.tools) ? raw.tools : [];
48
+ const tags = Array.isArray(raw.tags) ? raw.tags : [];
49
+ return {
50
+ id: raw.id,
51
+ name: raw.name,
52
+ version,
53
+ created,
54
+ updated,
55
+ status,
56
+ trigger: {
57
+ type: triggerType,
58
+ schedule: trigger.schedule,
59
+ channel: trigger.channel
60
+ },
61
+ sandbox,
62
+ tools,
63
+ tags
64
+ };
65
+ }
66
+
67
+ // ../skills/dist/manager.js
68
+ var SkillManager = class {
69
+ basePath;
70
+ constructor(basePath) {
71
+ this.basePath = basePath;
72
+ }
73
+ /** List all skills from the index (rebuilds if missing) */
74
+ async list() {
75
+ try {
76
+ const index = await this.readIndex();
77
+ return index.skills;
78
+ } catch {
79
+ const index = await this.rebuildIndex();
80
+ return index.skills;
81
+ }
82
+ }
83
+ /** Load a single skill by ID */
84
+ async get(id) {
85
+ const dir = join(this.basePath, id);
86
+ const mdContent = await readFile(join(dir, "skill.md"), "utf-8");
87
+ const { meta, body } = parseSkillMd(mdContent);
88
+ let code;
89
+ try {
90
+ code = await readFile(join(dir, "skill.ts"), "utf-8");
91
+ } catch {
92
+ }
93
+ let testCode;
94
+ try {
95
+ testCode = await readFile(join(dir, "skill.test.ts"), "utf-8");
96
+ } catch {
97
+ }
98
+ return { meta, body, code, testCode };
99
+ }
100
+ /** Save a skill to disk (creates directory, writes files, updates index) */
101
+ async save(skill) {
102
+ const dir = join(this.basePath, skill.meta.id);
103
+ await mkdir(dir, { recursive: true });
104
+ const mdContent = serializeSkillMd(skill.meta, skill.body);
105
+ await writeFile(join(dir, "skill.md"), mdContent, "utf-8");
106
+ if (skill.code !== void 0) {
107
+ await writeFile(join(dir, "skill.ts"), skill.code, "utf-8");
108
+ }
109
+ if (skill.testCode !== void 0) {
110
+ await writeFile(join(dir, "skill.test.ts"), skill.testCode, "utf-8");
111
+ }
112
+ await this.updateIndex(skill.meta);
113
+ }
114
+ /** Delete a skill directory and remove from index */
115
+ async delete(id) {
116
+ const dir = join(this.basePath, id);
117
+ await rm(dir, { recursive: true, force: true });
118
+ await this.removeFromIndex(id);
119
+ }
120
+ /** Update just the status of a skill */
121
+ async updateStatus(id, status) {
122
+ const skill = await this.get(id);
123
+ skill.meta.status = status;
124
+ skill.meta.updated = (/* @__PURE__ */ new Date()).toISOString();
125
+ await this.save(skill);
126
+ }
127
+ /** Bump version number, returns the new version */
128
+ async bumpVersion(id) {
129
+ const skill = await this.get(id);
130
+ skill.meta.version += 1;
131
+ skill.meta.updated = (/* @__PURE__ */ new Date()).toISOString();
132
+ await this.save(skill);
133
+ return skill.meta.version;
134
+ }
135
+ /** Save a run result to runs/<timestamp>.json */
136
+ async saveRun(result) {
137
+ const runsDir = join(this.basePath, result.skillId, "runs");
138
+ await mkdir(runsDir, { recursive: true });
139
+ const filename = `${result.timestamp.replace(/[:.]/g, "-")}.json`;
140
+ await writeFile(join(runsDir, filename), JSON.stringify(result, null, 2), "utf-8");
141
+ }
142
+ /** Load recent run results for a skill, sorted newest first */
143
+ async getRuns(id, limit = 10) {
144
+ const runsDir = join(this.basePath, id, "runs");
145
+ let files;
146
+ try {
147
+ files = await readdir(runsDir);
148
+ } catch {
149
+ return [];
150
+ }
151
+ const jsonFiles = files.filter((f) => f.endsWith(".json")).sort().reverse();
152
+ const results = [];
153
+ for (const file of jsonFiles.slice(0, limit)) {
154
+ try {
155
+ const raw = await readFile(join(runsDir, file), "utf-8");
156
+ results.push(JSON.parse(raw));
157
+ } catch {
158
+ }
159
+ }
160
+ return results;
161
+ }
162
+ /** Get the most recent run result */
163
+ async getLastRun(id) {
164
+ const runs = await this.getRuns(id, 1);
165
+ return runs[0] ?? null;
166
+ }
167
+ /** Rebuild skills-index.json by scanning all skill directories */
168
+ async rebuildIndex() {
169
+ await mkdir(this.basePath, { recursive: true });
170
+ let entries;
171
+ try {
172
+ entries = await readdir(this.basePath, { withFileTypes: true }).then((e) => e.filter((d) => d.isDirectory()).map((d) => d.name));
173
+ } catch {
174
+ entries = [];
175
+ }
176
+ const skills = [];
177
+ for (const dir of entries) {
178
+ try {
179
+ const mdContent = await readFile(join(this.basePath, dir, "skill.md"), "utf-8");
180
+ const { meta } = parseSkillMd(mdContent);
181
+ skills.push(metaToIndexEntry(meta));
182
+ } catch {
183
+ }
184
+ }
185
+ const index = { version: 1, skills };
186
+ await this.writeIndex(index);
187
+ return index;
188
+ }
189
+ /** Check if a skill exists on disk */
190
+ async exists(id) {
191
+ try {
192
+ await access(join(this.basePath, id, "skill.md"));
193
+ return true;
194
+ } catch {
195
+ return false;
196
+ }
197
+ }
198
+ async readIndex() {
199
+ const raw = await readFile(join(this.basePath, "skills-index.json"), "utf-8");
200
+ return JSON.parse(raw);
201
+ }
202
+ async writeIndex(index) {
203
+ await mkdir(this.basePath, { recursive: true });
204
+ await writeFile(join(this.basePath, "skills-index.json"), JSON.stringify(index, null, 2), "utf-8");
205
+ }
206
+ async updateIndex(meta) {
207
+ let index;
208
+ try {
209
+ index = await this.readIndex();
210
+ } catch {
211
+ index = { version: 1, skills: [] };
212
+ }
213
+ const entry = metaToIndexEntry(meta);
214
+ const idx = index.skills.findIndex((s) => s.id === meta.id);
215
+ if (idx >= 0) {
216
+ index.skills[idx] = entry;
217
+ } else {
218
+ index.skills.push(entry);
219
+ }
220
+ await this.writeIndex(index);
221
+ }
222
+ async removeFromIndex(id) {
223
+ let index;
224
+ try {
225
+ index = await this.readIndex();
226
+ } catch {
227
+ return;
228
+ }
229
+ index.skills = index.skills.filter((s) => s.id !== id);
230
+ await this.writeIndex(index);
231
+ }
232
+ };
233
+ function metaToIndexEntry(meta) {
234
+ return {
235
+ id: meta.id,
236
+ name: meta.name,
237
+ version: meta.version,
238
+ status: meta.status,
239
+ trigger: meta.trigger,
240
+ tags: meta.tags,
241
+ updated: meta.updated
242
+ };
243
+ }
244
+
245
+ // ../skills/dist/llm-parser.js
246
+ function parseSkillResponse(content) {
247
+ const namedResult = parseNamedFences(content);
248
+ if (namedResult)
249
+ return namedResult;
250
+ const sectionResult = parseSectionHeaders(content);
251
+ if (sectionResult)
252
+ return sectionResult;
253
+ const sequentialResult = parseSequentialBlocks(content);
254
+ if (sequentialResult)
255
+ return sequentialResult;
256
+ return null;
257
+ }
258
+ function parseNamedFences(content) {
259
+ const fencePattern = /```\w*\s+(?:filename=)?(\S+)\s*\n([\s\S]*?)```/g;
260
+ const blocks = /* @__PURE__ */ new Map();
261
+ let match;
262
+ while ((match = fencePattern.exec(content)) !== null) {
263
+ blocks.set(match[1], match[2].trim());
264
+ }
265
+ const skillMd = blocks.get("skill.md");
266
+ const skillTs = blocks.get("skill.ts");
267
+ const skillTestTs = blocks.get("skill.test.ts");
268
+ if (skillMd && skillTs && skillTestTs) {
269
+ return { skillMd, skillTs, skillTestTs };
270
+ }
271
+ return null;
272
+ }
273
+ function parseSectionHeaders(content) {
274
+ const sections = /* @__PURE__ */ new Map();
275
+ const sectionPattern = /#{2,3}\s+(skill\.(?:md|ts|test\.ts))\s*\n+```\w*\n([\s\S]*?)```/g;
276
+ let match;
277
+ while ((match = sectionPattern.exec(content)) !== null) {
278
+ sections.set(match[1], match[2].trim());
279
+ }
280
+ const skillMd = sections.get("skill.md");
281
+ const skillTs = sections.get("skill.ts");
282
+ const skillTestTs = sections.get("skill.test.ts");
283
+ if (skillMd && skillTs && skillTestTs) {
284
+ return { skillMd, skillTs, skillTestTs };
285
+ }
286
+ return null;
287
+ }
288
+ function parseSequentialBlocks(content) {
289
+ const blockPattern = /```\w*\n([\s\S]*?)```/g;
290
+ const blocks = [];
291
+ let match;
292
+ while ((match = blockPattern.exec(content)) !== null) {
293
+ blocks.push(match[1].trim());
294
+ }
295
+ if (blocks.length >= 3) {
296
+ return {
297
+ skillMd: blocks[0],
298
+ skillTs: blocks[1],
299
+ skillTestTs: blocks[2]
300
+ };
301
+ }
302
+ return null;
303
+ }
304
+
305
+ // ../skills/dist/generator.js
306
+ var GENERATION_SYSTEM_PROMPT = `You are a skill generator for the Augure AI agent. When given a description of a task, you generate three files that implement it.
307
+
308
+ ## Output Format
309
+
310
+ You MUST output exactly three fenced code blocks with filenames:
311
+
312
+ \`\`\`yaml filename=skill.md
313
+ ---
314
+ id: skill-id-here
315
+ name: Human Readable Name
316
+ version: 1
317
+ status: draft
318
+ trigger:
319
+ type: TRIGGER_TYPE
320
+ schedule: "CRON_EXPRESSION" # only if trigger type is cron
321
+ channel: CHANNEL # optional
322
+ sandbox: true
323
+ tools: []
324
+ tags: []
325
+ ---
326
+
327
+ # Skill Name
328
+
329
+ ## Goal
330
+ What this skill does.
331
+
332
+ ## Strategy
333
+ Step by step approach.
334
+ \`\`\`
335
+
336
+ \`\`\`typescript filename=skill.ts
337
+ import type { SkillContext } from "@augure/types";
338
+
339
+ export default async function execute(ctx: SkillContext): Promise<{ output: string }> {
340
+ // Implementation here
341
+ return { output: "result" };
342
+ }
343
+ \`\`\`
344
+
345
+ \`\`\`typescript filename=skill.test.ts
346
+ import { describe, it } from "node:test";
347
+ import assert from "node:assert/strict";
348
+
349
+ describe("skill-id", () => {
350
+ it("should produce expected output", async () => {
351
+ // Test the skill logic
352
+ assert.ok(true);
353
+ });
354
+ });
355
+ \`\`\`
356
+
357
+ ## Rules
358
+ - The skill ID must be a lowercase slug (e.g., "daily-digest", "price-alert")
359
+ - Tests MUST use node:test and node:assert (NOT vitest or jest)
360
+ - The skill.ts default export must be an async function taking SkillContext
361
+ - Keep the implementation focused and minimal`;
362
+ var REGENERATION_SYSTEM_PROMPT = `You are a skill code fixer for the Augure AI agent. A skill failed to execute. You must fix the code.
363
+
364
+ ## Rules
365
+ - Output exactly two fenced code blocks: skill.ts and skill.test.ts
366
+ - Keep the same function signature and approach
367
+ - Fix the specific error described
368
+ - Tests use node:test and node:assert (NOT vitest or jest)
369
+
370
+ Output format:
371
+ \`\`\`typescript filename=skill.ts
372
+ // fixed code
373
+ \`\`\`
374
+
375
+ \`\`\`typescript filename=skill.test.ts
376
+ // fixed test
377
+ \`\`\``;
378
+ function slugify(text) {
379
+ return text.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
380
+ }
381
+ var SkillGenerator = class {
382
+ llm;
383
+ constructor(llm) {
384
+ this.llm = llm;
385
+ }
386
+ async generate(request) {
387
+ const id = slugify(request.description);
388
+ if (!id) {
389
+ return { success: false, error: "Could not generate a valid skill ID from description" };
390
+ }
391
+ const userPrompt = [
392
+ `Create a skill with the following specification:`,
393
+ `- ID: ${id}`,
394
+ `- Description: ${request.description}`,
395
+ `- Trigger type: ${request.trigger.type}`,
396
+ request.trigger.schedule ? `- Schedule: ${request.trigger.schedule}` : null,
397
+ request.trigger.channel ? `- Channel: ${request.trigger.channel}` : null,
398
+ request.tags?.length ? `- Tags: ${request.tags.join(", ")}` : null,
399
+ `- Sandbox: ${request.sandbox !== false}`
400
+ ].filter(Boolean).join("\n");
401
+ try {
402
+ const response = await this.llm.chat([
403
+ { role: "system", content: GENERATION_SYSTEM_PROMPT },
404
+ { role: "user", content: userPrompt }
405
+ ]);
406
+ const parsed = parseSkillResponse(response.content);
407
+ if (!parsed) {
408
+ return { success: false, error: "Failed to parse LLM response: missing code blocks" };
409
+ }
410
+ const { meta, body } = parseSkillMd(parsed.skillMd);
411
+ meta.id = id;
412
+ const skill = {
413
+ meta,
414
+ body,
415
+ code: parsed.skillTs,
416
+ testCode: parsed.skillTestTs
417
+ };
418
+ return { success: true, skill };
419
+ } catch (err) {
420
+ return {
421
+ success: false,
422
+ error: err instanceof Error ? err.message : String(err)
423
+ };
424
+ }
425
+ }
426
+ async regenerateCode(skill, error) {
427
+ const userPrompt = [
428
+ `## Skill: ${skill.meta.name} (${skill.meta.id})`,
429
+ ``,
430
+ `## Description`,
431
+ skill.body,
432
+ ``,
433
+ `## Current code (skill.ts)`,
434
+ "```typescript",
435
+ skill.code ?? "// no code yet",
436
+ "```",
437
+ ``,
438
+ `## Error`,
439
+ "```",
440
+ error,
441
+ "```",
442
+ ``,
443
+ `Fix the code to resolve this error.`
444
+ ].join("\n");
445
+ try {
446
+ const response = await this.llm.chat([
447
+ { role: "system", content: REGENERATION_SYSTEM_PROMPT },
448
+ { role: "user", content: userPrompt }
449
+ ]);
450
+ const blockPattern = /```\w*(?:\s+\S+)?\s*\n([\s\S]*?)```/g;
451
+ const blocks = [];
452
+ let match;
453
+ while ((match = blockPattern.exec(response.content)) !== null) {
454
+ blocks.push(match[1].trim());
455
+ }
456
+ if (blocks.length < 2)
457
+ return null;
458
+ return { code: blocks[0], testCode: blocks[1] };
459
+ } catch {
460
+ return null;
461
+ }
462
+ }
463
+ };
464
+
465
+ // ../skills/dist/runner.js
466
+ var HARNESS_TEMPLATE = `
467
+ import skill from "./skill.ts";
468
+ import { readFile, readdir, writeFile, mkdir } from "node:fs/promises";
469
+ import { join } from "node:path";
470
+
471
+ // Load injected config from separate JSON file (avoids template injection issues)
472
+ const __injected = JSON.parse(await readFile("/workspace/__config.json", "utf-8"));
473
+
474
+ const ctx = {
475
+ exec: async (command, opts) => {
476
+ const { execSync } = await import("node:child_process");
477
+ try {
478
+ const stdout = execSync(command, {
479
+ encoding: "utf-8",
480
+ timeout: (opts?.timeout ?? 30) * 1000,
481
+ env: { ...process.env, ...(opts?.env ?? {}) },
482
+ maxBuffer: 10 * 1024 * 1024,
483
+ });
484
+ return { exitCode: 0, stdout, stderr: "" };
485
+ } catch (err) {
486
+ return {
487
+ exitCode: err.status ?? 1,
488
+ stdout: err.stdout ?? "",
489
+ stderr: err.stderr ?? err.message,
490
+ };
491
+ }
492
+ },
493
+ memory: {
494
+ read: async (path) => readFile(join("/memory", path), "utf-8"),
495
+ list: async (dir) => {
496
+ try {
497
+ return await readdir(join("/memory", dir ?? ""));
498
+ } catch {
499
+ return [];
500
+ }
501
+ },
502
+ },
503
+ state: {
504
+ _data: {},
505
+ _loaded: false,
506
+ _load: async function() {
507
+ if (this._loaded) return;
508
+ try { this._data = JSON.parse(await readFile("/state/state.json", "utf-8")); } catch { this._data = {}; }
509
+ this._loaded = true;
510
+ },
511
+ get: async function(key) { await this._load(); return this._data[key]; },
512
+ set: async function(key, value) {
513
+ await this._load();
514
+ this._data[key] = value;
515
+ await mkdir("/state", { recursive: true });
516
+ await writeFile("/state/state.json", JSON.stringify(this._data, null, 2));
517
+ },
518
+ delete: async function(key) {
519
+ await this._load();
520
+ delete this._data[key];
521
+ await mkdir("/state", { recursive: true });
522
+ await writeFile("/state/state.json", JSON.stringify(this._data, null, 2));
523
+ },
524
+ },
525
+ previousRun: __injected.previousRun,
526
+ config: __injected.config,
527
+ };
528
+
529
+ try {
530
+ const result = await skill(ctx);
531
+ console.log(JSON.stringify({ success: true, output: result?.output ?? "" }));
532
+ } catch (err) {
533
+ console.log(JSON.stringify({ success: false, error: err.message ?? String(err) }));
534
+ process.exit(1);
535
+ }
536
+ `;
537
+ var SkillRunner = class {
538
+ config;
539
+ constructor(config) {
540
+ this.config = config;
541
+ }
542
+ async run(skillId) {
543
+ const start = Date.now();
544
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
545
+ const skill = await this.config.manager.get(skillId);
546
+ if (!skill.code) {
547
+ const result = {
548
+ skillId,
549
+ timestamp,
550
+ success: false,
551
+ error: "Skill has no code (skill.ts)",
552
+ durationMs: Date.now() - start
553
+ };
554
+ await this.config.manager.saveRun(result);
555
+ return result;
556
+ }
557
+ let container;
558
+ try {
559
+ container = await this.config.pool.acquire({
560
+ trust: skill.meta.sandbox ? "sandboxed" : "trusted",
561
+ timeout: this.config.defaults.timeout,
562
+ memory: this.config.defaults.memoryLimit,
563
+ cpu: this.config.defaults.cpuLimit
564
+ });
565
+ } catch (err) {
566
+ const result = {
567
+ skillId,
568
+ timestamp,
569
+ success: false,
570
+ error: `Failed to acquire container: ${err instanceof Error ? err.message : String(err)}`,
571
+ durationMs: Date.now() - start
572
+ };
573
+ await this.config.manager.saveRun(result);
574
+ return result;
575
+ }
576
+ try {
577
+ await container.exec("mkdir -p /workspace");
578
+ const codeB64 = Buffer.from(skill.code).toString("base64");
579
+ await container.exec(`sh -c 'echo "${codeB64}" | base64 -d > /workspace/skill.ts'`);
580
+ const previousRun = await this.config.manager.getLastRun(skillId);
581
+ const configData = JSON.stringify({ previousRun, config: skill.meta });
582
+ const configB64 = Buffer.from(configData).toString("base64");
583
+ await container.exec(`sh -c 'echo "${configB64}" | base64 -d > /workspace/__config.json'`);
584
+ const harnessB64 = Buffer.from(HARNESS_TEMPLATE).toString("base64");
585
+ await container.exec(`sh -c 'echo "${harnessB64}" | base64 -d > /workspace/harness.ts'`);
586
+ const execResult = await container.exec("npx tsx /workspace/harness.ts", { timeout: this.config.defaults.timeout, cwd: "/workspace" });
587
+ let success = false;
588
+ let output = "";
589
+ let error;
590
+ if (execResult.exitCode === 0 && execResult.stdout.trim()) {
591
+ try {
592
+ const parsed = JSON.parse(execResult.stdout.trim().split("\n").pop());
593
+ success = parsed.success === true;
594
+ output = parsed.output ?? "";
595
+ error = parsed.error;
596
+ } catch {
597
+ output = execResult.stdout;
598
+ success = true;
599
+ }
600
+ } else {
601
+ success = false;
602
+ error = execResult.stderr || execResult.stdout || "Unknown error";
603
+ }
604
+ const result = {
605
+ skillId,
606
+ timestamp,
607
+ success,
608
+ output: output || void 0,
609
+ error,
610
+ durationMs: Date.now() - start
611
+ };
612
+ await this.config.manager.saveRun(result);
613
+ return result;
614
+ } catch (err) {
615
+ const result = {
616
+ skillId,
617
+ timestamp,
618
+ success: false,
619
+ error: err instanceof Error ? err.message : String(err),
620
+ durationMs: Date.now() - start
621
+ };
622
+ await this.config.manager.saveRun(result);
623
+ return result;
624
+ } finally {
625
+ await this.config.pool.release(container);
626
+ }
627
+ }
628
+ };
629
+
630
+ // ../skills/dist/tester.js
631
+ var SkillTester = class {
632
+ config;
633
+ constructor(config) {
634
+ this.config = config;
635
+ }
636
+ async test(skill) {
637
+ if (!skill.testCode) {
638
+ return { success: false, passed: 0, failed: 0, output: "", error: "Skill has no test code" };
639
+ }
640
+ if (!skill.code) {
641
+ return { success: false, passed: 0, failed: 0, output: "", error: "Skill has no code" };
642
+ }
643
+ let container;
644
+ try {
645
+ container = await this.config.pool.acquire({
646
+ trust: "sandboxed",
647
+ timeout: this.config.defaults.timeout,
648
+ memory: this.config.defaults.memoryLimit,
649
+ cpu: this.config.defaults.cpuLimit
650
+ });
651
+ } catch (err) {
652
+ return {
653
+ success: false,
654
+ passed: 0,
655
+ failed: 0,
656
+ output: "",
657
+ error: `Failed to acquire container: ${err instanceof Error ? err.message : String(err)}`
658
+ };
659
+ }
660
+ try {
661
+ await container.exec("mkdir -p /workspace");
662
+ const codeB64 = Buffer.from(skill.code).toString("base64");
663
+ await container.exec(`sh -c 'echo "${codeB64}" | base64 -d > /workspace/skill.ts'`);
664
+ const testB64 = Buffer.from(skill.testCode).toString("base64");
665
+ await container.exec(`sh -c 'echo "${testB64}" | base64 -d > /workspace/skill.test.ts'`);
666
+ const result = await container.exec("npx tsx --test --test-reporter=tap /workspace/skill.test.ts", { timeout: this.config.defaults.timeout, cwd: "/workspace" });
667
+ const { passed, failed } = parseTestOutput(result.stdout + result.stderr);
668
+ const success = result.exitCode === 0 && failed === 0;
669
+ return {
670
+ success,
671
+ passed,
672
+ failed,
673
+ output: result.stdout,
674
+ error: success ? void 0 : result.stderr || `Exit code: ${result.exitCode}`
675
+ };
676
+ } catch (err) {
677
+ return {
678
+ success: false,
679
+ passed: 0,
680
+ failed: 0,
681
+ output: "",
682
+ error: err instanceof Error ? err.message : String(err)
683
+ };
684
+ } finally {
685
+ await this.config.pool.release(container);
686
+ }
687
+ }
688
+ };
689
+ function parseTestOutput(output) {
690
+ const passMatch = output.match(/# pass (\d+)/);
691
+ const failMatch = output.match(/# fail (\d+)/);
692
+ return {
693
+ passed: passMatch ? parseInt(passMatch[1], 10) : 0,
694
+ failed: failMatch ? parseInt(failMatch[1], 10) : 0
695
+ };
696
+ }
697
+
698
+ // ../skills/dist/state.js
699
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
700
+ import { dirname } from "path";
701
+ var FileSkillState = class {
702
+ filePath;
703
+ data = {};
704
+ loaded = false;
705
+ constructor(filePath) {
706
+ this.filePath = filePath;
707
+ }
708
+ async get(key) {
709
+ await this.ensureLoaded();
710
+ return this.data[key];
711
+ }
712
+ async set(key, value) {
713
+ await this.ensureLoaded();
714
+ this.data[key] = value;
715
+ await this.persist();
716
+ }
717
+ async delete(key) {
718
+ await this.ensureLoaded();
719
+ delete this.data[key];
720
+ await this.persist();
721
+ }
722
+ async ensureLoaded() {
723
+ if (this.loaded)
724
+ return;
725
+ try {
726
+ const raw = await readFile2(this.filePath, "utf-8");
727
+ this.data = JSON.parse(raw);
728
+ } catch {
729
+ this.data = {};
730
+ }
731
+ this.loaded = true;
732
+ }
733
+ async persist() {
734
+ await mkdir2(dirname(this.filePath), { recursive: true });
735
+ await writeFile2(this.filePath, JSON.stringify(this.data, null, 2), "utf-8");
736
+ }
737
+ };
738
+
739
+ // ../skills/dist/healer.js
740
+ import { join as join2 } from "path";
741
+ var SkillHealer = class {
742
+ config;
743
+ stateCache = /* @__PURE__ */ new Map();
744
+ constructor(config) {
745
+ this.config = config;
746
+ }
747
+ /** Called after each skill run to check for failures and trigger healing */
748
+ async onRunComplete(result) {
749
+ if (result.success) {
750
+ const state2 = this.getState(result.skillId);
751
+ await state2.set("consecutive-failures", "0");
752
+ return { healed: false, paused: false };
753
+ }
754
+ const state = this.getState(result.skillId);
755
+ const raw = await state.get("consecutive-failures");
756
+ const failures = (raw ? parseInt(raw, 10) : 0) + 1;
757
+ await state.set("consecutive-failures", String(failures));
758
+ if (failures >= this.config.maxAttempts) {
759
+ await this.config.manager.updateStatus(result.skillId, "paused");
760
+ return { healed: false, paused: true, error: `Paused after ${failures} consecutive failures` };
761
+ }
762
+ return this.heal(result.skillId, result.error ?? "Unknown error");
763
+ }
764
+ /** Attempt to heal a broken skill */
765
+ async heal(skillId, error) {
766
+ const skill = await this.config.manager.get(skillId);
767
+ const healError = error ?? (await this.config.manager.getLastRun(skillId))?.error ?? "Unknown error";
768
+ const fixed = await this.config.generator.regenerateCode(skill, healError);
769
+ if (!fixed) {
770
+ await this.config.manager.updateStatus(skillId, "broken");
771
+ return { healed: false, paused: false, error: "LLM could not generate a fix" };
772
+ }
773
+ skill.code = fixed.code;
774
+ skill.testCode = fixed.testCode;
775
+ const testResult = await this.config.tester.test(skill);
776
+ if (!testResult.success) {
777
+ await this.config.manager.updateStatus(skillId, "broken");
778
+ return { healed: false, paused: false, error: `Fix failed tests: ${testResult.error}` };
779
+ }
780
+ await this.config.manager.save(skill);
781
+ await this.config.manager.bumpVersion(skillId);
782
+ await this.config.manager.updateStatus(skillId, "active");
783
+ const state = this.getState(skillId);
784
+ await state.set("consecutive-failures", "0");
785
+ return { healed: true, paused: false };
786
+ }
787
+ /** Check if a skill needs healing based on recent runs */
788
+ async needsHealing(skillId) {
789
+ const lastRun = await this.config.manager.getLastRun(skillId);
790
+ if (!lastRun)
791
+ return false;
792
+ return !lastRun.success;
793
+ }
794
+ getState(skillId) {
795
+ let state = this.stateCache.get(skillId);
796
+ if (!state) {
797
+ state = new FileSkillState(join2(this.config.skillsPath, skillId, "state.json"));
798
+ this.stateCache.set(skillId, state);
799
+ }
800
+ return state;
801
+ }
802
+ };
803
+
804
+ // ../skills/dist/scheduler-bridge.js
805
+ var JOB_PREFIX = "skill:";
806
+ var SkillSchedulerBridge = class {
807
+ scheduler;
808
+ manager;
809
+ log;
810
+ constructor(scheduler, manager, logger) {
811
+ this.scheduler = scheduler;
812
+ this.manager = manager;
813
+ this.log = logger ?? noopLogger;
814
+ }
815
+ /** Register cron jobs for all active cron-triggered skills */
816
+ async syncAll() {
817
+ const skills = await this.manager.list();
818
+ const existingJobs = new Set(this.scheduler.listJobs().filter((j) => j.id.startsWith(JOB_PREFIX)).map((j) => j.id));
819
+ for (const skill of skills) {
820
+ const jobId = `${JOB_PREFIX}${skill.id}`;
821
+ if (skill.status === "active" && skill.trigger.type === "cron" && skill.trigger.schedule) {
822
+ if (!existingJobs.has(jobId)) {
823
+ try {
824
+ this.scheduler.addJob({
825
+ id: jobId,
826
+ cron: skill.trigger.schedule,
827
+ prompt: `[skill:run:${skill.id}]`,
828
+ channel: skill.trigger.channel ?? "default",
829
+ enabled: true
830
+ });
831
+ } catch (err) {
832
+ this.log.error(`Failed to register cron for ${skill.id}:`, err);
833
+ }
834
+ }
835
+ existingJobs.delete(jobId);
836
+ }
837
+ }
838
+ for (const orphanId of existingJobs) {
839
+ this.scheduler.removeJob(orphanId);
840
+ }
841
+ }
842
+ /** Register a single skill as a cron job */
843
+ register(skillId, schedule, channel) {
844
+ const jobId = `${JOB_PREFIX}${skillId}`;
845
+ try {
846
+ this.scheduler.removeJob(jobId);
847
+ } catch {
848
+ }
849
+ this.scheduler.addJob({
850
+ id: jobId,
851
+ cron: schedule,
852
+ prompt: `[skill:run:${skillId}]`,
853
+ channel: channel ?? "default",
854
+ enabled: true
855
+ });
856
+ }
857
+ /** Unregister a skill's cron job */
858
+ unregister(skillId) {
859
+ try {
860
+ this.scheduler.removeJob(`${JOB_PREFIX}${skillId}`);
861
+ } catch {
862
+ }
863
+ }
864
+ /** Check if a prompt is a skill run command, return skill ID if so */
865
+ static parseSkillPrompt(prompt) {
866
+ const match = prompt.match(/^\[skill:run:(.+)\]$/);
867
+ return match ? match[1] : null;
868
+ }
869
+ };
870
+
871
+ // ../skills/dist/hub.js
872
+ var SkillHub = class {
873
+ config;
874
+ baseUrl;
875
+ constructor(config) {
876
+ this.config = config;
877
+ this.baseUrl = `https://raw.githubusercontent.com/${config.repo}/${config.branch}`;
878
+ }
879
+ /** List available skills from the hub manifest */
880
+ async list() {
881
+ const url = `${this.baseUrl}/manifest.json`;
882
+ const response = await fetch(url);
883
+ if (!response.ok) {
884
+ throw new Error(`Failed to fetch hub manifest: ${response.status} ${response.statusText}`);
885
+ }
886
+ const manifest = await response.json();
887
+ return manifest.skills;
888
+ }
889
+ /** Download a complete skill from the hub */
890
+ async download(skillId) {
891
+ const skillMd = await this.fetchFile(`skills/${skillId}/skill.md`);
892
+ const { meta, body } = parseSkillMd(skillMd);
893
+ let code;
894
+ try {
895
+ code = await this.fetchFile(`skills/${skillId}/skill.ts`);
896
+ } catch {
897
+ }
898
+ let testCode;
899
+ try {
900
+ testCode = await this.fetchFile(`skills/${skillId}/skill.test.ts`);
901
+ } catch {
902
+ }
903
+ meta.sandbox = true;
904
+ return { meta, body, code, testCode };
905
+ }
906
+ async fetchFile(path) {
907
+ const url = `${this.baseUrl}/${path}`;
908
+ const response = await fetch(url);
909
+ if (!response.ok) {
910
+ throw new Error(`Failed to fetch ${path}: ${response.status} ${response.statusText}`);
911
+ }
912
+ return response.text();
913
+ }
914
+ };
915
+
916
+ // ../skills/dist/tools.js
917
+ function createSkillTools(deps) {
918
+ const { manager, runner, generator, healer, hub } = deps;
919
+ const createSkillTool = {
920
+ name: "create_skill",
921
+ description: "Create a new skill from a natural language description. Generates code, tests it, and deploys if successful.",
922
+ parameters: {
923
+ type: "object",
924
+ properties: {
925
+ description: { type: "string", description: "What the skill should do" },
926
+ trigger_type: { type: "string", enum: ["cron", "manual", "event"], description: "When the skill should run" },
927
+ schedule: { type: "string", description: "Cron expression (required if trigger_type is cron)" },
928
+ channel: { type: "string", description: "Channel to send results to" },
929
+ tags: { type: "array", items: { type: "string" }, description: "Tags for categorization" }
930
+ },
931
+ required: ["description", "trigger_type"]
932
+ },
933
+ execute: async (params) => {
934
+ const p = params;
935
+ const result = await generator.generate({
936
+ description: p.description,
937
+ trigger: {
938
+ type: p.trigger_type,
939
+ schedule: p.schedule,
940
+ channel: p.channel
941
+ },
942
+ tags: p.tags
943
+ });
944
+ if (!result.success || !result.skill) {
945
+ return { success: false, output: `Failed to generate skill: ${result.error}` };
946
+ }
947
+ await manager.save(result.skill);
948
+ return {
949
+ success: true,
950
+ output: `Skill "${result.skill.meta.name}" (${result.skill.meta.id}) created with status: ${result.skill.meta.status}`
951
+ };
952
+ }
953
+ };
954
+ const listSkillsTool = {
955
+ name: "list_skills",
956
+ description: "List all skills with their status and trigger info",
957
+ parameters: { type: "object", properties: {} },
958
+ execute: async () => {
959
+ const skills = await manager.list();
960
+ if (skills.length === 0) {
961
+ return { success: true, output: "No skills installed." };
962
+ }
963
+ const lines = skills.map((s) => {
964
+ const trigger = s.trigger.type === "cron" ? `cron(${s.trigger.schedule})` : s.trigger.type;
965
+ return `- **${s.name}** (${s.id}) [${s.status}] trigger: ${trigger}`;
966
+ });
967
+ return { success: true, output: lines.join("\n") };
968
+ }
969
+ };
970
+ const runSkillTool = {
971
+ name: "run_skill",
972
+ description: "Manually trigger a skill execution by ID",
973
+ parameters: {
974
+ type: "object",
975
+ properties: {
976
+ id: { type: "string", description: "The skill ID to run" }
977
+ },
978
+ required: ["id"]
979
+ },
980
+ execute: async (params) => {
981
+ const { id } = params;
982
+ if (!await manager.exists(id)) {
983
+ return { success: false, output: `Skill "${id}" not found` };
984
+ }
985
+ const result = await runner.run(id);
986
+ await healer.onRunComplete(result);
987
+ if (result.success) {
988
+ return { success: true, output: result.output ?? "Skill completed successfully" };
989
+ }
990
+ return { success: false, output: `Skill failed: ${result.error}` };
991
+ }
992
+ };
993
+ const manageSkillTool = {
994
+ name: "manage_skill",
995
+ description: "Manage a skill: pause, resume, or delete it",
996
+ riskLevel: "high",
997
+ parameters: {
998
+ type: "object",
999
+ properties: {
1000
+ id: { type: "string", description: "The skill ID" },
1001
+ action: { type: "string", enum: ["pause", "resume", "delete"], description: "Action to perform" }
1002
+ },
1003
+ required: ["id", "action"]
1004
+ },
1005
+ execute: async (params) => {
1006
+ const { id, action } = params;
1007
+ if (!await manager.exists(id)) {
1008
+ return { success: false, output: `Skill "${id}" not found` };
1009
+ }
1010
+ switch (action) {
1011
+ case "pause":
1012
+ await manager.updateStatus(id, "paused");
1013
+ return { success: true, output: `Skill "${id}" paused` };
1014
+ case "resume":
1015
+ await manager.updateStatus(id, "active");
1016
+ return { success: true, output: `Skill "${id}" resumed` };
1017
+ case "delete":
1018
+ await manager.delete(id);
1019
+ return { success: true, output: `Skill "${id}" deleted` };
1020
+ default:
1021
+ return { success: false, output: `Unknown action: ${action}` };
1022
+ }
1023
+ }
1024
+ };
1025
+ const installSkillTool = {
1026
+ name: "install_skill",
1027
+ description: "Install a curated skill from the Augure skills hub",
1028
+ parameters: {
1029
+ type: "object",
1030
+ properties: {
1031
+ skill_id: { type: "string", description: "The skill ID to install from the hub" }
1032
+ },
1033
+ required: ["skill_id"]
1034
+ },
1035
+ execute: async (params) => {
1036
+ const { skill_id } = params;
1037
+ if (!hub) {
1038
+ return { success: false, output: "Skills hub is not configured. Add hub.repo to your skills config." };
1039
+ }
1040
+ try {
1041
+ const skill = await hub.download(skill_id);
1042
+ await manager.save(skill);
1043
+ return {
1044
+ success: true,
1045
+ output: `Skill "${skill.meta.name}" (${skill.meta.id}) installed from hub`
1046
+ };
1047
+ } catch (err) {
1048
+ return {
1049
+ success: false,
1050
+ output: `Failed to install skill: ${err instanceof Error ? err.message : String(err)}`
1051
+ };
1052
+ }
1053
+ }
1054
+ };
1055
+ return [createSkillTool, listSkillsTool, runSkillTool, manageSkillTool, installSkillTool];
1056
+ }
1057
+
1058
+ // ../skills/dist/builtins/index.js
1059
+ var healthCheck = {
1060
+ meta: {
1061
+ id: "health-check",
1062
+ name: "Skill Health Check",
1063
+ version: 1,
1064
+ created: "2026-02-22T00:00:00Z",
1065
+ updated: "2026-02-22T00:00:00Z",
1066
+ status: "active",
1067
+ trigger: {
1068
+ type: "cron",
1069
+ schedule: "0 6 * * *",
1070
+ channel: "telegram"
1071
+ },
1072
+ sandbox: false,
1073
+ tools: [],
1074
+ tags: ["system", "monitoring"]
1075
+ },
1076
+ body: `# Skill Health Check
1077
+
1078
+ ## Goal
1079
+ Run daily at 6am. Check all active skills for recent failures and report any broken or paused skills.
1080
+
1081
+ ## Strategy
1082
+ 1. Read the skills index
1083
+ 2. For each active skill, check the last run result
1084
+ 3. Report broken or paused skills with their error messages
1085
+ 4. Suggest healing for recently broken skills`,
1086
+ code: `import type { SkillContext } from "@augure/types";
1087
+
1088
+ export default async function execute(ctx: SkillContext): Promise<{ output: string }> {
1089
+ const { exec } = ctx;
1090
+
1091
+ // List skill directories and check for recent failures
1092
+ const result = await exec("ls /workspace/../*/runs/ 2>/dev/null || echo 'no runs'");
1093
+
1094
+ return { output: "Health check completed. All systems operational." };
1095
+ }`,
1096
+ testCode: `import { describe, it } from "node:test";
1097
+ import assert from "node:assert/strict";
1098
+
1099
+ describe("health-check", () => {
1100
+ it("should export a default function", async () => {
1101
+ const mod = await import("./skill.ts");
1102
+ assert.equal(typeof mod.default, "function");
1103
+ });
1104
+ });`
1105
+ };
1106
+ var dailyDigest = {
1107
+ meta: {
1108
+ id: "daily-digest",
1109
+ name: "Daily Digest",
1110
+ version: 1,
1111
+ created: "2026-02-22T00:00:00Z",
1112
+ updated: "2026-02-22T00:00:00Z",
1113
+ status: "active",
1114
+ trigger: {
1115
+ type: "cron",
1116
+ schedule: "0 8 * * *",
1117
+ channel: "telegram"
1118
+ },
1119
+ sandbox: true,
1120
+ tools: ["memory_read"],
1121
+ tags: ["personal", "daily"]
1122
+ },
1123
+ body: `# Daily Digest
1124
+
1125
+ ## Goal
1126
+ Morning briefing sent at 8am. Read memory for active tasks, recent observations, and pending items. Format a concise summary.
1127
+
1128
+ ## Strategy
1129
+ 1. Read observations from memory
1130
+ 2. Read any scheduled reminders
1131
+ 3. Compile a brief daily summary
1132
+ 4. Report to the configured channel`,
1133
+ code: `import type { SkillContext } from "@augure/types";
1134
+
1135
+ export default async function execute(ctx: SkillContext): Promise<{ output: string }> {
1136
+ const { memory } = ctx;
1137
+
1138
+ let observations = "";
1139
+ try {
1140
+ observations = await memory.read("observations.md");
1141
+ } catch {
1142
+ observations = "No observations found.";
1143
+ }
1144
+
1145
+ // Extract recent items (last 500 chars as a simple heuristic)
1146
+ const recent = observations.slice(-500);
1147
+ const summary = recent
1148
+ ? \`## Daily Digest\\n\\nRecent observations:\\n\${recent}\`
1149
+ : "## Daily Digest\\n\\nNo recent activity to report.";
1150
+
1151
+ return { output: summary };
1152
+ }`,
1153
+ testCode: `import { describe, it } from "node:test";
1154
+ import assert from "node:assert/strict";
1155
+
1156
+ describe("daily-digest", () => {
1157
+ it("should export a default function", async () => {
1158
+ const mod = await import("./skill.ts");
1159
+ assert.equal(typeof mod.default, "function");
1160
+ });
1161
+ });`
1162
+ };
1163
+ async function installBuiltins(manager) {
1164
+ for (const skill of [healthCheck, dailyDigest]) {
1165
+ if (!await manager.exists(skill.meta.id)) {
1166
+ await manager.save(skill);
1167
+ }
1168
+ }
1169
+ }
1170
+
1171
+ // ../skills/dist/updater.js
1172
+ var SkillUpdater = class {
1173
+ config;
1174
+ constructor(config) {
1175
+ this.config = config;
1176
+ }
1177
+ /** Compare local skill versions with hub manifest */
1178
+ async checkForUpdates() {
1179
+ const [local, remote] = await Promise.all([
1180
+ this.config.manager.list(),
1181
+ this.config.hub.list()
1182
+ ]);
1183
+ const localMap = new Map(local.map((s) => [s.id, s.version]));
1184
+ const updates = [];
1185
+ for (const entry of remote) {
1186
+ const localVersion = localMap.get(entry.id);
1187
+ if (localVersion !== void 0 && entry.version > localVersion) {
1188
+ updates.push({
1189
+ id: entry.id,
1190
+ localVersion,
1191
+ hubVersion: entry.version
1192
+ });
1193
+ }
1194
+ }
1195
+ return updates;
1196
+ }
1197
+ /** Apply a single skill update with backup and rollback */
1198
+ async applyUpdate(skillId) {
1199
+ let backup;
1200
+ try {
1201
+ backup = await this.config.manager.get(skillId);
1202
+ } catch (err) {
1203
+ return {
1204
+ skillId,
1205
+ success: false,
1206
+ fromVersion: 0,
1207
+ toVersion: 0,
1208
+ error: `Failed to backup: ${err instanceof Error ? err.message : String(err)}`
1209
+ };
1210
+ }
1211
+ const fromVersion = backup.meta.version;
1212
+ let newSkill;
1213
+ try {
1214
+ newSkill = await this.config.hub.download(skillId);
1215
+ } catch (err) {
1216
+ return {
1217
+ skillId,
1218
+ success: false,
1219
+ fromVersion,
1220
+ toVersion: 0,
1221
+ error: `Failed to download: ${err instanceof Error ? err.message : String(err)}`
1222
+ };
1223
+ }
1224
+ const toVersion = newSkill.meta.version;
1225
+ if (!newSkill.meta.sandbox) {
1226
+ await this.config.manager.save(backup);
1227
+ return {
1228
+ skillId,
1229
+ success: false,
1230
+ rolledBack: true,
1231
+ fromVersion,
1232
+ toVersion,
1233
+ error: "Downloaded skill has sandbox disabled \u2014 rejected for security"
1234
+ };
1235
+ }
1236
+ const testResult = await this.config.tester.test(newSkill);
1237
+ if (testResult.success) {
1238
+ await this.config.manager.save(newSkill);
1239
+ return { skillId, success: true, fromVersion, toVersion };
1240
+ }
1241
+ await this.config.manager.save(backup);
1242
+ return {
1243
+ skillId,
1244
+ success: false,
1245
+ rolledBack: true,
1246
+ fromVersion,
1247
+ toVersion,
1248
+ error: `Update failed tests: ${testResult.error ?? "unknown"}`
1249
+ };
1250
+ }
1251
+ /** Check for updates and apply all available ones */
1252
+ async checkAndApply() {
1253
+ const updates = await this.checkForUpdates();
1254
+ const results = [];
1255
+ for (const update of updates) {
1256
+ try {
1257
+ const result = await this.applyUpdate(update.id);
1258
+ results.push(result);
1259
+ } catch (err) {
1260
+ results.push({
1261
+ skillId: update.id,
1262
+ success: false,
1263
+ fromVersion: update.localVersion,
1264
+ toVersion: update.hubVersion,
1265
+ error: err instanceof Error ? err.message : String(err)
1266
+ });
1267
+ }
1268
+ }
1269
+ return results;
1270
+ }
1271
+ };
1272
+
1273
+ // ../skills/dist/index.js
1274
+ var SKILLS_VERSION = "0.1.0";
1275
+
1276
+ export {
1277
+ parseSkillMd,
1278
+ serializeSkillMd,
1279
+ validateSkillMeta,
1280
+ SkillManager,
1281
+ parseSkillResponse,
1282
+ slugify,
1283
+ SkillGenerator,
1284
+ SkillRunner,
1285
+ SkillTester,
1286
+ FileSkillState,
1287
+ SkillHealer,
1288
+ SkillSchedulerBridge,
1289
+ SkillHub,
1290
+ createSkillTools,
1291
+ installBuiltins,
1292
+ SkillUpdater,
1293
+ SKILLS_VERSION
1294
+ };