@agentforge-ai/cli 0.4.3 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/default/convex/agents.ts +204 -0
- package/dist/default/convex/apiKeys.ts +133 -0
- package/dist/default/convex/cronJobs.ts +224 -0
- package/dist/default/convex/files.ts +103 -0
- package/dist/default/convex/folders.ts +110 -0
- package/dist/default/convex/heartbeat.ts +371 -0
- package/dist/default/convex/logs.ts +66 -0
- package/dist/default/convex/mastraIntegration.ts +185 -0
- package/dist/default/convex/mcpConnections.ts +127 -0
- package/dist/default/convex/messages.ts +90 -0
- package/dist/default/convex/projects.ts +114 -0
- package/dist/default/convex/schema.ts +150 -83
- package/dist/default/convex/sessions.ts +174 -0
- package/dist/default/convex/settings.ts +79 -0
- package/dist/default/convex/skills.ts +178 -0
- package/dist/default/convex/threads.ts +100 -0
- package/dist/default/convex/usage.ts +195 -0
- package/dist/default/convex/vault.ts +397 -0
- package/dist/default/dashboard/app/main.tsx +7 -3
- package/dist/default/dashboard/app/routes/agents.tsx +103 -161
- package/dist/default/dashboard/app/routes/chat.tsx +163 -317
- package/dist/default/dashboard/app/routes/connections.tsx +247 -386
- package/dist/default/dashboard/app/routes/cron.tsx +127 -286
- package/dist/default/dashboard/app/routes/files.tsx +184 -167
- package/dist/default/dashboard/app/routes/index.tsx +63 -96
- package/dist/default/dashboard/app/routes/projects.tsx +106 -225
- package/dist/default/dashboard/app/routes/sessions.tsx +87 -253
- package/dist/default/dashboard/app/routes/settings.tsx +316 -532
- package/dist/default/dashboard/app/routes/skills.tsx +329 -216
- package/dist/default/dashboard/app/routes/usage.tsx +107 -150
- package/dist/default/dashboard/tsconfig.json +3 -2
- package/dist/default/dashboard/vite.config.ts +6 -0
- package/dist/index.js +256 -49
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/default/convex/agents.ts +204 -0
- package/templates/default/convex/apiKeys.ts +133 -0
- package/templates/default/convex/cronJobs.ts +224 -0
- package/templates/default/convex/files.ts +103 -0
- package/templates/default/convex/folders.ts +110 -0
- package/templates/default/convex/heartbeat.ts +371 -0
- package/templates/default/convex/logs.ts +66 -0
- package/templates/default/convex/mastraIntegration.ts +185 -0
- package/templates/default/convex/mcpConnections.ts +127 -0
- package/templates/default/convex/messages.ts +90 -0
- package/templates/default/convex/projects.ts +114 -0
- package/templates/default/convex/schema.ts +150 -83
- package/templates/default/convex/sessions.ts +174 -0
- package/templates/default/convex/settings.ts +79 -0
- package/templates/default/convex/skills.ts +178 -0
- package/templates/default/convex/threads.ts +100 -0
- package/templates/default/convex/usage.ts +195 -0
- package/templates/default/convex/vault.ts +397 -0
- package/templates/default/dashboard/app/main.tsx +7 -3
- package/templates/default/dashboard/app/routes/agents.tsx +103 -161
- package/templates/default/dashboard/app/routes/chat.tsx +163 -317
- package/templates/default/dashboard/app/routes/connections.tsx +247 -386
- package/templates/default/dashboard/app/routes/cron.tsx +127 -286
- package/templates/default/dashboard/app/routes/files.tsx +184 -167
- package/templates/default/dashboard/app/routes/index.tsx +63 -96
- package/templates/default/dashboard/app/routes/projects.tsx +106 -225
- package/templates/default/dashboard/app/routes/sessions.tsx +87 -253
- package/templates/default/dashboard/app/routes/settings.tsx +316 -532
- package/templates/default/dashboard/app/routes/skills.tsx +329 -216
- package/templates/default/dashboard/app/routes/usage.tsx +107 -150
- package/templates/default/dashboard/tsconfig.json +3 -2
- package/templates/default/dashboard/vite.config.ts +6 -0
|
@@ -1,252 +1,365 @@
|
|
|
1
1
|
import { createFileRoute } from '@tanstack/react-router';
|
|
2
2
|
import { DashboardLayout } from '../components/DashboardLayout';
|
|
3
3
|
import { useState, useMemo } from 'react';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import { Sparkles, Download, Trash2, Plus, Search,
|
|
7
|
-
|
|
8
|
-
// --- Mock Data and Types ---
|
|
9
|
-
type SkillCategory = 'Tools' | 'Knowledge' | 'Workflows' | 'Integrations';
|
|
10
|
-
|
|
11
|
-
interface Skill {
|
|
12
|
-
id: string;
|
|
13
|
-
name: string;
|
|
14
|
-
description: string;
|
|
15
|
-
category: SkillCategory;
|
|
16
|
-
version: string;
|
|
17
|
-
isInstalled: boolean;
|
|
18
|
-
}
|
|
4
|
+
import { useQuery, useMutation } from 'convex/react';
|
|
5
|
+
import { api } from '@convex/_generated/api';
|
|
6
|
+
import { Sparkles, Download, Trash2, Plus, Search, X, Check, Code, Globe, Calculator, FileText, Database, Mail, Wrench } from 'lucide-react';
|
|
19
7
|
|
|
20
|
-
const
|
|
21
|
-
{ id: '1', name: 'Web Scraper', description: 'Extracts data from websites using CSS selectors.', category: 'Tools', version: '1.2.0', isInstalled: true },
|
|
22
|
-
{ id: '2', name: 'Sentiment Analysis', description: 'Analyzes text to determine emotional tone.', category: 'Knowledge', version: '2.0.1', isInstalled: false },
|
|
23
|
-
{ id: '3', name: 'Daily Report', description: 'Generates a daily summary of activities.', category: 'Workflows', version: '1.0.0', isInstalled: true },
|
|
24
|
-
{ id: '4', name: 'GitHub Integration', description: 'Connects to GitHub to manage repositories.', category: 'Integrations', version: '1.5.3', isInstalled: false },
|
|
25
|
-
{ id: '5', name: 'Image Resizer', description: 'Resizes images to specified dimensions.', category: 'Tools', version: '1.1.0', isInstalled: false },
|
|
26
|
-
{ id: '6', name: 'Company Financials', description: 'Provides financial data for public companies.', category: 'Knowledge', version: '3.1.0', isInstalled: true },
|
|
27
|
-
];
|
|
8
|
+
export const Route = createFileRoute('/skills')({ component: SkillsPage });
|
|
28
9
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (!name || !description || !version) return;
|
|
42
|
-
onCreateSkill({ name, description, category, version });
|
|
43
|
-
onOpenChange(false);
|
|
44
|
-
setName('');
|
|
45
|
-
setDescription('');
|
|
46
|
-
setCategory('Tools');
|
|
47
|
-
setVersion('1.0.0');
|
|
48
|
-
};
|
|
10
|
+
// ─── Prebuilt Skills Catalog ─────────────────────────────────────
|
|
11
|
+
const PREBUILT_SKILLS = [
|
|
12
|
+
{
|
|
13
|
+
name: 'web-search',
|
|
14
|
+
displayName: 'Web Search',
|
|
15
|
+
description: 'Search the web using DuckDuckGo and return structured results. Supports query refinement and result filtering.',
|
|
16
|
+
category: 'Tools',
|
|
17
|
+
version: '1.0.0',
|
|
18
|
+
author: 'AgentForge',
|
|
19
|
+
icon: Globe,
|
|
20
|
+
code: `import { createTool } from '@mastra/core';
|
|
21
|
+
import { z } from 'zod';
|
|
49
22
|
|
|
50
|
-
|
|
23
|
+
export const webSearch = createTool({
|
|
24
|
+
id: 'web-search',
|
|
25
|
+
description: 'Search the web and return results',
|
|
26
|
+
inputSchema: z.object({
|
|
27
|
+
query: z.string().describe('Search query'),
|
|
28
|
+
maxResults: z.number().default(5).describe('Maximum results to return'),
|
|
29
|
+
}),
|
|
30
|
+
execute: async ({ context }) => {
|
|
31
|
+
const url = \`https://api.duckduckgo.com/?q=\${encodeURIComponent(context.query)}&format=json&no_redirect=1\`;
|
|
32
|
+
const res = await fetch(url);
|
|
33
|
+
const data = await res.json();
|
|
34
|
+
return { results: data.RelatedTopics?.slice(0, context.maxResults) || [], source: 'duckduckgo' };
|
|
35
|
+
},
|
|
36
|
+
});`,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'code-executor',
|
|
40
|
+
displayName: 'Code Executor',
|
|
41
|
+
description: 'Safely execute JavaScript/TypeScript code snippets in a sandboxed environment and return the output.',
|
|
42
|
+
category: 'Tools',
|
|
43
|
+
version: '1.0.0',
|
|
44
|
+
author: 'AgentForge',
|
|
45
|
+
icon: Code,
|
|
46
|
+
code: `import { createTool } from '@mastra/core';
|
|
47
|
+
import { z } from 'zod';
|
|
51
48
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
</div>
|
|
80
|
-
<div className="flex justify-end gap-2 pt-4">
|
|
81
|
-
<button type="button" onClick={() => onOpenChange(false)} className="px-4 py-2 rounded-md border border-border text-foreground hover:bg-zinc-800 transition-colors">Cancel</button>
|
|
82
|
-
<button type="submit" className="px-4 py-2 rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors">Create Skill</button>
|
|
83
|
-
</div>
|
|
84
|
-
</form>
|
|
85
|
-
</div>
|
|
86
|
-
</div>
|
|
87
|
-
);
|
|
88
|
-
}
|
|
49
|
+
export const codeExecutor = createTool({
|
|
50
|
+
id: 'code-executor',
|
|
51
|
+
description: 'Execute JavaScript code and return the result',
|
|
52
|
+
inputSchema: z.object({
|
|
53
|
+
code: z.string().describe('JavaScript code to execute'),
|
|
54
|
+
}),
|
|
55
|
+
execute: async ({ context }) => {
|
|
56
|
+
try {
|
|
57
|
+
const fn = new Function('return (async () => {' + context.code + '})()');
|
|
58
|
+
const result = await fn();
|
|
59
|
+
return { success: true, output: String(result) };
|
|
60
|
+
} catch (error: any) {
|
|
61
|
+
return { success: false, error: error.message };
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
});`,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'calculator',
|
|
68
|
+
displayName: 'Calculator',
|
|
69
|
+
description: 'Perform mathematical calculations. Supports basic arithmetic, trigonometry, and common math functions.',
|
|
70
|
+
category: 'Tools',
|
|
71
|
+
version: '1.0.0',
|
|
72
|
+
author: 'AgentForge',
|
|
73
|
+
icon: Calculator,
|
|
74
|
+
code: `import { createTool } from '@mastra/core';
|
|
75
|
+
import { z } from 'zod';
|
|
89
76
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
77
|
+
export const calculator = createTool({
|
|
78
|
+
id: 'calculator',
|
|
79
|
+
description: 'Evaluate a mathematical expression',
|
|
80
|
+
inputSchema: z.object({
|
|
81
|
+
expression: z.string().describe('Math expression to evaluate, e.g. "2 + 2 * 3"'),
|
|
82
|
+
}),
|
|
83
|
+
execute: async ({ context }) => {
|
|
84
|
+
try {
|
|
85
|
+
const sanitized = context.expression.replace(/[^0-9+\\-*/().%\\s]/g, '');
|
|
86
|
+
const result = Function('"use strict"; return (' + sanitized + ')')();
|
|
87
|
+
return { result: Number(result), expression: context.expression };
|
|
88
|
+
} catch (error: any) {
|
|
89
|
+
return { error: 'Invalid expression: ' + error.message };
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
});`,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'text-summarizer',
|
|
96
|
+
displayName: 'Text Summarizer',
|
|
97
|
+
description: 'Summarize long text into concise bullet points or paragraphs using extractive summarization.',
|
|
98
|
+
category: 'Knowledge',
|
|
99
|
+
version: '1.0.0',
|
|
100
|
+
author: 'AgentForge',
|
|
101
|
+
icon: FileText,
|
|
102
|
+
code: `import { createTool } from '@mastra/core';
|
|
103
|
+
import { z } from 'zod';
|
|
102
104
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
105
|
+
export const textSummarizer = createTool({
|
|
106
|
+
id: 'text-summarizer',
|
|
107
|
+
description: 'Summarize text into key points',
|
|
108
|
+
inputSchema: z.object({
|
|
109
|
+
text: z.string().describe('Text to summarize'),
|
|
110
|
+
maxSentences: z.number().default(3).describe('Maximum sentences in summary'),
|
|
111
|
+
}),
|
|
112
|
+
execute: async ({ context }) => {
|
|
113
|
+
const sentences = context.text.match(/[^.!?]+[.!?]+/g) || [context.text];
|
|
114
|
+
const scored = sentences.map((s, i) => ({
|
|
115
|
+
text: s.trim(),
|
|
116
|
+
score: s.split(' ').length * (i === 0 ? 1.5 : 1),
|
|
117
|
+
}));
|
|
118
|
+
scored.sort((a, b) => b.score - a.score);
|
|
119
|
+
return { summary: scored.slice(0, context.maxSentences).map(s => s.text).join(' ') };
|
|
120
|
+
},
|
|
121
|
+
});`,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'json-transformer',
|
|
125
|
+
displayName: 'JSON Transformer',
|
|
126
|
+
description: 'Parse, validate, and transform JSON data. Extract fields, flatten nested objects, and convert formats.',
|
|
127
|
+
category: 'Tools',
|
|
128
|
+
version: '1.0.0',
|
|
129
|
+
author: 'AgentForge',
|
|
130
|
+
icon: Database,
|
|
131
|
+
code: `import { createTool } from '@mastra/core';
|
|
132
|
+
import { z } from 'zod';
|
|
131
133
|
|
|
132
|
-
export const
|
|
134
|
+
export const jsonTransformer = createTool({
|
|
135
|
+
id: 'json-transformer',
|
|
136
|
+
description: 'Parse and transform JSON data',
|
|
137
|
+
inputSchema: z.object({
|
|
138
|
+
json: z.string().describe('JSON string to transform'),
|
|
139
|
+
operation: z.enum(['parse', 'flatten', 'extract']).describe('Operation to perform'),
|
|
140
|
+
path: z.string().optional().describe('JSON path for extraction (e.g. "data.users")'),
|
|
141
|
+
}),
|
|
142
|
+
execute: async ({ context }) => {
|
|
143
|
+
const data = JSON.parse(context.json);
|
|
144
|
+
if (context.operation === 'parse') return { result: data };
|
|
145
|
+
if (context.operation === 'extract' && context.path) {
|
|
146
|
+
const keys = context.path.split('.');
|
|
147
|
+
let val = data;
|
|
148
|
+
for (const k of keys) val = val?.[k];
|
|
149
|
+
return { result: val };
|
|
150
|
+
}
|
|
151
|
+
if (context.operation === 'flatten') {
|
|
152
|
+
const flat: Record<string, any> = {};
|
|
153
|
+
const recurse = (obj: any, prefix = '') => {
|
|
154
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
155
|
+
const key = prefix ? prefix + '.' + k : k;
|
|
156
|
+
if (typeof v === 'object' && v !== null && !Array.isArray(v)) recurse(v, key);
|
|
157
|
+
else flat[key] = v;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
recurse(data);
|
|
161
|
+
return { result: flat };
|
|
162
|
+
}
|
|
163
|
+
return { error: 'Unknown operation' };
|
|
164
|
+
},
|
|
165
|
+
});`,
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: 'email-drafter',
|
|
169
|
+
displayName: 'Email Drafter',
|
|
170
|
+
description: 'Generate professional email drafts based on context, tone, and recipient information.',
|
|
171
|
+
category: 'Workflows',
|
|
172
|
+
version: '1.0.0',
|
|
173
|
+
author: 'AgentForge',
|
|
174
|
+
icon: Mail,
|
|
175
|
+
code: `import { createTool } from '@mastra/core';
|
|
176
|
+
import { z } from 'zod';
|
|
177
|
+
|
|
178
|
+
export const emailDrafter = createTool({
|
|
179
|
+
id: 'email-drafter',
|
|
180
|
+
description: 'Draft a professional email',
|
|
181
|
+
inputSchema: z.object({
|
|
182
|
+
to: z.string().describe('Recipient name or role'),
|
|
183
|
+
subject: z.string().describe('Email subject'),
|
|
184
|
+
context: z.string().describe('What the email should convey'),
|
|
185
|
+
tone: z.enum(['formal', 'casual', 'friendly']).default('formal'),
|
|
186
|
+
}),
|
|
187
|
+
execute: async ({ context: ctx }) => {
|
|
188
|
+
const greeting = ctx.tone === 'formal' ? 'Dear' : ctx.tone === 'friendly' ? 'Hi' : 'Hello';
|
|
189
|
+
const closing = ctx.tone === 'formal' ? 'Best regards' : ctx.tone === 'friendly' ? 'Cheers' : 'Thanks';
|
|
190
|
+
return {
|
|
191
|
+
draft: \`Subject: \${ctx.subject}\\n\\n\${greeting} \${ctx.to},\\n\\n\${ctx.context}\\n\\n\${closing}\`,
|
|
192
|
+
metadata: { tone: ctx.tone, to: ctx.to },
|
|
193
|
+
};
|
|
194
|
+
},
|
|
195
|
+
});`,
|
|
196
|
+
},
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
const CATEGORIES = ['All', 'Tools', 'Knowledge', 'Workflows'];
|
|
133
200
|
|
|
134
201
|
function SkillsPage() {
|
|
135
|
-
const
|
|
202
|
+
const installedSkills = useQuery(api.skills.list, {}) ?? [];
|
|
203
|
+
const installSkill = useMutation(api.skills.create);
|
|
204
|
+
const removeSkill = useMutation(api.skills.remove);
|
|
205
|
+
const toggleSkill = useMutation(api.skills.toggleEnabled);
|
|
206
|
+
|
|
136
207
|
const [searchQuery, setSearchQuery] = useState('');
|
|
137
|
-
const [
|
|
138
|
-
const [
|
|
139
|
-
|
|
140
|
-
// const allSkillsQuery = useQuery(api.skills.list);
|
|
141
|
-
// const createSkillMutation = useMutation(api.skills.create);
|
|
142
|
-
// const isLoading = allSkillsQuery === undefined;
|
|
143
|
-
// const skillsData = allSkillsQuery || [];
|
|
144
|
-
|
|
145
|
-
const handleToggleCategory = (category: SkillCategory) => {
|
|
146
|
-
setSelectedCategories(prev =>
|
|
147
|
-
prev.includes(category)
|
|
148
|
-
? prev.filter(c => c !== category)
|
|
149
|
-
: [...prev, category]
|
|
150
|
-
);
|
|
151
|
-
};
|
|
208
|
+
const [categoryFilter, setCategoryFilter] = useState('All');
|
|
209
|
+
const [tab, setTab] = useState<'installed' | 'marketplace'>('marketplace');
|
|
210
|
+
const [selectedSkill, setSelectedSkill] = useState<typeof PREBUILT_SKILLS[0] | null>(null);
|
|
152
211
|
|
|
153
|
-
const
|
|
154
|
-
const newSkill: Skill = {
|
|
155
|
-
...newSkillData,
|
|
156
|
-
id: `custom-${Date.now()}`,
|
|
157
|
-
isInstalled: false,
|
|
158
|
-
};
|
|
159
|
-
setSkills(prev => [newSkill, ...prev]);
|
|
160
|
-
// createSkillMutation(newSkillData).catch(console.error);
|
|
161
|
-
};
|
|
212
|
+
const installedNames = new Set(installedSkills.map((s: any) => s.name));
|
|
162
213
|
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
214
|
+
const filteredMarketplace = useMemo(() => {
|
|
215
|
+
let result = PREBUILT_SKILLS;
|
|
216
|
+
if (categoryFilter !== 'All') result = result.filter(s => s.category === categoryFilter);
|
|
217
|
+
if (searchQuery) {
|
|
218
|
+
const q = searchQuery.toLowerCase();
|
|
219
|
+
result = result.filter(s => s.displayName.toLowerCase().includes(q) || s.description.toLowerCase().includes(q));
|
|
220
|
+
}
|
|
221
|
+
return result;
|
|
222
|
+
}, [searchQuery, categoryFilter]);
|
|
223
|
+
|
|
224
|
+
const filteredInstalled = useMemo(() => {
|
|
225
|
+
let result = installedSkills;
|
|
226
|
+
if (categoryFilter !== 'All') result = result.filter((s: any) => s.category === categoryFilter);
|
|
227
|
+
if (searchQuery) {
|
|
228
|
+
const q = searchQuery.toLowerCase();
|
|
229
|
+
result = result.filter((s: any) => s.displayName.toLowerCase().includes(q) || s.description.toLowerCase().includes(q));
|
|
230
|
+
}
|
|
231
|
+
return result;
|
|
232
|
+
}, [installedSkills, searchQuery, categoryFilter]);
|
|
167
233
|
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
skill.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
234
|
+
const handleInstall = async (skill: typeof PREBUILT_SKILLS[0]) => {
|
|
235
|
+
await installSkill({
|
|
236
|
+
name: skill.name,
|
|
237
|
+
displayName: skill.displayName,
|
|
238
|
+
description: skill.description,
|
|
239
|
+
category: skill.category,
|
|
240
|
+
version: skill.version,
|
|
241
|
+
author: skill.author,
|
|
242
|
+
code: skill.code,
|
|
243
|
+
});
|
|
244
|
+
};
|
|
175
245
|
|
|
176
|
-
const
|
|
246
|
+
const handleUninstall = async (id: any) => {
|
|
247
|
+
if (confirm('Uninstall this skill?')) {
|
|
248
|
+
await removeSkill({ id });
|
|
249
|
+
}
|
|
250
|
+
};
|
|
177
251
|
|
|
178
252
|
return (
|
|
179
253
|
<DashboardLayout>
|
|
180
|
-
<div className="
|
|
181
|
-
<
|
|
254
|
+
<div className="space-y-6">
|
|
255
|
+
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
|
|
182
256
|
<div>
|
|
183
|
-
<h1 className="text-
|
|
184
|
-
<p className="text-muted-foreground">
|
|
257
|
+
<h1 className="text-3xl font-bold">Skills</h1>
|
|
258
|
+
<p className="text-muted-foreground">Install prebuilt skills or create your own to extend agent capabilities.</p>
|
|
185
259
|
</div>
|
|
186
|
-
|
|
187
|
-
|
|
260
|
+
</div>
|
|
261
|
+
|
|
262
|
+
{/* Tabs */}
|
|
263
|
+
<div className="flex gap-1 bg-muted p-1 rounded-lg w-fit">
|
|
264
|
+
<button onClick={() => setTab('marketplace')} className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${tab === 'marketplace' ? 'bg-card shadow-sm' : 'text-muted-foreground hover:text-foreground'}`}>
|
|
265
|
+
<Sparkles className="w-4 h-4 inline mr-2" />Marketplace ({PREBUILT_SKILLS.length})
|
|
266
|
+
</button>
|
|
267
|
+
<button onClick={() => setTab('installed')} className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${tab === 'installed' ? 'bg-card shadow-sm' : 'text-muted-foreground hover:text-foreground'}`}>
|
|
268
|
+
<Check className="w-4 h-4 inline mr-2" />Installed ({installedSkills.length})
|
|
188
269
|
</button>
|
|
189
|
-
</
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
<
|
|
195
|
-
|
|
196
|
-
placeholder="Search skills by name..."
|
|
197
|
-
value={searchQuery}
|
|
198
|
-
onChange={(e) => setSearchQuery(e.target.value)}
|
|
199
|
-
className="w-full bg-card border border-border rounded-md pl-10 pr-4 py-2 focus:outline-none focus:ring-2 focus:ring-primary"
|
|
200
|
-
/>
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
{/* Filters */}
|
|
273
|
+
<div className="flex flex-col sm:flex-row gap-3">
|
|
274
|
+
<div className="relative flex-1 max-w-sm">
|
|
275
|
+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
276
|
+
<input type="text" placeholder="Search skills..." value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} className="pl-9 pr-3 py-2 bg-card border border-border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary w-full" />
|
|
201
277
|
</div>
|
|
202
|
-
<div className="flex
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
{CATEGORIES.map(category => (
|
|
206
|
-
<button
|
|
207
|
-
key={category}
|
|
208
|
-
onClick={() => handleToggleCategory(category)}
|
|
209
|
-
className={`px-3 py-1 text-sm rounded-full border transition-colors ${selectedCategories.includes(category) ? 'bg-primary text-primary-foreground border-primary' : 'bg-card hover:bg-zinc-800 border-border'}`}>
|
|
210
|
-
{category}
|
|
211
|
-
</button>
|
|
278
|
+
<div className="flex gap-2">
|
|
279
|
+
{CATEGORIES.map(cat => (
|
|
280
|
+
<button key={cat} onClick={() => setCategoryFilter(cat)} className={`px-3 py-1.5 rounded-lg text-sm ${categoryFilter === cat ? 'bg-primary text-primary-foreground' : 'bg-card border border-border text-muted-foreground hover:text-foreground'}`}>{cat}</button>
|
|
212
281
|
))}
|
|
213
|
-
{selectedCategories.length > 0 && (
|
|
214
|
-
<button onClick={() => setSelectedCategories([])} className="text-sm text-muted-foreground hover:text-foreground underline">Clear</button>
|
|
215
|
-
)}
|
|
216
282
|
</div>
|
|
217
283
|
</div>
|
|
218
284
|
|
|
219
|
-
{
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
<div className="
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
285
|
+
{/* Grid */}
|
|
286
|
+
{tab === 'marketplace' ? (
|
|
287
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
288
|
+
{filteredMarketplace.map(skill => {
|
|
289
|
+
const isInstalled = installedNames.has(skill.name);
|
|
290
|
+
const Icon = skill.icon;
|
|
291
|
+
return (
|
|
292
|
+
<div key={skill.name} className="bg-card border border-border rounded-lg p-5 shadow-sm hover:shadow-md transition-shadow">
|
|
293
|
+
<div className="flex items-start justify-between mb-3">
|
|
294
|
+
<div className="flex items-center gap-2">
|
|
295
|
+
<div className="w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center"><Icon className="w-4 h-4 text-primary" /></div>
|
|
296
|
+
<div>
|
|
297
|
+
<h3 className="font-semibold text-foreground">{skill.displayName}</h3>
|
|
298
|
+
<p className="text-xs text-muted-foreground">v{skill.version} by {skill.author}</p>
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
<span className="text-xs bg-muted px-2 py-0.5 rounded">{skill.category}</span>
|
|
302
|
+
</div>
|
|
303
|
+
<p className="text-sm text-muted-foreground mb-4 line-clamp-2">{skill.description}</p>
|
|
304
|
+
<div className="flex items-center justify-between pt-3 border-t border-border">
|
|
305
|
+
<button onClick={() => setSelectedSkill(skill)} className="text-xs text-primary hover:underline">View Code</button>
|
|
306
|
+
{isInstalled ? (
|
|
307
|
+
<span className="text-xs text-green-500 flex items-center gap-1"><Check className="w-3.5 h-3.5" /> Installed</span>
|
|
308
|
+
) : (
|
|
309
|
+
<button onClick={() => handleInstall(skill)} className="bg-primary text-primary-foreground px-3 py-1.5 rounded-lg text-xs hover:bg-primary/90 flex items-center gap-1">
|
|
310
|
+
<Download className="w-3.5 h-3.5" /> Install
|
|
311
|
+
</button>
|
|
312
|
+
)}
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
);
|
|
316
|
+
})}
|
|
237
317
|
</div>
|
|
238
318
|
) : (
|
|
239
|
-
|
|
240
|
-
<
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
319
|
+
filteredInstalled.length === 0 ? (
|
|
320
|
+
<div className="text-center py-16 bg-card border border-border rounded-lg">
|
|
321
|
+
<Wrench className="w-16 h-16 text-muted-foreground/30 mx-auto mb-4" />
|
|
322
|
+
<h3 className="text-lg font-semibold mb-2">No skills installed</h3>
|
|
323
|
+
<p className="text-muted-foreground">Browse the marketplace to install skills for your agents.</p>
|
|
324
|
+
</div>
|
|
325
|
+
) : (
|
|
326
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
327
|
+
{filteredInstalled.map((skill: any) => (
|
|
328
|
+
<div key={skill._id} className="bg-card border border-border rounded-lg p-5 shadow-sm">
|
|
329
|
+
<div className="flex items-start justify-between mb-3">
|
|
330
|
+
<h3 className="font-semibold text-foreground">{skill.displayName}</h3>
|
|
331
|
+
<span className={`text-xs px-2 py-0.5 rounded-full ${skill.isEnabled ? 'bg-green-500/10 text-green-500' : 'bg-muted text-muted-foreground'}`}>{skill.isEnabled ? 'Enabled' : 'Disabled'}</span>
|
|
332
|
+
</div>
|
|
333
|
+
<p className="text-sm text-muted-foreground mb-4 line-clamp-2">{skill.description}</p>
|
|
334
|
+
<div className="flex items-center justify-between pt-3 border-t border-border">
|
|
335
|
+
<button onClick={() => toggleSkill({ id: skill._id })} className="text-xs text-muted-foreground hover:text-foreground">{skill.isEnabled ? 'Disable' : 'Enable'}</button>
|
|
336
|
+
<button onClick={() => handleUninstall(skill._id)} className="p-1.5 rounded hover:bg-destructive/10"><Trash2 className="w-4 h-4 text-destructive" /></button>
|
|
337
|
+
</div>
|
|
338
|
+
</div>
|
|
339
|
+
))}
|
|
340
|
+
</div>
|
|
341
|
+
)
|
|
342
|
+
)}
|
|
343
|
+
|
|
344
|
+
{/* Code Preview Modal */}
|
|
345
|
+
{selectedSkill && (
|
|
346
|
+
<div className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4">
|
|
347
|
+
<div className="bg-card border border-border rounded-lg shadow-xl w-full max-w-2xl max-h-[80vh] flex flex-col">
|
|
348
|
+
<div className="flex justify-between items-center p-4 border-b border-border">
|
|
349
|
+
<h2 className="text-lg font-bold">{selectedSkill.displayName} — Source Code</h2>
|
|
350
|
+
<button onClick={() => setSelectedSkill(null)} className="text-muted-foreground hover:text-foreground"><X className="h-5 w-5" /></button>
|
|
351
|
+
</div>
|
|
352
|
+
<pre className="flex-1 overflow-auto p-4 text-xs font-mono bg-background text-foreground">{selectedSkill.code}</pre>
|
|
353
|
+
<div className="p-4 border-t border-border flex justify-end gap-2">
|
|
354
|
+
<button onClick={() => setSelectedSkill(null)} className="px-4 py-2 rounded-lg bg-muted text-muted-foreground text-sm">Close</button>
|
|
355
|
+
{!installedNames.has(selectedSkill.name) && (
|
|
356
|
+
<button onClick={() => { handleInstall(selectedSkill); setSelectedSkill(null); }} className="px-4 py-2 rounded-lg bg-primary text-primary-foreground text-sm hover:bg-primary/90">Install Skill</button>
|
|
357
|
+
)}
|
|
358
|
+
</div>
|
|
359
|
+
</div>
|
|
246
360
|
</div>
|
|
247
361
|
)}
|
|
248
362
|
</div>
|
|
249
|
-
<CreateSkillDialog open={isCreateDialogOpen} onOpenChange={setCreateDialogOpen} onCreateSkill={handleCreateSkill} />
|
|
250
363
|
</DashboardLayout>
|
|
251
364
|
);
|
|
252
365
|
}
|