maestro-agent 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/README.md +316 -2
  2. package/bin/maestro.ts +5 -0
  3. package/dist/maestro +0 -0
  4. package/dist/web/apple-touch-icon.png +0 -0
  5. package/dist/web/assets/Connections-BMA04Ycg.js +11 -0
  6. package/dist/web/assets/GanttView-DXjh0gxg.js +49 -0
  7. package/dist/web/assets/Home-Ct3Ho0Qt.js +1 -0
  8. package/dist/web/assets/HooksCrons--0kyVJcR.js +11 -0
  9. package/dist/web/assets/ProjectDetail-B_IqEpFu.js +1 -0
  10. package/dist/web/assets/Roles-D1tIQzto.js +24 -0
  11. package/dist/web/assets/Settings-yts4LUmH.js +11 -0
  12. package/dist/web/assets/Skills-DbuNLjIV.js +12 -0
  13. package/dist/web/assets/Wizard-vJol8-Y4.js +11 -0
  14. package/dist/web/assets/WorkspaceChat-DrsLs4m2.js +56 -0
  15. package/dist/web/assets/WorkspaceDashboard-B9vgrd2Z.js +6 -0
  16. package/dist/web/assets/WorkspaceNew-DoNGYHCG.js +1 -0
  17. package/dist/web/assets/WorkspaceProjects-DDp3mUse.js +6 -0
  18. package/dist/web/assets/WorkspaceSchedules-BTjmCbYG.js +1 -0
  19. package/dist/web/assets/WorkspaceTasks-mPU-bhKR.js +41 -0
  20. package/dist/web/assets/activity-CIA8bIA4.js +6 -0
  21. package/dist/web/assets/addon-fit-BlxrFPDK.js +1 -0
  22. package/dist/web/assets/arrow-right-S7ID7nDp.js +6 -0
  23. package/dist/web/assets/badge-DDTUzWIi.js +1 -0
  24. package/dist/web/assets/circle-check-B3P1qK0Z.js +6 -0
  25. package/dist/web/assets/clock-f9aYZox0.js +6 -0
  26. package/dist/web/assets/index-BRo4Du_s.js +11 -0
  27. package/dist/web/assets/index-C7kx39S9.js +196 -0
  28. package/dist/web/assets/index-D6LSdZea.css +1 -0
  29. package/dist/web/assets/plus-BHnOxbns.js +6 -0
  30. package/dist/web/assets/refresh-cw-BWX04Hg3.js +6 -0
  31. package/dist/web/assets/save-BLbb_9xz.js +6 -0
  32. package/dist/web/assets/sparkles-CDr6Dw1e.js +6 -0
  33. package/dist/web/assets/trash-2-9-ThEdey.js +6 -0
  34. package/dist/web/assets/useEventStream-DXt2Hmei.js +1 -0
  35. package/dist/web/assets/x-DVdKPXXy.js +6 -0
  36. package/dist/web/assets/xterm-DYP7pi_n.css +32 -0
  37. package/dist/web/assets/xterm-DlVFs1Kw.js +9 -0
  38. package/dist/web/favicon-512.png +0 -0
  39. package/dist/web/favicon.png +0 -0
  40. package/dist/web/index.html +15 -0
  41. package/package.json +49 -6
  42. package/src/api/agents.ts +76 -0
  43. package/src/api/audit.ts +19 -0
  44. package/src/api/autopilot.ts +73 -0
  45. package/src/api/chat.ts +801 -0
  46. package/src/api/chief.ts +84 -0
  47. package/src/api/config.ts +39 -0
  48. package/src/api/gantt.ts +72 -0
  49. package/src/api/hooks.ts +54 -0
  50. package/src/api/inbox.ts +125 -0
  51. package/src/api/lark.ts +32 -0
  52. package/src/api/memory.ts +37 -0
  53. package/src/api/ops.ts +89 -0
  54. package/src/api/projects.ts +105 -0
  55. package/src/api/roles.ts +123 -0
  56. package/src/api/runtimes.ts +62 -0
  57. package/src/api/scheduled-tasks.ts +203 -0
  58. package/src/api/sessions.ts +479 -0
  59. package/src/api/skills.ts +386 -0
  60. package/src/api/tasks.ts +457 -0
  61. package/src/api/telegram.ts +94 -0
  62. package/src/api/templates.ts +36 -0
  63. package/src/api/webhooks.ts +20 -0
  64. package/src/api/workspaces.ts +150 -0
  65. package/src/bridges/lark/index.ts +213 -0
  66. package/src/bridges/telegram/index.ts +273 -0
  67. package/src/bridges/telegram/polling.ts +185 -0
  68. package/src/chat/index.ts +86 -0
  69. package/src/chief/index.ts +461 -0
  70. package/src/core/cli.ts +333 -0
  71. package/src/core/db.ts +53 -0
  72. package/src/core/event-bus.ts +33 -0
  73. package/src/core/index.ts +6 -0
  74. package/src/core/migrations.ts +303 -0
  75. package/src/core/router.ts +69 -0
  76. package/src/core/schema.sql +232 -0
  77. package/src/core/server.ts +308 -0
  78. package/src/core/validate.ts +22 -0
  79. package/src/discovery/index.ts +194 -0
  80. package/src/gateway/adapters/telegram.ts +148 -0
  81. package/src/gateway/index.ts +31 -0
  82. package/src/gateway/manager.ts +176 -0
  83. package/src/gateway/types.ts +77 -0
  84. package/src/inbox/index.ts +500 -0
  85. package/src/ops/artifact-sync.ts +65 -0
  86. package/src/ops/autopilot.ts +338 -0
  87. package/src/ops/gc.ts +252 -0
  88. package/src/ops/index.ts +226 -0
  89. package/src/ops/project-serial.ts +52 -0
  90. package/src/ops/role-dispatch.ts +111 -0
  91. package/src/ops/runtime-scheduler.ts +447 -0
  92. package/src/ops/task-blocking.ts +65 -0
  93. package/src/ops/task-deps.ts +37 -0
  94. package/src/ops/task-workspace.ts +60 -0
  95. package/src/roles/index.ts +258 -0
  96. package/src/roles/prompt-assembler.ts +85 -0
  97. package/src/roles/workspace-role.ts +155 -0
  98. package/src/scheduler/index.ts +461 -0
  99. package/src/session/output-parser.ts +75 -0
  100. package/src/session/realtime-parser.ts +40 -0
  101. package/src/skills/builtin.ts +155 -0
  102. package/src/skills/skill-extractor.ts +452 -0
  103. package/src/skills/skill-md.ts +282 -0
  104. package/src/transport/http-api.ts +75 -0
  105. package/src/transport/index.ts +4 -0
  106. package/src/transport/local-pty.ts +119 -0
  107. package/src/transport/ssh.ts +176 -0
  108. package/src/transport/types.ts +20 -0
  109. package/src/workflows/index.ts +231 -0
  110. package/index.js +0 -1
  111. package/maestro-agent-0.0.1.tgz +0 -0
@@ -0,0 +1,386 @@
1
+ import type { Router } from "../core/router";
2
+ import { json, body } from "../core/router";
3
+ import type { HubContext } from "../core/server";
4
+ import { generateId, now } from "../core/db";
5
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, symlinkSync, unlinkSync, rmSync, lstatSync } from "fs";
6
+ import { join, resolve } from "path";
7
+ import { homedir } from "os";
8
+ import { parseSkillMd as parseSkillMdNew, serializeSkillMd, loadSkillFromDir } from "../skills/skill-md";
9
+ import type { SkillFrontmatter } from "../skills/skill-md";
10
+
11
+ // ─── Types ───────────────────────────────────────────────────────────────────
12
+
13
+ const AGENT_TYPES = ["claude", "codex", "gemini", "opencode", "hermes"] as const;
14
+ type AgentType = (typeof AGENT_TYPES)[number];
15
+
16
+ type AgentEnabledMap = Record<AgentType, boolean>;
17
+
18
+ interface SkillRow {
19
+ id: string;
20
+ name: string;
21
+ description: string | null;
22
+ source: string;
23
+ source_url: string | null;
24
+ platforms_json: string;
25
+ enabled: number;
26
+ config_json: string;
27
+ installed_at: number;
28
+ updated_at: number;
29
+ }
30
+
31
+ // ─── Helper Functions ────────────────────────────────────────────────────────
32
+
33
+ export function getSkillsDir(hubDir: string): string {
34
+ return join(hubDir, "skills");
35
+ }
36
+
37
+ export function getAgentDirs(): Record<AgentType, string> {
38
+ const home = homedir();
39
+ return {
40
+ claude: join(home, ".claude", "skills"),
41
+ codex: join(home, ".codex", "skills"),
42
+ gemini: join(home, ".gemini", "skills"),
43
+ opencode: join(home, ".config", "opencode", "skills"),
44
+ hermes: join(home, ".hermes", "skills"),
45
+ };
46
+ }
47
+
48
+ export function parseSkillMd(content: string): { name: string; description: string } {
49
+ const parsed = parseSkillMdNew(content);
50
+ return {
51
+ name: parsed.frontmatter.name || "Untitled",
52
+ description: parsed.frontmatter.description || "",
53
+ };
54
+ }
55
+
56
+ export function syncSkillToAgent(skillDir: string, agentDir: string, skillName: string): void {
57
+ if (!existsSync(agentDir)) {
58
+ mkdirSync(agentDir, { recursive: true });
59
+ }
60
+ const target = join(agentDir, skillName);
61
+ // Remove existing if present
62
+ const existingStat = lstatOrNull(target);
63
+ if (existingStat) {
64
+ if (existingStat.isSymbolicLink()) {
65
+ unlinkSync(target);
66
+ } else {
67
+ rmSync(target, { recursive: true, force: true });
68
+ }
69
+ }
70
+ symlinkSync(resolve(skillDir), target, "dir");
71
+ }
72
+
73
+ export function unsyncSkillFromAgent(agentDir: string, skillName: string): void {
74
+ const target = join(agentDir, skillName);
75
+ const stat = lstatOrNull(target);
76
+ if (!stat) return;
77
+ if (stat.isSymbolicLink()) {
78
+ unlinkSync(target);
79
+ } else {
80
+ rmSync(target, { recursive: true, force: true });
81
+ }
82
+ }
83
+
84
+ function defaultAgentEnabled(): AgentEnabledMap {
85
+ return { claude: false, codex: false, gemini: false, opencode: false, hermes: false };
86
+ }
87
+
88
+ function parseAgentEnabled(platformsJson: string): AgentEnabledMap {
89
+ try {
90
+ const parsed = JSON.parse(platformsJson);
91
+ if (typeof parsed === "object" && !Array.isArray(parsed)) {
92
+ const result = defaultAgentEnabled();
93
+ for (const agent of AGENT_TYPES) {
94
+ if (agent in parsed) result[agent] = !!parsed[agent];
95
+ }
96
+ return result;
97
+ }
98
+ } catch {}
99
+ return defaultAgentEnabled();
100
+ }
101
+
102
+ export function syncAllSkills(db: any, hubDir: string): { synced: number; errors: string[] } {
103
+ const skillsDir = getSkillsDir(hubDir);
104
+ const agentDirs = getAgentDirs();
105
+ const rows = db.query("SELECT * FROM skill ORDER BY name ASC").all() as SkillRow[];
106
+ let synced = 0;
107
+ const errors: string[] = [];
108
+
109
+ for (const row of rows) {
110
+ const agentState = parseAgentEnabled(row.platforms_json);
111
+ const skillDir = join(skillsDir, row.id);
112
+
113
+ if (!existsSync(skillDir)) continue;
114
+
115
+ for (const agent of AGENT_TYPES) {
116
+ try {
117
+ if (agentState[agent]) {
118
+ syncSkillToAgent(skillDir, agentDirs[agent], row.name);
119
+ synced++;
120
+ } else {
121
+ unsyncSkillFromAgent(agentDirs[agent], row.name);
122
+ }
123
+ } catch (err: any) {
124
+ errors.push(`Failed to sync skill "${row.name}" for agent "${agent}": ${err.message}`);
125
+ }
126
+ }
127
+ }
128
+
129
+ return { synced, errors };
130
+ }
131
+
132
+ // ─── Route Registration ──────────────────────────────────────────────────────
133
+
134
+ export function registerSkillRoutes(router: Router, ctx: HubContext) {
135
+ const skillsDir = getSkillsDir(ctx.hubDir);
136
+
137
+ // Ensure skills SSOT directory exists
138
+ if (!existsSync(skillsDir)) {
139
+ mkdirSync(skillsDir, { recursive: true });
140
+ }
141
+
142
+ // GET /api/skills — list all installed skills (scan SSOT dir + merge DB state)
143
+ router.get("/api/skills", () => {
144
+ const rows = ctx.db.query("SELECT * FROM skill ORDER BY name ASC").all() as SkillRow[];
145
+ const results = rows.map((row) => ({
146
+ ...row,
147
+ agents: parseAgentEnabled(row.platforms_json),
148
+ config: safeJsonParse(row.config_json),
149
+ dir_exists: existsSync(join(skillsDir, row.id)),
150
+ }));
151
+ return json(results);
152
+ });
153
+
154
+ // GET /api/skills/agents — return agent config
155
+ router.get("/api/skills/agents", () => {
156
+ const agentDirs = getAgentDirs();
157
+ const agents = AGENT_TYPES.map((agent) => ({
158
+ name: agent,
159
+ dir: agentDirs[agent],
160
+ available: existsSync(agentDirs[agent]) || canCreateDir(agentDirs[agent]),
161
+ }));
162
+ return json(agents);
163
+ });
164
+
165
+ // GET /api/skills/:id — get single skill details
166
+ router.get("/api/skills/:id", (_req, params) => {
167
+ const row = ctx.db.query("SELECT * FROM skill WHERE id = ?").get(params.id) as SkillRow | null;
168
+ if (!row) return json({ error: "Not found" }, 404);
169
+
170
+ const skillDir = join(skillsDir, row.id);
171
+ let content: string | null = null;
172
+ let frontmatter: SkillFrontmatter | null = null;
173
+ const mdPath = join(skillDir, "SKILL.md");
174
+ if (existsSync(mdPath)) {
175
+ content = readFileSync(mdPath, "utf-8");
176
+ const parsed = parseSkillMdNew(content);
177
+ frontmatter = parsed.frontmatter;
178
+ }
179
+
180
+ return json({
181
+ ...row,
182
+ agents: parseAgentEnabled(row.platforms_json),
183
+ config: safeJsonParse(row.config_json),
184
+ dir_exists: existsSync(skillDir),
185
+ content,
186
+ frontmatter,
187
+ });
188
+ });
189
+
190
+ // POST /api/skills — install a new skill
191
+ router.post("/api/skills", async (req) => {
192
+ const { name, description, content, source, source_url, agents, config, version, metadata, argument_hint } = await body(req);
193
+ if (!name) return json({ error: "name is required" }, 400);
194
+
195
+ const id = generateId("skill");
196
+ const ts = now();
197
+ const agentState = { ...defaultAgentEnabled(), ...(agents || {}) };
198
+ const skillDir = join(skillsDir, id);
199
+
200
+ // Create skill directory and SKILL.md with frontmatter
201
+ mkdirSync(skillDir, { recursive: true });
202
+ const frontmatter: SkillFrontmatter = {
203
+ name,
204
+ description: description || undefined,
205
+ version: version || undefined,
206
+ metadata: metadata || undefined,
207
+ "argument-hint": argument_hint || undefined,
208
+ platforms: agentState,
209
+ };
210
+ const bodyContent = content || `# ${name}\n\n${description || ""}`;
211
+ const mdContent = serializeSkillMd(frontmatter, bodyContent);
212
+ writeFileSync(join(skillDir, "SKILL.md"), mdContent, "utf-8");
213
+
214
+ // Save to DB
215
+ ctx.db.run(
216
+ `INSERT INTO skill (id, name, description, source, source_url, platforms_json, enabled, config_json, installed_at, updated_at)
217
+ VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
218
+ [id, name, description || null, source || "local", source_url || null, JSON.stringify(agentState), JSON.stringify(config || {}), ts, ts]
219
+ );
220
+
221
+ // Sync to enabled agents
222
+ const agentDirs = getAgentDirs();
223
+ for (const agent of AGENT_TYPES) {
224
+ if (agentState[agent]) {
225
+ try {
226
+ syncSkillToAgent(skillDir, agentDirs[agent], name);
227
+ } catch {}
228
+ }
229
+ }
230
+
231
+ ctx.bus.publish("skill.created", { id, name });
232
+ return json({ id, name, description, agents: agentState, installed_at: ts, updated_at: ts }, 201);
233
+ });
234
+
235
+ // DELETE /api/skills/:id — uninstall skill
236
+ router.delete("/api/skills/:id", (_req, params) => {
237
+ const row = ctx.db.query("SELECT * FROM skill WHERE id = ?").get(params.id) as SkillRow | null;
238
+ if (!row) return json({ error: "Not found" }, 404);
239
+
240
+ const skillDir = join(skillsDir, row.id);
241
+ const agentDirs = getAgentDirs();
242
+
243
+ // Remove from all agent directories
244
+ for (const agent of AGENT_TYPES) {
245
+ try {
246
+ unsyncSkillFromAgent(agentDirs[agent], row.name);
247
+ } catch {}
248
+ }
249
+
250
+ // Backup: move to .trash instead of permanent delete
251
+ const trashDir = join(skillsDir, ".trash");
252
+ if (!existsSync(trashDir)) mkdirSync(trashDir, { recursive: true });
253
+ if (existsSync(skillDir)) {
254
+ const backupPath = join(trashDir, `${row.id}_${Date.now()}`);
255
+ try {
256
+ const { renameSync } = require("fs");
257
+ renameSync(skillDir, backupPath);
258
+ } catch {
259
+ rmSync(skillDir, { recursive: true, force: true });
260
+ }
261
+ }
262
+
263
+ // Remove from DB
264
+ ctx.db.run("DELETE FROM skill WHERE id = ?", [params.id]);
265
+ ctx.bus.publish("skill.deleted", { id: params.id, name: row.name });
266
+ return json({ ok: true });
267
+ });
268
+
269
+ // POST /api/skills/:id/toggle — toggle skill for a specific agent
270
+ router.post("/api/skills/:id/toggle", async (req, params) => {
271
+ const row = ctx.db.query("SELECT * FROM skill WHERE id = ?").get(params.id) as SkillRow | null;
272
+ if (!row) return json({ error: "Not found" }, 404);
273
+
274
+ const { agent, enabled } = await body(req);
275
+ if (!agent || !AGENT_TYPES.includes(agent)) {
276
+ return json({ error: `Invalid agent. Must be one of: ${AGENT_TYPES.join(", ")}` }, 400);
277
+ }
278
+ if (typeof enabled !== "boolean") {
279
+ return json({ error: "enabled must be a boolean" }, 400);
280
+ }
281
+
282
+ const agentState = parseAgentEnabled(row.platforms_json);
283
+ agentState[agent as AgentType] = enabled;
284
+
285
+ const ts = now();
286
+ ctx.db.run(
287
+ "UPDATE skill SET platforms_json=?, updated_at=? WHERE id=?",
288
+ [JSON.stringify(agentState), ts, params.id]
289
+ );
290
+
291
+ // Sync/unsync the skill directory
292
+ const skillDir = join(skillsDir, row.id);
293
+ const agentDirs = getAgentDirs();
294
+ try {
295
+ if (enabled) {
296
+ syncSkillToAgent(skillDir, agentDirs[agent as AgentType], row.name);
297
+ } else {
298
+ unsyncSkillFromAgent(agentDirs[agent as AgentType], row.name);
299
+ }
300
+ } catch (err: any) {
301
+ return json({ error: `Sync failed: ${err.message}`, agents: agentState, updated_at: ts }, 500);
302
+ }
303
+
304
+ ctx.bus.publish("skill.toggled", { id: params.id, name: row.name, agent, enabled });
305
+ return json({ id: row.id, name: row.name, agents: agentState, updated_at: ts });
306
+ });
307
+
308
+ // POST /api/skills/sync — re-sync all skills to all agent directories
309
+ router.post("/api/skills/sync", () => {
310
+ const result = syncAllSkills(ctx.db, ctx.hubDir);
311
+ return json(result);
312
+ });
313
+
314
+ // POST /api/skills/scan — scan agent directories for unmanaged skills
315
+ router.post("/api/skills/scan", () => {
316
+ const agentDirs = getAgentDirs();
317
+ const managedNames = new Set<string>();
318
+ const rows = ctx.db.query("SELECT name FROM skill").all() as { name: string }[];
319
+ for (const r of rows) managedNames.add(r.name);
320
+
321
+ const unmanaged: { name: string; agent: AgentType; path: string; description: string; version?: string; argumentHint?: string }[] = [];
322
+
323
+ for (const agent of AGENT_TYPES) {
324
+ const dir = agentDirs[agent];
325
+ if (!existsSync(dir)) continue;
326
+
327
+ const entries = readdirSync(dir, { withFileTypes: true });
328
+ for (const entry of entries) {
329
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
330
+ if (entry.name.startsWith(".")) continue;
331
+ if (managedNames.has(entry.name)) continue;
332
+
333
+ const entryPath = join(dir, entry.name);
334
+ const loaded = loadSkillFromDir(entryPath);
335
+ const description = loaded?.frontmatter.description || "";
336
+ const version = loaded?.frontmatter.version;
337
+ const argumentHint = loaded?.frontmatter["argument-hint"];
338
+
339
+ unmanaged.push({ name: entry.name, agent, path: entryPath, description, version, argumentHint });
340
+ }
341
+ }
342
+
343
+ return json(unmanaged);
344
+ });
345
+
346
+ // POST /api/skills/extract — manually trigger skill extraction from a session
347
+ router.post("/api/skills/extract", async (req) => {
348
+ const { session_id, config } = await body<{ session_id: string; config?: any }>(req);
349
+ if (!session_id) return json({ error: "session_id required" }, 400);
350
+
351
+ const { extractSkillFromTranscript } = await import("../skills/skill-extractor");
352
+ const result = extractSkillFromTranscript(ctx, session_id, config);
353
+ return json(result, result.extracted ? 201 : 200);
354
+ });
355
+ }
356
+
357
+ // ─── Utilities ───────────────────────────────────────────────────────────────
358
+
359
+ function safeJsonParse(str: string): any {
360
+ try {
361
+ return JSON.parse(str);
362
+ } catch {
363
+ return {};
364
+ }
365
+ }
366
+
367
+ function canCreateDir(dir: string): boolean {
368
+ try {
369
+ const parts = dir.split("/");
370
+ for (let i = parts.length - 1; i > 0; i--) {
371
+ const parent = parts.slice(0, i).join("/");
372
+ if (existsSync(parent)) return true;
373
+ }
374
+ return false;
375
+ } catch {
376
+ return false;
377
+ }
378
+ }
379
+
380
+ function lstatOrNull(path: string): ReturnType<typeof lstatSync> | null {
381
+ try {
382
+ return lstatSync(path);
383
+ } catch {
384
+ return null;
385
+ }
386
+ }