@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.
- package/dist/default/agentforge.config.ts +78 -0
- package/dist/default/convex/agents.ts +16 -16
- 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 +20 -20
- package/dist/default/convex/mastraIntegration.ts +3 -2
- 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 +50 -33
- package/dist/default/package.json +1 -0
- package/dist/default/tsconfig.json +1 -0
- package/dist/index.js +691 -84
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/templates/default/agentforge.config.ts +78 -0
- package/templates/default/convex/agents.ts +16 -16
- 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 +20 -20
- package/templates/default/convex/mastraIntegration.ts +3 -2
- 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 +50 -33
- package/templates/default/package.json +1 -0
- package/templates/default/tsconfig.json +1 -0
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { v } from "convex/values";
|
|
2
|
-
import { mutation, query,
|
|
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
|
-
//
|
|
39
|
-
//
|
|
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 }, () =>
|
|
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 =
|
|
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
|
|
84
|
+
let entries;
|
|
73
85
|
if (args.userId) {
|
|
74
|
-
|
|
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
|
-
|
|
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(),
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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 (
|
|
258
|
-
export const retrieveSecret =
|
|
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
|
-
|
|
284
|
-
const decrypted = decodeSecret(entry.encryptedValue, entry.iv,
|
|
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:
|
|
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
|
|
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", {
|