@agentforge-ai/cli 0.5.0 → 0.5.2

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