@agentforge-ai/cli 0.5.0 → 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 (35) hide show
  1. package/dist/default/convex/agents.ts +8 -8
  2. package/dist/default/convex/apiKeys.ts +7 -7
  3. package/dist/default/convex/cronJobs.ts +12 -12
  4. package/dist/default/convex/files.ts +7 -7
  5. package/dist/default/convex/folders.ts +11 -11
  6. package/dist/default/convex/heartbeat.ts +18 -18
  7. package/dist/default/convex/mastraIntegration.ts +2 -1
  8. package/dist/default/convex/mcpConnections.ts +5 -5
  9. package/dist/default/convex/messages.ts +4 -4
  10. package/dist/default/convex/projects.ts +10 -10
  11. package/dist/default/convex/schema.ts +150 -83
  12. package/dist/default/convex/sessions.ts +12 -12
  13. package/dist/default/convex/settings.ts +3 -3
  14. package/dist/default/convex/skills.ts +8 -8
  15. package/dist/default/convex/threads.ts +9 -9
  16. package/dist/default/convex/usage.ts +16 -16
  17. package/dist/default/convex/vault.ts +47 -33
  18. package/package.json +1 -1
  19. package/templates/default/convex/agents.ts +8 -8
  20. package/templates/default/convex/apiKeys.ts +7 -7
  21. package/templates/default/convex/cronJobs.ts +12 -12
  22. package/templates/default/convex/files.ts +7 -7
  23. package/templates/default/convex/folders.ts +11 -11
  24. package/templates/default/convex/heartbeat.ts +18 -18
  25. package/templates/default/convex/mastraIntegration.ts +2 -1
  26. package/templates/default/convex/mcpConnections.ts +5 -5
  27. package/templates/default/convex/messages.ts +4 -4
  28. package/templates/default/convex/projects.ts +10 -10
  29. package/templates/default/convex/schema.ts +150 -83
  30. package/templates/default/convex/sessions.ts +12 -12
  31. package/templates/default/convex/settings.ts +3 -3
  32. package/templates/default/convex/skills.ts +8 -8
  33. package/templates/default/convex/threads.ts +9 -9
  34. package/templates/default/convex/usage.ts +16 -16
  35. package/templates/default/convex/vault.ts +47 -33
@@ -16,20 +16,20 @@ export const list = query({
16
16
  if (args.provider) {
17
17
  records = await ctx.db
18
18
  .query("usage")
19
- .withIndex("byProvider", (q) => q.eq("provider", args.provider))
20
- .take(100).collect();
19
+ .withIndex("byProvider", (q) => q.eq("provider", args.provider!))
20
+ .collect();
21
21
  } else if (args.agentId) {
22
22
  records = await ctx.db
23
23
  .query("usage")
24
- .withIndex("byAgentId", (q) => q.eq("agentId", args.agentId))
25
- .take(100).collect();
24
+ .withIndex("byAgentId", (q) => q.eq("agentId", args.agentId!))
25
+ .collect();
26
26
  } else if (args.userId) {
27
27
  records = await ctx.db
28
28
  .query("usage")
29
- .withIndex("byUserId", (q) => q.eq("userId", args.userId))
30
- .take(100).collect();
29
+ .withIndex("byUserId", (q) => q.eq("userId", args.userId!))
30
+ .collect();
31
31
  } else {
32
- records = await ctx.db.query("usage").take(100).collect();
32
+ records = await ctx.db.query("usage").collect();
33
33
  }
34
34
 
35
35
  // Filter by time range if provided
@@ -58,15 +58,15 @@ export const getStats = query({
58
58
  if (args.agentId) {
59
59
  records = await ctx.db
60
60
  .query("usage")
61
- .withIndex("byAgentId", (q) => q.eq("agentId", args.agentId))
62
- .take(100).collect();
61
+ .withIndex("byAgentId", (q) => q.eq("agentId", args.agentId!))
62
+ .collect();
63
63
  } else if (args.userId) {
64
64
  records = await ctx.db
65
65
  .query("usage")
66
- .withIndex("byUserId", (q) => q.eq("userId", args.userId))
67
- .take(100).collect();
66
+ .withIndex("byUserId", (q) => q.eq("userId", args.userId!))
67
+ .collect();
68
68
  } else {
69
- records = await ctx.db.query("usage").take(100).collect();
69
+ records = await ctx.db.query("usage").collect();
70
70
  }
71
71
 
72
72
  // Filter by time range
@@ -139,10 +139,10 @@ export const getByTimePeriod = query({
139
139
  if (args.userId) {
140
140
  records = await ctx.db
141
141
  .query("usage")
142
- .withIndex("byUserId", (q) => q.eq("userId", args.userId))
143
- .take(100).collect();
142
+ .withIndex("byUserId", (q) => q.eq("userId", args.userId!))
143
+ .collect();
144
144
  } else {
145
- records = await ctx.db.query("usage").take(100).collect();
145
+ records = await ctx.db.query("usage").collect();
146
146
  }
147
147
 
148
148
  records = records.filter((r) => r.timestamp >= startTime);
@@ -182,7 +182,7 @@ export const cleanup = mutation({
182
182
  const records = await ctx.db
183
183
  .query("usage")
184
184
  .withIndex("byTimestamp")
185
- .take(100).collect();
185
+ .collect();
186
186
 
187
187
  const toDelete = records.filter((r) => r.timestamp < args.olderThan);
188
188
 
@@ -1,5 +1,5 @@
1
1
  import { v } from "convex/values";
2
- import { mutation, query, action } from "./_generated/server";
2
+ import { mutation, query, internalMutation } from "./_generated/server";
3
3
 
4
4
  // ============================================================
5
5
  // SECURE VAULT - Encrypted secrets management
@@ -35,10 +35,20 @@ function maskSecret(value: string): string {
35
35
  return value.substring(0, 6) + "..." + value.substring(value.length - 4);
36
36
  }
37
37
 
38
- // Simple XOR-based encoding (in production, use proper AES-256-GCM with a KMS)
39
- // This provides a layer of obfuscation in the database
38
+ // Encryption key from Convex environment variable.
39
+ // Set VAULT_ENCRYPTION_KEY in your Convex dashboard under Settings > Environment Variables.
40
+ // If not set, a default key is used (NOT SECURE for production).
41
+ function getEncryptionKey(): string {
42
+ return process.env.VAULT_ENCRYPTION_KEY ?? "agentforge-default-key-set-env-var";
43
+ }
44
+
45
+ // XOR-based encoding with per-entry IV for database obfuscation.
46
+ // For production deployments with sensitive data, consider integrating
47
+ // with a proper KMS (e.g., AWS KMS, Cloudflare Workers Secrets).
40
48
  function encodeSecret(value: string, key: string): { encrypted: string; iv: string } {
41
- const iv = Array.from({ length: 16 }, () => Math.floor(Math.random() * 256).toString(16).padStart(2, "0")).join("");
49
+ const iv = Array.from({ length: 16 }, () =>
50
+ Math.floor(Math.random() * 256).toString(16).padStart(2, "0")
51
+ ).join("");
42
52
  const combined = key + iv;
43
53
  let encrypted = "";
44
54
  for (let i = 0; i < value.length; i++) {
@@ -52,15 +62,14 @@ function decodeSecret(encrypted: string, iv: string, key: string): string {
52
62
  const combined = key + iv;
53
63
  let decoded = "";
54
64
  for (let i = 0; i < encrypted.length; i += 4) {
55
- const charCode = parseInt(encrypted.substring(i, i + 4), 16) ^ combined.charCodeAt((i / 4) % combined.length);
65
+ const charCode =
66
+ parseInt(encrypted.substring(i, i + 4), 16) ^
67
+ combined.charCodeAt((i / 4) % combined.length);
56
68
  decoded += String.fromCharCode(charCode);
57
69
  }
58
70
  return decoded;
59
71
  }
60
72
 
61
- // The encryption key should come from environment variables in production
62
- const VAULT_ENCRYPTION_KEY = "agentforge-vault-key-change-in-production-2026";
63
-
64
73
  // ---- Queries ----
65
74
 
66
75
  export const list = query({
@@ -69,11 +78,21 @@ export const list = query({
69
78
  category: v.optional(v.string()),
70
79
  },
71
80
  handler: async (ctx, args) => {
72
- let q = ctx.db.query("vault");
81
+ let entries;
73
82
  if (args.userId) {
74
- q = ctx.db.query("vault").withIndex("byUserId", (q) => q.eq("userId", args.userId));
83
+ entries = await ctx.db
84
+ .query("vault")
85
+ .withIndex("byUserId", (q) => q.eq("userId", args.userId!))
86
+ .order("desc")
87
+ .collect();
88
+ } else {
89
+ entries = await ctx.db.query("vault").order("desc").collect();
75
90
  }
76
- const entries = await q.order("desc").take(100);
91
+
92
+ if (args.category) {
93
+ entries = entries.filter((e) => e.category === args.category);
94
+ }
95
+
77
96
  // Never return encrypted values - only masked
78
97
  return entries.map((entry) => ({
79
98
  _id: entry._id,
@@ -99,13 +118,13 @@ export const getAuditLog = query({
99
118
  handler: async (ctx, args) => {
100
119
  const limit = args.limit ?? 50;
101
120
  if (args.vaultEntryId) {
102
- return ctx.db
121
+ return await ctx.db
103
122
  .query("vaultAuditLog")
104
123
  .withIndex("byVaultEntryId", (q) => q.eq("vaultEntryId", args.vaultEntryId!))
105
124
  .order("desc")
106
125
  .take(limit);
107
126
  }
108
- return ctx.db.query("vaultAuditLog").order("desc").take(limit);
127
+ return await ctx.db.query("vaultAuditLog").order("desc").take(limit);
109
128
  },
110
129
  });
111
130
 
@@ -116,12 +135,13 @@ export const store = mutation({
116
135
  name: v.string(),
117
136
  category: v.string(),
118
137
  provider: v.optional(v.string()),
119
- value: v.string(), // Raw secret value - will be encrypted before storage
138
+ value: v.string(),
120
139
  userId: v.optional(v.string()),
121
140
  expiresAt: v.optional(v.number()),
122
141
  },
123
142
  handler: async (ctx, args) => {
124
- const { encrypted, iv } = encodeSecret(args.value, VAULT_ENCRYPTION_KEY);
143
+ const key = getEncryptionKey();
144
+ const { encrypted, iv } = encodeSecret(args.value, key);
125
145
  const masked = maskSecret(args.value);
126
146
  const now = Date.now();
127
147
 
@@ -134,14 +154,12 @@ export const store = mutation({
134
154
  maskedValue: masked,
135
155
  isActive: true,
136
156
  expiresAt: args.expiresAt,
137
- lastAccessedAt: undefined,
138
157
  accessCount: 0,
139
158
  userId: args.userId,
140
159
  createdAt: now,
141
160
  updatedAt: now,
142
161
  });
143
162
 
144
- // Audit log
145
163
  await ctx.db.insert("vaultAuditLog", {
146
164
  vaultEntryId: id,
147
165
  action: "created",
@@ -163,7 +181,8 @@ export const storeFromChat = mutation({
163
181
  userId: v.optional(v.string()),
164
182
  },
165
183
  handler: async (ctx, args) => {
166
- const { encrypted, iv } = encodeSecret(args.value, VAULT_ENCRYPTION_KEY);
184
+ const key = getEncryptionKey();
185
+ const { encrypted, iv } = encodeSecret(args.value, key);
167
186
  const masked = maskSecret(args.value);
168
187
  const now = Date.now();
169
188
 
@@ -181,7 +200,6 @@ export const storeFromChat = mutation({
181
200
  updatedAt: now,
182
201
  });
183
202
 
184
- // Audit log with auto_captured source
185
203
  await ctx.db.insert("vaultAuditLog", {
186
204
  vaultEntryId: id,
187
205
  action: "auto_captured",
@@ -207,14 +225,15 @@ export const update = mutation({
207
225
  const existing = await ctx.db.get(args.id);
208
226
  if (!existing) throw new Error("Vault entry not found");
209
227
 
210
- const updates: any = { updatedAt: Date.now() };
228
+ const updates: Record<string, unknown> = { updatedAt: Date.now() };
211
229
 
212
230
  if (args.name !== undefined) updates.name = args.name;
213
231
  if (args.isActive !== undefined) updates.isActive = args.isActive;
214
232
  if (args.expiresAt !== undefined) updates.expiresAt = args.expiresAt;
215
233
 
216
234
  if (args.value) {
217
- const { encrypted, iv } = encodeSecret(args.value, VAULT_ENCRYPTION_KEY);
235
+ const key = getEncryptionKey();
236
+ const { encrypted, iv } = encodeSecret(args.value, key);
218
237
  updates.encryptedValue = encrypted;
219
238
  updates.iv = iv;
220
239
  updates.maskedValue = maskSecret(args.value);
@@ -241,7 +260,6 @@ export const remove = mutation({
241
260
  const existing = await ctx.db.get(args.id);
242
261
  if (!existing) throw new Error("Vault entry not found");
243
262
 
244
- // Log deletion before removing
245
263
  await ctx.db.insert("vaultAuditLog", {
246
264
  vaultEntryId: args.id,
247
265
  action: "deleted",
@@ -254,8 +272,8 @@ export const remove = mutation({
254
272
  },
255
273
  });
256
274
 
257
- // Retrieve decrypted value (for internal agent use only - never expose to frontend)
258
- export const retrieveSecret = mutation({
275
+ // Internal-only: Retrieve decrypted value (only callable from other Convex functions)
276
+ export const retrieveSecret = internalMutation({
259
277
  args: {
260
278
  id: v.id("vault"),
261
279
  userId: v.optional(v.string()),
@@ -265,13 +283,11 @@ export const retrieveSecret = mutation({
265
283
  if (!entry) throw new Error("Vault entry not found");
266
284
  if (!entry.isActive) throw new Error("Vault entry is disabled");
267
285
 
268
- // Update access tracking
269
286
  await ctx.db.patch(args.id, {
270
287
  lastAccessedAt: Date.now(),
271
288
  accessCount: entry.accessCount + 1,
272
289
  });
273
290
 
274
- // Audit log
275
291
  await ctx.db.insert("vaultAuditLog", {
276
292
  vaultEntryId: args.id,
277
293
  action: "accessed",
@@ -280,14 +296,13 @@ export const retrieveSecret = mutation({
280
296
  timestamp: Date.now(),
281
297
  });
282
298
 
283
- // Decrypt and return
284
- const decrypted = decodeSecret(entry.encryptedValue, entry.iv, VAULT_ENCRYPTION_KEY);
299
+ const key = getEncryptionKey();
300
+ const decrypted = decodeSecret(entry.encryptedValue, entry.iv, key);
285
301
  return decrypted;
286
302
  },
287
303
  });
288
304
 
289
305
  // ---- Secret Detection Utility ----
290
- // This is exported for use by the chat message handler
291
306
 
292
307
  export const detectSecrets = query({
293
308
  args: { text: v.string() },
@@ -329,7 +344,7 @@ export const censorMessage = mutation({
329
344
  },
330
345
  handler: async (ctx, args) => {
331
346
  let censoredText = args.text;
332
- const storedSecrets: Array<{ name: string; masked: string; id: any }> = [];
347
+ const storedSecrets: Array<{ name: string; masked: string; id: unknown }> = [];
333
348
 
334
349
  for (const { pattern, category, provider, name } of SECRET_PATTERNS) {
335
350
  const regex = new RegExp(pattern, "g");
@@ -338,12 +353,11 @@ export const censorMessage = mutation({
338
353
  const secretValue = match[0];
339
354
  const masked = maskSecret(secretValue);
340
355
 
341
- // Replace in censored text
342
356
  censoredText = censoredText.replace(secretValue, `[REDACTED: ${masked}]`);
343
357
 
344
- // Auto-store if enabled
345
358
  if (args.autoStore !== false) {
346
- const { encrypted, iv } = encodeSecret(secretValue, VAULT_ENCRYPTION_KEY);
359
+ const key = getEncryptionKey();
360
+ const { encrypted, iv } = encodeSecret(secretValue, key);
347
361
  const now = Date.now();
348
362
 
349
363
  const id = await ctx.db.insert("vault", {