@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,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
|
+
});
|