@agentforge-ai/cli 0.4.3 → 0.5.1

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 (67) hide show
  1. package/dist/default/convex/agents.ts +204 -0
  2. package/dist/default/convex/apiKeys.ts +133 -0
  3. package/dist/default/convex/cronJobs.ts +224 -0
  4. package/dist/default/convex/files.ts +103 -0
  5. package/dist/default/convex/folders.ts +110 -0
  6. package/dist/default/convex/heartbeat.ts +371 -0
  7. package/dist/default/convex/logs.ts +66 -0
  8. package/dist/default/convex/mastraIntegration.ts +185 -0
  9. package/dist/default/convex/mcpConnections.ts +127 -0
  10. package/dist/default/convex/messages.ts +90 -0
  11. package/dist/default/convex/projects.ts +114 -0
  12. package/dist/default/convex/schema.ts +150 -83
  13. package/dist/default/convex/sessions.ts +174 -0
  14. package/dist/default/convex/settings.ts +79 -0
  15. package/dist/default/convex/skills.ts +178 -0
  16. package/dist/default/convex/threads.ts +100 -0
  17. package/dist/default/convex/usage.ts +195 -0
  18. package/dist/default/convex/vault.ts +397 -0
  19. package/dist/default/dashboard/app/main.tsx +7 -3
  20. package/dist/default/dashboard/app/routes/agents.tsx +103 -161
  21. package/dist/default/dashboard/app/routes/chat.tsx +163 -317
  22. package/dist/default/dashboard/app/routes/connections.tsx +247 -386
  23. package/dist/default/dashboard/app/routes/cron.tsx +127 -286
  24. package/dist/default/dashboard/app/routes/files.tsx +184 -167
  25. package/dist/default/dashboard/app/routes/index.tsx +63 -96
  26. package/dist/default/dashboard/app/routes/projects.tsx +106 -225
  27. package/dist/default/dashboard/app/routes/sessions.tsx +87 -253
  28. package/dist/default/dashboard/app/routes/settings.tsx +316 -532
  29. package/dist/default/dashboard/app/routes/skills.tsx +329 -216
  30. package/dist/default/dashboard/app/routes/usage.tsx +107 -150
  31. package/dist/default/dashboard/tsconfig.json +3 -2
  32. package/dist/default/dashboard/vite.config.ts +6 -0
  33. package/dist/index.js +256 -49
  34. package/dist/index.js.map +1 -1
  35. package/package.json +1 -1
  36. package/templates/default/convex/agents.ts +204 -0
  37. package/templates/default/convex/apiKeys.ts +133 -0
  38. package/templates/default/convex/cronJobs.ts +224 -0
  39. package/templates/default/convex/files.ts +103 -0
  40. package/templates/default/convex/folders.ts +110 -0
  41. package/templates/default/convex/heartbeat.ts +371 -0
  42. package/templates/default/convex/logs.ts +66 -0
  43. package/templates/default/convex/mastraIntegration.ts +185 -0
  44. package/templates/default/convex/mcpConnections.ts +127 -0
  45. package/templates/default/convex/messages.ts +90 -0
  46. package/templates/default/convex/projects.ts +114 -0
  47. package/templates/default/convex/schema.ts +150 -83
  48. package/templates/default/convex/sessions.ts +174 -0
  49. package/templates/default/convex/settings.ts +79 -0
  50. package/templates/default/convex/skills.ts +178 -0
  51. package/templates/default/convex/threads.ts +100 -0
  52. package/templates/default/convex/usage.ts +195 -0
  53. package/templates/default/convex/vault.ts +397 -0
  54. package/templates/default/dashboard/app/main.tsx +7 -3
  55. package/templates/default/dashboard/app/routes/agents.tsx +103 -161
  56. package/templates/default/dashboard/app/routes/chat.tsx +163 -317
  57. package/templates/default/dashboard/app/routes/connections.tsx +247 -386
  58. package/templates/default/dashboard/app/routes/cron.tsx +127 -286
  59. package/templates/default/dashboard/app/routes/files.tsx +184 -167
  60. package/templates/default/dashboard/app/routes/index.tsx +63 -96
  61. package/templates/default/dashboard/app/routes/projects.tsx +106 -225
  62. package/templates/default/dashboard/app/routes/sessions.tsx +87 -253
  63. package/templates/default/dashboard/app/routes/settings.tsx +316 -532
  64. package/templates/default/dashboard/app/routes/skills.tsx +329 -216
  65. package/templates/default/dashboard/app/routes/usage.tsx +107 -150
  66. package/templates/default/dashboard/tsconfig.json +3 -2
  67. package/templates/default/dashboard/vite.config.ts +6 -0
@@ -0,0 +1,110 @@
1
+ import { v } from "convex/values";
2
+ import { mutation, query } from "./_generated/server";
3
+
4
+ // Query: List folders
5
+ export const list = query({
6
+ args: {
7
+ parentId: v.optional(v.id("folders")),
8
+ projectId: v.optional(v.id("projects")),
9
+ userId: v.optional(v.string()),
10
+ },
11
+ handler: async (ctx, args) => {
12
+ if (args.projectId) {
13
+ return await ctx.db
14
+ .query("folders")
15
+ .withIndex("byProjectId", (q) => q.eq("projectId", args.projectId!))
16
+ .collect();
17
+ }
18
+
19
+ if (args.parentId) {
20
+ return await ctx.db
21
+ .query("folders")
22
+ .withIndex("byParentId", (q) => q.eq("parentId", args.parentId!))
23
+ .collect();
24
+ }
25
+
26
+ if (args.userId) {
27
+ return await ctx.db
28
+ .query("folders")
29
+ .withIndex("byUserId", (q) => q.eq("userId", args.userId!))
30
+ .collect();
31
+ }
32
+
33
+ return await ctx.db.query("folders").collect();
34
+ },
35
+ });
36
+
37
+ // Query: Get folder by ID
38
+ export const get = query({
39
+ args: { id: v.id("folders") },
40
+ handler: async (ctx, args) => {
41
+ return await ctx.db.get(args.id);
42
+ },
43
+ });
44
+
45
+ // Mutation: Create folder
46
+ export const create = mutation({
47
+ args: {
48
+ name: v.string(),
49
+ parentId: v.optional(v.id("folders")),
50
+ projectId: v.optional(v.id("projects")),
51
+ userId: v.optional(v.string()),
52
+ },
53
+ handler: async (ctx, args) => {
54
+ const now = Date.now();
55
+ const folderId = await ctx.db.insert("folders", {
56
+ ...args,
57
+ createdAt: now,
58
+ updatedAt: now,
59
+ });
60
+ return folderId;
61
+ },
62
+ });
63
+
64
+ // Mutation: Update folder
65
+ export const update = mutation({
66
+ args: {
67
+ id: v.id("folders"),
68
+ name: v.optional(v.string()),
69
+ parentId: v.optional(v.id("folders")),
70
+ },
71
+ handler: async (ctx, args) => {
72
+ const { id, ...updates } = args;
73
+ await ctx.db.patch(id, {
74
+ ...updates,
75
+ updatedAt: Date.now(),
76
+ });
77
+ return id;
78
+ },
79
+ });
80
+
81
+ // Mutation: Delete folder
82
+ export const remove = mutation({
83
+ args: { id: v.id("folders") },
84
+ handler: async (ctx, args) => {
85
+ // Delete all files in the folder
86
+ const files = await ctx.db
87
+ .query("files")
88
+ .withIndex("byFolderId", (q) => q.eq("folderId", args.id!))
89
+ .collect();
90
+
91
+ for (const file of files) {
92
+ await ctx.db.delete(file._id);
93
+ }
94
+
95
+ // Delete all subfolders recursively
96
+ const subfolders = await ctx.db
97
+ .query("folders")
98
+ .withIndex("byParentId", (q) => q.eq("parentId", args.id!))
99
+ .collect();
100
+
101
+ for (const subfolder of subfolders) {
102
+ // Recursive delete (will be called via mutation)
103
+ await ctx.db.delete(subfolder._id);
104
+ }
105
+
106
+ // Delete the folder itself
107
+ await ctx.db.delete(args.id);
108
+ return { success: true };
109
+ },
110
+ });
@@ -0,0 +1,371 @@
1
+ import { v } from "convex/values";
2
+ import { mutation, query, action } from "./_generated/server";
3
+ import { api } from "./_generated/api";
4
+
5
+ /**
6
+ * HEARTBEAT System
7
+ *
8
+ * Similar to OpenClaw's HEARTBEAT.md, this system allows agents to:
9
+ * 1. Check on ongoing conversations
10
+ * 2. Continue unfinished tasks
11
+ * 3. Perform scheduled tasks
12
+ * 4. Maintain context across sessions
13
+ *
14
+ * The heartbeat table stores the current state of each agent's awareness
15
+ * of ongoing tasks, pending actions, and conversation context.
16
+ */
17
+
18
+ // Add heartbeat table to schema (this will need to be added to schema.ts)
19
+ // heartbeats: defineTable({
20
+ // agentId: v.string(),
21
+ // threadId: v.optional(v.id("threads")),
22
+ // status: v.string(), // "active", "waiting", "completed", "error"
23
+ // currentTask: v.optional(v.string()),
24
+ // pendingTasks: v.array(v.string()),
25
+ // context: v.string(), // Markdown-formatted context
26
+ // lastCheck: v.number(),
27
+ // nextCheck: v.number(),
28
+ // metadata: v.optional(v.any()),
29
+ // })
30
+
31
+ // Query: Get heartbeat for an agent
32
+ export const get = query({
33
+ args: {
34
+ agentId: v.string(),
35
+ threadId: v.optional(v.id("threads")),
36
+ },
37
+ handler: async (ctx, args) => {
38
+ const heartbeats = await ctx.db
39
+ .query("heartbeats")
40
+ .withIndex("byAgentId", (q) => q.eq("agentId", args.agentId!))
41
+ .collect();
42
+
43
+ if (args.threadId) {
44
+ return heartbeats.find((h) => h.threadId === args.threadId);
45
+ }
46
+
47
+ return heartbeats[0]; // Return the most recent one
48
+ },
49
+ });
50
+
51
+ // Query: Get all active heartbeats
52
+ export const listActive = query({
53
+ args: {
54
+ userId: v.optional(v.string()),
55
+ },
56
+ handler: async (ctx, args) => {
57
+ const heartbeats = await ctx.db
58
+ .query("heartbeats")
59
+ .withIndex("byStatus", (q) => q.eq("status", "active"))
60
+ .collect();
61
+
62
+ if (args.userId) {
63
+ // Filter by userId if provided
64
+ const userAgents = await ctx.db
65
+ .query("agents")
66
+ .withIndex("byUserId", (q) => q.eq("userId", args.userId!))
67
+ .collect();
68
+
69
+ const userAgentIds = new Set(userAgents.map((a) => a.id));
70
+ return heartbeats.filter((h) => userAgentIds.has(h.agentId));
71
+ }
72
+
73
+ return heartbeats;
74
+ },
75
+ });
76
+
77
+ // Query: Get heartbeats due for check
78
+ export const getDueForCheck = query({
79
+ args: {},
80
+ handler: async (ctx) => {
81
+ const now = Date.now();
82
+ const heartbeats = await ctx.db
83
+ .query("heartbeats")
84
+ .withIndex("byNextCheck")
85
+ .collect();
86
+
87
+ return heartbeats.filter((h) => h.nextCheck <= now && h.status === "active");
88
+ },
89
+ });
90
+
91
+ // Mutation: Create or update heartbeat
92
+ export const upsert = mutation({
93
+ args: {
94
+ agentId: v.string(),
95
+ threadId: v.optional(v.id("threads")),
96
+ status: v.string(),
97
+ currentTask: v.optional(v.string()),
98
+ pendingTasks: v.array(v.string()),
99
+ context: v.string(),
100
+ checkIntervalMs: v.optional(v.number()), // How often to check (default: 5 minutes)
101
+ metadata: v.optional(v.any()),
102
+ },
103
+ handler: async (ctx, args) => {
104
+ const { checkIntervalMs = 5 * 60 * 1000, ...data } = args;
105
+ const now = Date.now();
106
+
107
+ // Check if heartbeat exists
108
+ const existing = await ctx.db
109
+ .query("heartbeats")
110
+ .withIndex("byAgentId", (q) => q.eq("agentId", args.agentId!))
111
+ .filter((q) =>
112
+ args.threadId
113
+ ? q.eq(q.field("threadId"), args.threadId!)
114
+ : q.eq(q.field("threadId"), undefined)
115
+ )
116
+ .first();
117
+
118
+ if (existing) {
119
+ // Update existing heartbeat
120
+ await ctx.db.patch(existing._id, {
121
+ ...data,
122
+ lastCheck: now,
123
+ nextCheck: now + checkIntervalMs,
124
+ });
125
+ return existing._id;
126
+ } else {
127
+ // Create new heartbeat
128
+ const heartbeatId = await ctx.db.insert("heartbeats", {
129
+ ...data,
130
+ lastCheck: now,
131
+ nextCheck: now + checkIntervalMs,
132
+ });
133
+ return heartbeatId;
134
+ }
135
+ },
136
+ });
137
+
138
+ // Mutation: Update heartbeat status
139
+ export const updateStatus = mutation({
140
+ args: {
141
+ agentId: v.string(),
142
+ threadId: v.optional(v.id("threads")),
143
+ status: v.string(),
144
+ currentTask: v.optional(v.string()),
145
+ },
146
+ handler: async (ctx, args) => {
147
+ const heartbeat = await ctx.db
148
+ .query("heartbeats")
149
+ .withIndex("byAgentId", (q) => q.eq("agentId", args.agentId!))
150
+ .filter((q) =>
151
+ args.threadId
152
+ ? q.eq(q.field("threadId"), args.threadId!)
153
+ : q.eq(q.field("threadId"), undefined)
154
+ )
155
+ .first();
156
+
157
+ if (!heartbeat) {
158
+ throw new Error(`Heartbeat not found for agent ${args.agentId}`);
159
+ }
160
+
161
+ await ctx.db.patch(heartbeat._id, {
162
+ status: args.status,
163
+ currentTask: args.currentTask,
164
+ lastCheck: Date.now(),
165
+ });
166
+
167
+ return heartbeat._id;
168
+ },
169
+ });
170
+
171
+ // Mutation: Add pending task
172
+ export const addPendingTask = mutation({
173
+ args: {
174
+ agentId: v.string(),
175
+ threadId: v.optional(v.id("threads")),
176
+ task: v.string(),
177
+ },
178
+ handler: async (ctx, args) => {
179
+ const heartbeat = await ctx.db
180
+ .query("heartbeats")
181
+ .withIndex("byAgentId", (q) => q.eq("agentId", args.agentId!))
182
+ .filter((q) =>
183
+ args.threadId
184
+ ? q.eq(q.field("threadId"), args.threadId!)
185
+ : q.eq(q.field("threadId"), undefined)
186
+ )
187
+ .first();
188
+
189
+ if (!heartbeat) {
190
+ throw new Error(`Heartbeat not found for agent ${args.agentId}`);
191
+ }
192
+
193
+ const updatedTasks = [...heartbeat.pendingTasks, args.task];
194
+
195
+ await ctx.db.patch(heartbeat._id, {
196
+ pendingTasks: updatedTasks,
197
+ });
198
+
199
+ return heartbeat._id;
200
+ },
201
+ });
202
+
203
+ // Mutation: Remove pending task
204
+ export const removePendingTask = mutation({
205
+ args: {
206
+ agentId: v.string(),
207
+ threadId: v.optional(v.id("threads")),
208
+ task: v.string(),
209
+ },
210
+ handler: async (ctx, args) => {
211
+ const heartbeat = await ctx.db
212
+ .query("heartbeats")
213
+ .withIndex("byAgentId", (q) => q.eq("agentId", args.agentId!))
214
+ .filter((q) =>
215
+ args.threadId
216
+ ? q.eq(q.field("threadId"), args.threadId!)
217
+ : q.eq(q.field("threadId"), undefined)
218
+ )
219
+ .first();
220
+
221
+ if (!heartbeat) {
222
+ throw new Error(`Heartbeat not found for agent ${args.agentId}`);
223
+ }
224
+
225
+ const updatedTasks = heartbeat.pendingTasks.filter((t) => t !== args.task);
226
+
227
+ await ctx.db.patch(heartbeat._id, {
228
+ pendingTasks: updatedTasks,
229
+ });
230
+
231
+ return heartbeat._id;
232
+ },
233
+ });
234
+
235
+ // Mutation: Update context
236
+ export const updateContext = mutation({
237
+ args: {
238
+ agentId: v.string(),
239
+ threadId: v.optional(v.id("threads")),
240
+ context: v.string(),
241
+ },
242
+ handler: async (ctx, args) => {
243
+ const heartbeat = await ctx.db
244
+ .query("heartbeats")
245
+ .withIndex("byAgentId", (q) => q.eq("agentId", args.agentId!))
246
+ .filter((q) =>
247
+ args.threadId
248
+ ? q.eq(q.field("threadId"), args.threadId!)
249
+ : q.eq(q.field("threadId"), undefined)
250
+ )
251
+ .first();
252
+
253
+ if (!heartbeat) {
254
+ throw new Error(`Heartbeat not found for agent ${args.agentId}`);
255
+ }
256
+
257
+ await ctx.db.patch(heartbeat._id, {
258
+ context: args.context,
259
+ lastCheck: Date.now(),
260
+ });
261
+
262
+ return heartbeat._id;
263
+ },
264
+ });
265
+
266
+ // Action: Generate heartbeat context from thread
267
+ export const generateContext = action({
268
+ args: {
269
+ agentId: v.string(),
270
+ threadId: v.id("threads"),
271
+ },
272
+ handler: async (ctx, args) => {
273
+ // Get thread and messages
274
+ const thread = await ctx.runQuery(api.threads.get, { id: args.threadId });
275
+ const messages = await ctx.runQuery(api.messages.list, {
276
+ threadId: args.threadId,
277
+ });
278
+
279
+ if (!thread) {
280
+ throw new Error(`Thread ${args.threadId} not found`);
281
+ }
282
+
283
+ // Generate markdown context
284
+ let context = `# Heartbeat Context\n\n`;
285
+ context += `**Agent ID:** ${args.agentId}\n`;
286
+ context += `**Thread:** ${thread.name || "Unnamed"}\n`;
287
+ context += `**Last Updated:** ${new Date().toISOString()}\n\n`;
288
+
289
+ context += `## Recent Conversation\n\n`;
290
+
291
+ // Include last 10 messages
292
+ const recentMessages = messages.slice(-10);
293
+ for (const msg of recentMessages) {
294
+ const timestamp = new Date(msg.createdAt).toLocaleString();
295
+ context += `### ${msg.role.toUpperCase()} (${timestamp})\n`;
296
+ context += `${msg.content}\n\n`;
297
+ }
298
+
299
+ context += `## Status\n\n`;
300
+ context += `- Total messages: ${messages.length}\n`;
301
+ context += `- Last activity: ${new Date(thread.updatedAt).toLocaleString()}\n`;
302
+
303
+ return context;
304
+ },
305
+ });
306
+
307
+ // Action: Process heartbeat check
308
+ export const processCheck = action({
309
+ args: {
310
+ agentId: v.string(),
311
+ threadId: v.optional(v.id("threads")),
312
+ },
313
+ handler: async (ctx, args) => {
314
+ // Get heartbeat
315
+ const heartbeat = await ctx.runQuery(api.heartbeat.get, {
316
+ agentId: args.agentId,
317
+ threadId: args.threadId,
318
+ });
319
+
320
+ if (!heartbeat) {
321
+ return { success: false, message: "Heartbeat not found" };
322
+ }
323
+
324
+ // Check if there are pending tasks
325
+ if (heartbeat.pendingTasks.length > 0) {
326
+ // TODO: Integrate with Mastra to execute pending tasks
327
+ // For now, just log
328
+ console.log(`Agent ${args.agentId} has ${heartbeat.pendingTasks.length} pending tasks`);
329
+ }
330
+
331
+ // Update last check time
332
+ await ctx.runMutation(api.heartbeat.updateStatus, {
333
+ agentId: args.agentId,
334
+ threadId: args.threadId,
335
+ status: heartbeat.status,
336
+ currentTask: heartbeat.currentTask,
337
+ });
338
+
339
+ return {
340
+ success: true,
341
+ pendingTasks: heartbeat.pendingTasks.length,
342
+ status: heartbeat.status,
343
+ };
344
+ },
345
+ });
346
+
347
+ // Mutation: Delete heartbeat
348
+ export const remove = mutation({
349
+ args: {
350
+ agentId: v.string(),
351
+ threadId: v.optional(v.id("threads")),
352
+ },
353
+ handler: async (ctx, args) => {
354
+ const heartbeat = await ctx.db
355
+ .query("heartbeats")
356
+ .withIndex("byAgentId", (q) => q.eq("agentId", args.agentId!))
357
+ .filter((q) =>
358
+ args.threadId
359
+ ? q.eq(q.field("threadId"), args.threadId!)
360
+ : q.eq(q.field("threadId"), undefined)
361
+ )
362
+ .first();
363
+
364
+ if (!heartbeat) {
365
+ throw new Error(`Heartbeat not found for agent ${args.agentId}`);
366
+ }
367
+
368
+ await ctx.db.delete(heartbeat._id);
369
+ return { success: true };
370
+ },
371
+ });
@@ -0,0 +1,66 @@
1
+ import { v } from "convex/values";
2
+ import { mutation, query } from "./_generated/server";
3
+
4
+ // Query: List recent logs
5
+ export const list = query({
6
+ args: {
7
+ level: v.optional(v.string()),
8
+ source: v.optional(v.string()),
9
+ limit: v.optional(v.number()),
10
+ },
11
+ handler: async (ctx, args) => {
12
+ let q = ctx.db.query("logs").withIndex("byTimestamp").order("desc");
13
+
14
+ const results = await q.collect();
15
+
16
+ let filtered = results;
17
+ if (args.level) {
18
+ filtered = filtered.filter((l) => l.level === args.level);
19
+ }
20
+ if (args.source) {
21
+ filtered = filtered.filter((l) => l.source === args.source);
22
+ }
23
+
24
+ return filtered.slice(0, args.limit || 100);
25
+ },
26
+ });
27
+
28
+ // Mutation: Add a log entry
29
+ export const add = mutation({
30
+ args: {
31
+ level: v.union(
32
+ v.literal("debug"),
33
+ v.literal("info"),
34
+ v.literal("warn"),
35
+ v.literal("error")
36
+ ),
37
+ source: v.string(),
38
+ message: v.string(),
39
+ metadata: v.optional(v.any()),
40
+ userId: v.optional(v.string()),
41
+ },
42
+ handler: async (ctx, args) => {
43
+ return await ctx.db.insert("logs", {
44
+ ...args,
45
+ timestamp: Date.now(),
46
+ });
47
+ },
48
+ });
49
+
50
+ // Mutation: Clear logs older than a given timestamp
51
+ export const clearOld = mutation({
52
+ args: { olderThan: v.number() },
53
+ handler: async (ctx, args) => {
54
+ const old = await ctx.db
55
+ .query("logs")
56
+ .withIndex("byTimestamp")
57
+ .filter((q) => q.lt(q.field("timestamp"), args.olderThan))
58
+ .collect();
59
+
60
+ for (const log of old) {
61
+ await ctx.db.delete(log._id);
62
+ }
63
+
64
+ return old.length;
65
+ },
66
+ });