@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.
- package/dist/default/convex/agents.ts +8 -8
- package/dist/default/convex/apiKeys.ts +7 -7
- package/dist/default/convex/cronJobs.ts +12 -12
- package/dist/default/convex/files.ts +7 -7
- package/dist/default/convex/folders.ts +11 -11
- package/dist/default/convex/heartbeat.ts +18 -18
- package/dist/default/convex/mastraIntegration.ts +2 -1
- package/dist/default/convex/mcpConnections.ts +5 -5
- package/dist/default/convex/messages.ts +4 -4
- package/dist/default/convex/projects.ts +10 -10
- package/dist/default/convex/schema.ts +150 -83
- package/dist/default/convex/sessions.ts +12 -12
- package/dist/default/convex/settings.ts +3 -3
- package/dist/default/convex/skills.ts +8 -8
- package/dist/default/convex/threads.ts +9 -9
- package/dist/default/convex/usage.ts +16 -16
- package/dist/default/convex/vault.ts +47 -33
- package/package.json +1 -1
- package/templates/default/convex/agents.ts +8 -8
- package/templates/default/convex/apiKeys.ts +7 -7
- package/templates/default/convex/cronJobs.ts +12 -12
- package/templates/default/convex/files.ts +7 -7
- package/templates/default/convex/folders.ts +11 -11
- package/templates/default/convex/heartbeat.ts +18 -18
- package/templates/default/convex/mastraIntegration.ts +2 -1
- package/templates/default/convex/mcpConnections.ts +5 -5
- package/templates/default/convex/messages.ts +4 -4
- package/templates/default/convex/projects.ts +10 -10
- package/templates/default/convex/schema.ts +150 -83
- package/templates/default/convex/sessions.ts +12 -12
- package/templates/default/convex/settings.ts +3 -3
- package/templates/default/convex/skills.ts +8 -8
- package/templates/default/convex/threads.ts +9 -9
- package/templates/default/convex/usage.ts +16 -16
- 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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
29
|
+
.withIndex("byUserId", (q) => q.eq("userId", args.userId!))
|
|
30
|
+
.collect();
|
|
31
31
|
} else {
|
|
32
|
-
records = await ctx.db.query("usage").
|
|
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
|
-
.
|
|
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
|
-
.
|
|
66
|
+
.withIndex("byUserId", (q) => q.eq("userId", args.userId!))
|
|
67
|
+
.collect();
|
|
68
68
|
} else {
|
|
69
|
-
records = await ctx.db.query("usage").
|
|
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
|
-
.
|
|
142
|
+
.withIndex("byUserId", (q) => q.eq("userId", args.userId!))
|
|
143
|
+
.collect();
|
|
144
144
|
} else {
|
|
145
|
-
records = await ctx.db.query("usage").
|
|
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
|
-
.
|
|
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,
|
|
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
|
-
//
|
|
39
|
-
//
|
|
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 }, () =>
|
|
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 =
|
|
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
|
|
81
|
+
let entries;
|
|
73
82
|
if (args.userId) {
|
|
74
|
-
|
|
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
|
-
|
|
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(),
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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 (
|
|
258
|
-
export const retrieveSecret =
|
|
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
|
-
|
|
284
|
-
const decrypted = decodeSecret(entry.encryptedValue, entry.iv,
|
|
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:
|
|
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
|
|
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", {
|