@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.
Files changed (65) hide show
  1. package/dist/default/convex/agents.ts +204 -0
  2. package/dist/default/convex/apiKeys.ts +133 -0
  3. package/dist/default/convex/cronJobs.ts +224 -0
  4. package/dist/default/convex/files.ts +103 -0
  5. package/dist/default/convex/folders.ts +110 -0
  6. package/dist/default/convex/heartbeat.ts +371 -0
  7. package/dist/default/convex/logs.ts +66 -0
  8. package/dist/default/convex/mastraIntegration.ts +184 -0
  9. package/dist/default/convex/mcpConnections.ts +127 -0
  10. package/dist/default/convex/messages.ts +90 -0
  11. package/dist/default/convex/projects.ts +114 -0
  12. package/dist/default/convex/sessions.ts +174 -0
  13. package/dist/default/convex/settings.ts +79 -0
  14. package/dist/default/convex/skills.ts +178 -0
  15. package/dist/default/convex/threads.ts +100 -0
  16. package/dist/default/convex/usage.ts +195 -0
  17. package/dist/default/convex/vault.ts +383 -0
  18. package/dist/default/dashboard/app/main.tsx +7 -3
  19. package/dist/default/dashboard/app/routes/agents.tsx +103 -161
  20. package/dist/default/dashboard/app/routes/chat.tsx +163 -317
  21. package/dist/default/dashboard/app/routes/connections.tsx +247 -386
  22. package/dist/default/dashboard/app/routes/cron.tsx +127 -286
  23. package/dist/default/dashboard/app/routes/files.tsx +184 -167
  24. package/dist/default/dashboard/app/routes/index.tsx +63 -96
  25. package/dist/default/dashboard/app/routes/projects.tsx +106 -225
  26. package/dist/default/dashboard/app/routes/sessions.tsx +87 -253
  27. package/dist/default/dashboard/app/routes/settings.tsx +316 -532
  28. package/dist/default/dashboard/app/routes/skills.tsx +329 -216
  29. package/dist/default/dashboard/app/routes/usage.tsx +107 -150
  30. package/dist/default/dashboard/tsconfig.json +3 -2
  31. package/dist/default/dashboard/vite.config.ts +6 -0
  32. package/dist/index.js +256 -49
  33. package/dist/index.js.map +1 -1
  34. package/package.json +1 -1
  35. package/templates/default/convex/agents.ts +204 -0
  36. package/templates/default/convex/apiKeys.ts +133 -0
  37. package/templates/default/convex/cronJobs.ts +224 -0
  38. package/templates/default/convex/files.ts +103 -0
  39. package/templates/default/convex/folders.ts +110 -0
  40. package/templates/default/convex/heartbeat.ts +371 -0
  41. package/templates/default/convex/logs.ts +66 -0
  42. package/templates/default/convex/mastraIntegration.ts +184 -0
  43. package/templates/default/convex/mcpConnections.ts +127 -0
  44. package/templates/default/convex/messages.ts +90 -0
  45. package/templates/default/convex/projects.ts +114 -0
  46. package/templates/default/convex/sessions.ts +174 -0
  47. package/templates/default/convex/settings.ts +79 -0
  48. package/templates/default/convex/skills.ts +178 -0
  49. package/templates/default/convex/threads.ts +100 -0
  50. package/templates/default/convex/usage.ts +195 -0
  51. package/templates/default/convex/vault.ts +383 -0
  52. package/templates/default/dashboard/app/main.tsx +7 -3
  53. package/templates/default/dashboard/app/routes/agents.tsx +103 -161
  54. package/templates/default/dashboard/app/routes/chat.tsx +163 -317
  55. package/templates/default/dashboard/app/routes/connections.tsx +247 -386
  56. package/templates/default/dashboard/app/routes/cron.tsx +127 -286
  57. package/templates/default/dashboard/app/routes/files.tsx +184 -167
  58. package/templates/default/dashboard/app/routes/index.tsx +63 -96
  59. package/templates/default/dashboard/app/routes/projects.tsx +106 -225
  60. package/templates/default/dashboard/app/routes/sessions.tsx +87 -253
  61. package/templates/default/dashboard/app/routes/settings.tsx +316 -532
  62. package/templates/default/dashboard/app/routes/skills.tsx +329 -216
  63. package/templates/default/dashboard/app/routes/usage.tsx +107 -150
  64. package/templates/default/dashboard/tsconfig.json +3 -2
  65. package/templates/default/dashboard/vite.config.ts +6 -0
@@ -0,0 +1,204 @@
1
+ import { v } from "convex/values";
2
+ import { mutation, query, action } from "./_generated/server";
3
+ import { api } from "./_generated/api";
4
+
5
+ // Query: Get all agents
6
+ export const list = query({
7
+ args: {
8
+ userId: v.optional(v.string()),
9
+ },
10
+ handler: async (ctx, args) => {
11
+ if (args.userId) {
12
+ return await ctx.db
13
+ .query("agents")
14
+ .withIndex("byUserId", (q) => q.eq("userId", args.userId))
15
+ .take(100).collect();
16
+ }
17
+ return await ctx.db.query("agents").take(100).collect();
18
+ },
19
+ });
20
+
21
+ // Query: Get agent by ID
22
+ export const get = query({
23
+ args: { id: v.string() },
24
+ handler: async (ctx, args) => {
25
+ return await ctx.db
26
+ .query("agents")
27
+ .withIndex("byAgentId", (q) => q.eq("id", args.id))
28
+ .first();
29
+ },
30
+ });
31
+
32
+ // Query: Get active agents
33
+ export const listActive = query({
34
+ args: {
35
+ userId: v.optional(v.string()),
36
+ },
37
+ handler: async (ctx, args) => {
38
+ let query = ctx.db
39
+ .query("agents")
40
+ .withIndex("byIsActive", (q) => q.eq("isActive", true));
41
+
42
+ const agents = await query.take(100).collect();
43
+
44
+ if (args.userId) {
45
+ return agents.filter((agent) => agent.userId === args.userId);
46
+ }
47
+
48
+ return agents;
49
+ },
50
+ });
51
+
52
+ // Mutation: Create a new agent
53
+ export const create = mutation({
54
+ args: {
55
+ id: v.string(),
56
+ name: v.string(),
57
+ description: v.optional(v.string()),
58
+ instructions: v.string(),
59
+ model: v.string(),
60
+ provider: v.string(),
61
+ tools: v.optional(v.any()),
62
+ temperature: v.optional(v.number()),
63
+ maxTokens: v.optional(v.number()),
64
+ topP: v.optional(v.number()),
65
+ userId: v.optional(v.string()),
66
+ },
67
+ handler: async (ctx, args) => {
68
+ const now = Date.now();
69
+ const agentId = await ctx.db.insert("agents", {
70
+ ...args,
71
+ isActive: true,
72
+ createdAt: now,
73
+ updatedAt: now,
74
+ });
75
+ return agentId;
76
+ },
77
+ });
78
+
79
+ // Mutation: Update an agent
80
+ export const update = mutation({
81
+ args: {
82
+ id: v.string(),
83
+ name: v.optional(v.string()),
84
+ description: v.optional(v.string()),
85
+ instructions: v.optional(v.string()),
86
+ model: v.optional(v.string()),
87
+ provider: v.optional(v.string()),
88
+ tools: v.optional(v.any()),
89
+ temperature: v.optional(v.number()),
90
+ maxTokens: v.optional(v.number()),
91
+ topP: v.optional(v.number()),
92
+ isActive: v.optional(v.boolean()),
93
+ },
94
+ handler: async (ctx, args) => {
95
+ const { id, ...updates } = args;
96
+ const agent = await ctx.db
97
+ .query("agents")
98
+ .withIndex("byAgentId", (q) => q.eq("id", id))
99
+ .first();
100
+
101
+ if (!agent) {
102
+ throw new Error(`Agent with id ${id} not found`);
103
+ }
104
+
105
+ await ctx.db.patch(agent._id, {
106
+ ...updates,
107
+ updatedAt: Date.now(),
108
+ });
109
+
110
+ return agent._id;
111
+ },
112
+ });
113
+
114
+ // Mutation: Delete an agent
115
+ export const remove = mutation({
116
+ args: { id: v.string() },
117
+ handler: async (ctx, args) => {
118
+ const agent = await ctx.db
119
+ .query("agents")
120
+ .withIndex("byAgentId", (q) => q.eq("id", args.id))
121
+ .first();
122
+
123
+ if (!agent) {
124
+ throw new Error(`Agent with id ${args.id} not found`);
125
+ }
126
+
127
+ await ctx.db.delete(agent._id);
128
+ return { success: true };
129
+ },
130
+ });
131
+
132
+ // Mutation: Toggle agent active status
133
+ export const toggleActive = mutation({
134
+ args: { id: v.string() },
135
+ handler: async (ctx, args) => {
136
+ const agent = await ctx.db
137
+ .query("agents")
138
+ .withIndex("byAgentId", (q) => q.eq("id", args.id))
139
+ .first();
140
+
141
+ if (!agent) {
142
+ throw new Error(`Agent with id ${args.id} not found`);
143
+ }
144
+
145
+ await ctx.db.patch(agent._id, {
146
+ isActive: !agent.isActive,
147
+ updatedAt: Date.now(),
148
+ });
149
+
150
+ return { success: true, isActive: !agent.isActive };
151
+ },
152
+ });
153
+
154
+ // Action: Run agent with Mastra (to be implemented with Mastra integration)
155
+ export const run = action({
156
+ args: {
157
+ agentId: v.string(),
158
+ prompt: v.string(),
159
+ threadId: v.optional(v.id("threads")),
160
+ userId: v.optional(v.string()),
161
+ },
162
+ handler: async (ctx, args) => {
163
+ // Get agent configuration
164
+ const agent = await ctx.runQuery(api.agents.get, { id: args.agentId });
165
+
166
+ if (!agent) {
167
+ throw new Error(`Agent with id ${args.agentId} not found`);
168
+ }
169
+
170
+ // Create or get thread
171
+ let threadId = args.threadId;
172
+ if (!threadId) {
173
+ threadId = await ctx.runMutation(api.threads.create, {
174
+ agentId: args.agentId,
175
+ userId: args.userId,
176
+ });
177
+ }
178
+
179
+ // Add user message
180
+ await ctx.runMutation(api.messages.add, {
181
+ threadId,
182
+ role: "user",
183
+ content: args.prompt,
184
+ });
185
+
186
+ // TODO: Integrate with Mastra to run the agent
187
+ // This will be implemented in the Mastra integration phase
188
+ // For now, return a placeholder response
189
+ const response = {
190
+ threadId,
191
+ message: "Agent execution will be implemented with Mastra integration",
192
+ agentId: args.agentId,
193
+ };
194
+
195
+ // Add assistant message placeholder
196
+ await ctx.runMutation(api.messages.add, {
197
+ threadId,
198
+ role: "assistant",
199
+ content: response.message,
200
+ });
201
+
202
+ return response;
203
+ },
204
+ });
@@ -0,0 +1,133 @@
1
+ import { v } from "convex/values";
2
+ import { mutation, query } from "./_generated/server";
3
+
4
+ // Query: List API keys
5
+ export const list = query({
6
+ args: {
7
+ userId: v.optional(v.string()),
8
+ provider: v.optional(v.string()),
9
+ },
10
+ handler: async (ctx, args) => {
11
+ if (args.provider) {
12
+ const keys = await ctx.db
13
+ .query("apiKeys")
14
+ .withIndex("byProvider", (q) => q.eq("provider", args.provider))
15
+ .take(100).collect();
16
+
17
+ if (args.userId) {
18
+ return keys.filter((k) => k.userId === args.userId);
19
+ }
20
+ return keys;
21
+ }
22
+
23
+ if (args.userId) {
24
+ return await ctx.db
25
+ .query("apiKeys")
26
+ .withIndex("byUserId", (q) => q.eq("userId", args.userId))
27
+ .take(100).collect();
28
+ }
29
+
30
+ return await ctx.db.query("apiKeys").take(100).collect();
31
+ },
32
+ });
33
+
34
+ // Query: Get API key by ID
35
+ export const get = query({
36
+ args: { id: v.id("apiKeys") },
37
+ handler: async (ctx, args) => {
38
+ return await ctx.db.get(args.id);
39
+ },
40
+ });
41
+
42
+ // Query: Get active API key for provider
43
+ export const getActiveForProvider = query({
44
+ args: {
45
+ provider: v.string(),
46
+ userId: v.optional(v.string()),
47
+ },
48
+ handler: async (ctx, args) => {
49
+ const keys = await ctx.db
50
+ .query("apiKeys")
51
+ .withIndex("byProvider", (q) => q.eq("provider", args.provider))
52
+ .take(100).collect();
53
+
54
+ const activeKeys = keys.filter((k) => k.isActive);
55
+
56
+ if (args.userId) {
57
+ return activeKeys.find((k) => k.userId === args.userId);
58
+ }
59
+
60
+ return activeKeys[0];
61
+ },
62
+ });
63
+
64
+ // Mutation: Create API key
65
+ export const create = mutation({
66
+ args: {
67
+ provider: v.string(),
68
+ keyName: v.string(),
69
+ encryptedKey: v.string(),
70
+ userId: v.optional(v.string()),
71
+ },
72
+ handler: async (ctx, args) => {
73
+ const keyId = await ctx.db.insert("apiKeys", {
74
+ ...args,
75
+ isActive: true,
76
+ createdAt: Date.now(),
77
+ });
78
+ return keyId;
79
+ },
80
+ });
81
+
82
+ // Mutation: Update API key
83
+ export const update = mutation({
84
+ args: {
85
+ id: v.id("apiKeys"),
86
+ keyName: v.optional(v.string()),
87
+ encryptedKey: v.optional(v.string()),
88
+ isActive: v.optional(v.boolean()),
89
+ },
90
+ handler: async (ctx, args) => {
91
+ const { id, ...updates } = args;
92
+ await ctx.db.patch(id, updates);
93
+ return id;
94
+ },
95
+ });
96
+
97
+ // Mutation: Toggle API key active status
98
+ export const toggleActive = mutation({
99
+ args: { id: v.id("apiKeys") },
100
+ handler: async (ctx, args) => {
101
+ const key = await ctx.db.get(args.id);
102
+
103
+ if (!key) {
104
+ throw new Error(`API key not found`);
105
+ }
106
+
107
+ await ctx.db.patch(args.id, {
108
+ isActive: !key.isActive,
109
+ });
110
+
111
+ return { success: true, isActive: !key.isActive };
112
+ },
113
+ });
114
+
115
+ // Mutation: Update last used timestamp
116
+ export const updateLastUsed = mutation({
117
+ args: { id: v.id("apiKeys") },
118
+ handler: async (ctx, args) => {
119
+ await ctx.db.patch(args.id, {
120
+ lastUsedAt: Date.now(),
121
+ });
122
+ return args.id;
123
+ },
124
+ });
125
+
126
+ // Mutation: Delete API key
127
+ export const remove = mutation({
128
+ args: { id: v.id("apiKeys") },
129
+ handler: async (ctx, args) => {
130
+ await ctx.db.delete(args.id);
131
+ return { success: true };
132
+ },
133
+ });
@@ -0,0 +1,224 @@
1
+ import { v } from "convex/values";
2
+ import { mutation, query, action } from "./_generated/server";
3
+
4
+ // Query: List cron jobs
5
+ export const list = query({
6
+ args: {
7
+ userId: v.optional(v.string()),
8
+ agentId: v.optional(v.string()),
9
+ isEnabled: v.optional(v.boolean()),
10
+ },
11
+ handler: async (ctx, args) => {
12
+ if (args.isEnabled !== undefined) {
13
+ const jobs = await ctx.db
14
+ .query("cronJobs")
15
+ .withIndex("byIsEnabled", (q) => q.eq("isEnabled", args.isEnabled))
16
+ .take(100).collect();
17
+
18
+ if (args.userId) {
19
+ return jobs.filter((j) => j.userId === args.userId);
20
+ }
21
+ if (args.agentId) {
22
+ return jobs.filter((j) => j.agentId === args.agentId);
23
+ }
24
+ return jobs;
25
+ }
26
+
27
+ if (args.agentId) {
28
+ return await ctx.db
29
+ .query("cronJobs")
30
+ .withIndex("byAgentId", (q) => q.eq("agentId", args.agentId))
31
+ .take(100).collect();
32
+ }
33
+
34
+ if (args.userId) {
35
+ return await ctx.db
36
+ .query("cronJobs")
37
+ .withIndex("byUserId", (q) => q.eq("userId", args.userId))
38
+ .take(100).collect();
39
+ }
40
+
41
+ return await ctx.db.query("cronJobs").take(100).collect();
42
+ },
43
+ });
44
+
45
+ // Query: Get cron job by ID
46
+ export const get = query({
47
+ args: { id: v.id("cronJobs") },
48
+ handler: async (ctx, args) => {
49
+ return await ctx.db.get(args.id);
50
+ },
51
+ });
52
+
53
+ // Query: Get jobs due to run
54
+ export const getDueJobs = query({
55
+ args: {},
56
+ handler: async (ctx) => {
57
+ const now = Date.now();
58
+ const jobs = await ctx.db
59
+ .query("cronJobs")
60
+ .withIndex("byNextRun")
61
+ .take(100).collect();
62
+
63
+ return jobs.filter((j) => j.isEnabled && j.nextRun && j.nextRun <= now);
64
+ },
65
+ });
66
+
67
+ // Mutation: Create cron job
68
+ export const create = mutation({
69
+ args: {
70
+ name: v.string(),
71
+ description: v.optional(v.string()),
72
+ schedule: v.string(),
73
+ agentId: v.string(),
74
+ prompt: v.string(),
75
+ userId: v.optional(v.string()),
76
+ metadata: v.optional(v.any()),
77
+ },
78
+ handler: async (ctx, args) => {
79
+ const now = Date.now();
80
+
81
+ // TODO: Parse cron expression to calculate nextRun
82
+ // For now, set it to 1 hour from now
83
+ const nextRun = now + 60 * 60 * 1000;
84
+
85
+ const jobId = await ctx.db.insert("cronJobs", {
86
+ ...args,
87
+ isEnabled: true,
88
+ nextRun,
89
+ createdAt: now,
90
+ updatedAt: now,
91
+ });
92
+ return jobId;
93
+ },
94
+ });
95
+
96
+ // Mutation: Update cron job
97
+ export const update = mutation({
98
+ args: {
99
+ id: v.id("cronJobs"),
100
+ name: v.optional(v.string()),
101
+ description: v.optional(v.string()),
102
+ schedule: v.optional(v.string()),
103
+ prompt: v.optional(v.string()),
104
+ isEnabled: v.optional(v.boolean()),
105
+ metadata: v.optional(v.any()),
106
+ },
107
+ handler: async (ctx, args) => {
108
+ const { id, ...updates } = args;
109
+
110
+ // If schedule changed, recalculate nextRun
111
+ if (updates.schedule) {
112
+ const now = Date.now();
113
+ (updates as any).nextRun = now + 60 * 60 * 1000; // TODO: Parse cron
114
+ }
115
+
116
+ await ctx.db.patch(id, {
117
+ ...updates,
118
+ updatedAt: Date.now(),
119
+ });
120
+ return id;
121
+ },
122
+ });
123
+
124
+ // Mutation: Toggle cron job enabled status
125
+ export const toggleEnabled = mutation({
126
+ args: { id: v.id("cronJobs") },
127
+ handler: async (ctx, args) => {
128
+ const job = await ctx.db.get(args.id);
129
+
130
+ if (!job) {
131
+ throw new Error(`Cron job not found`);
132
+ }
133
+
134
+ await ctx.db.patch(args.id, {
135
+ isEnabled: !job.isEnabled,
136
+ updatedAt: Date.now(),
137
+ });
138
+
139
+ return { success: true, isEnabled: !job.isEnabled };
140
+ },
141
+ });
142
+
143
+ // Mutation: Update last run time
144
+ export const updateLastRun = mutation({
145
+ args: {
146
+ id: v.id("cronJobs"),
147
+ nextRun: v.number(),
148
+ },
149
+ handler: async (ctx, args) => {
150
+ await ctx.db.patch(args.id, {
151
+ lastRun: Date.now(),
152
+ nextRun: args.nextRun,
153
+ });
154
+ return args.id;
155
+ },
156
+ });
157
+
158
+ // Mutation: Delete cron job
159
+ export const remove = mutation({
160
+ args: { id: v.id("cronJobs") },
161
+ handler: async (ctx, args) => {
162
+ // Delete all run history
163
+ const runs = await ctx.db
164
+ .query("cronJobRuns")
165
+ .withIndex("byCronJobId", (q) => q.eq("cronJobId", args.id))
166
+ .take(100).collect();
167
+
168
+ for (const run of runs) {
169
+ await ctx.db.delete(run._id);
170
+ }
171
+
172
+ await ctx.db.delete(args.id);
173
+ return { success: true };
174
+ },
175
+ });
176
+
177
+ // Mutation: Record cron job run
178
+ export const recordRun = mutation({
179
+ args: {
180
+ cronJobId: v.id("cronJobs"),
181
+ status: v.union(
182
+ v.literal("success"),
183
+ v.literal("failed"),
184
+ v.literal("running")
185
+ ),
186
+ output: v.optional(v.string()),
187
+ error: v.optional(v.string()),
188
+ },
189
+ handler: async (ctx, args) => {
190
+ const now = Date.now();
191
+ const runId = await ctx.db.insert("cronJobRuns", {
192
+ cronJobId: args.cronJobId,
193
+ status: args.status,
194
+ startedAt: now,
195
+ ...(args.status !== "running" && { completedAt: now }),
196
+ ...(args.output && { output: args.output }),
197
+ ...(args.error && { error: args.error }),
198
+ });
199
+ return runId;
200
+ },
201
+ });
202
+
203
+ // Query: Get run history for a cron job
204
+ export const getRunHistory = query({
205
+ args: {
206
+ cronJobId: v.id("cronJobs"),
207
+ limit: v.optional(v.number()),
208
+ },
209
+ handler: async (ctx, args) => {
210
+ const runs = await ctx.db
211
+ .query("cronJobRuns")
212
+ .withIndex("byCronJobId", (q) => q.eq("cronJobId", args.cronJobId))
213
+ .take(100).collect();
214
+
215
+ // Sort by startedAt descending
216
+ runs.sort((a, b) => b.startedAt - a.startedAt);
217
+
218
+ if (args.limit) {
219
+ return runs.slice(0, args.limit);
220
+ }
221
+
222
+ return runs;
223
+ },
224
+ });
@@ -0,0 +1,103 @@
1
+ import { v } from "convex/values";
2
+ import { mutation, query } from "./_generated/server";
3
+
4
+ // Query: List files
5
+ export const list = query({
6
+ args: {
7
+ folderId: v.optional(v.id("folders")),
8
+ projectId: v.optional(v.id("projects")),
9
+ userId: v.optional(v.string()),
10
+ },
11
+ handler: async (ctx, args) => {
12
+ if (args.folderId) {
13
+ return await ctx.db
14
+ .query("files")
15
+ .withIndex("byFolderId", (q) => q.eq("folderId", args.folderId))
16
+ .take(100).collect();
17
+ }
18
+
19
+ if (args.projectId) {
20
+ return await ctx.db
21
+ .query("files")
22
+ .withIndex("byProjectId", (q) => q.eq("projectId", args.projectId))
23
+ .take(100).collect();
24
+ }
25
+
26
+ if (args.userId) {
27
+ return await ctx.db
28
+ .query("files")
29
+ .withIndex("byUserId", (q) => q.eq("userId", args.userId))
30
+ .take(100).collect();
31
+ }
32
+
33
+ return await ctx.db.query("files").take(100).collect();
34
+ },
35
+ });
36
+
37
+ // Query: Get file by ID
38
+ export const get = query({
39
+ args: { id: v.id("files") },
40
+ handler: async (ctx, args) => {
41
+ return await ctx.db.get(args.id);
42
+ },
43
+ });
44
+
45
+ // Mutation: Create file metadata (file stored in Cloudflare R2)
46
+ export const create = mutation({
47
+ args: {
48
+ name: v.string(),
49
+ originalName: v.string(),
50
+ mimeType: v.string(),
51
+ size: v.number(),
52
+ url: v.string(),
53
+ folderId: v.optional(v.id("folders")),
54
+ projectId: v.optional(v.id("projects")),
55
+ userId: v.optional(v.string()),
56
+ metadata: v.optional(v.any()),
57
+ },
58
+ handler: async (ctx, args) => {
59
+ const fileId = await ctx.db.insert("files", {
60
+ ...args,
61
+ uploadedAt: Date.now(),
62
+ });
63
+ return fileId;
64
+ },
65
+ });
66
+
67
+ // Mutation: Update file metadata
68
+ export const update = mutation({
69
+ args: {
70
+ id: v.id("files"),
71
+ name: v.optional(v.string()),
72
+ folderId: v.optional(v.id("folders")),
73
+ metadata: v.optional(v.any()),
74
+ },
75
+ handler: async (ctx, args) => {
76
+ const { id, ...updates } = args;
77
+ await ctx.db.patch(id, updates);
78
+ return id;
79
+ },
80
+ });
81
+
82
+ // Mutation: Delete file
83
+ export const remove = mutation({
84
+ args: { id: v.id("files") },
85
+ handler: async (ctx, args) => {
86
+ await ctx.db.delete(args.id);
87
+ return { success: true };
88
+ },
89
+ });
90
+
91
+ // Mutation: Move file to folder
92
+ export const moveToFolder = mutation({
93
+ args: {
94
+ id: v.id("files"),
95
+ folderId: v.optional(v.id("folders")),
96
+ },
97
+ handler: async (ctx, args) => {
98
+ await ctx.db.patch(args.id, {
99
+ folderId: args.folderId,
100
+ });
101
+ return args.id;
102
+ },
103
+ });