@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.
Files changed (67) 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 +185 -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/schema.ts +150 -83
  13. package/dist/default/convex/sessions.ts +174 -0
  14. package/dist/default/convex/settings.ts +79 -0
  15. package/dist/default/convex/skills.ts +178 -0
  16. package/dist/default/convex/threads.ts +100 -0
  17. package/dist/default/convex/usage.ts +195 -0
  18. package/dist/default/convex/vault.ts +397 -0
  19. package/dist/default/dashboard/app/main.tsx +7 -3
  20. package/dist/default/dashboard/app/routes/agents.tsx +103 -161
  21. package/dist/default/dashboard/app/routes/chat.tsx +163 -317
  22. package/dist/default/dashboard/app/routes/connections.tsx +247 -386
  23. package/dist/default/dashboard/app/routes/cron.tsx +127 -286
  24. package/dist/default/dashboard/app/routes/files.tsx +184 -167
  25. package/dist/default/dashboard/app/routes/index.tsx +63 -96
  26. package/dist/default/dashboard/app/routes/projects.tsx +106 -225
  27. package/dist/default/dashboard/app/routes/sessions.tsx +87 -253
  28. package/dist/default/dashboard/app/routes/settings.tsx +316 -532
  29. package/dist/default/dashboard/app/routes/skills.tsx +329 -216
  30. package/dist/default/dashboard/app/routes/usage.tsx +107 -150
  31. package/dist/default/dashboard/tsconfig.json +3 -2
  32. package/dist/default/dashboard/vite.config.ts +6 -0
  33. package/dist/index.js +256 -49
  34. package/dist/index.js.map +1 -1
  35. package/package.json +1 -1
  36. package/templates/default/convex/agents.ts +204 -0
  37. package/templates/default/convex/apiKeys.ts +133 -0
  38. package/templates/default/convex/cronJobs.ts +224 -0
  39. package/templates/default/convex/files.ts +103 -0
  40. package/templates/default/convex/folders.ts +110 -0
  41. package/templates/default/convex/heartbeat.ts +371 -0
  42. package/templates/default/convex/logs.ts +66 -0
  43. package/templates/default/convex/mastraIntegration.ts +185 -0
  44. package/templates/default/convex/mcpConnections.ts +127 -0
  45. package/templates/default/convex/messages.ts +90 -0
  46. package/templates/default/convex/projects.ts +114 -0
  47. package/templates/default/convex/schema.ts +150 -83
  48. package/templates/default/convex/sessions.ts +174 -0
  49. package/templates/default/convex/settings.ts +79 -0
  50. package/templates/default/convex/skills.ts +178 -0
  51. package/templates/default/convex/threads.ts +100 -0
  52. package/templates/default/convex/usage.ts +195 -0
  53. package/templates/default/convex/vault.ts +397 -0
  54. package/templates/default/dashboard/app/main.tsx +7 -3
  55. package/templates/default/dashboard/app/routes/agents.tsx +103 -161
  56. package/templates/default/dashboard/app/routes/chat.tsx +163 -317
  57. package/templates/default/dashboard/app/routes/connections.tsx +247 -386
  58. package/templates/default/dashboard/app/routes/cron.tsx +127 -286
  59. package/templates/default/dashboard/app/routes/files.tsx +184 -167
  60. package/templates/default/dashboard/app/routes/index.tsx +63 -96
  61. package/templates/default/dashboard/app/routes/projects.tsx +106 -225
  62. package/templates/default/dashboard/app/routes/sessions.tsx +87 -253
  63. package/templates/default/dashboard/app/routes/settings.tsx +316 -532
  64. package/templates/default/dashboard/app/routes/skills.tsx +329 -216
  65. package/templates/default/dashboard/app/routes/usage.tsx +107 -150
  66. package/templates/default/dashboard/tsconfig.json +3 -2
  67. package/templates/default/dashboard/vite.config.ts +6 -0
@@ -1,322 +1,163 @@
1
1
  import { createFileRoute } from '@tanstack/react-router';
2
2
  import { DashboardLayout } from '../components/DashboardLayout';
3
- import { useState } from 'react';
4
- import { Button } from '../components/ui/button';
5
- import { Card, CardContent, CardHeader, CardTitle } from '../components/ui/card';
6
- import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogFooter } from '../components/ui/dialog';
7
- import { Input } from '../components/ui/input';
8
- import { Label } from '../components/ui/label';
9
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../components/ui/select';
10
- import { Switch } from '../components/ui/switch';
11
- import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../components/ui/table';
12
- import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../components/ui/tooltip';
13
- import { Clock, Play, Pause, Plus, History, AlertCircle, Trash2, Edit } from 'lucide-react';
14
- // import { useQuery, useMutation } from 'convex/react';
15
- // import { api } from '../../convex/_generated/api';
3
+ import { useState, useMemo } from 'react';
4
+ import { useQuery, useMutation } from 'convex/react';
5
+ import { api } from '@convex/_generated/api';
6
+ import { Clock, Play, Pause, Plus, History, Trash2, Edit, X, AlertCircle } from 'lucide-react';
16
7
 
17
- export const Route = createFileRoute('/cron')({ component: CronPageComponent });
8
+ export const Route = createFileRoute('/cron')({ component: CronPage });
18
9
 
19
- type CronJob = {
20
- id: string;
21
- name: string;
22
- schedule: string;
23
- scheduleReadable: string;
24
- agent: string;
25
- status: 'enabled' | 'disabled';
26
- lastRun: string | null;
27
- nextRun: string;
28
- };
10
+ function CronPage() {
11
+ const cronJobs = useQuery(api.cronJobs.list, {}) ?? [];
12
+ const agents = useQuery(api.agents.list, {}) ?? [];
13
+ const createCron = useMutation(api.cronJobs.create);
14
+ const removeCron = useMutation(api.cronJobs.remove);
15
+ const toggleCron = useMutation(api.cronJobs.toggleEnabled);
29
16
 
30
- const initialCronJobs: CronJob[] = [
31
- {
32
- id: 'cron_1',
33
- name: 'Daily Report',
34
- schedule: '0 9 * * *',
35
- scheduleReadable: 'Every day at 9:00 AM',
36
- agent: 'reporting-agent',
37
- status: 'enabled',
38
- lastRun: '2026-02-15 09:00:12',
39
- nextRun: '2026-02-16 09:00:00',
40
- },
41
- {
42
- id: 'cron_2',
43
- name: 'Hourly Sync',
44
- schedule: '0 * * * *',
45
- scheduleReadable: 'Every hour',
46
- agent: 'sync-agent',
47
- status: 'disabled',
48
- lastRun: '2026-02-15 14:00:05',
49
- nextRun: '2026-02-15 16:00:00',
50
- },
51
- {
52
- id: 'cron_3',
53
- name: 'Nightly Cleanup',
54
- schedule: '0 2 * * *',
55
- scheduleReadable: 'Every day at 2:00 AM',
56
- agent: 'cleanup-agent',
57
- status: 'enabled',
58
- lastRun: '2026-02-15 02:00:08',
59
- nextRun: '2026-02-16 02:00:00',
60
- },
61
- ];
62
-
63
- function CronPageComponent() {
64
- // const cronJobs = useQuery(api.cronJobs.list) ?? [];
65
- // const createCronJob = useMutation(api.cronJobs.create);
66
- // const updateCronJob = useMutation(api.cronJobs.update);
67
- // const deleteCronJob = useMutation(api.cronJobs.delete);
68
-
69
- const [cronJobs, setCronJobs] = useState<CronJob[]>(initialCronJobs);
70
17
  const [isModalOpen, setIsModalOpen] = useState(false);
71
- const [editingJob, setEditingJob] = useState<CronJob | null>(null);
72
- const [selectedJobHistory, setSelectedJobHistory] = useState<CronJob | null>(null);
18
+ const [historyJobId, setHistoryJobId] = useState<string | null>(null);
73
19
 
74
- const handleSaveJob = (jobData: Omit<CronJob, 'id'>) => {
75
- if (editingJob) {
76
- const updatedJob = { ...editingJob, ...jobData };
77
- setCronJobs(cronJobs.map(j => j.id === editingJob.id ? updatedJob : j));
78
- // updateCronJob({ id: updatedJob.id, ...jobData });
79
- } else {
80
- const newJob = { ...jobData, id: `cron_${Date.now()}` };
81
- setCronJobs([...cronJobs, newJob]);
82
- // createCronJob(jobData);
83
- }
20
+ const handleCreate = async (data: any) => {
21
+ await createCron(data);
84
22
  setIsModalOpen(false);
85
- setEditingJob(null);
86
- };
87
-
88
- const handleToggleStatus = (job: CronJob) => {
89
- const newStatus = job.status === 'enabled' ? 'disabled' : 'enabled';
90
- const updatedJob = { ...job, status: newStatus };
91
- setCronJobs(cronJobs.map(j => j.id === job.id ? updatedJob : j));
92
- // updateCronJob({ id: job.id, status: newStatus });
93
23
  };
94
24
 
95
- const handleDeleteJob = (id: string) => {
96
- if (window.confirm('Are you sure you want to delete this cron job?')) {
97
- setCronJobs(cronJobs.filter(j => j.id !== id));
98
- // deleteCronJob({ id });
25
+ const handleDelete = async (id: any) => {
26
+ if (confirm('Delete this cron job?')) {
27
+ await removeCron({ id });
99
28
  }
100
29
  };
101
30
 
102
- const openEditModal = (job: CronJob) => {
103
- setEditingJob(job);
104
- setIsModalOpen(true);
105
- };
106
-
107
- const openCreateModal = () => {
108
- setEditingJob(null);
109
- setIsModalOpen(true);
110
- };
111
-
112
31
  return (
113
32
  <DashboardLayout>
114
- <div className="p-6">
115
- <div className="flex justify-between items-center mb-6">
116
- <h1 className="text-3xl font-bold">Cron Jobs</h1>
117
- <Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
118
- <DialogTrigger asChild>
119
- <Button onClick={openCreateModal}>
120
- <Plus className="mr-2 h-4 w-4" /> Add Cron Job
121
- </Button>
122
- </DialogTrigger>
123
- <CronJobForm
124
- job={editingJob}
125
- onSave={handleSaveJob}
126
- onClose={() => setIsModalOpen(false)}
127
- />
128
- </Dialog>
33
+ <div className="space-y-6">
34
+ <div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
35
+ <div>
36
+ <h1 className="text-3xl font-bold">Scheduled Tasks</h1>
37
+ <p className="text-muted-foreground">Automate agent execution with cron schedules.</p>
38
+ </div>
39
+ <button onClick={() => setIsModalOpen(true)} className="bg-primary text-primary-foreground px-4 py-2 rounded-lg hover:bg-primary/90 flex items-center gap-2">
40
+ <Plus className="w-4 h-4" /> New Schedule
41
+ </button>
129
42
  </div>
130
43
 
131
- <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
132
- <Card className="lg:col-span-2">
133
- <CardHeader>
134
- <CardTitle>Scheduled Jobs</CardTitle>
135
- </CardHeader>
136
- <CardContent>
137
- {cronJobs.length > 0 ? (
138
- <Table>
139
- <TableHeader>
140
- <TableRow>
141
- <TableHead>Status</TableHead>
142
- <TableHead>Name</TableHead>
143
- <TableHead>Schedule</TableHead>
144
- <TableHead>Agent</TableHead>
145
- <TableHead>Last Run</TableHead>
146
- <TableHead>Next Run</TableHead>
147
- <TableHead className="text-right">Actions</TableHead>
148
- </TableRow>
149
- </TableHeader>
150
- <TableBody>
151
- {cronJobs.map(job => (
152
- <TableRow key={job.id}>
153
- <TableCell>
154
- <TooltipProvider>
155
- <Tooltip>
156
- <TooltipTrigger asChild>
157
- <button onClick={() => handleToggleStatus(job)}>
158
- {job.status === 'enabled' ? (
159
- <Play className="h-5 w-5 text-green-500" />
160
- ) : (
161
- <Pause className="h-5 w-5 text-yellow-500" />
162
- )}
163
- </button>
164
- </TooltipTrigger>
165
- <TooltipContent>
166
- <p>{job.status === 'enabled' ? 'Enabled (Click to disable)' : 'Disabled (Click to enable)'}</p>
167
- </TooltipContent>
168
- </Tooltip>
169
- </TooltipProvider>
170
- </TableCell>
171
- <TableCell className="font-medium">{job.name}</TableCell>
172
- <TableCell>
173
- <TooltipProvider>
174
- <Tooltip>
175
- <TooltipTrigger className="cursor-help">{job.scheduleReadable}</TooltipTrigger>
176
- <TooltipContent><p>{job.schedule}</p></TooltipContent>
177
- </Tooltip>
178
- </TooltipProvider>
179
- </TableCell>
180
- <TableCell>{job.agent}</TableCell>
181
- <TableCell>{job.lastRun || 'N/A'}</TableCell>
182
- <TableCell>{job.nextRun}</TableCell>
183
- <TableCell className="text-right space-x-2">
184
- <Button variant="ghost" size="icon" onClick={() => setSelectedJobHistory(job)}>
185
- <History className="h-4 w-4" />
186
- </Button>
187
- <Button variant="ghost" size="icon" onClick={() => openEditModal(job)}>
188
- <Edit className="h-4 w-4" />
189
- </Button>
190
- <Button variant="destructive" size="icon" onClick={() => handleDeleteJob(job.id)}>
191
- <Trash2 className="h-4 w-4" />
192
- </Button>
193
- </TableCell>
194
- </TableRow>
195
- ))}
196
- </TableBody>
197
- </Table>
198
- ) : (
199
- <div className="text-center py-12 text-muted-foreground">
200
- <Clock className="mx-auto h-12 w-12" />
201
- <h3 className="mt-4 text-lg font-semibold">No Cron Jobs Scheduled</h3>
202
- <p className="mt-2 text-sm">Get started by adding a new cron job.</p>
203
- </div>
204
- )}
205
- </CardContent>
206
- </Card>
44
+ {cronJobs.length === 0 ? (
45
+ <div className="text-center py-16 bg-card border border-border rounded-lg">
46
+ <Clock className="w-16 h-16 text-muted-foreground/30 mx-auto mb-4" />
47
+ <h3 className="text-lg font-semibold mb-2">No scheduled tasks</h3>
48
+ <p className="text-muted-foreground mb-4">Create a cron job to run agents on a schedule.</p>
49
+ <button onClick={() => setIsModalOpen(true)} className="bg-primary text-primary-foreground px-4 py-2 rounded-lg hover:bg-primary/90">
50
+ <Plus className="w-4 h-4 inline mr-2" />Create Schedule
51
+ </button>
52
+ </div>
53
+ ) : (
54
+ <div className="bg-card border border-border rounded-lg overflow-hidden">
55
+ <table className="w-full text-sm">
56
+ <thead className="bg-muted/50">
57
+ <tr>
58
+ <th className="text-left px-4 py-3 font-medium text-muted-foreground">Name</th>
59
+ <th className="text-left px-4 py-3 font-medium text-muted-foreground">Schedule</th>
60
+ <th className="text-left px-4 py-3 font-medium text-muted-foreground">Agent</th>
61
+ <th className="text-left px-4 py-3 font-medium text-muted-foreground">Status</th>
62
+ <th className="text-left px-4 py-3 font-medium text-muted-foreground">Last Run</th>
63
+ <th className="text-right px-4 py-3 font-medium text-muted-foreground">Actions</th>
64
+ </tr>
65
+ </thead>
66
+ <tbody>
67
+ {cronJobs.map((job: any) => (
68
+ <tr key={job._id} className="border-t border-border hover:bg-muted/30">
69
+ <td className="px-4 py-3">
70
+ <div>
71
+ <p className="font-medium">{job.name}</p>
72
+ {job.description && <p className="text-xs text-muted-foreground">{job.description}</p>}
73
+ </div>
74
+ </td>
75
+ <td className="px-4 py-3 font-mono text-xs">{job.schedule}</td>
76
+ <td className="px-4 py-3 text-muted-foreground">{job.agentId}</td>
77
+ <td className="px-4 py-3">
78
+ <span className={`text-xs px-2 py-0.5 rounded-full ${job.isEnabled ? 'bg-green-500/10 text-green-500' : 'bg-muted text-muted-foreground'}`}>
79
+ {job.isEnabled ? 'Enabled' : 'Disabled'}
80
+ </span>
81
+ </td>
82
+ <td className="px-4 py-3 text-xs text-muted-foreground">{job.lastRun ? new Date(job.lastRun).toLocaleString() : 'Never'}</td>
83
+ <td className="px-4 py-3 text-right">
84
+ <div className="flex items-center justify-end gap-1">
85
+ <button onClick={() => toggleCron({ id: job._id })} className="p-1.5 rounded hover:bg-muted" title={job.isEnabled ? 'Pause' : 'Resume'}>
86
+ {job.isEnabled ? <Pause className="w-4 h-4 text-muted-foreground" /> : <Play className="w-4 h-4 text-green-500" />}
87
+ </button>
88
+ <button onClick={() => handleDelete(job._id)} className="p-1.5 rounded hover:bg-destructive/10"><Trash2 className="w-4 h-4 text-destructive" /></button>
89
+ </div>
90
+ </td>
91
+ </tr>
92
+ ))}
93
+ </tbody>
94
+ </table>
95
+ </div>
96
+ )}
207
97
 
208
- <Card className="lg:col-span-1 h-fit">
209
- <CardHeader>
210
- <CardTitle className="flex items-center">
211
- <History className="mr-2 h-5 w-5" />
212
- Execution History
213
- </CardTitle>
214
- </CardHeader>
215
- <CardContent>
216
- {selectedJobHistory ? (
217
- <div>
218
- <h4 className="font-semibold mb-2">{selectedJobHistory.name}</h4>
219
- <ul className="space-y-2 text-sm text-muted-foreground">
220
- {[...Array(5)].map((_, i) => (
221
- <li key={i} className="flex items-center justify-between p-2 bg-background rounded-md border">
222
- <span>Run #{15 - i}</span>
223
- <span className="text-xs">{(new Date(Date.now() - i * 3600000)).toLocaleString()}</span>
224
- <span className="text-green-500">Success</span>
225
- </li>
226
- ))}
227
- </ul>
228
- </div>
229
- ) : (
230
- <div className="text-center py-10 text-muted-foreground">
231
- <AlertCircle className="mx-auto h-10 w-10" />
232
- <p className="mt-4 text-sm">Select a job to view its execution history.</p>
233
- </div>
234
- )}
235
- </CardContent>
236
- </Card>
237
- </div>
98
+ {isModalOpen && <CronModal agents={agents} onSave={handleCreate} onClose={() => setIsModalOpen(false)} />}
238
99
  </div>
239
100
  </DashboardLayout>
240
101
  );
241
102
  }
242
103
 
243
- interface CronJobFormProps {
244
- job: CronJob | null;
245
- onSave: (jobData: Omit<CronJob, 'id'>) => void;
246
- onClose: () => void;
247
- }
104
+ function CronModal({ agents, onSave, onClose }: { agents: any[]; onSave: (data: any) => void; onClose: () => void }) {
105
+ const [form, setForm] = useState({ name: '', description: '', schedule: '0 9 * * *', agentId: agents[0]?.id || '', prompt: '' });
248
106
 
249
- function CronJobForm({ job, onSave, onClose }: CronJobFormProps) {
250
- const [name, setName] = useState(job?.name || '');
251
- const [agent, setAgent] = useState(job?.agent || '');
252
- const [schedulePreset, setSchedulePreset] = useState('custom');
253
- const [customSchedule, setCustomSchedule] = useState(job?.schedule || '');
107
+ const presets = [
108
+ { label: 'Every minute', value: '* * * * *' },
109
+ { label: 'Every hour', value: '0 * * * *' },
110
+ { label: 'Daily at 9 AM', value: '0 9 * * *' },
111
+ { label: 'Weekly (Mon 9 AM)', value: '0 9 * * 1' },
112
+ { label: 'Monthly (1st at 9 AM)', value: '0 9 1 * *' },
113
+ ];
254
114
 
255
- const handleSubmit = (e: React.FormEvent) => {
115
+ const handleSubmit = (e: any) => {
256
116
  e.preventDefault();
257
- const schedule = schedulePreset === 'custom' ? customSchedule : schedulePreset;
258
- // This is a simplified readable schedule. A real implementation would use a library.
259
- const scheduleReadable = schedulePreset === 'custom' ? `Custom: ${customSchedule}` : `Every ${schedulePreset.split(' ')[1]}`;
260
-
261
- onSave({
262
- name,
263
- agent,
264
- schedule,
265
- scheduleReadable,
266
- status: job?.status || 'enabled',
267
- lastRun: job?.lastRun || null,
268
- nextRun: job?.nextRun || 'Calculating...',
269
- });
117
+ onSave(form);
270
118
  };
271
119
 
272
120
  return (
273
- <DialogContent className="sm:max-w-[425px]">
274
- <DialogHeader>
275
- <DialogTitle>{job ? 'Edit Cron Job' : 'Create Cron Job'}</DialogTitle>
276
- </DialogHeader>
277
- <form onSubmit={handleSubmit}>
278
- <div className="grid gap-4 py-4">
279
- <div className="grid grid-cols-4 items-center gap-4">
280
- <Label htmlFor="name" className="text-right">Name</Label>
281
- <Input id="name" value={name} onChange={e => setName(e.target.value)} className="col-span-3" placeholder="e.g., Daily Summary" required />
121
+ <div className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4">
122
+ <div className="bg-card border border-border rounded-lg shadow-xl w-full max-w-lg">
123
+ <div className="flex justify-between items-center p-4 border-b border-border">
124
+ <h2 className="text-lg font-bold">New Scheduled Task</h2>
125
+ <button onClick={onClose} className="text-muted-foreground hover:text-foreground"><X className="h-5 w-5" /></button>
126
+ </div>
127
+ <form onSubmit={handleSubmit} className="p-6 space-y-4">
128
+ <div>
129
+ <label className="block text-sm font-medium mb-1">Name</label>
130
+ <input type="text" value={form.name} onChange={(e) => setForm(prev => ({ ...prev, name: e.target.value }))} className="w-full bg-background border border-border rounded-md px-3 py-2 text-sm" placeholder="e.g. Daily Report" required />
282
131
  </div>
283
- <div className="grid grid-cols-4 items-center gap-4">
284
- <Label htmlFor="agent" className="text-right">Agent</Label>
285
- <Input id="agent" value={agent} onChange={e => setAgent(e.target.value)} className="col-span-3" placeholder="e.g., reporting-agent" required />
132
+ <div>
133
+ <label className="block text-sm font-medium mb-1">Description</label>
134
+ <input type="text" value={form.description} onChange={(e) => setForm(prev => ({ ...prev, description: e.target.value }))} className="w-full bg-background border border-border rounded-md px-3 py-2 text-sm" placeholder="Optional description" />
286
135
  </div>
287
- <div className="grid grid-cols-4 items-center gap-4">
288
- <Label htmlFor="schedule" className="text-right">Schedule</Label>
289
- <Select onValueChange={setSchedulePreset} defaultValue={schedulePreset}>
290
- <SelectTrigger className="col-span-3">
291
- <SelectValue placeholder="Select a preset" />
292
- </SelectTrigger>
293
- <SelectContent>
294
- <SelectItem value="0 * * * *">Every Hour</SelectItem>
295
- <SelectItem value="0 0 * * *">Every Day (midnight)</SelectItem>
296
- <SelectItem value="0 0 * * 1">Every Week (Monday)</SelectItem>
297
- <SelectItem value="custom">Custom Cron Expression</SelectItem>
298
- </SelectContent>
299
- </Select>
136
+ <div>
137
+ <label className="block text-sm font-medium mb-1">Agent</label>
138
+ <select value={form.agentId} onChange={(e) => setForm(prev => ({ ...prev, agentId: e.target.value }))} className="w-full bg-background border border-border rounded-md px-3 py-2 text-sm" required>
139
+ {agents.length === 0 ? <option value="">No agents available</option> : agents.map((a: any) => <option key={a.id} value={a.id}>{a.name}</option>)}
140
+ </select>
300
141
  </div>
301
- {schedulePreset === 'custom' && (
302
- <div className="grid grid-cols-4 items-center gap-4">
303
- <Label htmlFor="custom-schedule" className="text-right">Cron</Label>
304
- <Input
305
- id="custom-schedule"
306
- value={customSchedule}
307
- onChange={e => setCustomSchedule(e.target.value)}
308
- className="col-span-3"
309
- placeholder="* * * * *"
310
- required
311
- />
142
+ <div>
143
+ <label className="block text-sm font-medium mb-1">Cron Schedule</label>
144
+ <input type="text" value={form.schedule} onChange={(e) => setForm(prev => ({ ...prev, schedule: e.target.value }))} className="w-full bg-background border border-border rounded-md px-3 py-2 text-sm font-mono" required />
145
+ <div className="flex flex-wrap gap-1 mt-2">
146
+ {presets.map(p => (
147
+ <button key={p.value} type="button" onClick={() => setForm(prev => ({ ...prev, schedule: p.value }))} className={`text-xs px-2 py-1 rounded border ${form.schedule === p.value ? 'bg-primary text-primary-foreground border-primary' : 'bg-muted border-border text-muted-foreground hover:text-foreground'}`}>{p.label}</button>
148
+ ))}
312
149
  </div>
313
- )}
150
+ </div>
151
+ <div>
152
+ <label className="block text-sm font-medium mb-1">Prompt</label>
153
+ <textarea value={form.prompt} onChange={(e) => setForm(prev => ({ ...prev, prompt: e.target.value }))} rows={3} className="w-full bg-background border border-border rounded-md px-3 py-2 text-sm" placeholder="What should the agent do?" required />
154
+ </div>
155
+ </form>
156
+ <div className="p-4 border-t border-border flex justify-end gap-2">
157
+ <button onClick={onClose} className="px-4 py-2 rounded-lg bg-muted text-muted-foreground text-sm">Cancel</button>
158
+ <button onClick={handleSubmit} disabled={!form.name || !form.agentId || !form.prompt} className="px-4 py-2 rounded-lg bg-primary text-primary-foreground text-sm hover:bg-primary/90 disabled:opacity-50">Create Schedule</button>
314
159
  </div>
315
- <DialogFooter>
316
- <Button type="button" variant="ghost" onClick={onClose}>Cancel</Button>
317
- <Button type="submit">Save Job</Button>
318
- </DialogFooter>
319
- </form>
320
- </DialogContent>
160
+ </div>
161
+ </div>
321
162
  );
322
163
  }