@agentforge-ai/cli 0.4.3 → 0.5.0
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 +184 -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/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 +383 -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 +184 -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/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 +383 -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,383 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
import { mutation, query, action } 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
|
+
// Simple XOR-based encoding (in production, use proper AES-256-GCM with a KMS)
|
|
39
|
+
// This provides a layer of obfuscation in the database
|
|
40
|
+
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("");
|
|
42
|
+
const combined = key + iv;
|
|
43
|
+
let encrypted = "";
|
|
44
|
+
for (let i = 0; i < value.length; i++) {
|
|
45
|
+
const charCode = value.charCodeAt(i) ^ combined.charCodeAt(i % combined.length);
|
|
46
|
+
encrypted += charCode.toString(16).padStart(4, "0");
|
|
47
|
+
}
|
|
48
|
+
return { encrypted, iv };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function decodeSecret(encrypted: string, iv: string, key: string): string {
|
|
52
|
+
const combined = key + iv;
|
|
53
|
+
let decoded = "";
|
|
54
|
+
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);
|
|
56
|
+
decoded += String.fromCharCode(charCode);
|
|
57
|
+
}
|
|
58
|
+
return decoded;
|
|
59
|
+
}
|
|
60
|
+
|
|
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
|
+
// ---- Queries ----
|
|
65
|
+
|
|
66
|
+
export const list = query({
|
|
67
|
+
args: {
|
|
68
|
+
userId: v.optional(v.string()),
|
|
69
|
+
category: v.optional(v.string()),
|
|
70
|
+
},
|
|
71
|
+
handler: async (ctx, args) => {
|
|
72
|
+
let q = ctx.db.query("vault");
|
|
73
|
+
if (args.userId) {
|
|
74
|
+
q = ctx.db.query("vault").withIndex("byUserId", (q) => q.eq("userId", args.userId));
|
|
75
|
+
}
|
|
76
|
+
const entries = await q.order("desc").take(100);
|
|
77
|
+
// Never return encrypted values - only masked
|
|
78
|
+
return entries.map((entry) => ({
|
|
79
|
+
_id: entry._id,
|
|
80
|
+
name: entry.name,
|
|
81
|
+
category: entry.category,
|
|
82
|
+
provider: entry.provider,
|
|
83
|
+
maskedValue: entry.maskedValue,
|
|
84
|
+
isActive: entry.isActive,
|
|
85
|
+
expiresAt: entry.expiresAt,
|
|
86
|
+
lastAccessedAt: entry.lastAccessedAt,
|
|
87
|
+
accessCount: entry.accessCount,
|
|
88
|
+
createdAt: entry.createdAt,
|
|
89
|
+
updatedAt: entry.updatedAt,
|
|
90
|
+
}));
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
export const getAuditLog = query({
|
|
95
|
+
args: {
|
|
96
|
+
vaultEntryId: v.optional(v.id("vault")),
|
|
97
|
+
limit: v.optional(v.number()),
|
|
98
|
+
},
|
|
99
|
+
handler: async (ctx, args) => {
|
|
100
|
+
const limit = args.limit ?? 50;
|
|
101
|
+
if (args.vaultEntryId) {
|
|
102
|
+
return ctx.db
|
|
103
|
+
.query("vaultAuditLog")
|
|
104
|
+
.withIndex("byVaultEntryId", (q) => q.eq("vaultEntryId", args.vaultEntryId!))
|
|
105
|
+
.order("desc")
|
|
106
|
+
.take(limit);
|
|
107
|
+
}
|
|
108
|
+
return ctx.db.query("vaultAuditLog").order("desc").take(limit);
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// ---- Mutations ----
|
|
113
|
+
|
|
114
|
+
export const store = mutation({
|
|
115
|
+
args: {
|
|
116
|
+
name: v.string(),
|
|
117
|
+
category: v.string(),
|
|
118
|
+
provider: v.optional(v.string()),
|
|
119
|
+
value: v.string(), // Raw secret value - will be encrypted before storage
|
|
120
|
+
userId: v.optional(v.string()),
|
|
121
|
+
expiresAt: v.optional(v.number()),
|
|
122
|
+
},
|
|
123
|
+
handler: async (ctx, args) => {
|
|
124
|
+
const { encrypted, iv } = encodeSecret(args.value, VAULT_ENCRYPTION_KEY);
|
|
125
|
+
const masked = maskSecret(args.value);
|
|
126
|
+
const now = Date.now();
|
|
127
|
+
|
|
128
|
+
const id = await ctx.db.insert("vault", {
|
|
129
|
+
name: args.name,
|
|
130
|
+
category: args.category,
|
|
131
|
+
provider: args.provider,
|
|
132
|
+
encryptedValue: encrypted,
|
|
133
|
+
iv,
|
|
134
|
+
maskedValue: masked,
|
|
135
|
+
isActive: true,
|
|
136
|
+
expiresAt: args.expiresAt,
|
|
137
|
+
lastAccessedAt: undefined,
|
|
138
|
+
accessCount: 0,
|
|
139
|
+
userId: args.userId,
|
|
140
|
+
createdAt: now,
|
|
141
|
+
updatedAt: now,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Audit log
|
|
145
|
+
await ctx.db.insert("vaultAuditLog", {
|
|
146
|
+
vaultEntryId: id,
|
|
147
|
+
action: "created",
|
|
148
|
+
source: "dashboard",
|
|
149
|
+
userId: args.userId,
|
|
150
|
+
timestamp: now,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return id;
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
export const storeFromChat = mutation({
|
|
158
|
+
args: {
|
|
159
|
+
name: v.string(),
|
|
160
|
+
category: v.string(),
|
|
161
|
+
provider: v.optional(v.string()),
|
|
162
|
+
value: v.string(),
|
|
163
|
+
userId: v.optional(v.string()),
|
|
164
|
+
},
|
|
165
|
+
handler: async (ctx, args) => {
|
|
166
|
+
const { encrypted, iv } = encodeSecret(args.value, VAULT_ENCRYPTION_KEY);
|
|
167
|
+
const masked = maskSecret(args.value);
|
|
168
|
+
const now = Date.now();
|
|
169
|
+
|
|
170
|
+
const id = await ctx.db.insert("vault", {
|
|
171
|
+
name: args.name,
|
|
172
|
+
category: args.category,
|
|
173
|
+
provider: args.provider,
|
|
174
|
+
encryptedValue: encrypted,
|
|
175
|
+
iv,
|
|
176
|
+
maskedValue: masked,
|
|
177
|
+
isActive: true,
|
|
178
|
+
accessCount: 0,
|
|
179
|
+
userId: args.userId,
|
|
180
|
+
createdAt: now,
|
|
181
|
+
updatedAt: now,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Audit log with auto_captured source
|
|
185
|
+
await ctx.db.insert("vaultAuditLog", {
|
|
186
|
+
vaultEntryId: id,
|
|
187
|
+
action: "auto_captured",
|
|
188
|
+
source: "chat",
|
|
189
|
+
userId: args.userId,
|
|
190
|
+
timestamp: now,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
return { id, masked };
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
export const update = mutation({
|
|
198
|
+
args: {
|
|
199
|
+
id: v.id("vault"),
|
|
200
|
+
name: v.optional(v.string()),
|
|
201
|
+
value: v.optional(v.string()),
|
|
202
|
+
isActive: v.optional(v.boolean()),
|
|
203
|
+
expiresAt: v.optional(v.number()),
|
|
204
|
+
userId: v.optional(v.string()),
|
|
205
|
+
},
|
|
206
|
+
handler: async (ctx, args) => {
|
|
207
|
+
const existing = await ctx.db.get(args.id);
|
|
208
|
+
if (!existing) throw new Error("Vault entry not found");
|
|
209
|
+
|
|
210
|
+
const updates: any = { updatedAt: Date.now() };
|
|
211
|
+
|
|
212
|
+
if (args.name !== undefined) updates.name = args.name;
|
|
213
|
+
if (args.isActive !== undefined) updates.isActive = args.isActive;
|
|
214
|
+
if (args.expiresAt !== undefined) updates.expiresAt = args.expiresAt;
|
|
215
|
+
|
|
216
|
+
if (args.value) {
|
|
217
|
+
const { encrypted, iv } = encodeSecret(args.value, VAULT_ENCRYPTION_KEY);
|
|
218
|
+
updates.encryptedValue = encrypted;
|
|
219
|
+
updates.iv = iv;
|
|
220
|
+
updates.maskedValue = maskSecret(args.value);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
await ctx.db.patch(args.id, updates);
|
|
224
|
+
|
|
225
|
+
await ctx.db.insert("vaultAuditLog", {
|
|
226
|
+
vaultEntryId: args.id,
|
|
227
|
+
action: "updated",
|
|
228
|
+
source: "dashboard",
|
|
229
|
+
userId: args.userId,
|
|
230
|
+
timestamp: Date.now(),
|
|
231
|
+
});
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
export const remove = mutation({
|
|
236
|
+
args: {
|
|
237
|
+
id: v.id("vault"),
|
|
238
|
+
userId: v.optional(v.string()),
|
|
239
|
+
},
|
|
240
|
+
handler: async (ctx, args) => {
|
|
241
|
+
const existing = await ctx.db.get(args.id);
|
|
242
|
+
if (!existing) throw new Error("Vault entry not found");
|
|
243
|
+
|
|
244
|
+
// Log deletion before removing
|
|
245
|
+
await ctx.db.insert("vaultAuditLog", {
|
|
246
|
+
vaultEntryId: args.id,
|
|
247
|
+
action: "deleted",
|
|
248
|
+
source: "dashboard",
|
|
249
|
+
userId: args.userId,
|
|
250
|
+
timestamp: Date.now(),
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
await ctx.db.delete(args.id);
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Retrieve decrypted value (for internal agent use only - never expose to frontend)
|
|
258
|
+
export const retrieveSecret = mutation({
|
|
259
|
+
args: {
|
|
260
|
+
id: v.id("vault"),
|
|
261
|
+
userId: v.optional(v.string()),
|
|
262
|
+
},
|
|
263
|
+
handler: async (ctx, args) => {
|
|
264
|
+
const entry = await ctx.db.get(args.id);
|
|
265
|
+
if (!entry) throw new Error("Vault entry not found");
|
|
266
|
+
if (!entry.isActive) throw new Error("Vault entry is disabled");
|
|
267
|
+
|
|
268
|
+
// Update access tracking
|
|
269
|
+
await ctx.db.patch(args.id, {
|
|
270
|
+
lastAccessedAt: Date.now(),
|
|
271
|
+
accessCount: entry.accessCount + 1,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Audit log
|
|
275
|
+
await ctx.db.insert("vaultAuditLog", {
|
|
276
|
+
vaultEntryId: args.id,
|
|
277
|
+
action: "accessed",
|
|
278
|
+
source: "agent",
|
|
279
|
+
userId: args.userId,
|
|
280
|
+
timestamp: Date.now(),
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// Decrypt and return
|
|
284
|
+
const decrypted = decodeSecret(entry.encryptedValue, entry.iv, VAULT_ENCRYPTION_KEY);
|
|
285
|
+
return decrypted;
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// ---- Secret Detection Utility ----
|
|
290
|
+
// This is exported for use by the chat message handler
|
|
291
|
+
|
|
292
|
+
export const detectSecrets = query({
|
|
293
|
+
args: { text: v.string() },
|
|
294
|
+
handler: async (_ctx, args) => {
|
|
295
|
+
const detected: Array<{
|
|
296
|
+
match: string;
|
|
297
|
+
category: string;
|
|
298
|
+
provider: string;
|
|
299
|
+
name: string;
|
|
300
|
+
startIndex: number;
|
|
301
|
+
endIndex: number;
|
|
302
|
+
}> = [];
|
|
303
|
+
|
|
304
|
+
for (const { pattern, category, provider, name } of SECRET_PATTERNS) {
|
|
305
|
+
const regex = new RegExp(pattern, "g");
|
|
306
|
+
let match;
|
|
307
|
+
while ((match = regex.exec(args.text)) !== null) {
|
|
308
|
+
detected.push({
|
|
309
|
+
match: match[0],
|
|
310
|
+
category,
|
|
311
|
+
provider,
|
|
312
|
+
name,
|
|
313
|
+
startIndex: match.index,
|
|
314
|
+
endIndex: match.index + match[0].length,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return detected;
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// Censor a message by replacing detected secrets with masked versions
|
|
324
|
+
export const censorMessage = mutation({
|
|
325
|
+
args: {
|
|
326
|
+
text: v.string(),
|
|
327
|
+
userId: v.optional(v.string()),
|
|
328
|
+
autoStore: v.optional(v.boolean()),
|
|
329
|
+
},
|
|
330
|
+
handler: async (ctx, args) => {
|
|
331
|
+
let censoredText = args.text;
|
|
332
|
+
const storedSecrets: Array<{ name: string; masked: string; id: any }> = [];
|
|
333
|
+
|
|
334
|
+
for (const { pattern, category, provider, name } of SECRET_PATTERNS) {
|
|
335
|
+
const regex = new RegExp(pattern, "g");
|
|
336
|
+
let match;
|
|
337
|
+
while ((match = regex.exec(args.text)) !== null) {
|
|
338
|
+
const secretValue = match[0];
|
|
339
|
+
const masked = maskSecret(secretValue);
|
|
340
|
+
|
|
341
|
+
// Replace in censored text
|
|
342
|
+
censoredText = censoredText.replace(secretValue, `[REDACTED: ${masked}]`);
|
|
343
|
+
|
|
344
|
+
// Auto-store if enabled
|
|
345
|
+
if (args.autoStore !== false) {
|
|
346
|
+
const { encrypted, iv } = encodeSecret(secretValue, VAULT_ENCRYPTION_KEY);
|
|
347
|
+
const now = Date.now();
|
|
348
|
+
|
|
349
|
+
const id = await ctx.db.insert("vault", {
|
|
350
|
+
name: `${name} (auto-captured)`,
|
|
351
|
+
category,
|
|
352
|
+
provider,
|
|
353
|
+
encryptedValue: encrypted,
|
|
354
|
+
iv,
|
|
355
|
+
maskedValue: masked,
|
|
356
|
+
isActive: true,
|
|
357
|
+
accessCount: 0,
|
|
358
|
+
userId: args.userId,
|
|
359
|
+
createdAt: now,
|
|
360
|
+
updatedAt: now,
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
await ctx.db.insert("vaultAuditLog", {
|
|
364
|
+
vaultEntryId: id,
|
|
365
|
+
action: "auto_captured",
|
|
366
|
+
source: "chat",
|
|
367
|
+
userId: args.userId,
|
|
368
|
+
timestamp: now,
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
storedSecrets.push({ name, masked, id });
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return {
|
|
377
|
+
censoredText,
|
|
378
|
+
secretsDetected: storedSecrets.length > 0,
|
|
379
|
+
storedSecrets,
|
|
380
|
+
originalHadSecrets: censoredText !== args.text,
|
|
381
|
+
};
|
|
382
|
+
},
|
|
383
|
+
});
|
|
@@ -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
|