@agentforge-ai/cli 0.3.2 → 0.4.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.
@@ -0,0 +1,392 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Skill Creator — Built-in AgentForge Skill
5
+ *
6
+ * Provides tools for creating, discovering, and validating skills.
7
+ * This skill is automatically available in every AgentForge project.
8
+ */
9
+
10
+ // ─── Example Skills Registry ───────────────────────────────────────────
11
+ const EXAMPLE_SKILLS = [
12
+ {
13
+ name: 'web-search',
14
+ description: 'Search the web for information using a search API',
15
+ category: 'web',
16
+ complexity: 'medium',
17
+ template: `import { z } from 'zod';
18
+ export const tools = [{
19
+ name: 'web-search',
20
+ description: 'Search the web for information',
21
+ inputSchema: z.object({ query: z.string(), maxResults: z.number().optional().default(5) }),
22
+ outputSchema: z.object({ results: z.array(z.object({ title: z.string(), url: z.string(), snippet: z.string() })) }),
23
+ handler: async (input) => {
24
+ const response = await fetch(\`https://api.search.example/search?q=\${encodeURIComponent(input.query)}&limit=\${input.maxResults}\`);
25
+ return { results: (await response.json()).results };
26
+ },
27
+ }];`,
28
+ },
29
+ {
30
+ name: 'calculator',
31
+ description: 'Evaluate mathematical expressions safely',
32
+ category: 'utility',
33
+ complexity: 'simple',
34
+ template: `import { z } from 'zod';
35
+ export const tools = [{
36
+ name: 'calculate',
37
+ description: 'Evaluate a mathematical expression',
38
+ inputSchema: z.object({ expression: z.string().describe('Math expression, e.g. "2 + 2 * 3"') }),
39
+ outputSchema: z.object({ result: z.number(), expression: z.string() }),
40
+ handler: async (input) => {
41
+ const result = Function('"use strict"; return (' + input.expression + ')')();
42
+ return { result: Number(result), expression: input.expression };
43
+ },
44
+ }];`,
45
+ },
46
+ {
47
+ name: 'file-reader',
48
+ description: 'Read file contents from the filesystem',
49
+ category: 'file',
50
+ complexity: 'simple',
51
+ template: `import { z } from 'zod';
52
+ import { readFile } from 'fs/promises';
53
+ export const tools = [{
54
+ name: 'read-file',
55
+ description: 'Read the contents of a file',
56
+ inputSchema: z.object({ path: z.string(), encoding: z.string().optional().default('utf-8') }),
57
+ outputSchema: z.object({ content: z.string(), size: z.number() }),
58
+ handler: async (input) => {
59
+ const content = await readFile(input.path, input.encoding as BufferEncoding);
60
+ return { content, size: content.length };
61
+ },
62
+ }];`,
63
+ },
64
+ {
65
+ name: 'http-request',
66
+ description: 'Make HTTP requests to any URL',
67
+ category: 'web',
68
+ complexity: 'medium',
69
+ template: `import { z } from 'zod';
70
+ export const tools = [{
71
+ name: 'http-request',
72
+ description: 'Make an HTTP request to any URL',
73
+ inputSchema: z.object({
74
+ url: z.string().url(),
75
+ method: z.enum(['GET', 'POST', 'PUT', 'DELETE']).default('GET'),
76
+ headers: z.record(z.string()).optional(),
77
+ body: z.string().optional(),
78
+ }),
79
+ outputSchema: z.object({ status: z.number(), body: z.string() }),
80
+ handler: async (input) => {
81
+ const res = await fetch(input.url, { method: input.method, headers: input.headers, body: input.body });
82
+ return { status: res.status, body: await res.text() };
83
+ },
84
+ }];`,
85
+ },
86
+ {
87
+ name: 'json-transformer',
88
+ description: 'Transform and extract data from JSON using dot-notation paths',
89
+ category: 'data',
90
+ complexity: 'simple',
91
+ template: `import { z } from 'zod';
92
+ export const tools = [{
93
+ name: 'transform-json',
94
+ description: 'Extract data from JSON using dot-notation path',
95
+ inputSchema: z.object({ data: z.string(), path: z.string() }),
96
+ outputSchema: z.object({ result: z.any() }),
97
+ handler: async (input) => {
98
+ const obj = JSON.parse(input.data);
99
+ let current = obj;
100
+ for (const part of input.path.split('.')) {
101
+ current = current?.[part] ?? current?.[Number(part)];
102
+ }
103
+ return { result: current };
104
+ },
105
+ }];`,
106
+ },
107
+ {
108
+ name: 'text-summarizer',
109
+ description: 'Summarize long text into key points',
110
+ category: 'ai',
111
+ complexity: 'medium',
112
+ template: `import { z } from 'zod';
113
+ export const tools = [{
114
+ name: 'summarize',
115
+ description: 'Summarize text into key bullet points',
116
+ inputSchema: z.object({ text: z.string(), maxPoints: z.number().optional().default(5) }),
117
+ outputSchema: z.object({ summary: z.string(), keyPoints: z.array(z.string()) }),
118
+ handler: async (input) => {
119
+ const sentences = input.text.split(/[.!?]+/).filter(s => s.trim().length > 20);
120
+ const keyPoints = sentences.slice(0, input.maxPoints).map(s => s.trim());
121
+ return { summary: keyPoints.join('. ') + '.', keyPoints };
122
+ },
123
+ }];`,
124
+ },
125
+ {
126
+ name: 'csv-parser',
127
+ description: 'Parse CSV data into structured JSON',
128
+ category: 'data',
129
+ complexity: 'medium',
130
+ template: `import { z } from 'zod';
131
+ export const tools = [{
132
+ name: 'parse-csv',
133
+ description: 'Parse CSV string into array of objects',
134
+ inputSchema: z.object({ csv: z.string(), delimiter: z.string().optional().default(',') }),
135
+ outputSchema: z.object({ rows: z.array(z.record(z.string())), count: z.number() }),
136
+ handler: async (input) => {
137
+ const lines = input.csv.trim().split('\\n');
138
+ const headers = lines[0].split(input.delimiter).map(h => h.trim());
139
+ const rows = lines.slice(1).map(line => {
140
+ const values = line.split(input.delimiter);
141
+ return Object.fromEntries(headers.map((h, i) => [h, values[i]?.trim() ?? '']));
142
+ });
143
+ return { rows, count: rows.length };
144
+ },
145
+ }];`,
146
+ },
147
+ ];
148
+
149
+ // ─── Skill Creator Tools ───────────────────────────────────────────────
150
+ export const tools = [
151
+ {
152
+ name: 'create-skill',
153
+ description:
154
+ 'Generate a new AgentForge skill from a natural language description. ' +
155
+ 'Returns the complete skill code (index.ts), config (config.json), and documentation (SKILL.md). ' +
156
+ 'The user can then review and install the skill.',
157
+ inputSchema: z.object({
158
+ name: z.string().describe('Skill name in kebab-case (e.g., "web-search")'),
159
+ description: z.string().describe('What the skill should do'),
160
+ category: z
161
+ .enum(['utility', 'web', 'file', 'data', 'integration', 'ai', 'custom'])
162
+ .default('custom')
163
+ .describe('Skill category'),
164
+ toolNames: z
165
+ .array(z.string())
166
+ .optional()
167
+ .describe('Names of tools to include (optional, will be auto-generated)'),
168
+ }),
169
+ outputSchema: z.object({
170
+ name: z.string(),
171
+ indexTs: z.string(),
172
+ configJson: z.string(),
173
+ skillMd: z.string(),
174
+ installCommand: z.string(),
175
+ }),
176
+ handler: async (input: {
177
+ name: string;
178
+ description: string;
179
+ category: string;
180
+ toolNames?: string[];
181
+ }) => {
182
+ const toolName = input.toolNames?.[0] ?? input.name;
183
+
184
+ // Generate index.ts
185
+ const indexTs = `import { z } from 'zod';
186
+
187
+ /**
188
+ * ${input.name} — AgentForge Skill
189
+ * ${input.description}
190
+ */
191
+ export const tools = [
192
+ {
193
+ name: '${toolName}',
194
+ description: '${input.description}',
195
+ inputSchema: z.object({
196
+ input: z.string().describe('Input for ${toolName}'),
197
+ }),
198
+ outputSchema: z.object({
199
+ result: z.string(),
200
+ success: z.boolean(),
201
+ }),
202
+ handler: async (params: { input: string }) => {
203
+ // TODO: Implement your skill logic here
204
+ // This is a scaffold — replace with your actual implementation
205
+ return {
206
+ result: \`Processed: \${params.input}\`,
207
+ success: true,
208
+ };
209
+ },
210
+ },
211
+ ];
212
+
213
+ export default { tools };
214
+ `;
215
+
216
+ // Generate config.json
217
+ const configJson = JSON.stringify(
218
+ {
219
+ name: input.name,
220
+ version: '1.0.0',
221
+ description: input.description,
222
+ category: input.category,
223
+ author: 'User',
224
+ tools: [toolName],
225
+ dependencies: [],
226
+ agentInstructions: `You have access to the ${input.name} skill. ${input.description}`,
227
+ },
228
+ null,
229
+ 2
230
+ );
231
+
232
+ // Generate SKILL.md
233
+ const skillMd = `# ${input.name}
234
+
235
+ ${input.description}
236
+
237
+ ## Usage
238
+
239
+ ### Via CLI
240
+ \`\`\`bash
241
+ agentforge skills install ${input.name}
242
+ \`\`\`
243
+
244
+ ### Via Agent Chat
245
+ Ask your agent: "Use the ${toolName} tool to [your request]"
246
+
247
+ ## Tools
248
+
249
+ ### ${toolName}
250
+ ${input.description}
251
+
252
+ **Input:**
253
+ - \`input\` (string) — Input for ${toolName}
254
+
255
+ **Output:**
256
+ - \`result\` (string) — The processed result
257
+ - \`success\` (boolean) — Whether the operation succeeded
258
+
259
+ ## Configuration
260
+
261
+ Edit \`skills/${input.name}/config.json\` to customize behavior.
262
+ `;
263
+
264
+ return {
265
+ name: input.name,
266
+ indexTs,
267
+ configJson,
268
+ skillMd,
269
+ installCommand: `agentforge skills install ${input.name}`,
270
+ };
271
+ },
272
+ },
273
+
274
+ {
275
+ name: 'list-examples',
276
+ description:
277
+ 'List all available example skills with their descriptions, categories, and code templates. ' +
278
+ 'Use this to show users what kinds of skills they can create.',
279
+ inputSchema: z.object({
280
+ category: z
281
+ .string()
282
+ .optional()
283
+ .describe('Filter by category (utility, web, file, data, ai)'),
284
+ showCode: z
285
+ .boolean()
286
+ .optional()
287
+ .default(false)
288
+ .describe('Include the full code template in the response'),
289
+ }),
290
+ outputSchema: z.object({
291
+ examples: z.array(
292
+ z.object({
293
+ name: z.string(),
294
+ description: z.string(),
295
+ category: z.string(),
296
+ complexity: z.string(),
297
+ template: z.string().optional(),
298
+ })
299
+ ),
300
+ count: z.number(),
301
+ }),
302
+ handler: async (input: { category?: string; showCode?: boolean }) => {
303
+ let filtered = EXAMPLE_SKILLS;
304
+ if (input.category) {
305
+ filtered = filtered.filter((s) => s.category === input.category);
306
+ }
307
+ const examples = filtered.map((s) => ({
308
+ name: s.name,
309
+ description: s.description,
310
+ category: s.category,
311
+ complexity: s.complexity,
312
+ ...(input.showCode ? { template: s.template } : {}),
313
+ }));
314
+ return { examples, count: examples.length };
315
+ },
316
+ },
317
+
318
+ {
319
+ name: 'validate-skill',
320
+ description:
321
+ 'Validate a skill definition to ensure it has the correct structure, ' +
322
+ 'required fields, and valid Zod schemas before installation.',
323
+ inputSchema: z.object({
324
+ name: z.string().describe('Skill name to validate'),
325
+ indexTs: z.string().describe('The index.ts file content'),
326
+ configJson: z.string().describe('The config.json file content'),
327
+ }),
328
+ outputSchema: z.object({
329
+ isValid: z.boolean(),
330
+ errors: z.array(z.string()),
331
+ warnings: z.array(z.string()),
332
+ }),
333
+ handler: async (input: {
334
+ name: string;
335
+ indexTs: string;
336
+ configJson: string;
337
+ }) => {
338
+ const errors: string[] = [];
339
+ const warnings: string[] = [];
340
+
341
+ // Validate name
342
+ if (!/^[a-z][a-z0-9-]*$/.test(input.name)) {
343
+ errors.push(
344
+ 'Skill name must be kebab-case (lowercase letters, numbers, hyphens)'
345
+ );
346
+ }
347
+
348
+ // Validate index.ts
349
+ if (!input.indexTs.includes('export')) {
350
+ errors.push('index.ts must have at least one export');
351
+ }
352
+ if (!input.indexTs.includes('tools')) {
353
+ errors.push('index.ts must export a tools array');
354
+ }
355
+ if (!input.indexTs.includes('handler')) {
356
+ errors.push('Each tool must have a handler function');
357
+ }
358
+ if (!input.indexTs.includes('inputSchema')) {
359
+ warnings.push(
360
+ 'Tools should define inputSchema for type safety'
361
+ );
362
+ }
363
+ if (!input.indexTs.includes('outputSchema')) {
364
+ warnings.push(
365
+ 'Tools should define outputSchema for type safety'
366
+ );
367
+ }
368
+
369
+ // Validate config.json
370
+ try {
371
+ const config = JSON.parse(input.configJson);
372
+ if (!config.name) errors.push('config.json must have a "name" field');
373
+ if (!config.version)
374
+ errors.push('config.json must have a "version" field');
375
+ if (!config.description)
376
+ warnings.push('config.json should have a "description" field');
377
+ if (!config.category)
378
+ warnings.push('config.json should have a "category" field');
379
+ } catch {
380
+ errors.push('config.json is not valid JSON');
381
+ }
382
+
383
+ return {
384
+ isValid: errors.length === 0,
385
+ errors,
386
+ warnings,
387
+ };
388
+ },
389
+ },
390
+ ];
391
+
392
+ export default { tools };
@@ -1,22 +1,62 @@
1
1
  import { Agent } from '@agentforge-ai/core';
2
2
 
3
3
  /**
4
- * A sample AgentForge agent.
4
+ * AgentForge Your First Agent
5
5
  *
6
- * This is a starting point for your own agent. Modify the instructions,
7
- * model, and tools to suit your needs.
6
+ * This starter agent demonstrates how to configure an AI agent with
7
+ * AgentForge's multi-provider support. You can use OpenAI, OpenRouter,
8
+ * Anthropic, Google, or xAI as your LLM provider.
9
+ *
10
+ * Set your provider and API key in the .env file:
11
+ * OPENAI_API_KEY=sk-...
12
+ * OPENROUTER_API_KEY=sk-or-...
13
+ * ANTHROPIC_API_KEY=sk-ant-...
8
14
  */
15
+
16
+ // ─── Main Agent ────────────────────────────────────────────────────────
9
17
  const myAgent = new Agent({
10
18
  id: 'my-first-agent',
11
19
  name: 'My First Agent',
12
20
  instructions: `You are a helpful AI assistant built with AgentForge.
13
21
  You can help users with a variety of tasks.
14
- Be concise, accurate, and friendly.`,
22
+ Be concise, accurate, and friendly.
23
+
24
+ When you don't know something, say so honestly.
25
+ When asked about your capabilities, mention that you're powered by AgentForge.`,
26
+
27
+ // Choose your model — supports multiple providers:
28
+ // OpenAI: "openai:gpt-4o-mini", "openai:gpt-4o"
29
+ // OpenRouter: "openrouter:anthropic/claude-3.5-sonnet", "openrouter:google/gemini-pro"
30
+ // Anthropic: "anthropic:claude-3-5-sonnet-20241022"
31
+ // Google: "google:gemini-2.0-flash"
32
+ // xAI: "xai:grok-2"
15
33
  model: 'openai:gpt-4o-mini',
16
34
  });
17
35
 
18
36
  export default myAgent;
19
37
 
20
- // Example usage:
38
+ // ─── Example: Agent with Custom Tools ──────────────────────────────────
39
+ // import { z } from 'zod';
40
+ //
41
+ // const researchAgent = new Agent({
42
+ // id: 'research-agent',
43
+ // name: 'Research Agent',
44
+ // instructions: 'You are a research assistant that helps find and summarize information.',
45
+ // model: 'openrouter:anthropic/claude-3.5-sonnet',
46
+ // tools: [
47
+ // {
48
+ // name: 'calculator',
49
+ // description: 'Perform mathematical calculations',
50
+ // inputSchema: z.object({ expression: z.string() }),
51
+ // outputSchema: z.object({ result: z.number() }),
52
+ // handler: async (input) => {
53
+ // const result = Function('"use strict"; return (' + input.expression + ')')();
54
+ // return { result };
55
+ // },
56
+ // },
57
+ // ],
58
+ // });
59
+
60
+ // ─── Example: Using the Agent ──────────────────────────────────────────
21
61
  // const response = await myAgent.generate('Hello, what can you do?');
22
62
  // console.log(response.text);