@sugar-crash-studios/vibe-forge 0.4.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.
Files changed (201) hide show
  1. package/.claude/commands/clear-attention.md +63 -0
  2. package/.claude/commands/compact-context.md +52 -0
  3. package/.claude/commands/configure-vcs.md +102 -0
  4. package/.claude/commands/forge.md +171 -0
  5. package/.claude/commands/need-help.md +77 -0
  6. package/.claude/commands/update-status.md +64 -0
  7. package/.claude/commands/worker-loop.md +106 -0
  8. package/.claude/hooks/worker-loop.js +198 -0
  9. package/.claude/scripts/setup-worker-loop.sh +45 -0
  10. package/.claude/settings.local.json +46 -0
  11. package/LICENSE +21 -0
  12. package/README.md +238 -0
  13. package/agents/aegis/personality.md +294 -0
  14. package/agents/anvil/personality.md +276 -0
  15. package/agents/architect/personality.md +258 -0
  16. package/agents/crucible/personality.md +360 -0
  17. package/agents/ember/personality.md +291 -0
  18. package/agents/forge-master/capabilities.md +144 -0
  19. package/agents/forge-master/context-template.md +128 -0
  20. package/agents/forge-master/personality.md +138 -0
  21. package/agents/furnace/personality.md +340 -0
  22. package/agents/herald/personality.md +247 -0
  23. package/agents/loki/personality.md +108 -0
  24. package/agents/oracle/personality.md +283 -0
  25. package/agents/pixel/personality.md +113 -0
  26. package/agents/planning-hub/personality.md +320 -0
  27. package/agents/scribe/personality.md +251 -0
  28. package/agents/temper/personality.md +218 -0
  29. package/bin/cli.js +375 -0
  30. package/bin/dashboard/api/agents.js +333 -0
  31. package/bin/dashboard/api/dispatch.js +483 -0
  32. package/bin/dashboard/api/tasks.js +416 -0
  33. package/bin/dashboard/frontend/index.html +13 -0
  34. package/bin/dashboard/frontend/package.json +16 -0
  35. package/bin/dashboard/frontend/src/App.svelte +222 -0
  36. package/bin/dashboard/frontend/src/app.css +1777 -0
  37. package/bin/dashboard/frontend/src/lib/components/AgentCard.svelte +60 -0
  38. package/bin/dashboard/frontend/src/lib/components/AgentsPanel.svelte +57 -0
  39. package/bin/dashboard/frontend/src/lib/components/DispatchModal.svelte +180 -0
  40. package/bin/dashboard/frontend/src/lib/components/Footer.svelte +33 -0
  41. package/bin/dashboard/frontend/src/lib/components/Header.svelte +84 -0
  42. package/bin/dashboard/frontend/src/lib/components/IssueCard.svelte +33 -0
  43. package/bin/dashboard/frontend/src/lib/components/IssuesPanel.svelte +73 -0
  44. package/bin/dashboard/frontend/src/lib/components/KeyboardShortcutsModal.svelte +108 -0
  45. package/bin/dashboard/frontend/src/lib/components/MobileTabs.svelte +52 -0
  46. package/bin/dashboard/frontend/src/lib/components/NotificationCard.svelte +60 -0
  47. package/bin/dashboard/frontend/src/lib/components/NotificationsPanel.svelte +44 -0
  48. package/bin/dashboard/frontend/src/lib/components/TaskCard.svelte +63 -0
  49. package/bin/dashboard/frontend/src/lib/components/TasksPanel.svelte +82 -0
  50. package/bin/dashboard/frontend/src/lib/components/Toast.svelte +45 -0
  51. package/bin/dashboard/frontend/src/lib/stores/agents.js +34 -0
  52. package/bin/dashboard/frontend/src/lib/stores/issues.js +54 -0
  53. package/bin/dashboard/frontend/src/lib/stores/notifications.js +48 -0
  54. package/bin/dashboard/frontend/src/lib/stores/tasks.js +63 -0
  55. package/bin/dashboard/frontend/src/lib/stores/theme.js +33 -0
  56. package/bin/dashboard/frontend/src/lib/stores/toast.js +35 -0
  57. package/bin/dashboard/frontend/src/lib/stores/ui.js +25 -0
  58. package/bin/dashboard/frontend/src/lib/stores/voice.js +275 -0
  59. package/bin/dashboard/frontend/src/lib/stores/websocket.js +295 -0
  60. package/bin/dashboard/frontend/src/lib/utils/api.js +101 -0
  61. package/bin/dashboard/frontend/src/lib/utils/formatters.js +54 -0
  62. package/bin/dashboard/frontend/src/main.js +9 -0
  63. package/bin/dashboard/frontend/svelte.config.js +5 -0
  64. package/bin/dashboard/frontend/vite.config.js +20 -0
  65. package/bin/dashboard/public/assets/index-DnfVj9Ce.css +1 -0
  66. package/bin/dashboard/public/assets/index-Ze5h0kXQ.js +2 -0
  67. package/bin/dashboard/public/index.html +14 -0
  68. package/bin/dashboard/server.js +566 -0
  69. package/bin/forge-daemon.sh +463 -0
  70. package/bin/forge-setup.sh +645 -0
  71. package/bin/forge-spawn.sh +164 -0
  72. package/bin/forge.cmd +83 -0
  73. package/bin/forge.sh +533 -0
  74. package/bin/lib/agents.sh +177 -0
  75. package/bin/lib/colors.sh +44 -0
  76. package/bin/lib/config.sh +347 -0
  77. package/bin/lib/constants.sh +241 -0
  78. package/bin/lib/daemon/display.sh +128 -0
  79. package/bin/lib/daemon/notifications.sh +263 -0
  80. package/bin/lib/daemon/routing.sh +77 -0
  81. package/bin/lib/daemon/state.sh +115 -0
  82. package/bin/lib/daemon/sync.sh +95 -0
  83. package/bin/lib/database.sh +310 -0
  84. package/bin/lib/heimdall-setup.js +113 -0
  85. package/bin/lib/heimdall.js +265 -0
  86. package/bin/lib/json.sh +264 -0
  87. package/bin/lib/terminal.js +451 -0
  88. package/bin/lib/util.sh +126 -0
  89. package/bin/lib/vcs.js +349 -0
  90. package/config/agent-manifest.yaml +203 -0
  91. package/config/agents.json +168 -0
  92. package/config/task-template.md +159 -0
  93. package/config/task-types.yaml +106 -0
  94. package/context/agent-status/aegis.json +7 -0
  95. package/context/agent-status/anvil.json +7 -0
  96. package/context/agent-status/architect.json +7 -0
  97. package/context/agent-status/crucible.json +7 -0
  98. package/context/agent-status/ember.json +7 -0
  99. package/context/agent-status/furnace.json +7 -0
  100. package/context/agent-status/loki.json +7 -0
  101. package/context/agent-status/oracle.json +7 -0
  102. package/context/agent-status/pixel.json +7 -0
  103. package/context/agent-status/planning-hub.json +7 -0
  104. package/context/agent-status/scribe.json +7 -0
  105. package/context/agent-status/temper.json +7 -0
  106. package/context/feature-brainstorm.md +426 -0
  107. package/context/forge-state.yaml +19 -0
  108. package/context/modern-conventions.md +129 -0
  109. package/context/project-context-template.md +122 -0
  110. package/context/project-context.md +122 -0
  111. package/docs/TODO.md +150 -0
  112. package/docs/agents.md +409 -0
  113. package/docs/architecture/decisions/ADR-001-daemon-modularization.md +122 -0
  114. package/docs/architecture/vibe-lab-integration.md +684 -0
  115. package/docs/architecture.md +194 -0
  116. package/docs/bmad-gap-analysis-2026-03-31.md +444 -0
  117. package/docs/cleanup-workflow.md +329 -0
  118. package/docs/commands.md +451 -0
  119. package/docs/dashboard-mockup.html +989 -0
  120. package/docs/getting-started.md +261 -0
  121. package/docs/integration/forge-ownership-policy.md +112 -0
  122. package/docs/npm-publishing.md +132 -0
  123. package/docs/roadmap-2026.md +519 -0
  124. package/docs/security.md +144 -0
  125. package/docs/wireframes/dashboard-mvp.md +1164 -0
  126. package/docs/workflows/README.md +32 -0
  127. package/docs/workflows/azure-devops.md +108 -0
  128. package/docs/workflows/bitbucket.md +104 -0
  129. package/docs/workflows/git-only.md +130 -0
  130. package/docs/workflows/gitea.md +168 -0
  131. package/docs/workflows/github.md +103 -0
  132. package/docs/workflows/gitlab.md +105 -0
  133. package/docs/workflows.md +454 -0
  134. package/package.json +73 -0
  135. package/tasks/completed/ARCH-001-duplicate-agent-config.md +121 -0
  136. package/tasks/completed/ARCH-002-mixed-bash-node-implementation.md +88 -0
  137. package/tasks/completed/ARCH-003-worker-loop-hook-duplication.md +77 -0
  138. package/tasks/completed/ARCH-009-test-organization.md +78 -0
  139. package/tasks/completed/ARCH-011-jq-vs-nodejs-json.md +94 -0
  140. package/tasks/completed/ARCH-012-tmp-files-in-root.md +71 -0
  141. package/tasks/completed/ARCH-013-exit-code-constants.md +65 -0
  142. package/tasks/completed/ARCH-014-sed-incompatibility.md +96 -0
  143. package/tasks/completed/ARCH-015-docs-todo-tracking.md +83 -0
  144. package/tasks/completed/BUG-dash-001-tasks-filter-error.md +31 -0
  145. package/tasks/completed/BUG-dash-002-agents-unknown.md +41 -0
  146. package/tasks/completed/CLEAN-001.md +38 -0
  147. package/tasks/completed/CLEAN-002.md +43 -0
  148. package/tasks/completed/CLEAN-003.md +47 -0
  149. package/tasks/completed/CLEAN-004.md +56 -0
  150. package/tasks/completed/CLEAN-005.md +75 -0
  151. package/tasks/completed/CLEAN-006.md +47 -0
  152. package/tasks/completed/CLEAN-007.md +34 -0
  153. package/tasks/completed/CLEAN-008.md +49 -0
  154. package/tasks/completed/CLEAN-012.md +58 -0
  155. package/tasks/completed/CLEAN-013.md +45 -0
  156. package/tasks/completed/FEATURE-001a-dashboard-wireframes.md +162 -0
  157. package/tasks/completed/IMPL-007a-daemon-notifications-module.md +82 -0
  158. package/tasks/completed/IMPL-007b-daemon-sync-module.md +71 -0
  159. package/tasks/completed/IMPL-007c-daemon-state-module.md +80 -0
  160. package/tasks/completed/IMPL-007d-daemon-routing-module.md +77 -0
  161. package/tasks/completed/IMPL-007e-daemon-display-module.md +77 -0
  162. package/tasks/completed/IMPL-007f-daemon-integration.md +124 -0
  163. package/tasks/completed/PLAT-1-heimdall.md +420 -0
  164. package/tasks/completed/SEC-001-sql-injection-fix.md +58 -0
  165. package/tasks/completed/SEC-002-notification-injection-fix.md +45 -0
  166. package/tasks/completed/SEC-003-eval-injection-fix.md +54 -0
  167. package/tasks/completed/SEC-004-pid-race-condition-fix.md +49 -0
  168. package/tasks/completed/SEC-005-worker-loop-path-fix.md +51 -0
  169. package/tasks/completed/SEC-006-eval-agent-names.md +55 -0
  170. package/tasks/completed/SEC-007-spawn-escaping.md +67 -0
  171. package/tasks/completed/TASK-DASH-001-server-infrastructure.md +185 -0
  172. package/tasks/completed/TASK-anvil-001-dashboard-frontend.md +133 -0
  173. package/tasks/completed/review-bmad-aegis.md +89 -0
  174. package/tasks/completed/review-bmad-anvil.md +80 -0
  175. package/tasks/completed/review-bmad-crucible.md +81 -0
  176. package/tasks/completed/review-bmad-ember.md +90 -0
  177. package/tasks/completed/review-bmad-furnace.md +79 -0
  178. package/tasks/completed/review-bmad-pixel.md +82 -0
  179. package/tasks/completed/review-bmad-scribe.md +92 -0
  180. package/tasks/completed/review-bmad-sentinel.md +83 -0
  181. package/tasks/pending/ARCH-004-git-bash-detection-duplication.md +72 -0
  182. package/tasks/pending/ARCH-005-missing-src-directory.md +95 -0
  183. package/tasks/pending/ARCH-006-task-template-location.md +64 -0
  184. package/tasks/pending/ARCH-008-forge-master-vs-hub.md +81 -0
  185. package/tasks/pending/ARCH-010-missing-index-files.md +84 -0
  186. package/tasks/pending/CLEAN-009.md +31 -0
  187. package/tasks/pending/CLEAN-010.md +30 -0
  188. package/tasks/pending/CLEAN-011.md +30 -0
  189. package/tasks/pending/CLEAN-014.md +32 -0
  190. package/tasks/pending/DESIGN-dash-001-layout-review.md +45 -0
  191. package/tasks/pending/FEATURE-001-dashboard-mvp.md +268 -0
  192. package/tasks/review/ARCH-007-daemon-monolith.md +162 -0
  193. package/tasks/review/bmad-review-aegis.md +349 -0
  194. package/tasks/review/bmad-review-anvil.md +259 -0
  195. package/tasks/review/bmad-review-crucible.md +277 -0
  196. package/tasks/review/bmad-review-ember.md +307 -0
  197. package/tasks/review/bmad-review-furnace.md +285 -0
  198. package/tasks/review/bmad-review-pixel.md +329 -0
  199. package/tasks/review/bmad-review-scribe.md +361 -0
  200. package/tasks/review/bmad-review-sentinel.md +242 -0
  201. package/tasks/review/task-001.md +78 -0
@@ -0,0 +1,333 @@
1
+ /**
2
+ * Agents API - List and query agent status
3
+ *
4
+ * Reads agent status from:
5
+ * 1. SQLite database (if available) - daemon's aggregated view
6
+ * 2. JSON files in context/agent-status/ - direct file read fallback
7
+ *
8
+ * Endpoints:
9
+ * GET /api/agents - List all agents with their status
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ // Agent status directory
16
+ const AGENT_STATUS_DIR = 'context/agent-status';
17
+
18
+ // Known agents (for complete listing even if status file missing)
19
+ const KNOWN_AGENTS = [
20
+ 'planning-hub',
21
+ 'architect',
22
+ 'furnace',
23
+ 'anvil',
24
+ 'crucible',
25
+ 'temper',
26
+ 'ember',
27
+ 'scribe',
28
+ 'aegis',
29
+ 'pixel',
30
+ 'oracle',
31
+ 'loki'
32
+ ];
33
+
34
+ // Status display order
35
+ const STATUS_ORDER = {
36
+ 'working': 0,
37
+ 'testing': 1,
38
+ 'blocked': 2,
39
+ 'attention': 3,
40
+ 'idle': 4,
41
+ 'unknown': 5,
42
+ 'offline': 6
43
+ };
44
+
45
+ /**
46
+ * Try to read agent status from SQLite database
47
+ * @param {string} projectRoot - Project root directory
48
+ * @returns {Array|null} Agent statuses or null if DB not available
49
+ */
50
+ async function readFromDatabase(projectRoot) {
51
+ try {
52
+ // Check for better-sqlite3 (synchronous)
53
+ const Database = require('better-sqlite3');
54
+ const dbPath = path.join(projectRoot, '.forge', 'forge.db');
55
+
56
+ if (!fs.existsSync(dbPath)) {
57
+ return null;
58
+ }
59
+
60
+ const db = new Database(dbPath, { readonly: true });
61
+
62
+ try {
63
+ const rows = db.prepare(`
64
+ SELECT agent, status, task, message, updated_at
65
+ FROM agent_status
66
+ ORDER BY agent
67
+ `).all();
68
+
69
+ return rows.map(row => ({
70
+ agent: row.agent,
71
+ status: row.status || 'unknown',
72
+ task: row.task || null,
73
+ message: row.message || null,
74
+ updatedAt: row.updated_at || null,
75
+ source: 'database'
76
+ }));
77
+ } finally {
78
+ db.close();
79
+ }
80
+ } catch (err) {
81
+ // better-sqlite3 not available or DB error, fall back to files
82
+ return null;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Read agent status from JSON file
88
+ * @param {string} filePath - Path to status file
89
+ * @returns {Object|null} Agent status or null
90
+ */
91
+ function readStatusFile(filePath) {
92
+ try {
93
+ const content = fs.readFileSync(filePath, 'utf8');
94
+ const data = JSON.parse(content);
95
+ const stats = fs.statSync(filePath);
96
+
97
+ return {
98
+ agent: data.agent || path.basename(filePath, '.json'),
99
+ status: data.status || 'unknown',
100
+ task: data.task || null,
101
+ message: data.message || null,
102
+ updatedAt: data.updated || stats.mtime.toISOString(),
103
+ source: 'file'
104
+ };
105
+ } catch (err) {
106
+ console.error(`[Agents] Error reading ${filePath}: ${err.message}`);
107
+ return null;
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Read all agent statuses from files
113
+ * @param {string} projectRoot - Project root directory
114
+ * @returns {Array} Agent statuses
115
+ */
116
+ function readFromFiles(projectRoot) {
117
+ const statusDir = path.join(projectRoot, AGENT_STATUS_DIR);
118
+ const agents = [];
119
+
120
+ if (!fs.existsSync(statusDir)) {
121
+ return agents;
122
+ }
123
+
124
+ const files = fs.readdirSync(statusDir).filter(f => f.endsWith('.json'));
125
+
126
+ for (const file of files) {
127
+ const filePath = path.join(statusDir, file);
128
+ const status = readStatusFile(filePath);
129
+
130
+ if (status) {
131
+ agents.push(status);
132
+ }
133
+ }
134
+
135
+ return agents;
136
+ }
137
+
138
+ /**
139
+ * Get agent metadata (icon, role) from personality files
140
+ * @param {string} projectRoot - Project root directory
141
+ * @param {string} agentName - Agent name
142
+ * @returns {Object} Agent metadata
143
+ */
144
+ function getAgentMetadata(projectRoot, agentName) {
145
+ const personalityPath = path.join(projectRoot, 'agents', agentName, 'personality.md');
146
+
147
+ const defaults = {
148
+ icon: getDefaultIcon(agentName),
149
+ role: 'Agent',
150
+ color: getDefaultColor(agentName)
151
+ };
152
+
153
+ if (!fs.existsSync(personalityPath)) {
154
+ return defaults;
155
+ }
156
+
157
+ try {
158
+ const content = fs.readFileSync(personalityPath, 'utf8');
159
+
160
+ // Extract icon from first line or **Icon:** field
161
+ const iconMatch = content.match(/\*\*Icon:\*\*\s*(.+)/);
162
+ if (iconMatch) {
163
+ defaults.icon = iconMatch[1].trim();
164
+ }
165
+
166
+ // Extract role from **Role:** field
167
+ const roleMatch = content.match(/\*\*Role:\*\*\s*(.+)/);
168
+ if (roleMatch) {
169
+ defaults.role = roleMatch[1].trim();
170
+ }
171
+
172
+ return defaults;
173
+ } catch (err) {
174
+ return defaults;
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Get default icon for agent
180
+ * @param {string} agentName - Agent name
181
+ * @returns {string} Icon emoji
182
+ */
183
+ function getDefaultIcon(agentName) {
184
+ const icons = {
185
+ 'planning-hub': 'โš™๏ธ',
186
+ 'architect': '๐Ÿ›๏ธ',
187
+ 'furnace': '๐Ÿ”ฅ',
188
+ 'anvil': '๐Ÿ”จ',
189
+ 'crucible': '๐Ÿงช',
190
+ 'temper': 'โš–๏ธ',
191
+ 'ember': 'โœจ',
192
+ 'scribe': '๐Ÿ“',
193
+ 'aegis': '๐Ÿ”’',
194
+ 'pixel': '๐ŸŽจ',
195
+ 'oracle': '๐Ÿ”ฎ',
196
+ 'loki': '๐ŸŽญ'
197
+ };
198
+ return icons[agentName] || '๐Ÿค–';
199
+ }
200
+
201
+ /**
202
+ * Get default color for agent (for UI)
203
+ * @param {string} agentName - Agent name
204
+ * @returns {string} Color code
205
+ */
206
+ function getDefaultColor(agentName) {
207
+ const colors = {
208
+ 'planning-hub': '#6366f1',
209
+ 'architect': '#8b5cf6',
210
+ 'furnace': '#ef4444',
211
+ 'anvil': '#f97316',
212
+ 'crucible': '#22c55e',
213
+ 'temper': '#8b5cf6',
214
+ 'ember': '#eab308',
215
+ 'scribe': '#06b6d4',
216
+ 'aegis': '#14b8a6',
217
+ 'pixel': '#ec4899',
218
+ 'oracle': '#FBBF24',
219
+ 'loki': '#7C3AED'
220
+ };
221
+ return colors[agentName] || '#6b7280';
222
+ }
223
+
224
+ /**
225
+ * List all agents with their status
226
+ * @param {string} projectRoot - Project root directory
227
+ * @returns {Object} Agents listing with summary
228
+ */
229
+ async function listAgents(projectRoot) {
230
+ // Try database first, then fall back to files
231
+ let statuses = await readFromDatabase(projectRoot);
232
+
233
+ if (!statuses) {
234
+ statuses = readFromFiles(projectRoot);
235
+ }
236
+
237
+ // Create map for quick lookup
238
+ const statusMap = new Map();
239
+ for (const status of statuses) {
240
+ statusMap.set(status.agent, status);
241
+ }
242
+
243
+ // Build complete agent list
244
+ const agents = [];
245
+
246
+ for (const agentName of KNOWN_AGENTS) {
247
+ const status = statusMap.get(agentName);
248
+ const metadata = getAgentMetadata(projectRoot, agentName);
249
+
250
+ agents.push({
251
+ agent: agentName,
252
+ displayName: agentName.split('-').map(w =>
253
+ w.charAt(0).toUpperCase() + w.slice(1)
254
+ ).join(' '),
255
+ icon: metadata.icon,
256
+ role: metadata.role,
257
+ color: metadata.color,
258
+ status: status?.status || 'offline',
259
+ task: status?.task || null,
260
+ message: status?.message || null,
261
+ updatedAt: status?.updatedAt || null,
262
+ isOnline: status != null
263
+ });
264
+ }
265
+
266
+ // Add any unknown agents found in status files
267
+ for (const status of statuses) {
268
+ if (!KNOWN_AGENTS.includes(status.agent)) {
269
+ const metadata = getAgentMetadata(projectRoot, status.agent);
270
+ agents.push({
271
+ agent: status.agent,
272
+ displayName: status.agent.split('-').map(w =>
273
+ w.charAt(0).toUpperCase() + w.slice(1)
274
+ ).join(' '),
275
+ icon: metadata.icon,
276
+ role: metadata.role,
277
+ color: metadata.color,
278
+ status: status.status,
279
+ task: status.task,
280
+ message: status.message,
281
+ updatedAt: status.updatedAt,
282
+ isOnline: true
283
+ });
284
+ }
285
+ }
286
+
287
+ // Sort by status priority, then by name
288
+ agents.sort((a, b) => {
289
+ const sa = STATUS_ORDER[a.status] ?? 5;
290
+ const sb = STATUS_ORDER[b.status] ?? 5;
291
+ if (sa !== sb) return sa - sb;
292
+ return a.agent.localeCompare(b.agent);
293
+ });
294
+
295
+ // Build summary
296
+ const summary = {
297
+ total: agents.length,
298
+ online: agents.filter(a => a.isOnline).length,
299
+ working: agents.filter(a => a.status === 'working').length,
300
+ idle: agents.filter(a => a.status === 'idle').length,
301
+ blocked: agents.filter(a => a.status === 'blocked').length,
302
+ attention: agents.filter(a => a.status === 'attention').length
303
+ };
304
+
305
+ return {
306
+ agents,
307
+ summary,
308
+ dataSource: statuses.length > 0 && statuses[0].source || 'none'
309
+ };
310
+ }
311
+
312
+ /**
313
+ * Get single agent details
314
+ * @param {string} projectRoot - Project root directory
315
+ * @param {string} agentName - Agent name
316
+ * @returns {Object|null} Agent details or null
317
+ */
318
+ async function getAgent(projectRoot, agentName) {
319
+ // Sanitize agent name
320
+ const safeName = agentName.toLowerCase().replace(/[^a-z0-9-]/g, '');
321
+
322
+ const result = await listAgents(projectRoot);
323
+ return result.agents.find(a => a.agent === safeName) || null;
324
+ }
325
+
326
+ module.exports = {
327
+ listAgents,
328
+ getAgent,
329
+ readFromFiles,
330
+ readFromDatabase,
331
+ getAgentMetadata,
332
+ KNOWN_AGENTS
333
+ };