augure 0.2.0 → 0.3.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/bin.js CHANGED
@@ -1,22 +1,3459 @@
1
1
  #!/usr/bin/env node
2
- import { createRequire } from "node:module";
3
- import { defineCommand, runMain } from "citty";
4
- import { startCommand } from "./commands/start.js";
5
- import { initCommand } from "./commands/init.js";
6
- import { skillsCommand } from "./commands/skills.js";
7
- const require = createRequire(import.meta.url);
8
- const { version } = require("../package.json");
9
- const main = defineCommand({
10
- meta: {
11
- name: "augure",
12
- description: "Augure your proactive AI agent",
13
- version,
14
- },
15
- subCommands: {
16
- start: startCommand,
17
- init: initCommand,
18
- skills: skillsCommand,
2
+
3
+ // src/bin.ts
4
+ import { createRequire } from "module";
5
+ import { defineCommand as defineCommand4, runMain } from "citty";
6
+
7
+ // src/commands/start.ts
8
+ import { defineCommand } from "citty";
9
+ import { resolve as resolve2 } from "path";
10
+
11
+ // ../core/dist/config.js
12
+ import { readFile } from "fs/promises";
13
+ import JSON5 from "json5";
14
+ import { z } from "zod";
15
+ var LLMModelConfigSchema = z.object({
16
+ provider: z.enum(["openrouter", "anthropic", "openai"]),
17
+ apiKey: z.string().min(1),
18
+ model: z.string().min(1),
19
+ maxTokens: z.number().int().positive()
20
+ });
21
+ var LLMConfigSchema = z.object({
22
+ default: LLMModelConfigSchema,
23
+ reasoning: LLMModelConfigSchema.partial().optional(),
24
+ ingestion: LLMModelConfigSchema.partial().optional(),
25
+ monitoring: LLMModelConfigSchema.partial().optional(),
26
+ coding: LLMModelConfigSchema.partial().optional()
27
+ });
28
+ var AppConfigSchema = z.object({
29
+ identity: z.object({
30
+ name: z.string().min(1),
31
+ personality: z.string().min(1)
32
+ }),
33
+ llm: LLMConfigSchema,
34
+ channels: z.object({
35
+ telegram: z.object({
36
+ enabled: z.boolean(),
37
+ botToken: z.string(),
38
+ allowedUsers: z.array(z.number())
39
+ }).optional(),
40
+ whatsapp: z.object({ enabled: z.boolean() }).optional(),
41
+ web: z.object({ enabled: z.boolean(), port: z.number() }).optional()
42
+ }),
43
+ memory: z.object({
44
+ path: z.string().min(1),
45
+ autoIngest: z.boolean(),
46
+ maxRetrievalTokens: z.number().int().positive()
47
+ }),
48
+ scheduler: z.object({
49
+ heartbeatInterval: z.string().min(1),
50
+ jobs: z.array(z.object({
51
+ id: z.string().min(1),
52
+ cron: z.string().min(1),
53
+ prompt: z.string().min(1),
54
+ channel: z.string().min(1)
55
+ }))
56
+ }),
57
+ sandbox: z.object({
58
+ runtime: z.literal("docker"),
59
+ image: z.string().min(1).optional(),
60
+ defaults: z.object({
61
+ timeout: z.number().int().positive(),
62
+ memoryLimit: z.string().min(1),
63
+ cpuLimit: z.string().min(1)
64
+ }),
65
+ codeAgent: z.object({
66
+ command: z.string().min(1),
67
+ args: z.array(z.string()).optional(),
68
+ env: z.record(z.string(), z.string()).optional()
69
+ }).optional()
70
+ }),
71
+ tools: z.object({
72
+ webSearch: z.object({
73
+ provider: z.enum(["tavily", "exa", "searxng"]),
74
+ apiKey: z.string().optional(),
75
+ baseUrl: z.string().optional(),
76
+ maxResults: z.number().int().positive().optional()
77
+ }).optional(),
78
+ http: z.object({
79
+ defaultHeaders: z.record(z.string(), z.string()).optional(),
80
+ presets: z.record(z.string(), z.object({
81
+ baseUrl: z.string(),
82
+ headers: z.record(z.string(), z.string())
83
+ })).optional(),
84
+ timeoutMs: z.number().int().positive().optional(),
85
+ maxResponseBytes: z.number().int().positive().optional()
86
+ }).optional(),
87
+ email: z.object({
88
+ imap: z.object({
89
+ host: z.string(),
90
+ port: z.number(),
91
+ user: z.string(),
92
+ password: z.string()
93
+ }),
94
+ smtp: z.object({
95
+ host: z.string(),
96
+ port: z.number(),
97
+ user: z.string(),
98
+ password: z.string()
99
+ })
100
+ }).optional(),
101
+ github: z.object({ token: z.string() }).optional()
102
+ }),
103
+ security: z.object({
104
+ sandboxOnly: z.boolean(),
105
+ allowedHosts: z.array(z.string()),
106
+ maxConcurrentSandboxes: z.number().int().positive()
107
+ }),
108
+ skills: z.object({
109
+ path: z.string().min(1).default("./skills"),
110
+ maxFailures: z.number().int().positive().default(3),
111
+ autoSuggest: z.boolean().default(true),
112
+ hub: z.object({
113
+ repo: z.string().min(1),
114
+ branch: z.string().min(1).default("main")
115
+ }).optional()
116
+ }).optional(),
117
+ audit: z.object({
118
+ path: z.string().min(1).default("./logs"),
119
+ enabled: z.boolean().default(true)
120
+ }).optional(),
121
+ persona: z.object({
122
+ path: z.string().min(1).default("./config/personas")
123
+ }).optional()
124
+ });
125
+ function interpolateEnvVars(raw) {
126
+ return raw.replace(/\$\{([^}]+)\}/g, (match, varName) => {
127
+ const value = process.env[varName];
128
+ if (value === void 0) {
129
+ throw new Error(`Missing environment variable: ${varName}`);
130
+ }
131
+ return value;
132
+ });
133
+ }
134
+ async function loadConfig(path) {
135
+ const raw = await readFile(path, "utf-8");
136
+ const interpolated = interpolateEnvVars(raw);
137
+ const parsed = JSON5.parse(interpolated);
138
+ return AppConfigSchema.parse(parsed);
139
+ }
140
+
141
+ // ../core/dist/llm.js
142
+ var OpenRouterClient = class {
143
+ apiKey;
144
+ model;
145
+ maxTokens;
146
+ baseUrl;
147
+ constructor(config) {
148
+ this.apiKey = config.apiKey;
149
+ this.model = config.model;
150
+ this.maxTokens = config.maxTokens;
151
+ this.baseUrl = config.baseUrl ?? "https://openrouter.ai/api/v1";
152
+ }
153
+ async chat(messages) {
154
+ const response = await fetch(`${this.baseUrl}/chat/completions`, {
155
+ method: "POST",
156
+ headers: {
157
+ "Content-Type": "application/json",
158
+ Authorization: `Bearer ${this.apiKey}`
159
+ },
160
+ body: JSON.stringify({
161
+ model: this.model,
162
+ max_tokens: this.maxTokens,
163
+ messages: messages.map((m) => ({
164
+ role: m.role,
165
+ content: m.content,
166
+ ...m.toolCallId ? { tool_call_id: m.toolCallId } : {}
167
+ }))
168
+ })
169
+ });
170
+ if (!response.ok) {
171
+ const body = await response.text();
172
+ throw new Error(`OpenRouter API error ${response.status}: ${body}`);
173
+ }
174
+ const data = await response.json();
175
+ const choice = data.choices[0];
176
+ return {
177
+ content: choice.message.content ?? "",
178
+ toolCalls: (choice.message.tool_calls ?? []).map((tc) => ({
179
+ id: tc.id,
180
+ name: tc.function.name,
181
+ arguments: JSON.parse(tc.function.arguments)
182
+ })),
183
+ usage: {
184
+ inputTokens: data.usage.prompt_tokens,
185
+ outputTokens: data.usage.completion_tokens
186
+ }
187
+ };
188
+ }
189
+ };
190
+
191
+ // ../core/dist/context.js
192
+ function assembleContext(input) {
193
+ const { systemPrompt, memoryContent, toolSchemas, conversationHistory, persona } = input;
194
+ let system = systemPrompt;
195
+ if (persona) {
196
+ system += `
197
+
198
+ ## Active Persona
199
+ ${persona}`;
200
+ }
201
+ if (memoryContent) {
202
+ system += `
203
+
204
+ ## Memory
205
+ ${memoryContent}`;
206
+ }
207
+ if (toolSchemas.length > 0) {
208
+ const toolList = toolSchemas.map((s) => `- **${s.function.name}**: ${s.function.description}`).join("\n");
209
+ system += `
210
+
211
+ ## Available Tools
212
+ ${toolList}`;
213
+ }
214
+ const messages = [{ role: "system", content: system }];
215
+ messages.push(...conversationHistory);
216
+ return messages;
217
+ }
218
+
219
+ // ../core/dist/audit.js
220
+ import { appendFile, mkdir } from "fs/promises";
221
+ import { join } from "path";
222
+ function summarize(text, maxLen = 200) {
223
+ if (text.length <= maxLen)
224
+ return text;
225
+ return text.slice(0, maxLen) + "...";
226
+ }
227
+ var FileAuditLogger = class {
228
+ basePath;
229
+ pendingWrite = Promise.resolve();
230
+ initialized = false;
231
+ constructor(basePath) {
232
+ this.basePath = basePath;
233
+ }
234
+ log(entry) {
235
+ this.pendingWrite = this.pendingWrite.then(() => this.writeEntry(entry)).catch((err2) => console.error("[augure] Audit write error:", err2));
236
+ }
237
+ async close() {
238
+ await this.pendingWrite;
239
+ }
240
+ async writeEntry(entry) {
241
+ const dir = join(this.basePath, "actions");
242
+ if (!this.initialized) {
243
+ await mkdir(dir, { recursive: true });
244
+ this.initialized = true;
245
+ }
246
+ const date = entry.ts.slice(0, 10);
247
+ const filePath = join(dir, `${date}.jsonl`);
248
+ await appendFile(filePath, JSON.stringify(entry) + "\n");
249
+ }
250
+ };
251
+ var NullAuditLogger = class {
252
+ log(_entry) {
253
+ }
254
+ async close() {
255
+ }
256
+ };
257
+
258
+ // ../core/dist/agent.js
259
+ var Agent = class {
260
+ config;
261
+ conversationHistory = [];
262
+ state = "running";
263
+ constructor(config) {
264
+ this.config = config;
265
+ }
266
+ getState() {
267
+ return this.state;
268
+ }
269
+ setState(s) {
270
+ this.state = s;
271
+ }
272
+ setPersona(text) {
273
+ this.config.persona = text;
274
+ }
275
+ async handleMessage(incoming) {
276
+ if (this.state === "killed") {
277
+ return "Agent is in emergency stop mode. Send /resume to reactivate.";
278
+ }
279
+ const start = Date.now();
280
+ this.conversationHistory.push({
281
+ role: "user",
282
+ content: incoming.text
283
+ });
284
+ if (this.config.guard) {
285
+ this.conversationHistory = this.config.guard.compact(this.conversationHistory);
286
+ }
287
+ let memoryContent = this.config.memoryContent;
288
+ if (this.config.retriever) {
289
+ memoryContent = await this.config.retriever.retrieve();
290
+ }
291
+ const maxLoops = this.config.maxToolLoops ?? 10;
292
+ let loopCount = 0;
293
+ while (loopCount < maxLoops) {
294
+ const messages = assembleContext({
295
+ systemPrompt: this.config.systemPrompt,
296
+ memoryContent,
297
+ toolSchemas: this.config.tools.toFunctionSchemas(),
298
+ conversationHistory: this.conversationHistory,
299
+ persona: this.config.persona
300
+ });
301
+ const response = await this.config.llm.chat(messages);
302
+ if (response.toolCalls.length === 0) {
303
+ this.conversationHistory.push({
304
+ role: "assistant",
305
+ content: response.content
306
+ });
307
+ if (this.config.audit) {
308
+ this.config.audit.log({
309
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
310
+ trigger: incoming.channelType === "system" ? "heartbeat" : "user",
311
+ action: "chat",
312
+ inputSummary: summarize(incoming.text),
313
+ outputSummary: summarize(response.content),
314
+ tokens: {
315
+ input: response.usage.inputTokens,
316
+ output: response.usage.outputTokens,
317
+ model: this.config.modelName ?? ""
318
+ },
319
+ durationMs: Date.now() - start,
320
+ success: true
321
+ });
322
+ }
323
+ if (this.config.ingester) {
324
+ this.config.ingester.ingest(this.conversationHistory).catch((err2) => console.error("[augure] Ingestion error:", err2));
325
+ }
326
+ return response.content;
327
+ }
328
+ this.conversationHistory.push({
329
+ role: "assistant",
330
+ content: response.content || ""
331
+ });
332
+ for (const toolCall of response.toolCalls) {
333
+ const toolStart = Date.now();
334
+ const result = await this.config.tools.execute(toolCall.name, toolCall.arguments);
335
+ this.conversationHistory.push({
336
+ role: "tool",
337
+ content: result.output,
338
+ toolCallId: toolCall.id
339
+ });
340
+ if (this.config.audit) {
341
+ this.config.audit.log({
342
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
343
+ trigger: incoming.channelType === "system" ? "heartbeat" : "user",
344
+ action: toolCall.name,
345
+ inputSummary: summarize(JSON.stringify(toolCall.arguments)),
346
+ outputSummary: summarize(result.output),
347
+ durationMs: Date.now() - toolStart,
348
+ success: result.success,
349
+ error: result.success ? void 0 : result.output
350
+ });
351
+ }
352
+ }
353
+ loopCount++;
354
+ }
355
+ return "Max tool call loops reached. Please try again.";
356
+ }
357
+ getConversationHistory() {
358
+ return [...this.conversationHistory];
359
+ }
360
+ clearHistory() {
361
+ this.conversationHistory = [];
362
+ }
363
+ };
364
+
365
+ // ../core/dist/commands.js
366
+ async function handleCommand(text, ctx) {
367
+ const trimmed = text.trim();
368
+ if (!trimmed.startsWith("/")) {
369
+ return { handled: false };
370
+ }
371
+ const parts = trimmed.slice(1).split(/\s+/);
372
+ const command = parts[0]?.toLowerCase();
373
+ const arg = parts[1];
374
+ switch (command) {
375
+ case "pause": {
376
+ if (arg) {
377
+ if (!ctx.skillManager) {
378
+ return { handled: true, response: "Skills system is not configured." };
379
+ }
380
+ await ctx.skillManager.updateStatus(arg, "paused");
381
+ return { handled: true, response: `Skill "${arg}" paused.` };
382
+ }
383
+ ctx.scheduler.stop();
384
+ ctx.agent.setState("paused");
385
+ return { handled: true, response: "Agent paused. Scheduler stopped. Direct messages still accepted." };
386
+ }
387
+ case "resume": {
388
+ ctx.scheduler.start();
389
+ ctx.agent.setState("running");
390
+ return { handled: true, response: "Agent resumed. Scheduler restarted." };
391
+ }
392
+ case "kill": {
393
+ ctx.scheduler.stop();
394
+ await ctx.pool.destroyAll();
395
+ ctx.agent.setState("killed");
396
+ return { handled: true, response: "Emergency stop. All containers destroyed. Agent in read-only mode." };
397
+ }
398
+ case "status": {
399
+ const state = ctx.agent.getState();
400
+ return { handled: true, response: `Agent state: ${state}` };
401
+ }
402
+ default:
403
+ return { handled: false };
404
+ }
405
+ }
406
+
407
+ // ../core/dist/persona.js
408
+ import { readdir, readFile as readFile2 } from "fs/promises";
409
+ import { join as join2 } from "path";
410
+ import matter from "gray-matter";
411
+ var PersonaResolver = class {
412
+ personaDir;
413
+ personas = [];
414
+ constructor(personaDir) {
415
+ this.personaDir = personaDir;
416
+ }
417
+ async loadAll() {
418
+ this.personas = [];
419
+ let entries;
420
+ try {
421
+ entries = await readdir(this.personaDir);
422
+ } catch {
423
+ return;
424
+ }
425
+ for (const entry of entries) {
426
+ if (!entry.endsWith(".md"))
427
+ continue;
428
+ const content = await readFile2(join2(this.personaDir, entry), "utf-8");
429
+ const { data, content: body } = matter(content);
430
+ const meta = {
431
+ id: data.id ?? entry.replace(".md", ""),
432
+ name: data.name ?? entry.replace(".md", ""),
433
+ triggers: data.triggers,
434
+ priority: data.priority ?? 0
435
+ };
436
+ this.personas.push({ meta, body: body.trim() });
437
+ }
438
+ this.personas.sort((a, b) => a.meta.id.localeCompare(b.meta.id));
439
+ }
440
+ resolve(message, activeSkillId) {
441
+ const defaultPersona = this.personas.find((p) => p.meta.id === "default");
442
+ const baseParts = [];
443
+ if (defaultPersona) {
444
+ baseParts.push(defaultPersona.body);
445
+ }
446
+ let bestMatch;
447
+ let bestScore = 0;
448
+ const lowerMessage = message.toLowerCase();
449
+ for (const persona of this.personas) {
450
+ if (persona.meta.id === "default")
451
+ continue;
452
+ let score = 0;
453
+ if (persona.meta.triggers?.keywords) {
454
+ for (const kw of persona.meta.triggers.keywords) {
455
+ if (lowerMessage.includes(kw.toLowerCase())) {
456
+ score++;
457
+ }
458
+ }
459
+ }
460
+ if (activeSkillId && persona.meta.triggers?.skills) {
461
+ for (const pattern of persona.meta.triggers.skills) {
462
+ if (matchGlob(pattern, activeSkillId)) {
463
+ score += 2;
464
+ }
465
+ }
466
+ }
467
+ if (score === 0)
468
+ continue;
469
+ const priority = persona.meta.priority ?? 0;
470
+ const finalScore = score + priority;
471
+ if (finalScore > bestScore) {
472
+ bestScore = finalScore;
473
+ bestMatch = persona;
474
+ }
475
+ }
476
+ if (bestMatch) {
477
+ baseParts.push(bestMatch.body);
478
+ }
479
+ return baseParts.join("\n\n");
480
+ }
481
+ };
482
+ function matchGlob(pattern, value) {
483
+ if (pattern.endsWith("*")) {
484
+ return value.startsWith(pattern.slice(0, -1));
485
+ }
486
+ return pattern === value;
487
+ }
488
+
489
+ // ../core/dist/context-guard.js
490
+ var DEFAULT_CONFIG = {
491
+ maxContextTokens: 2e5,
492
+ reservedForOutput: 8192,
493
+ maxConversationTurns: 50
494
+ };
495
+ var ContextGuard = class {
496
+ config;
497
+ constructor(config = {}) {
498
+ this.config = { ...DEFAULT_CONFIG, ...config };
499
+ }
500
+ estimateTokens(text) {
501
+ return Math.ceil(text.length / 4);
502
+ }
503
+ compact(messages) {
504
+ let result = [...messages];
505
+ if (result.length > this.config.maxConversationTurns) {
506
+ result = result.slice(-this.config.maxConversationTurns);
507
+ }
508
+ const recentToolCutoff = Math.max(0, result.length - 10);
509
+ result = result.map((msg, i) => {
510
+ if (msg.role === "tool" && i < recentToolCutoff) {
511
+ return { ...msg, content: "[Tool result truncated]" };
512
+ }
513
+ return msg;
514
+ });
515
+ result = result.map((msg) => {
516
+ if (msg.role === "tool" && msg.content.length > 2e3) {
517
+ return { ...msg, content: msg.content.slice(0, 2e3) + "... [truncated]" };
518
+ }
519
+ return msg;
520
+ });
521
+ const budget = this.config.maxContextTokens - this.config.reservedForOutput;
522
+ let totalTokens = result.reduce((sum, m) => sum + this.estimateTokens(m.content), 0);
523
+ while (totalTokens > budget && result.length > 1) {
524
+ const removed = result.shift();
525
+ totalTokens -= this.estimateTokens(removed.content);
526
+ }
527
+ return result;
528
+ }
529
+ };
530
+
531
+ // ../channels/dist/telegram.js
532
+ import { Bot } from "grammy";
533
+ var TelegramChannel = class {
534
+ type = "telegram";
535
+ bot;
536
+ allowedUsers;
537
+ handlers = [];
538
+ constructor(config) {
539
+ this.bot = new Bot(config.botToken);
540
+ this.allowedUsers = new Set(config.allowedUsers);
541
+ this.bot.on("message:text", async (ctx) => {
542
+ const userId = ctx.from.id;
543
+ if (!this.isUserAllowed(userId)) {
544
+ return;
545
+ }
546
+ const incoming = {
547
+ id: String(ctx.message.message_id),
548
+ channelType: "telegram",
549
+ userId: String(userId),
550
+ text: ctx.message.text,
551
+ timestamp: new Date(ctx.message.date * 1e3),
552
+ replyTo: ctx.message.reply_to_message ? String(ctx.message.reply_to_message.message_id) : void 0
553
+ };
554
+ for (const handler of this.handlers) {
555
+ await handler(incoming);
556
+ }
557
+ });
558
+ }
559
+ isUserAllowed(userId) {
560
+ return this.allowedUsers.has(userId);
561
+ }
562
+ onMessage(handler) {
563
+ this.handlers.push(handler);
564
+ }
565
+ async start() {
566
+ await this.bot.start();
567
+ }
568
+ async stop() {
569
+ await this.bot.stop();
570
+ }
571
+ async send(message) {
572
+ await this.bot.api.sendMessage(Number(message.userId), message.text, {
573
+ parse_mode: "Markdown",
574
+ ...message.replyTo ? { reply_parameters: { message_id: Number(message.replyTo) } } : {}
575
+ });
576
+ }
577
+ };
578
+
579
+ // ../tools/dist/registry.js
580
+ var ToolRegistry = class {
581
+ tools = /* @__PURE__ */ new Map();
582
+ context;
583
+ register(tool) {
584
+ this.tools.set(tool.name, tool);
585
+ }
586
+ get(name) {
587
+ return this.tools.get(name);
588
+ }
589
+ list() {
590
+ return Array.from(this.tools.values());
591
+ }
592
+ toFunctionSchemas() {
593
+ return this.list().map((tool) => ({
594
+ type: "function",
595
+ function: {
596
+ name: tool.name,
597
+ description: tool.description,
598
+ parameters: tool.parameters
599
+ }
600
+ }));
601
+ }
602
+ async execute(name, params) {
603
+ const tool = this.tools.get(name);
604
+ if (!tool) {
605
+ throw new Error(`Tool not found: ${name}`);
606
+ }
607
+ return tool.execute(params, this.context);
608
+ }
609
+ setContext(ctx) {
610
+ this.context = ctx;
611
+ }
612
+ };
613
+
614
+ // ../tools/dist/memory.js
615
+ var memoryReadTool = {
616
+ name: "memory_read",
617
+ description: "Read content from memory. If path is provided, reads that file. If path is omitted, lists all memory files.",
618
+ parameters: {
619
+ type: "object",
620
+ properties: {
621
+ path: {
622
+ type: "string",
623
+ description: "The memory file path to read (omit to list all files)"
624
+ }
625
+ }
626
+ },
627
+ execute: async (params, ctx) => {
628
+ const { path } = params;
629
+ try {
630
+ if (!path) {
631
+ const files = await ctx.memory.list();
632
+ return { success: true, output: files.join("\n") };
633
+ }
634
+ const content = await ctx.memory.read(path);
635
+ return { success: true, output: content };
636
+ } catch (err2) {
637
+ return {
638
+ success: false,
639
+ output: err2 instanceof Error ? err2.message : String(err2)
640
+ };
641
+ }
642
+ }
643
+ };
644
+ var memoryWriteTool = {
645
+ name: "memory_write",
646
+ description: "Write content to a memory file at the given path",
647
+ parameters: {
648
+ type: "object",
649
+ properties: {
650
+ path: { type: "string", description: "The memory file path to write to" },
651
+ content: { type: "string", description: "The content to write" }
652
+ },
653
+ required: ["path", "content"]
654
+ },
655
+ execute: async (params, ctx) => {
656
+ const { path, content } = params;
657
+ try {
658
+ await ctx.memory.write(path, content);
659
+ return { success: true, output: `Written to ${path}` };
660
+ } catch (err2) {
661
+ return {
662
+ success: false,
663
+ output: err2 instanceof Error ? err2.message : String(err2)
664
+ };
665
+ }
666
+ }
667
+ };
668
+
669
+ // ../tools/dist/schedule.js
670
+ var scheduleTool = {
671
+ name: "schedule",
672
+ description: "Manage scheduled tasks: create, delete, or list cron jobs",
673
+ parameters: {
674
+ type: "object",
675
+ properties: {
676
+ action: {
677
+ type: "string",
678
+ enum: ["create", "delete", "list"],
679
+ description: "The scheduling action to perform"
680
+ },
681
+ id: { type: "string", description: "The schedule ID (for create/delete)" },
682
+ cron: { type: "string", description: "Cron expression (for create)" },
683
+ prompt: { type: "string", description: "The prompt to execute on schedule (for create)" }
684
+ },
685
+ required: ["action"]
686
+ },
687
+ execute: async (params, ctx) => {
688
+ const { action, id, cron, prompt } = params;
689
+ try {
690
+ switch (action) {
691
+ case "list": {
692
+ const jobs = ctx.scheduler.listJobs();
693
+ if (jobs.length === 0) {
694
+ return { success: true, output: "No scheduled jobs." };
695
+ }
696
+ const lines = jobs.map((j) => `- ${j.id}: "${j.prompt}" @ ${j.cron} (${j.enabled ? "enabled" : "disabled"})`);
697
+ return { success: true, output: lines.join("\n") };
698
+ }
699
+ case "create": {
700
+ if (!cron || !prompt) {
701
+ return { success: false, output: "Missing required fields: cron and prompt" };
702
+ }
703
+ const jobId = id ?? `job-${Date.now()}`;
704
+ ctx.scheduler.addJob({
705
+ id: jobId,
706
+ cron,
707
+ prompt,
708
+ channel: "default",
709
+ enabled: true
710
+ });
711
+ return { success: true, output: `Created job ${jobId}` };
712
+ }
713
+ case "delete": {
714
+ if (!id) {
715
+ return { success: false, output: "Missing required field: id" };
716
+ }
717
+ ctx.scheduler.removeJob(id);
718
+ return { success: true, output: `Deleted job ${id}` };
719
+ }
720
+ default:
721
+ return { success: false, output: `Unknown action: ${action}` };
722
+ }
723
+ } catch (err2) {
724
+ return {
725
+ success: false,
726
+ output: err2 instanceof Error ? err2.message : String(err2)
727
+ };
728
+ }
729
+ }
730
+ };
731
+
732
+ // ../tools/dist/web-search.js
733
+ var webSearchTool = {
734
+ name: "web_search",
735
+ description: "Search the web using the configured search provider (Tavily, Exa, or SearXNG)",
736
+ parameters: {
737
+ type: "object",
738
+ properties: {
739
+ query: { type: "string", description: "The search query" },
740
+ maxResults: {
741
+ type: "number",
742
+ description: "Max results (default: from config or 5)"
743
+ }
744
+ },
745
+ required: ["query"]
746
+ },
747
+ execute: async (params, ctx) => {
748
+ const { query, maxResults: maxResultsParam } = params;
749
+ const cfg = ctx.config.tools?.webSearch;
750
+ if (!cfg) {
751
+ return {
752
+ success: false,
753
+ output: "web_search is not configured. Set tools.webSearch in your config."
754
+ };
755
+ }
756
+ const maxResults = maxResultsParam ?? cfg.maxResults ?? 5;
757
+ try {
758
+ let results;
759
+ switch (cfg.provider) {
760
+ case "tavily":
761
+ if (!cfg.apiKey) {
762
+ return { success: false, output: "Tavily requires tools.webSearch.apiKey in config." };
763
+ }
764
+ results = await searchTavily(query, maxResults, cfg.apiKey);
765
+ break;
766
+ case "exa":
767
+ if (!cfg.apiKey) {
768
+ return { success: false, output: "Exa requires tools.webSearch.apiKey in config." };
769
+ }
770
+ results = await searchExa(query, maxResults, cfg.apiKey);
771
+ break;
772
+ case "searxng":
773
+ if (!cfg.baseUrl) {
774
+ return { success: false, output: "SearXNG requires tools.webSearch.baseUrl in config." };
775
+ }
776
+ results = await searchSearXNG(query, maxResults, cfg.baseUrl);
777
+ break;
778
+ default:
779
+ return {
780
+ success: false,
781
+ output: `Unknown search provider: ${cfg.provider}`
782
+ };
783
+ }
784
+ if (results.length === 0) {
785
+ return { success: true, output: "No results found." };
786
+ }
787
+ const formatted = results.map((r, i) => `${i + 1}. **${r.title}**
788
+ ${r.snippet}
789
+ ${r.url}`).join("\n\n");
790
+ return { success: true, output: formatted };
791
+ } catch (err2) {
792
+ return {
793
+ success: false,
794
+ output: err2 instanceof Error ? err2.message : String(err2)
795
+ };
796
+ }
797
+ }
798
+ };
799
+ async function searchTavily(query, maxResults, apiKey) {
800
+ const res = await fetch("https://api.tavily.com/search", {
801
+ method: "POST",
802
+ headers: {
803
+ Authorization: `Bearer ${apiKey}`,
804
+ "Content-Type": "application/json"
805
+ },
806
+ body: JSON.stringify({ query, max_results: maxResults })
807
+ });
808
+ if (!res.ok) {
809
+ throw new Error(`Tavily API error: ${res.status} ${res.statusText}`);
810
+ }
811
+ const data = await res.json();
812
+ return data.results.map((r) => ({
813
+ title: r.title,
814
+ url: r.url,
815
+ snippet: r.content
816
+ }));
817
+ }
818
+ async function searchExa(query, maxResults, apiKey) {
819
+ const res = await fetch("https://api.exa.ai/search", {
820
+ method: "POST",
821
+ headers: {
822
+ "x-api-key": apiKey,
823
+ "Content-Type": "application/json"
824
+ },
825
+ body: JSON.stringify({
826
+ query,
827
+ numResults: maxResults,
828
+ contents: { text: { maxCharacters: 300 } }
829
+ })
830
+ });
831
+ if (!res.ok) {
832
+ throw new Error(`Exa API error: ${res.status} ${res.statusText}`);
833
+ }
834
+ const data = await res.json();
835
+ return data.results.map((r) => ({
836
+ title: r.title,
837
+ url: r.url,
838
+ snippet: r.text
839
+ }));
840
+ }
841
+ async function searchSearXNG(query, maxResults, baseUrl) {
842
+ const url = new URL("/search", baseUrl);
843
+ url.searchParams.set("q", query);
844
+ url.searchParams.set("format", "json");
845
+ url.searchParams.set("pageno", "1");
846
+ const res = await fetch(url.toString(), {
847
+ method: "GET",
848
+ headers: { Accept: "application/json" }
849
+ });
850
+ if (!res.ok) {
851
+ throw new Error(`SearXNG API error: ${res.status} ${res.statusText}`);
852
+ }
853
+ const data = await res.json();
854
+ return data.results.slice(0, maxResults).map((r) => ({
855
+ title: r.title,
856
+ url: r.url,
857
+ snippet: r.content
858
+ }));
859
+ }
860
+
861
+ // ../tools/dist/http.js
862
+ var DEFAULT_TIMEOUT_MS = 1e4;
863
+ var DEFAULT_MAX_RESPONSE_BYTES = 1048576;
864
+ var MAX_OUTPUT_CHARS = 4e3;
865
+ var httpTool = {
866
+ name: "http",
867
+ description: "Make HTTP requests (GET or POST). Use a preset name to inject auth headers from config.",
868
+ parameters: {
869
+ type: "object",
870
+ properties: {
871
+ method: { type: "string", enum: ["GET", "POST"], description: "HTTP method" },
872
+ url: { type: "string", description: "Full URL, or path if using a preset" },
873
+ preset: { type: "string", description: "Config preset name for auth injection" },
874
+ body: { type: "object", description: "JSON body (POST only)" },
875
+ headers: { type: "object", description: "Additional headers (no auth \u2014 use preset)" }
876
+ },
877
+ required: ["method", "url"]
878
+ },
879
+ execute: async (params, ctx) => {
880
+ const { method, url, preset, body, headers: extraHeaders } = params;
881
+ if (method !== "GET" && method !== "POST") {
882
+ return { success: false, output: "Only GET or POST methods are allowed" };
883
+ }
884
+ const httpConfig = ctx.config.tools?.http;
885
+ let resolvedUrl = url;
886
+ let presetHeaders = {};
887
+ if (preset) {
888
+ const presetConfig = httpConfig?.presets?.[preset];
889
+ if (!presetConfig) {
890
+ return {
891
+ success: false,
892
+ output: `Unknown preset: "${preset}". Check your config.`
893
+ };
894
+ }
895
+ if (!url.startsWith("http")) {
896
+ resolvedUrl = presetConfig.baseUrl + url;
897
+ }
898
+ presetHeaders = presetConfig.headers ?? {};
899
+ }
900
+ const mergedHeaders = {
901
+ ...httpConfig?.defaultHeaders ?? {},
902
+ ...extraHeaders ?? {},
903
+ ...presetHeaders
904
+ };
905
+ if (body) {
906
+ mergedHeaders["Content-Type"] = "application/json";
907
+ }
908
+ const timeoutMs = httpConfig?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
909
+ const maxBytes = httpConfig?.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES;
910
+ const controller = new AbortController();
911
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
912
+ try {
913
+ const res = await fetch(resolvedUrl, {
914
+ method,
915
+ headers: mergedHeaders,
916
+ body: body ? JSON.stringify(body) : void 0,
917
+ signal: controller.signal
918
+ });
919
+ const raw = await res.arrayBuffer();
920
+ let text = new TextDecoder().decode(raw.slice(0, maxBytes));
921
+ if (raw.byteLength > maxBytes) {
922
+ text += "\n[response truncated at byte limit]";
923
+ }
924
+ if (text.length > MAX_OUTPUT_CHARS) {
925
+ text = text.slice(0, MAX_OUTPUT_CHARS) + "\n[truncated]";
926
+ }
927
+ const contentType = res.headers.get("content-type") ?? "";
928
+ const output = `Status: ${res.status} ${res.statusText}
929
+ Content-Type: ${contentType}
930
+
931
+ ${text}`;
932
+ return { success: res.ok, output };
933
+ } catch (err2) {
934
+ return {
935
+ success: false,
936
+ output: err2 instanceof Error ? err2.message : String(err2)
937
+ };
938
+ } finally {
939
+ clearTimeout(timer);
940
+ }
941
+ }
942
+ };
943
+
944
+ // ../tools/dist/email.js
945
+ import { ImapFlow } from "imapflow";
946
+ import { createTransport } from "nodemailer";
947
+
948
+ // ../tools/dist/sandbox-exec.js
949
+ var sandboxExecTool = {
950
+ name: "sandbox_exec",
951
+ description: "Execute a shell command in an isolated Docker container. Returns stdout, stderr, and exit code.",
952
+ parameters: {
953
+ type: "object",
954
+ properties: {
955
+ command: {
956
+ type: "string",
957
+ description: "The shell command to execute"
958
+ },
959
+ trust: {
960
+ type: "string",
961
+ enum: ["sandboxed", "trusted"],
962
+ description: "Trust level (default: sandboxed). 'sandboxed' has no network. 'trusted' has host network."
963
+ },
964
+ timeout: {
965
+ type: "number",
966
+ description: "Timeout in seconds (default: from config)"
967
+ }
968
+ },
969
+ required: ["command"]
970
+ },
971
+ execute: async (params, ctx) => {
972
+ const { command, trust, timeout } = params;
973
+ if (!ctx.pool) {
974
+ return { success: false, output: "Sandbox pool is not available" };
975
+ }
976
+ const defaults = ctx.config.sandbox.defaults;
977
+ const effectiveTimeout = timeout ?? defaults.timeout;
978
+ let container;
979
+ try {
980
+ container = await ctx.pool.acquire({
981
+ trust: trust ?? "sandboxed",
982
+ timeout: effectiveTimeout,
983
+ memory: defaults.memoryLimit,
984
+ cpu: defaults.cpuLimit
985
+ });
986
+ } catch (err2) {
987
+ return {
988
+ success: false,
989
+ output: `Failed to acquire container: ${err2 instanceof Error ? err2.message : String(err2)}`
990
+ };
991
+ }
992
+ try {
993
+ const result = await container.exec(command, {
994
+ timeout: effectiveTimeout
995
+ });
996
+ const parts = [];
997
+ if (result.stdout)
998
+ parts.push(result.stdout);
999
+ if (result.stderr)
1000
+ parts.push(`[stderr]
1001
+ ${result.stderr}`);
1002
+ parts.push(`Exit code: ${result.exitCode}`);
1003
+ return {
1004
+ success: result.exitCode === 0,
1005
+ output: parts.join("\n")
1006
+ };
1007
+ } catch (err2) {
1008
+ return {
1009
+ success: false,
1010
+ output: err2 instanceof Error ? err2.message : String(err2)
1011
+ };
1012
+ } finally {
1013
+ await ctx.pool.release(container);
1014
+ }
1015
+ }
1016
+ };
1017
+
1018
+ // ../tools/dist/opencode.js
1019
+ function shellEscape(s) {
1020
+ return `'${s.replace(/'/g, "'\\''")}'`;
1021
+ }
1022
+ var opencodeTool = {
1023
+ name: "opencode",
1024
+ description: "Run a code agent (claude-code, opencode, codex CLI) in a Docker container to perform a coding task.",
1025
+ parameters: {
1026
+ type: "object",
1027
+ properties: {
1028
+ task: {
1029
+ type: "string",
1030
+ description: "Natural language task description for the code agent"
1031
+ },
1032
+ trust: {
1033
+ type: "string",
1034
+ enum: ["sandboxed", "trusted"],
1035
+ description: "Trust level (default: trusted)"
1036
+ },
1037
+ timeout: {
1038
+ type: "number",
1039
+ description: "Timeout in seconds (default: from config)"
1040
+ }
1041
+ },
1042
+ required: ["task"]
1043
+ },
1044
+ execute: async (params, ctx) => {
1045
+ const { task, trust, timeout } = params;
1046
+ if (!ctx.pool) {
1047
+ return { success: false, output: "Sandbox pool is not available" };
1048
+ }
1049
+ const agentConfig = ctx.config.sandbox.codeAgent;
1050
+ if (!agentConfig) {
1051
+ return {
1052
+ success: false,
1053
+ output: "codeAgent is not configured in sandbox config"
1054
+ };
1055
+ }
1056
+ const defaults = ctx.config.sandbox.defaults;
1057
+ const effectiveTimeout = timeout ?? defaults.timeout;
1058
+ let container;
1059
+ try {
1060
+ container = await ctx.pool.acquire({
1061
+ trust: trust ?? "trusted",
1062
+ timeout: effectiveTimeout,
1063
+ memory: defaults.memoryLimit,
1064
+ cpu: defaults.cpuLimit
1065
+ });
1066
+ } catch (err2) {
1067
+ return {
1068
+ success: false,
1069
+ output: `Failed to acquire container: ${err2 instanceof Error ? err2.message : String(err2)}`
1070
+ };
1071
+ }
1072
+ try {
1073
+ const cmdParts = [agentConfig.command];
1074
+ if (agentConfig.args)
1075
+ cmdParts.push(...agentConfig.args);
1076
+ cmdParts.push(shellEscape(task));
1077
+ const command = cmdParts.join(" ");
1078
+ const result = await container.exec(command, {
1079
+ timeout: effectiveTimeout,
1080
+ env: agentConfig.env
1081
+ });
1082
+ const parts = [];
1083
+ if (result.stdout)
1084
+ parts.push(result.stdout);
1085
+ if (result.stderr)
1086
+ parts.push(`[stderr]
1087
+ ${result.stderr}`);
1088
+ parts.push(`Exit code: ${result.exitCode}`);
1089
+ return {
1090
+ success: result.exitCode === 0,
1091
+ output: parts.join("\n")
1092
+ };
1093
+ } catch (err2) {
1094
+ return {
1095
+ success: false,
1096
+ output: err2 instanceof Error ? err2.message : String(err2)
1097
+ };
1098
+ } finally {
1099
+ await ctx.pool.release(container);
1100
+ }
1101
+ }
1102
+ };
1103
+
1104
+ // ../core/dist/main.js
1105
+ import Dockerode from "dockerode";
1106
+
1107
+ // ../sandbox/dist/container.js
1108
+ import { PassThrough } from "stream";
1109
+ var MAX_OUTPUT_BYTES = 1024 * 1024;
1110
+ var DockerContainer = class {
1111
+ id;
1112
+ _status = "idle";
1113
+ raw;
1114
+ demux;
1115
+ constructor(raw, demux) {
1116
+ this.raw = raw;
1117
+ this.id = raw.id;
1118
+ this.demux = demux;
1119
+ }
1120
+ get status() {
1121
+ return this._status;
1122
+ }
1123
+ async exec(command, opts) {
1124
+ this._status = "busy";
1125
+ try {
1126
+ return await this._exec(command, opts);
1127
+ } finally {
1128
+ if (this._status !== "stopped") {
1129
+ this._status = "idle";
1130
+ }
1131
+ }
1132
+ }
1133
+ async stop() {
1134
+ if (this._status === "stopped")
1135
+ return;
1136
+ try {
1137
+ await this.raw.stop({ t: 5 });
1138
+ } catch {
1139
+ }
1140
+ try {
1141
+ await this.raw.remove({ force: true });
1142
+ } catch {
1143
+ }
1144
+ this._status = "stopped";
1145
+ }
1146
+ /* ------------------------------------------------------------------ */
1147
+ async _exec(command, opts) {
1148
+ const createOpts = {
1149
+ Cmd: ["sh", "-c", command],
1150
+ AttachStdout: true,
1151
+ AttachStderr: true
1152
+ };
1153
+ if (opts?.cwd) {
1154
+ createOpts.WorkingDir = opts.cwd;
1155
+ }
1156
+ if (opts?.env) {
1157
+ createOpts.Env = Object.entries(opts.env).map(([k, v]) => `${k}=${v}`);
1158
+ }
1159
+ const exec = await this.raw.exec(createOpts);
1160
+ const stream = await exec.start({ hijack: true, stdin: false });
1161
+ return new Promise((resolve5, reject) => {
1162
+ const stdoutPT = new PassThrough();
1163
+ const stderrPT = new PassThrough();
1164
+ const stdoutBufs = [];
1165
+ const stderrBufs = [];
1166
+ let stdoutLen = 0;
1167
+ let stderrLen = 0;
1168
+ stdoutPT.on("data", (chunk) => {
1169
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
1170
+ const remaining = MAX_OUTPUT_BYTES - stdoutLen;
1171
+ if (remaining > 0) {
1172
+ stdoutBufs.push(buf.subarray(0, remaining));
1173
+ stdoutLen += Math.min(buf.length, remaining);
1174
+ }
1175
+ });
1176
+ stderrPT.on("data", (chunk) => {
1177
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
1178
+ const remaining = MAX_OUTPUT_BYTES - stderrLen;
1179
+ if (remaining > 0) {
1180
+ stderrBufs.push(buf.subarray(0, remaining));
1181
+ stderrLen += Math.min(buf.length, remaining);
1182
+ }
1183
+ });
1184
+ let timer;
1185
+ let settled = false;
1186
+ let stdoutEnded = false;
1187
+ let stderrEnded = false;
1188
+ const cleanup = () => {
1189
+ if (timer)
1190
+ clearTimeout(timer);
1191
+ };
1192
+ if (opts?.timeout && opts.timeout > 0) {
1193
+ const timeoutMs = opts.timeout * 1e3;
1194
+ timer = setTimeout(() => {
1195
+ if (!settled) {
1196
+ settled = true;
1197
+ stream.destroy();
1198
+ reject(new Error(`Exec timed out after ${opts.timeout}s`));
1199
+ }
1200
+ }, timeoutMs);
1201
+ }
1202
+ const tryResolve = async () => {
1203
+ if (!stdoutEnded || !stderrEnded)
1204
+ return;
1205
+ if (settled)
1206
+ return;
1207
+ settled = true;
1208
+ cleanup();
1209
+ try {
1210
+ const info = await exec.inspect();
1211
+ resolve5({
1212
+ exitCode: info.ExitCode ?? 1,
1213
+ stdout: Buffer.concat(stdoutBufs).toString("utf-8"),
1214
+ stderr: Buffer.concat(stderrBufs).toString("utf-8")
1215
+ });
1216
+ } catch (err2) {
1217
+ reject(err2);
1218
+ }
1219
+ };
1220
+ stdoutPT.on("end", () => {
1221
+ stdoutEnded = true;
1222
+ tryResolve();
1223
+ });
1224
+ stderrPT.on("end", () => {
1225
+ stderrEnded = true;
1226
+ tryResolve();
1227
+ });
1228
+ stream.on("error", (err2) => {
1229
+ if (!settled) {
1230
+ settled = true;
1231
+ cleanup();
1232
+ reject(err2);
1233
+ }
1234
+ });
1235
+ this.demux(stream, stdoutPT, stderrPT);
1236
+ });
1237
+ }
1238
+ };
1239
+
1240
+ // ../sandbox/dist/pool.js
1241
+ function parseMemory(mem) {
1242
+ const match = mem.match(/^(\d+(?:\.\d+)?)\s*([mg])$/i);
1243
+ if (!match)
1244
+ throw new Error(`Invalid memory value: ${mem}`);
1245
+ const value = parseFloat(match[1]);
1246
+ const unit = match[2].toLowerCase();
1247
+ if (unit === "m")
1248
+ return Math.round(value * 1024 * 1024);
1249
+ return Math.round(value * 1024 * 1024 * 1024);
1250
+ }
1251
+ function parseCpu(cpu) {
1252
+ const value = parseFloat(cpu);
1253
+ if (Number.isNaN(value))
1254
+ throw new Error(`Invalid cpu value: ${cpu}`);
1255
+ return Math.round(value * 1e9);
1256
+ }
1257
+ var DockerContainerPool = class {
1258
+ docker;
1259
+ image;
1260
+ maxTotal;
1261
+ // C3: idle cache keyed by trust level to prevent cross-trust reuse
1262
+ idle = /* @__PURE__ */ new Map([
1263
+ ["sandboxed", /* @__PURE__ */ new Set()],
1264
+ ["trusted", /* @__PURE__ */ new Set()]
1265
+ ]);
1266
+ busy = /* @__PURE__ */ new Set();
1267
+ containerTrust = /* @__PURE__ */ new Map();
1268
+ constructor(docker, config) {
1269
+ this.docker = docker;
1270
+ this.image = config.image;
1271
+ this.maxTotal = config.maxTotal;
1272
+ }
1273
+ get idleCount() {
1274
+ let count = 0;
1275
+ for (const set of this.idle.values())
1276
+ count += set.size;
1277
+ return count;
1278
+ }
1279
+ /* ---- acquire ---- */
1280
+ async acquire(opts) {
1281
+ const trustIdle = this.idle.get(opts.trust);
1282
+ const cached = trustIdle.values().next();
1283
+ if (!cached.done) {
1284
+ const container2 = cached.value;
1285
+ trustIdle.delete(container2);
1286
+ this.busy.add(container2);
1287
+ return container2;
1288
+ }
1289
+ const total = this.idleCount + this.busy.size;
1290
+ if (total >= this.maxTotal) {
1291
+ throw new Error("Pool limit reached");
1292
+ }
1293
+ const raw = await this.docker.createContainer(this.buildCreateOpts(opts));
1294
+ await raw.start();
1295
+ const modem = this.docker.modem;
1296
+ const demux = modem.demuxStream.bind(modem);
1297
+ const container = new DockerContainer(raw, demux);
1298
+ this.containerTrust.set(container.id, opts.trust);
1299
+ this.busy.add(container);
1300
+ return container;
1301
+ }
1302
+ /* ---- release ---- */
1303
+ async release(container) {
1304
+ this.busy.delete(container);
1305
+ if (container.status === "stopped") {
1306
+ this.containerTrust.delete(container.id);
1307
+ return;
1308
+ }
1309
+ const trust = this.containerTrust.get(container.id) ?? "sandboxed";
1310
+ this.idle.get(trust).add(container);
1311
+ }
1312
+ /* ---- destroy ---- */
1313
+ async destroy(container) {
1314
+ this.busy.delete(container);
1315
+ for (const set of this.idle.values())
1316
+ set.delete(container);
1317
+ this.containerTrust.delete(container.id);
1318
+ await container.stop();
1319
+ }
1320
+ /* ---- destroyAll ---- */
1321
+ async destroyAll() {
1322
+ const all = [...this.busy];
1323
+ for (const set of this.idle.values()) {
1324
+ all.push(...set);
1325
+ set.clear();
1326
+ }
1327
+ this.busy.clear();
1328
+ this.containerTrust.clear();
1329
+ await Promise.allSettled(all.map((c) => c.stop()));
1330
+ }
1331
+ /* ---- stats ---- */
1332
+ stats() {
1333
+ const idle = this.idleCount;
1334
+ return {
1335
+ idle,
1336
+ busy: this.busy.size,
1337
+ total: idle + this.busy.size,
1338
+ maxTotal: this.maxTotal
1339
+ };
1340
+ }
1341
+ /* ---- internal ---- */
1342
+ buildCreateOpts(opts) {
1343
+ const isSandboxed = opts.trust === "sandboxed";
1344
+ const hostConfig = {
1345
+ Memory: parseMemory(opts.memory),
1346
+ NanoCpus: parseCpu(opts.cpu),
1347
+ PidsLimit: 512
1348
+ // I6: prevent fork bombs
1349
+ };
1350
+ if (!isSandboxed) {
1351
+ hostConfig.NetworkMode = "host";
1352
+ }
1353
+ if (opts.mounts?.length) {
1354
+ hostConfig.Binds = opts.mounts.map((m) => `${m.host}:${m.container}${m.readonly ? ":ro" : ""}`);
1355
+ }
1356
+ const createOpts = {
1357
+ Image: this.image,
1358
+ Cmd: ["sleep", "infinity"],
1359
+ WorkingDir: "/workspace",
1360
+ NetworkDisabled: isSandboxed,
1361
+ HostConfig: hostConfig
1362
+ };
1363
+ if (opts.env) {
1364
+ createOpts.Env = Object.entries(opts.env).map(([k, v]) => `${k}=${v}`);
1365
+ }
1366
+ return createOpts;
1367
+ }
1368
+ };
1369
+
1370
+ // ../memory/dist/store.js
1371
+ import { readFile as readFile3, writeFile, mkdir as mkdir2, readdir as readdir2, access } from "fs/promises";
1372
+ import { join as join3, dirname, relative } from "path";
1373
+ var FileMemoryStore = class {
1374
+ basePath;
1375
+ constructor(basePath) {
1376
+ this.basePath = basePath;
1377
+ }
1378
+ async read(path) {
1379
+ return readFile3(this.resolve(path), "utf-8");
1380
+ }
1381
+ async write(path, content) {
1382
+ const full = this.resolve(path);
1383
+ await mkdir2(dirname(full), { recursive: true });
1384
+ await writeFile(full, content, "utf-8");
1385
+ }
1386
+ async append(path, content) {
1387
+ const full = this.resolve(path);
1388
+ try {
1389
+ const existing = await readFile3(full, "utf-8");
1390
+ await writeFile(full, existing + content, "utf-8");
1391
+ } catch {
1392
+ await this.write(path, content);
1393
+ }
1394
+ }
1395
+ async list(directory) {
1396
+ const dir = directory ? this.resolve(directory) : this.basePath;
1397
+ return this.listRecursive(dir);
1398
+ }
1399
+ async exists(path) {
1400
+ try {
1401
+ await access(this.resolve(path));
1402
+ return true;
1403
+ } catch {
1404
+ return false;
1405
+ }
1406
+ }
1407
+ resolve(path) {
1408
+ return join3(this.basePath, path);
1409
+ }
1410
+ async listRecursive(dir) {
1411
+ const entries = await readdir2(dir, { withFileTypes: true });
1412
+ const files = [];
1413
+ for (const entry of entries) {
1414
+ const full = join3(dir, entry.name);
1415
+ if (entry.isDirectory()) {
1416
+ files.push(...await this.listRecursive(full));
1417
+ } else {
1418
+ files.push(relative(this.basePath, full));
1419
+ }
1420
+ }
1421
+ return files;
1422
+ }
1423
+ };
1424
+
1425
+ // ../memory/dist/ingest.js
1426
+ var EXTRACTION_PROMPT = `You are a memory extraction agent. Given a conversation, extract key factual observations about the user.
1427
+
1428
+ Rules:
1429
+ - Return a markdown bullet list of observations (one per line, starting with "- ")
1430
+ - Only extract facts, preferences, decisions, plans, and personal details
1431
+ - Be concise: one fact per bullet
1432
+ - If there are no notable observations, return exactly "No notable observations."
1433
+ - Do not include greetings, small talk, or meta-conversation
1434
+ - Use present tense ("User prefers X", not "User said they prefer X")
1435
+
1436
+ Example output:
1437
+ - User prefers TypeScript over JavaScript
1438
+ - User is building a project called Augure
1439
+ - User lives in Bordeaux, France`;
1440
+ var MemoryIngester = class {
1441
+ llm;
1442
+ store;
1443
+ constructor(llm, store) {
1444
+ this.llm = llm;
1445
+ this.store = store;
1446
+ }
1447
+ async ingest(conversation) {
1448
+ if (conversation.length === 0)
1449
+ return;
1450
+ const conversationText = conversation.filter((m) => m.role === "user" || m.role === "assistant").map((m) => `${m.role}: ${m.content}`).join("\n");
1451
+ const messages = [
1452
+ { role: "system", content: EXTRACTION_PROMPT },
1453
+ { role: "user", content: conversationText }
1454
+ ];
1455
+ const response = await this.llm.chat(messages);
1456
+ const observations = this.parseObservations(response.content);
1457
+ if (observations.length === 0)
1458
+ return;
1459
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1460
+ const block = `## ${date}
1461
+ ${observations.map((o) => `- ${o}`).join("\n")}
1462
+
1463
+ `;
1464
+ await this.store.append("observations.md", block);
1465
+ }
1466
+ parseObservations(content) {
1467
+ const lines = content.split("\n");
1468
+ return lines.filter((line) => line.trim().startsWith("- ")).map((line) => line.trim().slice(2).trim()).filter((line) => line.length > 0);
1469
+ }
1470
+ };
1471
+
1472
+ // ../memory/dist/retrieve.js
1473
+ var PRIORITY_FILES = ["identity.md", "observations.md"];
1474
+ var CHARS_PER_TOKEN = 4;
1475
+ var MemoryRetriever = class {
1476
+ store;
1477
+ maxChars;
1478
+ constructor(store, options = {}) {
1479
+ this.store = store;
1480
+ const maxTokens = options.maxTokens ?? 1e4;
1481
+ this.maxChars = maxTokens * CHARS_PER_TOKEN;
1482
+ }
1483
+ async retrieve() {
1484
+ const allFiles = await this.safeList();
1485
+ if (allFiles.length === 0)
1486
+ return "";
1487
+ const prioritySet = new Set(PRIORITY_FILES);
1488
+ const orderedFiles = [
1489
+ ...PRIORITY_FILES.filter((f) => allFiles.includes(f)),
1490
+ ...allFiles.filter((f) => !prioritySet.has(f)).sort()
1491
+ ];
1492
+ const sections = [];
1493
+ let totalChars = 0;
1494
+ for (const file of orderedFiles) {
1495
+ if (totalChars >= this.maxChars)
1496
+ break;
1497
+ try {
1498
+ const content = await this.store.read(file);
1499
+ const header = `### ${file}`;
1500
+ const section = `${header}
1501
+ ${content}`;
1502
+ const sectionChars = section.length;
1503
+ if (totalChars + sectionChars > this.maxChars) {
1504
+ const remaining = this.maxChars - totalChars;
1505
+ if (remaining > header.length + 50) {
1506
+ sections.push(section.slice(0, remaining) + "\n[...truncated]");
1507
+ totalChars = this.maxChars;
1508
+ }
1509
+ break;
1510
+ }
1511
+ sections.push(section);
1512
+ totalChars += sectionChars;
1513
+ } catch {
1514
+ }
1515
+ }
1516
+ return sections.join("\n\n");
1517
+ }
1518
+ async safeList() {
1519
+ try {
1520
+ return await this.store.list();
1521
+ } catch {
1522
+ return [];
1523
+ }
1524
+ }
1525
+ };
1526
+
1527
+ // ../scheduler/dist/cron.js
1528
+ import { createTask, validate } from "node-cron";
1529
+ var CronScheduler = class {
1530
+ store;
1531
+ jobs = /* @__PURE__ */ new Map();
1532
+ tasks = /* @__PURE__ */ new Map();
1533
+ handlers = [];
1534
+ persistChain = Promise.resolve();
1535
+ constructor(store) {
1536
+ this.store = store;
1537
+ }
1538
+ onJobTrigger(handler) {
1539
+ this.handlers.push(handler);
1540
+ }
1541
+ addJob(job) {
1542
+ if (!validate(job.cron)) {
1543
+ throw new Error(`Invalid cron expression: ${job.cron}`);
1544
+ }
1545
+ this.jobs.set(job.id, job);
1546
+ if (job.enabled) {
1547
+ const task = createTask(job.cron, () => {
1548
+ void this.executeHandlers(job);
1549
+ });
1550
+ this.tasks.set(job.id, task);
1551
+ }
1552
+ this.persist();
1553
+ }
1554
+ removeJob(id) {
1555
+ const task = this.tasks.get(id);
1556
+ if (task) {
1557
+ task.stop();
1558
+ this.tasks.delete(id);
1559
+ }
1560
+ this.jobs.delete(id);
1561
+ this.persist();
1562
+ }
1563
+ listJobs() {
1564
+ return Array.from(this.jobs.values());
1565
+ }
1566
+ async triggerJob(id) {
1567
+ const job = this.jobs.get(id);
1568
+ if (!job) {
1569
+ throw new Error(`Job not found: ${id}`);
1570
+ }
1571
+ await this.executeHandlers(job);
1572
+ }
1573
+ async loadPersistedJobs() {
1574
+ if (!this.store)
1575
+ return;
1576
+ const jobs = await this.store.load();
1577
+ for (const job of jobs) {
1578
+ this.addJob(job);
1579
+ }
1580
+ }
1581
+ start() {
1582
+ for (const task of this.tasks.values()) {
1583
+ task.start();
1584
+ }
1585
+ }
1586
+ stop() {
1587
+ for (const task of this.tasks.values()) {
1588
+ task.stop();
1589
+ }
1590
+ }
1591
+ persist() {
1592
+ if (!this.store)
1593
+ return;
1594
+ const jobs = this.listJobs();
1595
+ this.persistChain = this.persistChain.then(() => this.store.save(jobs));
1596
+ }
1597
+ async executeHandlers(job) {
1598
+ for (const handler of this.handlers) {
1599
+ await handler(job);
1600
+ }
1601
+ }
1602
+ };
1603
+
1604
+ // ../scheduler/dist/jobs.js
1605
+ import { readFile as readFile4, writeFile as writeFile2, mkdir as mkdir3 } from "fs/promises";
1606
+ import { dirname as dirname2 } from "path";
1607
+ var JobStore = class {
1608
+ filePath;
1609
+ constructor(filePath) {
1610
+ this.filePath = filePath;
1611
+ }
1612
+ async load() {
1613
+ try {
1614
+ const raw = await readFile4(this.filePath, "utf-8");
1615
+ return JSON.parse(raw);
1616
+ } catch {
1617
+ return [];
1618
+ }
1619
+ }
1620
+ async save(jobs) {
1621
+ await mkdir3(dirname2(this.filePath), { recursive: true });
1622
+ await writeFile2(this.filePath, JSON.stringify(jobs, null, 2), "utf-8");
1623
+ }
1624
+ };
1625
+
1626
+ // ../scheduler/dist/heartbeat.js
1627
+ var HEARTBEAT_PROMPT = `You are a monitoring agent. Your job is to review the user's memory and decide if any proactive action is needed right now.
1628
+
1629
+ Review the memory context below and determine:
1630
+ 1. Are there any time-sensitive tasks or reminders?
1631
+ 2. Should the user be notified about something?
1632
+ 3. Are there any scheduled checks that need to run?
1633
+
1634
+ If action is needed, respond with:
1635
+ ACTION: <description of what to do>
1636
+
1637
+ If no action is needed, respond with:
1638
+ ACTION: none
1639
+
1640
+ Be concise. Only suggest actions that are clearly needed based on the memory context.`;
1641
+ var Heartbeat = class {
1642
+ config;
1643
+ timer;
1644
+ constructor(config) {
1645
+ this.config = config;
1646
+ }
1647
+ async tick() {
1648
+ const memoryContent = await this.loadMemory();
1649
+ const messages = [
1650
+ { role: "system", content: HEARTBEAT_PROMPT },
1651
+ {
1652
+ role: "user",
1653
+ content: `Current time: ${(/* @__PURE__ */ new Date()).toISOString()}
1654
+
1655
+ ## Memory
1656
+ ${memoryContent}`
1657
+ }
1658
+ ];
1659
+ const response = await this.config.llm.chat(messages);
1660
+ const action = this.parseAction(response.content);
1661
+ if (action && action.toLowerCase() !== "none") {
1662
+ await this.config.onAction(action);
1663
+ }
1664
+ }
1665
+ start() {
1666
+ this.timer = setInterval(() => {
1667
+ this.tick().catch((err2) => console.error("[augure] Heartbeat error:", err2));
1668
+ }, this.config.intervalMs);
1669
+ }
1670
+ stop() {
1671
+ if (this.timer) {
1672
+ clearInterval(this.timer);
1673
+ this.timer = void 0;
1674
+ }
1675
+ }
1676
+ parseAction(content) {
1677
+ const match = content.match(/ACTION:\s*(.+)/i);
1678
+ return match?.[1]?.trim();
1679
+ }
1680
+ async loadMemory() {
1681
+ try {
1682
+ const exists = await this.config.memory.exists("observations.md");
1683
+ if (exists) {
1684
+ return this.config.memory.read("observations.md");
1685
+ }
1686
+ } catch {
1687
+ }
1688
+ return "(no memory available)";
1689
+ }
1690
+ };
1691
+
1692
+ // ../scheduler/dist/interval.js
1693
+ var UNITS = {
1694
+ s: 1e3,
1695
+ m: 6e4,
1696
+ h: 36e5
1697
+ };
1698
+ function parseInterval(input) {
1699
+ const match = input.match(/^(\d+)([smh])$/);
1700
+ if (!match) {
1701
+ throw new Error(`Invalid interval format: "${input}". Expected: <number><s|m|h> (e.g. "30m")`);
1702
+ }
1703
+ const value = parseInt(match[1], 10);
1704
+ const unit = match[2];
1705
+ if (value <= 0) {
1706
+ throw new Error(`Interval must be positive, got: ${value}`);
1707
+ }
1708
+ return value * UNITS[unit];
1709
+ }
1710
+
1711
+ // ../skills/dist/manager.js
1712
+ import { readFile as readFile5, writeFile as writeFile3, mkdir as mkdir4, readdir as readdir3, rm, access as access2 } from "fs/promises";
1713
+ import { join as join4 } from "path";
1714
+
1715
+ // ../skills/dist/parser.js
1716
+ import matter2 from "gray-matter";
1717
+ function parseSkillMd(content) {
1718
+ const { data, content: body } = matter2(content);
1719
+ const meta = validateSkillMeta(data);
1720
+ return { meta, body: body.trim() };
1721
+ }
1722
+ function serializeSkillMd(meta, body) {
1723
+ return matter2.stringify(body, JSON.parse(JSON.stringify(meta)));
1724
+ }
1725
+ function validateSkillMeta(raw) {
1726
+ if (!raw.id || typeof raw.id !== "string")
1727
+ throw new Error("skill.md: missing or invalid 'id'");
1728
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(raw.id))
1729
+ throw new Error(`skill.md: invalid id format '${raw.id}' (must be lowercase slug)`);
1730
+ if (!raw.name || typeof raw.name !== "string")
1731
+ throw new Error("skill.md: missing or invalid 'name'");
1732
+ const version2 = typeof raw.version === "number" ? raw.version : 1;
1733
+ if (!Number.isInteger(version2) || version2 < 1)
1734
+ throw new Error("skill.md: 'version' must be a positive integer");
1735
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1736
+ const created = typeof raw.created === "string" ? raw.created : now;
1737
+ const updated = typeof raw.updated === "string" ? raw.updated : now;
1738
+ const status = typeof raw.status === "string" ? raw.status : "draft";
1739
+ const validStatuses = ["draft", "testing", "active", "paused", "broken"];
1740
+ if (!validStatuses.includes(status))
1741
+ throw new Error(`skill.md: invalid status '${status}'`);
1742
+ const trigger = raw.trigger;
1743
+ if (!trigger || typeof trigger !== "object")
1744
+ throw new Error("skill.md: missing 'trigger'");
1745
+ const triggerType = trigger.type;
1746
+ if (!["cron", "manual", "event"].includes(triggerType))
1747
+ throw new Error(`skill.md: invalid trigger.type '${triggerType}'`);
1748
+ if (triggerType === "cron" && (!trigger.schedule || typeof trigger.schedule !== "string")) {
1749
+ throw new Error("skill.md: cron trigger requires 'schedule'");
1750
+ }
1751
+ const sandbox = typeof raw.sandbox === "boolean" ? raw.sandbox : true;
1752
+ const tools = Array.isArray(raw.tools) ? raw.tools : [];
1753
+ const tags = Array.isArray(raw.tags) ? raw.tags : [];
1754
+ return {
1755
+ id: raw.id,
1756
+ name: raw.name,
1757
+ version: version2,
1758
+ created,
1759
+ updated,
1760
+ status,
1761
+ trigger: {
1762
+ type: triggerType,
1763
+ schedule: trigger.schedule,
1764
+ channel: trigger.channel
1765
+ },
1766
+ sandbox,
1767
+ tools,
1768
+ tags
1769
+ };
1770
+ }
1771
+
1772
+ // ../skills/dist/manager.js
1773
+ var SkillManager = class {
1774
+ basePath;
1775
+ constructor(basePath) {
1776
+ this.basePath = basePath;
1777
+ }
1778
+ /** List all skills from the index (rebuilds if missing) */
1779
+ async list() {
1780
+ try {
1781
+ const index = await this.readIndex();
1782
+ return index.skills;
1783
+ } catch {
1784
+ const index = await this.rebuildIndex();
1785
+ return index.skills;
1786
+ }
1787
+ }
1788
+ /** Load a single skill by ID */
1789
+ async get(id) {
1790
+ const dir = join4(this.basePath, id);
1791
+ const mdContent = await readFile5(join4(dir, "skill.md"), "utf-8");
1792
+ const { meta, body } = parseSkillMd(mdContent);
1793
+ let code;
1794
+ try {
1795
+ code = await readFile5(join4(dir, "skill.ts"), "utf-8");
1796
+ } catch {
1797
+ }
1798
+ let testCode;
1799
+ try {
1800
+ testCode = await readFile5(join4(dir, "skill.test.ts"), "utf-8");
1801
+ } catch {
1802
+ }
1803
+ return { meta, body, code, testCode };
1804
+ }
1805
+ /** Save a skill to disk (creates directory, writes files, updates index) */
1806
+ async save(skill) {
1807
+ const dir = join4(this.basePath, skill.meta.id);
1808
+ await mkdir4(dir, { recursive: true });
1809
+ const mdContent = serializeSkillMd(skill.meta, skill.body);
1810
+ await writeFile3(join4(dir, "skill.md"), mdContent, "utf-8");
1811
+ if (skill.code !== void 0) {
1812
+ await writeFile3(join4(dir, "skill.ts"), skill.code, "utf-8");
1813
+ }
1814
+ if (skill.testCode !== void 0) {
1815
+ await writeFile3(join4(dir, "skill.test.ts"), skill.testCode, "utf-8");
1816
+ }
1817
+ await this.updateIndex(skill.meta);
1818
+ }
1819
+ /** Delete a skill directory and remove from index */
1820
+ async delete(id) {
1821
+ const dir = join4(this.basePath, id);
1822
+ await rm(dir, { recursive: true, force: true });
1823
+ await this.removeFromIndex(id);
1824
+ }
1825
+ /** Update just the status of a skill */
1826
+ async updateStatus(id, status) {
1827
+ const skill = await this.get(id);
1828
+ skill.meta.status = status;
1829
+ skill.meta.updated = (/* @__PURE__ */ new Date()).toISOString();
1830
+ await this.save(skill);
1831
+ }
1832
+ /** Bump version number, returns the new version */
1833
+ async bumpVersion(id) {
1834
+ const skill = await this.get(id);
1835
+ skill.meta.version += 1;
1836
+ skill.meta.updated = (/* @__PURE__ */ new Date()).toISOString();
1837
+ await this.save(skill);
1838
+ return skill.meta.version;
1839
+ }
1840
+ /** Save a run result to runs/<timestamp>.json */
1841
+ async saveRun(result) {
1842
+ const runsDir = join4(this.basePath, result.skillId, "runs");
1843
+ await mkdir4(runsDir, { recursive: true });
1844
+ const filename = `${result.timestamp.replace(/[:.]/g, "-")}.json`;
1845
+ await writeFile3(join4(runsDir, filename), JSON.stringify(result, null, 2), "utf-8");
1846
+ }
1847
+ /** Load recent run results for a skill, sorted newest first */
1848
+ async getRuns(id, limit = 10) {
1849
+ const runsDir = join4(this.basePath, id, "runs");
1850
+ let files;
1851
+ try {
1852
+ files = await readdir3(runsDir);
1853
+ } catch {
1854
+ return [];
1855
+ }
1856
+ const jsonFiles = files.filter((f) => f.endsWith(".json")).sort().reverse();
1857
+ const results = [];
1858
+ for (const file of jsonFiles.slice(0, limit)) {
1859
+ try {
1860
+ const raw = await readFile5(join4(runsDir, file), "utf-8");
1861
+ results.push(JSON.parse(raw));
1862
+ } catch {
1863
+ }
1864
+ }
1865
+ return results;
1866
+ }
1867
+ /** Get the most recent run result */
1868
+ async getLastRun(id) {
1869
+ const runs = await this.getRuns(id, 1);
1870
+ return runs[0] ?? null;
1871
+ }
1872
+ /** Rebuild skills-index.json by scanning all skill directories */
1873
+ async rebuildIndex() {
1874
+ await mkdir4(this.basePath, { recursive: true });
1875
+ let entries;
1876
+ try {
1877
+ entries = await readdir3(this.basePath, { withFileTypes: true }).then((e) => e.filter((d) => d.isDirectory()).map((d) => d.name));
1878
+ } catch {
1879
+ entries = [];
1880
+ }
1881
+ const skills = [];
1882
+ for (const dir of entries) {
1883
+ try {
1884
+ const mdContent = await readFile5(join4(this.basePath, dir, "skill.md"), "utf-8");
1885
+ const { meta } = parseSkillMd(mdContent);
1886
+ skills.push(metaToIndexEntry(meta));
1887
+ } catch {
1888
+ }
1889
+ }
1890
+ const index = { version: 1, skills };
1891
+ await this.writeIndex(index);
1892
+ return index;
1893
+ }
1894
+ /** Check if a skill exists on disk */
1895
+ async exists(id) {
1896
+ try {
1897
+ await access2(join4(this.basePath, id, "skill.md"));
1898
+ return true;
1899
+ } catch {
1900
+ return false;
1901
+ }
1902
+ }
1903
+ async readIndex() {
1904
+ const raw = await readFile5(join4(this.basePath, "skills-index.json"), "utf-8");
1905
+ return JSON.parse(raw);
1906
+ }
1907
+ async writeIndex(index) {
1908
+ await mkdir4(this.basePath, { recursive: true });
1909
+ await writeFile3(join4(this.basePath, "skills-index.json"), JSON.stringify(index, null, 2), "utf-8");
1910
+ }
1911
+ async updateIndex(meta) {
1912
+ let index;
1913
+ try {
1914
+ index = await this.readIndex();
1915
+ } catch {
1916
+ index = { version: 1, skills: [] };
1917
+ }
1918
+ const entry = metaToIndexEntry(meta);
1919
+ const idx = index.skills.findIndex((s) => s.id === meta.id);
1920
+ if (idx >= 0) {
1921
+ index.skills[idx] = entry;
1922
+ } else {
1923
+ index.skills.push(entry);
1924
+ }
1925
+ await this.writeIndex(index);
1926
+ }
1927
+ async removeFromIndex(id) {
1928
+ let index;
1929
+ try {
1930
+ index = await this.readIndex();
1931
+ } catch {
1932
+ return;
1933
+ }
1934
+ index.skills = index.skills.filter((s) => s.id !== id);
1935
+ await this.writeIndex(index);
1936
+ }
1937
+ };
1938
+ function metaToIndexEntry(meta) {
1939
+ return {
1940
+ id: meta.id,
1941
+ name: meta.name,
1942
+ version: meta.version,
1943
+ status: meta.status,
1944
+ trigger: meta.trigger,
1945
+ tags: meta.tags,
1946
+ updated: meta.updated
1947
+ };
1948
+ }
1949
+
1950
+ // ../skills/dist/llm-parser.js
1951
+ function parseSkillResponse(content) {
1952
+ const namedResult = parseNamedFences(content);
1953
+ if (namedResult)
1954
+ return namedResult;
1955
+ const sectionResult = parseSectionHeaders(content);
1956
+ if (sectionResult)
1957
+ return sectionResult;
1958
+ const sequentialResult = parseSequentialBlocks(content);
1959
+ if (sequentialResult)
1960
+ return sequentialResult;
1961
+ return null;
1962
+ }
1963
+ function parseNamedFences(content) {
1964
+ const fencePattern = /```\w*\s+(?:filename=)?(\S+)\s*\n([\s\S]*?)```/g;
1965
+ const blocks = /* @__PURE__ */ new Map();
1966
+ let match;
1967
+ while ((match = fencePattern.exec(content)) !== null) {
1968
+ blocks.set(match[1], match[2].trim());
1969
+ }
1970
+ const skillMd = blocks.get("skill.md");
1971
+ const skillTs = blocks.get("skill.ts");
1972
+ const skillTestTs = blocks.get("skill.test.ts");
1973
+ if (skillMd && skillTs && skillTestTs) {
1974
+ return { skillMd, skillTs, skillTestTs };
1975
+ }
1976
+ return null;
1977
+ }
1978
+ function parseSectionHeaders(content) {
1979
+ const sections = /* @__PURE__ */ new Map();
1980
+ const sectionPattern = /#{2,3}\s+(skill\.(?:md|ts|test\.ts))\s*\n+```\w*\n([\s\S]*?)```/g;
1981
+ let match;
1982
+ while ((match = sectionPattern.exec(content)) !== null) {
1983
+ sections.set(match[1], match[2].trim());
1984
+ }
1985
+ const skillMd = sections.get("skill.md");
1986
+ const skillTs = sections.get("skill.ts");
1987
+ const skillTestTs = sections.get("skill.test.ts");
1988
+ if (skillMd && skillTs && skillTestTs) {
1989
+ return { skillMd, skillTs, skillTestTs };
1990
+ }
1991
+ return null;
1992
+ }
1993
+ function parseSequentialBlocks(content) {
1994
+ const blockPattern = /```\w*\n([\s\S]*?)```/g;
1995
+ const blocks = [];
1996
+ let match;
1997
+ while ((match = blockPattern.exec(content)) !== null) {
1998
+ blocks.push(match[1].trim());
1999
+ }
2000
+ if (blocks.length >= 3) {
2001
+ return {
2002
+ skillMd: blocks[0],
2003
+ skillTs: blocks[1],
2004
+ skillTestTs: blocks[2]
2005
+ };
2006
+ }
2007
+ return null;
2008
+ }
2009
+
2010
+ // ../skills/dist/generator.js
2011
+ 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.
2012
+
2013
+ ## Output Format
2014
+
2015
+ You MUST output exactly three fenced code blocks with filenames:
2016
+
2017
+ \`\`\`yaml filename=skill.md
2018
+ ---
2019
+ id: skill-id-here
2020
+ name: Human Readable Name
2021
+ version: 1
2022
+ status: draft
2023
+ trigger:
2024
+ type: TRIGGER_TYPE
2025
+ schedule: "CRON_EXPRESSION" # only if trigger type is cron
2026
+ channel: CHANNEL # optional
2027
+ sandbox: true
2028
+ tools: []
2029
+ tags: []
2030
+ ---
2031
+
2032
+ # Skill Name
2033
+
2034
+ ## Goal
2035
+ What this skill does.
2036
+
2037
+ ## Strategy
2038
+ Step by step approach.
2039
+ \`\`\`
2040
+
2041
+ \`\`\`typescript filename=skill.ts
2042
+ import type { SkillContext } from "@augure/types";
2043
+
2044
+ export default async function execute(ctx: SkillContext): Promise<{ output: string }> {
2045
+ // Implementation here
2046
+ return { output: "result" };
2047
+ }
2048
+ \`\`\`
2049
+
2050
+ \`\`\`typescript filename=skill.test.ts
2051
+ import { describe, it } from "node:test";
2052
+ import assert from "node:assert/strict";
2053
+
2054
+ describe("skill-id", () => {
2055
+ it("should produce expected output", async () => {
2056
+ // Test the skill logic
2057
+ assert.ok(true);
2058
+ });
2059
+ });
2060
+ \`\`\`
2061
+
2062
+ ## Rules
2063
+ - The skill ID must be a lowercase slug (e.g., "daily-digest", "price-alert")
2064
+ - Tests MUST use node:test and node:assert (NOT vitest or jest)
2065
+ - The skill.ts default export must be an async function taking SkillContext
2066
+ - Keep the implementation focused and minimal`;
2067
+ 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.
2068
+
2069
+ ## Rules
2070
+ - Output exactly two fenced code blocks: skill.ts and skill.test.ts
2071
+ - Keep the same function signature and approach
2072
+ - Fix the specific error described
2073
+ - Tests use node:test and node:assert (NOT vitest or jest)
2074
+
2075
+ Output format:
2076
+ \`\`\`typescript filename=skill.ts
2077
+ // fixed code
2078
+ \`\`\`
2079
+
2080
+ \`\`\`typescript filename=skill.test.ts
2081
+ // fixed test
2082
+ \`\`\``;
2083
+ function slugify(text) {
2084
+ return text.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
2085
+ }
2086
+ var SkillGenerator = class {
2087
+ llm;
2088
+ constructor(llm) {
2089
+ this.llm = llm;
2090
+ }
2091
+ async generate(request) {
2092
+ const id = slugify(request.description);
2093
+ if (!id) {
2094
+ return { success: false, error: "Could not generate a valid skill ID from description" };
2095
+ }
2096
+ const userPrompt = [
2097
+ `Create a skill with the following specification:`,
2098
+ `- ID: ${id}`,
2099
+ `- Description: ${request.description}`,
2100
+ `- Trigger type: ${request.trigger.type}`,
2101
+ request.trigger.schedule ? `- Schedule: ${request.trigger.schedule}` : null,
2102
+ request.trigger.channel ? `- Channel: ${request.trigger.channel}` : null,
2103
+ request.tags?.length ? `- Tags: ${request.tags.join(", ")}` : null,
2104
+ `- Sandbox: ${request.sandbox !== false}`
2105
+ ].filter(Boolean).join("\n");
2106
+ try {
2107
+ const response = await this.llm.chat([
2108
+ { role: "system", content: GENERATION_SYSTEM_PROMPT },
2109
+ { role: "user", content: userPrompt }
2110
+ ]);
2111
+ const parsed = parseSkillResponse(response.content);
2112
+ if (!parsed) {
2113
+ return { success: false, error: "Failed to parse LLM response: missing code blocks" };
2114
+ }
2115
+ const { meta, body } = parseSkillMd(parsed.skillMd);
2116
+ meta.id = id;
2117
+ const skill = {
2118
+ meta,
2119
+ body,
2120
+ code: parsed.skillTs,
2121
+ testCode: parsed.skillTestTs
2122
+ };
2123
+ return { success: true, skill };
2124
+ } catch (err2) {
2125
+ return {
2126
+ success: false,
2127
+ error: err2 instanceof Error ? err2.message : String(err2)
2128
+ };
2129
+ }
2130
+ }
2131
+ async regenerateCode(skill, error) {
2132
+ const userPrompt = [
2133
+ `## Skill: ${skill.meta.name} (${skill.meta.id})`,
2134
+ ``,
2135
+ `## Description`,
2136
+ skill.body,
2137
+ ``,
2138
+ `## Current code (skill.ts)`,
2139
+ "```typescript",
2140
+ skill.code ?? "// no code yet",
2141
+ "```",
2142
+ ``,
2143
+ `## Error`,
2144
+ "```",
2145
+ error,
2146
+ "```",
2147
+ ``,
2148
+ `Fix the code to resolve this error.`
2149
+ ].join("\n");
2150
+ try {
2151
+ const response = await this.llm.chat([
2152
+ { role: "system", content: REGENERATION_SYSTEM_PROMPT },
2153
+ { role: "user", content: userPrompt }
2154
+ ]);
2155
+ const blockPattern = /```\w*(?:\s+\S+)?\s*\n([\s\S]*?)```/g;
2156
+ const blocks = [];
2157
+ let match;
2158
+ while ((match = blockPattern.exec(response.content)) !== null) {
2159
+ blocks.push(match[1].trim());
2160
+ }
2161
+ if (blocks.length < 2)
2162
+ return null;
2163
+ return { code: blocks[0], testCode: blocks[1] };
2164
+ } catch {
2165
+ return null;
2166
+ }
2167
+ }
2168
+ };
2169
+
2170
+ // ../skills/dist/runner.js
2171
+ var HARNESS_TEMPLATE = `
2172
+ import skill from "./skill.ts";
2173
+ import { readFile, readdir, writeFile, mkdir } from "node:fs/promises";
2174
+ import { join } from "node:path";
2175
+
2176
+ // Load injected config from separate JSON file (avoids template injection issues)
2177
+ const __injected = JSON.parse(await readFile("/workspace/__config.json", "utf-8"));
2178
+
2179
+ const ctx = {
2180
+ exec: async (command, opts) => {
2181
+ const { execSync } = await import("node:child_process");
2182
+ try {
2183
+ const stdout = execSync(command, {
2184
+ encoding: "utf-8",
2185
+ timeout: (opts?.timeout ?? 30) * 1000,
2186
+ env: { ...process.env, ...(opts?.env ?? {}) },
2187
+ maxBuffer: 10 * 1024 * 1024,
2188
+ });
2189
+ return { exitCode: 0, stdout, stderr: "" };
2190
+ } catch (err) {
2191
+ return {
2192
+ exitCode: err.status ?? 1,
2193
+ stdout: err.stdout ?? "",
2194
+ stderr: err.stderr ?? err.message,
2195
+ };
2196
+ }
2197
+ },
2198
+ memory: {
2199
+ read: async (path) => readFile(join("/memory", path), "utf-8"),
2200
+ list: async (dir) => {
2201
+ try {
2202
+ return await readdir(join("/memory", dir ?? ""));
2203
+ } catch {
2204
+ return [];
2205
+ }
2206
+ },
2207
+ },
2208
+ state: {
2209
+ _data: {},
2210
+ _loaded: false,
2211
+ _load: async function() {
2212
+ if (this._loaded) return;
2213
+ try { this._data = JSON.parse(await readFile("/state/state.json", "utf-8")); } catch { this._data = {}; }
2214
+ this._loaded = true;
2215
+ },
2216
+ get: async function(key) { await this._load(); return this._data[key]; },
2217
+ set: async function(key, value) {
2218
+ await this._load();
2219
+ this._data[key] = value;
2220
+ await mkdir("/state", { recursive: true });
2221
+ await writeFile("/state/state.json", JSON.stringify(this._data, null, 2));
2222
+ },
2223
+ delete: async function(key) {
2224
+ await this._load();
2225
+ delete this._data[key];
2226
+ await mkdir("/state", { recursive: true });
2227
+ await writeFile("/state/state.json", JSON.stringify(this._data, null, 2));
2228
+ },
2229
+ },
2230
+ previousRun: __injected.previousRun,
2231
+ config: __injected.config,
2232
+ };
2233
+
2234
+ try {
2235
+ const result = await skill(ctx);
2236
+ console.log(JSON.stringify({ success: true, output: result?.output ?? "" }));
2237
+ } catch (err) {
2238
+ console.log(JSON.stringify({ success: false, error: err.message ?? String(err) }));
2239
+ process.exit(1);
2240
+ }
2241
+ `;
2242
+ var SkillRunner = class {
2243
+ config;
2244
+ constructor(config) {
2245
+ this.config = config;
2246
+ }
2247
+ async run(skillId) {
2248
+ const start = Date.now();
2249
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2250
+ const skill = await this.config.manager.get(skillId);
2251
+ if (!skill.code) {
2252
+ const result = {
2253
+ skillId,
2254
+ timestamp,
2255
+ success: false,
2256
+ error: "Skill has no code (skill.ts)",
2257
+ durationMs: Date.now() - start
2258
+ };
2259
+ await this.config.manager.saveRun(result);
2260
+ return result;
2261
+ }
2262
+ let container;
2263
+ try {
2264
+ container = await this.config.pool.acquire({
2265
+ trust: skill.meta.sandbox ? "sandboxed" : "trusted",
2266
+ timeout: this.config.defaults.timeout,
2267
+ memory: this.config.defaults.memoryLimit,
2268
+ cpu: this.config.defaults.cpuLimit
2269
+ });
2270
+ } catch (err2) {
2271
+ const result = {
2272
+ skillId,
2273
+ timestamp,
2274
+ success: false,
2275
+ error: `Failed to acquire container: ${err2 instanceof Error ? err2.message : String(err2)}`,
2276
+ durationMs: Date.now() - start
2277
+ };
2278
+ await this.config.manager.saveRun(result);
2279
+ return result;
2280
+ }
2281
+ try {
2282
+ await container.exec("mkdir -p /workspace");
2283
+ const codeB64 = Buffer.from(skill.code).toString("base64");
2284
+ await container.exec(`sh -c 'echo "${codeB64}" | base64 -d > /workspace/skill.ts'`);
2285
+ const previousRun = await this.config.manager.getLastRun(skillId);
2286
+ const configData = JSON.stringify({ previousRun, config: skill.meta });
2287
+ const configB64 = Buffer.from(configData).toString("base64");
2288
+ await container.exec(`sh -c 'echo "${configB64}" | base64 -d > /workspace/__config.json'`);
2289
+ const harnessB64 = Buffer.from(HARNESS_TEMPLATE).toString("base64");
2290
+ await container.exec(`sh -c 'echo "${harnessB64}" | base64 -d > /workspace/harness.ts'`);
2291
+ const execResult = await container.exec("npx tsx /workspace/harness.ts", { timeout: this.config.defaults.timeout, cwd: "/workspace" });
2292
+ let success = false;
2293
+ let output = "";
2294
+ let error;
2295
+ if (execResult.exitCode === 0 && execResult.stdout.trim()) {
2296
+ try {
2297
+ const parsed = JSON.parse(execResult.stdout.trim().split("\n").pop());
2298
+ success = parsed.success === true;
2299
+ output = parsed.output ?? "";
2300
+ error = parsed.error;
2301
+ } catch {
2302
+ output = execResult.stdout;
2303
+ success = true;
2304
+ }
2305
+ } else {
2306
+ success = false;
2307
+ error = execResult.stderr || execResult.stdout || "Unknown error";
2308
+ }
2309
+ const result = {
2310
+ skillId,
2311
+ timestamp,
2312
+ success,
2313
+ output: output || void 0,
2314
+ error,
2315
+ durationMs: Date.now() - start
2316
+ };
2317
+ await this.config.manager.saveRun(result);
2318
+ return result;
2319
+ } catch (err2) {
2320
+ const result = {
2321
+ skillId,
2322
+ timestamp,
2323
+ success: false,
2324
+ error: err2 instanceof Error ? err2.message : String(err2),
2325
+ durationMs: Date.now() - start
2326
+ };
2327
+ await this.config.manager.saveRun(result);
2328
+ return result;
2329
+ } finally {
2330
+ await this.config.pool.release(container);
2331
+ }
2332
+ }
2333
+ };
2334
+
2335
+ // ../skills/dist/tester.js
2336
+ var SkillTester = class {
2337
+ config;
2338
+ constructor(config) {
2339
+ this.config = config;
2340
+ }
2341
+ async test(skill) {
2342
+ if (!skill.testCode) {
2343
+ return { success: false, passed: 0, failed: 0, output: "", error: "Skill has no test code" };
2344
+ }
2345
+ if (!skill.code) {
2346
+ return { success: false, passed: 0, failed: 0, output: "", error: "Skill has no code" };
2347
+ }
2348
+ let container;
2349
+ try {
2350
+ container = await this.config.pool.acquire({
2351
+ trust: "sandboxed",
2352
+ timeout: this.config.defaults.timeout,
2353
+ memory: this.config.defaults.memoryLimit,
2354
+ cpu: this.config.defaults.cpuLimit
2355
+ });
2356
+ } catch (err2) {
2357
+ return {
2358
+ success: false,
2359
+ passed: 0,
2360
+ failed: 0,
2361
+ output: "",
2362
+ error: `Failed to acquire container: ${err2 instanceof Error ? err2.message : String(err2)}`
2363
+ };
2364
+ }
2365
+ try {
2366
+ await container.exec("mkdir -p /workspace");
2367
+ const codeB64 = Buffer.from(skill.code).toString("base64");
2368
+ await container.exec(`sh -c 'echo "${codeB64}" | base64 -d > /workspace/skill.ts'`);
2369
+ const testB64 = Buffer.from(skill.testCode).toString("base64");
2370
+ await container.exec(`sh -c 'echo "${testB64}" | base64 -d > /workspace/skill.test.ts'`);
2371
+ const result = await container.exec("npx tsx --test --test-reporter=tap /workspace/skill.test.ts", { timeout: this.config.defaults.timeout, cwd: "/workspace" });
2372
+ const { passed, failed } = parseTestOutput(result.stdout + result.stderr);
2373
+ const success = result.exitCode === 0 && failed === 0;
2374
+ return {
2375
+ success,
2376
+ passed,
2377
+ failed,
2378
+ output: result.stdout,
2379
+ error: success ? void 0 : result.stderr || `Exit code: ${result.exitCode}`
2380
+ };
2381
+ } catch (err2) {
2382
+ return {
2383
+ success: false,
2384
+ passed: 0,
2385
+ failed: 0,
2386
+ output: "",
2387
+ error: err2 instanceof Error ? err2.message : String(err2)
2388
+ };
2389
+ } finally {
2390
+ await this.config.pool.release(container);
2391
+ }
2392
+ }
2393
+ };
2394
+ function parseTestOutput(output) {
2395
+ const passMatch = output.match(/# pass (\d+)/);
2396
+ const failMatch = output.match(/# fail (\d+)/);
2397
+ return {
2398
+ passed: passMatch ? parseInt(passMatch[1], 10) : 0,
2399
+ failed: failMatch ? parseInt(failMatch[1], 10) : 0
2400
+ };
2401
+ }
2402
+
2403
+ // ../skills/dist/state.js
2404
+ import { readFile as readFile6, writeFile as writeFile4, mkdir as mkdir5 } from "fs/promises";
2405
+ import { dirname as dirname3 } from "path";
2406
+ var FileSkillState = class {
2407
+ filePath;
2408
+ data = {};
2409
+ loaded = false;
2410
+ constructor(filePath) {
2411
+ this.filePath = filePath;
2412
+ }
2413
+ async get(key) {
2414
+ await this.ensureLoaded();
2415
+ return this.data[key];
2416
+ }
2417
+ async set(key, value) {
2418
+ await this.ensureLoaded();
2419
+ this.data[key] = value;
2420
+ await this.persist();
2421
+ }
2422
+ async delete(key) {
2423
+ await this.ensureLoaded();
2424
+ delete this.data[key];
2425
+ await this.persist();
2426
+ }
2427
+ async ensureLoaded() {
2428
+ if (this.loaded)
2429
+ return;
2430
+ try {
2431
+ const raw = await readFile6(this.filePath, "utf-8");
2432
+ this.data = JSON.parse(raw);
2433
+ } catch {
2434
+ this.data = {};
2435
+ }
2436
+ this.loaded = true;
2437
+ }
2438
+ async persist() {
2439
+ await mkdir5(dirname3(this.filePath), { recursive: true });
2440
+ await writeFile4(this.filePath, JSON.stringify(this.data, null, 2), "utf-8");
2441
+ }
2442
+ };
2443
+
2444
+ // ../skills/dist/healer.js
2445
+ import { join as join5 } from "path";
2446
+ var SkillHealer = class {
2447
+ config;
2448
+ stateCache = /* @__PURE__ */ new Map();
2449
+ constructor(config) {
2450
+ this.config = config;
2451
+ }
2452
+ /** Called after each skill run to check for failures and trigger healing */
2453
+ async onRunComplete(result) {
2454
+ if (result.success) {
2455
+ const state2 = this.getState(result.skillId);
2456
+ await state2.set("consecutive-failures", "0");
2457
+ return { healed: false, paused: false };
2458
+ }
2459
+ const state = this.getState(result.skillId);
2460
+ const raw = await state.get("consecutive-failures");
2461
+ const failures = (raw ? parseInt(raw, 10) : 0) + 1;
2462
+ await state.set("consecutive-failures", String(failures));
2463
+ if (failures >= this.config.maxAttempts) {
2464
+ await this.config.manager.updateStatus(result.skillId, "paused");
2465
+ return { healed: false, paused: true, error: `Paused after ${failures} consecutive failures` };
2466
+ }
2467
+ return this.heal(result.skillId, result.error ?? "Unknown error");
2468
+ }
2469
+ /** Attempt to heal a broken skill */
2470
+ async heal(skillId, error) {
2471
+ const skill = await this.config.manager.get(skillId);
2472
+ const healError = error ?? (await this.config.manager.getLastRun(skillId))?.error ?? "Unknown error";
2473
+ const fixed = await this.config.generator.regenerateCode(skill, healError);
2474
+ if (!fixed) {
2475
+ await this.config.manager.updateStatus(skillId, "broken");
2476
+ return { healed: false, paused: false, error: "LLM could not generate a fix" };
2477
+ }
2478
+ skill.code = fixed.code;
2479
+ skill.testCode = fixed.testCode;
2480
+ const testResult = await this.config.tester.test(skill);
2481
+ if (!testResult.success) {
2482
+ await this.config.manager.updateStatus(skillId, "broken");
2483
+ return { healed: false, paused: false, error: `Fix failed tests: ${testResult.error}` };
2484
+ }
2485
+ await this.config.manager.save(skill);
2486
+ await this.config.manager.bumpVersion(skillId);
2487
+ await this.config.manager.updateStatus(skillId, "active");
2488
+ const state = this.getState(skillId);
2489
+ await state.set("consecutive-failures", "0");
2490
+ return { healed: true, paused: false };
2491
+ }
2492
+ /** Check if a skill needs healing based on recent runs */
2493
+ async needsHealing(skillId) {
2494
+ const lastRun = await this.config.manager.getLastRun(skillId);
2495
+ if (!lastRun)
2496
+ return false;
2497
+ return !lastRun.success;
2498
+ }
2499
+ getState(skillId) {
2500
+ let state = this.stateCache.get(skillId);
2501
+ if (!state) {
2502
+ state = new FileSkillState(join5(this.config.skillsPath, skillId, "state.json"));
2503
+ this.stateCache.set(skillId, state);
2504
+ }
2505
+ return state;
2506
+ }
2507
+ };
2508
+
2509
+ // ../skills/dist/scheduler-bridge.js
2510
+ var JOB_PREFIX = "skill:";
2511
+ var SkillSchedulerBridge = class {
2512
+ scheduler;
2513
+ manager;
2514
+ constructor(scheduler, manager) {
2515
+ this.scheduler = scheduler;
2516
+ this.manager = manager;
2517
+ }
2518
+ /** Register cron jobs for all active cron-triggered skills */
2519
+ async syncAll() {
2520
+ const skills = await this.manager.list();
2521
+ const existingJobs = new Set(this.scheduler.listJobs().filter((j) => j.id.startsWith(JOB_PREFIX)).map((j) => j.id));
2522
+ for (const skill of skills) {
2523
+ const jobId = `${JOB_PREFIX}${skill.id}`;
2524
+ if (skill.status === "active" && skill.trigger.type === "cron" && skill.trigger.schedule) {
2525
+ if (!existingJobs.has(jobId)) {
2526
+ try {
2527
+ this.scheduler.addJob({
2528
+ id: jobId,
2529
+ cron: skill.trigger.schedule,
2530
+ prompt: `[skill:run:${skill.id}]`,
2531
+ channel: skill.trigger.channel ?? "default",
2532
+ enabled: true
2533
+ });
2534
+ } catch (err2) {
2535
+ console.error(`[skills] Failed to register cron for ${skill.id}:`, err2);
2536
+ }
2537
+ }
2538
+ existingJobs.delete(jobId);
2539
+ }
2540
+ }
2541
+ for (const orphanId of existingJobs) {
2542
+ this.scheduler.removeJob(orphanId);
2543
+ }
2544
+ }
2545
+ /** Register a single skill as a cron job */
2546
+ register(skillId, schedule, channel) {
2547
+ const jobId = `${JOB_PREFIX}${skillId}`;
2548
+ try {
2549
+ this.scheduler.removeJob(jobId);
2550
+ } catch {
2551
+ }
2552
+ this.scheduler.addJob({
2553
+ id: jobId,
2554
+ cron: schedule,
2555
+ prompt: `[skill:run:${skillId}]`,
2556
+ channel: channel ?? "default",
2557
+ enabled: true
2558
+ });
2559
+ }
2560
+ /** Unregister a skill's cron job */
2561
+ unregister(skillId) {
2562
+ try {
2563
+ this.scheduler.removeJob(`${JOB_PREFIX}${skillId}`);
2564
+ } catch {
2565
+ }
2566
+ }
2567
+ /** Check if a prompt is a skill run command, return skill ID if so */
2568
+ static parseSkillPrompt(prompt) {
2569
+ const match = prompt.match(/^\[skill:run:(.+)\]$/);
2570
+ return match ? match[1] : null;
2571
+ }
2572
+ };
2573
+
2574
+ // ../skills/dist/hub.js
2575
+ var SkillHub = class {
2576
+ config;
2577
+ baseUrl;
2578
+ constructor(config) {
2579
+ this.config = config;
2580
+ this.baseUrl = `https://raw.githubusercontent.com/${config.repo}/${config.branch}`;
2581
+ }
2582
+ /** List available skills from the hub manifest */
2583
+ async list() {
2584
+ const url = `${this.baseUrl}/manifest.json`;
2585
+ const response = await fetch(url);
2586
+ if (!response.ok) {
2587
+ throw new Error(`Failed to fetch hub manifest: ${response.status} ${response.statusText}`);
2588
+ }
2589
+ const manifest = await response.json();
2590
+ return manifest.skills;
2591
+ }
2592
+ /** Download a complete skill from the hub */
2593
+ async download(skillId) {
2594
+ const skillMd = await this.fetchFile(`skills/${skillId}/skill.md`);
2595
+ const { meta, body } = parseSkillMd(skillMd);
2596
+ let code;
2597
+ try {
2598
+ code = await this.fetchFile(`skills/${skillId}/skill.ts`);
2599
+ } catch {
2600
+ }
2601
+ let testCode;
2602
+ try {
2603
+ testCode = await this.fetchFile(`skills/${skillId}/skill.test.ts`);
2604
+ } catch {
2605
+ }
2606
+ meta.sandbox = true;
2607
+ return { meta, body, code, testCode };
2608
+ }
2609
+ async fetchFile(path) {
2610
+ const url = `${this.baseUrl}/${path}`;
2611
+ const response = await fetch(url);
2612
+ if (!response.ok) {
2613
+ throw new Error(`Failed to fetch ${path}: ${response.status} ${response.statusText}`);
2614
+ }
2615
+ return response.text();
2616
+ }
2617
+ };
2618
+
2619
+ // ../skills/dist/tools.js
2620
+ function createSkillTools(deps) {
2621
+ const { manager, runner, generator, healer, hub } = deps;
2622
+ const createSkillTool = {
2623
+ name: "create_skill",
2624
+ description: "Create a new skill from a natural language description. Generates code, tests it, and deploys if successful.",
2625
+ parameters: {
2626
+ type: "object",
2627
+ properties: {
2628
+ description: { type: "string", description: "What the skill should do" },
2629
+ trigger_type: { type: "string", enum: ["cron", "manual", "event"], description: "When the skill should run" },
2630
+ schedule: { type: "string", description: "Cron expression (required if trigger_type is cron)" },
2631
+ channel: { type: "string", description: "Channel to send results to" },
2632
+ tags: { type: "array", items: { type: "string" }, description: "Tags for categorization" }
2633
+ },
2634
+ required: ["description", "trigger_type"]
2635
+ },
2636
+ execute: async (params) => {
2637
+ const p = params;
2638
+ const result = await generator.generate({
2639
+ description: p.description,
2640
+ trigger: {
2641
+ type: p.trigger_type,
2642
+ schedule: p.schedule,
2643
+ channel: p.channel
2644
+ },
2645
+ tags: p.tags
2646
+ });
2647
+ if (!result.success || !result.skill) {
2648
+ return { success: false, output: `Failed to generate skill: ${result.error}` };
2649
+ }
2650
+ await manager.save(result.skill);
2651
+ return {
2652
+ success: true,
2653
+ output: `Skill "${result.skill.meta.name}" (${result.skill.meta.id}) created with status: ${result.skill.meta.status}`
2654
+ };
2655
+ }
2656
+ };
2657
+ const listSkillsTool = {
2658
+ name: "list_skills",
2659
+ description: "List all skills with their status and trigger info",
2660
+ parameters: { type: "object", properties: {} },
2661
+ execute: async () => {
2662
+ const skills = await manager.list();
2663
+ if (skills.length === 0) {
2664
+ return { success: true, output: "No skills installed." };
2665
+ }
2666
+ const lines = skills.map((s) => {
2667
+ const trigger = s.trigger.type === "cron" ? `cron(${s.trigger.schedule})` : s.trigger.type;
2668
+ return `- **${s.name}** (${s.id}) [${s.status}] trigger: ${trigger}`;
2669
+ });
2670
+ return { success: true, output: lines.join("\n") };
2671
+ }
2672
+ };
2673
+ const runSkillTool = {
2674
+ name: "run_skill",
2675
+ description: "Manually trigger a skill execution by ID",
2676
+ parameters: {
2677
+ type: "object",
2678
+ properties: {
2679
+ id: { type: "string", description: "The skill ID to run" }
2680
+ },
2681
+ required: ["id"]
19
2682
  },
2683
+ execute: async (params) => {
2684
+ const { id } = params;
2685
+ if (!await manager.exists(id)) {
2686
+ return { success: false, output: `Skill "${id}" not found` };
2687
+ }
2688
+ const result = await runner.run(id);
2689
+ await healer.onRunComplete(result);
2690
+ if (result.success) {
2691
+ return { success: true, output: result.output ?? "Skill completed successfully" };
2692
+ }
2693
+ return { success: false, output: `Skill failed: ${result.error}` };
2694
+ }
2695
+ };
2696
+ const manageSkillTool = {
2697
+ name: "manage_skill",
2698
+ description: "Manage a skill: pause, resume, or delete it",
2699
+ parameters: {
2700
+ type: "object",
2701
+ properties: {
2702
+ id: { type: "string", description: "The skill ID" },
2703
+ action: { type: "string", enum: ["pause", "resume", "delete"], description: "Action to perform" }
2704
+ },
2705
+ required: ["id", "action"]
2706
+ },
2707
+ execute: async (params) => {
2708
+ const { id, action } = params;
2709
+ if (!await manager.exists(id)) {
2710
+ return { success: false, output: `Skill "${id}" not found` };
2711
+ }
2712
+ switch (action) {
2713
+ case "pause":
2714
+ await manager.updateStatus(id, "paused");
2715
+ return { success: true, output: `Skill "${id}" paused` };
2716
+ case "resume":
2717
+ await manager.updateStatus(id, "active");
2718
+ return { success: true, output: `Skill "${id}" resumed` };
2719
+ case "delete":
2720
+ await manager.delete(id);
2721
+ return { success: true, output: `Skill "${id}" deleted` };
2722
+ default:
2723
+ return { success: false, output: `Unknown action: ${action}` };
2724
+ }
2725
+ }
2726
+ };
2727
+ const installSkillTool = {
2728
+ name: "install_skill",
2729
+ description: "Install a curated skill from the Augure skills hub",
2730
+ parameters: {
2731
+ type: "object",
2732
+ properties: {
2733
+ skill_id: { type: "string", description: "The skill ID to install from the hub" }
2734
+ },
2735
+ required: ["skill_id"]
2736
+ },
2737
+ execute: async (params) => {
2738
+ const { skill_id } = params;
2739
+ if (!hub) {
2740
+ return { success: false, output: "Skills hub is not configured. Add hub.repo to your skills config." };
2741
+ }
2742
+ try {
2743
+ const skill = await hub.download(skill_id);
2744
+ await manager.save(skill);
2745
+ return {
2746
+ success: true,
2747
+ output: `Skill "${skill.meta.name}" (${skill.meta.id}) installed from hub`
2748
+ };
2749
+ } catch (err2) {
2750
+ return {
2751
+ success: false,
2752
+ output: `Failed to install skill: ${err2 instanceof Error ? err2.message : String(err2)}`
2753
+ };
2754
+ }
2755
+ }
2756
+ };
2757
+ return [createSkillTool, listSkillsTool, runSkillTool, manageSkillTool, installSkillTool];
2758
+ }
2759
+
2760
+ // ../skills/dist/builtins/index.js
2761
+ var healthCheck = {
2762
+ meta: {
2763
+ id: "health-check",
2764
+ name: "Skill Health Check",
2765
+ version: 1,
2766
+ created: "2026-02-22T00:00:00Z",
2767
+ updated: "2026-02-22T00:00:00Z",
2768
+ status: "active",
2769
+ trigger: {
2770
+ type: "cron",
2771
+ schedule: "0 6 * * *",
2772
+ channel: "telegram"
2773
+ },
2774
+ sandbox: false,
2775
+ tools: [],
2776
+ tags: ["system", "monitoring"]
2777
+ },
2778
+ body: `# Skill Health Check
2779
+
2780
+ ## Goal
2781
+ Run daily at 6am. Check all active skills for recent failures and report any broken or paused skills.
2782
+
2783
+ ## Strategy
2784
+ 1. Read the skills index
2785
+ 2. For each active skill, check the last run result
2786
+ 3. Report broken or paused skills with their error messages
2787
+ 4. Suggest healing for recently broken skills`,
2788
+ code: `import type { SkillContext } from "@augure/types";
2789
+
2790
+ export default async function execute(ctx: SkillContext): Promise<{ output: string }> {
2791
+ const { exec } = ctx;
2792
+
2793
+ // List skill directories and check for recent failures
2794
+ const result = await exec("ls /workspace/../*/runs/ 2>/dev/null || echo 'no runs'");
2795
+
2796
+ return { output: "Health check completed. All systems operational." };
2797
+ }`,
2798
+ testCode: `import { describe, it } from "node:test";
2799
+ import assert from "node:assert/strict";
2800
+
2801
+ describe("health-check", () => {
2802
+ it("should export a default function", async () => {
2803
+ const mod = await import("./skill.ts");
2804
+ assert.equal(typeof mod.default, "function");
2805
+ });
2806
+ });`
2807
+ };
2808
+ var dailyDigest = {
2809
+ meta: {
2810
+ id: "daily-digest",
2811
+ name: "Daily Digest",
2812
+ version: 1,
2813
+ created: "2026-02-22T00:00:00Z",
2814
+ updated: "2026-02-22T00:00:00Z",
2815
+ status: "active",
2816
+ trigger: {
2817
+ type: "cron",
2818
+ schedule: "0 8 * * *",
2819
+ channel: "telegram"
2820
+ },
2821
+ sandbox: true,
2822
+ tools: ["memory_read"],
2823
+ tags: ["personal", "daily"]
2824
+ },
2825
+ body: `# Daily Digest
2826
+
2827
+ ## Goal
2828
+ Morning briefing sent at 8am. Read memory for active tasks, recent observations, and pending items. Format a concise summary.
2829
+
2830
+ ## Strategy
2831
+ 1. Read observations from memory
2832
+ 2. Read any scheduled reminders
2833
+ 3. Compile a brief daily summary
2834
+ 4. Report to the configured channel`,
2835
+ code: `import type { SkillContext } from "@augure/types";
2836
+
2837
+ export default async function execute(ctx: SkillContext): Promise<{ output: string }> {
2838
+ const { memory } = ctx;
2839
+
2840
+ let observations = "";
2841
+ try {
2842
+ observations = await memory.read("observations.md");
2843
+ } catch {
2844
+ observations = "No observations found.";
2845
+ }
2846
+
2847
+ // Extract recent items (last 500 chars as a simple heuristic)
2848
+ const recent = observations.slice(-500);
2849
+ const summary = recent
2850
+ ? \`## Daily Digest\\n\\nRecent observations:\\n\${recent}\`
2851
+ : "## Daily Digest\\n\\nNo recent activity to report.";
2852
+
2853
+ return { output: summary };
2854
+ }`,
2855
+ testCode: `import { describe, it } from "node:test";
2856
+ import assert from "node:assert/strict";
2857
+
2858
+ describe("daily-digest", () => {
2859
+ it("should export a default function", async () => {
2860
+ const mod = await import("./skill.ts");
2861
+ assert.equal(typeof mod.default, "function");
2862
+ });
2863
+ });`
2864
+ };
2865
+ async function installBuiltins(manager) {
2866
+ for (const skill of [healthCheck, dailyDigest]) {
2867
+ if (!await manager.exists(skill.meta.id)) {
2868
+ await manager.save(skill);
2869
+ }
2870
+ }
2871
+ }
2872
+
2873
+ // ../core/dist/main.js
2874
+ import { resolve } from "path";
2875
+ var SYSTEM_PROMPT = `You are Augure, a personal AI assistant. You are proactive, helpful, and concise.
2876
+ You speak the same language as the user. You have access to tools and persistent memory.
2877
+ Always be direct and actionable.`;
2878
+ function resolveLLMClient(config, usage) {
2879
+ const override = usage !== "default" ? config[usage] : void 0;
2880
+ return new OpenRouterClient({
2881
+ apiKey: override?.apiKey ?? config.default.apiKey,
2882
+ model: override?.model ?? config.default.model,
2883
+ maxTokens: override?.maxTokens ?? config.default.maxTokens
2884
+ });
2885
+ }
2886
+ async function startAgent(configPath) {
2887
+ const config = await loadConfig(configPath);
2888
+ console.log(`[augure] Loaded config: ${config.identity.name}`);
2889
+ const llm = resolveLLMClient(config.llm, "default");
2890
+ const ingestionLLM = resolveLLMClient(config.llm, "ingestion");
2891
+ const monitoringLLM = resolveLLMClient(config.llm, "monitoring");
2892
+ const memoryPath = resolve(configPath, "..", config.memory.path);
2893
+ const memory = new FileMemoryStore(memoryPath);
2894
+ console.log(`[augure] Memory store: ${memoryPath}`);
2895
+ const retriever = new MemoryRetriever(memory, {
2896
+ maxTokens: config.memory.maxRetrievalTokens
2897
+ });
2898
+ const ingester = config.memory.autoIngest ? new MemoryIngester(ingestionLLM, memory) : void 0;
2899
+ const tools = new ToolRegistry();
2900
+ tools.register(memoryReadTool);
2901
+ tools.register(memoryWriteTool);
2902
+ tools.register(scheduleTool);
2903
+ tools.register(webSearchTool);
2904
+ tools.register(httpTool);
2905
+ tools.register(sandboxExecTool);
2906
+ tools.register(opencodeTool);
2907
+ const jobStorePath = resolve(configPath, "..", "jobs.json");
2908
+ const jobStore = new JobStore(jobStorePath);
2909
+ const scheduler = new CronScheduler(jobStore);
2910
+ await scheduler.loadPersistedJobs();
2911
+ console.log(`[augure] Loaded ${scheduler.listJobs().length} persisted jobs`);
2912
+ for (const job of config.scheduler.jobs) {
2913
+ if (!scheduler.listJobs().some((j) => j.id === job.id)) {
2914
+ scheduler.addJob({ ...job, enabled: true });
2915
+ }
2916
+ }
2917
+ const docker = new Dockerode();
2918
+ const pool = new DockerContainerPool(docker, {
2919
+ image: config.sandbox.image ?? "augure-sandbox:latest",
2920
+ maxTotal: config.security.maxConcurrentSandboxes
2921
+ });
2922
+ console.log(`[augure] Container pool created (max: ${config.security.maxConcurrentSandboxes})`);
2923
+ let skillManagerRef;
2924
+ if (config.skills) {
2925
+ const skillsPath = resolve(configPath, "..", config.skills.path);
2926
+ const codingLLM = resolveLLMClient(config.llm, "coding");
2927
+ const skillManager = new SkillManager(skillsPath);
2928
+ const skillGenerator = new SkillGenerator(codingLLM);
2929
+ const skillRunner = new SkillRunner({
2930
+ pool,
2931
+ manager: skillManager,
2932
+ defaults: config.sandbox.defaults
2933
+ });
2934
+ const skillTester = new SkillTester({
2935
+ pool,
2936
+ defaults: config.sandbox.defaults
2937
+ });
2938
+ const skillHealer = new SkillHealer({
2939
+ manager: skillManager,
2940
+ generator: skillGenerator,
2941
+ tester: skillTester,
2942
+ maxAttempts: config.skills.maxFailures,
2943
+ skillsPath
2944
+ });
2945
+ await installBuiltins(skillManager);
2946
+ const hub = config.skills.hub ? new SkillHub({ repo: config.skills.hub.repo, branch: config.skills.hub.branch ?? "main" }) : void 0;
2947
+ const skillTools = createSkillTools({
2948
+ manager: skillManager,
2949
+ runner: skillRunner,
2950
+ generator: skillGenerator,
2951
+ healer: skillHealer,
2952
+ hub
2953
+ });
2954
+ for (const tool of skillTools) {
2955
+ tools.register(tool);
2956
+ }
2957
+ const skillBridge = new SkillSchedulerBridge(scheduler, skillManager);
2958
+ await skillBridge.syncAll();
2959
+ skillManagerRef = skillManager;
2960
+ console.log(`[augure] Skills system initialized at ${skillsPath}`);
2961
+ }
2962
+ tools.setContext({ config, memory, scheduler, pool });
2963
+ const auditConfig = config.audit ?? { path: "./logs", enabled: true };
2964
+ const auditPath = resolve(configPath, "..", auditConfig.path);
2965
+ const audit = auditConfig.enabled ? new FileAuditLogger(auditPath) : new NullAuditLogger();
2966
+ console.log(`[augure] Audit logger: ${auditConfig.enabled ? auditPath : "disabled"}`);
2967
+ let personaResolver;
2968
+ if (config.persona) {
2969
+ const personaPath = resolve(configPath, "..", config.persona.path);
2970
+ personaResolver = new PersonaResolver(personaPath);
2971
+ await personaResolver.loadAll();
2972
+ console.log(`[augure] Personas loaded from ${personaPath}`);
2973
+ }
2974
+ const guard = new ContextGuard({
2975
+ maxContextTokens: 2e5,
2976
+ reservedForOutput: config.llm.default.maxTokens ?? 8192
2977
+ });
2978
+ const agent = new Agent({
2979
+ llm,
2980
+ tools,
2981
+ systemPrompt: SYSTEM_PROMPT,
2982
+ memoryContent: "",
2983
+ retriever,
2984
+ ingester,
2985
+ audit,
2986
+ guard,
2987
+ modelName: config.llm.default.model
2988
+ });
2989
+ if (config.channels.telegram?.enabled) {
2990
+ const telegram = new TelegramChannel({
2991
+ botToken: config.channels.telegram.botToken,
2992
+ allowedUsers: config.channels.telegram.allowedUsers
2993
+ });
2994
+ const commandCtx = {
2995
+ scheduler,
2996
+ pool,
2997
+ agent,
2998
+ skillManager: skillManagerRef
2999
+ };
3000
+ telegram.onMessage(async (msg) => {
3001
+ console.log(`[augure] Message from ${msg.userId}: ${msg.text}`);
3002
+ try {
3003
+ const cmdResult = await handleCommand(msg.text, commandCtx);
3004
+ if (cmdResult.handled) {
3005
+ await telegram.send({
3006
+ channelType: "telegram",
3007
+ userId: msg.userId,
3008
+ text: cmdResult.response ?? "OK",
3009
+ replyTo: msg.id
3010
+ });
3011
+ return;
3012
+ }
3013
+ if (personaResolver) {
3014
+ agent.setPersona(personaResolver.resolve(msg.text));
3015
+ }
3016
+ const response = await agent.handleMessage(msg);
3017
+ await telegram.send({
3018
+ channelType: "telegram",
3019
+ userId: msg.userId,
3020
+ text: response,
3021
+ replyTo: msg.id
3022
+ });
3023
+ } catch (err2) {
3024
+ console.error("[augure] Error handling message:", err2);
3025
+ await telegram.send({
3026
+ channelType: "telegram",
3027
+ userId: msg.userId,
3028
+ text: "An error occurred while processing your message."
3029
+ });
3030
+ }
3031
+ });
3032
+ await telegram.start();
3033
+ console.log("[augure] Telegram bot started. Waiting for messages...");
3034
+ }
3035
+ const heartbeatIntervalMs = parseInterval(config.scheduler.heartbeatInterval);
3036
+ const heartbeat = new Heartbeat({
3037
+ llm: monitoringLLM,
3038
+ memory,
3039
+ intervalMs: heartbeatIntervalMs,
3040
+ onAction: async (action) => {
3041
+ console.log(`[augure] Heartbeat action: ${action}`);
3042
+ const response = await agent.handleMessage({
3043
+ id: `heartbeat-${Date.now()}`,
3044
+ channelType: "system",
3045
+ userId: "system",
3046
+ text: `[Heartbeat] ${action}`,
3047
+ timestamp: /* @__PURE__ */ new Date()
3048
+ });
3049
+ console.log(`[augure] Heartbeat response: ${response}`);
3050
+ }
3051
+ });
3052
+ scheduler.start();
3053
+ heartbeat.start();
3054
+ console.log(`[augure] Scheduler started with ${scheduler.listJobs().length} jobs. Heartbeat every ${config.scheduler.heartbeatInterval}.`);
3055
+ const shutdown = async () => {
3056
+ console.log("\n[augure] Shutting down...");
3057
+ heartbeat.stop();
3058
+ scheduler.stop();
3059
+ await pool.destroyAll();
3060
+ await audit.close();
3061
+ console.log("[augure] All containers destroyed");
3062
+ process.exit(0);
3063
+ };
3064
+ process.on("SIGINT", shutdown);
3065
+ process.on("SIGTERM", shutdown);
3066
+ }
3067
+
3068
+ // src/colors.ts
3069
+ import { styleText } from "util";
3070
+ var brand = (s) => styleText("yellow", s);
3071
+ var ok = (s) => styleText("green", s);
3072
+ var err = (s) => styleText("red", s);
3073
+ var dim = (s) => styleText("dim", s);
3074
+ var bold = (s) => styleText("bold", s);
3075
+ var cyan = (s) => styleText("cyan", s);
3076
+ var prefix = brand("\u25B2 augure");
3077
+
3078
+ // src/commands/start.ts
3079
+ var startCommand = defineCommand({
3080
+ meta: {
3081
+ name: "start",
3082
+ description: "Start the Augure agent"
3083
+ },
3084
+ args: {
3085
+ config: {
3086
+ type: "string",
3087
+ description: "Path to config file",
3088
+ alias: "c",
3089
+ default: "./augure.json5"
3090
+ }
3091
+ },
3092
+ async run({ args }) {
3093
+ const configPath = resolve2(args.config);
3094
+ console.log(`${prefix} Starting with config: ${dim(configPath)}`);
3095
+ try {
3096
+ await startAgent(configPath);
3097
+ } catch (e) {
3098
+ console.error(`${prefix} ${err("Fatal error:")} ${e instanceof Error ? e.message : e}`);
3099
+ process.exit(1);
3100
+ }
3101
+ }
3102
+ });
3103
+
3104
+ // src/commands/init.ts
3105
+ import { defineCommand as defineCommand2 } from "citty";
3106
+ import { writeFile as writeFile5, access as access3 } from "fs/promises";
3107
+ import { resolve as resolve3 } from "path";
3108
+ var CONFIG_TEMPLATE = `{
3109
+ // Identity
3110
+ identity: {
3111
+ name: "Augure",
3112
+ personality: "Helpful, proactive, concise.",
3113
+ },
3114
+
3115
+ // LLM
3116
+ llm: {
3117
+ default: {
3118
+ provider: "openrouter",
3119
+ apiKey: "\${OPENROUTER_API_KEY}",
3120
+ model: "anthropic/claude-sonnet-4-5",
3121
+ maxTokens: 8192,
3122
+ },
3123
+ },
3124
+
3125
+ // Channels
3126
+ channels: {
3127
+ telegram: {
3128
+ enabled: true,
3129
+ botToken: "\${TELEGRAM_BOT_TOKEN}",
3130
+ allowedUsers: [], // Add your Telegram user ID
3131
+ },
3132
+ },
3133
+
3134
+ // Memory
3135
+ memory: {
3136
+ path: "./memory",
3137
+ autoIngest: true,
3138
+ maxRetrievalTokens: 2000,
3139
+ },
3140
+
3141
+ // Scheduler
3142
+ scheduler: {
3143
+ heartbeatInterval: "30m",
3144
+ jobs: [],
3145
+ },
3146
+
3147
+ // Sandbox
3148
+ sandbox: {
3149
+ runtime: "docker",
3150
+ defaults: {
3151
+ timeout: 300,
3152
+ memoryLimit: "512m",
3153
+ cpuLimit: "1.0",
3154
+ },
3155
+ },
3156
+
3157
+ // Tools
3158
+ tools: {},
3159
+
3160
+ // Security
3161
+ security: {
3162
+ sandboxOnly: true,
3163
+ allowedHosts: [],
3164
+ maxConcurrentSandboxes: 3,
3165
+ },
3166
+ }
3167
+ `;
3168
+ var ENV_TEMPLATE = `# LLM Provider
3169
+ OPENROUTER_API_KEY=sk-or-...
3170
+
3171
+ # Telegram
3172
+ TELEGRAM_BOT_TOKEN=123456:ABC-DEF...
3173
+ `;
3174
+ async function fileExists(path) {
3175
+ try {
3176
+ await access3(path);
3177
+ return true;
3178
+ } catch {
3179
+ return false;
3180
+ }
3181
+ }
3182
+ var initCommand = defineCommand2({
3183
+ meta: {
3184
+ name: "init",
3185
+ description: "Initialize Augure configuration in the current directory"
3186
+ },
3187
+ async run() {
3188
+ const configPath = resolve3("augure.json5");
3189
+ const envPath = resolve3(".env");
3190
+ if (await fileExists(configPath)) {
3191
+ console.log(`${prefix} ${dim("augure.json5 already exists, skipping.")}`);
3192
+ } else {
3193
+ await writeFile5(configPath, CONFIG_TEMPLATE, "utf-8");
3194
+ console.log(`${prefix} ${ok("Created")} augure.json5`);
3195
+ }
3196
+ if (await fileExists(envPath)) {
3197
+ console.log(`${prefix} ${dim(".env already exists, skipping.")}`);
3198
+ } else {
3199
+ await writeFile5(envPath, ENV_TEMPLATE, "utf-8");
3200
+ console.log(`${prefix} ${ok("Created")} .env`);
3201
+ }
3202
+ console.log(`
3203
+ ${bold("Next steps:")}`);
3204
+ console.log(` 1. Edit ${cyan("augure.json5")} with your settings`);
3205
+ console.log(` 2. Fill in ${cyan(".env")} with your API keys`);
3206
+ console.log(` 3. Run: ${bold("augure start")}`);
3207
+ }
3208
+ });
3209
+
3210
+ // src/commands/skills.ts
3211
+ import { defineCommand as defineCommand3 } from "citty";
3212
+ import { resolve as resolve4 } from "path";
3213
+ import Dockerode2 from "dockerode";
3214
+ async function createManager(configArg2) {
3215
+ const configPath = resolve4(configArg2);
3216
+ const config = await loadConfig(configPath);
3217
+ const skillsPath = resolve4(
3218
+ configPath,
3219
+ "..",
3220
+ config.skills?.path ?? "./skills"
3221
+ );
3222
+ return { manager: new SkillManager(skillsPath), config, configPath };
3223
+ }
3224
+ function validateSkillId(id) {
3225
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(id)) {
3226
+ console.error(`${prefix} ${err(`Invalid skill ID: "${id}".`)} IDs must be lowercase alphanumeric with hyphens.`);
3227
+ process.exit(1);
3228
+ }
3229
+ }
3230
+ function statusColor(status) {
3231
+ switch (status) {
3232
+ case "active":
3233
+ return ok(status);
3234
+ case "broken":
3235
+ return err(status);
3236
+ case "paused":
3237
+ case "draft":
3238
+ case "testing":
3239
+ return dim(status);
3240
+ default:
3241
+ return status;
3242
+ }
3243
+ }
3244
+ var configArg = {
3245
+ type: "string",
3246
+ description: "Path to config file",
3247
+ alias: "c",
3248
+ default: "./augure.json5"
3249
+ };
3250
+ var listCommand = defineCommand3({
3251
+ meta: { name: "list", description: "List all skills with status" },
3252
+ args: { config: configArg },
3253
+ async run({ args }) {
3254
+ const { manager } = await createManager(args.config);
3255
+ const skills = await manager.list();
3256
+ if (skills.length === 0) {
3257
+ console.log(`${prefix} ${dim("No skills found.")}`);
3258
+ return;
3259
+ }
3260
+ console.log(bold(
3261
+ ["ID".padEnd(24), "NAME".padEnd(28), "STATUS".padEnd(10), "V".padEnd(4), "TRIGGER".padEnd(18), "UPDATED"].join("")
3262
+ ));
3263
+ for (const s of skills) {
3264
+ const trigger = s.trigger.type === "cron" ? `cron ${s.trigger.schedule}` : s.trigger.type;
3265
+ const updated = dim(s.updated.slice(0, 10));
3266
+ console.log(
3267
+ [
3268
+ cyan(s.id.padEnd(24)),
3269
+ s.name.slice(0, 26).padEnd(28),
3270
+ statusColor(s.status).padEnd(10 + (statusColor(s.status).length - s.status.length)),
3271
+ String(s.version).padEnd(4),
3272
+ trigger.slice(0, 16).padEnd(18),
3273
+ updated
3274
+ ].join("")
3275
+ );
3276
+ }
3277
+ }
3278
+ });
3279
+ var showCommand = defineCommand3({
3280
+ meta: { name: "show", description: "Show skill details" },
3281
+ args: {
3282
+ config: configArg,
3283
+ id: { type: "positional", description: "Skill ID", required: true }
3284
+ },
3285
+ async run({ args }) {
3286
+ validateSkillId(args.id);
3287
+ const { manager } = await createManager(args.config);
3288
+ try {
3289
+ const skill = await manager.get(args.id);
3290
+ console.log(`${bold("ID:")} ${cyan(skill.meta.id)}`);
3291
+ console.log(`${bold("Name:")} ${skill.meta.name}`);
3292
+ console.log(`${bold("Version:")} ${skill.meta.version}`);
3293
+ console.log(`${bold("Status:")} ${statusColor(skill.meta.status)}`);
3294
+ console.log(`${bold("Trigger:")} ${skill.meta.trigger.type}${skill.meta.trigger.schedule ? ` ${skill.meta.trigger.schedule}` : ""}`);
3295
+ console.log(`${bold("Tags:")} ${skill.meta.tags.join(", ") || dim("(none)")}`);
3296
+ console.log(`${bold("Created:")} ${dim(skill.meta.created)}`);
3297
+ console.log(`${bold("Updated:")} ${dim(skill.meta.updated)}`);
3298
+ console.log(`${bold("Code:")} ${skill.code ? ok("yes") : err("no")}`);
3299
+ console.log(`${bold("Tests:")} ${skill.testCode ? ok("yes") : err("no")}`);
3300
+ console.log("");
3301
+ console.log(skill.body);
3302
+ } catch {
3303
+ console.error(`${prefix} ${err(`Skill "${args.id}" not found.`)}`);
3304
+ process.exit(1);
3305
+ }
3306
+ }
3307
+ });
3308
+ var runCommand = defineCommand3({
3309
+ meta: { name: "run", description: "Run a skill manually" },
3310
+ args: {
3311
+ config: configArg,
3312
+ id: { type: "positional", description: "Skill ID", required: true }
3313
+ },
3314
+ async run({ args }) {
3315
+ validateSkillId(args.id);
3316
+ const { manager, config } = await createManager(args.config);
3317
+ if (!await manager.exists(args.id)) {
3318
+ console.error(`${prefix} ${err(`Skill "${args.id}" not found.`)}`);
3319
+ process.exit(1);
3320
+ }
3321
+ console.log(`${prefix} Running skill ${cyan(args.id)}...`);
3322
+ const docker = new Dockerode2();
3323
+ const pool = new DockerContainerPool(docker, {
3324
+ image: config.sandbox.image ?? "augure-sandbox:latest",
3325
+ maxTotal: config.security.maxConcurrentSandboxes
3326
+ });
3327
+ const runner = new SkillRunner({
3328
+ pool,
3329
+ manager,
3330
+ defaults: config.sandbox.defaults
3331
+ });
3332
+ try {
3333
+ const result = await runner.run(args.id);
3334
+ console.log(`${bold("Status:")} ${result.success ? ok("OK") : err("FAILED")}`);
3335
+ console.log(`${bold("Duration:")} ${dim(`${result.durationMs}ms`)}`);
3336
+ if (result.output) console.log(`${bold("Output:")} ${result.output}`);
3337
+ if (result.error) console.error(`${bold("Error:")} ${err(result.error)}`);
3338
+ if (!result.success) process.exit(1);
3339
+ } finally {
3340
+ try {
3341
+ await pool.destroyAll();
3342
+ } catch {
3343
+ }
3344
+ }
3345
+ }
3346
+ });
3347
+ var pauseCommand = defineCommand3({
3348
+ meta: { name: "pause", description: "Pause a skill" },
3349
+ args: {
3350
+ config: configArg,
3351
+ id: { type: "positional", description: "Skill ID", required: true }
3352
+ },
3353
+ async run({ args }) {
3354
+ validateSkillId(args.id);
3355
+ const { manager } = await createManager(args.config);
3356
+ try {
3357
+ await manager.updateStatus(args.id, "paused");
3358
+ console.log(`${prefix} ${ok(`Skill "${args.id}" paused.`)}`);
3359
+ } catch {
3360
+ console.error(`${prefix} ${err(`Skill "${args.id}" not found.`)}`);
3361
+ process.exit(1);
3362
+ }
3363
+ }
3364
+ });
3365
+ var resumeCommand = defineCommand3({
3366
+ meta: { name: "resume", description: "Resume a paused skill" },
3367
+ args: {
3368
+ config: configArg,
3369
+ id: { type: "positional", description: "Skill ID", required: true }
3370
+ },
3371
+ async run({ args }) {
3372
+ validateSkillId(args.id);
3373
+ const { manager } = await createManager(args.config);
3374
+ try {
3375
+ await manager.updateStatus(args.id, "active");
3376
+ console.log(`${prefix} ${ok(`Skill "${args.id}" resumed.`)}`);
3377
+ } catch {
3378
+ console.error(`${prefix} ${err(`Skill "${args.id}" not found.`)}`);
3379
+ process.exit(1);
3380
+ }
3381
+ }
3382
+ });
3383
+ var deleteCommand = defineCommand3({
3384
+ meta: { name: "delete", description: "Delete a skill" },
3385
+ args: {
3386
+ config: configArg,
3387
+ id: { type: "positional", description: "Skill ID", required: true }
3388
+ },
3389
+ async run({ args }) {
3390
+ validateSkillId(args.id);
3391
+ const { manager } = await createManager(args.config);
3392
+ if (!await manager.exists(args.id)) {
3393
+ console.error(`${prefix} ${err(`Skill "${args.id}" not found.`)}`);
3394
+ process.exit(1);
3395
+ }
3396
+ await manager.delete(args.id);
3397
+ console.log(`${prefix} ${ok(`Skill "${args.id}" deleted.`)}`);
3398
+ }
3399
+ });
3400
+ var logsCommand = defineCommand3({
3401
+ meta: { name: "logs", description: "Show recent execution logs for a skill" },
3402
+ args: {
3403
+ config: configArg,
3404
+ id: { type: "positional", description: "Skill ID", required: true }
3405
+ },
3406
+ async run({ args }) {
3407
+ validateSkillId(args.id);
3408
+ const { manager } = await createManager(args.config);
3409
+ const runs = await manager.getRuns(args.id, 10);
3410
+ if (runs.length === 0) {
3411
+ console.log(`${prefix} ${dim(`No runs found for skill "${args.id}".`)}`);
3412
+ return;
3413
+ }
3414
+ console.log(bold(
3415
+ ["TIMESTAMP".padEnd(26), "OK".padEnd(6), "DURATION".padEnd(12), "OUTPUT"].join("")
3416
+ ));
3417
+ for (const r of runs) {
3418
+ const status = r.success ? ok("yes") : err("no ");
3419
+ const output = (r.output ?? r.error ?? "").slice(0, 60);
3420
+ console.log(
3421
+ [
3422
+ dim(r.timestamp.slice(0, 24).padEnd(26)),
3423
+ status + " ".repeat(Math.max(0, 6 - (r.success ? 3 : 3))),
3424
+ dim(`${r.durationMs}ms`.padEnd(12)),
3425
+ r.success ? output : err(output)
3426
+ ].join("")
3427
+ );
3428
+ }
3429
+ }
3430
+ });
3431
+ var skillsCommand = defineCommand3({
3432
+ meta: { name: "skills", description: "Manage skills" },
3433
+ subCommands: {
3434
+ list: listCommand,
3435
+ show: showCommand,
3436
+ run: runCommand,
3437
+ pause: pauseCommand,
3438
+ resume: resumeCommand,
3439
+ delete: deleteCommand,
3440
+ logs: logsCommand
3441
+ }
3442
+ });
3443
+
3444
+ // src/bin.ts
3445
+ var require2 = createRequire(import.meta.url);
3446
+ var { version } = require2("../package.json");
3447
+ var main = defineCommand4({
3448
+ meta: {
3449
+ name: "augure",
3450
+ description: "Augure \u2014 your proactive AI agent",
3451
+ version
3452
+ },
3453
+ subCommands: {
3454
+ start: startCommand,
3455
+ init: initCommand,
3456
+ skills: skillsCommand
3457
+ }
20
3458
  });
21
3459
  runMain(main);
22
- //# sourceMappingURL=bin.js.map