@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.
- package/dist/default/convex/agents.ts +204 -0
- package/dist/default/convex/apiKeys.ts +133 -0
- package/dist/default/convex/cronJobs.ts +224 -0
- package/dist/default/convex/files.ts +103 -0
- package/dist/default/convex/folders.ts +110 -0
- package/dist/default/convex/heartbeat.ts +371 -0
- package/dist/default/convex/logs.ts +66 -0
- package/dist/default/convex/mastraIntegration.ts +185 -0
- package/dist/default/convex/mcpConnections.ts +127 -0
- package/dist/default/convex/messages.ts +90 -0
- package/dist/default/convex/projects.ts +114 -0
- package/dist/default/convex/schema.ts +150 -83
- package/dist/default/convex/sessions.ts +174 -0
- package/dist/default/convex/settings.ts +79 -0
- package/dist/default/convex/skills.ts +178 -0
- package/dist/default/convex/threads.ts +100 -0
- package/dist/default/convex/usage.ts +195 -0
- package/dist/default/convex/vault.ts +397 -0
- package/dist/default/dashboard/app/main.tsx +7 -3
- package/dist/default/dashboard/app/routes/agents.tsx +103 -161
- package/dist/default/dashboard/app/routes/chat.tsx +163 -317
- package/dist/default/dashboard/app/routes/connections.tsx +247 -386
- package/dist/default/dashboard/app/routes/cron.tsx +127 -286
- package/dist/default/dashboard/app/routes/files.tsx +184 -167
- package/dist/default/dashboard/app/routes/index.tsx +63 -96
- package/dist/default/dashboard/app/routes/projects.tsx +106 -225
- package/dist/default/dashboard/app/routes/sessions.tsx +87 -253
- package/dist/default/dashboard/app/routes/settings.tsx +316 -532
- package/dist/default/dashboard/app/routes/skills.tsx +329 -216
- package/dist/default/dashboard/app/routes/usage.tsx +107 -150
- package/dist/default/dashboard/tsconfig.json +3 -2
- package/dist/default/dashboard/vite.config.ts +6 -0
- package/dist/index.js +256 -49
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/default/convex/agents.ts +204 -0
- package/templates/default/convex/apiKeys.ts +133 -0
- package/templates/default/convex/cronJobs.ts +224 -0
- package/templates/default/convex/files.ts +103 -0
- package/templates/default/convex/folders.ts +110 -0
- package/templates/default/convex/heartbeat.ts +371 -0
- package/templates/default/convex/logs.ts +66 -0
- package/templates/default/convex/mastraIntegration.ts +185 -0
- package/templates/default/convex/mcpConnections.ts +127 -0
- package/templates/default/convex/messages.ts +90 -0
- package/templates/default/convex/projects.ts +114 -0
- package/templates/default/convex/schema.ts +150 -83
- package/templates/default/convex/sessions.ts +174 -0
- package/templates/default/convex/settings.ts +79 -0
- package/templates/default/convex/skills.ts +178 -0
- package/templates/default/convex/threads.ts +100 -0
- package/templates/default/convex/usage.ts +195 -0
- package/templates/default/convex/vault.ts +397 -0
- package/templates/default/dashboard/app/main.tsx +7 -3
- package/templates/default/dashboard/app/routes/agents.tsx +103 -161
- package/templates/default/dashboard/app/routes/chat.tsx +163 -317
- package/templates/default/dashboard/app/routes/connections.tsx +247 -386
- package/templates/default/dashboard/app/routes/cron.tsx +127 -286
- package/templates/default/dashboard/app/routes/files.tsx +184 -167
- package/templates/default/dashboard/app/routes/index.tsx +63 -96
- package/templates/default/dashboard/app/routes/projects.tsx +106 -225
- package/templates/default/dashboard/app/routes/sessions.tsx +87 -253
- package/templates/default/dashboard/app/routes/settings.tsx +316 -532
- package/templates/default/dashboard/app/routes/skills.tsx +329 -216
- package/templates/default/dashboard/app/routes/usage.tsx +107 -150
- package/templates/default/dashboard/tsconfig.json +3 -2
- package/templates/default/dashboard/vite.config.ts +6 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
import { mutation, query, internalMutation } from "./_generated/server";
|
|
3
|
+
|
|
4
|
+
// ============================================================
|
|
5
|
+
// SECURE VAULT - Encrypted secrets management
|
|
6
|
+
// ============================================================
|
|
7
|
+
|
|
8
|
+
// Secret pattern detection for auto-capture from chat
|
|
9
|
+
const SECRET_PATTERNS = [
|
|
10
|
+
{ pattern: /sk-[a-zA-Z0-9]{20,}/, category: "api_key", provider: "openai", name: "OpenAI API Key" },
|
|
11
|
+
{ pattern: /sk-ant-[a-zA-Z0-9-]{20,}/, category: "api_key", provider: "anthropic", name: "Anthropic API Key" },
|
|
12
|
+
{ pattern: /sk-or-[a-zA-Z0-9]{20,}/, category: "api_key", provider: "openrouter", name: "OpenRouter API Key" },
|
|
13
|
+
{ pattern: /AIza[a-zA-Z0-9_-]{35}/, category: "api_key", provider: "google", name: "Google API Key" },
|
|
14
|
+
{ pattern: /xai-[a-zA-Z0-9]{20,}/, category: "api_key", provider: "xai", name: "xAI API Key" },
|
|
15
|
+
{ pattern: /ghp_[a-zA-Z0-9]{36}/, category: "token", provider: "github", name: "GitHub Personal Access Token" },
|
|
16
|
+
{ pattern: /gho_[a-zA-Z0-9]{36}/, category: "token", provider: "github", name: "GitHub OAuth Token" },
|
|
17
|
+
{ pattern: /glpat-[a-zA-Z0-9_-]{20,}/, category: "token", provider: "gitlab", name: "GitLab Personal Access Token" },
|
|
18
|
+
{ pattern: /xoxb-[a-zA-Z0-9-]+/, category: "token", provider: "slack", name: "Slack Bot Token" },
|
|
19
|
+
{ pattern: /xoxp-[a-zA-Z0-9-]+/, category: "token", provider: "slack", name: "Slack User Token" },
|
|
20
|
+
{ pattern: /AKIA[A-Z0-9]{16}/, category: "credential", provider: "aws", name: "AWS Access Key ID" },
|
|
21
|
+
{ pattern: /sk_live_[a-zA-Z0-9]{24,}/, category: "api_key", provider: "stripe", name: "Stripe Live Secret Key" },
|
|
22
|
+
{ pattern: /sk_test_[a-zA-Z0-9]{24,}/, category: "api_key", provider: "stripe", name: "Stripe Test Secret Key" },
|
|
23
|
+
{ pattern: /pk_live_[a-zA-Z0-9]{24,}/, category: "api_key", provider: "stripe", name: "Stripe Live Publishable Key" },
|
|
24
|
+
{ pattern: /SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}/, category: "api_key", provider: "sendgrid", name: "SendGrid API Key" },
|
|
25
|
+
{ pattern: /[a-f0-9]{32}-us[0-9]{1,2}/, category: "api_key", provider: "mailchimp", name: "Mailchimp API Key" },
|
|
26
|
+
{ pattern: /-----BEGIN (RSA |EC |DSA )?PRIVATE KEY-----/, category: "secret", provider: "ssh", name: "Private Key" },
|
|
27
|
+
{ pattern: /eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}/, category: "token", provider: "jwt", name: "JWT Token" },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
// Mask a secret value for display (show first 4 and last 4 chars)
|
|
31
|
+
function maskSecret(value: string): string {
|
|
32
|
+
if (value.length <= 12) {
|
|
33
|
+
return value.substring(0, 3) + "..." + value.substring(value.length - 3);
|
|
34
|
+
}
|
|
35
|
+
return value.substring(0, 6) + "..." + value.substring(value.length - 4);
|
|
36
|
+
}
|
|
37
|
+
|
|
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).
|
|
48
|
+
function encodeSecret(value: string, key: string): { encrypted: string; iv: string } {
|
|
49
|
+
const iv = Array.from({ length: 16 }, () =>
|
|
50
|
+
Math.floor(Math.random() * 256).toString(16).padStart(2, "0")
|
|
51
|
+
).join("");
|
|
52
|
+
const combined = key + iv;
|
|
53
|
+
let encrypted = "";
|
|
54
|
+
for (let i = 0; i < value.length; i++) {
|
|
55
|
+
const charCode = value.charCodeAt(i) ^ combined.charCodeAt(i % combined.length);
|
|
56
|
+
encrypted += charCode.toString(16).padStart(4, "0");
|
|
57
|
+
}
|
|
58
|
+
return { encrypted, iv };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function decodeSecret(encrypted: string, iv: string, key: string): string {
|
|
62
|
+
const combined = key + iv;
|
|
63
|
+
let decoded = "";
|
|
64
|
+
for (let i = 0; i < encrypted.length; i += 4) {
|
|
65
|
+
const charCode =
|
|
66
|
+
parseInt(encrypted.substring(i, i + 4), 16) ^
|
|
67
|
+
combined.charCodeAt((i / 4) % combined.length);
|
|
68
|
+
decoded += String.fromCharCode(charCode);
|
|
69
|
+
}
|
|
70
|
+
return decoded;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ---- Queries ----
|
|
74
|
+
|
|
75
|
+
export const list = query({
|
|
76
|
+
args: {
|
|
77
|
+
userId: v.optional(v.string()),
|
|
78
|
+
category: v.optional(v.string()),
|
|
79
|
+
},
|
|
80
|
+
handler: async (ctx, args) => {
|
|
81
|
+
let entries;
|
|
82
|
+
if (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();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (args.category) {
|
|
93
|
+
entries = entries.filter((e) => e.category === args.category);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Never return encrypted values - only masked
|
|
97
|
+
return entries.map((entry) => ({
|
|
98
|
+
_id: entry._id,
|
|
99
|
+
name: entry.name,
|
|
100
|
+
category: entry.category,
|
|
101
|
+
provider: entry.provider,
|
|
102
|
+
maskedValue: entry.maskedValue,
|
|
103
|
+
isActive: entry.isActive,
|
|
104
|
+
expiresAt: entry.expiresAt,
|
|
105
|
+
lastAccessedAt: entry.lastAccessedAt,
|
|
106
|
+
accessCount: entry.accessCount,
|
|
107
|
+
createdAt: entry.createdAt,
|
|
108
|
+
updatedAt: entry.updatedAt,
|
|
109
|
+
}));
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
export const getAuditLog = query({
|
|
114
|
+
args: {
|
|
115
|
+
vaultEntryId: v.optional(v.id("vault")),
|
|
116
|
+
limit: v.optional(v.number()),
|
|
117
|
+
},
|
|
118
|
+
handler: async (ctx, args) => {
|
|
119
|
+
const limit = args.limit ?? 50;
|
|
120
|
+
if (args.vaultEntryId) {
|
|
121
|
+
return await ctx.db
|
|
122
|
+
.query("vaultAuditLog")
|
|
123
|
+
.withIndex("byVaultEntryId", (q) => q.eq("vaultEntryId", args.vaultEntryId!))
|
|
124
|
+
.order("desc")
|
|
125
|
+
.take(limit);
|
|
126
|
+
}
|
|
127
|
+
return await ctx.db.query("vaultAuditLog").order("desc").take(limit);
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// ---- Mutations ----
|
|
132
|
+
|
|
133
|
+
export const store = mutation({
|
|
134
|
+
args: {
|
|
135
|
+
name: v.string(),
|
|
136
|
+
category: v.string(),
|
|
137
|
+
provider: v.optional(v.string()),
|
|
138
|
+
value: v.string(),
|
|
139
|
+
userId: v.optional(v.string()),
|
|
140
|
+
expiresAt: v.optional(v.number()),
|
|
141
|
+
},
|
|
142
|
+
handler: async (ctx, args) => {
|
|
143
|
+
const key = getEncryptionKey();
|
|
144
|
+
const { encrypted, iv } = encodeSecret(args.value, key);
|
|
145
|
+
const masked = maskSecret(args.value);
|
|
146
|
+
const now = Date.now();
|
|
147
|
+
|
|
148
|
+
const id = await ctx.db.insert("vault", {
|
|
149
|
+
name: args.name,
|
|
150
|
+
category: args.category,
|
|
151
|
+
provider: args.provider,
|
|
152
|
+
encryptedValue: encrypted,
|
|
153
|
+
iv,
|
|
154
|
+
maskedValue: masked,
|
|
155
|
+
isActive: true,
|
|
156
|
+
expiresAt: args.expiresAt,
|
|
157
|
+
accessCount: 0,
|
|
158
|
+
userId: args.userId,
|
|
159
|
+
createdAt: now,
|
|
160
|
+
updatedAt: now,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
await ctx.db.insert("vaultAuditLog", {
|
|
164
|
+
vaultEntryId: id,
|
|
165
|
+
action: "created",
|
|
166
|
+
source: "dashboard",
|
|
167
|
+
userId: args.userId,
|
|
168
|
+
timestamp: now,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
return id;
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
export const storeFromChat = mutation({
|
|
176
|
+
args: {
|
|
177
|
+
name: v.string(),
|
|
178
|
+
category: v.string(),
|
|
179
|
+
provider: v.optional(v.string()),
|
|
180
|
+
value: v.string(),
|
|
181
|
+
userId: v.optional(v.string()),
|
|
182
|
+
},
|
|
183
|
+
handler: async (ctx, args) => {
|
|
184
|
+
const key = getEncryptionKey();
|
|
185
|
+
const { encrypted, iv } = encodeSecret(args.value, key);
|
|
186
|
+
const masked = maskSecret(args.value);
|
|
187
|
+
const now = Date.now();
|
|
188
|
+
|
|
189
|
+
const id = await ctx.db.insert("vault", {
|
|
190
|
+
name: args.name,
|
|
191
|
+
category: args.category,
|
|
192
|
+
provider: args.provider,
|
|
193
|
+
encryptedValue: encrypted,
|
|
194
|
+
iv,
|
|
195
|
+
maskedValue: masked,
|
|
196
|
+
isActive: true,
|
|
197
|
+
accessCount: 0,
|
|
198
|
+
userId: args.userId,
|
|
199
|
+
createdAt: now,
|
|
200
|
+
updatedAt: now,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
await ctx.db.insert("vaultAuditLog", {
|
|
204
|
+
vaultEntryId: id,
|
|
205
|
+
action: "auto_captured",
|
|
206
|
+
source: "chat",
|
|
207
|
+
userId: args.userId,
|
|
208
|
+
timestamp: now,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
return { id, masked };
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
export const update = mutation({
|
|
216
|
+
args: {
|
|
217
|
+
id: v.id("vault"),
|
|
218
|
+
name: v.optional(v.string()),
|
|
219
|
+
value: v.optional(v.string()),
|
|
220
|
+
isActive: v.optional(v.boolean()),
|
|
221
|
+
expiresAt: v.optional(v.number()),
|
|
222
|
+
userId: v.optional(v.string()),
|
|
223
|
+
},
|
|
224
|
+
handler: async (ctx, args) => {
|
|
225
|
+
const existing = await ctx.db.get(args.id);
|
|
226
|
+
if (!existing) throw new Error("Vault entry not found");
|
|
227
|
+
|
|
228
|
+
const updates: Record<string, unknown> = { updatedAt: Date.now() };
|
|
229
|
+
|
|
230
|
+
if (args.name !== undefined) updates.name = args.name;
|
|
231
|
+
if (args.isActive !== undefined) updates.isActive = args.isActive;
|
|
232
|
+
if (args.expiresAt !== undefined) updates.expiresAt = args.expiresAt;
|
|
233
|
+
|
|
234
|
+
if (args.value) {
|
|
235
|
+
const key = getEncryptionKey();
|
|
236
|
+
const { encrypted, iv } = encodeSecret(args.value, key);
|
|
237
|
+
updates.encryptedValue = encrypted;
|
|
238
|
+
updates.iv = iv;
|
|
239
|
+
updates.maskedValue = maskSecret(args.value);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
await ctx.db.patch(args.id, updates);
|
|
243
|
+
|
|
244
|
+
await ctx.db.insert("vaultAuditLog", {
|
|
245
|
+
vaultEntryId: args.id,
|
|
246
|
+
action: "updated",
|
|
247
|
+
source: "dashboard",
|
|
248
|
+
userId: args.userId,
|
|
249
|
+
timestamp: Date.now(),
|
|
250
|
+
});
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
export const remove = mutation({
|
|
255
|
+
args: {
|
|
256
|
+
id: v.id("vault"),
|
|
257
|
+
userId: v.optional(v.string()),
|
|
258
|
+
},
|
|
259
|
+
handler: async (ctx, args) => {
|
|
260
|
+
const existing = await ctx.db.get(args.id);
|
|
261
|
+
if (!existing) throw new Error("Vault entry not found");
|
|
262
|
+
|
|
263
|
+
await ctx.db.insert("vaultAuditLog", {
|
|
264
|
+
vaultEntryId: args.id,
|
|
265
|
+
action: "deleted",
|
|
266
|
+
source: "dashboard",
|
|
267
|
+
userId: args.userId,
|
|
268
|
+
timestamp: Date.now(),
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
await ctx.db.delete(args.id);
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Internal-only: Retrieve decrypted value (only callable from other Convex functions)
|
|
276
|
+
export const retrieveSecret = internalMutation({
|
|
277
|
+
args: {
|
|
278
|
+
id: v.id("vault"),
|
|
279
|
+
userId: v.optional(v.string()),
|
|
280
|
+
},
|
|
281
|
+
handler: async (ctx, args) => {
|
|
282
|
+
const entry = await ctx.db.get(args.id);
|
|
283
|
+
if (!entry) throw new Error("Vault entry not found");
|
|
284
|
+
if (!entry.isActive) throw new Error("Vault entry is disabled");
|
|
285
|
+
|
|
286
|
+
await ctx.db.patch(args.id, {
|
|
287
|
+
lastAccessedAt: Date.now(),
|
|
288
|
+
accessCount: entry.accessCount + 1,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
await ctx.db.insert("vaultAuditLog", {
|
|
292
|
+
vaultEntryId: args.id,
|
|
293
|
+
action: "accessed",
|
|
294
|
+
source: "agent",
|
|
295
|
+
userId: args.userId,
|
|
296
|
+
timestamp: Date.now(),
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const key = getEncryptionKey();
|
|
300
|
+
const decrypted = decodeSecret(entry.encryptedValue, entry.iv, key);
|
|
301
|
+
return decrypted;
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// ---- Secret Detection Utility ----
|
|
306
|
+
|
|
307
|
+
export const detectSecrets = query({
|
|
308
|
+
args: { text: v.string() },
|
|
309
|
+
handler: async (_ctx, args) => {
|
|
310
|
+
const detected: Array<{
|
|
311
|
+
match: string;
|
|
312
|
+
category: string;
|
|
313
|
+
provider: string;
|
|
314
|
+
name: string;
|
|
315
|
+
startIndex: number;
|
|
316
|
+
endIndex: number;
|
|
317
|
+
}> = [];
|
|
318
|
+
|
|
319
|
+
for (const { pattern, category, provider, name } of SECRET_PATTERNS) {
|
|
320
|
+
const regex = new RegExp(pattern, "g");
|
|
321
|
+
let match;
|
|
322
|
+
while ((match = regex.exec(args.text)) !== null) {
|
|
323
|
+
detected.push({
|
|
324
|
+
match: match[0],
|
|
325
|
+
category,
|
|
326
|
+
provider,
|
|
327
|
+
name,
|
|
328
|
+
startIndex: match.index,
|
|
329
|
+
endIndex: match.index + match[0].length,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return detected;
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// Censor a message by replacing detected secrets with masked versions
|
|
339
|
+
export const censorMessage = mutation({
|
|
340
|
+
args: {
|
|
341
|
+
text: v.string(),
|
|
342
|
+
userId: v.optional(v.string()),
|
|
343
|
+
autoStore: v.optional(v.boolean()),
|
|
344
|
+
},
|
|
345
|
+
handler: async (ctx, args) => {
|
|
346
|
+
let censoredText = args.text;
|
|
347
|
+
const storedSecrets: Array<{ name: string; masked: string; id: unknown }> = [];
|
|
348
|
+
|
|
349
|
+
for (const { pattern, category, provider, name } of SECRET_PATTERNS) {
|
|
350
|
+
const regex = new RegExp(pattern, "g");
|
|
351
|
+
let match;
|
|
352
|
+
while ((match = regex.exec(args.text)) !== null) {
|
|
353
|
+
const secretValue = match[0];
|
|
354
|
+
const masked = maskSecret(secretValue);
|
|
355
|
+
|
|
356
|
+
censoredText = censoredText.replace(secretValue, `[REDACTED: ${masked}]`);
|
|
357
|
+
|
|
358
|
+
if (args.autoStore !== false) {
|
|
359
|
+
const key = getEncryptionKey();
|
|
360
|
+
const { encrypted, iv } = encodeSecret(secretValue, key);
|
|
361
|
+
const now = Date.now();
|
|
362
|
+
|
|
363
|
+
const id = await ctx.db.insert("vault", {
|
|
364
|
+
name: `${name} (auto-captured)`,
|
|
365
|
+
category,
|
|
366
|
+
provider,
|
|
367
|
+
encryptedValue: encrypted,
|
|
368
|
+
iv,
|
|
369
|
+
maskedValue: masked,
|
|
370
|
+
isActive: true,
|
|
371
|
+
accessCount: 0,
|
|
372
|
+
userId: args.userId,
|
|
373
|
+
createdAt: now,
|
|
374
|
+
updatedAt: now,
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
await ctx.db.insert("vaultAuditLog", {
|
|
378
|
+
vaultEntryId: id,
|
|
379
|
+
action: "auto_captured",
|
|
380
|
+
source: "chat",
|
|
381
|
+
userId: args.userId,
|
|
382
|
+
timestamp: now,
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
storedSecrets.push({ name, masked, id });
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
censoredText,
|
|
392
|
+
secretsDetected: storedSecrets.length > 0,
|
|
393
|
+
storedSecrets,
|
|
394
|
+
originalHadSecrets: censoredText !== args.text,
|
|
395
|
+
};
|
|
396
|
+
},
|
|
397
|
+
});
|
|
@@ -16,9 +16,13 @@ declare module "@tanstack/react-router" {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
// Initialize Convex client
|
|
19
|
-
const convexUrl =
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
const convexUrl = (import.meta as any).env?.VITE_CONVEX_URL;
|
|
20
|
+
if (!convexUrl) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
"Missing VITE_CONVEX_URL environment variable. " +
|
|
23
|
+
"Run 'agentforge dashboard' from your project root, or create dashboard/.env.local with VITE_CONVEX_URL=<your-url>"
|
|
24
|
+
);
|
|
25
|
+
}
|
|
22
26
|
const convex = new ConvexReactClient(convexUrl);
|
|
23
27
|
|
|
24
28
|
// Render
|