diff --git a/apps/cli/package.json b/apps/cli/package.json index 77e2ed3a..94adbfcf 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -10,8 +10,7 @@ }, "files": [ "dist", - "bin", - "examples" + "bin" ], "bin": { "rowboatx": "bin/app.js" diff --git a/apps/cli/src/app.ts b/apps/cli/src/app.ts index 634923bf..6740321c 100644 --- a/apps/cli/src/app.ts +++ b/apps/cli/src/app.ts @@ -4,7 +4,6 @@ import { stdin as input, stdout as output } from "node:process"; import fs from "fs"; import { promises as fsp } from "fs"; import path from "path"; -import { fileURLToPath } from "url"; import { WorkDir, getModelConfig, updateModelConfig } from "./application/config/config.js"; import { RunEvent } from "./application/entities/run-events.js"; import { createInterface, Interface } from "node:readline/promises"; @@ -12,12 +11,8 @@ import { ToolCallPart } from "./application/entities/message.js"; import { Agent } from "./application/entities/agent.js"; import { McpServerConfig, McpServerDefinition } from "./application/entities/mcp.js"; import { z } from "zod"; -import { Flavor, ModelConfig } from "./application/entities/models.js"; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const PackageRoot = path.resolve(__dirname, ".."); -const ExamplesDir = path.join(PackageRoot, "examples"); +import { Flavor } from "./application/entities/models.js"; +import { examples } from "./examples/index.js"; export async function updateState(agent: string, runId: string) { const state = new AgentState(agent, runId); @@ -413,51 +408,14 @@ function renderCurrentModel(provider: string, flavor: string, model: string) { console.log(""); } -const ExampleSchema = z.object({ - id: z.string().min(1), - "post-install-instructions": z.string().optional(), - description: z.string().optional(), - entryAgent: z.string().optional(), - agents: z.array(Agent).min(1), - mcpServers: z.record(z.string(), McpServerDefinition).optional(), -}).refine( - (data) => !data.entryAgent || data.agents.some((agent) => agent.name === data.entryAgent), - { - message: "entryAgent must reference one of the defined agents", - path: ["entryAgent"], - }, -); - -async function readExampleFile(exampleName: string): Promise { - const examplePath = path.join(ExamplesDir, `${exampleName}.json`); - try { - return await fsp.readFile(examplePath, "utf8"); - } catch (error: any) { - if (error?.code === "ENOENT") { - const availableExamples = await listAvailableExamples(); - const listMessage = availableExamples.length - ? `Available examples: ${availableExamples.join(", ")}` - : "No packaged examples were found."; - throw new Error(`Unknown example '${exampleName}'. ${listMessage}`); - } - // Re-throw other errors (permission issues, etc.) - throw error; - } -} - async function listAvailableExamples(): Promise { - try { - const entries = await fsp.readdir(ExamplesDir); - return entries - .filter((entry) => entry.endsWith(".json")) - .map((entry) => entry.replace(/\.json$/, "")) - .sort(); - } catch { - return []; - } + return Object.keys(examples); } -async function writeAgents(agents: z.infer[]) { +async function writeAgents(agents: z.infer[] | undefined) { + if (!agents) { + return; + } await fsp.mkdir(path.join(WorkDir, "agents"), { recursive: true }); await Promise.all( agents.map(async (agent) => { @@ -509,22 +467,17 @@ async function mergeMcpServers(servers: Record agent.name), + id: example.id, + entryAgent: example.entryAgent, + importedAgents: example.agents?.map((agent) => agent.name) ?? [], addedServers: serverMerge.added, skippedServers: serverMerge.skipped, postInstallInstructions, diff --git a/apps/cli/src/application/config/config.ts b/apps/cli/src/application/config/config.ts index e0d6b27c..4702a765 100644 --- a/apps/cli/src/application/config/config.ts +++ b/apps/cli/src/application/config/config.ts @@ -12,19 +12,6 @@ let modelConfig: z.infer | null = null; const baseMcpConfig: z.infer = { mcpServers: { - firecrawl: { - command: "npx", - args: ["-y", "supergateway", "--stdio", "npx -y firecrawl-mcp"], - env: { - FIRECRAWL_API_KEY: "fc-aaacee4bdd164100a4d83af85bef6fdc", - }, - }, - test: { - url: "http://localhost:3000", - headers: { - "Authorization": "Bearer test", - }, - }, } }; diff --git a/apps/cli/src/application/entities/example.ts b/apps/cli/src/application/entities/example.ts new file mode 100644 index 00000000..8857a55c --- /dev/null +++ b/apps/cli/src/application/entities/example.ts @@ -0,0 +1,12 @@ +import z from "zod" +import { Agent } from "./agent.js" +import { McpServerDefinition } from "./mcp.js" + +export const Example = z.object({ + id: z.string(), + instructions: z.string().optional(), + description: z.string().optional(), + entryAgent: z.string().optional(), + agents: z.array(Agent).optional(), + mcpServers: z.record(z.string(), McpServerDefinition).optional(), +}); \ No newline at end of file diff --git a/apps/cli/src/examples/gemini3-test.json b/apps/cli/src/examples/gemini3-test.json new file mode 100644 index 00000000..54f63c38 --- /dev/null +++ b/apps/cli/src/examples/gemini3-test.json @@ -0,0 +1,8 @@ +{ + "id": "gemini3_svg_pelican", + "provider": "google", + "model": "gemini-3.0-pro", + "description": "Outputs a single valid SVG depicting a pelican riding a bicycle.", + "instructions": "You must output only a single, valid, self-contained SVG XML depicting a pelican riding a bicycle. Requirements: 1) Output must be ONLY raw SVG XML (no markdown fences, no explanations). 2) Use viewBox=\"0 0 512 512\" and set width/height to 512. 3) Include clear, recognizable pelican and bicycle using basic shapes/paths. 4) No external refs, images, scripts, or styles; use inline attributes only. 5) Keep IDs minimal; keep total file size reasonable.", + "tools": {} +} \ No newline at end of file diff --git a/apps/cli/src/examples/index.ts b/apps/cli/src/examples/index.ts new file mode 100644 index 00000000..83abf58e --- /dev/null +++ b/apps/cli/src/examples/index.ts @@ -0,0 +1,9 @@ +import twitterPodcast from './twitter-podcast.json' with { type: 'json' }; +import gemini3Test from './gemini3-test.json' with { type: 'json' }; +import { Example } from '../application/entities/example.js'; +import z from 'zod'; + +export const examples: Record> = { + "twitter-podcast": Example.parse(twitterPodcast), + "gemini3-test": Example.parse(gemini3Test), +}; \ No newline at end of file diff --git a/apps/cli/examples/twitter-podcast.json b/apps/cli/src/examples/twitter-podcast.json similarity index 99% rename from apps/cli/examples/twitter-podcast.json rename to apps/cli/src/examples/twitter-podcast.json index 28420c98..05f2c136 100644 --- a/apps/cli/examples/twitter-podcast.json +++ b/apps/cli/src/examples/twitter-podcast.json @@ -1,6 +1,6 @@ { "id": "twitter-podcast", - "post-import-instructions": "This example workflow generates a narrated podcast episode from recent AI-related tweets using multiple agents.", + "instructions": "This example workflow generates a narrated podcast episode from recent AI-related tweets using multiple agents.", "description": "Generates a narrated podcast episode from recent AI-related tweets using multiple agents.", "entryAgent": "tweet-podcast", "agents": [